attaform 0.24.4 → 0.26.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 (92) hide show
  1. package/dist/chunks/devtools.cjs +1 -1
  2. package/dist/chunks/devtools.mjs +1 -1
  3. package/dist/chunks/fingerprint2.cjs +1 -1
  4. package/dist/chunks/fingerprint2.mjs +1 -1
  5. package/dist/index.cjs +4 -4
  6. package/dist/index.d.cts +5 -5
  7. package/dist/index.d.mts +5 -5
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.mjs +4 -4
  10. package/dist/nuxt.d.cts +1 -1
  11. package/dist/nuxt.d.mts +1 -1
  12. package/dist/nuxt.d.ts +1 -1
  13. package/dist/runtime/plugins/attaform.cjs +2 -2
  14. package/dist/runtime/plugins/attaform.mjs +2 -2
  15. package/dist/shared/{attaform.CO0e7YVY.d.cts → attaform.55hFR_cl.d.ts} +3 -0
  16. package/dist/shared/{attaform.Ib7oSpxj.d.cts → attaform.B71rfNL5.d.mts} +1 -1
  17. package/dist/shared/{attaform.DTn1J0Eq.d.mts → attaform.BFfY7HRI.d.cts} +1 -1
  18. package/dist/shared/{attaform.CAVK56f8.cjs → attaform.BPl-ECRT.cjs} +49 -2
  19. package/dist/shared/attaform.BPl-ECRT.cjs.map +1 -0
  20. package/dist/shared/{attaform.CBMI57RP.cjs → attaform.BQflyVOL.cjs} +3 -3
  21. package/dist/shared/{attaform.CBMI57RP.cjs.map → attaform.BQflyVOL.cjs.map} +1 -1
  22. package/dist/shared/{attaform.DWKcw1ud.cjs → attaform.BRzKBj2g.cjs} +101 -4
  23. package/dist/shared/attaform.BRzKBj2g.cjs.map +1 -0
  24. package/dist/shared/{attaform.EeVCVnoH.d.ts → attaform.Be5otUVw.d.ts} +1 -1
  25. package/dist/shared/{attaform.iJ4y3sHU.mjs → attaform.BoYqnIPI.mjs} +223 -81
  26. package/dist/shared/attaform.BoYqnIPI.mjs.map +1 -0
  27. package/dist/shared/{attaform.B1N47eDM.d.ts → attaform.CEY_WLRJ.d.mts} +127 -56
  28. package/dist/shared/{attaform.CwqTCeTZ.mjs → attaform.CSXDW2tO.mjs} +4 -4
  29. package/dist/shared/{attaform.CwqTCeTZ.mjs.map → attaform.CSXDW2tO.mjs.map} +1 -1
  30. package/dist/shared/{attaform.lcTiMoma.mjs → attaform.CXVmZtFq.mjs} +3 -3
  31. package/dist/shared/{attaform.lcTiMoma.mjs.map → attaform.CXVmZtFq.mjs.map} +1 -1
  32. package/dist/shared/{attaform.CE-KJZ0j.cjs → attaform.Cs0UZ7q6.cjs} +4 -4
  33. package/dist/shared/{attaform.CE-KJZ0j.cjs.map → attaform.Cs0UZ7q6.cjs.map} +1 -1
  34. package/dist/shared/{attaform.AkX9wzfY.cjs → attaform.D51IwDtP.cjs} +2 -2
  35. package/dist/shared/{attaform.AkX9wzfY.cjs.map → attaform.D51IwDtP.cjs.map} +1 -1
  36. package/dist/shared/{attaform.QFrCtJBO.d.mts → attaform.DBNjHgNj.d.cts} +86 -17
  37. package/dist/shared/{attaform.QFrCtJBO.d.cts → attaform.DBNjHgNj.d.mts} +86 -17
  38. package/dist/shared/{attaform.QFrCtJBO.d.ts → attaform.DBNjHgNj.d.ts} +86 -17
  39. package/dist/shared/{attaform.DwsfkfSY.mjs → attaform.DGhIix5D.mjs} +48 -3
  40. package/dist/shared/attaform.DGhIix5D.mjs.map +1 -0
  41. package/dist/shared/{attaform.CO0e7YVY.d.mts → attaform.DJRz_bzM.d.cts} +3 -0
  42. package/dist/shared/{attaform.ChY7tIrn.mjs → attaform.DSOIz08n.mjs} +4 -4
  43. package/dist/shared/{attaform.ChY7tIrn.mjs.map → attaform.DSOIz08n.mjs.map} +1 -1
  44. package/dist/shared/{attaform.Buedl_UL.cjs → attaform.Du9RZp8m.cjs} +223 -81
  45. package/dist/shared/attaform.Du9RZp8m.cjs.map +1 -0
  46. package/dist/shared/{attaform.CO0e7YVY.d.ts → attaform.JPsaqjLl.d.mts} +3 -0
  47. package/dist/shared/{attaform.CqW4gF_Z.d.cts → attaform.MgaYKlPP.d.cts} +127 -56
  48. package/dist/shared/{attaform.CZpUm-2l.mjs → attaform.NqYEzRcM.mjs} +2 -2
  49. package/dist/shared/{attaform.CZpUm-2l.mjs.map → attaform.NqYEzRcM.mjs.map} +1 -1
  50. package/dist/shared/{attaform.WvcckZMD.mjs → attaform.Vp2-CWZB.mjs} +3 -2
  51. package/dist/shared/attaform.Vp2-CWZB.mjs.map +1 -0
  52. package/dist/shared/{attaform.CjdepGnw.cjs → attaform.ZXZ6dH72.cjs} +3 -1
  53. package/dist/shared/attaform.ZXZ6dH72.cjs.map +1 -0
  54. package/dist/shared/{attaform.f0l5ydy1.d.mts → attaform.ceYeVkjn.d.ts} +127 -56
  55. package/dist/shared/{attaform.QwLSdxuk.mjs → attaform.k9V0py0s.mjs} +101 -5
  56. package/dist/shared/attaform.k9V0py0s.mjs.map +1 -0
  57. package/dist/shared/{attaform.Bnm0FQaH.cjs → attaform.tZ0YKisf.cjs} +4 -4
  58. package/dist/shared/{attaform.Bnm0FQaH.cjs.map → attaform.tZ0YKisf.cjs.map} +1 -1
  59. package/dist/transforms.cjs +2 -1
  60. package/dist/transforms.cjs.map +1 -1
  61. package/dist/transforms.d.cts +13 -1
  62. package/dist/transforms.d.mts +13 -1
  63. package/dist/transforms.d.ts +13 -1
  64. package/dist/transforms.mjs +1 -1
  65. package/dist/vite.cjs +2 -1
  66. package/dist/vite.cjs.map +1 -1
  67. package/dist/vite.mjs +2 -1
  68. package/dist/vite.mjs.map +1 -1
  69. package/dist/zod-v3.cjs +2 -2
  70. package/dist/zod-v3.d.cts +3 -3
  71. package/dist/zod-v3.d.mts +3 -3
  72. package/dist/zod-v3.d.ts +3 -3
  73. package/dist/zod-v3.mjs +2 -2
  74. package/dist/zod-v4.cjs +2 -2
  75. package/dist/zod-v4.d.cts +6 -6
  76. package/dist/zod-v4.d.mts +6 -6
  77. package/dist/zod-v4.d.ts +6 -6
  78. package/dist/zod-v4.mjs +2 -2
  79. package/dist/zod.cjs +5 -5
  80. package/dist/zod.d.cts +5 -5
  81. package/dist/zod.d.mts +5 -5
  82. package/dist/zod.d.ts +5 -5
  83. package/dist/zod.mjs +5 -5
  84. package/package.json +1 -1
  85. package/dist/shared/attaform.Buedl_UL.cjs.map +0 -1
  86. package/dist/shared/attaform.CAVK56f8.cjs.map +0 -1
  87. package/dist/shared/attaform.CjdepGnw.cjs.map +0 -1
  88. package/dist/shared/attaform.DWKcw1ud.cjs.map +0 -1
  89. package/dist/shared/attaform.DwsfkfSY.mjs.map +0 -1
  90. package/dist/shared/attaform.QwLSdxuk.mjs.map +0 -1
  91. package/dist/shared/attaform.WvcckZMD.mjs.map +0 -1
  92. package/dist/shared/attaform.iJ4y3sHU.mjs.map +0 -1
