attaform 0.27.6 → 0.28.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 (184) hide show
  1. package/dist/abstract.d.mts +5 -4
  2. package/dist/abstract.mjs +4 -3
  3. package/dist/chunks/fingerprint.mjs +0 -1
  4. package/dist/chunks/fingerprint2.mjs +1 -2
  5. package/dist/dev/abstract.mjs +4 -0
  6. package/dist/{chunks → dev/chunks}/dev-key-collision-warnings.mjs +0 -1
  7. package/dist/{chunks → dev/chunks}/devtools.mjs +5 -6
  8. package/dist/{chunks/fingerprint.cjs → dev/chunks/fingerprint.mjs} +7 -10
  9. package/dist/{chunks/fingerprint2.cjs → dev/chunks/fingerprint2.mjs} +32 -35
  10. package/dist/dev/directive.mjs +1 -0
  11. package/dist/dev/history.mjs +89 -0
  12. package/dist/dev/index.mjs +5 -0
  13. package/dist/dev/runtime/plugins/attaform.mjs +40 -0
  14. package/dist/{shared/attaform.D6tk90Rc.cjs → dev/shared/attaform-dev.6qxD-Yqo.mjs} +187 -43
  15. package/dist/{shared/attaform.CQN9R62B.cjs → dev/shared/attaform-dev.BJGA_UOS.mjs} +1 -4
  16. package/dist/{shared/attaform.DYEr28Pt.mjs → dev/shared/attaform-dev.BR8u0YOZ.mjs} +6 -7
  17. package/dist/dev/shared/attaform-dev.BXE2cr0L.mjs +220 -0
  18. package/dist/dev/shared/attaform-dev.By4PRjt0.mjs +248 -0
  19. package/dist/{shared/attaform.B1ZYt2Li.mjs → dev/shared/attaform-dev.D2I8e_Mm.mjs} +132 -568
  20. package/dist/dev/shared/attaform-dev.Dr2YzS5B.mjs +197 -0
  21. package/dist/{shared/attaform.nMaz2C5P.mjs → dev/shared/attaform-dev.P7cseiEG.mjs} +178 -397
  22. package/dist/{shared/attaform.CKH2lGdL.mjs → dev/shared/attaform-dev.WmuhD_CZ.mjs} +3944 -5056
  23. package/dist/{shared/attaform.DMxM2kol.mjs → dev/shared/attaform-dev.oYrkP9dj.mjs} +160 -508
  24. package/dist/dev/zod-v3.mjs +17 -0
  25. package/dist/dev/zod-v4.mjs +16 -0
  26. package/dist/dev/zod.mjs +5 -0
  27. package/dist/directive.d.mts +20 -0
  28. package/dist/directive.mjs +1 -0
  29. package/dist/esbuild.mjs +0 -1
  30. package/dist/history.d.mts +59 -0
  31. package/dist/history.mjs +89 -0
  32. package/dist/index.d.mts +11 -5
  33. package/dist/index.mjs +5 -4
  34. package/dist/nuxt.d.mts +1 -1
  35. package/dist/nuxt.mjs +5 -2
  36. package/dist/rollup.mjs +0 -1
  37. package/dist/rspack.d.mts +6 -1
  38. package/dist/rspack.mjs +0 -1
  39. package/dist/runtime/components/AttaformDevtoolsPanel.vue +9 -8
  40. package/dist/runtime/plugins/attaform.mjs +1 -2
  41. package/dist/shared/{attaform.Fp3sCrVs.d.ts → attaform.B4VkS43-.d.mts} +2 -2
  42. package/dist/shared/{attaform.DrvMscNX.cjs → attaform.B8k8JrmK.mjs} +4073 -5630
  43. package/dist/shared/attaform.BJGA_UOS.mjs +0 -1
  44. package/dist/shared/{attaform.CoR4mWfv.mjs → attaform.C09fpdLS.mjs} +172 -16
  45. package/dist/shared/{attaform.D8kliaKw.cjs → attaform.CJP3RkGa.mjs} +141 -590
  46. package/dist/shared/{attaform.DniawQLP.d.cts → attaform.Cgu-hdSb.d.mts} +268 -68
  47. package/dist/shared/attaform.Cs2Rbdlo.mjs +0 -1
  48. package/dist/shared/{attaform.CHlJLAiv.d.mts → attaform.D5V4tZWq.d.mts} +69 -179
  49. package/dist/shared/{attaform.B9Wyhfro.cjs → attaform.D6DCnnQu.mjs} +11 -16
  50. package/dist/shared/attaform.DhnPb6nK.mjs +0 -1
  51. package/dist/shared/attaform.Dq4vWxuG.mjs +197 -0
  52. package/dist/shared/attaform.JfSWdEcb.d.mts +44 -0
  53. package/dist/shared/attaform.KvlEedlF.mjs +0 -1
  54. package/dist/shared/{attaform.DdQN7sgN.cjs → attaform.NLmerepI.mjs} +175 -557
  55. package/dist/shared/attaform.h0i6LijT.mjs +242 -0
  56. package/dist/shared/{attaform.Biytrv0-.cjs → attaform.uLbun31C.mjs} +204 -544
  57. package/dist/shared/attaform.v0fQtWyA.mjs +0 -1
  58. package/dist/shared/attaform.wfY8XFvm.mjs +197 -0
  59. package/dist/transforms.mjs +0 -1
  60. package/dist/vite.d.mts +17 -5
  61. package/dist/vite.mjs +24 -2
  62. package/dist/webpack.d.mts +6 -1
  63. package/dist/webpack.mjs +0 -1
  64. package/dist/zod-v3.d.mts +11 -6
  65. package/dist/zod-v3.mjs +17 -4
  66. package/dist/zod-v4.d.mts +12 -6
  67. package/dist/zod-v4.mjs +16 -4
  68. package/dist/zod.d.mts +4 -3
  69. package/dist/zod.mjs +5 -4
  70. package/package.json +23 -8
  71. package/skills/attaform/SKILL.md +2 -1
  72. package/dist/abstract.cjs +0 -48
  73. package/dist/abstract.cjs.map +0 -1
  74. package/dist/abstract.d.cts +0 -42
  75. package/dist/abstract.d.ts +0 -42
  76. package/dist/abstract.mjs.map +0 -1
  77. package/dist/chunks/dev-key-collision-warnings.cjs +0 -25
  78. package/dist/chunks/dev-key-collision-warnings.cjs.map +0 -1
  79. package/dist/chunks/dev-key-collision-warnings.mjs.map +0 -1
  80. package/dist/chunks/devtools.cjs +0 -157
  81. package/dist/chunks/devtools.cjs.map +0 -1
  82. package/dist/chunks/devtools.mjs.map +0 -1
  83. package/dist/chunks/fingerprint.cjs.map +0 -1
  84. package/dist/chunks/fingerprint.mjs.map +0 -1
  85. package/dist/chunks/fingerprint2.cjs.map +0 -1
  86. package/dist/chunks/fingerprint2.mjs.map +0 -1
  87. package/dist/esbuild.cjs +0 -28
  88. package/dist/esbuild.cjs.map +0 -1
  89. package/dist/esbuild.d.cts +0 -56
  90. package/dist/esbuild.d.ts +0 -56
  91. package/dist/esbuild.mjs.map +0 -1
  92. package/dist/index.cjs +0 -51
  93. package/dist/index.cjs.map +0 -1
  94. package/dist/index.d.cts +0 -249
  95. package/dist/index.d.ts +0 -249
  96. package/dist/index.mjs.map +0 -1
  97. package/dist/nuxt.cjs +0 -84
  98. package/dist/nuxt.cjs.map +0 -1
  99. package/dist/nuxt.d.cts +0 -66
  100. package/dist/nuxt.d.ts +0 -66
  101. package/dist/nuxt.mjs.map +0 -1
  102. package/dist/rollup.cjs +0 -24
  103. package/dist/rollup.cjs.map +0 -1
  104. package/dist/rollup.d.cts +0 -35
  105. package/dist/rollup.d.ts +0 -35
  106. package/dist/rollup.mjs.map +0 -1
  107. package/dist/rspack.cjs +0 -10
  108. package/dist/rspack.cjs.map +0 -1
  109. package/dist/rspack.d.cts +0 -40
  110. package/dist/rspack.d.ts +0 -40
  111. package/dist/rspack.mjs.map +0 -1
  112. package/dist/runtime/components/AttaformDevtoolsPanel.vue.d.ts +0 -7
  113. package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +0 -35
  114. package/dist/runtime/plugins/attaform.cjs +0 -43
  115. package/dist/runtime/plugins/attaform.cjs.map +0 -1
  116. package/dist/runtime/plugins/attaform.d.cts +0 -5
  117. package/dist/runtime/plugins/attaform.d.ts +0 -5
  118. package/dist/runtime/plugins/attaform.mjs.map +0 -1
  119. package/dist/shared/attaform.B1ZYt2Li.mjs.map +0 -1
  120. package/dist/shared/attaform.B9Wyhfro.cjs.map +0 -1
  121. package/dist/shared/attaform.BGDV42hI.d.cts +0 -232
  122. package/dist/shared/attaform.BJGA_UOS.mjs.map +0 -1
  123. package/dist/shared/attaform.Biytrv0-.cjs.map +0 -1
  124. package/dist/shared/attaform.Bn0IzhD_.cjs +0 -800
  125. package/dist/shared/attaform.Bn0IzhD_.cjs.map +0 -1
  126. package/dist/shared/attaform.C9fPTxjB.d.cts +0 -41
  127. package/dist/shared/attaform.C9fPTxjB.d.ts +0 -41
  128. package/dist/shared/attaform.CKH2lGdL.mjs.map +0 -1
  129. package/dist/shared/attaform.CQN9R62B.cjs.map +0 -1
  130. package/dist/shared/attaform.CoR4mWfv.mjs.map +0 -1
  131. package/dist/shared/attaform.Cs2Rbdlo.mjs.map +0 -1
  132. package/dist/shared/attaform.D6tk90Rc.cjs.map +0 -1
  133. package/dist/shared/attaform.D8kliaKw.cjs.map +0 -1
  134. package/dist/shared/attaform.DLknk9nF.d.mts +0 -232
  135. package/dist/shared/attaform.DMxM2kol.mjs.map +0 -1
  136. package/dist/shared/attaform.DRUT703x.d.cts +0 -56
  137. package/dist/shared/attaform.DRUT703x.d.ts +0 -56
  138. package/dist/shared/attaform.DYEr28Pt.mjs.map +0 -1
  139. package/dist/shared/attaform.DdQN7sgN.cjs.map +0 -1
  140. package/dist/shared/attaform.DhnPb6nK.mjs.map +0 -1
  141. package/dist/shared/attaform.DniawQLP.d.mts +0 -4763
  142. package/dist/shared/attaform.DniawQLP.d.ts +0 -4763
  143. package/dist/shared/attaform.DoQm_Vkd.cjs +0 -58
  144. package/dist/shared/attaform.DoQm_Vkd.cjs.map +0 -1
  145. package/dist/shared/attaform.DrvMscNX.cjs.map +0 -1
  146. package/dist/shared/attaform.F2Q8iv38.d.ts +0 -2494
  147. package/dist/shared/attaform.FmqKO1Ex.d.cts +0 -2494
  148. package/dist/shared/attaform.KvlEedlF.mjs.map +0 -1
  149. package/dist/shared/attaform.RaTzp1WF.cjs +0 -26
  150. package/dist/shared/attaform.RaTzp1WF.cjs.map +0 -1
  151. package/dist/shared/attaform.nMaz2C5P.mjs.map +0 -1
  152. package/dist/shared/attaform.v0fQtWyA.mjs.map +0 -1
  153. package/dist/shared/attaform.yUrz7C2z.cjs +0 -29
  154. package/dist/shared/attaform.yUrz7C2z.cjs.map +0 -1
  155. package/dist/transforms.cjs +0 -12
  156. package/dist/transforms.cjs.map +0 -1
  157. package/dist/transforms.d.cts +0 -73
  158. package/dist/transforms.d.ts +0 -73
  159. package/dist/transforms.mjs.map +0 -1
  160. package/dist/vite.cjs +0 -363
  161. package/dist/vite.cjs.map +0 -1
  162. package/dist/vite.d.cts +0 -133
  163. package/dist/vite.d.ts +0 -133
  164. package/dist/vite.mjs.map +0 -1
  165. package/dist/webpack.cjs +0 -10
  166. package/dist/webpack.cjs.map +0 -1
  167. package/dist/webpack.d.cts +0 -37
  168. package/dist/webpack.d.ts +0 -37
  169. package/dist/webpack.mjs.map +0 -1
  170. package/dist/zod-v3.cjs +0 -53
  171. package/dist/zod-v3.cjs.map +0 -1
  172. package/dist/zod-v3.d.cts +0 -246
  173. package/dist/zod-v3.d.ts +0 -246
  174. package/dist/zod-v3.mjs.map +0 -1
  175. package/dist/zod-v4.cjs +0 -55
  176. package/dist/zod-v4.cjs.map +0 -1
  177. package/dist/zod-v4.d.cts +0 -172
  178. package/dist/zod-v4.d.ts +0 -172
  179. package/dist/zod-v4.mjs.map +0 -1
  180. package/dist/zod.cjs +0 -51
  181. package/dist/zod.cjs.map +0 -1
  182. package/dist/zod.d.cts +0 -7
  183. package/dist/zod.d.ts +0 -7
  184. package/dist/zod.mjs.map +0 -1
