react-formal
Version:
Classy HTML form management for React
136 lines (132 loc) • 6.56 kB
JavaScript
;
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;