attaform 0.29.0 → 0.30.1

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 (79) hide show
  1. package/dist/abstract.d.mts +15 -21
  2. package/dist/abstract.mjs +4 -4
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/dev-key-collision-warnings.mjs +47 -8
  5. package/dist/dev/chunks/devtools.mjs +2 -2
  6. package/dist/dev/directive.mjs +1 -1
  7. package/dist/dev/history.mjs +1 -2
  8. package/dist/dev/index.mjs +5 -5
  9. package/dist/dev/runtime/plugins/attaform.mjs +3 -3
  10. package/dist/dev/shared/{attaform-dev.BO-71R54.mjs → attaform-dev.BJK1kqHY.mjs} +44 -198
  11. package/dist/dev/shared/{attaform-dev.D8aoKb89.mjs → attaform-dev.BMQ3GcgD.mjs} +51 -64
  12. package/dist/dev/shared/{attaform-dev.BgM6FdtA.mjs → attaform-dev.BUIFcTYf.mjs} +94 -62
  13. package/dist/dev/shared/{attaform-dev.q6UutkCx.mjs → attaform-dev.CT7MDjPy.mjs} +92 -152
  14. package/dist/dev/shared/{attaform-dev.ButTkwHd.mjs → attaform-dev.CZz4GYZ6.mjs} +26 -15
  15. package/dist/dev/shared/{attaform-dev.DD5m2W51.mjs → attaform-dev.Chm4D6SJ.mjs} +2 -4
  16. package/dist/dev/shared/{attaform-dev.DrzObAZo.mjs → attaform-dev.DuEE4GU1.mjs} +4 -4
  17. package/dist/dev/shared/{attaform-dev.DHuydLAC.mjs → attaform-dev.DxHdkkPG.mjs} +674 -734
  18. package/dist/dev/shared/{attaform-dev.A-TpJW0B.mjs → attaform-dev.zlvDr0WB.mjs} +3 -21
  19. package/dist/dev/zod-v3.mjs +6 -6
  20. package/dist/dev/zod-v4.mjs +6 -6
  21. package/dist/dev/zod.mjs +5 -5
  22. package/dist/directive.d.mts +4 -4
  23. package/dist/directive.mjs +1 -1
  24. package/dist/esbuild.d.mts +11 -0
  25. package/dist/esbuild.mjs +1 -1
  26. package/dist/history.d.mts +3 -32
  27. package/dist/history.mjs +1 -2
  28. package/dist/index.d.mts +67 -70
  29. package/dist/index.mjs +5 -5
  30. package/dist/nuxt.d.mts +6 -14
  31. package/dist/nuxt.mjs +3 -7
  32. package/dist/rollup.d.mts +11 -0
  33. package/dist/rollup.mjs +1 -1
  34. package/dist/rspack.d.mts +19 -11
  35. package/dist/rspack.mjs +1 -1
  36. package/dist/runtime/components/AttaformDevtoolsPanel.vue +12 -12
  37. package/dist/runtime/components/DevtoolsValueTree.vue +2 -2
  38. package/dist/runtime/plugins/attaform.mjs +3 -3
  39. package/dist/shared/{attaform.CIWLMY-R.d.mts → attaform.2FslhB9R.d.mts} +83 -95
  40. package/dist/shared/{attaform.Cvao8bW5.mjs → attaform.8mfKZCwH.mjs} +7 -7
  41. package/dist/shared/{attaform.DRUT703x.d.mts → attaform.B-Tb62qV.d.mts} +12 -12
  42. package/dist/shared/{attaform.v0fQtWyA.mjs → attaform.B1rH9ht6.mjs} +1 -1
  43. package/dist/shared/{attaform.BndjUUYX.mjs → attaform.B2iebkPA.mjs} +4 -4
  44. package/dist/shared/{attaform.DhnPb6nK.mjs → attaform.BAf3UNn4.mjs} +1 -1
  45. package/dist/shared/{attaform.CZMAYwSK.mjs → attaform.BIWicztt.mjs} +651 -678
  46. package/dist/shared/attaform.BcQwgWaq.d.mts +2153 -0
  47. package/dist/shared/attaform.Br8EBn5B.d.mts +4553 -0
  48. package/dist/shared/{attaform.C2MVyuxC.mjs → attaform.CO918TdU.mjs} +44 -198
  49. package/dist/shared/{attaform.CZYt_5N_.mjs → attaform.CdZIEf7_.mjs} +1 -19
  50. package/dist/shared/{attaform.B0dKL8ST.mjs → attaform.DRsANMok.mjs} +94 -62
  51. package/dist/shared/{attaform.DffRL5pP.mjs → attaform.D_f1WgYf.mjs} +46 -59
  52. package/dist/shared/{attaform.D1fnTifF.mjs → attaform.D_pt1gOj.mjs} +92 -150
  53. package/dist/shared/attaform.L0kUn3vL.d.mts +42 -0
  54. package/dist/shared/{attaform.D0x-qSXm.mjs → attaform.WiGdIqgx.mjs} +1 -3
  55. package/dist/shared/{attaform.BK0cgu2K.mjs → attaform.ZYn2ahzJ.mjs} +3 -3
  56. package/dist/transforms.d.mts +153 -26
  57. package/dist/transforms.mjs +1 -1
  58. package/dist/vite.d.mts +29 -52
  59. package/dist/vite.mjs +8 -3
  60. package/dist/webpack.d.mts +20 -12
  61. package/dist/webpack.mjs +1 -1
  62. package/dist/zod-v3.d.mts +90 -96
  63. package/dist/zod-v3.mjs +6 -6
  64. package/dist/zod-v4.d.mts +119 -119
  65. package/dist/zod-v4.mjs +6 -6
  66. package/dist/zod.d.mts +5 -5
  67. package/dist/zod.mjs +5 -5
  68. package/package.json +9 -7
  69. package/skills/attaform/SKILL.md +1 -1
  70. package/skills/attaform/references/custom-components.md +1 -1
  71. package/dist/chunks/fingerprint.mjs +0 -183
  72. package/dist/chunks/fingerprint2.mjs +0 -159
  73. package/dist/dev/chunks/fingerprint.mjs +0 -183
  74. package/dist/dev/chunks/fingerprint2.mjs +0 -159
  75. package/dist/dev/shared/attaform-dev.BxfsuwBR.mjs +0 -44
  76. package/dist/shared/attaform.BxfsuwBR.mjs +0 -44
  77. package/dist/shared/attaform.CsoeRi3P.d.mts +0 -5155
  78. package/dist/shared/attaform.DDtqHYv1.d.mts +0 -2465
  79. package/dist/shared/attaform.axHXVQO7.d.mts +0 -44
