attaform 0.27.6 → 0.29.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 (192) hide show
  1. package/dist/abstract.d.mts +5 -4
  2. package/dist/abstract.mjs +4 -3
  3. package/dist/chunks/fingerprint.mjs +1 -2
  4. package/dist/chunks/fingerprint2.mjs +2 -3
  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/dev/shared/attaform-dev.A-TpJW0B.mjs +207 -0
  15. package/dist/dev/shared/attaform-dev.BO-71R54.mjs +820 -0
  16. package/dist/{shared/attaform.D6tk90Rc.cjs → dev/shared/attaform-dev.BgM6FdtA.mjs} +324 -73
  17. package/dist/dev/shared/attaform-dev.ButTkwHd.mjs +375 -0
  18. package/dist/{shared/attaform.BJGA_UOS.mjs → dev/shared/attaform-dev.BxfsuwBR.mjs} +8 -1
  19. package/dist/{shared/attaform.nMaz2C5P.mjs → dev/shared/attaform-dev.D8aoKb89.mjs} +190 -400
  20. package/dist/dev/shared/attaform-dev.DD5m2W51.mjs +220 -0
  21. package/dist/{shared/attaform.CKH2lGdL.mjs → dev/shared/attaform-dev.DHuydLAC.mjs} +3867 -4731
  22. package/dist/{shared/attaform.DYEr28Pt.mjs → dev/shared/attaform-dev.DrzObAZo.mjs} +6 -7
  23. package/dist/{shared/attaform.B1ZYt2Li.mjs → dev/shared/attaform-dev.q6UutkCx.mjs} +274 -672
  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 +14 -6
  33. package/dist/index.mjs +5 -4
  34. package/dist/nuxt.d.mts +10 -10
  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.CoR4mWfv.mjs → attaform.B0dKL8ST.mjs} +309 -46
  42. package/dist/shared/{attaform.Cs2Rbdlo.mjs → attaform.BK0cgu2K.mjs} +123 -140
  43. package/dist/shared/{attaform.B9Wyhfro.cjs → attaform.BndjUUYX.mjs} +11 -16
  44. package/dist/shared/{attaform.CQN9R62B.cjs → attaform.BxfsuwBR.mjs} +9 -4
  45. package/dist/shared/attaform.C2MVyuxC.mjs +811 -0
  46. package/dist/shared/{attaform.Fp3sCrVs.d.ts → attaform.CIWLMY-R.d.mts} +2 -2
  47. package/dist/shared/attaform.CZMAYwSK.mjs +6383 -0
  48. package/dist/shared/attaform.CZYt_5N_.mjs +207 -0
  49. package/dist/shared/{attaform.DniawQLP.d.cts → attaform.CsoeRi3P.d.mts} +490 -98
  50. package/dist/shared/attaform.Cvao8bW5.mjs +358 -0
  51. package/dist/shared/attaform.D0x-qSXm.mjs +197 -0
  52. package/dist/shared/{attaform.D8kliaKw.cjs → attaform.D1fnTifF.mjs} +279 -692
  53. package/dist/shared/{attaform.CHlJLAiv.d.mts → attaform.DDtqHYv1.d.mts} +155 -184
  54. package/dist/shared/{attaform.Biytrv0-.cjs → attaform.DffRL5pP.mjs} +215 -547
  55. package/dist/shared/attaform.DhnPb6nK.mjs +0 -1
  56. package/dist/shared/attaform.KvlEedlF.mjs +0 -1
  57. package/dist/shared/attaform.axHXVQO7.d.mts +44 -0
  58. package/dist/shared/attaform.v0fQtWyA.mjs +0 -1
  59. package/dist/transforms.d.mts +0 -22
  60. package/dist/transforms.mjs +1 -2
  61. package/dist/vite.d.mts +30 -9
  62. package/dist/vite.mjs +27 -4
  63. package/dist/webpack.d.mts +6 -1
  64. package/dist/webpack.mjs +0 -1
  65. package/dist/zod-v3.d.mts +13 -6
  66. package/dist/zod-v3.mjs +17 -4
  67. package/dist/zod-v4.d.mts +19 -26
  68. package/dist/zod-v4.mjs +16 -4
  69. package/dist/zod.d.mts +4 -3
  70. package/dist/zod.mjs +5 -4
  71. package/package.json +38 -20
  72. package/skills/attaform/SKILL.md +6 -2
  73. package/skills/attaform/references/custom-components.md +7 -2
  74. package/skills/attaform/references/errors.md +8 -3
  75. package/skills/attaform/references/saving.md +77 -0
  76. package/skills/attaform/references/wizards.md +10 -8
  77. package/dist/abstract.cjs +0 -48
  78. package/dist/abstract.cjs.map +0 -1
  79. package/dist/abstract.d.cts +0 -42
  80. package/dist/abstract.d.ts +0 -42
  81. package/dist/abstract.mjs.map +0 -1
  82. package/dist/chunks/dev-key-collision-warnings.cjs +0 -25
  83. package/dist/chunks/dev-key-collision-warnings.cjs.map +0 -1
  84. package/dist/chunks/dev-key-collision-warnings.mjs.map +0 -1
  85. package/dist/chunks/devtools.cjs +0 -157
  86. package/dist/chunks/devtools.cjs.map +0 -1
  87. package/dist/chunks/devtools.mjs.map +0 -1
  88. package/dist/chunks/fingerprint.cjs.map +0 -1
  89. package/dist/chunks/fingerprint.mjs.map +0 -1
  90. package/dist/chunks/fingerprint2.cjs.map +0 -1
  91. package/dist/chunks/fingerprint2.mjs.map +0 -1
  92. package/dist/esbuild.cjs +0 -28
  93. package/dist/esbuild.cjs.map +0 -1
  94. package/dist/esbuild.d.cts +0 -56
  95. package/dist/esbuild.d.ts +0 -56
  96. package/dist/esbuild.mjs.map +0 -1
  97. package/dist/index.cjs +0 -51
  98. package/dist/index.cjs.map +0 -1
  99. package/dist/index.d.cts +0 -249
  100. package/dist/index.d.ts +0 -249
  101. package/dist/index.mjs.map +0 -1
  102. package/dist/nuxt.cjs +0 -84
  103. package/dist/nuxt.cjs.map +0 -1
  104. package/dist/nuxt.d.cts +0 -66
  105. package/dist/nuxt.d.ts +0 -66
  106. package/dist/nuxt.mjs.map +0 -1
  107. package/dist/rollup.cjs +0 -24
  108. package/dist/rollup.cjs.map +0 -1
  109. package/dist/rollup.d.cts +0 -35
  110. package/dist/rollup.d.ts +0 -35
  111. package/dist/rollup.mjs.map +0 -1
  112. package/dist/rspack.cjs +0 -10
  113. package/dist/rspack.cjs.map +0 -1
  114. package/dist/rspack.d.cts +0 -40
  115. package/dist/rspack.d.ts +0 -40
  116. package/dist/rspack.mjs.map +0 -1
  117. package/dist/runtime/components/AttaformDevtoolsPanel.vue.d.ts +0 -7
  118. package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +0 -35
  119. package/dist/runtime/plugins/attaform.cjs +0 -43
  120. package/dist/runtime/plugins/attaform.cjs.map +0 -1
  121. package/dist/runtime/plugins/attaform.d.cts +0 -5
  122. package/dist/runtime/plugins/attaform.d.ts +0 -5
  123. package/dist/runtime/plugins/attaform.mjs.map +0 -1
  124. package/dist/shared/attaform.B1ZYt2Li.mjs.map +0 -1
  125. package/dist/shared/attaform.B9Wyhfro.cjs.map +0 -1
  126. package/dist/shared/attaform.BGDV42hI.d.cts +0 -232
  127. package/dist/shared/attaform.BJGA_UOS.mjs.map +0 -1
  128. package/dist/shared/attaform.Biytrv0-.cjs.map +0 -1
  129. package/dist/shared/attaform.Bn0IzhD_.cjs +0 -800
  130. package/dist/shared/attaform.Bn0IzhD_.cjs.map +0 -1
  131. package/dist/shared/attaform.C9fPTxjB.d.cts +0 -41
  132. package/dist/shared/attaform.C9fPTxjB.d.ts +0 -41
  133. package/dist/shared/attaform.CKH2lGdL.mjs.map +0 -1
  134. package/dist/shared/attaform.CQN9R62B.cjs.map +0 -1
  135. package/dist/shared/attaform.CoR4mWfv.mjs.map +0 -1
  136. package/dist/shared/attaform.Cs2Rbdlo.mjs.map +0 -1
  137. package/dist/shared/attaform.D6tk90Rc.cjs.map +0 -1
  138. package/dist/shared/attaform.D8kliaKw.cjs.map +0 -1
  139. package/dist/shared/attaform.DLknk9nF.d.mts +0 -232
  140. package/dist/shared/attaform.DMxM2kol.mjs +0 -1275
  141. package/dist/shared/attaform.DMxM2kol.mjs.map +0 -1
  142. package/dist/shared/attaform.DRUT703x.d.cts +0 -56
  143. package/dist/shared/attaform.DRUT703x.d.ts +0 -56
  144. package/dist/shared/attaform.DYEr28Pt.mjs.map +0 -1
  145. package/dist/shared/attaform.DdQN7sgN.cjs +0 -1302
  146. package/dist/shared/attaform.DdQN7sgN.cjs.map +0 -1
  147. package/dist/shared/attaform.DhnPb6nK.mjs.map +0 -1
  148. package/dist/shared/attaform.DniawQLP.d.mts +0 -4763
  149. package/dist/shared/attaform.DniawQLP.d.ts +0 -4763
  150. package/dist/shared/attaform.DoQm_Vkd.cjs +0 -58
  151. package/dist/shared/attaform.DoQm_Vkd.cjs.map +0 -1
  152. package/dist/shared/attaform.DrvMscNX.cjs +0 -7697
  153. package/dist/shared/attaform.DrvMscNX.cjs.map +0 -1
  154. package/dist/shared/attaform.F2Q8iv38.d.ts +0 -2494
  155. package/dist/shared/attaform.FmqKO1Ex.d.cts +0 -2494
  156. package/dist/shared/attaform.KvlEedlF.mjs.map +0 -1
  157. package/dist/shared/attaform.RaTzp1WF.cjs +0 -26
  158. package/dist/shared/attaform.RaTzp1WF.cjs.map +0 -1
  159. package/dist/shared/attaform.nMaz2C5P.mjs.map +0 -1
  160. package/dist/shared/attaform.v0fQtWyA.mjs.map +0 -1
  161. package/dist/shared/attaform.yUrz7C2z.cjs +0 -29
  162. package/dist/shared/attaform.yUrz7C2z.cjs.map +0 -1
  163. package/dist/transforms.cjs +0 -12
  164. package/dist/transforms.cjs.map +0 -1
  165. package/dist/transforms.d.cts +0 -73
  166. package/dist/transforms.d.ts +0 -73
  167. package/dist/transforms.mjs.map +0 -1
  168. package/dist/vite.cjs +0 -363
  169. package/dist/vite.cjs.map +0 -1
  170. package/dist/vite.d.cts +0 -133
  171. package/dist/vite.d.ts +0 -133
  172. package/dist/vite.mjs.map +0 -1
  173. package/dist/webpack.cjs +0 -10
  174. package/dist/webpack.cjs.map +0 -1
  175. package/dist/webpack.d.cts +0 -37
  176. package/dist/webpack.d.ts +0 -37
  177. package/dist/webpack.mjs.map +0 -1
  178. package/dist/zod-v3.cjs +0 -53
  179. package/dist/zod-v3.cjs.map +0 -1
  180. package/dist/zod-v3.d.cts +0 -246
  181. package/dist/zod-v3.d.ts +0 -246
  182. package/dist/zod-v3.mjs.map +0 -1
  183. package/dist/zod-v4.cjs +0 -55
  184. package/dist/zod-v4.cjs.map +0 -1
  185. package/dist/zod-v4.d.cts +0 -172
  186. package/dist/zod-v4.d.ts +0 -172
  187. package/dist/zod-v4.mjs.map +0 -1
  188. package/dist/zod.cjs +0 -51
  189. package/dist/zod.cjs.map +0 -1
  190. package/dist/zod.d.cts +0 -7
  191. package/dist/zod.d.ts +0 -7
  192. package/dist/zod.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { G as GenericForm, F as FormKey, a as UseFormReturnType, a4 as RegisterValue, T as PathKey, k as DisplayCtx, w as GetDisplayState, l as DisplayMachine, S as Path, a9 as SlimPrimitiveKind, C as CoercionEntry, e as CoercionRegistry, af as ValidationError, A as AbstractSchema, aj as WriteMeta, h as DeepPartial, ak as WriteShape, as as TransformAbortHolder, ad as ValidateOn, d as AttaformDefaults, at as RegisterModelDynamicCustomDirective } from './attaform.DniawQLP.mjs';
