attaform 0.18.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +3 -0
  2. package/dist/chunks/devtools.cjs +1 -1
  3. package/dist/chunks/devtools.mjs +1 -1
  4. package/dist/chunks/indexeddb.cjs +1 -1
  5. package/dist/chunks/indexeddb.mjs +1 -1
  6. package/dist/chunks/local-storage.cjs +1 -1
  7. package/dist/chunks/local-storage.mjs +1 -1
  8. package/dist/chunks/session-storage.cjs +1 -1
  9. package/dist/chunks/session-storage.mjs +1 -1
  10. package/dist/index.cjs +4 -7
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +77 -110
  13. package/dist/index.d.mts +77 -110
  14. package/dist/index.d.ts +77 -110
  15. package/dist/index.mjs +5 -5
  16. package/dist/nuxt.d.cts +1 -1
  17. package/dist/nuxt.d.mts +1 -1
  18. package/dist/nuxt.d.ts +1 -1
  19. package/dist/runtime/components/AttaformDevtoolsPanel.vue +2 -2
  20. package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +1 -3
  21. package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +1 -3
  22. package/dist/runtime/plugins/attaform.cjs +2 -2
  23. package/dist/runtime/plugins/attaform.mjs +2 -2
  24. package/dist/shared/{attaform.CDmaxrt2.mjs → attaform.BKozEdTr.mjs} +305 -178
  25. package/dist/shared/attaform.BKozEdTr.mjs.map +1 -0
  26. package/dist/shared/{attaform.Bubm_slq.cjs → attaform.BM6YD9kZ.cjs} +212 -269
  27. package/dist/shared/attaform.BM6YD9kZ.cjs.map +1 -0
  28. package/dist/shared/{attaform.5UhpSVFI.cjs → attaform.BPxsYtTe.cjs} +2 -26
  29. package/dist/shared/attaform.BPxsYtTe.cjs.map +1 -0
  30. package/dist/shared/{attaform.BqK_L4gK.cjs → attaform.BPy-4qRx.cjs} +305 -180
  31. package/dist/shared/attaform.BPy-4qRx.cjs.map +1 -0
  32. package/dist/shared/attaform.BWgAFnsj.mjs +770 -0
  33. package/dist/shared/attaform.BWgAFnsj.mjs.map +1 -0
  34. package/dist/shared/{attaform.CGX1CNpz.d.ts → attaform.Bh3ACtts.d.ts} +152 -111
  35. package/dist/shared/{attaform.CXpzmj38.mjs → attaform.BupwXkj_.mjs} +213 -270
  36. package/dist/shared/attaform.BupwXkj_.mjs.map +1 -0
  37. package/dist/shared/{attaform.Dlk1jMuv.cjs → attaform.CIn4bMsD.cjs} +263 -799
  38. package/dist/shared/attaform.CIn4bMsD.cjs.map +1 -0
  39. package/dist/shared/{attaform.CZ-XtZt_.mjs → attaform.CKFbKFb6.mjs} +2265 -1509
  40. package/dist/shared/attaform.CKFbKFb6.mjs.map +1 -0
  41. package/dist/shared/{attaform.CuN7ZhBy.d.cts → attaform.D5-1XGQU.d.cts} +152 -111
  42. package/dist/shared/{attaform.-1GQTX2T.mjs → attaform.DEBvCjeH.mjs} +257 -793
  43. package/dist/shared/attaform.DEBvCjeH.mjs.map +1 -0
  44. package/dist/shared/{attaform.II89Pcf4.cjs → attaform.DL4CQ-oW.cjs} +2270 -1514
  45. package/dist/shared/attaform.DL4CQ-oW.cjs.map +1 -0
  46. package/dist/shared/{attaform.FnEwjhvX.d.ts → attaform.DSD85fHb.d.cts} +1 -19
  47. package/dist/shared/{attaform.CRmmNAYp.d.cts → attaform.DSD85fHb.d.mts} +1 -19
  48. package/dist/shared/{attaform.D9wuTGu9.d.mts → attaform.DSD85fHb.d.ts} +1 -19
  49. package/dist/shared/{attaform.B7rzpK1U.d.cts → attaform.DkA5J8NW.d.cts} +1 -17
  50. package/dist/shared/{attaform.B7rzpK1U.d.mts → attaform.DkA5J8NW.d.mts} +1 -17
  51. package/dist/shared/{attaform.B7rzpK1U.d.ts → attaform.DkA5J8NW.d.ts} +1 -17
  52. package/dist/shared/{attaform.B957T6NU.d.ts → attaform.Dl5kDY-A.d.ts} +1 -1
  53. package/dist/shared/attaform.Dmb6itxC.cjs +781 -0
  54. package/dist/shared/attaform.Dmb6itxC.cjs.map +1 -0
  55. package/dist/shared/{attaform.M-RanbyV.d.mts → attaform.DoKXru-a.d.mts} +1 -1
  56. package/dist/shared/attaform.DvA-CJJW.mjs +1876 -0
  57. package/dist/shared/attaform.DvA-CJJW.mjs.map +1 -0
  58. package/dist/shared/{attaform.D1gzu2GL.d.mts → attaform.EMzJcQci.d.mts} +152 -111
  59. package/dist/shared/attaform.EZG6fOFb.mjs +35 -0
  60. package/dist/shared/attaform.EZG6fOFb.mjs.map +1 -0
  61. package/dist/shared/{attaform.XDjA7sRz.d.cts → attaform.GbDo_lJi.d.cts} +1 -1
  62. package/dist/shared/{attaform.Ca5_6Ky-.d.mts → attaform.SfhU0OEY.d.cts} +499 -116
  63. package/dist/shared/{attaform.Ca5_6Ky-.d.cts → attaform.SfhU0OEY.d.mts} +499 -116
  64. package/dist/shared/{attaform.Ca5_6Ky-.d.ts → attaform.SfhU0OEY.d.ts} +499 -116
  65. package/dist/shared/attaform.jgzuNZVC.cjs +1882 -0
  66. package/dist/shared/attaform.jgzuNZVC.cjs.map +1 -0
  67. package/dist/transforms.cjs +2 -2
  68. package/dist/transforms.d.cts +22 -13
  69. package/dist/transforms.d.mts +22 -13
  70. package/dist/transforms.d.ts +22 -13
  71. package/dist/transforms.mjs +1 -1
  72. package/dist/vite.cjs +8 -7
  73. package/dist/vite.cjs.map +1 -1
  74. package/dist/vite.mjs +8 -7
  75. package/dist/vite.mjs.map +1 -1
  76. package/dist/zod-v3.cjs +3 -3
  77. package/dist/zod-v3.d.cts +32 -6
  78. package/dist/zod-v3.d.mts +32 -6
  79. package/dist/zod-v3.d.ts +32 -6
  80. package/dist/zod-v3.mjs +3 -3
  81. package/dist/zod-v4.cjs +3 -3
  82. package/dist/zod-v4.d.cts +12 -8
  83. package/dist/zod-v4.d.mts +12 -8
  84. package/dist/zod-v4.d.ts +12 -8
  85. package/dist/zod-v4.mjs +3 -3
  86. package/dist/zod.cjs +8 -8
  87. package/dist/zod.cjs.map +1 -1
  88. package/dist/zod.d.cts +6 -6
  89. package/dist/zod.d.mts +6 -6
  90. package/dist/zod.d.ts +6 -6
  91. package/dist/zod.mjs +6 -6
  92. package/package.json +2 -2
  93. package/dist/shared/attaform.-1GQTX2T.mjs.map +0 -1
  94. package/dist/shared/attaform.5UhpSVFI.cjs.map +0 -1
  95. package/dist/shared/attaform.BqK_L4gK.cjs.map +0 -1
  96. package/dist/shared/attaform.Bubm_slq.cjs.map +0 -1
  97. package/dist/shared/attaform.C8CyvYa_.cjs +0 -36
  98. package/dist/shared/attaform.C8CyvYa_.cjs.map +0 -1
  99. package/dist/shared/attaform.CDmaxrt2.mjs.map +0 -1
  100. package/dist/shared/attaform.CXpzmj38.mjs.map +0 -1
  101. package/dist/shared/attaform.CZ-XtZt_.mjs.map +0 -1
  102. package/dist/shared/attaform.D13GMFgK.mjs +0 -32
  103. package/dist/shared/attaform.D13GMFgK.mjs.map +0 -1
  104. package/dist/shared/attaform.DUHru0OF.cjs +0 -1600
  105. package/dist/shared/attaform.DUHru0OF.cjs.map +0 -1
  106. package/dist/shared/attaform.Df0tU0Ut.mjs +0 -1594
  107. package/dist/shared/attaform.Df0tU0Ut.mjs.map +0 -1
  108. package/dist/shared/attaform.Dl161U6E.mjs +0 -57
  109. package/dist/shared/attaform.Dl161U6E.mjs.map +0 -1
  110. package/dist/shared/attaform.Dlk1jMuv.cjs.map +0 -1
  111. package/dist/shared/attaform.II89Pcf4.cjs.map +0 -1
