UNPKG

niceform-hook

Version:
670 lines (669 loc) 28.6 kB
import {jsx}from'react/jsx-runtime';import {createContext as createContext$1,useRef,useEffect,useContext,useState,useMemo,useCallback,memo}from'react';import {useController as useController$1,useForm as useForm$1}from'react-hook-form';function createProvider(ProviderOriginal) { return ({ value, children }) => { const valueRef = useRef(value); const listenersRef = useRef(new Set()); const contextValue = useRef({ value: valueRef, registerListener: (listener) => { listenersRef.current.add(listener); return () => listenersRef.current.delete(listener); } }); useEffect(() => { valueRef.current = value; listenersRef.current.forEach((listener) => { listener(value); }); }, [value]); return (jsx(ProviderOriginal, { value: contextValue.current, children: children })); }; } function createContext(defaultValue) { const context = createContext$1(defaultValue); delete context.Consumer; context.Provider = createProvider(context.Provider); return context; }/* * This code is based on an implementation provided by Luke Edwards * [https://github.com/lukeed/dequal] * Copyright (c) Luke Edwards <luke.edwards05@gmail.com> (lukeed.com) * release v2.0.3 */ const has = Object.prototype.hasOwnProperty; function dequalLite(foo, bar) { let ctor, len; if (foo === bar) return true; if (foo && bar && (ctor = foo.constructor) === bar.constructor) { if (ctor === Date) return foo.getTime() === bar.getTime(); if (ctor === RegExp) return foo.toString() === bar.toString(); if (ctor === Array) { if ((len = foo.length) === bar.length) { while (len-- && dequalLite(foo[len], bar[len])) ; } return len === -1; } if (!ctor || typeof foo === 'object') { len = 0; for (ctor in foo) { if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false; if (!(ctor in bar) || !dequalLite(foo[ctor], bar[ctor])) return false; } return Object.keys(bar).length === len; } } return foo !== foo && bar !== bar; }function useContextSelector(context, selector, deepComparison = false) { const { value, registerListener } = useContext(context); const selectorRef = useRef(selector); const [selectedValue, setSelectedValue] = useState(() => selector(value.current)); const _selectedValue = useRef(selectedValue); _selectedValue.current = selectedValue; useEffect(() => { selectorRef.current = selector; }); useEffect(() => { const updateValueIfNeeded = (newValue) => { const newSelectedValue = selectorRef.current(newValue); const compare = deepComparison ? dequalLite : Object.is; if (!compare(_selectedValue.current, newSelectedValue)) { setSelectedValue(() => newSelectedValue); } }; const unregisterListener = registerListener(updateValueIfNeeded); return unregisterListener; }, [registerListener, value, deepComparison]); return selectedValue; }const context = createContext(null); function NiceformHookProvider({ children, ...props }) { return (jsx(context.Provider, { value: props, children: children })); } function useNiceformHookContext(callback, deepComparison = false) { return useContextSelector(context, callback, deepComparison); }function useNiceformContext() { return useNiceformHookContext(state => { return state.form; }); }function useDataRefByContext(callback) { const fieldMethodsRef = useRef(); return useNiceformHookContext(state => { fieldMethodsRef.current = callback(state); return fieldMethodsRef; }); }function useComputeInputedValueHandler(controller) { const name = controller.field.name; const inputRef = useDataRefByContext(state => { var _a; return (_a = state.form.getField(name)) === null || _a === void 0 ? void 0 : _a.input; }); const setValue = useNiceformHookContext(state => state.form.methods.setValue); const fieldsInputedCalled = useNiceformHookContext(state => state.form.control.fieldsInputedCalled); const isInputedValueRef = fieldsInputedCalled.has(name); const computeInputedValue = (callback) => { const input = inputRef.current; let value = controller.field.value; if (!input || isInputedValueRef || controller.fieldState.isDirty) return; value = input(value); if (value === undefined) return; fieldsInputedCalled.add(name); controller.field.value = value; callback(value); }; computeInputedValue(value => setTimeout(setValue, 100, name, value)); const value = controller.field.value; useEffect(() => { computeInputedValue(value => setValue(name, value)); }, [value]); }function debounce(fn, ms) { let timer = 0; return function (...args) { clearTimeout(timer); timer = setTimeout(fn.bind(this, ...args), ms || 0); }; } function dynamicDebounce() { let timer = 0; return function (fn, ms, ...args) { clearTimeout(timer); timer = setTimeout(fn.bind(this, args), ms || 0); }; }function getDependentFieldsBy(name, fieldsToSearch) { const allField = [...fieldsToSearch.values()]; const fieldSelected = fieldsToSearch.get(name); if (!fieldSelected) return []; const fieldsToClean = allField.reduce((collection, field) => { if (fieldSelected.name && field.dependsOnToClear && [field.dependsOnToClear].flat().includes(fieldSelected.name) && field.name !== fieldSelected.name) { collection.push(field); } return collection; }, []); return fieldsToClean; }function flattenJson(obj) { const flattenedObj = {}; function flatten(innerObj, path) { var _a; for (const key in innerObj) { const newPath = path ? `${path}.${key}` : key; if (((_a = innerObj[key]) === null || _a === void 0 ? void 0 : _a.constructor) === ({}).constructor || Array.isArray(innerObj[key])) { flatten(innerObj[key], newPath); } else { flattenedObj[newPath] = innerObj[key]; } } } flatten(obj, ""); return flattenedObj; }function unflattenJson(obj) { const unflattenedObj = {}; for (const key in obj) { const keys = key.split("."); let innerObj = unflattenedObj; for (let i = 0; i < keys.length - 1; i++) { const currentKey = keys[i]; if (!innerObj[currentKey]) { innerObj[currentKey] = !isNaN(Number(keys[i + 1])) ? [] : {}; } innerObj = innerObj[currentKey]; } innerObj[keys.at(-1)] = obj[key]; } return unflattenedObj; }function getOutputtedValues({ fields, values }) { const valuesFlatterned = flattenJson(values); const result = [...fields.values()].reduce((obj, field) => { if (!field.name || !field.output || field.active === false) return obj; const value = valuesFlatterned[field.name]; obj[field.name] = field.output(value); return obj; }, {}); return unflattenJson({ ...valuesFlatterned, ...result }); }const isFieldActive = (arg) => { if (typeof arg === 'object') { return arg.active !== false; } return arg !== false; };function normalizeFieldPayload(field, deps) { if (typeof field !== 'function') return field; return field(deps); }const isDateObject = (value) => value instanceof Date; const isNullOrUndefined = (value) => value == null; const isObjectType = (value) => typeof value === 'object'; const isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !isDateObject(value);const isCheckBoxInput = (element) => element.type === 'checkbox'; const getEventValue = (event) => isObject(event) && event.target ? isCheckBoxInput(event.target) ? event.target.checked : event.target.value : event;function useDebounceChangeFieldHandler(controller, time = 400, enabled = true) { const [value, setValue] = useState(controller.field.value); const debounceSubmitDefinitions = useNiceformHookContext(state => state.form.control.debounceSubmitDefinitions); const onChangeDebounce = useMemo(() => debounce(controller.field.onChange, time), [time, controller.field.onChange]); const newOnChange = useCallback((evt) => { const value = getEventValue(evt); setValue(value); onChangeDebounce(value); debounceSubmitDefinitions.set(time); }, [setValue, onChangeDebounce, time]); if (!enabled) return; const controlledValue = controller.field.value; useEffect(() => { if (!Object.is(value, controlledValue)) { setValue(controlledValue); } }, [controlledValue]); controller.field.onChange = newOnChange; controller.field.value = value; }function useForm() { return useNiceformHookContext(state => state.form.methods); }function useController(props) { const { control } = useForm(); return useController$1({ control, ...props }); }function useInactiveFieldHandler(controller) { const isFieldActive$1 = useNiceformHookContext(state => { const field = state.form.getField(controller.field.name); return isFieldActive(field === null || field === void 0 ? void 0 : field.active); }); const [value, setValue] = useState(); if (!isFieldActive$1) { controller.field = { ...controller.field, onBlur: () => null, onChange: (evt) => setValue(evt.target.value), value, ref: () => null }; } }const obj = {}; function useField(name, config) { var _a; if (!config) config = obj; const methods = useNiceformHookContext(state => state.form.methods); const errorsControl = useNiceformHookContext(state => state.form.control.errorsControl); const fieldsRegistered = useNiceformHookContext(state => state.form.control.fieldsRegistered); const validationByErrorsControl = (errorsControl || []).reduce((obj, error, i) => { const field = fieldsRegistered.get(name); if (!field) return obj; obj['errors-control-' + i] = (value, formValues) => error({ value, formValues, fieldsRegistered, methods, field }); return obj; }, {}); const volatileFieldRef = useDataRefByContext(state => { var _a, _b, _c, _d; const field = state.form.getField(name); if (!field) return {}; return { input: (_a = field.input) !== null && _a !== void 0 ? _a : config.input, onBlur: (_b = field.onBlur) !== null && _b !== void 0 ? _b : config.onBlur, onChange: (_c = field.onChange) !== null && _c !== void 0 ? _c : config.onChange, validate: (_d = field.validate) !== null && _d !== void 0 ? _d : config.validate }; }); const { rules, others } = useNiceformHookContext(state => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v; const field = state.form.getField(name); if (!field) return { rules: {}, others: {} }; const debounceTime = ((_e = (_d = (_b = (_a = field.debounceTime) !== null && _a !== void 0 ? _a : config.debounceTime) !== null && _b !== void 0 ? _b : (_c = state.form.control.parameters) === null || _c === void 0 ? void 0 : _c.debounceTime) !== null && _d !== void 0 ? _d : state.form.control.config.debounceTime) !== null && _e !== void 0 ? _e : 400); const enableDebounce = ((_k = (_j = (_g = (_f = field.enableDebounce) !== null && _f !== void 0 ? _f : config.enableDebounce) !== null && _g !== void 0 ? _g : (_h = state.form.control.parameters) === null || _h === void 0 ? void 0 : _h.enableDebounce) !== null && _j !== void 0 ? _j : state.form.control.config.enableDebounce) !== null && _k !== void 0 ? _k : false) && debounceTime > 0; return { rules: { min: (_l = field.min) !== null && _l !== void 0 ? _l : config.min, max: (_m = field.max) !== null && _m !== void 0 ? _m : config.max, deps: field.deps, maxLength: (_o = field.maxLength) !== null && _o !== void 0 ? _o : config.maxLength, minLength: (_p = field.minLength) !== null && _p !== void 0 ? _p : config.minLength, pattern: (_q = field.pattern) !== null && _q !== void 0 ? _q : config.pattern, required: (_r = field.required) !== null && _r !== void 0 ? _r : config.required, value: (_s = field.value) !== null && _s !== void 0 ? _s : config.value, }, others: { shouldUnregister: (_t = field.shouldUnregister) !== null && _t !== void 0 ? _t : config.shouldUnregister, isActive: ((_u = field.active) !== null && _u !== void 0 ? _u : config.active) !== false, disabled: (_v = field.disabled) !== null && _v !== void 0 ? _v : config.disabled, debounceTime, enableDebounce } }; }, true); const controller = useController({ name, shouldUnregister: others.shouldUnregister, rules: others.isActive ? { onBlur: (...args) => { var _a, _b; return (_b = (_a = volatileFieldRef.current) === null || _a === void 0 ? void 0 : _a.onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, onChange: (...args) => { var _a, _b; return (_b = (_a = volatileFieldRef.current) === null || _a === void 0 ? void 0 : _a.onChange) === null || _b === void 0 ? void 0 : _b.call(_a, ...args); }, shouldUnregister: others.shouldUnregister, deps: rules.deps, max: rules.max, min: rules.min, maxLength: rules.maxLength, minLength: rules.minLength, pattern: rules.pattern, required: rules.required, validate: { ...validationByErrorsControl, ...(_a = volatileFieldRef.current) === null || _a === void 0 ? void 0 : _a.validate }, value: rules.value, } : {}, disabled: others.disabled }); useComputeInputedValueHandler(controller); useDebounceChangeFieldHandler(controller, others.debounceTime, others.enableDebounce); useInactiveFieldHandler(controller); return controller; }const RenderReactElement = memo(function RenderReactElement(props) { const hook = useField(props.name); return props.render(hook); }); const renderReactElement = (props) => jsx(RenderReactElement, { ...props });function useDataRef(data) { const ref = useRef(data); ref.current = data; return ref; }function useChangeField(props) { const oldsValuesByName = useRef(null); const onChangeFieldRef = useDataRef(props.onChangeField); const watch = props.methods.watch; const methods = props.methods; if (!oldsValuesByName.current) { oldsValuesByName.current = new Map(); } useEffect(() => { const subscription = watch((values, { name, type }) => { if (name === undefined || !onChangeFieldRef.current) return; const field = props.fields.get(name); if (!field) return; const value = methods.getValues(name); if (Object.is(oldsValuesByName.current.get(name), value)) return; oldsValuesByName.current.set(name, value); onChangeFieldRef.current(field, value); }); return () => subscription.unsubscribe(); }, [watch, onChangeFieldRef, methods]); }function useDependentFieldsToClear(input) { const { methods, optionsWhenCleaning } = input; const dataRef = useDataRef({ methods, optionsWhenCleaning }); const watch = methods.watch; useEffect(() => { const subscription = watch((values, { name, type }) => { if (name === undefined) return; const methods = dataRef.current.methods; const optionsWhenCleaning = dataRef.current.optionsWhenCleaning; const fields = input.fields; const currentValue = methods.getValues(name); const shouldCleanChain = optionsWhenCleaning === null || optionsWhenCleaning === void 0 ? void 0 : optionsWhenCleaning.shouldCleanChain; if (currentValue === undefined && !shouldCleanChain) return; const fieldsDependents = getDependentFieldsBy(name, fields || []); fieldsDependents.forEach(field => { const currentFieldValue = methods.getValues(field.name); if (!field.name || currentFieldValue === undefined) return; methods.setValue(field.name, null, optionsWhenCleaning); }); }); return () => subscription.unsubscribe(); }, [watch, dataRef]); }function useInitialValues(props) { const flatternInitialValues = flattenJson(props.initialValues); for (const fieldName in flatternInitialValues) { const value = flatternInitialValues[fieldName]; if (value === undefined) continue; if (props.methods.getValues(fieldName) !== undefined) continue; props.methods.register(fieldName); props.methods.setValue(fieldName, value); } }/* * This code is based on an implementation provided by Federico Brigante * [https://github.com/fregante/many-keys-map] * Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com) * release v2.0.1 */ const nullKey = Symbol('null'); // `objectHashes` key for null let keyCounter = 0; class ManyKeysMap extends Map { constructor() { super(); this._objectHashes = new WeakMap(); this._symbolHashes = new Map(); // https://github.com/tc39/ecma262/issues/1194 this._publicKeys = new Map(); const [pairs] = arguments; // Map compat if (pairs === null || pairs === undefined) { return; } if (typeof pairs[Symbol.iterator] !== 'function') { throw new TypeError(typeof pairs + ' is not iterable (cannot read property Symbol(Symbol.iterator))'); } for (const [keys, value] of pairs) { this.set(keys, value); } } _getPublicKeys(keys, create = false) { if (!Array.isArray(keys)) { throw new TypeError('The keys parameter must be an array'); } const privateKey = this._getPrivateKey(keys, create); let publicKey; if (privateKey && this._publicKeys.has(privateKey)) { publicKey = this._publicKeys.get(privateKey); } else if (create) { publicKey = [...keys]; // Regenerate keys array to avoid external interaction this._publicKeys.set(privateKey, publicKey); } return { privateKey, publicKey }; } _getPrivateKey(keys, create = false) { const privateKeys = []; for (let key of keys) { if (key === null) { key = nullKey; } const hashes = typeof key === 'object' || typeof key === 'function' ? '_objectHashes' : (typeof key === 'symbol' ? '_symbolHashes' : false); if (!hashes) { privateKeys.push(key); } else if (this[hashes].has(key)) { privateKeys.push(this[hashes].get(key)); } else if (create) { const privateKey = `@@mkm-ref-${keyCounter++}@@`; this[hashes].set(key, privateKey); privateKeys.push(privateKey); } else { return false; } } return JSON.stringify(privateKeys); } set(keys, value) { const { publicKey } = this._getPublicKeys(keys, true); return super.set(publicKey, value); } get(keys) { const { publicKey } = this._getPublicKeys(keys); return super.get(publicKey); } has(keys) { const { publicKey } = this._getPublicKeys(keys); return super.has(publicKey); } delete(keys) { const { publicKey, privateKey } = this._getPublicKeys(keys); return Boolean(publicKey && super.delete(publicKey) && this._publicKeys.delete(privateKey)); } clear() { super.clear(); this._symbolHashes.clear(); this._publicKeys.clear(); } get [Symbol.toStringTag]() { return 'ManyKeysMap'; } get size() { // @ts-ignore return super.size; } }function useMemoize() { const cache = useRef(new ManyKeysMap()); const memoize = useCallback(function memoize(callback, dependencies) { let currentValue = cache.current.get(dependencies); if (!currentValue) { if (typeof callback === 'function') { cache.current.set(dependencies, callback()); } else { cache.current.set(dependencies, callback); } currentValue = cache.current.get(dependencies); } return currentValue; }, []); return memoize; }function useMemoizeCallback() { const cache = useRef(new ManyKeysMap()); const memoizeCallback = useCallback(function memoizeCallback(callback, dependencies) { let currentValue = cache.current.get(dependencies); if (!currentValue) { cache.current.set(dependencies, callback); currentValue = cache.current.get(dependencies); } return currentValue; }, []); return memoizeCallback; }function useOnErrorDuringSubmit({ getField, methods, onErrorDuringSubmit }) { useEffect(() => { const errors = methods.formState.errors; if (Object.keys(errors).length && methods.formState.submitCount) onErrorDuringSubmit === null || onErrorDuringSubmit === void 0 ? void 0 : onErrorDuringSubmit(methods.formState.errors, { methods, getField }); }, [methods.formState.submitCount]); }function create(config) { const components = new Map(); for (const key in config.components) { components.set(key, config.components[key]); } return function useForm(parameters) { const repository = useRef({ fieldsRegistered: new Map(), componentsDefinitions: new Map(components), errorsControl: config.errorsControl, debounceSubmitDefinitions: { debounceRegistry: { registeredAt: 0, time: 0 }, set(time) { this.debounceRegistry.registeredAt = Date.now(); this.debounceRegistry.time = time; }, getRemainingTime() { const timeDiff = Date.now() - this.debounceRegistry.registeredAt; return Math.max(0, this.debounceRegistry.time - timeDiff + 50); }, isActiveDebounce() { const timeDiff = Date.now() - this.debounceRegistry.registeredAt; return timeDiff < this.debounceRegistry.time; } }, fieldsInputedCalled: new Set() }); const submitDynamicDebounce = useMemo(() => dynamicDebounce(), []); const memoize = useMemoize(); const memoizeCallback = useMemoizeCallback(); const methods = useForm$1(parameters); const getField = useCallback((name) => { return repository.current.fieldsRegistered.get(name); }, []); useOnErrorDuringSubmit({ getField, methods, onErrorDuringSubmit: config.onErrorDuringSubmit }); useInitialValues({ methods, initialValues: (parameters === null || parameters === void 0 ? void 0 : parameters.initialValues) || {} }); useDependentFieldsToClear({ methods, get fields() { return repository.current.fieldsRegistered; }, }); useChangeField({ methods, get fields() { return repository.current.fieldsRegistered; }, onChangeField: parameters === null || parameters === void 0 ? void 0 : parameters.onChangeField }); const renderField = useCallback(field => { const { render, ..._field } = normalizeFieldPayload(field, { getField, methods }); const type = _field.type; const name = _field.name; if (!name) return null; repository.current.fieldsRegistered.set(name, _field); if (type === 'node' && render) { return renderReactElement({ name, render }); } const component = repository.current.componentsDefinitions.get(type); const fieldComponent = component === null || component === void 0 ? void 0 : component.render(_field); return fieldComponent; }, [ methods, getField ]); const renderFields = useCallback(fields => { return fields.filter(Boolean).map(renderField); }, [renderField]); if (!methods.handleSubmit.prototype) { const handleSubmit = methods.handleSubmit; methods.handleSubmit = function (onValid, onInvalid) { const resolver = handleSubmit(async (values, event) => { const valuesOutputted = getOutputtedValues({ fields: repository.current.fieldsRegistered, values: values }); onValid(valuesOutputted, event); }, onInvalid); return async function (evt) { var _a; const enableDebounce = ((_a = parameters === null || parameters === void 0 ? void 0 : parameters.enableDebounceOnSubmit) !== null && _a !== void 0 ? _a : config.enableDebounceOnSubmit) && repository.current.debounceSubmitDefinitions.isActiveDebounce(); if (!enableDebounce) return resolver(evt); const timeDebounce = repository.current.debounceSubmitDefinitions.getRemainingTime(); evt === null || evt === void 0 ? void 0 : evt.preventDefault(); submitDynamicDebounce(() => { resolver(evt); }, timeDebounce); }; }; } // -------------------------------------------------------------------------------------------- const result = useRef({ methods, renderField, renderFields, getField, memoize, memoizeCallback, control: { get errorsControl() { return repository.current.errorsControl; }, get fieldsRegistered() { return repository.current.fieldsRegistered; }, get fieldsInputedCalled() { return repository.current.fieldsInputedCalled; }, get debounceSubmitDefinitions() { return repository.current.debounceSubmitDefinitions; }, get parameters() { return parameters; }, get config() { return config; } } }); result.current.renderField = renderField; result.current.renderFields = renderFields; return result.current; }; }export{NiceformHookProvider,create,useField,useNiceformContext,useNiceformHookContext};//# sourceMappingURL=index.esm.mjs.map