attaform 0.23.0 → 0.24.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 (99) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/devtools.cjs +1 -1
  3. package/dist/chunks/devtools.mjs +1 -1
  4. package/dist/chunks/fingerprint2.cjs +1 -1
  5. package/dist/chunks/fingerprint2.mjs +1 -1
  6. package/dist/index.cjs +5 -150
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +6 -132
  9. package/dist/index.d.mts +6 -132
  10. package/dist/index.d.ts +6 -132
  11. package/dist/index.mjs +5 -150
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/nuxt.d.cts +1 -1
  14. package/dist/nuxt.d.mts +1 -1
  15. package/dist/nuxt.d.ts +1 -1
  16. package/dist/runtime/components/AttaformDevtoolsPanel.vue +2 -2
  17. package/dist/runtime/plugins/attaform.cjs +2 -2
  18. package/dist/runtime/plugins/attaform.mjs +2 -2
  19. package/dist/shared/{attaform.Dx9-QQE2.mjs → attaform.0-00cYGw.mjs} +2 -2
  20. package/dist/shared/{attaform.Dx9-QQE2.mjs.map → attaform.0-00cYGw.mjs.map} +1 -1
  21. package/dist/shared/{attaform.D52oJiYC.d.cts → attaform.4zesozTg.d.mts} +38 -6
  22. package/dist/shared/{attaform.Db4E4IW6.cjs → attaform.B7UdTs_o.cjs} +79 -79
  23. package/dist/shared/attaform.B7UdTs_o.cjs.map +1 -0
  24. package/dist/shared/{attaform.BibT5AS_.cjs → attaform.BOi6n2Pn.cjs} +2 -2
  25. package/dist/shared/{attaform.BibT5AS_.cjs.map → attaform.BOi6n2Pn.cjs.map} +1 -1
  26. package/dist/shared/{attaform.DCkSNnPr.d.mts → attaform.Bk7vnQhG.d.cts} +38 -6
  27. package/dist/shared/{attaform.BGMRvckW.d.ts → attaform.Bq6Copxn.d.cts} +41 -25
  28. package/dist/shared/{attaform.DuPneYR0.d.cts → attaform.BrFPMFgi.d.ts} +41 -25
  29. package/dist/shared/{attaform.alpG7rT7.mjs → attaform.BunnTiTw.mjs} +4 -4
  30. package/dist/shared/attaform.BunnTiTw.mjs.map +1 -0
  31. package/dist/shared/{attaform.DrY8srOp.d.mts → attaform.BwAcpoRw.d.mts} +41 -25
  32. package/dist/shared/{attaform.CaYj3ZfY.cjs → attaform.C-1W0T1n.cjs} +6 -4
  33. package/dist/shared/attaform.C-1W0T1n.cjs.map +1 -0
  34. package/dist/shared/{attaform.Dd1Kmmaj.mjs → attaform.C-tQKknW.mjs} +6 -4
  35. package/dist/shared/attaform.C-tQKknW.mjs.map +1 -0
  36. package/dist/shared/{attaform.BhI9Icek.mjs → attaform.C0au8oXd.mjs} +36 -31
  37. package/dist/shared/attaform.C0au8oXd.mjs.map +1 -0
  38. package/dist/shared/attaform.CjdepGnw.cjs +27 -0
  39. package/dist/shared/attaform.CjdepGnw.cjs.map +1 -0
  40. package/dist/shared/{attaform.DbyTD8N2.cjs → attaform.Cn6JoG9o.cjs} +8 -6
  41. package/dist/shared/attaform.Cn6JoG9o.cjs.map +1 -0
  42. package/dist/shared/{attaform.Cmb_LCie.cjs → attaform.CrrIaHM8.cjs} +4 -4
  43. package/dist/shared/attaform.CrrIaHM8.cjs.map +1 -0
  44. package/dist/shared/{attaform.BJnNK75Y.d.mts → attaform.DBhrKb2j.d.cts} +206 -168
  45. package/dist/shared/{attaform.BJnNK75Y.d.ts → attaform.DBhrKb2j.d.mts} +206 -168
  46. package/dist/shared/{attaform.BJnNK75Y.d.cts → attaform.DBhrKb2j.d.ts} +206 -168
  47. package/dist/shared/{attaform.DsQkXE3o.cjs → attaform.DRQjF16I.cjs} +248 -195
  48. package/dist/shared/attaform.DRQjF16I.cjs.map +1 -0
  49. package/dist/shared/attaform.DdjDqTah.d.cts +56 -0
  50. package/dist/shared/attaform.DdjDqTah.d.mts +56 -0
  51. package/dist/shared/attaform.DdjDqTah.d.ts +56 -0
  52. package/dist/shared/{attaform.WEwfXcHq.d.ts → attaform.Df4xXKbE.d.ts} +38 -6
  53. package/dist/shared/{attaform.BFWb6hDk.mjs → attaform.DhXl0Kdr.mjs} +7 -1
  54. package/dist/shared/attaform.DhXl0Kdr.mjs.map +1 -0
  55. package/dist/shared/{attaform.CR6wGvNu.cjs → attaform.DwLw3Kzv.cjs} +7 -1
  56. package/dist/shared/attaform.DwLw3Kzv.cjs.map +1 -0
  57. package/dist/shared/{attaform.CzVta5o2.mjs → attaform.FY5r1BpA.mjs} +8 -6
  58. package/dist/shared/attaform.FY5r1BpA.mjs.map +1 -0
  59. package/dist/shared/{attaform.CtJOd7ox.mjs → attaform.NWrEGrNo.mjs} +247 -193
  60. package/dist/shared/attaform.NWrEGrNo.mjs.map +1 -0
  61. package/dist/shared/attaform.WvcckZMD.mjs +21 -0
  62. package/dist/shared/attaform.WvcckZMD.mjs.map +1 -0
  63. package/dist/transforms.cjs +1 -1
  64. package/dist/transforms.mjs +1 -1
  65. package/dist/vite.cjs +1 -1
  66. package/dist/vite.mjs +1 -1
  67. package/dist/zod-v3.cjs +2 -2
  68. package/dist/zod-v3.d.cts +12 -11
  69. package/dist/zod-v3.d.mts +12 -11
  70. package/dist/zod-v3.d.ts +12 -11
  71. package/dist/zod-v3.mjs +2 -2
  72. package/dist/zod-v4.cjs +2 -2
  73. package/dist/zod-v4.d.cts +6 -5
  74. package/dist/zod-v4.d.mts +6 -5
  75. package/dist/zod-v4.d.ts +6 -5
  76. package/dist/zod-v4.mjs +2 -2
  77. package/dist/zod.cjs +5 -5
  78. package/dist/zod.cjs.map +1 -1
  79. package/dist/zod.d.cts +21 -52
  80. package/dist/zod.d.mts +21 -52
  81. package/dist/zod.d.ts +21 -52
  82. package/dist/zod.mjs +5 -5
  83. package/dist/zod.mjs.map +1 -1
  84. package/package.json +1 -1
  85. package/dist/shared/attaform.BFWb6hDk.mjs.map +0 -1
  86. package/dist/shared/attaform.BhI9Icek.mjs.map +0 -1
  87. package/dist/shared/attaform.CR6wGvNu.cjs.map +0 -1
  88. package/dist/shared/attaform.CaYj3ZfY.cjs.map +0 -1
  89. package/dist/shared/attaform.Cmb_LCie.cjs.map +0 -1
  90. package/dist/shared/attaform.CtJOd7ox.mjs.map +0 -1
  91. package/dist/shared/attaform.CzVta5o2.mjs.map +0 -1
  92. package/dist/shared/attaform.Db4E4IW6.cjs.map +0 -1
  93. package/dist/shared/attaform.DbyTD8N2.cjs.map +0 -1
  94. package/dist/shared/attaform.Dd1Kmmaj.mjs.map +0 -1
  95. package/dist/shared/attaform.DsQkXE3o.cjs.map +0 -1
  96. package/dist/shared/attaform.alpG7rT7.mjs.map +0 -1
  97. package/dist/shared/attaform.nf83TIR5.d.cts +0 -35
  98. package/dist/shared/attaform.nf83TIR5.d.mts +0 -35
  99. package/dist/shared/attaform.nf83TIR5.d.ts +0 -35