@@ -136,6 +136,28 @@ declare function isUnset(value: unknown): value is Unset;
136
136
  type GenericForm = Record<string, unknown>;
137
137
  /** Internal helper — `true` when `T` is an object or array. */
138
138
  type IsObjectOrArray<T> = T extends GenericForm ? true : T extends Array<unknown> ? true : false;
139
+ /**
140
+ * Shared recursion body backing `PartialFlatPath` and `RegisterFlatPath`.
141
+ * One walk over `Form`; `Mode` controls whether intermediate container
142
+ * paths are emitted alongside their reachable leaves.
143
+ *
144
+ * - `'partial'`: emit every container path (object containers, nested-
145
+ * object array roots, array-of-object element containers). Used by
146
+ * `setValue` / `form.values.<path>` / every read-side API that needs
147
+ * to address a container.
148
+ * - `'register'`: skip container paths. `v-register` binds onto a
149
+ * leaf-backing native element (`<input>`, `<select>`, `<textarea>`),
150
+ * so container paths aren't registrable. Primitive arrays still
151
+ * admit the array-root path under both modes (multi-select and
152
+ * grouped-checkbox bindings register onto the array itself).
153
+ *
154
+ * Both walkers were independent recursions before; threading `Mode`
155
+ * through one body keeps the surface guarded by a single source of
156
+ * truth, and `PartialFlatPath` / `RegisterFlatPath` stay byte-
157
+ * identical to the prior hand-written walks (see
158
+ * `test/types/flat-path-walker.test.ts`).
159
+ */
160
+ 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;
139
161
  /**
140
162
  * Implementation detail backing `FlatPath` in its default
141
163
  * (partial-path) mode. Exported so `rollup-plugin-dts` preserves it
@@ -146,8 +168,7 @@ type IsObjectOrArray<T> = T extends GenericForm ? true : T extends Array<unknown
146
168
  * when multiple complex forms share a scope. Consumers should reach
147
169
  * for `FlatPath` instead; this alias is not part of the stable surface.
148
170
  */
149
- type PartialFlatPath<Form, Key extends keyof Form = keyof Form> = IsObjectOrArray<Form> extends true ? Key extends string ? Form[Key] extends infer Value ? Value extends Array<infer ArrayItem> ? `${Key}` | `${Key}.${number}` | `${Key}.${number}.${PartialFlatPath<ArrayItem>}` : Value extends GenericForm ? `${Key}` | `${Key}.${PartialFlatPath<Value>}` : `${Key}` : never : Key extends number ? `${Key}` | (Form[Key] extends GenericForm ? `${Key}.${PartialFlatPath<Form[Key]>}` : Form[Key] extends Array<infer ArrayItem> ? IsObjectOrArray<ArrayItem> extends true ? `${Key}.${number}` | `${Key}.${number}.${PartialFlatPath<ArrayItem>}` : `${Key}.${number}` : never) : never : never;
150
- type CompleteFlatPath<Form, Key extends keyof Form = keyof Form> = IsObjectOrArray<Form> extends true ? Key extends string ? Form[Key] extends infer Value ? Value extends Array<infer ArrayItem> ? `${Key}.${number}.${CompleteFlatPath<ArrayItem>}` : Value extends GenericForm ? `${Key}.${CompleteFlatPath<Value>}` : `${Key}` : never : Key extends number ? `${Key}` | (Form[Key] extends GenericForm ? `${Key}.${CompleteFlatPath<Form[Key]>}` : Form[Key] extends Array<infer ArrayItem> ? IsObjectOrArray<ArrayItem> extends true ? `${Key}.${number}.${CompleteFlatPath<ArrayItem>}` : `${Key}.${number}` : never) : never : never;
171
+ type PartialFlatPath<Form, Key extends keyof Form = keyof Form> = FlatPathBuilder<Form, 'partial', Key>;
151
172
  /**
152
173
  * Union of dotted-string paths reachable inside `Form`, e.g. for
153
174
  * `{ user: { email: string }, items: string[] }`:
@@ -157,11 +178,8 @@ type CompleteFlatPath<Form, Key extends keyof Form = keyof Form> = IsObjectOrArr
157
178
  * Used by every path-addressed API (`setValue(path, value)`,
158
179
  * `register(path)`, `toRef(path)`, etc.) so paths autocomplete in
159
180
  * the IDE and typos compile-error.
160
- *
161
- * Set `ForceFullPath` to `true` to restrict to leaf paths only
162
- * (no intermediate container paths).
163
181
  */
164
- type FlatPath<Form, Key extends keyof Form = keyof Form, ForceFullPath extends boolean = false> = ForceFullPath extends true ? CompleteFlatPath<Form, Key> : PartialFlatPath<Form, Key>;
182
+ type FlatPath<Form, Key extends keyof Form = keyof Form> = PartialFlatPath<Form, Key>;
165
183
  /**
166
184
  * Convert a tuple of path segments to its dotted-string equivalent.
167
185
  *
@@ -251,6 +269,31 @@ type LiftedValueShape<T> = [T] extends [
251
269
  type DeepPartial<T> = T extends Primitive ? T : T extends Array<infer ArrayItem> ? DeepPartial<ArrayItem>[] : T extends object ? {
252
270
  [Key in keyof T]?: DeepPartial<T[Key]>;
253
271
  } : T;
272
+ /**
273
+ * Shared descent body backing `NestedType` and `NestedReadType`. The
274
+ * recursion is identical — segment-by-segment, distributing over
275
+ * union members via `KeyofUnion` / `ValueOfUnion`. The two walkers
276
+ * diverge only at the leaf:
277
+ *
278
+ * - `TaintArrayCrossings extends false` (`NestedType` mode): leaves
279
+ * are returned untouched. Used by strict write-side APIs that need
280
+ * the exact resolved type (`setValue`'s value parameter,
281
+ * `form.fields.<path>`'s state map).
282
+ * - `TaintArrayCrossings extends true` (`NestedReadType` mode): leaves
283
+ * are widened with `| undefined` whenever any segment in the walk
284
+ * was numeric (array index). Reflects the runtime possibility of an
285
+ * out-of-bounds read.
286
+ *
287
+ * `_Tainted` propagates the array-crossing under taint mode; under
288
+ * strict mode it stays `false` through every recursion arm. Both
289
+ * arms strip nullishness at the root (`_RootValue = NonNullable<…>`)
290
+ * — the prior `NestedType.FilterOutNullishTypesDuringRecursion` flag
291
+ * was vestigial, never overridden from the outside.
292
+ *
293
+ * Not part of the stable consumer surface — reach for `NestedType` or
294
+ * `NestedReadType` directly.
295
+ */
296
+ 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;
254
297
  /**
255
298
  * Resolve the type at a dotted-string path inside `RootValue`. Used
256
299
  * by the strict (write-side) APIs to derive the type at a path:
@@ -264,11 +307,16 @@ type DeepPartial<T> = T extends Primitive ? T : T extends Array<infer ArrayItem>
264
307
  * useful value type (vs. silently collapsing to `never` because
265
308
  * `keyof (A|B|C)` would be the intersection of all variants' keys).
266
309
  *
310
+ * Composed over `NestedTypeBuilder` with array-crossing tainting OFF,
311
+ * so leaves return their exact resolved type. The companion
312
+ * `NestedReadType` shares the same recursion body but enables
313
+ * tainting for read-side APIs.
314
+ *
267
315
  * TypeScript caps conditional-type recursion at around 50 levels;
268
316
  * paths deeper than that resolve to `never`. Real form schemas
269
317
  * never reach this depth.
270
318
  */
