UNPKG

@mapbox/mr-ui

Version:

UI components for Mapbox projects

170 lines (169 loc) 7.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = Form; var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _objects = _interopRequireDefault(require("shallow-equal/objects")); var _reactSubmittable = _interopRequireDefault(require("@mapbox/react-submittable")); var _loaderFull = _interopRequireDefault(require("../loader-full")); var _loaderLocal = _interopRequireDefault(require("../loader-local")); var _use_previous = _interopRequireDefault(require("../utils/use_previous")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const formStates = { preSubmission: 'PRE_SUBMISSION', errorSubmission: 'SUBMITTED_WITH_ERRORS', pendingSubmission: 'PENDING', completeSubmission: 'COMPLETE' }; function setControlProperties(config, values) { const controlValues = {}; const controlValidationErrors = {}; Object.keys(config).forEach(controlName => { const controlConfig = config[controlName]; if (values && values[controlName]) { controlValues[controlName] = values[controlName]; } else { controlValues[controlName] = controlConfig.initialValue !== undefined ? controlConfig.initialValue : ''; } controlValidationErrors[controlName] = ''; }); return { controlValues, controlValidationErrors }; } function validateControlValue(value, validators, controlValues) { if (!validators) return ''; if (!Array.isArray(validators)) { validators = [validators]; } for (let i = 0, l = validators.length; i < l; i++) { const errorMessage = validators[i](value, controlValues); if (errorMessage) { return errorMessage; } } return ''; } function Form(_ref) { let { config, renderForm, handleFormData, onChange, onCancel } = _ref; const formState = (0, _react.useRef)(formStates.preSubmission); const mounted = (0, _react.useRef)(false); const { controlValues: initControlValues, controlValidationErrors: initControlValidationErrors } = setControlProperties(config); const [controlValidationErrors, setControlValidationErrors] = (0, _react.useState)(initControlValidationErrors); const [controlValues, setControlValues] = (0, _react.useState)(initControlValues); const [ready, setReady] = (0, _react.useState)(false); const prevConfig = (0, _use_previous.default)(config); (0, _react.useEffect)(() => { mounted.current = true; setReady(true); return () => { mounted.current = false; }; }, []); (0, _react.useEffect)(() => { if (!(0, _objects.default)(config, prevConfig)) { const { controlValues: updatedControlValues, controlValidationErrors: updatedControlValidationErrors } = setControlProperties(config, controlValues); setControlValues(updatedControlValues); setControlValidationErrors(updatedControlValidationErrors); formState.current = formStates.preSubmission; } }, [config, prevConfig, controlValues]); const checkValidation = async nextFormState => { const validationErrors = { ...controlValidationErrors }; for (const controlName of Object.keys(controlValues)) { const errorMessage = await validateControlValue(controlValues[controlName], config[controlName].validator, controlValues); if (errorMessage) { validationErrors[controlName] = errorMessage; nextFormState = formStates.errorSubmission; } else { validationErrors[controlName] = ''; } } setControlValidationErrors(validationErrors); formState.current = nextFormState; }; const onControlChange = (controlValue, controlName) => { const nextControlValues = { ...controlValues }; nextControlValues[controlName] = controlValue; if (formState.current !== formStates.preSubmission) { checkValidation(formStates.preSubmission).then(() => { setControlValues(nextControlValues); }); } else { setControlValues(nextControlValues); } if (onChange) onChange(nextControlValues); }; const getControlProps = controlName => { const control = config[controlName]; return { ...control, id: controlName, onChange: onControlChange, validationError: controlValidationErrors[controlName], value: controlValues[controlName] }; }; const onSubmit = () => { checkValidation(formStates.pendingSubmission).then(() => { if (formState.current === formStates.pendingSubmission) submitForm(); }); }; const submitForm = () => { // Promise.resolve because if handleFormData is a promise, // we want to handle loading and error states. Promise.resolve(handleFormData(controlValues)).then(() => { if (!mounted.current) return; if (formState.current === formStates.pendingSubmission) { formState.current = formStates.completeSubmission; } }).catch(err => { if (!mounted.current) return; if (formState.current === formStates.pendingSubmission) { formState.current = formStates.completeSubmission; } console.error(err); }); }; const formProps = { onEnter: onSubmit, noValidate: true }; if (onCancel) formProps.onCancel = onCancel; return /*#__PURE__*/_react.default.createElement("div", { className: "relative" }, /*#__PURE__*/_react.default.createElement(_reactSubmittable.default, formProps, renderForm(getControlProps, onSubmit)), formState.current === formStates.pendingSubmission && /*#__PURE__*/_react.default.createElement(_loaderFull.default, null), !ready && /*#__PURE__*/_react.default.createElement(_loaderLocal.default, null)); } Form.propTypes = { /** An object containing keys that correspond to control names. The value of each control name key are objects containing the configuration of a control. */ config: _propTypes.default.object.isRequired, /** Renders the form layout. When called a get argument is passed to return the configuration object of each control type. */ renderForm: _propTypes.default.func.isRequired, /** Called when the form has successfully passed validation and returns an object containing keys the coorespond to control names with values equalling the users inputted value. */ handleFormData: _propTypes.default.func.isRequired, /** Called when any control component registered with the form changes in value. */ onChange: _propTypes.default.func, /** Called when ESC is pressed. */ onCancel: _propTypes.default.func };