2
- import { Ref, ComputedRef, App, InjectionKey, Plugin } from 'vue';
1
+ import { G as GenericForm, F as FormKey, a as UseFormReturnType, a7 as RegisterValue, V as PathKey, k as DisplayCtx, w as GetDisplayState, l as DisplayMachine, ac as SlimPrimitiveKind, C as CoercionEntry, e as CoercionRegistry, aw as FieldRecord, ax as ErrorCell, ai as ValidationError, ay as OriginalsRecord, A as AbstractSchema, T as Path, am as WriteMeta, h as DeepPartial, an as WriteShape, az as AttaformDomBinding, aA as TransformAbortHolder, ag as ValidateOn, d as AttaformDefaults } from './attaform.CsoeRi3P.mjs';
2
+ import { Ref, ComputedRef, ShallowRef, App, InjectionKey, Plugin } from 'vue';
3
3
 
4
4
  /**
5
5
  * Options accepted by `injectForm` when passing an object instead of
@@ -121,6 +121,22 @@ declare function injectForm<Form extends GenericForm, GetValueFormType extends G
121
121
  * composable's `onMounted` warn fires once per instance to surface
122
122
  * the misuse case at runtime.
123
123
  *
124
+ * A wrapper meant to work both ways says so with
125
+ * `useRegister({ optional: true })`, which silences the unbound
126
+ * diagnostic and changes nothing else. Without it the diagnostic still
127
+ * earns its keep: a single-mode wrapper whose parent forgot
128
+ * `v-register` renders a field that looks fine and stores nothing.
129
+ *
130
+ * `rv?.isBound` is the test for "did a parent bind me", and it is the
131
+ * one to reach for: script-setup code holds the Proxy, which is an
132
+ * object and so always truthy, while a template holds the unwrapped
133
+ * ref, which is `undefined` when unbound. Those two disagree, and
134
+ * `isBound` reads the same on both. A dual-mode wrapper (one used
135
+ * both `v-register`-bound and plain `v-model`-bound) branches on it,
136
+ * and does not need to duplicate its element to do so: the
137
+ * compile-time value injection keeps an author-written `:value` /
138
+ * `:checked` as its unbound leg, so one element serves both (#620).
139
+ *
124
140
  * Diagnostic: in dev mode, a single `console.warn` fires per instance
125
141
  * at `onMounted` if the captured value is still `undefined` — by then
126
142
  * the parent has had its full mount lifecycle to bind, so a missing
@@ -149,8 +165,48 @@ declare function injectForm<Form extends GenericForm, GetValueFormType extends G
149
165
  * the hybrid's only `.value`. Older code that read `rv.value?.path`
150
166
  * keeps working; new code can write `rv.path` directly.
151
167
  */