@@ -0,0 +1,4553 @@
1
+ import { ObjectDirective, Ref, ComputedRef, MaybeRefOrGetter } from 'vue';
2
+
3
+ /**
4
+ * Schema-attached field metadata: the shared types used by both Zod
5
+ * adapters and the unified `attaform/zod` entry so a consumer's data
6
+ * flow reads the same shape regardless of which path runs at lookup.
7
+ *
8
+ * Storage lives in the cross-adapter `field-meta-store` core: a pair
9
+ * of WeakMaps (single-payload for last-write-wins reads, list-of-
10
+ * payloads for shared-schema disambiguation). Every entry's
11
+ * `fieldMeta` re-exports the same registry-shaped object, so
12
+ * `withMeta`/`fieldMeta.add` writes from one entry surface at lookup
13
+ * through any other.
14
+ *
15
+ * `withMeta(schema, payload)` clones the schema before registering,
16
+ * so each call gets fresh identity (the WeakMap keys on reference).
17
+ * The cloning strategy depends on the major: Zod 4 schemas use the
18
+ * native `.clone()`, Zod 3 schemas reconstruct via constructor +
19
+ * `_def`. The unified entry's `withMeta` runtime-branches on which
20
+ * one is in play.
21
+ *
22
+ * Reads are unified through `AbstractSchema.getFieldMetaAtPath(path)`,
23
+ * which returns a fully-resolved `ResolvedFieldMeta` (label /
24
+ * description / placeholder / meta) so the per-leaf and per-container
25
+ * `FieldState` producers in core never see the version split.
26
+ */
27
+ /**
28
+ * The metadata a consumer attaches to a schema node: a short label
29
+ * (presentational), longer description (helper text), placeholder
30
+ * (input affordance). Declared as `interface` (not `type`) so
31
+ * downstream apps can extend the shape via TypeScript declaration
32
+ * merging when they want to register richer payloads (tooltips,
33
+ * icons, badge counts, etc.):
34
+ *
35
+ * declare module 'attaform/zod' {
36
+ * interface FieldMetaPayload {
37
+ * tooltip?: string
38
+ * }
39
+ * }
40
+ *
41
+ * After augmentation, `withMeta(schema, { tooltip: '…' })` is typed
42
+ * and `state.meta.tooltip` reads back as `string | undefined`.
43
+ *
44
+ * Every key is optional. Empty payloads (no keys registered) are
45
+ * indistinguishable from "not registered at all", both surfacing as
46
+ * fallbacks (humanize for label, undefined for the rest).
47
+ */
48
+ interface FieldMetaPayload {
49
+ label?: string;
50
+ description?: string;
51
+ placeholder?: string;
52
+ }
53
+ /**
54
+ * The fully-resolved metadata returned by
55
+ * `AbstractSchema.getFieldMetaAtPath(path)`. Adapters apply the
56
+ * precedence rules:
57
+ *
58
+ * - `label`: registry payload → `humanize(lastSegment)`
59
+ * - `description`: registry payload → schema's `.describe()` value → `undefined`
60
+ * - `placeholder`: registry payload → `undefined`
61
+ * - `meta`: the full registered payload, frozen; `{}` if nothing registered
62
+ *
63
+ * `label` is always a non-empty string at leaves (humanize fallback
64
+ * guarantees this for any non-numeric segment). For containers it
65
+ * may collapse to the empty string when the path is empty (root) or
66
+ * the segment is a numeric index, so callers display "" or substitute
67
+ * a context-appropriate fallback.
68
+ */
69
+ type ResolvedFieldMeta = {
70
+ readonly label: string;
71
+ readonly description: string | undefined;
72
+ readonly placeholder: string | undefined;
73
+ readonly meta: Readonly<FieldMetaPayload>;
74
+ };
75
+
76
+ declare const pathKeyBrand: unique symbol;
77
+ /**
78
+ * Branded string identifier for a canonicalised path, and a useful `Map`
79
+ * key: two paths resolving to the same canonical form produce the same
80
+ * `PathKey`. Treat it as opaque and do not parse it.
81
+ */
82
+ type PathKey = string & {
83
+ readonly [pathKeyBrand]: 'PathKey';
84
+ };
85
+ /** A single path segment: a property name, or an array index. */
86
+ type Segment = string | number;
87
+ /** A structured path as a read-only sequence of segments. */
88
+ type Path = readonly Segment[];
89
+ /**
90
+ * Parse a dotted-string path into structured segments.
91
+ *
92
+ * ```ts
93
+ * parseDottedPath('user.address.line1') // ['user', 'address', 'line1']
94
+ * parseDottedPath('items.0.name') // ['items', 0, 'name']
95
+ * parseDottedPath('') // [''] (the empty-string key)
96
+ * ```
97
+ *
98
+ * The empty-string input `''` is the **literal empty-key path** `['']`,
99
+ * an ordinary (if rare) field address, not the root. Use the array
100
+ * form `[]` for the root. Form-level errors (root `.refine()`,
101
+ * `setErrors` with no path) live at the root path `[]` and are read via
102
+ * `errors([])`, never `errors('')`.
103
+ *
104
+ * Throws `InvalidPathError` for paths with empty INTERNAL segments
105
+ * (`'a..b'`, leading or trailing dots). For keys containing literal
106
+ * dots, pass an array form (`['user.name']`) instead.
107
+ */
108
+ declare function parseDottedPath(path: string): Segment[];
109
+ /**
110
+ * Canonicalise a path into structured segments plus a stable string
111
+ * key. Accepts either dotted-string or array form; integer-looking
112
+ * segments normalise to numbers.
113
+ *
114
+ * ```ts
115
+ * canonicalizePath('items.0.name')
116
+ * // { segments: ['items', 0, 'name'], key: '["items",0,"name"]' as PathKey }
117
+ *
118
+ * canonicalizePath(['items', 0, 'name'])
119
+ * // → same result
120
+ * ```
121
+ *
122
+ * The returned `key` works as a `Map` or `Set` key: equal paths produce
123
+ * equal keys whichever input form they arrived in.
124
+ */
125
+ declare function canonicalizePath(input: string | Path): {
126
+ segments: readonly Segment[];
127
+ key: PathKey;
128
+ };
129
+ /**
130
+ * The root path, an empty segment tuple. Pass it to any API taking a
131
+ * `Path` to address the form value whole. It is also where form-level
132
+ * errors live: a root `.refine()` message, a hydration failure, and a
133
+ * `setErrors` entry with no path all land at `[]`. The aggregate reads
134
+ * (`errors()`, `errors([])`, `meta.errors`) surface them alongside field
135
+ * errors, and `meta.ownErrors` returns the root bucket alone.
136
+ *
137
+ * The empty SEGMENT tuple `[]` is structurally unconstructible as a field
138
+ * path, so it can never collide with a schema key. The empty STRING key
139
+ * `''` (path `['']`, key `'[""]'`) is an ordinary field address.
140
+ */
141
+ declare const ROOT_PATH: Path;
142
+ /** Stable string key for the root path. */
143
+ declare const ROOT_PATH_KEY: PathKey;
144
+ /**
145
+ * `true` when `path` starts with every segment of `prefix`, in order. The
146
+ * empty `prefix` matches every path, the ROOT prefix being universal.
147
+ *
148
+ * Walks segments rather than `PathKey` strings because the data it
149
+ * operates on (`meta.errors[].path`, for one) carries segment arrays
150
+ * directly.
151
+ *
152
+ * ```ts
153
+ * isPathPrefix(['cargo'], ['cargo', 'items', 0, 'sku']) // true
154
+ * isPathPrefix(['cargo', 'items'], ['cargo']) // false (path shorter)
155
+ * isPathPrefix([], ['anything']) // true (root prefix)
156
+ * ```
157
+ */
158
+ declare function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean;
159
+
160
+ /**
161
+ * Per-path field status. Replaced wholesale (not mutated in place) on
162
+ * every change. Five semantic groups:
163
+ *
164
+ * - `connected`: is a DOM element registered for this path?
165
+ * - `focused` / `blurred`: DOM-state flags. `null` while no element
166
+ * is connected (no DOM means the concepts don't apply); plain
167
+ * booleans once connected, with the invariant `blurred === !focused`
168
+ * enforced by `markFocused`.
169
+ * - `touched`: focus/blur history, not DOM state. Always a plain
170
+ * boolean: `false` at registration, sticky `true` after first blur,
171
+ * cleared only by `form.reset()` / `form.resetField(path)`. Persists
172
+ * across disconnects so v-if'd-away fields don't lose their touched
173
+ * state on rehide (wizard "show review of touched fields" patterns
174
+ * rely on this).
175
+ * - `interacted`: value-mutation history, not DOM state. Plain
176
+ * boolean: `false` at registration, sticky `true` once the user
177
+ * issues a value edit through the directive's input listeners
178
+ * (never on hydration, default seeding, or programmatic setValue);
179
+ * cleared with `touched` by `form.reset()` / `form.resetField(path)`.
180
+ * - `blurredAfterInteraction`: the first blur that follows a value
181
+ * edit (the field has been edited and then left). Plain boolean,
182
+ * sticky `true`. A tab-through blur with no prior edit does NOT set
183
+ * it (`interacted` is still false at that blur). Composes
184
+ * `interacted` with the departure; drives the default display gate.
185
+ */
186
+ type FieldRecord = {
187
+ readonly path: Path;
188
+ readonly updatedAt: string | null;
189
+ readonly connected: boolean;
190
+ readonly focused: boolean | null;
191
+ readonly blurred: boolean | null;
192
+ readonly touched: boolean;
193
+ readonly interacted: boolean;
194
+ readonly blurredAfterInteraction: boolean;
195
+ };
196
+ /** Per-path DOM element tracking. Client-only. */
197
+ type ElementRecord = {
198
+ /**
199
+ * Original Path captured at first registration. Stored alongside the
200
+ * elements Set so the DOM-order sort cache can recover the structured
201
+ * Path without round-tripping through `JSON.parse(pathKey)`.
202
+ */
203
+ readonly path: Path;
204
+ readonly elements: Set<HTMLElement>;
205
+ };
206
+ /**
207
+ * Per-path record stored in `originals`. Pairing `segments` with the tracked
208
+ * value means `dirty` and `resetField`'s container loop don't have to
209
+ * `JSON.parse(pathKey)` on every iteration, the canonical Path already
210
+ * sitting next to the value it belongs to. PathKey still keys the Map (the
211
+ * stable string is the only collision-free identifier), but downstream
212
+ * iteration reads `segments` directly.
213
+ */
214
+ type OriginalsRecord = {
215
+ readonly segments: Path;
216
+ readonly value: unknown;
217
+ };
218
+
219
+ /** Internal brand for the `Unset` type. Never exposed at runtime. */
220
+ declare const _unsetBrand: unique symbol;
221
+ /**
222
+ * Brand-typed sentinel admitted at every primitive leaf of
223
+ * `DefaultValuesShape<T>`, `setValue`, and `reset`. The runtime value
224
+ * is exported as {@link unset} under the same name.
225
+ */
226
+ type Unset = typeof _unsetBrand;
227
+ /**
228
+ * The `unset` sentinel: pass it as a primitive leaf's value to mark
229
+ * the field **displayed-empty** while storage holds the schema's slim
230
+ * default (`0` / `''` / `false` / `0n`).
231
+ *
232
+ * Use it wherever a primitive leaf value is expected:
233
+ *
234
+ * ```ts
235
+ * const form = useForm({
236
+ * schema: z.object({ income: z.number() }),
237
+ * defaultValues: { income: unset }, // UI starts blank, storage holds 0
238
+ * key: 'housing',
239
+ * })
240
+ *
241
+ * form.setValue('income', unset) // re-blank a field after a write
242
+ * form.reset({ income: unset }) // reset to the blank state
243
+ * ```
244
+ *
245
+ * Accepted at any `string` / `number` / `boolean` / `bigint` leaf in
246
+ * `defaultValues`, `setValue(path, unset)`, and `form.reset({ … })`.
247
+ *
248
+ * The path joins the form's `blankPaths` set as long as it stays
249
+ * unset. Required schemas (no `.optional()` / `.nullable()` /
250
+ * `.default(N)`) raise `"No value supplied"` on submit while a leaf
251
+ * is in `blankPaths`; optional / nullable / has-default schemas
252
+ * accept the empty case as their wrapper allows.
253
+ *
254
+ * Storage never holds the symbol; the runtime translates it at the
255
+ * API boundary, so reads through `form.values` always see the slim
256
+ * default. Cross-bundle / SSR-safe: backed by `Symbol.for(...)` so
257
+ * every realm gets the same sentinel.
258
+ *
259
+ * @see {@link isUnset}, the type guard that narrows a value back to {@link Unset}.
260
+ * @see `docs/validation/blank.md` for the model behind blank-aware fields.
261
+ */
262
+ declare const unset: Unset;
263
+ /**
264
+ * Type guard: `true` when `value` is the `unset` sentinel.
265
+ *
266
+ * ```ts
267
+ * if (isUnset(payload.income)) {
268
+ * // payload.income is the sentinel; the field will display empty
269
+ * }
270
+ * ```
271
+ */
272
+ declare function isUnset(value: unknown): value is Unset;
273
+
274
+ /**
275
+ * The minimum shape any form value satisfies, a plain record. Use it as
276
+ * a constraint for composables that work generically across forms, such
277
+ * as a custom hook taking any form's `useForm` return.
278
+ */
279
+ type GenericForm = Record<string, unknown>;
280
+ /** `true` when `T` is an object or array. */
281
+ type IsObjectOrArray<T> = T extends GenericForm ? true : T extends Array<unknown> ? true : false;
282
+ /**
283
+ * Shared recursion body backing `PartialFlatPath` and
284
+ * `RegisterFlatPath`. One walk over `Form`, with `Mode` deciding
285
+ * whether container paths are emitted alongside their reachable leaves.
286
+ *
287
+ * - `'partial'`: emit every container path (object containers,
288
+ * nested-object array roots, array-of-object element containers).
289
+ * Used by `setValue`, `form.values.<path>`, and every read-side API
290
+ * that can address a container.
291
+ * - `'register'`: skip container paths, since `v-register` binds onto a
292
+ * leaf-backing native element. Primitive arrays still admit the
293
+ * array-root path under both modes, because multi-select and
294
+ * grouped-checkbox bindings register onto the array itself.
295
+ */
296
+ type FlatPathBuilder<Form, Mode extends 'partial' | 'register', Key extends keyof Form = keyof Form> = IsObjectOrArray<Form> extends true ? Key extends string ? Form[Key] extends infer Value ? Value extends Array<infer ArrayItem> ? IsObjectOrArray<ArrayItem> extends true ? Mode extends 'partial' ? `${Key}` | `${Key}.${number}` | `${Key}.${number}.${FlatPathBuilder<ArrayItem, Mode>}` : `${Key}.${number}.${FlatPathBuilder<ArrayItem, Mode>}` : `${Key}` | `${Key}.${number}` : Value extends GenericForm ? Mode extends 'partial' ? `${Key}` | `${Key}.${FlatPathBuilder<Value, Mode>}` : `${Key}.${FlatPathBuilder<Value, Mode>}` : `${Key}` : never : Key extends number ? `${Key}` | (Form[Key] extends GenericForm ? `${Key}.${FlatPathBuilder<Form[Key], Mode>}` : Form[Key] extends Array<infer ArrayItem> ? IsObjectOrArray<ArrayItem> extends true ? Mode extends 'partial' ? `${Key}.${number}` | `${Key}.${number}.${FlatPathBuilder<ArrayItem, Mode>}` : `${Key}.${number}.${FlatPathBuilder<ArrayItem, Mode>}` : `${Key}.${number}` : never) : never : never;
297
+ /**
298
+ * Backs `FlatPath` in its default partial-path mode. Reach for
299
+ * `FlatPath`; this alias is not part of the stable surface.
300
+ *
301
+ * It is exported so `rollup-plugin-dts` keeps it as a named alias in
302
+ * the bundled `.d.ts` rather than inlining the full template-literal
303
+ * recursion at every reference site. Inlined, it compounds into TS2589
304
+ * territory once several complex forms share a scope.
305
+ *
306
+ * The `Form extends unknown` wrapper distributes over a top-level
307
+ * union so each member contributes its OWN `keyof`. That is what makes
308
+ * a `z.discriminatedUnion` root expose every variant's keys as
309
+ * addressable paths, rather than the intersection that naked
310
+ * `keyof (A | B)` collapses to. Interior unions already distribute
311
+ * inside `FlatPathBuilder` at its `infer Value` step.
312
+ */
313
+ type PartialFlatPath<Form> = Form extends unknown ? FlatPathBuilder<Form, 'partial'> : never;
314
+ /**
315
+ * Union of dotted-string paths reachable inside `Form`. For
316
+ * `{ user: { email: string }, items: string[] }`:
317
+ *
318
+ * `'user' | 'user.email' | 'items' | 'items.0' | 'items.1' | ...`
319
+ *
320
+ * Every path-addressed API (`setValue(path, value)`, `register(path)`,
321
+ * `toRef(path)`) takes one, so paths autocomplete and typos are
322
+ * compile errors.
323
+ */
324
+ type FlatPath<Form> = PartialFlatPath<Form>;
325
+ /**
326
+ * Convert a tuple of path segments to its dotted-string equivalent.
327
+ *
328
+ * `JoinSegments<['cargo', 'items', 0, 'sku']>` is `'cargo.items.0.sku'`
329
+ *
330
+ * Depth is bounded by the tuple length, typically 3 or 4, so the cost
331
+ * does not scale with `FlatPath<Form>`. Template literal types
332
+ * distribute over unions, so a segment union propagates into the joined
333
+ * path: `JoinSegments<['pickup' | 'delivery', 'line1']>` is
334
+ * `'pickup.line1' | 'delivery.line1'`. That is what makes tuple-form
335
+ * path APIs work inside a `v-for` over a prefix variable, since the
336
+ * joined result is checked against the existing `FlatPath<Form>` /
337
+ * `RegisterFlatPath<Form>` unions instead of a separate enumeration.
338
+ */
339
+ type JoinSegments<S extends ReadonlyArray<string | number>, Acc extends string = ''> = S extends readonly [
340
+ infer Head extends string | number,
341
+ ...infer Rest extends ReadonlyArray<string | number>
342
+ ] ? Acc extends '' ? JoinSegments<Rest, `${Head}`> : JoinSegments<Rest, `${Acc}.${Head}`> : Acc;
343
+ /**
344
+ * The advice half of a segment-array rejection, used when `S` never
345
+ * inferred a tuple at all.
346
+ *
347
+ * A caller passing a plain `string` lands here: inference falls back to
348
+ * the constraint, and `JoinSegments` of a non-tuple array is `''`.
349
+ * Naming a path would be a lie, so the message names the remedies
350
+ * instead. This is the shape behind #568, where a row component
351
+ * received its path prefix as an untyped `string` prop and every
352
+ * concatenation widened to `string`.
353
+ */
354
+ type PlainStringAdvice = 'attaform: a plain string cannot be checked against the schema. Pass a literal path, type the dynamic prefix, or use the segment-array form.';
355
+ /**
356
+ * Why a segment array was rejected, as a type the compiler prints.
357
+ *
358
+ * Every segment-array overload brands its parameter
359
+ * `S & (<joined path is valid> ? unknown : <this>)`. The brand must
360
+ * stay a string literal: `S & never` is `never`, which makes TypeScript
361
+ * report the parameter as `never` and say nothing about what was wrong,
362
+ * while a string literal intersects without collapsing, so the sentence
363
+ * survives into the diagnostic and the argument keeps its inferred
364
+ * tuple type:
365
+ *
366
+ * ```
367
+ * Argument of type '["boxes", 3, "nope"]' is not assignable to parameter
368
+ * of type 'readonly ["boxes", 3, "nope"] &
369
+ * "attaform: 'boxes.3.nope' is not a path in this form's schema"'.
370
+ * ```
371
+ *
372
+ * Nothing can satisfy the brand, so a wrong path stays a compile error;
373
+ * only the message changes.
374
+ */
375
+ type SegmentPathRejection<Joined extends string> = [Joined] extends [''] ? PlainStringAdvice : `attaform: '${Joined}' is not a path in this form's schema`;
376
+ /**
377
+ * `SegmentPathRejection` for `register`, whose accepted set is
378
+ * `RegisterFlatPath` rather than `FlatPath`.
379
+ *
380
+ * Worth its own sentence: a container path is in the schema and still
381
+ * not registrable, so "not a path in this form's schema" would be false
382
+ * exactly where it is most likely to be read.
383
+ */
384
+ type SegmentRegisterRejection<Joined extends string> = [Joined] extends [''] ? PlainStringAdvice : `attaform: '${Joined}' is not a registrable path. v-register binds a leaf input, so container paths are excluded.`;
385
+ /**
386
+ * `true` when `T` is a union, `false` for a single type. Gates
387
+ * non-homomorphic mapped-type forms so a single object type keeps its
388
+ * homomorphic `[K in keyof T]` lookup, preserving literal keys instead
389
+ * of widening to an index signature.
390
+ */
391
+ type IsUnion<T, U = T> = T extends T ? ([U] extends [T] ? false : true) : never;
392
+ /**
393
+ * Union of all keys across all members of `T`. For a single object type
394
+ * this equals `keyof T`; for `A | B` it produces `keyof A | keyof B`,
395
+ * where naked `keyof (A | B)` would intersect to common keys only.
396
+ *
397
+ * Paired with `ValueOfUnion` to merge variant key sets in the chained
398
+ * metadata proxies (`form.fields`, `form.errors`), so per-variant
399
+ * leaves stay addressable through one shape whichever discriminant is
400
+ * active.
401
+ */
402
+ type KeyofUnion<T> = T extends unknown ? keyof T : never;
403
+ /**
404
+ * Value at key `K` across union members of `T`. Members holding `K`
405
+ * contribute `T[K]`; members lacking it contribute `undefined`.
406
+ *
407
+ * That mirrors the metadata proxies at runtime: chained access works at
408
+ * every union member, and the leaf carries `T | undefined` because the
409
+ * key is absent in some variants, where the runtime returns a stable
410
+ * stub.
411
+ */
412
+ type ValueOfUnion<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T[K] : undefined : never;
413
+ /**
414
+ * Value at key `K` across union members of `T`, dropping members that
415
+ * LACK `K` entirely: they contribute `never`, not `undefined`. The
416
+ * counterpart to `ValueOfUnion`, which injects a synthetic `undefined`
417
+ * for absent-variant keys so chained reads stay safe.
418
+ *
419
+ * `form.fields` uses it at discriminated-union keys so a variant-only
420
+ * field types as node-optional `FieldState<X> | undefined`, the node
421
+ * being absent when its variant is not active, rather than
422
+ * value-optional `FieldState<X | undefined>`, which would falsely
423
+ * promise a readable node. A genuine `undefined` from an OPTIONAL
424
+ * declaration survives; only the synthetic one is stripped.
425
+ */
426
+ type PresentValueOfUnion<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T[K] : never : never;
427
+ /**
428
+ * Apply the discriminated-union key merge to a shape carrying values
429
+ * rather than metadata leaves. Single object types map homomorphically;
430
+ * unions of objects merge keys via `KeyofUnion` / `ValueOfUnion` so
431
+ * per-variant fields are reachable through one chained-access shape.
432
+ *
433
+ * `ValuesSurface` uses it to make an oversized-only
434
+ * `form.values.cargo.permitNumber` typecheck whichever variant is
435
+ * active, matching the runtime, where plain JS access on a missing key
436
+ * returns `undefined` rather than throwing.
437
+ *
438
+ * Date / Map / Set / RegExp / function leaves stay opaque, since a
439
+ * value read of those should preserve the platform shape.
440
+ */
441
+ type LiftedValueShape<T> = [T] extends [
442
+ string | number | boolean | bigint | symbol | null | undefined
443
+ ] ? T : [T] extends [
444
+ Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown)
445
+ ] ? T : [T] extends [ReadonlyArray<unknown>] ? T : [T] extends [object] ? [IsUnion<T>] extends [true] ? {
446
+ [K in KeyofUnion<T>]: LiftedValueShape<ValueOfUnion<T, K>>;
447
+ } : {
448
+ [K in keyof T]: LiftedValueShape<T[K]>;
449
+ } : T;
450
+ /**
451
+ * Recursive `Partial`: every property at every depth is optional, so a
452
+ * partial override at any nesting level is valid.
453
+ */
454
+ type DeepPartial<T> = T extends Primitive ? T : T extends Array<infer ArrayItem> ? DeepPartial<ArrayItem>[] : T extends object ? {
455
+ [Key in keyof T]?: DeepPartial<T[Key]>;
456
+ } : T;
457
+ /**
458
+ * Shared descent body backing `NestedType` and `NestedReadType`. Both
459
+ * walk segment by segment, distributing over union members via
460
+ * `KeyofUnion` / `ValueOfUnion`, and diverge only at the leaf:
461
+ *
462
+ * - `TaintArrayCrossings extends false` (`NestedType`): leaves are
463
+ * returned untouched, which is what the strict write-side APIs need
464
+ * (`setValue`'s value parameter, `form.fields.<path>`).
465
+ * - `TaintArrayCrossings extends true` (`NestedReadType`): leaves widen
466
+ * with `| undefined` once any segment in the walk was an array index,
467
+ * reflecting an out-of-bounds read at runtime.
468
+ *
469
+ * `_Tainted` carries the array crossing under taint mode and stays
470
+ * `false` through every arm under strict mode. Both strip nullishness
471
+ * at the root.
472
+ *
473
+ * Reach for `NestedType` or `NestedReadType`; this is not part of the
474
+ * stable surface.
475
+ */
476
+ type NestedTypeBuilder<RootValue, FlattenedPath extends string, TaintArrayCrossings extends boolean, _Tainted extends boolean = false, _RootValue = NonNullable<RootValue>> = IsObjectOrArray<_RootValue> extends false ? never : FlattenedPath extends `${infer Key}.${infer Rest}` ? Key extends `${number}` ? Key extends KeyofUnion<_RootValue> ? NestedTypeBuilder<ValueOfUnion<_RootValue, Key>, Rest, TaintArrayCrossings, TaintArrayCrossings extends true ? true : _Tainted> : Key extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? NestedTypeBuilder<ValueOfUnion<_RootValue, NumericKey>, Rest, TaintArrayCrossings, TaintArrayCrossings extends true ? true : _Tainted> : never : never : Key extends KeyofUnion<_RootValue> ? NestedTypeBuilder<ValueOfUnion<_RootValue, Key>, Rest, TaintArrayCrossings, _Tainted> : never : FlattenedPath extends `${number}` ? FlattenedPath extends KeyofUnion<_RootValue> ? TaintArrayCrossings extends true ? ValueOfUnion<_RootValue, FlattenedPath> | undefined : ValueOfUnion<_RootValue, FlattenedPath> : FlattenedPath extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? TaintArrayCrossings extends true ? ValueOfUnion<_RootValue, NumericKey> | undefined : ValueOfUnion<_RootValue, NumericKey> : never : never : FlattenedPath extends KeyofUnion<_RootValue> ? _Tainted extends true ? ValueOfUnion<_RootValue, FlattenedPath> | undefined : ValueOfUnion<_RootValue, FlattenedPath> : never;
477
+ /**
478
+ * Resolve the type at a dotted-string path inside `RootValue`, for the
479
+ * strict write-side APIs:
480
+ *
481
+ * `NestedType<{ user: { email: string } }, 'user.email'>` is `string`
482
+ *
483
+ * On a discriminated-union descent it uses `KeyofUnion` /
484
+ * `ValueOfUnion`, so a per-variant key resolves to `T | undefined`
485
+ * rather than `never`. That keeps `NestedType` in lockstep with
486
+ * `FlatPath`: every path `FlatPath` says is reachable resolves to a
487
+ * useful value type, instead of collapsing to the intersection of all
488
+ * variants' keys.
489
+ *
490
+ * TypeScript caps conditional-type recursion near 50 levels, so paths
491
+ * deeper than that resolve to `never`. Real schemas do not reach it.
492
+ */
493
+ type NestedType<RootValue, FlattenedPath extends string> = NestedTypeBuilder<RootValue, FlattenedPath, false>;
494
+ /**
495
+ * Primitive-leaf marker used by `DeepPartial` and the sibling
496
+ * structural walkers. Reach for `DeepPartial`; this is not part of the
497
+ * stable surface, and is exported only so the bundled `.d.ts`
498
+ * references one alias instead of re-emitting the union at every
499
+ * recursion branch of every walker.
500
+ */
501
+ type Primitive = string | number | boolean | symbol | bigint | null | undefined;
502
+ /**
503
+ * Distinguish a tuple from a regular array, for write-side helpers that
504
+ * must preserve tuple positions instead of widening to
505
+ * `Array<element>`.
506
+ *
507
+ * `IsTuple<[string, number]>` is `true`
508
+ * `IsTuple<string[]>` is `false`
509
+ */
510
+ type IsTuple<T extends readonly unknown[]> = number extends T['length'] ? false : true;
511
+ /**
512
+ * Path-resolved type for the read-side APIs (`form.values.<path>`,
513
+ * `form.toRef(path)`, `register(path).innerRef`). Like `NestedType`,
514
+ * except that once the walk crosses an array index the result is tagged
515
+ * `| undefined`, honouring the out-of-bounds read the runtime allows.
516
+ * Discriminated-union descents follow the same rule as `NestedType`.
517
+ */
518
+ type NestedReadType<RootValue, FlattenedPath extends string> = NestedTypeBuilder<RootValue, FlattenedPath, true>;
519
+ /**
520
+ * The array a path actually addresses, nullish stripped, or `never`
521
+ * when the path does not address an array. Shared by `ArrayPath`
522
+ * (membership) and `ArrayItem` (element type) so the filter and the
523
+ * extractor cannot disagree about what counts as one.
524
+ *
525
+ * The strip is load-bearing. `Form` is the schema's INPUT shape, so
526
+ * `z.array(row).default([])` resolves to `row[] | undefined`, and
527
+ * `NestedType` tags a discriminated union's per-variant keys the same
528
+ * way on purpose. A bare `extends readonly unknown[]` predicate reads
529
+ * that tag as "not an array", which is how every optional, defaulted,
530
+ * nullable and DU-variant array fell out of the field-array helpers
531
+ * while the runtime went on accepting all of them (#541).
532
+ *
533
+ * `extends infer Leaf` binds the stripped leaf once so the guards below
534
+ * do not re-instantiate `NestedType`, and the tuple wraps suppress
535
+ * distribution so a union leaf is judged whole: `A[] | B[]` is an
536
+ * array, `A[] | string` is not.
537
+ *
538
+ * Reach for `ArrayPath` / `ArrayItem`; this alias exists so
539
+ * `rollup-plugin-dts` keeps one named reference in the bundled `.d.ts`.
540
+ */
541
+ type ArrayLeafOf<Form, P extends string> = Exclude<NestedType<Form, P>, undefined | null> extends infer Leaf ? [Leaf] extends [never] ? never : [Leaf] extends [readonly unknown[]] ? Leaf : never : never;
542
+ /**
543
+ * `FlatPath<Form>` narrowed to the paths whose leaf is an array, so the
544
+ * typed field-array helpers (append, remove, swap) accept only paths
545
+ * that address one: `append('email', ...)` on a `{ email: string }` is
546
+ * a compile error. Optionality is not a disqualifier; see `ArrayLeafOf`
547
+ * for what counts as an array here.
548
+ *
549
+ * `P extends string` re-triggers distribution over the `FlatPath<Form>`
550
+ * union so the conditional evaluates per member. Without it the branch
551
+ * reduces against the union as a whole and collapses to `never` as soon
552
+ * as one member fails the predicate.
553
+ */
554
+ type ArrayPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends string ? [ArrayLeafOf<Form, P>] extends [never] ? never : P : never;
555
+ /**
556
+ * Element type of the array addressed by `Path`. Callers constrain
557
+ * `Path extends ArrayPath<Form>`, so this is always well-defined. It
558
+ * rides on `ArrayLeafOf`, the same alias `ArrayPath` admits the path
559
+ * with, so an accepted path never hands its helper a `never` value
560
+ * slot.
561
+ */
562
+ type ArrayItem<Form, Path extends ArrayPath<Form>> = ArrayLeafOf<Form, Path> extends ReadonlyArray<infer Item> ? Item : never;
563
+ /**
564
+ * The record a path actually addresses, nullish stripped, or `never`
565
+ * when the path does not address one. Shared by `RecordPath`
566
+ * (membership) and `RecordValue` (value type).
567
+ *
568
+ * `string extends keyof Leaf` is the index-signature probe: it holds
569
+ * for `Record<string, V>`, whose `keyof` is `string`, and fails for a
570
+ * fixed object, whose `keyof` is a literal key union. The leading array
571
+ * guard keeps arrays, which also satisfy the object check, out of the
572
+ * record set.
573
+ *
574
+ * Nullish is stripped first for the reason `ArrayLeafOf` gives, with an
575
+ * extra bite here: `keyof (Record<string, V> | undefined)` is `never`,
576
+ * so on a `.default({})` or `.optional()` record the probe could not
577
+ * fire at all and `form.record(path)` rejected the path (#541).
578
+ *
579
+ * Reach for `RecordPath` / `RecordValue`; exported for the same `.d.ts`
580
+ * reason as `ArrayLeafOf`.
581
+ */
582
+ type RecordLeafOf<Form, P extends string> = Exclude<NestedType<Form, P>, undefined | null> extends infer Leaf ? [Leaf] extends [never] ? never : [Leaf] extends [readonly unknown[]] ? never : [Leaf] extends [Record<string, unknown>] ? string extends keyof Leaf ? Leaf : never : never : never;
583
+ /**
584
+ * Companion to `ArrayPath`: `FlatPath<Form>` narrowed to the paths
585
+ * whose leaf is a record, meaning an object with an open string-keyed
586
+ * index signature such as `z.record(z.string(), V)`. A fixed-shape
587
+ * `z.object({ ... })` is excluded, since its keys are statically known
588
+ * and it has no `string` index signature.
589
+ */
590
+ type RecordPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends string ? [RecordLeafOf<Form, P>] extends [never] ? never : P : never;
591
+ /**
592
+ * Value type of the record addressed by `Path`, the `V` in a
593
+ * `Record<string, V>`. Callers constrain `Path extends
594
+ * RecordPath<Form>`, so the leaf is always an open string-keyed record.
595
+ * It rides on `RecordLeafOf`, the same alias `RecordPath` admits the
596
+ * path with, so the two cannot disagree.
597
+ */
598
+ type RecordValue<Form, Path extends RecordPath<Form>> = RecordLeafOf<Form, Path> extends Record<string, infer Value> ? Value : never;
599
+ /**
600
+ * Widens primitive-literal leaves to their primitive supertype, to
601
+ * match the runtime's slim-primitive write contract.
602
+ *
603
+ * WriteShape<{ color: 'red' | 'green' }>
604
+ * // { color: string }
605
+ * WriteShape<{ count: 42 }>
606
+ * // { count: number }
607
+ *
608
+ * The runtime write gate accepts any value whose primitive type matches
609
+ * the schema's slim primitive set at that path. Refinement-level
610
+ * constraints (enum membership, literal equality, format checks, length
611
+ * and range bounds, regex, custom predicates) are not enforced at write
612
+ * time; they surface through field-level validation. So
613
+ * `setValue('color', 'magenta')` and `defaultValues: { color: 'teal' }`
614
+ * are not type errors despite being out-of-enum at the validation
615
+ * layer.
616
+ *
617
+ * Tuple positions keep their literal types through the homomorphic
618
+ * mapped form, so `[string, number]` stays a 2-tuple of widened
619
+ * primitives instead of collapsing to `Array<string | number>`, and
620
+ * tuple detection runs before the array branch so positionally-typed
621
+ * literals survive. Date / RegExp / Map / Set / function instances pass
622
+ * through unchanged, since the runtime accepts them as their own slim
623
+ * kinds.
624
+ *
625
+ * This stays STRICT, with no `| Unset` widening, because it types the
626
+ * prev-value argument of `setValue(path, prev => ...)`, which always
627
+ * receives a real value, plus read-side types and internals like
628
+ * `FieldStateMap<T>`. The consumer-facing write-value type is
629
+ * `DefaultValuesShape<T>`, which adds `| Unset` at every recursable
630
+ * position.
631
+ */
632
+ type WriteShape<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends bigint ? bigint : T extends symbol ? symbol : T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T : T extends readonly [unknown, ...unknown[]] ? {
633
+ -readonly [K in keyof T]: WriteShape<T[K]>;
634
+ } : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
635
+ -readonly [K in keyof T]: WriteShape<T[K]>;
636
+ } : Array<WriteShape<U>> : T extends object ? {
637
+ [K in keyof T]: WriteShape<T[K]>;
638
+ } : T;
639
+ /**
640
+ * Walk `T` and add `| Unset` at every primitive leaf except symbol,
641
+ * null and undefined, at every opaque leaf (`Date`, `RegExp`, `Map`,
642
+ * `Set`, functions), and at every container position. The recursion
643
+ * topology mirrors `WriteShape<T>`, which is what lets
644
+ * `DefaultValuesShape<T>` be a one-line composition.
645
+ *
646
+ * Symbol / null / undefined leaves pass through untouched, so the
647
+ * runtime sentinel does not pollute leaf semantics it has no business
648
+ * carrying. Container positions widen so a single `unset` at any level
649
+ * recursively marks every descendant primitive blank.
650
+ *
651
+ * Reach for `DefaultValuesShape`; this is not part of the stable
652
+ * surface.
653
+ */
654
+ type AugmentWithUnset<T> = T extends string | number | boolean | bigint ? T | Unset : T extends symbol | null | undefined ? T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T | Unset : T extends readonly [unknown, ...unknown[]] ? {
655
+ -readonly [K in keyof T]: AugmentWithUnset<T[K]>;
656
+ } | Unset : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
657
+ -readonly [K in keyof T]: AugmentWithUnset<T[K]>;
658
+ } | Unset : Array<AugmentWithUnset<U>> | Unset : T extends object ? {
659
+ [K in keyof T]: AugmentWithUnset<T[K]>;
660
+ } | Unset : T;
661
+ /**
662
+ * `WriteShape<T>` plus `Unset`, the brand-typed sentinel that marks a
663
+ * leaf as starting displayed-empty in `defaultValues`, `setValue` and
664
+ * `reset`.
665
+ *
666
+ * `Unset` is admitted at every position: primitive leaves, opaque
667
+ * leaves (`Date`, `RegExp`, `Map`, `Set`, functions), and containers
668
+ * (objects, arrays, tuples, records, discriminated unions, optional and
669
+ * nullable wrappers). A container `unset` recursively marks every
670
+ * primitive descendant blank, so `defaultValues: { profile: unset }`
671
+ * and `setValue('cargo', unset)` typecheck cleanly.
672
+ *
673
+ * DefaultValuesShape<{ income: number; name: string; age: 21 }>
674
+ * // { income: number | Unset; name: string | Unset; age: number | Unset } | Unset
675
+ */
676
+ type DefaultValuesShape<T> = AugmentWithUnset<WriteShape<T>>;
677
+ /**
678
+ * The type accepted at `defaultValues` and at `reset()`'s parameter:
679
+ * `DeepPartial` and `DefaultValuesShape` fused into a single walk, so
680
+ * every level is optional and every position, primitive leaf, opaque
681
+ * leaf or container, admits `| Unset`.
682
+ *
683
+ * The fusion is deliberate and should not be re-split into
684
+ * `DeepPartial<DefaultValuesShape<F>>`. Both passes have identical
685
+ * topology (object to mapped, tuple to positional, array to recurse,
686
+ * primitive to terminal), and walking twice exhausts the depth budget
687
+ * at call sites wiring several complex forms into one scope. One walk
688
+ * also keeps opaque leaves (`Date`, `Map`, `Set`, `RegExp`, functions)
689
+ * intact when their containing property is optional, where
690
+ * `DeepPartial`'s pass would structurally destructure them.
691
+ *
692
+ * ```ts
693
+ * type T = DefaultValuesInput<{
694
+ * email: string
695
+ * joinedAt: Date
696
+ * profile: { name: string; age: number }
697
+ * }>
698
+ * // → {
699
+ * // email?: string | Unset
700
+ * // joinedAt?: Date | Unset
701
+ * // profile?: { name?: string | Unset; age?: number | Unset } | Unset
702
+ * // } | Unset
703
+ * ```
704
+ */
705
+ type DefaultValuesInput<T> = T extends string ? string | Unset : T extends number ? number | Unset : T extends boolean ? boolean | Unset : T extends bigint ? bigint | Unset : T extends symbol ? symbol : T extends null | undefined ? T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T | Unset : T extends readonly [unknown, ...unknown[]] ? {
706
+ -readonly [K in keyof T]?: DefaultValuesInput<T[K]>;
707
+ } | Unset : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
708
+ -readonly [K in keyof T]?: DefaultValuesInput<T[K]>;
709
+ } | Unset : Array<DefaultValuesInput<U>> | Unset : T extends object ? {
710
+ [K in keyof T]?: DefaultValuesInput<T[K]>;
711
+ } | Unset : T;
712
+ /**
713
+ * The type accepted at a `useForm` overload's `defaultValues` slot.
714
+ * `Form` is the schema's input projection; `SchemaInput` is the
715
+ * schema's own `z.input<Schema>`. A value is one of:
716
+ *
717
+ * - `DefaultValuesInput<Form>`, the partial, `Unset`-widened
718
+ * in-progress shape. `defaultValues` deliberately accepts a form
719
+ * mid-completion: a leaf may be blank or hold a not-yet-valid value,
720
+ * so a `z.email()` field accepts any `string`. Sharp, fully
721
+ * validated types are produced only at `handleSubmit`.
722
+ * - `SchemaInput`, the schema's full input shape.
723
+ * - a sync or async factory returning either.
724
+ *
725
+ * The `SchemaInput` arm is redundant at a concrete call site, since a
726
+ * schema's input is always assignable to its own `DefaultValuesInput`,
727
+ * and it does not weaken per-field checking (`number` is still rejected
728
+ * where `string` is expected). It earns its place in the generic
729
+ * form-wrapper case (#422): when the schema is a free type parameter
730
+ * `S`, a forwarded `z.input<S>` is REFLEXIVELY assignable to that arm,
731
+ * a relation TypeScript can decide even under a generic, whereas
732
+ * `DefaultValuesInput<Form>` alone stays a deferred conditional cascade
733
+ * that is not provably assignable and trips TS2589 / TS2769. No runtime
734
+ * effect; the slot only governs what the type checker accepts.
735
+ *
736
+ * `DefaultValuesInput<Form>` is computed ONCE, as the `DVI` argument to
737
+ * `AcceptableDefaultsOf`, rather than inlined in each arm, which would
738
+ * re-instantiate the deep cascade three times per overload and, across
739
+ * the unified entry's two overloads, tip the bundled `.d.ts` into
740
+ * TS2589. The arms stay a DIRECT union rather than a conditional over
741
+ * the cascade, so the `SchemaInput` arm remains reflexively matchable
742
+ * under a generic.
743
+ */
744
+ type AcceptableDefaults<Form, SchemaInput> = AcceptableDefaultsOf<DefaultValuesInput<Form>, SchemaInput>;
745
+ type AcceptableDefaultsOf<DVI, SchemaInput> = DVI | SchemaInput | (() => DVI | SchemaInput) | (() => Promise<DVI | SchemaInput>);
746
+
747
+ /**
748
+ * Identifier for a form: the string passed to `useForm({ key })`, used
749
+ * to look a form up by name from a distant component and to label
750
+ * errors and DevTools entries. An anonymous `useForm` allocates one
751
+ * automatically, so pick one only when the form needs stable identity.
752
+ */
753
+ type FormKey = string;
754
+ /** Per-form options threaded from `useForm` into the adapter factory. */
755
+ interface SchemaFactoryOptions {
756
+ /** Recursion ceiling for walks through recursive schemas. */
757
+ maxRecursionDepth: number;
758
+ }
759
+ /**
760
+ * A JSON-serialisable value: the recursive shape of anything that
761
+ * survives a `JSON.stringify` / `JSON.parse` round-trip unchanged. The
762
+ * type of `ValidationError.data`.
763
+ *
764
+ * The object arm is a named interface rather than an inline index
765
+ * signature because TypeScript expands an anonymous recursive alias
766
+ * eagerly and hits TS2589 once `Json` is checked inside a large
767
+ * structural type such as the form store. A named reference defers
768
+ * that expansion.
769
+ */
770
+ type Json = string | number | boolean | null | JsonArray | JsonObject;
771
+ interface JsonArray extends Array<Json> {
772
+ }
773
+ interface JsonObject {
774
+ [key: string]: Json;
775
+ }
776
+ /**
777
+ * One validation failure, as returned by `validate()`, `parse()` and
778
+ * `handleSubmit`'s `onError`, and accepted leniently as `ErrorInput` by
779
+ * `form.setErrors`.
780
+ *
781
+ * Which form produced an error is envelope-level identity: the
782
+ * `ValidationResponse` or submit result carrying the list stamps
783
+ * `formKey` once, and an aggregator merging lists across forms (the
784
+ * wizard) stamps its own envelope.
785
+ */
786
+ type ValidationError = {
787
+ /** Human-readable message describing the failure. */
788
+ message: string;
789
+ /**
790
+ * Structured path of the offending field, such as
791
+ * `['user', 'address', 0, 'line1']`.
792
+ *
793
+ * The root path `[]` is the form-level bucket, the home for errors
794
+ * belonging to no specific field: root `.refine()` messages,
795
+ * `setErrors` entries with no path, hydration failures,
796
+ * server-emitted banners. The empty-STRING path `['']` is unrelated
797
+ * and means an ordinary field whose key is the empty string.
798
+ */
799
+ path: (string | number)[];
800
+ /**
801
+ * Stable machine identifier for the failure, scoped by prefix:
802
+ *
803
+ * - `atta:` for Attaform's own codes, see `AttaformErrorCode`.
804
+ * - an adapter prefix such as `zod:`, forwarded from the underlying
805
+ * schema library's issue code where it has one.
806
+ * - anything else for consumer-defined codes (`api:duplicate-email`,
807
+ * `auth:expired-token`). Pick a prefix and stay consistent, so
808
+ * error renderers and tests branch on `code` rather than matching
809
+ * exact message strings.
810
+ */
811
+ code: string;
812
+ /**
813
+ * Optional structured payload. Attaform never sets or reads this; it
814
+ * is a passthrough for whatever a server sends alongside the message
815
+ * (a captcha challenge, a lockout `unlocks_at`, an MFA step-up
816
+ * descriptor) so the UI can act on it. Survives serialise, hydrate,
817
+ * undo and redo unchanged.
818
+ */
819
+ data?: Json | null;
820
+ };
821
+ /**
822
+ * One path's slot in the form's tagged error store. The two sources
823
+ * that can put an error at a path stay segregated: `schema` holds the
824
+ * validation pipeline's verdicts, `user` holds `setErrors` entries and
825
+ * their history and SSR replays. Reads merge schema, then derived
826
+ * blank, then user, and each writer replaces exactly its own side.
827
+ *
828
+ * Cells are immutable. Every write replaces the whole cell, or deletes
829
+ * the key when both sides are empty, so Vue's per-key Map tracking
830
+ * fires for a change on either side. An empty side is the shared frozen
831
+ * `NO_ERRORS` array, never a fresh allocation.
832
+ *
833
+ * @internal
834
+ */
835
+ type ErrorCell = {
836
+ readonly schema: readonly ValidationError[];
837
+ readonly user: readonly ValidationError[];
838
+ };
839
+ /**
840
+ * The lenient input `form.setErrors` accepts: a real `Error`, or a
841
+ * partial `ValidationError` where every field is optional.
842
+ *
843
+ * - `message`: omitted or empty coerces to `"Unknown error"`.
844
+ * - `path`: defaults to `[]`, a form-level error. Ignored by the
845
+ * path-scoped `setErrors(path, ...)` form, which stamps its own.
846
+ * - `code`: defaults to `atta:user-error`.
847
+ * - `data`: forwarded verbatim onto the produced `ValidationError`.
848
+ *
849
+ * Because every field is optional, `ValidationError` is a subtype of
850
+ * `ErrorInput`: a `ValidationError[]` read back out, or a server
851
+ * response already emitting the shape, pipes straight into
852
+ * `form.setErrors` with no adapter.
853
+ */
854
+ type ErrorInput = Error | {
855
+ message?: string;
856
+ path?: (string | number)[];
857
+ code?: string;
858
+ data?: Json | null;
859
+ };
860
+ /**
861
+ * A parse verdict as an `AbstractSchema` produces it. Adapter authors
862
+ * implement THESE.
863
+ *
864
+ * A schema knows nothing about which form asked, being a pure function
865
+ * of the schema it wraps, which is what lets one `AbstractSchema` serve
866
+ * every form built on the same schema. The owning store stamps its own
867
+ * `formKey` on the way out, producing the `ValidationResponse` arms
868
+ * below.
869
+ */
870
+ type SchemaParseSuccess<TData> = {
871
+ /** The parsed value at the validated subtree, or the whole form when `validate()` took no path. */
872
+ data: TData;
873
+ errors: undefined;
874
+ success: true;
875
+ };
876
+ /** Schema-level verdict when no data could be produced, such as a top-level type mismatch. */
877
+ type SchemaParseErrorWithoutData = {
878
+ data: undefined;
879
+ /** Non-empty list of failures. */
880
+ errors: ValidationError[];
881
+ success: false;
882
+ };
883
+ /** Schema-level verdict when the parser produced partial data alongside failures. */
884
+ type SchemaParseErrorWithData<TData> = {
885
+ data: TData;
886
+ errors: ValidationError[];
887
+ success: false;
888
+ };
889
+ /** Settled schema-level verdict. Discriminate on `success`. */
890
+ type SchemaParseResult<TData> = SchemaParseSuccess<TData> | SchemaParseErrorWithData<TData> | SchemaParseErrorWithoutData;
891
+ /** Schema-level `getDefaultValues` verdict: defaults always come back. */
892
+ type SchemaDefaultsResult<TData> = SchemaParseSuccess<TData> | SchemaParseErrorWithData<TData>;
893
+ /** Settled validation result when the form or subtree parsed successfully. */
894
+ type ValidationResponseSuccess<TData> = SchemaParseSuccess<TData> & {
895
+ /** The form this verdict belongs to. Stamped by the store, not the schema. */
896
+ formKey: FormKey;
897
+ };
898
+ /** Settled validation result when no data could be produced. */
899
+ type ValidationResponseErrorWithoutData = SchemaParseErrorWithoutData & {
900
+ formKey: FormKey;
901
+ };
902
+ /** Settled validation result when the parser produced partial data alongside failures. */
903
+ type ValidationResponseErrorWithData<TData> = SchemaParseErrorWithData<TData> & {
904
+ formKey: FormKey;
905
+ };
906
+ /**
907
+ * Settled validation result. Discriminate on `success`:
908
+ *
909
+ * ```ts
910
+ * if (result.success) {
911
+ * // result.data is the parsed value, errors is undefined
912
+ * } else {
913
+ * // result.errors is non-empty, data may or may not be set
914
+ * }
915
+ * ```
916
+ */
917
+ type ValidationResponse<TData> = ValidationResponseSuccess<TData> | ValidationResponseErrorWithData<TData> | ValidationResponseErrorWithoutData;
918
+ /**
919
+ * `ValidationResponse` without the `data` payload, for the reactive
920
+ * `validate()` status, whose consumers need only the success flag and
921
+ * the error list.
922
+ */
923
+ type ValidationResponseWithoutValue<Form> = Omit<ValidationResponse<Form>, 'data'>;
924
+ /** Options bag for `form.parse`. */
925
+ type ParseOptions = {
926
+ /**
927
+ * Turn the pure read into an authoritative run: the verdict is
928
+ * committed to the error store at the parsed scope, and in-flight
929
+ * per-field validation is cancelled first. Default `false`.
930
+ */
931
+ readonly commit?: boolean;
932
+ };
933
+ /**
934
+ * Sync-or-async return shape for `AbstractSchema.validateAtPath`. The
935
+ * adapter returns the response inline when the schema and the caller's
936
+ * options permit synchronous validation, and a `Promise<T>` otherwise.
937
+ * Callers that do not care simply `await`, which works for both; the
938
+ * reshape pre-pass, which prevents flicker, branches on
939
+ * `instanceof Promise`.
940
+ */
941
+ type MaybePromise<T> = T | Promise<T>;
942
+ /** Options accepted by `AbstractSchema.validateAtPath`. */
943
+ type ValidateOptions = {
944
+ /**
945
+ * Ask the adapter to return the response inline where the schema
946
+ * permits synchronous validation. A preference, not a guarantee: a
947
+ * structurally async schema, meaning any verdict that resolves only
948
+ * through a Promise (async refinements, async transforms or pipes)
949
+ * in whichever library the adapter wraps, still falls back to a
950
+ * `Promise<T>`.
951
+ *
952
+ * Omitted or `false`, the adapter is free to use its async path.
953
+ * Every call site outside the reshape pre-pass leaves it so.
954
+ */
955
+ sync?: boolean;
956
+ };
957
+ /**
958
+ * Configuration passed to `AbstractSchema.getDefaultValues`. Adapters
959
+ * receive `useDefaultSchemaValues`, which chooses between honoring
960
+ * `.default(x)` wrappers and using empty or falsy fallbacks, plus an
961
+ * optional `constraints` overlay merged into the derived defaults so
962
+ * the runtime can stamp user-supplied defaults at construction.
963
+ * Exported so adapter authors can co-implement the service contract.
964
+ */
965
+ type GetDefaultValuesConfig<Form> = {
966
+ useDefaultSchemaValues: boolean;
967
+ constraints?: DeepPartial<WriteShape<Form>> | undefined;
968
+ };
969
+ /**
970
+ * The contract a schema adapter implements so the form runtime can
971
+ * read defaults, validate, and walk paths against any underlying
972
+ * schema library.
973
+ *
974
+ * Most consumers never touch this type directly, since the typed entry
975
+ * points (`attaform/zod`, `attaform/zod-v3`) wire an adapter
976
+ * automatically. Implement it only to add support for a new schema
977
+ * library.
978
+ */
979
+ type AbstractSchema<Form, GetValueFormType> = {
980
+ getDefaultValues(config: GetDefaultValuesConfig<Form>): SchemaDefaultsResult<Form>;
981
+ /**
982
+ * The schema-prescribed default at `path`. The runtime uses it to
983
+ * fill structural gaps, so every `setValue` write leaves the form
984
+ * satisfying the slim schema, meaning objects, arrays and primitives
985
+ * without refinement-level constraints.
986
+ *
987
+ * - Object property: the property's schema default.
988
+ * - Array element: the element default. Positions past the array's
989
+ * current length still resolve, since every position has the same
990
+ * element type.
991
+ * - Tuple position: the position's default, and `undefined` out of
992
+ * range.
993
+ * - Optional / Default / Nullable / Readonly / Catch / Pipe: the
994
+ * inner default.
995
+ * - Discriminated union: the first variant's default, matching
996
+ * `validateAtPath`'s first-success rule.
997
+ * - Leaf: the primitive default (`''`, `0`, `false`), or the
998
+ * wrapper's `.default(x)` value when present.
999
+ * - A path the schema does not declare: `undefined`.
1000
+ *
1001
+ * An adapter may return `undefined` for any path it cannot resolve;
1002
+ * callers read that as "do not fill" and keep the existing data.
1003
+ */
1004
+ getDefaultAtPath(path: Path): unknown;
1005
+ /**
1006
+ * The schema's appropriate nullish value at `path`: the underlying
1007
+ * type's empty concrete, with `.default(x)` wrappers explicitly NOT
1008
+ * honoured. This is what makes `form.clear(path)` differ from
1009
+ * `reset`: it ignores declared defaults and produces `false`, `0`,
1010
+ * `''`, `[]` or a recursively empty object instead.
1011
+ *
1012
+ * Sub-path resolution mirrors `getDefaultAtPath`; only leaves differ.
1013
+ *
1014
+ * - Primitive leaf: the falsy concrete (`''`, `0`, `false`, `0n`,
1015
+ * `new Date(0)`).
1016
+ * - Array / Set / Record: empty.
1017
+ * - `Optional<T>`: `undefined`, the wrapper's absent marker.
1018
+ * - `Nullable<T>`: `null`, the wrapper's explicit empty.
1019
+ * - `Default<T>` / `Prefault<T>` / `Catch<T>`: the inner schema's
1020
+ * empty. The declared default is INTENTIONALLY skipped.
1021
+ * - `Readonly<T>` / `preprocess(fn, T)`: the inner schema's empty.
1022
+ * - Object: recursive, every property gets its own empty.
1023
+ * - Discriminated union: the first variant's recursive empty.
1024
+ * - A path the schema does not declare: `undefined`.
1025
+ *
1026
+ * An adapter may return `undefined` for any path it cannot resolve;
1027
+ * callers read that as "do not write" and leave storage unchanged.
1028
+ */
1029
+ getEmptyValueAtPath(path: Path): unknown;
1030
+ /**
1031
+ * Whether `path` resolves to, or descends through, a schema-side
1032
+ * normalizer that runs at parse rather than at the write boundary.
1033
+ * In Zod v4 that is `z.preprocess(fn, inner)` and `z.coerce.X()`,
1034
+ * which both desugar to `ZodPipe<ZodTransform, inner>`; in Zod v3 it
1035
+ * is `ZodEffects` with `_def.effect.type === 'preprocess'`.
1036
+ *
1037
+ * The slim-primitive write gate consults it. True at a path means the
1038
+ * gate takes the consumer's raw value verbatim and stops walking
1039
+ * children: storage holds the user's input, and the normalizer fires
1040
+ * during `safeParse`, not at `setValue` time.
1041
+ *
1042
+ * Prefix semantic: true if ANY ancestor of `path` resolves to such a
1043
+ * wrapper, so descendants of a preprocess-wrapped container
1044
+ * short-circuit the gate too. Adapters cache by canonical path key.
1045
+ */
1046
+ isPreprocessOrCoerceLeaf(path: Path): boolean;
1047
+ /**
1048
+ * Whether `path` resolves to an opaque leaf: a schema that declares a
1049
+ * value without describing its shape. In Zod v4 that is `z.any()`,
1050
+ * `z.unknown()` and `z.custom(...)`, which is what both
1051
+ * `z.instanceof(X)` and `z.custom<T>()` compile to; in Zod v3 it is
1052
+ * `z.any()` and `z.unknown()`, with `z.instanceof(X)` peeling through
1053
+ * `ZodEffects` to `ZodAny`.
1054
+ *
1055
+ * The slim-primitive write gate consults it. An opaque leaf admits
1056
+ * every kind, containers included, and declares no sub-paths. Without
1057
+ * this the gate accepts an array at the leaf and then walks into it,
1058
+ * checking `files.0` against a schema that never declared that path;
1059
+ * the empty accept set there reads as "not in your schema" and no-ops
1060
+ * the whole write (#542).
1061
+ *
1062
+ * Exact-path semantic, unlike `isPreprocessOrCoerceLeaf`: it answers
1063
+ * for the node at `path` only. Descendants stay unwritable on their
1064
+ * own, because the schema genuinely does not declare them and
1065
+ * fabricating them would put phantom nodes on `form.fields`. Writing
1066
+ * the whole value at the opaque leaf is the supported move.
1067
+ */
1068
+ isOpaqueLeafAtPath(path: Path): boolean;
1069
+ /**
1070
+ * Distinguish a tuple, fixed-length and position-typed, from an
1071
+ * unbounded array at `path`. The runtime calls this on every write
1072
+ * that descends into an array branch and treats the answer as
1073
+ * definitive.
1074
+ *
1075
+ * - `number`: a tuple of this structural length. The runtime pads the
1076
+ * consumer's value to that length and recurses position by
1077
+ * position.
1078
+ * - `null`: not a tuple. Covers the unbounded array, where the
1079
+ * runtime uses the consumer's length and reuses one element default
1080
+ * for every position, and also every path that does not resolve to
1081
+ * an array at all, where `getDefaultAtPath` yields no element
1082
+ * default either and the consumer's array passes through unchanged.
1083
+ *
1084
+ * Wrappers (optional, nullable, default, readonly, catch, pipe, lazy)
1085
+ * are peeled before the type check, so `optional(z.tuple([...]))`
1086
+ * reports its tuple length.
1087
+ */
1088
+ arrayShapeAtPath(path: Path): number | null;
1089
+ /**
1090
+ * Whether the schema at `path` is a FIXED object, meaning a closed
1091
+ * set of declared keys (`z.object`), as opposed to an open or union
1092
+ * container (array, record, map, set, union, discriminated union)
1093
+ * whose element schema matches any segment.
1094
+ *
1095
+ * `form.fields` and `form.errors` use this to resolve a collision. A
1096
+ * fixed object's declared keys are known ahead of any data, so a key
1097
+ * the schema owns descends to a real terminal even before the live
1098
+ * value is populated, which keeps a declared-but-absent `optional`
1099
+ * field registrable. An open container cannot promise that: its
1100
+ * element schema accepts ANY segment, so the proxy falls back to the
1101
+ * keys the data currently holds and reads a genuinely absent key (an
1102
+ * out-of-bounds index, a missing record key, an inactive variant key)
1103
+ * as `undefined` rather than a phantom node.
1104
+ *
1105
+ * The empty path, the root form, is always a fixed object. Wrappers
1106
+ * are peeled before the kind check, so `z.object({...}).optional()`
1107
+ * still reports `true`. An undeclared path reports `false`.
1108
+ */
1109
+ isFixedObjectAtPath(path: Path): boolean;
1110
+ /**
1111
+ * How the container at `path` spells the keys of its own entries:
1112
+ * `'number'` for an array or tuple, `'string'` for an object or
1113
+ * record, and for a `z.map` the kind its declared key type accepts.
1114
+ * `undefined` for anything else, a leaf and an undeclared path
1115
+ * included.
1116
+ *
1117
+ * The write walkers consult it at a `Map`, where a new entry must be
1118
+ * filed under a key of the declared type and the path segment alone
1119
+ * cannot say which: an integer-looking segment canonicalises to a
1120
+ * number, so `scores.42` against a `z.map(z.string(), V)` would
1121
+ * otherwise take the number `42` as its key and fail the map's own
1122
+ * parse. An entry the map already holds needs no ruling, since its
1123
+ * existing key wins.
1124
+ *
1125
+ * A map whose key type is neither string-ish nor number-ish, or which
1126
+ * admits both, reports `undefined`: no segment can spell such a key,
1127
+ * so the map stays a whole value with no addressable entries.
1128
+ *
1129
+ * Adapters MAY cache per path; the runtime calls this on the write
1130
+ * path.
1131
+ */
1132
+ entryKeyKindAtPath(path: Path): 'string' | 'number' | undefined;
1133
+ /**
1134
+ * Every sub-schema that could resolve at `path`. Multiple results are
1135
+ * expected only for union branches, where the adapter cannot pick a
1136
+ * single winner until the data lands.
1137
+ *
1138
+ * `path` is the canonical `Segment[]`, walked segment by segment, so
1139
+ * a literal-dot key (`['user.name']`) never collides with the
1140
+ * sibling-pair form (`['user', 'name']`).
1141
+ */
1142
+ getSchemasAtPath(path: Path): AbstractSchema<unknown, GetValueFormType>[];
1143
+ /**
1144
+ * Validate the subtree at `path`, or the whole form when `path` is
1145
+ * `undefined`. As with `getSchemasAtPath`, `path` is the canonical
1146
+ * `Segment[]` rather than a dotted string, so two schemas with
1147
+ * colliding dotted forms stay distinct at the adapter boundary.
1148
+ *
1149
+ * Adapters MUST NOT throw: an error comes back as a `success: false`
1150
+ * response with a populated `errors` array.
1151
+ *
1152
+ * The `MaybePromise` return has three cases:
1153
+ * - `options.sync === true` and a sync-capable schema: the adapter
1154
+ * SHOULD return the response inline. That lets the runtime batch
1155
+ * error writes with a coincident value mutation into one Vue
1156
+ * reactive flush, which is what prevents the `{}` flicker
1157
+ * observable during a discriminated-union variant reshape.
1158
+ * - `options.sync === true` and an async-only schema, meaning any
1159
+ * verdict that resolves only through a Promise: the adapter MUST
1160
+ * fall back to `Promise<T>`. The flag is a preference; sync is not
1161
+ * always achievable.
1162
+ * - `options.sync` omitted or `false`: the adapter SHOULD return
1163
+ * `Promise<T>`.
1164
+ *
1165
+ * Callers that do not care simply `await`, which works for both arms.
1166
+ * A caller that must tell them apart branches on `instanceof Promise`.
1167
+ */
1168
+ validateAtPath(data: unknown, path: Path | undefined, options?: ValidateOptions): MaybePromise<SchemaParseResult<GetValueFormType>>;
1169
+ /**
1170
+ * The set of primitive `typeof`-style kinds the leaf schema at `path`
1171
+ * accepts at write time. The sync sister to `getSchemasAtPath` and
1172
+ * `validateAtPath`, used by `setValueAtPath` to gate writes without
1173
+ * round-tripping through async validation.
1174
+ *
1175
+ * Wrappers (optional, nullable, default, refinement, transform, pipe,
1176
+ * readonly, catch, lazy) are peeled. Refinement-level constraints,
1177
+ * meaning format checks such as email or uuid, min and max length,
1178
+ * enum membership, literal equality and regex, are IGNORED here:
1179
+ * those are a validation-time concern.
1180
+ *
1181
+ * The returned set unions across union branches and intersects across
1182
+ * intersection sides. Conventions:
1183
+ *
1184
+ * - Empty set: no kind admitted, so the gate rejects every write to
1185
+ * the path. Surfaces for `never`-typed schemas and for paths that
1186
+ * do not resolve in the schema at all, such as a typo.
1187
+ * - Permissive set, every kind: unknown or unconstrained, so the gate
1188
+ * accepts any value. Surfaces for `any`, `unknown` and `void`, and
1189
+ * where a lazy peel failed and the adapter cannot introspect.
1190
+ * - String-valued enums return `{'string'}`; numeric enums
1191
+ * `{'number'}`; literal types `{primitiveKindOf(literalValue)}`.
1192
+ * - Object and array containers return `{'object'}` / `{'array'}`;
1193
+ * the runtime walker recurses into entries at write time.
1194
+ * - Nullable and optional wrappers add `'null'` / `'undefined'` to
1195
+ * the inner set.
1196
+ */
1197
+ getSlimPrimitiveTypesAtPath(path: Path): ReadonlySet<SlimPrimitiveKind>;
1198
+ /**
1199
+ * Whether `path` resolves to a LEAF, meaning a path the schema
1200
+ * declares no sub-paths under. `form.values`, `form.errors` and
1201
+ * `form.fields` query this at every step to choose between
1202
+ * descending into a sub-proxy and terminating with a leaf value.
1203
+ *
1204
+ * That branching is what keeps reserved leaf-prop names (`dirty`,
1205
+ * `errors`, `valid`) from shadowing schema keys: they inject only at
1206
+ * the FieldState terminal, not at every depth, so a schema field
1207
+ * literally named `dirty` at depth 2 or deeper stays reachable in its
1208
+ * own right.
1209
+ *
1210
+ * - Object / Array / Map / Set, under any wrapper: `false`, a
1211
+ * container, so descend further.
1212
+ * - Primitive (string, number, boolean, bigint, symbol, null,
1213
+ * undefined, date, function): `true`. `date` and `function` are
1214
+ * leaves because their values are opaque; do not drill into them.
1215
+ * - Opaque leaf (`z.any()`, `z.unknown()`, `z.custom()`, and the
1216
+ * `z.instanceof(X)` that compiles to it): `true`, and this answers
1217
+ * AHEAD of the kind test. An opaque leaf admits every kind
1218
+ * including the container ones, so classifying it off the slim
1219
+ * primitive set alone would call it a container and hang phantom
1220
+ * nodes under it on `form.fields`. See `isOpaqueLeafAtPath`.
1221
+ * Leafness asks whether the schema declares sub-paths here, and an
1222
+ * opaque leaf declares none.
1223
+ * - Optional / Nullable / Default / Catch: transparent. They add
1224
+ * `'null'` / `'undefined'` to the inner kind set without changing
1225
+ * the classification.
1226
+ * - Discriminated union root: `false`, since the variants are
1227
+ * objects and the kind set holds `'object'`.
1228
+ * - A union's discriminator key: `true`, since the literal type
1229
+ * resolves to `{'string'}` or `{'number'}`.
1230
+ * - A variant-only key: `true` if it resolves to a primitive in any
1231
+ * variant. Schema-static, so it does NOT read live storage to
1232
+ * decide which variant is active.
1233
+ * - Empty path, the root: `false`, the form-as-object.
1234
+ * - A path the schema does not declare: `false`. The proxy descends
1235
+ * permissively and leaf-prop reads at the unknown path return
1236
+ * `undefined` from the store. Treating unknown paths as containers
1237
+ * preserves the schema's authority and keeps a typo from
1238
+ * re-introducing shadowing.
1239
+ *
1240
+ * Adapters MAY cache per path: this is called on every proxy `get`
1241
+ * trap hit. The reference implementation memoises a
1242
+ * `Map<PathKey, boolean>` keyed by `canonicalizePath(path).key`, with
1243
+ * a lifetime tied to the adapter, one per `useForm()` call.
1244
+ */
1245
+ isLeafAtPath(path: Path): boolean;
1246
+ /**
1247
+ * Whether the leaf at `path` is required, meaning the schema does NOT
1248
+ * admit empty through `.optional()`, `.nullable()`, `.default(N)` or
1249
+ * `.catch(N)` at the leaf or any wrapper.
1250
+ *
1251
+ * The submit and validate path uses it to raise "No value supplied"
1252
+ * when a field is in the form's `blankPaths` set, because the user
1253
+ * cleared it or never answered, AND the schema treats it as required.
1254
+ * Without it a strict numeric leaf silently accepts its slim default
1255
+ * of `0` for an unanswered field, so an unanswered income reads as
1256
+ * zero and passes validation.
1257
+ *
1258
+ * - Optional / Nullable / Default / Catch at any wrapper layer, root
1259
+ * or nested: `false`. The schema author opted into accepting empty.
1260
+ * - Readonly / Pipe / Lazy: transparent, peel and re-check the inner
1261
+ * schema.
1262
+ * - Union or discriminated union: `false` if ANY branch admits empty,
1263
+ * since a union accepts what its most permissive branch accepts.
1264
+ * This matches `validateAtPath`'s first-success rule.
1265
+ * - Intersection: `true` if EITHER side requires the path, since an
1266
+ * intersection needs both sides to accept, so one side rejecting
1267
+ * empty makes the whole rejection.
1268
+ * - A path the schema does not declare: `false`.
1269
+ * - Empty path, the root: `true`, always required as an object.
1270
+ *
1271
+ * Refinement-level constraints (length, format, custom predicates)
1272
+ * are not consulted. Those run at parse time inside `validateAtPath`
1273
+ * and surface as schema errors regardless. This answers only whether
1274
+ * the leaf is required at all; refinements layer on top.
1275
+ */
1276
+ isRequiredAtPath(path: Path): boolean;
1277
+ /**
1278
+ * If the schema at `path` is, or wraps, a discriminated union, its
1279
+ * discriminator key plus a `getVariantDefault(value)` lookup;
1280
+ * otherwise `undefined`. Wrappers are peeled transparently.
1281
+ *
1282
+ * Two reshapes share this lookup:
1283
+ *
1284
+ * 1. Discriminator-key write. The runtime calls it with the PARENT
1285
+ * path. If `discriminatorKey` matches the path's last segment,
1286
+ * the write changes which variant is active, so parent storage
1287
+ * is replaced with the matching variant's slim default and the
1288
+ * old variant's keys (an `address` left over after switching to
1289
+ * `sms`) cannot leak.
1290
+ * 2. Whole-union write. The runtime calls it with the path itself.
1291
+ * If the info exists and the consumer's value carries the
1292
+ * discriminator key, the merge uses that variant's default
1293
+ * instead of the first-variant fallback `getDefaultAtPath`
1294
+ * returns for a union.
1295
+ *
1296
+ * An adapter that does not model discriminated unions can return
1297
+ * `undefined` unconditionally; the reshape is then a no-op.
1298
+ */
1299
+ getUnionDiscriminatorAtPath(path: Path): UnionDiscriminatorContext | undefined;
1300
+ /**
1301
+ * Resolved field metadata for the schema node at `path`: label,
1302
+ * description and placeholder, plus the full registered payload as
1303
+ * `meta` for consumer-augmented keys. Reads through the shared
1304
+ * cross-adapter field-meta store, with one-way fallbacks:
1305
+ *
1306
+ * - `label`: registry payload, else `humanize(lastSegment)`
1307
+ * - `description`: registry payload, else `schema.description`
1308
+ * (the `.describe()` value), else `undefined`
1309
+ * - `placeholder`: registry payload, else `undefined`
1310
+ * - `meta`: registry payload, frozen; an empty object when
1311
+ * nothing was registered
1312
+ *
1313
+ * `path` is the canonical `Segment[]`, and the empty path resolves to
1314
+ * the root schema's metadata. Multiple candidates from union branches
1315
+ * resolve against the first match, following the same first-success
1316
+ * precedent as `getDefaultAtPath` and `validateAtPath`: register on
1317
+ * the union root for shared metadata, on individual branches for
1318
+ * variant-specific metadata.
1319
+ *
1320
+ * Optional. A missing implementation is treated as a stub returning
1321
+ * `EMPTY_RESOLVED_FIELD_META`, so consumers see the humanized `label`
1322
+ * fallback and `undefined` elsewhere.
1323
+ */
1324
+ getFieldMetaAtPath?(path: Path): ResolvedFieldMeta;
1325
+ /**
1326
+ * Whether `validateAtPath` MAY have to run asynchronously to surface
1327
+ * every error this schema can produce. The runtime asks at
1328
+ * construction: `false` means the construction-time sync seed is
1329
+ * authoritative and no extra microtask is spent, `true` queues a
1330
+ * one-shot full-form async pass so async-only verdicts (refinements,
1331
+ * transforms or pipes that resolve only through a Promise) surface
1332
+ * without waiting for a user mutation.
1333
+ *
1334
+ * Optional and best-effort. A missing implementation is treated as
1335
+ * `() => false`, and async-only errors then fire on first user
1336
+ * mutation instead.
1337
+ *
1338
+ * For a per-path query, compose with `getSchemasAtPath(path)`: each
1339
+ * candidate sub-schema exposes its own `needsAsyncValidation`, so
1340
+ * "does the cargo subtree contain async work?" is the union of the
1341
+ * per-candidate answers, with no separate top-level overload.
1342
+ */
1343
+ needsAsyncValidation?(): boolean;
1344
+ /**
1345
+ * Whether the schema carries a refine, check or transform at any
1346
+ * NON-LEAF position: a container node (object, array, tuple, union,
1347
+ * intersection, record, map, set) or the root itself. `false` means
1348
+ * every check is leaf-local, so a per-keystroke
1349
+ * `validateAtPath(form, leafPath)` catches the same verdicts as a
1350
+ * whole-form pass, because no ancestor refine reads the form's wider
1351
+ * state.
1352
+ *
1353
+ * The runtime uses it at the per-keystroke schedule to scope
1354
+ * field-level validation to the changed subtree where it can, and
1355
+ * falls back to a whole-form pass when an ancestor refine, such as a
1356
+ * cross-field equality or a sum constraint, could be moved by a leaf
1357
+ * write.
1358
+ *
1359
+ * Optional, and a missing implementation is treated as `() => true`,
1360
+ * the conservative whole-form answer. Detection is best-effort and
1361
+ * the two error directions are not symmetric: returning `true` where
1362
+ * no container refine exists only loses a perf win, while returning
1363
+ * `false` where one exists lets an ancestor verdict go stale. Bias
1364
+ * toward `true` when in doubt.
1365
+ */
1366
+ hasContainerOrRootRefine?(): boolean;
1367
+ /**
1368
+ * Whether the schema tree holds at least one discriminated union at
1369
+ * any depth, inside arrays, tuples, records and lazy schemas
1370
+ * included. The store computes its per-form union capability flag
1371
+ * from this once at construction: `false` lets every write skip the
1372
+ * cross-variant ancestor guard and the variant-reshape dispatch, and
1373
+ * stops stub correction from running at all.
1374
+ *
1375
+ * Optional, and an adapter that omits it is treated as containing
1376
+ * unions, which keeps the conservative per-write probes on. Never
1377
+ * return `false` for a schema that DOES hold a discriminated union:
1378
+ * that disables variant reshape and stub correction for the form.
1379
+ */
1380
+ hasDiscriminatedUnions?(): boolean;
1381
+ };
1382
+ /**
1383
+ * A discriminated union as the adapter reports it: the discriminator
1384
+ * key plus a lookup from a discriminator literal to the matching
1385
+ * variant's slim default. Returned by
1386
+ * `AbstractSchema.getUnionDiscriminatorAtPath`.
1387
+ */
1388
+ type UnionDiscriminatorContext = {
1389
+ /**
1390
+ * The property name whose literal value selects the variant, such as
1391
+ * `'channel'` for a union split on `{ channel: 'sms' | 'email' }`.
1392
+ */
1393
+ readonly discriminatorKey: string;
1394
+ /**
1395
+ * Slim default for the variant whose discriminator literal equals
1396
+ * `value`, or `undefined` when no variant matches, in which case the
1397
+ * runtime skips the reshape and falls back to a plain write.
1398
+ */
1399
+ getVariantDefault(value: unknown): unknown;
1400
+ /**
1401
+ * Whether `value` is a literal one of the discriminator's variants
1402
+ * recognises. Reshape uses it to choose between seeking a variant
1403
+ * default and emitting a stub state. NOT used at the write gate,
1404
+ * where consumer-side value validity is a validation-time concern.
1405
+ */
1406
+ isVariantSelected(value: unknown): boolean;
1407
+ };
1408
+ /**
1409
+ * The primitive kinds the slim-primitive write contract recognises:
1410
+ * `typeof` plus a few well-known reference shapes (`Date`, `Array`,
1411
+ * `Map`, `Set`, plain `object`, `null`).
1412
+ *
1413
+ * The runtime gate's `slimKindOf(value)` returns one of these for a
1414
+ * value, and the adapter's `getSlimPrimitiveTypesAtPath(path)` returns
1415
+ * the set a leaf accepts. A write is gated by
1416
+ * `accepted.has(slimKindOf(value))`.
1417
+ */
1418
+ type SlimPrimitiveKind = 'string' | 'number' | 'boolean' | 'bigint' | 'date' | 'null' | 'undefined' | 'object' | 'array' | 'symbol' | 'function' | 'map' | 'set' | 'file';
1419
+ /**
1420
+ * The "no result yet" status the reactive `validate()` ref holds while
1421
+ * a run is in flight. Narrow against `pending` to reach the settled
1422
+ * fields:
1423
+ *
1424
+ * ```ts
1425
+ * const status = form.validate()
1426
+ * watchEffect(() => {
1427
+ * if (status.value.pending) return
1428
+ * // status.value.success / status.value.errors are now safe to read
1429
+ * })
1430
+ * ```
1431
+ */
1432
+ type PendingValidationStatus = {
1433
+ readonly pending: true;
1434
+ readonly errors: undefined;
1435
+ readonly success: false;
1436
+ readonly formKey: FormKey;
1437
+ };
1438
+ /** Settled status of a reactive `validate()` call. Mirrors the latest result. */
1439
+ type SettledValidationStatus<Form> = {
1440
+ readonly pending: false;
1441
+ } & ValidationResponseWithoutValue<Form>;
1442
+ /**
1443
+ * The value type of the ref `validate()` returns. Discriminate on
1444
+ * `pending` to switch between the in-flight and settled states.
1445
+ */
1446
+ type ReactiveValidationStatus<Form> = PendingValidationStatus | SettledValidationStatus<Form>;
1447
+ /**
1448
+ * When per-field validation runs.
1449
+ *
1450
+ * - `'change'` (default): every committed write schedules a validation
1451
+ * for the affected path. At `debounceMs: 0`, also the default, the
1452
+ * run is synchronous in the write handler; a positive `debounceMs`
1453
+ * coalesces rapid bursts.
1454
+ * - `'blur'`: validate when the user tabs away from a registered
1455
+ * field. No debounce; `debounceMs` is a type error.
1456
+ * - `'submit'`: no live validation. `handleSubmit` and explicit
1457
+ * `validate()` / `parse()` calls are the only validation surfaces.
1458
+ * `debounceMs` is a type error.
1459
+ *
1460
+ * Only validation timing varies by mode. How often a value commits to
1461
+ * storage is the directive's concern: `<input v-register>` commits per
1462
+ * keystroke, `<input v-register.lazy>` defers to blur.
1463
+ */
1464
+ type ValidateOn = 'change' | 'blur' | 'submit';
1465
+ /**
1466
+ * Validation timing: `validateOn` is the trigger and `debounceMs` the
1467
+ * wait after the last committed write. The union is what makes pairing
1468
+ * `debounceMs` with `'blur'` or `'submit'` a type error rather than a
1469
+ * silent runtime drop.
1470
+ */
1471
+ type ValidateOnConfig = {
1472
+ /** Validation trigger. Default `'change'`. */
1473
+ validateOn?: 'change';
1474
+ /**
1475
+ * Milliseconds to wait after the last committed write before
1476
+ * running validation, to coalesce rapid bursts into one pass.
1477
+ * Useful for slow async adapters, or to smooth inline feedback
1478
+ * under heavy typing.
1479
+ *
1480
+ * Default `0`: validation runs synchronously after the write,
1481
+ * with no `setTimeout`. Schema work still rides
1482
+ * `Promise.resolve().then(validateAtPath)`, so even at `0` errors
1483
+ * land a microtask later rather than in the same statement.
1484
+ *
1485
+ * This debounces validation only. A value commits to
1486
+ * `form.values` immediately either way.
1487
+ */
1488
+ debounceMs?: number;
1489
+ } | {
1490
+ /** Validation trigger. */
1491
+ validateOn: 'blur' | 'submit';
1492
+ /** `debounceMs` is not allowed with `'blur'` or `'submit'`. */
1493
+ debounceMs?: never;
1494
+ };
1495
+ /**
1496
+ * Per-write metadata, tagging a write so listeners and the write funnel
1497
+ * can treat it specially. Every field is internal; do not set any of
1498
+ * them from consumer code.
1499
+ */
1500
+ type WriteMeta = {
1501
+ /**
1502
+ * Add the written path to the store's `blankPaths` set: storage holds
1503
+ * a real, schema-conformant value, the slim default, but the UI shows
1504
+ * the field as empty. The next write to that path WITHOUT this flag
1505
+ * removes it from the set, the user having typed something real.
1506
+ *
1507
+ * Set by `markBlank()` on the register binding, and by the `unset`
1508
+ * translation in `setValue`, `reset` and `useAbstractForm`
1509
+ * construction.
1510
+ */
1511
+ readonly blank?: boolean;
1512
+ /**
1513
+ * Skip the discriminator-aware variant reshape inside
1514
+ * `setValueAtPath` for this write. Set by the reshape itself when it
1515
+ * re-enters with the new variant default, so the literal
1516
+ * discriminator inside that default cannot loop forever.
1517
+ */
1518
+ readonly skipDiscriminatorReshape?: boolean;
1519
+ /**
1520
+ * Record an array structural mutation precisely enough to replay the
1521
+ * index permutation it produced. Set by the typed array helpers in
1522
+ * `array-engine.ts`, and used by `setValueAtPath` to surgically clear
1523
+ * variant memory for just the indices the operation invalidated.
1524
+ *
1525
+ * Without the hint, a raw whole-array `setValue(arrayPath, [...])`
1526
+ * clears all memory under the array, because the runtime cannot tell
1527
+ * which indices stayed put.
1528
+ */
1529
+ readonly arrayOp?: {
1530
+ readonly kind: 'insert';
1531
+ readonly index: number;
1532
+ } | {
1533
+ readonly kind: 'remove';
1534
+ readonly index: number;
1535
+ } | {
1536
+ readonly kind: 'move';
1537
+ readonly from: number;
1538
+ readonly to: number;
1539
+ } | {
1540
+ readonly kind: 'swap';
1541
+ readonly a: number;
1542
+ readonly b: number;
1543
+ } | {
1544
+ readonly kind: 'replace-at';
1545
+ readonly index: number;
1546
+ };
1547
+ /**
1548
+ * Per-instance config threaded through writes so each
1549
+ * `useForm({ key })` call site honors its own `validateOn`,
1550
+ * `debounceMs` and `rememberVariants` even while sharing a store with
1551
+ * sibling calls, as a modal and a main form rendering the same
1552
+ * logical form do.
1553
+ *
1554
+ * Set by `buildFormApi` from the per-instance options bag; the store
1555
+ * reads each field with a fallback to its construction-time defaults.
1556
+ */
1557
+ readonly instance?: {
1558
+ readonly validateOn?: ValidateOn;
1559
+ readonly debounceMs?: number;
1560
+ readonly rememberVariants?: boolean;
1561
+ };
1562
+ /**
1563
+ * Mark this `applyFormReplacement` call as a hydration step, meaning
1564
+ * the async-`defaultValues`, `activate()` or `rehydrate()` path.
1565
+ * Modules that snapshot form state, the history module in particular,
1566
+ * treat hydration as the baseline: stacks reset to a single seed of
1567
+ * the post-hydration value, so a later `undo()` cannot recover the
1568
+ * transient pre-hydration default.
1569
+ *
1570
+ * Set by the activate path in `create-form-store.ts`.
1571
+ */
1572
+ readonly hydration?: boolean;
1573
+ };
1574
+ /**
1575
+ * The store slice the history runtime binds to. Structural on purpose:
1576
+ * `historyPlugin()` ships from the separate `attaform/history` entry,
1577
+ * so the runtime receives the store through this seam instead of
1578
+ * importing the store module, which would pull history internals onto
1579
+ * every form's eager path. A FormStore satisfies it as-is.
1580
+ *
1581
+ * @internal
1582
+ */
1583
+ type HistoryKernel = {
1584
+ readonly form: Ref<unknown>;
1585
+ readonly blankPaths: Set<PathKey>;
1586
+ readonly errorCells: Map<PathKey, ErrorCell>;
1587
+ onFormChange(listener: (next: unknown, meta?: WriteMeta) => void): () => void;
1588
+ applyFormReplacement(next: unknown): void;
1589
+ restoreErrorCells(entries: ReadonlyArray<readonly [PathKey, ErrorCell]>): void;
1590
+ };
1591
+ /**
1592
+ * The live undo/redo runtime a {@link HistoryPlugin} attaches to one
1593
+ * form. Cached on the store so every `useForm` and `injectForm`
1594
+ * consumer of the same key shares one chain; `buildFormApi` adapts it
1595
+ * into the public `form.history` namespace.
1596
+ *
1597
+ * @internal
1598
+ */
1599
+ type HistoryModule = {
1600
+ undo(): boolean;
1601
+ redo(): boolean;
1602
+ clear(): void;
1603
+ canUndo: Readonly<ComputedRef<boolean>>;
1604
+ canRedo: Readonly<ComputedRef<boolean>>;
1605
+ historySize: Readonly<ComputedRef<number>>;
1606
+ dispose(): void;
1607
+ };
1608
+ /**
1609
+ * Opt-in undo/redo, created by `historyPlugin()` from
1610
+ * `attaform/history` and passed to `useForm({ history })`:
1611
+ *
1612
+ * ```ts
1613
+ * import { historyPlugin } from 'attaform/history'
1614
+ *
1615
+ * const form = useForm({ schema, history: historyPlugin({ max: 200 }) })
1616
+ * ```
1617
+ *
1618
+ * Every mutation records a position, and `form.history.undo()` /
1619
+ * `form.history.redo()` walk the chain. `reset()` is itself a mutation,
1620
+ * so the pre-reset state stays one undo away. Hydration is the floor:
1621
+ * once it applies, the chain reseeds with the hydrated value and
1622
+ * `undo()` cannot reach the transient pre-hydration default.
1623
+ *
1624
+ * A plugin instance is a reusable configuration, not per-form state, so
1625
+ * passing one instance to several forms gives each its own independent
1626
+ * chain.
1627
+ */
1628
+ type HistoryPlugin = {
1629
+ /**
1630
+ * Bind a fresh history runtime to one form's store. Called by
1631
+ * `useForm` when the store is first created.
1632
+ *
1633
+ * @internal
1634
+ */
1635
+ readonly attach: (kernel: HistoryKernel) => HistoryModule;
1636
+ };
1637
+ /**
1638
+ * The undo/redo namespace at `form.history`, holding every
1639
+ * history-related method and reactive flag, so there is one address to
1640
+ * read from.
1641
+ *
1642
+ * Always present on a `useForm()` return whether or not `history` was
1643
+ * configured. Unconfigured, the methods are no-ops returning `false` or
1644
+ * `void`, `canUndo` and `canRedo` read `false`, and `size` reads `0`,
1645
+ * so a template needs no conditional.
1646
+ *
1647
+ * `canUndo`, `canRedo` and `size` read as plain values rather than
1648
+ * refs; no `.value`.
1649
+ */
1650
+ type FormHistoryNamespace = {
1651
+ /**
1652
+ * Step back one position. `true` when a step was taken, `false` at
1653
+ * the oldest reachable position or when history is not configured.
1654
+ */
1655
+ readonly undo: () => boolean;
1656
+ /**
1657
+ * Replay the next step forward. `true` on success, `false` when
1658
+ * nothing is queued or history is not configured. The forward branch
1659
+ * is dropped as soon as a new mutation lands.
1660
+ */
1661
+ readonly redo: () => boolean;
1662
+ /**
1663
+ * Wipe the undo and redo branches and reseed the chain with the
1664
+ * current form state as the new baseline. Values, errors and
1665
+ * blankPaths all stay put; only past and future reset. Afterwards
1666
+ * `canUndo` and `canRedo` are `false` and `size` is `1`. A no-op when
1667
+ * history is not configured.
1668
+ */
1669
+ readonly clear: () => void;
1670
+ /** `true` when at least one undo step is available. */
1671
+ readonly canUndo: boolean;
1672
+ /** `true` when `undo()` has been called and a `redo()` would replay. */
1673
+ readonly canRedo: boolean;
1674
+ /**
1675
+ * Total reachable positions: the current one plus everything
1676
+ * reachable by `undo()` and `redo()`. Useful for a debug overlay; UI
1677
+ * driving undo and redo buttons should gate on `canUndo` and
1678
+ * `canRedo`. Reads `0` when history is not configured.
1679
+ */
1680
+ readonly size: number;
1681
+ };
1682
+ /**
1683
+ * Configuration passed to `useForm`. Only `schema` is required.
1684
+ *
1685
+ * ```ts
1686
+ * const form = useForm({
1687
+ * schema: signupSchema,
1688
+ * defaultValues: { email: '' },
1689
+ * validateOn: 'change',
1690
+ * debounceMs: 200,
1691
+ * })
1692
+ * ```
1693
+ */
1694
+ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema extends AbstractSchema<Form, GetValueFormType>, DefaultValues extends DefaultValuesInput<Form>, K extends FormKey = FormKey> = {
1695
+ /**
1696
+ * The schema describing the form's shape and validation rules. A
1697
+ * typed entry point such as `attaform/zod` takes the underlying
1698
+ * library's schema directly and wraps an adapter; the abstract entry
1699
+ * point takes any object implementing `AbstractSchema`.
1700
+ *
1701
+ * For a schema that depends on the form's identity or per-form
1702
+ * options, pass a factory `(key, options) => schema` instead, called
1703
+ * once per form. Most adapters ignore the options argument; the typed
1704
+ * Zod entry points use it to thread the recursion cap into the
1705
+ * adapter closure.
1706
+ */
1707
+ schema: Schema | ((key: FormKey, options: SchemaFactoryOptions) => Schema);
1708
+ /**
1709
+ * Optional identifier for this form. Omit it for a one-off form and
1710
+ * Attaform allocates a unique key automatically, SSR-safe and stable
1711
+ * across hydration.
1712
+ *
1713
+ * Pass a string when the form needs identity:
1714
+ * - to look it up from a distant component via `injectForm(key)`;
1715
+ * - to share state across components, since several `useForm({ key })`
1716
+ * calls with the same key resolve to the same form;
1717
+ * - to give DevTools and validation errors a recognisable label.
1718
+ *
1719
+ * Keys starting with `__atta:` are reserved and throw
1720
+ * `ReservedFormKeyError`.
1721
+ *
1722
+ * A string literal is preserved on `form.key`, so `useWizard` and
1723
+ * other consumers can discriminate against the union of known keys at
1724
+ * compile time.
1725
+ */
1726
+ key?: K;
1727
+ /**
1728
+ * Initial values applied over the schema's defaults. A field not
1729
+ * named here falls back to the schema default, or to the primitive
1730
+ * default for the slot's type.
1731
+ *
1732
+ * Values must satisfy the slim primitive type at each path (string,
1733
+ * number, boolean, Date) but do NOT have to satisfy refinement-level
1734
+ * constraints such as format checks, enum membership or length and
1735
+ * range bounds. A refinement-invalid default passes through and
1736
+ * surfaces as a field error, which is what lets you rehydrate stale
1737
+ * saved data without discarding the user's input.
1738
+ *
1739
+ * Accepts a plain value, a sync function, or an async function:
1740
+ *
1741
+ * ```ts
1742
+ * // Plain value: applies at construction.
1743
+ * defaultValues: { email: '' }
1744
+ *
1745
+ * // Sync function: invoked on a microtask after construction.
1746
+ * defaultValues: () => buildDraft()
1747
+ *
1748
+ * // Async function: the form starts with the schema's slim defaults
1749
+ * // and `form.hydrating` is true while the promise is in flight; on
1750
+ * // resolve the values apply and `hydrating` flips false. Under SSR
1751
+ * // the factory fires via `onServerPrefetch`, so the resolved payload
1752
+ * // bakes into hydration transfer state and the client never
1753
+ * // re-fetches.
1754
+ * defaultValues: async () => api.fetchDraft(userId)
1755
+ * ```
1756
+ *
1757
+ * An error thrown by a function-form factory surfaces on
1758
+ * `form.hydrateError` and leaves the form usable with slim defaults.
1759
+ * Call `form.rehydrate()` to re-fire the factory.
1760
+ */
1761
+ defaultValues?: DefaultValues | (() => DefaultValues) | (() => Promise<DefaultValues>);
1762
+ /**
1763
+ * Move keyboard and screen-reader focus to the first errored field
1764
+ * when a submit attempt fails validation, after errors are populated
1765
+ * and before your `onError` callback runs. Default `true`: focusing
1766
+ * the broken field is an accessibility baseline, and browsers scroll
1767
+ * the focused element into view as part of the move.
1768
+ *
1769
+ * Pass `false` to drive the nudge yourself from `onError` with
1770
+ * `form.focusFirstError()` or `form.scrollToFirstError()`, both of
1771
+ * which stay available either way.
1772
+ *
1773
+ * The focus requests `focusVisible: true` so the browser paints a
1774
+ * focus ring despite the move being programmatic. On a UA that does
1775
+ * not support that hint, a non-text control (radio, checkbox, custom
1776
+ * widget) can end up focused with no visible ring after a
1777
+ * pointer-driven submit; pair this with your own indicator, such as a
1778
+ * `:focus-within` ring on the option wrapper, if you target those
1779
+ * browsers.
1780
+ *
1781
+ * If no errored field has a mounted, visible element, the focus
1782
+ * silently no-ops.
1783
+ */
1784
+ focusOnInvalidSubmit?: boolean;
1785
+ /**
1786
+ * Freeze the form's data. While this resolves truthy every value
1787
+ * write no-ops at the store's write chokepoint, programmatic
1788
+ * `setValue`, the `v-register` directive and host-model events alike;
1789
+ * native registered inputs render the HTML `disabled` attribute;
1790
+ * component hosts receive a `disabled` flag on their register value;
1791
+ * and every field's `displayState` drops to `'idle'`, so error,
1792
+ * pending and success signals stand down. The first blocked write
1793
+ * logs a one-time dev warning. Writes never throw.
1794
+ *
1795
+ * `reset()` and `defaultValues` hydration still apply while frozen,
1796
+ * so a disabled form can be populated or cleared programmatically.
1797
+ * This is a data freeze only: the form stays navigable, which is what
1798
+ * a read-only review page wants.
1799
+ *
1800
+ * Accepts a boolean, ref, computed or getter, read live so the freeze
1801
+ * tracks a reactive source. `undefined` resolves to `false`.
1802
+ *
1803
+ * A shared store resolves `disabled` from its first
1804
+ * `useForm({ key })` call; a later caller passing a different value
1805
+ * is ignored.
1806
+ */
1807
+ disabled?: MaybeRefOrGetter<boolean | undefined>;
1808
+ /**
1809
+ * When per-field validation runs. Default `'change'`. See
1810
+ * `ValidateOn` for mode semantics.
1811
+ *
1812
+ * This sets validation timing only. How often a value commits to
1813
+ * storage is the directive's concern: per keystroke for
1814
+ * `<input v-register>`, per blur for `<input v-register.lazy>`.
1815
+ */
1816
+ validateOn?: ValidateOn;
1817
+ /**
1818
+ * Milliseconds to wait after the last committed write before running
1819
+ * validation, to coalesce rapid bursts. Default `0`: validation runs
1820
+ * synchronously after the write, with no `setTimeout`.
1821
+ *
1822
+ * Only accepted under `validateOn: 'change'`. Passing it with
1823
+ * `'blur'` or `'submit'` is a type error.
1824
+ */
1825
+ debounceMs?: number;
1826
+ /**
1827
+ * Opt-in undo/redo, off by default. Pass `historyPlugin()` from
1828
+ * `attaform/history`, which caps at 128 positions, or
1829
+ * `historyPlugin({ max: N })` to tune it. See {@link HistoryPlugin}
1830
+ * for how the chain behaves, and `form.history` for the surface it
1831
+ * adds.
1832
+ */
1833
+ history?: HistoryPlugin;
1834
+ /**
1835
+ * Whether to remember each discriminated-union variant's typed state
1836
+ * across switches. Default `true`.
1837
+ *
1838
+ * At `true`, switching `notify.channel` from `email`, holding
1839
+ * `address: 'foo@bar.com'`, to `sms` and back lands on
1840
+ * `address: 'foo@bar.com'` again: the runtime snapshots the outgoing
1841
+ * variant's subtree on switch-out and restores the incoming variant's
1842
+ * prior subtree on switch-in. Every union at every nesting depth is
1843
+ * memorized independently.
1844
+ *
1845
+ * At `false`, the outgoing variant's typed state is dropped on every
1846
+ * switch and the incoming variant initializes from its slim default.
1847
+ *
1848
+ * Memory is in-memory only and does not survive a fresh mount, so a
1849
+ * page reload starts every discriminator empty. `reset()` clears it;
1850
+ * `resetField(path)` clears any entry whose union path equals or sits
1851
+ * under `path`.
1852
+ */
1853
+ rememberVariants?: boolean;
1854
+ /**
1855
+ * Schema-driven coercion of user-typed DOM values at the v-register
1856
+ * layer. Two rules ship, string to number and string to boolean, each
1857
+ * firing only where the schema declares that single type at the path.
1858
+ * Defaults to on; `false` disables coercion form-wide and the slim
1859
+ * gate rejects mismatches instead.
1860
+ *
1861
+ * Coercion applies ONLY to user-typed DOM values. A programmatic
1862
+ * write is never coerced.
1863
+ */
1864
+ coerce?: boolean;
1865
+ /**
1866
+ * @internal
1867
+ * SSR prefetch mark, set by the `attaform/vite` compile-time
1868
+ * transform on a `useForm` call whose surrounding SFC template, or a
1869
+ * computed feeding it, reads the form's reactive state. The flag
1870
+ * enqueues the form on the registry's SSR prefetch queue, so an async
1871
+ * `defaultValues` factory runs inside `onServerPrefetch` and the
1872
+ * resolved payload bakes into the hydration transfer state.
1873
+ *
1874
+ * Consumers do not write this; `form.activate()` is the documented
1875
+ * escape hatch for where the transform's static analysis cannot see a
1876
+ * reference, such as cross-module sharing, dynamic property access or
1877
+ * a headless context.
1878
+ */
1879
+ __ssrAccessed?: boolean;
1880
+ };
1881
+ /**
1882
+ * Callback invoked by `handleSubmit` after the form parses successfully.
1883
+ * It receives the strictly-typed parsed value, with refinements already
1884
+ * run, so enum, literal and format constraints are honoured.
1885
+ */
1886
+ type OnSubmit<Form extends GenericForm> = (form: Form) => void | Promise<void>;
1887
+ /**
1888
+ * Callback invoked by `handleSubmit` on a failed submit. Fires when
1889
+ * client validation fails AND when the submit callback leaves errors in
1890
+ * the user-error layer (the `setErrors(...); return` server-rejection
1891
+ * pattern). Receives the full list of errors. Bind this when you want to
1892
+ * react to submit failures explicitly (alongside or instead of the
1893
+ * automatic `focusOnInvalidSubmit` nudge).
1894
+ */
1895
+ type OnError = (error: ValidationError[]) => void | Promise<void>;
1896
+ /**
1897
+ * The display-state verdict at a path: the one signal a UI needs to
1898
+ * decide what, if anything, to surface about validation right now.
1899
+ * Rolled up at containers and at the form root
1900
+ * (`form.meta.displayState`).
1901
+ *
1902
+ * - `'idle'`: nothing to surface, either because the timing gate has
1903
+ * not opened yet or because it has and no verdict is worth showing.
1904
+ * - `'pending'`: a run is in flight at this path and the prior verdict
1905
+ * is stale. Drive a spinner or a "Checking..." affordance.
1906
+ * - `'error'`: a blocking error the timing gate has cleared for
1907
+ * display.
1908
+ * - `'success'`: validation passed and the gate has cleared a positive
1909
+ * confirmation, the green-check pattern.
1910
+ *
1911
+ * The four `show*` booleans on `FieldState` are sugar over this enum
1912
+ * (`showErrors === (displayState === 'error')`, and so on), so they can
1913
+ * never contradict it.
1914
+ */
1915
+ type DisplayState = 'idle' | 'pending' | 'error' | 'success';
1916
+ /**
1917
+ * The `FieldState` keys layered on FROM the display-state predicate,
1918
+ * plus `firstError` and `firstOwnError`, computed alongside them.
1919
+ *
1920
+ * They are `Omit`'d from the predicate's own arguments so it cannot
1921
+ * read its own output and form a cycle. Enforced at the type level and
1922
+ * at runtime both: the base objects passed in literally lack these
1923
+ * keys, so neither an `as` cast nor a vanilla-JS caller can reach them.
1924
+ * `FieldStateBase` and `FormMetaBase` in `field-state-api.ts` omit the
1925
+ * same set in lockstep.
1926
+ */
1927
+ type FieldStateDerivedKey = 'displayState' | 'showErrors' | 'showPending' | 'showSuccess' | 'showIdle' | 'firstError' | 'firstOwnError';
1928
+ /**
1929
+ * One step of the display state machine: the verdict the field should
1930
+ * render now, as `display`, projected to `displayState` and the `show*`
1931
+ * booleans, plus two optional timing cells the engine reads.
1932
+ */
1933
+ type DisplayMachine = {
1934
+ readonly display: DisplayState;
1935
+ /**
1936
+ * An absolute `Date.now()` stamp meaning "re-evaluate this field no
1937
+ * later than here". The engine keeps one timer per form aimed at the
1938
+ * nearest `reviewAt` across all active fields; when it fires, the
1939
+ * dependent field computeds re-run and call the reducer again.
1940
+ *
1941
+ * A machine with no `reviewAt` and a non-pending `display` is
1942
+ * terminal, and the engine evicts it.
1943
+ */
1944
+ readonly reviewAt?: number;
1945
+ /**
1946
+ * The stamp at which `'pending'` was first shown: the memory the
1947
+ * min-visible hold needs, so a spinner that has only just appeared is
1948
+ * not yanked away the instant validation resolves. Opaque to the
1949
+ * engine, and a custom reducer may carry extra memory fields of its
1950
+ * own alongside it.
1951
+ */
1952
+ readonly pendingShownAt?: number;
1953
+ };
1954
+ /**
1955
+ * Inputs to the display reducer. `field` and `formMeta` are the
1956
+ * reactive snapshots it resolves against, minus the derived
1957
+ * `displayState`, `show*` and `firstError` keys, so the reducer can
1958
+ * never read its own output and form a cycle. See
1959
+ * `FieldStateDerivedKey`.
1960
+ */
1961
+ type DisplayCtx = {
1962
+ readonly field: Omit<FieldState, FieldStateDerivedKey>;
1963
+ readonly formMeta: Omit<FormMeta, FieldStateDerivedKey>;
1964
+ /**
1965
+ * `Date.now()` at which the field's current validation streak opened,
1966
+ * or `null` when nothing is in flight. This, not `field.validating`,
1967
+ * is the timing anchor: the elapsed wait is `now - validatingSince`.
1968
+ * Pinned to the start of the streak, so overlapping sub-runs do not
1969
+ * reset it.
1970
+ */
1971
+ readonly validatingSince: number | null;
1972
+ /**
1973
+ * The same anchor for an in-flight async `register` transform, or
1974
+ * `null` when none is running, including for a sync-only chain, which
1975
+ * never defers. It folds into the one in-flight clock the reducer
1976
+ * already runs for validation, so a deferred transform rides the
1977
+ * anti-flash spinner timing identically.
1978
+ */
1979
+ readonly transformingSince: number | null;
1980
+ /**
1981
+ * The engine's clock, injected so the reducer stays pure and
1982
+ * deterministic. Frozen to `0` under SSR, where there is no clock.
1983
+ */
1984
+ readonly now: number;
1985
+ };
1986
+ /**
1987
+ * Pure transition reducer resolving a path's `displayState`. Given the
1988
+ * field's previous `DisplayMachine` and the current `DisplayCtx` it
1989
+ * returns the next machine: the engine owns the clock and the timers,
1990
+ * the reducer owns the timing policy.
1991
+ *
1992
+ * It runs on every field-state read, and again whenever a `reviewAt`
1993
+ * deadline fires, so the whole app's validation-display behavior flows
1994
+ * from this one function in `core/display-state.ts`.
1995
+ *
1996
+ * @internal
1997
+ */
1998
+ type GetDisplayState = (prev: DisplayMachine, ctx: DisplayCtx) => DisplayMachine;
1999
+ /**
2000
+ * The submit handler `handleSubmit(onSubmit, onError)` returns. Bind it
2001
+ * to a `<form>`:
2002
+ *
2003
+ * ```vue
2004
+ * <form @submit="onSubmit">...</form>
2005
+ * ```
2006
+ *
2007
+ * It takes the originating `Event` and calls `event.preventDefault()`
2008
+ * itself, so bind with `@submit`, not `@submit.prevent`, which would
2009
+ * only prevent the default a second time. Called imperatively with no
2010
+ * event, the `preventDefault` step is skipped.
2011
+ */
2012
+ type SubmitHandler = (event?: Event) => Promise<void>;
2013
+ /**
2014
+ * The type of `form.handleSubmit`. Pass an `onSubmit` for the happy
2015
+ * path, and optionally an `onError` that receives the validation errors
2016
+ * when parsing fails.
2017
+ *
2018
+ * ```ts
2019
+ * const onSubmit = form.handleSubmit(
2020
+ * (data) => api.signup(data),
2021
+ * (errors) => console.log(errors),
2022
+ * )
2023
+ * ```
2024
+ */
2025
+ type HandleSubmit<Form extends GenericForm> = (onSubmit: OnSubmit<Form>, onError?: OnError) => SubmitHandler;
2026
+ /**
2027
+ * Per-leaf internal tracker record, distinct from `FieldState.meta`,
2028
+ * which surfaces the registry-attached label, description and
2029
+ * placeholder payload as `Readonly<FieldMetaPayload>`.
2030
+ *
2031
+ * Surfaced for custom-adapter authors threading metadata through their
2032
+ * own pipelines. Most consumers do not reach for it, since the matching
2033
+ * fields appear in friendlier shape on `FieldState`.
2034
+ */
2035
+ type MetaTrackerValue = {
2036
+ /** ISO timestamp of the most recent write at this path. `null` if never written. */
2037
+ updatedAt: string | null;
2038
+ /** Value as it arrived, before any transforms. Distinguishes a parse-coerced read from raw user input. */
2039
+ rawValue: unknown;
2040
+ /** `true` while at least one binding to this path is currently mounted. */
2041
+ connected: boolean;
2042
+ /** Form this metadata belongs to. */
2043
+ formKey: FormKey;
2044
+ /** Dotted-string path to this leaf, or `null` when not applicable. */
2045
+ path: string | null;
2046
+ /**
2047
+ * `true` when storage and the visible display diverge at this path.
2048
+ * Reserved for the case the schema cannot see on its own: storage
2049
+ * forces a value, `0` for a numeric leaf or `0n` for a bigint leaf,
2050
+ * while the DOM input shows `''`, so the runtime needs a side channel
2051
+ * to tell "user typed 0" from "user supplied nothing".
2052
+ *
2053
+ * Set automatically for numeric leaves, by the directive's input
2054
+ * listener on clear and by the construction-time pass when the
2055
+ * consumer supplied no value. Set explicitly for any primitive leaf
2056
+ * through `setValue(path, unset)`, `defaultValues: { x: unset }` or
2057
+ * `reset({ x: unset })`, which is the documented opt-in for strings,
2058
+ * booleans and other types that do not otherwise diverge. Cleared on
2059
+ * the first non-`unset` write.
2060
+ *
2061
+ * Errors are reactive end to end, so any required path with
2062
+ * `blank: true` puts a "No value supplied" entry in `form.errors`
2063
+ * immediately, with no `validate()` or `handleSubmit` call. Most
2064
+ * consumers gate UI on `errors[path]` and `touched` instead; read
2065
+ * `blank` when you want pre-error introspection, such as a "the user
2066
+ * has not decided yet" indicator or a "review unanswered fields"
2067
+ * hint.
2068
+ *
2069
+ * See `docs/validation/blank.md` for the full conceptual model.
2070
+ */
2071
+ blank: boolean;
2072
+ };
2073
+ type RegisterFlatPath<Form> = Form extends unknown ? FlatPathBuilder<Form, 'register'> : never;
2074
+ /**
2075
+ * A transformation applied to a field's value as user input flows from
2076
+ * the DOM through the directive's assigner. Composes left to right via
2077
+ * the `transforms: [...]` array on `register()`.
2078
+ *
2079
+ * The shape is deliberately generic-erased rather than per-path-typed,
2080
+ * so a personal library of transforms (`trim`, `lowercase`, `slugify`,
2081
+ * `clamp`) plugs into any `register()` slot whatever the path's value
2082
+ * type. Write defensive bodies that no-op on a type mismatch:
2083
+ *
2084
+ * ```ts
2085
+ * export const trim: RegisterTransform = (v) =>
2086
+ * typeof v === 'string' ? v.trim() : v
2087
+ * ```
2088
+ *
2089
+ * Call-site type safety is delegated to the slim-primitive gate: a
2090
+ * transform producing a value the path's storage does not accept is
2091
+ * rejected at write time with a standard diagnostic.
2092
+ *
2093
+ * Transforms may be sync or async. The chain stays fully synchronous,
2094
+ * with the value reaching form state in the same tick, until a
2095
+ * transform returns a thenable; from there the write defers, the field
2096
+ * reads `busy` and `transforming` while the chain settles, and the
2097
+ * resolved value commits once it lands. Rapid edits discard all but the
2098
+ * latest, and a rejection surfaces on `field.transformError` rather
2099
+ * than throwing or logging:
2100
+ *
2101
+ * ```ts
2102
+ * export const normalize: RegisterTransform = async (v, ctx) => {
2103
+ * const res = await fetch(`/normalize?q=${v}`, { signal: ctx?.signal })
2104
+ * return res.text()
2105
+ * }
2106
+ * ```
2107
+ *
2108
+ * `ctx.signal` aborts when the run is superseded by a newer edit, or is
2109
+ * torn down by `reset()` or unmount. Thread it into cancellable I/O so
2110
+ * a stale request is dropped. A sync chain never touches it and
2111
+ * allocates no controller.
2112
+ *
2113
+ * A synchronous throw is caught and aborts the pipeline: later
2114
+ * transforms do not run, nothing is written, and the assigner returns
2115
+ * `false`, so a buggy or defensively-throwing transform never crashes
2116
+ * the host app. An async rejection is the `transformError` channel
2117
+ * above, not a throw into the host app.
2118
+ */
2119
+ type RegisterTransform = (value: unknown, ctx?: TransformContext) => unknown;
2120
+ /** Options for `register(path, options)`, applied at the binding's own call site. */
2121
+ type RegisterOptions = {
2122
+ /**
2123
+ * Transformation pipeline applied to user-typed values before they
2124
+ * reach form state, composing left to right: each transform receives
2125
+ * the previous one's output, and the first receives the
2126
+ * directive-extracted DOM value.
2127
+ *
2128
+ * The full order is
2129
+ * `DOM event -> modifier cast (.lazy/.trim/.number) -> transforms[0] -> ... -> assigner`.
2130
+ *
2131
+ * User input only. `form.setValue(...)`,
2132
+ * `rv.setValueWithInternalPath(...)`, `form.reset()`, hydration, SSR
2133
+ * replay and `markBlank()` all bypass transforms, because they write
2134
+ * canonical state rather than normalized user input. To apply the
2135
+ * same normalization to a programmatic write, compose it yourself:
2136
+ *
2137
+ * ```ts
2138
+ * form.setValue('email', slugify(lowercase(rawValue)))
2139
+ * ```
2140
+ *
2141
+ * See `RegisterTransform` for the sync and async contract. For a
2142
+ * pattern that needs to inspect the `RegisterValue` itself, such as
2143
+ * rejection with a side effect, redirection to other fields or custom
2144
+ * DOM mutation, use `@update:registerValue` on the bound element
2145
+ * instead; see "Custom assigners" in the API docs.
2146
+ */
2147
+ transforms?: ReadonlyArray<RegisterTransform>;
2148
+ };
2149
+ /**
2150
+ * The narrow kernel surface the DOM binding drives, structurally a
2151
+ * subset of the form store: the field-record connect and disconnect
2152
+ * transitions, focus marking, which owns blur-validation, the record
2153
+ * and merged-error reads the focus walk needs, and the async-transform
2154
+ * abort for a fully detached path.
2155
+ *
2156
+ * @internal
2157
+ */
2158
+ type DomBindingKernel = {
2159
+ readonly noteDomConnected: (path: Path) => void;
2160
+ readonly noteDomDisconnected: (path: Path) => void;
2161
+ readonly markFocused: (path: Path, focused: boolean, meta?: {
2162
+ readonly instance?: WriteMeta['instance'];
2163
+ }) => void;
2164
+ readonly getFieldRecord: (path: Path) => FieldRecord | undefined;
2165
+ readonly getErrorsForPath: (path: Path) => ValidationError[];
2166
+ readonly cancelTransformsUnder: (prefix: Path) => void;
2167
+ };
2168
+ /**
2169
+ * The form store's DOM slice: the element registry behind
2170
+ * `field.element` and `field.elements`, no-latch host focus anchors,
2171
+ * and first-error focus resolution. Implemented in the directive
2172
+ * cluster's lazy graph and armed into the store's `domBinding` slot on
2173
+ * first use, see `RegisterValue.ensureDomBinding`. A `null` slot means
2174
+ * nothing in the app ever registered an element.
2175
+ *
2176
+ * @internal
2177
+ */
2178
+ type AttaformDomBinding = {
2179
+ readonly elements: Map<PathKey, ElementRecord>;
2180
+ readonly attach: (segments: Path, element: HTMLElement, formInstanceId: string, instanceMeta: WriteMeta['instance'] | undefined) => void;
2181
+ readonly detach: (segments: Path, element: HTMLElement) => void;
2182
+ readonly markHostConnected: (segments: Path, connected: boolean, hostEl: HTMLElement, formInstanceId: string) => void;
2183
+ readonly getFirstErrorElement: (formInstanceId: string) => {
2184
+ path: Path;
2185
+ element: HTMLElement;
2186
+ } | null;
2187
+ };
2188
+ /**
2189
+ * Factory the directive and `useRegister` inject through
2190
+ * `RegisterValue.ensureDomBinding` to arm a store's `domBinding` slot.
2191
+ *
2192
+ * @internal
2193
+ */
2194
+ type DomBindingFactory = (kernel: DomBindingKernel) => AttaformDomBinding;
2195
+ /**
2196
+ * What `form.register(path)` returns. Pass it to a native input via
2197
+ * `v-register`:
2198
+ *
2199
+ * ```vue
2200
+ * <input v-register="form.register('email')" />
2201
+ * ```
2202
+ *
2203
+ * Or read `innerRef` directly when integrating a custom component.
2204
+ *
2205
+ * It is a `shallowReadonly` reactive proxy: top-level reads track in
2206
+ * reactive scopes, mutations are blocked, and inner refs (`innerRef`,
2207
+ * `displayValue`) keep their `Ref` shape.
2208
+ *
2209
+ * `path`, `formKey` and `formInstanceId` are the wrapper-component
2210
+ * primitives: a generic component using `useRegister()` derives field
2211
+ * state and form identity from them without re-threading props from
2212
+ * its parent.
2213
+ */
2214
+ type RegisterValue<Value = unknown> = Readonly<{
2215
+ /**
2216
+ * Live, read-only value at this path. Watch it to drive UI that
2217
+ * depends on the field's current value.
2218
+ */
2219
+ innerRef: Readonly<Ref<Value>>;
2220
+ /**
2221
+ * Attach an HTML element to this binding. `v-register` calls it
2222
+ * automatically; expose it to a custom integration that registers an
2223
+ * element by hand.
2224
+ *
2225
+ * Recording the element drives the form's element map, behind
2226
+ * `field.meta.connected`, `focusFirstError` and `scrollToFirstError`.
2227
+ */
2228
+ registerElement: (el: HTMLElement) => void;
2229
+ /**
2230
+ * Detach an HTML element, dropping it from the form's element map.
2231
+ * Pair with `registerElement` for custom integrations.
2232
+ */
2233
+ deregisterElement: (el: HTMLElement) => void;
2234
+ /**
2235
+ * Write the field's value programmatically, returning `true` when
2236
+ * accepted and `false` when rejected, as for a wrong primitive type
2237
+ * at the path.
2238
+ *
2239
+ * This is the write path for custom directives and consumer
2240
+ * assigners: it routes through the same funnel, and the same
2241
+ * per-instance meta, as the directive's default assigner.
2242
+ * Caller-supplied `meta` passes through unchanged.
2243
+ */
2244
+ setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
2245
+ /**
2246
+ * Commit a value emitted by a third-party component bound through
2247
+ * `v-register`'s compile-time v-model desugar. Writes the component's
2248
+ * typed model output as authoritative, with no coercion, AND marks
2249
+ * the field interacted, since a v-model host has no DOM input
2250
+ * listener to flip the sticky `interacted` bit. The injected
2251
+ * `onUpdate:modelValue` handler is the only caller. `true` when the
2252
+ * write was accepted.
2253
+ *
2254
+ * @internal
2255
+ */
2256
+ setValueFromHost: (value: unknown) => boolean;
2257
+ /**
2258
+ * Mark this field DOM-connected during SSR, so a server-rendered
2259
+ * template reading `form.fields.<path>.connected` does not flicker on
2260
+ * hydration. `v-register` calls it for you; a no-op on the client.
2261
+ *
2262
+ * @internal
2263
+ */
2264
+ markConnectedOptimistically: () => void;
2265
+ /**
2266
+ * Mark this field DOM-connected or disconnected for a `v-register`
2267
+ * component host that binds value through the v-model desugar but
2268
+ * exposes no single inner control to register: a composite widget, or
2269
+ * one where none was found. The directive calls it on mount and
2270
+ * unmount. Distinct from the SSR-only
2271
+ * `markConnectedOptimistically`.
2272
+ *
2273
+ * `hostEl` is the host root, recorded on connect as the field's
2274
+ * focus-first-error anchor. A no-latch host registers no control, so
2275
+ * it would otherwise be invisible to `focusFirstError` and
2276
+ * `scrollToFirstError`, which walk the registered-element set. On an
2277
+ * invalid submit the error walk resolves the host root to its first
2278
+ * focusable descendant. The directive passes the same element on both
2279
+ * edges.
2280
+ *
2281
+ * @internal
2282
+ */
2283
+ markHostConnected: (connected: boolean, hostEl: HTMLElement) => void;
2284
+ /**
2285
+ * Mark this field focused or blurred for a `v-register` component
2286
+ * host with no single latched control: a composite widget whose focus
2287
+ * moves between inner segments, or a control-less one. The directive
2288
+ * tracks focusin and focusout on the widget root and forwards here,
2289
+ * so focus state and blur-validation still arm without an
2290
+ * element-level focus listener. Carries this binding's instance meta,
2291
+ * including its `validateOn`.
2292
+ *
2293
+ * @internal
2294
+ */
2295
+ markFocused: (focused: boolean) => void;
2296
+ /**
2297
+ * `true` when an element already registered for this binding's path
2298
+ * is inside, or is, `hostElement`. The directive's component-host
2299
+ * branch reads it to tell a `useRegister` wrapper, whose inner
2300
+ * control self-registered, from a third-party component, which
2301
+ * registered nothing, so the latch runs only for the latter.
2302
+ *
2303
+ * @internal
2304
+ */
2305
+ hasRegisteredDescendant: (hostElement: HTMLElement) => boolean;
2306
+ /**
2307
+ * Arm this binding's form store with the DOM-binding implementation:
2308
+ * element registry, focus listeners, first-error focus resolution.
2309
+ * That implementation lives in the directive cluster's lazy graph, so
2310
+ * the directive and `useRegister` inject its factory here before any
2311
+ * element call, and once armed the store's `domBinding` slot serves
2312
+ * every later registration.
2313
+ *
2314
+ * Optional, so a hand-rolled RegisterValue owning its own element
2315
+ * handling need not declare it.
2316
+ *
2317
+ * @internal
2318
+ */
2319
+ ensureDomBinding?: (factory: DomBindingFactory) => void;
2320
+ /**
2321
+ * Canonical JSON-encoded path key for this binding, such as
2322
+ * `'["items",0,"name"]'`. Useful for stable Map and Set keys, log
2323
+ * messages, and equality against another `RegisterValue`'s path.
2324
+ * Treat it as opaque: for `form.fields(...)` and `form.values(...)`
2325
+ * lookups inside a wrapper component, use `segments`.
2326
+ */
2327
+ path: PathKey;
2328
+ /**
2329
+ * Structured path segments for this binding, such as
2330
+ * `['items', 0, 'name']`. The consumer-friendly form for
2331
+ * `form.fields(...)` and `form.values(...)` lookups in a generic
2332
+ * wrapper:
2333
+ *
2334
+ * ```ts
2335
+ * const rv = useRegister()
2336
+ * const form = injectForm()
2337
+ * const field = computed(() => form.fields(rv.value?.segments ?? []))
2338
+ * ```
2339
+ *
2340
+ * Frozen at runtime, so a wrapper can read it without defensive
2341
+ * copying.
2342
+ */
2343
+ segments: Path;
2344
+ /**
2345
+ * The form's `key`, supplied or auto-allocated, mirroring `form.key`.
2346
+ * Useful in a wrapper that targets a specific form by key without
2347
+ * prop-drilling.
2348
+ */
2349
+ formKey: string;
2350
+ /**
2351
+ * Per-mount identifier for the form instance, stable across the
2352
+ * form's lifetime. The directive uses it to scope element
2353
+ * registrations to a single mount; it is exposed here for wrappers
2354
+ * that must disambiguate sibling forms sharing a `key`.
2355
+ */
2356
+ formInstanceId: string;
2357
+ /**
2358
+ * The readonly handle the directive iterates for this binding's
2359
+ * transform pipeline. See `RegisterOptions.transforms` for the public
2360
+ * contract. Optional, so a hand-rolled mock need not declare an empty
2361
+ * array; the directive falls back to a no-op pipeline.
2362
+ *
2363
+ * @internal
2364
+ */
2365
+ transforms?: ReadonlyArray<RegisterTransform>;
2366
+ /**
2367
+ * Schema-driven coercion closure baked at register time, capturing
2368
+ * the path's slim accept set and the resolved coercion index so the
2369
+ * per-event hot path is one function call. The identity function when
2370
+ * coercion is disabled or the path admits no coercion target.
2371
+ * Optional, so a hand-rolled mock need not declare it; the directive
2372
+ * falls back to identity.
2373
+ *
2374
+ * @internal
2375
+ */
2376
+ coerce?: (value: unknown) => unknown;
2377
+ /**
2378
+ * Element-level coercion closure for container paths (`z.array(...)`,
2379
+ * `z.set(...)`), coercing a scalar DOM-side value such as an option's
2380
+ * `value` attribute or a checkbox's value against the container's
2381
+ * element type. `undefined` when the path is not a container, where
2382
+ * `coerce` is used exclusively.
2383
+ *
2384
+ * The directive's read-side comparisons in `setChecked` and
2385
+ * `setSelected` use it to keep parity with the change handler's
2386
+ * write-side path-level coerce.
2387
+ *
2388
+ * @internal
2389
+ */
2390
+ coerceElement?: (value: unknown) => unknown;
2391
+ /**
2392
+ * Read-only string view of the field's current value: what the
2393
+ * compile-time `:value` injection reads on every input, textarea and
2394
+ * select bound by `v-register`.
2395
+ *
2396
+ * `''` when the path is in the form's `blankPaths` set, or storage is
2397
+ * `null` or `undefined`; otherwise `String(storage)`. The blank branch
2398
+ * is what lets a user clear a numeric field without the next render
2399
+ * patching `el.value` back to the slim default `'0'`.
2400
+ */
2401
+ displayValue: Readonly<Ref<string>>;
2402
+ /**
2403
+ * Blank-aware presentation for a component host's `:modelValue`, the
2404
+ * typed-model analog of `displayValue`'s `''`: `undefined` when the
2405
+ * path is in `blankPaths`, otherwise the raw typed storage. Lets a
2406
+ * cleared numeric field read empty in a v-model-bound component as it
2407
+ * does in a native input. Read only by the compile-time
2408
+ * component-bridge transform.
2409
+ *
2410
+ * @internal
2411
+ */
2412
+ hostModelValue: Readonly<Ref<Value | undefined>>;
2413
+ /**
2414
+ * Live `true` when this binding's form is frozen via
2415
+ * `useForm({ disabled })`. The compile-time transforms bind it to the
2416
+ * host's `:disabled`, so a native `<input v-register>` renders the
2417
+ * HTML `disabled` attribute on both server and client, and a
2418
+ * component host receives a `disabled` prop. Read it from a custom
2419
+ * `useRegister` integration to drive your own disabled affordance.
2420
+ */
2421
+ disabled: Readonly<Ref<boolean>>;
2422
+ /**
2423
+ * Add this field's path to the form's `blankPaths` set, writing the
2424
+ * slim default to storage. Returns the `setValueAtPath` verdict:
2425
+ * `true` accepted, `false` rejected by the slim-primitive gate.
2426
+ *
2427
+ * Called by the directive's input listener on a numeric clear, and by
2428
+ * the `setValue(path, unset)` translation.
2429
+ *
2430
+ * @internal
2431
+ */
2432
+ markBlank: () => boolean;
2433
+ /**
2434
+ * Flip this field's sticky `interacted` flag, the signal that the
2435
+ * user has made at least one value edit here, an insert or a delete.
2436
+ * Called by the directive's input and change listeners on genuine
2437
+ * user input, never by hydration or a programmatic write. Idempotent,
2438
+ * since the store skips the write once set.
2439
+ *
2440
+ * @internal
2441
+ */
2442
+ markInteracted: () => void;
2443
+ /**
2444
+ * `true` when the schema's slim primitive set at this path includes
2445
+ * `'undefined'`, meaning the leaf was declared `.optional()`, or sits
2446
+ * in a union admitting `undefined`. Cached at register time.
2447
+ *
2448
+ * The directive's text-input listener reads it to map a DOM clear
2449
+ * onto `undefined` storage rather than `''`, so the `.optional()`
2450
+ * absent semantic survives user interaction. Without it, a user who
2451
+ * typed an invalid value into an optional field and then cleared it
2452
+ * would be stuck with a permanent validation error, storage holding
2453
+ * `''`, which is neither `undefined` nor a valid inner value.
2454
+ *
2455
+ * @internal
2456
+ */
2457
+ acceptsUndefined: boolean;
2458
+ /**
2459
+ * `true` when the schema's slim primitive set at this path includes
2460
+ * `'string'`. Cached at register time alongside `acceptsUndefined`.
2461
+ *
2462
+ * The directive's text-input listener reads it so a DOM clear on a
2463
+ * numeric-only, boolean-only or bigint-only leaf takes the
2464
+ * `markBlank` path instead of writing `''` through the assigner. The
2465
+ * slim gate would reject the empty string anyway, and the directive's
2466
+ * post-write force-sync would then snap the DOM back to the last
2467
+ * accepted value, making the final character undeletable. Through
2468
+ * `markBlank`, storage holds the slim default with the blank meta and
2469
+ * the DOM stays empty.
2470
+ *
2471
+ * @internal
2472
+ */
2473
+ acceptsString: boolean;
2474
+ /**
2475
+ * The field's aria satellite ids, mirroring `FieldState.aria`. The
2476
+ * directive points `aria-describedby` at `errorId` while the field is
2477
+ * in its error state. Optional for mock tolerance; the directive
2478
+ * skips aria wiring when absent.
2479
+ *
2480
+ * @internal
2481
+ */
2482
+ aria?: {
2483
+ readonly errorId: string;
2484
+ readonly descriptionId: string;
2485
+ };
2486
+ /**
2487
+ * Whether the schema marks this path required, from
2488
+ * `schema.isRequiredAtPath(segments)`. Drives `aria-required`.
2489
+ * Optional for the same mock tolerance as `aria`.
2490
+ *
2491
+ * @internal
2492
+ */
2493
+ isRequired?: boolean;
2494
+ /**
2495
+ * The gated display-state verdict for this path, reusing the same
2496
+ * field-state identity as `form.fields`. The directive watches it to
2497
+ * keep `aria-invalid`, `aria-busy` and `aria-describedby` in lockstep
2498
+ * with the visible error state, even on an async tick with no parent
2499
+ * re-render. Optional; the directive skips aria wiring when absent.
2500
+ *
2501
+ * @internal
2502
+ */
2503
+ ariaDisplayState?: Readonly<Ref<DisplayState>>;
2504
+ }>;
2505
+ /**
2506
+ * Mutable holder for an async-transform run's `AbortController`, shared
2507
+ * by the directive, which creates the controller the first time a
2508
+ * transform reaches for `ctx.signal`, and the store, which aborts it
2509
+ * when the run is superseded, cancelled or reset. `controller` stays
2510
+ * `null` until `ctx.signal` is touched, so a purely sync chain never
2511
+ * allocates one. `aborted` latches `true` the moment the store tears the
2512
+ * run down, so a signal accessed AFTER teardown still resolves to an
2513
+ * already-aborted signal rather than a live one.
2514
+ */
2515
+ type TransformAbortHolder = {
2516
+ controller: AbortController | null;
2517
+ aborted: boolean;
2518
+ };
2519
+ /**
2520
+ * The second argument handed to every transform in a `transforms: [...]`
2521
+ * chain. `signal` is an `AbortSignal` that aborts when the run is
2522
+ * superseded by newer input or torn down by a reset or cancel, so a
2523
+ * transform doing cancellable I/O (a `fetch`, a worker round-trip) can
2524
+ * pass `ctx.signal` through and bail the moment its result stops
2525
+ * mattering.
2526
+ *
2527
+ * The signal is lazy: its backing `AbortController` is allocated on
2528
+ * first access, so a purely synchronous chain that never reaches for
2529
+ * `ctx.signal` allocates nothing, and having no in-flight I/O to cancel,
2530
+ * never aborts.
2531
+ */
2532
+ type TransformContext = {
2533
+ readonly signal: AbortSignal;
2534
+ };
2535
+ /**
2536
+ * Custom assigner installed on an element via the directive's
2537
+ * `[assignKey]` slot OR an `@update:registerValue` listener. Called
2538
+ * by the directive when a DOM event (input / change / etc.) fires
2539
+ * on the bound element.
2540
+ *
2541
+ * The directive passes the extracted value plus the `RegisterValue`
2542
+ * the directive is currently bound to, regardless of install path.
2543
+ * The second arg lets a top-level handler write back to form state
2544
+ * without having to capture the RV via closure:
2545
+ *
2546
+ * ```ts
2547
+ * function upperCaseAssigner(value: unknown, rv: RegisterValue): void {
2548
+ * rv.setValueWithInternalPath(String(value ?? '').toUpperCase())
2549
+ * }
2550
+ * ```
2551
+ *
2552
+ * The `registerValue` parameter is typed optional only to keep
2553
+ * standalone invocations from outside the directive (rare; manual
2554
+ * dispatch in tests, for example) type-checkable; the directive
2555
+ * itself always supplies it at fire time.
2556
+ *
2557
+ * Return `true` when the write was accepted, `false` when it was
2558
+ * rejected (e.g. the value didn't match the path's expected type).
2559
+ * `undefined` is treated as "succeeded" so simple assigners can
2560
+ * just return `void`.
2561
+ */
2562
+ type CustomDirectiveRegisterAssignerFn = (value: unknown, registerValue?: RegisterValue) => boolean | undefined;
2563
+ /**
2564
+ * Generic shape of a v-register directive variant. Used by the
2565
+ * library's text / checkbox / radio / select directive types and
2566
+ * available for custom integrations that need to drop in their own
2567
+ * variant.
2568
+ *
2569
+ * The value generic admits `undefined` because `useRegister()` may
2570
+ * return `undefined` (a wrapper component rendered without a parent
2571
+ * `registerValue`); binding that value to `v-register` is supported
2572
+ * and installs a no-op assigner at runtime.
2573
+ */
2574
+ type CustomRegisterDirective<T, Modifiers extends string = string> = ObjectDirective<T & {
2575
+ _assigning?: boolean;
2576
+ /**
2577
+ * Snapshot of the last `value.innerRef.value` reference the
2578
+ * directive's DOM-sync (setSelected / setChecked / radio
2579
+ * `el.checked = …`) was applied for. Used by every input
2580
+ * directive's `updated` / `beforeUpdate` to skip the per-render
2581
+ * DOM sync when the model is identity-unchanged, so a parent
2582
+ * re-render (a typed character in a sibling, an async-validation
2583
+ * tick, any reactive read) cannot clobber an in-progress user
2584
+ * interaction. Identity comparison is sound because every form
2585
+ * write produces a fresh value at the path: scalars are new
2586
+ * primitives, and arrays and Sets get fresh references along the
2587
+ * spine through diff-apply, so reference equality on
2588
+ * `innerRef.value` tracks "did the model move" exactly. The
2589
+ * `_assigning` gate stays alongside it, short-circuiting the
2590
+ * immediate post-write render where the user's own input has
2591
+ * already put the DOM in sync.
2592
+ */
2593
+ _lastAppliedModel?: unknown;
2594
+ /**
2595
+ * Variant-specific "repaint the DOM from current storage" closure,
2596
+ * stashed by each input directive's `created` hook (it mirrors that
2597
+ * variant's post-write force-sync block). The deferred async-transform
2598
+ * orchestrator calls it once the resolved value has committed, so a
2599
+ * bare `<input v-register>` with no other reactive reader still paints
2600
+ * the normalized result without depending on a parent re-render.
2601
+ */
2602
+ _syncFromStorage?: () => void;
2603
+ [S: symbol]: CustomDirectiveRegisterAssignerFn;
2604
+ }, RegisterValue | undefined, Modifiers, string>;
2605
+ /**
2606
+ * Modifier names supported by `v-register` on `<input type="text">`,
2607
+ * `<input type="number">`, and `<textarea>`. Mirrors Vue's
2608
+ * `v-model` modifier semantics on the same elements; combine freely
2609
+ * (`<input v-register.lazy.trim.number="..." />`).
2610
+ */
2611
+ type RegisterTextModifier =
2612
+ /**
2613
+ * Write on `change` (blur) instead of `input`. The reactive
2614
+ * model only updates after the user tabs/clicks out of the
2615
+ * field. IME composition handlers are skipped under `.lazy`, since
2616
+ * composition events do not gate writes.
2617
+ */
2618
+ 'lazy'
2619
+ /**
2620
+ * Strip leading and trailing whitespace on blur. The form holds
2621
+ * the user's raw input (whitespace included) while they're
2622
+ * typing; on `change` (blur / commit) the value is trimmed
2623
+ * once and written back to both the model and the visible DOM.
2624
+ * Combine with `.lazy` to skip the mid-typing writes entirely.
2625
+ */
2626
+ | 'trim'
2627
+ /**
2628
+ * Cast the value with `parseFloat` before writing. A value that
2629
+ * does not parse as a number (`'abc'`) passes through unchanged,
2630
+ * and the slim-primitive gate then sees a string heading for a
2631
+ * numeric slot and rejects the write. Auto-applied for
2632
+ * `<input type="number">`, where an explicit `.number` is
2633
+ * redundant.
2634
+ */
2635
+ | 'number';
2636
+ /**
2637
+ * v-register directive variant for `<input type="text">`,
2638
+ * `<input type="number">` and `<textarea>`. Supports the `.lazy`,
2639
+ * `.trim` and `.number` modifiers; `RegisterTextModifier` carries
2640
+ * the per-modifier semantics.
2641
+ */
2642
+ type RegisterTextCustomDirective = CustomRegisterDirective<HTMLInputElement | HTMLTextAreaElement, RegisterTextModifier>;
2643
+ /** v-register directive variant for checkboxes. No modifiers. */
2644
+ type RegisterCheckboxCustomDirective = CustomRegisterDirective<HTMLInputElement>;
2645
+ /** v-register directive variant for radio inputs. No modifiers. */
2646
+ type RegisterRadioCustomDirective = CustomRegisterDirective<HTMLInputElement>;
2647
+ /**
2648
+ * Modifier name supported by `v-register` on `<select>`. Mirrors
2649
+ * Vue's `v-model` `.number` on the same element.
2650
+ */
2651
+ type RegisterSelectModifier =
2652
+ /**
2653
+ * Cast each selected option's `value` with `parseFloat` before
2654
+ * writing, so form state holds numbers rather than numeric
2655
+ * strings. The case for it is markup whose option values are
2656
+ * written as strings against a schema expecting numbers.
2657
+ */
2658
+ 'number';
2659
+ /**
2660
+ * v-register directive variant for `<select>`. Supports `.number`;
2661
+ * `RegisterSelectModifier` carries the semantics.
2662
+ */
2663
+ type RegisterSelectCustomDirective = CustomRegisterDirective<HTMLSelectElement, RegisterSelectModifier>;
2664
+ /** v-register directive variant for the dynamic input/select/textarea bridge. */
2665
+ type RegisterModelDynamicCustomDirective = ObjectDirective<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, RegisterValue | undefined, string>;
2666
+ /**
2667
+ * The `v-register` directive. Binds a form field to a native
2668
+ * input, select, textarea, checkbox, or radio:
2669
+ *
2670
+ * ```vue
2671
+ * <input v-register="form.register('email')" />
2672
+ * <select v-register="form.register('country')">
2673
+ * <option value="us">US</option>
2674
+ * <option value="uk">UK</option>
2675
+ * </select>
2676
+ * ```
2677
+ *
2678
+ * It also works on a custom component whose root is NOT a native
2679
+ * input: call `useRegister()` in the child's setup to read the
2680
+ * parent's binding, then re-bind `v-register` onto an inner native
2681
+ * element. When the wrapper's root IS the input, attribute
2682
+ * fallthrough handles it and `useRegister` is unnecessary.
2683
+ *
2684
+ * ```vue
2685
+ * <!-- Parent -->
2686
+ * <MyField label="Email" v-register="form.register('email')" />
2687
+ *
2688
+ * <!-- MyField.vue (root is <label>, not <input>) -->
2689
+ * <script setup>
2690
+ * import { useRegister } from 'attaform'
2691
+ * defineProps<{ label: string }>()
2692
+ * const register = useRegister()
2693
+ * </script>
2694
+ * <template>
2695
+ * <label>
2696
+ * <span>{{ label }}</span>
2697
+ * <input v-register="register" />
2698
+ * </label>
2699
+ * </template>
2700
+ * ```
2701
+ *
2702
+ * Modifier support varies by element:
2703
+ * - text / number / textarea: `.lazy`, `.trim`, `.number`
2704
+ * - select: `.number`
2705
+ * - checkbox / radio: none
2706
+ *
2707
+ * See `RegisterTextModifier` / `RegisterSelectModifier` for
2708
+ * per-modifier semantics.
2709
+ *
2710
+ * Delivered by the Vite or Nuxt plugin's compile-time binding, and by
2711
+ * `installVRegister(app)` from `attaform/directive` everywhere else.
2712
+ * Most consumers never import the directive itself; it is exposed for
2713
+ * integrations that install directives manually.
2714
+ */
2715
+ type RegisterDirective = RegisterTextCustomDirective | RegisterCheckboxCustomDirective | RegisterSelectCustomDirective | RegisterRadioCustomDirective | RegisterModelDynamicCustomDirective;
2716
+ /**
2717
+ * Module augmentation: register `v-register` with Vue's template
2718
+ * type system. Lives in `types-api` because every public entry
2719
+ * (`attaform`, `attaform/zod`, `attaform/zod-v3`, `attaform/zod-v4`)
2720
+ * transitively reaches this file via the `useForm` return type, so
2721
+ * the augmentation propagates to consumer SFCs regardless of which
2722
+ * entry they import from, and whether or not they install
2723
+ * `attaform/nuxt` or the Vite plugin.
2724
+ *
2725
+ * Augmentation targets `vue` rather than `@vue/runtime-core`:
2726
+ * `GlobalDirectives` is originally declared in `@vue/runtime-core`,
2727
+ * but consumers and Volar's strict-template codegen both resolve
2728
+ * the interface through `vue`'s `export * from '@vue/runtime-dom'`
2729
+ * to `export * from '@vue/runtime-core'` chain. TypeScript merges
2730
+ * interfaces across re-exports, so augmenting `'vue'` reaches Volar
2731
+ * without needing `@vue/runtime-core` to be hoisted into the
2732
+ * library's own `node_modules` for its own typecheck.
2733
+ */
2734
+ declare module 'vue' {
2735
+ interface GlobalDirectives {
2736
+ /**
2737
+ * The `v-register` directive. Binds a form field to a native
2738
+ * input, select, textarea, checkbox, or radio:
2739
+ *
2740
+ * ```vue
2741
+ * <input v-register="form.register('email')" />
2742
+ * ```
2743
+ *
2744
+ * It also works on a custom component whose root is NOT a native
2745
+ * input: call `useRegister()` in the child's setup to read the
2746
+ * parent's binding, then re-bind `v-register` onto an inner
2747
+ * native element. When the wrapper's root IS the input,
2748
+ * attribute fallthrough handles it and `useRegister` is
2749
+ * unnecessary.
2750
+ *
2751
+ * Modifier support varies by element:
2752
+ * - text / number / textarea: `.lazy`, `.trim`, `.number`
2753
+ * - select: `.number`
2754
+ * - checkbox / radio: none
2755
+ */
2756
+ vRegister: RegisterDirective;
2757
+ }
2758
+ }
2759
+ /**
2760
+ * Callback form of `setValue`'s value argument. Receives the previous
2761
+ * value at the path and returns the next value:
2762
+ *
2763
+ * ```ts
2764
+ * form.setValue('count', (prev) => prev + 1)
2765
+ * form.setValue((prev) => ({ ...prev, name: 'Ada' }))
2766
+ * ```
2767
+ *
2768
+ * A whole-form callback replaces the whole form. Any key the return
2769
+ * omits is refilled from the SCHEMA's declared defaults, or the slim
2770
+ * value where none is declared, so an omitted key is never carried over
2771
+ * from the previous value or from `defaultValues`. Spread `prev` to keep
2772
+ * the rest of the form.
2773
+ */
2774
+ type SetValueCallback<Read, Write = Read> = (prev: Read) => Read | Write;
2775
+ /**
2776
+ * The value argument of `form.setValue`. Either the next value
2777
+ * directly, or a callback that derives it from the previous value.
2778
+ *
2779
+ * Type parameters:
2780
+ * - `Write`: what the direct value form accepts, the storable shape at
2781
+ * the path.
2782
+ * - `Read`: what the callback's `prev` argument exposes, defaulting to
2783
+ * `Write`. For a whole-form callback the read shape tags array
2784
+ * elements as possibly-undefined, to match runtime reality.
2785
+ */
2786
+ type SetValuePayload<Write, Read = Write> = Write | SetValueCallback<Read, Write>;
2787
+ /**
2788
+ * Detect `any` distinctly from `unknown`. The trick: `1 & any` is `any`
2789
+ * and `0 extends any` is `true`; `1 & unknown` is `1` and `0 extends 1`
2790
+ * is `false`. Used to fork `PathSetValuePayload` so `z.any()` paths
2791
+ * resolve to `any` (matching the read-side surface) and `z.unknown()` /
2792
+ * preprocess paths resolve to `unknown` (matching Zod's input typing).
2793
+ */
2794
+ type IsAny<T> = 0 extends 1 & T ? true : false;
2795
+ /**
2796
+ * Resolves `setValue`'s `value` argument type at a single `Path` leaf.
2797
+ *
2798
+ * Three branches, one per Zod input-typing case:
2799
+ *
2800
+ * 1. **`any` leaf (`z.any()`)**: the schema input type is `any`, and
2801
+ * so is the whole form API surface at this path (read, register,
2802
+ * fields). This branch returns raw `any` to stay consistent with
2803
+ * them. A call site passing an unannotated `(prev) => ...` may hit
2804
+ * `noImplicitAny` under the consumer's tsconfig; annotating
2805
+ * `(prev: any) => ...` opts into the looser shape explicitly.
2806
+ *
2807
+ * 2. **`unknown` leaf (`z.unknown()`, `z.preprocess()` input)**: the
2808
+ * schema input is unconstrained and consumers narrow before use.
2809
+ * The branch returns
2810
+ * `({} | null | undefined) | ((prev: unknown) => unknown)` rather
2811
+ * than a `SetValuePayload<unknown, ...>`-style union, for three
2812
+ * reasons:
2813
+ *
2814
+ * a. **Union absorption.** `unknown | X` collapses to `unknown`,
2815
+ * erasing the callback union member. With the callback shape
2816
+ * gone, TS has no contextual type for `prev` and decays it to
2817
+ * implicit `any` under `noImplicitAny`. The triple
2818
+ * `{} | null | undefined` covers the same value space as
2819
+ * `unknown` but is NOT subject to absorption, so the callback
2820
+ * branch survives and `prev` infers cleanly to `unknown`.
2821
+ *
2822
+ * b. **`NonNullable<unknown> = {}`.** Applying `NonNullable` to
2823
+ * the read slot for an unknown leaf narrows `prev` to `{}`,
2824
+ * which is looser than `unknown` and allows ad-hoc property
2825
+ * access. This branch keeps the read slot as `unknown` so the
2826
+ * consumer has to narrow.
2827
+ *
2828
+ * c. **`Unset`-widening does not apply.** `DefaultValuesShape`
2829
+ * widens a primitive leaf to admit `unset`, and an unknown leaf
2830
+ * has no primitive to widen. The open-form triple covers the
2831
+ * same value space the runtime accepts, `unset` included, since
2832
+ * a symbol is a `{}`.
2833
+ *
2834
+ * 3. **Every other leaf** flows through unchanged via
2835
+ * `SetValuePayload<DefaultValuesShape<Leaf>, NonNullable<WriteShape<Leaf>>>`.
2836
+ */
2837
+ type PathSetValuePayload<Leaf> = IsAny<Leaf> extends true ? any : unknown extends Leaf ? // eslint-disable-next-line @typescript-eslint/no-empty-object-type
2838
+ ({} | null | undefined) | ((prev: unknown) => unknown) : SetValuePayload<DefaultValuesShape<Leaf>, NonNullable<WriteShape<Leaf>>>;
2839
+ /**
2840
+ * Per-field reactive shape returned by `form.fields.<leaf-path>` and
2841
+ * `form.fields(path)`. Slim, readonly across the board. One type lives at
2842
+ * every path, with aggregations rolled up at containers.
2843
+ *
2844
+ * Leaf-aware: dot-access injects these keys at LEAF paths only. At a
2845
+ * container path the proxy descends without injecting, so a schema field
2846
+ * literally named `dirty` at depth 2 or deeper stays reachable as a
2847
+ * descent target and nothing is shadowed. The container call form
2848
+ * (`form.fields('address')`) returns a `FieldState` whose keys aggregate
2849
+ * the descendant leaves.
2850
+ */
2851
+ type FieldState<Value = unknown> = {
2852
+ readonly value: Value;
2853
+ readonly original: Value;
2854
+ readonly pristine: boolean;
2855
+ readonly dirty: boolean;
2856
+ readonly focused: boolean | null;
2857
+ readonly blurred: boolean | null;
2858
+ readonly touched: boolean;
2859
+ /**
2860
+ * `true` once the user has issued at least one value edit on this
2861
+ * field through `v-register` (an insert or a delete), sticky
2862
+ * thereafter and preserved across disconnects. Distinct from
2863
+ * `dirty`: typing `"a"` then deleting it back to empty leaves the
2864
+ * field net-unchanged (`dirty: false`) yet `interacted: true`.
2865
+ * Distinct from `touched`: tabbing through a field without editing
2866
+ * flips `touched` but never `interacted`. Set only by user input,
2867
+ * never by hydration or programmatic `setValue`; cleared by
2868
+ * `form.reset()` / `form.resetField(path)`. Containers roll it up as
2869
+ * a disjunction (any descendant interacted).
2870
+ */
2871
+ readonly interacted: boolean;
2872
+ /**
2873
+ * `true` once the user has blurred this field after editing it: the
2874
+ * first time they edit a value and then leave. Sticky thereafter and
2875
+ * preserved across disconnects; a tab-through with no edit never sets
2876
+ * it (`interacted` is still false at that blur). It composes
2877
+ * `interacted` with the departure and drives the default display gate,
2878
+ * so errors reveal once the user finishes a pass and leaves, then stay
2879
+ * visible through a re-focus to be fixed live. Set only by user
2880
+ * input/blur, never by hydration or programmatic writes; cleared by
2881
+ * `form.reset()` / `form.resetField(path)`. Containers roll it up as a
2882
+ * disjunction.
2883
+ */
2884
+ readonly blurredAfterInteraction: boolean;
2885
+ readonly connected: boolean;
2886
+ /**
2887
+ * The first DOM element bound to this path via `v-register`, or
2888
+ * `null` when none is registered (initial mount, post-unmount,
2889
+ * SSR). "First" means first by registration order. Reach for it to
2890
+ * call a native DOM method on a field's input (`focus()`,
2891
+ * `scrollIntoView()`, `select()`, `setSelectionRange()`) without
2892
+ * Attaform having to verb every imperative:
2893
+ *
2894
+ * ```ts
2895
+ * form.fields.email.element?.focus()
2896
+ * form.fields.email.element?.scrollIntoView({ block: 'center' })
2897
+ * ```
2898
+ *
2899
+ * On a path with multiple bindings (input syncing, mirrored shadow
2900
+ * inputs), prefer `elements` and pick the target yourself. Reactive:
2901
+ * a register or deregister triggers re-evaluation.
2902
+ */
2903
+ readonly element: HTMLElement | null;
2904
+ /**
2905
+ * Every DOM element currently bound to this path via `v-register`,
2906
+ * in registration order, or an empty array when none is. Two
2907
+ * bindings on one path is a supported shape, for input syncing or
2908
+ * mirrored shadow inputs:
2909
+ *
2910
+ * ```ts
2911
+ * for (const el of form.fields.email.elements) el.blur()
2912
+ * ```
2913
+ *
2914
+ * For the common single-binding case, `element` is sugar over
2915
+ * `elements[0] ?? null`.
2916
+ */
2917
+ readonly elements: readonly HTMLElement[];
2918
+ readonly updatedAt: string | null;
2919
+ /**
2920
+ * Every `ValidationError` in this path's SUBTREE: the node's own
2921
+ * bucket plus every descendant's, sorted by schema-declaration order.
2922
+ * On a leaf (no descendants) this equals `ownErrors`. On a container
2923
+ * it rolls up the whole fieldset, so a parent's `errors` is non-empty
2924
+ * whenever any child is invalid.
2925
+ *
2926
+ * For the errors pinned at THIS path alone (a container's own
2927
+ * cross-field `.refine()`, or `setErrors` at this path), read
2928
+ * `ownErrors`.
2929
+ */
2930
+ readonly errors: readonly ValidationError[];
2931
+ /**
2932
+ * Every `ValidationError` at THIS exact path's own bucket, excluding
2933
+ * descendants: schema errors, blank-required errors, and `setErrors`
2934
+ * pinned here, in `schema`, `blank`, `user` order. The exact-path
2935
+ * counterpart to the subtree-scoped `errors`.
2936
+ *
2937
+ * On a leaf this is the same array as `errors` (a leaf has no
2938
+ * descendants). On a container it holds only the container's OWN
2939
+ * errors, a cross-field `.refine()` on the fieldset or a path-less
2940
+ * `setErrors` at this node, without dragging in any child error:
2941
+ *
2942
+ * ```vue
2943
+ * <p v-if="form.fields.address.firstOwnError">
2944
+ * {{ form.fields.address.firstOwnError.message }}
2945
+ * </p>
2946
+ * ```
2947
+ *
2948
+ * On `form.meta` this is the root `[]` bucket: form-level errors from
2949
+ * a root `.refine()` or a path-less `setErrors`. `form.meta.ownErrors`
2950
+ * (with `firstOwnError`) is the banner accessor for those.
2951
+ */
2952
+ readonly ownErrors: readonly ValidationError[];
2953
+ /**
2954
+ * `true` while a per-field validation run is in flight at this path.
2955
+ * Reflects field-level debounced runs (`validate-on-change`) and
2956
+ * cross-field re-validations targeting this path. A whole-form
2957
+ * `validate()` or `parse()` drives `form.meta.validating` alone and
2958
+ * never flips a per-field flag.
2959
+ *
2960
+ * Per-field analogue of `form.meta.validating`. Use for a tight
2961
+ * "Checking…" indicator next to a single async-validated input
2962
+ * without commandeering the whole-form spinner.
2963
+ */
2964
+ readonly validating: boolean;
2965
+ /**
2966
+ * `true` when this field has no errors AND no per-field validation
2967
+ * is in flight (`errors.length === 0 && !validating`). Confidence
2968
+ * that "we've checked, and we have no problems right now." Use for
2969
+ * green-checkmark / `aria-invalid` UX.
2970
+ *
2971
+ * Validation-only: an in-flight async transform does NOT clamp
2972
+ * `valid` to `false`. `valid` is the verdict on the last committed
2973
+ * value; `busy` is the union "work in flight" signal.
2974
+ */
2975
+ readonly valid: boolean;
2976
+ /**
2977
+ * `true` while an async `register` transform is in flight at this
2978
+ * path: a transform returned a thenable and the resolved value has
2979
+ * not yet committed to form state. Always `false` for a sync-only
2980
+ * chain, which reaches form state in the same tick with no deferral.
2981
+ * Containers roll it up as a disjunction (any descendant transforming).
2982
+ */
2983
+ readonly transforming: boolean;
2984
+ /**
2985
+ * `transforming || validating`: the "work is in flight at this path"
2986
+ * signal. Drives `aria-busy` through `displayState` on a
2987
+ * revealed field, and is the surface to bind for a busy indicator on a
2988
+ * field not yet revealed (where `displayState` stays idle by the
2989
+ * reveal gate). Containers roll it up as a disjunction.
2990
+ */
2991
+ readonly busy: boolean;
2992
+ /**
2993
+ * The `Error` from the most recent async transform that rejected at
2994
+ * this path, else `null`. A per-field normalization-failure channel
2995
+ * separate from validation `errors`: a transform that rejects (a
2996
+ * failed fetch, a parse error) surfaces here instead of crashing the
2997
+ * host app or logging. Cleared when a fresh transform starts or a
2998
+ * write supersedes it. Leaf-only: a container never rolls it up, and
2999
+ * reads `null`.
3000
+ */
3001
+ readonly transformError: Error | null;
3002
+ /**
3003
+ * The single display-state verdict at this path: `'idle'`,
3004
+ * `'pending'`, `'error'`, or `'success'`. The source of truth the
3005
+ * four `show*` booleans below derive from. Bind it directly when one
3006
+ * branch over the set reads cleaner than four flags:
3007
+ *
3008
+ * ```vue
3009
+ * <FieldStatusIcon :state="form.fields.email.displayState" />
3010
+ * ```
3011
+ *
3012
+ * Resolved by the display heuristic: gate closed → `'idle'`; a run
3013
+ * in flight → a delayed `'pending'`; an own-path error → `'error'`;
3014
+ * otherwise earned `valid` → `'success'`, else `'idle'`. The gate
3015
+ * opens after the first submit attempt OR once the field is edited
3016
+ * and left.
3017
+ *
3018
+ * Available on container paths too: `form.fields.users[0].displayState`
3019
+ * rolls up over the row's descendants.
3020
+ */
3021
+ readonly displayState: DisplayState;
3022
+ /**
3023
+ * `displayState === 'error'`. The centralised "render this field's
3024
+ * errors right now?" gate, so templates avoid re-spelling the
3025
+ * heuristic at every error site:
3026
+ *
3027
+ * ```vue
3028
+ * <span v-if="form.fields.email.showErrors">
3029
+ * {{ form.fields.email.firstError?.message }}
3030
+ * </span>
3031
+ * ```
3032
+ *
3033
+ * Kept plural to match `errors` / `firstError`. On container paths it
3034
+ * rolls up over descendants (any descendant resolving to `'error'`
3035
+ * flips the container on).
3036
+ */
3037
+ readonly showErrors: boolean;
3038
+ /**
3039
+ * `displayState === 'pending'`. A per-field validation run is in
3040
+ * flight at this path and the prior verdict is stale; drive a spinner
3041
+ * or a "Checking…" affordance.
3042
+ */
3043
+ readonly showPending: boolean;
3044
+ /**
3045
+ * `displayState === 'success'`. Validation has passed and the timing
3046
+ * gate has cleared a positive confirmation; drive the green-check
3047
+ * pattern.
3048
+ */
3049
+ readonly showSuccess: boolean;
3050
+ /**
3051
+ * `displayState === 'idle'`. Nothing to surface yet, either
3052
+ * pre-interaction or gate-open with no verdict worth showing. Read it
3053
+ * to suppress helper text the moment another signal takes over.
3054
+ */
3055
+ readonly showIdle: boolean;
3056
+ /**
3057
+ * The first `ValidationError` at this path in the deterministic
3058
+ * schema-declaration order, equivalent to `errors[0]` and exposed as
3059
+ * sugar for "show the highest-priority error message and ignore the
3060
+ * rest":
3061
+ *
3062
+ * ```vue
3063
+ * <span v-if="form.fields.email.showErrors">
3064
+ * {{ form.fields.email.firstError?.message }}
3065
+ * </span>
3066
+ * ```
3067
+ *
3068
+ * `undefined` when no errors exist, and independent of `showErrors`:
3069
+ * the data primitive is always available, and the heuristic decides
3070
+ * only when to render it.
3071
+ *
3072
+ * On container paths, the first error in the aggregated subtree
3073
+ * (descendants sorted by `pathOrdinals`). For the container's OWN
3074
+ * first error only, read `firstOwnError`.
3075
+ */
3076
+ readonly firstError: ValidationError | undefined;
3077
+ /**
3078
+ * The first `ValidationError` in `ownErrors` (this exact path's own
3079
+ * bucket), or `undefined` when that bucket is empty. Equivalent to
3080
+ * `ownErrors[0]`, the exact-path counterpart to `firstError`.
3081
+ *
3082
+ * On a leaf this equals `firstError`. On a container it is the
3083
+ * container's own cross-field error (a fieldset `.refine()`), never a
3084
+ * child's. `form.meta.firstOwnError` is the form-level banner: the
3085
+ * first root `[]` error from a root `.refine()` or a path-less
3086
+ * `setErrors`:
3087
+ *
3088
+ * ```vue
3089
+ * <p v-if="form.meta.firstOwnError" role="alert">
3090
+ * {{ form.meta.firstOwnError.message }}
3091
+ * </p>
3092
+ * ```
3093
+ */
3094
+ readonly firstOwnError: ValidationError | undefined;
3095
+ readonly path: ReadonlyArray<string | number>;
3096
+ /**
3097
+ * Stable, SSR-safe DOM id for this field, unique across every mount
3098
+ * on the page. Derived from the form's key and this path, folded with
3099
+ * the form's per-mount `instanceId` so two simultaneous mounts of the
3100
+ * same keyed form never collide. Bind it to wire a label and its
3101
+ * input without inventing your own id:
3102
+ *
3103
+ * ```vue
3104
+ * <label :for="form.fields.email.id">Email</label>
3105
+ * <input :id="form.fields.email.id" v-register="form.register('email')" />
3106
+ * ```
3107
+ *
3108
+ * Treat as identity, not state: stable for the path across the form's
3109
+ * lifetime, opaque, not meant to be parsed.
3110
+ */
3111
+ readonly id: string;
3112
+ /**
3113
+ * Satellite ids derived from {@link id} for the elements that
3114
+ * describe this field. Wire them to an error node and a description
3115
+ * node so assistive tech announces them with the input. The
3116
+ * `v-register` directive points `aria-describedby` at `errorId`
3117
+ * automatically while the field is in its error state; you render the
3118
+ * matching element and id it:
3119
+ *
3120
+ * ```vue
3121
+ * <input v-register="form.register('email')" />
3122
+ * <span :id="form.fields.email.aria.errorId" v-if="form.fields.email.showErrors">
3123
+ * {{ form.fields.email.firstError?.message }}
3124
+ * </span>
3125
+ * ```
3126
+ *
3127
+ * `descriptionId` is for opt-in help text; chain it into your own
3128
+ * `aria-describedby` when you render a persistent description element.
3129
+ */
3130
+ readonly aria: {
3131
+ readonly errorId: string;
3132
+ readonly descriptionId: string;
3133
+ };
3134
+ /**
3135
+ * Stable identity for this field as an element of its parent array,
3136
+ * suitable as a Vue `:key` when iterating array elements. An allocated
3137
+ * token (not derived from the element's value) that follows the
3138
+ * element across inserts, removals, moves, and swaps, so a row keeps
3139
+ * its component instance across a reorder. Empty for fields that are
3140
+ * not array elements. Treat as opaque identity, not state.
3141
+ */
3142
+ readonly key: string;
3143
+ readonly blank: boolean;
3144
+ /**
3145
+ * `true` when this field's form is frozen via `useForm({ disabled })`.
3146
+ * While disabled, value writes no-op and this field's `displayState`
3147
+ * is forced to `'idle'` so error, pending, and success signals stand
3148
+ * down. A form-level flag: every field of a disabled form reads
3149
+ * `true`, and the form root exposes it as `form.meta.disabled`. Bind
3150
+ * read-only or styling affordances off this.
3151
+ */
3152
+ readonly disabled: boolean;
3153
+ /**
3154
+ * Presentational label for this field. It resolves through the
3155
+ * shared cross-adapter field-meta store, written with
3156
+ * `schema.register(fieldMeta, {...})` on Zod 4 or the `withMeta()`
3157
+ * helper on either major, and falls back to a humanized form of the
3158
+ * path's last segment when nothing is registered. Always a string.
3159
+ *
3160
+ * ```ts
3161
+ * z.string().register(fieldMeta, { label: 'Reference' })
3162
+ * // template: <label>{{ form.fields.reference.label }}</label>
3163
+ * ```
3164
+ *
3165
+ * A numeric segment (an array index) collapses to the empty string,
3166
+ * so a consumer wanting "Item 3" substitutes their own format.
3167
+ */
3168
+ readonly label: string;
3169
+ /**
3170
+ * Helper-text description for this field. It reads the registered
3171
+ * `description` first, falls back to the schema's own
3172
+ * `.describe('...')` value (both Zod majors expose that as
3173
+ * `schema.description`), and is `undefined` when neither is set.
3174
+ *
3175
+ * Useful for `aria-describedby`-linked help text, and distinct from
3176
+ * `label`: a description is longer prose, a label a short
3177
+ * presentational noun.
3178
+ */
3179
+ readonly description: string | undefined;
3180
+ /**
3181
+ * Placeholder hint for input affordance. Reads from the
3182
+ * registered `placeholder`; `undefined` otherwise.
3183
+ */
3184
+ readonly placeholder: string | undefined;
3185
+ /**
3186
+ * The full registered metadata payload, frozen, or an empty object
3187
+ * when nothing is registered. It is the escape hatch for
3188
+ * consumer-augmented keys, declared through module augmentation on
3189
+ * `FieldMetaPayload`:
3190
+ *
3191
+ * ```ts
3192
+ * declare module 'attaform/zod' {
3193
+ * interface FieldMetaPayload { tooltip?: string }
3194
+ * }
3195
+ * // template: {{ form.fields.email.meta.tooltip }}
3196
+ * ```
3197
+ */
3198
+ readonly meta: Readonly<FieldMetaPayload>;
3199
+ };
3200
+ /** `true` only for bare `unknown`, the storage shape of a preprocess or
3201
+ * coerce leaf. `any` is excluded, since it absorbs every test. */
3202
+ type IsUnknown<T> = IsAny<T> extends true ? false : unknown extends T ? true : false;
3203
+ /**
3204
+ * Leaf-shape dispatch table for `LeafWalker`. Each entry maps a walker
3205
+ * kind to the leaf type that walker produces at a primitive, Date or
3206
+ * non-recursable position. The lookup `LeafSchemeFor<T>[Kind]` threads
3207
+ * `T` through the leaf type when the kind needs it, as `field` does for
3208
+ * `FieldState<T>`; a kind that does not depend on `T` ignores it, as
3209
+ * `errors` does in always producing
3210
+ * `readonly ValidationError[] | undefined`.
3211
+ *
3212
+ * Adding a walker is one entry here plus a one-line wrapper alias
3213
+ * (`type FooShape<T> = LeafWalker<T, 'foo'>`), because the topology is
3214
+ * shared and only the leaf changes.
3215
+ *
3216
+ * The `errors` entry threads `T` to preserve `| undefined` when the
3217
+ * value type itself includes undefined (DU variant-only fields whose
3218
+ * lifted shape resolves to `X | undefined`). A statically-known leaf
3219
+ * collapses to `readonly ValidationError[]`
3220
+ * with no undefined, while a dynamic-key boundary (an array index, a
3221
+ * record key) re-introduces `| undefined` through the structural
3222
+ * index-signature channels.
3223
+ *
3224
+ * A preprocess or coerce leaf (StorageShape `unknown`) is statically
3225
+ * known too, and the IsUnknown filter keeps it on the non-optional
3226
+ * branch rather than letting `undefined extends unknown` sweep it into
3227
+ * the dynamic arm.
3228
+ *
3229
+ * An implementation-detail surface: consumers reach for `FieldStateMap`
3230
+ * or `FormErrorsSurface` instead.
3231
+ */
3232
+ interface LeafSchemeFor<T> {
3233
+ field: FieldState<T>;
3234
+ errors: IsUnknown<T> extends true ? readonly ValidationError[] : undefined extends T ? readonly ValidationError[] | undefined : readonly ValidationError[];
3235
+ }
3236
+ /**
3237
+ * Generic walk that produces a proxy shape over `T` with leaves
3238
+ * dispatched through `LeafSchemeFor<T>[Kind]`. The walk topology is
3239
+ * identical across walker kinds and only the leaf type differs: an
3240
+ * object maps homomorphically, an object-union merges by KeyofUnion, an
3241
+ * array indexes, and a primitive or Date terminates.
3242
+ *
3243
+ * `FieldStateMapEntry` and `ErrorsProxyShape` share it rather than
3244
+ * walking the same shape twice per useForm return type, once for the
3245
+ * fields proxy and once for the errors proxy. Sharing lets the bundled
3246
+ * `.d.ts` carry one walker body plus per-kind one-line wrappers,
3247
+ * halving what these two proxies contribute to recursive depth at a
3248
+ * consumer call site.
3249
+ *
3250
+ * `StripOptional` controls whether optional modifiers on input
3251
+ * properties are stripped at every recursion level. `true`, the
3252
+ * default, matches the fields proxy, where every known leaf carries a
3253
+ * `FieldState` wrapper whatever the source `?` says. `false` matches
3254
+ * the errors proxy, whose shape stays structurally identical to the
3255
+ * input form, optional keys included.
3256
+ *
3257
+ * An implementation-detail surface: consumers reach for `FieldStateMap`
3258
+ * or `FormErrorsSurface` instead.
3259
+ */
3260
+ type LeafWalker<T, Kind extends keyof LeafSchemeFor<unknown>, StripOptional extends boolean = true> = [T] extends [string | number | boolean | bigint | symbol | null | undefined | Date | File] ? LeafSchemeFor<T>[Kind] : [T] extends [ReadonlyArray<infer U>] ? {
3261
+ readonly [K: number]: LeafWalker<U, Kind, StripOptional>;
3262
+ } & ContainerSelfErrorsSlot<T, Kind> : [T] extends [object] ? string extends keyof T ? {
3263
+ readonly [K in keyof T]: LeafWalker<T[K], Kind, StripOptional>;
3264
+ } & ContainerSelfErrorsSlot<T, Kind> : [IsUnion<T>] extends [true] ? StripOptional extends true ? {
3265
+ readonly [K in KeyofUnion<T>]-?: DiscriminatedLeaf<T, K, Kind, StripOptional>;
3266
+ } & ContainerSelfErrorsSlot<T, Kind> : {
3267
+ readonly [K in KeyofUnion<T>]: DiscriminatedLeaf<T, K, Kind, StripOptional>;
3268
+ } & ContainerSelfErrorsSlot<T, Kind> : StripOptional extends true ? {
3269
+ readonly [K in keyof T]-?: LeafWalker<T[K], Kind, StripOptional>;
3270
+ } & ContainerSelfErrorsSlot<T, Kind> : {
3271
+ readonly [K in keyof T]: LeafWalker<T[K], Kind, StripOptional>;
3272
+ } & ContainerSelfErrorsSlot<T, Kind> : LeafSchemeFor<T>[Kind];
3273
+ /**
3274
+ * One key of a discriminated-union container in `LeafWalker`. A key
3275
+ * present and required in EVERY variant is universal, and its node is
3276
+ * always reachable. A variant-only or optional-in-some key is a dynamic
3277
+ * hop: its node is `undefined` while its variant is inactive, so it
3278
+ * carries NODE-optionality (`LeafWalker<...> | undefined`), not
3279
+ * VALUE-optionality (`LeafWalker<... | undefined>`).
3280
+ * `PresentValueOfUnion` strips the synthetic absent-variant `undefined`
3281
+ * so the present node resolves to the precise value type, while a
3282
+ * genuine `undefined` from an `optional` declaration survives.
3283
+ *
3284
+ * Universality is `[T] extends [Record<K, unknown>]`, true exactly when
3285
+ * the whole union satisfies "has K, required", which holds only when
3286
+ * every variant declares K as a required property.
3287
+ */
3288
+ type DiscriminatedLeaf<T, K extends PropertyKey, Kind extends keyof LeafSchemeFor<unknown>, StripOptional extends boolean> = [T] extends [Record<K, unknown>] ? LeafWalker<PresentValueOfUnion<T, K>, Kind, StripOptional> : LeafWalker<PresentValueOfUnion<T, K>, Kind, StripOptional> | undefined;
3289
+ /**
3290
+ * Intersection augmenting every container in the `form.errors` walker
3291
+ * with a `''` slot. At a container of depth 1 or deeper it is the
3292
+ * container-self sentinel, home to cross-field refine errors and
3293
+ * server-side container marks; at the root it addresses the literal
3294
+ * empty-key field. Gated on `Kind extends 'errors'` so `form.values`
3295
+ * and `form.fields` surfaces stay untouched. Carve-out for schemas that
3296
+ * legitimately declare a `''` field: the declared field type wins; at
3297
+ * runtime the two collide harmlessly (errors at the literal leaf and
3298
+ * any container-self errors share the slot via array concat).
3299
+ *
3300
+ * Global (root) errors are NOT this slot: they live at the root `[]`
3301
+ * and are read via the `errors([])` call-form, never `errors['']`.
3302
+ */
3303
+ type ContainerSelfErrorsSlot<T, Kind> = Kind extends 'errors' ? '' extends keyof T ? unknown : {
3304
+ readonly ['']: readonly ValidationError[];
3305
+ } : unknown;
3306
+ /**
3307
+ * Recursive type behind `form.fields`. Leaf-aware branching: at
3308
+ * a primitive path (string, number, boolean, bigint, Date) the proxy
3309
+ * returns a `FieldState`; at a container path (object, array) it
3310
+ * descends without injecting leaf keys.
3311
+ *
3312
+ * Field-name collisions at depth 2+ resolve unambiguously: a schema
3313
+ * field literally named `dirty` at depth 2 is reachable as a
3314
+ * descent target (`form.fields.address.dirty` returns the
3315
+ * FieldState for `address.dirty`). Reading `dirty` AT the
3316
+ * leaf-view (`form.fields.address.dirty.dirty`) reads the leaf's own
3317
+ * dirty boolean, because a path segment and a leaf prop occupy
3318
+ * different proxy depths.
3319
+ *
3320
+ * The runtime implementation queries `schema.isLeafAtPath(segments)`
3321
+ * at every step, and this type approximates that with
3322
+ * "T extends primitive". The two agree for typical schemas; an exotic
3323
+ * adapter-defined leaf kind, a custom `Date`-like, may need a runtime
3324
+ * check, and the runtime is authoritative.
3325
+ *
3326
+ * The mapped type strips optional flags (`-?:`) because the field-
3327
+ * state surface always exposes a record per known leaf, regardless
3328
+ * of whether the schema field is declared `.optional()`. An optional
3329
+ * schema means the VALUE can be undefined, which
3330
+ * `FieldState<string | undefined>` carries, while the wrapper itself
3331
+ * always exists. Without the strip, `form.fields.notes` on a
3332
+ * `notes?: string` would type as `FieldState<...> | undefined` and
3333
+ * force consumers to optional-chain through every reactive read.
3334
+ *
3335
+ * For discriminated-union containers the object branch uses
3336
+ * `[T] extends [object]` (non-distributive) plus
3337
+ * `KeyofUnion` / `ValueOfUnion` to merge variant key sets, so a
3338
+ * refrigerated-only `form.fields.cargo.tempMinC` is reachable whatever
3339
+ * the active variant, typed as `FieldState<number | undefined>`. That
3340
+ * matches the runtime's stub `FieldState` for an inactive-variant
3341
+ * path.
3342
+ */
3343
+ type FieldStateMapEntry<T> = LeafWalker<T, 'field'>;
3344
+ /**
3345
+ * Result of the `form.fields(path)` string call form. A path the schema
3346
+ * declares resolves to its `FieldState`: a leaf's value type, or a
3347
+ * container's rolled-up aggregate, where every FieldState property
3348
+ * exists whatever the value type. A path the schema lacks resolves to
3349
+ * `undefined`, because a typo is not a field and the runtime hands back
3350
+ * `undefined` rather than a phantom stub. A non-literal `string` could
3351
+ * be either, so it widens to `FieldState<unknown> | undefined`.
3352
+ *
3353
+ * Named rather than inlined into the `FieldStateMap` call signature so
3354
+ * the conditional is one cached type. That keeps two
3355
+ * structurally-identical `FieldStateMap` instantiations, such as the
3356
+ * unified `attaform/zod` return and `UseFormReturnV4`, relatable rather
3357
+ * than collapsing into a nominal "two different types with this name"
3358
+ * mismatch.
3359
+ */
3360
+ type FieldCallResult<Form, P extends string> = [P] extends [FlatPath<Form>] ? FieldState<NestedType<Form, P>> : string extends P ? FieldState<unknown> | undefined : undefined;
3361
+ /**
3362
+ * Type of `form.fields`, a leaf-aware drillable callable Proxy. At a
3363
+ * leaf path it resolves to a `FieldState<Value>`; at a container path
3364
+ * it returns a sub-proxy you can keep drilling.
3365
+ *
3366
+ * Augmented with the callable signatures so dot-access and function-
3367
+ * call coexist on the same identifier:
3368
+ *
3369
+ * ```ts
3370
+ * form.fields.email.value // string (leaf-prop on FieldState)
3371
+ * form.fields('email').value // function-call (dynamic / programmatic)
3372
+ * form.fields(['users', 0, 'name']) // path-array form
3373
+ * form.fields() // root proxy
3374
+ * ```
3375
+ *
3376
+ * Single-bracket dotted access (`form.fields['address.city']`) is
3377
+ * deliberately NOT supported, because JS object semantics read the
3378
+ * dotted string as a single key. Use chained dot or bracket access, or
3379
+ * the callable form.
3380
+ */
3381
+ type FieldStateMap<Form extends GenericForm> = LeafWalker<Form, 'field'> & {
3382
+ /**
3383
+ * String-path form (dynamic / programmatic). See {@link FieldCallResult}:
3384
+ * a path the schema declares resolves to its precise `FieldState`, a
3385
+ * path it lacks to `undefined`, and a non-literal `string` to
3386
+ * `FieldState<unknown> | undefined`.
3387
+ */
3388
+ <P extends string>(path: P): FieldCallResult<Form, P>;
3389
+ /**
3390
+ * Tuple-segment form. The segment-array spelling of the string
3391
+ * call form above, for a path built from variables. The two resolve
3392
+ * to the same proxy, so `form.fields(['users', i, 'email'])` and
3393
+ * `form.fields(\`users.${i}.email\`)` are interchangeable.
3394
+ *
3395
+ * Resolves to that path's `FieldState`, which at a container is its
3396
+ * rolled-up aggregate (`displayState`, `valid`, `dirty`, ... all
3397
+ * exist regardless of the value type), exactly as the string form
3398
+ * does. Note this is the CALL form: it hands back the path's state,
3399
+ * not a drillable sub-proxy. To walk into a container's children use
3400
+ * dot/bracket access (`form.fields.users[i].email`), which is what
3401
+ * descends.
3402
+ */
3403
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>)): FieldState<NestedType<Form, JoinSegments<S>>>;
3404
+ /**
3405
+ * Dynamic-array fallback for a caller passing `Path`-typed runtime
3406
+ * segments, as forwarding `RegisterValue.segments` to resolve a field
3407
+ * view does. The path may not resolve, so the result widens with
3408
+ * `| undefined`; cast when the value type is known.
3409
+ */
3410
+ (segments: ReadonlyArray<string | number>): FieldState<unknown> | undefined;
3411
+ /**
3412
+ * The no-arg call returns the root FieldState, the same as
3413
+ * `form.fields([])`, aggregating over the whole form as one
3414
+ * conjunction across every active-variant leaf.
3415
+ */
3416
+ (): FieldState<Form>;
3417
+ };
3418
+ /**
3419
+ * Untyped error map keyed by dotted-string path: the same data
3420
+ * `form.errors` exposes, as a plain record. The case for it is routing
3421
+ * API errors that may land on paths the form's TypeScript type does not
3422
+ * know about.
3423
+ */
3424
+ type FormErrorRecord = Record<string, ValidationError[]>;
3425
+ /**
3426
+ * Type of `form.errors`. Leaf-aware drillable callable Proxy. At a
3427
+ * statically-known leaf the proxy resolves to `readonly ValidationError[]`
3428
+ * (empty array when no errors land); at dynamic boundaries (array
3429
+ * indices, record keys, DU variant-only fields) it resolves to
3430
+ * `readonly ValidationError[] | undefined`. At a container path it
3431
+ * returns a sub-proxy you can keep drilling.
3432
+ *
3433
+ * Dot/bracket access mirrors the schema shape:
3434
+ *
3435
+ * ```ts
3436
+ * form.errors.email // readonly ValidationError[] (static leaf)
3437
+ * form.errors.user.profile.email // readonly ValidationError[] (chained static leaves)
3438
+ * form.errors.posts[3]?.title // readonly ValidationError[] | undefined (past array boundary)
3439
+ * form.errors.address // sub-proxy (a container, so descend)
3440
+ * ```
3441
+ *
3442
+ * Callable form for dynamic / programmatic paths:
3443
+ *
3444
+ * ```ts
3445
+ * form.errors('user.profile.email') // dotted-string
3446
+ * form.errors(['user', 'profile', 'email']) // path-array
3447
+ * form.errors() // root proxy
3448
+ * ```
3449
+ *
3450
+ * Single-bracket dotted access (`form.errors['user.profile.email']`) is
3451
+ * intentionally NOT supported: JS object semantics read the dotted string
3452
+ * as one key, which lands on a path that does not exist. Use chained
3453
+ * dot/bracket access, or the callable form.
3454
+ *
3455
+ * The underlying shape mirrors the schema. A statically-known primitive
3456
+ * leaf exposes `readonly ValidationError[]`, always an array and empty
3457
+ * when no errors land; a leaf whose value type itself includes `undefined`
3458
+ * (a DU variant-only field) keeps the `| undefined` branch. A container
3459
+ * exposes a sub-shape to keep drilling, and an array exposes
3460
+ * numeric-indexed sub-shapes where reading an index introduces
3461
+ * `| undefined` through noUncheckedIndexedAccess. The callable signatures
3462
+ * are intersected in, so dot access and a function call coexist on one
3463
+ * identifier.
3464
+ */
3465
+ type FormErrorsSurface<Form> = ErrorsProxyShape<Form> & {
3466
+ (path: string): readonly ValidationError[];
3467
+ /**
3468
+ * Tuple-segment form. Validated against `FlatPath<Form>` so literal
3469
+ * tuples that don't resolve to a known path fail at the call site.
3470
+ * Dynamic `Path`-typed inputs hit the untyped fallback overload below.
3471
+ */
3472
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>)): readonly ValidationError[];
3473
+ (segments: ReadonlyArray<string | number>): readonly ValidationError[];
3474
+ /**
3475
+ * No-arg call returns the whole-form error aggregate, the same as
3476
+ * `form.meta.errors` and `form.errors([])`: every field error plus
3477
+ * the root bucket. `form.errors(path)` is uniform at every depth,
3478
+ * including `[]`, so the root is no exception. For the root bucket
3479
+ * alone (a root `.refine()`, a path-less `setErrors`), read
3480
+ * `form.meta.ownErrors`. Always a readonly array; empty when the form
3481
+ * has no errors.
3482
+ */
3483
+ (): readonly ValidationError[];
3484
+ };
3485
+ /**
3486
+ * Implementation-detail walker backing `form.errors` typed proxy.
3487
+ * A thin alias over `LeafWalker<T, 'errors', false>`, whose topology is
3488
+ * defined once at `LeafWalker` and parameterized through
3489
+ * `LeafSchemeFor`. The `false` preserves optional-key modifiers, since
3490
+ * the errors proxy mirrors the input shape including `?`, where the
3491
+ * fields proxy alias strips them under the default `true`.
3492
+ *
3493
+ * Exported so the bundled `.d.ts` references a single alias body
3494
+ * rather than re-emitting the full union-aware recursion at every
3495
+ * consumer call site that types `form.errors`. Multiple useForm
3496
+ * instances in one scope otherwise compound this into TS2589
3497
+ * territory. Consumers should reach for `FormErrorsSurface` instead.
3498
+ */
3499
+ type ErrorsProxyShape<T> = LeafWalker<T, 'errors', false>;
3500
+ /**
3501
+ * Type of `form.values`: a drillable readonly callable proxy. Unlike
3502
+ * `form.errors` and `form.fields`, containers here are USEFUL
3503
+ * terminals, because every container in `values` carries real data
3504
+ * where in errors and fields it carries a derivation:
3505
+ * `form.values.address` returns the actual `{ city, ... }` subtree and
3506
+ * keeps drilling.
3507
+ *
3508
+ * ```ts
3509
+ * form.values.email // string (the value)
3510
+ * form.values.address // { city, ... } an object, drillable
3511
+ * form.values.address.city // string (chained descent)
3512
+ * form.values('address.city') // function-call (dynamic / programmatic)
3513
+ * form.values(['address', 'city']) // path-array form
3514
+ * form.values() // the whole form value (root)
3515
+ * ```
3516
+ *
3517
+ * The two shapes answer different questions. Dot access is the
3518
+ * REACTIVE view: per-key tracking, no copying, always the live value.
3519
+ * The call form returns a SNAPSHOT: a detached plain object that keeps
3520
+ * what it held at capture time, so it is the shape to hand an async
3521
+ * call, a serialiser, or a diff.
3522
+ *
3523
+ * ```ts
3524
+ * const before = form.values() // detached; survives later writes
3525
+ * await api.save(before) // cannot mutate underneath the call
3526
+ * form.values.email // reactive read; use this in computed
3527
+ * ```
3528
+ *
3529
+ * Prefer dot access inside a `computed`, `watchEffect`, or template:
3530
+ * it tracks the one key you read, where the call form depends on the
3531
+ * whole form and re-runs the consumer on any change.
3532
+ *
3533
+ * The copy is deep across plain objects and arrays. Non-plain
3534
+ * instances (Map, Set, File, Date) are shared by reference, matching
3535
+ * `JSON.stringify` behaviour; `structuredClone(form.values())` gives a
3536
+ * fully detached copy when one is needed.
3537
+ *
3538
+ * Single-bracket dotted access (`form.values['address.city']`) is
3539
+ * deliberately NOT supported, because JS object semantics read the
3540
+ * dotted string as a single key. Use chained dot or bracket access, or
3541
+ * the callable form.
3542
+ *
3543
+ * The chained shape applies the discriminated-union lift via
3544
+ * `LiftedValueShape<F>` so per-variant keys are reachable without
3545
+ * narrowing first: `form.values.cargo.permitNumber` types as
3546
+ * `string | undefined` whatever the active cargo variant, matching the
3547
+ * runtime, where plain JS object access on a missing variant key
3548
+ * returns `undefined`. The strict-variant shape is still required on
3549
+ * the WRITE side, where `setValue` and `defaultValues` use the unlifted
3550
+ * `WriteShape` so a partial or cross-variant object cannot reach the
3551
+ * form.
3552
+ */
3553
+ type ValuesSurface<F> = Readonly<LiftedValueShape<F>> & {
3554
+ (path: string): unknown;
3555
+ (path: ReadonlyArray<string | number>): unknown;
3556
+ (): Readonly<F>;
3557
+ };
3558
+ /**
3559
+ * Reactive form-level flags, counters, and aggregates returned as
3560
+ * `form.meta`. "Meta" because every other surface (`form.values`,
3561
+ * `form.errors`, `form.fields`) is data-shaped, where `form.meta`
3562
+ * holds facts derived ABOUT the form.
3563
+ *
3564
+ * Read its fields directly, with no `.value`: they auto-unwrap inside
3565
+ * the reactive object:
3566
+ *
3567
+ * ```vue
3568
+ * <button :disabled="form.meta.submitting">Save</button>
3569
+ * ```
3570
+ *
3571
+ * Watch a single field via the getter form:
3572
+ *
3573
+ * ```ts
3574
+ * watch(() => form.meta.submitting, (value) => …)
3575
+ * ```
3576
+ *
3577
+ * Per-field state (touched, dirty, errors) lives behind
3578
+ * `form.fields.<path>`; this is the aggregate view across the
3579
+ * whole form.
3580
+ *
3581
+ * Read-only at runtime: an assignment is refused with a dev-console
3582
+ * warning and never lands, because Attaform does not throw from a read
3583
+ * surface. Destructuring snapshots the current values, so reach for
3584
+ * `toRefs()` when you need reactive handles to individual fields.
3585
+ */
3586
+ type FormMeta<F = unknown> = FieldState<F> & {
3587
+ /**
3588
+ * `true` while a `handleSubmit`-produced submit handler is running.
3589
+ * Covers both the validation phase and your async submit callback.
3590
+ * Useful for disabling the submit button.
3591
+ */
3592
+ readonly submitting: boolean;
3593
+ /**
3594
+ * How many times the submit handler has been invoked, regardless of
3595
+ * outcome (validation failure, callback success, callback throw).
3596
+ * Useful for "show errors after first submit attempt" UX.
3597
+ */
3598
+ readonly submissionAttempts: number;
3599
+ /**
3600
+ * How many times wizard navigation (`wizard.next`, `wizard.back`,
3601
+ * `wizard.goTo`) has actually departed this form. Bumped on real
3602
+ * departures only: no-ops like `back()` from the first step, a
3603
+ * same-key `goTo`, or a `next()` blocked by failed activation leave
3604
+ * the counter at its prior value.
3605
+ *
3606
+ * A pure introspection counter: it serves "this form has been visited
3607
+ * and left" UX (analytics, prior-step badges) and drives no part of
3608
+ * the display heuristic. Reveal-on-submit runs entirely through
3609
+ * `submissionAttempts`, which `wizard.handleSubmit` bumps on every
3610
+ * form, since it always validates the whole step list.
3611
+ *
3612
+ * `submissionAttempts` counts `handleSubmit` passes alone, so a wizard
3613
+ * departure and a form submission stay separately introspectable.
3614
+ * `form.validate()` is a read-only inspection primitive and bumps no
3615
+ * counter at all.
3616
+ *
3617
+ * Cleared by `form.reset()`.
3618
+ */
3619
+ readonly departAttempts: number;
3620
+ /**
3621
+ * The error thrown or rejected by the most recent submit callback (or
3622
+ * its `onError` handler), coerced to a real `Error` (a non-`Error`
3623
+ * throw keeps its origin on `.cause`). Cleared to `null` at the start
3624
+ * of each new submission attempt; stays `null` on success.
3625
+ *
3626
+ * The submit handler does NOT re-throw: its returned promise always
3627
+ * resolves, so binding it to `@submit` never manufactures a window
3628
+ * unhandledrejection. This is the channel for an UNEXPECTED submit
3629
+ * failure, a thrown exception or a rejected promise, read the same way
3630
+ * in a template and after an imperative `await submit()`. An EXPECTED
3631
+ * rejection handled the documented way (`setErrors(...)` then
3632
+ * `return`, no throw) surfaces through the error store and `onError`
3633
+ * instead, leaving `submitError` null. Like `hydrateError` it stays
3634
+ * distinct from the curated user-error store, so render it where you
3635
+ * choose:
3636
+ *
3637
+ * ```vue
3638
+ * <p v-if="form.meta.submitError">{{ form.meta.submitError.message }}</p>
3639
+ * ```
3640
+ */
3641
+ readonly submitError: Error | null;
3642
+ /**
3643
+ * Scalar mirror of `meta.errors.length`. Read it from templates and
3644
+ * `watch()` without indexing the underlying array.
3645
+ *
3646
+ * It tracks `errors.length` exactly, through the same computed graph,
3647
+ * so a `watch(form.meta.errorCount, ...)` fires exactly when the
3648
+ * aggregate error count changes.
3649
+ */
3650
+ readonly errorCount: number;
3651
+ /**
3652
+ * `true` once a `handleSubmit` callback has resolved without throwing
3653
+ * AND left no errors behind. It is independent of
3654
+ * `submissionAttempts`: a failed submit (a validation failure, a
3655
+ * callback rejection, or a callback calling `setErrors` and returning)
3656
+ * increments attempts and leaves `submitted` false. A template reads
3657
+ * it as "the form has been
3658
+ * submitted successfully at least once."
3659
+ *
3660
+ * The error check is scoped to the user-error layer (`setErrors` /
3661
+ * `clearErrors`): the documented server-rejection pattern
3662
+ * (`setErrors(response.errors); return`) is a failed submit, so it does
3663
+ * not flip `submitted`. A background async refinement that writes a
3664
+ * schema error mid-submit does not retroactively fail it.
3665
+ *
3666
+ * Cleared by `form.reset()` alongside `submissionAttempts` and
3667
+ * `submitError`. For "the user has attempted a submit," read
3668
+ * `submissionAttempts > 0` directly.
3669
+ */
3670
+ readonly submitted: boolean;
3671
+ /**
3672
+ * Per-`useForm()`-call identity. Stable for the lifetime of one
3673
+ * `useForm()` call; new on every fresh mount. Orthogonal to
3674
+ * `form.key`: the key identifies a SHARED FormStore (so two
3675
+ * `useForm({ key: 'signup' })` calls return the same store and the
3676
+ * same key), while `instanceId` identifies THIS call site, which is
3677
+ * what disambiguates two forms sharing a key, a sidebar and a main
3678
+ * view rendering the same form.
3679
+ *
3680
+ * The format is opaque, derived from Vue 3.5+ `useId()`. Treat it as
3681
+ * identity rather than state: do not parse it, compare it ordinally,
3682
+ * or persist it.
3683
+ *
3684
+ * Common patterns:
3685
+ *
3686
+ * - **Devtools panels** disambiguating shared-key form mounts.
3687
+ * - **Telemetry / logging hooks** tagging events with which mount
3688
+ * triggered them.
3689
+ * - **E2E test selectors** stamping `data-form-id={form.meta.instanceId}`
3690
+ * onto a wrapper to assert which form was focused.
3691
+ * - **Vue `:key`** for a keyed list of dynamically-rendered forms, a
3692
+ * drag-reorder say, since identity is stable per useForm() call.
3693
+ */
3694
+ readonly instanceId: string;
3695
+ };
3696
+ /**
3697
+ * Read-only view returned by `form.blankPaths.value`. Exposes lookup
3698
+ * (`.has`), aggregate (`.size`), and iteration over the form's
3699
+ * blank-marked paths.
3700
+ *
3701
+ * `.has(input)` and the iterator yield consistent results across both
3702
+ * input conventions the library accepts everywhere a path is named:
3703
+ *
3704
+ * - **Dotted string**: `'user.email'`, matching what `register('user.email')`
3705
+ * or `setValue('items.0.sku', …)` accept. Convenient when no segment
3706
+ * contains a literal dot.
3707
+ * - **Array form**: `['user', 'email']`, mirroring `register(['user', 'email'])`.
3708
+ * Required when a single segment contains literal dots, so
3709
+ * `['address.primary']` for a top-level key named `address.primary`,
3710
+ * which the dotted form would parse as two segments.
3711
+ *
3712
+ * Iteration yields `Path` arrays, so a consumer building debug UI or
3713
+ * persisting the set never has to guess whether a dot in a segment is a
3714
+ * separator or part of the name.
3715
+ *
3716
+ * Mutating the view does nothing. Writes go through
3717
+ * `setValue(path, unset)`, `markBlank()` on a register binding, or the
3718
+ * directive's input listener on a numeric clear.
3719
+ */
3720
+ interface BlankPathsView {
3721
+ /** Number of blank-marked paths. */
3722
+ readonly size: number;
3723
+ /**
3724
+ * `true` when the path is in the blank set. Accepts dotted-string
3725
+ * form (parsed by [[parseDottedPath]]) or the array form.
3726
+ */
3727
+ has(input: string | Path): boolean;
3728
+ /** Snapshot of all blank-marked paths as segment arrays. */
3729
+ values(): readonly Path[];
3730
+ /** Iterates the blank-marked paths as segment arrays. */
3731
+ [Symbol.iterator](): IterableIterator<Path>;
3732
+ }
3733
+ /**
3734
+ * The no-arg `form.record()` call form, present only when the form root
3735
+ * is itself an open record (`z.record(K, V)`). `string extends keyof
3736
+ * Form` is the open-keyset probe: true for `Record<string, V>`, false
3737
+ * for a fixed `z.object` shape. On a fixed object this resolves to
3738
+ * `unknown`, which contributes nothing to the intersection in `record`
3739
+ * below, so the no-arg call form simply does not exist there (and
3740
+ * `form.record()` stays a compile error, as it should when the root has
3741
+ * a closed key set). On a record root it mirrors the path-addressed
3742
+ * `record(path)` overload: one `FieldState` per entry, keyed by the
3743
+ * record's own runtime keys.
3744
+ */
3745
+ type RootRecordView<Form> = string extends keyof Form ? Form extends Record<string, infer RootValue> ? () => Readonly<Record<string, FieldState<RootValue>>> : unknown : unknown;
3746
+ /**
3747
+ * The object returned by `useForm`. Holds every reactive ref, write
3748
+ * helper, and lifecycle method bound to one form.
3749
+ *
3750
+ * ```ts
3751
+ * const form = useForm({ schema })
3752
+ * form.register('email') // bind to <input v-register>
3753
+ * form.values.email // current value (proxy, no .value)
3754
+ * form.fields.email.dirty // per-field flags
3755
+ * form.errors.email // readonly ValidationError[]
3756
+ * form.setValue('email', 'a@b.c')
3757
+ * form.handleSubmit(onSubmit) // returns a submit handler
3758
+ * form.meta.submitting // form-level reactive flag
3759
+ * ```
3760
+ *
3761
+ * Three generic slots split the write view, parse view, and read view:
3762
+ *
3763
+ * - `Form` is the **input / write shape** (`z.input<Schema>`), used by
3764
+ * `setValue`, `defaultValues` and `register`'s write side. Loose: a
3765
+ * preprocess path accepts `unknown` at the write boundary, and a
3766
+ * defaulted field accepts its inner type optionally.
3767
+ *
3768
+ * - `GetValueFormType` is the **output / parsed shape**
3769
+ * (`z.output<Schema>`), used by `handleSubmit`'s `onSubmit` callback
3770
+ * and `form.parse()`'s success payload: the shape after refinements
3771
+ * have fired and transforms have run.
3772
+ *
3773
+ * - `ReadForm` is the **read / storage shape**, used by `values`,
3774
+ * `fields`, `register`'s read side and `toRef`. Precise per key: at a
3775
+ * write-boundary wrapper (`default`, `prefault`, `catch`, `readonly`,
3776
+ * `preprocess`) the value is `z.output<Inner>`, because the default
3777
+ * has fired and preprocess has normalized; at a transform or pipe it
3778
+ * stays `z.input<Inner>`, because transforms are deferred until
3779
+ * parse. A schema-agnostic call site defaults it to `Form`.
3780
+ *
3781
+ * For schemas without write-boundary wrappers or transforms the three
3782
+ * shapes coincide.
3783
+ */
3784
+ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends GenericForm = Form, ReadForm extends GenericForm = Form, K extends FormKey = FormKey> = {
3785
+ /**
3786
+ * Wraps your submit logic with validation and error routing.
3787
+ *
3788
+ * ```ts
3789
+ * const onSubmit = form.handleSubmit(
3790
+ * (data) => api.signup(data),
3791
+ * (errors) => console.log(errors),
3792
+ * )
3793
+ * // Bind the returned handler: <form @submit="onSubmit">
3794
+ * ```
3795
+ *
3796
+ * `data` is the strictly-typed parsed value: refinements have fired
3797
+ * and `.transform()`s have run, so the payload matches
3798
+ * `z.output<Schema>`. Where the input type differs from the output
3799
+ * type, as in `z.string().transform(v => v.length > 10)`, `data` is
3800
+ * the output shape while `form.values` stays the input shape.
3801
+ */
3802
+ handleSubmit: HandleSubmit<GetValueFormType>;
3803
+ /**
3804
+ * Reactive readonly proxy over the form's storage value, read
3805
+ * identically in script and template: no `.value`, no auto-unwrap
3806
+ * rules, the Pinia setup-store pattern.
3807
+ *
3808
+ * ```vue
3809
+ * <script setup>
3810
+ * const form = useForm({ schema, key: 'login' })
3811
+ * </script>
3812
+ *
3813
+ * <template>
3814
+ * <p>{{ form.values.email }}</p>
3815
+ * <p>{{ form.values.address.city }}</p>
3816
+ * </template>
3817
+ * ```
3818
+ *
3819
+ * Writes are blocked at the proxy boundary: go through `setValue`,
3820
+ * the directive, or a field-array helper. The slim-primitive write
3821
+ * gate is the only path into storage.
3822
+ *
3823
+ * Reads reflect what's storable: enum-typed slots widen to their
3824
+ * primitive supertype (`string`), so refinement-invalid but
3825
+ * structurally-valid values are visible. Storage holds the
3826
+ * `z.input<Schema>` shape and `.transform()`s have NOT run, so on a
3827
+ * `z.string().transform(v => v.length > 10)` the value reads as
3828
+ * `string`, not `boolean`. Reach for `handleSubmit` or `form.parse()`
3829
+ * when you need the post-transform output shape.
3830
+ */
3831
+ values: ValuesSurface<WriteShape<ReadForm>>;
3832
+ /**
3833
+ * Reactive per-field state proxy, a Pinia-style nested object: read
3834
+ * leaf properties (`value`, `dirty`, `touched`, `errors`, `blurred`,
3835
+ * `focused`, `blank`) directly off the field's path:
3836
+ *
3837
+ * ```vue
3838
+ * <p v-if="form.fields.email.touched && form.fields.email.errors.length">
3839
+ * {{ form.fields.email.errors[0].message }}
3840
+ * </p>
3841
+ * <p>City dirty? {{ form.fields.address.city.dirty }}</p>
3842
+ * ```
3843
+ *
3844
+ * The same proxy descends at every level: `address` reads the
3845
+ * FieldState for the address object, and `address.city` descends into
3846
+ * the nested leaf.
3847
+ *
3848
+ * Leaf values follow the slim WriteShape contract: enum-typed leaves
3849
+ * widen to their primitive supertype, and the leaf value reflects
3850
+ * the `z.input<Schema>` shape (transforms deferred until parse).
3851
+ * The errors array, dirty flag, focus state, etc. are unaffected.
3852
+ *
3853
+ * Shadowing: at depth 2+, FieldState keys (`dirty`, `touched`,
3854
+ * `errors`, `blank`, `focused`, `blurred`, `value`,
3855
+ * `original`, `pristine`, `connected`, `updatedAt`, `path`) win
3856
+ * over schema field names. Top-level fields are NOT shadowed.
3857
+ * Document edge case; rename the offending schema field if the
3858
+ * collision matters.
3859
+ */
3860
+ fields: FieldStateMap<WriteShape<ReadForm>>;
3861
+ /**
3862
+ * Write to the form programmatically. Two forms:
3863
+ *
3864
+ * - `setValue(value)` replaces the whole form.
3865
+ * - `setValue(path, value)` writes at one path.
3866
+ *
3867
+ * Either takes a callback in place of `value` to derive the next
3868
+ * value from the previous one:
3869
+ *
3870
+ * ```ts
3871
+ * form.setValue('count', (prev) => prev + 1)
3872
+ * form.setValue((prev) => ({ ...prev, name: 'Ada' }))
3873
+ * ```
3874
+ *
3875
+ * Returns `true` when the write is accepted. A `false` means the
3876
+ * value did not match the slot's expected type, as writing a number
3877
+ * to a string field does not, and form state is unchanged. A
3878
+ * refinement-level mismatch (an out-of-enum value, a failing format
3879
+ * check) DOES succeed and surfaces as a field error instead.
3880
+ */
3881
+ setValue: {
3882
+ /**
3883
+ * Replace the whole form. Pass a value or a callback receiving
3884
+ * the previous form.
3885
+ *
3886
+ * ```ts
3887
+ * form.setValue({ name: 'Ada', email: 'a@b.c' })
3888
+ * form.setValue((prev) => ({ ...prev, name: 'Ada' }))
3889
+ * ```
3890
+ *
3891
+ * Returns `true` when the write was accepted, `false` when the
3892
+ * value didn't match the expected shape (e.g. wrong primitive
3893
+ * type at a leaf). Refinement-level mismatches (out-of-enum
3894
+ * values, failing format checks, etc.) succeed and surface as
3895
+ * field errors instead.
3896
+ */
3897
+ <Value extends SetValuePayload<DefaultValuesShape<Form>, WriteShape<Form>>>(value: Value): boolean;
3898
+ /**
3899
+ * Write at a specific path. Pass a value or a callback receiving
3900
+ * the previous value at that path.
3901
+ *
3902
+ * ```ts
3903
+ * form.setValue('email', 'a@b.c')
3904
+ * form.setValue('count', (prev) => prev + 1)
3905
+ * form.setValue('income', unset) // numeric leaf marked displayed-empty
3906
+ * ```
3907
+ *
3908
+ * Returns `true` when the write was accepted, `false` when the
3909
+ * value didn't match the slot's expected primitive type.
3910
+ * Refinement-level mismatches succeed and surface as field
3911
+ * errors. Pass the `unset` symbol at any primitive leaf to mark
3912
+ * it blank (storage holds the slim default; UI displays
3913
+ * empty; submit raises "No value supplied" for required schemas).
3914
+ */
3915
+ <Path extends FlatPath<Form>, Value extends PathSetValuePayload<NestedType<Form, Path>>>(path: Path, value: Value): boolean;
3916
+ /**
3917
+ * Tuple-segment form, equivalent to the dotted-string overload and
3918
+ * the one to reach for when a path is built from variables or
3919
+ * arrays: `form.setValue([prefix, 'line1'], 'value')`. The resolved
3920
+ * leaf type is exact, matching the dotted-string form.
3921
+ */
3922
+ <const S extends ReadonlyArray<string | number>, Value extends PathSetValuePayload<NestedType<Form, JoinSegments<S>>>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>), value: Value): boolean;
3923
+ };
3924
+ /**
3925
+ * Reactive validation status. Re-runs whenever the form (or the
3926
+ * subtree at `path`) mutates. The returned ref carries a `pending`
3927
+ * flag, so gate on `!status.value.pending` before reading `success`
3928
+ * or `errors`.
3929
+ *
3930
+ * ```ts
3931
+ * const status = form.validate()
3932
+ * watchEffect(() => {
3933
+ * if (status.value.pending) return
3934
+ * if (!status.value.success) console.log(status.value.errors)
3935
+ * })
3936
+ * ```
3937
+ *
3938
+ * A stale in-flight run is dropped automatically: the ref settles
3939
+ * only to results from the most recent call.
3940
+ */
3941
+ validate: (path?: FlatPath<Form>) => Readonly<Ref<ReactiveValidationStatus<Form>>>;
3942
+ /**
3943
+ * Resolve once every in-flight async `register({ transforms })` run
3944
+ * has settled, either globally or, with `path`, at or under it.
3945
+ * Resolve-never-reject: a transform that throws still settles the
3946
+ * field (its failure lands on `field.transformError`), so the returned
3947
+ * promise always resolves.
3948
+ *
3949
+ * `handleSubmit` awaits this internally before parsing, so a submit
3950
+ * fired the instant after an async transform still validates the
3951
+ * resolved value. Reach for it directly when you need the same
3952
+ * guarantee outside submit, before reading `form.values` in an
3953
+ * imperative flow or a test:
3954
+ *
3955
+ * ```ts
3956
+ * input.value = ' a@b.com '
3957
+ * await form.settleTransforms('email')
3958
+ * // form.values.email is now the normalized value
3959
+ * ```
3960
+ */
3961
+ settleTransforms: (path?: FlatPath<Form>) => Promise<void>;
3962
+ /**
3963
+ * Imperative one-shot parse. It runs the full pipeline, refinements,
3964
+ * `.transform()`s and blank-required composition, against the current
3965
+ * form snapshot and RETAINS the parsed data.
3966
+ *
3967
+ * Storage holds the honest input view: the values you wrote, with
3968
+ * preprocess normalization applied and `.transform()` deferred. Where
3969
+ * the input type differs from the output type, as on a
3970
+ * `z.string().transform(v => v.length > 10)`, `form.values.X` is the
3971
+ * input shape and `(await form.parse()).data?.X` the output shape.
3972
+ *
3973
+ * ```ts
3974
+ * const result = await form.parse()
3975
+ * if (result.success) {
3976
+ * // result.data matches z.output<typeof schema>
3977
+ * } else {
3978
+ * // result.errors is the validation failure list
3979
+ * }
3980
+ * ```
3981
+ *
3982
+ * By default the call is a PURE read: nothing is written to
3983
+ * `form.errors`, and in-flight per-field validation runs are left
3984
+ * alone. Pass `{ commit: true }` to make the run authoritative: the
3985
+ * verdict is committed to the error store at the parsed scope and any
3986
+ * in-flight per-field runs are cancelled first, mirroring
3987
+ * `handleSubmit`, so `await form.parse('email', { commit: true })`
3988
+ * lands a deterministic view of `form.errors.email`:
3989
+ *
3990
+ * ```ts
3991
+ * const result = await form.parse({ commit: true })
3992
+ * if (!result.success) showErrors(result.errors)
3993
+ * ```
3994
+ *
3995
+ * Always async, and there is no synchronous variant by design: a
3996
+ * schema can carry async refinements or transforms, so a sync parse
3997
+ * would silently miss them the moment one is added. One always-
3998
+ * awaited `parse` closes that category of bug entirely. The returned
3999
+ * promise never rejects, since a thrown adapter lands as a
4000
+ * `success: false` response. Pass a path to parse a subtree only;
4001
+ * `meta.validating` flips `true` while the promise is in flight.
4002
+ */
4003
+ parse: {
4004
+ (path?: FlatPath<Form>, options?: ParseOptions): Promise<ValidationResponse<GetValueFormType>>;
4005
+ (options: ParseOptions): Promise<ValidationResponse<GetValueFormType>>;
4006
+ };
4007
+ /**
4008
+ * Bind a path to a native input via `v-register`. Returns a
4009
+ * `RegisterValue` carrying the live ref and event handlers the
4010
+ * directive needs.
4011
+ *
4012
+ * ```vue
4013
+ * <input v-register="form.register('email')" />
4014
+ * <input v-register="form.register('username', { transforms: [trim] })" />
4015
+ * ```
4016
+ *
4017
+ * It also accepts a segment-array form for a caller building paths
4018
+ * dynamically, above all inside a `v-for` over a prefix variable,
4019
+ * where dotted-string concatenation widens the prefix's literal union
4020
+ * to plain `string`:
4021
+ *
4022
+ * ```vue
4023
+ * <fieldset v-for="block in [{ prefix: 'pickup' }, { prefix: 'delivery' }] as const">
4024
+ * <input v-register="form.register([block.prefix, 'line1'])" />
4025
+ * </fieldset>
4026
+ * ```
4027
+ *
4028
+ * Pass `options.transforms` to run a sync normalisation pipeline over
4029
+ * user-typed values before they reach form state.
4030
+ */
4031
+ register: {
4032
+ <Path extends RegisterFlatPath<Form>>(path: Path, options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, Path>>;
4033
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [RegisterFlatPath<Form>] ? unknown : SegmentRegisterRejection<JoinSegments<S>>), options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>;
4034
+ };
4035
+ /**
4036
+ * The form's identifier: the explicit `key` passed to `useForm`, or
4037
+ * an auto-generated unique id when `key` was omitted. Every
4038
+ * `ValidationError` this form produces carries it as `formKey`, so a
4039
+ * shared error list routes back to the right form.
4040
+ *
4041
+ * Typed as the literal `K` when an explicit `key` was passed, and as
4042
+ * `FormKey` when it was omitted.
4043
+ */
4044
+ key: K;
4045
+ /**
4046
+ * `true` while a function-form `defaultValues` factory is in flight,
4047
+ * between `useForm` construction and the moment the factory resolves:
4048
+ * the next microtask for a sync function, the promise settling for an
4049
+ * async one. `false` otherwise, a plain `defaultValues` included.
4050
+ *
4051
+ * The form is fully usable while `hydrating` is `true`, holding the
4052
+ * schema's slim defaults. The flag exists so a template can show a
4053
+ * spinner or dim the form while real data loads:
4054
+ *
4055
+ * ```vue
4056
+ * <div :aria-busy="form.hydrating">…</div>
4057
+ * ```
4058
+ *
4059
+ * Exposed as an auto-unwrapping `boolean` with no `.value`.
4060
+ * Reactivity survives through a getter that tracks the underlying ref
4061
+ * at the access site, so `watch(() => form.hydrating, ...)` and a
4062
+ * template read both fire on change. Reading it activates the form's
4063
+ * factory under the lazy-by-default rule.
4064
+ */
4065
+ readonly hydrating: boolean;
4066
+ /**
4067
+ * The error from the most recent function-form `defaultValues` factory,
4068
+ * normalized to a `ValidationError` (code `atta:hydration-failed`) so the
4069
+ * shape matches every other surface in `form.errors` / `form.meta.errors`.
4070
+ * `null` on construction, on successful resolution, and whenever no
4071
+ * factory has fired. Updates with each `form.rehydrate()` call.
4072
+ *
4073
+ * Distinct from `meta.submitError` so retry buttons and recovery UX can
4074
+ * stay focused on the load-time failure without entangling the submit
4075
+ * pipeline. Read directly in templates and script (no `.value`);
4076
+ * reactivity is preserved via a getter:
4077
+ *
4078
+ * ```vue
4079
+ * <p v-if="form.hydrateError">{{ form.hydrateError.message }}</p>
4080
+ * ```
4081
+ */
4082
+ readonly hydrateError: ValidationError | null;
4083
+ /**
4084
+ * `true` once the form's defaults have been applied, whether from a
4085
+ * plain `defaultValues` at construction or an async factory that
4086
+ * settled successfully. It stays `false` for a dormant lazy form
4087
+ * whose factory has not activated, and for a failed activation, which
4088
+ * sets `hydrateError`. Once `true` it stays `true` through refetches
4089
+ * so stale-while-revalidate UIs can keep rendering the prior values
4090
+ * while a `rehydrate()` is in flight.
4091
+ *
4092
+ * Composes with `hydrating` and `hydrateError`:
4093
+ *
4094
+ * ```vue
4095
+ * <Spinner v-if="!form.ready && form.hydrating" />
4096
+ * <ErrorBanner v-if="!form.ready && form.hydrateError" :error="form.hydrateError" />
4097
+ * <form v-if="form.ready">…</form>
4098
+ * ```
4099
+ *
4100
+ * Exposed as a reactive `boolean` with no `.value`. Reading it
4101
+ * activates the factory under the lazy-by-default rule, since
4102
+ * observing readiness implies use.
4103
+ */
4104
+ readonly ready: boolean;
4105
+ /**
4106
+ * Re-fire the captured `defaultValues` factory and re-apply its
4107
+ * payload over the current form values. Useful when the upstream
4108
+ * source changes (the user picks a different draft, a background
4109
+ * sync indicates fresh server data, etc.).
4110
+ *
4111
+ * Resolves after `hydrating` flips back to `false`. Throws
4112
+ * synchronously when the form was constructed with a plain-value
4113
+ * `defaultValues` (nothing to re-fire).
4114
+ *
4115
+ * The payload becomes the form's defaults, the same way a
4116
+ * `reset(next)` argument does, so `form.reset()` afterwards lands on
4117
+ * the values just fetched. Does NOT clear touched / submit state;
4118
+ * chain `form.reset()` for that.
4119
+ */
4120
+ rehydrate(): Promise<void>;
4121
+ /**
4122
+ * Idempotent activation. Forms are lazy-by-default: a function-form
4123
+ * `defaultValues` factory fires on the first reactive interaction
4124
+ * (reading `form.values`, calling `form.setValue`, etc.). Call
4125
+ * `form.activate()` to kick the factory explicitly, usually from
4126
+ * `setup`, so SSR's `onServerPrefetch` hook awaits the resolution
4127
+ * before the page renders. A later call returns the in-flight promise
4128
+ * until the factory settles, and resolves immediately after. A
4129
+ * plain-value form, having captured no factory, always returns a
4130
+ * resolved promise.
4131
+ */
4132
+ activate(): Promise<void>;
4133
+ /**
4134
+ * Reactive map of field errors, keyed by dotted path. Populated
4135
+ * automatically by `handleSubmit` and per-field validation; cleared
4136
+ * on validation success.
4137
+ *
4138
+ * Read in templates with no `.value`:
4139
+ *
4140
+ * ```vue
4141
+ * <p v-if="form.errors.email">{{ form.errors.email[0].message }}</p>
4142
+ * ```
4143
+ *
4144
+ * Watch from script via the getter form:
4145
+ *
4146
+ * ```ts
4147
+ * watch(() => form.errors.email, (errors) => …)
4148
+ * ```
4149
+ *
4150
+ * Use bracket access for a nested dotted key
4151
+ * (`form.errors['user.profile.email']`), since JS dot notation splits
4152
+ * on a literal dot.
4153
+ *
4154
+ * Read-only: populate it through `setErrors` and `clearErrors`. A
4155
+ * server response already emitting `ValidationError[]` pipes straight
4156
+ * into `setErrors` with no adapter.
4157
+ */
4158
+ errors: FormErrorsSurface<Form>;
4159
+ /**
4160
+ * The escape hatch for the rare case a consumer needs a `Ref<T>`:
4161
+ * handing the value to an external composable that expects a Vue ref,
4162
+ * or watching a single path with `watch(formRef, ...)`.
4163
+ *
4164
+ * ```ts
4165
+ * const emailRef = form.toRef('email') // Readonly<Ref<string>>
4166
+ * watch(emailRef, (next) => console.log(next))
4167
+ * ```
4168
+ *
4169
+ * Returns `Readonly<Ref<...>>`: writes go through `setValue`,
4170
+ * `register()` or a field-array helper, never the ref. Prefer
4171
+ * `form.values.email` for a direct read in a template or script, and
4172
+ * keep `toRef` for ref-shaped interop.
4173
+ */
4174
+ toRef: {
4175
+ <Path extends FlatPath<Form>>(path: Path): Readonly<Ref<NestedReadType<WriteShape<ReadForm>, Path>>>;
4176
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>)): Readonly<Ref<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>>;
4177
+ };
4178
+ /**
4179
+ * Set the form's manual error layer. One surface for server-side
4180
+ * errors, optimistic-UI errors, and form-level banners: a field error
4181
+ * and a global (form-level) error are the same thing at different
4182
+ * paths, so there is no separate field/form split.
4183
+ *
4184
+ * Input is lenient ({@link ErrorInput}): a real `Error`, a partial
4185
+ * `{ message?, path?, code?, data? }`, or an array of either. The form
4186
+ * stamps its own `formKey`, defaults a missing `code` to
4187
+ * `atta:user-error`, and coerces a missing or empty `message` to
4188
+ * `"Unknown error"` instead of throwing. A server that already emits
4189
+ * `ValidationError[]` satisfies the input shape directly, so
4190
+ * `form.setErrors(response.errors)` needs no adapter.
4191
+ *
4192
+ * Three call forms, mirroring `setValue`:
4193
+ *
4194
+ * ```ts
4195
+ * // Whole-layer replace. An entry with no `path` is a global,
4196
+ * // form-level error (path `[]`); add a `path` to target a field.
4197
+ * form.setErrors([{ path: ['email'], message: 'Already taken' }])
4198
+ * form.setErrors({ message: 'Capacity exceeded' }) // global banner
4199
+ * form.setErrors(new Error('Network unreachable')) // message coerced
4200
+ *
4201
+ * // Functional update. `prev` is the current manual layer, flat.
4202
+ * form.setErrors((prev) => [...prev, { message: 'And one more' }])
4203
+ *
4204
+ * // Path-scoped. The path is stamped onto every entry, and only that
4205
+ * // path's bucket is replaced. `prev` is that path's manual errors.
4206
+ * form.setErrors('email', [{ message: 'Already taken' }])
4207
+ * form.setErrors(['profile', 'handle'], { message: 'Reserved' })
4208
+ * form.setErrors('email', (prev) => prev.slice(0, 1))
4209
+ * ```
4210
+ *
4211
+ * Replaces only the manual layer; schema/validation errors live in a
4212
+ * separate store and merge on read. Pass `[]` to clear the manual
4213
+ * layer (or use `clearErrors`, which also clears the schema layer).
4214
+ */
4215
+ setErrors: {
4216
+ (update: (prev: ValidationError[]) => ErrorInput | ErrorInput[]): void;
4217
+ (errors: ErrorInput | ErrorInput[]): void;
4218
+ (path: string | (string | number)[], errors: ErrorInput | ErrorInput[] | ((prev: ValidationError[]) => ErrorInput | ErrorInput[])): void;
4219
+ };
4220
+ /**
4221
+ * Clear errors at one path, or everywhere. Clears BOTH the manual
4222
+ * layer (set through `setErrors`) and the schema/validation layer at
4223
+ * the target. With always-on validation the schema half re-populates
4224
+ * on the next mutation if the value is still invalid, so the cleared
4225
+ * state is short-lived for a field that is still wrong.
4226
+ *
4227
+ * ```ts
4228
+ * form.clearErrors('email') // one field
4229
+ * form.clearErrors([]) // the global, form-level bucket
4230
+ * form.clearErrors() // every error on the form
4231
+ * ```
4232
+ */
4233
+ clearErrors: (path?: string | (string | number)[]) => void;
4234
+ /**
4235
+ * Form-level reactive flags, counters, and aggregates (`dirty`,
4236
+ * `valid`, `submitting`, `submissionAttempts`, and the flat `errors`
4237
+ * array). See `FormMeta` for the full shape. Read leaves directly
4238
+ * with no `.value`.
4239
+ *
4240
+ * For per-field state (touched, focused, blurred, errors at one
4241
+ * path), use `form.fields.<path>` instead. Undo/redo state lives at
4242
+ * `form.history` (see `FormHistoryNamespace`).
4243
+ */
4244
+ meta: FormMeta<Form>;
4245
+ /**
4246
+ * Restore the form to its defaults. Without arguments, re-applies
4247
+ * whatever the defaults currently are: `useForm({ defaultValues })`
4248
+ * on a fresh form, or the last values a `reset(next)` or an async
4249
+ * `defaultValues` factory settled on.
4250
+ *
4251
+ * Resets:
4252
+ * - the form value back to defaults;
4253
+ * - the dirty baseline (so the next edit flips `dirty` correctly);
4254
+ * - field errors;
4255
+ * - touched / focused / blurred per-field flags;
4256
+ * - submission state (`submitting` / `submissionAttempts` /
4257
+ * `submitted` / `submitError`).
4258
+ *
4259
+ * `nextDefaultValues` RE-SEATS the defaults rather than applying for
4260
+ * one call, so the form has a single set of defaults that `reset()`,
4261
+ * `resetField(path)`, and `dirty` all read. That is what makes the
4262
+ * save-then-discard shape work: reset from the resource the server
4263
+ * returned, and a later Discard returns to the save instead of
4264
+ * rolling back across it.
4265
+ *
4266
+ * ```ts
4267
+ * const saved = await api.save(form.values())
4268
+ * form.reset(toValues(saved)) // the saved resource is now the baseline
4269
+ * // …later…
4270
+ * form.reset() // back to the saved resource
4271
+ * ```
4272
+ *
4273
+ * The argument is sparse: it folds over the defaults already in
4274
+ * force, so paths it does not name keep the value they had, and
4275
+ * `reset({})` changes nothing. Arrays are replaced wholesale rather
4276
+ * than merged element-wise. Pass `unset` at a path to withdraw its
4277
+ * value and mark it blank.
4278
+ */
4279
+ reset: (nextDefaultValues?: DefaultValuesInput<Form>) => void;
4280
+ /**
4281
+ * Restore a single field (or a sub-tree like `'user'`) to its
4282
+ * initial value. Clears errors and touched flags for the field
4283
+ * and its descendants; leaves siblings and submission state alone.
4284
+ *
4285
+ * No-op when the path doesn't exist on the form (e.g. a typo'd
4286
+ * dynamic key).
4287
+ */
4288
+ resetField: (path: FlatPath<Form>) => void;
4289
+ /**
4290
+ * Wipe a field, or the whole form, to the appropriate nullish value
4291
+ * for its declared type: the underlying type's empty or falsy
4292
+ * concrete, with any `.default(x)` wrapper DELIBERATELY skipped.
4293
+ * Orthogonal to `reset` and `resetField` by design.
4294
+ *
4295
+ * ```ts
4296
+ * const schema = z.object({
4297
+ * notify: z.boolean().default(true),
4298
+ * count: z.number().default(5),
4299
+ * })
4300
+ * const form = useForm({ schema })
4301
+ *
4302
+ * form.reset() // notify → true, count → 5 (defaults)
4303
+ * form.clear() // notify → false, count → 0 (falsy-for-type)
4304
+ * form.clear('notify') // → false (NOT the declared default true)
4305
+ * ```
4306
+ *
4307
+ * Per-wrapper semantics:
4308
+ *
4309
+ * - `.default(x)`, `.prefault(x)` and `.catch(x)` give the inner
4310
+ * schema's empty; the default is DELIBERATELY skipped.
4311
+ * - `.optional()` gives `undefined`, the wrapper's absent marker.
4312
+ * - `.nullable()` gives `null`, its explicit empty.
4313
+ * - An object recurses, so every property gets its own empty.
4314
+ * - An array, Set or Record gives the empty one.
4315
+ *
4316
+ * Returns `true` when the write was accepted and `false` when the
4317
+ * adapter could not resolve an empty value at the path, as happens
4318
+ * when the path is not in the schema. Form state is unchanged on a
4319
+ * `false`.
4320
+ *
4321
+ * Sugar over `setValue(path, schema.getEmptyValueAtPath(path))` with
4322
+ * no separate bookkeeping, so variant memory, history and listeners
4323
+ * all see a regular write at the path.
4324
+ *
4325
+ * `clear()` with no argument targets the whole form, while
4326
+ * `clear('')` targets the empty-string path slot SPECIFICALLY. The
4327
+ * two are not interchangeable, matching `touch()` and `touch('')`
4328
+ * from #184.
4329
+ */
4330
+ clear: {
4331
+ (): boolean;
4332
+ <Path extends FlatPath<Form> | ''>(path: Path): boolean;
4333
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form> | ''] ? unknown : SegmentPathRejection<JoinSegments<S>>)): boolean;
4334
+ };
4335
+ /**
4336
+ * The undo/redo namespace: `form.history.{undo, redo, clear, canUndo,
4337
+ * canRedo, size}`. Always present, and inert when `useForm({ history })`
4338
+ * was not configured. `FormHistoryNamespace` carries the
4339
+ * field-by-field semantics.
4340
+ */
4341
+ history: FormHistoryNamespace;
4342
+ /**
4343
+ * Focus the first errored field's first visible element. Returns
4344
+ * `true` when an element was focused, `false` when no candidate
4345
+ * element exists (no errors, or every errored field is unmounted
4346
+ * or hidden).
4347
+ *
4348
+ * Pass `{ preventScroll: true }` if you're scrolling separately
4349
+ * (e.g. via `scrollToFirstError`) and don't want the browser to
4350
+ * fight the explicit scroll.
4351
+ *
4352
+ * Requests `focusVisible: true` so the UA paints a focus ring even
4353
+ * though the move is programmatic. Honored where supported; on
4354
+ * browsers without it, non-text controls focused after a pointer
4355
+ * submit may show no ring, so you may still want an app-side
4356
+ * indicator.
4357
+ */
4358
+ focusFirstError: (options?: {
4359
+ preventScroll?: boolean;
4360
+ }) => boolean;
4361
+ /**
4362
+ * Scroll the first errored field's first visible element into
4363
+ * view. Returns `true` when the call ran, `false` when no
4364
+ * candidate element exists.
4365
+ *
4366
+ * `options` is forwarded to `Element.scrollIntoView` unchanged.
4367
+ */
4368
+ scrollToFirstError: (options?: ScrollIntoViewOptions) => boolean;
4369
+ /**
4370
+ * Run the form's own invalid-submit nudge imperatively: exactly what
4371
+ * `handleSubmit` does after a failed submit, but available standalone
4372
+ * and honoring this form's `useForm({ focusOnInvalidSubmit })` choice.
4373
+ *
4374
+ * Used by `useWizard` after navigating to the first failing form
4375
+ * during `wizard.handleSubmit`, so the failing form's own configured
4376
+ * behavior fires once its DOM is in view.
4377
+ *
4378
+ * No-op when this form opted out, or when no errored field is
4379
+ * currently registered. Use `focusFirstError()` to focus regardless
4380
+ * of the configured choice.
4381
+ */
4382
+ applyInvalidSubmitPolicy: () => void;
4383
+ /**
4384
+ * Programmatically mark fields as `touched`, the descriptive "this
4385
+ * field was visited" flag.
4386
+ *
4387
+ * ```ts
4388
+ * form.touch('email') // one leaf
4389
+ * form.touch('profile') // every leaf under profile
4390
+ * form.touch(['profile', 'name']) // segment-array form
4391
+ * form.touch() // every leaf in the form
4392
+ * ```
4393
+ *
4394
+ * **This does not reveal errors under the default display
4395
+ * heuristic.** `touched` also flips on a bare focus and blur with no
4396
+ * edit, so the library-default gate ignores it and reads
4397
+ * `blurredAfterInteraction` instead, the stricter bit that only a
4398
+ * blur *following* an edit sets. Reach for `touch()` when your own
4399
+ * analytics reads `touched`, and for
4400
+ * {@link UseFormReturnType.interact} to make a seeded or imported
4401
+ * value surface its errors.
4402
+ *
4403
+ * A pure flag write: it mutates no value, focused or blurred state
4404
+ * and triggers no validation. Idempotent, so re-calling on an
4405
+ * already-touched field is a no-op. Touched is sticky-true; pair it
4406
+ * with `form.reset()` or `form.resetField()` to clear.
4407
+ */
4408
+ touch: (path?: FlatPath<Form> | (string | number)[]) => void;
4409
+ /**
4410
+ * Simulate a complete focus, edit and blur over every leaf under
4411
+ * `path`, or the whole form when it is omitted, so a value that
4412
+ * arrived without a DOM gesture reveals its errors under the default
4413
+ * display heuristic.
4414
+ *
4415
+ * ```ts
4416
+ * form.interact('profile') // arm one subtree
4417
+ * await form.interact(['members', 2]) // errors committed on resolve
4418
+ * form.interact() // every leaf in the form
4419
+ * ```
4420
+ *
4421
+ * Flips the full interaction ladder (`touched`, `interacted`, and
4422
+ * `blurredAfterInteraction`) and runs the subtree's validation, so
4423
+ * the default gate (`submissionAttempts > 0 ||
4424
+ * blurredAfterInteraction`) opens through its front door. Use it to
4425
+ * arm one region's errors without a form-wide submit, which would
4426
+ * light up every other field on the page: a field-array row edited in
4427
+ * a modal, a server-seeded section, a pasted import.
4428
+ *
4429
+ * Walks schema leaves, so it reaches fields that are currently
4430
+ * `v-if`'d away or were never mounted; the flags are sticky, so
4431
+ * such a subtree stays revealed when it remounts.
4432
+ *
4433
+ * Flags land synchronously, and the returned promise resolves once
4434
+ * the subtree's validation has committed, so an awaiting caller reads
4435
+ * `showErrors` and `errors` immediately after. It never rejects, and
4436
+ * ignoring it is fine.
4437
+ *
4438
+ * No-op on a disabled form, matching every other interaction-
4439
+ * lifecycle write. Does not mutate value, `focused`, or `blurred`:
4440
+ * those are DOM-owned, and the display gate reads none of them.
4441
+ * Sticky like `touched`; pair with `form.reset()` /
4442
+ * `form.resetField()` to clear.
4443
+ */
4444
+ interact: (path?: FlatPath<Form> | (string | number)[]) => Promise<void>;
4445
+ /**
4446
+ * Append `value` to the array at `path`.
4447
+ *
4448
+ * ```ts
4449
+ * form.append('items', { name: 'New' })
4450
+ * ```
4451
+ */
4452
+ append: <Path extends ArrayPath<Form>>(path: Path, value: ArrayItem<Form, Path>) => void;
4453
+ /** Prepend `value` to the array at `path`. */
4454
+ prepend: <Path extends ArrayPath<Form>>(path: Path, value: ArrayItem<Form, Path>) => void;
4455
+ /**
4456
+ * Insert `value` into the array at `path` at the given `index`.
4457
+ * Behaves like `Array.prototype.splice`: `index` is clamped into
4458
+ * `[0, length]`, and negative indices count from the end.
4459
+ */
4460
+ insert: <Path extends ArrayPath<Form>>(path: Path, index: number, value: ArrayItem<Form, Path>) => void;
4461
+ /** Remove the element at `index` from the array at `path`. No-op when out of range. */
4462
+ remove: <Path extends ArrayPath<Form>>(path: Path, index: number) => void;
4463
+ /** Swap the elements at indices `a` and `b`. No-op when either is out of range. */
4464
+ swap: <Path extends ArrayPath<Form>>(path: Path, a: number, b: number) => void;
4465
+ /**
4466
+ * Move the element at `from` to `to`. Useful for drag-and-drop
4467
+ * reordering. No-op when either index is out of range.
4468
+ */
4469
+ move: <Path extends ArrayPath<Form>>(path: Path, from: number, to: number) => void;
4470
+ /** Replace the element at `index` with `value`. No-op when out of range. */
4471
+ replace: <Path extends ArrayPath<Form>>(path: Path, index: number, value: ArrayItem<Form, Path>) => void;
4472
+ /**
4473
+ * Read-only, reactive view of the array at `path` as one `FieldState`
4474
+ * per element, in index order. Each entry carries its element `key`,
4475
+ * an allocated identity token, so a `v-for` keyed by it keeps a row's
4476
+ * component instance across an insert, removal, move, or swap:
4477
+ *
4478
+ * ```vue
4479
+ * <div v-for="(row, i) in form.list('contacts')" :key="row.key">
4480
+ * <input v-register="form.register(`contacts.${i}.name`)" />
4481
+ * <p v-if="row.showErrors">{{ row.firstError?.message }}</p>
4482
+ * </div>
4483
+ * ```
4484
+ *
4485
+ * Entries are the same field states `form.fields` exposes, so reads
4486
+ * stay live. `form.fields(path)` remains the single aggregated
4487
+ * container for the whole array; `list` is the per-element view.
4488
+ * For a record, reach for `record`, which keys each entry by its own
4489
+ * key.
4490
+ */
4491
+ list: <Path extends ArrayPath<Form>>(path: Path) => readonly FieldState<ArrayItem<Form, Path>>[];
4492
+ /**
4493
+ * Read-only, reactive view of the record at `path` as one `FieldState`
4494
+ * per entry, keyed by the entry's own key. Where `list` hands back an
4495
+ * ordered array for an array path, `record` hands back a keyed object
4496
+ * for a record path, so you iterate it by key:
4497
+ *
4498
+ * ```vue
4499
+ * <div v-for="(field, key) in form.record('scoresByTeam')" :key="key">
4500
+ * <label>{{ key }}</label>
4501
+ * <input v-register="form.register(`scoresByTeam.${key}`)" />
4502
+ * <p v-if="field.showErrors">{{ field.firstError?.message }}</p>
4503
+ * </div>
4504
+ * ```
4505
+ *
4506
+ * Entries are the same field states `form.fields` exposes, so reads
4507
+ * stay live, and the keyed shape mirrors the record's own keys: an
4508
+ * entry appears once you write its key (`form.setValue`) and drops
4509
+ * when the key leaves. `form.fields(path)` remains the single
4510
+ * aggregated container for the whole record; `record` is the
4511
+ * per-entry view.
4512
+ *
4513
+ * When the form root is itself a record (`useForm({ schema:
4514
+ * z.record(K, V) })`, a dictionary form), call `form.record()` with
4515
+ * no argument for the root entry view:
4516
+ *
4517
+ * ```vue
4518
+ * <div v-for="(member, id) in form.record()" :key="id">
4519
+ * <input v-register="form.register(id)" />
4520
+ * <p v-if="member.showErrors">{{ member.firstError?.message }}</p>
4521
+ * </div>
4522
+ * ```
4523
+ */
4524
+ record: RootRecordView<Form> & (<Path extends RecordPath<Form>>(path: Path) => Readonly<Record<string, FieldState<RecordValue<Form, Path>>>>);
4525
+ /**
4526
+ * Read-only view of the form's blank path set, and reactive: Vue 3.5
4527
+ * tracks `.has()`, `for..of` and size accesses, so a consumer drives
4528
+ * conditional UI off it directly:
4529
+ *
4530
+ * ```ts
4531
+ * watchEffect(() => {
4532
+ * if (form.blankPaths.value.size > 0) {
4533
+ * const paths = [...form.blankPaths.value] // Path[][], segments per entry
4534
+ * console.warn('unanswered fields:', paths.map((p) => p.join('.')))
4535
+ * }
4536
+ * })
4537
+ * ```
4538
+ *
4539
+ * `.has(input)` accepts the dotted-string form (`'user.email'`) or
4540
+ * the array form (`['user', 'email']`), which disambiguates a key
4541
+ * carrying literal dots such as `['address.primary']`.
4542
+ * [[BlankPathsView]] carries the full surface.
4543
+ *
4544
+ * For per-path access, use `form.fields.<path>.blank`.
4545
+ * Writes happen through `setValue(path, unset)`,
4546
+ * `markBlank()` on a register binding, and the directive's
4547
+ * input listener on numeric clear.
4548
+ */
4549
+ blankPaths: ComputedRef<BlankPathsView>;
4550
+ };
4551
+
4552
+ export { ROOT_PATH as R, canonicalizePath as aj, isPathPrefix as ak, isUnset as al, parseDottedPath as am, unset as an, ROOT_PATH_KEY as z };
4553
+ export type { SchemaDefaultsResult as $, AbstractSchema as A, ReactiveValidationStatus as B, CustomDirectiveRegisterAssignerFn as C, DefaultValuesInput as D, ErrorInput as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, ParseOptions as P, RecordPath as Q, RecordValue as S, RegisterDirective as T, UseFormConfiguration as U, RegisterFlatPath as V, RegisterOptions as W, RegisterSelectModifier as X, RegisterTextModifier as Y, RegisterTransform as Z, RegisterValue as _, UseFormReturnType as a, SchemaParseErrorWithData as a0, SchemaParseErrorWithoutData as a1, SchemaParseResult as a2, SchemaParseSuccess as a3, Segment as a4, SetValueCallback as a5, SetValuePayload as a6, SettledValidationStatus as a7, SlimPrimitiveKind as a8, SubmitHandler as a9, Unset as aa, ValidateOn as ab, ValidateOnConfig as ac, ValidationError as ad, ValidationResponse as ae, ValidationResponseWithoutValue as af, ValueOfUnion as ag, WriteMeta as ah, WriteShape as ai, RegisterModelDynamicCustomDirective as ao, SchemaFactoryOptions as ap, AcceptableDefaults as aq, DisplayCtx as ar, GetDisplayState as as, DisplayMachine as at, FieldRecord as au, ErrorCell as av, OriginalsRecord as aw, AttaformDomBinding as ax, TransformAbortHolder as ay, ArrayItem as b, ArrayPath as c, DeepPartial as d, DefaultValuesShape as e, DisplayState as f, ErrorsProxyShape as g, FieldMetaPayload as h, FieldState as i, FieldStateMap as j, FieldStateMapEntry as k, FlatPath as l, FormErrorRecord as m, FormErrorsSurface as n, FormMeta as o, HistoryPlugin as p, IsUnion as q, Json as r, NestedType as s, OnSubmit as t, PartialFlatPath as u, Path as v, PathKey as w, PendingValidationStatus as x, Primitive as y };