@@ -1,3 +1,5 @@
1
+ import './attaform.DBNjHgNj.mjs';
2
+
1
3
  /**
2
4
  * Typed error classes thrown by the form library. Each one signals a
3
5
  * distinct misuse so calling code can branch on `instanceof` instead
@@ -9,6 +11,7 @@
9
11
  * extends the standard `Error`, so existing `instanceof Error` usage
10
12
  * keeps working.
11
13
  */
14
+
12
15
  /**
13
16
  * Base for every error class thrown by `attaform`. Sets
14
17
  * `this.name` from the constructor's `new.target.name`, so subclasses
@@ -1,4 +1,4 @@
1
- import { F as FormKey, W as PathKey, m as DisplayCtx, d as GetDisplayState, n as DisplayMachine, V as Path, aa as SlimPrimitiveKind, C as CoercionEntry, g as CoercionRegistry, G as GenericForm, ag as ValidationError, a as AbstractSchema, ak as WriteMeta, j as DeepPartial, al as WriteShape, at as TransformAbortHolder, ae as ValidateOn, A as AttaformDefaults, b as UseFormReturnType, c as RegisterValue } from './attaform.QFrCtJBO.cjs';
1
+ import { F as FormKey, b as UseFormReturnType, G as GenericForm, W as PathKey, m as DisplayCtx, d as GetDisplayState, n as DisplayMachine, V as Path, aa as SlimPrimitiveKind, C as CoercionEntry, g as CoercionRegistry, ag as ValidationError, a as AbstractSchema, ak as WriteMeta, j as DeepPartial, al as WriteShape, at as TransformAbortHolder, ae as ValidateOn, A as AttaformDefaults, c as RegisterValue } from './attaform.DBNjHgNj.cjs';
2
2
  import { Ref, ComputedRef, App, InjectionKey } from 'vue';
3
3
 
4
4
  /**
@@ -137,23 +137,27 @@ declare const _lazyBrand: unique symbol;
137
137
  */
138
138
  type LazyMarker<Ctx = WizardCtx> = {
139
139
  readonly [_lazyBrand]: true;
140
- readonly resolve: (ctx: Ctx) => AnyForm | string | undefined;
140
+ readonly resolve: (ctx: Ctx) => AnyForm | string | null | undefined;
141
141
  };
142
142
  /**
143
143
  * One position in the source `useWizard({ steps })` array. Each slot
144
- * resolves to a compiled `{ key, form }` step:
144
+ * resolves to a compiled `{ key, form }` step, or drops out:
145
145
  *
146
146
  * - `AnyForm` — a form declared via `useForm`. Surfaced as-is.
147
147
  * - `string` — bare key. The wizard generates a noop form
148
148
  * under the hood so the external surface stays
149
149
  * uniform across affordance positions (intro,
150
150
  * terms, congratulations, review surfaces).
151
+ * - `null` / `undefined` — a literal absence, dropped from the compiled
152
+ * list. Lets a conditional step read inline as
153
+ * `cond ? form : null` without pre-filtering the
154
+ * array.
151
155
  * - function — eager slot, re-evaluates reactively. Returns
152
- * one of the above, or `undefined` to drop the
153
- * slot from the compiled list.
156
+ * one of the above, or `null` / `undefined` to
157
+ * drop the slot from the compiled list.
154
158
  * - `LazyMarker` — memoized function slot (see `lazy`).
155
159
  */
156
- type StepSlot<Ctx = WizardCtx> = AnyForm | string | ((ctx: Ctx) => AnyForm | string | undefined) | LazyMarker<Ctx>;
160
+ type StepSlot<Ctx = WizardCtx> = AnyForm | string | null | undefined | ((ctx: Ctx) => AnyForm | string | null | undefined) | LazyMarker<Ctx>;
157
161
  /**
158
162
  * Shape returned by the `restore` callback. Carries the active step's
159
163
  * key; intentionally open-ended (object form) so future additions land
@@ -178,8 +182,9 @@ type WizardRestoreFn = () => WizardRestoreState | undefined;
178
182
  type WizardPersistFn = (state: WizardRestoreState) => void;
179
183
  /**
180
184
  * Submit context passed to the `onSubmit` callback registered via
181
- * `wizard.handleSubmit(onSubmit, onError?)`. Same shape on every step;
182
- * `isFinal` distinguishes intermediate vs final calls.
185
+ * `wizard.handleSubmit(onSubmit, onError?)`. `handleSubmit` always
186
+ * processes the whole step list, so `values` carries every form's
187
+ * parsed output regardless of which step fired the submit.
183
188
  *
184
189
  * - `values` — namespaced aggregate keyed by form key, mirroring
185
190
  * `wizard.allValues`. Reflects parsed output for every
@@ -190,9 +195,11 @@ type WizardPersistFn = (state: WizardRestoreState) => void;
190
195
  * because the form ref carries its schema info.
191
196
  * - `currentKey` — key of the step that fired this submission.
192
197
  * - `isFinal` — `true` when `currentKey` is the last position in
193
- * `wizard.steps`. Intermediate calls validate the active
194
- * form only and advance; final calls validate every form
195
- * and stay on the terminal step.
198
+ * `wizard.steps`. Positional only: it reports where the
199
+ * submit fired, never what got validated. A user who steps
200
+ * back, edits, and submits from the middle sees
201
+ * `isFinal === false`, yet the whole list is still
202
+ * processed and `done` still latches on success.
196
203
  */