@@ -164,6 +164,65 @@ declare const ROOT_PATH_KEY: PathKey;
164
164
  */
165
165
  declare function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean;
166
166
 
167
+ /**
168
+ * Per-path field status. Replaced wholesale (not mutated in place) on
169
+ * every change. Five semantic groups:
170
+ *
171
+ * - `connected` — is a DOM element registered for this path?
172
+ * - `focused` / `blurred` — DOM-state flags. `null` while no element
173
+ * is connected (no DOM means the concepts don't apply); plain
174
+ * booleans once connected, with the invariant `blurred === !focused`
175
+ * enforced by `markFocused`.
176
+ * - `touched` — focus/blur history, not DOM state. Always a plain
177
+ * boolean: `false` at registration, sticky `true` after first blur,
178
+ * cleared only by `form.reset()` / `form.resetField(path)`. Persists
179
+ * across disconnects so v-if'd-away fields don't lose their touched
180
+ * state on rehide (wizard "show review of touched fields" patterns
181
+ * rely on this).
182
+ * - `interacted` — value-mutation history, not DOM state. Plain
183
+ * boolean: `false` at registration, sticky `true` once the user
184
+ * issues a value edit through the directive's input listeners
185
+ * (never on hydration, default seeding, or programmatic setValue);
186
+ * cleared with `touched` by `form.reset()` / `form.resetField(path)`.
187
+ * - `blurredAfterInteraction` — the first blur that follows a value
188
+ * edit (the field has been edited and then left). Plain boolean,
189
+ * sticky `true`. A tab-through blur with no prior edit does NOT set
190
+ * it (`interacted` is still false at that blur). Composes
191
+ * `interacted` with the departure; drives the default display gate.
192
+ */
193
+ type FieldRecord = {
194
+ readonly path: Path;
195
+ readonly updatedAt: string | null;
196
+ readonly connected: boolean;
197
+ readonly focused: boolean | null;
198
+ readonly blurred: boolean | null;
199
+ readonly touched: boolean;
200
+ readonly interacted: boolean;
201
+ readonly blurredAfterInteraction: boolean;
202
+ };
203
+ /** Per-path DOM element tracking. Client-only. */
204
+ type ElementRecord = {
205
+ /**
206
+ * Original Path captured at first registration. Stored alongside the
207
+ * elements Set so the DOM-order sort cache can recover the structured
208
+ * Path without round-tripping through `JSON.parse(pathKey)`.
209
+ */
210
+ readonly path: Path;
211
+ readonly elements: Set<HTMLElement>;
212
+ };
213
+ /**
214
+ * Per-path record stored in `originals`. Pairing `segments` with the tracked
215
+ * value means `dirty` and `resetField`'s container loop don't have to
216
+ * `JSON.parse(pathKey)` on every iteration — the canonical Path is already
217
+ * sitting next to the value it belongs to. PathKey still keys the Map (the
218
+ * stable string is the only collision-free identifier), but downstream
219
+ * iteration reads `segments` directly.
220
+ */
221
+ type OriginalsRecord = {
222
+ readonly segments: Path;
223
+ readonly value: unknown;
224
+ };
225
+
167
226
  /** Internal brand for the `Unset` type. Never exposed at runtime. */