271
- type NestedType<RootValue, FlattenedPath extends string, FilterOutNullishTypesDuringRecursion extends boolean = true, _RootValue = FilterOutNullishTypesDuringRecursion extends false ? RootValue : NonNullable<RootValue>> = IsObjectOrArray<_RootValue> extends false ? never : FlattenedPath extends `${infer Key}.${infer Rest}` ? Key extends `${number}` ? Key extends KeyofUnion<_RootValue> ? NestedType<ValueOfUnion<_RootValue, Key>, Rest, FilterOutNullishTypesDuringRecursion> : Key extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? NestedType<ValueOfUnion<_RootValue, NumericKey>, Rest, FilterOutNullishTypesDuringRecursion> : never : never : Key extends KeyofUnion<_RootValue> ? NestedType<ValueOfUnion<_RootValue, Key>, Rest, FilterOutNullishTypesDuringRecursion> : never : FlattenedPath extends `${number}` ? FlattenedPath extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, FlattenedPath> : FlattenedPath extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, NumericKey> : never : never : FlattenedPath extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, FlattenedPath> : never;
319
+ type NestedType<RootValue, FlattenedPath extends string> = NestedTypeBuilder<RootValue, FlattenedPath, false>;
272
320
  /**
273
321
  * Implementation-detail primitive-leaf marker used by `DeepPartial`
274
322
  * and sibling structural walkers. Exported so the bundled `.d.ts`
@@ -300,7 +348,7 @@ type IsTuple<T extends readonly unknown[]> = number extends T['length'] ? false
300
348
  * `register(path).innerRef` so the compile-time type honours the
301
349
  * runtime possibility of a missing array position.
302
350
  */
303
- type NestedReadType<RootValue, FlattenedPath extends string, _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> ? NestedReadType<ValueOfUnion<_RootValue, Key>, Rest, true> : Key extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? NestedReadType<ValueOfUnion<_RootValue, NumericKey>, Rest, true> : never : never : Key extends KeyofUnion<_RootValue> ? NestedReadType<ValueOfUnion<_RootValue, Key>, Rest, _Tainted> : never : FlattenedPath extends `${number}` ? FlattenedPath extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, FlattenedPath> | undefined : FlattenedPath extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, NumericKey> | undefined : never : never : FlattenedPath extends KeyofUnion<_RootValue> ? _Tainted extends true ? ValueOfUnion<_RootValue, FlattenedPath> | undefined : ValueOfUnion<_RootValue, FlattenedPath> : never;
351
+ type NestedReadType<RootValue, FlattenedPath extends string> = NestedTypeBuilder<RootValue, FlattenedPath, true>;
304
352
  /**
305
353
  * Filter FlatPath<Form> down to the subset of paths whose resolved leaf
306
354
  * is an array. Used by the typed field-array helpers (append / remove /
@@ -319,6 +367,26 @@ type ArrayPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends stri
319
367
  * constrain `Path extends ArrayPath<Form>` so this is always well-defined.
320
368
  */
321
369
  type ArrayItem<Form, Path extends ArrayPath<Form>> = NestedType<Form, Path> extends ReadonlyArray<infer Item> ? Item : never;
370
+ /**
371
+ * Companion to `ArrayPath`: filter `FlatPath<Form>` down to the subset
372
+ * of paths whose resolved leaf is a record (an object with an open
373
+ * string-keyed index signature, e.g. `z.record(z.string(), V)`). A
374
+ * fixed-shape object (`z.object({ ... })`) is excluded — its keys are
375
+ * statically known, so it has no `string` index signature.
376
+ *
377
+ * `string extends keyof T` is the index-signature probe: it holds for
378
+ * `Record<string, V>` (`keyof` is `string`) and fails for a fixed object
379
+ * (`keyof` is the literal key union). The leading array guard keeps
380
+ * arrays (which also satisfy the object check) out of the record set.
381
+ */
382
+ type RecordPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends string ? NestedType<Form, P> extends readonly unknown[] ? never : NestedType<Form, P> extends Record<string, unknown> ? string extends keyof NestedType<Form, P> ? P : never : never : never;
383
+ /**
384
+ * Value type of the record addressed by `Path` — the `V` in a
385
+ * `Record<string, V>`. Callers constrain `Path extends RecordPath<Form>`,
386
+ * so the leaf is always an open string-keyed record and this is
387
+ * well-defined.
388
+ */
389
+ type RecordValue<Form, Path extends RecordPath<Form>> = NestedType<Form, Path> extends Record<string, infer Value> ? Value : never;
322
390
  /**
323
391
  * Widens primitive-literal leaves to their primitive supertype to
324
392
  * match the runtime "slim-primitive write contract."
@@ -368,6 +436,28 @@ type WriteShape<T> = T extends string | number | boolean | bigint | symbol | nul
368
436
  } : Array<WriteShape<U>> : T extends object ? {
369
437
  [K in keyof T]: WriteShape<T[K]>;
370
438
  } : T;
439
+ /**
440
+ * Walk `T` and add `| Unset` at every primitive leaf (except symbol /
441
+ * null / undefined), every opaque leaf (`Date`, `RegExp`, `Map`,
442
+ * `Set`, functions), and every container position (object, tuple,
443
+ * array). The recursion topology mirrors `WriteShape<T>` exactly —
444
+ * `DefaultValuesShape<T>` is then a 1-line composition.
445
+ *
446
+ * Symbol / null / undefined leaves pass through untouched so the
447
+ * runtime sentinel doesn't pollute leaf semantics it has no business
448
+ * carrying. Container positions widen so a single `unset` at any
449
+ * level recursively marks every descendant primitive blank.
450
+ *
451
+ * Not part of the stable consumer-facing surface — reach for
452
+ * `DefaultValuesShape` instead.
453
+ */
454
+ 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[]] ? {
455
+ -readonly [K in keyof T]: AugmentWithUnset<T[K]>;
456
+ } | Unset : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
457
+ -readonly [K in keyof T]: AugmentWithUnset<T[K]>;
458
+ } | Unset : Array<AugmentWithUnset<U>> | Unset : T extends object ? {
459
+ [K in keyof T]: AugmentWithUnset<T[K]>;
460
+ } | Unset : T;
371
461
  /**
372
462
  * Like `WriteShape<T>`, but additionally widens every primitive leaf
373
463
  * (`string`, `number`, `boolean`, `bigint`) to admit `Unset` — the
@@ -381,8 +471,10 @@ type WriteShape<T> = T extends string | number | boolean | bigint | symbol | nul
381
471
  * descendant blank, so `defaultValues: { profile: unset }` and
382
472
  * `setValue('cargo', unset)` typecheck cleanly.
383
473
  *
384
- * The recursion mirrors `WriteShape<T>` exactly so `defaultValues`
385
- * stays compatible at every nested position. Tuple positions,
474
+ * Composed as `AugmentWithUnset<WriteShape<T>>`: stage 1 widens
475
+ * primitive literals, stage 2 adds `| Unset` everywhere — the prior
476
+ * inline walker hand-synced this in a single body. The composition
477
+ * stays compatible at every nested position; tuple positions,
386
478
  * unbounded arrays, and nested records all flow through unchanged.
387
479
  *
388
480
  * Example:
@@ -393,13 +485,7 @@ type WriteShape<T> = T extends string | number | boolean | bigint | symbol | nul
393
485
  * Used by `UseFormConfiguration.defaultValues`, `setValue`'s value
394
486
  * parameter, and `reset`'s parameter.
395
487
  */