152
- type UseRegisterReturn<V = unknown> = RegisterValue<V> & Ref<RegisterValue<V> | undefined>;
153
- declare function useRegister<V = unknown>(): UseRegisterReturn<V> | undefined;
168
+ type UseRegisterReturn<V = unknown> = RegisterValue<V> & Ref<RegisterValue<V> | undefined> & {
169
+ /**
170
+ * Whether a parent has actually bound this wrapper. `false` until a
171
+ * `RegisterValue` lands, `true` once one has, and reactive in both
172
+ * directions: a parent that binds on a later render flips it, and
173
+ * so does one that stops binding.
174
+ *
175
+ * This is the test a dual-mode wrapper reaches for, and the reason
176
+ * it exists as a field rather than a truthiness check on the
177
+ * return: the composable cannot answer "is this bound" at setup
178
+ * time. A parent is free to bind on a later render
179
+ * (`v-register="ready ? form.register('x') : undefined"`), and
180
+ * `useRegister` has to keep serving that, so it can never return a
181
+ * plain `undefined` for "unbound right now". Script-setup code
182
+ * reading `rv` directly sees the hybrid Proxy, which is an object
183
+ * and therefore always truthy; a template sees the unwrapped ref,
184
+ * which IS `undefined` when unbound. `rv?.isBound` reads the same
185
+ * on both sides (#620).
186
+ */
187
+ readonly isBound: boolean;
188
+ };
189
+ /**
190
+ * Options for `useRegister`.
191
+ */
192
+ type UseRegisterOptions = {
193
+ /**
194
+ * Declares that this wrapper is used both with and without a form, so
195
+ * a render with no parent binding is intended rather than a mistake.
196
+ * Silences the unbound diagnostic; nothing else changes.
197
+ *
198
+ * The diagnostic exists because a single-mode wrapper whose parent
199
+ * forgot `v-register` renders a field that looks fine and stores
200
+ * nothing, which is an expensive afternoon. Once a wrapper is
201
+ * deliberately dual-mode, the same signal fires on correct code and
202
+ * tells the author to do something wrong, so this is the one fact
203
+ * that separates the two cases and only the author has it (#620).
204
+ *
205
+ * Pair it with `rv?.isBound` to branch on what actually happened.
206
+ */
207
+ readonly optional?: boolean;
208
+ };
209
+ declare function useRegister<V = unknown>(options?: UseRegisterOptions): UseRegisterReturn<V> | undefined;
154
210
 
