react-f0rm 1.3.0 → 1.5.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.
Files changed (99) hide show
  1. package/README.md +98 -1140
  2. package/devtools.d.ts +1 -0
  3. package/devtools.js +1 -0
  4. package/dist/array-Bu7W8BSz.d.ts +54 -0
  5. package/dist/devtools/index.cjs.js +1 -1
  6. package/dist/devtools/index.cjs.js.map +1 -1
  7. package/dist/devtools/index.d.cts +24 -0
  8. package/dist/devtools/index.d.mts +24 -0
  9. package/dist/devtools/index.d.ts +8 -17
  10. package/dist/devtools/index.mjs +1 -1
  11. package/dist/devtools/index.mjs.map +1 -1
  12. package/dist/errors-DA4ReEd9.mjs +2 -0
  13. package/dist/errors-DA4ReEd9.mjs.map +1 -0
  14. package/dist/errors-TzyWwBfw.cjs.js +2 -0
  15. package/dist/errors-TzyWwBfw.cjs.js.map +1 -0
  16. package/dist/index.cjs.js +1 -1
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.d.cts +1253 -0
  19. package/dist/index.d.mts +1253 -0
  20. package/dist/index.d.ts +570 -895
  21. package/dist/index.mjs +1 -1
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/index.umd.js +1256 -535
  24. package/dist/index.umd.js.map +1 -1
  25. package/dist/index.umd.min.js +2 -2
  26. package/dist/index.umd.min.js.map +1 -1
  27. package/dist/persist.cjs.js +1 -1
  28. package/dist/persist.cjs.js.map +1 -1
  29. package/dist/persist.d.cts +28 -0
  30. package/dist/persist.d.mts +28 -0
  31. package/dist/persist.d.ts +9 -30
  32. package/dist/persist.mjs +1 -1
  33. package/dist/persist.mjs.map +1 -1
  34. package/dist/resolvers/standard-schema.cjs.js +1 -1
  35. package/dist/resolvers/standard-schema.cjs.js.map +1 -1
  36. package/dist/resolvers/standard-schema.d.cts +2 -0
  37. package/dist/resolvers/standard-schema.d.mts +2 -0
  38. package/dist/resolvers/standard-schema.d.ts +1 -67
  39. package/dist/resolvers/standard-schema.mjs +1 -1
  40. package/dist/resolvers/standard-schema.mjs.map +1 -1
  41. package/dist/resolvers/yup.cjs.js +1 -1
  42. package/dist/resolvers/yup.cjs.js.map +1 -1
  43. package/dist/resolvers/yup.d.cts +6 -0
  44. package/dist/resolvers/yup.d.mts +6 -0
  45. package/dist/resolvers/yup.d.ts +0 -1
  46. package/dist/resolvers/yup.mjs +1 -1
  47. package/dist/resolvers/yup.mjs.map +1 -1
  48. package/dist/resolvers/zod.cjs.js +1 -1
  49. package/dist/resolvers/zod.cjs.js.map +1 -1
  50. package/dist/resolvers/zod.d.cts +34 -0
  51. package/dist/resolvers/zod.d.mts +34 -0
  52. package/dist/resolvers/zod.d.ts +30 -3
  53. package/dist/resolvers/zod.mjs +1 -1
  54. package/dist/resolvers/zod.mjs.map +1 -1
  55. package/dist/server/index.cjs.js +1 -1
  56. package/dist/server/index.cjs.js.map +1 -1
  57. package/dist/server/index.d.cts +38 -0
  58. package/dist/server/index.d.mts +38 -0
  59. package/dist/server/index.d.ts +26 -65
  60. package/dist/server/index.mjs +1 -1
  61. package/dist/server/index.mjs.map +1 -1
  62. package/dist/standard-schema-BAaTmAHh.d.ts +682 -0
  63. package/dist/standardSchema-5WezjHlp.mjs +2 -0
  64. package/dist/standardSchema-5WezjHlp.mjs.map +1 -0
  65. package/dist/standardSchema-DINHlsYR.cjs.js +2 -0
  66. package/dist/standardSchema-DINHlsYR.cjs.js.map +1 -0
  67. package/dist/validate-2pX-N1O6.cjs.js +2 -0
  68. package/dist/validate-2pX-N1O6.cjs.js.map +1 -0
  69. package/dist/validate-C0HOsP9v.mjs +2 -0
  70. package/dist/validate-C0HOsP9v.mjs.map +1 -0
  71. package/dist/values-BbPnLByD.cjs.js +2 -0
  72. package/dist/values-BbPnLByD.cjs.js.map +1 -0
  73. package/dist/values-CHsmcZk4.mjs +2 -0
  74. package/dist/values-CHsmcZk4.mjs.map +1 -0
  75. package/package.json +75 -28
  76. package/persist.d.ts +1 -0
  77. package/persist.js +1 -0
  78. package/resolvers/standard-schema.d.ts +1 -0
  79. package/resolvers/standard-schema.js +1 -0
  80. package/resolvers/yup.d.ts +1 -0
  81. package/resolvers/yup.js +1 -0
  82. package/resolvers/zod.d.ts +1 -0
  83. package/resolvers/zod.js +1 -0
  84. package/server.d.ts +1 -0
  85. package/server.js +1 -0
  86. package/dist/errors-BKrUdpfI.cjs.js +0 -2
  87. package/dist/errors-BKrUdpfI.cjs.js.map +0 -1
  88. package/dist/errors-CrQBddrJ.mjs +0 -2
  89. package/dist/errors-CrQBddrJ.mjs.map +0 -1
  90. package/dist/form-CeKSBs31.d.ts +0 -486
  91. package/dist/validate-CNtuUhmk.mjs +0 -2
  92. package/dist/validate-CNtuUhmk.mjs.map +0 -1
  93. package/dist/validate-Cl4ksNFu.cjs.js +0 -2
  94. package/dist/validate-Cl4ksNFu.cjs.js.map +0 -1
  95. package/dist/validate-nksgv1pR.d.ts +0 -272
  96. package/dist/values-Cu6awQOJ.cjs.js +0 -2
  97. package/dist/values-Cu6awQOJ.cjs.js.map +0 -1
  98. package/dist/values-DRY-a32G.mjs +0 -2
  99. package/dist/values-DRY-a32G.mjs.map +0 -1