168
227
  declare const _unsetBrand: unique symbol;
169
228
  /**
@@ -722,11 +781,13 @@ interface JsonObject {
722
781
  * structured array — `['user', 'address', 0, 'line1']` for a nested
723
782
  * field, `[]` (the root path) for a form-level error (root `.refine()`
724
783
  * messages, `setErrors` entries with no path, hydration failures,
725
- * server-emitted form banners). `formKey` identifies which form
726
- * produced the error so a single error list can be routed to multiple
727
- * forms. The optional `data` slot carries an arbitrary server payload.
784
+ * server-emitted form banners). Which form produced an error is
785
+ * envelope-level identity: the `ValidationResponse` / submit result
786
+ * that carries the list stamps `formKey` once, and aggregators that
787
+ * merge lists across forms (the wizard) stamp their own envelope. The
788
+ * optional `data` slot carries an arbitrary server payload.
728
789
  *
729
- * Returned by `validate()` / `validateAsync()` / `handleSubmit`'s
790
+ * Returned by `validate()` / `parse()` / `handleSubmit`'s
730
791
  * `onError` callback, and accepted (leniently, as `ErrorInput`) by
731
792
  * `form.setErrors`.
732
793
  */
@@ -741,8 +802,6 @@ type ValidationError = {
741
802
  * literal empty-key field.
742
803
  */
743
804
  path: (string | number)[];
744
- /** Identifies which form produced this error. */
745
- formKey: FormKey;
746
805
  /**
747
806
  * Stable machine identifier for the failure, scoped by prefix:
748
807
  *
@@ -764,6 +823,23 @@ type ValidationError = {
764
823
  */
765
824
  data?: Json | null;
766
825
  };
