attaform 0.18.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +3 -0
  2. package/dist/chunks/devtools.cjs +1 -1
  3. package/dist/chunks/devtools.mjs +1 -1
  4. package/dist/chunks/indexeddb.cjs +1 -1
  5. package/dist/chunks/indexeddb.mjs +1 -1
  6. package/dist/chunks/local-storage.cjs +1 -1
  7. package/dist/chunks/local-storage.mjs +1 -1
  8. package/dist/chunks/session-storage.cjs +1 -1
  9. package/dist/chunks/session-storage.mjs +1 -1
  10. package/dist/index.cjs +4 -7
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +77 -110
  13. package/dist/index.d.mts +77 -110
  14. package/dist/index.d.ts +77 -110
  15. package/dist/index.mjs +5 -5
  16. package/dist/nuxt.d.cts +1 -1
  17. package/dist/nuxt.d.mts +1 -1
  18. package/dist/nuxt.d.ts +1 -1
  19. package/dist/runtime/components/AttaformDevtoolsPanel.vue +2 -2
  20. package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +1 -3
  21. package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +1 -3
  22. package/dist/runtime/plugins/attaform.cjs +2 -2
  23. package/dist/runtime/plugins/attaform.mjs +2 -2
  24. package/dist/shared/{attaform.CDmaxrt2.mjs → attaform.BKozEdTr.mjs} +305 -178
  25. package/dist/shared/attaform.BKozEdTr.mjs.map +1 -0
  26. package/dist/shared/{attaform.Bubm_slq.cjs → attaform.BM6YD9kZ.cjs} +212 -269
  27. package/dist/shared/attaform.BM6YD9kZ.cjs.map +1 -0
  28. package/dist/shared/{attaform.5UhpSVFI.cjs → attaform.BPxsYtTe.cjs} +2 -26
  29. package/dist/shared/attaform.BPxsYtTe.cjs.map +1 -0
  30. package/dist/shared/{attaform.BqK_L4gK.cjs → attaform.BPy-4qRx.cjs} +305 -180
  31. package/dist/shared/attaform.BPy-4qRx.cjs.map +1 -0
  32. package/dist/shared/attaform.BWgAFnsj.mjs +770 -0
  33. package/dist/shared/attaform.BWgAFnsj.mjs.map +1 -0
  34. package/dist/shared/{attaform.CGX1CNpz.d.ts → attaform.Bh3ACtts.d.ts} +152 -111
  35. package/dist/shared/{attaform.CXpzmj38.mjs → attaform.BupwXkj_.mjs} +213 -270
  36. package/dist/shared/attaform.BupwXkj_.mjs.map +1 -0
  37. package/dist/shared/{attaform.Dlk1jMuv.cjs → attaform.CIn4bMsD.cjs} +263 -799
  38. package/dist/shared/attaform.CIn4bMsD.cjs.map +1 -0
  39. package/dist/shared/{attaform.CZ-XtZt_.mjs → attaform.CKFbKFb6.mjs} +2265 -1509
  40. package/dist/shared/attaform.CKFbKFb6.mjs.map +1 -0
  41. package/dist/shared/{attaform.CuN7ZhBy.d.cts → attaform.D5-1XGQU.d.cts} +152 -111
  42. package/dist/shared/{attaform.-1GQTX2T.mjs → attaform.DEBvCjeH.mjs} +257 -793
  43. package/dist/shared/attaform.DEBvCjeH.mjs.map +1 -0
  44. package/dist/shared/{attaform.II89Pcf4.cjs → attaform.DL4CQ-oW.cjs} +2270 -1514
  45. package/dist/shared/attaform.DL4CQ-oW.cjs.map +1 -0
  46. package/dist/shared/{attaform.FnEwjhvX.d.ts → attaform.DSD85fHb.d.cts} +1 -19
  47. package/dist/shared/{attaform.CRmmNAYp.d.cts → attaform.DSD85fHb.d.mts} +1 -19
  48. package/dist/shared/{attaform.D9wuTGu9.d.mts → attaform.DSD85fHb.d.ts} +1 -19
  49. package/dist/shared/{attaform.B7rzpK1U.d.cts → attaform.DkA5J8NW.d.cts} +1 -17
  50. package/dist/shared/{attaform.B7rzpK1U.d.mts → attaform.DkA5J8NW.d.mts} +1 -17
  51. package/dist/shared/{attaform.B7rzpK1U.d.ts → attaform.DkA5J8NW.d.ts} +1 -17
  52. package/dist/shared/{attaform.B957T6NU.d.ts → attaform.Dl5kDY-A.d.ts} +1 -1
  53. package/dist/shared/attaform.Dmb6itxC.cjs +781 -0
  54. package/dist/shared/attaform.Dmb6itxC.cjs.map +1 -0
  55. package/dist/shared/{attaform.M-RanbyV.d.mts → attaform.DoKXru-a.d.mts} +1 -1
  56. package/dist/shared/attaform.DvA-CJJW.mjs +1876 -0
  57. package/dist/shared/attaform.DvA-CJJW.mjs.map +1 -0
  58. package/dist/shared/{attaform.D1gzu2GL.d.mts → attaform.EMzJcQci.d.mts} +152 -111
  59. package/dist/shared/attaform.EZG6fOFb.mjs +35 -0
  60. package/dist/shared/attaform.EZG6fOFb.mjs.map +1 -0
  61. package/dist/shared/{attaform.XDjA7sRz.d.cts → attaform.GbDo_lJi.d.cts} +1 -1
  62. package/dist/shared/{attaform.Ca5_6Ky-.d.mts → attaform.SfhU0OEY.d.cts} +499 -116
  63. package/dist/shared/{attaform.Ca5_6Ky-.d.cts → attaform.SfhU0OEY.d.mts} +499 -116
  64. package/dist/shared/{attaform.Ca5_6Ky-.d.ts → attaform.SfhU0OEY.d.ts} +499 -116
  65. package/dist/shared/attaform.jgzuNZVC.cjs +1882 -0
  66. package/dist/shared/attaform.jgzuNZVC.cjs.map +1 -0
  67. package/dist/transforms.cjs +2 -2
  68. package/dist/transforms.d.cts +22 -13
  69. package/dist/transforms.d.mts +22 -13
  70. package/dist/transforms.d.ts +22 -13
  71. package/dist/transforms.mjs +1 -1
  72. package/dist/vite.cjs +8 -7
  73. package/dist/vite.cjs.map +1 -1
  74. package/dist/vite.mjs +8 -7
  75. package/dist/vite.mjs.map +1 -1
  76. package/dist/zod-v3.cjs +3 -3
  77. package/dist/zod-v3.d.cts +32 -6
  78. package/dist/zod-v3.d.mts +32 -6
  79. package/dist/zod-v3.d.ts +32 -6
  80. package/dist/zod-v3.mjs +3 -3
  81. package/dist/zod-v4.cjs +3 -3
  82. package/dist/zod-v4.d.cts +12 -8
  83. package/dist/zod-v4.d.mts +12 -8
  84. package/dist/zod-v4.d.ts +12 -8
  85. package/dist/zod-v4.mjs +3 -3
  86. package/dist/zod.cjs +8 -8
  87. package/dist/zod.cjs.map +1 -1
  88. package/dist/zod.d.cts +6 -6
  89. package/dist/zod.d.mts +6 -6
  90. package/dist/zod.d.ts +6 -6
  91. package/dist/zod.mjs +6 -6
  92. package/package.json +2 -2
  93. package/dist/shared/attaform.-1GQTX2T.mjs.map +0 -1
  94. package/dist/shared/attaform.5UhpSVFI.cjs.map +0 -1
  95. package/dist/shared/attaform.BqK_L4gK.cjs.map +0 -1
  96. package/dist/shared/attaform.Bubm_slq.cjs.map +0 -1
  97. package/dist/shared/attaform.C8CyvYa_.cjs +0 -36
  98. package/dist/shared/attaform.C8CyvYa_.cjs.map +0 -1
  99. package/dist/shared/attaform.CDmaxrt2.mjs.map +0 -1
  100. package/dist/shared/attaform.CXpzmj38.mjs.map +0 -1
  101. package/dist/shared/attaform.CZ-XtZt_.mjs.map +0 -1
  102. package/dist/shared/attaform.D13GMFgK.mjs +0 -32
  103. package/dist/shared/attaform.D13GMFgK.mjs.map +0 -1
  104. package/dist/shared/attaform.DUHru0OF.cjs +0 -1600
  105. package/dist/shared/attaform.DUHru0OF.cjs.map +0 -1
  106. package/dist/shared/attaform.Df0tU0Ut.mjs +0 -1594
  107. package/dist/shared/attaform.Df0tU0Ut.mjs.map +0 -1
  108. package/dist/shared/attaform.Dl161U6E.mjs +0 -57
  109. package/dist/shared/attaform.Dl161U6E.mjs.map +0 -1
  110. package/dist/shared/attaform.Dlk1jMuv.cjs.map +0 -1
  111. package/dist/shared/attaform.II89Pcf4.cjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { s as FormKey, ae as SlimPrimitiveKind, C as CoercionEntry, g as CoercionRegistry, G as GenericForm, R as PathKey, Q as Path, am as ValidationError, A as AbstractSchema, ac as ShouldShowErrors, aq as WriteMeta, D as DeepPartial, ar as WriteShape, ak as ValidateOn, W as PersistOptInRegistry, a8 as Segment, f as AttaformDefaults, aj as UseFormReturnType, a6 as RegisterValue } from './attaform.Ca5_6Ky-.mjs';