155
211
  /**
156
212
  * Per-form display engine: owns the clock and the timers that the pure
@@ -206,71 +262,12 @@ type DisplayEngine = {
206
262
  clear(): void;
207
263
  /** Tear down for good: `clear()` plus detaching the visibility listener. */
208
264
  dispose(): void;
209
- /** Introspection for tests: count of retained machines. */
210
- size(): number;
211
- /** Introspection for tests: whether a path currently has a retained machine. */
212
- has(key: PathKey): boolean;
213
- /** Introspection for tests: whether a deadline timer is currently armed. */
214
- hasTimer(): boolean;
215
- };
216
-
217
- /**
218
- * Per-path field status. Replaced wholesale (not mutated in place) on
219
- * every change. Five semantic groups:
220
- *
221
- * - `connected` — is a DOM element registered for this path?
222
- * - `focused` / `blurred` — DOM-state flags. `null` while no element
223
- * is connected (no DOM means the concepts don't apply); plain
224
- * booleans once connected, with the invariant `blurred === !focused`
225
- * enforced by `markFocused`.
226
- * - `touched` — focus/blur history, not DOM state. Always a plain
227
- * boolean: `false` at registration, sticky `true` after first blur,
228
- * cleared only by `form.reset()` / `form.resetField(path)`. Persists
229
- * across disconnects so v-if'd-away fields don't lose their touched
230
- * state on rehide (wizard "show review of touched fields" patterns
231
- * rely on this).
232
- * - `interacted` — value-mutation history, not DOM state. Plain
233
- * boolean: `false` at registration, sticky `true` once the user
234
- * issues a value edit through the directive's input listeners
235
- * (never on hydration, default seeding, or programmatic setValue);
236
- * cleared with `touched` by `form.reset()` / `form.resetField(path)`.
237
- * - `blurredAfterInteraction` — the first blur that follows a value
238
- * edit (the field has been edited and then left). Plain boolean,
239
- * sticky `true`. A tab-through blur with no prior edit does NOT set
240
- * it (`interacted` is still false at that blur). Composes
241
- * `interacted` with the departure; drives the default display gate.
242
- */
243
- type FieldRecord = {
244
- readonly path: Path;
245
- readonly updatedAt: string | null;
246
- readonly connected: boolean;
247
- readonly focused: boolean | null;
248
- readonly blurred: boolean | null;
249
- readonly touched: boolean;
250
- readonly interacted: boolean;
251
- readonly blurredAfterInteraction: boolean;
252
- };
253
- /** Per-path DOM element tracking. Client-only. */
254
- type ElementRecord = {
255
- /**
256
- * Original Path captured at first registration. Stored alongside the
257
- * elements Set so the DOM-order sort cache can recover the structured
258
- * Path without round-tripping through `JSON.parse(pathKey)`.
259
- */
260
- readonly path: Path;
261
- readonly elements: Set<HTMLElement>;
262
- };
263
- /**
264
- * Per-path record stored in `originals`. Pairing `segments` with the tracked
265
- * value means `dirty` and `resetField`'s container loop don't have to
266
- * `JSON.parse(pathKey)` on every iteration — the canonical Path is already
267
- * sitting next to the value it belongs to. PathKey still keys the Map (the
268
- * stable string is the only collision-free identifier), but downstream
269
- * iteration reads `segments` directly.
270
- */
271
- type OriginalsRecord = {
272
- readonly segments: Path;
273
- readonly value: unknown;
265
+ /** Introspection for tests: count of retained machines. Dev builds only. */
266
+ size?(): number;
267
+ /** Introspection for tests: whether a path has a retained machine. Dev builds only. */
268
+ has?(key: PathKey): boolean;
269
+ /** Introspection for tests: whether a deadline timer is armed. Dev builds only. */
270
+ hasTimer?(): boolean;
274
271
  };