@@ -101,10 +101,11 @@ type Path = readonly Segment[];
101
101
  * parseDottedPath('') // [''] (the empty-string key)
102
102
  * ```
103
103
  *
104
- * The empty-string input `''` is the **literal empty-key path**, not
105
- * the root. Use the array form `[]` for root. Form-level errors
106
- * (root `.refine()`) live at the empty-string path bucket so
107
- * `errors('')` returns them without sweeping every field error too.
104
+ * The empty-string input `''` is the **literal empty-key path** `['']`,
105
+ * an ordinary (if rare) field address, not the root. Use the array
106
+ * form `[]` for the root. Form-level errors (root `.refine()`,
107
+ * `setErrors` with no path) live at the root path `[]` and are read via
108
+ * `errors([])`, never `errors('')`.
108
109
  *
109
110
  * Throws `InvalidPathError` for paths with empty INTERNAL segments
110
111
  * (`'a..b'`, leading or trailing dots). For keys containing literal
@@ -132,8 +133,17 @@ declare function canonicalizePath(input: string | Path): {
132
133
  key: PathKey;
133
134
  };
134
135
  /**
135
- * The root path — an empty segment tuple. Pass to APIs that accept
136
- * a `Path` to address the form value as a whole.
136
+ * The root path — an empty segment tuple. Pass to APIs that accept a
137
+ * `Path` to address the form value as a whole, and the home for
138
+ * form-level (global) errors: root `.refine()` messages, hydration
139
+ * failures, and `setErrors` entries with no path all live at `[]`. Aggregate
140
+ * reads (`errors()`, `meta.errors`) surface them alongside field
141
+ * errors; `errors([])` returns the global bucket alone.
142
+ *
143
+ * The empty SEGMENT tuple `[]` is structurally unconstructible as a
144
+ * field path, so it can never collide with a schema key, unlike the
145
+ * empty STRING key `''` (path `['']`, key `'[""]'`), which is an
146
+ * ordinary field address.
137
147
  */
138
148
  declare const ROOT_PATH: Path;
139
149
  /** Stable string key for the root path. */
@@ -248,8 +258,17 @@ type FlatPathBuilder<Form, Mode extends 'partial' | 'register', Key extends keyo
248
258
  * Inlining at consumer call sites compounds into TS2589 territory
249
259
  * when multiple complex forms share a scope. Consumers should reach
250
260
  * for `FlatPath` instead; this alias is not part of the stable surface.
261
+ *
262
+ * The `Form extends unknown` wrapper distributes over a top-level
263
+ * union so each member contributes its OWN `keyof` — a discriminated-
264
+ * union root (`z.discriminatedUnion`) thus exposes every variant's
265
+ * keys as addressable paths, not just the discriminator that naked
266
+ * `keyof (A | B)` would intersect to. For a single object / array /
267
+ * record `Form` it is a one-member no-op and stays byte-identical to
268
+ * the prior walk. Interior unions already distribute inside
269
+ * `FlatPathBuilder` via its `infer Value` step.
251
270
  */
252
- type PartialFlatPath<Form, Key extends keyof Form = keyof Form> = FlatPathBuilder<Form, 'partial', Key>;
271
+ type PartialFlatPath<Form> = Form extends unknown ? FlatPathBuilder<Form, 'partial'> : never;
253
272
  /**
254
273
  * Union of dotted-string paths reachable inside `Form`, e.g. for
255
274
  * `{ user: { email: string }, items: string[] }`:
@@ -260,7 +279,7 @@ type PartialFlatPath<Form, Key extends keyof Form = keyof Form> = FlatPathBuilde
260
279
  * `register(path)`, `toRef(path)`, etc.) so paths autocomplete in
261
280
  * the IDE and typos compile-error.
262
281
  */
263
- type FlatPath<Form, Key extends keyof Form = keyof Form> = PartialFlatPath<Form, Key>;
282
+ type FlatPath<Form> = PartialFlatPath<Form>;
264
283
  /**
265
284
  * Convert a tuple of path segments to its dotted-string equivalent.
266
285
  *
@@ -645,25 +664,45 @@ interface SchemaFactoryOptions {
645
664
  /** Resolved recursion ceiling (per-form > app-default > library default). */
646
665
  maxRecursionDepth: number;
647
666
  }
667
+ /**
668
+ * A JSON-serialisable value: the recursive shape of anything that
669
+ * survives a `JSON.stringify` / `JSON.parse` round-trip unchanged.
670
+ * The type of `ValidationError.data`.
671
+ *
672
+ * The object arm is a named interface rather than an inline index
673
+ * signature: TypeScript expands an anonymous recursive alias eagerly
674
+ * and tips into a depth-limit error (TS2589) when `Json` is checked
675
+ * inside a large structural type (e.g. the form store). A named
676
+ * reference defers that expansion.
677
+ */
678
+ type Json = string | number | boolean | null | JsonArray | JsonObject;
679
+ interface JsonArray extends Array<Json> {
680
+ }
681
+ interface JsonObject {
682
+ [key: string]: Json;
683
+ }
648
684
  /**
649
685
  * One validation failure. `path` points at the offending field as a
650
686
  * structured array — `['user', 'address', 0, 'line1']` for a nested
651
- * field, `['']` (the empty-string path) for a form-level error
652
- * (root `.refine()` messages, `setFormErrors()` entries, server-
653
- * emitted form banners). `formKey` identifies which form produced
654
- * the error so a single error list can be routed to multiple forms.
687
+ * field, `[]` (the root path) for a form-level error (root `.refine()`
688
+ * messages, `setErrors` entries with no path, hydration failures,
689
+ * server-emitted form banners). `formKey` identifies which form
690
+ * produced the error so a single error list can be routed to multiple
691
+ * forms. The optional `data` slot carries an arbitrary server payload.
655
692
  *
656
693
  * Returned by `validate()` / `validateAsync()` / `handleSubmit`'s
657
- * `onError` callback, and by `parseApiErrors` for server responses.
694
+ * `onError` callback, and accepted (leniently, as `ErrorInput`) by
695
+ * `form.setErrors`.
658
696
  */
659
697
  type ValidationError = {
660
698
  /** Human-readable message describing the failure. */
661
699
  message: string;
662
700
  /**
663
- * Structured path of the offending field. The empty-string path
664
- * `['']` is the form-level bucket — the dedicated home for errors
665
- * that don't belong to any specific field, distinct from the
666
- * whole-form subtree address `[]`.
701
+ * Structured path of the offending field. The root path `[]` is the
702
+ * form-level bucket — the dedicated home for errors that don't belong
703
+ * to any specific field (root `.refine()`, `setErrors`, hydration
704
+ * failures). The empty-STRING path `['']` is unrelated: an ordinary
705
+ * literal empty-key field.
667
706
  */
668
707
  path: (string | number)[];
669
708
  /** Identifies which form produced this error. */
@@ -680,6 +719,38 @@ type ValidationError = {
680
719
  * exact-message string matching.
681
720
  */
682
721
  code: string;
722
+ /**
723
+ * Optional structured payload attached to the error. Attaform never
724
+ * sets or reads this; it is a passthrough slot for whatever a server
725
+ * sends alongside the message (a captcha challenge, a lockout
726
+ * `unlocks_at` timestamp, an MFA step-up descriptor) so the UI can
727
+ * act on it. Survives serialise / hydrate and undo / redo unchanged.
728
+ */
729
+ data?: Json | null;
730
+ };
731
+ /**
732
+ * The lenient input shape `form.setErrors` accepts: a real `Error`, or
733
+ * a partial `ValidationError` where every field is optional.
734
+ *
735
+ * - `message`: omitted or empty coerces to `"Unknown error"`.
736
+ * - `path`: defaults to `[]` (a global, form-level error). Ignored by
737
+ * the path-scoped `setErrors(path, …)` form, which stamps its own.
738
+ * - `code`: defaults to `atta:user-error`.
739
+ * - `data`: forwarded verbatim onto the produced `ValidationError`.
740
+ * - `formKey`: accepted but ignored — the form always stamps its own.
741
+ *
742
+ * Because every field is optional and `formKey` is accepted-and-ignored,
743
+ * `ValidationError` is a subtype of `ErrorInput`: a `ValidationError[]`
744
+ * you read back, or a server response that already emits the shape, pipes
745
+ * straight into `form.setErrors` with no adapter and no excess-property
746
+ * friction.
747
+ */
748
+ type ErrorInput = Error | {
749
+ message?: string;
750
+ path?: (string | number)[];
751
+ code?: string;
752
+ data?: Json | null;
753
+ formKey?: FormKey;
683
754
  };
684
755
  /** Settled validation result when the form (or subtree) parsed successfully. */
685
756
  type ValidationResponseSuccess<TData> = {
@@ -1931,9 +2002,11 @@ type AttaformDefaults = {
1931
2002
  */
1932
2003
  type OnSubmit<Form extends GenericForm> = (form: Form) => void | Promise<void>;
1933
2004
  /**
1934
- * Callback invoked by `handleSubmit` when validation fails. Receives
1935
- * the full list of errors. Bind this when you want to react to
1936
- * submit failures explicitly (alongside or instead of the
2005
+ * Callback invoked by `handleSubmit` on a failed submit. Fires when
2006
+ * client validation fails AND when the submit callback leaves errors in
2007
+ * the user-error layer (the `setErrors(...); return` server-rejection
2008
+ * pattern). Receives the full list of errors. Bind this when you want to
2009
+ * react to submit failures explicitly (alongside or instead of the
1937
2010
  * automatic `onInvalidSubmit` UI nudge).
1938
2011
  */
1939
2012
  type OnError = (error: ValidationError[]) => void | Promise<void>;
@@ -2124,7 +2197,7 @@ type MetaTrackerValue = {
2124
2197
  */
2125
2198
  blank: boolean;
2126
2199
  };
2127
- type RegisterFlatPath<Form, Key extends keyof Form = keyof Form> = FlatPathBuilder<Form, 'register', Key>;
2200
+ type RegisterFlatPath<Form> = Form extends unknown ? FlatPathBuilder<Form, 'register'> : never;
2128
2201
  /**
2129
2202
  * A transformation applied to a field's value as user input flows
2130
2203
  * from DOM through the directive's assigner. Composes left-to-right
@@ -3306,13 +3379,17 @@ type LeafWalker<T, Kind extends keyof LeafSchemeFor<unknown>, StripOptional exte
3306
3379
  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;
3307
3380
  /**
3308
3381
  * Intersection augmenting every container in the `form.errors` walker
3309
- * with a `''` sentinel slot the per-container home for cross-field
3310
- * refine errors, server-side container marks, and (at root) form-level
3311
- * errors. Gated on `Kind extends 'errors'` so `form.values` and
3312
- * `form.fields` surfaces stay untouched. Carve-out for schemas that
3382
+ * with a `''` slot. At a depth >= 1 container it's the container-self
3383
+ * sentinel the home for cross-field refine errors and server-side
3384
+ * container marks; at root the `''` property addresses the literal
3385
+ * empty-key field. Gated on `Kind extends 'errors'` so `form.values`
3386
+ * and `form.fields` surfaces stay untouched. Carve-out for schemas that
3313
3387
  * legitimately declare a `''` field: the declared field type wins; at
3314
3388
  * runtime the two collide harmlessly (errors at the literal leaf and
3315
3389
  * any container-self errors share the slot via array concat).
3390
+ *
3391
+ * Global (root) errors are NOT this slot: they live at the root `[]`
3392
+ * and are read via the `errors([])` call-form, never `errors['']`.
3316
3393
  */
3317
3394
  type ContainerSelfErrorsSlot<T, Kind> = Kind extends 'errors' ? '' extends keyof T ? unknown : {
3318
3395
  readonly ['']: readonly ValidationError[];
@@ -3442,9 +3519,11 @@ type FormErrorsSurface<Form> = ErrorsProxyShape<Form> & {
3442
3519
  <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): readonly ValidationError[];
3443
3520
  (segments: ReadonlyArray<string | number>): readonly ValidationError[];
3444
3521
  /**
3445
- * No-arg call returns the form-level error aggregate — same as
3446
- * `form.errors([])` and `form.meta.errors`. Always a readonly array;
3447
- * empty when the form has no errors.
3522
+ * No-arg call returns the whole-form error aggregate — same as
3523
+ * `form.meta.errors`: every field error plus the global bucket.
3524
+ * Distinct from `form.errors([])`, which returns ONLY the global
3525
+ * (root) bucket. Always a readonly array; empty when the form has no
3526
+ * errors.
3448
3527
  */
3449
3528
  (): readonly ValidationError[];
3450
3529
  };
@@ -3500,63 +3579,6 @@ type ValuesSurface<F> = Readonly<LiftedValueShape<F>> & {
3500
3579
  (path: ReadonlyArray<string | number>): unknown;
3501
3580
  (): Readonly<F>;
3502
3581
  };
3503
- /**
3504
- * A single server-side error entry. Carries both the human-readable
3505
- * `message` and a stable `code` identifier — both fields are required.
3506
- * The `code` is stamped verbatim onto the produced `ValidationError`,
3507
- * so consumers can branch on it without string-matching on `message`.
3508
- *
3509
- * Pick a prefix for your codes (`api:`, `auth:`, etc.) and stay
3510
- * consistent so error-rendering UIs can switch on the code.
3511
- */
3512
- type ApiErrorEntry = {
3513
- /** Human-readable failure description. */
3514
- message: string;
3515
- /**
3516
- * Stable machine identifier for the failure (e.g. `'api:duplicate-email'`).
3517
- * Forwarded verbatim onto the produced `ValidationError`.
3518
- */
3519
- code: string;
3520
- };
3521
- /**
3522
- * Shape of a server-side error details record. Keys are dotted field
3523
- * paths; values are either a single entry, an array of entries, or a
3524
- * mix of structured and bare-string entries. Each entry is one of:
3525
- *
3526
- * - **Structured** — `{ message: string, code: string }`. The `code`
3527
- * forwards verbatim onto the produced `ValidationError`.
3528
- * - **Bare string** — a plain string. The Rails / Django REST
3529
- * Framework / Laravel default JSON shape (`{ field: ["msg"] }`).
3530
- * Synthesized into `{ message: <string>, code: <defaultCode> }` at
3531
- * parse time, where `defaultCode` defaults to `'api:unknown'` and
3532
- * is configurable via `parseApiErrors`'s options bag.
3533
- *
3534
- * Multiple entries at the same path produce multiple
3535
- * `ValidationError`s — useful for a single field that fails multiple
3536
- * checks (e.g. `password` is too short *and* missing a digit).
3537
- */
3538
- type ApiErrorDetails = Record<string, ApiErrorValue>;
3539
- /**
3540
- * One entry inside an {@link ApiErrorDetails} value — either the
3541
- * strict `{ message, code }` object, or a bare string (synthesised
3542
- * with the parser's `defaultCode`).
3543
- */
3544
- type ApiErrorValue = string | ApiErrorEntry | ReadonlyArray<string | ApiErrorEntry>;
3545
- /**
3546
- * Outer envelope `parseApiErrors` accepts. Both the wrapped form
3547
- * (`{ error: { details } }`) and the unwrapped form (`{ details }`)
3548
- * are recognised; raw detail records (`{ email: { message, code } }`)
3549
- * are also accepted directly.
3550
- */
3551
- type ApiErrorEnvelope = {
3552
- /** Wrapped error envelope — `parseApiErrors` reads `details` from inside. */
3553
- error?: {
3554
- details?: ApiErrorDetails;
3555
- [k: string]: unknown;
3556
- };
3557
- /** Unwrapped error envelope. */
3558
- details?: ApiErrorDetails;
3559
- };
3560
3582
  /**
3561
3583
  * Reactive form-level flags, counters, and aggregates returned as
3562
3584
  * `form.meta`. "Meta" because every other surface (`form.values`,
@@ -3629,10 +3651,14 @@ type FormMeta<F = unknown> = FieldState<F> & {
3629
3651
  *
3630
3652
  * The submit handler does NOT re-throw — its returned promise always
3631
3653
  * resolves, so binding it to `@submit.prevent` never manufactures a
3632
- * `window` unhandledrejection. This is the single channel for "the
3633
- * submit failed", read the same way in templates and after an
3634
- * imperative `await submit()`. Like `hydrateError`, it stays distinct
3635
- * from the curated user-error store: render it where you choose:
3654
+ * `window` unhandledrejection. This is the channel for an UNEXPECTED
3655
+ * submit failure (a thrown exception or rejected promise), read the
3656
+ * same way in templates and after an imperative `await submit()`. An
3657
+ * EXPECTED rejection handled the documented way (`setErrors(...)` then
3658
+ * `return`, no throw) surfaces through the error store and `onError`
3659
+ * instead, leaving `submitError` `null`. Like `hydrateError`, it stays
3660
+ * distinct from the curated user-error store: render it where you
3661
+ * choose:
3636
3662
  *
3637
3663
  * ```vue