197
204
  type WizardSubmitContext = {
198
205
  readonly values: Readonly<Record<FormKey, unknown>>;
@@ -209,9 +216,11 @@ type WizardSubmitContext = {
209
216
  type WizardOnSubmit = (ctx: WizardSubmitContext) => void | Promise<void>;
210
217
  /**
211
218
  * Optional `onError` callback registered via `wizard.handleSubmit`.
212
- * Receives the aggregate error list. Entries originate from per-form
213
- * validation, activation failures (`atta:activation-failed`), and a
214
- * submit callback that left errors on a processed step (the
219
+ * Receives the aggregate error list spanning EVERY step (handleSubmit
220
+ * validates the whole wizard), so a failed submit surfaces every form's
221
+ * errors at once, not just the active step's. Entries originate from
222
+ * per-form validation, activation failures (`atta:activation-failed`),
223
+ * and a submit callback that left errors on a processed step (the
215
224
  * `setErrors(...); return` server-rejection path). Sync or async; the
216
225
  * returned promise gates `wizard.submitting`.
217
226
  */
@@ -296,24 +305,55 @@ type WizardOptions = {
296
305
  */
297
306
  readonly persist?: WizardPersistFn | false;
298
307
  };
308
+ /**
309
+ * True when a single slot is guaranteed to contribute exactly one
310
+ * compiled step: a bare form or affordance string that is neither
311
+ * nullish nor a (maybe-absent) function / `lazy()` slot. One of these
312
+ * anywhere in the tuple proves the compiled list is non-empty.
313
+ */
314
+ type IsGuaranteedStep<T> = [null] extends [T] ? false : [undefined] extends [T] ? false : T extends LazyMarker | ((...args: unknown[]) => unknown) ? false : T extends string ? true : T extends {
315
+ readonly key: string;
316
+ } ? true : false;
299
317
  /**
300
318
  * Predicate: is the steps tuple statically guaranteed to compile to a
301
- * non-empty list? A tuple passes when (a) it's not the empty array
302
- * literal and (b) it carries no function or `lazy()` slot those
303
- * slot kinds can resolve to `undefined` at runtime and drop the
304
- * compiled position. Form slots and bare-string affordance slots
305
- * always preserve their position; a tuple made of only those kinds is
306
- * statically safe.
319
+ * non-empty list? True when at least one element is a guaranteed step
320
+ * (see `IsGuaranteedStep`). Function / `lazy()` slots may resolve to
321
+ * nothing, and `null` / `undefined` (a literal absence or a
322
+ * `cond ? form : null` union) is an explicit drop, so none of those
323
+ * prove non-emptiness; a tuple of only maybe-absent slots stays
324
+ * honestly `| undefined`.
307
325
  *
308
326
  * Used to narrow `currentStep` / `activeForm` to their non-`undefined`
309
327
  * shapes in the common-case wizard, while keeping the honest union
310
328
  * everywhere a runtime drop is reachable.
311
329
  */
312
- type StaticallyNonEmpty<S> = S extends readonly [] ? false : S extends readonly (infer Item)[] ? Item extends LazyMarker | ((...args: unknown[]) => unknown) ? false : true : false;
330
+ type StaticallyNonEmpty<S> = S extends readonly [infer First, ...infer Rest] ? IsGuaranteedStep<First> extends true ? true : StaticallyNonEmpty<Rest> : false;
313
331
  /** Active step's key, narrowed to `string` when `S` is statically safe. */
314
332
  type CurrentStepOf<S> = StaticallyNonEmpty<S> extends true ? FormKey : FormKey | undefined;
315
- /** Active step's form handle, narrowed to `AnyForm` when `S` is statically safe. */
316
- type ActiveFormOf<S> = StaticallyNonEmpty<S> extends true ? AnyForm : AnyForm | undefined;
333
+ /**
334
+ * Active step's form handle, schema-erased to `UseFormReturnType<GenericForm>`
335
+ * and narrowed to non-`undefined` when `S` is statically safe. The runtime
336
+ * value is a live facade over the active step, so reads (`.values`, `.meta`,
337
+ * `.history`) and `.handleSubmit` always target the current step. Values are
338
+ * loose here because the active step's schema is not statically known; reach
339
+ * for the original form ref or `ctx.get(ref)` when you need typed per-step
340
+ * values.
341
+ */
342
+ type ActiveFormOf<S> = StaticallyNonEmpty<S> extends true ? UseFormReturnType<GenericForm> : UseFormReturnType<GenericForm> | undefined;
343
+ /**
344
+ * Per-slot contribution to {@link FormsRecordOf}. Strips a `null` /
345
+ * `undefined` (a literal absence or a `cond ? form : null` union) off the
346
+ * slot first, so a conditionally-present form still maps to its key and
347
+ * stays concretely typed on `wizard.forms`; a purely nullish slot
348
+ * contributes nothing.
349
+ */
350
+ type SlotRecord<First, F = Exclude<First, null | undefined>> = [F] extends [never] ? unknown : F extends string ? {
351
+ readonly [P in F]: AnyForm;
352
+ } : F extends {
353
+ readonly key: infer K extends string;
354
+ } ? {
355
+ readonly [P in K]: F;
356
+ } : unknown;
317
357
  /**
318
358
  * Recursive tuple walk that builds the static portion of
319
359
  * `wizard.forms`. Each step slot contributes to the record:
@@ -325,7 +365,9 @@ type ActiveFormOf<S> = StaticallyNonEmpty<S> extends true ? AnyForm : AnyForm |
325
365
  * the form's own `key` becomes the record key, and the value is
326
366
  * the concrete form handle type — so drilling
327
367
  * `wizard.forms.shipping.values.address` carries the schema-derived
328
- * field types through.
368
+ * field types through. A form kept behind a `cond ? form : null`
369
+ * conditional still maps to its key.
370
+ * - **`null` / `undefined` slot**: contributes nothing.
329
371
  * - **Function / `lazy()` slot**: contributes nothing to the static
330
372
  * map. Runtime-resolved forms are still reachable via the
331
373
  * catch-all index signature on `WizardForms` (typed as `AnyForm`).
@@ -336,13 +378,7 @@ type ActiveFormOf<S> = StaticallyNonEmpty<S> extends true ? AnyForm : AnyForm |
336
378
  type FormsRecordOf<S> = S extends readonly [
337
379
  infer First,
338
380
  ...infer Rest extends ReadonlyArray<StepSlot>
339
- ] ? (First extends string ? {
340
- readonly [P in First]: AnyForm;
341
- } : First extends {
342
- readonly key: infer K extends string;
343
- } ? {
344
- readonly [P in K]: First;
345
- } : unknown) & FormsRecordOf<Rest> : unknown;
381
+ ] ? SlotRecord<First> & FormsRecordOf<Rest> : unknown;
346
382
  /**
347
383
  * `wizard.forms` typed view. Combines the static per-step type map
348
384
  * with a catch-all `Record<FormKey, AnyForm>` fallback so:
@@ -376,9 +412,15 @@ type WizardForms<S> = FormsRecordOf<S> & Readonly<Record<FormKey, AnyForm>>;
376
412
  * slots). Otherwise reads as `string | undefined`
377
413
  * so the degenerate case (empty list at runtime)
378
414
  * surfaces honestly.
379
- * - `activeForm` — the active step's form handle. Same narrowing as
380
- * `currentStep`. Noop forms cover string slots in
381
- * the normal path.
415
+ * - `activeForm` — a LIVE view of the active step's form. Operating
416
+ * through it always targets the current step, so a
417
+ * handler captured once at setup
418
+ * (`wizard.activeForm.handleSubmit(() =>
419
+ * wizard.next())`) retargets as the wizard advances.
420
+ * No longer `===` the raw per-step handle; reach for
421
+ * `wizard.forms[key]` when you need raw identity.
422
+ * Same `undefined` narrowing as `currentStep`. Noop
423
+ * forms cover string slots in the normal path.
382
424
  * - `activeIndex` — 0-based position of the active step.
383
425
  * - `isFinalStep` — `true` when `currentStep === steps[count - 1].key`.
384
426
  * - `steps` — ordered list of compiled `{ key, form }` slots.
@@ -404,13 +446,13 @@ type WizardForms<S> = FormsRecordOf<S> & Readonly<Record<FormKey, AnyForm>>;
404
446
  * Forward-looking; reactive to current form
405
447
  * validity. Gates "Finish button enable" style UI.
406
448
  * - `done` — monotonic latch: flips `true` the first time a
407
- * final-step `handleSubmit` resolves without throwing
408
- * AND leaves no errors set on any step, and stays
409
- * `true` through subsequent edits or invalidations. A
410
- * callback that calls `setErrors` and returns (the
411
- * documented server-rejection path) is a failed submit,
412
- * so it does not flip `done`. Only `reset()` flips it
413
- * back. Gates "show success card" style UI that should
449
+ * `handleSubmit` resolves without throwing AND leaves
450
+ * no errors set on any step, and stays `true` through
451
+ * subsequent edits or invalidations. A callback that
452
+ * calls `setErrors` and returns (the documented
453
+ * server-rejection path) is a failed submit, so it
454
+ * does not flip `done`. Only `reset()` flips it back.
455
+ * Gates "show success card" style UI that should
414
456
  * reflect submission history rather than current
415
457
  * validity.
416
458
  * - `submitting` — `true` while a `wizard.handleSubmit` call is in
@@ -434,11 +476,23 @@ type WizardForms<S> = FormsRecordOf<S> & Readonly<Record<FormKey, AnyForm>>;
434
476
  * `back()` does not pop; the trail is the audit
435
477
  * log, not the back-stack.
436
478
  * - `next/back/goTo` — pure navigation. Refuses while `submitting`.
437
- * - `handleSubmit(onSubmit, onError?)` universal across all steps.
438
- * Intermediate calls validate the active form and
439
- * advance; final calls validate every form. Returns
440
- * an event handler suitable for `<form @submit>` or
441
- * imperative use.
479
+ * - `tryNext()` validate the active step, then advance iff it
480
+ * passed; invalid input keeps the pin put under the
481
+ * form's standard error reveal (first error focused,
482
+ * display state advanced). The inline-bindable
483
+ * shorthand for `activeForm.handleSubmit(() =>
484
+ * next())`. Resolves to whether the pin moved. No-ops
485
+ * to `false` on a degenerate or final-step wizard
486
+ * (finish via `handleSubmit`).
487
+ * - `handleSubmit(onSubmit, onError?)` — always validates the entire
488
+ * step list, from any step, and never advances the
489
+ * pin: on success it latches `done`; on any error it
490
+ * focuses the first failing step and fires `onError`
491
+ * with errors spanning every step. To gate advancing
492
+ * a step on its own validity, compose with the active
493
+ * form's submit: `activeForm.handleSubmit(() =>
494
+ * wizard.next())`. Returns an event handler suitable
495
+ * for `<form @submit>` or imperative use.
442
496
  * - `reset()` — zeros wizard lifecycle (`submissionAttempts`,
443
497
  * `visited`), resets every form, returns
444
498
  * `currentStep` to `steps[0].key`, and invokes
@@ -468,6 +522,7 @@ type UseWizardReturnType<S extends ReadonlyArray<StepSlot> = ReadonlyArray<StepS
468
522
  readonly next: () => Promise<void>;
469
523
  readonly back: () => void;
470
524
  readonly goTo: (key: string) => void;
525
+ readonly tryNext: () => Promise<boolean>;
471
526
  readonly handleSubmit: (onSubmit: WizardOnSubmit, onError?: WizardOnError) => (event?: Event) => Promise<void>;
472
527
  readonly reset: () => void;
473
528
  };
@@ -1546,8 +1601,10 @@ type InjectFormInput = {
1546
1601
  * independent of component-tree position.
1547
1602
  *
1548
1603
  * Returns `null` when no matching form exists (no ambient ancestor, or
1549
- * the named key isn't registered). A dev-mode warning points at the
1550
- * call site to help diagnose typos. Always narrow before using:
1604
+ * the named key isn't registered yet). A dev-mode warning points at the
1605
+ * call site, lists the registered keys, and flags the mount-timing case
1606
+ * (a form created by a child or sibling isn't registered until its own
1607
+ * setup runs). Always narrow before using:
1551
1608
  *
1552
1609
  * ```ts
1553
1610
  * const form = injectForm<Shape>('signup')
@@ -1650,9 +1707,10 @@ declare function useRegister<V = unknown>(): UseRegisterReturn<V> | undefined;
1650
1707
  *
1651
1708
  * The wizard's surface is read-only from the consumer's side:
1652
1709
  * navigation (`next` / `back` / `goTo`) walks positional indices,
1653
- * `handleSubmit` validates the active form on intermediate steps and
1654
- * the whole wizard on the final step, and URL synchronization rides on
1655
- * `restore` / `persist` callbacks that default to `?step=<key>`.
1710
+ * `handleSubmit` validates the whole step list from any step and never
1711
+ * advances (gate advance with `activeForm.handleSubmit(() =>
1712
+ * wizard.next())`), and URL synchronization rides on `restore` /
1713
+ * `persist` callbacks that default to `?step=<key>`.
1656
1714
  */
1657
1715
  declare function useWizard<const S extends ReadonlyArray<StepSlot>>(options: WizardOptions & {
1658
1716
  readonly steps: S;
@@ -1696,8 +1754,10 @@ type InjectWizardInput = {
1696
1754
  * constructed with `useWizard({ steps, key })` to be reachable.
1697
1755
  *
1698
1756
  * Returns `null` when no matching wizard exists (no ambient ancestor,
1699
- * or the named key isn't registered). A dev-mode warning points at the
1700
- * call site to help diagnose typos. Always narrow before using:
1757
+ * or the named key isn't registered yet). A dev-mode warning points at
1758
+ * the call site, lists the registered keys, and flags the mount-timing
1759
+ * case (a wizard created by a child or sibling isn't registered until
1760
+ * its own setup runs). Always narrow before using:
1701
1761
  *
1702
1762
  * ```ts
1703
1763
  * const wizard = injectWizard('signup')
@@ -1747,13 +1807,13 @@ declare function injectWizard(input?: string | InjectWizardInput): UseWizardRetu
1747
1807
  * this one.
1748
1808
  * - `wizard.reset()` clears every lazy slot's cache so a reboot truly
1749
1809
  * resolves from scratch.
1750
- * - Resolving to `undefined` drops the slot from the compiled list
1751
- * until the resolver next re-fires.
1810
+ * - Resolving to `null` or `undefined` drops the slot from the compiled
1811
+ * list until the resolver next re-fires.
1752
1812
  *
1753
1813
  * The runtime brand returned by `lazy()` is opaque. Use
1754
1814
  * {@link isLazyMarker} to detect it.
1755
1815
  */
1756
- declare function lazy<Ctx = WizardCtx>(resolve: (ctx: Ctx) => AnyForm | string | undefined): LazyMarker<Ctx>;
1816
+ declare function lazy<Ctx = WizardCtx>(resolve: (ctx: Ctx) => AnyForm | string | null | undefined): LazyMarker<Ctx>;
1757
1817
 
1758
1818
  /**
1759
1819
  * Stable identifiers for library-emitted `ValidationError` codes.
@@ -1820,6 +1880,17 @@ declare const AttaformErrorCode: {
1820
1880
  * Override it per error by passing your own `code` (`api:…`, `auth:…`).
1821
1881
  */
1822
1882
  readonly UserError: "atta:user-error";
1883
+ /**
1884
+ * Stamped on a `ValidationError` synthesized from a throw or rejection
1885
+ * out of a `handleSubmit` `onSubmit` callback. The raw error also lands
1886
+ * on `form.meta.submitError` (a raw-`Error` inspection channel); this
1887
+ * code marks the copy piped into the user-error layer so the failure
1888
+ * also surfaces on `form.errors` / `meta.ownErrors` / `firstOwnError`
1889
+ * where the UI already reads. A throw carrying its own `path` / `code`
1890
+ * keeps them; a bare `Error` or a non-Error throw defaults here at the
1891
+ * form-level path `[]`.
1892
+ */
1893
+ readonly SubmitError: "atta:submit-error";
1823
1894
  };
1824
1895
  type AttaformErrorCode = (typeof AttaformErrorCode)[keyof typeof AttaformErrorCode];
1825
1896
 
@@ -1,4 +1,4 @@
1
- import { i as getRegistryFromApp, m as pathKeyToDotted } from './attaform.DwsfkfSY.mjs';
1
+ import { i as getRegistryFromApp, m as pathKeyToDotted } from './attaform.DGhIix5D.mjs';
2
2
 
3
3
  function renderAttaformState(app) {
4
4
  const registry = getRegistryFromApp(app);
@@ -32,4 +32,4 @@ function hydrateAttaformState(app, payload) {
32
32
  const DEVTOOLS_WINDOW_KEY = "__attaform_devtools__";
33
33
 
34
34
  export { DEVTOOLS_WINDOW_KEY as D, hydrateAttaformState as h, renderAttaformState as r };
35
- //# sourceMappingURL=attaform.CZpUm-2l.mjs.map
35
+ //# sourceMappingURL=attaform.NqYEzRcM.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"attaform.CZpUm-2l.mjs","sources":["../../src/runtime/core/serialize.ts","../../src/runtime/core/devtools-shared.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormKey } from '../types/types-api'\nimport { pathKeyToDotted, type PathKey } from './paths'\nimport { getRegistryFromApp, type SerializedFormData } from './registry'\n\n/**\n * Serialised snapshot of every form in a Vue app, produced by\n * `renderAttaformState` and consumed by `hydrateAttaformState`.\n *\n * JSON-safe — pass to `JSON.stringify`, `devalue`, or any other\n * serialiser before embedding in your SSR payload.\n */\nexport type SerializedAttaformState = {\n /** Tuples of `[formKey, snapshot]` for every form in the app. */\n readonly forms: ReadonlyArray<readonly [FormKey, SerializedFormData]>\n}\n\n/**\n * Snapshot every form on a Vue app for SSR. Call from your server\n * entry after rendering the app:\n *\n * ```ts\n * import { renderToString } from '@vue/server-renderer'\n * import { renderAttaformState, escapeForInlineScript } from 'attaform'\n *\n * const html = await renderToString(app)\n * const state = renderAttaformState(app)\n * const payload = escapeForInlineScript(JSON.stringify(state))\n *\n * return `\n * ${html}\n * <script>window.__ATTAFORM_STATE__ = ${payload}</script>\n * `\n * ```\n *\n * Pair with `hydrateAttaformState` on the client to restore the\n * forms in their server-rendered state. Nuxt users don't need this —\n * `attaform/nuxt` wires SSR automatically.\n */\nexport function renderAttaformState(app: App): SerializedAttaformState {\n const registry = getRegistryFromApp(app)\n const forms: Array<readonly [FormKey, SerializedFormData]> = []\n for (const [key, state] of registry.forms) {\n // Skip the blank field when the set is empty so the\n // wire payload stays minimal for forms that don't use it. The\n // optional shape on the consuming side handles the absence\n // cleanly (defaults to \"no blank paths\"). PathKey → dotted at\n // the boundary so the wire shape matches the rest of the\n // public path notation.\n const transientList: string[] = []\n for (const pk of state.blankPaths) {\n const d = pathKeyToDotted(pk as PathKey)\n if (d !== null) transientList.push(d)\n }\n forms.push([\n key,\n {\n form: state.form.value,\n schemaErrors: Array.from(state.schemaErrors.entries()),\n userErrors: Array.from(state.userErrors.entries()),\n fields: Array.from(state.fields.entries()),\n ...(transientList.length > 0 ? { blankPaths: transientList } : {}),\n },\n ])\n }\n return { forms }\n}\n\n/**\n * Restore forms from a server-rendered snapshot on the client. Call\n * from your client entry before mounting:\n *\n * ```ts\n * import { createApp } from 'vue'\n * import { createAttaform, hydrateAttaformState } from 'attaform'\n *\n * const app = createApp(App).use(createAttaform())\n * hydrateAttaformState(app, window.__ATTAFORM_STATE__)\n * app.mount('#app')\n * ```\n *\n * The next `useForm({ key })` call for each serialised form picks up\n * the snapshot transparently — no further action is required.\n */\nexport function hydrateAttaformState(app: App, payload: SerializedAttaformState): void {\n const registry = getRegistryFromApp(app)\n for (const [key, data] of payload.forms) {\n registry.pendingHydration.set(key, data)\n }\n}\n","/**\n * Shared building blocks for Attaform's two devtools surfaces — the Vue\n * DevTools (Chrome-extension) inspector wired up in `./devtools.ts`, and\n * the Nuxt DevTools (overlay) panel wired up via `../../nuxt.ts` +\n * `../pages/_attaform_devtools.vue`.\n *\n * Houses the window-bridge contract both surfaces consume so a new\n * bridge field lands in one file. Both surfaces render RAW form values\n * by design — DevTools is a dev-only surface, and redaction across every\n * place a value surfaces is impractical security theater rather than a\n * real safeguard.\n */\nimport type { AttaformRegistry } from './registry'\n\n/**\n * Property key on `window` that the Nuxt-side dev plugin attaches the\n * bridge object to. The iframe-mounted overlay panel reads\n * `window.parent[DEVTOOLS_WINDOW_KEY]` to reach the host app's registry.\n *\n * Underscored + namespaced to make accidental collision with consumer\n * globals vanishingly unlikely. Stable across versions — bumping it\n * would silently disconnect older library builds from newer overlay\n * panels in the same browser tab during a library upgrade.\n */\nexport const DEVTOOLS_WINDOW_KEY = '__attaform_devtools__'\n\n/**\n * Shape of the object the host plugin attaches to `window` in dev mode.\n * The iframe overlay panel reads this to discover the live registry and\n * render its forms.\n *\n * Single-registry assumption: the latest `createAttaform()` install\n * wins. Multi-app pages (rare; typically only seen in micro-frontend\n * setups) will only see one app's forms in the panel. Documented but\n * not actively supported — the alternative (a Set of registries with\n * union-rendering) is a future call if a real consumer hits it.\n */\nexport interface AttaformDevtoolsBridge {\n registry: AttaformRegistry\n /**\n * The library version, surfaced in the panel's footer for support /\n * bug-report context. Read from `package.json` at host-plugin init.\n */\n version: string\n}\n\ndeclare global {\n interface Window {\n [DEVTOOLS_WINDOW_KEY]?: AttaformDevtoolsBridge\n }\n}\n"],"names":[],"mappings":";;AAuCO,SAAS,oBAAoB,GAAA,EAAmC;AACrE,EAAA,MAAM,QAAA,GAAW,mBAAmB,GAAG,CAAA;AACvC,EAAA,MAAM,QAAuD,EAAC;AAC9D,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,SAAS,KAAA,EAAO;AAOzC,IAAA,MAAM,gBAA0B,EAAC;AACjC,IAAA,KAAA,MAAW,EAAA,IAAM,MAAM,UAAA,EAAY;AACjC,MAAA,MAAM,CAAA,GAAI,gBAAgB,EAAa,CAAA;AACvC,MAAA,IAAI,CAAA,KAAM,IAAA,EAAM,aAAA,CAAc,IAAA,CAAK,CAAC,CAAA;AAAA,IACtC;AACA,IAAA,KAAA,CAAM,IAAA,CAAK;AAAA,MACT,GAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAM,MAAM,IAAA,CAAK,KAAA;AAAA,QACjB,cAAc,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,SAAS,CAAA;AAAA,QACrD,YAAY,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAA,CAAW,SAAS,CAAA;AAAA,QACjD,QAAQ,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,SAAS,CAAA;AAAA,QACzC,GAAI,cAAc,MAAA,GAAS,CAAA,GAAI,EAAE,UAAA,EAAY,aAAA,KAAkB;AAAC;AAClE,KACD,CAAA;AAAA,EACH;AACA,EAAA,OAAO,EAAE,KAAA,EAAM;AACjB;AAkBO,SAAS,oBAAA,CAAqB,KAAU,OAAA,EAAwC;AACrF,EAAA,MAAM,QAAA,GAAW,mBAAmB,GAAG,CAAA;AACvC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,CAAA,IAAK,QAAQ,KAAA,EAAO;AACvC,IAAA,QAAA,CAAS,gBAAA,CAAiB,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AAAA,EACzC;AACF;;ACjEO,MAAM,mBAAA,GAAsB;;;;"}
1
+ {"version":3,"file":"attaform.NqYEzRcM.mjs","sources":["../../src/runtime/core/serialize.ts","../../src/runtime/core/devtools-shared.ts"],"sourcesContent":["import type { App } from 'vue'\nimport type { FormKey } from '../types/types-api'\nimport { pathKeyToDotted, type PathKey } from './paths'\nimport { getRegistryFromApp, type SerializedFormData } from './registry'\n\n/**\n * Serialised snapshot of every form in a Vue app, produced by\n * `renderAttaformState` and consumed by `hydrateAttaformState`.\n *\n * JSON-safe — pass to `JSON.stringify`, `devalue`, or any other\n * serialiser before embedding in your SSR payload.\n */\nexport type SerializedAttaformState = {\n /** Tuples of `[formKey, snapshot]` for every form in the app. */\n readonly forms: ReadonlyArray<readonly [FormKey, SerializedFormData]>\n}\n\n/**\n * Snapshot every form on a Vue app for SSR. Call from your server\n * entry after rendering the app:\n *\n * ```ts\n * import { renderToString } from '@vue/server-renderer'\n * import { renderAttaformState, escapeForInlineScript } from 'attaform'\n *\n * const html = await renderToString(app)\n * const state = renderAttaformState(app)\n * const payload = escapeForInlineScript(JSON.stringify(state))\n *\n * return `\n * ${html}\n * <script>window.__ATTAFORM_STATE__ = ${payload}</script>\n * `\n * ```\n *\n * Pair with `hydrateAttaformState` on the client to restore the\n * forms in their server-rendered state. Nuxt users don't need this —\n * `attaform/nuxt` wires SSR automatically.\n */\nexport function renderAttaformState(app: App): SerializedAttaformState {\n const registry = getRegistryFromApp(app)\n const forms: Array<readonly [FormKey, SerializedFormData]> = []\n for (const [key, state] of registry.forms) {\n // Skip the blank field when the set is empty so the\n // wire payload stays minimal for forms that don't use it. The\n // optional shape on the consuming side handles the absence\n // cleanly (defaults to \"no blank paths\"). PathKey → dotted at\n // the boundary so the wire shape matches the rest of the\n // public path notation.\n const transientList: string[] = []\n for (const pk of state.blankPaths) {\n const d = pathKeyToDotted(pk as PathKey)\n if (d !== null) transientList.push(d)\n }\n forms.push([\n key,\n {\n form: state.form.value,\n schemaErrors: Array.from(state.schemaErrors.entries()),\n userErrors: Array.from(state.userErrors.entries()),\n fields: Array.from(state.fields.entries()),\n ...(transientList.length > 0 ? { blankPaths: transientList } : {}),\n },\n ])\n }\n return { forms }\n}\n\n/**\n * Restore forms from a server-rendered snapshot on the client. Call\n * from your client entry before mounting:\n *\n * ```ts\n * import { createApp } from 'vue'\n * import { createAttaform, hydrateAttaformState } from 'attaform'\n *\n * const app = createApp(App).use(createAttaform())\n * hydrateAttaformState(app, window.__ATTAFORM_STATE__)\n * app.mount('#app')\n * ```\n *\n * The next `useForm({ key })` call for each serialised form picks up\n * the snapshot transparently — no further action is required.\n */\nexport function hydrateAttaformState(app: App, payload: SerializedAttaformState): void {\n const registry = getRegistryFromApp(app)\n for (const [key, data] of payload.forms) {\n registry.pendingHydration.set(key, data)\n }\n}\n","/**\n * Shared building blocks for Attaform's two devtools surfaces — the Vue\n * DevTools (Chrome-extension) inspector wired up in `./devtools.ts`, and\n * the Nuxt DevTools (overlay) panel wired up via `../../nuxt.ts` +\n * `../pages/_attaform_devtools.vue`.\n *\n * Houses the window-bridge contract both surfaces consume so a new\n * bridge field lands in one file. Both surfaces render RAW form values\n * by design — DevTools is a dev-only surface, and redaction across every\n * place a value surfaces is impractical security theater rather than a\n * real safeguard.\n */\nimport type { AttaformRegistry } from './registry'\n\n/**\n * Property key on `window` that the Nuxt-side dev plugin attaches the\n * bridge object to. The iframe-mounted overlay panel reads\n * `window.parent[DEVTOOLS_WINDOW_KEY]` to reach the host app's registry.\n *\n * Underscored + namespaced to make accidental collision with consumer\n * globals vanishingly unlikely. Stable across versions — bumping it\n * would silently disconnect older library builds from newer overlay\n * panels in the same browser tab during a library upgrade.\n */\nexport const DEVTOOLS_WINDOW_KEY = '__attaform_devtools__'\n\n/**\n * Shape of the object the host plugin attaches to `window` in dev mode.\n * The iframe overlay panel reads this to discover the live registry and\n * render its forms.\n *\n * Single-registry assumption: the latest `createAttaform()` install\n * wins. Multi-app pages (rare; typically only seen in micro-frontend\n * setups) will only see one app's forms in the panel. Documented but\n * not actively supported — the alternative (a Set of registries with\n * union-rendering) is a future call if a real consumer hits it.\n */\nexport interface AttaformDevtoolsBridge {\n registry: AttaformRegistry\n /**\n * The library version, surfaced in the panel's footer for support /\n * bug-report context. Read from `package.json` at host-plugin init.\n */\n version: string\n}\n\ndeclare global {\n interface Window {\n [DEVTOOLS_WINDOW_KEY]?: AttaformDevtoolsBridge\n }\n}\n"],"names":[],"mappings":";;AAuCO,SAAS,oBAAoB,GAAA,EAAmC;AACrE,EAAA,MAAM,QAAA,GAAW,mBAAmB,GAAG,CAAA;AACvC,EAAA,MAAM,QAAuD,EAAC;AAC9D,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,SAAS,KAAA,EAAO;AAOzC,IAAA,MAAM,gBAA0B,EAAC;AACjC,IAAA,KAAA,MAAW,EAAA,IAAM,MAAM,UAAA,EAAY;AACjC,MAAA,MAAM,CAAA,GAAI,gBAAgB,EAAa,CAAA;AACvC,MAAA,IAAI,CAAA,KAAM,IAAA,EAAM,aAAA,CAAc,IAAA,CAAK,CAAC,CAAA;AAAA,IACtC;AACA,IAAA,KAAA,CAAM,IAAA,CAAK;AAAA,MACT,GAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAM,MAAM,IAAA,CAAK,KAAA;AAAA,QACjB,cAAc,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,SAAS,CAAA;AAAA,QACrD,YAAY,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAA,CAAW,SAAS,CAAA;AAAA,QACjD,QAAQ,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,SAAS,CAAA;AAAA,QACzC,GAAI,cAAc,MAAA,GAAS,CAAA,GAAI,EAAE,UAAA,EAAY,aAAA,KAAkB;AAAC;AAClE,KACD,CAAA;AAAA,EACH;AACA,EAAA,OAAO,EAAE,KAAA,EAAM;AACjB;AAkBO,SAAS,oBAAA,CAAqB,KAAU,OAAA,EAAwC;AACrF,EAAA,MAAM,QAAA,GAAW,mBAAmB,GAAG,CAAA;AACvC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,CAAA,IAAK,QAAQ,KAAA,EAAO;AACvC,IAAA,QAAA,CAAS,gBAAA,CAAiB,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA;AAAA,EACzC;AACF;;ACjEO,MAAM,mBAAA,GAAsB;;;;"}
@@ -3,6 +3,7 @@ import { isRef } from 'vue';
3
3
  const V_REGISTER_MARKER = Symbol.for("attaform:v-register-directive");
4
4
  const REGISTER_OWNER_MARKER = Symbol.for("attaform:register-owner-marker");
5
5
  const SSR_COMPONENT_HOST_MODIFIER = "attaformComponentHost";
6
+ const V_REGISTER_COMPILED_MODIFIER = "attaformCompiled";
6
7
  function isRegisterValue(val) {
7
8
  if (typeof val !== "object" || val === null) return false;
8
9
  if (!("innerRef" in val)) return false;
@@ -17,5 +18,5 @@ function isTransforming(value) {
17
18
  return isRegisterValue(value) && value.transforming;
18
19
  }
19
20
 
20
- export { REGISTER_OWNER_MARKER as R, SSR_COMPONENT_HOST_MODIFIER as S, V_REGISTER_MARKER as V, isTransforming as a, isRegisterValue as i };
21
- //# sourceMappingURL=attaform.WvcckZMD.mjs.map
21
+ export { REGISTER_OWNER_MARKER as R, SSR_COMPONENT_HOST_MODIFIER as S, V_REGISTER_COMPILED_MODIFIER as V, isTransforming as a, V_REGISTER_MARKER as b, isRegisterValue as i };
22
+ //# sourceMappingURL=attaform.Vp2-CWZB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attaform.Vp2-CWZB.mjs","sources":["../../src/runtime/core/register-protocol.ts"],"sourcesContent":["import { isRef } from 'vue'\nimport type { InternalRegisterValue, RegisterValue } from '../types/types-api'\n\n// Shared register-protocol primitives: the markers and value guards that\n// the v-register directive, its file / lifecycle satellites, and\n// useRegister all build on. They live in this leaf (depending only on vue\n// and the types module) so those modules don't import directive.ts back,\n// which would form an import cycle.\n\n/**\n * Marker installed on the v-register directive object so consumers\n * (notably `useRegister`) can identify it in a child vnode's\n * directive list even when the consumer's bundler hasn't installed\n * attaform's compile-time transforms. `Symbol.for(...)` round-trips\n * across duplicate bundle copies: `attaform` and `attaform/zod` can\n * land on different `vRegister` references in the playground or\n * under pnpm-hoist edge cases, but both carry the same marker.\n */\nexport const V_REGISTER_MARKER: unique symbol = Symbol.for('attaform:v-register-directive')\n\n/**\n * Marker on the rendered root DOM element. Set by `useRegister`'s\n * `onMounted` hook; read by the directive's deferred warn check to\n * skip the \"is a no-op\" warn for components that handle binding via\n * an inner v-register.\n *\n * `Symbol.for(...)` so the marker round-trips across duplicate copies\n * of attaform — see `assignKey` in core/directive.ts for the same\n * reasoning. `useRegister` and the directive are typically loaded\n * from the same module copy, but a consumer importing from\n * `attaform/zod` (Vite-optimized bundle) and the Nuxt\n * plugin's relative-path import (live ESM) can land on different\n * copies; a global symbol means the marker check still works.\n */\nexport const REGISTER_OWNER_MARKER: unique symbol = Symbol.for('attaform:register-owner-marker')\n\n/**\n * Directive modifier the `componentBridgeTransform` stamps onto a\n * `v-register` that lands on a component host (or kebab custom element).\n * It is the compile-time -> runtime signal the directive's `getSSRProps`\n * reads under compiled SSR, where Vue passes a `null` vnode and the host\n * is otherwise indistinguishable from a native control. autoAria then\n * suppresses its attrs on the host root (the inner control the component\n * re-binds via useRegister carries them). Namespaced to avoid collision\n * with any author-written modifier. (#404)\n */\nexport const SSR_COMPONENT_HOST_MODIFIER = 'attaformComponentHost'\n\n/**\n * Directive modifier the `redundantBindingWarnTransform` stamps onto\n * every `v-register` it processes. It is the compile-time -> runtime\n * signal that Attaform's template transforms are active, and it lets the\n * runtime redundant-binding diagnostic (in the directive's `created`\n * hook) stand down.\n *\n * The hand-off matters because `inputTextAreaNodeTransform` strips the\n * author's `value` / `checked` and injects its own. Once that has run,\n * `vnode.props` no longer reflects what the author wrote, so a runtime\n * check would flag every field. With this marker present the compile\n * layer owns detection (it reads the authored props BEFORE injection);\n * with it absent (no bundler plugin) the runtime layer reads the\n * untouched `vnode.props` directly. Exactly one layer fires per\n * consumer, never both.\n *\n * Namespaced to avoid collision with any author-written modifier, like\n * `SSR_COMPONENT_HOST_MODIFIER` above.\n */\nexport const V_REGISTER_COMPILED_MODIFIER = 'attaformCompiled'\n\n/**\n * Type guard for a `RegisterValue`. Returns `true` when `val` looks\n * like the object returned from `form.register(path)`.\n *\n * ```ts\n * if (isRegisterValue(slotValue)) {\n * // slotValue.innerRef is now a Ref<unknown>\n * }\n * ```\n *\n * Useful when building wrapper components that accept either a\n * `RegisterValue` or a plain ref via the same prop.\n */\nexport function isRegisterValue<Value = unknown>(val: unknown): val is RegisterValue<Value> {\n if (typeof val !== 'object' || val === null) return false\n if (!('innerRef' in val)) return false\n if (!isRef(val.innerRef)) return false\n if (!('registerElement' in val)) return false\n if (typeof val.registerElement !== 'function') return false\n if (!('setValueWithInternalPath' in val)) return false\n if (typeof val.setValueWithInternalPath !== 'function') return false\n return true\n}\n\n/**\n * `true` while a deferred async transform is in flight at this path.\n * `beginTransform` flips it synchronously inside the assigner, so a\n * listener's post-write force-sync block reads it (right after the\n * assigner returns) to skip snapping the DOM back to stale storage —\n * the resolved value is painted in by the orchestrator's `syncDom`\n * once the run lands.\n */\nexport function isTransforming(value: unknown): boolean {\n return isRegisterValue(value) && (value as InternalRegisterValue).transforming\n}\n"],"names":[],"mappings":";;AAkBO,MAAM,iBAAA,GAAmC,MAAA,CAAO,GAAA,CAAI,+BAA+B;AAgBnF,MAAM,qBAAA,GAAuC,MAAA,CAAO,GAAA,CAAI,gCAAgC;AAYxF,MAAM,2BAAA,GAA8B;AAqBpC,MAAM,4BAAA,GAA+B;AAerC,SAAS,gBAAiC,GAAA,EAA2C;AAC1F,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,MAAM,OAAO,KAAA;AACpD,EAAA,IAAI,EAAE,UAAA,IAAc,GAAA,CAAA,EAAM,OAAO,KAAA;AACjC,EAAA,IAAI,CAAC,KAAA,CAAM,GAAA,CAAI,QAAQ,GAAG,OAAO,KAAA;AACjC,EAAA,IAAI,EAAE,iBAAA,IAAqB,GAAA,CAAA,EAAM,OAAO,KAAA;AACxC,EAAA,IAAI,OAAO,GAAA,CAAI,eAAA,KAAoB,UAAA,EAAY,OAAO,KAAA;AACtD,EAAA,IAAI,EAAE,0BAAA,IAA8B,GAAA,CAAA,EAAM,OAAO,KAAA;AACjD,EAAA,IAAI,OAAO,GAAA,CAAI,wBAAA,KAA6B,UAAA,EAAY,OAAO,KAAA;AAC/D,EAAA,OAAO,IAAA;AACT;AAUO,SAAS,eAAe,KAAA,EAAyB;AACtD,EAAA,OAAO,eAAA,CAAgB,KAAK,CAAA,IAAM,KAAA,CAAgC,YAAA;AACpE;;;;"}
@@ -5,6 +5,7 @@ const vue = require('vue');
5
5
  const V_REGISTER_MARKER = Symbol.for("attaform:v-register-directive");
6
6
  const REGISTER_OWNER_MARKER = Symbol.for("attaform:register-owner-marker");
7
7
  const SSR_COMPONENT_HOST_MODIFIER = "attaformComponentHost";
8
+ const V_REGISTER_COMPILED_MODIFIER = "attaformCompiled";
8
9
  function isRegisterValue(val) {
9
10
  if (typeof val !== "object" || val === null) return false;
10
11
  if (!("innerRef" in val)) return false;
@@ -21,7 +22,8 @@ function isTransforming(value) {
21
22
 
22
23
  exports.REGISTER_OWNER_MARKER = REGISTER_OWNER_MARKER;
23
24
  exports.SSR_COMPONENT_HOST_MODIFIER = SSR_COMPONENT_HOST_MODIFIER;
25
+ exports.V_REGISTER_COMPILED_MODIFIER = V_REGISTER_COMPILED_MODIFIER;
24
26
  exports.V_REGISTER_MARKER = V_REGISTER_MARKER;
25
27
  exports.isRegisterValue = isRegisterValue;
26
28
  exports.isTransforming = isTransforming;
27
- //# sourceMappingURL=attaform.CjdepGnw.cjs.map
29
+ //# sourceMappingURL=attaform.ZXZ6dH72.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attaform.ZXZ6dH72.cjs","sources":["../../src/runtime/core/register-protocol.ts"],"sourcesContent":["import { isRef } from 'vue'\nimport type { InternalRegisterValue, RegisterValue } from '../types/types-api'\n\n// Shared register-protocol primitives: the markers and value guards that\n// the v-register directive, its file / lifecycle satellites, and\n// useRegister all build on. They live in this leaf (depending only on vue\n// and the types module) so those modules don't import directive.ts back,\n// which would form an import cycle.\n\n/**\n * Marker installed on the v-register directive object so consumers\n * (notably `useRegister`) can identify it in a child vnode's\n * directive list even when the consumer's bundler hasn't installed\n * attaform's compile-time transforms. `Symbol.for(...)` round-trips\n * across duplicate bundle copies: `attaform` and `attaform/zod` can\n * land on different `vRegister` references in the playground or\n * under pnpm-hoist edge cases, but both carry the same marker.\n */\nexport const V_REGISTER_MARKER: unique symbol = Symbol.for('attaform:v-register-directive')\n\n/**\n * Marker on the rendered root DOM element. Set by `useRegister`'s\n * `onMounted` hook; read by the directive's deferred warn check to\n * skip the \"is a no-op\" warn for components that handle binding via\n * an inner v-register.\n *\n * `Symbol.for(...)` so the marker round-trips across duplicate copies\n * of attaform — see `assignKey` in core/directive.ts for the same\n * reasoning. `useRegister` and the directive are typically loaded\n * from the same module copy, but a consumer importing from\n * `attaform/zod` (Vite-optimized bundle) and the Nuxt\n * plugin's relative-path import (live ESM) can land on different\n * copies; a global symbol means the marker check still works.\n */\nexport const REGISTER_OWNER_MARKER: unique symbol = Symbol.for('attaform:register-owner-marker')\n\n/**\n * Directive modifier the `componentBridgeTransform` stamps onto a\n * `v-register` that lands on a component host (or kebab custom element).\n * It is the compile-time -> runtime signal the directive's `getSSRProps`\n * reads under compiled SSR, where Vue passes a `null` vnode and the host\n * is otherwise indistinguishable from a native control. autoAria then\n * suppresses its attrs on the host root (the inner control the component\n * re-binds via useRegister carries them). Namespaced to avoid collision\n * with any author-written modifier. (#404)\n */\nexport const SSR_COMPONENT_HOST_MODIFIER = 'attaformComponentHost'\n\n/**\n * Directive modifier the `redundantBindingWarnTransform` stamps onto\n * every `v-register` it processes. It is the compile-time -> runtime\n * signal that Attaform's template transforms are active, and it lets the\n * runtime redundant-binding diagnostic (in the directive's `created`\n * hook) stand down.\n *\n * The hand-off matters because `inputTextAreaNodeTransform` strips the\n * author's `value` / `checked` and injects its own. Once that has run,\n * `vnode.props` no longer reflects what the author wrote, so a runtime\n * check would flag every field. With this marker present the compile\n * layer owns detection (it reads the authored props BEFORE injection);\n * with it absent (no bundler plugin) the runtime layer reads the\n * untouched `vnode.props` directly. Exactly one layer fires per\n * consumer, never both.\n *\n * Namespaced to avoid collision with any author-written modifier, like\n * `SSR_COMPONENT_HOST_MODIFIER` above.\n */\nexport const V_REGISTER_COMPILED_MODIFIER = 'attaformCompiled'\n\n/**\n * Type guard for a `RegisterValue`. Returns `true` when `val` looks\n * like the object returned from `form.register(path)`.\n *\n * ```ts\n * if (isRegisterValue(slotValue)) {\n * // slotValue.innerRef is now a Ref<unknown>\n * }\n * ```\n *\n * Useful when building wrapper components that accept either a\n * `RegisterValue` or a plain ref via the same prop.\n */\nexport function isRegisterValue<Value = unknown>(val: unknown): val is RegisterValue<Value> {\n if (typeof val !== 'object' || val === null) return false\n if (!('innerRef' in val)) return false\n if (!isRef(val.innerRef)) return false\n if (!('registerElement' in val)) return false\n if (typeof val.registerElement !== 'function') return false\n if (!('setValueWithInternalPath' in val)) return false\n if (typeof val.setValueWithInternalPath !== 'function') return false\n return true\n}\n\n/**\n * `true` while a deferred async transform is in flight at this path.\n * `beginTransform` flips it synchronously inside the assigner, so a\n * listener's post-write force-sync block reads it (right after the\n * assigner returns) to skip snapping the DOM back to stale storage —\n * the resolved value is painted in by the orchestrator's `syncDom`\n * once the run lands.\n */\nexport function isTransforming(value: unknown): boolean {\n return isRegisterValue(value) && (value as InternalRegisterValue).transforming\n}\n"],"names":["isRef"],"mappings":";;;;AAkBO,MAAM,iBAAA,GAAmC,MAAA,CAAO,GAAA,CAAI,+BAA+B;AAgBnF,MAAM,qBAAA,GAAuC,MAAA,CAAO,GAAA,CAAI,gCAAgC;AAYxF,MAAM,2BAAA,GAA8B;AAqBpC,MAAM,4BAAA,GAA+B;AAerC,SAAS,gBAAiC,GAAA,EAA2C;AAC1F,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,MAAM,OAAO,KAAA;AACpD,EAAA,IAAI,EAAE,UAAA,IAAc,GAAA,CAAA,EAAM,OAAO,KAAA;AACjC,EAAA,IAAI,CAACA,SAAA,CAAM,GAAA,CAAI,QAAQ,GAAG,OAAO,KAAA;AACjC,EAAA,IAAI,EAAE,iBAAA,IAAqB,GAAA,CAAA,EAAM,OAAO,KAAA;AACxC,EAAA,IAAI,OAAO,GAAA,CAAI,eAAA,KAAoB,UAAA,EAAY,OAAO,KAAA;AACtD,EAAA,IAAI,EAAE,0BAAA,IAA8B,GAAA,CAAA,EAAM,OAAO,KAAA;AACjD,EAAA,IAAI,OAAO,GAAA,CAAI,wBAAA,KAA6B,UAAA,EAAY,OAAO,KAAA;AAC/D,EAAA,OAAO,IAAA;AACT;AAUO,SAAS,eAAe,KAAA,EAAyB;AACtD,EAAA,OAAO,eAAA,CAAgB,KAAK,CAAA,IAAM,KAAA,CAAgC,YAAA;AACpE;;;;;;;;;"}