UNPKG

react-formal

Version:

Classy HTML form management for React

136 lines (132 loc) 6.56 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formGetter = exports.default = exports.BindingContext = void 0; exports.formSetter = formSetter; exports.useBindingContext = void 0; var _propertyExpr = _interopRequireDefault(require("property-expr")); var _useUpdatedRef = _interopRequireDefault(require("@restart/hooks/useUpdatedRef")); var _react = _interopRequireWildcard(require("react")); var _updateIn = _interopRequireDefault(require("./utils/updateIn")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && 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 _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* eslint-disable @typescript-eslint/no-empty-function */ const formGetter = (path, model) => path ? _propertyExpr.default.getter(path, true)(model || {}) : model; exports.formGetter = formGetter; function formSetter(path, value, fieldValue) { return (0, _updateIn.default)(value, path, fieldValue); } const BindingContext = /*#__PURE__*/_react.default.createContext({ getValue() {}, updateBindingValue() {} }); exports.BindingContext = BindingContext; BindingContext.displayName = 'ReactFormalValueContext'; const useBindingContext = () => { return (0, _react.useContext)(BindingContext); }; exports.useBindingContext = useBindingContext; const isEvent = e => typeof e == 'object' && e != null && 'target' in e; function parseValueFromEvent(target, fieldValue, fieldSchema) { const { type, value, checked, options, multiple, files } = target; if (type === 'file') return multiple ? files : files && files[0]; if (multiple) { var _fieldSchema$_subType; // @ts-ignore const innerType = fieldSchema == null ? void 0 : (_fieldSchema$_subType = fieldSchema._subType) == null ? void 0 : _fieldSchema$_subType._type; return Array.from(options).filter(opt => opt.selected).map(({ value: option }) => innerType == 'number' ? parseFloat(option) : option); } if (/number|range/.test(type)) { let parsed = parseFloat(value); return isNaN(parsed) ? null : parsed; } if (type === 'checkbox') { const isArray = Array.isArray(fieldValue); const isBool = !isArray && (fieldSchema == null ? void 0 : fieldSchema._type) === 'boolean'; if (isBool) return checked; const nextValue = isArray ? [...fieldValue] : []; const idx = nextValue.indexOf(value); if (checked) { if (idx === -1) nextValue.push(value); } else nextValue.splice(idx, 1); return nextValue; } return value; } function useFormBindingContext({ formValue, onChange, setter = formSetter, getter = formGetter, getSchemaForPath }) { // Why is this so complicated? // Well, calling onChange, from a binding multiple times would trigger // a change multiple times. Duh. This change, when controlled, might not flush // back through by the time the next change is called, leaving the updateBindingValue() // with a stale copy of `model`. React's setState avoids this with it's function // signature of useState, so we "queue" model changes locally in state, and // then "flush" them in an effect when the update is finished. let formValueRef = (0, _useUpdatedRef.default)(formValue); let pendingChangeRef = (0, _react.useRef)(false); let [pendingChange, setPendingChange] = (0, _react.useState)([formValue, []]); // This assumes that we won't get an update until all the queued setState's fire, // then if there is a pending change we fire onChange with it and the consolidated // paths (0, _react.useLayoutEffect)(() => { const [nextFormValue, paths] = pendingChange; if (pendingChangeRef.current) { pendingChangeRef.current = false; onChange(nextFormValue, paths); } }); const updateBindingValue = (0, _react.useCallback)((mapValue, args) => { setPendingChange(pendingState => { let [nextModel, paths] = pendingState; // If there are no unflushed changes then use the current props model, assuming it // would be up to date. if (!pendingChangeRef.current) { pendingChangeRef.current = true; nextModel = formValueRef.current; paths = []; } Object.keys(mapValue).forEach(key => { let field = mapValue[key]; let value; if (typeof field === 'function') value = field(...args);else if (field === '.' || field == null || args[0] == null) value = args[0];else { value = _propertyExpr.default.getter(field, true)(args[0]); } if (paths.indexOf(key) === -1) paths.push(key); if (isEvent(value)) value = parseValueFromEvent(value.target, formGetter(key, nextModel), getSchemaForPath(key, nextModel)); nextModel = setter(key, nextModel, value, formSetter); }); return [nextModel, paths]; }); }, [formValueRef, getSchemaForPath, setter]); const getValue = (0, _react.useCallback)(pathOrAccessor => typeof pathOrAccessor === 'function' ? pathOrAccessor(formValue, getter) : getter(pathOrAccessor, formValue), [getter, formValue]); return (0, _react.useMemo)(() => ({ getValue, updateBindingValue, getSchemaForPath: getSchemaForPath, updateFormValue: nextFormValue => { setPendingChange(() => { pendingChangeRef.current = true; return [nextFormValue, []]; }); }, formValue }), [getValue, updateBindingValue, setPendingChange, getSchemaForPath, formValue]); } var _default = useFormBindingContext; exports.default = _default;