3638
3664
  * <p v-if="form.meta.submitError">{{ form.meta.submitError.message }}</p>
@@ -3649,11 +3675,18 @@ type FormMeta<F = unknown> = FieldState<F> & {
3649
3675
  */
3650
3676
  readonly errorCount: number;
3651
3677
  /**
3652
- * `true` once a `handleSubmit` callback has resolved without
3653
- * throwing. Independent of `submissionAttempts` — a failed submit
3654
- * (validation failure or callback rejection) increments attempts but
3655
- * leaves `submitted` at `false`. Templates read it as "the form has
3656
- * been submitted successfully at least once."
3678
+ * `true` once a `handleSubmit` callback has resolved without throwing
3679
+ * AND left no errors behind. Independent of `submissionAttempts` — a
3680
+ * failed submit (validation failure, callback rejection, or a callback
3681
+ * that calls `setErrors` and returns) increments attempts but leaves
3682
+ * `submitted` at `false`. Templates read it as "the form has been
3683
+ * submitted successfully at least once."
3684
+ *
3685
+ * The error check is scoped to the user-error layer (`setErrors` /
3686
+ * `clearErrors`): the documented server-rejection pattern
3687
+ * (`setErrors(response.errors); return`) is a failed submit, so it does
3688
+ * not flip `submitted`. A background async refinement that writes a
3689
+ * schema error mid-submit does not retroactively fail it.
3657
3690
  *
3658
3691
  * Cleared by `form.reset()` alongside `submissionAttempts` and
3659
3692
  * `submitError`. For "the user has attempted a submit," read
@@ -3760,6 +3793,19 @@ interface BlankPathsView {
3760
3793
  /** Iterates the blank-marked paths as segment arrays. */
3761
3794
  [Symbol.iterator](): IterableIterator<Path>;
3762
3795
  }