396
- type DefaultValuesShape<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? 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 : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T | Unset : T extends readonly [unknown, ...unknown[]] ? {
397
- -readonly [K in keyof T]: DefaultValuesShape<T[K]>;
398
- } | Unset : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
399
- -readonly [K in keyof T]: DefaultValuesShape<T[K]>;
400
- } | Unset : Array<DefaultValuesShape<U>> | Unset : T extends object ? {
401
- [K in keyof T]: DefaultValuesShape<T[K]>;
402
- } | Unset : T;
488
+ type DefaultValuesShape<T> = AugmentWithUnset<WriteShape<T>>;
403
489
  /**
404
490
  * Single-walker fusion of `DeepPartial` and `DefaultValuesShape` — the
405
491
  * type accepted at `defaultValues`, `reset()`'s parameter, and every
@@ -693,6 +779,15 @@ type MaybePromise<T> = T | Promise<T>;
693
779
  type ValidateOptions = {
694
780
  sync?: boolean;
695
781
  };
782
+ /**
783
+ * Configuration passed to `AbstractSchema.getDefaultValues`. Adapters
784
+ * receive `useDefaultSchemaValues` (honor `.default(x)` wrappers vs.
785
+ * empty/falsy fallbacks), an optional `strict` mode (refinement
786
+ * preservation), and an optional `constraints` overlay merged into the
787
+ * derived defaults so the runtime can stamp user-supplied defaults at
788
+ * construction. Exported so adapter authors can co-implement the
789
+ * service contract.
790
+ */
696
791
  type GetDefaultValuesConfig<Form> = {
697
792
  useDefaultSchemaValues: boolean;
698
793
  /**
@@ -1072,6 +1167,32 @@ type AbstractSchema<Form, GetValueFormType> = {
1072
1167
  * answers without a separate top-level overload.
1073
1168
  */
1074
1169
  needsAsyncValidation?(): boolean;
1170
+ /**
1171
+ * Return `true` iff the schema carries a refine / check / transform
1172
+ * at any NON-LEAF position — a container node (object / array /
1173
+ * tuple / union / intersection / record / map / set) or the root
1174
+ * itself. False means every check this schema runs is leaf-local,
1175
+ * so a per-keystroke `validateAtPath(form, leafPath)` catches the
1176
+ * same verdicts as a whole-form pass — no ancestor refine reads
1177
+ * the form's wider state.
1178
+ *
1179
+ * The runtime uses this at the per-keystroke schedule to scope
1180
+ * field-level validation to the changed subtree when it can,
1181
+ * falling back to a whole-form pass when an ancestor refine
1182
+ * (cross-field equality, sum constraints, etc.) could be moved
1183
+ * by a leaf write. Optional. The runtime treats a missing
1184
+ * implementation as `() => true` — conservative whole-form,
1185
+ * preserving correctness for adapters that don't yet model
1186
+ * container-refine detection.
1187
+ *
1188
+ * Detection is best-effort: false negatives (returning `true`
1189
+ * when no container refine exists) only lose a perf win and
1190
+ * still validate correctly; false positives (returning `false`
1191
+ * when a container refine exists) would let an ancestor verdict
1192
+ * go stale and are the real risk — implementations should bias
1193
+ * toward returning `true` when in doubt.
1194
+ */
1195
+ hasContainerOrRootRefine?(): boolean;
1075
1196
  };
1076
1197
  /**
1077
1198
  * Adapter-returned info for a discriminated union — its discriminator
@@ -1308,20 +1429,24 @@ type WriteMeta = {
1308
1429
  */
1309
1430
  readonly skipDiscriminatorReshape?: boolean;
1310
1431
  /**
1311
- * Hint about an array structural mutation, set by `field-arrays.ts`
1312
- * helpers so `setValueAtPath` can surgically clear variant memory
1313
- * for indices the operation invalidated. Without this hint, a raw
1314
- * whole-array `setValue(arrayPath, [...])` clears all memory under
1315
- * the array (the runtime can't tell which indices stayed put).
1316
- * Internal don't set from consumer code.
1432
+ * Records an array structural mutation precisely enough to replay the
1433
+ * exact index permutation it produced, set by `field-arrays.ts`
1434
+ * helpers. `setValueAtPath` uses it to surgically clear variant memory
1435
+ * for the indices the operation invalidated. Without this hint, a raw
1436
+ * whole-array `setValue(arrayPath, [...])` clears all memory under the
1437
+ * array (the runtime can't tell which indices stayed put). Internal —
1438
+ * don't set from consumer code.
1317
1439
  */
