react-stepzilla
Advanced tools
Comparing version 6.0.2 to 7.0.0
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, |
597
docs/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, |
{ | ||
"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"> | ||
</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"> | ||
</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> |
314
src/main.js
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
45
191
3127192
14915