3796
+ /**
3797
+ * The no-arg `form.record()` call form, present only when the form root
3798
+ * is itself an open record (`z.record(K, V)`). `string extends keyof
3799
+ * Form` is the open-keyset probe: true for `Record<string, V>`, false
3800
+ * for a fixed `z.object` shape. On a fixed object this resolves to
3801
+ * `unknown`, which contributes nothing to the intersection in `record`
3802
+ * below, so the no-arg call form simply does not exist there (and
3803
+ * `form.record()` stays a compile error, as it should when the root has
3804
+ * a closed key set). On a record root it mirrors the path-addressed
3805
+ * `record(path)` overload: one `FieldState` per entry, keyed by the
3806
+ * record's own runtime keys.
3807
+ */
3808
+ type RootRecordView<Form> = string extends keyof Form ? Form extends Record<string, infer RootValue> ? () => Readonly<Record<string, FieldState<RootValue>>> : unknown : unknown;
3763
3809
  type UseFormReturnType<Form extends GenericForm, GetValueFormType extends GenericForm = Form, ReadForm extends GenericForm = Form, K extends FormKey = FormKey> = {
3764
3810
  /**
3765
3811
  * Wraps your submit logic with validation and error routing.
@@ -4008,18 +4054,14 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4008
4054
  * user-typed values before they reach form state.
4009
4055
  */
4010
4056
  register: {
4011
- <Path extends RegisterFlatPath<Form, keyof Form>>(path: Path, options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, Path>>;
4012
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [RegisterFlatPath<Form, keyof Form>] ? unknown : never), options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>;
4057
+ <Path extends RegisterFlatPath<Form>>(path: Path, options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, Path>>;
4058
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [RegisterFlatPath<Form>] ? unknown : never), options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>;
4013
4059
  };
4014
4060
  /**
4015
4061
  * The form's identifier — either the explicit `key` passed to
4016
4062
  * `useForm` or an auto-generated unique id when `key` was omitted.
4017
- * Use it when feeding API errors through `parseApiErrors`:
4018
- *
4019
- * ```ts
4020
- * const result = parseApiErrors(serverPayload, { formKey: form.key })
4021
- * if (result.ok) form.setFieldErrors(result.errors)
4022
- * ```
4063
+ * Every `ValidationError` this form produces carries it as `formKey`,
4064
+ * so a shared error list can be routed back to the right form.
4023
4065
  *
4024
4066
  * Typed as the literal `K` when an explicit `key` was passed; falls
4025
4067
  * back to `FormKey` when omitted (auto-generated id).
@@ -4131,9 +4173,9 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4131
4173
  * (`form.errors['user.profile.email']`) — JS dot notation splits
4132
4174
  * on literal dots.
4133
4175
  *
4134
- * Read-only — populate via `setFieldErrors`, `addFieldErrors`, and
4135
- * `clearFieldErrors`. Server-side errors flow through
4136
- * `parseApiErrors` first.
4176
+ * Read-only — populate via `setErrors` / `clearErrors`. A server
4177
+ * response that already emits `ValidationError[]` pipes straight into
4178
+ * `setErrors` with no adapter.
4137
4179
  */
4138
4180
  errors: FormErrorsSurface<Form>;
4139
4181
  /**
@@ -4156,76 +4198,61 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4156
4198
  <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): Readonly<Ref<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>>;
4157
4199
  };
4158
4200
  /**
4159
- * Replace every field error for this form with the provided list.
4160
- * Useful after `parseApiErrors` produces a fresh batch from a
4161
- * server response.
4201
+ * Set the form's manual error layer. One surface for server-side
4202
+ * errors, optimistic-UI errors, and form-level banners: a field error
4203
+ * and a global (form-level) error are the same thing at different
4204
+ * paths, so there is no separate field/form split.
4162
4205
  *
4163
- * ```ts
4164
- * const result = parseApiErrors(payload, { formKey: form.key })
4165
- * if (result.ok) form.setFieldErrors(result.errors)
4166
- * ```
4167
- */
4168
- setFieldErrors: (errors: ValidationError[]) => void;
4169
- /**
4170
- * Append errors to the existing set without clearing prior entries.
4171
- * Use when reporting an additional issue alongside existing errors
4172
- * (e.g. a partial server response).
4173
- */
4174
- addFieldErrors: (errors: ValidationError[]) => void;
4175
- /**
4176
- * Clear errors. Pass a path to clear errors for a single field;
4177
- * call with no arguments to clear every error on the form.
4206
+ * Input is lenient ({@link ErrorInput}): a real `Error`, a partial
4207
+ * `{ message?, path?, code?, data? }`, or an array of either. The form
4208
+ * stamps its own `formKey`, defaults a missing `code` to
4209
+ * `atta:user-error`, and coerces a missing or empty `message` to
4210
+ * `"Unknown error"` instead of throwing. A server that already emits
4211
+ * `ValidationError[]` satisfies the input shape directly, so
4212
+ * `form.setErrors(response.errors)` needs no adapter.
4178
4213
  *
4179
- * ```ts
4180
- * form.clearFieldErrors('email') // clear one field
4181
- * form.clearFieldErrors() // clear all
4182
- * ```
4183
- */
4184
- clearFieldErrors: (path?: string | (string | number)[]) => void;
4185
- /**
4186
- * Replace the form-level errors — the entries at the empty path
4187
- * (`path: []`) — without disturbing any field-level errors. Pass an
4188
- * empty array to clear them all.
4214
+ * Three call forms, mirroring `setValue`:
4189
4215
  *
4190
4216
  * ```ts
4191
- * form.setFormErrors([{ message: 'Capacity exceeded' }])
4192
- * form.setFormErrors([
4193
- * { message: 'Capacity exceeded', code: 'capacity:exceeded' },
4194
- * { message: 'Pickup window full' },
4195
- * ])
4196
- * form.setFormErrors([]) // clear
4217
+ * // Whole-layer replace. An entry with no `path` is a global,
4218
+ * // form-level error (path `[]`); add a `path` to target a field.
4219
+ * form.setErrors([{ path: ['email'], message: 'Already taken' }])
4220
+ * form.setErrors({ message: 'Capacity exceeded' }) // global banner
4221
+ * form.setErrors(new Error('Network unreachable')) // message coerced
4222
+ *
4223
+ * // Functional update. `prev` is the current manual layer, flat.
4224
+ * form.setErrors((prev) => [...prev, { message: 'And one more' }])
4225
+ *
4226
+ * // Path-scoped. The path is stamped onto every entry, and only that
4227
+ * // path's bucket is replaced. `prev` is that path's manual errors.
4228
+ * form.setErrors('email', [{ message: 'Already taken' }])
4229
+ * form.setErrors(['profile', 'handle'], { message: 'Reserved' })
4230
+ * form.setErrors('email', (prev) => prev.slice(0, 1))
4197
4231
  * ```
4198
4232
  *
4199
- * Only `message` is required. `code` defaults to `'atta:form-error'`.
4200
- * Any caller-provided `path` or `formKey` is ignored `path` is
4201
- * always forced to `[]` (this API is form-level-only by definition)
4202
- * and `formKey` is filled in from the form instance. The lenient
4203
- * input shape lets you pipe `parseApiErrors` output (or any
4204
- * `ValidationError[]`) straight in:
4233
+ * Replaces only the manual layer; schema/validation errors live in a
4234
+ * separate store and merge on read. Pass `[]` to clear the manual
4235
+ * layer (or use `clearErrors`, which also clears the schema layer).
4236
+ */
4237
+ setErrors: {
4238
+ (update: (prev: ValidationError[]) => ErrorInput | ErrorInput[]): void;
4239
+ (errors: ErrorInput | ErrorInput[]): void;
4240
+ (path: string | (string | number)[], errors: ErrorInput | ErrorInput[] | ((prev: ValidationError[]) => ErrorInput | ErrorInput[])): void;
4241
+ };
4242
+ /**
4243
+ * Clear errors at one path, or everywhere. Clears BOTH the manual
4244
+ * layer (set through `setErrors`) and the schema/validation layer at
4245
+ * the target. With always-on validation the schema half re-populates
4246
+ * on the next mutation if the value is still invalid, so the cleared
4247
+ * state is short-lived for a field that is still wrong.
4205
4248
  *
4206
4249
  * ```ts
4207
- * const result = parseApiErrors(payload, { formKey: form.key })
4208
- * if (result.ok) form.setFormErrors(result.errors)
4250
+ * form.clearErrors('email') // one field
4251
+ * form.clearErrors([]) // the global, form-level bucket
4252
+ * form.clearErrors() // every error on the form
4209
4253
  * ```
4210
- *
4211
- * Form-level errors land at the empty-string path bucket
4212
- * (`path: ['']`). They surface in `form.meta.errors` (alongside
4213
- * field errors), in `form.errors()` / `form.errors([])` (whole-form
4214
- * subtree aggregates), and — uniquely — in `form.errors('')`,
4215
- * which returns ONLY the form-level bucket. They're excluded from
4216
- * the path-keyed `form.errors` drill proxy because no nested-object
4217
- * key represents the empty-string path. Read them via
4218
- * `meta.errors.filter(e => e.path.length === 1 && e.path[0] === '')`
4219
- * if you need a programmatic split.
4220
4254
  */
4221
- setFormErrors: (errors: ReadonlyArray<Partial<ValidationError> & {
4222
- message: string;
4223
- }>) => void;
4224
- /**
4225
- * Clear every form-level error. Equivalent to `setFormErrors([])`;
4226
- * field errors are untouched.
4227
- */
4228
- clearFormErrors: () => void;
4255
+ clearErrors: (path?: string | (string | number)[]) => void;
4229
4256
  /**
4230
4257
  * Form-level reactive flags, counters, and aggregates (`dirty`,
4231
4258
  * `valid`, `submitting`, `submissionAttempts`, and the flat `errors`
@@ -4436,8 +4463,19 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4436
4463
  * when the key leaves. `form.fields(path)` remains the single
4437
4464
  * aggregated container for the whole record; `record` is the
4438
4465
  * per-entry view.
4466
+ *
4467
+ * When the form root is itself a record (`useForm({ schema:
4468
+ * z.record(K, V) })` — a dictionary form), call `form.record()` with
4469
+ * no argument for the root entry view:
4470
+ *
4471
+ * ```vue
4472
+ * <div v-for="(member, id) in form.record()" :key="id">
4473
+ * <input v-register="form.register(id)" />
4474
+ * <p v-if="member.showErrors">{{ member.firstError?.message }}</p>
4475
+ * </div>
4476
+ * ```
4439
4477
  */
4440
- record: <Path extends RecordPath<Form>>(path: Path) => Readonly<Record<string, FieldState<RecordValue<Form, Path>>>>;
4478
+ record: RootRecordView<Form> & (<Path extends RecordPath<Form>>(path: Path) => Readonly<Record<string, FieldState<RecordValue<Form, Path>>>>);
4441
4479
  /**
4442
4480
  * Read-only view of the form's blank path set. Reactive — Vue 3.5
4443
4481
  * tracks `.has()` / `for..of` / size accesses, so consumers can drive
@@ -4465,5 +4503,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4465
4503
  blankPaths: ComputedRef<BlankPathsView>;
4466
4504
  };
4467
4505
 
4468
- export { ROOT_PATH as $, ROOT_PATH_KEY as a0, canonicalizePath as an, isPathPrefix as ao, isUnset as ap, parseDottedPath as aq, unset as ar };
4469
- export type { AttaformDefaults as A, HistoryConfig as B, CoercionEntry as C, DefaultValuesInput as D, ErrorsProxyShape as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, IsUnion as J, JoinSegments as K, KeyofUnion as L, LiftedValueShape as M, MetaTrackerValue as N, NestedReadType as O, NestedType as P, OnError as Q, RegisterModelDynamicCustomDirective as R, OnInvalidSubmitPolicy as S, OnSubmit as T, UseFormConfiguration as U, ValidationError as V, PartialFlatPath as W, Path as X, PathKey as Y, PendingValidationStatus as Z, Primitive as _, AbstractSchema as a, ReactiveValidationStatus as a1, RegisterDirective as a2, RegisterFlatPath as a3, RegisterOptions as a4, RegisterSelectModifier as a5, RegisterTextModifier as a6, RegisterTransform as a7, Segment as a8, SetValueCallback as a9, SetValuePayload as aa, SettledValidationStatus as ab, SlimPrimitiveKind as ac, SlimRuntimeOf as ad, SubmitHandler as ae, Unset as af, ValidateOn as ag, ValidateOnConfig as ah, ValidationResponse as ai, ValidationResponseWithoutValue as aj, ValueOfUnion as ak, WriteMeta as al, WriteShape as am, SchemaFactoryOptions as as, TransformAbortHolder as at, UseFormReturnType as b, RegisterValue as c, GetDisplayState as d, ApiErrorEnvelope as e, ApiErrorDetails as f, ApiErrorEntry as g, ArrayItem as h, ArrayPath as i, CoercionRegistry as j, CoercionResult as k, CustomDirectiveRegisterAssignerFn as l, DeepPartial as m, DefaultValuesResponse as n, DefaultValuesShape as o, DisplayCtx as p, DisplayMachine as q, DisplayState as r, FieldMetaPayload as s, FieldState as t, FieldStateMap as u, FieldStateMapEntry as v, FlatPath as w, FormErrorRecord as x, FormErrorsSurface as y, FormMeta as z };
4506
+ export { ROOT_PATH as Z, ROOT_PATH_KEY as _, canonicalizePath as am, isPathPrefix as an, isUnset as ao, parseDottedPath as ap, unset as aq };
4507
+ export type { ReactiveValidationStatus as $, AttaformDefaults as A, Json as B, CoercionEntry 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, NestedType as O, OnError as P, OnInvalidSubmitPolicy as Q, RegisterModelDynamicCustomDirective as R, OnSubmit as S, PartialFlatPath as T, UseFormConfiguration as U, Path as V, PathKey as W, PendingValidationStatus as X, Primitive as Y, AbstractSchema as a, RegisterDirective as a0, RegisterFlatPath as a1, RegisterOptions as a2, RegisterSelectModifier as a3, RegisterTextModifier as a4, RegisterTransform as a5, Segment as a6, SetValueCallback as a7, SetValuePayload as a8, SettledValidationStatus as a9, SlimPrimitiveKind as aa, SlimRuntimeOf as ab, SubmitHandler as ac, Unset as ad, ValidateOn as ae, ValidateOnConfig as af, ValidationError as ag, ValidationResponse as ah, ValidationResponseWithoutValue as ai, ValueOfUnion as aj, WriteMeta as ak, WriteShape as al, SchemaFactoryOptions as ar, TransformAbortHolder as as, UseFormReturnType as b, RegisterValue as c, GetDisplayState as d, ArrayItem as e, ArrayPath as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DeepPartial as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayCtx as m, DisplayMachine as n, DisplayState as o, ErrorsProxyShape as p, FieldMetaPayload as q, FieldState as r, FieldStateMap as s, FieldStateMapEntry as t, FlatPath as u, FormErrorRecord as v, FormErrorsSurface as w, FormMeta as x, HistoryConfig as y, IsUnion as z };