1318
1440
  readonly arrayOp?: {
1319
- readonly kind: 'shift-from';
1441
+ readonly kind: 'insert';
1442
+ readonly index: number;
1443
+ } | {
1444
+ readonly kind: 'remove';
1320
1445
  readonly index: number;
1321
1446
  } | {
1322
- readonly kind: 'shift-range';
1323
- readonly fromIndex: number;
1324
- readonly toIndex: number;
1447
+ readonly kind: 'move';
1448
+ readonly from: number;
1449
+ readonly to: number;
1325
1450
  } | {
1326
1451
  readonly kind: 'swap';
1327
1452
  readonly a: number;
@@ -1736,16 +1861,14 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1736
1861
  */
1737
1862
  coerce?: boolean | CoercionRegistry;
1738
1863
  /**
1739
- * Per-form override of the `shouldShowErrors` heuristic that drives
1740
- * `field.showErrors` and `form.meta.showErrors`. Falls back to
1741
- * `AttaformDefaults.shouldShowErrors`, then to the library default
1742
- * (`defaultShouldShowErrors`). See `AttaformDefaults.shouldShowErrors`
1743
- * for the resolution rules and predicate signature.
1744
- *
1745
- * Boolean shorthand: `true` → always show *when errors exist*;
1746
- * `false` → never show.
1864
+ * Per-form override of the `getDisplayState` heuristic that drives
1865
+ * `field.displayState` and the `show*` booleans (and their `form.meta`
1866
+ * rollups). Falls back to `AttaformDefaults.getDisplayState`, then to
1867
+ * the library default (`defaultDisplayState`). See
1868
+ * `AttaformDefaults.getDisplayState` for the resolution rules and
1869
+ * predicate signature.
1747
1870
  */
1748
- shouldShowErrors?: ShouldShowErrorsConfig;
1871
+ getDisplayState?: GetDisplayState;
1749
1872
  /**
1750
1873
  * Recursion ceiling for schema walks that descend through recursive
1751
1874
  * schemas (Zod's `z.lazy(...)` today). Default `64`. Per-form value
@@ -1773,8 +1896,9 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1773
1896
  maxRecursionDepth?: number;
1774
1897
  /**
1775
1898
  * Override the path-segment name stems treated as sensitive for this
1776
- * form. Sensitive paths are excluded from persistence writes,
1777
- * multi-tab sync broadcasts, AND the DevTools redact walk.
1899
+ * form. Sensitive paths are excluded from persistence writes and
1900
+ * multi-tab sync broadcasts. (DevTools renders raw values by design;
1901
+ * it does not redact.)
1778
1902
  *
1779
1903
  * Resolution: per-form value (this field) > global default
1780
1904
  * (`createAttaform({ defaults: { sensitiveNames } })`) > library
@@ -1818,6 +1942,20 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1818
1942
  * channel would be solo by construction.
1819
1943
  */
1820
1944
  multiTab?: boolean;
1945
+ /**
1946
+ * Whether `v-register` automatically manages aria attributes
1947
+ * (`aria-invalid`, `aria-busy`, `aria-required`, `aria-describedby`)
1948
+ * from the field's display state. **Defaults to `true`.**
1949
+ *
1950
+ * **Resolution order (per-register override > per-form > global > library):**
1951
+ *
1952
+ * register(path, { autoAria }) > useForm({ autoAria }) > AttaformDefaults.autoAria > library default (`true`)
1953
+ *
1954
+ * Set `false` to leave all aria wiring to your own markup form-wide.
1955
+ * Any aria attribute you author yourself is always left untouched,
1956
+ * independent of this flag.
1957
+ */
1958
+ autoAria?: boolean;
1821
1959
  /**
1822
1960
  * @internal
1823
1961
  * SSR prefetch mark — set by the `attaform/vite` compile-time
@@ -1899,33 +2037,41 @@ type AttaformDefaults = {
1899
2037
  */
1900
2038
  coerce?: boolean | CoercionRegistry;
1901
2039
  /**
1902
- * Default for `useForm({ shouldShowErrors })`. Centralised heuristic
1903
- * that drives `field.showErrors` (and `form.meta.showErrors`)a
1904
- * boolean that gates whether a path's errors are *ready* to render.
2040
+ * Default for `useForm({ getDisplayState })`. The centralised
2041
+ * heuristic that resolves every path's `field.displayState` — and thus
2042
+ * the `show*` booleans and their `form.meta` rollups to one of
2043
+ * `'idle' | 'pending' | 'error' | 'success'`.
1905
2044
  *
1906
2045
  * Resolution order (per-form wins):
1907
2046
  *
1908
- * useForm({ shouldShowErrors }) > AttaformDefaults > library default
2047
+ * useForm({ getDisplayState }) > AttaformDefaults > library default
1909
2048
  *
1910
- * The library default reads "show after the first submit attempt OR
1911
- * after the field has been interacted with AND changed":
2049
+ * The library default opens one timing gate, then resolves by
2050
+ * precedence: gate closed `'idle'`; a run in flight → `'pending'`;
2051
+ * an own-path error → `'error'`; otherwise `valid` → `'success'`, else
2052
+ * `'idle'`. The gate opens after the first submit attempt OR once the
2053
+ * field is touched and not currently focused:
1912
2054
  *
1913
2055
  * ```ts
1914
- * (field, formMeta) =>
1915
- * formMeta.submissionAttempts > 0 || (field.touched === true && field.dirty)
2056
+ * (field, formMeta) => {
2057
+ * const gateOpen =
2058
+ * formMeta.submissionAttempts > 0 ||
2059
+ * (field.touched === true && field.focused !== true)
2060
+ * if (!gateOpen) return 'idle'
2061
+ * if (field.validating === true) return 'pending'
2062
+ * // ...own-path error → 'error'; valid → 'success'; else 'idle'
2063
+ * }
1916
2064
  * ```
1917
2065
  *
1918
- * Compose with the library default via the public
1919
- * `defaultShouldShowErrors` export. Boolean shorthand is supported:
1920
- * `true` always show *when errors exist*; `false` → never show. The
1921
- * predicate is invoked only when `errors.length > 0`, so authors
1922
- * don't re-check inside.
2066
+ * Compose with the library default via the public `defaultDisplayState`
2067
+ * export. The predicate runs on every field-state read, so it owns the
2068
+ * idle / pending / error / success decision outright.
1923
2069
  *
1924
- * The predicate's args are `Omit`'d of `showErrors` / `firstError`
1925
- * to prevent recursive predicates those are derived FROM this
1926
- * predicate, so reading them inside would be a self-reference.
2070
+ * The predicate's args are `Omit`'d of the derived `displayState` /
2071
+ * `show*` / `firstError` keys (see `FieldStateDerivedKey`) to prevent
2072
+ * a self-referential predicate.
1927
2073
  */
1928
- shouldShowErrors?: ShouldShowErrorsConfig;
2074
+ getDisplayState?: GetDisplayState;
1929
2075
  /**
1930
2076
  * Default for `useForm({ maxRecursionDepth })`. Recursion ceiling
1931
2077
  * for schema walks that descend through recursive schemas (Zod's
@@ -1978,9 +2124,10 @@ type AttaformDefaults = {
1978
2124
  maxRecursionDepth?: number;
1979
2125
  /**
1980
2126
  * Override the path-segment name stems treated as sensitive.
1981
- * Sensitive paths are excluded from persistence writes, multi-tab
1982
- * sync broadcasts, AND the DevTools redact walk one configurable
1983
- * source of truth across every surface.
2127
+ * Sensitive paths are excluded from persistence writes and multi-tab
2128
+ * sync broadcasts one configurable source of truth across those
2129
+ * surfaces. (DevTools renders raw values by design; it does not
2130
+ * redact.)
1984
2131
  *
1985
2132
  * Library default is `DEFAULT_SENSITIVE_NAMES` (exported from
1986
2133
  * `attaform`); compose to extend:
@@ -2025,6 +2172,21 @@ type AttaformDefaults = {
2025
2172
  * the multi-tab-sync recipe's Security section for the threat model.
2026
2173
  */
2027
2174
  multiTab?: boolean;
2175
+ /**
2176
+ * App-wide default for `useForm({ autoAria })`. Library default is
2177
+ * `true`: `v-register` keeps `aria-invalid` / `aria-busy` /
2178
+ * `aria-required` / `aria-describedby` in sync with each field's
2179
+ * display state out of the box.
2180
+ *
2181
+ * **Resolution order (per-form wins):**
2182
+ *
2183
+ * useForm({ autoAria }) > AttaformDefaults.autoAria > library default (`true`)
2184
+ *
2185
+ * Set `false` once at the plugin level to make every form manage its
2186
+ * own aria markup. Authored aria attributes are always preserved
2187
+ * regardless of this setting.
2188
+ */
2189
+ autoAria?: boolean;
2028
2190
  };
2029
2191
  /**
2030
2192
  * Callback invoked by `handleSubmit` after the form parses successfully.
@@ -2040,45 +2202,59 @@ type OnSubmit<Form extends GenericForm> = (form: Form) => void | Promise<void>;
2040
2202
  */
2041
2203
  type OnError = (error: ValidationError[]) => void | Promise<void>;
2042
2204
  /**
2043
- * Predicate that drives `field.showErrors` (and `form.meta.showErrors`).
2044
- * Receives the field's reactive state plus the form's reactive meta;
2045
- * returns `true` to render the field's errors, `false` to keep them
2046
- * hidden. The framework gates the call on `errors.length > 0`, so
2047
- * authors don't re-check error presence inside.
2048
- *
2049
- * Both arguments are `Omit`'d of `showErrors` / `firstError` those
2050
- * are derived FROM this predicate, so reading them inside would be a
2051
- * self-reference. The omit is enforced at the type level AND at
2052
- * runtime: the keys literally are not present on the objects passed
2053
- * in, so `as` casting in TS or vanilla-JS bypass cannot create a
2054
- * cycle.
2055
- *
2056
- * The library default `defaultShouldShowErrors` is publicly
2057
- * exported so a layered predicate can compose with it:
2205
+ * The display-state verdict at a path: the single signal a UI needs to
2206
+ * decide what (if anything) to surface about validation right now.
2207
+ * Rolled up at containers and at the form root (`form.meta.displayState`).
2208
+ *
2209
+ * - `'idle'` nothing to surface. Either pre-interaction (the timing
2210
+ * gate hasn't opened) or gate-open with no verdict worth showing.
2211
+ * - `'pending'` a validation run is in flight at this path; the prior
2212
+ * verdict is stale. Drive a spinner / "Checking…" affordance.
2213
+ * - `'error'` a blocking error the timing gate has cleared for display.
2214
+ * - `'success'` validation passed and the gate has cleared a positive
2215
+ * confirmation (the green-check pattern).
2216
+ *
2217
+ * The four `show*` booleans on `FieldState` are sugar over this enum
2218
+ * (`showErrors === (displayState === 'error')`, and so on), so they can
2219
+ * never contradict it.
2220
+ */
2221
+ type DisplayState = 'idle' | 'pending' | 'error' | 'success';
2222
+ /**
2223
+ * Keys on `FieldState` layered on FROM the display-state predicate
2224
+ * (plus `firstError`, computed alongside them). `Omit`'d from the
2225
+ * predicate's arguments so a predicate cannot read its own output and
2226
+ * form a cycle — enforced at the type level AND at runtime: the base
2227
+ * objects passed in literally lack these keys, so an `as` cast in TS
2228
+ * or a vanilla-JS caller still can't reach them. `FieldStateBase` /
2229
+ * `FormMetaBase` (field-state-api.ts) omit the same set in lockstep.
2230
+ */
2231
+ type FieldStateDerivedKey = 'displayState' | 'showErrors' | 'showPending' | 'showSuccess' | 'showIdle' | 'firstError';
2232
+ /**
2233
+ * Predicate that resolves a path's `displayState`. Receives the field's
2234
+ * reactive state plus the form's reactive meta (both minus the derived
2235
+ * `displayState` / `show*` / `firstError` keys — see `FieldStateDerivedKey`)
2236
+ * and returns the single enum verdict; the `show*` booleans derive from
2237
+ * the result. Runs unconditionally on every field-state read, so the
2238
+ * idle / pending / error / success decision lives in exactly one place
2239
+ * and the whole app's validation-display behavior flows from it.
2240
+ *
2241
+ * The library default — `defaultDisplayState` — is publicly exported so
2242
+ * a layered predicate can compose with it:
2058
2243
  *
2059
2244
  * ```ts
2060
- * import { defaultShouldShowErrors } from 'attaform'
2245
+ * import { defaultDisplayState } from 'attaform'
2061
2246
  *
2062
2247
  * useForm({
2063
2248
  * schema,
2064
- * shouldShowErrors: (field, formMeta) =>
2065
- * field.path[0] === 'urgent' || defaultShouldShowErrors(field, formMeta),
2249
+ * // Defer to the default everywhere, but never show a success check on `username`.
2250
+ * getDisplayState: (field, formMeta) => {
2251
+ * const state = defaultDisplayState(field, formMeta)
2252
+ * return field.path[0] === 'username' && state === 'success' ? 'idle' : state
2253
+ * },
2066
2254
  * })
2067
2255
  * ```
2068
2256
  */
