UNPKG

informed

Version:

A lightweight framework and utility for building powerful forms in React applications

383 lines (350 loc) 13.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js'); var React = require('react'); var Context = require('../Context.js'); var useFormApi = require('./useFormApi.js'); var useFormController = require('./useFormController.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var useMultistep = function useMultistep(_ref) { var initialStep = _ref.initialStep, multistepApiRef = _ref.multistepApiRef; // Get the formApi var _useFormController = useFormController.useFormController(), validate = _useFormController.validate, asyncValidate = _useFormController.asyncValidate, getFormState = _useFormController.getFormState, getFieldState = _useFormController.getFieldState, emitter = _useFormController.emitter; var formApi = useFormApi.useFormApi(); // Get scope for relevance var scope = React.useContext(Context.ScopeContext); // Track number of steps var nSteps = React.useRef(0); // Track current step var currentStep = React.useRef(); // Track array of steps var _useState = React.useState(function () { return []; }), _useState2 = _rollupPluginBabelHelpers.slicedToArray(_useState, 1), steps = _useState2[0]; // Track our steps by name var _useState3 = React.useState(function () { return new Map(); }), _useState4 = _rollupPluginBabelHelpers.slicedToArray(_useState3, 1), stepsMap = _useState4[0]; // Form state will be used to trigger rerenders var _useState5 = React.useState({ steps: [], goal: null }), _useState6 = _rollupPluginBabelHelpers.slicedToArray(_useState5, 2), multistepState = _useState6[0], setState = _useState6[1]; // YES! this is important! Otherwise it would get a new api object every render /// That would cause unessissarry re-renders! so do not remove useMemeo! var multistepApi = React.useMemo(function () { // ---------- Define the api functions ---------- var register = function register(name, step) { // Create step meta var stepMeta = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, step), {}, { index: nSteps.current }); // Add step to ordered array steps.push(stepMeta); // Add step to named map stepsMap.set(name, stepMeta); // Inc number of steps nSteps.current = nSteps.current + 1; // Determine if we have initial goal and it just registered var initialGoal = null; var startingStep = null; // There is no initial step so we start at first one if (!initialStep) { startingStep = steps[0].name; } // Otherwise we wait until our initial step has registered and then set our goal! else if (initialStep && name === initialStep) { initialGoal = initialStep; startingStep = steps[0].name; } // console.log('WTF', name, initialGoal, startingStep); // Update the state setState(function (prev) { if (!prev.current && startingStep) { // Update the current step currentStep.current = startingStep; } return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { steps: steps, goal: prev.goal || initialGoal, initialGoal: prev.goal || initialGoal, current: prev.current || startingStep }); }); }; var deregister = function deregister(step) { var stepMeta = stepsMap.get(step); // Remove step at index steps.splice(stepMeta.index, 1); // Update indexes steps.forEach(function (s, i) { return s.index = i; }); // Remove step to named map stepsMap["delete"](step); // Dec number of steps nSteps.current = steps.length; // console.log('WTF', name); // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { steps: steps }); }); }; var getNextStep = function getNextStep() { // Get current step meta var stepMeta = stepsMap.get(currentStep.current); // Start searching from current step for a relevant next step var nextStep; for (var i = stepMeta.index + 1; i < steps.length; i++) { // Potential next step nextStep = steps[i]; // Check relevance var formState = getFormState(); if (nextStep.relevant ? nextStep.relevant({ formState: formState, formApi: formApi, scope: scope, relevanceDeps: nextStep.relDepsRef.current }) : true) { return nextStep.name; } } // IF we get here there are not next steps so we return nothing return undefined; }; var getPreviousStep = function getPreviousStep() { // Get current step meta var stepMeta = stepsMap.get(currentStep.current); // Start searching from current step for a relevant next step var previousStep; for (var i = stepMeta.index - 1; i >= 0; i--) { // Potential previous step previousStep = steps[i]; // Check relevance var formState = getFormState(); if (previousStep.relevant ? previousStep.relevant({ formState: formState, formApi: formApi, scope: scope, relevanceDeps: previousStep.relDepsRef.current }) : true) { return previousStep.name; } } // IF we get here there are no previous steps so we return nothing return undefined; }; // Helper function for next var proceed = function proceed(nextStep, cb) { // Get the multistep state values if (cb && typeof cb === 'function') { var fieldState = getFieldState(currentStep.current); // Simply making value --> values because it makes more sense in this context var subState = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, fieldState), {}, { values: fieldState.value, errors: fieldState.error }); cb(subState).then(function () { // Update the current step currentStep.current = nextStep; // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { current: nextStep }); }); })["catch"](function () { // TODO mayyybe do something here ?? }); } else { // Update the current step currentStep.current = nextStep; // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { current: nextStep }); }); } }; var next = function next(cb) { var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, skip = _ref2.skip; // Get the next step var nextStep = getNextStep(); if (nextStep) { // Touch all the fields if (!skip) formApi.touchAllFields(); // Validate the form if (!skip) validate(); // Async validate the form // We pass in a callback to proceed if we succeed async validation! if (!skip) asyncValidate(function () { return proceed(nextStep, cb); }); // Only proceed if we are valid and we are NOT currently async validating if (skip || getFormState().valid && getFormState().validating === 0) { proceed(nextStep, cb); } else if (!getFormState().valid) { // If we are not valid then clear out the goal setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { goal: null }); }); } } }; var previous = function previous() { // Get the next step var previousStep = getPreviousStep(); // Clean up all multistep errors steps.forEach(function (step) { formApi.clearError(step.name); }); // Update the current step if (previousStep) { // Update the current step currentStep.current = previousStep; // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { current: previousStep }); }); } }; var setCurrent = function setCurrent(step) { // Get current step meta var goalIndex = stepsMap.get(step).index; var currIndex = stepsMap.get(currentStep.current).index; // Clean up all multistep errors steps.forEach(function (step) { formApi.clearError(step.name); }); // console.log('GOAL', goalIndex, 'CURRENT', currIndex); // If the goal is behind then just go straight there if (goalIndex < currIndex) { // Update the current step currentStep.current = step; // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { current: step }); }); } // If the goal is ahead then start walking! ;) else { // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { goal: step }); }); } }; var metGoal = function metGoal() { // Update the state setState(function (prev) { // We clear out initialGoal also so a reset does not make us try to get there again return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { goal: null, initialGoal: null }); }); }; var getCurrentStep = function getCurrentStep() { return currentStep.current; }; // ---------- Define the api ---------- var api = { register: register, deregister: deregister, next: next, previous: previous, getNextStep: getNextStep, getPreviousStep: getPreviousStep, setCurrent: setCurrent, metGoal: metGoal, getCurrentStep: getCurrentStep }; // Set the ref if (multistepApiRef) { multistepApiRef.current = api; } // return the api return api; }, []); // Register for events when multistep relevance changes React.useEffect(function () { var listener = function listener() { // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { nextStep: multistepApi.getNextStep(), previousStep: multistepApi.getPreviousStep() }); }); }; emitter.on('multistep-relevance', listener); return function () { emitter.removeListener('multistep-relevance', listener); }; }, []); // Also re evaluate when current changes React.useEffect(function () { if (multistepState.current) { // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { nextStep: multistepApi.getNextStep(), previousStep: multistepApi.getPreviousStep() }); }); } }, [multistepState.current]); // Register for events when reset is called React.useEffect(function () { var listener = function listener() { // Update the state setState(function (prev) { return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { goal: prev.goal || prev.initialGoal }); }); }; emitter.on('reset', listener); return function () { emitter.removeListener('reset', listener); }; }, []); // Render funtion that will provide state and api var render = function render(children) { return /*#__PURE__*/React__default["default"].createElement(Context.MultistepApiContext.Provider, { value: multistepApi }, /*#__PURE__*/React__default["default"].createElement(Context.MultistepStateContext.Provider, { value: multistepState }, children)); }; return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, multistepApi), multistepState), {}, { render: render }); }; exports.useMultistep = useMultistep;