826
+ /**
827
+ * One path's slot in the form's tagged error store. The two sources
828
+ * that can put an error at a path stay segregated inside the cell:
829
+ * `schema` holds the validation pipeline's verdicts, `user` holds
830
+ * `setErrors` entries (and their history / SSR replays). Reads merge
831
+ * schema -> derived-blank -> user; each writer replaces exactly its
832
+ * own side. Cells are immutable — every write replaces the whole cell
833
+ * (or deletes the key when both sides empty), so Vue's per-key Map
834
+ * tracking fires for any side's change. An empty side is the shared
835
+ * frozen `NO_ERRORS` array, never a fresh allocation.
836
+ *
837
+ * @internal
838
+ */
839
+ type ErrorCell = {
840
+ readonly schema: readonly ValidationError[];
841
+ readonly user: readonly ValidationError[];
842
+ };
767
843
  /**
768
844
  * The lenient input shape `form.setErrors` accepts: a real `Error`, or
769
845
  * a partial `ValidationError` where every field is optional.
@@ -773,20 +849,17 @@ type ValidationError = {
773
849
  * the path-scoped `setErrors(path, …)` form, which stamps its own.
774
850
  * - `code`: defaults to `atta:user-error`.
775
851
  * - `data`: forwarded verbatim onto the produced `ValidationError`.
776
- * - `formKey`: accepted but ignored — the form always stamps its own.
777
852
  *
778
- * Because every field is optional and `formKey` is accepted-and-ignored,
779
- * `ValidationError` is a subtype of `ErrorInput`: a `ValidationError[]`
780
- * you read back, or a server response that already emits the shape, pipes
781
- * straight into `form.setErrors` with no adapter and no excess-property
782
- * friction.
853
+ * Because every field is optional, `ValidationError` is a subtype of
854
+ * `ErrorInput`: a `ValidationError[]` you read back, or a server
855
+ * response that already emits the shape, pipes straight into
856
+ * `form.setErrors` with no adapter.
783
857
  */
784
858
  type ErrorInput = Error | {
785
859
  message?: string;
786
860
  path?: (string | number)[];
787
861
  code?: string;
788
862
  data?: Json | null;
789
- formKey?: FormKey;
790
863
  };
791
864
  /** Settled validation result when the form (or subtree) parsed successfully. */