2069
- type ShouldShowErrors = (field: Omit<FieldState, 'showErrors' | 'firstError'>, formMeta: Omit<FormMeta, 'showErrors' | 'firstError'>) => boolean;
2070
- /**
2071
- * Configuration shape for `shouldShowErrors`. A predicate function or
2072
- * a boolean shorthand:
2073
- *
2074
- * - `true` — always show errors (when any exist).
2075
- * - `false` — never show errors.
2076
- * - function — custom predicate, see `ShouldShowErrors`.
2077
- *
2078
- * Resolved through three tiers (per-form > plugin defaults > library
2079
- * default).
2080
- */
2081
- type ShouldShowErrorsConfig = ShouldShowErrors | boolean;
2257
+ type GetDisplayState = (field: Omit<FieldState, FieldStateDerivedKey>, formMeta: Omit<FormMeta, FieldStateDerivedKey>) => DisplayState;
2082
2258
  /**
2083
2259
  * Submit handler returned by `handleSubmit(onSubmit, onError)`. Bind
2084
2260
  * it to a `<form>`:
@@ -2162,7 +2338,7 @@ type MetaTrackerValue = {
2162
2338
  */
2163
2339
  blank: boolean;
2164
2340
  };
2165
- type RegisterFlatPath<Form, 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 ? `${Key}.${number}.${RegisterFlatPath<ArrayItem>}` : `${Key}` | `${Key}.${number}` : Value extends GenericForm ? `${Key}.${RegisterFlatPath<Value>}` : `${Key}` : never : Key extends number ? `${Key}` | (Form[Key] extends GenericForm ? `${Key}.${RegisterFlatPath<Form[Key]>}` : Form[Key] extends Array<infer ArrayItem> ? IsObjectOrArray<ArrayItem> extends true ? `${Key}.${number}.${RegisterFlatPath<ArrayItem>}` : `${Key}` | `${Key}.${number}` : never) : never : never;
2341
+ type RegisterFlatPath<Form, Key extends keyof Form = keyof Form> = FlatPathBuilder<Form, 'register', Key>;
2166
2342
  /**
2167
2343
  * Sync transformation applied to a field's value as user input flows
2168
2344
  * from DOM through the directive's assigner. Composes left-to-right
@@ -2268,9 +2444,10 @@ type RegisterOptions = {
2268
2444
  * If multiple inputs bind to the same path, the path keeps
2269
2445
  * persisting as long as any opted-in input is mounted.
2270
2446
  *
2271
- * Throws `SensitivePersistFieldError` when the path looks
2272
- * sensitive (password / cvv / ssn / token / etc.) unless
2273
- * `acknowledgeSensitive: true` is also set.
2447
+ * When the path looks sensitive (password / cvv / ssn / token /
2448
+ * etc.) the opt-in is skipped with a one-shot dev warning unless
2449
+ * `acknowledgeSensitive: true` is also set — the field simply isn't
2450
+ * persisted (the secure default). It never throws.
2274
2451
  */
2275
2452
  persist?: boolean;
2276
2453
  /**
@@ -2330,6 +2507,22 @@ type RegisterOptions = {
2330
2507
  * instead — see the "Custom assigners" section in the API docs.
2331
2508
  */
2332
2509
  transforms?: ReadonlyArray<RegisterTransform>;
2510
+ /**
2511
+ * Per-binding override for automatic aria management, the narrowest
2512
+ * tier of the `autoAria` cascade. By default the directive keeps
2513
+ * `aria-invalid` / `aria-busy` / `aria-required` / `aria-describedby`
2514
+ * in sync with the field's display state. Pass `autoAria: false` to
2515
+ * leave every aria attribute on this element to you (the directive
2516
+ * still manages value binding and registration), or `autoAria: true`
2517
+ * to re-enable management on one binding even when the form set
2518
+ * `useForm({ autoAria: false })`.
2519
+ *
2520
+ * Overrides `useForm({ autoAria })` and
2521
+ * `createAttaform({ defaults: { autoAria } })`. Writing an aria
2522
+ * attribute yourself also locks the directive out of that one
2523
+ * attribute, regardless of this flag.
2524
+ */
2525
+ autoAria?: boolean;
2333
2526
  };
2334
2527
  /**
2335
2528
  * The object returned by `form.register(path)`. Pass it to a native
@@ -2439,7 +2632,7 @@ type RegisterValue<Value = unknown> = Readonly<{
2439
2632
  /**
2440
2633
  * Resolved sensitive-path predicate honoring this form's
2441
2634
  * `sensitiveNames` cascade. The directive calls this through
2442
- * `enforceSensitiveCheck` when a `register('path', { persist: true })`
2635
+ * `allowSensitivePersist` when a `register('path', { persist: true })`
2443
2636
  * binding mounts so a per-form custom list (e.g. extending with
2444
2637
  * `'mrn'`) gates persistence enrolment correctly.
2445
2638
  * @internal
@@ -2530,6 +2723,16 @@ type RegisterValue<Value = unknown> = Readonly<{
2530
2723
  * @internal
2531
2724
  */
2532
2725
  markBlank: () => boolean;
2726
+ /**
2727
+ * Flip this field's sticky `interacted` flag — the signal that the
2728
+ * user has issued at least one value edit here (an insert or a
2729
+ * delete). Called by the directive's input / change listeners on
2730
+ * every genuine user input; never by hydration or programmatic
2731
+ * writes. Idempotent (the store skips the write once set). Don't
2732
+ * call from consumer code.
2733
+ * @internal
2734
+ */
2735
+ markInteracted: () => void;
2533
2736
  /**
2534
2737
  * `true` when the schema's slim primitive set at this path includes
2535
2738
  * `'undefined'` — i.e. the leaf was declared `.optional()` (or as
@@ -2561,6 +2764,42 @@ type RegisterValue<Value = unknown> = Readonly<{
2561
2764
  * @internal
2562
2765
  */
2563
2766
  acceptsString: boolean;
2767
+ /**
2768
+ * The field's aria satellite ids, mirroring `FieldState.aria`. The
2769
+ * directive points `aria-describedby` at `errorId` while the field
2770
+ * is in its error state. Optional so hand-rolled `RegisterValue`
2771
+ * mocks don't have to declare it; the directive skips aria wiring
2772
+ * when absent.
2773
+ * @internal
2774
+ */
2775
+ aria?: {
2776
+ readonly errorId: string;
2777
+ readonly descriptionId: string;
2778
+ };
2779
+ /**
2780
+ * Whether the schema marks this path required, from
2781
+ * `schema.isRequiredAtPath(segments)`. Drives `aria-required`.
2782
+ * Optional for the same mock-tolerance reason as `aria`.
2783
+ * @internal
2784
+ */
2785
+ isRequired?: boolean;
2786
+ /**
2787
+ * Whether the directive should auto-manage aria attributes for this
2788
+ * binding. Resolves the per-register `autoAria` override against the
2789
+ * form-level value: `options.autoAria ?? formAutoAria`. The directive
2790
+ * treats an absent value as off.
2791
+ * @internal
2792
+ */
2793
+ ariaEnabled?: boolean;
2794
+ /**
2795
+ * The gated display-state verdict for this path, reusing the same
2796
+ * field-state identity as `form.fields`. The directive watches it to
2797
+ * keep `aria-invalid` / `aria-busy` / `aria-describedby` in lockstep
2798
+ * with the visible error state, even on async ticks with no parent
2799
+ * re-render. Optional; the directive skips aria wiring when absent.
2800
+ * @internal
2801
+ */
2802
+ ariaDisplayState?: Readonly<Ref<DisplayState>>;
2564
2803
  }>;