275
272
 
276
273
  /**
@@ -327,20 +324,21 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
327
324
  readonly formKey: FormKey;
328
325
  readonly form: Ref<F>;
329
326
  readonly fields: Map<PathKey, FieldRecord>;
330
- readonly elements: Map<PathKey, ElementRecord>;
331
327
  /**
332
- * Schema-driven errors. Written ONLY by the schema validation pipeline:
333
- * `scheduleFieldValidation`, `handleSubmit`, the construction-time seed,
334
- * history restore, and hydration. Cleared by `reset` / `resetField` and by
335
- * a successful submit. `setErrors` / `clearErrors` do NOT touch this Map.
336
- */
337
- readonly schemaErrors: Map<PathKey, ValidationError[]>;
338
- /**
339
- * User-injected errors. Written ONLY by the `setErrors` / `clearErrors`
340
- * API surface (and history / hydration replay). Survives schema revalidation and
328
+ * The tagged error store: one cell per error-bearing path, each cell
329
+ * segregating its two sources. The `schema` side is written ONLY by
330
+ * the validation pipeline (`scheduleFieldValidation`, `handleSubmit`,
331
+ * the construction-time seed, history restore, hydration) and cleared
332
+ * by `reset` / `resetField` and a successful submit; the `user` side
333
+ * is written ONLY by the `setErrors` / `clearErrors` API surface (and
334
+ * history / hydration replay) and survives schema revalidation and
341
335
  * successful submits — the consumer owns its lifetime explicitly.
336
+ * A key exists iff a side is non-empty; cells are replaced, never
337
+ * mutated, so Vue's per-key Map tracking fires for either side's
338
+ * change. Derived blank entries are NOT stored here — they synthesize
339
+ * at read (`derivedBlankErrors`).
342
340
  */
343
- readonly userErrors: Map<PathKey, ValidationError[]>;
341
+ readonly errorCells: Map<PathKey, ErrorCell>;
344
342
  /**
345
343
  * Reactively-derived "No value supplied" errors. Pure function of
346
344
  * `(blankPaths, schema.isRequiredAtPath)` — no writers, no clears.
@@ -524,10 +522,22 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
524
522
  * Resolves after `hydrating` flips back to `false`; consumers can
525
523
  * `await form.rehydrate()` to gate UI on the fresh load.
526
524
  *
527
- * Does NOT touch dirty / touched / submit state — chain
528
- * `form.reset()` if you want a clean baseline.
525
+ * Does NOT touch touched / submit state; chain `form.reset()` if you
526
+ * want a clean surface. It DOES re-seat the defaults (see
527
+ * `adoptResolvedDefaults`), so dirty afterwards means "differs from
528
+ * what the factory just returned", and `form.reset()` lands on the
529
+ * fresh values rather than throwing them away.
529
530
  */
530
531
  rehydrate(): Promise<void>;
532
+ /**
533
+ * Adopt a resolved async-defaults value as the form's durable
534
+ * defaults and re-seed the dirty baseline from it. Called by the
535
+ * activate / rehydrate orchestrator once its factory settles.
536
+ * Exposed on the store (rather than imported) because
537
+ * `form-activation` is imported BY this module; a value import back
538
+ * the other way would close a cycle.
539
+ */
540
+ adoptResolvedDefaults(value: unknown): void;
531
541
  /**
532
542
  * Incremented by every `reset()` call. The submit wrapper captures
533
543
  * this at entry and skips writing `submitError` from a catch that
@@ -538,7 +548,7 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
538
548
  readonly submissionGeneration: Ref<number>;
539
549
  /**
540
550
  * Counts in-flight validation calls across every `validate()` ref and
541
- * every `validateAsync(...)` / `handleSubmit` pre-check. `validating`
551
+ * every committing `parse(...)` / `handleSubmit` pre-check. `validating`
542
552
  * on the public API mirrors `activeValidations.value > 0`. Tracked
543
553
  * separately from submissions because a validate-while-submitting
544
554
  * (e.g. a debounced field check overlapping a submit) needs to show
@@ -598,7 +608,7 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
598
608
  * call) and decremented in the matching `.finally` — so the per-path
599
609
  * bookkeeping is exactly co-extensive with the form-wide counter for
600
610
  * the field-scheduled branch. Whole-form `validate()` /
601
- * `validateAsync()` runs touch `activeValidations` only; they don't
611
+ * `parse()` runs touch `activeValidations` only; they don't
602
612
  * have a single field path and so don't contribute here.
603
613
  *
604
614
  * Counter (not Set) because two runs for the same path can briefly
@@ -687,7 +697,7 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
687
697
  /**
688
698
  * Stable identity for the array element at `path`. An array element
689
699
  * (numeric last segment) carries its allocated identity token,
690
- * maintained by `array-identity.ts` across structural mutations.
700
+ * maintained by the arrays engine across structural mutations.
691
701
  * Empty for any non-array-element path: a record entry, a
692
702
  * fixed-object field, a container, or the root. Backs `FieldState.key`.
693
703
  */
