intro.js-react
Version:
Intro.js React Wrapper
292 lines (275 loc) • 8.61 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _intro = _interopRequireDefault(require("intro.js"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _react = require("react");
var _server = require("react-dom/server");
var introJsPropTypes = _interopRequireWildcard(require("../../helpers/proptypes.cjs"));
var introJsDefaultProps = _interopRequireWildcard(require("../../helpers/defaultProps.cjs"));
var _server2 = require("../../helpers/server.cjs");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Intro.js Steps Component.
*/
class Steps extends _react.Component {
/**
* React Props
* @type {Object}
*/
static propTypes = {
enabled: _propTypes.default.bool,
initialStep: _propTypes.default.number.isRequired,
steps: _propTypes.default.arrayOf(_propTypes.default.shape({
element: _propTypes.default.oneOfType([_propTypes.default.string, /* istanbul ignore next */
typeof Element === 'undefined' ? _propTypes.default.any : _propTypes.default.instanceOf(Element)]),
intro: _propTypes.default.node.isRequired,
position: introJsPropTypes.tooltipPosition,
tooltipClass: _propTypes.default.string,
highlightClass: _propTypes.default.string
})).isRequired,
onStart: _propTypes.default.func,
onExit: _propTypes.default.func.isRequired,
onBeforeExit: _propTypes.default.func,
onBeforeChange: _propTypes.default.func,
onAfterChange: _propTypes.default.func,
onChange: _propTypes.default.func,
onPreventChange: _propTypes.default.func,
onComplete: _propTypes.default.func,
options: introJsPropTypes.options
};
/**
* React Default Props
* @type {Object}
*/
static defaultProps = {
enabled: false,
onStart: null,
onBeforeExit: null,
onBeforeChange: null,
onAfterChange: null,
onChange: null,
onPreventChange: null,
onComplete: null,
options: introJsDefaultProps.options
};
/**
* Creates a new instance of the component.
* @class
* @param {Object} props - The props of the component.
*/
constructor(props) {
super(props);
this.introJs = null;
this.isConfigured = false;
// We need to manually keep track of the visibility state to avoid a callback hell.
this.isVisible = false;
this.installIntroJs();
}
/**
* Lifecycle: componentDidMount.
* We use this event to enable Intro.js steps at mount time if enabled right from the start.
*/
componentDidMount() {
if (this.props.enabled) {
this.configureIntroJs();
this.renderSteps();
}
}
/**
* Lifecycle: componentDidUpdate.
* @param {Object} prevProps - The previous props.
*/
componentDidUpdate(prevProps) {
const {
enabled,
steps,
options
} = this.props;
if (!this.isConfigured || prevProps.steps !== steps || prevProps.options !== options) {
this.configureIntroJs();
this.renderSteps();
}
if (prevProps.enabled !== enabled) {
this.renderSteps();
}
}
/**
* Lifecycle: componentWillUnmount.
* We use this even to hide the steps when the component is unmounted.
*/
componentWillUnmount() {
this.introJs.exit();
}
/**
* Triggered when Intro.js steps are exited.
*/
onExit = () => {
const {
onExit
} = this.props;
this.isVisible = false;
onExit(this.introJs._currentStep);
};
/**
* Triggered before exiting the intro.
* @return {Boolean} Returning `false` will prevent exiting the intro.
*/
onBeforeExit = () => {
const {
onBeforeExit
} = this.props;
if (onBeforeExit) {
return onBeforeExit(this.introJs._currentStep);
}
return true;
};
/**
* Triggered before changing step.
* @return {Boolean} Returning `false` will prevent the step transition.
*/
onBeforeChange = nextElement => {
if (!this.isVisible) {
return true;
}
const {
onBeforeChange,
onPreventChange
} = this.props;
if (onBeforeChange) {
const continueStep = onBeforeChange(this.introJs._currentStep, nextElement);
if (continueStep === false && onPreventChange) {
setTimeout(() => {
onPreventChange(this.introJs._currentStep);
}, 0);
}
return continueStep;
}
return true;
};
/**
* Triggered after changing step.
* @param {HTMLElement} element - The element associated to the new step.
*/
onAfterChange = element => {
if (!this.isVisible) {
return;
}
const {
onAfterChange
} = this.props;
if (onAfterChange) {
onAfterChange(this.introJs._currentStep, element);
}
};
/**
* Triggered when changing step.
* @param {HTMLElement} element - The element associated to the next step.
*/
onChange = element => {
if (!this.isVisible) {
return;
}
const {
onChange
} = this.props;
if (onChange) {
onChange(this.introJs._currentStep, element);
}
};
/**
* Triggered when completing all the steps.
*/
onComplete = () => {
const {
onComplete
} = this.props;
if (onComplete) {
onComplete();
}
};
/**
* Updates the element associated to a step based on its index.
* This is useful when the associated element is not present in the DOM on page load.
* @param {number} stepIndex - The index of the step to update.
*/
updateStepElement = stepIndex => {
const element = document.querySelector(this.introJs._options.steps[stepIndex].element);
if (element) {
this.introJs._introItems[stepIndex].element = element;
this.introJs._introItems[stepIndex].position = this.introJs._options.steps[stepIndex].position || 'auto';
}
};
/**
* Installs Intro.js.
*/
installIntroJs() {
if ((0, _server2.isServer)()) {
return;
}
this.introJs = (0, _intro.default)();
this.introJs.onexit(this.onExit);
this.introJs.onbeforeexit(this.onBeforeExit);
this.introJs.onbeforechange(this.onBeforeChange);
this.introJs.onafterchange(this.onAfterChange);
this.introJs.onchange(this.onChange);
this.introJs.oncomplete(this.onComplete);
}
/**
* Configures Intro.js if not already configured.
*/
configureIntroJs() {
const {
options,
steps
} = this.props;
const sanitizedSteps = steps.map(step => {
if ( /*#__PURE__*/(0, _react.isValidElement)(step.intro)) {
return {
...step,
intro: (0, _server.renderToStaticMarkup)(step.intro)
};
}
return step;
});
this.introJs.setOptions({
...options,
steps: sanitizedSteps
});
this.isConfigured = true;
}
/**
* Renders the Intro.js steps.
*/
renderSteps() {
const {
enabled,
initialStep,
steps,
onStart
} = this.props;
if (enabled && steps.length > 0 && !this.isVisible) {
this.introJs.start();
this.isVisible = true;
this.introJs.goToStepNumber(initialStep + 1);
if (onStart) {
onStart(this.introJs._currentStep);
}
} else if (!enabled && this.isVisible) {
this.isVisible = false;
this.introJs.exit();
}
}
/**
* Renders the component.
* @return {null} We do not want to render anything.
*/
render() {
return null;
}
}
exports.default = Steps;