2565
2804
  /**
2566
2805
  * Custom assigner installed on an element via the directive's
@@ -2876,6 +3115,32 @@ type FieldState<Value = unknown> = {
2876
3115
  readonly focused: boolean | null;
2877
3116
  readonly blurred: boolean | null;
2878
3117
  readonly touched: boolean;
3118
+ /**
3119
+ * `true` once the user has issued at least one value edit on this
3120
+ * field through `v-register` (an insert or a delete), sticky
3121
+ * thereafter and preserved across disconnects. Distinct from
3122
+ * `dirty`: typing `"a"` then deleting it back to empty leaves the
3123
+ * field net-unchanged (`dirty: false`) yet `interacted: true`.
3124
+ * Distinct from `touched`: tabbing through a field without editing
3125
+ * flips `touched` but never `interacted`. Set only by user input,
3126
+ * never by hydration or programmatic `setValue`; cleared by
3127
+ * `form.reset()` / `form.resetField(path)`. Containers roll it up as
3128
+ * a disjunction (any descendant interacted).
3129
+ */
3130
+ readonly interacted: boolean;
3131
+ /**
3132
+ * `true` once the user has blurred this field after editing it: the
3133
+ * first time they edit a value and then leave. Sticky thereafter and
3134
+ * preserved across disconnects; a tab-through with no edit never sets
3135
+ * it (`interacted` is still false at that blur). It composes
3136
+ * `interacted` with the departure and drives the default display gate,
3137
+ * so errors reveal once the user finishes a pass and leaves, then stay
3138
+ * visible through a re-focus to be fixed live. Set only by user
3139
+ * input/blur, never by hydration or programmatic writes; cleared by
3140
+ * `form.reset()` / `form.resetField(path)`. Containers roll it up as a
3141
+ * disjunction.
3142
+ */
3143
+ readonly blurredAfterInteraction: boolean;
2879
3144
  readonly connected: boolean;
2880
3145
  /**
2881
3146
  * The first DOM element bound to this path via `v-register`, or
@@ -2932,9 +3197,29 @@ type FieldState<Value = unknown> = {
2932
3197
  */
2933
3198
  readonly valid: boolean;
2934
3199
  /**
2935
- * Centralised "should I render this field's errors right now?"
2936
- * gate. Wraps `errors.length > 0 && shouldShowErrors(field, formMeta)`
2937
- * so templates avoid re-spelling the heuristic at every error site:
3200
+ * The single display-state verdict at this path: `'idle'`,
3201
+ * `'pending'`, `'error'`, or `'success'`. The source of truth the
3202
+ * four `show*` booleans below derive from. Bind it directly when one
3203
+ * branch over the set reads cleaner than four flags:
3204
+ *
3205
+ * ```vue
3206
+ * <FieldStatusIcon :state="form.fields.email.displayState" />
3207
+ * ```
3208
+ *
3209
+ * Resolved by the `getDisplayState` heuristic:
3210
+ * `useForm({ getDisplayState })` →
3211
+ * `createAttaform({ defaults: { getDisplayState } })` → library
3212
+ * default (`defaultDisplayState`). Override per form, app-wide, or
3213
+ * compose with `defaultDisplayState` for a layered predicate.
3214
+ *
3215
+ * Available on container paths too: `form.fields.users[0].displayState`
3216
+ * rolls up over the row's descendants.
3217
+ */
3218
+ readonly displayState: DisplayState;
3219
+ /**
3220
+ * `displayState === 'error'`. The centralised "render this field's
3221
+ * errors right now?" gate, so templates avoid re-spelling the
3222
+ * heuristic at every error site:
2938
3223
  *
2939
3224
  * ```vue
2940
3225
  * <span v-if="form.fields.email.showErrors">
@@ -2942,20 +3227,29 @@ type FieldState<Value = unknown> = {
2942
3227
  * </span>
2943
3228
  * ```
2944
3229
  *
2945
- * The heuristic itself comes from `useForm({ shouldShowErrors })`
2946
- * `createAttaform({ defaults: { shouldShowErrors } })` library
2947
- * default (`defaultShouldShowErrors` show after first submit OR
2948
- * after touched-and-dirty). Override per form, app-wide, or
2949
- * compose with `defaultShouldShowErrors` for a layered predicate.
2950
- *
2951
- * Falls back to `false` whenever there are no errors — the gate
2952
- * skips the predicate entirely in that case.
2953
- *
2954
- * Available on container paths too: `form.fields.users[0].showErrors`
2955
- * aggregates over the row's descendants (any descendant with a
2956
- * qualifying error flips the container on).
3230
+ * Kept plural to match `errors` / `firstError`. On container paths it
3231
+ * rolls up over descendants (any descendant resolving to `'error'`
3232
+ * flips the container on).
2957
3233
  */
2958
3234
  readonly showErrors: boolean;
3235
+ /**
3236
+ * `displayState === 'pending'`. A per-field validation run is in
3237
+ * flight at this path and the prior verdict is stale; drive a spinner
3238
+ * or a "Checking…" affordance.
3239
+ */
3240
+ readonly showPending: boolean;
3241
+ /**
3242
+ * `displayState === 'success'`. Validation has passed and the timing
3243
+ * gate has cleared a positive confirmation; drive the green-check
3244
+ * pattern.
3245
+ */
3246
+ readonly showSuccess: boolean;
3247
+ /**
3248
+ * `displayState === 'idle'`. Nothing to surface yet — pre-interaction,
3249
+ * or gate-open with no verdict worth showing. Read it to suppress
3250
+ * helper text the moment any other signal takes over.
3251
+ */
3252
+ readonly showIdle: boolean;
2959
3253
  /**
2960
3254
  * The first `ValidationError` at this path in the deterministic
2961
3255
  * schema-declaration order — equivalent to `errors[0]`, exposed as
@@ -2977,6 +3271,53 @@ type FieldState<Value = unknown> = {
2977
3271
  */
2978
3272
  readonly firstError: ValidationError | undefined;
2979
3273
  readonly path: ReadonlyArray<string | number>;
3274
+ /**
3275
+ * Stable, SSR-safe DOM id for this field, unique across every mount
3276
+ * on the page. Derived from the form's key and this path, folded with
3277
+ * the form's per-mount `instanceId` so two simultaneous mounts of the
3278
+ * same keyed form never collide. Bind it to wire a label and its
3279
+ * input without inventing your own id:
3280
+ *
3281
+ * ```vue
3282
+ * <label :for="form.fields.email.id">Email</label>
3283
+ * <input :id="form.fields.email.id" v-register="form.register('email')" />
3284
+ * ```
3285
+ *
3286
+ * Treat as identity, not state: stable for the path across the form's
3287
+ * lifetime, opaque, not meant to be parsed.
3288
+ */
3289
+ readonly id: string;
3290
+ /**
3291
+ * Satellite ids derived from {@link id} for the elements that
3292
+ * describe this field. Wire them to an error node and a description
3293
+ * node so assistive tech announces them with the input. The
3294
+ * `v-register` directive points `aria-describedby` at `errorId`
3295
+ * automatically while the field is in its error state; you render the
3296
+ * matching element and id it:
3297
+ *
3298
+ * ```vue
3299
+ * <input v-register="form.register('email')" />
3300
+ * <span :id="form.fields.email.aria.errorId" v-if="form.fields.email.showErrors">
3301
+ * {{ form.fields.email.firstError?.message }}
3302
+ * </span>
3303
+ * ```
3304
+ *
3305
+ * `descriptionId` is for opt-in help text; chain it into your own
3306
+ * `aria-describedby` when you render a persistent description element.
3307
+ */
3308
+ readonly aria: {
3309
+ readonly errorId: string;
3310
+ readonly descriptionId: string;
3311
+ };
3312
+ /**
3313
+ * Stable identity for this field as an element of its parent array,
3314
+ * suitable as a Vue `:key` when iterating array elements. An allocated
3315
+ * token (not derived from the element's value) that follows the
3316
+ * element across inserts, removals, moves, and swaps, so a row keeps
3317
+ * its component instance across a reorder. Empty for fields that are
3318
+ * not array elements. Treat as opaque identity, not state.
3319
+ */
3320
+ readonly key: string;
2980
3321
  readonly blank: boolean;