@@ -700,7 +710,7 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
700
710
  /**
701
711
  * Replace `schemaErrors` under `path` with `errors`, keying each
702
712
  * error by its OWN absolute path. Used by validation pipelines
703
- * (scheduleFieldValidation, validateAsync, handleSubmit, reset)
713
+ * (scheduleFieldValidation, the committing parse, handleSubmit, reset)
704
714
  * to commit a parse result wholesale — entries not in the new
705
715
  * pass get dropped from the subtree, surviving keys update in
706
716
  * place to preserve insertion order. Pass `path === []` for the
@@ -711,10 +721,18 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
711
721
  setUserErrorsForPath(path: Path, errors: readonly ValidationError[]): void;
712
722
  clearUserErrors(path?: Path): void;
713
723
  /**
714
- * Merged read — returns `[...schemaErrors[path], ...userErrors[path]]`.
715
- * Schema errors come first (structural validation before business logic),
716
- * matching the iteration order for `getFirstErrorElement` and the
717
- * top-level `errors` drillable Proxy.
724
+ * Rebuild the whole tagged store from a snapshot's `[key, cell]`
725
+ * entries — the history ring buffer's restore road. Entry arrays are
726
+ * cloned in, so the snapshot stays detached from the live store;
727
+ * cells with both sides empty are skipped.
728
+ */
729
+ restoreErrorCells(entries: ReadonlyArray<readonly [PathKey, ErrorCell]>): void;
730
+ /**
731
+ * Merged read — the cell at `path` in schema -> blank -> user order.
732
+ * Schema errors come first (structural validation before business
733
+ * logic), the derived blank entry synthesizes between, and user
734
+ * entries close, matching the iteration order for
735
+ * `getFirstErrorElement` and the top-level `errors` drillable Proxy.
718
736
  */
719
737
  getErrorsForPath(path: Path): ValidationError[];
720
738
  /**
@@ -729,15 +747,29 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
729
747
  */
730
748
  ensurePathOrdinal(key: PathKey): number;
731
749
  /**
732
- * Register `element` as a binding for `path`, tagged with the calling
733
- * `useForm()` instance's `formInstanceId`. The ID is the disambiguator
734
- * used by `getFirstErrorElement` to scope focus / scroll to elements
735
- * THIS form instance owns — important when two `useForm()` calls share
736
- * a `key` (e.g. sidebar + main rendering the same form), since both
737
- * write into one shared element store.
750
+ * The store's DOM slice, `null` until the directive cluster or
751
+ * `useRegister` arms it via `RegisterValue.ensureDomBinding`. Element
752
+ * registration, host anchors, and first-error focus resolution all
753
+ * live behind it; eager readers treat `null` as an empty registry.
738
754
  */
739
- registerElement(path: Path, element: HTMLElement, formInstanceId: string): boolean;
740
- deregisterElement(path: Path, element: HTMLElement): number;
755
+ readonly domBinding: ShallowRef<AttaformDomBinding | null>;
756
+ /**
757
+ * Field-record connect transition, driven by the DOM binding on
758
+ * element attach / host connect (and by the SSR-only
759
+ * `markConnectedOptimistically`): `connected: true`, with
760
+ * `focused` / `blurred` lifted from `null` to optimistic booleans
761
+ * only when currently null — an existing boolean from an early focus
762
+ * event is never clobbered.
763
+ */
764
+ noteDomConnected(path: Path): void;
765
+ /**
766
+ * Field-record disconnect transition, driven by the DOM binding when
767
+ * a path's last element detaches or its host disconnects:
768
+ * `connected: false`, `focused` / `blurred` back to `null` (DOM-state
769
+ * properties are meaningless with nothing attached; interaction
770
+ * history stays).
771
+ */
772
+ noteDomDisconnected(path: Path): void;
741
773
  /**
742
774
  * Optional `meta.instance` carries per-`useForm()`-instance overrides
743
775
  * for `validateOn` / `debounceMs` so the blur-trigger respects the
@@ -777,20 +809,6 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
777
809
  * source there).
778
810
  */
779
811
  markConnectedOptimistically(path: Path): void;
780
- /**
781
- * Client-side connected mark for a `v-register` component host that binds
782
- * value via the v-model channel but has no single inner control to latch
783
- * (a composite widget, or none found). The directive calls it on mount
784
- * (`true`) and unmount (`false`). Distinct from the SSR-only
785
- * `markConnectedOptimistically`; idempotent on the leading edge.
786
- *
787
- * `hostEl` (the host root) is recorded on connect as this path's
788
- * focus-first-error anchor, scoped to `formInstanceId` the same way a
789
- * registered control is. A no-latch host enters no `elements` record, so
790
- * without this it is absent from `getFirstErrorElement`'s walk; the anchor
791
- * lets that walk resolve a focus target from the host subtree.
792
- */
793
- markHostConnected(path: Path, connected: boolean, hostEl: HTMLElement, formInstanceId: string): void;
794
812
  /**
795
813
  * Leaf-only pristine check. `originals` is populated via
796
814
  * `diffAndApply`'s `added` patches, which fire only on primitive
@@ -829,33 +847,6 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
829
847
  hasRemovedSubtreeUnder(path: Path): boolean;
830
848
  getFieldRecord(path: Path): FieldRecord | undefined;
831
849
  getOriginalAtPath(path: Path): unknown;
832
- /**
833
- * Returns the first errored field's first connected, visible DOM
834
- * element scoped to `formInstanceId` — the target that
835
- * `focusFirstError` / `scrollToFirstError` act on. "First" is
836
- * VISUAL-first (DOM-tree order via `compareDocumentPosition`), not
837
- * schema-declaration order, so a field rendered above another in the
838
- * template focuses first regardless of which one the schema declared
839
- * earlier. CSS `order:` flexbox/grid reordering is NOT respected
840
- * (DOM-tree order wins) — documented as a tradeoff against forcing
841
- * sync layout on every comparison.
842
- *
843
- * The `formInstanceId` filter scopes focus to elements registered
844
- * through THIS form instance. When two `useForm({ key })` calls share
845
- * a key, both register into the same element store; without the
846
- * filter, the sidebar form's submit could focus the main form's
847
- * input. With it, each `useForm()` callsite focuses only its own
848
- * elements.
849
- *
850
- * Returns `null` when every errored path has no currently-attached
851
- * element registered to this instance (fields behind `v-if="false"`,
852
- * unmounted components, or a hidden `display:none` parent). Callers
853
- * get the choice of no-op or a dev-only warning.
854
- */
855
- getFirstErrorElement(formInstanceId: string): {
856
- path: Path;
857
- element: HTMLElement;
858
- } | null;
859
850
  /**
860
851
  * Cancel every in-flight field-level validation run — clears timers
861
852
  * for debounced 'change' runs that haven't fired, latches `aborted`
@@ -958,21 +949,7 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
958
949
  */