792
865
  type ValidationResponseSuccess<TData> = {
@@ -831,10 +904,19 @@ type ValidationResponse<TData> = ValidationResponseSuccess<TData> | ValidationRe
831
904
  type DefaultValuesResponse<TData> = ValidationResponseSuccess<TData> | ValidationResponseErrorWithData<TData>;
832
905
  /**
833
906
  * Trimmed `ValidationResponse` that omits the `data` payload. Used by
834
- * `validate()` / `validateAsync()` since consumers usually only need
835
- * the success flag and error list at those entry points.
907
+ * the reactive `validate()` status, whose consumers only need the
908
+ * success flag and error list.
836
909
  */
837
910
  type ValidationResponseWithoutValue<Form> = Omit<ValidationResponse<Form>, 'data'>;
911
+ /**
912
+ * Options bag for `form.parse`. `commit: true` turns the pure read
913
+ * into an authoritative run: the verdict is committed to the error
914
+ * store at the parsed scope and in-flight per-field validation runs
915
+ * are cancelled first. Default `false`.
916
+ */
917
+ type ParseOptions = {
918
+ readonly commit?: boolean;
919
+ };
838
920
  /**
839
921
  * Sync-or-async return shape for `AbstractSchema.validateAtPath`. The
840
922
  * adapter returns the response inline when the schema and the
@@ -998,7 +1080,7 @@ type AbstractSchema<Form, GetValueFormType> = {
998
1080
  * the gate accepts the consumer's raw value verbatim and stops
999
1081
  * walking children — storage holds the user's input, and the
1000
1082
  * normalizer fires during `safeParse` (handleSubmit / validate /
1001
- * validateAsync), not at `setValue` time.
1083
+ * parse), not at `setValue` time.
1002
1084
  *
1003
1085
  * Path-prefix semantic: returns true if ANY ancestor of `path`
1004
1086
  * resolves to such a wrapper, so descendants under a preprocess-
@@ -1010,25 +1092,24 @@ type AbstractSchema<Form, GetValueFormType> = {
1010
1092
  * Distinguish a tuple (fixed-length, position-typed) from an
1011
1093
  * unbounded array at `path`. The runtime calls this on every
1012
1094
  * `mergeStructural` / `setAtPathWithSchemaFill` write that descends
1013
- * into an array branch — caching the answer at the schema level
1014
- * replaces the per-write 1M-index probe + sequential probe loop
1015
- * (up to 1024 schema lookups) the runtime previously used.
1095
+ * into an array branch, and the answer is definitive — the runtime
1096
+ * never second-guesses it.
1016
1097
  *
1017
1098
  * Return values:
1018
1099
  * - `number` → tuple of this structural length. The runtime pads
1019
1100
  * the consumer to this length and recurses position-by-position.
1020
- * - `null` → unbounded array. The runtime uses the consumer's
1021
- * length and reuses one element default for every position.
1022
- * - `undefined` → the path doesn't resolve to an array OR the
1023
- * adapter can't determine the shape. The runtime falls back to
1024
- * a probe loop in this case (defensive — every built-in adapter
1025
- * returns `number` or `null`).
1101
+ * - `null` → not a tuple. Covers the unbounded array (the runtime
1102
+ * uses the consumer's length and reuses one element default for
1103
+ * every position) and every path that doesn't resolve to an
1104
+ * array at all — there `getDefaultAtPath` yields no element
1105
+ * default either, so the consumer's array passes through
1106
+ * unchanged.
1026
1107
  *
1027
1108
  * Wrappers (optional / nullable / default / readonly / catch /
1028
1109
  * pipe / lazy) are peeled transparently before the type check, so
1029
1110
  * `optional(z.tuple([...]))` reports its tuple length.
1030
1111
  */
1031
- arrayShapeAtPath(path: Path): number | null | undefined;
1112
+ arrayShapeAtPath(path: Path): number | null;
1032
1113
  /**
1033
1114
  * Whether the schema at `path` is a FIXED object: a closed set of
1034
1115
  * declared keys (`z.object`), as opposed to an open or union container
@@ -1307,6 +1388,19 @@ type AbstractSchema<Form, GetValueFormType> = {
1307
1388
  * toward returning `true` when in doubt.
1308
1389
  */
1309
1390
  hasContainerOrRootRefine?(): boolean;
1391
+ /**
1392
+ * `true` when the schema tree contains at least one discriminated
1393
+ * union at any depth (inside arrays, tuples, records, and lazy
1394
+ * schemas included). The store computes its per-form DU capability
1395
+ * flag from this once at construction: `false` lets every write skip
1396
+ * the cross-variant ancestor guard and the variant-reshape dispatch,
1397
+ * and DU stub correction never runs. Optional: an adapter that omits
1398
+ * it is treated as containing unions, keeping the conservative
1399
+ * per-write probes on. Never return `false` for a schema that DOES
1400
+ * hold a discriminated union — that disables variant reshape and
1401
+ * stub correction for the form.
1402
+ */
1403
+ hasDiscriminatedUnions?(): boolean;
1310
1404
  };
1311
1405
  /**
1312
1406
  * Adapter-returned info for a discriminated union — its discriminator
@@ -1422,7 +1516,7 @@ type OnInvalidSubmitPolicy = 'none' | 'focus-first-error' | 'scroll-to-first-err
1422
1516
  * registered field. No debounce — `debounceMs` is rejected by the
1423
1517
  * type.
1424
1518
  * - `'submit'`: no live validation. `handleSubmit` and explicit
1425
- * `validate()` / `validateAsync()` calls are the only validation
1519
+ * `validate()` / `parse()` calls are the only validation
1426
1520
  * surfaces. `debounceMs` is rejected by the type.
1427
1521
  */
1428
1522
  type ValidateOn = 'change' | 'blur' | 'submit';
