@mapbox/mr-ui
Version:
UI components for Mapbox projects
170 lines (169 loc) • 7.39 kB
JavaScript
;
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
};