959
950
  registerCleanup(fn: () => void): void;
960
951
  /**
961
- * Register an async drain function. Called by the registry before
962
- * `dispose()` so async background work — chiefly the persistence
963
- * layer's debounced storage writes — has a chance to settle without
964
- * losing the last keystroke. Each registered function is awaited in
965
- * parallel; failures are swallowed to keep eviction reliable.
966
- */
967
- registerDrain(fn: () => Promise<void>): void;
968
- /**
969
- * Drain async work registered via `registerDrain`. Resolves once
970
- * every registered drain has settled (in parallel). Safe to call
971
- * repeatedly — registered drains decide their own idempotency.
972
- */
973
- awaitPendingWrites(): Promise<void>;
974
- /**
975
- * Cache for per-state modules (history, persistence) that must
952
+ * Cache for per-state modules (chiefly history) that must
976
953
  * outlive any single consumer. Subsequent `useForm` / `injectForm`
977
954
  * calls for the same key read from this map so the public API shape
978
955
  * is identical regardless of mount order. Keyed by a string identifier
@@ -1577,6 +1554,12 @@ type WizardForms<S> = FormsRecordOf<S> & Readonly<Record<FormKey, AnyForm>>;
1577
1554
  * `back()` does not pop; the trail is the audit
1578
1555
  * log, not the back-stack.
1579
1556
  * - `next/back/goTo` — pure navigation. Refuses while `submitting`.
1557
+ * The one exception is `next()` on an UNCLEARED
1558
+ * `gate()` step, which behaves as `tryNext()` so
1559
+ * wiring Next straight to it can never skip the
1560
+ * gate's confirmation. Once that gate clears, `next()`
1561
+ * is plain navigation again and does not re-submit to
1562
+ * re-confirm.
1580
1563
  * - `tryNext()` — submit the active step, and once that submit
1581
1564
  * resolves clean, advance; invalid input keeps the pin
1582
1565
  * put under the form's standard error reveal (first
@@ -1586,6 +1569,13 @@ type WizardForms<S> = FormsRecordOf<S> & Readonly<Record<FormKey, AnyForm>>;
1586
1569
  * (`@click="wizard.tryNext()"`); resolves to whether
1587
1570
  * the pin moved. No-ops to `false` on a degenerate or
1588
1571
  * final-step wizard (finish via `handleSubmit`).
1572
+ * Called while the active step's form already has a
1573
+ * submit in flight (the usual case being from inside
1574
+ * that form's own callback), it rides that submit
1575
+ * rather than starting a second one, so one action
1576
+ * costs one submission. The pin has not moved by the
1577
+ * time it resolves, so the answer is `false`; the
1578
+ * advance lands with the in-flight submit.
1589
1579
  * - `handleSubmit(onSubmit, onError?)` — always validates the entire
1590
1580
  * step list, from any step, and never advances the
1591
1581
  * pin: on success it latches `done`; on any error it
@@ -1783,13 +1773,6 @@ type AttaformRegistry = {
1783
1773
  * @internal
1784
1774
  */
1785
1775
  readonly shouldPrefetch: (key: FormKey) => boolean;
1786
- /**
1787
- * Wait for all pending persistence writes across every live form
1788
- * to settle. Useful for SSR shutdown and integration tests that
1789
- * need a deterministic teardown.
1790
- * @internal
1791
- */
1792
- readonly shutdown: () => Promise<void>;
1793
1776
  };
1794
1777
  /**
1795
1778
  * The Vue `InjectionKey` under which the registry is provided on the
@@ -2167,6 +2150,8 @@ declare function createAttaform(options?: AttaformPluginOptions): Plugin;
2167
2150
  * serialiser before embedding in your SSR payload.
2168
2151
  */
2169
2152
  type SerializedAttaformState = {
2153
+ /** Envelope version — see {@link ATTAFORM_STATE_VERSION}. */
2154
+ readonly v: number;
2170
2155
  /** Tuples of `[formKey, snapshot]` for every form in the app. */
2171
2156
  readonly forms: ReadonlyArray<readonly [FormKey, SerializedFormData]>;
2172
2157
  };
@@ -2206,10 +2191,16 @@ declare function renderAttaformState(app: App): SerializedAttaformState;
2206
2191
  * app.mount('#app')