@@ -1499,8 +1593,8 @@ type WriteMeta = {
1499
1593
  readonly skipDiscriminatorReshape?: boolean;
1500
1594
  /**
1501
1595
  * Records an array structural mutation precisely enough to replay the
1502
- * exact index permutation it produced, set by `field-arrays.ts`
1503
- * helpers. `setValueAtPath` uses it to surgically clear variant memory
1596
+ * exact index permutation it produced, set by the typed array helpers
1597
+ * in `array-engine.ts`. `setValueAtPath` uses it to surgically clear variant memory
1504
1598
  * for the indices the operation invalidated. Without this hint, a raw
1505
1599
  * whole-array `setValue(arrayPath, [...])` clears all memory under the
1506
1600
  * array (the runtime can't tell which indices stayed put). Internal —
@@ -1551,19 +1645,68 @@ type WriteMeta = {
1551
1645
  readonly hydration?: boolean;
1552
1646
  };
1553
1647
  /**
1554
- * Undo/redo configuration passed via `useForm({ history })`.
1648
+ * The store slice the history runtime binds to. Structural on purpose:
1649
+ * `historyPlugin()` ships from the separate `attaform/history` entry, so
1650
+ * the runtime receives the store through this seam instead of importing
1651
+ * the store module (which would pull the history internals onto every
1652
+ * form's eager path). A FormStore satisfies it as-is.
1653
+ *
1654
+ * @internal
1655
+ */
1656
+ type HistoryKernel = {
1657
+ readonly form: Ref<unknown>;
1658
+ readonly blankPaths: Set<PathKey>;
1659
+ readonly errorCells: Map<PathKey, ErrorCell>;
1660
+ onFormChange(listener: (next: unknown, meta?: WriteMeta) => void): () => void;
1661
+ applyFormReplacement(next: unknown): void;
1662
+ restoreErrorCells(entries: ReadonlyArray<readonly [PathKey, ErrorCell]>): void;
1663
+ };
1664
+ /**
1665
+ * The live undo/redo runtime a {@link HistoryPlugin} attaches to one
1666
+ * form. Cached on the FormStore so every `useForm` / `injectForm`
1667
+ * consumer of the same key shares one chain; `buildFormApi` adapts it
1668
+ * into the public `form.history` namespace.
1669
+ *
1670
+ * @internal
1671
+ */
1672
+ type HistoryModule = {
1673
+ undo(): boolean;
1674
+ redo(): boolean;
1675
+ clear(): void;
1676
+ canUndo: Readonly<ComputedRef<boolean>>;
1677
+ canRedo: Readonly<ComputedRef<boolean>>;
1678
+ historySize: Readonly<ComputedRef<number>>;
1679
+ dispose(): void;
1680
+ };
1681
+ /**
1682
+ * Opt-in undo/redo, created by `historyPlugin()` from `attaform/history`
1683
+ * and passed via `useForm({ history })`:
1555
1684
  *
1556
- * - `true` — enable with the default position cap (`max: 128`).
1557
- * - `{ max }` — enable and tune the bounded history size.
1685
+ * ```ts
1686
+ * import { historyPlugin } from 'attaform/history'
1558
1687
  *
1559
- * When enabled, every mutation records a forward delta; `form.history.undo()`
1688
+ * const form = useForm({ schema, history: historyPlugin({ max: 200 }) })
1689
+ * ```
1690
+ *
1691
+ * When enabled, every mutation records a position; `form.history.undo()`
1560
1692
  * / `form.history.redo()` walk the chain. `reset()` is itself a mutation —
1561
1693
  * the pre-reset state stays one undo away. Persistence hydration is the
1562
1694
  * floor: after hydrate applies, the chain reseeds with the hydrated value
1563
1695
  * and `undo()` cannot reach the transient pre-hydration default.
1696
+ *
1697
+ * One plugin instance is a reusable configuration, not per-form state:
1698
+ * passing the same instance to several forms (or setting it once via
1699
+ * `createAttaform({ defaults: { history } })`) gives each form its own
1700
+ * independent chain.
1564
1701
  */
1565
- type HistoryConfig = true | {
1566
- max?: number;
1702
+ type HistoryPlugin = {
1703
+ /**
1704
+ * Bind a fresh history runtime to one form's store. Called by
1705
+ * `useForm` when the store is first created.
1706
+ *
1707
+ * @internal
1708
+ */
1709
+ readonly attach: (kernel: HistoryKernel) => HistoryModule;
1567
1710
  };
1568
1711
  /**
1569
1712
  * Consolidated undo/redo namespace at `form.history`. All history-related
@@ -1781,16 +1924,17 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1781
1924
  */
1782
1925
  debounceMs?: number;
1783
1926
  /**
1784
- * Opt-in undo/redo. Off by default. `true` enables with a 128-position
1785
- * cap; `{ max: N }` tunes the cap.
1927
+ * Opt-in undo/redo. Off by default. Pass `historyPlugin()` from
1928
+ * `attaform/history` — a 128-position cap by default,
1929
+ * `historyPlugin({ max: N })` to tune it.
1786
1930
  *
1787
- * Every mutation records a forward delta. `form.history.undo()` walks
1931
+ * Every mutation records a position. `form.history.undo()` walks
1788
1932
  * one step back; `form.history.redo()` walks one step forward.
1789
1933
  * `reset()` is itself a mutation, so the pre-reset state stays one
1790
1934
  * undo away. The consolidated `form.history` namespace also exposes
1791
1935
  * `clear()`, `canUndo`, `canRedo`, and `size`.
1792
1936
  */
1793
- history?: HistoryConfig;
1937
+ history?: HistoryPlugin;
1794
1938
  /**
1795
1939
  * Whether to remember the typed state of each discriminated-union
1796
1940
  * variant across switches. Default `true`.
@@ -1936,8 +2080,8 @@ type AttaformDefaults = {
1936
2080
  * `validateOn` resolves to `'change'`. Default `0` (synchronous).
1937
2081
  */
1938
2082
  debounceMs?: number;
1939
- /** Default for `useForm({ history })`. */
1940
- history?: HistoryConfig;
2083
+ /** Default for `useForm({ history })` — a `historyPlugin()` instance. */
2084
+ history?: HistoryPlugin;
1941
2085
  /** Default for `useForm({ rememberVariants })`. */
1942
2086
  rememberVariants?: boolean;
1943
2087
  /** Default for `useForm({ disabled })` — freeze the form's data. */
@@ -2016,7 +2160,7 @@ type AttaformDefaults = {
2016
2160
  *
2017
2161
  * "Permissive fallback" means storage and reads keep working at any
2018
2162
  * depth; only the per-write type gate stops checking past the cap.
2019
- * Full schema validation (`validateAsync`, `handleSubmit`) still runs
2163
+ * Full schema validation (`parse`, `handleSubmit`) still runs
2020
2164
  * against the real schema, so refinement errors at any depth still
2021
2165
  * surface — the cap only affects the *write-time gate*.
2022
2166
  *
@@ -2425,6 +2569,49 @@ type RegisterOptions = {
2425
2569
  */
2426
2570
  autoAria?: boolean;
2427
2571
  };
2572
+ /**
2573
+ * The narrow kernel surface the DOM binding drives. Structurally a
2574
+ * subset of the form store: the field-record connect/disconnect
2575
+ * transitions, focus marking (which owns blur-validation), the record
2576
+ * and merged-error reads the focus walk needs, and the async-transform
2577
+ * abort for a fully-detached path.
2578
+ * @internal
2579
+ */
2580
+ type DomBindingKernel = {
2581
+ readonly noteDomConnected: (path: Path) => void;
2582
+ readonly noteDomDisconnected: (path: Path) => void;
2583
+ readonly markFocused: (path: Path, focused: boolean, meta?: {
2584
+ readonly instance?: WriteMeta['instance'];
2585
+ }) => void;
2586
+ readonly getFieldRecord: (path: Path) => FieldRecord | undefined;
2587
+ readonly getErrorsForPath: (path: Path) => ValidationError[];
2588
+ readonly cancelTransformsUnder: (prefix: Path) => void;
2589
+ };
2590
+ /**
2591
+ * The form store's DOM slice: the element registry backing
2592
+ * `field.element` / `field.elements`, no-latch host focus anchors, and
2593
+ * first-error focus resolution. Implemented in the directive cluster's
2594
+ * lazy graph and armed into the store's `domBinding` slot on first use
2595
+ * (see `RegisterValue.ensureDomBinding`); a `null` slot means nothing
2596
+ * in the app ever registered an element.
2597
+ * @internal
2598
+ */
2599
+ type AttaformDomBinding = {
2600
+ readonly elements: Map<PathKey, ElementRecord>;
2601
+ readonly attach: (segments: Path, element: HTMLElement, formInstanceId: string, instanceMeta: WriteMeta['instance'] | undefined) => void;
2602
+ readonly detach: (segments: Path, element: HTMLElement) => void;
2603
+ readonly markHostConnected: (segments: Path, connected: boolean, hostEl: HTMLElement, formInstanceId: string) => void;
2604
+ readonly getFirstErrorElement: (formInstanceId: string) => {
2605
+ path: Path;
2606
+ element: HTMLElement;
2607
+ } | null;
2608
+ };
2609
+ /**
2610
+ * Factory the directive / `useRegister` inject through
2611
+ * `RegisterValue.ensureDomBinding` to arm a store's `domBinding` slot.
2612
+ * @internal
2613
+ */
2614
+ type DomBindingFactory = (kernel: DomBindingKernel) => AttaformDomBinding;
2428
2615
  /**
2429
2616
  * The object returned by `form.register(path)`. Pass it to a native
2430
2617
  * input via `v-register`:
@@ -2530,6 +2717,17 @@ type RegisterValue<Value = unknown> = Readonly<{
2530
2717
  * @internal
2531
2718
  */
2532
2719
  hasRegisteredDescendant: (hostElement: HTMLElement) => boolean;
2720
+ /**
2721
+ * Arm this binding's form store with the DOM-binding implementation
2722
+ * (element registry, focus listeners, first-error focus resolution).
2723
+ * The implementation lives in the directive cluster's lazy graph, so
2724
+ * the directive and `useRegister` inject its factory here before any
2725
+ * element call; once armed, the store's `domBinding` slot serves every
2726
+ * later registration. Optional so hand-rolled RegisterValues, which
2727
+ * own their element handling, don't have to declare it.
2728
+ * @internal
2729
+ */
2730
+ ensureDomBinding?: (factory: DomBindingFactory) => void;
2533
2731
  /**
2534
2732
  * Canonical, JSON-encoded path key for this binding (e.g.
2535
2733
  * `'["items",0,"name"]'`). Useful for stable Map / Set keys, log
@@ -2958,10 +3156,10 @@ type RegisterModelDynamicCustomDirective = ObjectDirective<HTMLInputElement | HT
2958
3156
  * See `RegisterTextModifier` / `RegisterSelectModifier` for
2959
3157
  * per-modifier semantics.
2960
3158
  *
2961
- * Registered globally by `createAttaform()` (and by the
2962
- * `attaform/nuxt` module). Most consumers don't import the
2963
- * directive itself — it's exposed for integrations that install
2964
- * directives manually.
3159
+ * Delivered by the Vite / Nuxt plugin's compile-time binding, or by
3160
+ * `installVRegister(app)` from `attaform/directive` everywhere else.
3161
+ * Most consumers don't import the directive itself — it's exposed for
3162
+ * integrations that install directives manually.
2965
3163
  */
2966
3164
  type RegisterDirective = RegisterTextCustomDirective | RegisterCheckboxCustomDirective | RegisterSelectCustomDirective | RegisterRadioCustomDirective | RegisterModelDynamicCustomDirective;
2967
3165
  /**
@@ -3206,7 +3404,7 @@ type FieldState<Value = unknown> = {
3206
3404
  * `true` while a per-field validation run is in flight at this path.
3207
3405
  * Reflects field-level debounced runs (`validate-on-change`) and
3208
3406
  * cross-field re-validations targeting this path. Whole-form
3209
- * `validate()` / `validateAsync()` calls drive `form.meta.validating`
3407
+ * `validate()` / `parse()` calls drive `form.meta.validating`
3210
3408
  * only — they don't flip per-field flags.
3211
3409
  *
3212
3410
  * Per-field analogue of `form.meta.validating`. Use for a tight
@@ -4173,19 +4371,6 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4173
4371
  * settles to results from the most recent call.
4174
4372
  */
4175
4373
  validate: (path?: FlatPath<Form>) => Readonly<Ref<ReactiveValidationStatus<Form>>>;
4176
- /**
4177
- * Run validation once and return the result. Unlike `validate()`,
4178
- * this does not subscribe to form reactivity.
4179
- *
4180
- * ```ts
4181
- * const result = await form.validateAsync()
4182
- * if (!result.success) showErrors(result.errors)
4183
- * ```
4184
- *
4185
- * Pass a path to validate a subtree. `state.validating` flips
4186
- * `true` while the promise is in flight.
4187
- */
4188
- validateAsync: (path?: FlatPath<Form>) => Promise<ValidationResponseWithoutValue<Form>>;
4189
4374
  /**
4190
4375
  * Resolve once every in-flight async `register({ transforms })` run
4191
4376
  * has settled — globally, or (with `path`) only at-or-under that path.
@@ -4207,9 +4392,9 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4207
4392
  */
4208
4393
  settleTransforms: (path?: FlatPath<Form>) => Promise<void>;
4209
4394
  /**
4210
- * Imperative one-shot parse. Same pipeline as `validateAsync` —
4211
- * runs refinements, applies `.transform()`s, composes blank-required
4212
- * errors — but RETAINS the parsed data instead of stripping it.
4395
+ * Imperative one-shot parse. Runs the full pipeline — refinements,
4396
+ * `.transform()`s, blank-required composition — against the current
4397
+ * form snapshot and RETAINS the parsed data.
4213
4398
  *
4214
4399
  * Storage holds the "honest input view" — values you wrote, with
4215
4400
  * preprocess normalization applied but `.transform()` deferred. For
@@ -4227,16 +4412,31 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4227
4412
  * }
4228
4413
  * ```
4229
4414
  *
4415
+ * By default the call is a PURE read: nothing is written to
4416
+ * `form.errors`, and in-flight per-field validation runs are left
4417
+ * alone. Pass `{ commit: true }` to make the run authoritative —
4418
+ * the verdict is committed to the error store at the parsed scope
4419
+ * and any in-flight per-field runs are cancelled first (mirroring
4420
+ * `handleSubmit`), so `await form.parse('email', { commit: true })`
4421
+ * lands a deterministic view of `form.errors.email`:
4422
+ *
4423
+ * ```ts
4424
+ * const result = await form.parse({ commit: true })
4425
+ * if (!result.success) showErrors(result.errors)
4426
+ * ```
4427
+ *
4230
4428
  * Always async, and there is no synchronous variant by design: a
4231
4429
  * schema can carry async refinements or transforms, so a sync parse
4232
4430
  * would silently miss them the moment one is added. One always-
4233
4431
  * awaited `parse` closes that category of bug entirely. The returned
4234
4432
  * promise never rejects (a thrown adapter lands as a `success: false`
4235
4433
  * response). Pass a path to parse a subtree only. `meta.validating`
4236
- * flips `true` while the promise is in flight (shared with
4237
- * validateAsync).
4434
+ * flips `true` while the promise is in flight.
4238
4435
  */
4239
- parse: (path?: FlatPath<Form>) => Promise<ValidationResponse<GetValueFormType>>;
4436
+ parse: {
4437
+ (path?: FlatPath<Form>, options?: ParseOptions): Promise<ValidationResponse<GetValueFormType>>;
4438
+ (options: ParseOptions): Promise<ValidationResponse<GetValueFormType>>;
4439
+ };
4240
4440
  /**
4241
4441
  * Bind a path to a native input via `v-register`. Returns a
4242
4442
  * `RegisterValue` carrying the live ref and event handlers the
@@ -4759,5 +4959,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4759
4959
  blankPaths: ComputedRef<BlankPathsView>;
4760
4960
  };
4761
4961
 
4762
- export { ROOT_PATH as X, ROOT_PATH_KEY as Y, canonicalizePath as al, isPathPrefix as am, isUnset as an, parseDottedPath as ao, unset as ap };
4763
- export type { RegisterFlatPath as $, AbstractSchema as A, NestedType 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, OnError as O, OnInvalidSubmitPolicy as P, OnSubmit as Q, PartialFlatPath as R, Path as S, PathKey as T, UseFormConfiguration as U, PendingValidationStatus as V, Primitive as W, ReactiveValidationStatus as Z, RegisterDirective as _, UseFormReturnType as a, RegisterOptions as a0, RegisterSelectModifier as a1, RegisterTextModifier as a2, RegisterTransform as a3, RegisterValue as a4, Segment as a5, SetValueCallback as a6, SetValuePayload as a7, SettledValidationStatus as a8, SlimPrimitiveKind as a9, SlimRuntimeOf as aa, SubmitHandler as ab, Unset as ac, ValidateOn as ad, ValidateOnConfig as ae, ValidationError as af, ValidationResponse as ag, ValidationResponseWithoutValue as ah, ValueOfUnion as ai, WriteMeta as aj, WriteShape as ak, AcceptableDefaults as aq, SchemaFactoryOptions as ar, TransformAbortHolder as as, RegisterModelDynamicCustomDirective as at, ArrayItem as b, ArrayPath as c, AttaformDefaults as d, CoercionRegistry as e, CoercionResult as f, CustomDirectiveRegisterAssignerFn as g, DeepPartial as h, DefaultValuesResponse as i, DefaultValuesShape as j, DisplayCtx as k, DisplayMachine as l, DisplayState as m, ErrorsProxyShape as n, FieldMetaPayload as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormMeta as v, GetDisplayState as w, HistoryConfig as x, IsUnion as y, Json as z };
4962
+ export { ROOT_PATH as Y, ROOT_PATH_KEY as Z, canonicalizePath as am, isPathPrefix as an, isUnset as ao, parseDottedPath as ap, unset as aq };
4963
+ export type { RegisterDirective as $, AbstractSchema as A, NestedType 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, OnError as O, OnInvalidSubmitPolicy as P, OnSubmit as Q, ParseOptions as R, PartialFlatPath as S, Path as T, UseFormConfiguration as U, PathKey as V, PendingValidationStatus as W, Primitive as X, ReactiveValidationStatus as _, UseFormReturnType as a, RegisterFlatPath as a0, RegisterOptions as a1, RegisterSelectModifier as a2, RegisterTextModifier as a3, RegisterTransform as a4, RegisterValue 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, RegisterModelDynamicCustomDirective as ar, AcceptableDefaults as as, SchemaFactoryOptions as at, FieldRecord as au, ErrorCell as av, OriginalsRecord as aw, AttaformDomBinding as ax, TransformAbortHolder as ay, ArrayItem as b, ArrayPath as c, AttaformDefaults as d, CoercionRegistry as e, CoercionResult as f, CustomDirectiveRegisterAssignerFn as g, DeepPartial as h, DefaultValuesResponse as i, DefaultValuesShape as j, DisplayCtx as k, DisplayMachine as l, DisplayState as m, ErrorsProxyShape as n, FieldMetaPayload as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormMeta as v, GetDisplayState as w, HistoryPlugin as x, IsUnion as y, Json as z };
@@ -791,4 +791,3 @@ function hasPreamble(element) {
791
791
  }
792
792
 
793
793
  export { vRegisterPreambleTransform as a, componentBridgeTransform as c, inputTextAreaNodeTransform as i, redundantBindingWarnTransform as r, vRegisterHintTransform as v };
794
- //# sourceMappingURL=attaform.Cs2Rbdlo.mjs.map