UNPKG

@dotconnor/grommet

Version:

focus on the essential experience

350 lines (298 loc) 13.4 kB
"use strict"; exports.__esModule = true; exports.Form = void 0; var _react = _interopRequireWildcard(require("react")); var _FormContext = require("./FormContext"); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } var defaultMessages = { invalid: 'invalid', required: 'required' }; var defaultValue = {}; var defaultTouched = {}; var defaultValidationResults = { errors: {}, infos: {} }; // validations is an array from Object.entries() var validate = function validate(validations, value, omitValid) { var nextErrors = {}; var nextInfos = {}; validations.forEach(function (_ref) { var name = _ref[0], validation = _ref[1]; if (!omitValid) { nextErrors[name] = undefined; nextInfos[name] = undefined; } var result = validation(value[name], value); // typeof error === 'object' is implied for both cases of error with // a status message and for an error object that is a react node if (typeof result === 'object') { if (result.status === 'info') { nextInfos[name] = result.message; } else { nextErrors[name] = result.message || result; // could be a node } } else if (typeof result === 'string') { nextErrors[name] = result; } }); return [nextErrors, nextInfos]; }; var Form = /*#__PURE__*/(0, _react.forwardRef)(function (_ref2, ref) { var children = _ref2.children, _ref2$errors = _ref2.errors, errorsProp = _ref2$errors === void 0 ? defaultValidationResults.errors : _ref2$errors, _ref2$infos = _ref2.infos, infosProp = _ref2$infos === void 0 ? defaultValidationResults.infos : _ref2$infos, _ref2$messages = _ref2.messages, messages = _ref2$messages === void 0 ? defaultMessages : _ref2$messages, onChange = _ref2.onChange, _onReset = _ref2.onReset, _onSubmit = _ref2.onSubmit, onValidate = _ref2.onValidate, _ref2$validate = _ref2.validate, validateOn = _ref2$validate === void 0 ? 'submit' : _ref2$validate, valueProp = _ref2.value, rest = _objectWithoutPropertiesLoose(_ref2, ["children", "errors", "infos", "messages", "onChange", "onReset", "onSubmit", "onValidate", "validate", "value"]); var _useState = (0, _react.useState)(valueProp || defaultValue), valueState = _useState[0], setValueState = _useState[1]; var value = (0, _react.useMemo)(function () { return valueProp || valueState; }, [valueProp, valueState]); var _useState2 = (0, _react.useState)(defaultTouched), touched = _useState2[0], setTouched = _useState2[1]; var _useState3 = (0, _react.useState)(defaultValidationResults), validationResults = _useState3[0], setValidationResults = _useState3[1]; (0, _react.useEffect)(function () { return setValidationResults({ errors: errorsProp, infos: infosProp }); }, [errorsProp, infosProp]); var validations = (0, _react.useRef)({}); // clear any errors when value changes (0, _react.useEffect)(function () { setValidationResults(function (prevValidationResults) { var _validate = validate(Object.entries(validations.current).filter(function (_ref3) { var n = _ref3[0]; return prevValidationResults.errors[n] || prevValidationResults.infos[n]; }), value), nextErrors = _validate[0], nextInfos = _validate[1]; return { errors: _extends({}, prevValidationResults.errors, nextErrors), infos: _extends({}, prevValidationResults.infos, nextInfos) }; }); }, [touched, value]); // There are three basic patterns of handling form input value state: // // 1 - form controlled // // In this model, the caller sets `value` and `onChange` properties // on the Form component to supply the values used by the input fields. // In useFormContext(), componentValue would be undefined and formValue // is be set to whatever the form state has. Whenever the form state // changes, we update the contextValue so the input component will use // that. When the input component changes, we will call update() to // update the form state. // // 2 - input controlled // // In this model, the caller sets `value` and `onChange` properties // on the input components, like TextInput, to supply the value for it. // In useFormContext(), componentValue is this value and we ensure to // update the form state, via update(), and set the contextValue from // the componentValue. When the input component changes, we will // call update() to update the form state. // // 3 - uncontrolled // // In this model, the caller doesn't set a `value` or `onChange` property // at either the form or input component levels. // In useFormContext(), componentValue is undefined and valueProp is // undefined and nothing much happens here. That is, unless the // calling component needs to know the state in order to work, such // as CheckBox or Select. In this case, those components supply // an initialValue, which will trigger updating the contextValue so // they can have access to it. // var useFormInput = function useFormInput(name, componentValue, initialValue) { var _useState4 = (0, _react.useState)(initialValue), inputValue = _useState4[0], setInputValue = _useState4[1]; var formValue = name ? value[name] : undefined; // This effect is for pattern #2, where the controlled input // component is driving the value via componentValue. (0, _react.useEffect)(function () { if (name && // we have somewhere to put this componentValue !== undefined && // input driving componentValue !== formValue // don't already have it ) { setValueState(function (prevValue) { var nextValue = _extends({}, prevValue); nextValue[name] = componentValue; return nextValue; }); // don't onChange on programmatic changes } }, [componentValue, formValue, name]); var useValue; if (componentValue !== undefined) // input component drives, pattern #2 useValue = componentValue;else if (valueProp && name && formValue !== undefined) // form drives, pattern #1 useValue = formValue;else if (formValue === undefined) // form has reset, so reset input value as well useValue = initialValue;else useValue = inputValue; return [useValue, function (nextComponentValue) { if (name) { // we have somewhere to put this var nextTouched = _extends({}, touched); nextTouched[name] = true; if (!touched[name]) { // don't update if not needed setTouched(nextTouched); } var nextValue = _extends({}, value); nextValue[name] = nextComponentValue; setValueState(nextValue); if (onChange) onChange(nextValue, { touched: nextTouched }); } if (initialValue !== undefined) setInputValue(nextComponentValue); }]; }; var useFormField = function useFormField(_ref4) { var errorArg = _ref4.error, infoArg = _ref4.info, name = _ref4.name, required = _ref4.required, validateArg = _ref4.validate; var error = errorArg || validationResults.errors[name]; var info = infoArg || validationResults.infos[name]; (0, _react.useEffect)(function () { var validateSingle = function validateSingle(aValidate, value2, data) { var result; if (typeof aValidate === 'function') { result = aValidate(value2, data); } else if (aValidate.regexp) { if (!aValidate.regexp.test(value2)) { result = aValidate.message || messages.invalid; if (aValidate.status) { result = { message: result, status: aValidate.status }; } } } return result; }; var validateField = function validateField(value2, data) { var result; if (required && ( // false is for CheckBox value2 === undefined || value2 === '' || value2 === false)) { result = messages.required; } else if (validateArg) { if (Array.isArray(validateArg)) { validateArg.some(function (aValidate) { result = validateSingle(aValidate, value2, data); return !!result; }); } else { result = validateSingle(validateArg, value2, data); } } return result; }; if (validateArg || required) { validations.current[name] = validateField; return function () { return delete validations.current[name]; }; } return undefined; }, [error, name, required, validateArg]); return { error: error, info: info, inForm: true, onBlur: validateOn === 'blur' ? function () { // run validations on touched keys var _validate2 = validate(Object.entries(validations.current).filter(function (_ref5) { var n = _ref5[0]; return touched[n] || n === name; }), value), nextErrors = _validate2[0], nextInfos = _validate2[1]; // give user access to errors that have occurred on validation setValidationResults(function (prevValidationResults) { // keep any previous errors and infos for untouched keys, // which probably came from a submit var nextValidationResults = { errors: _extends({}, prevValidationResults.errors, nextErrors), infos: _extends({}, prevValidationResults.infos, nextInfos) }; if (onValidate) onValidate(nextValidationResults); return nextValidationResults; }); } : undefined }; }; return /*#__PURE__*/_react["default"].createElement("form", _extends({ ref: ref }, rest, { onReset: function onReset(event) { if (!valueProp) { setValueState(defaultValue); if (onChange) onChange(defaultValue, { touched: defaultTouched }); } setTouched(defaultTouched); setValidationResults(defaultValidationResults); if (_onReset) { event.persist(); // extract from React's synthetic event pool var adjustedEvent = event; adjustedEvent.value = defaultValue; _onReset(adjustedEvent); } }, onSubmit: function onSubmit(event) { // Don't submit the form via browser form action. We don't want it // if the validation fails. And, we assume a javascript action handler // otherwise. event.preventDefault(); var _validate3 = validate(Object.entries(validations.current), value, true), nextErrors = _validate3[0], nextInfos = _validate3[1]; setValidationResults(function () { var nextValidationResults = { errors: nextErrors, infos: nextInfos }; if (onValidate) onValidate(nextValidationResults); return nextValidationResults; }); if (Object.keys(nextErrors).length === 0 && _onSubmit) { event.persist(); // extract from React's synthetic event pool var adjustedEvent = event; adjustedEvent.value = value; adjustedEvent.touched = touched; _onSubmit(adjustedEvent); } } }), /*#__PURE__*/_react["default"].createElement(_FormContext.FormContext.Provider, { value: { useFormField: useFormField, useFormInput: useFormInput } }, children)); }); Form.displayName = 'Form'; var FormDoc; if (process.env.NODE_ENV !== 'production') { FormDoc = require('./doc').doc(Form); // eslint-disable-line global-require } var FormWrapper = FormDoc || Form; exports.Form = FormWrapper;