2207
2192
  * ```
2208
2193
  *
2194
+ * Accepts `unknown` because the payload crosses the wire untyped
2195
+ * (an inline `<script>` tag, `JSON.parse`): the envelope is
2196
+ * validated at runtime, and anything that isn't a version-matched
2197
+ * `renderAttaformState` snapshot is skipped wholesale, so forms
2198
+ * construct fresh from their schemas instead.
2199
+ *
2209
2200
  * The next `useForm({ key })` call for each serialised form picks up
2210
2201
  * the snapshot transparently — no further action is required.
2211
2202
  */
2212
- declare function hydrateAttaformState(app: App, payload: SerializedAttaformState): void;
2203
+ declare function hydrateAttaformState(app: App, payload: unknown): void;
2213
2204
 
2214
2205
  /**
2215
2206
  * Escape a JSON string so it's safe to embed inside an inline
@@ -2242,26 +2233,6 @@ declare function escapeForInlineScript(json: string): string;
2242
2233
  */
2243
2234
  declare const assignKey: unique symbol;
2244
2235
 
2245
- /**
2246
- * The `v-register` directive. Bind a form field to a native input,
2247
- * select, textarea, checkbox, or radio:
2248
- *
2249
- * ```vue
2250
- * <input v-register="form.register('email')" />
2251
- * <select v-register="form.register('country')">
2252
- * <option value="us">US</option>
2253
- * <option value="uk">UK</option>
2254
- * </select>
2255
- * ```
2256
- *
2257
- * The directive picks the right binding strategy automatically based
2258
- * on the element's `tagName` and `type`. Registered globally by
2259
- * `createAttaform()`. Most consumers never import it directly, but
2260
- * it's exposed for advanced integrations that wire directives
2261
- * manually.
2262
- */
2263
- declare const vRegister: RegisterModelDynamicCustomDirective;
2264
-
2265
2236
  /**
2266
2237
  * Type guard for a `RegisterValue`. Returns `true` when `val` looks
2267
2238
  * like the object returned from `form.register(path)`.
@@ -2490,5 +2461,5 @@ declare function makeDefaultDisplayState({ showDelay, minVisible, }: DisplayTimi
2490
2461
  */
2491
2462
  declare const defaultDisplayState: GetDisplayState;
2492
2463
 
2493
- export { renderAttaformState as $, createAttaform as B, DEFAULT_TIMINGS as D, createRegistry as E, defaultCoercionRules as H, defaultDisplayState as J, defineCoercion as K, escapeForInlineScript as M, gate as N, OutsideSetupError as O, getRegistryFromApp as P, hydrateAttaformState as Q, RegistryNotInstalledError as R, injectForm as T, injectWizard as V, isRegisterValue as X, kAttaformRegistry as Y, lazy as Z, makeDefaultDisplayState as _, useRegister as a0, useRegistry as a1, useWizard as a2, vRegister as a3, AttaformError as c, AttaformErrorCode as d, DEVTOOLS_WINDOW_KEY as f, InvalidPathError as i, InvalidUseFormConfigError as j, ReservedFormKeyError as k, SubmitErrorHandlerError as n, assignKey as z };
2494
- export type { AttaformRegistry as A, CompiledStep as C, FormStatus as F, GateMarker as G, InjectWizardInput as I, LazyMarker as L, SerializedAttaformState as S, UseRegisterReturn as U, WizardAggregateError as W, AnyForm as a, AttaformDevtoolsBridge as b, AttaformPluginOptions as e, DisplayTimings as g, FormStatusSeed as h, SerializedFormData as l, StepSlot as m, UseWizardReturnType as o, WizardCtx as p, WizardCtxForm as q, WizardOnError as r, WizardOnSubmit as s, WizardOptions as t, WizardPersistFn as u, WizardRestoreFn as v, WizardRestoreState as w, WizardStatusesProxy as x, WizardSubmitContext as y };
2464
+ export { makeDefaultDisplayState as $, assignKey as B, DEFAULT_TIMINGS as D, createAttaform as E, createRegistry as H, defaultCoercionRules as J, defaultDisplayState as K, defineCoercion as M, escapeForInlineScript as N, OutsideSetupError as O, gate as P, getRegistryFromApp as Q, RegistryNotInstalledError as R, hydrateAttaformState as T, injectForm as V, injectWizard as X, isRegisterValue as Y, kAttaformRegistry as Z, lazy as _, renderAttaformState as a0, useRegister as a1, useRegistry as a2, useWizard as a3, AttaformError as c, AttaformErrorCode as d, DEVTOOLS_WINDOW_KEY as f, InvalidPathError as i, InvalidUseFormConfigError as j, ReservedFormKeyError as k, SubmitErrorHandlerError as n };
2465
+ export type { AttaformRegistry as A, CompiledStep as C, FormStatus as F, GateMarker as G, InjectWizardInput as I, LazyMarker as L, SerializedAttaformState as S, UseRegisterOptions as U, WizardAggregateError as W, AnyForm as a, AttaformDevtoolsBridge as b, AttaformPluginOptions as e, DisplayTimings as g, FormStatusSeed as h, SerializedFormData as l, StepSlot as m, UseRegisterReturn as o, UseWizardReturnType as p, WizardCtx as q, WizardCtxForm as r, WizardOnError as s, WizardOnSubmit as t, WizardOptions as u, WizardPersistFn as v, WizardRestoreFn as w, WizardRestoreState as x, WizardStatusesProxy as y, WizardSubmitContext as z };