react-f0rm 1.3.0 → 1.4.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 +88 -1143
- package/devtools.d.ts +1 -0
- package/devtools.js +1 -0
- package/dist/array-3hAjl6Bz.d.ts +160 -0
- package/dist/devtools/index.cjs.js +1 -1
- package/dist/devtools/index.cjs.js.map +1 -1
- package/dist/devtools/index.d.cts +34 -0
- package/dist/devtools/index.d.mts +34 -0
- package/dist/devtools/index.d.ts +3 -2
- package/dist/devtools/index.mjs +1 -1
- package/dist/devtools/index.mjs.map +1 -1
- package/dist/errors-8oPux7GR.cjs.js +2 -0
- package/dist/errors-8oPux7GR.cjs.js.map +1 -0
- package/dist/errors-ClH3eV1R.mjs +2 -0
- package/dist/errors-ClH3eV1R.mjs.map +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.cts +1833 -0
- package/dist/index.d.mts +1833 -0
- package/dist/index.d.ts +372 -117
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +859 -225
- 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 +1 -1
- package/dist/persist.cjs.js.map +1 -1
- package/dist/persist.d.cts +49 -0
- package/dist/persist.d.mts +49 -0
- package/dist/persist.mjs +1 -1
- package/dist/persist.mjs.map +1 -1
- 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.cts +2 -0
- package/dist/resolvers/standard-schema.d.mts +2 -0
- package/dist/resolvers/standard-schema.d.ts +1 -67
- 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.cts +6 -0
- package/dist/resolvers/yup.d.mts +6 -0
- package/dist/resolvers/yup.d.ts +0 -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.cts +6 -0
- package/dist/resolvers/zod.d.mts +6 -0
- package/dist/resolvers/zod.d.ts +0 -1
- package/dist/resolvers/zod.mjs +1 -1
- package/dist/resolvers/zod.mjs.map +1 -1
- package/dist/server/index.cjs.js +1 -1
- package/dist/server/index.cjs.js.map +1 -1
- package/dist/server/index.d.cts +106 -0
- package/dist/server/index.d.mts +106 -0
- package/dist/server/index.d.ts +31 -2
- package/dist/server/index.mjs +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/standard-schema-CYbnugL7.d.ts +1184 -0
- package/dist/standardSchema-BpZvg0tZ.mjs +2 -0
- package/dist/standardSchema-BpZvg0tZ.mjs.map +1 -0
- package/dist/standardSchema-CxV_DhBO.cjs.js +2 -0
- package/dist/standardSchema-CxV_DhBO.cjs.js.map +1 -0
- package/dist/validate-BEjNsNvd.mjs +2 -0
- package/dist/validate-BEjNsNvd.mjs.map +1 -0
- package/dist/validate-BvN6hiEG.cjs.js +2 -0
- package/dist/validate-BvN6hiEG.cjs.js.map +1 -0
- package/dist/values-Bod74Kzu.mjs +2 -0
- package/dist/values-Bod74Kzu.mjs.map +1 -0
- package/dist/values-DKpOCD5J.cjs.js +2 -0
- package/dist/values-DKpOCD5J.cjs.js.map +1 -0
- package/package.json +72 -27
- package/persist.d.ts +1 -0
- package/persist.js +1 -0
- package/resolvers/standard-schema.d.ts +1 -0
- package/resolvers/standard-schema.js +1 -0
- package/resolvers/yup.d.ts +1 -0
- package/resolvers/yup.js +1 -0
- package/resolvers/zod.d.ts +1 -0
- package/resolvers/zod.js +1 -0
- package/server.d.ts +1 -0
- package/server.js +1 -0
- package/dist/errors-BKrUdpfI.cjs.js +0 -2
- package/dist/errors-BKrUdpfI.cjs.js.map +0 -1
- package/dist/errors-CrQBddrJ.mjs +0 -2
- package/dist/errors-CrQBddrJ.mjs.map +0 -1
- package/dist/form-CeKSBs31.d.ts +0 -486
- package/dist/validate-CNtuUhmk.mjs +0 -2
- package/dist/validate-CNtuUhmk.mjs.map +0 -1
- package/dist/validate-Cl4ksNFu.cjs.js +0 -2
- package/dist/validate-Cl4ksNFu.cjs.js.map +0 -1
- package/dist/validate-nksgv1pR.d.ts +0 -272
- package/dist/values-Cu6awQOJ.cjs.js +0 -2
- package/dist/values-Cu6awQOJ.cjs.js.map +0 -1
- package/dist/values-DRY-a32G.mjs +0 -2
- package/dist/values-DRY-a32G.mjs.map +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{V as t,F as n}from"./errors-ClH3eV1R.mjs";function e(t){return!!t&&"object"==typeof t&&"function"==typeof t["~standard"]?.validate}function r(t){return{type:"standard",message:t?.message||"Validation failed"}}function s(t){return async n=>{const e=await t["~standard"].validate(n);if(e.issues?.length)return e.issues.map(r)}}function o(e){return async s=>{const o=await e["~standard"].validate(s),{issues:u}=o;if(!u?.length)return{[t]:!0,values:"value"in o?o.value:void 0};const f={};for(const t of u){const e=a(t);if(e.length)i(f,e,r(t));else{const e=f[n]??=[];Array.isArray(e)&&e.push(r(t))}}return{[t]:!0,errors:c(f)||{}}}}function a(t){const n=t.path||[],e=[];for(const t of n){const n="object"==typeof t&&null!==t?t.key:t;e.push(String(n))}return e}function i(t,n,e){let r=t;for(let t=0;t<n.length-1;t++){const e=n[t];let s=r[e];if(void 0===s&&(s=r[e]={}),!u(s))return;r=s}const s=n[n.length-1],o=r[s];void 0===o?r[s]=[e]:Array.isArray(o)&&o.push(e)}function u(t){return!!t&&"object"==typeof t&&!Array.isArray(t)}function c(t){let n=!1;const e={};return Object.entries(t).forEach(([t,r])=>{if(u(r)){const s=c(r);s&&(e[t]=s,n=!0)}else e[t]=r,n=!0}),n?e:void 0}export{s as a,e as h,o as s};
|
|
2
|
+
//# sourceMappingURL=standardSchema-BpZvg0tZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standardSchema-BpZvg0tZ.mjs","sources":["../src/standardSchema.ts"],"sourcesContent":["/**\n * Standard Schema v1 (https://standardschema.dev) contract and adapters,\n * shared by the resolver entry (`react-f0rm/resolvers/standard-schema`)\n * and the core's direct-schema support (`createForm({validate: schema})`,\n * `useField({validate: schema})`).\n *\n * The types are a structural copy of the spec — zero runtime or type\n * dependencies on any schema library, so zod v3.24+/v4, valibot v1,\n * arktype and every other v1 implementer work without importing them.\n * This module is framework-free and imports nothing but\n * `core/errors`' VALIDATION_OUTCOME brand at runtime.\n */\nimport {FORM_ERROR, VALIDATION_OUTCOME} from './core/errors';\nimport type {FieldError, ValidationOutcome, Validator} from './form';\n\n/** A single Standard Schema issue. */\nexport type StandardSchemaIssue = {\n readonly message: string;\n readonly path?:\n ReadonlyArray<PropertyKey | {readonly key: PropertyKey}> | undefined;\n};\n\n/** The union Standard Schema's `validate` resolves to. */\nexport type StandardSchemaResult<Output = unknown> =\n | {readonly value: Output; readonly issues?: undefined}\n | {readonly issues: ReadonlyArray<StandardSchemaIssue>};\n\n/**\n * Structural Standard Schema v1. `types` is optional in the spec but\n * implemented by zod v3.24+/v4, valibot v1 and arktype — it is what\n * {@link InferSchemaValues} and `createForm`'s `TValues` inference read.\n */\nexport type StandardSchemaV1<Input = unknown, Output = Input> = {\n readonly '~standard': {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (\n value: Input\n ) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n readonly types?: {\n readonly input: Input;\n readonly output: Output;\n };\n };\n};\n\n/**\n * The values type a Standard Schema produces: the `Output` of its\n * `~standard.types`, resolved structurally without importing the schema\n * library. Coercions/transforms land in the output — `z.coerce.date()`\n * infers `Date`, not `string`. `never` when the schema does not expose\n * `types` (pass an explicit `TValues` then).\n *\n * @example\n * ```ts\n * const schema = z.object({email: z.string().email()});\n * type Values = InferSchemaValues<typeof schema>; // {email: string}\n * const form = useForm<InferSchemaValues<typeof schema>>();\n * ```\n */\nexport type InferSchemaValues<S> = S extends {\n readonly '~standard': {readonly types: {readonly output: infer Out}};\n}\n ? Out\n : never;\n\n/**\n * Does the value implement the Standard Schema v1 props? A plain boolean\n * predicate, not a type guard: narrowing a\n * `Fn | StandardSchemaV1<In, Out>` union through the guard fails either\n * way (a generics-narrowing guard is contravariance-blocked, an\n * intersection guard keeps both members) — call sites cast after the\n * check instead.\n */\nexport function hasStandardProps(schema: any): boolean {\n return (\n !!schema &&\n typeof schema === 'object' &&\n typeof schema['~standard']?.validate === 'function'\n );\n}\n\n/** Issue → stored error entry. */\nfunction toFieldError(issue: StandardSchemaIssue | undefined): FieldError {\n return {type: 'standard', message: issue?.message || 'Validation failed'};\n}\n\n/**\n * Field-level Standard Schema adapter: validate a single value with any\n * schema implementing '~standard' and map every issue to a FieldError, so\n * a value breaking several rules surfaces all of them (setErrorByPath\n * stores the array; error/errorObject readers still see the first).\n * The schema's parsed output is validation-only here — field validators\n * never rewrite the value; form-level schemas own coercion.\n */\nexport function schemaToFieldValidator(\n schema: StandardSchemaV1<any, any>\n): Validator {\n return async (value: any) => {\n const result = await schema['~standard'].validate(value);\n if (!result.issues?.length) return undefined;\n return result.issues.map(toFieldError);\n };\n}\n\n/**\n * Form-level Standard Schema adapter: validate the whole values object and\n * return a ValidationOutcome. On failure `errors` carries the nested shape\n * Options.validate expects ({a: {b: FieldError[]}}; ensureValidate flattens\n * it back to per-field errors, keeping every issue of a path). Issues\n * without a path are form-level errors and land on the FORM_ERROR key.\n * On success `values` carries the schema's parsed output (coerce/transform\n * results included), which the form stores as its parsedValues baseline —\n * the layer getValues reads above initialValues.\n *\n * `createForm({validate: schema})` wraps the schema through this adapter\n * automatically (no resolver import needed), and `TValues` infers from\n * the schema's output type.\n */\nexport function schemaToFormValidator<T extends Record<string, any>>(\n schema: StandardSchemaV1<any, T>\n): (values: T) => Promise<ValidationOutcome<T>> {\n return async (values: T) => {\n const result = await schema['~standard'].validate(values);\n const {issues} = result;\n if (!issues?.length) {\n // Success: expose the schema's parsed output. `in` keeps the union\n // narrowed (the success variant is the one carrying `value`).\n return {\n [VALIDATION_OUTCOME]: true,\n values: 'value' in result ? result.value : undefined\n };\n }\n const errors: Record<string, any> = {};\n for (const issue of issues) {\n const segments = toPathSegments(issue);\n if (segments.length) {\n assignAtPath(errors, segments, toFieldError(issue));\n } else {\n // Pathless issues are all form-level: they accumulate on the\n // FORM_ERROR slot instead of the first shadowing the rest. (A\n // nested path literally named like FORM_ERROR would have made the\n // slot a branch — skip then.)\n const slot = (errors[FORM_ERROR] ??= []);\n if (Array.isArray(slot)) slot.push(toFieldError(issue));\n }\n }\n return {[VALIDATION_OUTCOME]: true, errors: pruneEmpty(errors) || {}};\n };\n}\n\n/**\n * Stringify an issue path: PropertyKey or {key} path segments → strings.\n */\nfunction toPathSegments(issue: StandardSchemaIssue): string[] {\n const path = issue.path || [];\n const segments: string[] = [];\n for (const segment of path) {\n const key =\n typeof segment === 'object' && segment !== null\n ? (segment as {key: PropertyKey}).key\n : segment;\n segments.push(String(key));\n }\n return segments;\n}\n\n/**\n * Append the error at a nested path. Leaves are FieldError[] arrays, so\n * several issues on one field accumulate in issue order; an issue whose\n * path conflicts with an existing leaf or crosses it is skipped.\n */\nfunction assignAtPath(\n root: Record<string, any>,\n segments: string[],\n error: FieldError\n): void {\n let node = root;\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i];\n let next = node[segment];\n if (next === undefined) {\n next = node[segment] = {};\n }\n if (!isBranch(next)) return;\n node = next;\n }\n const leaf = segments[segments.length - 1];\n const slot = node[leaf];\n if (slot === undefined) node[leaf] = [error];\n else if (Array.isArray(slot)) slot.push(error);\n}\n\n/**\n * A branch is a plain container built while nesting; the leaves it carries\n * are the FieldError[] arrays assignAtPath appends.\n */\nfunction isBranch(value: any): value is Record<string, any> {\n return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Drop empty branch objects left behind by conflicting issue paths.\n */\nfunction pruneEmpty(\n node: Record<string, any>\n): Record<string, any> | undefined {\n let hasLeaf = false;\n const result: Record<string, any> = {};\n Object.entries(node).forEach(([key, value]) => {\n if (isBranch(value)) {\n const pruned = pruneEmpty(value);\n if (pruned) {\n result[key] = pruned;\n hasLeaf = true;\n }\n } else {\n result[key] = value;\n hasLeaf = true;\n }\n });\n return hasLeaf ? result : undefined;\n}\n"],"names":["hasStandardProps","schema","validate","toFieldError","issue","type","message","schemaToFieldValidator","async","value","result","issues","length","map","schemaToFormValidator","values","VALIDATION_OUTCOME","errors","segments","toPathSegments","assignAtPath","slot","FORM_ERROR","Array","isArray","push","pruneEmpty","path","segment","key","String","root","error","node","i","next","isBranch","leaf","hasLeaf","Object","entries","forEach","pruned"],"mappings":"iDA0EO,SAASA,EAAiBC,GAC/B,QACIA,GACgB,iBAAXA,GACkC,mBAAlCA,EAAO,cAAcC,QAEhC,CAGA,SAASC,EAAaC,GACpB,MAAO,CAACC,KAAM,WAAYC,QAASF,GAAOE,SAAW,oBACvD,CAUO,SAASC,EACdN,GAEA,OAAOO,MAAOC,IACZ,MAAMC,QAAeT,EAAO,aAAaC,SAASO,GAClD,GAAKC,EAAOC,QAAQC,OACpB,OAAOF,EAAOC,OAAOE,IAAIV,GAE7B,CAgBO,SAASW,EACdb,GAEA,OAAOO,MAAOO,IACZ,MAAML,QAAeT,EAAO,aAAaC,SAASa,IAC5CJ,OAACA,GAAUD,EACjB,IAAKC,GAAQC,OAGX,MAAO,CACLI,CAACA,IAAqB,EACtBD,OAAQ,UAAWL,EAASA,EAAOD,WAAQ,GAG/C,MAAMQ,EAA8B,CAAA,EACpC,IAAA,MAAWb,KAASO,EAAQ,CAC1B,MAAMO,EAAWC,EAAef,GAChC,GAAIc,EAASN,OACXQ,EAAaH,EAAQC,EAAUf,EAAaC,QACvC,CAKL,MAAMiB,EAAQJ,EAAOK,KAAgB,GACjCC,MAAMC,QAAQH,MAAYI,KAAKtB,EAAaC,GAClD,CACF,CACA,MAAO,CAACY,CAACA,IAAqB,EAAMC,OAAQS,EAAWT,IAAW,IAEtE,CAKA,SAASE,EAAef,GACtB,MAAMuB,EAAOvB,EAAMuB,MAAQ,GACrBT,EAAqB,GAC3B,IAAA,MAAWU,KAAWD,EAAM,CAC1B,MAAME,EACe,iBAAZD,GAAoC,OAAZA,EAC1BA,EAA+BC,IAChCD,EACNV,EAASO,KAAKK,OAAOD,GACvB,CACA,OAAOX,CACT,CAOA,SAASE,EACPW,EACAb,EACAc,GAEA,IAAIC,EAAOF,EACX,IAAA,IAASG,EAAI,EAAGA,EAAIhB,EAASN,OAAS,EAAGsB,IAAK,CAC5C,MAAMN,EAAUV,EAASgB,GACzB,IAAIC,EAAOF,EAAKL,GAIhB,QAHa,IAATO,IACFA,EAAOF,EAAKL,GAAW,CAAA,IAEpBQ,EAASD,GAAO,OACrBF,EAAOE,CACT,CACA,MAAME,EAAOnB,EAASA,EAASN,OAAS,GAClCS,EAAOY,EAAKI,QACL,IAAThB,EAAoBY,EAAKI,GAAQ,CAACL,GAC7BT,MAAMC,QAAQH,IAAOA,EAAKI,KAAKO,EAC1C,CAMA,SAASI,EAAS3B,GAChB,QAASA,GAA0B,iBAAVA,IAAuBc,MAAMC,QAAQf,EAChE,CAKA,SAASiB,EACPO,GAEA,IAAIK,GAAU,EACd,MAAM5B,EAA8B,CAAA,EAapC,OAZA6B,OAAOC,QAAQP,GAAMQ,QAAQ,EAAEZ,EAAKpB,MAClC,GAAI2B,EAAS3B,GAAQ,CACnB,MAAMiC,EAAShB,EAAWjB,GACtBiC,IACFhC,EAAOmB,GAAOa,EACdJ,GAAU,EAEd,MACE5B,EAAOmB,GAAOpB,EACd6B,GAAU,IAGPA,EAAU5B,OAAS,CAC5B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var t=require("./errors-8oPux7GR.cjs.js");function r(t){return{type:"standard",message:t?.message||"Validation failed"}}function e(t){const r=t.path||[],e=[];for(const t of r){const r="object"==typeof t&&null!==t?t.key:t;e.push(String(r))}return e}function n(t,r,e){let n=t;for(let t=0;t<r.length-1;t++){const e=r[t];let o=n[e];if(void 0===o&&(o=n[e]={}),!s(o))return;n=o}const o=r[r.length-1],a=n[o];void 0===a?n[o]=[e]:Array.isArray(a)&&a.push(e)}function s(t){return!!t&&"object"==typeof t&&!Array.isArray(t)}function o(t){let r=!1;const e={};return Object.entries(t).forEach(([t,n])=>{if(s(n)){const s=o(n);s&&(e[t]=s,r=!0)}else e[t]=n,r=!0}),r?e:void 0}exports.hasStandardProps=function(t){return!!t&&"object"==typeof t&&"function"==typeof t["~standard"]?.validate},exports.schemaToFieldValidator=function(t){return async e=>{const n=await t["~standard"].validate(e);if(n.issues?.length)return n.issues.map(r)}},exports.schemaToFormValidator=function(s){return async a=>{const i=await s["~standard"].validate(a),{issues:c}=i;if(!c?.length)return{[t.VALIDATION_OUTCOME]:!0,values:"value"in i?i.value:void 0};const u={};for(const s of c){const o=e(s);if(o.length)n(u,o,r(s));else{const e=u[t.FORM_ERROR]??=[];Array.isArray(e)&&e.push(r(s))}}return{[t.VALIDATION_OUTCOME]:!0,errors:o(u)||{}}}};
|
|
2
|
+
//# sourceMappingURL=standardSchema-CxV_DhBO.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standardSchema-CxV_DhBO.cjs.js","sources":["../src/standardSchema.ts"],"sourcesContent":["/**\n * Standard Schema v1 (https://standardschema.dev) contract and adapters,\n * shared by the resolver entry (`react-f0rm/resolvers/standard-schema`)\n * and the core's direct-schema support (`createForm({validate: schema})`,\n * `useField({validate: schema})`).\n *\n * The types are a structural copy of the spec — zero runtime or type\n * dependencies on any schema library, so zod v3.24+/v4, valibot v1,\n * arktype and every other v1 implementer work without importing them.\n * This module is framework-free and imports nothing but\n * `core/errors`' VALIDATION_OUTCOME brand at runtime.\n */\nimport {FORM_ERROR, VALIDATION_OUTCOME} from './core/errors';\nimport type {FieldError, ValidationOutcome, Validator} from './form';\n\n/** A single Standard Schema issue. */\nexport type StandardSchemaIssue = {\n readonly message: string;\n readonly path?:\n ReadonlyArray<PropertyKey | {readonly key: PropertyKey}> | undefined;\n};\n\n/** The union Standard Schema's `validate` resolves to. */\nexport type StandardSchemaResult<Output = unknown> =\n | {readonly value: Output; readonly issues?: undefined}\n | {readonly issues: ReadonlyArray<StandardSchemaIssue>};\n\n/**\n * Structural Standard Schema v1. `types` is optional in the spec but\n * implemented by zod v3.24+/v4, valibot v1 and arktype — it is what\n * {@link InferSchemaValues} and `createForm`'s `TValues` inference read.\n */\nexport type StandardSchemaV1<Input = unknown, Output = Input> = {\n readonly '~standard': {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (\n value: Input\n ) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n readonly types?: {\n readonly input: Input;\n readonly output: Output;\n };\n };\n};\n\n/**\n * The values type a Standard Schema produces: the `Output` of its\n * `~standard.types`, resolved structurally without importing the schema\n * library. Coercions/transforms land in the output — `z.coerce.date()`\n * infers `Date`, not `string`. `never` when the schema does not expose\n * `types` (pass an explicit `TValues` then).\n *\n * @example\n * ```ts\n * const schema = z.object({email: z.string().email()});\n * type Values = InferSchemaValues<typeof schema>; // {email: string}\n * const form = useForm<InferSchemaValues<typeof schema>>();\n * ```\n */\nexport type InferSchemaValues<S> = S extends {\n readonly '~standard': {readonly types: {readonly output: infer Out}};\n}\n ? Out\n : never;\n\n/**\n * Does the value implement the Standard Schema v1 props? A plain boolean\n * predicate, not a type guard: narrowing a\n * `Fn | StandardSchemaV1<In, Out>` union through the guard fails either\n * way (a generics-narrowing guard is contravariance-blocked, an\n * intersection guard keeps both members) — call sites cast after the\n * check instead.\n */\nexport function hasStandardProps(schema: any): boolean {\n return (\n !!schema &&\n typeof schema === 'object' &&\n typeof schema['~standard']?.validate === 'function'\n );\n}\n\n/** Issue → stored error entry. */\nfunction toFieldError(issue: StandardSchemaIssue | undefined): FieldError {\n return {type: 'standard', message: issue?.message || 'Validation failed'};\n}\n\n/**\n * Field-level Standard Schema adapter: validate a single value with any\n * schema implementing '~standard' and map every issue to a FieldError, so\n * a value breaking several rules surfaces all of them (setErrorByPath\n * stores the array; error/errorObject readers still see the first).\n * The schema's parsed output is validation-only here — field validators\n * never rewrite the value; form-level schemas own coercion.\n */\nexport function schemaToFieldValidator(\n schema: StandardSchemaV1<any, any>\n): Validator {\n return async (value: any) => {\n const result = await schema['~standard'].validate(value);\n if (!result.issues?.length) return undefined;\n return result.issues.map(toFieldError);\n };\n}\n\n/**\n * Form-level Standard Schema adapter: validate the whole values object and\n * return a ValidationOutcome. On failure `errors` carries the nested shape\n * Options.validate expects ({a: {b: FieldError[]}}; ensureValidate flattens\n * it back to per-field errors, keeping every issue of a path). Issues\n * without a path are form-level errors and land on the FORM_ERROR key.\n * On success `values` carries the schema's parsed output (coerce/transform\n * results included), which the form stores as its parsedValues baseline —\n * the layer getValues reads above initialValues.\n *\n * `createForm({validate: schema})` wraps the schema through this adapter\n * automatically (no resolver import needed), and `TValues` infers from\n * the schema's output type.\n */\nexport function schemaToFormValidator<T extends Record<string, any>>(\n schema: StandardSchemaV1<any, T>\n): (values: T) => Promise<ValidationOutcome<T>> {\n return async (values: T) => {\n const result = await schema['~standard'].validate(values);\n const {issues} = result;\n if (!issues?.length) {\n // Success: expose the schema's parsed output. `in` keeps the union\n // narrowed (the success variant is the one carrying `value`).\n return {\n [VALIDATION_OUTCOME]: true,\n values: 'value' in result ? result.value : undefined\n };\n }\n const errors: Record<string, any> = {};\n for (const issue of issues) {\n const segments = toPathSegments(issue);\n if (segments.length) {\n assignAtPath(errors, segments, toFieldError(issue));\n } else {\n // Pathless issues are all form-level: they accumulate on the\n // FORM_ERROR slot instead of the first shadowing the rest. (A\n // nested path literally named like FORM_ERROR would have made the\n // slot a branch — skip then.)\n const slot = (errors[FORM_ERROR] ??= []);\n if (Array.isArray(slot)) slot.push(toFieldError(issue));\n }\n }\n return {[VALIDATION_OUTCOME]: true, errors: pruneEmpty(errors) || {}};\n };\n}\n\n/**\n * Stringify an issue path: PropertyKey or {key} path segments → strings.\n */\nfunction toPathSegments(issue: StandardSchemaIssue): string[] {\n const path = issue.path || [];\n const segments: string[] = [];\n for (const segment of path) {\n const key =\n typeof segment === 'object' && segment !== null\n ? (segment as {key: PropertyKey}).key\n : segment;\n segments.push(String(key));\n }\n return segments;\n}\n\n/**\n * Append the error at a nested path. Leaves are FieldError[] arrays, so\n * several issues on one field accumulate in issue order; an issue whose\n * path conflicts with an existing leaf or crosses it is skipped.\n */\nfunction assignAtPath(\n root: Record<string, any>,\n segments: string[],\n error: FieldError\n): void {\n let node = root;\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i];\n let next = node[segment];\n if (next === undefined) {\n next = node[segment] = {};\n }\n if (!isBranch(next)) return;\n node = next;\n }\n const leaf = segments[segments.length - 1];\n const slot = node[leaf];\n if (slot === undefined) node[leaf] = [error];\n else if (Array.isArray(slot)) slot.push(error);\n}\n\n/**\n * A branch is a plain container built while nesting; the leaves it carries\n * are the FieldError[] arrays assignAtPath appends.\n */\nfunction isBranch(value: any): value is Record<string, any> {\n return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Drop empty branch objects left behind by conflicting issue paths.\n */\nfunction pruneEmpty(\n node: Record<string, any>\n): Record<string, any> | undefined {\n let hasLeaf = false;\n const result: Record<string, any> = {};\n Object.entries(node).forEach(([key, value]) => {\n if (isBranch(value)) {\n const pruned = pruneEmpty(value);\n if (pruned) {\n result[key] = pruned;\n hasLeaf = true;\n }\n } else {\n result[key] = value;\n hasLeaf = true;\n }\n });\n return hasLeaf ? result : undefined;\n}\n"],"names":["toFieldError","issue","type","message","toPathSegments","path","segments","segment","key","push","String","assignAtPath","root","error","node","i","length","next","isBranch","leaf","slot","Array","isArray","value","pruneEmpty","hasLeaf","result","Object","entries","forEach","pruned","schema","validate","async","issues","map","values","VALIDATION_OUTCOME","errors","FORM_ERROR"],"mappings":"uDAmFA,SAASA,EAAaC,GACpB,MAAO,CAACC,KAAM,WAAYC,QAASF,GAAOE,SAAW,oBACvD,CAqEA,SAASC,EAAeH,GACtB,MAAMI,EAAOJ,EAAMI,MAAQ,GACrBC,EAAqB,GAC3B,IAAA,MAAWC,KAAWF,EAAM,CAC1B,MAAMG,EACe,iBAAZD,GAAoC,OAAZA,EAC1BA,EAA+BC,IAChCD,EACND,EAASG,KAAKC,OAAOF,GACvB,CACA,OAAOF,CACT,CAOA,SAASK,EACPC,EACAN,EACAO,GAEA,IAAIC,EAAOF,EACX,IAAA,IAASG,EAAI,EAAGA,EAAIT,EAASU,OAAS,EAAGD,IAAK,CAC5C,MAAMR,EAAUD,EAASS,GACzB,IAAIE,EAAOH,EAAKP,GAIhB,QAHa,IAATU,IACFA,EAAOH,EAAKP,GAAW,CAAA,IAEpBW,EAASD,GAAO,OACrBH,EAAOG,CACT,CACA,MAAME,EAAOb,EAASA,EAASU,OAAS,GAClCI,EAAON,EAAKK,QACL,IAATC,EAAoBN,EAAKK,GAAQ,CAACN,GAC7BQ,MAAMC,QAAQF,IAAOA,EAAKX,KAAKI,EAC1C,CAMA,SAASK,EAASK,GAChB,QAASA,GAA0B,iBAAVA,IAAuBF,MAAMC,QAAQC,EAChE,CAKA,SAASC,EACPV,GAEA,IAAIW,GAAU,EACd,MAAMC,EAA8B,CAAA,EAapC,OAZAC,OAAOC,QAAQd,GAAMe,QAAQ,EAAErB,EAAKe,MAClC,GAAIL,EAASK,GAAQ,CACnB,MAAMO,EAASN,EAAWD,GACtBO,IACFJ,EAAOlB,GAAOsB,EACdL,GAAU,EAEd,MACEC,EAAOlB,GAAOe,EACdE,GAAU,IAGPA,EAAUC,OAAS,CAC5B,0BApJO,SAA0BK,GAC/B,QACIA,GACgB,iBAAXA,GACkC,mBAAlCA,EAAO,cAAcC,QAEhC,iCAeO,SACLD,GAEA,OAAOE,MAAOV,IACZ,MAAMG,QAAeK,EAAO,aAAaC,SAAST,GAClD,GAAKG,EAAOQ,QAAQlB,OACpB,OAAOU,EAAOQ,OAAOC,IAAInC,GAE7B,gCAgBO,SACL+B,GAEA,OAAOE,MAAOG,IACZ,MAAMV,QAAeK,EAAO,aAAaC,SAASI,IAC5CF,OAACA,GAAUR,EACjB,IAAKQ,GAAQlB,OAGX,MAAO,CACL,CAACqB,EAAAA,qBAAqB,EACtBD,OAAQ,UAAWV,EAASA,EAAOH,WAAQ,GAG/C,MAAMe,EAA8B,CAAA,EACpC,IAAA,MAAWrC,KAASiC,EAAQ,CAC1B,MAAM5B,EAAWF,EAAeH,GAChC,GAAIK,EAASU,OACXL,EAAa2B,EAAQhC,EAAUN,EAAaC,QACvC,CAKL,MAAMmB,EAAQkB,EAAOC,EAAAA,cAAgB,GACjClB,MAAMC,QAAQF,MAAYX,KAAKT,EAAaC,GAClD,CACF,CACA,MAAO,CAAC,CAACoC,EAAAA,qBAAqB,EAAMC,OAAQd,EAAWc,IAAW,IAEtE"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{emit as e}from"@for-fun/event-emitter";import{h as t,w as r,V as n,s as o,b as a,c as i,i as l,n as s,a as u,d as c,e as f,f as d}from"./errors-ClH3eV1R.mjs";import{g as v,a as m,h as y}from"./values-Bod74Kzu.mjs";function g(t,r,n){let o=null,a=null,i=!1,l=null,s=null;const u=e=>void 0!==e&&!(Array.isArray(e)&&0===e.length),c=()=>{i||(i=!0,function({emitter:t,validating:r},n){r.add(n.key),e(t,"validating",n)}(t,r))},v=()=>{i&&(i=!1,function({emitter:t,validating:r},n){r.delete(n.key),e(t,"validating",n)}(t,r))},y=()=>{const e=n.sync();if(!e)return!1;const o=e(m(t,r),{form:t,path:r});return u(o)?(f(t,r,o),s="sync",!0):(n.validate()&&"sync"!==s||(f(t,r,void 0),s=null),!1)},g=e=>{if(n.asyncAlways?.()){const o=(()=>{const e=n.sync();if(!e)return null;const o=e(m(t,r),{form:t,path:r});if(void 0===o)return null;const a=Array.isArray(o)?o:[o];return a.length?a:null})(),a=void 0===e?[]:Array.isArray(e)?e:[e];f(t,r,[...o??[],...a]),s=u(e)?"validator":o?"sync":null}else f(t,r,e),s=u(e)?"validator":null},h=()=>{null!==o&&(clearTimeout(o),o=null),a?.abort(),l={}},w=()=>{const e=n.validate();if(!e)return void v();a?.abort(),a=new AbortController;const o=l={};let i;try{i=e(m(t,r),{form:t,path:r,signal:a.signal})}catch(e){throw v(),e}if(!d(i))return g(i),void v();c(),i.then(e=>{l===o&&g(e)}).catch(()=>{}).finally(()=>{l===o&&(v(),l=null)})},p=()=>{if(o=null,y()&&!n.asyncAlways?.())return h(),void v();w()};return t.validators.set(r.key,()=>{if(y()&&!n.asyncAlways?.())return h(),void v();if(!n.validate())return;const e=n.debounce();if(e>0)return null!==o?clearTimeout(o):c(),void(o=setTimeout(p,e));w()}),()=>{t.validators.delete(r.key),null!==o&&(clearTimeout(o),o=null),v(),a?.abort()}}async function h(e,n,o){const a=t=>r(e.emitter,"validating",()=>function(e){for(const t of e.validating)if(t!==A)return!1;return!0}(e),()=>!1);return e.validators.forEach(e=>e()),await a(),e.validate&&await C(e),!t(e)}function w(e,t,r=[],n){Object.entries(t).forEach(([t,o])=>{const a=[...r,...l(t)?[t]:s(t)];"string"==typeof o?o&&(u(e,a,o),p(e,a,n)):Array.isArray(o)||c(o)?(u(e,a,o),p(e,a,n)):o&&"object"==typeof o&&w(e,o,a,n)})}function p(e,t,r){if(!r)return;const n=i(t),o=e.errors.get(n.key);o&&r.set(n.key,o)}function b(t,r){const l=t.validateDeps||"onSubmit"!==t.validateMode?function(e){let t=k.get(e);t||(t=new Map,k.set(e,t));return t}(t):void 0;if(l&&(!function(t,r){for(const[n,o]of r){t.errors.get(n)===o&&(t.errors.delete(n),a(t),e(t.emitter,"errors",i(JSON.parse(n))))}}(t,l),l.clear()),r){if("object"==typeof r&&n in r){const e=r;return e.errors&&w(t,e.errors,[],l),void o(t,e.values)}w(t,r,[],l)}}const k=new WeakMap;const A="__form_validate__";const j=Symbol("form-validate-settled"),T=new WeakMap;function C(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(v(t),{form:t,signal:e.signal})).then(e=>{b(t,e)})}const o=function(e){let t=T.get(e);return t||(t={timer:null,controller:null,round:null,marked:!1,waiters:[]},T.set(e,t)),t}(t);return null!==o.timer?clearTimeout(o.timer):(o.marked=!0,t.validating.add(A),e(t.emitter,"validating")),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 a;try{a=Promise.resolve(n(v(e),{form:e,signal:o.signal}))}catch(e){a=Promise.reject(e)}return a.then(n=>{t.round===r&&b(e,n)},e=>{if(t.round===r)throw e})})(t,o,e).then(()=>M(t,o,e,j),r=>M(t,o,e,r))},n),new Promise((e,t)=>{o.waiters.push({resolve:e,reject:t})})}function M(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(A),e(t.emitter,"validating"));const a=r.waiters;r.waiters=[];for(const e of a)o===j?e.resolve():e.reject(o)}function P(e,t,r){e.validateDeps?.has(t.key)&&e.validate&&("onChange"===r||"all"===r||"onTouched"===r&&y(e,t)||"onChange"===e.reValidateMode&&function(e){const t=k.get(e);if(!t)return!1;for(const[r,n]of t)if(e.errors.get(r)===n)return!0;return!1}(e))&&C(e).catch(()=>{})}const _=new WeakMap;function D(e,t,r){const n=_.get(e)?.get(t.key);if(n?.size)for(const o of n)o!==t.key&&("onChange"===r||"all"===r||"onTouched"===r&&y(e,t)||"onChange"===e.reValidateMode&&e.errors.has(o))&&e.validators.get(o)?.()}export{P as a,D as b,g as c,C as r,h as t};
|
|
2
|
+
//# sourceMappingURL=validate-BEjNsNvd.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-BEjNsNvd.mjs","sources":["../src/core/validate.ts"],"sourcesContent":["import {emit} from '../emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport {isIndex, isPromise, normalizePath, waitUntil} from '../util';\nimport type {\n FieldError,\n FieldErrorEntry,\n Form,\n ValidationMode,\n ValidateResult,\n ValidationOutcome\n} from '../form';\nimport {\n VALIDATION_OUTCOME,\n getErrors,\n getFirstError,\n hasErrors,\n setError,\n setErrorByPath\n} from './errors';\nimport {hasTouchedByPath, setTouchedByPath} from './touched';\nimport {getValueByPath, getValues} from './values';\nimport {\n bumpErrorsVersion,\n isFieldError,\n isSegmentsPath,\n setParsedValues\n} from './internals';\n\nexport function unsetValidatingByPath(\n {emitter, validating}: Form,\n path: Path\n): void {\n validating.delete(path.key);\n // Path payload lets key-scoped subscribers (onKeyEvent) skip unrelated\n // fields; payload-less listeners ignore it.\n emit(emitter, 'validating', path);\n}\n\nexport function setValidatingByPath(\n {emitter, validating}: Form,\n path: Path\n): void {\n validating.add(path.key);\n emit(emitter, 'validating', path);\n}\n\n/**\n * Field validator. Returns an error (a string, a FieldError, or an array\n * mixing both) or undefined when valid; may return a Promise for async\n * validation.\n *\n * The second argument carries the validation context. `meta.signal` is\n * aborted as soon as the round is superseded — a newer round started, or\n * the field unregistered — so async validators can cancel their underlying\n * work (fetch, timers) instead of racing a stale result home. Stale\n * results are dropped independently by the registration's lock\n * ({@link registerValidatorByPath}), so validators that ignore the signal\n * stay correct too. Validators written against the older two-argument\n * signature keep working.\n */\nexport type Validator = (\n value: any,\n meta: {form: Form; path: Path; signal: AbortSignal}\n) =>\n | string\n | FieldError\n | (string | FieldError)[]\n | undefined\n | Promise<string | FieldError | (string | FieldError)[] | undefined>;\n\n/**\n * Synchronous pre-validator for {@link registerValidatorByPath}'s `sync`\n * accessor — declarative `required` rules compiled by `rulesToValidator`\n * in practice, but any sync-only check works. Runs on every kick, never\n * debounced: its errors land immediately and, while present,\n * short-circuit the debounced validator for that kick (the expensive\n * check never sees a value the gate already rejects). Must be synchronous\n * — unlike a {@link Validator} it may not return a Promise — and its meta\n * carries no `signal`: there is nothing to abort in a synchronous check.\n */\nexport type SyncValidator = (\n value: any,\n meta: {form: Form; path: Path}\n) => string | FieldError | (string | FieldError)[] | undefined;\n\n/** Live options for {@link registerValidatorByPath}: read at every kick\n * through accessors, so callers (React's `useValidate`) can swap the\n * validator/debounce/sync-gate per render without re-subscribing the\n * registration mid-flight. */\nexport type ValidatorRegistration = {\n /** Current debounced validator (or undefined — a sync-only\n * registration). */\n validate: () => Validator | undefined;\n /** Debounce delay in milliseconds; 0 (default) runs immediately. */\n debounce: () => number;\n /** Synchronous pre-validator, run on every kick (never debounced). */\n sync: () => SyncValidator | undefined;\n /** Whether the debounced validator still runs when the sync gate\n * failed — TanStack Form's `asyncAlways`: the gate's errors land\n * immediately (never debounced), then the validator's own result lands\n * alongside them (per-source semantics) instead of the gate\n * short-circuiting the whole kick. Optional — absent means false\n * (gate failure owns the kick's outcome), so pre-existing\n * framework-free registrations keep working unchanged. */\n asyncAlways?: () => boolean;\n};\n\n/**\n * Register a field validator's kick at `path` in {@link Form.validators}\n * — the framework-free machinery behind `useValidate`. Returns a\n * disposer that drops the registration and cancels any pending debounce\n * window or in-flight round (its signal aborts and its validating mark\n * is released).\n *\n * Contract of the registered kick (the same contract `trigger` /\n * `ensureValidate` rely on when they run every entry, and the\n * user-change gate relies on when it runs the changed path's entry):\n * - the `sync` gate runs immediately on every kick — never debounced —\n * and while it returns errors, the debounced validator is skipped for\n * that kick and any pending window or in-flight round is superseded —\n * unless `asyncAlways` is set, in which case the validator still runs\n * and its result lands alongside the gate's errors (per-source);\n * - a positive `debounce` merges kicks inside the window: only the last\n * one runs the validator, and while the timer is pending the field\n * counts as validating so `trigger`/`ensureValidate` wait it out;\n * - async results land under a lock: a superseded round's outcome —\n * rejection included — is dropped, and only the owning round releases\n * the validating mark;\n * - a synchronous throw inside the validator propagates to the caller\n * (the validating mark is not left stuck behind it).\n *\n * Registering at a path already registered by another mount replaces it\n * (last-wins, the historical `useValidate` behavior); the disposer drops\n * its own registration unconditionally.\n *\n * @param form\n * @param path\n * @param registration live validator/debounce/sync accessors\n * @return disposer: unregister and cancel pending work\n */\nexport function registerValidatorByPath(\n form: Form,\n path: Path,\n registration: ValidatorRegistration\n): () => void {\n // The pending debounce timer and the current round's controller live in\n // this closure so the disposer below can cancel them.\n let timer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n // Whether this registration currently holds the path's slot in\n // form.validating. The mark is taken when a debounce window opens or an\n // async round starts, and released by whichever round settles last —\n // including a later sync round that supersedes an in-flight async one\n // (its own .finally is lock-gated out by then).\n let marked = false;\n // The async-round lock: only the latest round may land its result or\n // release the mark; a superseded round's outcome is dropped wholesale.\n let lock: object | null = null;\n // Which source wrote the error currently on display — the sync gate or\n // the debounced validator. Tracked so a passing sync check can clear\n // its own stale error immediately instead of leaving it on screen until\n // the debounced round lands. External writers (setError, form-level\n // validate) are invisible here; a passing round clearing them matches\n // the long-standing \"a field validator owns its whole key\" contract.\n let errorSource: 'sync' | 'validator' | null = null;\n /** Does a validator result land errors? `[]` normalizes away exactly\n * like undefined in setErrorByPath. */\n const hasErrors = (errors: any): boolean =>\n errors !== undefined && !(Array.isArray(errors) && errors.length === 0);\n const mark = () => {\n if (marked) return;\n marked = true;\n setValidatingByPath(form, path);\n };\n const unmark = () => {\n if (!marked) return;\n marked = false;\n unsetValidatingByPath(form, path);\n };\n\n /** Run the synchronous gate on the field's current value. Its errors\n * land immediately — the gate is never debounced. Returns true when\n * errors landed (the kick's whole outcome for the debounced validator).\n * A passing gate clears the field's errors when they were its own from\n * an earlier kick, or when no debounced validator exists to own the\n * round. */\n const runSync = (): boolean => {\n const sync = registration.sync();\n if (!sync) return false;\n const errors = sync(getValueByPath(form, path), {form, path});\n if (!hasErrors(errors)) {\n // A stale error the gate itself wrote is answered by the gate\n // alone; a rules-only registration's passing check is the whole\n // round. With a debounced validator registered, its upcoming round\n // owns the outcome and lands it later.\n if (!registration.validate() || errorSource === 'sync') {\n setErrorByPath(form, path, undefined);\n errorSource = null;\n }\n return false;\n }\n setErrorByPath(form, path, errors);\n errorSource = 'sync';\n return true;\n };\n\n /** Re-run the sync gate purely for its error list — no store write.\n * asyncAlways landings merge it with the validator's result so each\n * source keeps its own errors on display (TanStack's per-source\n * errorMap shape); the non-asyncAlways path never calls this. */\n const collectSyncErrors = (): (string | FieldError)[] | null => {\n const sync = registration.sync();\n if (!sync) return null;\n const errors = sync(getValueByPath(form, path), {form, path});\n if (errors === undefined) return null;\n const list = Array.isArray(errors) ? errors : [errors];\n return list.length ? list : null;\n };\n\n /** Land a validator result. asyncAlways merges the gate's current\n * verdict (re-collected — the value may have drifted since the round\n * started) ahead of the validator's errors, so a still-failing gate\n * keeps its own errors on screen; the default path stays the\n * historical \"validator owns the whole key\" write. */\n const land = (\n result: string | FieldError | (string | FieldError)[] | undefined\n ): void => {\n if (registration.asyncAlways?.()) {\n const gate = collectSyncErrors();\n const own =\n result === undefined ? [] : Array.isArray(result) ? result : [result];\n setErrorByPath(form, path, [...(gate ?? []), ...own]);\n errorSource = hasErrors(result) ? 'validator' : gate ? 'sync' : null;\n } else {\n setErrorByPath(form, path, result);\n errorSource = hasErrors(result) ? 'validator' : null;\n }\n };\n\n /** Drop any pending window or in-flight round without landing it: the\n * sync gate now owns the outcome, so the debounced validator must not\n * run for this value. */\n const supersede = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n controller?.abort();\n lock = {};\n };\n\n /** Run the debounced validator on the field's current value and land\n * its result — the sync gate has already passed. */\n const runValidator = () => {\n const fn = registration.validate();\n if (!fn) {\n unmark();\n return;\n }\n // Abort the superseded round's signal: a listening validator should\n // stop its underlying work. The lock refresh below independently\n // drops any result that still arrives, signal or not.\n controller?.abort();\n controller = new AbortController();\n const round = (lock = {});\n let result;\n try {\n result = fn(getValueByPath(form, path), {\n form,\n path,\n signal: controller.signal\n });\n } catch (e) {\n // A throwing sync validator propagates to the caller as it always\n // has; just don't leave the validating mark stuck behind it.\n unmark();\n throw e;\n }\n if (!isPromise(result)) {\n land(result);\n // Error first, then release the mark: 'validating' subscribers\n // (trigger) re-read state on wake and must see the landed error.\n unmark();\n return;\n }\n mark();\n result\n .then(\n (error: string | FieldError | (string | FieldError)[] | undefined) => {\n if (lock === round) {\n land(error);\n }\n }\n )\n // A rejected round is the normal way a signal-listening validator\n // gives up (fetch throws AbortError once aborted); swallow it and\n // let the owning round write the outcome.\n .catch(() => {})\n .finally(() => {\n if (lock === round) {\n unmark();\n lock = null;\n }\n });\n };\n\n /** A debounce window fired: the value may have drifted since the last\n * kick (programmatic writes do not kick validators), so re-run the\n * sync gate before spending the debounced validator on a value the\n * gate already rejects. Under asyncAlways a failing gate does not own\n * the outcome — the validator still runs and both verdicts land. */\n const run = () => {\n timer = null;\n if (runSync() && !registration.asyncAlways?.()) {\n supersede();\n unmark();\n return;\n }\n runValidator();\n };\n\n const kick = () => {\n if (runSync() && !registration.asyncAlways?.()) {\n supersede();\n unmark();\n return;\n }\n if (!registration.validate()) return;\n const debounce = registration.debounce();\n if (debounce > 0) {\n // Only the last kick inside the window runs: restart the timer on\n // every kick. The mark keeps trigger/ensureValidate's\n // validating-set wait covering the pending timer, not just\n // in-flight promises.\n if (timer !== null) clearTimeout(timer);\n else mark();\n timer = setTimeout(run, debounce);\n return;\n }\n runValidator();\n };\n\n form.validators.set(path.key, kick);\n return () => {\n form.validators.delete(path.key);\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n unmark();\n controller?.abort();\n };\n}\n\n/**\n * Set field error\n * @param form\n * @param name\n * @param error string is normalized to {type: 'custom', message}; a\n * FieldError object is stored as-is; an array holds several errors\n * (falsy items dropped, strings normalized); undefined clears\n */\n/** Options accepted by {@link trigger}. `shouldTouch` defaults to `false`;\n * omitting the options object entirely keeps the plain validate-only\n * behavior, so the historical two-argument calls are untouched. */\nexport type TriggerOptions = {\n /** Mark every path in the triggered scope as touched — even when\n * validation fails — once the round settles. Mirrors react-hook-form's\n * trigger `shouldTouch`. Defaults to `false`. */\n shouldTouch?: boolean;\n /**\n * Focus the first errored field in the triggered scope once the round\n * settles (and only when the round left errors) — react-hook-form's\n * trigger `shouldFocus` counterpart. Rides the 'focusError' event\n * channel like a failed submit's auto-focus: only mounted bound fields\n * react, unmounted ones are silent no-ops. Without `name` the first key\n * of the errors Map wins (the same rule handleSubmit applies); with\n * `name` the first errored triggered key does. Defaults to `false`.\n */\n shouldFocus?: boolean;\n};\n\n/**\n * Trigger field validation.\n *\n * Without `name` every registered field validator runs. A single `name` —\n * dotted string or segments array — runs only that field's validator, and\n * an array of names runs each one in order. An empty array is a no-op, as\n * is any name with no registered validator. An array argument counts as\n * one segments path only when it mixes in numbers (`['items', 0]`); pure\n * string arrays are name lists, so `['a', 'b']` triggers fields `a` and\n * `b`, not the nested path `a.b`.\n *\n * `options.shouldTouch` marks the triggered scope — the given names, or\n * every registered field when `name` is omitted — as touched after the\n * round settles, whether validation passed or failed. The wait/settle\n * logic is untouched: the marking rides on top of the settled round, so\n * subscribers observe errors and touched together rather than mid-flight.\n *\n * The returned promise waits for the triggered validation to settle —\n * async validators included — so their errors have already landed in\n * `form.errors` when it resolves. It never rejects: landing errors is the\n * expected outcome here, not a failure. Resolves `true` when the triggered\n * scope is error-free, `false` otherwise. Without `name` the scope is all\n * fields plus the form-level `validate` result (which runs after field\n * validators settle, same pipeline as {@link ensureValidate}); with `name`\n * only those fields' own errors count and form-level `validate` is\n * skipped (RHF semantics).\n *\n * Fire-and-forget callers may ignore the promise: the validator kicks\n * still happen synchronously, matching the pre-promise behavior.\n *\n * @param form\n * @param name field name(s) to trigger, or all fields when omitted\n * @param options extra behavior toggles ({@link TriggerOptions}); omitted,\n * validation alone runs — no touched marking\n * @return whether the triggered scope is error-free once validation settles\n */\nexport async function trigger(\n form: Form,\n name?: Name | Name[],\n options?: TriggerOptions\n): Promise<boolean> {\n // Never reject (an error landing is a normal outcome, not a failure), so\n // waitUntil's isReject is permanently false. Without a name the wait is\n // deliberately conservative — every FIELD validator, unrelated in-flight\n // ones included, because the round covers the whole form (and the\n // form-level validate's own window is excluded via fieldsSettled —\n // callers wait that out through the kick's promise instead, so a pending\n // window never gates the next kick). With a name the wait narrows to the\n // triggered keys only: a slow async validator on field B must not hold\n // trigger('a') hostage when the round never reads B.\n const settle = (keys?: string[]) =>\n waitUntil(\n form.emitter,\n 'validating',\n () =>\n keys === undefined\n ? fieldsSettled(form)\n : keys.every(key => !form.validating.has(key)),\n () => false\n );\n\n if (name === undefined) {\n form.validators.forEach(validator => validator());\n await settle();\n if (form.validate) await runFormValidate(form);\n // shouldTouch marks the whole registered scope — every key the round\n // could have validated — pass or fail alike.\n if (options?.shouldTouch) touchKeys(form, [...form.validators.keys()]);\n // First error across the errors Map — the same rule a failed submit's\n // auto-focus applies (a form-level error may land first; it has no\n // element, so it is a silent no-op like every unbound path).\n if (options?.shouldFocus) {\n const firstKey = form.errors.keys().next().value;\n if (firstKey !== undefined) emit(form.emitter, 'focusError', firstKey);\n }\n return !hasErrors(form);\n }\n\n const keys: string[] =\n typeof name === 'string' || isSegmentsPath(name)\n ? [createPath(name).key]\n : name.map(one => createPath(one).key);\n keys.forEach(key => form.validators.get(key)?.());\n await settle(keys);\n if (options?.shouldTouch) touchKeys(form, keys);\n // Focus the first errored key among the triggered scope — trigger('a')\n // never focuses B's pre-existing error.\n if (options?.shouldFocus) {\n const firstKey = keys.find(key => form.errors.has(key));\n if (firstKey !== undefined) emit(form.emitter, 'focusError', firstKey);\n }\n return keys.every(key => !form.errors.has(key));\n}\n\n/** trigger's `shouldTouch` marking: touch every key in the triggered scope\n * through {@link setTouchedByPath}, which no-ops on already-touched keys\n * and emits the path-carrying 'touched' event per newly touched one. Keys\n * are the stored JSON-stringified segments shape, so parse them back into\n * Path — normalizePath passes segment arrays through untouched, making the\n * key round-trip exact. */\nfunction touchKeys(form: Form, keys: string[]): void {\n keys.forEach(key => setTouchedByPath(form, createPath(JSON.parse(key))));\n}\n\n/**\n * Flatten a form-level validate result and write each leaf error through\n * setError. Nested objects descend into deeper paths ({a: {b: 'msg'}} sets\n * the 'a.b' error), array values contribute every non-empty string they\n * hold as separate errors (zod flatten() formErrors style), and\n * FieldError-shaped objects are stored as-is. Falsy values are skipped.\n *\n * When `footprint` is passed (forms with `validateDeps` or a live\n * `validateMode`), every leaf this round actually stored is recorded into\n * it — the exact stored array — so the next round can drop exactly what\n * this one wrote.\n */\nfunction setFormErrors(\n form: Form,\n result: Record<string, any>,\n segments: PathSegments = [],\n footprint?: Map<string, FieldError[]>\n): void {\n Object.entries(result).forEach(([key, value]) => {\n // Error-tree keys are explicit object keys, not path expressions:\n // a numeric key ('0' — Standard Schema issue paths stringify array\n // indices) stays a literal string segment instead of feeding the\n // path parser, whose dotted-numeric rule governs path strings only.\n const path: PathSegments = [\n ...segments,\n ...(isIndex(key) ? [key] : normalizePath(key))\n ];\n if (typeof value === 'string') {\n if (value) {\n setError(form, path, value);\n recordFootprint(form, path, footprint);\n }\n } else if (Array.isArray(value)) {\n setError(form, path, value);\n recordFootprint(form, path, footprint);\n } else if (isFieldError(value)) {\n setError(form, path, value);\n recordFootprint(form, path, footprint);\n } else if (value && typeof value === 'object') {\n setFormErrors(form, value, path, footprint);\n }\n });\n}\n\n/** Record one leaf write of a form-level validate round: the path key and\n * the exact array now stored there. Nothing is recorded when the write\n * normalized away (all-empty arrays) — there is no error to own. The\n * stored array is read back from the errors Map because setErrorByPath\n * owns normalization. */\nfunction recordFootprint(\n form: Form,\n segments: PathSegments,\n footprint: Map<string, FieldError[]> | undefined\n): void {\n if (!footprint) return;\n const path = createPath(segments);\n const stored = form.errors.get(path.key);\n if (stored) footprint.set(path.key, stored);\n}\n\n/**\n * Land a form-level validate result. A plain record keeps the\n * long-standing behavior — flattened into field errors by\n * {@link setFormErrors}. A branded {@link ValidationOutcome} splits\n * instead: `errors` flattens exactly like a plain record, and `values`\n * (the schema's parsed output — coerced/transformed values included)\n * becomes the form's parsedValues baseline. Falsy results are skipped,\n * branded or not.\n *\n * Forms that re-run the validate on user input — via {@link\n * Options.validateDeps} or a live {@link Options.validateMode} cadence —\n * additionally get round-scoped error ownership: before the new result\n * lands, the errors the previous round wrote are dropped\n * ({@link clearFormValidateErrors}), so a re-run that passes makes the\n * cross-field error disappear — and the new round's own writes become the\n * tracked footprint. Submit-only forms (no deps, `validateMode:\n * 'onSubmit'`) keep the historical write-only behavior untouched.\n */\nfunction applyValidateResult(\n form: Form,\n result: ValidateResult<any> | undefined\n): void {\n const footprint =\n form.validateDeps || form.validateMode !== 'onSubmit'\n ? getFormErrorFootprint(form)\n : undefined;\n if (footprint) {\n clearFormValidateErrors(form, footprint);\n footprint.clear();\n }\n if (!result) return;\n if (typeof result === 'object' && VALIDATION_OUTCOME in result) {\n const outcome = result as ValidationOutcome<any>;\n if (outcome.errors) setFormErrors(form, outcome.errors, [], footprint);\n setParsedValues(form, outcome.values);\n return;\n }\n setFormErrors(form, result as Record<string, any>, [], footprint);\n}\n\n/** Per-form error footprint of the last form-level validate round: every\n * path key it flattened onto, with the exact array instance it stored.\n * Tracked only for forms that re-run the validate on user input\n * (`validateDeps` or a live `validateMode`) — held in a WeakMap so the\n * Form shape and the submit-only pipeline stay untouched. */\nconst formErrorFootprints = new WeakMap<Form, Map<string, FieldError[]>>();\n\nfunction getFormErrorFootprint(form: Form): Map<string, FieldError[]> {\n let footprint = formErrorFootprints.get(form);\n if (!footprint) {\n footprint = new Map();\n formErrorFootprints.set(form, footprint);\n }\n return footprint;\n}\n\n/** Does the form still show an error the last form-level round wrote?\n * Compared by identity, not key membership: once a field validator,\n * `setServerErrors`, a manual `setError` or `clearErrors` replaces the\n * stored array, that error is no longer the round's to own — neither the\n * dep-change gate nor the next round's clearing may touch it. */\nfunction hasFormValidateErrors(form: Form): boolean {\n const footprint = formErrorFootprints.get(form);\n if (!footprint) return false;\n for (const [key, written] of footprint) {\n if (form.errors.get(key) === written) return true;\n }\n return false;\n}\n\n/** Drop the last form-level round's errors before the next round lands.\n * Per key the stored array is identity-checked — an error overwritten or\n * cleared by anyone else in between survives — and each drop emits the\n * same path-payload 'errors' event {@link setErrorByPath} would, so\n * subscribed fields re-render exactly like on any error write. */\nfunction clearFormValidateErrors(\n form: Form,\n footprint: Map<string, FieldError[]>\n): void {\n for (const [key, written] of footprint) {\n const stored = form.errors.get(key);\n if (stored !== written) continue;\n form.errors.delete(key);\n bumpErrorsVersion(form);\n emit(form.emitter, 'errors', createPath(JSON.parse(key)));\n }\n}\n\n/** Key the form-level validate round reserves in `form.validating` while\n * its debounce window is pending or its async round is in flight. Real\n * path keys are JSON-stringified segments (always bracketed), so a bare\n * word can never collide. */\nconst FORM_VALIDATING_KEY = '__form_validate__';\n\n/** Are all FIELD validation rounds drained? trigger/ensureValidate wait on\n * this before kicking the form-level validate (its errors gate whether the\n * form-level round may run at all). The form validate's own reserved key\n * is deliberately excluded: its window is waited out through the kick's\n * returned promise instead, so a pending window or in-flight form round\n * never gates the next kick — a kick during an in-flight round opens a\n * new window and the newer round supersedes, mirroring the per-field\n * `validateDebounce` contract. */\nfunction fieldsSettled(form: Form): boolean {\n for (const key of form.validating) {\n if (key !== FORM_VALIDATING_KEY) return false;\n }\n return true;\n}\n\n/** Sentinel telling {@link settleFormValidate} the round landed cleanly —\n * distinct from every rejection payload, including `undefined`. */\nconst SETTLED = Symbol('form-validate-settled');\n\n/** Per-form bookkeeping for the debounced form-level validate: the\n * pending window timer, the in-flight round, and the waiters merged into\n * the current window group. Held in a WeakMap so the Form instance shape\n * is untouched for forms that never set `validateDebounce`. */\ntype FormValidateState = {\n timer: ReturnType<typeof setTimeout> | null;\n controller: AbortController | null;\n /** Identity of the in-flight round; a superseded round's outcome\n * (rejection included) is dropped by comparing against it. */\n round: object | null;\n /** Whether this state currently holds FORM_VALIDATING_KEY in\n * form.validating. */\n marked: boolean;\n waiters: Array<{resolve: () => void; reject: (error: unknown) => void}>;\n};\n\nconst formValidateStates = new WeakMap<Form, FormValidateState>();\n\nfunction getFormValidateState(form: Form): FormValidateState {\n let state = formValidateStates.get(form);\n if (!state) {\n state = {\n timer: null,\n controller: null,\n round: null,\n marked: false,\n waiters: []\n };\n formValidateStates.set(form, state);\n }\n return state;\n}\n\n/**\n * Run the form-level `validate` and land its result, honoring the form's\n * `validateDebounce` option.\n *\n * Undebounced (`0`/undefined) the caller's await *is* the validate call —\n * the long-standing pipeline, unchanged: no validating mark, no round\n * gating, immediate values snapshot, rejection propagating to the caller.\n *\n * Debounced, the kick opens (or restarts — kicks inside the window merge)\n * a window during which the form counts as validating, so `trigger` /\n * `ensureValidate` / submit wait the window out exactly like a field's\n * `validateDebounce` window. When the timer fires, the round reads the\n * then-current values, supersedes (aborts) any in-flight round, and lands\n * its result. The returned promise settles once the window group's final\n * round has landed — rejecting when that round's validate callback threw\n * or its promise rejected, mirroring the undebounced propagation — so\n * merged callers all observe the same outcome.\n *\n * Only called under `if (form.validate)`. Public: the form-level-only\n * round (field validators excluded) — `useForm`'s `validateOnMount` run\n * and cross-cutting re-checks use it; `trigger`/`ensureValidate` compose\n * it after the field validators settle.\n */\nexport function runFormValidate(form: Form): Promise<void> {\n const validate = form.validate;\n if (!validate) return Promise.resolve();\n const debounce = form.validateDebounce ?? 0;\n if (debounce <= 0) {\n // Standalone controller: nothing supersedes an undebounced call, so\n // its signal never fires — it exists for argument-shape parity with\n // the debounced rounds (and with field-level meta.signal).\n const controller = new AbortController();\n return Promise.resolve(\n validate(getValues(form), {form, signal: controller.signal})\n ).then(result => {\n applyValidateResult(form, result);\n });\n }\n const state = getFormValidateState(form);\n // (Re)open the window: a kick while the timer is pending restarts it\n // (only the last kick's values run); one while a round is in flight\n // keeps the validating mark held and defers to the new window's round.\n if (state.timer !== null) clearTimeout(state.timer);\n else {\n state.marked = true;\n form.validating.add(FORM_VALIDATING_KEY);\n emit(form.emitter, 'validating');\n }\n state.timer = setTimeout(() => {\n state.timer = null;\n const round = (state.round = {});\n runFormValidateRound(form, state, round).then(\n () => settleFormValidate(form, state, round, SETTLED),\n error => settleFormValidate(form, state, round, error)\n );\n }, debounce);\n return new Promise<void>((resolve, reject) => {\n state.waiters.push({resolve, reject});\n });\n}\n\n/** Run one form-level validate round with the form's current values.\n * Aborts the previous in-flight round's signal; a superseded round's\n * outcome — rejection included — is dropped by the round gate, exactly\n * like the field-level lock. */\nfunction runFormValidateRound(\n form: Form,\n state: FormValidateState,\n round: object\n): Promise<void> {\n const validate = form.validate;\n if (!validate) return Promise.resolve();\n state.controller?.abort();\n const controller = (state.controller = new AbortController());\n let outcome: Promise<any>;\n try {\n outcome = Promise.resolve(\n validate(getValues(form), {form, signal: controller.signal})\n );\n } catch (error) {\n outcome = Promise.reject(error);\n }\n return outcome.then(\n result => {\n if (state.round === round) applyValidateResult(form, result);\n },\n error => {\n if (state.round === round) throw error;\n }\n );\n}\n\n/** Land the window group's outcome: release the validating mark — after\n * the round's errors/values have already landed, because 'validating'\n * subscribers (trigger, ensureValidate) re-read state on wake — and\n * settle every merged waiter. A superseded round never lands here (the\n * newer round owns the release), and a window that re-opened while the\n * round was in flight defers: the mark and the waiters carry over to the\n * pending timer's round. */\nfunction settleFormValidate(\n form: Form,\n state: FormValidateState,\n round: object,\n outcome: unknown\n): void {\n if (state.round !== round) return;\n state.round = null;\n if (state.timer !== null) return;\n if (state.marked) {\n state.marked = false;\n form.validating.delete(FORM_VALIDATING_KEY);\n emit(form.emitter, 'validating');\n }\n const waiters = state.waiters;\n state.waiters = [];\n for (const waiter of waiters) {\n if (outcome === SETTLED) waiter.resolve();\n else waiter.reject(outcome);\n }\n}\n\n/**\n * Form-level twin of the gated validator kick in `useField`'s onChange:\n * re-run the form-level `validate` after a user change to a field listed\n * in `validateDeps`. Called from the field's own change pipeline (typing\n * and `changeValue` alike — both route through the mounted field's\n * onChange), so programmatic `setValue` writes do not re-run it, exactly\n * like they do not re-run field validators.\n *\n * The gate mirrors the per-field matrix with the *changed field's*\n * effective `mode` (a per-field override governs when its changes may\n * fire validation) and the form-level `reValidateMode` against the last\n * round's error footprint ({@link hasFormValidateErrors} — field\n * validators' errors never arm this kick):\n * - `mode` `'onChange'`/`'all'` — every dep change re-runs;\n * - `mode` `'onTouched'` — dep changes re-run once the field was touched;\n * - otherwise the re-run waits for `reValidateMode: 'onChange'` (the\n * default) while the last round's error is still live — the\n * submit-then-fix flow: the mismatch lands on submit, editing the\n * dependency re-runs the validate and clears it.\n * `reValidateMode: 'onBlur'`/`'onSubmit'` never re-run on a change (a\n * change is not a blur; submit re-runs are the submit pipeline's job).\n *\n * The kick is fire-and-forget: async round rejections are swallowed\n * (nothing in an event handler can await them), while a synchronous\n * throw inside the validate callback propagates to the caller exactly\n * like a field validator's does.\n *\n * A no-op unless the form set `validateDeps` listing `path` — forms\n * without the option pay one property check here.\n */\nexport function revalidateFormOnChange(\n form: Form,\n path: Path,\n mode: ValidationMode\n): void {\n if (!form.validateDeps?.has(path.key) || !form.validate) return;\n if (\n mode === 'onChange' ||\n mode === 'all' ||\n (mode === 'onTouched' && hasTouchedByPath(form, path)) ||\n (form.reValidateMode === 'onChange' && hasFormValidateErrors(form))\n ) {\n runFormValidate(form).catch(() => {});\n }\n}\n\n/** Per-form registry of field-level `validateDeps` declarations ({@link\n * revalidateDependentsOnChange}): dep path key -> every dependent field key\n * that listed it. Held in a WeakMap so the Form shape is untouched for\n * forms whose fields never declare deps. */\nconst fieldValidateDeps = new WeakMap<Form, Map<string, Set<string>>>();\n\n/** Register one field's validateDeps declaration: `key` re-validates when\n * any path in `depKeys` takes a user change. Idempotent per (key, dep)\n * pair, so StrictMode's double effect is harmless. */\nexport function registerFieldValidateDeps(\n form: Form,\n key: string,\n depKeys: string[]\n): void {\n let deps = fieldValidateDeps.get(form);\n if (!deps) {\n deps = new Map();\n fieldValidateDeps.set(form, deps);\n }\n for (const depKey of depKeys) {\n let dependents = deps.get(depKey);\n if (!dependents) {\n dependents = new Set();\n deps.set(depKey, dependents);\n }\n dependents.add(key);\n }\n}\n\n/** Drop one field's validateDeps registration ({@link\n * registerFieldValidateDeps}). Entries nobody lists anymore are removed so\n * the registry never outlives its fields. */\nexport function unregisterFieldValidateDeps(\n form: Form,\n key: string,\n depKeys: string[]\n): void {\n const deps = fieldValidateDeps.get(form);\n if (!deps) return;\n for (const depKey of depKeys) {\n const dependents = deps.get(depKey);\n if (!dependents?.delete(key)) continue;\n if (!dependents.size) deps.delete(depKey);\n }\n}\n\n/**\n * Field-level twin of {@link revalidateFormOnChange}: after a user change\n * to `path`, re-run every field validator that declared `path` in its\n * `validateDeps` (useField option). Same channel, same gate: the kick\n * rides the changed field's own onChange pipeline (typing and\n * `changeValue` alike), so programmatic `setValue` writes never fire it —\n * exactly like field validators and the form-level `validateDeps`.\n *\n * The gate mirrors the form-level matrix with the *changed field's*\n * effective `mode` and the form-level `reValidateMode` against each\n * dependent's live error:\n * - `mode` `'onChange'`/`'all'` — every dep change re-runs the dependent;\n * - `mode` `'onTouched'` — once the changed field was touched;\n * - otherwise the re-run waits for `reValidateMode: 'onChange'` (the\n * default) while the dependent still shows an error — the\n * submit-then-fix flow: the mismatch lands on submit, editing the\n * dependency re-validates the dependent and a passing round clears it\n * (a field validator owns its whole key, so the re-run's result\n * replaces whatever the previous round wrote — the field-level shape\n * of the form-level footprint reclaim).\n *\n * The kick is an ordinary validator kick: the dependent's own\n * `validateDebounce` window applies, and a synchronous throw inside its\n * validate propagates to the caller like any field validator's would.\n *\n * A no-op unless some field declared `path` as a dep — forms without any\n * field-level `validateDeps` pay one property check here.\n */\nexport function revalidateDependentsOnChange(\n form: Form,\n path: Path,\n mode: ValidationMode\n): void {\n const dependents = fieldValidateDeps.get(form)?.get(path.key);\n if (!dependents?.size) return;\n for (const dependent of dependents) {\n // A self-dep changes nothing: the field's own onChange above already\n // validated it under the same gate.\n if (dependent === path.key) continue;\n if (\n mode === 'onChange' ||\n mode === 'all' ||\n (mode === 'onTouched' && hasTouchedByPath(form, path)) ||\n (form.reValidateMode === 'onChange' && form.errors.has(dependent))\n ) {\n form.validators.get(dependent)?.();\n }\n }\n}\n\n/** The Error {@link ensureValidate} rejects with: `message` is the first\n * error's display text ({@link getFirstError}) — the long-standing shape\n * — and `.errors` carries the complete flattened error list ({@link\n * getErrors}: `{path, type, message}` entries, dotted display paths) so\n * catchers can branch on types and locate fields without re-reading the\n * form. */\nexport type FormValidationError = Error & {errors: FieldErrorEntry[]};\n\n/** Build {@link ensureValidate}'s rejection: first error's message, every\n * error attached. */\nfunction validationError(form: Form): FormValidationError {\n const error = new Error(getFirstError(form)) as FormValidationError;\n error.errors = getErrors(form);\n return error;\n}\n\n/**\n * Validate and throw if any field error.\n * @param form\n * @return resolve if no error; reject and stop validate if has an error\n */\nexport async function ensureValidate(form: Form): Promise<void> {\n form.validators.forEach(validator => validator());\n\n await waitUntil(\n form.emitter,\n 'validating',\n () => fieldsSettled(form),\n () => hasErrors(form)\n ).catch(() => {\n throw validationError(form);\n });\n\n if (form.validate) {\n await runFormValidate(form);\n if (hasErrors(form)) throw validationError(form);\n }\n}\n\n/**\n * Validate and return if any field error.\n * @param form\n * @return error message string or void\n */\nexport async function validate(form: Form): Promise<void | string> {\n return ensureValidate(form).catch(e => e.message);\n}\n"],"names":["registerValidatorByPath","form","path","registration","timer","controller","marked","lock","errorSource","hasErrors","errors","Array","isArray","length","mark","emitter","validating","add","key","emit","setValidatingByPath","unmark","delete","unsetValidatingByPath","runSync","sync","getValueByPath","setErrorByPath","validate","land","result","asyncAlways","gate","list","collectSyncErrors","own","supersede","clearTimeout","abort","runValidator","fn","AbortController","round","signal","e","isPromise","then","error","catch","finally","run","validators","set","debounce","setTimeout","async","trigger","name","options","settle","keys","waitUntil","FORM_VALIDATING_KEY","fieldsSettled","forEach","validator","runFormValidate","setFormErrors","segments","footprint","Object","entries","value","isIndex","normalizePath","setError","recordFootprint","isFieldError","createPath","stored","get","applyValidateResult","validateDeps","validateMode","formErrorFootprints","Map","getFormErrorFootprint","written","bumpErrorsVersion","JSON","parse","clearFormValidateErrors","clear","VALIDATION_OUTCOME","outcome","setParsedValues","values","WeakMap","SETTLED","formValidateStates","Promise","resolve","validateDebounce","getValues","state","waiters","getFormValidateState","reject","runFormValidateRound","settleFormValidate","push","waiter","revalidateFormOnChange","mode","has","hasTouchedByPath","reValidateMode","hasFormValidateErrors","fieldValidateDeps","revalidateDependentsOnChange","dependents","size","dependent"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var e=require("@for-fun/event-emitter"),t=require("./errors-8oPux7GR.cjs.js"),r=require("./values-DKpOCD5J.cjs.js");function n(e,r,a=[],i){Object.entries(r).forEach(([r,l])=>{const s=[...a,...t.isIndex(r)?[r]:t.normalizePath(r)];"string"==typeof l?l&&(t.setError(e,s,l),o(e,s,i)):Array.isArray(l)||t.isFieldError(l)?(t.setError(e,s,l),o(e,s,i)):l&&"object"==typeof l&&n(e,l,s,i)})}function o(e,r,n){if(!n)return;const o=t.create(r),a=e.errors.get(o.key);a&&n.set(o.key,a)}function a(r,o){const a=r.validateDeps||"onSubmit"!==r.validateMode?function(e){let t=i.get(e);t||(t=new Map,i.set(e,t));return t}(r):void 0;if(a&&(!function(r,n){for(const[o,a]of n){r.errors.get(o)===a&&(r.errors.delete(o),t.bumpErrorsVersion(r),e.emit(r.emitter,"errors",t.create(JSON.parse(o))))}}(r,a),a.clear()),o){if("object"==typeof o&&t.VALIDATION_OUTCOME in o){const e=o;return e.errors&&n(r,e.errors,[],a),void t.setParsedValues(r,e.values)}n(r,o,[],a)}}const i=new WeakMap;const l="__form_validate__";const s=Symbol("form-validate-settled"),u=new WeakMap;function c(t){const n=t.validate;if(!n)return Promise.resolve();const o=t.validateDebounce??0;if(o<=0){const e=new AbortController;return Promise.resolve(n(r.getValues(t),{form:t,signal:e.signal})).then(e=>{a(t,e)})}const i=function(e){let t=u.get(e);return t||(t={timer:null,controller:null,round:null,marked:!1,waiters:[]},u.set(e,t)),t}(t);return null!==i.timer?clearTimeout(i.timer):(i.marked=!0,t.validating.add(l),e.emit(t.emitter,"validating")),i.timer=setTimeout(()=>{i.timer=null;const e=i.round={};(function(e,t,n){const o=e.validate;if(!o)return Promise.resolve();t.controller?.abort();const i=t.controller=new AbortController;let l;try{l=Promise.resolve(o(r.getValues(e),{form:e,signal:i.signal}))}catch(e){l=Promise.reject(e)}return l.then(r=>{t.round===n&&a(e,r)},e=>{if(t.round===n)throw e})})(t,i,e).then(()=>d(t,i,e,s),r=>d(t,i,e,r))},o),new Promise((e,t)=>{i.waiters.push({resolve:e,reject:t})})}function d(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(l),e.emit(t.emitter,"validating"));const a=r.waiters;r.waiters=[];for(const e of a)o===s?e.resolve():e.reject(o)}const f=new WeakMap;exports.registerValidatorByPath=function(n,o,a){let i=null,l=null,s=!1,u=null,c=null;const d=e=>void 0!==e&&!(Array.isArray(e)&&0===e.length),f=()=>{s||(s=!0,function({emitter:t,validating:r},n){r.add(n.key),e.emit(t,"validating",n)}(n,o))},v=()=>{s&&(s=!1,function({emitter:t,validating:r},n){r.delete(n.key),e.emit(t,"validating",n)}(n,o))},m=()=>{const e=a.sync();if(!e)return!1;const i=e(r.getValueByPath(n,o),{form:n,path:o});return d(i)?(t.setErrorByPath(n,o,i),c="sync",!0):(a.validate()&&"sync"!==c||(t.setErrorByPath(n,o,void 0),c=null),!1)},y=e=>{if(a.asyncAlways?.()){const i=(()=>{const e=a.sync();if(!e)return null;const t=e(r.getValueByPath(n,o),{form:n,path:o});if(void 0===t)return null;const i=Array.isArray(t)?t:[t];return i.length?i:null})(),l=void 0===e?[]:Array.isArray(e)?e:[e];t.setErrorByPath(n,o,[...i??[],...l]),c=d(e)?"validator":i?"sync":null}else t.setErrorByPath(n,o,e),c=d(e)?"validator":null},h=()=>{null!==i&&(clearTimeout(i),i=null),l?.abort(),u={}},g=()=>{const e=a.validate();if(!e)return void v();l?.abort(),l=new AbortController;const i=u={};let s;try{s=e(r.getValueByPath(n,o),{form:n,path:o,signal:l.signal})}catch(e){throw v(),e}if(!t.isPromise(s))return y(s),void v();f(),s.then(e=>{u===i&&y(e)}).catch(()=>{}).finally(()=>{u===i&&(v(),u=null)})},p=()=>{if(i=null,m()&&!a.asyncAlways?.())return h(),void v();g()};return n.validators.set(o.key,()=>{if(m()&&!a.asyncAlways?.())return h(),void v();if(!a.validate())return;const e=a.debounce();if(e>0)return null!==i?clearTimeout(i):f(),void(i=setTimeout(p,e));g()}),()=>{n.validators.delete(o.key),null!==i&&(clearTimeout(i),i=null),v(),l?.abort()}},exports.revalidateDependentsOnChange=function(e,t,n){const o=f.get(e)?.get(t.key);if(o?.size)for(const a of o)a!==t.key&&("onChange"===n||"all"===n||"onTouched"===n&&r.hasTouchedByPath(e,t)||"onChange"===e.reValidateMode&&e.errors.has(a))&&e.validators.get(a)?.()},exports.revalidateFormOnChange=function(e,t,n){e.validateDeps?.has(t.key)&&e.validate&&("onChange"===n||"all"===n||"onTouched"===n&&r.hasTouchedByPath(e,t)||"onChange"===e.reValidateMode&&function(e){const t=i.get(e);if(!t)return!1;for(const[r,n]of t)if(e.errors.get(r)===n)return!0;return!1}(e))&&c(e).catch(()=>{})},exports.runFormValidate=c,exports.trigger=async function(e,r,n){const o=r=>t.waitUntil(e.emitter,"validating",()=>function(e){for(const t of e.validating)if(t!==l)return!1;return!0}(e),()=>!1);return e.validators.forEach(e=>e()),await o(),e.validate&&await c(e),!t.hasErrors(e)};
|
|
2
|
+
//# sourceMappingURL=validate-BvN6hiEG.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-BvN6hiEG.cjs.js","sources":["../src/core/validate.ts"],"sourcesContent":["import {emit} from '../emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport {isIndex, isPromise, normalizePath, waitUntil} from '../util';\nimport type {\n FieldError,\n FieldErrorEntry,\n Form,\n ValidationMode,\n ValidateResult,\n ValidationOutcome\n} from '../form';\nimport {\n VALIDATION_OUTCOME,\n getErrors,\n getFirstError,\n hasErrors,\n setError,\n setErrorByPath\n} from './errors';\nimport {hasTouchedByPath, setTouchedByPath} from './touched';\nimport {getValueByPath, getValues} from './values';\nimport {\n bumpErrorsVersion,\n isFieldError,\n isSegmentsPath,\n setParsedValues\n} from './internals';\n\nexport function unsetValidatingByPath(\n {emitter, validating}: Form,\n path: Path\n): void {\n validating.delete(path.key);\n // Path payload lets key-scoped subscribers (onKeyEvent) skip unrelated\n // fields; payload-less listeners ignore it.\n emit(emitter, 'validating', path);\n}\n\nexport function setValidatingByPath(\n {emitter, validating}: Form,\n path: Path\n): void {\n validating.add(path.key);\n emit(emitter, 'validating', path);\n}\n\n/**\n * Field validator. Returns an error (a string, a FieldError, or an array\n * mixing both) or undefined when valid; may return a Promise for async\n * validation.\n *\n * The second argument carries the validation context. `meta.signal` is\n * aborted as soon as the round is superseded — a newer round started, or\n * the field unregistered — so async validators can cancel their underlying\n * work (fetch, timers) instead of racing a stale result home. Stale\n * results are dropped independently by the registration's lock\n * ({@link registerValidatorByPath}), so validators that ignore the signal\n * stay correct too. Validators written against the older two-argument\n * signature keep working.\n */\nexport type Validator = (\n value: any,\n meta: {form: Form; path: Path; signal: AbortSignal}\n) =>\n | string\n | FieldError\n | (string | FieldError)[]\n | undefined\n | Promise<string | FieldError | (string | FieldError)[] | undefined>;\n\n/**\n * Synchronous pre-validator for {@link registerValidatorByPath}'s `sync`\n * accessor — declarative `required` rules compiled by `rulesToValidator`\n * in practice, but any sync-only check works. Runs on every kick, never\n * debounced: its errors land immediately and, while present,\n * short-circuit the debounced validator for that kick (the expensive\n * check never sees a value the gate already rejects). Must be synchronous\n * — unlike a {@link Validator} it may not return a Promise — and its meta\n * carries no `signal`: there is nothing to abort in a synchronous check.\n */\nexport type SyncValidator = (\n value: any,\n meta: {form: Form; path: Path}\n) => string | FieldError | (string | FieldError)[] | undefined;\n\n/** Live options for {@link registerValidatorByPath}: read at every kick\n * through accessors, so callers (React's `useValidate`) can swap the\n * validator/debounce/sync-gate per render without re-subscribing the\n * registration mid-flight. */\nexport type ValidatorRegistration = {\n /** Current debounced validator (or undefined — a sync-only\n * registration). */\n validate: () => Validator | undefined;\n /** Debounce delay in milliseconds; 0 (default) runs immediately. */\n debounce: () => number;\n /** Synchronous pre-validator, run on every kick (never debounced). */\n sync: () => SyncValidator | undefined;\n /** Whether the debounced validator still runs when the sync gate\n * failed — TanStack Form's `asyncAlways`: the gate's errors land\n * immediately (never debounced), then the validator's own result lands\n * alongside them (per-source semantics) instead of the gate\n * short-circuiting the whole kick. Optional — absent means false\n * (gate failure owns the kick's outcome), so pre-existing\n * framework-free registrations keep working unchanged. */\n asyncAlways?: () => boolean;\n};\n\n/**\n * Register a field validator's kick at `path` in {@link Form.validators}\n * — the framework-free machinery behind `useValidate`. Returns a\n * disposer that drops the registration and cancels any pending debounce\n * window or in-flight round (its signal aborts and its validating mark\n * is released).\n *\n * Contract of the registered kick (the same contract `trigger` /\n * `ensureValidate` rely on when they run every entry, and the\n * user-change gate relies on when it runs the changed path's entry):\n * - the `sync` gate runs immediately on every kick — never debounced —\n * and while it returns errors, the debounced validator is skipped for\n * that kick and any pending window or in-flight round is superseded —\n * unless `asyncAlways` is set, in which case the validator still runs\n * and its result lands alongside the gate's errors (per-source);\n * - a positive `debounce` merges kicks inside the window: only the last\n * one runs the validator, and while the timer is pending the field\n * counts as validating so `trigger`/`ensureValidate` wait it out;\n * - async results land under a lock: a superseded round's outcome —\n * rejection included — is dropped, and only the owning round releases\n * the validating mark;\n * - a synchronous throw inside the validator propagates to the caller\n * (the validating mark is not left stuck behind it).\n *\n * Registering at a path already registered by another mount replaces it\n * (last-wins, the historical `useValidate` behavior); the disposer drops\n * its own registration unconditionally.\n *\n * @param form\n * @param path\n * @param registration live validator/debounce/sync accessors\n * @return disposer: unregister and cancel pending work\n */\nexport function registerValidatorByPath(\n form: Form,\n path: Path,\n registration: ValidatorRegistration\n): () => void {\n // The pending debounce timer and the current round's controller live in\n // this closure so the disposer below can cancel them.\n let timer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n // Whether this registration currently holds the path's slot in\n // form.validating. The mark is taken when a debounce window opens or an\n // async round starts, and released by whichever round settles last —\n // including a later sync round that supersedes an in-flight async one\n // (its own .finally is lock-gated out by then).\n let marked = false;\n // The async-round lock: only the latest round may land its result or\n // release the mark; a superseded round's outcome is dropped wholesale.\n let lock: object | null = null;\n // Which source wrote the error currently on display — the sync gate or\n // the debounced validator. Tracked so a passing sync check can clear\n // its own stale error immediately instead of leaving it on screen until\n // the debounced round lands. External writers (setError, form-level\n // validate) are invisible here; a passing round clearing them matches\n // the long-standing \"a field validator owns its whole key\" contract.\n let errorSource: 'sync' | 'validator' | null = null;\n /** Does a validator result land errors? `[]` normalizes away exactly\n * like undefined in setErrorByPath. */\n const hasErrors = (errors: any): boolean =>\n errors !== undefined && !(Array.isArray(errors) && errors.length === 0);\n const mark = () => {\n if (marked) return;\n marked = true;\n setValidatingByPath(form, path);\n };\n const unmark = () => {\n if (!marked) return;\n marked = false;\n unsetValidatingByPath(form, path);\n };\n\n /** Run the synchronous gate on the field's current value. Its errors\n * land immediately — the gate is never debounced. Returns true when\n * errors landed (the kick's whole outcome for the debounced validator).\n * A passing gate clears the field's errors when they were its own from\n * an earlier kick, or when no debounced validator exists to own the\n * round. */\n const runSync = (): boolean => {\n const sync = registration.sync();\n if (!sync) return false;\n const errors = sync(getValueByPath(form, path), {form, path});\n if (!hasErrors(errors)) {\n // A stale error the gate itself wrote is answered by the gate\n // alone; a rules-only registration's passing check is the whole\n // round. With a debounced validator registered, its upcoming round\n // owns the outcome and lands it later.\n if (!registration.validate() || errorSource === 'sync') {\n setErrorByPath(form, path, undefined);\n errorSource = null;\n }\n return false;\n }\n setErrorByPath(form, path, errors);\n errorSource = 'sync';\n return true;\n };\n\n /** Re-run the sync gate purely for its error list — no store write.\n * asyncAlways landings merge it with the validator's result so each\n * source keeps its own errors on display (TanStack's per-source\n * errorMap shape); the non-asyncAlways path never calls this. */\n const collectSyncErrors = (): (string | FieldError)[] | null => {\n const sync = registration.sync();\n if (!sync) return null;\n const errors = sync(getValueByPath(form, path), {form, path});\n if (errors === undefined) return null;\n const list = Array.isArray(errors) ? errors : [errors];\n return list.length ? list : null;\n };\n\n /** Land a validator result. asyncAlways merges the gate's current\n * verdict (re-collected — the value may have drifted since the round\n * started) ahead of the validator's errors, so a still-failing gate\n * keeps its own errors on screen; the default path stays the\n * historical \"validator owns the whole key\" write. */\n const land = (\n result: string | FieldError | (string | FieldError)[] | undefined\n ): void => {\n if (registration.asyncAlways?.()) {\n const gate = collectSyncErrors();\n const own =\n result === undefined ? [] : Array.isArray(result) ? result : [result];\n setErrorByPath(form, path, [...(gate ?? []), ...own]);\n errorSource = hasErrors(result) ? 'validator' : gate ? 'sync' : null;\n } else {\n setErrorByPath(form, path, result);\n errorSource = hasErrors(result) ? 'validator' : null;\n }\n };\n\n /** Drop any pending window or in-flight round without landing it: the\n * sync gate now owns the outcome, so the debounced validator must not\n * run for this value. */\n const supersede = () => {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n controller?.abort();\n lock = {};\n };\n\n /** Run the debounced validator on the field's current value and land\n * its result — the sync gate has already passed. */\n const runValidator = () => {\n const fn = registration.validate();\n if (!fn) {\n unmark();\n return;\n }\n // Abort the superseded round's signal: a listening validator should\n // stop its underlying work. The lock refresh below independently\n // drops any result that still arrives, signal or not.\n controller?.abort();\n controller = new AbortController();\n const round = (lock = {});\n let result;\n try {\n result = fn(getValueByPath(form, path), {\n form,\n path,\n signal: controller.signal\n });\n } catch (e) {\n // A throwing sync validator propagates to the caller as it always\n // has; just don't leave the validating mark stuck behind it.\n unmark();\n throw e;\n }\n if (!isPromise(result)) {\n land(result);\n // Error first, then release the mark: 'validating' subscribers\n // (trigger) re-read state on wake and must see the landed error.\n unmark();\n return;\n }\n mark();\n result\n .then(\n (error: string | FieldError | (string | FieldError)[] | undefined) => {\n if (lock === round) {\n land(error);\n }\n }\n )\n // A rejected round is the normal way a signal-listening validator\n // gives up (fetch throws AbortError once aborted); swallow it and\n // let the owning round write the outcome.\n .catch(() => {})\n .finally(() => {\n if (lock === round) {\n unmark();\n lock = null;\n }\n });\n };\n\n /** A debounce window fired: the value may have drifted since the last\n * kick (programmatic writes do not kick validators), so re-run the\n * sync gate before spending the debounced validator on a value the\n * gate already rejects. Under asyncAlways a failing gate does not own\n * the outcome — the validator still runs and both verdicts land. */\n const run = () => {\n timer = null;\n if (runSync() && !registration.asyncAlways?.()) {\n supersede();\n unmark();\n return;\n }\n runValidator();\n };\n\n const kick = () => {\n if (runSync() && !registration.asyncAlways?.()) {\n supersede();\n unmark();\n return;\n }\n if (!registration.validate()) return;\n const debounce = registration.debounce();\n if (debounce > 0) {\n // Only the last kick inside the window runs: restart the timer on\n // every kick. The mark keeps trigger/ensureValidate's\n // validating-set wait covering the pending timer, not just\n // in-flight promises.\n if (timer !== null) clearTimeout(timer);\n else mark();\n timer = setTimeout(run, debounce);\n return;\n }\n runValidator();\n };\n\n form.validators.set(path.key, kick);\n return () => {\n form.validators.delete(path.key);\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n unmark();\n controller?.abort();\n };\n}\n\n/**\n * Set field error\n * @param form\n * @param name\n * @param error string is normalized to {type: 'custom', message}; a\n * FieldError object is stored as-is; an array holds several errors\n * (falsy items dropped, strings normalized); undefined clears\n */\n/** Options accepted by {@link trigger}. `shouldTouch` defaults to `false`;\n * omitting the options object entirely keeps the plain validate-only\n * behavior, so the historical two-argument calls are untouched. */\nexport type TriggerOptions = {\n /** Mark every path in the triggered scope as touched — even when\n * validation fails — once the round settles. Mirrors react-hook-form's\n * trigger `shouldTouch`. Defaults to `false`. */\n shouldTouch?: boolean;\n /**\n * Focus the first errored field in the triggered scope once the round\n * settles (and only when the round left errors) — react-hook-form's\n * trigger `shouldFocus` counterpart. Rides the 'focusError' event\n * channel like a failed submit's auto-focus: only mounted bound fields\n * react, unmounted ones are silent no-ops. Without `name` the first key\n * of the errors Map wins (the same rule handleSubmit applies); with\n * `name` the first errored triggered key does. Defaults to `false`.\n */\n shouldFocus?: boolean;\n};\n\n/**\n * Trigger field validation.\n *\n * Without `name` every registered field validator runs. A single `name` —\n * dotted string or segments array — runs only that field's validator, and\n * an array of names runs each one in order. An empty array is a no-op, as\n * is any name with no registered validator. An array argument counts as\n * one segments path only when it mixes in numbers (`['items', 0]`); pure\n * string arrays are name lists, so `['a', 'b']` triggers fields `a` and\n * `b`, not the nested path `a.b`.\n *\n * `options.shouldTouch` marks the triggered scope — the given names, or\n * every registered field when `name` is omitted — as touched after the\n * round settles, whether validation passed or failed. The wait/settle\n * logic is untouched: the marking rides on top of the settled round, so\n * subscribers observe errors and touched together rather than mid-flight.\n *\n * The returned promise waits for the triggered validation to settle —\n * async validators included — so their errors have already landed in\n * `form.errors` when it resolves. It never rejects: landing errors is the\n * expected outcome here, not a failure. Resolves `true` when the triggered\n * scope is error-free, `false` otherwise. Without `name` the scope is all\n * fields plus the form-level `validate` result (which runs after field\n * validators settle, same pipeline as {@link ensureValidate}); with `name`\n * only those fields' own errors count and form-level `validate` is\n * skipped (RHF semantics).\n *\n * Fire-and-forget callers may ignore the promise: the validator kicks\n * still happen synchronously, matching the pre-promise behavior.\n *\n * @param form\n * @param name field name(s) to trigger, or all fields when omitted\n * @param options extra behavior toggles ({@link TriggerOptions}); omitted,\n * validation alone runs — no touched marking\n * @return whether the triggered scope is error-free once validation settles\n */\nexport async function trigger(\n form: Form,\n name?: Name | Name[],\n options?: TriggerOptions\n): Promise<boolean> {\n // Never reject (an error landing is a normal outcome, not a failure), so\n // waitUntil's isReject is permanently false. Without a name the wait is\n // deliberately conservative — every FIELD validator, unrelated in-flight\n // ones included, because the round covers the whole form (and the\n // form-level validate's own window is excluded via fieldsSettled —\n // callers wait that out through the kick's promise instead, so a pending\n // window never gates the next kick). With a name the wait narrows to the\n // triggered keys only: a slow async validator on field B must not hold\n // trigger('a') hostage when the round never reads B.\n const settle = (keys?: string[]) =>\n waitUntil(\n form.emitter,\n 'validating',\n () =>\n keys === undefined\n ? fieldsSettled(form)\n : keys.every(key => !form.validating.has(key)),\n () => false\n );\n\n if (name === undefined) {\n form.validators.forEach(validator => validator());\n await settle();\n if (form.validate) await runFormValidate(form);\n // shouldTouch marks the whole registered scope — every key the round\n // could have validated — pass or fail alike.\n if (options?.shouldTouch) touchKeys(form, [...form.validators.keys()]);\n // First error across the errors Map — the same rule a failed submit's\n // auto-focus applies (a form-level error may land first; it has no\n // element, so it is a silent no-op like every unbound path).\n if (options?.shouldFocus) {\n const firstKey = form.errors.keys().next().value;\n if (firstKey !== undefined) emit(form.emitter, 'focusError', firstKey);\n }\n return !hasErrors(form);\n }\n\n const keys: string[] =\n typeof name === 'string' || isSegmentsPath(name)\n ? [createPath(name).key]\n : name.map(one => createPath(one).key);\n keys.forEach(key => form.validators.get(key)?.());\n await settle(keys);\n if (options?.shouldTouch) touchKeys(form, keys);\n // Focus the first errored key among the triggered scope — trigger('a')\n // never focuses B's pre-existing error.\n if (options?.shouldFocus) {\n const firstKey = keys.find(key => form.errors.has(key));\n if (firstKey !== undefined) emit(form.emitter, 'focusError', firstKey);\n }\n return keys.every(key => !form.errors.has(key));\n}\n\n/** trigger's `shouldTouch` marking: touch every key in the triggered scope\n * through {@link setTouchedByPath}, which no-ops on already-touched keys\n * and emits the path-carrying 'touched' event per newly touched one. Keys\n * are the stored JSON-stringified segments shape, so parse them back into\n * Path — normalizePath passes segment arrays through untouched, making the\n * key round-trip exact. */\nfunction touchKeys(form: Form, keys: string[]): void {\n keys.forEach(key => setTouchedByPath(form, createPath(JSON.parse(key))));\n}\n\n/**\n * Flatten a form-level validate result and write each leaf error through\n * setError. Nested objects descend into deeper paths ({a: {b: 'msg'}} sets\n * the 'a.b' error), array values contribute every non-empty string they\n * hold as separate errors (zod flatten() formErrors style), and\n * FieldError-shaped objects are stored as-is. Falsy values are skipped.\n *\n * When `footprint` is passed (forms with `validateDeps` or a live\n * `validateMode`), every leaf this round actually stored is recorded into\n * it — the exact stored array — so the next round can drop exactly what\n * this one wrote.\n */\nfunction setFormErrors(\n form: Form,\n result: Record<string, any>,\n segments: PathSegments = [],\n footprint?: Map<string, FieldError[]>\n): void {\n Object.entries(result).forEach(([key, value]) => {\n // Error-tree keys are explicit object keys, not path expressions:\n // a numeric key ('0' — Standard Schema issue paths stringify array\n // indices) stays a literal string segment instead of feeding the\n // path parser, whose dotted-numeric rule governs path strings only.\n const path: PathSegments = [\n ...segments,\n ...(isIndex(key) ? [key] : normalizePath(key))\n ];\n if (typeof value === 'string') {\n if (value) {\n setError(form, path, value);\n recordFootprint(form, path, footprint);\n }\n } else if (Array.isArray(value)) {\n setError(form, path, value);\n recordFootprint(form, path, footprint);\n } else if (isFieldError(value)) {\n setError(form, path, value);\n recordFootprint(form, path, footprint);\n } else if (value && typeof value === 'object') {\n setFormErrors(form, value, path, footprint);\n }\n });\n}\n\n/** Record one leaf write of a form-level validate round: the path key and\n * the exact array now stored there. Nothing is recorded when the write\n * normalized away (all-empty arrays) — there is no error to own. The\n * stored array is read back from the errors Map because setErrorByPath\n * owns normalization. */\nfunction recordFootprint(\n form: Form,\n segments: PathSegments,\n footprint: Map<string, FieldError[]> | undefined\n): void {\n if (!footprint) return;\n const path = createPath(segments);\n const stored = form.errors.get(path.key);\n if (stored) footprint.set(path.key, stored);\n}\n\n/**\n * Land a form-level validate result. A plain record keeps the\n * long-standing behavior — flattened into field errors by\n * {@link setFormErrors}. A branded {@link ValidationOutcome} splits\n * instead: `errors` flattens exactly like a plain record, and `values`\n * (the schema's parsed output — coerced/transformed values included)\n * becomes the form's parsedValues baseline. Falsy results are skipped,\n * branded or not.\n *\n * Forms that re-run the validate on user input — via {@link\n * Options.validateDeps} or a live {@link Options.validateMode} cadence —\n * additionally get round-scoped error ownership: before the new result\n * lands, the errors the previous round wrote are dropped\n * ({@link clearFormValidateErrors}), so a re-run that passes makes the\n * cross-field error disappear — and the new round's own writes become the\n * tracked footprint. Submit-only forms (no deps, `validateMode:\n * 'onSubmit'`) keep the historical write-only behavior untouched.\n */\nfunction applyValidateResult(\n form: Form,\n result: ValidateResult<any> | undefined\n): void {\n const footprint =\n form.validateDeps || form.validateMode !== 'onSubmit'\n ? getFormErrorFootprint(form)\n : undefined;\n if (footprint) {\n clearFormValidateErrors(form, footprint);\n footprint.clear();\n }\n if (!result) return;\n if (typeof result === 'object' && VALIDATION_OUTCOME in result) {\n const outcome = result as ValidationOutcome<any>;\n if (outcome.errors) setFormErrors(form, outcome.errors, [], footprint);\n setParsedValues(form, outcome.values);\n return;\n }\n setFormErrors(form, result as Record<string, any>, [], footprint);\n}\n\n/** Per-form error footprint of the last form-level validate round: every\n * path key it flattened onto, with the exact array instance it stored.\n * Tracked only for forms that re-run the validate on user input\n * (`validateDeps` or a live `validateMode`) — held in a WeakMap so the\n * Form shape and the submit-only pipeline stay untouched. */\nconst formErrorFootprints = new WeakMap<Form, Map<string, FieldError[]>>();\n\nfunction getFormErrorFootprint(form: Form): Map<string, FieldError[]> {\n let footprint = formErrorFootprints.get(form);\n if (!footprint) {\n footprint = new Map();\n formErrorFootprints.set(form, footprint);\n }\n return footprint;\n}\n\n/** Does the form still show an error the last form-level round wrote?\n * Compared by identity, not key membership: once a field validator,\n * `setServerErrors`, a manual `setError` or `clearErrors` replaces the\n * stored array, that error is no longer the round's to own — neither the\n * dep-change gate nor the next round's clearing may touch it. */\nfunction hasFormValidateErrors(form: Form): boolean {\n const footprint = formErrorFootprints.get(form);\n if (!footprint) return false;\n for (const [key, written] of footprint) {\n if (form.errors.get(key) === written) return true;\n }\n return false;\n}\n\n/** Drop the last form-level round's errors before the next round lands.\n * Per key the stored array is identity-checked — an error overwritten or\n * cleared by anyone else in between survives — and each drop emits the\n * same path-payload 'errors' event {@link setErrorByPath} would, so\n * subscribed fields re-render exactly like on any error write. */\nfunction clearFormValidateErrors(\n form: Form,\n footprint: Map<string, FieldError[]>\n): void {\n for (const [key, written] of footprint) {\n const stored = form.errors.get(key);\n if (stored !== written) continue;\n form.errors.delete(key);\n bumpErrorsVersion(form);\n emit(form.emitter, 'errors', createPath(JSON.parse(key)));\n }\n}\n\n/** Key the form-level validate round reserves in `form.validating` while\n * its debounce window is pending or its async round is in flight. Real\n * path keys are JSON-stringified segments (always bracketed), so a bare\n * word can never collide. */\nconst FORM_VALIDATING_KEY = '__form_validate__';\n\n/** Are all FIELD validation rounds drained? trigger/ensureValidate wait on\n * this before kicking the form-level validate (its errors gate whether the\n * form-level round may run at all). The form validate's own reserved key\n * is deliberately excluded: its window is waited out through the kick's\n * returned promise instead, so a pending window or in-flight form round\n * never gates the next kick — a kick during an in-flight round opens a\n * new window and the newer round supersedes, mirroring the per-field\n * `validateDebounce` contract. */\nfunction fieldsSettled(form: Form): boolean {\n for (const key of form.validating) {\n if (key !== FORM_VALIDATING_KEY) return false;\n }\n return true;\n}\n\n/** Sentinel telling {@link settleFormValidate} the round landed cleanly —\n * distinct from every rejection payload, including `undefined`. */\nconst SETTLED = Symbol('form-validate-settled');\n\n/** Per-form bookkeeping for the debounced form-level validate: the\n * pending window timer, the in-flight round, and the waiters merged into\n * the current window group. Held in a WeakMap so the Form instance shape\n * is untouched for forms that never set `validateDebounce`. */\ntype FormValidateState = {\n timer: ReturnType<typeof setTimeout> | null;\n controller: AbortController | null;\n /** Identity of the in-flight round; a superseded round's outcome\n * (rejection included) is dropped by comparing against it. */\n round: object | null;\n /** Whether this state currently holds FORM_VALIDATING_KEY in\n * form.validating. */\n marked: boolean;\n waiters: Array<{resolve: () => void; reject: (error: unknown) => void}>;\n};\n\nconst formValidateStates = new WeakMap<Form, FormValidateState>();\n\nfunction getFormValidateState(form: Form): FormValidateState {\n let state = formValidateStates.get(form);\n if (!state) {\n state = {\n timer: null,\n controller: null,\n round: null,\n marked: false,\n waiters: []\n };\n formValidateStates.set(form, state);\n }\n return state;\n}\n\n/**\n * Run the form-level `validate` and land its result, honoring the form's\n * `validateDebounce` option.\n *\n * Undebounced (`0`/undefined) the caller's await *is* the validate call —\n * the long-standing pipeline, unchanged: no validating mark, no round\n * gating, immediate values snapshot, rejection propagating to the caller.\n *\n * Debounced, the kick opens (or restarts — kicks inside the window merge)\n * a window during which the form counts as validating, so `trigger` /\n * `ensureValidate` / submit wait the window out exactly like a field's\n * `validateDebounce` window. When the timer fires, the round reads the\n * then-current values, supersedes (aborts) any in-flight round, and lands\n * its result. The returned promise settles once the window group's final\n * round has landed — rejecting when that round's validate callback threw\n * or its promise rejected, mirroring the undebounced propagation — so\n * merged callers all observe the same outcome.\n *\n * Only called under `if (form.validate)`. Public: the form-level-only\n * round (field validators excluded) — `useForm`'s `validateOnMount` run\n * and cross-cutting re-checks use it; `trigger`/`ensureValidate` compose\n * it after the field validators settle.\n */\nexport function runFormValidate(form: Form): Promise<void> {\n const validate = form.validate;\n if (!validate) return Promise.resolve();\n const debounce = form.validateDebounce ?? 0;\n if (debounce <= 0) {\n // Standalone controller: nothing supersedes an undebounced call, so\n // its signal never fires — it exists for argument-shape parity with\n // the debounced rounds (and with field-level meta.signal).\n const controller = new AbortController();\n return Promise.resolve(\n validate(getValues(form), {form, signal: controller.signal})\n ).then(result => {\n applyValidateResult(form, result);\n });\n }\n const state = getFormValidateState(form);\n // (Re)open the window: a kick while the timer is pending restarts it\n // (only the last kick's values run); one while a round is in flight\n // keeps the validating mark held and defers to the new window's round.\n if (state.timer !== null) clearTimeout(state.timer);\n else {\n state.marked = true;\n form.validating.add(FORM_VALIDATING_KEY);\n emit(form.emitter, 'validating');\n }\n state.timer = setTimeout(() => {\n state.timer = null;\n const round = (state.round = {});\n runFormValidateRound(form, state, round).then(\n () => settleFormValidate(form, state, round, SETTLED),\n error => settleFormValidate(form, state, round, error)\n );\n }, debounce);\n return new Promise<void>((resolve, reject) => {\n state.waiters.push({resolve, reject});\n });\n}\n\n/** Run one form-level validate round with the form's current values.\n * Aborts the previous in-flight round's signal; a superseded round's\n * outcome — rejection included — is dropped by the round gate, exactly\n * like the field-level lock. */\nfunction runFormValidateRound(\n form: Form,\n state: FormValidateState,\n round: object\n): Promise<void> {\n const validate = form.validate;\n if (!validate) return Promise.resolve();\n state.controller?.abort();\n const controller = (state.controller = new AbortController());\n let outcome: Promise<any>;\n try {\n outcome = Promise.resolve(\n validate(getValues(form), {form, signal: controller.signal})\n );\n } catch (error) {\n outcome = Promise.reject(error);\n }\n return outcome.then(\n result => {\n if (state.round === round) applyValidateResult(form, result);\n },\n error => {\n if (state.round === round) throw error;\n }\n );\n}\n\n/** Land the window group's outcome: release the validating mark — after\n * the round's errors/values have already landed, because 'validating'\n * subscribers (trigger, ensureValidate) re-read state on wake — and\n * settle every merged waiter. A superseded round never lands here (the\n * newer round owns the release), and a window that re-opened while the\n * round was in flight defers: the mark and the waiters carry over to the\n * pending timer's round. */\nfunction settleFormValidate(\n form: Form,\n state: FormValidateState,\n round: object,\n outcome: unknown\n): void {\n if (state.round !== round) return;\n state.round = null;\n if (state.timer !== null) return;\n if (state.marked) {\n state.marked = false;\n form.validating.delete(FORM_VALIDATING_KEY);\n emit(form.emitter, 'validating');\n }\n const waiters = state.waiters;\n state.waiters = [];\n for (const waiter of waiters) {\n if (outcome === SETTLED) waiter.resolve();\n else waiter.reject(outcome);\n }\n}\n\n/**\n * Form-level twin of the gated validator kick in `useField`'s onChange:\n * re-run the form-level `validate` after a user change to a field listed\n * in `validateDeps`. Called from the field's own change pipeline (typing\n * and `changeValue` alike — both route through the mounted field's\n * onChange), so programmatic `setValue` writes do not re-run it, exactly\n * like they do not re-run field validators.\n *\n * The gate mirrors the per-field matrix with the *changed field's*\n * effective `mode` (a per-field override governs when its changes may\n * fire validation) and the form-level `reValidateMode` against the last\n * round's error footprint ({@link hasFormValidateErrors} — field\n * validators' errors never arm this kick):\n * - `mode` `'onChange'`/`'all'` — every dep change re-runs;\n * - `mode` `'onTouched'` — dep changes re-run once the field was touched;\n * - otherwise the re-run waits for `reValidateMode: 'onChange'` (the\n * default) while the last round's error is still live — the\n * submit-then-fix flow: the mismatch lands on submit, editing the\n * dependency re-runs the validate and clears it.\n * `reValidateMode: 'onBlur'`/`'onSubmit'` never re-run on a change (a\n * change is not a blur; submit re-runs are the submit pipeline's job).\n *\n * The kick is fire-and-forget: async round rejections are swallowed\n * (nothing in an event handler can await them), while a synchronous\n * throw inside the validate callback propagates to the caller exactly\n * like a field validator's does.\n *\n * A no-op unless the form set `validateDeps` listing `path` — forms\n * without the option pay one property check here.\n */\nexport function revalidateFormOnChange(\n form: Form,\n path: Path,\n mode: ValidationMode\n): void {\n if (!form.validateDeps?.has(path.key) || !form.validate) return;\n if (\n mode === 'onChange' ||\n mode === 'all' ||\n (mode === 'onTouched' && hasTouchedByPath(form, path)) ||\n (form.reValidateMode === 'onChange' && hasFormValidateErrors(form))\n ) {\n runFormValidate(form).catch(() => {});\n }\n}\n\n/** Per-form registry of field-level `validateDeps` declarations ({@link\n * revalidateDependentsOnChange}): dep path key -> every dependent field key\n * that listed it. Held in a WeakMap so the Form shape is untouched for\n * forms whose fields never declare deps. */\nconst fieldValidateDeps = new WeakMap<Form, Map<string, Set<string>>>();\n\n/** Register one field's validateDeps declaration: `key` re-validates when\n * any path in `depKeys` takes a user change. Idempotent per (key, dep)\n * pair, so StrictMode's double effect is harmless. */\nexport function registerFieldValidateDeps(\n form: Form,\n key: string,\n depKeys: string[]\n): void {\n let deps = fieldValidateDeps.get(form);\n if (!deps) {\n deps = new Map();\n fieldValidateDeps.set(form, deps);\n }\n for (const depKey of depKeys) {\n let dependents = deps.get(depKey);\n if (!dependents) {\n dependents = new Set();\n deps.set(depKey, dependents);\n }\n dependents.add(key);\n }\n}\n\n/** Drop one field's validateDeps registration ({@link\n * registerFieldValidateDeps}). Entries nobody lists anymore are removed so\n * the registry never outlives its fields. */\nexport function unregisterFieldValidateDeps(\n form: Form,\n key: string,\n depKeys: string[]\n): void {\n const deps = fieldValidateDeps.get(form);\n if (!deps) return;\n for (const depKey of depKeys) {\n const dependents = deps.get(depKey);\n if (!dependents?.delete(key)) continue;\n if (!dependents.size) deps.delete(depKey);\n }\n}\n\n/**\n * Field-level twin of {@link revalidateFormOnChange}: after a user change\n * to `path`, re-run every field validator that declared `path` in its\n * `validateDeps` (useField option). Same channel, same gate: the kick\n * rides the changed field's own onChange pipeline (typing and\n * `changeValue` alike), so programmatic `setValue` writes never fire it —\n * exactly like field validators and the form-level `validateDeps`.\n *\n * The gate mirrors the form-level matrix with the *changed field's*\n * effective `mode` and the form-level `reValidateMode` against each\n * dependent's live error:\n * - `mode` `'onChange'`/`'all'` — every dep change re-runs the dependent;\n * - `mode` `'onTouched'` — once the changed field was touched;\n * - otherwise the re-run waits for `reValidateMode: 'onChange'` (the\n * default) while the dependent still shows an error — the\n * submit-then-fix flow: the mismatch lands on submit, editing the\n * dependency re-validates the dependent and a passing round clears it\n * (a field validator owns its whole key, so the re-run's result\n * replaces whatever the previous round wrote — the field-level shape\n * of the form-level footprint reclaim).\n *\n * The kick is an ordinary validator kick: the dependent's own\n * `validateDebounce` window applies, and a synchronous throw inside its\n * validate propagates to the caller like any field validator's would.\n *\n * A no-op unless some field declared `path` as a dep — forms without any\n * field-level `validateDeps` pay one property check here.\n */\nexport function revalidateDependentsOnChange(\n form: Form,\n path: Path,\n mode: ValidationMode\n): void {\n const dependents = fieldValidateDeps.get(form)?.get(path.key);\n if (!dependents?.size) return;\n for (const dependent of dependents) {\n // A self-dep changes nothing: the field's own onChange above already\n // validated it under the same gate.\n if (dependent === path.key) continue;\n if (\n mode === 'onChange' ||\n mode === 'all' ||\n (mode === 'onTouched' && hasTouchedByPath(form, path)) ||\n (form.reValidateMode === 'onChange' && form.errors.has(dependent))\n ) {\n form.validators.get(dependent)?.();\n }\n }\n}\n\n/** The Error {@link ensureValidate} rejects with: `message` is the first\n * error's display text ({@link getFirstError}) — the long-standing shape\n * — and `.errors` carries the complete flattened error list ({@link\n * getErrors}: `{path, type, message}` entries, dotted display paths) so\n * catchers can branch on types and locate fields without re-reading the\n * form. */\nexport type FormValidationError = Error & {errors: FieldErrorEntry[]};\n\n/** Build {@link ensureValidate}'s rejection: first error's message, every\n * error attached. */\nfunction validationError(form: Form): FormValidationError {\n const error = new Error(getFirstError(form)) as FormValidationError;\n error.errors = getErrors(form);\n return error;\n}\n\n/**\n * Validate and throw if any field error.\n * @param form\n * @return resolve if no error; reject and stop validate if has an error\n */\nexport async function ensureValidate(form: Form): Promise<void> {\n form.validators.forEach(validator => validator());\n\n await waitUntil(\n form.emitter,\n 'validating',\n () => fieldsSettled(form),\n () => hasErrors(form)\n ).catch(() => {\n throw validationError(form);\n });\n\n if (form.validate) {\n await runFormValidate(form);\n if (hasErrors(form)) throw validationError(form);\n }\n}\n\n/**\n * Validate and return if any field error.\n * @param form\n * @return error message string or void\n */\nexport async function validate(form: Form): Promise<void | string> {\n return ensureValidate(form).catch(e => e.message);\n}\n"],"names":["setFormErrors","form","result","segments","footprint","Object","entries","forEach","key","value","path","isIndex","normalizePath","setError","recordFootprint","Array","isArray","isFieldError","createPath","stored","errors","get","set","applyValidateResult","validateDeps","validateMode","formErrorFootprints","Map","getFormErrorFootprint","written","delete","bumpErrorsVersion","emit","emitter","JSON","parse","clearFormValidateErrors","clear","VALIDATION_OUTCOME","outcome","setParsedValues","values","WeakMap","FORM_VALIDATING_KEY","SETTLED","formValidateStates","runFormValidate","validate","Promise","resolve","debounce","validateDebounce","controller","AbortController","getValues","signal","then","state","timer","round","marked","waiters","getFormValidateState","clearTimeout","validating","add","setTimeout","abort","error","reject","runFormValidateRound","settleFormValidate","push","waiter","fieldValidateDeps","registration","lock","errorSource","hasErrors","length","mark","setValidatingByPath","unmark","unsetValidatingByPath","runSync","sync","getValueByPath","setErrorByPath","land","asyncAlways","gate","list","collectSyncErrors","own","supersede","runValidator","fn","e","isPromise","catch","finally","run","validators","mode","dependents","size","dependent","hasTouchedByPath","reValidateMode","has","hasFormValidateErrors","async","name","options","settle","keys","waitUntil","fieldsSettled","validator"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{emit as e}from"@for-fun/event-emitter";import{v as t,g as s,j as i,k as a,l as n,c as r,m as o,u,p as l,o as c,q as d,b as f,r as h}from"./errors-ClH3eV1R.mjs";function g({emitter:t,touched:s},i){s.has(i.key)||(s.add(i.key),e(t,"touched",i))}function v({touched:e},t){return e.has(t.key)}function m({touched:e}){return Array.from(e,e=>JSON.parse(e).join("."))}function y(e){let s=t.get(e);return s?s.version>0&&(s.result=k(e),s.version=0):(s={version:0,result:k(e)},t.set(e,s)),s.result}function k(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=o(r,JSON.parse(e),t,n);for(const e of a)r=u(r,JSON.parse(e));return r}function p({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 d(s.get(t),r.slice(e))}return d(t??e,r)}}function S(t,s,i,r){const{emitter:o,values:u,deleted:c}=t,d="function"==typeof i?i(p(t,s)):i;u.set(s.key,d),N(u,s),O(c,s),l(t,s),!1===r?.shouldDirty&&h(t,s,d),a(t),n(t),r?.shouldTouch&&g(t,s),r?.shouldValidate&&t.validators.get(s.key)?.(),e(o,"change",s)}function V(e,t,s){const{values:i,deleted:r}=e;i.set(t.key,s),N(i,t),O(r,t),l(e,t),a(e),n(e)}function b({emitter:t},s){e(t,"change",s)}function J(t,s,r){const{key:o,value:u}=s,{emitter:l,values:c,touched:d,errors:h,validating:g,deleted:v}=t;c.delete(o),i(t,o),function(e,t){for(let s=1;s<t.length;s++)if(e.has(JSON.stringify(t.slice(0,s))))return!0;const s=`${JSON.stringify(t).slice(0,-1)},`;for(const t of e.keys())if(t.startsWith(s))return!0;return!1}(c,u)||v.add(o),d.delete(o),h.delete(o)&&f(t),g.delete(o),a(t),n(t),e(l,"change",s),e(l,"touched",s),e(l,"errors",s),e(l,"validating",s)}function N(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)}function O(e,{key:t}){if(e.size)for(const s of e)(s===t||s.startsWith(`${t.slice(0,-1)},`)||t.startsWith(`${s.slice(0,-1)},`))&&e.delete(s)}function W(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 $(t,o,u){const l=[];t.initialValues=o??t.initialValues,t.parsedValues=void 0,s(t);const{emitter:c,touched:d,values:f,deleted:h,validating:g}=t;f.clear(),h.clear(),i(t),d.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 l)S(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{p as a,m as b,g as c,S as d,J as e,V as f,y as g,v as h,b as i,$ as r,W as s};
|
|
2
|
+
//# sourceMappingURL=values-Bod74Kzu.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values-Bod74Kzu.mjs","sources":["../src/core/touched.ts","../src/core/values.ts"],"sourcesContent":["import {emit} from '../emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport type {FieldPath} from '../types';\nimport type {Form} from '../form';\n\n/**\n * Set field touched state\n * @param form\n * @param name\n */\nexport function setTouched(form: Form, name: Name): void {\n setTouchedByPath(form, createPath(name));\n}\n\n/**\n * Set field touched state\n * @param form\n * @param path\n */\nexport function setTouchedByPath({emitter, touched}: Form, path: Path): void {\n if (touched.has(path.key)) return;\n touched.add(path.key);\n // Path payload lets key-scoped subscribers (onKeyEvent) skip unrelated\n // fields; payload-less listeners ignore it.\n emit(emitter, 'touched', path);\n}\n\n/**\n * Check if field has been touched\n * @param form\n * @param name\n */\nexport function hasTouched<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): boolean {\n return hasTouchedByPath(form, createPath(name));\n}\n\n/**\n * Check if field has been touched\n * @param form\n * @param path\n */\nexport function hasTouchedByPath({touched}: Form, path: Path): boolean {\n return touched.has(path.key);\n}\n\n/**\n * Is dirty -- any value differs from initialValues\n * @param form\n */\n/**\n * Get touched fields as user-facing dotted paths ('a.b', 'a.0.c'), unlike\n * the JSON array keys stored in the touched Set.\n * @param form\n * @return array of touched fields' dotted paths\n */\nexport function getTouchedFields({touched}: Form): string[] {\n return Array.from(touched, key =>\n (JSON.parse(key) as PathSegments).join('.')\n );\n}\n\n/**\n * Is touched -- any field has been touched\n * @param form\n */\nexport function isTouched({touched}: Form): boolean {\n return touched.size > 0;\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 */\n","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 bumpErrorsVersion,\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)) bumpErrorsVersion(form);\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 bumpErrorsVersion(form);\n emit(emitter, 'errors', path);\n }\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n}\n\n/**\n * @param form\n */\n"],"names":["setTouchedByPath","emitter","touched","path","has","key","add","emit","hasTouchedByPath","getTouchedFields","Array","from","JSON","parse","join","getValues","form","cache","valuesCaches","get","version","result","computeValues","set","initialValues","parsedValues","values","deleted","owned","Set","merged","value","setOwned","unset","getValueByPath","segments","i","length","ancestorKey","stringify","slice","setValueByPath","options","next","pruneDescendantKeys","reviveBranch","pruneDirtyBaselines","shouldDirty","setDirtyBaseline","bumpDirtyVersion","bumpValuesVersion","shouldTouch","shouldValidate","validators","seedValueByPath","emitChangeByPath","removeFieldByPath","errors","validating","delete","clearDirtyBaselines","stem","keys","startsWith","hasLiveBranch","size","k","tombstone","setInitialValues","isEqual","clear","reset","keptValues","clearErrors","isSubmitting","submitCount","isSubmitted","isSubmitSuccessful","createPath"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var e=require("@for-fun/event-emitter"),t=require("./errors-8oPux7GR.cjs.js");function s({emitter:t,touched:s},i){s.has(i.key)||(s.add(i.key),e.emit(t,"touched",i))}function i(e){const{initialValues:s,parsedValues:i,values:r,deleted:n}=e,u=new Set;let a=i??s;for(const[e,s]of r)a=t.setOwned(a,JSON.parse(e),s,u);for(const e of n)a=t.unset(a,JSON.parse(e));return a}function r({initialValues:e,parsedValues:s,values:i,deleted:r},n){const{key:u,value:a}=n;if(i.has(u))return i.get(u);if(!r.has(u)){for(let e=a.length-1;e>0;e--){const s=JSON.stringify(a.slice(0,e));if(i.has(s))return t.get(i.get(s),a.slice(e))}return t.get(s??e,a)}}function n(i,n,o,l){const{emitter:c,values:d,deleted:f}=i,m="function"==typeof o?o(r(i,n)):o;d.set(n.key,m),u(d,n),a(f,n),t.pruneDirtyBaselines(i,n),!1===l?.shouldDirty&&t.setDirtyBaseline(i,n,m),t.bumpDirtyVersion(i),t.bumpValuesVersion(i),l?.shouldTouch&&s(i,n),l?.shouldValidate&&i.validators.get(n.key)?.(),e.emit(c,"change",n)}function u(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)}function a(e,{key:t}){if(e.size)for(const s of e)(s===t||s.startsWith(`${t.slice(0,-1)},`)||t.startsWith(`${s.slice(0,-1)},`))&&e.delete(s)}exports.emitChangeByPath=function({emitter:t},s){e.emit(t,"change",s)},exports.getTouchedFields=function({touched:e}){return Array.from(e,e=>JSON.parse(e).join("."))},exports.getValueByPath=r,exports.getValues=function(e){let s=t.valuesCaches.get(e);return s?s.version>0&&(s.result=i(e),s.version=0):(s={version:0,result:i(e)},t.valuesCaches.set(e,s)),s.result},exports.hasTouchedByPath=function({touched:e},t){return e.has(t.key)},exports.removeFieldByPath=function(s,i,r){const{key:n,value:u}=i,{emitter:a,values:o,touched:l,errors:c,validating:d,deleted:f}=s;o.delete(n),t.clearDirtyBaselines(s,n),function(e,t){for(let s=1;s<t.length;s++)if(e.has(JSON.stringify(t.slice(0,s))))return!0;const s=`${JSON.stringify(t).slice(0,-1)},`;for(const t of e.keys())if(t.startsWith(s))return!0;return!1}(o,u)||f.add(n),l.delete(n),c.delete(n)&&t.bumpErrorsVersion(s),d.delete(n),t.bumpDirtyVersion(s),t.bumpValuesVersion(s),e.emit(a,"change",i),e.emit(a,"touched",i),e.emit(a,"errors",i),e.emit(a,"validating",i)},exports.reset=function(s,i,r){const u=[];s.initialValues=i??s.initialValues,s.parsedValues=void 0,t.clearErrors(s);const{emitter:a,touched:o,values:l,deleted:c,validating:d}=s;l.clear(),c.clear(),t.clearDirtyBaselines(s),o.clear(),d.clear(),s.isSubmitting=!1,s.submitCount=0,s.isSubmitted=!1,s.isSubmitSuccessful=void 0,t.bumpDirtyVersion(s),t.bumpValuesVersion(s);for(const{segments:e,value:i}of u)n(s,t.create(e),i);e.emit(a,"change"),e.emit(a,"touched"),e.emit(a,"validating"),e.emit(a,"submitting"),e.emit(a,"submitCount"),e.emit(a,"submitSuccessful"),e.emit(a,"reset")},exports.seedValueByPath=function(e,s,i){const{values:r,deleted:n}=e;r.set(s.key,i),u(r,s),a(n,s),t.pruneDirtyBaselines(e,s),t.bumpDirtyVersion(e),t.bumpValuesVersion(e)},exports.setInitialValues=function(s,i){s.initialValues===i||t.isEqual(s.initialValues,i)||(s.initialValues=i,s.parsedValues=void 0,s.values.clear(),s.deleted.clear(),t.clearDirtyBaselines(s),t.bumpDirtyVersion(s),t.bumpValuesVersion(s),e.emit(s.emitter,"change"))},exports.setTouchedByPath=s,exports.setValueByPath=n;
|
|
2
|
+
//# sourceMappingURL=values-DKpOCD5J.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values-DKpOCD5J.cjs.js","sources":["../src/core/touched.ts","../src/core/values.ts"],"sourcesContent":["import {emit} from '../emitter';\nimport createPath from '../path';\nimport type {Name, Path, PathSegments} from '../path';\nimport type {FieldPath} from '../types';\nimport type {Form} from '../form';\n\n/**\n * Set field touched state\n * @param form\n * @param name\n */\nexport function setTouched(form: Form, name: Name): void {\n setTouchedByPath(form, createPath(name));\n}\n\n/**\n * Set field touched state\n * @param form\n * @param path\n */\nexport function setTouchedByPath({emitter, touched}: Form, path: Path): void {\n if (touched.has(path.key)) return;\n touched.add(path.key);\n // Path payload lets key-scoped subscribers (onKeyEvent) skip unrelated\n // fields; payload-less listeners ignore it.\n emit(emitter, 'touched', path);\n}\n\n/**\n * Check if field has been touched\n * @param form\n * @param name\n */\nexport function hasTouched<\n T extends Record<string, any> = any,\n P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments\n>(form: Form<T>, name: P): boolean {\n return hasTouchedByPath(form, createPath(name));\n}\n\n/**\n * Check if field has been touched\n * @param form\n * @param path\n */\nexport function hasTouchedByPath({touched}: Form, path: Path): boolean {\n return touched.has(path.key);\n}\n\n/**\n * Is dirty -- any value differs from initialValues\n * @param form\n */\n/**\n * Get touched fields as user-facing dotted paths ('a.b', 'a.0.c'), unlike\n * the JSON array keys stored in the touched Set.\n * @param form\n * @return array of touched fields' dotted paths\n */\nexport function getTouchedFields({touched}: Form): string[] {\n return Array.from(touched, key =>\n (JSON.parse(key) as PathSegments).join('.')\n );\n}\n\n/**\n * Is touched -- any field has been touched\n * @param form\n */\nexport function isTouched({touched}: Form): boolean {\n return touched.size > 0;\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 */\n","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 bumpErrorsVersion,\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)) bumpErrorsVersion(form);\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 bumpErrorsVersion(form);\n emit(emitter, 'errors', path);\n }\n bumpDirtyVersion(form);\n bumpValuesVersion(form);\n}\n\n/**\n * @param form\n */\n"],"names":["setTouchedByPath","emitter","touched","path","has","key","add","emit","computeValues","form","initialValues","parsedValues","values","deleted","owned","Set","merged","value","setOwned","JSON","parse","unset","getValueByPath","segments","get","i","length","ancestorKey","stringify","slice","setValueByPath","options","next","set","pruneDescendantKeys","reviveBranch","pruneDirtyBaselines","shouldDirty","setDirtyBaseline","bumpDirtyVersion","bumpValuesVersion","shouldTouch","shouldValidate","validators","size","stem","k","keys","startsWith","delete","tombstone","Array","from","join","cache","valuesCaches","version","result","errors","validating","clearDirtyBaselines","hasLiveBranch","keptValues","clearErrors","clear","isSubmitting","submitCount","isSubmitted","isSubmitSuccessful","createPath","isEqual"],"mappings":"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"}
|