2981
3322
  /**
2982
3323
  * Presentational label for this field. Resolves through the
@@ -3422,8 +3763,8 @@ type FormMeta<F = unknown> = FieldState<F> & {
3422
3763
  *
3423
3764
  * Pure introspection counter — useful for "this form has been
3424
3765
  * visited and left" UX (analytics, prior-step badges, layered
3425
- * `shouldShowErrors` predicates) but does NOT drive the library's
3426
- * default `shouldShowErrors` heuristic. The reveal-on-submit story
3766
+ * `getDisplayState` predicates) but does NOT drive the library's
3767
+ * default `getDisplayState` heuristic. The reveal-on-submit story
3427
3768
  * runs entirely through `submissionAttempts`, which
3428
3769
  * `wizard.handleSubmit` bumps on the active form at intermediate
3429
3770
  * steps and on every form at the final step.
@@ -4110,9 +4451,9 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4110
4451
  * paths in the persisted draft are preserved (this is a merge,
4111
4452
  * not a replace).
4112
4453
  *
4113
- * Throws `SensitivePersistFieldError` for sensitive-looking paths
4114
- * unless you pass `{ acknowledgeSensitive: true }`. No-op when
4115
- * `useForm({ persist })` wasn't configured.
4454
+ * For sensitive-looking paths, warns once and no-ops unless you pass
4455
+ * `{ acknowledgeSensitive: true }` — it never throws. Also a no-op
4456
+ * when `useForm({ persist })` wasn't configured.
4116
4457
  */
4117
4458
  persist: (path: FlatPath<Form>, options?: {
4118
4459
  acknowledgeSensitive?: boolean;
@@ -4217,6 +4558,48 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4217
4558
  move: <Path extends ArrayPath<Form>>(path: Path, from: number, to: number) => void;
4218
4559
  /** Replace the element at `index` with `value`. No-op when out of range. */
4219
4560
  replace: <Path extends ArrayPath<Form>>(path: Path, index: number, value: ArrayItem<Form, Path>) => void;
4561
+ /**
4562
+ * Read-only, reactive view of the array at `path` as one `FieldState`
4563
+ * per element, in index order. Each entry carries its element `key`,
4564
+ * an allocated identity token, so a `v-for` keyed by it keeps a row's
4565
+ * component instance across an insert, removal, move, or swap:
4566
+ *
4567
+ * ```vue
4568
+ * <div v-for="(row, i) in form.list('contacts')" :key="row.key">
4569
+ * <input v-register="form.register(`contacts.${i}.name`)" />
4570
+ * <p v-if="row.showErrors">{{ row.firstError?.message }}</p>
4571
+ * </div>
4572
+ * ```
4573
+ *
4574
+ * Entries are the same field states `form.fields` exposes, so reads
4575
+ * stay live. `form.fields(path)` remains the single aggregated
4576
+ * container for the whole array; `list` is the per-element view.
4577
+ * For a record, reach for `record`, which keys each entry by its own
4578
+ * key.
4579
+ */
4580
+ list: <Path extends ArrayPath<Form>>(path: Path) => readonly FieldState<ArrayItem<Form, Path>>[];
4581
+ /**
4582
+ * Read-only, reactive view of the record at `path` as one `FieldState`
4583
+ * per entry, keyed by the entry's own key. Where `list` hands back an
4584
+ * ordered array for an array path, `record` hands back a keyed object
4585
+ * for a record path, so you iterate it by key:
4586
+ *
4587
+ * ```vue
4588
+ * <div v-for="(field, key) in form.record('scoresByTeam')" :key="key">
4589
+ * <label>{{ key }}</label>
4590
+ * <input v-register="form.register(`scoresByTeam.${key}`)" />
4591
+ * <p v-if="field.showErrors">{{ field.firstError?.message }}</p>
4592
+ * </div>
4593
+ * ```
4594
+ *
4595
+ * Entries are the same field states `form.fields` exposes, so reads
4596
+ * stay live, and the keyed shape mirrors the record's own keys: an
4597
+ * entry appears once you write its key (`form.setValue`) and drops
4598
+ * when the key leaves. `form.fields(path)` remains the single
4599
+ * aggregated container for the whole record; `record` is the
4600
+ * per-entry view.
4601
+ */
4602
+ record: <Path extends RecordPath<Form>>(path: Path) => Readonly<Record<string, FieldState<RecordValue<Form, Path>>>>;
4220
4603
  /**
4221
4604
  * Read-only view of the form's blank path set. Reactive — Vue 3.5
4222
4605
  * tracks `.has()` / `for..of` / size accesses, so consumers can drive
@@ -4244,5 +4627,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4244
4627
  blankPaths: ComputedRef<BlankPathsView>;
4245
4628
  };
4246
4629
 
4247
- export { ROOT_PATH as Y, ROOT_PATH_KEY as Z, canonicalizePath as as, isPathPrefix as at, isUnset as au, parseDottedPath as av, unset as aw };
4248
- export type { RegisterDirective as $, AbstractSchema as A, OnSubmit as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload 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, PartialFlatPath as P, Path as Q, PathKey as R, PendingValidationStatus as S, PersistConfig as T, PersistConfigOptions as U, PersistIncludeMode as V, PersistOptInRegistry as W, Primitive as X, ReactiveValidationStatus as _, ApiErrorDetails as a, RegisterFlatPath as a0, RegisterModelDynamicCustomDirective as a1, RegisterOptions as a2, RegisterSelectModifier as a3, RegisterTextModifier as a4, RegisterTransform as a5, RegisterValue as a6, SchemaFactoryOptions as a7, Segment as a8, SetValueCallback as a9, SetValuePayload as aa, SettledValidationStatus as ab, ShouldShowErrors as ac, ShouldShowErrorsConfig as ad, SlimPrimitiveKind as ae, SlimRuntimeOf as af, SubmitHandler as ag, Unset as ah, UseFormConfiguration as ai, UseFormReturnType as aj, ValidateOn as ak, ValidateOnConfig as al, ValidationError as am, ValidationResponse as an, ValidationResponseWithoutValue as ao, ValueOfUnion as ap, WriteMeta as aq, WriteShape as ar, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, FieldState as m, FieldStateMap as n, FieldStateMapEntry as o, FlatPath as p, FormErrorRecord as q, FormErrorsSurface as r, FormKey as s, FormMeta as t, FormStorage as u, FormStorageKind as v, HistoryConfig as w, IsUnion as x, NestedType as y, OnInvalidSubmitPolicy as z };
4630
+ export { ROOT_PATH_KEY as $, ROOT_PATH as _, canonicalizePath as as, isPathPrefix as at, isUnset as au, parseDottedPath as av, unset as aw };
4631
+ export type { AbstractSchema as A, NestedType as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload 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, OnInvalidSubmitPolicy as P, OnSubmit as Q, PartialFlatPath as R, Path as S, PathKey as T, PendingValidationStatus as U, PersistConfig as V, PersistConfigOptions as W, PersistIncludeMode as X, PersistOptInRegistry as Y, Primitive as Z, ApiErrorDetails as a, ReactiveValidationStatus as a0, RegisterDirective as a1, RegisterFlatPath as a2, RegisterModelDynamicCustomDirective as a3, RegisterOptions as a4, RegisterSelectModifier as a5, RegisterTextModifier as a6, RegisterTransform as a7, RegisterValue as a8, SchemaFactoryOptions as a9, Segment as aa, SetValueCallback as ab, SetValuePayload as ac, SettledValidationStatus as ad, SlimPrimitiveKind as ae, SlimRuntimeOf as af, SubmitHandler as ag, Unset as ah, UseFormConfiguration as ai, UseFormReturnType as aj, ValidateOn as ak, ValidateOnConfig as al, ValidationError as am, ValidationResponse as an, ValidationResponseWithoutValue as ao, ValueOfUnion as ap, WriteMeta as aq, WriteShape as ar, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayState as m, FieldState as n, FieldStateMap as o, FieldStateMapEntry as p, FlatPath as q, FormErrorRecord as r, FormErrorsSurface as s, FormKey as t, FormMeta as u, FormStorage as v, FormStorageKind as w, GetDisplayState as x, HistoryConfig as y, IsUnion as z };