react-f0rm 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -39
- package/dist/devtools/index.cjs.js +1 -1
- package/dist/devtools/index.cjs.js.map +1 -1
- package/dist/devtools/index.d.ts +2 -2
- package/dist/devtools/index.mjs +1 -1
- package/dist/devtools/index.mjs.map +1 -1
- package/dist/errors-BKrUdpfI.cjs.js +2 -0
- package/dist/errors-BKrUdpfI.cjs.js.map +1 -0
- package/dist/errors-CrQBddrJ.mjs +2 -0
- package/dist/errors-CrQBddrJ.mjs.map +1 -0
- package/dist/form-CeKSBs31.d.ts +486 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1097 -118
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1082 -544
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +2 -2
- package/dist/index.umd.min.js.map +1 -1
- package/dist/persist.cjs.js +2 -0
- package/dist/persist.cjs.js.map +1 -0
- package/dist/persist.d.ts +49 -0
- package/dist/persist.mjs +2 -0
- package/dist/persist.mjs.map +1 -0
- package/dist/resolvers/standard-schema.cjs.js +1 -1
- package/dist/resolvers/standard-schema.cjs.js.map +1 -1
- package/dist/resolvers/standard-schema.d.ts +7 -5
- package/dist/resolvers/standard-schema.mjs +1 -1
- package/dist/resolvers/standard-schema.mjs.map +1 -1
- package/dist/resolvers/yup.cjs.js +1 -1
- package/dist/resolvers/yup.cjs.js.map +1 -1
- package/dist/resolvers/yup.d.ts +1 -1
- package/dist/resolvers/yup.mjs +1 -1
- package/dist/resolvers/yup.mjs.map +1 -1
- package/dist/resolvers/zod.cjs.js +1 -1
- package/dist/resolvers/zod.cjs.js.map +1 -1
- package/dist/resolvers/zod.d.ts +1 -1
- package/dist/resolvers/zod.mjs +1 -1
- package/dist/resolvers/zod.mjs.map +1 -1
- package/dist/server/index.cjs.js +2 -0
- package/dist/server/index.cjs.js.map +1 -0
- package/dist/server/index.d.ts +77 -0
- package/dist/server/index.mjs +2 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/validate-CNtuUhmk.mjs +2 -0
- package/dist/validate-CNtuUhmk.mjs.map +1 -0
- package/dist/validate-Cl4ksNFu.cjs.js +2 -0
- package/dist/validate-Cl4ksNFu.cjs.js.map +1 -0
- package/dist/validate-nksgv1pR.d.ts +272 -0
- package/dist/values-Cu6awQOJ.cjs.js +2 -0
- package/dist/values-Cu6awQOJ.cjs.js.map +1 -0
- package/dist/values-DRY-a32G.mjs +2 -0
- package/dist/values-DRY-a32G.mjs.map +1 -0
- package/package.json +87 -29
- package/dist/form-2_tBkEXU.mjs +0 -2
- package/dist/form-2_tBkEXU.mjs.map +0 -1
- package/dist/form-BiDaJLjD.d.ts +0 -826
- package/dist/form-BwLNQ6WB.cjs.js +0 -2
- package/dist/form-BwLNQ6WB.cjs.js.map +0 -1
- package/dist/validate-2XUilILy.d.ts +0 -22
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values-Cu6awQOJ.cjs.js","sources":["../src/core/values.ts"],"sourcesContent":["import {emit} from '../emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport type {FieldPath, PathValueOf} from '../types';\nimport {freezeValues, get, isEqual, setOwned, unset} from '../util';\nimport type {FieldError, Form} from '../form';\nimport {clearErrors, getErrorByPath, getFieldErrorsByPath} from './errors';\nimport {isFieldDirtyByPath} from './dirty';\nimport {setTouchedByPath} from './touched';\nimport {\n bumpDirtyVersion,\n bumpValuesVersion,\n clearDirtyBaselines,\n getDirtyBaseline,\n pruneDirtyBaselines,\n setDirtyBaseline,\n valuesCaches\n} from './internals';\n\n/** Dev-only flag, replaced at build time (rollup.config.js `replace`);\n * defined for the test environment in vitest.config.ts. */\ndeclare const __DEV__: boolean;\n\n/**\n * Get form values: the values Map layered over parsedValues (when a schema\n * validation produced them) layered over initialValues.\n *\n * Merged with copy-on-write ownership tracking ({@link setOwned}): every\n * distinct container on a written path is allocated once and shared by all\n * paths through it, instead of re-copying the whole branch for every key.\n * One owned set spans the whole merge, so containers borrowed from the\n * parsedValues tree are copied before mutation exactly like initialValues\n * ones. The result is a freshly merged tree per mutation, with untouched\n * branches sharing references with the baseline exactly like chained\n * `set` did.\n *\n * Memoized per form like {@link getDirtyFields}: every value write bumps a\n * `version` counter ({@link bumpValuesVersion}) while reads reset it, so\n * consecutive reads hand back the same reference (submit, changeValue and\n * form-level validate all read the whole tree, often several times per\n * interaction). Treat the result as read-only — the next read after a\n * write returns a fresh tree, but between writes the cached one is shared\n * with every other reader.\n *\n * parsedValues is the schema's complete output tree: once validation\n * succeeds it replaces the initialValues baseline (fields the schema\n * dropped disappear), while live edits in the values Map still win over\n * both. It never affects dirty state — {@link isDirty} and\n * {@link getDirtyFields} compare live edits against initialValues only,\n * because parsing is not a user edit.\n *\n * @param form\n */\nexport function getValues<T extends Record<string, any> = any>(\n form: Form<T>\n): T {\n let cache = valuesCaches.get(form);\n if (!cache) {\n cache = {version: 0, result: computeValues(form)};\n valuesCaches.set(form, cache);\n } else if (cache.version > 0) {\n cache.result = computeValues(form);\n cache.version = 0;\n }\n return cache.result as T;\n}\n\nfunction computeValues(form: Form): any {\n const {initialValues, parsedValues, values, deleted} = form;\n const owned = new Set<object>();\n let merged = parsedValues ?? initialValues;\n for (const [key, value] of values) {\n merged = setOwned(merged, JSON.parse(key), value, owned);\n }\n // Unregistered fields leave a tombstone in `deleted`; remove those paths\n // from the merged result so they don't fall back to initialValues. unset\n // is immutable (set() shares untouched branches with initialValues, so a\n // mutating delete would corrupt them) and deletes the key outright rather\n // than writing undefined, which would leave `a: undefined` entries behind\n // in anything that spreads getValues().\n for (const key of deleted) {\n merged = unset(merged, JSON.parse(key));\n }\n // DEV-only: hand back a frozen snapshot (a clone — freezing the merged\n // tree in place would also freeze the initialValues/parsedValues\n // containers it borrows from). Consumer mutations then throw at the\n // offending site instead of silently corrupting the shared cache.\n return __DEV__ ? freezeValues(merged) : merged;\n}\n\n/**\n * Get field value\n * @param form\n * @param name\n */\nexport function getValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): PathValueOf<T, P> {\n return getValueByPath(form, createPath(name));\n}\n\n/**\n * Get field value by path\n * @param form\n * @param path\n */\nexport function getValueByPath(\n {initialValues, parsedValues, values, deleted}: Form,\n path: Path\n): any {\n const {key, value: segments} = path;\n if (values.has(key)) return values.get(key);\n // Unregistered path: the tombstone blocks the initialValues fallback.\n if (deleted.has(key)) return undefined;\n // A live ancestor key is a whole-branch write (setValue at a parent\n // path, every useFieldArray operation): it replaces the subtree below\n // it, the same way getValues' merge layers it over the baseline, so\n // reads under it resolve from that stored value instead of falling\n // back to the pre-edit initialValues snapshot. Nearest ancestor first:\n // a finer write is layered over a coarser one (setValueByPath drops the\n // superseded descendant keys), so the closest live ancestor is the\n // newest generation. Paths the ancestor's value does not carry read\n // undefined — the baseline must not fill holes inside a replaced\n // branch.\n for (let i = segments.length - 1; i > 0; i--) {\n const ancestorKey = JSON.stringify(segments.slice(0, i));\n if (values.has(ancestorKey)) {\n return get(values.get(ancestorKey), segments.slice(i));\n }\n }\n // Same layering as getValues: parsed values (when present) are the\n // baseline above initialValues.\n return get(parsedValues ?? initialValues, segments);\n}\n\n/** Options accepted by {@link setValue} / {@link setValueByPath} / {@link\n * changeValue} / {@link changeValueByPath}. `shouldValidate`/`shouldTouch`\n * default to `false`; omitting the options object entirely keeps the plain\n * set-value behavior (no validation, no touched marking, dirty stays\n * derived). */\nexport type SetFieldOptions = {\n /** Run the field's registered validator (if any) after the value lands,\n * same as triggering that single field. Defaults to `false`. */\n shouldValidate?: boolean;\n /** Mark the field as touched. Defaults to `false`. */\n shouldTouch?: boolean;\n /** Land the value as a commit instead of an edit: the value becomes the\n * field's dirty-comparison baseline, so `getDirtyFields`/`isDirty`/\n * `getFieldState().isDirty` read the field as clean, and a later write\n * dirties it only by differing from the new baseline. `true` (or\n * omitting the flag) keeps the default derived behavior — dirty while\n * the live value differs from initialValues. */\n shouldDirty?: boolean;\n};\n\n/**\n * Set field value. The value may also be an updater function receiving\n * the field's current value and returning the next one (TanStack Form's\n * `setFieldValue` contract) — handy for increments and array transforms:\n * `setValue(form, 'count', c => c + 1)`. The tradeoff: a function can\n * never itself be stored as a field value through this function.\n * @param form\n * @param name\n * @param value\n * @param options\n */\nexport function setValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(\n form: Form<T>,\n name: P,\n value: PathValueOf<T, P> | ((prev: PathValueOf<T, P>) => PathValueOf<T, P>),\n options?: SetFieldOptions\n): void {\n setValueByPath(form, createPath(name), value, options);\n}\n\n/**\n * Set field value. The value may also be an updater function receiving\n * the field's current value and returning the next one (TanStack Form's\n * `setFieldValue` contract) — note that a function can therefore never\n * itself be stored as a field value through this function.\n * @param form\n * @param path\n * @param value\n * @param options\n */\nexport function setValueByPath(\n form: Form,\n path: Path,\n value: any | ((prev: any) => any),\n options?: SetFieldOptions\n): void {\n const {emitter, values, deleted} = form;\n const next =\n typeof value === 'function' ? value(getValueByPath(form, path)) : value;\n values.set(path.key, next);\n // The write replaces the whole subtree below it, so descendant keys in\n // the values Map belong to an older generation of that subtree: without\n // this prune they would shadow the new value on exact-key reads and\n // double-apply over it in getValues' insertion-ordered merge (a stale\n // `a.b` would survive a fresh `a` write, or corrupt an array branch\n // into an object when applied later).\n pruneDescendantKeys(values, path);\n reviveBranch(deleted, path);\n // Baselines under the replaced subtree die with it — before the emit, so\n // subscribers reading dirty state inside the emission never see a stale\n // commit suppressing the write they are being told about.\n pruneDirtyBaselines(form, path);\n if (options?.shouldDirty === false) setDirtyBaseline(form, path, next);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n if (options?.shouldTouch) setTouchedByPath(form, path);\n if (options?.shouldValidate) form.validators.get(path.key)?.();\n emit(emitter, 'change', path);\n}\n\n/**\n * The write of {@link setValueByPath} minus the `'change'` emit: the\n * render-time {@link useField} `initialValue` seed. The field's first\n * paint (SSR included — effects never run on the server) must already\n * carry the value, so the write happens during render where emitting is\n * illegal; the seeding field announces it from its post-commit effect\n * through {@link emitChangeByPath} instead.\n *\n * Everything else matches a plain write: descendant keys of the seeded\n * path are pruned, the branch's tombstones and committed baselines are\n * revived/dropped, and both memo caches are invalidated. Like the effect\n * seed it replaces, the caller guards it to paths with no value yet.\n */\nexport function seedValueByPath(form: Form, path: Path, value: any): void {\n const {values, deleted} = form;\n values.set(path.key, value);\n pruneDescendantKeys(values, path);\n reviveBranch(deleted, path);\n pruneDirtyBaselines(form, path);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n}\n\n/** Announce a {@link seedValueByPath} that happened during render: the\n * payload-carrying `'change'` emit {@link setValueByPath} would have\n * fired, split out so it can run post-commit where emitting is safe.\n * Subscribers that rendered after the seed re-read an unchanged snapshot\n * and bail; subscribers from earlier commits resync. */\nexport function emitChangeByPath({emitter}: Form, path: Path): void {\n emit(emitter, 'change', path);\n}\n\n/** Per-form registry of mounted fields' validation-mode overrides: path\n * key -> the field's `mode` option (undefined = follow {@link Form.mode})\n * plus an owner token so competing mounts at one path clean up safely.\n * Presence of an entry is the \"a field is mounted at this path\" signal\n * that routes {@link changeValueByPath} into the gated user-change\n * pipeline ({@link userChangeByPath}). Held in a WeakMap so the Form\n * shape carries only plain state fields. */\n/** Snapshot of one field's aggregated state, as {@link getFieldState}\n * returns it. `errors` is the stored array shared with the form — treat it\n * as read-only, like every {@link getFieldErrors} result. */\nexport type FieldState<T = any> = {\n value: T;\n error: FieldError | undefined;\n errors: FieldError[];\n isDirty: boolean;\n isTouched: boolean;\n isValidating: boolean;\n};\n\n/**\n * Get one field's aggregated state: the layered value ({@link getValue}),\n * the first error ({@link getError}) and every error ({@link\n * getFieldErrors}), dirtiness, the touched flag, and whether a validator\n * is in flight. `isDirty` applies the same per-field rule as {@link\n * getDirtyFields}: a live value exists and differs from initialValues at\n * that path (parsedValues never counts — parsing is not an edit).\n *\n * @param form\n * @param name\n */\nexport function getFieldState<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): FieldState<PathValueOf<T, P>> {\n const path = createPath(name);\n const {touched, validating} = form;\n return {\n value: getValueByPath(form, path),\n error: getErrorByPath(form, path),\n errors: getFieldErrorsByPath(form, path),\n // The shared per-field rule (committed baselines included): the field\n // is dirty while its live value differs from its effective baseline.\n isDirty: isFieldDirtyByPath(form, path),\n isTouched: touched.has(path.key),\n isValidating: validating.has(path.key)\n };\n}\n\n/**\n * Remove a field: by default its live value drops out of reads and\n * `getValues()` (the path is tombstoned, so it never falls back to\n * initialValues), its dirty baseline, touched flag and errors are cleared.\n * The keep-flags preserve slices of that state instead.\n *\n * @param form\n * @param name\n */\n/**\n * Options accepted by {@link removeField}. All flags default to `false` —\n * the historical remove semantics (value dropped, path tombstoned, dirty\n * baseline/touched/errors cleared). Names mirror react-hook-form's\n * `unregister` options to ease migration; RHF's `shouldValidate` and\n * `keepDefaultValue` have no counterparts (removal never validates, and\n * the tombstone is exactly the \"do not revive from initialValues\" choice).\n */\nexport type RemoveFieldOptions = {\n /** Keep the field's live value and dirty baseline instead of\n * tombstoning: reads and `getValues()` keep returning the value, submit\n * includes it, and dirtiness against initialValues is preserved. */\n keepValue?: boolean;\n /** Keep the field's dirty baseline. Implies `keepValue` — a removed\n * value has nothing to be dirty about. */\n keepDirty?: boolean;\n /** Keep the field's touched flag instead of clearing it. */\n keepTouched?: boolean;\n /** Keep the field's errors instead of clearing them. */\n keepError?: boolean;\n};\n\nexport function removeField(\n form: Form,\n name: Name,\n options?: RemoveFieldOptions\n): void {\n removeFieldByPath(form, createPath(name), options);\n}\n\n/**\n * Remove field\n * @param form\n * @param path\n * @param options keep-flags to preserve slices of state through the removal\n */\nexport function removeFieldByPath(\n form: Form,\n path: Path,\n options?: RemoveFieldOptions\n): void {\n const {key, value: segments} = path;\n const {emitter, values, touched, errors, validating, deleted} = form;\n if (!options?.keepValue && !options?.keepDirty) {\n values.delete(key);\n // The field is gone; a remount starts fresh rather than inheriting a\n // baseline committed by the previous incarnation.\n clearDirtyBaselines(form, key);\n // Tombstone the unregistered path so later reads do not fall back to\n // initialValues and \"revive\" the field's old initial value. A tombstone\n // never shadows live values: skip it when the branch is already covered\n // by a live ancestor key (e.g. a FieldArray rewrite stored the whole\n // array at the parent path) or a still-mounted descendant key.\n if (!hasLiveBranch(values, segments)) deleted.add(key);\n }\n if (!options?.keepTouched) touched.delete(key);\n if (!options?.keepError) errors.delete(key);\n validating.delete(key);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n // Path-payload emits, scoped exactly like the writes above: every\n // mutation is bounded to this path's key (exact deletes in the four\n // stores, an exact-key tombstone), so the same matching the write sites\n // use decides who re-syncs. Leaf watchers on the path and BELOW it wake\n // (their reads fall back through the removed key), branch watchers on\n // ancestors wake (their subtree lost a leaf — the wizard/tab unmount\n // case), and global listeners (`on`, useWatch aggregates like\n // useDirtyFields/getValues readers) wake regardless — an emit with a\n // payload still reaches every plain listener. Sibling fields stay\n // asleep: unmounting one tab's fields no longer re-renders every other\n // field's subscriber.\n emit(emitter, 'change', path);\n emit(emitter, 'touched', path);\n emit(emitter, 'errors', path);\n emit(emitter, 'validating', path);\n}\n\n/**\n * Does a live value cover the branch at `segments` -- either at an ancestor\n * key or below it at a descendant key?\n */\nfunction hasLiveBranch(\n values: Map<string, any>,\n segments: PathSegments\n): boolean {\n for (let i = 1; i < segments.length; i++) {\n if (values.has(JSON.stringify(segments.slice(0, i)))) return true;\n }\n const stem = `${JSON.stringify(segments).slice(0, -1)},`;\n for (const key of values.keys()) {\n if (key.startsWith(stem)) return true;\n }\n return false;\n}\n\n/**\n * Writing a value replaces the subtree below the written path, so drop the\n * values Map keys under it: they were set against an older generation of\n * that subtree and would otherwise shadow the fresh value (exact-key reads\n * in {@link getValueByPath}) or re-apply over it (getValues' merge).\n * Deleting while iterating `keys()` is safe for a Map.\n */\nfunction pruneDescendantKeys(values: Map<string, any>, {key}: Path): void {\n if (!values.size) return;\n const stem = `${key.slice(0, -1)},`;\n for (const k of values.keys()) {\n if (k.startsWith(stem)) values.delete(k);\n }\n}\n\n/**\n * Writing a value revives its whole branch: drop any removal tombstone for\n * the path itself, its ancestors, or its descendants (a remounted field\n * overwrites its own tombstone; rewriting a parent array supersedes the\n * tombstones of shifted child paths).\n */\nfunction reviveBranch(deleted: Set<string>, {key}: Path): void {\n if (!deleted.size) return;\n for (const tombstone of deleted) {\n if (\n tombstone === key ||\n tombstone.startsWith(`${key.slice(0, -1)},`) ||\n key.startsWith(`${tombstone.slice(0, -1)},`)\n ) {\n deleted.delete(tombstone);\n }\n }\n}\n\n/**\n * Set form initialValues\n *\n * Content-based early return: a new reference with equal content (the\n * re-rendered inline literal) is a no-op, so committed edits survive, while\n * genuinely changed content swaps the baseline and re-seeds — live values\n * and tombstones are cleared, touched flags and errors survive.\n * @param form\n * @param initialValues\n */\nexport function setInitialValues(form: Form, initialValues: any): void {\n if (\n form.initialValues === initialValues ||\n isEqual(form.initialValues, initialValues)\n ) {\n return;\n }\n form.initialValues = initialValues;\n // A new baseline invalidates the previous schema parse.\n form.parsedValues = undefined;\n form.values.clear();\n form.deleted.clear();\n // ...and every baseline committed against the old one.\n clearDirtyBaselines(form);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n emit(form.emitter, 'change');\n}\n\n/** Options accepted by {@link reset}. Every flag defaults to `false` —\n * omitting the object (or any flag) keeps the plain full-reset behavior.\n * Names mirror react-hook-form's reset options to ease migration. */\nexport type ResetOptions = {\n /** Keep the current values of fields that are dirty — differ from the\n * pre-reset initialValues (the same rule {@link getDirtyFields} applies).\n * Clean fields fall back to the new initialValues as usual. */\n keepDirtyValues?: boolean;\n /** Keep every field's current live value instead of returning to the\n * baseline (react-hook-form's `keepValues` — a strict superset of\n * `keepDirtyValues`, which only preserves dirty fields' values).\n * Dirtiness is recomputed against the post-reset baseline, so kept\n * values that differ from a newly provided baseline count as dirty. */\n keepValues?: boolean;\n /** Ignore a newly provided `initialValues` argument and keep the current\n * baseline — fields still return to it (react-hook-form's\n * `keepDefaultValues`). */\n keepDefaultValues?: boolean;\n /** Keep the touched set instead of clearing it. */\n keepTouched?: boolean;\n /** Keep field errors instead of clearing them. */\n keepErrors?: boolean;\n /** Keep the submitted flag (`isSubmitted`) instead of clearing it —\n * react-hook-form's `keepIsSubmitted`. */\n keepIsSubmitted?: boolean;\n /** Keep the last submit's success flag (`isSubmitSuccessful`) instead of\n * clearing it. */\n keepIsSubmitSuccessful?: boolean;\n /** Keep `submitCount` instead of resetting it to 0. */\n keepSubmitCount?: boolean;\n /** Keep `isSubmitting` instead of resetting it to false. */\n keepIsSubmitting?: boolean;\n};\n\n/** Collect every leaf path of the merged values tree into `out` —\n * structured segments (numeric for array indexes) so each leaf can be\n * written back with setValueByPath. Objects with no enumerable keys\n * (Date, File, plain empty objects) are leaves themselves. */\nfunction collectValueLeaves(\n node: any,\n segments: PathSegments,\n out: {segments: PathSegments; value: any}[]\n): void {\n if (node !== null && typeof node === 'object') {\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n collectValueLeaves(node[i], [...segments, i], out);\n }\n return;\n }\n const keys = Object.keys(node);\n if (keys.length > 0) {\n for (const k of keys) {\n collectValueLeaves(node[k], [...segments, k], out);\n }\n return;\n }\n }\n out.push({segments, value: node});\n}\n\n/**\n * Reset form\n * @param form\n * @param initialValues new baseline — omitted (or undefined), the form\n * keeps its current initialValues and fields simply return to them\n * (react-hook-form's reset-without-values semantics)\n * @param options keep-flags to preserve slices of state through the reset\n */\nexport function reset(\n form: Form,\n initialValues?: any,\n options?: ResetOptions\n): void {\n // Snapshot the live values being preserved before the wipe: dirtiness\n // is measured against the pre-reset initialValues, so capture must\n // happen before form.values and form.initialValues are touched. The\n // snapshot carries structured segments, not dotted strings — a name\n // segment may itself contain '.' or quotes, and the dotted spelling does\n // not round-trip through the parser (dotted keys stay display-only, like\n // getDirtyFields' output). keepValues keeps every live value; the older\n // keepDirtyValues narrows the same snapshot to fields whose value\n // differs from their effective baseline.\n const keptValues: {segments: PathSegments; value: any}[] = [];\n if (options?.keepValues) {\n // Every leaf of the CURRENT merged tree — live edits and clean\n // baseline fields alike — is written back after the wipe, so a field\n // that never had a live edit keeps its pre-reset value instead of\n // adopting the new baseline's.\n collectValueLeaves(getValues(form), [], keptValues);\n } else if (options?.keepDirtyValues) {\n for (const [key, value] of form.values) {\n const segments = JSON.parse(key) as PathSegments;\n // Same predicate as getDirtyFields/forEachDirtyField: a live value\n // differing from its effective baseline (committed baselines read\n // clean and are not kept).\n if (getDirtyBaseline(form, key, segments) !== value) {\n keptValues.push({segments, value});\n }\n }\n }\n // Omitting values is a return-to-initialValues reset, not a wipe: an\n // undefined baseline would make getValues() return undefined (and every\n // consumer of it crash), so the current baseline survives when no new\n // one is provided.\n form.initialValues = options?.keepDefaultValues\n ? form.initialValues\n : (initialValues ?? form.initialValues);\n // The fresh baseline drops any schema parse from the previous cycle.\n form.parsedValues = undefined;\n if (!options?.keepErrors) clearErrors(form);\n const {emitter, touched, values, deleted, validating} = form;\n values.clear();\n deleted.clear();\n clearDirtyBaselines(form);\n if (!options?.keepTouched) touched.clear();\n validating.clear();\n if (!options?.keepIsSubmitting) form.isSubmitting = false;\n if (!options?.keepSubmitCount) form.submitCount = 0;\n if (!options?.keepIsSubmitted) form.isSubmitted = false;\n if (!options?.keepIsSubmitSuccessful) form.isSubmitSuccessful = undefined;\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n // Write the kept values back over the fresh baseline: plain\n // setValueByPath, so no validation fires and nothing is marked touched.\n for (const {segments, value} of keptValues) {\n setValueByPath(form, createPath(segments), value);\n }\n emit(emitter, 'change');\n emit(emitter, 'touched');\n emit(emitter, 'validating');\n emit(emitter, 'submitting');\n emit(emitter, 'submitCount');\n emit(emitter, 'submitSuccessful');\n emit(emitter, 'reset');\n}\n\n/** Options accepted by {@link resetField}. The flags default to `false`;\n * `value` has no default — omitted, the field falls back to initialValues;\n * provided, the explicit value becomes the live value with no fallback at\n * all. Mirrors react-hook-form's resetField options (`value` plays their\n * `defaultValue`'s role) to ease migration. */\nexport type ResetFieldOptions = {\n /** Keep the field's touched flag instead of clearing it. */\n keepTouched?: boolean;\n /** Keep the field's errors instead of clearing them. */\n keepErrors?: boolean;\n /** Explicit post-reset value for the field — never falls back to\n * initialValues. */\n value?: any;\n};\n\n/**\n * Reset a single field: drop its live value (reads fall back to the\n * baseline — initialValues, or the schema's parsed output when one\n * exists, in which case the path is removed from parsedValues and the\n * initial value pinned back so the field reads initialValues again),\n * clear its touched flag and errors, and revive the path's removal\n * tombstones — the inverse of {@link removeFieldByPath}. Other fields\n * and the submission flags are untouched; see {@link reset} for the\n * form-wide counterpart.\n *\n * @param form\n * @param name\n * @param options\n */\nexport function resetField<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P, options?: ResetFieldOptions): void {\n const path = createPath(name);\n const {emitter, values, touched, errors, deleted} = form;\n values.delete(path.key);\n // The field returns to its baseline; commits from before the reset no\n // longer shadow the comparison.\n clearDirtyBaselines(form, path.key);\n // A parse baseline wholesale-shadows initialValues in reads (see\n // getValues), so unset alone would read the path as undefined. Remove\n // the path from the tree (immutable — parsedValues shares branches with\n // the schema's own output) and pin the initial value back as the live\n // value: equal to initialValues, so the field stays clean.\n if (form.parsedValues !== undefined) {\n form.parsedValues = unset(form.parsedValues, path.value);\n const initial = get(form.initialValues, path.value);\n if (initial !== undefined) values.set(path.key, initial);\n }\n if (options && 'value' in options) {\n values.set(path.key, options.value);\n }\n // A reset re-registers the branch, same as a write: tombstones on the\n // path or around it stop applying.\n reviveBranch(deleted, path);\n // Payload-less by design (unlike removeFieldByPath, whose mutations are\n // key-bounded): reviveBranch can un-tombstone ancestor or descendant\n // paths, whose readers must re-sync too.\n emit(emitter, 'change');\n if (!options?.keepTouched && touched.delete(path.key)) {\n emit(emitter, 'touched', path);\n }\n if (!options?.keepErrors && errors.delete(path.key)) {\n emit(emitter, 'errors', path);\n }\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n}\n\n/**\n * @param form\n */\n"],"names":["computeValues","form","initialValues","parsedValues","values","deleted","owned","Set","merged","key","value","setOwned","JSON","parse","unset","setValueByPath","path","options","emitter","next","segments","has","get","i","length","ancestorKey","stringify","slice","getValueByPath","set","size","stem","k","keys","startsWith","delete","pruneDescendantKeys","tombstone","reviveBranch","pruneDirtyBaselines","bumpDirtyVersion","bumpValuesVersion","emit","cache","valuesCaches","version","result","keptValues","clearErrors","touched","validating","clear","clearDirtyBaselines","isSubmitting","submitCount","isSubmitted","isSubmitSuccessful","createPath","isEqual"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{emit as e}from"@for-fun/event-emitter";import{v as t,j as s,k as i,l as a,m as n,c as r,o as l,u,p as o,q as c,r as f}from"./errors-CrQBddrJ.mjs";function d(e){let s=t.get(e);return s?s.version>0&&(s.result=m(e),s.version=0):(s={version:0,result:m(e)},t.set(e,s)),s.result}function m(e){const{initialValues:t,parsedValues:s,values:i,deleted:a}=e,n=new Set;let r=s??t;for(const[e,t]of i)r=l(r,JSON.parse(e),t,n);for(const e of a)r=u(r,JSON.parse(e));return r}function v(t,s,i,r){const{emitter:l,values:u,deleted:c}=t,d="function"==typeof i?i(function({initialValues:e,parsedValues:t,values:s,deleted:i},a){const{key:n,value:r}=a;if(s.has(n))return s.get(n);if(!i.has(n)){for(let e=r.length-1;e>0;e--){const t=JSON.stringify(r.slice(0,e));if(s.has(t))return f(s.get(t),r.slice(e))}return f(t??e,r)}}(t,s)):i;u.set(s.key,d),function(e,{key:t}){if(!e.size)return;const s=`${t.slice(0,-1)},`;for(const t of e.keys())t.startsWith(s)&&e.delete(t)}(u,s),function(e,{key:t}){if(!e.size)return;for(const s of e)(s===t||s.startsWith(`${t.slice(0,-1)},`)||t.startsWith(`${s.slice(0,-1)},`))&&e.delete(s)}(c,s),o(t,s),a(t),n(t),e(l,"change",s)}function g(t,s){t.initialValues===s||c(t.initialValues,s)||(t.initialValues=s,t.parsedValues=void 0,t.values.clear(),t.deleted.clear(),i(t),a(t),n(t),e(t.emitter,"change"))}function h(t,l,u){const o=[];t.initialValues=l??t.initialValues,t.parsedValues=void 0,s(t);const{emitter:c,touched:f,values:d,deleted:m,validating:g}=t;d.clear(),m.clear(),i(t),f.clear(),g.clear(),t.isSubmitting=!1,t.submitCount=0,t.isSubmitted=!1,t.isSubmitSuccessful=void 0,a(t),n(t);for(const{segments:e,value:s}of o)v(t,r(e),s);e(c,"change"),e(c,"touched"),e(c,"validating"),e(c,"submitting"),e(c,"submitCount"),e(c,"submitSuccessful"),e(c,"reset")}export{d as g,h as r,g as s};
|
|
2
|
+
//# sourceMappingURL=values-DRY-a32G.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values-DRY-a32G.mjs","sources":["../src/core/values.ts"],"sourcesContent":["import {emit} from '../emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport type {FieldPath, PathValueOf} from '../types';\nimport {freezeValues, get, isEqual, setOwned, unset} from '../util';\nimport type {FieldError, Form} from '../form';\nimport {clearErrors, getErrorByPath, getFieldErrorsByPath} from './errors';\nimport {isFieldDirtyByPath} from './dirty';\nimport {setTouchedByPath} from './touched';\nimport {\n bumpDirtyVersion,\n bumpValuesVersion,\n clearDirtyBaselines,\n getDirtyBaseline,\n pruneDirtyBaselines,\n setDirtyBaseline,\n valuesCaches\n} from './internals';\n\n/** Dev-only flag, replaced at build time (rollup.config.js `replace`);\n * defined for the test environment in vitest.config.ts. */\ndeclare const __DEV__: boolean;\n\n/**\n * Get form values: the values Map layered over parsedValues (when a schema\n * validation produced them) layered over initialValues.\n *\n * Merged with copy-on-write ownership tracking ({@link setOwned}): every\n * distinct container on a written path is allocated once and shared by all\n * paths through it, instead of re-copying the whole branch for every key.\n * One owned set spans the whole merge, so containers borrowed from the\n * parsedValues tree are copied before mutation exactly like initialValues\n * ones. The result is a freshly merged tree per mutation, with untouched\n * branches sharing references with the baseline exactly like chained\n * `set` did.\n *\n * Memoized per form like {@link getDirtyFields}: every value write bumps a\n * `version` counter ({@link bumpValuesVersion}) while reads reset it, so\n * consecutive reads hand back the same reference (submit, changeValue and\n * form-level validate all read the whole tree, often several times per\n * interaction). Treat the result as read-only — the next read after a\n * write returns a fresh tree, but between writes the cached one is shared\n * with every other reader.\n *\n * parsedValues is the schema's complete output tree: once validation\n * succeeds it replaces the initialValues baseline (fields the schema\n * dropped disappear), while live edits in the values Map still win over\n * both. It never affects dirty state — {@link isDirty} and\n * {@link getDirtyFields} compare live edits against initialValues only,\n * because parsing is not a user edit.\n *\n * @param form\n */\nexport function getValues<T extends Record<string, any> = any>(\n form: Form<T>\n): T {\n let cache = valuesCaches.get(form);\n if (!cache) {\n cache = {version: 0, result: computeValues(form)};\n valuesCaches.set(form, cache);\n } else if (cache.version > 0) {\n cache.result = computeValues(form);\n cache.version = 0;\n }\n return cache.result as T;\n}\n\nfunction computeValues(form: Form): any {\n const {initialValues, parsedValues, values, deleted} = form;\n const owned = new Set<object>();\n let merged = parsedValues ?? initialValues;\n for (const [key, value] of values) {\n merged = setOwned(merged, JSON.parse(key), value, owned);\n }\n // Unregistered fields leave a tombstone in `deleted`; remove those paths\n // from the merged result so they don't fall back to initialValues. unset\n // is immutable (set() shares untouched branches with initialValues, so a\n // mutating delete would corrupt them) and deletes the key outright rather\n // than writing undefined, which would leave `a: undefined` entries behind\n // in anything that spreads getValues().\n for (const key of deleted) {\n merged = unset(merged, JSON.parse(key));\n }\n // DEV-only: hand back a frozen snapshot (a clone — freezing the merged\n // tree in place would also freeze the initialValues/parsedValues\n // containers it borrows from). Consumer mutations then throw at the\n // offending site instead of silently corrupting the shared cache.\n return __DEV__ ? freezeValues(merged) : merged;\n}\n\n/**\n * Get field value\n * @param form\n * @param name\n */\nexport function getValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): PathValueOf<T, P> {\n return getValueByPath(form, createPath(name));\n}\n\n/**\n * Get field value by path\n * @param form\n * @param path\n */\nexport function getValueByPath(\n {initialValues, parsedValues, values, deleted}: Form,\n path: Path\n): any {\n const {key, value: segments} = path;\n if (values.has(key)) return values.get(key);\n // Unregistered path: the tombstone blocks the initialValues fallback.\n if (deleted.has(key)) return undefined;\n // A live ancestor key is a whole-branch write (setValue at a parent\n // path, every useFieldArray operation): it replaces the subtree below\n // it, the same way getValues' merge layers it over the baseline, so\n // reads under it resolve from that stored value instead of falling\n // back to the pre-edit initialValues snapshot. Nearest ancestor first:\n // a finer write is layered over a coarser one (setValueByPath drops the\n // superseded descendant keys), so the closest live ancestor is the\n // newest generation. Paths the ancestor's value does not carry read\n // undefined — the baseline must not fill holes inside a replaced\n // branch.\n for (let i = segments.length - 1; i > 0; i--) {\n const ancestorKey = JSON.stringify(segments.slice(0, i));\n if (values.has(ancestorKey)) {\n return get(values.get(ancestorKey), segments.slice(i));\n }\n }\n // Same layering as getValues: parsed values (when present) are the\n // baseline above initialValues.\n return get(parsedValues ?? initialValues, segments);\n}\n\n/** Options accepted by {@link setValue} / {@link setValueByPath} / {@link\n * changeValue} / {@link changeValueByPath}. `shouldValidate`/`shouldTouch`\n * default to `false`; omitting the options object entirely keeps the plain\n * set-value behavior (no validation, no touched marking, dirty stays\n * derived). */\nexport type SetFieldOptions = {\n /** Run the field's registered validator (if any) after the value lands,\n * same as triggering that single field. Defaults to `false`. */\n shouldValidate?: boolean;\n /** Mark the field as touched. Defaults to `false`. */\n shouldTouch?: boolean;\n /** Land the value as a commit instead of an edit: the value becomes the\n * field's dirty-comparison baseline, so `getDirtyFields`/`isDirty`/\n * `getFieldState().isDirty` read the field as clean, and a later write\n * dirties it only by differing from the new baseline. `true` (or\n * omitting the flag) keeps the default derived behavior — dirty while\n * the live value differs from initialValues. */\n shouldDirty?: boolean;\n};\n\n/**\n * Set field value. The value may also be an updater function receiving\n * the field's current value and returning the next one (TanStack Form's\n * `setFieldValue` contract) — handy for increments and array transforms:\n * `setValue(form, 'count', c => c + 1)`. The tradeoff: a function can\n * never itself be stored as a field value through this function.\n * @param form\n * @param name\n * @param value\n * @param options\n */\nexport function setValue<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(\n form: Form<T>,\n name: P,\n value: PathValueOf<T, P> | ((prev: PathValueOf<T, P>) => PathValueOf<T, P>),\n options?: SetFieldOptions\n): void {\n setValueByPath(form, createPath(name), value, options);\n}\n\n/**\n * Set field value. The value may also be an updater function receiving\n * the field's current value and returning the next one (TanStack Form's\n * `setFieldValue` contract) — note that a function can therefore never\n * itself be stored as a field value through this function.\n * @param form\n * @param path\n * @param value\n * @param options\n */\nexport function setValueByPath(\n form: Form,\n path: Path,\n value: any | ((prev: any) => any),\n options?: SetFieldOptions\n): void {\n const {emitter, values, deleted} = form;\n const next =\n typeof value === 'function' ? value(getValueByPath(form, path)) : value;\n values.set(path.key, next);\n // The write replaces the whole subtree below it, so descendant keys in\n // the values Map belong to an older generation of that subtree: without\n // this prune they would shadow the new value on exact-key reads and\n // double-apply over it in getValues' insertion-ordered merge (a stale\n // `a.b` would survive a fresh `a` write, or corrupt an array branch\n // into an object when applied later).\n pruneDescendantKeys(values, path);\n reviveBranch(deleted, path);\n // Baselines under the replaced subtree die with it — before the emit, so\n // subscribers reading dirty state inside the emission never see a stale\n // commit suppressing the write they are being told about.\n pruneDirtyBaselines(form, path);\n if (options?.shouldDirty === false) setDirtyBaseline(form, path, next);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n if (options?.shouldTouch) setTouchedByPath(form, path);\n if (options?.shouldValidate) form.validators.get(path.key)?.();\n emit(emitter, 'change', path);\n}\n\n/**\n * The write of {@link setValueByPath} minus the `'change'` emit: the\n * render-time {@link useField} `initialValue` seed. The field's first\n * paint (SSR included — effects never run on the server) must already\n * carry the value, so the write happens during render where emitting is\n * illegal; the seeding field announces it from its post-commit effect\n * through {@link emitChangeByPath} instead.\n *\n * Everything else matches a plain write: descendant keys of the seeded\n * path are pruned, the branch's tombstones and committed baselines are\n * revived/dropped, and both memo caches are invalidated. Like the effect\n * seed it replaces, the caller guards it to paths with no value yet.\n */\nexport function seedValueByPath(form: Form, path: Path, value: any): void {\n const {values, deleted} = form;\n values.set(path.key, value);\n pruneDescendantKeys(values, path);\n reviveBranch(deleted, path);\n pruneDirtyBaselines(form, path);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n}\n\n/** Announce a {@link seedValueByPath} that happened during render: the\n * payload-carrying `'change'` emit {@link setValueByPath} would have\n * fired, split out so it can run post-commit where emitting is safe.\n * Subscribers that rendered after the seed re-read an unchanged snapshot\n * and bail; subscribers from earlier commits resync. */\nexport function emitChangeByPath({emitter}: Form, path: Path): void {\n emit(emitter, 'change', path);\n}\n\n/** Per-form registry of mounted fields' validation-mode overrides: path\n * key -> the field's `mode` option (undefined = follow {@link Form.mode})\n * plus an owner token so competing mounts at one path clean up safely.\n * Presence of an entry is the \"a field is mounted at this path\" signal\n * that routes {@link changeValueByPath} into the gated user-change\n * pipeline ({@link userChangeByPath}). Held in a WeakMap so the Form\n * shape carries only plain state fields. */\n/** Snapshot of one field's aggregated state, as {@link getFieldState}\n * returns it. `errors` is the stored array shared with the form — treat it\n * as read-only, like every {@link getFieldErrors} result. */\nexport type FieldState<T = any> = {\n value: T;\n error: FieldError | undefined;\n errors: FieldError[];\n isDirty: boolean;\n isTouched: boolean;\n isValidating: boolean;\n};\n\n/**\n * Get one field's aggregated state: the layered value ({@link getValue}),\n * the first error ({@link getError}) and every error ({@link\n * getFieldErrors}), dirtiness, the touched flag, and whether a validator\n * is in flight. `isDirty` applies the same per-field rule as {@link\n * getDirtyFields}: a live value exists and differs from initialValues at\n * that path (parsedValues never counts — parsing is not an edit).\n *\n * @param form\n * @param name\n */\nexport function getFieldState<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): FieldState<PathValueOf<T, P>> {\n const path = createPath(name);\n const {touched, validating} = form;\n return {\n value: getValueByPath(form, path),\n error: getErrorByPath(form, path),\n errors: getFieldErrorsByPath(form, path),\n // The shared per-field rule (committed baselines included): the field\n // is dirty while its live value differs from its effective baseline.\n isDirty: isFieldDirtyByPath(form, path),\n isTouched: touched.has(path.key),\n isValidating: validating.has(path.key)\n };\n}\n\n/**\n * Remove a field: by default its live value drops out of reads and\n * `getValues()` (the path is tombstoned, so it never falls back to\n * initialValues), its dirty baseline, touched flag and errors are cleared.\n * The keep-flags preserve slices of that state instead.\n *\n * @param form\n * @param name\n */\n/**\n * Options accepted by {@link removeField}. All flags default to `false` —\n * the historical remove semantics (value dropped, path tombstoned, dirty\n * baseline/touched/errors cleared). Names mirror react-hook-form's\n * `unregister` options to ease migration; RHF's `shouldValidate` and\n * `keepDefaultValue` have no counterparts (removal never validates, and\n * the tombstone is exactly the \"do not revive from initialValues\" choice).\n */\nexport type RemoveFieldOptions = {\n /** Keep the field's live value and dirty baseline instead of\n * tombstoning: reads and `getValues()` keep returning the value, submit\n * includes it, and dirtiness against initialValues is preserved. */\n keepValue?: boolean;\n /** Keep the field's dirty baseline. Implies `keepValue` — a removed\n * value has nothing to be dirty about. */\n keepDirty?: boolean;\n /** Keep the field's touched flag instead of clearing it. */\n keepTouched?: boolean;\n /** Keep the field's errors instead of clearing them. */\n keepError?: boolean;\n};\n\nexport function removeField(\n form: Form,\n name: Name,\n options?: RemoveFieldOptions\n): void {\n removeFieldByPath(form, createPath(name), options);\n}\n\n/**\n * Remove field\n * @param form\n * @param path\n * @param options keep-flags to preserve slices of state through the removal\n */\nexport function removeFieldByPath(\n form: Form,\n path: Path,\n options?: RemoveFieldOptions\n): void {\n const {key, value: segments} = path;\n const {emitter, values, touched, errors, validating, deleted} = form;\n if (!options?.keepValue && !options?.keepDirty) {\n values.delete(key);\n // The field is gone; a remount starts fresh rather than inheriting a\n // baseline committed by the previous incarnation.\n clearDirtyBaselines(form, key);\n // Tombstone the unregistered path so later reads do not fall back to\n // initialValues and \"revive\" the field's old initial value. A tombstone\n // never shadows live values: skip it when the branch is already covered\n // by a live ancestor key (e.g. a FieldArray rewrite stored the whole\n // array at the parent path) or a still-mounted descendant key.\n if (!hasLiveBranch(values, segments)) deleted.add(key);\n }\n if (!options?.keepTouched) touched.delete(key);\n if (!options?.keepError) errors.delete(key);\n validating.delete(key);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n // Path-payload emits, scoped exactly like the writes above: every\n // mutation is bounded to this path's key (exact deletes in the four\n // stores, an exact-key tombstone), so the same matching the write sites\n // use decides who re-syncs. Leaf watchers on the path and BELOW it wake\n // (their reads fall back through the removed key), branch watchers on\n // ancestors wake (their subtree lost a leaf — the wizard/tab unmount\n // case), and global listeners (`on`, useWatch aggregates like\n // useDirtyFields/getValues readers) wake regardless — an emit with a\n // payload still reaches every plain listener. Sibling fields stay\n // asleep: unmounting one tab's fields no longer re-renders every other\n // field's subscriber.\n emit(emitter, 'change', path);\n emit(emitter, 'touched', path);\n emit(emitter, 'errors', path);\n emit(emitter, 'validating', path);\n}\n\n/**\n * Does a live value cover the branch at `segments` -- either at an ancestor\n * key or below it at a descendant key?\n */\nfunction hasLiveBranch(\n values: Map<string, any>,\n segments: PathSegments\n): boolean {\n for (let i = 1; i < segments.length; i++) {\n if (values.has(JSON.stringify(segments.slice(0, i)))) return true;\n }\n const stem = `${JSON.stringify(segments).slice(0, -1)},`;\n for (const key of values.keys()) {\n if (key.startsWith(stem)) return true;\n }\n return false;\n}\n\n/**\n * Writing a value replaces the subtree below the written path, so drop the\n * values Map keys under it: they were set against an older generation of\n * that subtree and would otherwise shadow the fresh value (exact-key reads\n * in {@link getValueByPath}) or re-apply over it (getValues' merge).\n * Deleting while iterating `keys()` is safe for a Map.\n */\nfunction pruneDescendantKeys(values: Map<string, any>, {key}: Path): void {\n if (!values.size) return;\n const stem = `${key.slice(0, -1)},`;\n for (const k of values.keys()) {\n if (k.startsWith(stem)) values.delete(k);\n }\n}\n\n/**\n * Writing a value revives its whole branch: drop any removal tombstone for\n * the path itself, its ancestors, or its descendants (a remounted field\n * overwrites its own tombstone; rewriting a parent array supersedes the\n * tombstones of shifted child paths).\n */\nfunction reviveBranch(deleted: Set<string>, {key}: Path): void {\n if (!deleted.size) return;\n for (const tombstone of deleted) {\n if (\n tombstone === key ||\n tombstone.startsWith(`${key.slice(0, -1)},`) ||\n key.startsWith(`${tombstone.slice(0, -1)},`)\n ) {\n deleted.delete(tombstone);\n }\n }\n}\n\n/**\n * Set form initialValues\n *\n * Content-based early return: a new reference with equal content (the\n * re-rendered inline literal) is a no-op, so committed edits survive, while\n * genuinely changed content swaps the baseline and re-seeds — live values\n * and tombstones are cleared, touched flags and errors survive.\n * @param form\n * @param initialValues\n */\nexport function setInitialValues(form: Form, initialValues: any): void {\n if (\n form.initialValues === initialValues ||\n isEqual(form.initialValues, initialValues)\n ) {\n return;\n }\n form.initialValues = initialValues;\n // A new baseline invalidates the previous schema parse.\n form.parsedValues = undefined;\n form.values.clear();\n form.deleted.clear();\n // ...and every baseline committed against the old one.\n clearDirtyBaselines(form);\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n emit(form.emitter, 'change');\n}\n\n/** Options accepted by {@link reset}. Every flag defaults to `false` —\n * omitting the object (or any flag) keeps the plain full-reset behavior.\n * Names mirror react-hook-form's reset options to ease migration. */\nexport type ResetOptions = {\n /** Keep the current values of fields that are dirty — differ from the\n * pre-reset initialValues (the same rule {@link getDirtyFields} applies).\n * Clean fields fall back to the new initialValues as usual. */\n keepDirtyValues?: boolean;\n /** Keep every field's current live value instead of returning to the\n * baseline (react-hook-form's `keepValues` — a strict superset of\n * `keepDirtyValues`, which only preserves dirty fields' values).\n * Dirtiness is recomputed against the post-reset baseline, so kept\n * values that differ from a newly provided baseline count as dirty. */\n keepValues?: boolean;\n /** Ignore a newly provided `initialValues` argument and keep the current\n * baseline — fields still return to it (react-hook-form's\n * `keepDefaultValues`). */\n keepDefaultValues?: boolean;\n /** Keep the touched set instead of clearing it. */\n keepTouched?: boolean;\n /** Keep field errors instead of clearing them. */\n keepErrors?: boolean;\n /** Keep the submitted flag (`isSubmitted`) instead of clearing it —\n * react-hook-form's `keepIsSubmitted`. */\n keepIsSubmitted?: boolean;\n /** Keep the last submit's success flag (`isSubmitSuccessful`) instead of\n * clearing it. */\n keepIsSubmitSuccessful?: boolean;\n /** Keep `submitCount` instead of resetting it to 0. */\n keepSubmitCount?: boolean;\n /** Keep `isSubmitting` instead of resetting it to false. */\n keepIsSubmitting?: boolean;\n};\n\n/** Collect every leaf path of the merged values tree into `out` —\n * structured segments (numeric for array indexes) so each leaf can be\n * written back with setValueByPath. Objects with no enumerable keys\n * (Date, File, plain empty objects) are leaves themselves. */\nfunction collectValueLeaves(\n node: any,\n segments: PathSegments,\n out: {segments: PathSegments; value: any}[]\n): void {\n if (node !== null && typeof node === 'object') {\n if (Array.isArray(node)) {\n for (let i = 0; i < node.length; i++) {\n collectValueLeaves(node[i], [...segments, i], out);\n }\n return;\n }\n const keys = Object.keys(node);\n if (keys.length > 0) {\n for (const k of keys) {\n collectValueLeaves(node[k], [...segments, k], out);\n }\n return;\n }\n }\n out.push({segments, value: node});\n}\n\n/**\n * Reset form\n * @param form\n * @param initialValues new baseline — omitted (or undefined), the form\n * keeps its current initialValues and fields simply return to them\n * (react-hook-form's reset-without-values semantics)\n * @param options keep-flags to preserve slices of state through the reset\n */\nexport function reset(\n form: Form,\n initialValues?: any,\n options?: ResetOptions\n): void {\n // Snapshot the live values being preserved before the wipe: dirtiness\n // is measured against the pre-reset initialValues, so capture must\n // happen before form.values and form.initialValues are touched. The\n // snapshot carries structured segments, not dotted strings — a name\n // segment may itself contain '.' or quotes, and the dotted spelling does\n // not round-trip through the parser (dotted keys stay display-only, like\n // getDirtyFields' output). keepValues keeps every live value; the older\n // keepDirtyValues narrows the same snapshot to fields whose value\n // differs from their effective baseline.\n const keptValues: {segments: PathSegments; value: any}[] = [];\n if (options?.keepValues) {\n // Every leaf of the CURRENT merged tree — live edits and clean\n // baseline fields alike — is written back after the wipe, so a field\n // that never had a live edit keeps its pre-reset value instead of\n // adopting the new baseline's.\n collectValueLeaves(getValues(form), [], keptValues);\n } else if (options?.keepDirtyValues) {\n for (const [key, value] of form.values) {\n const segments = JSON.parse(key) as PathSegments;\n // Same predicate as getDirtyFields/forEachDirtyField: a live value\n // differing from its effective baseline (committed baselines read\n // clean and are not kept).\n if (getDirtyBaseline(form, key, segments) !== value) {\n keptValues.push({segments, value});\n }\n }\n }\n // Omitting values is a return-to-initialValues reset, not a wipe: an\n // undefined baseline would make getValues() return undefined (and every\n // consumer of it crash), so the current baseline survives when no new\n // one is provided.\n form.initialValues = options?.keepDefaultValues\n ? form.initialValues\n : (initialValues ?? form.initialValues);\n // The fresh baseline drops any schema parse from the previous cycle.\n form.parsedValues = undefined;\n if (!options?.keepErrors) clearErrors(form);\n const {emitter, touched, values, deleted, validating} = form;\n values.clear();\n deleted.clear();\n clearDirtyBaselines(form);\n if (!options?.keepTouched) touched.clear();\n validating.clear();\n if (!options?.keepIsSubmitting) form.isSubmitting = false;\n if (!options?.keepSubmitCount) form.submitCount = 0;\n if (!options?.keepIsSubmitted) form.isSubmitted = false;\n if (!options?.keepIsSubmitSuccessful) form.isSubmitSuccessful = undefined;\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n // Write the kept values back over the fresh baseline: plain\n // setValueByPath, so no validation fires and nothing is marked touched.\n for (const {segments, value} of keptValues) {\n setValueByPath(form, createPath(segments), value);\n }\n emit(emitter, 'change');\n emit(emitter, 'touched');\n emit(emitter, 'validating');\n emit(emitter, 'submitting');\n emit(emitter, 'submitCount');\n emit(emitter, 'submitSuccessful');\n emit(emitter, 'reset');\n}\n\n/** Options accepted by {@link resetField}. The flags default to `false`;\n * `value` has no default — omitted, the field falls back to initialValues;\n * provided, the explicit value becomes the live value with no fallback at\n * all. Mirrors react-hook-form's resetField options (`value` plays their\n * `defaultValue`'s role) to ease migration. */\nexport type ResetFieldOptions = {\n /** Keep the field's touched flag instead of clearing it. */\n keepTouched?: boolean;\n /** Keep the field's errors instead of clearing them. */\n keepErrors?: boolean;\n /** Explicit post-reset value for the field — never falls back to\n * initialValues. */\n value?: any;\n};\n\n/**\n * Reset a single field: drop its live value (reads fall back to the\n * baseline — initialValues, or the schema's parsed output when one\n * exists, in which case the path is removed from parsedValues and the\n * initial value pinned back so the field reads initialValues again),\n * clear its touched flag and errors, and revive the path's removal\n * tombstones — the inverse of {@link removeFieldByPath}. Other fields\n * and the submission flags are untouched; see {@link reset} for the\n * form-wide counterpart.\n *\n * @param form\n * @param name\n * @param options\n */\nexport function resetField<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P, options?: ResetFieldOptions): void {\n const path = createPath(name);\n const {emitter, values, touched, errors, deleted} = form;\n values.delete(path.key);\n // The field returns to its baseline; commits from before the reset no\n // longer shadow the comparison.\n clearDirtyBaselines(form, path.key);\n // A parse baseline wholesale-shadows initialValues in reads (see\n // getValues), so unset alone would read the path as undefined. Remove\n // the path from the tree (immutable — parsedValues shares branches with\n // the schema's own output) and pin the initial value back as the live\n // value: equal to initialValues, so the field stays clean.\n if (form.parsedValues !== undefined) {\n form.parsedValues = unset(form.parsedValues, path.value);\n const initial = get(form.initialValues, path.value);\n if (initial !== undefined) values.set(path.key, initial);\n }\n if (options && 'value' in options) {\n values.set(path.key, options.value);\n }\n // A reset re-registers the branch, same as a write: tombstones on the\n // path or around it stop applying.\n reviveBranch(deleted, path);\n // Payload-less by design (unlike removeFieldByPath, whose mutations are\n // key-bounded): reviveBranch can un-tombstone ancestor or descendant\n // paths, whose readers must re-sync too.\n emit(emitter, 'change');\n if (!options?.keepTouched && touched.delete(path.key)) {\n emit(emitter, 'touched', path);\n }\n if (!options?.keepErrors && errors.delete(path.key)) {\n emit(emitter, 'errors', path);\n }\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n}\n\n/**\n * @param form\n */\n"],"names":["getValues","form","cache","valuesCaches","get","version","result","computeValues","set","initialValues","parsedValues","values","deleted","owned","Set","merged","key","value","setOwned","JSON","parse","unset","setValueByPath","path","options","emitter","next","segments","has","i","length","ancestorKey","stringify","slice","getValueByPath","size","stem","k","keys","startsWith","delete","pruneDescendantKeys","tombstone","reviveBranch","pruneDirtyBaselines","bumpDirtyVersion","bumpValuesVersion","emit","setInitialValues","isEqual","clear","clearDirtyBaselines","reset","keptValues","clearErrors","touched","validating","isSubmitting","submitCount","isSubmitted","isSubmitSuccessful","createPath"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-f0rm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"packageManager": "pnpm@11.4.0",
|
|
5
5
|
"description": "react form",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
@@ -31,6 +31,16 @@
|
|
|
31
31
|
"types": "./dist/devtools/index.d.ts",
|
|
32
32
|
"import": "./dist/devtools/index.mjs",
|
|
33
33
|
"require": "./dist/devtools/index.cjs.js"
|
|
34
|
+
},
|
|
35
|
+
"./server": {
|
|
36
|
+
"types": "./dist/server/index.d.ts",
|
|
37
|
+
"import": "./dist/server/index.mjs",
|
|
38
|
+
"require": "./dist/server/index.cjs.js"
|
|
39
|
+
},
|
|
40
|
+
"./persist": {
|
|
41
|
+
"types": "./dist/persist.d.ts",
|
|
42
|
+
"import": "./dist/persist.mjs",
|
|
43
|
+
"require": "./dist/persist.cjs.js"
|
|
34
44
|
}
|
|
35
45
|
},
|
|
36
46
|
"types": "dist/index.d.ts",
|
|
@@ -64,7 +74,14 @@
|
|
|
64
74
|
"keywords": [
|
|
65
75
|
"react",
|
|
66
76
|
"form",
|
|
67
|
-
"react-form"
|
|
77
|
+
"react-form",
|
|
78
|
+
"react-hook-form",
|
|
79
|
+
"tanstack-form",
|
|
80
|
+
"controlled-form",
|
|
81
|
+
"form-validation",
|
|
82
|
+
"typescript",
|
|
83
|
+
"standard-schema",
|
|
84
|
+
"form-library"
|
|
68
85
|
],
|
|
69
86
|
"author": "wmzy <1256573276@qq.com>",
|
|
70
87
|
"license": "ISC",
|
|
@@ -73,7 +90,7 @@
|
|
|
73
90
|
},
|
|
74
91
|
"homepage": "https://github.com/wmzy/react-f0rm#readme",
|
|
75
92
|
"peerDependencies": {
|
|
76
|
-
"react": ">=
|
|
93
|
+
"react": ">=18.0.0",
|
|
77
94
|
"valibot": ">=1.0.0",
|
|
78
95
|
"yup": ">=0.32.0",
|
|
79
96
|
"zod": ">=3.0.0"
|
|
@@ -94,31 +111,37 @@
|
|
|
94
111
|
"@babel/preset-env": "^8.0.2",
|
|
95
112
|
"@babel/preset-react": "^8.0.1",
|
|
96
113
|
"@babel/register": "^8.0.1",
|
|
97
|
-
"@
|
|
114
|
+
"@emotion/react": "^11.14.0",
|
|
115
|
+
"@emotion/styled": "^11.14.1",
|
|
116
|
+
"@eslint/eslintrc": "^3.3.7",
|
|
98
117
|
"@eslint/js": "^10.0.1",
|
|
118
|
+
"@mui/material": "^7.3.11",
|
|
119
|
+
"@radix-ui/react-label": "^2.1.15",
|
|
99
120
|
"@rollup/plugin-babel": "^7.1.0",
|
|
100
121
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
101
122
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
102
123
|
"@rollup/plugin-replace": "^6.0.3",
|
|
103
124
|
"@rollup/plugin-terser": "^1.0.0",
|
|
104
125
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
105
|
-
"@storybook/addon-docs": "^10.
|
|
106
|
-
"@storybook/addon-links": "^10.
|
|
107
|
-
"@storybook/react": "^10.
|
|
108
|
-
"@storybook/react-vite": "^10.
|
|
126
|
+
"@storybook/addon-docs": "^10.6.0",
|
|
127
|
+
"@storybook/addon-links": "^10.6.0",
|
|
128
|
+
"@storybook/react": "^10.6.0",
|
|
129
|
+
"@storybook/react-vite": "^10.6.0",
|
|
130
|
+
"@tanstack/react-form": "^1.33.5",
|
|
109
131
|
"@testing-library/dom": "^10.4.1",
|
|
110
132
|
"@testing-library/jest-dom": "^7.0.1",
|
|
111
|
-
"@testing-library/react": "^16.3.
|
|
112
|
-
"@testing-library/user-event": "^14.6.
|
|
133
|
+
"@testing-library/react": "^16.3.3",
|
|
134
|
+
"@testing-library/user-event": "^14.6.7",
|
|
113
135
|
"@types/react": "^19.2.18",
|
|
114
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
115
|
-
"@typescript-eslint/parser": "^8.
|
|
116
|
-
"@vitest/coverage-v8": "^
|
|
117
|
-
"@vitest/ui": "^
|
|
136
|
+
"@typescript-eslint/eslint-plugin": "^8.69.0",
|
|
137
|
+
"@typescript-eslint/parser": "^8.69.0",
|
|
138
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
139
|
+
"@vitest/ui": "^5.0.0",
|
|
140
|
+
"antd": "^5.29.3",
|
|
118
141
|
"babel-loader": "^10.1.1",
|
|
119
142
|
"commitizen": "^4.3.2",
|
|
120
143
|
"cross-env": "^10.1.0",
|
|
121
|
-
"eslint": "^10.
|
|
144
|
+
"eslint": "^10.10.0",
|
|
122
145
|
"eslint-config-prettier": "^10.1.8",
|
|
123
146
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
124
147
|
"eslint-plugin-builtin-compat": "0.0.2",
|
|
@@ -127,37 +150,72 @@
|
|
|
127
150
|
"eslint-plugin-prettier": "^5.5.6",
|
|
128
151
|
"eslint-plugin-react": "^7.37.5",
|
|
129
152
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
130
|
-
"eslint-plugin-storybook": "^10.
|
|
131
|
-
"globals": "^17.
|
|
153
|
+
"eslint-plugin-storybook": "^10.6.0",
|
|
154
|
+
"globals": "^17.12.0",
|
|
132
155
|
"husky": "^9.1.7",
|
|
133
156
|
"jsdom": "^30.0.1",
|
|
134
|
-
"lint-staged": "^17.
|
|
157
|
+
"lint-staged": "^17.5.0",
|
|
135
158
|
"prettier": "^3.9.6",
|
|
136
159
|
"react": "^19.2.8",
|
|
137
160
|
"react-dom": "^19.2.8",
|
|
138
|
-
"react-hook-form": "^7.
|
|
161
|
+
"react-hook-form": "^7.87.0",
|
|
139
162
|
"rimraf": "^6.1.3",
|
|
140
|
-
"rollup": "^4.
|
|
163
|
+
"rollup": "^4.63.1",
|
|
141
164
|
"rollup-plugin-dts": "^6.5.1",
|
|
142
165
|
"rollup-plugin-esbuild": "^6.2.1",
|
|
143
166
|
"size-limit": "^13.0.3",
|
|
144
|
-
"storybook": "^10.
|
|
167
|
+
"storybook": "^10.6.0",
|
|
145
168
|
"typescript": "^6.0.3",
|
|
146
|
-
"vitest": "^
|
|
147
|
-
},
|
|
148
|
-
"dependencies": {
|
|
149
|
-
"@for-fun/event-emitter": "^1.0.1",
|
|
150
|
-
"use-sync-external-store": "^1.6.0"
|
|
169
|
+
"vitest": "^5.0.0"
|
|
151
170
|
},
|
|
152
171
|
"size-limit": [
|
|
153
172
|
{
|
|
154
173
|
"path": "dist/index.mjs",
|
|
155
|
-
"limit": "
|
|
174
|
+
"limit": "12 KB",
|
|
175
|
+
"ignore": [
|
|
176
|
+
"@for-fun/event-emitter"
|
|
177
|
+
]
|
|
156
178
|
},
|
|
157
179
|
{
|
|
158
180
|
"path": "dist/index.mjs",
|
|
159
|
-
"limit": "
|
|
181
|
+
"limit": "12.5 KB",
|
|
182
|
+
"gzip": true,
|
|
183
|
+
"ignore": [
|
|
184
|
+
"@for-fun/event-emitter"
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"path": "dist/devtools/index.mjs",
|
|
189
|
+
"limit": "9 KB",
|
|
190
|
+
"gzip": true
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"path": "dist/resolvers/standard-schema.mjs",
|
|
194
|
+
"limit": "1.5 KB",
|
|
195
|
+
"gzip": true
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"path": "dist/resolvers/zod.mjs",
|
|
199
|
+
"limit": "1 KB",
|
|
200
|
+
"gzip": true
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"path": "dist/resolvers/yup.mjs",
|
|
204
|
+
"limit": "1 KB",
|
|
205
|
+
"gzip": true
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"path": "dist/server/index.mjs",
|
|
209
|
+
"limit": "5 KB",
|
|
210
|
+
"gzip": true
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"path": "dist/persist.mjs",
|
|
214
|
+
"limit": "3 KB",
|
|
160
215
|
"gzip": true
|
|
161
216
|
}
|
|
162
|
-
]
|
|
217
|
+
],
|
|
218
|
+
"dependencies": {
|
|
219
|
+
"@for-fun/event-emitter": "^1.1.0"
|
|
220
|
+
}
|
|
163
221
|
}
|
package/dist/form-2_tBkEXU.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function e(e,t){for(var r=arguments.length,n=new Array(r>2?r-2:0),o=2;o<r;o++)n[o-2]=arguments[o];(e.get(t)||[]).forEach(function(e){return e.apply(void 0,n)})}function t(e,t,r){var n=function(e,t){var r=e.get(t);if(r)return r;var n=new Set;return e.set(t,n),n}(e,t);return n.add(r),function(){return n.delete(r)}}const r=new Map;function n(e){if(Array.isArray(e))return e;const t=r.get(e);if(t)return t;const n=function(e){const t=[];let r="";const n=()=>{if(o(r)){const n=[...t.map(String),r].join("."),o=t.reduce((e,t)=>e+("number"==typeof t?`[${t}]`:`${e?".":""}${t}`),"");throw new TypeError(`Numeric path segment must use bracket notation: "${n}" → "${o}[${r}]" (path: ${e})`)}t.push(r),r=""};for(let s=0;s<e.length;s++){const i=e[s];if("."===i)""!==r&&n();else if("["===i){""!==r&&n();const i=e[s+1];if('"'===i||"'"===i){const r=e.indexOf(i,s+2);if(-1===r)throw new TypeError(`Unterminated quote in path: ${e}`);if("]"!==e[r+1])throw new TypeError(`Expected "]" after quoted segment in path: ${e}`);t.push(e.slice(s+2,r)),s=r+1}else{const r=e.indexOf("]",s+1);if(-1===r)throw new TypeError(`Unterminated bracket in path: ${e}`);const n=e.slice(s+1,r);t.push(o(n)?Number(n):n),s=r}}else r+=i}""===r&&0!==t.length||n();return t}(e);if(r.size>=1e4){const e=r.keys().next();e.done||r.delete(e.value)}return r.set(e,n),n}const o=e=>/^-?\d+$/.test(e);function s(e,t){if(!t.length||null==e)return e;const[r,...n]=t;if(n.length){const t=s(e[r],n);return t===e[r]?e:i(e,[r],t)}if(Array.isArray(e)){if(!(r in e))return e;const t=e.slice();return delete t[r],t}if("object"!=typeof e||!(r in e))return e;const o={...e};return delete o[r],o}function i(e,t,r){if(!t.length)return r;const[n,...s]=t,u="number"==typeof n?n:Array.isArray(e)&&"string"==typeof n&&o(n)?Number(n):void 0;if(void 0!==u){const t=Array.isArray(e)?e.slice():[];return t[u]=i(t[u],s,r),t}return{...e,[n]:i(e&&e[n],s,r)}}function u(e,t,r,n){if(!t.length)return r;let s=e,i=null,u="";for(let c=0;c<t.length;c++){const a=t[c];if(!n.has(s)){let t;t="number"==typeof a||Array.isArray(s)&&"string"==typeof a&&o(a)?Array.isArray(s)?s.slice():[]:{...s},n.add(t),0===c?e=t:i[u]=t,s=t}c===t.length-1?s[a]=r:(i=s,u=a,s=s[a])}return e}function c(e){const t=n(e);return{value:t,key:JSON.stringify(t)}}const a=Symbol("validation-outcome"),l=new WeakMap;function f(e){const t=l.get(e);t&&t.version++}function d(e){let t=l.get(e);return t?t.version>0&&(t.result=g(e),t.version=0):(t={version:0,result:g(e)},l.set(e,t)),t.result}function g(e){const{initialValues:t,parsedValues:r,values:n,deleted:o}=e,i=new Set;let c=r??t;for(const[e,t]of n)c=u(c,JSON.parse(e),t,i);for(const e of o)c=s(c,JSON.parse(e));return c}function m(t,r,n,o){const{emitter:s,values:i,deleted:u}=t;i.set(r.key,n),function(e,{key:t}){if(!e.size)return;const r=`${t.slice(0,-1)},`;for(const t of e.keys())t.startsWith(r)&&e.delete(t)}(i,r),function(e,{key:t}){if(!e.size)return;for(const r of e)(r===t||r.startsWith(`${t.slice(0,-1)},`)||t.startsWith(`${r.slice(0,-1)},`))&&e.delete(r)}(u,r),function(e,{key:t}){const r=v.get(e);if(!r?.size)return;const n=`${t.slice(0,-1)},`;for(const e of r.keys())e.startsWith(n)&&r.delete(e)}(t,r),b(t),f(t),e(s,"change",r)}function y({errors:e}){const t=[];for(const[r,n]of e){const e=JSON.parse(r).join(".");for(const{type:r,message:o}of n)t.push({path:e,type:r,message:o})}return t}function p(t,r,n){!function({emitter:t,errors:r},n,o){const s=function(e){if("string"==typeof e)return e?[{type:"custom",message:e}]:void 0;if(O(e))return[e];if(!e)return;const t=[];return e.forEach(e=>{"string"==typeof e&&e?t.push({type:"custom",message:e}):O(e)&&t.push(e)}),t.length?t:void 0}(o);s?r.set(n.key,s):r.delete(n.key);e(t,"errors",n)}(t,c(r),n)}const v=new WeakMap;function h(e,t,r){const n=v.get(e);return n?.has(t)?n.get(t):(o=e.initialValues,r.reduce((e,t)=>{if(null!=e)return e[t]},o));var o}const w=new WeakMap;function b(e){const t=w.get(e);t&&t.version++}function k(e){const t={};return function(e,t){for(const[r,n]of e.values){const o=JSON.parse(r);h(e,r,o)!==n&&t(o.join("."))}}(e,e=>{t[e]=!0}),t}function A(e){let t=w.get(e);if(t){if(t.version>0){const r=k(e);(function(e,t){const r=Object.keys(e);return r.length===Object.keys(t).length&&r.every(e=>!0===t[e])})(t.result,r)||(t.result=r),t.version=0}}else t={version:0,result:k(e)},w.set(e,t);return t.result}function S({touched:e}){return Array.from(e,e=>JSON.parse(e).join("."))}function $(t,r,n){const o=[];t.initialValues=r,t.parsedValues=void 0,function(t){const{emitter:r,errors:n}=t;n.clear(),e(r,"errors")}(t);const{emitter:s,touched:i,values:u,deleted:a,validating:l}=t;u.clear(),a.clear(),function(e){const t=v.get(e);t&&t.clear()}(t),i.clear(),l.clear(),t.isSubmitting=!1,t.submitCount=0,t.isSubmitSuccessful=void 0,b(t),f(t);for(const{segments:e,value:r}of o)m(t,c(e),r);e(s,"change"),e(s,"touched"),e(s,"validating"),e(s,"submitting"),e(s,"submitCount"),e(s,"submitSuccessful"),e(s,"reset")}async function j(r,n){const o=e=>{return n=r.emitter,o="validating",s=()=>function(e){for(const t of e.validating)if(t!==J)return!1;return!0}(r),i=()=>!1,new Promise((e,r)=>{if(i())return void r();if(s())return void e();const u=t(n,o,()=>{if(i())return u(),void r();s()&&(u(),e())})});var n,o,s,i};return r.validators.forEach(e=>e()),await o(),r.validate&&await function(t){const r=t.validate;if(!r)return Promise.resolve();const n=t.validateDebounce??0;if(n<=0){const e=new AbortController;return Promise.resolve(r(d(t),{form:t,signal:e.signal})).then(e=>{W(t,e)})}const o=function(e){let t=P.get(e);t||(t={timer:null,controller:null,round:null,marked:!1,waiters:[]},P.set(e,t));return t}(t);null!==o.timer?clearTimeout(o.timer):(o.marked=!0,t.validating.add(J),e(t.emitter,"validating"));return o.timer=setTimeout(()=>{o.timer=null;const e=o.round={};(function(e,t,r){const n=e.validate;if(!n)return Promise.resolve();t.controller?.abort();const o=t.controller=new AbortController;let s;try{s=Promise.resolve(n(d(e),{form:e,signal:o.signal}))}catch(e){s=Promise.reject(e)}return s.then(n=>{t.round===r&&W(e,n)},e=>{if(t.round===r)throw e})})(t,o,e).then(()=>T(t,o,e,M),r=>T(t,o,e,r))},n),new Promise((e,t)=>{o.waiters.push({resolve:e,reject:t})})}(r),!function({errors:e}){return e.size>0}(r)}function O(e){return!!e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.message}function N(e,t,r=[],s){Object.entries(t).forEach(([t,i])=>{const u=[...r,...o(t)?[t]:n(t)];"string"==typeof i?i&&(p(e,u,i),E(e,u,s)):Array.isArray(i)||O(i)?(p(e,u,i),E(e,u,s)):i&&"object"==typeof i&&N(e,i,u,s)})}function E(e,t,r){if(!r)return;const n=c(t),o=e.errors.get(n.key);o&&r.set(n.key,o)}function W(t,r){const n=t.validateDeps?function(e){let t=V.get(e);t||(t=new Map,V.set(e,t));return t}(t):void 0;if(n&&(!function(t,r){for(const[n,o]of r){t.errors.get(n)===o&&(t.errors.delete(n),e(t.emitter,"errors",c(JSON.parse(n))))}}(t,n),n.clear()),r){if("object"==typeof r&&a in r){const o=r;return o.errors&&N(t,o.errors,[],n),void function(t,r){void 0!==r&&r!==t.parsedValues&&(t.parsedValues=r,f(t),e(t.emitter,"change"))}(t,o.values)}N(t,r,[],n)}}const V=new WeakMap;const J="__form_validate__";const M=Symbol("form-validate-settled"),P=new WeakMap;function T(t,r,n,o){if(r.round!==n)return;if(r.round=null,null!==r.timer)return;r.marked&&(r.marked=!1,t.validating.delete(J),e(t.emitter,"validating"));const s=r.waiters;r.waiters=[];for(const e of s)o===M?e.resolve():e.reject(o)}export{a as V,A as a,d as b,y as c,S as g,t as o,$ as r,j as t};
|
|
2
|
-
//# sourceMappingURL=form-2_tBkEXU.mjs.map
|