1
+ import { t as FormKey, ae as SlimPrimitiveKind, C as CoercionEntry, g as CoercionRegistry, G as GenericForm, T as PathKey, S as Path, am as ValidationError, A as AbstractSchema, x as GetDisplayState, aq as WriteMeta, D as DeepPartial, ar as WriteShape, ak as ValidateOn, Y as PersistOptInRegistry, f as AttaformDefaults, aj as UseFormReturnType, a8 as RegisterValue } from './attaform.SfhU0OEY.mjs';
2
2
  import { Ref, ComputedRef, App, InjectionKey } from 'vue';
3
3
 
4
4
  /**
@@ -61,7 +61,7 @@ type FormStatus = {
61
61
  * entry carries the formKey + path tuple so consumers can route to the
62
62
  * offending field from a wizard-wide error summary.
63
63
  */
64
- type AggregateError = {
64
+ type WizardAggregateError = {
65
65
  readonly formKey: FormKey;
66
66
  readonly path: ReadonlyArray<string | number>;
67
67
  readonly message: string;
@@ -213,7 +213,7 @@ type WizardOnSubmit = (ctx: WizardSubmitContext) => void | Promise<void>;
213
213
  * validation and activation failures (`atta:activation-failed`). Sync
214
214
  * or async; the returned promise gates `wizard.submitting`.
215
215
  */
216
- type WizardOnError = (errors: readonly AggregateError[]) => void | Promise<void>;
216
+ type WizardOnError = (errors: readonly WizardAggregateError[]) => void | Promise<void>;
217
217
  /**
218
218
  * Options for `useWizard({ steps, … })`. `steps` is the only required
219
219
  * field; the rest are optional and default sensibly for the common
@@ -439,7 +439,7 @@ type UseWizardReturnType<S extends ReadonlyArray<StepSlot> = ReadonlyArray<StepS
439
439
  readonly count: number;
440
440
  readonly statuses: WizardStatusesProxy<Record<string, FormStatus>>;
441
441
  readonly allValues: Readonly<Record<FormKey, unknown>>;
442
- readonly allErrors: Readonly<Record<FormKey, readonly AggregateError[]>>;
442
+ readonly allErrors: Readonly<Record<FormKey, readonly WizardAggregateError[]>>;
443
443
  readonly progress: number;
444
444
  readonly canAdvance: boolean;
445
445
  readonly canGoBack: boolean;
@@ -517,19 +517,29 @@ declare const defaultCoercionRules: CoercionRegistry;
517
517
  */
518
518
  /**
519
519
  * Per-path field status. Replaced wholesale (not mutated in place) on
520
- * every change. Three semantic groups:
520
+ * every change. Five semantic groups:
521
521
  *
522
522
  * - `connected` — is a DOM element registered for this path?
523
523
  * - `focused` / `blurred` — DOM-state flags. `null` while no element
524
524
  * is connected (no DOM means the concepts don't apply); plain
525
525
  * booleans once connected, with the invariant `blurred === !focused`
526
526
  * enforced by `markFocused`.
527
- * - `touched` — interaction history, not DOM state. Always a plain
527
+ * - `touched` — focus/blur history, not DOM state. Always a plain
528
528
  * boolean: `false` at registration, sticky `true` after first blur,
529
529
  * cleared only by `form.reset()` / `form.resetField(path)`. Persists
530
530
  * across disconnects so v-if'd-away fields don't lose their touched
531
531
  * state on rehide (wizard "show review of touched fields" patterns
532
532
  * rely on this).
533
+ * - `interacted` — value-mutation history, not DOM state. Plain
534
+ * boolean: `false` at registration, sticky `true` once the user
535
+ * issues a value edit through the directive's input listeners
536
+ * (never on hydration, default seeding, or programmatic setValue);
537
+ * cleared with `touched` by `form.reset()` / `form.resetField(path)`.
538
+ * - `blurredAfterInteraction` — the first blur that follows a value
539
+ * edit (the field has been edited and then left). Plain boolean,
540
+ * sticky `true`. A tab-through blur with no prior edit does NOT set
541
+ * it (`interacted` is still false at that blur). Composes
542
+ * `interacted` with the departure; drives the default display gate.
533
543
  */
534
544
  type FieldRecord = {
535
545
  readonly path: Path;
@@ -538,6 +548,8 @@ type FieldRecord = {
538
548
  readonly focused: boolean | null;
539
549
  readonly blurred: boolean | null;
540
550
  readonly touched: boolean;
551
+ readonly interacted: boolean;
552
+ readonly blurredAfterInteraction: boolean;
541
553
  };
542
554
  /** Per-path DOM element tracking. Client-only. */
543
555
  type ElementRecord = {
@@ -649,15 +661,14 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
649
661
  */
650
662
  readonly ssr: boolean;
651
663
  /**
652
- * Resolved `shouldShowErrors` predicate driving `field.showErrors`
653
- * and `form.meta.showErrors`. Resolved once at construction via
654
- * `resolveShouldShowErrors(options.shouldShowErrors)` so the
655
- * field-state computeds don't repeat the boolean-vs-function
656
- * branch on every read. Boolean shorthand has already been lifted
657
- * to a constant predicate by the time it lands here; `undefined`
658
- * config falls through to `defaultShouldShowErrors`.
664
+ * Resolved `getDisplayState` predicate driving `field.displayState`,
665
+ * the `show*` booleans, and their `form.meta` rollups. Resolved once
666
+ * at construction via `resolveGetDisplayState(options.getDisplayState)`;
667
+ * `undefined` config falls through to `defaultDisplayState`. The
668
+ * field-state computeds read the resolved function directly on every
669
+ * read.
659
670
  */
660
- readonly shouldShowErrors: ShouldShowErrors;
671
+ readonly getDisplayState: GetDisplayState;
661
672
  readonly submitting: Ref<boolean>;
662
673
  readonly activeSubmissions: Ref<number>;
663
674
  readonly submissionAttempts: Ref<number>;
@@ -690,6 +701,15 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
690
701
  * plain-value forms. Read by `form.rehydrate()`.
691
702
  */
692
703
  readonly defaultValuesFactory: Ref<(() => unknown | Promise<unknown>) | undefined>;
704
+ /**
705
+ * `true` when this store carries an SSR prefetch queue (server path
706
+ * where `state.activate()` must enqueue intent before deciding
707
+ * whether to fire). The flag lets `buildFormApi` skip the lazy
708
+ * activation gate for forms with no factory AND no SSR prefetch —
709
+ * the common client-side case where `gated()` is otherwise pure
710
+ * reactive overhead on every public method call.
711
+ */
712
+ readonly hasSsrPrefetch: boolean;
693
713
  /**
694
714
  * `true` once the form's effective defaults have been applied —
695
715
  * sync `defaultValues` at construction, or async factory whose
@@ -786,6 +806,14 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
786
806
  * answer.
787
807
  */
788
808
  pathHasAsyncValidation(path: Path): boolean;
809
+ /**
810
+ * Precomputed-key shortcut for `pathHasAsyncValidation`. The
811
+ * canonical key is required and must correspond to `segments`; the
812
+ * helper skips the `canonicalizePath` round-trip so descendant-walk
813
+ * loops (whose Map iteration already yields the canonical key) can
814
+ * read the async-gate verdict without a per-leaf canonicalize.
815
+ */
816
+ pathHasAsyncValidationByKey(key: PathKey, segments: Path): boolean;
789
817
  /**
790
818
  * Per-path counter of in-flight field-level validation runs.
791
819
  * `field.validating` on `FieldState` mirrors
@@ -831,16 +859,16 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
831
859
  */
832
860
  setValueAtPath(path: Path, value: unknown, meta?: WriteMeta): boolean;
833
861
  getValueAtPath(path: Path): unknown;
834
- reset(nextDefaultValues?: DeepPartial<WriteShape<F>>): void;
835
- resetField(path: Path): void;
836
862
  /**
837
- * Wipe `path` (or the whole form when `path === ''`) to the
838
- * schema's "appropriate nullish value" the underlying type's
839
- * empty/falsy concrete, with `.default()` / `.catch()` wrappers
840
- * INTENTIONALLY skipped. Sugar for
841
- * `setValueAtPath(path, schema.getEmptyValueAtPath(path))`.
863
+ * Stable identity for the array element at `path`. An array element
864
+ * (numeric last segment) carries its allocated identity token,
865
+ * maintained by `array-identity.ts` across structural mutations.
866
+ * Empty for any non-array-element path: a record entry, a
867
+ * fixed-object field, a container, or the root. Backs `FieldState.key`.
842
868
  */
843
- clear(path: Path): boolean;
869
+ arrayElementKey(path: Path): string;
870
+ reset(nextDefaultValues?: DeepPartial<WriteShape<F>>): void;
871
+ resetField(path: Path): void;
844
872
  setSchemaErrorsForPath(path: Path, errors: ValidationError[]): void;
845
873
  setAllSchemaErrors(errors: readonly ValidationError[]): void;
846
874
  clearSchemaErrors(path?: Path): void;
@@ -893,7 +921,12 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
893
921
  markFocused(path: Path, focused: boolean, meta?: {
894
922
  readonly instance?: WriteMeta['instance'];
895
923
  }): void;
896
- markTouched(path: Path): void;
924
+ /**
925
+ * Flip `interacted: true` on a leaf — the sticky value-mutation flag.
926
+ * Driven by the directive's input listeners (via the RegisterValue's
927
+ * `markInteracted`); idempotent, never set by programmatic writes.
928
+ */
929
+ markInteracted(path: Path): void;
897
930
  /**
898
931
  * Walk every active-variant leaf under `segments` and flip
899
932
  * `touched: true`. Powers `form.touch(path?)`. Idempotent;
@@ -920,6 +953,22 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
920
953
  * isn't exposed to consumers.
921
954
  */
922
955
  isPristineAtPath(path: Path): boolean;
956
+ /**
957
+ * Precomputed-key shortcut for `isPristineAtPath`. The canonical
958
+ * key is required and must correspond to `segments`; the helper
959
+ * skips the `canonicalizePath` round-trip so descendant-walk loops
960
+ * (whose Map iteration already yields the canonical key) can read
961
+ * the pristine verdict without a per-leaf canonicalize.
962
+ */
963
+ isPristineAtPathByKey(key: PathKey, segments: Path): boolean;
964
+ /**
965
+ * Whether any tracked array under `path` has changed shape — a reorder,
966
+ * insert, or removal — relative to its construction/reset baseline. The
967
+ * structural half of `dirty`: per-element baselines travel with their
968
+ * element across a mutation, so a positional value comparison alone can
969
+ * no longer see the shape change.
970
+ */
971
+ hasStructuralChangeUnder(path: Path): boolean;
923
972
  getFieldRecord(path: Path): FieldRecord | undefined;
924
973
  getOriginalAtPath(path: Path): unknown;
925
974
  /**
@@ -1053,7 +1102,7 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
1053
1102
  * Resolved sensitive-path predicate for THIS form. Honors the
1054
1103
  * cascade (`useForm({ sensitiveNames })` > global default >
1055
1104
  * library `DEFAULT_SENSITIVE_NAMES`). Used by:
1056
- * - persistence enforcement (`enforceSensitiveCheck` at write time);
1105
+ * - the persistence opt-in gate (`allowSensitivePersist`);
1057
1106
  * - the multi-tab sync module (outbound strip + inbound reject);
1058
1107
  * - DevTools edit rejection;
1059
1108
  * - any future surface that needs to flag "this path holds
@@ -1064,14 +1113,6 @@ type FormStore<F extends GenericForm, G extends GenericForm = F> = {
1064
1113
  * resolved-config surface.
1065
1114
  */
1066
1115
  readonly isSensitivePath: (path: Path | PathKey | string) => boolean;
1067
- /**
1068
- * Single-segment variant of `isSensitivePath`. Used by the DevTools
1069
- * redact walk to short-circuit whole subtrees the moment any
1070
- * ancestor segment matches — saving an O(leaves × ancestors) regex
1071
- * sweep per timeline event. Resolved from the same `sensitiveNames`
1072
- * cascade as `isSensitivePath`.
1073
- */
1074
- readonly segmentMatchesSensitive: (segment: Segment) => boolean;
1075
1116
  /**
1076
1117
  * Canonical path keys explicitly opted OUT of multi-tab sync by
1077
1118
  * `register(path, { multiTab: false })`. The sync module's outbound
@@ -1395,6 +1436,83 @@ type InjectFormInput = {
1395
1436
  */
1396
1437
  declare function injectForm<Form extends GenericForm, GetValueFormType extends GenericForm = Form>(input?: FormKey | InjectFormInput): UseFormReturnType<Form, GetValueFormType> | null;
1397
1438
 
1439
+ /**
1440
+ * Re-bind a parent's `v-register` onto an inner native element. Use
1441
+ * inside a component that wraps a single form field whose root is
1442
+ * NOT the input itself (e.g. a labelled-row that renders `<label>`
1443
+ * around the input).
1444
+ *
1445
+ * ```vue
1446
+ * <!-- Parent -->
1447
+ * <MyInput v-register="form.register('email')" />
1448
+ *
1449
+ * <!-- MyInput.vue -->
1450
+ * <script setup lang="ts">
1451
+ * import { useRegister } from 'attaform'
1452
+ * const rv = useRegister()
1453
+ * // rv.path / rv.segments / rv.formKey / rv.formInstanceId / rv.innerRef
1454
+ * // are all reachable directly — no `.value` unwrap.
1455
+ * </script>
1456
+ *
1457
+ * <template>
1458
+ * <label class="field">
1459
+ * <span>Email</span>
1460
+ * <input v-register="rv" />
1461
+ * </label>
1462
+ * </template>
1463
+ * ```
1464
+ *
1465
+ * Returns a hybrid Proxy: it answers `__v_isRef` / `.value` like a
1466
+ * Vue `Ref<RegisterValue | undefined>` (so templates auto-unwrap
1467
+ * correctly and `v-register="rv"` feeds the underlying RV to the
1468
+ * directive — preserving the directive's path-migration diff across
1469
+ * renders), AND every other property read pierces to the captured
1470
+ * RV's field (so `rv.path` works directly in script setup). Reads
1471
+ * inside reactive scopes (`computed` / `watchEffect`) track the
1472
+ * underlying `shallowRef`, so `rv.path` re-runs when the parent
1473
+ * rebinds to a different path.
1474
+ *
1475
+ * Unbound state: when the parent didn't pass `v-register`, every
1476
+ * piercing read returns `undefined` at runtime, and the return type
1477
+ * surfaces this honestly as `UseRegisterReturn<V> | undefined`.
1478
+ * Consumers defend with optional chaining (`rv?.formKey`,
1479
+ * `rv?.segments`); the directive accepts `undefined` peacefully (its
1480
+ * binding value type is already `RegisterValue<V> | undefined`), so
1481
+ * `v-register="rv"` works whether or not a parent has bound. The
1482
+ * composable's `onMounted` warn fires once per instance to surface
1483
+ * the misuse case at runtime.
1484
+ *
1485
+ * Diagnostic: in dev mode, a single `console.warn` fires per instance
1486
+ * at `onMounted` if the captured value is still `undefined` — by then
1487
+ * the parent has had its full mount lifecycle to bind, so a missing
1488
+ * binding is conclusive misuse. The warn does NOT fire on every read
1489
+ * of the proxy, and is intentionally silent under SSR
1490
+ * (`renderToString` skips `onMounted`); the CSR hydration pass
1491
+ * surfaces the same diagnostic without double-counting through Nuxt's
1492
+ * `dev:ssr-logs` channel.
1493
+ *
1494
+ * When the wrapper's root IS the input itself, Vue's attribute
1495
+ * fallthrough handles the binding and `useRegister` is unnecessary.
1496
+ * For wrappers that bind multiple fields (compound forms), use
1497
+ * `injectForm<Form>(key?)` and call `ctx.register(...)` directly.
1498
+ */
1499
+
1500
+ /**
1501
+ * Return type of `useRegister()`. Hybrid of `RegisterValue<V>` (so
1502
+ * `rv.path` / `rv.segments` / `rv.formKey` etc. work directly in
1503
+ * script setup) and `Ref<RegisterValue<V> | undefined>` (so Vue's
1504
+ * template auto-unwrap surfaces the underlying RV to `v-register`
1505
+ * and the directive's path-migration diff sees the real RV across
1506
+ * renders).
1507
+ *
1508
+ * The two surfaces don't clash at the type level: `RegisterValue`
1509
+ * doesn't carry a `value` field, and `Ref<T>`'s `value: T` becomes
1510
+ * the hybrid's only `.value`. Older code that read `rv.value?.path`
1511
+ * keeps working; new code can write `rv.path` directly.
1512
+ */
1513
+ type UseRegisterReturn<V = unknown> = RegisterValue<V> & Ref<RegisterValue<V> | undefined>;
1514
+ declare function useRegister<V = unknown>(): UseRegisterReturn<V> | undefined;
1515
+
1398
1516
  /**
1399
1517
  * Multistep-form orchestrator built around an ordered list of step slots.
1400
1518
  * Each slot resolves to a participating form: an existing `useForm`
@@ -1510,83 +1628,6 @@ declare function injectWizard(input?: string | InjectWizardInput): UseWizardRetu
1510
1628
  */
1511
1629
  declare function lazy<Ctx = WizardCtx>(resolve: (ctx: Ctx) => AnyForm | string | undefined): LazyMarker<Ctx>;
1512
1630
 
1513
- /**
1514
- * Re-bind a parent's `v-register` onto an inner native element. Use
1515
- * inside a component that wraps a single form field whose root is
1516
- * NOT the input itself (e.g. a labelled-row that renders `<label>`
1517
- * around the input).
1518
- *
1519
- * ```vue
1520
- * <!-- Parent -->
1521
- * <MyInput v-register="form.register('email')" />
1522
- *
1523
- * <!-- MyInput.vue -->
1524
- * <script setup lang="ts">
1525
- * import { useRegister } from 'attaform'
1526
- * const rv = useRegister()
1527
- * // rv.path / rv.segments / rv.formKey / rv.formInstanceId / rv.innerRef
1528
- * // are all reachable directly — no `.value` unwrap.
1529
- * </script>
1530
- *
1531
- * <template>
1532
- * <label class="field">
1533
- * <span>Email</span>
1534
- * <input v-register="rv" />
1535
- * </label>
1536
- * </template>
1537
- * ```
1538
- *
1539
- * Returns a hybrid Proxy: it answers `__v_isRef` / `.value` like a
1540
- * Vue `Ref<RegisterValue | undefined>` (so templates auto-unwrap
1541
- * correctly and `v-register="rv"` feeds the underlying RV to the
1542
- * directive — preserving the directive's path-migration diff across
1543
- * renders), AND every other property read pierces to the captured
1544
- * RV's field (so `rv.path` works directly in script setup). Reads
1545
- * inside reactive scopes (`computed` / `watchEffect`) track the
1546
- * underlying `shallowRef`, so `rv.path` re-runs when the parent
1547
- * rebinds to a different path.
1548
- *
1549
- * Unbound state: when the parent didn't pass `v-register`, every
1550
- * piercing read returns `undefined` at runtime, and the return type
1551
- * surfaces this honestly as `UseRegisterReturn<V> | undefined`.
1552
- * Consumers defend with optional chaining (`rv?.formKey`,
1553
- * `rv?.segments`); the directive accepts `undefined` peacefully (its
1554
- * binding value type is already `RegisterValue<V> | undefined`), so
1555
- * `v-register="rv"` works whether or not a parent has bound. The
1556
- * composable's `onMounted` warn fires once per instance to surface
1557
- * the misuse case at runtime.
1558
- *
1559
- * Diagnostic: in dev mode, a single `console.warn` fires per instance
1560
- * at `onMounted` if the captured value is still `undefined` — by then
1561
- * the parent has had its full mount lifecycle to bind, so a missing
1562
- * binding is conclusive misuse. The warn does NOT fire on every read
1563
- * of the proxy, and is intentionally silent under SSR
1564
- * (`renderToString` skips `onMounted`); the CSR hydration pass
1565
- * surfaces the same diagnostic without double-counting through Nuxt's
1566
- * `dev:ssr-logs` channel.
1567
- *
1568
- * When the wrapper's root IS the input itself, Vue's attribute
1569
- * fallthrough handles the binding and `useRegister` is unnecessary.
1570
- * For wrappers that bind multiple fields (compound forms), use
1571
- * `injectForm<Form>(key?)` and call `ctx.register(...)` directly.
1572
- */
1573
-
1574
- /**
1575
- * Return type of `useRegister()`. Hybrid of `RegisterValue<V>` (so
1576
- * `rv.path` / `rv.segments` / `rv.formKey` etc. work directly in
1577
- * script setup) and `Ref<RegisterValue<V> | undefined>` (so Vue's
1578
- * template auto-unwrap surfaces the underlying RV to `v-register`
1579
- * and the directive's path-migration diff sees the real RV across
1580
- * renders).
1581
- *
1582
- * The two surfaces don't clash at the type level: `RegisterValue`
1583
- * doesn't carry a `value` field, and `Ref<T>`'s `value: T` becomes
1584
- * the hybrid's only `.value`. Older code that read `rv.value?.path`
1585
- * keeps working; new code can write `rv.path` directly.
1586
- */
1587
- type UseRegisterReturn<V = unknown> = RegisterValue<V> & Ref<RegisterValue<V> | undefined>;
1588
- declare function useRegister<V = unknown>(): UseRegisterReturn<V> | undefined;
1589
-
1590
1631
  /**
1591
1632
  * Stable identifiers for library-emitted `ValidationError` codes.
1592
1633
  *
@@ -1647,5 +1688,5 @@ declare const AttaformErrorCode: {
1647
1688
  };
1648
1689
  type AttaformErrorCode = (typeof AttaformErrorCode)[keyof typeof AttaformErrorCode];
1649
1690
 
1650
- export { AttaformErrorCode as b, createRegistry as p, defaultCoercionRules as q, defineCoercion as r, getRegistryFromApp as s, injectForm as t, injectWizard as u, kAttaformRegistry as v, lazy as w, useRegister as x, useRegistry as y, useWizard as z };
1651
- export type { AggregateError as A, CompiledStep as C, FormStatus as F, InjectWizardInput as I, LazyMarker as L, SSRDetectOptions as S, UseRegisterReturn as U, WizardCtx as W, AnyForm as a, AttaformRegistry as c, SerializedFormData as d, StepSlot as e, UseWizardReturnType as f, WizardCtxForm as g, WizardOnError as h, WizardOnSubmit as i, WizardOptions as j, WizardPersistFn as k, WizardRestoreFn as l, WizardRestoreState as m, WizardStatusesProxy as n, WizardSubmitContext as o };
1691
+ export { AttaformErrorCode as a, createRegistry as p, defaultCoercionRules as q, defineCoercion as r, getRegistryFromApp as s, injectForm as t, injectWizard as u, kAttaformRegistry as v, lazy as w, useRegister as x, useRegistry as y, useWizard as z };
1692
+ export type { AnyForm as A, CompiledStep as C, FormStatus as F, InjectWizardInput as I, LazyMarker as L, SSRDetectOptions as S, UseRegisterReturn as U, WizardAggregateError as W, AttaformRegistry as b, SerializedFormData as c, StepSlot as d, UseWizardReturnType as e, WizardCtx as f, WizardCtxForm as g, WizardOnError as h, WizardOnSubmit as i, WizardOptions as j, WizardPersistFn as k, WizardRestoreFn as l, WizardRestoreState as m, WizardStatusesProxy as n, WizardSubmitContext as o };
@@ -0,0 +1,35 @@
1
+ import { r as getRegistryFromApp, C as pathKeyToDotted } from './attaform.BKozEdTr.mjs';
2
+
3
+ function renderAttaformState(app) {
4
+ const registry = getRegistryFromApp(app);
5
+ const forms = [];
6
+ for (const [key, state] of registry.forms) {
7
+ const transientList = [];
8
+ for (const pk of state.blankPaths) {
9
+ const d = pathKeyToDotted(pk);
10
+ if (d !== null) transientList.push(d);
11
+ }
12
+ forms.push([
13
+ key,
14
+ {
15
+ form: state.form.value,
16
+ schemaErrors: Array.from(state.schemaErrors.entries()),
17
+ userErrors: Array.from(state.userErrors.entries()),
18
+ fields: Array.from(state.fields.entries()),
19
+ ...transientList.length > 0 ? { blankPaths: transientList } : {}
20
+ }
21
+ ]);
22
+ }
23
+ return { forms };
24
+ }
25
+ function hydrateAttaformState(app, payload) {
26
+ const registry = getRegistryFromApp(app);
27
+ for (const [key, data] of payload.forms) {
28
+ registry.pendingHydration.set(key, data);
29
+ }
30
+ }
31
+
32
+ const DEVTOOLS_WINDOW_KEY = "__attaform_devtools__";
33
+
34
+ export { DEVTOOLS_WINDOW_KEY as D, hydrateAttaformState as h, renderAttaformState as r };
35
+ //# sourceMappingURL=attaform.EZG6fOFb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attaform.EZG6fOFb.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,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { G as GenericForm, p as FlatPath, y as NestedType, s as FormKey, ai as UseFormConfiguration, A as AbstractSchema, j as DefaultValuesInput, al as ValidateOnConfig, aj as UseFormReturnType } from './attaform.Ca5_6Ky-.cjs';
2
+ import { G as GenericForm, q as FlatPath, B as NestedType, t as FormKey, ai as UseFormConfiguration, A as AbstractSchema, j as DefaultValuesInput, al as ValidateOnConfig, aj as UseFormReturnType } from './attaform.SfhU0OEY.cjs';
3
3
 
4
4
  /**
5
5
  * The shape `form.values.<key>` returns at runtime.