@@ -1,486 +0,0 @@
1
- import { EventEmitter } from '@for-fun/event-emitter';
2
-
3
- type PathSegments = (string | number)[];
4
- type Name = string | PathSegments;
5
- type Path = {
6
- value: PathSegments;
7
- key: string;
8
- };
9
-
10
- /**
11
- * Compile-time field path utilities: `FieldPath<T>` enumerates the valid
12
- * path strings for a values shape `T` ('a', 'a.b', 'a[0]', 'a[b]', ...),
13
- * and `PathValue<T, P>` resolves the leaf type a path points at.
14
- * The grammar mirrors the paths accepted at runtime by `normalizePath`:
15
- * numeric segments are bracket-only ('a[0]', never 'a.0' — dotted
16
- * numerics throw a TypeError at runtime).
17
- */
18
- /** `true` only for the `any` type (`0 extends 1 & any`). */
19
- type IsAny<T> = 0 extends 1 & T ? true : false;
20
- type Primitive = null | undefined | string | number | boolean | symbol | bigint;
21
- /** Depth countdown: Prev[9] = 8 ... Prev[1] = 0, Prev[0] = never stops recursion. */
22
- type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
23
- /** Paths are capped at 10 segments to keep instantiation depth bounded. */
24
- type MaxDepth = 9;
25
- /**
26
- * Valid path continuations after a segment: `.k` / `[k]` / `[0]`,
27
- * optionally followed by deeper continuations into the child node.
28
- * Numeric segments are bracket-only (`.0` throws at runtime); object
29
- * keys that are themselves numeric strings likewise enumerate just the
30
- * bracket subscript.
31
- */
32
- type Continue<T, D extends number> = [D] extends [never] ? never : IsAny<T> extends true ? string : T extends Primitive | Function ? never : T extends readonly (infer U)[] ? `[${number}]` | `[${number}]${Continue<U, Prev[D]>}` : {
33
- [K in Extract<keyof T, string>]: (K extends `${number}` ? never : `.${K}`) | `[${K}]` | (K extends `${number}` ? never : `.${K}${Continue<T[K], Prev[D]>}`) | `[${K}]${Continue<T[K], Prev[D]>}`;
34
- }[Extract<keyof T, string>];
35
- /**
36
- * Every valid field path string for a values shape `T`.
37
- * @example FieldPath<{a: {b: string}}> // 'a' | 'a.b' | 'a[b]'
38
- */
39
- type FieldPath<T> = IsAny<T> extends true ? string : T extends Primitive | Function ? never : T extends readonly (infer U)[] ? `[${number}]` | `[${number}]${Continue<U, MaxDepth>}` : {
40
- [K in Extract<keyof T, string>]: K extends `${number}` ? never : K | `${K}${Continue<T[K], MaxDepth>}`;
41
- }[Extract<keyof T, string>];
42
- /** Resolve `T[K]` for one bare segment: array index -> element, object key -> value. */
43
- type Lookup<T, K extends string> = K extends `${number}` ? T extends readonly (infer U)[] ? U : never : K extends keyof T ? T[K] : never;
44
- /** One dot-separated chunk: a bare segment plus any `[k]` / `[0]` suffixes. */
45
- type ChunkValue<T, C extends string> = C extends `${infer Key}[${infer Tail}` ? ChunkSuffix<Lookup<T, Key>, `[${Tail}`> : Lookup<T, C>;
46
- type ChunkSuffix<T, S extends string> = S extends `[${infer Key}]${infer Rest}` ? Rest extends '' ? Lookup<T, Key> : PathOf<Lookup<T, Key>, Rest> : never;
47
- /** Resolve the value type the path string `P` points at inside `T`. */
48
- type PathOf<T, P extends string> = P extends '' ? never : P extends `[${infer Key}]${infer Rest}` ? Rest extends '' ? Lookup<T, Key> : PathOf<Lookup<T, Key>, Rest> : P extends `.${infer Rest}` ? PathOf<T, Rest> : P extends `${infer Chunk}.${infer Rest}` ? PathOf<ChunkValue<T, Chunk>, Rest> : ChunkValue<T, P>;
49
- /**
50
- * The value type at path `P` of a values shape `T`.
51
- * @example PathValue<{a: {b: string}}, 'a.b'> // string
52
- */
53
- type PathValue<T, P extends FieldPath<T>> = PathOf<T, P & string>;
54
- /**
55
- * The value type at path `P` of `T`, or `any` when `P` is not a known
56
- * field path (plain `string` / segment-array calls keep their old behavior).
57
- */
58
- type PathValueOf<T, P> = P extends FieldPath<T> ? PathValue<T, Extract<P, FieldPath<T>>> : any;
59
-
60
- /** Options accepted by {@link setFocus}. All flags default to `false`. */
61
- type SetFocusOptions = {
62
- /** Select the field's text after focusing it. Bound fields call
63
- * `select()` on their element; elements without one (custom `as`
64
- * components) just focus. */
65
- shouldSelect?: boolean;
66
- };
67
- /**
68
- * Programmatically focus a bound field's element (e.g. the <Field>'s
69
- * input).
70
- *
71
- * Rides the same 'focusError' event channel a failed handleSubmit uses to
72
- * focus the first errored field: the payload is the target's path key,
73
- * with the focus options as a second, backward-compatible argument (older
74
- * subscribers declared with a single `key` parameter simply ignore it).
75
- * Being event-driven, it is a silent no-op when the field is unmounted or
76
- * nothing subscribes — unknown names never throw.
77
- *
78
- * @param form form instance
79
- * @param name field name (dot path or segments path)
80
- * @param options focus options
81
- */
82
- declare function setFocus(form: Form, name: Name, options?: SetFocusOptions): void;
83
-
84
- /** Reserved top-level path segment for form-level errors. The Standard
85
- * Schema form-level adapter lands path-less issues under this key; the
86
- * exported constant replaces the magic string, and readers consume it via
87
- * getError(form, FORM_ERROR) / getFieldErrors(form, FORM_ERROR). */
88
- declare const FORM_ERROR = "_form";
89
- /** When a field is validated:
90
- * - `'onSubmit'` (default): only on submit
91
- * - `'onBlur'`: when the field loses focus
92
- * - `'onChange'`: on every change
93
- * - `'onTouched'`: on first blur, then on every change
94
- * - `'all'`: on both change and blur
95
- */
96
- /** Brand marking a form-level validate result as a structured
97
- * {@link ValidationOutcome} (parsed values and/or errors) rather than a
98
- * plain nested error record. Symbols cannot collide with user error
99
- * records, so detection is an exact `VALIDATION_OUTCOME in result`. */
100
- declare const VALIDATION_OUTCOME: unique symbol;
101
- /** Structured form-level validate result: `errors` uses the same nested
102
- * shape a plain error record uses, `values` is the schema's parsed output
103
- * (coerce/transform results included). Either side may be omitted. */
104
- /**
105
- * Get field error
106
- * @param form
107
- * @param name
108
- * @return FieldError object or undefined
109
- */
110
- declare function getError<T extends Record<string, any> = any, P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments>(form: Form<T>, name: P): FieldError | undefined;
111
- /**
112
- * Get field error by path
113
- * @param form
114
- * @param path
115
- * @return first FieldError of the field, or undefined
116
- */
117
- declare function getErrorByPath({ errors }: Form, path: Path): FieldError | undefined;
118
- /**
119
- * Get all errors of a field
120
- * @param form
121
- * @param name
122
- * @return every error registered for the field (insertion order); an empty
123
- * array when the field has none
124
- */
125
- declare function getFieldErrors<T extends Record<string, any> = any, P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments>(form: Form<T>, name: P): FieldError[];
126
- /**
127
- * Get all errors of a field by path
128
- * @param form
129
- * @param path
130
- * @return every error registered for the field (insertion order); an empty
131
- * array when the field has none
132
- */
133
- declare function getFieldErrorsByPath({ errors }: Form, path: Path): FieldError[];
134
- /**
135
- * Get all errors
136
- * @param form
137
- * @return array of {path, type, message} entries, in insertion order; path
138
- * is the user-facing dotted field path ('a.b', 'list.0'), and a
139
- * field holding several errors contributes one entry per error
140
- */
141
- declare function getErrors({ errors }: Form): FieldErrorEntry[];
142
- /**
143
- * Get first error message
144
- * @param form
145
- * @return first error's message string, or undefined when there are no errors
146
- */
147
- declare function getFirstError({ errors }: Form): string | undefined;
148
- /** Snapshot of one field's aggregated state, as {@link getFieldState}
149
- * returns it. `errors` is the stored array shared with the form — treat it
150
- * as read-only, like every {@link getFieldErrors} result. */
151
- /** Options accepted by {@link setError}. */
152
- type SetErrorOptions = {
153
- /**
154
- * Focus the named field's element after the error lands (react-hook-form's
155
- * `setError` `shouldFocus`). Rides the same 'focusError' channel
156
- * `setFocus` and a failed submit's auto-focus use: only mounted bound
157
- * fields react, unmounted ones are silent no-ops.
158
- */
159
- shouldFocus?: boolean;
160
- };
161
- /**
162
- * Set field error
163
- * @param form
164
- * @param name
165
- * @param error string is normalized to {type: 'custom', message}; a
166
- * FieldError object is stored as-is; an array holds several errors
167
- * (falsy items dropped, strings normalized); undefined clears
168
- * @param options {@link SetErrorOptions} — `shouldFocus` focuses the field
169
- * after the error lands
170
- */
171
- declare function setError<T extends Record<string, any> = any, P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments>(form: Form<T>, name: P, error: string | FieldError | (string | FieldError)[] | undefined, options?: SetErrorOptions): void;
172
- /**
173
- * Set field error
174
- * @param form
175
- * @param path
176
- * @param error string is normalized to {type: 'custom', message}; a
177
- * FieldError object is stored as-is; an array holds several errors
178
- * (falsy items dropped, strings normalized); undefined clears
179
- * @param options {@link SetErrorOptions} — `shouldFocus` focuses the field
180
- * after the error lands
181
- */
182
- declare function setErrorByPath(form: Form, path: Path, error: string | FieldError | (string | FieldError)[] | undefined, options?: SetErrorOptions): void;
183
- /**
184
- * Clear errors
185
- * @param form
186
- * @param name a single path or a list of paths; omit to clear every error
187
- */
188
- declare function clearErrors(form: Form, name?: Name | Name[]): void;
189
- /** Options accepted by {@link setServerErrors}. */
190
- type SetServerErrorsOptions = {
191
- /** Keep existing field errors instead of clearing them first. Defaults
192
- * to `false`: a fresh server response replaces the prior error state. */
193
- keepExisting?: boolean;
194
- };
195
- /**
196
- * Land a server-side error response on the form: each entry becomes the
197
- * named field's error(s) with `type: 'server'`, ready for the same
198
- * renderError/`useError` channel client-side validation uses. Takes the
199
- * flat `Record<string, string | string[]>` shape REST APIs commonly
200
- * return (RealWorld: `422 {errors: {email: ['has already been taken']}}`)
201
- * without a hand-rolled `Object.entries` + `setError` loop.
202
- *
203
- * A string value lands as one error, a string array as several (first one
204
- * is what `getError`/`error` expose); an empty array clears that field's
205
- * errors. By default every existing error is cleared first — a fresh
206
- * response describes the current state, not a patch onto stale client
207
- * errors; pass `keepExisting: true` to layer instead.
208
- * @param form
209
- * @param errors field errors keyed by name
210
- * @param options
211
- */
212
- declare function setServerErrors(form: Form, errors: Record<string, string | string[]>, options?: SetServerErrorsOptions): void;
213
- /**
214
- * Set field touched state
215
- * @param form
216
- * @param name
217
- */
218
- /**
219
- * @param form
220
- */
221
- declare function hasErrors({ errors }: Form): boolean;
222
-
223
- /** A field error: `type` identifies the error kind ('custom' for plain
224
- * string errors), `message` is the display text. */
225
- type FieldError = {
226
- type: string;
227
- message: string;
228
- };
229
- /** A flattened entry from {@link getErrors}. */
230
- type FieldErrorEntry = {
231
- path: string;
232
- type: string;
233
- message: string;
234
- };
235
- /** When a field is validated:
236
- * - `'onSubmit'` (default): only on submit
237
- * - `'onBlur'`: when the field loses focus
238
- * - `'onChange'`: on every change
239
- * - `'onTouched'`: on first blur, then on every change
240
- * - `'all'`: on both change and blur
241
- */
242
- type ValidationMode = 'onSubmit' | 'onBlur' | 'onChange' | 'onTouched' | 'all';
243
- /** When a field is re-validated after it already has an error:
244
- * - `'onChange'` (default): on every change
245
- * - `'onBlur'`: when the field loses focus
246
- * - `'onSubmit'`: only on submit (no live re-validation)
247
- */
248
- type ReValidateMode = 'onChange' | 'onBlur' | 'onSubmit';
249
- /** Structured form-level validate result: `errors` uses the same nested
250
- * shape a plain error record uses, `values` is the schema's parsed output
251
- * (coerce/transform results included). Either side may be omitted.
252
- *
253
- * The brand constant itself lives in the errors module (the leaf module of
254
- * the core dependency graph — every consumer imports it from there) and is
255
- * re-exported below with `export *`. */
256
- type ValidationOutcome<T> = {
257
- [VALIDATION_OUTCOME]: true;
258
- errors?: Record<string, any>;
259
- values?: T;
260
- };
261
- /** What a form-level validate function may return: a plain nested error
262
- * record (flattened into field errors — the long-standing shape), or a
263
- * branded {@link ValidationOutcome} whose `values` become the form's
264
- * parsedValues baseline. */
265
- type ValidateResult<T> = Record<string, any> | ValidationOutcome<T> | Promise<Record<string, any> | ValidationOutcome<T>>;
266
- /** Context passed to a form-level `validate` function's second argument.
267
- * `signal` aborts as soon as the round is superseded — a newer round
268
- * started (which only happens under a positive `validateDebounce`, where
269
- * kicks merge into windows) — so async validators can cancel their
270
- * underlying work instead of racing a stale result home. Stale results
271
- * are dropped independently by the round gate, so validators that ignore
272
- * the signal stay correct too; the same contract field-level validators
273
- * get through their own `meta`. */
274
- type FormValidateMeta<T extends Record<string, any> = any> = {
275
- form: Form<T>;
276
- signal: AbortSignal;
277
- };
278
- /** Form-level validator: receives all values (plus {@link
279
- * FormValidateMeta} as an optional second argument) and returns a
280
- * {@link ValidateResult} — sync or async — or `undefined`/nothing when
281
- * valid (the runtime skips falsy results, so implicit-return callbacks
282
- * type-check). */
283
- type FormValidateFn<T extends Record<string, any> = any> = (values: T, meta: FormValidateMeta<T>) => ValidateResult<T> | undefined;
284
- /**
285
- * The emitter event table for {@link Form.emitter}: each event's payload
286
- * tuple. Path-carrying events declare an optional single `Path` payload —
287
- * emit sites send it for single-field mutations and omit it for bulk
288
- * payload-less broadcasts (reset, setInitialValues, clear-all), both of
289
- * which subscribers handle. `focusError` carries the target's path key
290
- * plus optional {@link SetFocusOptions}.
291
- */
292
- type FormEvents = ['change', [path?: Path]] | ['errors', [path?: Path]] | ['touched', [path?: Path]] | ['validating', [path?: Path]] | ['submitting', []] | ['submitCount', []] | ['submitSuccessful', []] | ['reset', []] | ['disabled', []] | ['status', []] | ['loading', []] | ['focusError', [key: string, options?: SetFocusOptions]];
293
- type Form<T extends Record<string, any> = any> = {
294
- emitter: EventEmitter<FormEvents>;
295
- mode: ValidationMode;
296
- reValidateMode: ReValidateMode;
297
- initialValues: T;
298
- values: Map<string, any>;
299
- /** Tombstones of unregistered field paths (JSON path keys): reading or
300
- * merging values must not fall back to initialValues for these paths. */
301
- deleted: Set<string>;
302
- /** Every error registered for a field, as a non-empty array (the
303
- * write-side {@link setErrorByPath} normalizes to this invariant, so
304
- * readers never need to guard against an empty list). Readers wanting
305
- * the display error take the first entry ({@link getError}); readers
306
- * wanting all of them use {@link getFieldErrors}. */
307
- errors: Map<string, FieldError[]>;
308
- touched: Set<string>;
309
- /** Per-field validation kicks, registered by {@link
310
- * registerValidatorByPath} (`useValidate` is the React-side
311
- * registration): each is the field's debounce/lock-aware kick —
312
- * invoking it validates the field's current value. `trigger` /
313
- * `ensureValidate` run every entry; the user-change gate ({@link
314
- * userChangeByPath}) runs the entry at the changed path. */
315
- validators: Map<string, () => void>;
316
- validating: Set<string>;
317
- /** Parsed values from the last successful schema validation: the
318
- * schema's complete output tree (coerced/transformed values included).
319
- * Sits between initialValues and the values Map in {@link getValues}
320
- * until `reset`/`setInitialValues` clears it. Never affects dirty
321
- * state — that compares live edits against initialValues only. */
322
- parsedValues: T | undefined;
323
- /** Form-level validator, seeded from {@link Options.validate}. May
324
- * receive a second {@link FormValidateMeta} argument. */
325
- validate?: FormValidateFn<T>;
326
- /** Delay in milliseconds before the form-level `validate` runs; seeded
327
- * from {@link Options.validateDebounce} and fixed at create time. */
328
- validateDebounce?: number;
329
- /** Path keys (JSON-stringified segments) of the fields whose user
330
- * changes re-run the form-level `validate`; normalized from {@link
331
- * Options.validateDeps} at create time and fixed thereafter. */
332
- validateDeps?: ReadonlySet<string>;
333
- isSubmitting: boolean;
334
- /** Whether a submit has been attempted — set by `handleSubmit` on every
335
- * attempt (validation outcome aside), cleared by `reset`.
336
- * `useFormState().isSubmitted` reads it (react-hook-form's
337
- * `formState.isSubmitted` semantics). */
338
- isSubmitted: boolean;
339
- submitCount: number;
340
- isSubmitSuccessful: boolean | undefined;
341
- /** True while an async {@link Options.initialValues} source (a Promise,
342
- * or a thunk returning one) is still pending — the form starts empty
343
- * and the resolved values become the baseline via setInitialValues when
344
- * it lands. Flips through the payload-less 'loading' event
345
- * (`useIsLoading` / `useFormState().isLoading`). */
346
- isLoading: boolean;
347
- /** Form-level default for a bound field's unmount behavior, seeded from
348
- * {@link Options.shouldUnregister}: `true` (the default) tombstones an
349
- * unmounted field, `false` keeps its value (react-hook-form's
350
- * `shouldUnregister` semantics). A field's own `shouldUnregister` option
351
- * overrides this. */
352
- shouldUnregister?: boolean;
353
- /** Form-level disabled flag, OR-ed into every bound field's `disabled`
354
- * (form flag || the field's own option). Seeded from
355
- * {@link Options}.disabled at create time and toggled at runtime with
356
- * {@link setDisabled}, which emits a payload-less 'disabled' event so
357
- * subscribed fields re-render. */
358
- disabled: boolean;
359
- /** Form-level default for mount validation, seeded from
360
- * {@link Options.validateOnMount}: `true` makes every mounted field
361
- * with a validator kick once after mount (deferred until an async
362
- * {@link Options.initialValues} source lands), and makes `useForm` run
363
- * the form-level `validate` once. A field's own `validateOnMount`
364
- * option overrides this flag in either direction. */
365
- validateOnMount: boolean;
366
- /** Form-level default for {@link UseValidateOptions.asyncAlways}:
367
- * whether a field's debounced validator still runs when its `required`
368
- * gate failed. A field's own `asyncAlways` option overrides this flag
369
- * in either direction. Seeded from {@link Options.asyncAlways}. */
370
- asyncAlways: boolean;
371
- /**
372
- * User-owned metadata slot for non-field state — session flags, server
373
- * backfill that belongs to no field, step indices (Formik's `status`
374
- * role). Written with {@link setStatus}, which emits the payload-less
375
- * 'status' event; read directly or reactively through {@link useStatus}.
376
- * Starts `undefined`.
377
- */
378
- status: any;
379
- };
380
- type Options<T extends Record<string, any> = any> = {
381
- /**
382
- * The values baseline. Sync objects seed immediately (SSR renders
383
- * them). Async sources — a Promise, or a thunk returning a value or
384
- * Promise (react-hook-form's async `defaultValues` shape) — start the
385
- * form empty with `isLoading: true` and land the resolved values as
386
- * the baseline via setInitialValues once they resolve: value
387
- * subscribers re-sync, dirty/touched state starts clean, and a later
388
- * `reset()` returns to the resolved baseline. A rejected source flips
389
- * isLoading back to false, keeps the form empty, and logs the error in
390
- * DEV — attach a `.catch` on the source itself to handle it. The thunk
391
- * runs at create time: keep its identity stable (module scope or
392
- * useMemo) when passing it inline, and note StrictMode double-invokes
393
- * it in development, like every render-phase call.
394
- */
395
- initialValues?: T | Promise<T> | (() => T | Promise<T>);
396
- /** When fields are validated. Defaults to `'onSubmit'`. See
397
- * {@link ValidationMode}. */
398
- mode?: ValidationMode;
399
- /** When a field is re-validated after it already has an error — it only
400
- * takes effect once the field has an error. Defaults to `'onChange'`. See
401
- * {@link ReValidateMode}. */
402
- reValidateMode?: ReValidateMode;
403
- /**
404
- * Form-level validator. Returns a record of errors keyed by field path;
405
- * nested objects are flattened ('a.b' style) and array values contribute
406
- * every non-empty string they hold as separate errors (zod `flatten()`
407
- * formErrors style). Schema adapters instead return a branded
408
- * {@link ValidationOutcome}: `errors` flattens the same way, `values`
409
- * (the schema's parsed output) becomes the form's parsedValues baseline
410
- * that {@link getValues} layers over initialValues.
411
- */
412
- validate?: FormValidateFn<T>;
413
- /**
414
- * Milliseconds to debounce the form-level `validate`: kicks from
415
- * `trigger`/`ensureValidate`/submit inside the window merge into one
416
- * run, and while the timer is pending the form counts as validating,
417
- * so `trigger` and submit wait the window out — the same contract the
418
- * per-field `validateDebounce` gives field validators. The merged run
419
- * reads the values current when its timer fires. Defaults to `0`
420
- * (validate runs immediately, exactly as before this option existed).
421
- */
422
- validateDebounce?: number;
423
- /** Fields whose user changes re-run the form-level `validate` — the
424
- * cross-field dependency list (password-confirm mismatch and friends).
425
- * Each entry is a field path ('password', 'user.email', 'items.0.qty');
426
- * a user change to a listed field re-runs the form-level `validate`
427
- * under the same mode/`reValidateMode` gating the field's own
428
- * validator gets. Omit it and the form-level `validate` only runs on
429
- * `trigger`/submit, exactly as before this option existed.
430
- *
431
- * Opting in also changes what a re-run may clear: each round first
432
- * drops the errors the previous round wrote (paths it flattened onto),
433
- * so a dep change that fixes the cross-field error makes it disappear.
434
- * Errors the round never wrote — field validators', `setServerErrors`,
435
- * manual `setError` — are never touched. TanStack Form's counterpart is
436
- * `onChangeListenTo` (v1) / validator `triggers` (v2 alpha). */
437
- validateDeps?: FieldPath<T>[];
438
- /**
439
- * Form-level default for a bound field's unmount behavior. `true` (the
440
- * default) tombstones an unmounted field — it drops out of
441
- * `getValues()` instead of reviving its initial value (this library's
442
- * historical default); `false` keeps the value, matching
443
- * react-hook-form's `shouldUnregister`. A field's own
444
- * `useField({shouldUnregister})` option overrides the form-level flag
445
- * in either direction.
446
- */
447
- shouldUnregister?: boolean;
448
- /** Start the form with every bound field disabled — the flag bound
449
- * fields OR with their own `disabled` option (a field cannot opt out
450
- * of a disabled form). Toggle later with {@link setDisabled}.
451
- * Defaults to `false`. */
452
- disabled?: boolean;
453
- /**
454
- * Form-level default for field validation's `asyncAlways`: when true,
455
- * a field whose `required` gate failed still runs its debounced
456
- * validator (the gate's errors land immediately, the validator's own
457
- * result lands alongside them per-source). TanStack Form's
458
- * `asyncAlways` counterpart. A field's own
459
- * `useField({asyncAlways})` option overrides the form-level flag in
460
- * either direction. Defaults to `false`.
461
- */
462
- asyncAlways?: boolean;
463
- /**
464
- * Validate on mount: `true` makes every mounted field with a validator
465
- * (declarative `rules` or a `validate` callback) run it once after
466
- * mount, instead of waiting for the first submit/change — errors show
467
- * immediately for an untouched form (Formik's `validateOnMount` /
468
- * TanStack Form's per-field `validateOnMount`). The form-level
469
- * `validate` also runs once after mount. Mount kicks are deferred
470
- * while an async `initialValues` source is still pending: validating
471
- * the empty shell would land spurious required errors, so the kicks
472
- * fire after the resolved baseline lands instead. A field's own
473
- * `useField({validateOnMount})` option overrides the form-level flag
474
- * in either direction. Defaults to `false`.
475
- */
476
- validateOnMount?: boolean;
477
- };
478
- /**
479
- * Create form instance
480
- * @param options
481
- * @return form instance
482
- */
483
- declare function create<T extends Record<string, any> = any>(options?: Options<T>): Form<T>;
484
-
485
- export { setFocus as A, setServerErrors as B, FORM_ERROR as g, VALIDATION_OUTCOME as m, clearErrors as p, create as q, getError as r, getErrorByPath as s, getErrors as t, getFieldErrors as u, getFieldErrorsByPath as v, getFirstError as w, hasErrors as x, setError as y, setErrorByPath as z };
486
- export type { Form as F, Name as N, Options as O, Path as P, ReValidateMode as R, SetErrorOptions as S, ValidationMode as V, FieldError as a, FieldPath as b, PathSegments as c, PathValueOf as d, FieldErrorEntry as e, FormEvents as f, FormValidateFn as h, FormValidateMeta as i, PathValue as j, SetFocusOptions as k, SetServerErrorsOptions as l, ValidateResult as n, ValidationOutcome as o };
@@ -1,2 +0,0 @@
1
- import{emit as e}from"@for-fun/event-emitter";import{h as r,w as t,V as o,s as n,c as i,b as s,n as a,e as l,f as c}from"./errors-CrQBddrJ.mjs";import{g as u}from"./values-DRY-a32G.mjs";async function f(o,n,i){const s=e=>t(o.emitter,"validating",()=>function(e){for(const r of e.validating)if(r!==w)return!1;return!0}(o),()=>!1);return o.validators.forEach(e=>e()),await s(),o.validate&&await function(r){const t=r.validate;if(!t)return Promise.resolve();const o=r.validateDebounce??0;if(o<=0){const e=new AbortController;return Promise.resolve(t(u(r),{form:r,signal:e.signal})).then(e=>{v(r,e)})}const n=function(e){let r=y.get(e);r||(r={timer:null,controller:null,round:null,marked:!1,waiters:[]},y.set(e,r));return r}(r);null!==n.timer?clearTimeout(n.timer):(n.marked=!0,r.validating.add(w),e(r.emitter,"validating"));return n.timer=setTimeout(()=>{n.timer=null;const e=n.round={};(function(e,r,t){const o=e.validate;if(!o)return Promise.resolve();r.controller?.abort();const n=r.controller=new AbortController;let i;try{i=Promise.resolve(o(u(e),{form:e,signal:n.signal}))}catch(e){i=Promise.reject(e)}return i.then(o=>{r.round===t&&v(e,o)},e=>{if(r.round===t)throw e})})(r,n,e).then(()=>b(r,n,e,p),t=>b(r,n,e,t))},o),new Promise((e,r)=>{n.waiters.push({resolve:e,reject:r})})}(o),!r(o)}function m(e,r,t=[],o){Object.entries(r).forEach(([r,n])=>{const i=[...t,...s(r)?[r]:a(r)];"string"==typeof n?n&&(l(e,i,n),d(e,i,o)):Array.isArray(n)||c(n)?(l(e,i,n),d(e,i,o)):n&&"object"==typeof n&&m(e,n,i,o)})}function d(e,r,t){if(!t)return;const o=i(r),n=e.errors.get(o.key);n&&t.set(o.key,n)}function v(r,t){const s=r.validateDeps?function(e){let r=g.get(e);r||(r=new Map,g.set(e,r));return r}(r):void 0;if(s&&(!function(r,t){for(const[o,n]of t){r.errors.get(o)===n&&(r.errors.delete(o),e(r.emitter,"errors",i(JSON.parse(o))))}}(r,s),s.clear()),t){if("object"==typeof t&&o in t){const e=t;return e.errors&&m(r,e.errors,[],s),void n(r,e.values)}m(r,t,[],s)}}const g=new WeakMap;const w="__form_validate__";const p=Symbol("form-validate-settled"),y=new WeakMap;function b(r,t,o,n){if(t.round!==o)return;if(t.round=null,null!==t.timer)return;t.marked&&(t.marked=!1,r.validating.delete(w),e(r.emitter,"validating"));const i=t.waiters;t.waiters=[];for(const e of i)n===p?e.resolve():e.reject(n)}export{f as t};
2
- //# sourceMappingURL=validate-CNtuUhmk.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-CNtuUhmk.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 {isFieldError, isSegmentsPath, setParsedValues} 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 (validateDeps forms only), every leaf this\n * round actually stored is recorded into it — the exact stored array —\n * so the next round can drop exactly what 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 opted into `validateDeps` additionally get round-scoped\n * error ownership: before the new result lands, the errors the previous\n * round wrote are dropped ({@link clearFormValidateErrors}), so a re-run\n * that passes makes the cross-field error disappear — and the new\n * round's own writes become the tracked footprint. Forms without the\n * option keep the historical write-only behavior untouched.\n */\nfunction applyValidateResult(\n form: Form,\n result: ValidateResult<any> | undefined\n): void {\n const footprint = form.validateDeps ? getFormErrorFootprint(form) : 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 opted into `validateDeps` — held in a\n * WeakMap so the Form shape and the non-opted 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 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":["async","trigger","form","name","options","settle","keys","waitUntil","emitter","key","validating","FORM_VALIDATING_KEY","fieldsSettled","validators","forEach","validator","validate","Promise","resolve","debounce","validateDebounce","controller","AbortController","getValues","signal","then","result","applyValidateResult","state","formValidateStates","get","timer","round","marked","waiters","set","getFormValidateState","clearTimeout","add","emit","setTimeout","abort","outcome","error","reject","runFormValidateRound","settleFormValidate","SETTLED","push","runFormValidate","hasErrors","setFormErrors","segments","footprint","Object","entries","value","path","isIndex","normalizePath","setError","recordFootprint","Array","isArray","isFieldError","createPath","stored","errors","validateDeps","formErrorFootprints","Map","getFormErrorFootprint","written","delete","JSON","parse","clearFormValidateErrors","clear","VALIDATION_OUTCOME","setParsedValues","values","WeakMap","waiter"],"mappings":"0LA8ZAA,eAAsBC,EACpBC,EACAC,EACAC,GAWA,MAAMC,EAAUC,GACdC,EACEL,EAAKM,QACL,aACA,IA4MN,SAAuBN,GACrB,IAAA,MAAWO,KAAOP,EAAKQ,WACrB,GAAID,IAAQE,EAAqB,OAAO,EAE1C,OAAO,CACT,CA/MYC,CAAcV,GAEpB,KAAM,GAiBR,OAbAA,EAAKW,WAAWC,QAAQC,GAAaA,WAC/BV,IACFH,EAAKc,gBAqQN,SAAyBd,GAC9B,MAAMc,EAAWd,EAAKc,SACtB,IAAKA,EAAU,OAAOC,QAAQC,UAC9B,MAAMC,EAAWjB,EAAKkB,kBAAoB,EAC1C,GAAID,GAAY,EAAG,CAIjB,MAAME,EAAa,IAAIC,gBACvB,OAAOL,QAAQC,QACbF,EAASO,EAAUrB,GAAO,CAACA,OAAMsB,OAAQH,EAAWG,UACpDC,KAAKC,IACLC,EAAoBzB,EAAMwB,IAE9B,CACA,MAAME,EArDR,SAA8B1B,GAC5B,IAAI0B,EAAQC,EAAmBC,IAAI5B,GAC9B0B,IACHA,EAAQ,CACNG,MAAO,KACPV,WAAY,KACZW,MAAO,KACPC,QAAQ,EACRC,QAAS,IAEXL,EAAmBM,IAAIjC,EAAM0B,IAE/B,OAAOA,CACT,CAwCgBQ,CAAqBlC,GAIf,OAAhB0B,EAAMG,MAAgBM,aAAaT,EAAMG,QAE3CH,EAAMK,QAAS,EACf/B,EAAKQ,WAAW4B,IAAI3B,GACpB4B,EAAKrC,EAAKM,QAAS,eAUrB,OARAoB,EAAMG,MAAQS,WAAW,KACvBZ,EAAMG,MAAQ,KACd,MAAMC,EAASJ,EAAMI,MAAQ,CAAA,GAejC,SACE9B,EACA0B,EACAI,GAEA,MAAMhB,EAAWd,EAAKc,SACtB,IAAKA,EAAU,OAAOC,QAAQC,UAC9BU,EAAMP,YAAYoB,QAClB,MAAMpB,EAAcO,EAAMP,WAAa,IAAIC,gBAC3C,IAAIoB,EACJ,IACEA,EAAUzB,QAAQC,QAChBF,EAASO,EAAUrB,GAAO,CAACA,OAAMsB,OAAQH,EAAWG,SAExD,OAASmB,GACPD,EAAUzB,QAAQ2B,OAAOD,EAC3B,CACA,OAAOD,EAAQjB,KACbC,IACME,EAAMI,QAAUA,GAAOL,EAAoBzB,EAAMwB,IAEvDiB,IACE,GAAIf,EAAMI,QAAUA,EAAO,MAAMW,GAGvC,EAvCIE,CAAqB3C,EAAM0B,EAAOI,GAAOP,KACvC,IAAMqB,EAAmB5C,EAAM0B,EAAOI,EAAOe,GAC7CJ,GAASG,EAAmB5C,EAAM0B,EAAOI,EAAOW,KAEjDxB,GACI,IAAIF,QAAc,CAACC,EAAS0B,KACjChB,EAAMM,QAAQc,KAAK,CAAC9B,UAAS0B,YAEjC,CAzS6BK,CAAgB/C,IAWjCgD,EAAUhD,EAiBtB,CAuBA,SAASiD,EACPjD,EACAwB,EACA0B,EAAyB,GACzBC,GAEAC,OAAOC,QAAQ7B,GAAQZ,QAAQ,EAAEL,EAAK+C,MAKpC,MAAMC,EAAqB,IACtBL,KACCM,EAAQjD,GAAO,CAACA,GAAOkD,EAAclD,IAEtB,iBAAV+C,EACLA,IACFI,EAAS1D,EAAMuD,EAAMD,GACrBK,EAAgB3D,EAAMuD,EAAMJ,IAErBS,MAAMC,QAAQP,IAGdQ,EAAaR,IAFtBI,EAAS1D,EAAMuD,EAAMD,GACrBK,EAAgB3D,EAAMuD,EAAMJ,IAInBG,GAA0B,iBAAVA,GACzBL,EAAcjD,EAAMsD,EAAOC,EAAMJ,IAGvC,CAOA,SAASQ,EACP3D,EACAkD,EACAC,GAEA,IAAKA,EAAW,OAChB,MAAMI,EAAOQ,EAAWb,GAClBc,EAAShE,EAAKiE,OAAOrC,IAAI2B,EAAKhD,KAChCyD,GAAQb,EAAUlB,IAAIsB,EAAKhD,IAAKyD,EACtC,CAkBA,SAASvC,EACPzB,EACAwB,GAEA,MAAM2B,EAAYnD,EAAKkE,aAqBzB,SAA+BlE,GAC7B,IAAImD,EAAYgB,EAAoBvC,IAAI5B,GACnCmD,IACHA,MAAgBiB,IAChBD,EAAoBlC,IAAIjC,EAAMmD,IAEhC,OAAOA,CACT,CA5BwCkB,CAAsBrE,QAAQ,EAKpE,GAJImD,KAgDN,SACEnD,EACAmD,GAEA,IAAA,MAAY5C,EAAK+D,KAAYnB,EAAW,CACvBnD,EAAKiE,OAAOrC,IAAIrB,KAChB+D,IACftE,EAAKiE,OAAOM,OAAOhE,GACnB8B,EAAKrC,EAAKM,QAAS,SAAUyD,EAAWS,KAAKC,MAAMlE,KACrD,CACF,CAzDImE,CAAwB1E,EAAMmD,GAC9BA,EAAUwB,SAEPnD,EAAL,CACA,GAAsB,iBAAXA,GAAuBoD,KAAsBpD,EAAQ,CAC9D,MAAMgB,EAAUhB,EAGhB,OAFIgB,EAAQyB,QAAQhB,EAAcjD,EAAMwC,EAAQyB,OAAQ,GAAId,QAC5D0B,EAAgB7E,EAAMwC,EAAQsC,OAEhC,CACA7B,EAAcjD,EAAMwB,EAA+B,GAAI2B,EAP1C,CAQf,CAMA,MAAMgB,MAA0BY,QA8ChC,MAAMtE,EAAsB,oBAmB5B,MAAMoC,SAAiB,yBAkBjBlB,MAAyBoD,QAoH/B,SAASnC,EACP5C,EACA0B,EACAI,EACAU,GAEA,GAAId,EAAMI,QAAUA,EAAO,OAE3B,GADAJ,EAAMI,MAAQ,KACM,OAAhBJ,EAAMG,MAAgB,OACtBH,EAAMK,SACRL,EAAMK,QAAS,EACf/B,EAAKQ,WAAW+D,OAAO9D,GACvB4B,EAAKrC,EAAKM,QAAS,eAErB,MAAM0B,EAAUN,EAAMM,QACtBN,EAAMM,QAAU,GAChB,IAAA,MAAWgD,KAAUhD,EACfQ,IAAYK,EAASmC,EAAOhE,UAC3BgE,EAAOtC,OAAOF,EAEvB"}
@@ -1,2 +0,0 @@
1
- "use strict";var e=require("@for-fun/event-emitter"),r=require("./errors-BKrUdpfI.cjs.js"),t=require("./values-Cu6awQOJ.cjs.js");function n(e,t,i=[],s){Object.entries(t).forEach(([t,a])=>{const l=[...i,...r.isIndex(t)?[t]:r.normalizePath(t)];"string"==typeof a?a&&(r.setError(e,l,a),o(e,l,s)):Array.isArray(a)||r.isFieldError(a)?(r.setError(e,l,a),o(e,l,s)):a&&"object"==typeof a&&n(e,a,l,s)})}function o(e,t,n){if(!n)return;const o=r.create(t),i=e.errors.get(o.key);i&&n.set(o.key,i)}function i(t,o){const i=t.validateDeps?function(e){let r=s.get(e);r||(r=new Map,s.set(e,r));return r}(t):void 0;if(i&&(!function(t,n){for(const[o,i]of n){t.errors.get(o)===i&&(t.errors.delete(o),e.emit(t.emitter,"errors",r.create(JSON.parse(o))))}}(t,i),i.clear()),o){if("object"==typeof o&&r.VALIDATION_OUTCOME in o){const e=o;return e.errors&&n(t,e.errors,[],i),void r.setParsedValues(t,e.values)}n(t,o,[],i)}}const s=new WeakMap;const a="__form_validate__";const l=Symbol("form-validate-settled"),c=new WeakMap;function u(r,t,n,o){if(t.round!==n)return;if(t.round=null,null!==t.timer)return;t.marked&&(t.marked=!1,r.validating.delete(a),e.emit(r.emitter,"validating"));const i=t.waiters;t.waiters=[];for(const e of i)o===l?e.resolve():e.reject(o)}exports.trigger=async function(n,o,s){const f=e=>r.waitUntil(n.emitter,"validating",()=>function(e){for(const r of e.validating)if(r!==a)return!1;return!0}(n),()=>!1);return n.validators.forEach(e=>e()),await f(),n.validate&&await function(r){const n=r.validate;if(!n)return Promise.resolve();const o=r.validateDebounce??0;if(o<=0){const e=new AbortController;return Promise.resolve(n(t.getValues(r),{form:r,signal:e.signal})).then(e=>{i(r,e)})}const s=function(e){let r=c.get(e);r||(r={timer:null,controller:null,round:null,marked:!1,waiters:[]},c.set(e,r));return r}(r);null!==s.timer?clearTimeout(s.timer):(s.marked=!0,r.validating.add(a),e.emit(r.emitter,"validating"));return s.timer=setTimeout(()=>{s.timer=null;const e=s.round={};(function(e,r,n){const o=e.validate;if(!o)return Promise.resolve();r.controller?.abort();const s=r.controller=new AbortController;let a;try{a=Promise.resolve(o(t.getValues(e),{form:e,signal:s.signal}))}catch(e){a=Promise.reject(e)}return a.then(t=>{r.round===n&&i(e,t)},e=>{if(r.round===n)throw e})})(r,s,e).then(()=>u(r,s,e,l),t=>u(r,s,e,t))},o),new Promise((e,r)=>{s.waiters.push({resolve:e,reject:r})})}(n),!r.hasErrors(n)};
2
- //# sourceMappingURL=validate-Cl4ksNFu.cjs.js.map