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

react-stepzilla

Package Overview
Dependencies
Maintainers
2
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-stepzilla - npm Package Compare versions

Comparing version 6.0.2 to 7.0.0

.github/FUNDING.yml

597

dist/main.js

@@ -6,3 +6,3 @@ "use strict";

});
exports.default = void 0;
exports.default = StepZilla;

@@ -21,400 +21,349 @@ var _react = _interopRequireWildcard(require("react"));

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function StepZilla(props) {
var _useState = (0, _react.useState)(props.startAtStep),
_useState2 = _slicedToArray(_useState, 2),
compState = _useState2[0],
setCompState = _useState2[1];
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
var _useState3 = (0, _react.useState)(null),
_useState4 = _slicedToArray(_useState3, 2),
navState = _useState4[0],
setNavState = _useState4[1];
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
var hidden = {
display: 'none'
};
(0, _react.useEffect)(function () {
setNavState(getNavStates(props.startAtStep, props.steps.length));
}, []); // update the header nav states via classes so they can be styled via css
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
var getNavStates = function getNavStates(indx, length) {
var styles = [];
var StepZilla =
/*#__PURE__*/
function (_Component) {
_inherits(StepZilla, _Component);
for (var i = 0; i < length; i++) {
if (i < indx || !props.prevBtnOnLastStep && indx === length - 1) {
styles.push('done');
} else if (i === indx) {
styles.push('doing');
} else {
styles.push('todo');
}
}
function StepZilla(props) {
var _this;
_classCallCheck(this, StepZilla);
_this = _possibleConstructorReturn(this, _getPrototypeOf(StepZilla).call(this, props));
_this.state = {
compState: _this.props.startAtStep,
navState: _this.getNavStates(_this.props.startAtStep, _this.props.steps.length)
return {
styles: styles
};
_this.hidden = {
display: 'none'
}; // if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
};
_this.nextTextOnFinalActionStep = _this.props.nextTextOnFinalActionStep ? _this.props.nextTextOnFinalActionStep : _this.props.nextButtonText;
var getPrevNextBtnLayout = function getPrevNextBtnLayout(currentStep) {
// first set default values
var showPreviousBtn = true;
var showNextBtn = true;
var nextStepText = props.nextButtonText; // first step hide previous btn
_this.applyValidationFlagsToSteps();
if (currentStep === 0) {
showPreviousBtn = false;
} // second to last step change next btn text if supplied as props
return _this;
} // extend the "steps" array with flags to indicate if they have been validated
if (currentStep === props.steps.length - 2) {
// if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
var nextTextOnFinalActionStep = props.nextTextOnFinalActionStep ? props.nextTextOnFinalActionStep : props.nextButtonText;
nextStepText = nextTextOnFinalActionStep || nextStepText;
} // last step hide next btn, hide previous btn if supplied as props
_createClass(StepZilla, [{
key: "applyValidationFlagsToSteps",
value: function applyValidationFlagsToSteps() {
var _this2 = this;
this.props.steps.map(function (i, idx) {
if (_this2.props.dontValidate) {
i.validated = true;
} else {
// check if isValidated was exposed in the step, if yes then set initial state as not validated (false) or vice versa
// if HOCValidation is used for the step then mark it as "requires to be validated. i.e. false"
i.validated = i.component.type && i.component.type.prototype && i.component.type.prototype.isValidated && _this2.isStepAtIndexHOCValidationBased(idx) ? false : true;
}
if (currentStep >= props.steps.length - 1) {
showNextBtn = false;
showPreviousBtn = props.prevBtnOnLastStep === false ? false : true;
}
return i;
});
} // update the header nav states via classes so they can be styled via css
return {
showPreviousBtn: showPreviousBtn,
showNextBtn: showNextBtn,
nextStepText: nextStepText
};
}; // which step are we in?
}, {
key: "getNavStates",
value: function getNavStates(indx, length) {
var styles = [];
for (var i = 0; i < length; i++) {
if (i < indx || !this.props.prevBtnOnLastStep && indx === length - 1) {
styles.push('done');
} else if (i === indx) {
styles.push('doing');
} else {
styles.push('todo');
}
}
return {
current: indx,
styles: styles
};
var checkNavState = function checkNavState(nextStep) {
if (props.onStepChange) {
props.onStepChange(nextStep);
}
}, {
key: "getPrevNextBtnLayout",
value: function getPrevNextBtnLayout(currentStep) {
// first set default values
var showPreviousBtn = true;
var showNextBtn = true;
var nextStepText = this.props.nextButtonText; // first step hide previous btn
}; // set the nav state
if (currentStep === 0) {
showPreviousBtn = false;
} // second to last step change next btn text if supplied as props
var adjustNavState = function adjustNavState(next) {
setNavState(getNavStates(next, props.steps.length));
if (currentStep === this.props.steps.length - 2) {
nextStepText = this.props.nextTextOnFinalActionStep || nextStepText;
} // last step hide next btn, hide previous btn if supplied as props
if (next < props.steps.length) {
setCompState(next);
}
checkNavState(next);
}; // handles keydown on enter being pressed in any Child component input area. in this case it goes to the next (ignore textareas as they should allow line breaks)
if (currentStep >= this.props.steps.length - 1) {
showNextBtn = false;
showPreviousBtn = this.props.prevBtnOnLastStep === false ? false : true;
}
return {
showPreviousBtn: showPreviousBtn,
showNextBtn: showNextBtn,
nextStepText: nextStepText
};
} // which step are we in?
}, {
key: "checkNavState",
value: function checkNavState(nextStep) {
if (this.props.onStepChange) {
this.props.onStepChange(nextStep);
var handleKeyDown = function handleKeyDown(evt) {
if (evt.which === 13) {
if (!props.preventEnterSubmission && evt.target.type !== 'textarea') {
next();
} else if (evt.target.type !== 'textarea') {
evt.preventDefault();
}
} // set the nav state
}
}; // this utility method lets Child components invoke a direct jump to another step
}, {
key: "setNavState",
value: function setNavState(next) {
this.setState({
navState: this.getNavStates(next, this.props.steps.length)
});
if (next < this.props.steps.length) {
this.setState({
compState: next
});
}
var _jumpToStep = function jumpToStep(evt) {
if (typeof evt.target === 'undefined') {
// a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event
adjustNavState(evt);
} else {
// the main navigation step ui is invoking a jump between steps
// if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
if (!props.stepsNavigation || evt.target.value === compState) {
evt.preventDefault();
evt.stopPropagation();
return;
} // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling)
this.checkNavState(next);
} // handles keydown on enter being pressed in any Child component input area. in this case it goes to the next (ignore textareas as they should allow line breaks)
}, {
key: "handleKeyDown",
value: function handleKeyDown(evt) {
if (evt.which === 13) {
if (!this.props.preventEnterSubmission && evt.target.type !== 'textarea') {
this.next();
} else if (evt.target.type !== 'textarea') {
evt.preventDefault();
}
}
} // this utility method lets Child components invoke a direct jump to another step
evt.persist();
var movingBack = evt.target.value < compState; // are we trying to move back or front?
}, {
key: "jumpToStep",
value: function jumpToStep(evt) {
var _this3 = this;
var passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
if (typeof evt.target === 'undefined') {
// a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event
this.setNavState(evt);
} else {
// the main navigation step ui is invoking a jump between steps
// if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
if (!this.props.stepsNavigation || evt.target.value === this.state.compState) {
evt.preventDefault();
evt.stopPropagation();
return;
} // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling)
var proceed = false; // flag on if we should move on
abstractStepMoveAllowedToPromise(movingBack).then(function (valid) {
// validation was a success (promise or sync validation). In it was a Promise's resolve()
// ... then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync
proceed = typeof valid === 'undefined' ? true : valid;
evt.persist();
var movingBack = evt.target.value < this.state.compState; // are we trying to move back or front?
if (!movingBack) {
updateStepValidationFlag(proceed);
}
var passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
var proceed = false; // flag on if we should move on
this.abstractStepMoveAllowedToPromise(movingBack).then(function () {
var valid = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
// validation was a success (promise or sync validation). In it was a Promise's resolve()
// ... then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync
proceed = valid;
if (proceed) {
if (!movingBack) {
_this3.updateStepValidationFlag(proceed);
}
// looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and
// ... 'some' that to get a decision on if we should allow moving forward
passThroughStepsNotValid = props.steps.reduce(function (a, c, i) {
if (i >= compState && i < evt.target.value) {
a.push(c.validated);
}
if (proceed) {
if (!movingBack) {
// looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and
// ... 'some' that to get a decision on if we should allow moving forward
passThroughStepsNotValid = _this3.props.steps.reduce(function (a, c, i) {
if (i >= _this3.state.compState && i < evt.target.value) {
a.push(c.validated);
}
return a;
}, []).some(function (c) {
return c === false;
});
}
}
}).catch(function () {
// Promise based validation was a fail (i.e reject())
if (!movingBack) {
_this3.updateStepValidationFlag(false);
}
}).then(function () {
// this is like finally(), executes if error no no error
if (proceed && !passThroughStepsNotValid) {
if (evt.target.value === _this3.props.steps.length - 1 && _this3.state.compState === _this3.props.steps.length - 1) {
_this3.setNavState(_this3.props.steps.length);
} else {
_this3.setNavState(evt.target.value);
}
}
}).catch(function (e) {
if (e) {
// see note below called "CatchRethrowing"
// ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically
setTimeout(function () {
throw e;
return a;
}, []).some(function (c) {
return c === false;
});
}
});
}
} // move next via next button
}, {
key: "next",
value: function next() {
var _this4 = this;
this.abstractStepMoveAllowedToPromise().then(function () {
var proceed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
// validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined,
// ... so make it true. Or else 'proceed' will carry the true/false value from sync validation
_this4.updateStepValidationFlag(proceed);
if (proceed) {
_this4.setNavState(_this4.state.compState + 1);
}
}).catch(function () {
// Promise based validation was a fail (i.e reject())
if (!movingBack) {
updateStepValidationFlag(false);
}
}).then(function () {
// this is like finally(), executes if error no no error
if (proceed && !passThroughStepsNotValid) {
if (evt.target.value === props.steps.length - 1 && compState === props.steps.length - 1) {
adjustNavState(props.steps.length);
} else {
adjustNavState(evt.target.value);
}
}
}).catch(function (e) {
if (e) {
// CatchRethrowing: as we wrap StepMoveAllowed() to resolve as a Promise, the then() is invoked and the next React Component is loaded.
// ... during the render, if there are JS errors thrown (e.g. ReferenceError) it gets swallowed by the Promise library and comes in here (catch)
// ... so we need to rethrow it outside the execution stack so it behaves like a notmal JS error (i.e. halts and prints to console)
//
// see note below called "CatchRethrowing"
// ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically
setTimeout(function () {
throw e;
});
} // Promise based validation was a fail (i.e reject())
}
});
}
}; // move next via next button
_this4.updateStepValidationFlag(false);
});
} // move behind via previous button
var next = function next() {
abstractStepMoveAllowedToPromise().then(function () {
var proceed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
// validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined,
// ... so make it true. Or else 'proceed' will carry the true/false value from sync validation
updateStepValidationFlag(proceed);
}, {
key: "previous",
value: function previous() {
if (this.state.compState > 0) {
this.setNavState(this.state.compState - 1);
if (proceed) {
adjustNavState(compState + 1);
}
} // update step's validation flag
}).catch(function (e) {
if (e) {
// CatchRethrowing: as we wrap StepMoveAllowed() to resolve as a Promise, the then() is invoked and the next React Component is loaded.
// ... during the render, if there are JS errors thrown (e.g. ReferenceError) it gets swallowed by the Promise library and comes in here (catch)
// ... so we need to rethrow it outside the execution stack so it behaves like a notmal JS error (i.e. halts and prints to console)
//
setTimeout(function () {
throw e;
});
} // Promise based validation was a fail (i.e reject())
}, {
key: "updateStepValidationFlag",
value: function updateStepValidationFlag() {
var val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
this.props.steps[this.state.compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
} // are we allowed to move forward? via the next button or via jumpToStep?
}, {
key: "stepMoveAllowed",
value: function stepMoveAllowed() {
var skipValidationExecution = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var proceed = false;
updateStepValidationFlag(false);
});
}; // move behind via previous button
if (this.props.dontValidate) {
var previous = function previous() {
if (compState > 0) {
adjustNavState(compState - 1);
}
}; // update step's validation flag
var updateStepValidationFlag = function updateStepValidationFlag() {
var val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
props.steps[compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
};
var activeComponentRef = (0, _react.useRef)(null); // are we allowed to move forward? via the next button or via jumpToStep?
var stepMoveAllowed = function stepMoveAllowed() {
var skipValidationExecution = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var proceed = false;
if (props.dontValidate) {
proceed = true;
} else {
if (skipValidationExecution) {
// we are moving backwards in steps, in this case dont validate as it means the user is not commiting to "save"
proceed = true;
} else if (isStepAtIndexHOCValidationBased(compState)) {
// the user is using a higer order component (HOC) for validation (e.g react-validation-mixin), this wraps the StepZilla steps as a HOC,
// so use hocValidationAppliedTo to determine if this step needs the aync validation as per react-validation-mixin interface
proceed = activeComponentRef.current.refs.component.isValidated();
} else if (activeComponentRef.current === null || typeof activeComponentRef.current.isValidated === 'undefined') {
// if its a form component, it should have implemeted a public isValidated class (also pure componenets wont even have refs - i.e. a empty object). If not then continue
proceed = true;
} else {
if (skipValidationExecution) {
// we are moving backwards in steps, in this case dont validate as it means the user is not commiting to "save"
proceed = true;
} else if (this.isStepAtIndexHOCValidationBased(this.state.compState)) {
// the user is using a higer order component (HOC) for validation (e.g react-validation-mixin), this wraps the StepZilla steps as a HOC,
// so use hocValidationAppliedTo to determine if this step needs the aync validation as per react-validation-mixin interface
proceed = this.refs.activeComponent.refs.component.isValidated();
} else if (Object.keys(this.refs).length === 0 || typeof this.refs.activeComponent.isValidated === 'undefined') {
// if its a form component, it should have implemeted a public isValidated class (also pure componenets wont even have refs - i.e. a empty object). If not then continue
proceed = true;
} else {
// user is moving forward in steps, invoke validation as its available
proceed = this.refs.activeComponent.isValidated();
}
// user is moving forward in steps, invoke validation as its available
proceed = activeComponentRef.current.isValidated();
}
return proceed;
}
}, {
key: "isStepAtIndexHOCValidationBased",
value: function isStepAtIndexHOCValidationBased(stepIndex) {
return this.props.hocValidationAppliedTo.length > 0 && this.props.hocValidationAppliedTo.indexOf(stepIndex) > -1;
} // a validation method is each step can be sync or async (Promise based), this utility abstracts the wrapper stepMoveAllowed to be Promise driven regardless of validation return type
}, {
key: "abstractStepMoveAllowedToPromise",
value: function abstractStepMoveAllowedToPromise(movingBack) {
return _promise.default.resolve(this.stepMoveAllowed(movingBack));
} // get the classmame of steps
return proceed;
};
}, {
key: "getClassName",
value: function getClassName(className, i) {
var liClassName = "".concat(className, "-").concat(this.state.navState.styles[i]); // if step ui based navigation is disabled, then dont highlight step
var isStepAtIndexHOCValidationBased = function isStepAtIndexHOCValidationBased(stepIndex) {
return props.hocValidationAppliedTo && props.hocValidationAppliedTo.length > 0 && props.hocValidationAppliedTo.indexOf(stepIndex) > -1;
}; // a validation method is each step can be sync or async (Promise based), this utility abstracts the wrapper stepMoveAllowed to be Promise driven regardless of validation return type
if (!this.props.stepsNavigation) {
var abstractStepMoveAllowedToPromise = function abstractStepMoveAllowedToPromise(movingBack) {
return _promise.default.resolve(stepMoveAllowed(movingBack));
}; // get the classmame of steps
var getClassName = function getClassName(className, i) {
var liClassName = '';
if (navState && navState.styles) {
liClassName = "".concat(className, "-").concat(navState.styles[i]); // if step ui based navigation is disabled, then dont highlight step
if (!props.stepsNavigation) {
liClassName += ' no-hl';
}
}
return liClassName;
} // render the steps as stepsNavigation
return liClassName;
}; // render the steps as stepsNavigation
}, {
key: "renderSteps",
value: function renderSteps() {
var _this5 = this;
return this.props.steps.map(function (s, i) {
return _react.default.createElement("li", {
className: _this5.getClassName('progtrckr', i),
onClick: function onClick(evt) {
_this5.jumpToStep(evt);
},
key: i,
value: i
}, _react.default.createElement("em", null, i + 1), _react.default.createElement("span", null, _this5.props.steps[i].name));
});
} // main render of stepzilla container
var renderSteps = function renderSteps() {
return props.steps.map(function (s, i) {
return _react.default.createElement("li", {
className: getClassName('progtrckr', i),
onClick: function onClick(evt) {
_jumpToStep(evt);
},
key: i,
value: i
}, _react.default.createElement("em", null, i + 1), _react.default.createElement("span", null, props.steps[i].name));
});
};
}, {
key: "render",
value: function render() {
var _this6 = this;
var _getPrevNextBtnLayout = getPrevNextBtnLayout(compState),
nextStepText = _getPrevNextBtnLayout.nextStepText,
showNextBtn = _getPrevNextBtnLayout.showNextBtn,
showPreviousBtn = _getPrevNextBtnLayout.showPreviousBtn; // clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
var props = this.props;
var _this$getPrevNextBtnL = this.getPrevNextBtnLayout(this.state.compState),
nextStepText = _this$getPrevNextBtnL.nextStepText,
showNextBtn = _this$getPrevNextBtnL.showNextBtn,
showPreviousBtn = _this$getPrevNextBtnL.showPreviousBtn; // clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
var cloneExtensions = {
jumpToStep: function jumpToStep(t) {
_jumpToStep(t);
}
};
var componentPointer = null;
var compToRender = null;
if (props.steps[compState]) {
componentPointer = props.steps[compState].component; // S: ref binding -----
// we need to bind a ref to it so we can use the imperitive "isValidated" method when needed to prevent navigation
// ... we only can do this if its a (1) React Class based component or (2) A Hooks based stateful component wrapped in forwardRef
// (1) can only update refs if its a regular React component (not a pure component - i.e. function components with no state), so lets check that
var cloneExtensions = {
jumpToStep: function jumpToStep(t) {
_this6.jumpToStep(t);
}
};
var componentPointer = this.props.steps[this.state.compState].component; // can only update refs if its a regular React component (not a pure component), so lets check that
if (componentPointer instanceof _react.Component || componentPointer.type && componentPointer.type.prototype instanceof _react.Component) {
// unit test deteceted that instanceof Component can be in either of these locations so test both (not sure why this is the case)
cloneExtensions.ref = 'activeComponent';
if (componentPointer instanceof _react.Component || componentPointer.type && componentPointer.type.prototype instanceof _react.Component) {
// unit test deteceted that instanceof Component can be in either of these locations so test both (not sure why this is the case)
cloneExtensions.ref = activeComponentRef;
} else {
// (2) after react hooks was released, functional components can have state and therefore support refs
// ... we do this via forwardRefs. So we need to support this as well
// ... after testing, if both the below types are objects then it's a hooks function component wrapped in forwardRef
if (_typeof(componentPointer) === 'object' && _typeof(componentPointer.type) === 'object') {
cloneExtensions.ref = activeComponentRef;
}
} // E: ref binding -----
var compToRender = _react.default.cloneElement(componentPointer, cloneExtensions);
return _react.default.createElement("div", {
className: "multi-step",
onKeyDown: function onKeyDown(evt) {
_this6.handleKeyDown(evt);
}
}, this.props.showSteps ? _react.default.createElement("ol", {
className: "progtrckr"
}, this.renderSteps()) : _react.default.createElement("span", null), compToRender, _react.default.createElement("div", {
style: this.props.showNavigation ? {} : this.hidden,
className: "footer-buttons"
}, _react.default.createElement("button", {
type: "button",
style: showPreviousBtn ? {} : this.hidden,
className: props.backButtonCls,
onClick: function onClick() {
_this6.previous();
},
id: "prev-button"
}, this.props.backButtonText), _react.default.createElement("button", {
type: "button",
style: showNextBtn ? {} : this.hidden,
className: props.nextButtonCls,
onClick: function onClick() {
_this6.next();
},
id: "next-button"
}, nextStepText)));
compToRender = _react.default.cloneElement(componentPointer, cloneExtensions);
} // main render of stepzilla container
return _react.default.createElement("div", {
className: "multi-step",
onKeyDown: function onKeyDown(evt) {
handleKeyDown(evt);
}
}]);
}, props.showSteps ? _react.default.createElement("ol", {
className: "progtrckr"
}, renderSteps()) : _react.default.createElement("span", null), compToRender, _react.default.createElement("div", {
style: props.showNavigation ? {} : hidden,
className: "footer-buttons"
}, _react.default.createElement("button", {
type: "button",
style: showPreviousBtn ? {} : hidden,
className: props.backButtonCls,
onClick: function onClick() {
previous();
},
id: "prev-button"
}, props.backButtonText), _react.default.createElement("button", {
type: "button",
style: showNextBtn ? {} : hidden,
className: props.nextButtonCls,
onClick: function onClick() {
next();
},
id: "next-button"
}, nextStepText)));
}
return StepZilla;
}(_react.Component);
exports.default = StepZilla;
StepZilla.defaultProps = {

@@ -421,0 +370,0 @@ showSteps: true,

@@ -6,3 +6,3 @@ "use strict";

});
exports.default = void 0;
exports.default = StepZilla;

@@ -21,400 +21,349 @@ var _react = _interopRequireWildcard(require("react"));

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function StepZilla(props) {
var _useState = (0, _react.useState)(props.startAtStep),
_useState2 = _slicedToArray(_useState, 2),
compState = _useState2[0],
setCompState = _useState2[1];
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
var _useState3 = (0, _react.useState)(null),
_useState4 = _slicedToArray(_useState3, 2),
navState = _useState4[0],
setNavState = _useState4[1];
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
var hidden = {
display: 'none'
};
(0, _react.useEffect)(function () {
setNavState(getNavStates(props.startAtStep, props.steps.length));
}, []); // update the header nav states via classes so they can be styled via css
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
var getNavStates = function getNavStates(indx, length) {
var styles = [];
var StepZilla =
/*#__PURE__*/
function (_Component) {
_inherits(StepZilla, _Component);
for (var i = 0; i < length; i++) {
if (i < indx || !props.prevBtnOnLastStep && indx === length - 1) {
styles.push('done');
} else if (i === indx) {
styles.push('doing');
} else {
styles.push('todo');
}
}
function StepZilla(props) {
var _this;
_classCallCheck(this, StepZilla);
_this = _possibleConstructorReturn(this, _getPrototypeOf(StepZilla).call(this, props));
_this.state = {
compState: _this.props.startAtStep,
navState: _this.getNavStates(_this.props.startAtStep, _this.props.steps.length)
return {
styles: styles
};
_this.hidden = {
display: 'none'
}; // if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
};
_this.nextTextOnFinalActionStep = _this.props.nextTextOnFinalActionStep ? _this.props.nextTextOnFinalActionStep : _this.props.nextButtonText;
var getPrevNextBtnLayout = function getPrevNextBtnLayout(currentStep) {
// first set default values
var showPreviousBtn = true;
var showNextBtn = true;
var nextStepText = props.nextButtonText; // first step hide previous btn
_this.applyValidationFlagsToSteps();
if (currentStep === 0) {
showPreviousBtn = false;
} // second to last step change next btn text if supplied as props
return _this;
} // extend the "steps" array with flags to indicate if they have been validated
if (currentStep === props.steps.length - 2) {
// if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
var nextTextOnFinalActionStep = props.nextTextOnFinalActionStep ? props.nextTextOnFinalActionStep : props.nextButtonText;
nextStepText = nextTextOnFinalActionStep || nextStepText;
} // last step hide next btn, hide previous btn if supplied as props
_createClass(StepZilla, [{
key: "applyValidationFlagsToSteps",
value: function applyValidationFlagsToSteps() {
var _this2 = this;
this.props.steps.map(function (i, idx) {
if (_this2.props.dontValidate) {
i.validated = true;
} else {
// check if isValidated was exposed in the step, if yes then set initial state as not validated (false) or vice versa
// if HOCValidation is used for the step then mark it as "requires to be validated. i.e. false"
i.validated = i.component.type && i.component.type.prototype && i.component.type.prototype.isValidated && _this2.isStepAtIndexHOCValidationBased(idx) ? false : true;
}
if (currentStep >= props.steps.length - 1) {
showNextBtn = false;
showPreviousBtn = props.prevBtnOnLastStep === false ? false : true;
}
return i;
});
} // update the header nav states via classes so they can be styled via css
return {
showPreviousBtn: showPreviousBtn,
showNextBtn: showNextBtn,
nextStepText: nextStepText
};
}; // which step are we in?
}, {
key: "getNavStates",
value: function getNavStates(indx, length) {
var styles = [];
for (var i = 0; i < length; i++) {
if (i < indx || !this.props.prevBtnOnLastStep && indx === length - 1) {
styles.push('done');
} else if (i === indx) {
styles.push('doing');
} else {
styles.push('todo');
}
}
return {
current: indx,
styles: styles
};
var checkNavState = function checkNavState(nextStep) {
if (props.onStepChange) {
props.onStepChange(nextStep);
}
}, {
key: "getPrevNextBtnLayout",
value: function getPrevNextBtnLayout(currentStep) {
// first set default values
var showPreviousBtn = true;
var showNextBtn = true;
var nextStepText = this.props.nextButtonText; // first step hide previous btn
}; // set the nav state
if (currentStep === 0) {
showPreviousBtn = false;
} // second to last step change next btn text if supplied as props
var adjustNavState = function adjustNavState(next) {
setNavState(getNavStates(next, props.steps.length));
if (currentStep === this.props.steps.length - 2) {
nextStepText = this.props.nextTextOnFinalActionStep || nextStepText;
} // last step hide next btn, hide previous btn if supplied as props
if (next < props.steps.length) {
setCompState(next);
}
checkNavState(next);
}; // handles keydown on enter being pressed in any Child component input area. in this case it goes to the next (ignore textareas as they should allow line breaks)
if (currentStep >= this.props.steps.length - 1) {
showNextBtn = false;
showPreviousBtn = this.props.prevBtnOnLastStep === false ? false : true;
}
return {
showPreviousBtn: showPreviousBtn,
showNextBtn: showNextBtn,
nextStepText: nextStepText
};
} // which step are we in?
}, {
key: "checkNavState",
value: function checkNavState(nextStep) {
if (this.props.onStepChange) {
this.props.onStepChange(nextStep);
var handleKeyDown = function handleKeyDown(evt) {
if (evt.which === 13) {
if (!props.preventEnterSubmission && evt.target.type !== 'textarea') {
next();
} else if (evt.target.type !== 'textarea') {
evt.preventDefault();
}
} // set the nav state
}
}; // this utility method lets Child components invoke a direct jump to another step
}, {
key: "setNavState",
value: function setNavState(next) {
this.setState({
navState: this.getNavStates(next, this.props.steps.length)
});
if (next < this.props.steps.length) {
this.setState({
compState: next
});
}
var _jumpToStep = function jumpToStep(evt) {
if (typeof evt.target === 'undefined') {
// a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event
adjustNavState(evt);
} else {
// the main navigation step ui is invoking a jump between steps
// if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
if (!props.stepsNavigation || evt.target.value === compState) {
evt.preventDefault();
evt.stopPropagation();
return;
} // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling)
this.checkNavState(next);
} // handles keydown on enter being pressed in any Child component input area. in this case it goes to the next (ignore textareas as they should allow line breaks)
}, {
key: "handleKeyDown",
value: function handleKeyDown(evt) {
if (evt.which === 13) {
if (!this.props.preventEnterSubmission && evt.target.type !== 'textarea') {
this.next();
} else if (evt.target.type !== 'textarea') {
evt.preventDefault();
}
}
} // this utility method lets Child components invoke a direct jump to another step
evt.persist();
var movingBack = evt.target.value < compState; // are we trying to move back or front?
}, {
key: "jumpToStep",
value: function jumpToStep(evt) {
var _this3 = this;
var passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
if (typeof evt.target === 'undefined') {
// a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event
this.setNavState(evt);
} else {
// the main navigation step ui is invoking a jump between steps
// if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
if (!this.props.stepsNavigation || evt.target.value === this.state.compState) {
evt.preventDefault();
evt.stopPropagation();
return;
} // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling)
var proceed = false; // flag on if we should move on
abstractStepMoveAllowedToPromise(movingBack).then(function (valid) {
// validation was a success (promise or sync validation). In it was a Promise's resolve()
// ... then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync
proceed = typeof valid === 'undefined' ? true : valid;
evt.persist();
var movingBack = evt.target.value < this.state.compState; // are we trying to move back or front?
if (!movingBack) {
updateStepValidationFlag(proceed);
}
var passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
var proceed = false; // flag on if we should move on
this.abstractStepMoveAllowedToPromise(movingBack).then(function () {
var valid = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
// validation was a success (promise or sync validation). In it was a Promise's resolve()
// ... then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync
proceed = valid;
if (proceed) {
if (!movingBack) {
_this3.updateStepValidationFlag(proceed);
}
// looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and
// ... 'some' that to get a decision on if we should allow moving forward
passThroughStepsNotValid = props.steps.reduce(function (a, c, i) {
if (i >= compState && i < evt.target.value) {
a.push(c.validated);
}
if (proceed) {
if (!movingBack) {
// looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and
// ... 'some' that to get a decision on if we should allow moving forward
passThroughStepsNotValid = _this3.props.steps.reduce(function (a, c, i) {
if (i >= _this3.state.compState && i < evt.target.value) {
a.push(c.validated);
}
return a;
}, []).some(function (c) {
return c === false;
});
}
}
}).catch(function () {
// Promise based validation was a fail (i.e reject())
if (!movingBack) {
_this3.updateStepValidationFlag(false);
}
}).then(function () {
// this is like finally(), executes if error no no error
if (proceed && !passThroughStepsNotValid) {
if (evt.target.value === _this3.props.steps.length - 1 && _this3.state.compState === _this3.props.steps.length - 1) {
_this3.setNavState(_this3.props.steps.length);
} else {
_this3.setNavState(evt.target.value);
}
}
}).catch(function (e) {
if (e) {
// see note below called "CatchRethrowing"
// ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically
setTimeout(function () {
throw e;
return a;
}, []).some(function (c) {
return c === false;
});
}
});
}
} // move next via next button
}, {
key: "next",
value: function next() {
var _this4 = this;
this.abstractStepMoveAllowedToPromise().then(function () {
var proceed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
// validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined,
// ... so make it true. Or else 'proceed' will carry the true/false value from sync validation
_this4.updateStepValidationFlag(proceed);
if (proceed) {
_this4.setNavState(_this4.state.compState + 1);
}
}).catch(function () {
// Promise based validation was a fail (i.e reject())
if (!movingBack) {
updateStepValidationFlag(false);
}
}).then(function () {
// this is like finally(), executes if error no no error
if (proceed && !passThroughStepsNotValid) {
if (evt.target.value === props.steps.length - 1 && compState === props.steps.length - 1) {
adjustNavState(props.steps.length);
} else {
adjustNavState(evt.target.value);
}
}
}).catch(function (e) {
if (e) {
// CatchRethrowing: as we wrap StepMoveAllowed() to resolve as a Promise, the then() is invoked and the next React Component is loaded.
// ... during the render, if there are JS errors thrown (e.g. ReferenceError) it gets swallowed by the Promise library and comes in here (catch)
// ... so we need to rethrow it outside the execution stack so it behaves like a notmal JS error (i.e. halts and prints to console)
//
// see note below called "CatchRethrowing"
// ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically
setTimeout(function () {
throw e;
});
} // Promise based validation was a fail (i.e reject())
}
});
}
}; // move next via next button
_this4.updateStepValidationFlag(false);
});
} // move behind via previous button
var next = function next() {
abstractStepMoveAllowedToPromise().then(function () {
var proceed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
// validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined,
// ... so make it true. Or else 'proceed' will carry the true/false value from sync validation
updateStepValidationFlag(proceed);
}, {
key: "previous",
value: function previous() {
if (this.state.compState > 0) {
this.setNavState(this.state.compState - 1);
if (proceed) {
adjustNavState(compState + 1);
}
} // update step's validation flag
}).catch(function (e) {
if (e) {
// CatchRethrowing: as we wrap StepMoveAllowed() to resolve as a Promise, the then() is invoked and the next React Component is loaded.
// ... during the render, if there are JS errors thrown (e.g. ReferenceError) it gets swallowed by the Promise library and comes in here (catch)
// ... so we need to rethrow it outside the execution stack so it behaves like a notmal JS error (i.e. halts and prints to console)
//
setTimeout(function () {
throw e;
});
} // Promise based validation was a fail (i.e reject())
}, {
key: "updateStepValidationFlag",
value: function updateStepValidationFlag() {
var val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
this.props.steps[this.state.compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
} // are we allowed to move forward? via the next button or via jumpToStep?
}, {
key: "stepMoveAllowed",
value: function stepMoveAllowed() {
var skipValidationExecution = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var proceed = false;
updateStepValidationFlag(false);
});
}; // move behind via previous button
if (this.props.dontValidate) {
var previous = function previous() {
if (compState > 0) {
adjustNavState(compState - 1);
}
}; // update step's validation flag
var updateStepValidationFlag = function updateStepValidationFlag() {
var val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
props.steps[compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
};
var activeComponentRef = (0, _react.useRef)(null); // are we allowed to move forward? via the next button or via jumpToStep?
var stepMoveAllowed = function stepMoveAllowed() {
var skipValidationExecution = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var proceed = false;
if (props.dontValidate) {
proceed = true;
} else {
if (skipValidationExecution) {
// we are moving backwards in steps, in this case dont validate as it means the user is not commiting to "save"
proceed = true;
} else if (isStepAtIndexHOCValidationBased(compState)) {
// the user is using a higer order component (HOC) for validation (e.g react-validation-mixin), this wraps the StepZilla steps as a HOC,
// so use hocValidationAppliedTo to determine if this step needs the aync validation as per react-validation-mixin interface
proceed = activeComponentRef.current.refs.component.isValidated();
} else if (activeComponentRef.current === null || typeof activeComponentRef.current.isValidated === 'undefined') {
// if its a form component, it should have implemeted a public isValidated class (also pure componenets wont even have refs - i.e. a empty object). If not then continue
proceed = true;
} else {
if (skipValidationExecution) {
// we are moving backwards in steps, in this case dont validate as it means the user is not commiting to "save"
proceed = true;
} else if (this.isStepAtIndexHOCValidationBased(this.state.compState)) {
// the user is using a higer order component (HOC) for validation (e.g react-validation-mixin), this wraps the StepZilla steps as a HOC,
// so use hocValidationAppliedTo to determine if this step needs the aync validation as per react-validation-mixin interface
proceed = this.refs.activeComponent.refs.component.isValidated();
} else if (Object.keys(this.refs).length === 0 || typeof this.refs.activeComponent.isValidated === 'undefined') {
// if its a form component, it should have implemeted a public isValidated class (also pure componenets wont even have refs - i.e. a empty object). If not then continue
proceed = true;
} else {
// user is moving forward in steps, invoke validation as its available
proceed = this.refs.activeComponent.isValidated();
}
// user is moving forward in steps, invoke validation as its available
proceed = activeComponentRef.current.isValidated();
}
return proceed;
}
}, {
key: "isStepAtIndexHOCValidationBased",
value: function isStepAtIndexHOCValidationBased(stepIndex) {
return this.props.hocValidationAppliedTo.length > 0 && this.props.hocValidationAppliedTo.indexOf(stepIndex) > -1;
} // a validation method is each step can be sync or async (Promise based), this utility abstracts the wrapper stepMoveAllowed to be Promise driven regardless of validation return type
}, {
key: "abstractStepMoveAllowedToPromise",
value: function abstractStepMoveAllowedToPromise(movingBack) {
return _promise.default.resolve(this.stepMoveAllowed(movingBack));
} // get the classmame of steps
return proceed;
};
}, {
key: "getClassName",
value: function getClassName(className, i) {
var liClassName = "".concat(className, "-").concat(this.state.navState.styles[i]); // if step ui based navigation is disabled, then dont highlight step
var isStepAtIndexHOCValidationBased = function isStepAtIndexHOCValidationBased(stepIndex) {
return props.hocValidationAppliedTo && props.hocValidationAppliedTo.length > 0 && props.hocValidationAppliedTo.indexOf(stepIndex) > -1;
}; // a validation method is each step can be sync or async (Promise based), this utility abstracts the wrapper stepMoveAllowed to be Promise driven regardless of validation return type
if (!this.props.stepsNavigation) {
var abstractStepMoveAllowedToPromise = function abstractStepMoveAllowedToPromise(movingBack) {
return _promise.default.resolve(stepMoveAllowed(movingBack));
}; // get the classmame of steps
var getClassName = function getClassName(className, i) {
var liClassName = '';
if (navState && navState.styles) {
liClassName = "".concat(className, "-").concat(navState.styles[i]); // if step ui based navigation is disabled, then dont highlight step
if (!props.stepsNavigation) {
liClassName += ' no-hl';
}
}
return liClassName;
} // render the steps as stepsNavigation
return liClassName;
}; // render the steps as stepsNavigation
}, {
key: "renderSteps",
value: function renderSteps() {
var _this5 = this;
return this.props.steps.map(function (s, i) {
return _react.default.createElement("li", {
className: _this5.getClassName('progtrckr', i),
onClick: function onClick(evt) {
_this5.jumpToStep(evt);
},
key: i,
value: i
}, _react.default.createElement("em", null, i + 1), _react.default.createElement("span", null, _this5.props.steps[i].name));
});
} // main render of stepzilla container
var renderSteps = function renderSteps() {
return props.steps.map(function (s, i) {
return _react.default.createElement("li", {
className: getClassName('progtrckr', i),
onClick: function onClick(evt) {
_jumpToStep(evt);
},
key: i,
value: i
}, _react.default.createElement("em", null, i + 1), _react.default.createElement("span", null, props.steps[i].name));
});
};
}, {
key: "render",
value: function render() {
var _this6 = this;
var _getPrevNextBtnLayout = getPrevNextBtnLayout(compState),
nextStepText = _getPrevNextBtnLayout.nextStepText,
showNextBtn = _getPrevNextBtnLayout.showNextBtn,
showPreviousBtn = _getPrevNextBtnLayout.showPreviousBtn; // clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
var props = this.props;
var _this$getPrevNextBtnL = this.getPrevNextBtnLayout(this.state.compState),
nextStepText = _this$getPrevNextBtnL.nextStepText,
showNextBtn = _this$getPrevNextBtnL.showNextBtn,
showPreviousBtn = _this$getPrevNextBtnL.showPreviousBtn; // clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
var cloneExtensions = {
jumpToStep: function jumpToStep(t) {
_jumpToStep(t);
}
};
var componentPointer = null;
var compToRender = null;
if (props.steps[compState]) {
componentPointer = props.steps[compState].component; // S: ref binding -----
// we need to bind a ref to it so we can use the imperitive "isValidated" method when needed to prevent navigation
// ... we only can do this if its a (1) React Class based component or (2) A Hooks based stateful component wrapped in forwardRef
// (1) can only update refs if its a regular React component (not a pure component - i.e. function components with no state), so lets check that
var cloneExtensions = {
jumpToStep: function jumpToStep(t) {
_this6.jumpToStep(t);
}
};
var componentPointer = this.props.steps[this.state.compState].component; // can only update refs if its a regular React component (not a pure component), so lets check that
if (componentPointer instanceof _react.Component || componentPointer.type && componentPointer.type.prototype instanceof _react.Component) {
// unit test deteceted that instanceof Component can be in either of these locations so test both (not sure why this is the case)
cloneExtensions.ref = 'activeComponent';
if (componentPointer instanceof _react.Component || componentPointer.type && componentPointer.type.prototype instanceof _react.Component) {
// unit test deteceted that instanceof Component can be in either of these locations so test both (not sure why this is the case)
cloneExtensions.ref = activeComponentRef;
} else {
// (2) after react hooks was released, functional components can have state and therefore support refs
// ... we do this via forwardRefs. So we need to support this as well
// ... after testing, if both the below types are objects then it's a hooks function component wrapped in forwardRef
if (_typeof(componentPointer) === 'object' && _typeof(componentPointer.type) === 'object') {
cloneExtensions.ref = activeComponentRef;
}
} // E: ref binding -----
var compToRender = _react.default.cloneElement(componentPointer, cloneExtensions);
return _react.default.createElement("div", {
className: "multi-step",
onKeyDown: function onKeyDown(evt) {
_this6.handleKeyDown(evt);
}
}, this.props.showSteps ? _react.default.createElement("ol", {
className: "progtrckr"
}, this.renderSteps()) : _react.default.createElement("span", null), compToRender, _react.default.createElement("div", {
style: this.props.showNavigation ? {} : this.hidden,
className: "footer-buttons"
}, _react.default.createElement("button", {
type: "button",
style: showPreviousBtn ? {} : this.hidden,
className: props.backButtonCls,
onClick: function onClick() {
_this6.previous();
},
id: "prev-button"
}, this.props.backButtonText), _react.default.createElement("button", {
type: "button",
style: showNextBtn ? {} : this.hidden,
className: props.nextButtonCls,
onClick: function onClick() {
_this6.next();
},
id: "next-button"
}, nextStepText)));
compToRender = _react.default.cloneElement(componentPointer, cloneExtensions);
} // main render of stepzilla container
return _react.default.createElement("div", {
className: "multi-step",
onKeyDown: function onKeyDown(evt) {
handleKeyDown(evt);
}
}]);
}, props.showSteps ? _react.default.createElement("ol", {
className: "progtrckr"
}, renderSteps()) : _react.default.createElement("span", null), compToRender, _react.default.createElement("div", {
style: props.showNavigation ? {} : hidden,
className: "footer-buttons"
}, _react.default.createElement("button", {
type: "button",
style: showPreviousBtn ? {} : hidden,
className: props.backButtonCls,
onClick: function onClick() {
previous();
},
id: "prev-button"
}, props.backButtonText), _react.default.createElement("button", {
type: "button",
style: showNextBtn ? {} : hidden,
className: props.nextButtonCls,
onClick: function onClick() {
next();
},
id: "next-button"
}, nextStepText)));
}
return StepZilla;
}(_react.Component);
exports.default = StepZilla;
StepZilla.defaultProps = {

@@ -421,0 +370,0 @@ showSteps: true,

{
"name": "react-stepzilla",
"version": "6.0.2",
"version": "7.0.0",
"description": "A react multi-step, wizard component for managing data collection via forms and sub components",
"main": "./dist/main.js",
"engines": {
"node": ">=8.0.0"
"node": ">=12.0.0"
},

@@ -16,2 +16,3 @@ "scripts": {

"example": "webpack-dev-server --open",
"start": "npm run example",
"build-example": "node ./node_modules/gulp/bin/gulp.js build-example"

@@ -42,2 +43,3 @@ },

"@babel/register": "^7.0.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.2",
"babel-eslint": "^10.0.1",

@@ -48,4 +50,3 @@ "babel-loader": "^8.0.5",

"del": "^2.2.2",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.8.0",
"enzyme": "^3.11.0",
"eslint": "^5.12.1",

@@ -70,4 +71,4 @@ "eslint-config-airbnb": "^17.1.0",

"prop-types": "^15.5.8",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^8.1.0",

@@ -86,6 +87,5 @@ "react-redux": "^6.0.0",

"peerDependencies": {
"react": "^16.4.1",
"react-dom": "^16.4.1"
},
"dependencies": {}
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}

@@ -6,2 +6,3 @@ # react stepzilla [![npm version](https://badge.fury.io/js/react-stepzilla.svg)](https://badge.fury.io/js/react-stepzilla)

```
v7.0.0: React hooks support! (finally)
v6.0.0: dev tools updated to latest versions for security and stability (webpack, gulp, babel, node env)

@@ -11,6 +12,6 @@ v5.0.0: ported to react and react-dom 16.4.1. Redux demo implementation (finally!)

v4.7.2: optimised react, react-dom dependency loading (peerDependencies)
v4.3.0: now supporting higer order component based validation via react-validation-mixin!
v4.3.0: now supporting higher order component based validation via react-validation-mixin!
```
### what does it do?
### what can it do?
something like this of course:

@@ -27,3 +28,3 @@

### get started
## get started (how to use it in your apps)
- run

@@ -49,3 +50,3 @@ ```

```
*as of v4.2.0 you can also use Pure Components but they wont support validation, see Step2.js in the examples directory for more info.*
*as of v7.0.0 you can use React Hooks based function components that also support custom state based validation using the `isValidated` method (see Step5.js in the examples directory). Note that Pure Components (functions without state or refs) can also be used but they wont support validation, see Step2.js in the examples directory for more info.*

@@ -113,12 +114,3 @@ - and now render it out somewhere in your app

- *if one of your components is a form that requires validation before moving to the next component, then that component needs to implement a `isValidated()` public method which validates the form and returns true/false if the data is valid. For an e.g. on this have a look at the `src/examples/Step3` component.*
- *validation can also be Async and therefore Promise based. This is useful if you do server side validation or you want to save data to a server and only proceed if it was a success. For an e.g. on this have a look at the `src/examples/Step5` component.*
- *also if you want some default style, copy the source from `src/css/main.css` code into your project (the above look in the picture also requires bootstrap)*
- *check out `src/examples/` for how `onStepChange` can be used to persist last known step state across browser reloads (using `startAtStep` pulled from session storage)*
#### jumpToStep() utility
### jumpToStep() utility
- stepzilla injects an utility method called `jumpToStep` as a prop into all your react step components

@@ -131,10 +123,19 @@ - this utility methods lets you jump between steps from inside your react component

#### step validation
- your individual react steps can implement some checks to prevent moving onto the next step, this is called "validation" in stepzilla.
- to use this feature, you need to implement a public `isValidated()` method in your react step component.
- this `isValidated()` method can return a static `true/false` (true to proceed and false to prevent). It can also return a `Promise` which in turn should `resolve` or `reject` (which maps to the static `true/false` behaviour)
- stepzilla also supports advanced form validation via `react-validation-mixin`
- check out sample code in the `src/examples` directory. (Step3.js and Step4.js show you all this in action)
### dev
### step validation & the isValidated() utility
each component step can expose a local `isValidated` method that will be invoked during runtime by StepZilla to determine if we can go to next step. This utility is available to Class based component and Hooks components.
- to use this feature, you need to implement a `isValidated()` method in your react step component.
- this `isValidated()` method should return a bool `true/false` (true to proceed and false to prevent). It can also return a `Promise` which in turn should `resolve` or `reject` (which maps to the static `true/false` behaviour)
- if your step is a from, note that stepzilla also supports advanced form validation via `react-validation-mixin`
- validation can also be Async and therefore Promise based. This is useful if you do server side validation or you want to save data to a server and only proceed if it was a success. For an e.g. on this have a look at the `src/examples/Step5` component.
- for class components, check out sample code in the `src/examples` directory. (Step3.js and Step4.js show you all this in action)
- for hooks components, you will need to use the `forwardRef` and the `useImperativeHandle` primitives to make this work, a full example is in `src/examples/Step5.js`
### styling & custom step change logic
- if you want some default style, copy the source from `src/css/main.css` code into your project (the above look in the picture also requires bootstrap)
- check out `src/examples/` for how `onStepChange` can be used to persist last known step state across browser reloads (using `startAtStep` pulled from session storage)
## dev (upgrade core library)
- all node source is in src/main.js

@@ -156,3 +157,3 @@ - you need to install dependencies first `npm install`

- run `npm install`
- then run `npm run example`
- then run `npm start`
- then go to `http://localhost:8080/webpack-dev-server/src/examples/index.html` in your browser

@@ -183,4 +184,4 @@ - hot reload will work as you dev

### dev todo
- ~~write the tests~~
- improve code coverage
- migrate to jest

@@ -187,0 +188,0 @@ ### community dev tips

@@ -11,2 +11,3 @@ 'use strict';

import Step6 from './Step6';
import Step7 from './Step7';

@@ -50,3 +51,4 @@ import '../css/main.css';

{name: 'Step5', component: <Step5 getStore={() => (this.getStore())} updateStore={(u) => {this.updateStore(u)}} />},
{name: 'Step6', component: <Step6 getStore={() => (this.getStore())} updateStore={(u) => {this.updateStore(u)}} />}
{name: 'Step6', component: <Step6 getStore={() => (this.getStore())} updateStore={(u) => {this.updateStore(u)}} />},
{name: 'Step7', component: <Step7 getStore={() => (this.getStore())} updateStore={(u) => {this.updateStore(u)}} />}
]

@@ -53,0 +55,0 @@

@@ -1,112 +0,56 @@

'use strict';
import React, {useState, forwardRef, useImperativeHandle} from 'react';
import React, { Component } from 'react';
import Promise from 'promise';
export default class Step5 extends Component {
constructor(props) {
super(props);
this.state = {
saving: false
};
this.isValidated = this.isValidated.bind(this);
// We need to wrap component in `forwardRef` in order to gain
// access to the ref object that is assigned using the `ref` prop.
// This ref is passed as the second parameter to the function component.
const HooksWithValidation = forwardRef(({jumpToStep}, ref) => {
const [valid, setValid] = useState(false);
const [uiError, setUiError] = useState(false);
const toggleValidState = () => {
setUiError(false);
setValid(!valid);
}
componentDidMount() {}
// The component instance will be extended
// with whatever you return from the callback passed
// as the second argument
useImperativeHandle(ref, () => ({
isValidated() {
if (!valid) {
setUiError(true);
return false;
} else {
// all good, let's proceed
return true;
}
}
}));
componentWillUnmount() {}
// This review screen had the 'Save' button, on clicking this is called
isValidated() {
/*
typically this method needs to return true or false (to indicate if the local forms are validated, so StepZilla can move to the next step),
but in this example we simulate an ajax request which is async. In the case of async validation or server saving etc. return a Promise and StepZilla will wait
... for the resolve() to work out if we can move to the next step
So here are the rules:
~~~~~~~~~~~~~~~~~~~~~~~~
SYNC action (e.g. local JS form validation).. if you return:
true/undefined: validation has passed. Move to next step.
false: validation failed. Stay on current step
~~~~~~~~~~~~~~~~~~~~~~~~
ASYNC return (server side validation or saving data to server etc).. you need to return a Promise which can resolve like so:
resolve(): validation/save has passed. Move to next step.
reject(): validation/save failed. Stay on current step
*/
this.setState({
saving: true
});
return new Promise((resolve, reject) => {
setTimeout(() => {
this.setState({
saving: true
});
this.props.updateStore({savedToCloud: true}); // Update store here (this is just an example, in reality you will do it via redux or flux)
// call resolve() to indicate that server validation or other aync method was a success.
// ... only then will it move to the next step. reject() will indicate a fail
resolve();
// reject(); // or reject
}, 5000);
});
}
jumpToStep(toStep) {
// We can explicitly move to a step (we -1 as its a zero based index)
this.props.jumpToStep(toStep-1); // The StepZilla library injects this jumpToStep utility into each component
}
render() {
const savingCls = this.state.saving ? 'saving col-md-12 show' : 'saving col-md-12 hide';
return (
<div className="step step5 review">
<div className="row">
<form id="Form" className="form-horizontal">
<div className="form-group">
<label className="col-md-12 control-label">
<h1>Step 4: Review your Details and 'Save'</h1>
</label>
return <div className="step step5">
<div className="row">
<form id="Form" className="form-horizontal">
<div className="form-group">
<label className="col-md-12 control-label">
<h1>Step 5: A React Hooks Component Example (with state based validation)</h1>
</label>
<div className="row content">
<div className="col-md-12">
<br />
<span>My validation state is {valid.toString().toUpperCase()}.</span>
{!valid && <><br /><br /><span className="red">If you want to move to the "Next" step you need change the validation state to TRUE. This demonstrates how a modern Hooks based React component can use the <u>isValidated</u> method to instruct StepZilla to proceed to "Next". Use the button below to toggle it to TRUE.</span></>}
<br /><br />
<div className="btn btn-info" onClick={toggleValidState} style={{marginLeft: '0'}}>Toggle Validation State</div>
{uiError && <div className="val-err-tooltip" style={{marginTop: '1rem', display: 'table'}}>You need to use the the toggle button above to set validation state to TRUE to proceed</div>}
</div>
<div className="form-group">
<div className="col-md-12 control-label">
<div className="col-md-12 txt">
<div className="col-md-4">
Gender
</div>
<div className="col-md-4">
{this.props.getStore().gender}
</div>
</div>
<div className="col-md-12 txt">
<div className="col-md-4">
Email
</div>
<div className="col-md-4">
{this.props.getStore().email}
</div>
</div>
<div className="col-md-12 txt">
<div className="col-md-4">
Emergency Email
</div>
<div className="col-md-4">
{this.props.getStore().emailEmergency}
</div>
</div>
<div className="col-md-12 eg-jump-lnk">
<a href="#" onClick={() => this.jumpToStep(1)}>e.g. showing how we use the jumpToStep method helper method to jump back to step 1</a>
</div>
<h2 className={savingCls}>Saving to Cloud, pls wait (by the way, we are using a Promise to do this :)...</h2>
</div>
<div className="col-md-12 eg-jump-lnk">
<a href="#" onClick={() => jumpToStep(0)}>e.g. showing how we use the jumpToStep method helper method to jump back to step 1</a>
</div>
</form>
</div>
</div>
</div>
)
}
}
</form>
</div>
</div>
});
export default HooksWithValidation;
'use strict';
import React, { Component } from 'react';
import Promise from 'promise';
export default class Step6 extends Component {
export default class Step5 extends Component {
constructor(props) {

@@ -10,4 +11,6 @@ super(props);

this.state = {
savedToCloud: props.getStore().savedToCloud
saving: false
};
this.isValidated = this.isValidated.bind(this);
}

@@ -19,8 +22,49 @@

// not required as this component has no forms or user entry
// isValidated() {}
// This review screen had the 'Save' button, on clicking this is called
isValidated() {
/*
typically this method needs to return true or false (to indicate if the local forms are validated, so StepZilla can move to the next step),
but in this example we simulate an ajax request which is async. In the case of async validation or server saving etc. return a Promise and StepZilla will wait
... for the resolve() to work out if we can move to the next step
So here are the rules:
~~~~~~~~~~~~~~~~~~~~~~~~
SYNC action (e.g. local JS form validation).. if you return:
true/undefined: validation has passed. Move to next step.
false: validation failed. Stay on current step
~~~~~~~~~~~~~~~~~~~~~~~~
ASYNC return (server side validation or saving data to server etc).. you need to return a Promise which can resolve like so:
resolve(): validation/save has passed. Move to next step.
reject(): validation/save failed. Stay on current step
*/
this.setState({
saving: true
});
return new Promise((resolve, reject) => {
setTimeout(() => {
this.setState({
saving: true
});
this.props.updateStore({savedToCloud: true}); // Update store here (this is just an example, in reality you will do it via redux or flux)
// call resolve() to indicate that server validation or other aync method was a success.
// ... only then will it move to the next step. reject() will indicate a fail
resolve();
// reject(); // or reject
}, 5000);
});
}
jumpToStep(toStep) {
// We can explicitly move to a step (we -1 as its a zero based index)
this.props.jumpToStep(toStep-1); // The StepZilla library injects this jumpToStep utility into each component
}
render() {
const savingCls = this.state.saving ? 'saving col-md-12 show' : 'saving col-md-12 hide';
return (
<div className="step step6">
<div className="step step6 review">
<div className="row">

@@ -30,14 +74,37 @@ <form id="Form" className="form-horizontal">

<label className="col-md-12 control-label">
{
(this.state.savedToCloud)
?
<div>
<h1>Thanks!</h1>
<h2>Data was successfully saved to cloud...</h2>
</div>
:
<h1>You have updated data, go <a onClick={() => {this.props.jumpToStep(4)}}>back</a> and Save again!</h1>
}
<h1>Step 4: Review your Details and 'Save'</h1>
</label>
</div>
<div className="form-group">
<div className="col-md-12 control-label">
<div className="col-md-12 txt">
<div className="col-md-4">
Gender
</div>
<div className="col-md-4">
{this.props.getStore().gender}
</div>
</div>
<div className="col-md-12 txt">
<div className="col-md-4">
Email
</div>
<div className="col-md-4">
{this.props.getStore().email}
</div>
</div>
<div className="col-md-12 txt">
<div className="col-md-4">
Emergency Email
</div>
<div className="col-md-4">
{this.props.getStore().emailEmergency}
</div>
</div>
<div className="col-md-12 eg-jump-lnk">
<a href="#" onClick={() => this.jumpToStep(1)}>e.g. showing how we use the jumpToStep method helper method to jump back to step 1</a>
</div>
<h2 className={savingCls}>Saving to Cloud, pls wait (by the way, we are using a Promise to do this :)...</h2>
</div>
</div>
</form>

@@ -44,0 +111,0 @@ </div>

@@ -1,45 +0,25 @@

import React, { Component } from 'react';
import React, {
Component, useEffect, useState, useRef
} from 'react';
import PropTypes from 'prop-types';
import Promise from 'promise';
export default class StepZilla extends Component {
constructor(props) {
super(props);
export default function StepZilla(props) {
const [compState, setCompState] = useState(props.startAtStep);
const [navState, setNavState] = useState(null);
this.state = {
compState: this.props.startAtStep,
navState: this.getNavStates(this.props.startAtStep, this.props.steps.length)
};
const hidden = {
display: 'none'
};
this.hidden = {
display: 'none'
};
useEffect(() => {
setNavState(getNavStates(props.startAtStep, props.steps.length));
}, []);
// if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
this.nextTextOnFinalActionStep = (this.props.nextTextOnFinalActionStep) ? this.props.nextTextOnFinalActionStep : this.props.nextButtonText;
this.applyValidationFlagsToSteps();
}
// extend the "steps" array with flags to indicate if they have been validated
applyValidationFlagsToSteps() {
this.props.steps.map((i, idx) => {
if (this.props.dontValidate) {
i.validated = true;
} else {
// check if isValidated was exposed in the step, if yes then set initial state as not validated (false) or vice versa
// if HOCValidation is used for the step then mark it as "requires to be validated. i.e. false"
i.validated = i.component.type && i.component.type.prototype && i.component.type.prototype.isValidated && this.isStepAtIndexHOCValidationBased(idx) ? false : true;
}
return i;
});
}
// update the header nav states via classes so they can be styled via css
getNavStates(indx, length) {
const getNavStates = (indx, length) => {
const styles = [];
for (let i = 0; i < length; i++) {
if (i < indx || (!this.props.prevBtnOnLastStep && (indx === length - 1))) {
if (i < indx || (!props.prevBtnOnLastStep && (indx === length - 1))) {
styles.push('done');

@@ -53,10 +33,10 @@ } else if (i === indx) {

return { current: indx, styles };
}
return { styles };
};
getPrevNextBtnLayout(currentStep) {
const getPrevNextBtnLayout = (currentStep) => {
// first set default values
let showPreviousBtn = true;
let showNextBtn = true;
let nextStepText = this.props.nextButtonText;
let nextStepText = props.nextButtonText;

@@ -69,10 +49,12 @@ // first step hide previous btn

// second to last step change next btn text if supplied as props
if (currentStep === this.props.steps.length - 2) {
nextStepText = this.props.nextTextOnFinalActionStep || nextStepText;
if (currentStep === props.steps.length - 2) {
// if user did not give a custom nextTextOnFinalActionStep, the nextButtonText becomes the default
const nextTextOnFinalActionStep = (props.nextTextOnFinalActionStep) ? props.nextTextOnFinalActionStep : props.nextButtonText;
nextStepText = nextTextOnFinalActionStep || nextStepText;
}
// last step hide next btn, hide previous btn if supplied as props
if (currentStep >= this.props.steps.length - 1) {
if (currentStep >= props.steps.length - 1) {
showNextBtn = false;
showPreviousBtn = this.props.prevBtnOnLastStep === false ? false : true;
showPreviousBtn = props.prevBtnOnLastStep === false ? false : true;
}

@@ -85,27 +67,27 @@

};
}
};
// which step are we in?
checkNavState(nextStep) {
if (this.props.onStepChange) {
this.props.onStepChange(nextStep);
const checkNavState = (nextStep) => {
if (props.onStepChange) {
props.onStepChange(nextStep);
}
}
};
// set the nav state
setNavState(next) {
this.setState({ navState: this.getNavStates(next, this.props.steps.length) });
const adjustNavState = (next) => {
setNavState(getNavStates(next, props.steps.length));
if (next < this.props.steps.length) {
this.setState({ compState: next });
if (next < props.steps.length) {
setCompState(next);
}
this.checkNavState(next);
}
checkNavState(next);
};
// handles keydown on enter being pressed in any Child component input area. in this case it goes to the next (ignore textareas as they should allow line breaks)
handleKeyDown(evt) {
const handleKeyDown = (evt) => {
if (evt.which === 13) {
if (!this.props.preventEnterSubmission && evt.target.type !== 'textarea') {
this.next();
if (!props.preventEnterSubmission && evt.target.type !== 'textarea') {
next();
} else if (evt.target.type !== 'textarea') {

@@ -115,12 +97,12 @@ evt.preventDefault();

}
}
};
// this utility method lets Child components invoke a direct jump to another step
jumpToStep(evt) {
const jumpToStep = (evt) => {
if (typeof evt.target === 'undefined') {
// a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event
this.setNavState(evt);
adjustNavState(evt);
} else { // the main navigation step ui is invoking a jump between steps
// if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore
if (!this.props.stepsNavigation || evt.target.value === this.state.compState) {
if (!props.stepsNavigation || evt.target.value === compState) {
evt.preventDefault();

@@ -135,14 +117,14 @@ evt.stopPropagation();

const movingBack = evt.target.value < this.state.compState; // are we trying to move back or front?
const movingBack = evt.target.value < compState; // are we trying to move back or front?
let passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic...
let proceed = false; // flag on if we should move on
this.abstractStepMoveAllowedToPromise(movingBack)
.then((valid = true) => {
abstractStepMoveAllowedToPromise(movingBack)
.then((valid) => {
// validation was a success (promise or sync validation). In it was a Promise's resolve()
// ... then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync
proceed = valid;
proceed = typeof valid === 'undefined' ? true : valid;
if (!movingBack) {
this.updateStepValidationFlag(proceed);
updateStepValidationFlag(proceed);
}

@@ -154,5 +136,5 @@

// ... 'some' that to get a decision on if we should allow moving forward
passThroughStepsNotValid = this.props.steps
passThroughStepsNotValid = props.steps
.reduce((a, c, i) => {
if (i >= this.state.compState && i < evt.target.value) {
if (i >= compState && i < evt.target.value) {
a.push(c.validated);

@@ -171,3 +153,3 @@ }

if (!movingBack) {
this.updateStepValidationFlag(false);
updateStepValidationFlag(false);
}

@@ -178,7 +160,7 @@ })

if (proceed && !passThroughStepsNotValid) {
if (evt.target.value === (this.props.steps.length - 1)
&& this.state.compState === (this.props.steps.length - 1)) {
this.setNavState(this.props.steps.length);
if (evt.target.value === (props.steps.length - 1)
&& compState === (props.steps.length - 1)) {
adjustNavState(props.steps.length);
} else {
this.setNavState(evt.target.value);
adjustNavState(evt.target.value);
}

@@ -195,14 +177,14 @@ }

}
}
};
// move next via next button
next() {
this.abstractStepMoveAllowedToPromise()
const next = () => {
abstractStepMoveAllowedToPromise()
.then((proceed = true) => {
// validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined,
// ... so make it true. Or else 'proceed' will carry the true/false value from sync validation
this.updateStepValidationFlag(proceed);
updateStepValidationFlag(proceed);
if (proceed) {
this.setNavState(this.state.compState + 1);
adjustNavState(compState + 1);
}

@@ -220,23 +202,25 @@ })

// Promise based validation was a fail (i.e reject())
this.updateStepValidationFlag(false);
updateStepValidationFlag(false);
});
}
};
// move behind via previous button
previous() {
if (this.state.compState > 0) {
this.setNavState(this.state.compState - 1);
const previous = () => {
if (compState > 0) {
adjustNavState(compState - 1);
}
}
};
// update step's validation flag
updateStepValidationFlag(val = true) {
this.props.steps[this.state.compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
}
const updateStepValidationFlag = (val = true) => {
props.steps[compState].validated = val; // note: if a step component returns 'underfined' then treat as "true".
};
const activeComponentRef = useRef(null);
// are we allowed to move forward? via the next button or via jumpToStep?
stepMoveAllowed(skipValidationExecution = false) {
const stepMoveAllowed = (skipValidationExecution = false) => {
let proceed = false;
if (this.props.dontValidate) {
if (props.dontValidate) {
proceed = true;

@@ -247,7 +231,7 @@ } else {

proceed = true;
} else if (this.isStepAtIndexHOCValidationBased(this.state.compState)) {
} else if (isStepAtIndexHOCValidationBased(compState)) {
// the user is using a higer order component (HOC) for validation (e.g react-validation-mixin), this wraps the StepZilla steps as a HOC,
// so use hocValidationAppliedTo to determine if this step needs the aync validation as per react-validation-mixin interface
proceed = this.refs.activeComponent.refs.component.isValidated();
} else if (Object.keys(this.refs).length === 0 || typeof this.refs.activeComponent.isValidated === 'undefined') {
proceed = activeComponentRef.current.refs.component.isValidated();
} else if (activeComponentRef.current === null || typeof activeComponentRef.current.isValidated === 'undefined') {
// if its a form component, it should have implemeted a public isValidated class (also pure componenets wont even have refs - i.e. a empty object). If not then continue

@@ -257,3 +241,3 @@ proceed = true;

// user is moving forward in steps, invoke validation as its available
proceed = this.refs.activeComponent.isValidated();
proceed = activeComponentRef.current.isValidated();
}

@@ -263,92 +247,110 @@ }

return proceed;
}
};
isStepAtIndexHOCValidationBased(stepIndex) {
return (this.props.hocValidationAppliedTo.length > 0 && this.props.hocValidationAppliedTo.indexOf(stepIndex) > -1);
}
const isStepAtIndexHOCValidationBased = (stepIndex) => {
return (props.hocValidationAppliedTo && props.hocValidationAppliedTo.length > 0 && props.hocValidationAppliedTo.indexOf(stepIndex) > -1);
};
// a validation method is each step can be sync or async (Promise based), this utility abstracts the wrapper stepMoveAllowed to be Promise driven regardless of validation return type
abstractStepMoveAllowedToPromise(movingBack) {
return Promise.resolve(this.stepMoveAllowed(movingBack));
}
const abstractStepMoveAllowedToPromise = (movingBack) => {
return Promise.resolve(stepMoveAllowed(movingBack));
};
// get the classmame of steps
getClassName(className, i) {
let liClassName = `${className}-${this.state.navState.styles[i]}`;
const getClassName = (className, i) => {
let liClassName = '';
// if step ui based navigation is disabled, then dont highlight step
if (!this.props.stepsNavigation) {
liClassName += ' no-hl';
if (navState && navState.styles) {
liClassName = `${className}-${navState.styles[i]}`;
// if step ui based navigation is disabled, then dont highlight step
if (!props.stepsNavigation) {
liClassName += ' no-hl';
}
}
return liClassName;
}
};
// render the steps as stepsNavigation
renderSteps() {
return this.props.steps.map((s, i) => (
<li className={this.getClassName('progtrckr', i)} onClick={(evt) => { this.jumpToStep(evt); }} key={i} value={i}>
const renderSteps = () => {
return props.steps.map((s, i) => (
<li className={getClassName('progtrckr', i)} onClick={(evt) => { jumpToStep(evt); }} key={i} value={i}>
<em>{i + 1}</em>
<span>{this.props.steps[i].name}</span>
<span>{props.steps[i].name}</span>
</li>
));
}
};
// main render of stepzilla container
render() {
const { props } = this;
const { nextStepText, showNextBtn, showPreviousBtn } = this.getPrevNextBtnLayout(this.state.compState);
const { nextStepText, showNextBtn, showPreviousBtn } = getPrevNextBtnLayout(compState);
// clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
const cloneExtensions = {
jumpToStep: (t) => {
this.jumpToStep(t);
}
};
// clone the step component dynamically and tag it as activeComponent so we can validate it on next. also bind the jumpToStep piping method
const cloneExtensions = {
jumpToStep: (t) => {
jumpToStep(t);
}
};
const componentPointer = this.props.steps[this.state.compState].component;
let componentPointer = null;
let compToRender = null;
// can only update refs if its a regular React component (not a pure component), so lets check that
if (props.steps[compState]) {
componentPointer = props.steps[compState].component;
// S: ref binding -----
// we need to bind a ref to it so we can use the imperitive "isValidated" method when needed to prevent navigation
// ... we only can do this if its a (1) React Class based component or (2) A Hooks based stateful component wrapped in forwardRef
// (1) can only update refs if its a regular React component (not a pure component - i.e. function components with no state), so lets check that
if (componentPointer instanceof Component
|| (componentPointer.type && componentPointer.type.prototype instanceof Component)) {
// unit test deteceted that instanceof Component can be in either of these locations so test both (not sure why this is the case)
cloneExtensions.ref = 'activeComponent';
cloneExtensions.ref = activeComponentRef;
} else {
// (2) after react hooks was released, functional components can have state and therefore support refs
// ... we do this via forwardRefs. So we need to support this as well
// ... after testing, if both the below types are objects then it's a hooks function component wrapped in forwardRef
if (typeof componentPointer === 'object' && typeof componentPointer.type === 'object') {
cloneExtensions.ref = activeComponentRef;
}
}
// E: ref binding -----
const compToRender = React.cloneElement(componentPointer, cloneExtensions);
compToRender = React.cloneElement(componentPointer, cloneExtensions);
}
return (
<div className="multi-step" onKeyDown={(evt) => { this.handleKeyDown(evt); }}>
{
this.props.showSteps
? <ol className="progtrckr">
{this.renderSteps()}
</ol>
: <span></span>
}
// main render of stepzilla container
return (
<div className="multi-step" onKeyDown={(evt) => { handleKeyDown(evt); }}>
{
props.showSteps
? <ol className="progtrckr">
{renderSteps()}
</ol>
: <span></span>
}
{compToRender}
<div style={this.props.showNavigation ? {} : this.hidden} className="footer-buttons">
<button
type="button"
style={showPreviousBtn ? {} : this.hidden}
className={props.backButtonCls}
onClick={() => { this.previous(); }}
id="prev-button"
>
{this.props.backButtonText}
</button>
<button
type="button"
style={showNextBtn ? {} : this.hidden}
className={props.nextButtonCls}
onClick={() => { this.next(); }}
id="next-button"
>
{nextStepText}
</button>
</div>
{compToRender}
<div style={props.showNavigation ? {} : hidden} className="footer-buttons">
<button
type="button"
style={showPreviousBtn ? {} : hidden}
className={props.backButtonCls}
onClick={() => { previous(); }}
id="prev-button"
>
{props.backButtonText}
</button>
<button
type="button"
style={showNextBtn ? {} : hidden}
className={props.nextButtonCls}
onClick={() => { next(); }}
id="next-button"
>
{nextStepText}
</button>
</div>
);
}
</div>
);
}

@@ -355,0 +357,0 @@

@@ -52,2 +52,6 @@ import React from 'react';

// console.log('------------------');
// console.log(enzymeWrapper.debug());
// console.log('------------------');
return {

@@ -87,3 +91,2 @@ props,

describe('default props based render', () => {

@@ -98,5 +101,7 @@ describe('showSteps: true use case', () => {

/*
// CAN'T TEST YET AS ENZYME SHALLOW DOES NOT SUPPORT HOOKS YET - https://stackoverflow.com/a/59174911/849552
describe('stepsNavigation: true use case', () => {
const { enzymeWrapper } = setup(3);
it('should render steps classes in header with correct classes to indicate navigation is working', () => {

@@ -128,2 +133,3 @@ expect(enzymeWrapper.find('.progtrckr').childAt(0).hasClass('progtrckr-doing')).to.be.true;

});
*/

@@ -268,2 +274,4 @@ describe('showNavigation: true use case', () => {

/*
// CAN'T TEST YET AS ENZYME SHALLOW DOES NOT SUPPORT HOOKS YET - https://stackoverflow.com/a/59174911/849552
describe('startAtStep: 0 use case', () => {

@@ -276,2 +284,4 @@ const { enzymeWrapper } = setup(3);

});
*/
}); // end - default props based render group

@@ -292,2 +302,4 @@

/*
// CAN'T TEST YET AS ENZYME SHALLOW DOES NOT SUPPORT HOOKS YET - https://stackoverflow.com/a/59174911/849552
describe('stepsNavigation: false use case', () => {

@@ -319,2 +331,3 @@ const { enzymeWrapper } = setup(3, {

});
*/

@@ -400,2 +413,4 @@ describe('showNavigation: false use case', () => {

/*
// CAN'T TEST YET AS ENZYME SHALLOW DOES NOT SUPPORT HOOKS YET - https://stackoverflow.com/a/59174911/849552
describe('startAtStep: 2 use case', () => {

@@ -410,2 +425,3 @@ const { enzymeWrapper } = setup(3, {

});
*/

@@ -412,0 +428,0 @@ describe('onStepChange: not null use case', () => {

// init enzyme 3
const Enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');
const Adapter = require('@wojtekmaj/enzyme-adapter-react-17');

@@ -5,0 +5,0 @@ Enzyme.configure({ adapter: new Adapter() });

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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