Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-joyride

Package Overview
Dependencies
Maintainers
1
Versions
137
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-joyride - npm Package Compare versions

Comparing version 2.0.0 to 2.0.1

types/index.d.ts

135

package.json
{
"name": "react-joyride",
"version": "2.0.0",
"description": "Create walkthroughs and guided tours for your apps",
"version": "2.0.1",
"description": "Create guided tours for your apps",
"author": "Gil Barbara <gilbarbara@gmail.com>",

@@ -28,4 +28,6 @@ "repository": {

"lib",
"src"
"src",
"types/*.ts"
],
"types": "./types/index.d.ts",
"peerDependencies": {

@@ -37,8 +39,8 @@ "react": "^0.14.0 || ^15.0.0 || ^16.0.0",

"dependencies": {
"deep-diff": "^1.0.1",
"deepmerge": "^2.1.1",
"deep-diff": "^1.0.2",
"deepmerge": "^3.0.0",
"exenv": "^1.2.2",
"is-lite": "^0.2.0",
"is-lite": "^0.2.2",
"nested-property": "^0.0.7",
"react-floater": "^0.5.5",
"react-floater": "^0.6.2",
"react-proptype-conditional-require": "^1.0.4",

@@ -48,54 +50,69 @@ "scroll": "^2.0.3",

"scrollparent": "^2.0.1",
"tree-changes": "^0.3.2"
"tree-changes": "^0.4.0"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-jest": "^23.4.2",
"@babel/core": "^7.2.2",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-proposal-decorators": "^7.2.3",
"@babel/plugin-proposal-do-expressions": "^7.2.0",
"@babel/plugin-proposal-export-default-from": "^7.2.0",
"@babel/plugin-proposal-export-namespace-from": "^7.2.0",
"@babel/plugin-proposal-function-sent": "^7.2.0",
"@babel/plugin-proposal-json-strings": "^7.2.0",
"@babel/plugin-proposal-logical-assignment-operators": "^7.2.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.2.0",
"@babel/plugin-proposal-numeric-separator": "^7.2.0",
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/plugin-proposal-pipeline-operator": "^7.2.0",
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/plugin-transform-flow-strip-types": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@types/react": "^16.7.18",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"babel-plugin-array-includes": "^2.0.3",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-plugin-jsx-remove-data-test-id": "^1.2.1",
"babel-plugin-transform-node-env-inline": "^0.4.3",
"babel-plugin-transform-react-remove-prop-types": "^0.4.14",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.21",
"bundlesize": "^0.17.0",
"chalk": "^2.4.1",
"cross-env": "^5.2.0",
"date-fns": "^1.29.0",
"enzyme": "^3.4.0",
"enzyme-adapter-react-16": "^1.2.0",
"eslint": "^5.3.0",
"eslint-config-airbnb": "^17.0.0",
"eslint-plugin-babel": "^5.1.0",
"eslint-plugin-flowtype": "^2.50.0",
"eslint-plugin-import": "^2.13.0",
"eslint-plugin-jsx-a11y": "^6.1.1",
"eslint-plugin-react": "^7.10.0",
"flow-bin": "^0.76.0",
"husky": "^0.14.3",
"jest": "^23.4.2",
"jest-chain": "^1.0.3",
"date-fns": "^1.30.1",
"dtslint": "^0.4.2",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.7.1",
"eslint": "^5.11.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-flowtype": "^3.2.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.2",
"eslint-plugin-react": "^7.11.1",
"flow-bin": "^0.89.0",
"husky": "^1.2.1",
"jest": "^23.6.0",
"jest-chain": "^1.0.5",
"jest-environment-jsdom-global": "^1.1.0",
"jest-enzyme": "^6.0.3",
"jest-extended": "^0.7.2",
"jest-enzyme": "^7.0.1",
"jest-extended": "^0.11.0",
"jest-watch-typeahead": "^0.2.0",
"prop-types": "^15.6.2",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"rimraf": "^2.6.2",
"rollup": "^0.62.0",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.1.4",
"rollup-plugin-node-resolve": "^3.3.0"
"rollup": "^0.68.2",
"rollup-plugin-babel": "^4.1.0",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-filesize": "^5.0.1",
"rollup-plugin-node-resolve": "^4.0.0",
"yargs": "^12.0.5"
},
"scripts": {
"build": "npm run clean && npm run build:cjs && npm run build:es",
"build:cjs": "cross-env NODE_ENV=production npm run build:cjs:src && npm run build:cjs:constants",
"build:cjs:src": "rollup -c -f cjs -o lib/index.js",
"build:cjs:constants": "rollup -i src/constants/index.js -c -f cjs -o lib/constants.js",
"build:es": "cross-env NODE_ENV=production npm run build:es:src && npm run build:es:constants",
"build:es:src": "cross-env NODE_ENV=production rollup -c",
"build:es:constants": "cross-env NODE_ENV=production rollup -i src/constants/index.js -c -o es/constants.js",
"build": "cross-env NODE_ENV=production npm run clean && rollup -c",
"watch": "cross-env NODE_ENV=production rollup -cw",

@@ -105,7 +122,23 @@ "clean": "rimraf es && rimraf lib",

"test": "jest --coverage",
"test:watch": "jest --watch",
"precommit": "node tools commits && npm run lint && npm test",
"postmerge": "node tools update && npm update",
"prepublishOnly": "npm run build"
"test:watch": "jest --watch --verbose",
"bundlesize": "bundlesize",
"validate": "npm run lint && npm test && flow && npm run build && npm run bundlesize",
"prepublishOnly": "npm run validate"
},
"bundlesize": [
{
"path": "./es/index.js",
"maxSize": "20 kB"
},
{
"path": "./lib/index.js",
"maxSize": "20 kB"
}
],
"husky": {
"hooks": {
"post-merge": "node tools update",
"pre-commit": "node tools upstream && npm run validate"
}
}
}

@@ -7,15 +7,17 @@ # React Joyride

Create a tour for your app!
Use it to showcase your app for new users! Or explain functionality of complex features!
#### Create awesome tours for your app!
#### View the demo [here](https://2zpjporp4p.codesandbox.io/)
Showcase your app to new users or explain functionality of new features.
You can edit the demo [here](https://codesandbox.io/s/2zpjporp4p)
It uses [react-floater](https://github.com/gilbarbara/react-floater) for positioning and styling.
And you can use your own components too!
> If you are looking for the documentation for the old 1.x version, go [here](https://github.com/gilbarbara/react-joyride/tree/v1.11.4)
**View the demo [here](https://2zpjporp4p.codesandbox.io/)**
**Read the [docs](https://gilbarbara.gitbook.io/react-joyride/)**
## Setup
```bash
npm i react-joyride@next
npm i react-joyride
```

@@ -25,7 +27,3 @@

Just set a `steps` array to the Joyride component and you're good to go!
You can use your own component for the tooltip body or beacon, if you want.
```js
```jsx
import Joyride from 'react-joyride';

@@ -35,13 +33,10 @@

state = {
run: false,
steps: [
{
target: '.my-first-step',
content: 'This if my awesome feature!',
placement: 'bottom',
content: 'This is my awesome feature!',
},
{
target: '.my-other-step',
content: 'This if my awesome feature!',
placement: 'bottom',
content: 'This another awesome feature!',
},

@@ -52,12 +47,4 @@ ...

componentDidMount() {
this.setState({ run: true });
}
callback = (data) => {
const { action, index, type } = data;
};
render () {
const { steps, run } = this.state;
const { steps } = this.state;

@@ -68,4 +55,2 @@ return (

steps={steps}
run={run}
callback={this.callback}
...

@@ -79,18 +64,1 @@ />

```
## Documentation
[Props](docs/props.md)
[Step](docs/step.md)
[Styling](docs/styling.md)
[Callback](docs/callback.md)
[Constants](docs/constants.md)
[Migration from 1.x](docs/migration.md)
This library uses [react-floater](https://github.com/gilbarbara/react-floater) and [popper.js](https://github.com/FezVrasta/popper.js) for positioning and styling.

@@ -12,27 +12,27 @@ import React from 'react';

const css = `
@keyframes joyride-beacon-inner {
20% {
opacity: 0.9;
}
90% {
opacity: 0.7;
}
}
@keyframes joyride-beacon-outer {
0% {
transform: scale(1);
}
45% {
opacity: 0.7;
transform: scale(0.75);
}
100% {
opacity: 0.9;
transform: scale(1);
}
}
@keyframes joyride-beacon-inner {
20% {
opacity: 0.9;
}
90% {
opacity: 0.7;
}
}
@keyframes joyride-beacon-outer {
0% {
transform: scale(1);
}
45% {
opacity: 0.7;
transform: scale(0.75);
}
100% {
opacity: 0.9;
transform: scale(1);
}
}
`;

@@ -77,8 +77,4 @@

if (beaconComponent) {
if (React.isValidElement(beaconComponent)) {
component = React.cloneElement(beaconComponent, props);
}
else {
component = beaconComponent(props);
}
const BeaconComponent = beaconComponent;
component = <BeaconComponent {...props} />;
}

@@ -89,5 +85,6 @@ else {

key="JoyrideBeacon"
className="joyride-beacon"
className="react-joyride__beacon"
style={styles.beacon}
type="button"
data-test-id="button-beacon"
{...props}

@@ -94,0 +91,0 @@ >

@@ -9,4 +9,4 @@ import React from 'react';

getElement,
getScrollParent,
getScrollTo,
getScrollParent,
hasCustomScrollParent,

@@ -19,6 +19,3 @@ isFixed,

import ACTIONS from '../constants/actions';
import EVENTS from '../constants/events';
import LIFECYCLE from '../constants/lifecycle';
import STATUS from '../constants/status';
import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '../constants';

@@ -31,8 +28,3 @@ import Step from './Step';

this.store = new Store({
...props,
controlled: props.run && is.number(props.stepIndex),
});
this.state = this.store.getState();
this.helpers = this.store.getHelpers();
this.state = this.initStore();
}

@@ -52,5 +44,7 @@

disableScrolling: PropTypes.bool,
disableScrollParentFix: PropTypes.bool,
floaterProps: PropTypes.shape({
offset: PropTypes.number,
}),
getHelpers: PropTypes.func,
hideBackButton: PropTypes.bool,

@@ -81,2 +75,4 @@ locale: PropTypes.object,

disableScrolling: false,
disableScrollParentFix: false,
getHelpers: () => {},
hideBackButton: false,

@@ -96,22 +92,5 @@ run: true,

const {
debug,
disableCloseOnEsc,
run,
steps,
} = this.props;
const { disableCloseOnEsc, debug, run, steps } = this.props;
const { start } = this.store;
log({
title: 'init',
data: [
{ key: 'props', value: this.props },
{ key: 'state', value: this.state },
],
debug,
});
// Sync the store to this component state.
this.store.addListener(this.syncState);
if (validateSteps(steps, debug) && run) {

@@ -127,11 +106,15 @@ start();

componentWillReceiveProps(nextProps) {
componentDidUpdate(prevProps, prevState) {
if (!canUseDOM) return;
const { action, status } = this.state;
const { steps, stepIndex } = this.props;
const { debug, run, steps: nextSteps, stepIndex: nextStepIndex } = nextProps;
const { setSteps, start, stop, update } = this.store;
const diffProps = !isEqual(this.props, nextProps);
const { changed } = treeChanges(this.props, nextProps);
const { action, controlled, index, lifecycle, status } = this.state;
const { debug, run, stepIndex, steps } = this.props;
const { steps: prevSteps, stepIndex: prevStepIndex } = prevProps;
const { setSteps, reset, start, stop, update } = this.store;
const { changed: changedProps } = treeChanges(prevProps, this.props);
const { changed, changedFrom, changedTo } = treeChanges(prevState, this.state);
const diffProps = !isEqual(prevProps, this.props);
const diffState = !isEqual(prevState, this.state);
const step = getMergedStep(steps[index], this.props);
if (diffProps) {

@@ -141,4 +124,4 @@ log({

data: [
{ key: 'nextProps', value: nextProps },
{ key: 'props', value: this.props },
{ key: 'nextProps', value: this.props },
{ key: 'props', value: prevProps },
],

@@ -148,21 +131,21 @@ debug,

const stepsChanged = !isEqual(nextSteps, steps);
const stepIndexChanged = is.number(nextStepIndex) && changed('stepIndex');
const stepsChanged = !isEqual(prevSteps, steps);
const stepIndexChanged = is.number(stepIndex) && changedProps('stepIndex');
/* istanbul ignore else */
if (changed('run')) {
if (run) {
start(nextStepIndex);
if (stepsChanged) {
if (validateSteps(steps, debug)) {
setSteps(steps);
}
else {
stop();
console.warn('Steps are not valid', steps); //eslint-disable-line no-console
}
}
if (stepsChanged) {
if (validateSteps(nextSteps, debug)) {
setSteps(nextSteps);
/* istanbul ignore else */
if (changedProps('run')) {
if (run) {
start(stepIndex);
}
else {
console.warn('Steps are not valid', nextSteps); //eslint-disable-line no-console
stop();
}

@@ -173,3 +156,3 @@ }

if (stepIndexChanged) {
let nextAction = stepIndex < nextStepIndex ? ACTIONS.NEXT : ACTIONS.PREV;
let nextAction = prevStepIndex < stepIndex ? ACTIONS.NEXT : ACTIONS.PREV;

@@ -183,3 +166,3 @@ if (action === ACTIONS.STOP) {

action: action === ACTIONS.CLOSE ? ACTIONS.CLOSE : nextAction,
index: nextStepIndex,
index: stepIndex,
lifecycle: LIFECYCLE.INIT,

@@ -190,13 +173,3 @@ });

}
}
componentDidUpdate(prevProps, prevState) {
if (!canUseDOM) return;
const { index, lifecycle, status } = this.state;
const { debug, steps } = this.props;
const { changed, changedFrom, changedTo } = treeChanges(prevState, this.state);
const diffState = !isEqual(prevState, this.state);
let step = getMergedStep(steps[index], this.props);
if (diffState) {

@@ -213,24 +186,65 @@ log({

let currentIndex = index;
const callbackData = {
...this.state,
index,
step,
};
const isAfterAction = changedTo('action', [
ACTIONS.NEXT,
ACTIONS.PREV,
ACTIONS.SKIP,
ACTIONS.CLOSE,
]);
if (changed('status')) {
let type = EVENTS.TOUR_STATUS;
if (isAfterAction && changedTo('status', STATUS.PAUSED)) {
const prevStep = getMergedStep(steps[prevState.index], this.props);
if (changedTo('status', STATUS.FINISHED) || changedTo('status', STATUS.SKIPPED)) {
type = EVENTS.TOUR_END;
// Return the last step when the tour is finished
step = getMergedStep(steps[prevState.index], this.props);
currentIndex = prevState.index;
this.callback({
...callbackData,
index: prevState.index,
lifecycle: LIFECYCLE.COMPLETE,
step: prevStep,
type: EVENTS.STEP_AFTER,
});
}
if (changedTo('status', [STATUS.FINISHED, STATUS.SKIPPED])) {
const prevStep = getMergedStep(steps[prevState.index], this.props);
if (!controlled) {
this.callback({
...callbackData,
index: prevState.index,
lifecycle: LIFECYCLE.COMPLETE,
step: prevStep,
type: EVENTS.STEP_AFTER,
});
}
else if (changedFrom('status', STATUS.READY, STATUS.RUNNING)) {
type = EVENTS.TOUR_START;
}
this.callback({
...this.state,
index: currentIndex,
step,
type,
...callbackData,
type: EVENTS.TOUR_END,
// Return the last step when the tour is finished
step: prevStep,
index: prevState.index,
});
reset();
}
else if (changedFrom('status', [STATUS.IDLE, STATUS.READY], STATUS.RUNNING)) {
this.callback({
...callbackData,
type: EVENTS.TOUR_START,
});
}
else if (changed('status')) {
this.callback({
...callbackData,
type: EVENTS.TOUR_STATUS,
});
}
else if (changedTo('action', ACTIONS.RESET)) {
this.callback({
...callbackData,
type: EVENTS.TOUR_STATUS,
});
}

@@ -244,7 +258,2 @@ if (step) {

}
if (changedTo('lifecycle', LIFECYCLE.INIT)) {
delete this.beaconPopper;
delete this.tooltipPopper;
}
}

@@ -262,12 +271,41 @@ }

initStore = () => {
const { debug, getHelpers, run, stepIndex } = this.props;
this.store = new Store({
...this.props,
controlled: run && is.number(stepIndex),
});
this.helpers = this.store.getHelpers();
const { addListener } = this.store;
log({
title: 'init',
data: [
{ key: 'props', value: this.props },
{ key: 'state', value: this.state },
],
debug,
});
// Sync the store to this component's state.
addListener(this.syncState);
getHelpers(this.helpers);
return this.store.getState();
};
scrollToStep(prevState) {
const { index, lifecycle, status } = this.state;
const { debug, disableScrolling, scrollToFirstStep, scrollOffset, steps } = this.props;
const { debug, disableScrolling, disableScrollParentFix, scrollToFirstStep, scrollOffset, steps } = this.props;
const step = getMergedStep(steps[index], this.props);
/* istanbul ignore else */
if (step) {
const target = getElement(step.target);
const shouldScroll = step
&& !disableScrolling
&& step.placement !== 'center'
&& (!step.isFixed || !isFixed(target)) // fixed steps don't need to scroll

@@ -278,5 +316,5 @@ && (prevState.lifecycle !== lifecycle && [LIFECYCLE.BEACON, LIFECYCLE.TOOLTIP].includes(lifecycle))

if (status === STATUS.RUNNING && shouldScroll) {
const hasCustomScroll = hasCustomScrollParent(target);
const scrollParent = getScrollParent(target);
let scrollY = Math.floor(getScrollTo(target, scrollOffset));
const hasCustomScroll = hasCustomScrollParent(target, disableScrollParentFix);
const scrollParent = getScrollParent(target, disableScrollParentFix);
let scrollY = Math.floor(getScrollTo(target, scrollOffset, disableScrollParentFix)) || 0;

@@ -293,5 +331,7 @@ log({

/* istanbul ignore else */
if (lifecycle === LIFECYCLE.BEACON && this.beaconPopper) {
const { placement, popper } = this.beaconPopper;
/* istanbul ignore else */
if (!['bottom'].includes(placement) && !hasCustomScroll) {

@@ -304,3 +344,3 @@ scrollY = Math.floor(popper.top - scrollOffset);

if (['top', 'right'].includes(placement) && !flipped && !hasCustomScroll) {
if (['top', 'right', 'left'].includes(placement) && !flipped && !hasCustomScroll) {
scrollY = Math.floor(popper.top - scrollOffset);

@@ -313,3 +353,6 @@ }

if (status === STATUS.RUNNING && shouldScroll && scrollY >= 0) {
scrollY = scrollY >= 0 ? scrollY : 0;
/* istanbul ignore else */
if (status === STATUS.RUNNING && shouldScroll) {
scrollTo(scrollY, scrollParent);

@@ -364,3 +407,3 @@ }

getPopper = (popper, type) => {
setPopper = (popper, type) => {
if (type === 'wrapper') {

@@ -378,3 +421,3 @@ this.beaconPopper = popper;

const { index, status } = this.state;
const { continuous, debug, disableScrolling, steps } = this.props;
const { continuous, debug, steps } = this.props;
const step = getMergedStep(steps[index], this.props);

@@ -390,4 +433,3 @@ let output;

debug={debug}
disableScrolling={disableScrolling}
getPopper={this.getPopper}
setPopper={this.setPopper}
helpers={this.helpers}

@@ -401,3 +443,3 @@ step={step}

return (
<div className="joyride">
<div className="react-joyride">
{output}

@@ -404,0 +446,0 @@ </div>

@@ -14,3 +14,3 @@ import React from 'react';

} from '../modules/dom';
import { getBrowser, isLegacy } from '../modules/helpers';
import { getBrowser, isLegacy, log } from '../modules/helpers';

@@ -21,3 +21,3 @@ import LIFECYCLE from '../constants/lifecycle';

export default class Overlay extends React.Component {
export default class JoyrideOverlay extends React.Component {
constructor(props) {

@@ -34,4 +34,6 @@ super(props);

static propTypes = {
debug: PropTypes.bool.isRequired,
disableOverlay: PropTypes.bool.isRequired,
disableScrolling: PropTypes.bool.isRequired,
disableScrollParentFix: PropTypes.bool.isRequired,
lifecycle: PropTypes.string.isRequired,

@@ -50,7 +52,19 @@ onClickOverlay: PropTypes.func.isRequired,

componentDidMount() {
const { disableScrolling, target } = this.props;
const { debug, disableScrolling, disableScrollParentFix, target } = this.props;
/* istanbul ignore else */
if (!disableScrolling) {
const element = getElement(target);
this.scrollParent = hasCustomScrollParent(element) ? getScrollParent(element) : document;
this.scrollParent = getScrollParent(element, disableScrollParentFix);
/* istanbul ignore else */
if (hasCustomScrollParent(element, true)) {
log({
title: 'step has a custom scroll parent and can cause trouble with scrolling',
data: [
{ key: 'parent', value: this.scrollParent },
],
debug,
});
}
}

@@ -61,6 +75,7 @@

componentWillReceiveProps(nextProps) {
const { disableScrolling, lifecycle, spotlightClicks } = nextProps;
const { changed, changedTo } = treeChanges(this.props, nextProps);
componentDidUpdate(prevProps) {
const { disableScrolling, lifecycle, spotlightClicks } = this.props;
const { changed, changedTo } = treeChanges(prevProps, this.props);
/* istanbul ignore else */
if (!disableScrolling) {

@@ -97,2 +112,3 @@ if (changedTo('lifecycle', LIFECYCLE.TOOLTIP)) {

/* istanbul ignore else */
if (!disableScrolling) {

@@ -121,3 +137,2 @@ clearTimeout(this.scrollTimeout);

const { isScrolling } = this.state;
if (!isScrolling) {

@@ -130,3 +145,2 @@ this.setState({ isScrolling: true, showSpotlight: false });

this.scrollTimeout = setTimeout(() => {
clearTimeout(this.scrollTimeout);
this.setState({ isScrolling: false, showSpotlight: true });

@@ -148,7 +162,7 @@ this.scrollParent.removeEventListener('scroll', this.handleScroll);

const { showSpotlight } = this.state;
const { spotlightClicks, spotlightPadding, styles, target } = this.props;
const { disableScrollParentFix, spotlightClicks, spotlightPadding, styles, target } = this.props;
const element = getElement(target);
const elementRect = getClientRect(element);
const isFixedTarget = isFixed(element);
const top = getElementPosition(element, spotlightPadding);
const top = getElementPosition(element, spotlightPadding, disableScrollParentFix);

@@ -182,2 +196,9 @@ return {

let baseStyles = styles.overlay;
/* istanbul ignore else */
if (isLegacy()) {
baseStyles = placement === 'center' ? styles.overlayLegacyCenter : styles.overlayLegacy;
}
const stylesOverlay = {

@@ -187,3 +208,3 @@ cursor: disableOverlay ? 'default' : 'pointer',

pointerEvents: mouseOverSpotlight ? 'none' : 'auto',
...(isLegacy() ? styles.overlayLegacy : styles.overlay),
...baseStyles,
};

@@ -209,3 +230,3 @@

<div
className="joyride-overlay"
className="react-joyride__overlay"
style={stylesOverlay}

@@ -212,0 +233,0 @@ onClick={onClickOverlay}

@@ -13,2 +13,4 @@ import React from 'react';

this.node = document.createElement('div');
/* istanbul ignore else */
if (props.id) {

@@ -15,0 +17,0 @@ this.node.id = props.id;

@@ -7,3 +7,3 @@ import React from 'react';

key="JoyrideSpotlight"
className="joyride-spotlight"
className="react-joyride__spotlight"
style={styles}

@@ -10,0 +10,0 @@ />

@@ -8,7 +8,6 @@ import React from 'react';

import ACTIONS from '../constants/actions';
import LIFECYCLE from '../constants/lifecycle';
import { ACTIONS, EVENTS, LIFECYCLE, STATUS } from '../constants';
import { getElement, isFixed } from '../modules/dom';
import { log } from '../modules/helpers';
import { getElement, isElementVisible, isFixed } from '../modules/dom';
import { log, hideBeacon } from '../modules/helpers';
import { setScope, removeScope } from '../modules/scope';

@@ -20,5 +19,3 @@ import { validateStep } from '../modules/step';

import Tooltip from './Tooltip/index';
import JoyridePortal from './Portal';
import EVENTS from '../constants/events';
import STATUS from '../constants/status';
import Portal from './Portal';

@@ -32,6 +29,6 @@ export default class JoyrideStep extends React.Component {

debug: PropTypes.bool.isRequired,
getPopper: PropTypes.func.isRequired,
helpers: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
lifecycle: PropTypes.string.isRequired,
setPopper: PropTypes.func.isRequired,
size: PropTypes.number.isRequired,

@@ -48,2 +45,4 @@ status: PropTypes.string.isRequired,

disableOverlayClose: PropTypes.bool,
disableScrolling: PropTypes.bool,
disableScrollParentFix: PropTypes.bool,
event: PropTypes.string,

@@ -54,2 +53,4 @@ floaterProps: PropTypes.shape({

hideBackButton: PropTypes.bool,
hideCloseButton: PropTypes.bool,
hideFooter: PropTypes.bool,
isFixed: PropTypes.bool,

@@ -93,28 +94,11 @@ locale: PropTypes.object,

componentWillReceiveProps(nextProps) {
const { action, continuous, debug, index, lifecycle, step, update } = this.props;
const { changed, changedFrom } = treeChanges(this.props, nextProps);
const skipBeacon = continuous && action !== ACTIONS.CLOSE && (index > 0 || action === ACTIONS.PREV);
if (changedFrom('lifecycle', LIFECYCLE.INIT, LIFECYCLE.READY)) {
update({ lifecycle: step.disableBeacon || skipBeacon ? LIFECYCLE.TOOLTIP : LIFECYCLE.BEACON });
}
if (changed('index')) {
log({
title: `step:${lifecycle}`,
data: [
{ key: 'props', value: this.props },
],
debug,
});
}
}
componentDidUpdate(prevProps) {
const { action, callback, controlled, index, lifecycle, size, status, step, update } = this.props;
const { action, callback, continuous, controlled, debug, index, lifecycle, size, status, step, update } = this.props;
const { changed, changedTo, changedFrom } = treeChanges(prevProps, this.props);
const state = { action, controlled, index, lifecycle, size, status };
const isAfterAction = [
const skipBeacon = continuous && action !== ACTIONS.CLOSE && (index > 0 || action === ACTIONS.PREV);
const hasStoreChanged = changed('action') || changed('index') || changed('lifecycle') || changed('status');
const hasStarted = changedFrom('lifecycle', [LIFECYCLE.TOOLTIP, LIFECYCLE.INIT], LIFECYCLE.INIT);
const isAfterAction = changedTo('action', [
ACTIONS.NEXT,

@@ -124,7 +108,5 @@ ACTIONS.PREV,

ACTIONS.CLOSE,
].includes(action) && changed('action');
]);
const hasChangedIndex = changed('index') && changedFrom('lifecycle', LIFECYCLE.TOOLTIP, LIFECYCLE.INIT);
if (!changed('status') && (hasChangedIndex || (controlled && isAfterAction))) {
if (isAfterAction && (hasStarted || controlled)) {
callback({

@@ -140,7 +122,8 @@ ...state,

// There's a step to use, but there's no target in the DOM
if (step) {
const hasRenderedTarget = !!getElement(step.target);
if (hasStoreChanged && step) {
const element = getElement(step.target);
const hasRenderedTarget = !!element && isElementVisible(element);
if (hasRenderedTarget) {
if (changedFrom('status', STATUS.READY, STATUS.RUNNING) || changed('index')) {
if (changedFrom('status', STATUS.READY, STATUS.RUNNING) || changedFrom('lifecycle', LIFECYCLE.INIT, LIFECYCLE.READY)) {
callback({

@@ -153,4 +136,3 @@ ...state,

}
if (!hasRenderedTarget) {
else {
console.warn('Target not mounted', step); //eslint-disable-line no-console

@@ -169,2 +151,16 @@ callback({

if (changedFrom('lifecycle', LIFECYCLE.INIT, LIFECYCLE.READY)) {
update({ lifecycle: hideBeacon(step) || skipBeacon ? LIFECYCLE.TOOLTIP : LIFECYCLE.BEACON });
}
if (changed('index')) {
log({
title: `step:${lifecycle}`,
data: [
{ key: 'props', value: this.props },
],
debug,
});
}
/* istanbul ignore else */

@@ -189,7 +185,4 @@ if (changedTo('lifecycle', LIFECYCLE.BEACON)) {

if (changedFrom('lifecycle', LIFECYCLE.TOOLTIP, LIFECYCLE.INIT)) {
if (changedFrom('lifecycle', [LIFECYCLE.TOOLTIP, LIFECYCLE.INIT], LIFECYCLE.INIT)) {
removeScope();
}
if (changedTo('lifecycle', LIFECYCLE.INIT)) {
delete this.beaconPopper;

@@ -200,2 +193,6 @@ delete this.tooltipPopper;

componentWillUnmount() {
removeScope();
}
/**

@@ -229,3 +226,3 @@ * Beacon click/hover event listener

setPopper = (popper, type) => {
const { action, getPopper, update } = this.props;
const { action, setPopper, update } = this.props;

@@ -239,3 +236,3 @@ if (type === 'wrapper') {

getPopper(popper, type);
setPopper(popper, type);

@@ -253,3 +250,3 @@ if (this.beaconPopper && this.tooltipPopper) {

return !!(step.disableBeacon || lifecycle === LIFECYCLE.TOOLTIP);
return !!(hideBeacon(step) || lifecycle === LIFECYCLE.TOOLTIP);
}

@@ -260,3 +257,2 @@

continuous,
controlled,
debug,

@@ -276,10 +272,11 @@ helpers,

return (
<div key={`JoyrideStep-${index}`} className="joyride-step">
<JoyridePortal>
<div key={`JoyrideStep-${index}`} className="react-joyride__step">
<Portal id="react-joyride-portal">
<Overlay
{...step}
debug={debug}
lifecycle={lifecycle}
onClickOverlay={this.handleClickOverlay}
/>
</JoyridePortal>
</Portal>
<Floater

@@ -289,8 +286,7 @@ component={(

continuous={continuous}
controlled={controlled}
helpers={helpers}
index={index}
isLastStep={index + 1 === size}
setTooltipRef={this.setTooltipRef}
size={size}
isLastStep={index + 1 === size}
step={step}

@@ -301,3 +297,3 @@ />

getPopper={this.setPopper}
id={`react-joyride:${index}`}
id={`react-joyride-step-${index}`}
isPositioned={step.isFixed || isFixed(target)}

@@ -304,0 +300,0 @@ open={this.open}

import React from 'react';
import PropTypes from 'prop-types';
const CloseBtn = ({ styles, ...props }) => {
const JoyrideTooltipCloseBtn = ({ styles, ...props }) => {
const { color, height, width, ...style } = styles;

@@ -32,6 +32,6 @@

CloseBtn.propTypes = {
JoyrideTooltipCloseBtn.propTypes = {
styles: PropTypes.object.isRequired,
};
export default CloseBtn;
export default JoyrideTooltipCloseBtn;

@@ -18,3 +18,13 @@ import React from 'react';

}) => {
const { content, hideBackButton, locale, showProgress, showSkipButton, title, styles } = step;
const {
content,
hideBackButton,
hideCloseButton,
hideFooter,
locale,
showProgress,
showSkipButton,
title,
styles,
} = step;
const { back, close, last, next, skip } = locale;

@@ -26,11 +36,6 @@ const output = {

if (continuous) {
if (isLastStep) {
output.primary = last;
}
else {
output.primary = next;
}
output.primary = isLastStep ? last : next;
if (showProgress) {
output.primary += ` (${index + 1}/${size})`;
output.primary = <span>{output.primary} ({index + 1}/{size})</span>;
}

@@ -44,2 +49,3 @@ }

type="button"
data-test-id="button-skip"
{...skipProps}

@@ -57,2 +63,3 @@ >

type="button"
data-test-id="button-back"
{...backProps}

@@ -65,3 +72,9 @@ >

output.close = (<CloseBtn {...closeProps} styles={styles.buttonClose} />);
output.close = !hideCloseButton && (
<CloseBtn
styles={styles.buttonClose}
data-test-id="button-close"
{...closeProps}
/>
);

@@ -71,2 +84,3 @@ return (

key="JoyrideTooltip"
className="react-joyride__tooltip"
ref={setTooltipRef}

@@ -84,13 +98,16 @@ style={styles.tooltip}

</div>
<div style={styles.tooltipFooter}>
{output.skip}
{output.back}
<button
style={styles.buttonNext}
type="button"
{...primaryProps}
>
{output.primary}
</button>
</div>
{!hideFooter && (
<div style={styles.tooltipFooter}>
{output.skip}
{output.back}
<button
style={styles.buttonNext}
type="button"
data-test-id="button-primary"
{...primaryProps}
>
{output.primary}
</button>
</div>
)}
</div>

@@ -97,0 +114,0 @@ );

@@ -52,4 +52,4 @@ import React from 'react';

const { continuous, index, isLastStep, setTooltipRef, size, step } = this.props;
const { content, locale, title, tooltipComponent } = step;
const { back, close, last, next, skip } = locale;
const { beaconComponent, tooltipComponent, ...cleanStep } = step;
const { back, close, last, next, skip } = step.locale;
let primaryText = continuous ? next : close;

@@ -72,18 +72,12 @@

...buttonProps,
content,
continuous,
index,
isLastStep,
locale,
setTooltipRef,
size,
title,
step: cleanStep,
};
if (React.isValidElement(tooltipComponent)) {
component = React.cloneElement(tooltipComponent, renderProps);
}
else {
component = tooltipComponent(renderProps);
}
const TooltipComponent = tooltipComponent;
component = <TooltipComponent {...renderProps} />;
}

@@ -90,0 +84,0 @@ else {

@@ -6,7 +6,5 @@ export default {

RESET: 'reset',
RESTART: 'restart',
PREV: 'prev',
NEXT: 'next',
GO: 'go',
INDEX: 'index',
CLOSE: 'close',

@@ -13,0 +11,0 @@ SKIP: 'skip',

@@ -6,3 +6,2 @@ export default {

TOOLTIP: 'tooltip',
TOOLTIP_CLOSE: 'close',
STEP_AFTER: 'step:after',

@@ -9,0 +8,0 @@ TOUR_END: 'tour:end',

@@ -1,7 +0,4 @@

import ACTIONS from './actions';
import EVENTS from './events';
import LIFECYCLE from './lifecycle';
import STATUS from './status';
export { ACTIONS, EVENTS, LIFECYCLE, STATUS };
export default { ACTIONS, EVENTS, LIFECYCLE, STATUS };
export { default as ACTIONS } from './actions';
export { default as EVENTS } from './events';
export { default as LIFECYCLE } from './lifecycle';
export { default as STATUS } from './status';
import Joyride from './components';
export { default as ACTIONS } from './constants/actions';
export { default as EVENTS } from './constants/events';
export { default as LIFECYCLE } from './constants/lifecycle';
export { default as STATUS } from './constants/status';
export default Joyride;
// @flow
import scroll from 'scroll';
import scrollDoc from 'scroll-doc';
import getScrollParent from 'scrollparent';
import scrollParent from 'scrollparent';
export { getScrollParent };
/**

@@ -38,2 +36,18 @@ * Find the bounding client rect

/**
* Find and return the target DOM element based on a step's 'target'.
*
* @private
* @param {string|HTMLElement} element
*
* @returns {HTMLElement|null}
*/
export function getElement(element: string | HTMLElement): ?HTMLElement {
if (typeof element === 'string') {
return element ? document.querySelector(element) : null;
}
return element;
}
/**
* Find the bounding client rect relative to the parent

@@ -69,2 +83,9 @@ *

/**
* Get computed style property
*
* @param {HTMLElement} el
*
* @returns {Object}
*/
export function getStyleComputedProperty(el: HTMLElement): Object {

@@ -78,9 +99,50 @@ if (!el || el.nodeType !== 1) {

export function hasCustomScrollParent(element: ?HTMLElement): boolean {
if (!element) {
return false;
/**
* Get scroll parent with fix
*
* @param {HTMLElement} element
* @param {boolean} skipFix
*
* @returns {*}
*/
export function getScrollParent(element: HTMLElement, skipFix: boolean): HTMLElement {
const parent = scrollParent(element);
if (parent.isSameNode(scrollDoc())) {
return scrollDoc();
}
return getScrollParent(element) !== scrollDoc();
const hasScrolling = parent.scrollHeight > parent.offsetHeight;
if (!hasScrolling && !skipFix) {
parent.style.overflow = 'initial';
return scrollDoc();
}
return parent;
}
/**
* Check if the element has custom scroll parent
*
* @param {HTMLElement} element
* @param {boolean} skipFix
*
* @returns {boolean}
*/
export function hasCustomScrollParent(element: ?HTMLElement, skipFix: boolean): boolean {
if (!element) return false;
const parent = getScrollParent(element, skipFix);
return !parent.isSameNode(scrollDoc());
}
/**
* Check if the element has custom offset parent
*
* @param {HTMLElement} element
*
* @returns {boolean}
*/
export function hasCustomOffsetParent(element: HTMLElement): boolean {

@@ -90,2 +152,35 @@ return element.offsetParent !== document.body;

/**
* Check if the element is visible
*
* @param {HTMLElement} element
*
* @returns {boolean}
*/
export function isElementVisible(element: ?HTMLElement): boolean {
if (!element) return false;
let parentElement = element;
while (parentElement) {
if (parentElement === document.body) break;
if (parentElement instanceof HTMLElement) {
const { display, visibility } = getComputedStyle(parentElement);
if (display === 'none' || visibility === 'hidden') {
return false;
}
}
parentElement = parentElement.parentNode;
}
return true;
}
/**
* Check if the element is fixed
* @param {HTMLElement} el
* @returns {boolean}
*/
export function isFixed(el: ?HTMLElement | Node): boolean {

@@ -110,2 +205,22 @@ if (!el || !(el instanceof HTMLElement)) {

/**
* Find and return the target DOM element based on a step's 'target'.
*
* @private
* @param {string|HTMLElement} element
* @param {number} offset
* @param {boolean} skipFix
*
* @returns {HTMLElement|undefined}
*/
export function getElementPosition(element: HTMLElement, offset: number, skipFix: boolean): number {
const elementRect = getClientRect(element);
const parent = getScrollParent(element, skipFix);
const hasScrollParent = hasCustomScrollParent(element, skipFix);
const top = elementRect.top + (!hasScrollParent && !isFixed(element) ? parent.scrollTop : 0);
return Math.floor(top - offset);
}
/**
* Get the scrollTop position

@@ -115,6 +230,7 @@ *

* @param {number} offset
* @param {boolean} skipFix
*
* @returns {number}
*/
export function getScrollTo(element: HTMLElement, offset: number): number {
export function getScrollTo(element: HTMLElement, offset: number, skipFix: boolean): number {
if (!element) {

@@ -124,6 +240,6 @@ return 0;

const parent = getScrollParent(element);
const parent = scrollParent(element);
let top = element.offsetTop;
if (hasCustomScrollParent(element) && !hasCustomOffsetParent(element)) {
if (hasCustomScrollParent(element, skipFix) && !hasCustomOffsetParent(element)) {
top -= parent.offsetTop;

@@ -136,36 +252,7 @@ }

/**
* Find and return the target DOM element based on a step's 'target'.
*
* @private
* @param {string|HTMLElement} element
*
* @returns {HTMLElement|undefined}
* Scroll to position
* @param {number} value
* @param {HTMLElement} element
* @returns {Promise<*>}
*/
export function getElement(element: string | HTMLElement): ?HTMLElement {
if (typeof element !== 'string') {
return element;
}
return element ? document.querySelector(element) : null;
}
/**
* Find and return the target DOM element based on a step's 'target'.
*
* @private
* @param {string|HTMLElement} element
* @param {number} offset
*
* @returns {HTMLElement|undefined}
*/
export function getElementPosition(element: HTMLElement, offset: number): number {
const elementRect = getClientRect(element);
const scrollParent = getScrollParent(element);
const hasScrollParent = hasCustomScrollParent(element);
const top = elementRect.top + (!hasScrollParent && !isFixed(element) ? scrollParent.scrollTop : 0);
return Math.floor(top - offset);
}
export function scrollTo(value: number, element: HTMLElement = scrollDoc()): Promise<*> {

@@ -172,0 +259,0 @@ return new Promise((resolve, reject) => {

@@ -9,6 +9,2 @@ // @flow

export function isMobile(): boolean {
return ('ontouchstart' in window) && /Mobi/.test(navigator.userAgent);
}
/**

@@ -75,47 +71,2 @@ * Convert hex to RGB

/**
* Detect legacy browsers
*
* @returns {boolean}
*/
export function isLegacy(): boolean {
return !['chrome', 'safari', 'firefox', 'opera'].includes(getBrowser());
}
/**
* Log method calls if debug is enabled
*
* @private
* @param {Object} arg
* @param {string} arg.title - The title the logger was called from
* @param {Object|Array} [arg.data] - The data to be logged
* @param {boolean} [arg.warn] - If true, the message will be a warning
* @param {boolean} [arg.debug] - Nothing will be logged unless debug is true
*/
export function log({ title, data, warn = false, debug = false }: Object) {
/* eslint-disable no-console */
const logFn = warn ? console.warn || console.error : console.log;
if (debug && title && data) {
console.groupCollapsed(`%creact-joyride: ${title}`, 'color: #ff0044; font-weight: bold; font-size: 12px;');
if (Array.isArray(data)) {
data.forEach(d => {
if (is.plainObject(d) && d.key) {
logFn.apply(console, [d.key, d.value]);
}
else {
logFn.apply(console, [d]);
}
});
}
else {
logFn.apply(console, [data]);
}
console.groupEnd();
}
/* eslint-enable */
}
export function hasKey(value: Object, key: string): boolean {

@@ -138,27 +89,40 @@ return Object.prototype.hasOwnProperty.call(value, key);

export function isEqual(a: any, b: any): boolean {
let p;
export function hideBeacon(step: Object): boolean {
return step.disableBeacon || step.placement === 'center';
}
export function isEqual(left: any, right: any): boolean {
let t;
const leftIsDOM = is.domElement(left);
const rightIsDom = is.domElement(right);
for (p in a) {
if (Object.prototype.hasOwnProperty.call(a, p)) {
if (typeof b[p] === 'undefined') {
if (leftIsDOM && rightIsDom) {
return left.isSameNode(right);
}
if ((leftIsDOM && !rightIsDom) || (!leftIsDOM && rightIsDom)) {
return true;
}
for (const p in left) {
if (Object.prototype.hasOwnProperty.call(left, p)) {
if (typeof right[p] === 'undefined') {
return false;
}
if (b[p] && !a[p]) {
if (right[p] && !left[p]) {
return false;
}
t = typeof a[p];
t = typeof left[p];
if (t === 'object' && !isEqual(a[p], b[p])) {
if (t === 'object' && !isEqual(left[p], right[p])) {
return false;
}
if (t === 'function' && (typeof b[p] === 'undefined' || a[p].toString() !== b[p].toString())) {
if (t === 'function' && (typeof right[p] === 'undefined' || left[p].toString() !== right[p].toString())) {
return false;
}
if (a[p] !== b[p]) {
if (left[p] !== right[p]) {
return false;

@@ -169,5 +133,7 @@ }

for (p in b) {
if (typeof a[p] === 'undefined') {
return false;
for (const p in right) {
if (Object.prototype.hasOwnProperty.call(right, p)) {
if (typeof left[p] === 'undefined') {
return false;
}
}

@@ -178,1 +144,55 @@ }

}
/**
* Detect legacy browsers
*
* @returns {boolean}
*/
export function isLegacy(): boolean {
return !['chrome', 'safari', 'firefox', 'opera'].includes(getBrowser());
}
export function isMobile(): boolean {
return ('ontouchstart' in window) && /Mobi/.test(navigator.userAgent);
}
/**
* Log method calls if debug is enabled
*
* @private
* @param {Object} arg
* @param {string} arg.title - The title the logger was called from
* @param {Object|Array} [arg.data] - The data to be logged
* @param {boolean} [arg.warn] - If true, the message will be a warning
* @param {boolean} [arg.debug] - Nothing will be logged unless debug is true
*/
export function log({ title, data, warn = false, debug = false }: Object) {
/* eslint-disable no-console */
const logFn = warn ? console.warn || console.error : console.log;
if (debug) {
if (title && data) {
console.groupCollapsed(`%creact-joyride: ${title}`, 'color: #ff0044; font-weight: bold; font-size: 12px;');
if (Array.isArray(data)) {
data.forEach(d => {
if (is.plainObject(d) && d.key) {
logFn.apply(console, [d.key, d.value]);
}
else {
logFn.apply(console, [d]);
}
});
}
else {
logFn.apply(console, [data]);
}
console.groupEnd();
}
else {
console.error('log', 'Missing title or data props');
}
}
/* eslint-enable */
}
const validTabNodes = /input|select|textarea|button|object/;
const TAB_KEY = 9;
let modalElement = null;
function isHidden(element) {
const noSize = element.offsetWidth <= 0 && element.offsetHeight <= 0;
class Scope {
constructor(tooltip) {
this.tooltip = tooltip;
}
if (noSize && !element.innerHTML) return true;
canBeTabbed = (element) => {
let tabIndex = element.getAttribute('tabindex');
if (tabIndex === null) tabIndex = undefined;
const isTabIndexNaN = isNaN(tabIndex);
return (isTabIndexNaN || tabIndex >= 0) && this.canHaveFocus(element, !isTabIndexNaN);
};
const style = window.getComputedStyle(element);
return noSize ? style.getPropertyValue('overflow') !== 'visible' : style.getPropertyValue('display') === 'none';
}
canHaveFocus = (element, isTabIndexNotNaN) => {
const nodeName = element.nodeName.toLowerCase();
const res = (validTabNodes.test(nodeName) && !element.disabled)
|| (nodeName === 'a' ? element.href || isTabIndexNotNaN : isTabIndexNotNaN);
return res && this.isVisible(element);
};
function isVisible(element) {
let parentElement = element;
while (parentElement) {
if (parentElement === document.body) break;
if (isHidden(parentElement)) return false;
parentElement = parentElement.parentNode;
}
return true;
}
findValidTabElements = (element) => [].slice.call(element.querySelectorAll('*'), 0).filter(this.canBeTabbed);
function canHaveFocus(element, isTabIndexNotNaN) {
const nodeName = element.nodeName.toLowerCase();
const res = (validTabNodes.test(nodeName) && !element.disabled)
|| (nodeName === 'a' ? element.href || isTabIndexNotNaN : isTabIndexNotNaN);
return res && isVisible(element);
}
handleKeyDown = (e) => {
if (!this.tooltip) {
return;
}
function canBeTabbed(element) {
let tabIndex = element.getAttribute('tabindex');
if (tabIndex === null) tabIndex = undefined;
const isTabIndexNaN = isNaN(tabIndex);
return (isTabIndexNaN || tabIndex >= 0) && canHaveFocus(element, !isTabIndexNaN);
}
if (e.keyCode === TAB_KEY) {
this.interceptTab(this.tooltip, e);
}
};
function findValidTabElements(element) {
return [].slice.call(element.querySelectorAll('*'), 0).filter(canBeTabbed);
}
interceptTab = (node, event) => {
const elements = this.findValidTabElements(node);
const { shiftKey } = event;
function interceptTab(node, event) {
const elements = findValidTabElements(node);
const { shiftKey } = event;
if (!elements.length) {
event.preventDefault();
return;
}
if (!elements.length) {
let x = elements.indexOf(document.activeElement);
if (x === -1 || (!shiftKey && x + 1 === elements.length)) {
x = 0;
}
else {
x += shiftKey ? -1 : 1;
}
event.preventDefault();
return;
}
let x = elements.indexOf(document.activeElement);
elements[x].focus();
};
if (x === -1 || (!shiftKey && x + 1 === elements.length)) {
x = 0;
}
else {
x += shiftKey ? -1 : 1;
}
isHidden = (element) => {
const noSize = element.offsetWidth <= 0 && element.offsetHeight <= 0;
const style = window.getComputedStyle(element);
event.preventDefault();
if (noSize && !element.innerHTML) return true;
elements[x].focus();
return (noSize && style.getPropertyValue('overflow') !== 'visible') || style.getPropertyValue('display') === 'none';
};
isVisible = (element) => {
let parentElement = element;
while (parentElement) {
if (parentElement === document.body) break;
if (this.isHidden(parentElement)) return false;
parentElement = parentElement.parentNode;
}
return true;
};
}
function handleKeyDown(e) {
if (!modalElement) {
return;
}
let scope;
if (e.keyCode === TAB_KEY) {
interceptTab(modalElement, e);
}
}
export function setScope(element) {
modalElement = element;
scope = new Scope(element);
window.addEventListener('keydown', handleKeyDown, false);
window.addEventListener('keydown', scope.handleKeyDown, false);
}
export function removeScope() {
modalElement = null;
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keydown', scope.handleKeyDown);
}

@@ -11,60 +11,3 @@ // @flow

import type { StepProps, JoyrideProps } from '../config/types';
/**
* Validate if a step is valid
*
* @param {Object} step - A step object
* @param {boolean} debug
*
* @returns {boolean} - True if the step is valid, false otherwise
*/
export function validateStep(step: StepProps, debug: boolean = false): boolean {
if (!is.plainObject(step)) {
log({
title: 'validateStep',
data: 'step must be an object',
warn: true,
debug,
});
return false;
}
if (!step.target) {
log({
title: 'validateStep',
data: 'target is missing from the step',
warn: true,
debug,
});
return false;
}
return true;
}
/**
* Validate if steps is valid
*
* @param {Array} steps - A steps array
* @param {boolean} debug
*
* @returns {boolean} - True if the steps are valid, false otherwise
*/
export function validateSteps(steps: Array<Object>, debug: boolean = false): boolean {
if (!is.array(steps)) {
log({
title: 'validateSteps',
data: 'steps must be an array',
warn: true,
debug,
});
return false;
}
return steps.every(d => validateStep(d, debug));
}
function getTourProps(props: JoyrideProps): JoyrideProps {
function getTourProps(props: Object): Object {
const sharedTourProps = [

@@ -76,2 +19,3 @@ 'beaconComponent',

'disableScrolling',
'disableScrollParentFix',
'floaterProps',

@@ -97,8 +41,8 @@ 'hideBackButton',

export function getMergedStep(step: StepProps, props: JoyrideProps): StepProps {
if (!step) return undefined;
export function getMergedStep(step: StepProps, props: JoyrideProps): ?StepProps {
if (!step) return null;
const mergedStep = deepmerge.all([getTourProps(props), DEFAULTS.step, step]);
const mergedStep = deepmerge.all([getTourProps(props), DEFAULTS.step, step], { isMergeableObject: is.plainObject });
const mergedStyles = getStyles(deepmerge(props.styles || {}, step.styles || {}));
const scrollParent = hasCustomScrollParent(getElement(step.target));
const scrollParent = hasCustomScrollParent(getElement(step.target), mergedStep.disableScrollParentFix);
const floaterProps = deepmerge.all([props.floaterProps || {}, DEFAULTS.floaterProps, mergedStep.floaterProps || {}]);

@@ -135,1 +79,56 @@

}
/**
* Validate if a step is valid
*
* @param {Object} step - A step object
* @param {boolean} debug
*
* @returns {boolean} - True if the step is valid, false otherwise
*/
export function validateStep(step: StepProps, debug: boolean = false): boolean {
if (!is.plainObject(step)) {
log({
title: 'validateStep',
data: 'step must be an object',
warn: true,
debug,
});
return false;
}
if (!step.target) {
log({
title: 'validateStep',
data: 'target is missing from the step',
warn: true,
debug,
});
return false;
}
return true;
}
/**
* Validate if steps is valid
*
* @param {Array} steps - A steps array
* @param {boolean} debug
*
* @returns {boolean} - True if the steps are valid, false otherwise
*/
export function validateSteps(steps: Array<Object>, debug: boolean = false): boolean {
if (!is.array(steps)) {
log({
title: 'validateSteps',
data: 'steps must be an array',
warn: true,
debug,
});
return false;
}
return steps.every(d => validateStep(d, debug));
}
// @flow
import is from 'is-lite';
import STATUS from '../constants/status';
import ACTIONS from '../constants/actions';
import LIFECYCLE from '../constants/lifecycle';
import { ACTIONS, LIFECYCLE, STATUS } from '../constants';
import { hasValidKeys } from './helpers';
import type { StateHelpers, StateInstance, StateObject } from '../config/types';
const defaultState: StateObject = {
const defaultState: StoreState = {
action: '',

@@ -22,3 +18,3 @@ controlled: false,

export default function createStore(props: StateObject): StateInstance {
export default function createStore(props: StoreState): StoreInstance {
const store: Map<string, any> = new Map();

@@ -43,10 +39,6 @@ const data: Map<string, any> = new Map();

addListener(listener: Function) {
this.listener = listener;
}
setState(nextState: Object, initial: boolean = false) {
const state = this.getState();
const { action, index, lifecycle, status } = {
const { action, index, lifecycle, size, status } = {
...state,

@@ -59,2 +51,3 @@ ...nextState,

store.set('lifecycle', lifecycle);
store.set('size', size);
store.set('status', status);

@@ -74,3 +67,3 @@

getState(): Object {
getState(): StoreState {
if (!store.size) {

@@ -80,17 +73,13 @@ return { ...defaultState };

const index = parseInt(store.get('index'), 10);
const steps = this.getSteps();
const size = steps.length;
return {
action: store.get('action'),
controlled: store.get('controlled'),
index,
lifecycle: store.get('lifecycle'),
size,
status: store.get('status'),
action: store.get('action') || '',
controlled: store.get('controlled') || false,
index: parseInt(store.get('index'), 10),
lifecycle: store.get('lifecycle') || '',
size: store.get('size') || 0,
status: store.get('status') || '',
};
}
getNextState(state: StateObject, force: ?boolean = false): Object {
getNextState(state: Object, force: ?boolean = false): StoreState {
const { action, controlled, index, size, status } = this.getState();

@@ -102,4 +91,6 @@ const newIndex = is.number(state.index) ? state.index : index;

action: state.action || action,
controlled,
index: nextIndex,
lifecycle: state.lifecycle || LIFECYCLE.INIT,
size: state.size || size,
status: nextIndex === size ? STATUS.FINISHED : (state.status || status),

@@ -109,3 +100,3 @@ };

hasUpdatedState(oldState: StateObject): boolean {
hasUpdatedState(oldState: StoreState): boolean {
const before = JSON.stringify(oldState);

@@ -117,11 +108,2 @@ const after = JSON.stringify(this.getState());

setSteps = (steps: Array<Object>) => {
const { size, status } = this.getState();
data.set('steps', steps);
if (status === STATUS.WAITING && !size && steps.length) {
this.setState({ status: STATUS.RUNNING });
}
};
getSteps(): Array<Object> {

@@ -133,14 +115,10 @@ const steps = data.get('steps');

getHelpers(): StateHelpers {
getHelpers(): StoreHelpers {
return {
start: this.start,
stop: this.stop,
restart: this.restart,
reset: this.reset,
prev: this.prev,
next: this.next,
go: this.go,
index: this.index,
close: this.close,
skip: this.skip,
reset: this.reset,
info: this.info,

@@ -150,3 +128,23 @@ };

update = (state: StateObject) => {
setSteps = (steps: Array<Object>) => {
const { size, status } = this.getState();
const state = {
size: steps.length,
status,
};
data.set('steps', steps);
if (status === STATUS.WAITING && !size && steps.length) {
state.status = STATUS.RUNNING;
}
this.setState(state);
};
addListener = (listener: Function) => {
this.listener = listener;
};
update = (state: StoreState) => {
if (!hasValidKeys(state, validKeys)) {

@@ -165,8 +163,2 @@ throw new Error('state is not valid');

steps = (nextSteps) => {
if (!is.array(nextSteps)) return;
this.setSteps(nextSteps);
};
start = (nextIndex: number) => {

@@ -195,22 +187,2 @@ const { index, size } = this.getState();

restart = () => {
const { controlled } = this.getState();
if (controlled) return;
this.setState({
...this.getNextState({ action: ACTIONS.RESTART, index: 0 }),
status: STATUS.RUNNING,
});
};
reset = () => {
const { controlled } = this.getState();
if (controlled) return;
this.setState({
...this.getNextState({ action: ACTIONS.RESET, index: 0 }),
status: STATUS.READY,
});
};
prev = () => {

@@ -227,2 +199,3 @@ const { index, status } = this.getState();

const { index, status } = this.getState();
if (status !== STATUS.RUNNING) return;

@@ -233,19 +206,10 @@

go = (number) => {
const { index, status } = this.getState();
if (status !== STATUS.RUNNING) return;
go = (nextIndex) => {
const { controlled, status } = this.getState();
if (controlled || status !== STATUS.RUNNING) return;
this.setState({
...this.getNextState({ action: ACTIONS.GO, index: index + number }),
});
};
index = (nextIndex) => {
const { status } = this.getState();
if (status !== STATUS.RUNNING) return;
const step = this.getSteps()[nextIndex];
this.setState({
...this.getNextState({ action: ACTIONS.INDEX, index: nextIndex }),
...this.getNextState({ action: ACTIONS.GO, index: nextIndex }),
status: step ? status : STATUS.FINISHED,

@@ -275,2 +239,12 @@ });

reset = (restart = false) => {
const { controlled } = this.getState();
if (controlled) return;
this.setState({
...this.getNextState({ action: ACTIONS.RESET, index: 0 }),
status: restart ? STATUS.RUNNING : STATUS.READY,
});
};
info = (): Object => this.getState()

@@ -277,0 +251,0 @@ }

@@ -15,3 +15,3 @@ import deepmerge from 'deepmerge';

const buttonReset = {
const buttonBase = {
backgroundColor: 'transparent',

@@ -22,2 +22,3 @@ border: 0,

cursor: 'pointer',
fontSize: 16,
lineHeight: 1,

@@ -65,3 +66,3 @@ padding: 8,

beacon: {
...buttonReset,
...buttonBase,
display: 'inline-block',

@@ -118,3 +119,3 @@ height: options.beaconSize,

fontSize: 18,
margin: '0 0 10px 0',
margin: 0,
},

@@ -131,3 +132,3 @@ tooltipContent: {

buttonNext: {
...buttonReset,
...buttonBase,
backgroundColor: options.primaryColor,

@@ -138,3 +139,3 @@ borderRadius: 4,

buttonBack: {
...buttonReset,
...buttonBase,
color: options.primaryColor,

@@ -145,3 +146,3 @@ marginLeft: 'auto',

buttonClose: {
...buttonReset,
...buttonBase,
color: options.textColor,

@@ -156,3 +157,3 @@ height: 14,

buttonSkip: {
...buttonReset,
...buttonBase,
color: options.textColor,

@@ -169,2 +170,6 @@ fontSize: 14,

},
overlayLegacyCenter: {
...overlay,
backgroundColor: options.overlayColor,
},
spotlight: {

@@ -182,3 +187,3 @@ ...spotlight,

},
floater: {
options: {
zIndex: options.zIndex,

@@ -185,0 +190,0 @@ },

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc