attaform 0.17.2 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +77 -36
  2. package/dist/chunks/devtools.cjs +10 -37
  3. package/dist/chunks/devtools.cjs.map +1 -1
  4. package/dist/chunks/devtools.mjs +10 -37
  5. package/dist/chunks/devtools.mjs.map +1 -1
  6. package/dist/chunks/indexeddb.cjs +4 -4
  7. package/dist/chunks/indexeddb.cjs.map +1 -1
  8. package/dist/chunks/indexeddb.mjs +1 -1
  9. package/dist/chunks/local-storage.cjs +2 -2
  10. package/dist/chunks/local-storage.cjs.map +1 -1
  11. package/dist/chunks/local-storage.mjs +1 -1
  12. package/dist/chunks/session-storage.cjs +2 -2
  13. package/dist/chunks/session-storage.cjs.map +1 -1
  14. package/dist/chunks/session-storage.mjs +1 -1
  15. package/dist/index.cjs +42 -37
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +159 -196
  18. package/dist/index.d.mts +159 -196
  19. package/dist/index.d.ts +159 -196
  20. package/dist/index.mjs +5 -7
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/nuxt.cjs +31 -40
  23. package/dist/nuxt.cjs.map +1 -1
  24. package/dist/nuxt.d.cts +8 -1
  25. package/dist/nuxt.d.mts +8 -1
  26. package/dist/nuxt.d.ts +8 -1
  27. package/dist/nuxt.mjs +32 -41
  28. package/dist/nuxt.mjs.map +1 -1
  29. package/dist/runtime/components/AttaformDevtoolsPanel.d.vue.ts +7 -0
  30. package/dist/runtime/components/AttaformDevtoolsPanel.vue +453 -0
  31. package/dist/runtime/components/AttaformDevtoolsPanel.vue.d.ts +7 -0
  32. package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +37 -0
  33. package/dist/runtime/components/DevtoolsValueTree.vue +192 -0
  34. package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +37 -0
  35. package/dist/runtime/plugins/attaform.cjs +17 -6
  36. package/dist/runtime/plugins/attaform.cjs.map +1 -1
  37. package/dist/runtime/plugins/attaform.mjs +15 -4
  38. package/dist/runtime/plugins/attaform.mjs.map +1 -1
  39. package/dist/shared/{attaform.C0iFnTN0.d.ts → attaform.2b7M2mww.d.mts} +57 -23
  40. package/dist/shared/attaform.5UhpSVFI.cjs +63 -0
  41. package/dist/shared/attaform.5UhpSVFI.cjs.map +1 -0
  42. package/dist/shared/attaform.BDdFdjeX.mjs +57 -0
  43. package/dist/shared/attaform.BDdFdjeX.mjs.map +1 -0
  44. package/dist/shared/{attaform.Dee2rU1P.cjs → attaform.BqK_L4gK.cjs} +310 -24
  45. package/dist/shared/attaform.BqK_L4gK.cjs.map +1 -0
  46. package/dist/shared/attaform.Bubm_slq.cjs.map +1 -1
  47. package/dist/shared/attaform.CXpzmj38.mjs.map +1 -1
  48. package/dist/shared/{attaform.Drt6fivF.mjs → attaform.CtNUB9nf.mjs} +74 -76
  49. package/dist/shared/attaform.CtNUB9nf.mjs.map +1 -0
  50. package/dist/shared/{attaform.C5MH4lNh.d.mts → attaform.DF8wo-ry.d.ts} +4 -4
  51. package/dist/shared/attaform.DK9aj0N8.d.ts +1651 -0
  52. package/dist/shared/{attaform.BPRHR3Zs.cjs → attaform.DUHru0OF.cjs} +83 -85
  53. package/dist/shared/attaform.DUHru0OF.cjs.map +1 -0
  54. package/dist/shared/{attaform.C6lbmMUe.d.ts → attaform.DVLB6CAn.d.mts} +4 -4
  55. package/dist/shared/{attaform.C_5aB6EQ.d.ts → attaform.Dj9mwbaV.d.cts} +756 -148
  56. package/dist/shared/{attaform.C_5aB6EQ.d.mts → attaform.Dj9mwbaV.d.mts} +756 -148
  57. package/dist/shared/{attaform.C_5aB6EQ.d.cts → attaform.Dj9mwbaV.d.ts} +756 -148
  58. package/dist/shared/{attaform.BV40t5y2.cjs → attaform.Dlk1jMuv.cjs} +245 -108
  59. package/dist/shared/attaform.Dlk1jMuv.cjs.map +1 -0
  60. package/dist/shared/attaform.DoSuaKMd.d.cts +1651 -0
  61. package/dist/shared/{attaform.B3ZaPIzS.mjs → attaform.DsC3rZHG.mjs} +1804 -219
  62. package/dist/shared/attaform.DsC3rZHG.mjs.map +1 -0
  63. package/dist/shared/{attaform.Cer8JO_P.cjs → attaform.II89Pcf4.cjs} +1860 -272
  64. package/dist/shared/attaform.II89Pcf4.cjs.map +1 -0
  65. package/dist/shared/{attaform.CHorcsIU.d.cts → attaform.M33WKVV4.d.cts} +57 -23
  66. package/dist/shared/{attaform.CIEQgJnM.mjs → attaform.Xhg0AYNa.mjs} +300 -26
  67. package/dist/shared/attaform.Xhg0AYNa.mjs.map +1 -0
  68. package/dist/shared/{attaform.CpERWz3u.mjs → attaform.Xt0A3QUd.mjs} +232 -95
  69. package/dist/shared/attaform.Xt0A3QUd.mjs.map +1 -0
  70. package/dist/shared/attaform.iTqxvl-P.d.mts +1651 -0
  71. package/dist/shared/{attaform.CuE-bS1C.d.mts → attaform.tsNFcEW7.d.ts} +57 -23
  72. package/dist/shared/{attaform.DtMN-MAm.d.cts → attaform.tts_OM7j.d.cts} +4 -4
  73. package/dist/vite.cjs +288 -2
  74. package/dist/vite.cjs.map +1 -1
  75. package/dist/vite.mjs +288 -2
  76. package/dist/vite.mjs.map +1 -1
  77. package/dist/zod-v3.cjs +11 -8
  78. package/dist/zod-v3.cjs.map +1 -1
  79. package/dist/zod-v3.d.cts +6 -6
  80. package/dist/zod-v3.d.mts +6 -6
  81. package/dist/zod-v3.d.ts +6 -6
  82. package/dist/zod-v3.mjs +3 -3
  83. package/dist/zod-v4.cjs +11 -8
  84. package/dist/zod-v4.cjs.map +1 -1
  85. package/dist/zod-v4.d.cts +5 -5
  86. package/dist/zod-v4.d.mts +5 -5
  87. package/dist/zod-v4.d.ts +5 -5
  88. package/dist/zod-v4.mjs +3 -3
  89. package/dist/zod.cjs +15 -16
  90. package/dist/zod.cjs.map +1 -1
  91. package/dist/zod.d.cts +127 -40
  92. package/dist/zod.d.mts +127 -40
  93. package/dist/zod.d.ts +127 -40
  94. package/dist/zod.mjs +7 -11
  95. package/dist/zod.mjs.map +1 -1
  96. package/package.json +18 -7
  97. package/dist/shared/attaform.B1jvxsOF.d.mts +0 -156
  98. package/dist/shared/attaform.B3ZaPIzS.mjs.map +0 -1
  99. package/dist/shared/attaform.BBM2muQ9.cjs +0 -101
  100. package/dist/shared/attaform.BBM2muQ9.cjs.map +0 -1
  101. package/dist/shared/attaform.BPRHR3Zs.cjs.map +0 -1
  102. package/dist/shared/attaform.BV40t5y2.cjs.map +0 -1
  103. package/dist/shared/attaform.C6qzEdIM.d.cts +0 -156
  104. package/dist/shared/attaform.C8LVFVVe.cjs +0 -32
  105. package/dist/shared/attaform.C8LVFVVe.cjs.map +0 -1
  106. package/dist/shared/attaform.CIEQgJnM.mjs.map +0 -1
  107. package/dist/shared/attaform.CTwNcpLE.d.ts +0 -156
  108. package/dist/shared/attaform.Cer8JO_P.cjs.map +0 -1
  109. package/dist/shared/attaform.CpERWz3u.mjs.map +0 -1
  110. package/dist/shared/attaform.Dee2rU1P.cjs.map +0 -1
  111. package/dist/shared/attaform.Drt6fivF.mjs.map +0 -1
  112. package/dist/shared/attaform.Vo-Kft0t.mjs +0 -29
  113. package/dist/shared/attaform.Vo-Kft0t.mjs.map +0 -1
  114. package/dist/shared/attaform.h1sq3BFu.mjs +0 -92
  115. package/dist/shared/attaform.h1sq3BFu.mjs.map +0 -1
@@ -1,6 +1,5 @@
1
- import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, reactive, watch, markRaw, toRaw, shallowRef, getCurrentInstance, provide, useId, inject } from 'vue';
2
- import { _ as __DEV__, e as SubmitErrorHandlerError, A as AnonPersistError, l as captureUserCallSite, m as enforceSensitiveCheck, s as segmentMatchesSensitive, n as isSensitivePath, o as createPersistOptInRegistry, b as InvalidUseFormConfigError, p as ensureAttaformInstalled, j as useRegistry, q as kFormContext, r as kFormInstanceId, d as ReservedFormKeyError, t as createIsSensitivePath, w as createSegmentMatchesSensitive } from './attaform.CIEQgJnM.mjs';
3
- import { c as canonicalizePath, s as segmentsForPathKey, i as isPathPrefix, F as FORM_ERRORS_PATH_KEY, R as ROOT_PATH, b as FORM_ERRORS_PATH } from './attaform.h1sq3BFu.mjs';
1
+ import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, reactive, watch, markRaw, toRaw, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, effectScope, nextTick } from 'vue';
2
+ import { a as canonicalizePath, s as segmentsForPathKey, m as isPathPrefix, F as FORM_ERRORS_PATH_KEY, _ as __DEV__, r as pathKeyToDotted, i as SubmitErrorHandlerError, A as AnonPersistError, t as captureUserCallSite, R as ROOT_PATH, w as enforceSensitiveCheck, x as FORM_ERRORS_PATH, y as coerceToPathKey, z as segmentMatchesSensitive, B as isSensitivePath, C as createPersistOptInRegistry, d as InvalidUseFormConfigError, E as ensureAttaformInstalled, q as useRegistry, G as kFormContext, H as kFormInstanceId, h as ReservedFormKeyError, J as createIsSensitivePath, K as createSegmentMatchesSensitive, k as kAttaformWizardActiveStepResolver, L as kAttaformAncestorWizard } from './attaform.Xhg0AYNa.mjs';
4
3
 
5
4
  const NOT_FOUND = Symbol("NOT_FOUND");
6
5
  function descendStep(value, segment) {
@@ -121,7 +120,12 @@ function mergeStructural(schema, path, consumer, defaultValue = schema.getDefaul
121
120
  return mergeStructuralImpl(schema, scratch, consumer, defaultValue);
122
121
  }
123
122
  function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
124
- if (consumer === void 0) return defaultValue;
123
+ if (consumer === void 0) {
124
+ if (schema.getSlimPrimitiveTypesAtPath?.(scratch).has("undefined") === true) {
125
+ return void 0;
126
+ }
127
+ return defaultValue;
128
+ }
125
129
  if (consumer === null) return null;
126
130
  if (Array.isArray(consumer)) {
127
131
  const shape = resolveArrayShape(schema, scratch);
@@ -161,7 +165,7 @@ function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
161
165
  let mutated = false;
162
166
  const out = { ...consumer };
163
167
  for (const key of Object.keys(defaultValue)) {
164
- if (!(key in consumer) || consumer[key] === void 0) {
168
+ if (!(key in consumer)) {
165
169
  const defAtKey = defaultValue[key];
166
170
  scratch.push(key);
167
171
  const filled = mergeStructuralImpl(schema, scratch, void 0, defAtKey);
@@ -173,10 +177,12 @@ function mergeStructuralImpl(schema, scratch, consumer, defaultValue) {
173
177
  }
174
178
  }
175
179
  for (const key of Object.keys(consumer)) {
180
+ const cVal = consumer[key];
181
+ if (cVal === void 0) continue;
176
182
  scratch.push(key);
177
- const merged = mergeStructuralImpl(schema, scratch, consumer[key], defaultValue[key]);
183
+ const merged = mergeStructuralImpl(schema, scratch, cVal, defaultValue[key]);
178
184
  scratch.pop();
179
- if (merged !== consumer[key]) {
185
+ if (merged !== cVal) {
180
186
  out[key] = merged;
181
187
  mutated = true;
182
188
  }
@@ -504,7 +510,7 @@ function buildLeafFieldStateBase(state, segments, key) {
504
510
  dirty: !pristine,
505
511
  focused: record?.focused ?? null,
506
512
  blurred: record?.blurred ?? null,
507
- touched: record?.touched ?? null,
513
+ touched: record?.touched ?? false,
508
514
  connected: record?.connected ?? false,
509
515
  element: firstElement,
510
516
  elements: elementsArr,
@@ -646,10 +652,12 @@ function buildSurfaceProxy(opts) {
646
652
  if (opts.leafKeys !== void 0) return leafViewProxyAt(segs);
647
653
  return opts.resolveLeaf(segs);
648
654
  }
655
+ if (opts.isTerminalAt?.(segs) === true) return opts.resolveLeaf(segs);
649
656
  return containerProxyAt(segs);
650
657
  }
651
658
  function containerProxyAt(segments) {
652
- const cacheKey = JSON.stringify(segments);
659
+ const isArrayLike = opts.isArrayContainer?.(segments) === true;
660
+ const cacheKey = `${JSON.stringify(segments)}+${isArrayLike ? "A" : "O"}`;
653
661
  const existing = containerCache.get(cacheKey);
654
662
  if (existing !== void 0) return existing;
655
663
  const snapshotContainer = () => opts.materializeContainer === void 0 ? {} : opts.materializeContainer(segments);
@@ -659,7 +667,7 @@ function buildSurfaceProxy(opts) {
659
667
  return this;
660
668
  }
661
669
  const containerToPrimitive = (hint) => hint === "number" ? NaN : containerToString();
662
- const target = (() => {
670
+ const target = isArrayLike ? [] : (() => {
663
671
  });
664
672
  const proxy = new Proxy(target, {
665
673
  apply(_, __, args) {
@@ -674,7 +682,14 @@ function buildSurfaceProxy(opts) {
674
682
  return Reflect.get(target, key);
675
683
  }
676
684
  if (typeof key !== "string") return void 0;
685
+ if (key === "__v_skip") return true;
686
+ if (key === "__v_isReactive" || key === "__v_isReadonly" || key === "__v_isShallow" || key === "__v_isRef" || key === "__v_raw") {
687
+ return void 0;
688
+ }
677
689
  if (key === "toJSON") return containerToJSON;
690
+ if (key === "length" && (isArrayLike || opts.isArrayContainer?.(segments) === true)) {
691
+ return opts.containerOwnKeys === void 0 ? 0 : opts.containerOwnKeys(segments).length;
692
+ }
678
693
  const childSegs = [...segments, keyToSegment(key)];
679
694
  if (key === "toString" || key === "valueOf") {
680
695
  if (!schemaHasPath(childSegs)) {
@@ -687,11 +702,38 @@ function buildSurfaceProxy(opts) {
687
702
  if (typeof key === "symbol") return Reflect.has(target, key);
688
703
  return true;
689
704
  },
690
- // Containers are descend-only `JSON.stringify(form.fields.address)`
691
- // returns `{}` (no leaf keys to enumerate). Consumers who want
692
- // structural data use `form.values.<container>` instead.
693
- ownKeys: () => [],
694
- getOwnPropertyDescriptor: () => void 0,
705
+ // Live enumeration. When the surface provides `containerOwnKeys`,
706
+ // `Object.keys(proxy)` / `Object.entries(proxy)` /
707
+ // `v-for="(child, key) in proxy"` reflect whichever keys the
708
+ // underlying form data currently holds at this path: array
709
+ // indices (`'0'`, `'1'`, …) when the live value is an array,
710
+ // object keys when it's a record. `getOwnPropertyDescriptor`
711
+ // descends to the per-key sub-proxy so iteration yields the
712
+ // same objects dot-access would. Without `containerOwnKeys`,
713
+ // the container stays non-enumerable for callers that don't
714
+ // need iteration — `JSON.stringify` still serialises through
715
+ // the `toJSON` trap above either way.
716
+ ownKeys: () => {
717
+ const liveKeys = opts.containerOwnKeys === void 0 ? [] : opts.containerOwnKeys(segments);
718
+ if (isArrayLike) return ["length", ...liveKeys];
719
+ return [...liveKeys];
720
+ },
721
+ getOwnPropertyDescriptor(_, key) {
722
+ if (typeof key !== "string") return void 0;
723
+ if (isArrayLike && key === "length") {
724
+ const length = opts.containerOwnKeys === void 0 ? 0 : opts.containerOwnKeys(segments).length;
725
+ return { configurable: false, enumerable: false, value: length, writable: true };
726
+ }
727
+ if (opts.containerOwnKeys === void 0) return void 0;
728
+ const liveKeys = opts.containerOwnKeys(segments);
729
+ if (!liveKeys.includes(key)) return void 0;
730
+ return {
731
+ configurable: true,
732
+ enumerable: true,
733
+ value: descendOrTerminate([...segments, keyToSegment(key)]),
734
+ writable: false
735
+ };
736
+ },
695
737
  // Block writes at the proxy boundary. Mutations go through
696
738
  // `setValue`, the directive, or the field-array helpers.
697
739
  set: () => false,
@@ -782,22 +824,47 @@ function buildErrorsProxy(state) {
782
824
  return buildSurfaceProxy({
783
825
  schema: state.schema,
784
826
  resolveLeaf: (path) => {
785
- const { key } = canonicalizePath(path);
786
- const userForKey = state.userErrors.get(key);
787
- const isActive = hasAtPath(state.form.value, path);
827
+ const isContainerSelfAccess = path.length > 1 && path[path.length - 1] === "";
828
+ const collectAtKey = (key2, active2, into) => {
829
+ if (active2) {
830
+ const s = state.schemaErrors.get(key2);
831
+ const b = state.derivedBlankErrors.value.get(key2);
832
+ if (s !== void 0) into.push(...s);
833
+ if (b !== void 0) into.push(...b);
834
+ }
835
+ const u = state.userErrors.get(key2);
836
+ if (u !== void 0) into.push(...u);
837
+ };
788
838
  const merged = [];
789
- if (isActive) {
790
- const schemaForKey = state.schemaErrors.get(key);
791
- const blankForKey = state.derivedBlankErrors.value.get(key);
792
- if (schemaForKey !== void 0) merged.push(...schemaForKey);
793
- if (blankForKey !== void 0) merged.push(...blankForKey);
839
+ if (isContainerSelfAccess) {
840
+ const containerPath = path.slice(0, -1);
841
+ const containerKey = canonicalizePath(containerPath).key;
842
+ const literalKey = canonicalizePath(path).key;
843
+ const active2 = hasAtPath(state.form.value, containerPath);
844
+ collectAtKey(containerKey, active2, merged);
845
+ if (literalKey !== containerKey) collectAtKey(literalKey, active2, merged);
846
+ return merged;
794
847
  }
795
- if (userForKey !== void 0) merged.push(...userForKey);
796
- return merged.length === 0 ? void 0 : merged;
848
+ const { key } = canonicalizePath(path);
849
+ const isFormLevel = key === FORM_ERRORS_PATH_KEY;
850
+ const active = isFormLevel || hasAtPath(state.form.value, path);
851
+ collectAtKey(key, active, merged);
852
+ return merged;
797
853
  },
798
854
  // No leafKeys — at a leaf, the resolved value (the merged array or
799
855
  // undefined) IS the terminal.
800
856
  materializeContainer: (segments) => materializeErrors(state, segments),
857
+ // Any path ending in `''` is a meaningful terminal at the proxy
858
+ // layer: at root it's the form-level bucket; at depth >= 1 it's
859
+ // the container-self sentinel that surfaces cross-field refines
860
+ // and container-targeted marks. `resolveLeaf` translates `[...,
861
+ // '']` lookups to the parent container path before querying the
862
+ // stores. When a schema legitimately owns a `''` field, the
863
+ // literal leaf and any container-self errors share the slot
864
+ // (errors concatenate) — vanishingly rare, accepted as the
865
+ // ergonomic cost of one unified sentinel convention at every
866
+ // depth.
867
+ isTerminalAt: (segs) => segs.length >= 1 && segs[segs.length - 1] === "",
801
868
  // Call-form aggregates: `form.errors(path)` returns a single
802
869
  // `ValidationError[]` for any depth (leaf or container) — same
803
870
  // shared `aggregateErrorsAt` helper that `form.meta.errors` and
@@ -807,12 +874,31 @@ function buildErrorsProxy(state) {
807
874
  // when valid) so consumer code that branches on truthiness keeps
808
875
  // working — the call-form just extends that semantic to
809
876
  // containers and dynamic paths.
810
- resolveCallTarget: (path) => {
811
- const errs = aggregateErrorsAt(state, path);
812
- return errs.length === 0 ? void 0 : errs;
813
- }
877
+ resolveCallTarget: (path) => aggregateErrorsAt(state, path),
878
+ // Mirror `form.fields` enumeration: `Object.keys(form.errors.items)`
879
+ // and `v-for="(errs, idx) in form.errors.items"` walk the live
880
+ // array indices / object keys at the path. Iteration yields the
881
+ // descended sub-proxies (one per live key), so consumers can
882
+ // `form.errors.items[idx]` straight from the entry.
883
+ containerOwnKeys: (segments) => liveKeysAtPath$1(state, segments),
884
+ isArrayContainer: (segments) => isArrayPath$1(state, segments)
814
885
  });
815
886
  }
887
+ function liveKeysAtPath$1(state, segments) {
888
+ const value = getAtPath(state.form.value, segments);
889
+ if (value === null || value === void 0) return [];
890
+ if (Array.isArray(value)) {
891
+ const keys = new Array(value.length);
892
+ for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
893
+ return keys;
894
+ }
895
+ if (typeof value === "object") return Object.keys(value);
896
+ return [];
897
+ }
898
+ function isArrayPath$1(state, segments) {
899
+ if (segments.length === 0) return false;
900
+ return Array.isArray(getAtPath(state.form.value, segments));
901
+ }
816
902
  function materializeErrors(state, containerSegments) {
817
903
  const liveContainer = getAtPath(state.form.value, containerSegments);
818
904
  const tree = Array.isArray(liveContainer) ? [] : {};
@@ -821,12 +907,33 @@ function materializeErrors(state, containerSegments) {
821
907
  if (errors.length === 0) continue;
822
908
  const fullPath = segmentsForPathKey(pathKey);
823
909
  if (fullPath === null) continue;
824
- if (fullPath.length <= containerSegments.length) continue;
825
- for (let i = 0; i < containerSegments.length; i++) {
826
- if (fullPath[i] !== containerSegments[i]) continue entries;
910
+ const isSyntheticFormLevel = fullPath.length === 1 && fullPath[0] === "";
911
+ if (!isSyntheticFormLevel) {
912
+ if (fullPath.length < containerSegments.length) continue;
913
+ for (let i = 0; i < containerSegments.length; i++) {
914
+ if (fullPath[i] !== containerSegments[i]) continue entries;
915
+ }
916
+ } else if (containerSegments.length !== 0) {
917
+ continue;
918
+ }
919
+ if (applyActivePathFilter && !isSyntheticFormLevel && !hasAtPath(state.form.value, fullPath))
920
+ continue;
921
+ let placePath;
922
+ if (isSyntheticFormLevel) {
923
+ placePath = [""];
924
+ } else {
925
+ const relativePath = fullPath.slice(containerSegments.length);
926
+ if (relativePath.length === 0) {
927
+ placePath = [""];
928
+ } else if (state.schema.isLeafAtPath(fullPath)) {
929
+ placePath = relativePath;
930
+ } else if (state.schema.getSlimPrimitiveTypesAtPath(fullPath).size > 0) {
931
+ placePath = [...relativePath, ""];
932
+ } else {
933
+ placePath = relativePath;
934
+ }
827
935
  }
828
- if (applyActivePathFilter && !hasAtPath(state.form.value, fullPath)) continue;
829
- placeAt(tree, fullPath.slice(containerSegments.length), errors);
936
+ placeAt(tree, placePath, errors);
830
937
  }
831
938
  };
832
939
  collect(state.schemaErrors, true);
@@ -1014,9 +1121,26 @@ function buildFieldStateProxy(state, getFormMetaBase, options) {
1014
1121
  leafKeys: FIELD_STATE_KEYS,
1015
1122
  readLeafKey: (computed, key) => computed.value[key],
1016
1123
  materializeContainer: (segments) => materializeFields(state, segments, snapshotFieldStateAt),
1017
- resolveCallTarget: (path) => fieldStateTerminalAt(path)
1124
+ resolveCallTarget: (path) => fieldStateTerminalAt(path),
1125
+ containerOwnKeys: (segments) => liveKeysAtPath(state, segments),
1126
+ isArrayContainer: (segments) => isArrayPath(state, segments)
1018
1127
  });
1019
1128
  }
1129
+ function liveKeysAtPath(state, segments) {
1130
+ const value = getAtPath(state.form.value, segments);
1131
+ if (value === null || value === void 0) return [];
1132
+ if (Array.isArray(value)) {
1133
+ const keys = new Array(value.length);
1134
+ for (let i = 0; i < value.length; i += 1) keys[i] = String(i);
1135
+ return keys;
1136
+ }
1137
+ if (typeof value === "object") return Object.keys(value);
1138
+ return [];
1139
+ }
1140
+ function isArrayPath(state, segments) {
1141
+ if (segments.length === 0) return false;
1142
+ return Array.isArray(getAtPath(state.form.value, segments));
1143
+ }
1020
1144
  function materializeFields(state, containerSegments, snapshotFieldStateAt) {
1021
1145
  const liveValue = getAtPath(state.form.value, containerSegments);
1022
1146
  return walk$2(liveValue, containerSegments, state.schema, snapshotFieldStateAt);
@@ -1048,6 +1172,7 @@ const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
1048
1172
  const PERSISTENCE_KEY_PREFIX = "attaform:";
1049
1173
  const RESERVED_KEY_PREFIX = "__atta:";
1050
1174
  const ANONYMOUS_FORM_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon:`;
1175
+ const ANONYMOUS_WIZARD_KEY_PREFIX = `${RESERVED_KEY_PREFIX}anon-wizard:`;
1051
1176
  const DEFAULT_MAX_RECURSION_DEPTH = 64;
1052
1177
  function normalizeNumericOption(config) {
1053
1178
  const { value, source, allowInfinity, min, defaultValue } = config;
@@ -1082,7 +1207,7 @@ async function getStorageAdapter(storage) {
1082
1207
  }
1083
1208
  }
1084
1209
  }
1085
- const PERSISTED_ENVELOPE_VERSION = 4;
1210
+ const PERSISTED_ENVELOPE_VERSION = 5;
1086
1211
  function readPersistedPayload(value) {
1087
1212
  if (value === null || value === void 0 || typeof value !== "object") return null;
1088
1213
  const envelope = value;
@@ -1104,7 +1229,15 @@ function warnVersionMismatch(observedVersion) {
1104
1229
  );
1105
1230
  }
1106
1231
  function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPaths) {
1107
- const transientList = blankPaths !== void 0 && blankPaths.size > 0 ? [...blankPaths] : void 0;
1232
+ let transientList;
1233
+ if (blankPaths !== void 0 && blankPaths.size > 0) {
1234
+ const dotted = [];
1235
+ for (const key of blankPaths) {
1236
+ const d = pathKeyToDotted(key);
1237
+ if (d !== null) dotted.push(d);
1238
+ }
1239
+ transientList = dotted.length > 0 ? dotted : void 0;
1240
+ }
1108
1241
  if (include === "form") {
1109
1242
  if (transientList === void 0) return { v: PERSISTED_ENVELOPE_VERSION, data: { form } };
1110
1243
  return {
@@ -1272,13 +1405,40 @@ const AttaformErrorCode = {
1272
1405
  NoValueSupplied: "atta:no-value-supplied",
1273
1406
  /** The schema adapter's `validateAtPath` threw synchronously. */
1274
1407
  AdapterThrew: "atta:adapter-threw",
1408
+ /**
1409
+ * User code inside a `z.preprocess`, `.refine`, or `.transform`
1410
+ * threw (sync or async). The adapter caught the throw and surfaced
1411
+ * it as a `ValidationError` at the field path so the form's normal
1412
+ * error pipeline handles it instead of leaking as an unhandled
1413
+ * rejection or routing through `submitError`.
1414
+ */
1415
+ ValidatorThrew: "atta:validator-threw",
1416
+ /**
1417
+ * A function-form `defaultValues` factory threw or its promise
1418
+ * rejected. The runtime captures the raw error on `form.hydrateError`
1419
+ * and ALSO surfaces a form-level `ValidationError` (path `[]`) so
1420
+ * the standard error pipeline carries the signal. Critical for the
1421
+ * SSR round-trip: `hydrateError` itself does not ride the wire
1422
+ * payload, but `schemaErrors` does, so the client sees the failure
1423
+ * after rehydration without an extra channel.
1424
+ */
1425
+ HydrationFailed: "atta:hydration-failed",
1275
1426
  /** The supplied path didn't resolve to any node in the schema. */
1276
- PathNotFound: "atta:path-not-found"
1427
+ PathNotFound: "atta:path-not-found",
1428
+ /**
1429
+ * A walked form's `activate()` (async `defaultValues` factory) threw
1430
+ * during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
1431
+ * `ValidationError` at the form-level path (`[]`) so the wizard's
1432
+ * aggregate error pipeline can carry the failure alongside ordinary
1433
+ * validation errors. The raw factory error remains on
1434
+ * `form.hydrateError` for retry UX.
1435
+ */
1436
+ ActivationFailed: "atta:activation-failed"
1277
1437
  };
1278
1438
 
1279
1439
  const warnedNoScopeStores = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
1280
1440
  function buildProcessForm(state, formInstanceId, options = {}) {
1281
- const invalidPolicy = options.onInvalidSubmit ?? "none";
1441
+ const invalidPolicy = options.onInvalidSubmit ?? "focus-first-error";
1282
1442
  function validate(pathInput) {
1283
1443
  const result = ref({
1284
1444
  pending: true,
@@ -1341,7 +1501,15 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1341
1501
  const dataAtPath = segments === void 0 ? state.form.value : state.getValueAtPath(segments);
1342
1502
  try {
1343
1503
  state.activeValidations.value += 1;
1504
+ state.cancelFieldValidation();
1344
1505
  const refinement = await runRefinementValidation(dataAtPath, segments);
1506
+ const scopePath = segments ?? [];
1507
+ const errors = refinement.success ? [] : refinement.errors;
1508
+ const reStamped = segments === void 0 ? errors : errors.map((err) => ({
1509
+ ...err,
1510
+ path: [...segments, ...err.path]
1511
+ }));
1512
+ state.applySchemaErrorsForSubtree(scopePath, reStamped);
1345
1513
  return stripData(composeWithDerivedBlank(refinement, segments));
1346
1514
  } catch (err) {
1347
1515
  return adapterThrowResponse(err);
@@ -1438,6 +1606,9 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1438
1606
  state.clearSchemaErrors();
1439
1607
  }
1440
1608
  await onSubmit(merged.data);
1609
+ if (state.submissionGeneration.value === genAtEntry) {
1610
+ state.submitted.value = true;
1611
+ }
1441
1612
  state.emitSubmitSuccess();
1442
1613
  } catch (err) {
1443
1614
  if (state.submissionGeneration.value === genAtEntry) {
@@ -1451,7 +1622,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1451
1622
  state.activeSubmissions.value = Math.max(0, state.activeSubmissions.value - 1);
1452
1623
  if (state.submissionGeneration.value === genAtEntry) {
1453
1624
  state.submitting.value = state.activeSubmissions.value > 0;
1454
- state.submitCount.value += 1;
1625
+ state.submissionAttempts.value += 1;
1455
1626
  }
1456
1627
  }
1457
1628
  };
@@ -1545,6 +1716,7 @@ function slimKindOf(value) {
1545
1716
  if (value instanceof Date) return "date";
1546
1717
  if (value instanceof Map) return "map";
1547
1718
  if (value instanceof Set) return "set";
1719
+ if (typeof File !== "undefined" && value instanceof File) return "file";
1548
1720
  const t = typeof value;
1549
1721
  switch (t) {
1550
1722
  case "string":
@@ -1575,6 +1747,7 @@ function isSlimPrimitiveValid(schema, store, path, value) {
1575
1747
  return walk$1(schema, store, path, value);
1576
1748
  }
1577
1749
  function walk$1(schema, store, path, value) {
1750
+ if (schema.isPreprocessOrCoerceLeaf(path)) return true;
1578
1751
  const accepted = schema.getSlimPrimitiveTypesAtPath(path);
1579
1752
  const kind = isLeafValue(value) ? slimKindOf(value) : Array.isArray(value) ? "array" : "object";
1580
1753
  if (!accepted.has(kind)) {
@@ -1804,6 +1977,11 @@ function attachFocusListeners(state, segments, element, instanceMeta) {
1804
1977
  element.addEventListener("focus", handleFocus);
1805
1978
  element.addEventListener("blur", handleBlur);
1806
1979
  target[attaformListenersSymbol] = { handleFocus, handleBlur };
1980
+ const rootNode = element.getRootNode();
1981
+ const activeElement = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : null;
1982
+ if (activeElement === element) {
1983
+ state.markFocused(segments, true, focusMeta);
1984
+ }
1807
1985
  }
1808
1986
  function detachFocusListeners(element) {
1809
1987
  const target = element;
@@ -1840,6 +2018,9 @@ function buildRegister(state, formInstanceId, instanceConfig) {
1840
2018
  return String(raw);
1841
2019
  });
1842
2020
  const slimDefault = state.schema.getDefaultAtPath(segments);
2021
+ const slimTypes = state.schema.getSlimPrimitiveTypesAtPath(segments);
2022
+ const acceptsUndefined = slimTypes.has("undefined");
2023
+ const acceptsString = slimTypes.has("string");
1843
2024
  const persist = options?.persist === true;
1844
2025
  const acknowledgeSensitive = options?.acknowledgeSensitive === true;
1845
2026
  const multiTab = options?.multiTab !== false;
@@ -1923,7 +2104,9 @@ function buildRegister(state, formInstanceId, instanceConfig) {
1923
2104
  ...unmarkNoSync !== void 0 ? { unmarkNoSync } : {},
1924
2105
  transforms,
1925
2106
  coerce,
1926
- ...coerceElement !== void 0 ? { coerceElement } : {}
2107
+ ...coerceElement !== void 0 ? { coerceElement } : {},
2108
+ acceptsUndefined,
2109
+ acceptsString
1927
2110
  };
1928
2111
  return shallowReadonly(internalRv);
1929
2112
  };
@@ -1946,13 +2129,7 @@ function walkUnsetSentinels(values, schema) {
1946
2129
  }
1947
2130
  function walk(input, segments, schema, paths) {
1948
2131
  if (isUnset(input)) {
1949
- const slim = schema.getDefaultAtPath(segments);
1950
- if (!isPrimitiveOrEmpty(slim)) {
1951
- warnNonPrimitiveLeaf(segments, slim);
1952
- return walkUnspecified(slim, segments, paths);
1953
- }
1954
- paths.push(canonicalizePath(segments).key);
1955
- return slim;
2132
+ return expandUnsetAt(segments, schema, paths);
1956
2133
  }
1957
2134
  if (input === void 0) {
1958
2135
  const slim = schema.getDefaultAtPath(segments);
@@ -1975,6 +2152,7 @@ function walk(input, segments, schema, paths) {
1975
2152
  if (typeof input === "object") {
1976
2153
  const slim = schema.getDefaultAtPath(segments);
1977
2154
  const inputKeys = Object.keys(input);
2155
+ const inputKeysSet = new Set(inputKeys);
1978
2156
  const allKeys = new Set(inputKeys);
1979
2157
  if (slim !== null && slim !== void 0 && typeof slim === "object" && !Array.isArray(slim) && !(slim instanceof Date) && !(slim instanceof RegExp) && !(slim instanceof Map) && !(slim instanceof Set)) {
1980
2158
  for (const k of Object.keys(slim)) allKeys.add(k);
@@ -1983,6 +2161,11 @@ function walk(input, segments, schema, paths) {
1983
2161
  let mutated = allKeys.size !== inputKeys.length;
1984
2162
  for (const key of allKeys) {
1985
2163
  const orig = input[key];
2164
+ if (orig === void 0 && inputKeysSet.has(key)) {
2165
+ out[key] = void 0;
2166
+ mutated = true;
2167
+ continue;
2168
+ }
1986
2169
  const walked = walk(orig, [...segments, key], schema, paths);
1987
2170
  out[key] = walked;
1988
2171
  if (walked !== orig) mutated = true;
@@ -1993,7 +2176,7 @@ function walk(input, segments, schema, paths) {
1993
2176
  }
1994
2177
  function walkUnspecified(slim, segments, paths) {
1995
2178
  if (isPrimitiveOrEmpty(slim)) {
1996
- if (isNumericPrimitive(slim)) {
2179
+ if (isSlimNumericPrimitive(slim)) {
1997
2180
  paths.push(canonicalizePath(segments).key);
1998
2181
  }
1999
2182
  return slim;
@@ -2018,13 +2201,7 @@ function substituteUnsetSentinels(value, prefix, schema) {
2018
2201
  }
2019
2202
  function substitute(input, segments, schema, paths) {
2020
2203
  if (isUnset(input)) {
2021
- const slim = schema.getDefaultAtPath(segments);
2022
- if (!isPrimitiveOrEmpty(slim)) {
2023
- warnNonPrimitiveLeaf(segments, slim);
2024
- return slim;
2025
- }
2026
- paths.push(canonicalizePath(segments).key);
2027
- return slim;
2204
+ return expandUnsetAt(segments, schema, paths);
2028
2205
  }
2029
2206
  if (input === void 0 || input === null) return input;
2030
2207
  if (input instanceof Date || input instanceof RegExp || input instanceof Map || input instanceof Set || typeof input === "function") {
@@ -2058,20 +2235,40 @@ function isPrimitiveOrEmpty(value) {
2058
2235
  const t = typeof value;
2059
2236
  return t === "string" || t === "number" || t === "boolean" || t === "bigint";
2060
2237
  }
2061
- function isNumericPrimitive(value) {
2062
- const t = typeof value;
2063
- return t === "number" || t === "bigint";
2064
- }
2065
- const warnedNonPrimitivePaths = __DEV__ ? /* @__PURE__ */ new Set() : null;
2066
- function warnNonPrimitiveLeaf(segments, slim) {
2067
- if (warnedNonPrimitivePaths === null) return;
2068
- const dotted = segments.map(String).join(".");
2069
- if (warnedNonPrimitivePaths.has(dotted)) return;
2070
- warnedNonPrimitivePaths.add(dotted);
2071
- const slimType = slim === null ? "null" : slim instanceof Date ? "Date" : typeof slim;
2072
- console.warn(
2073
- `[attaform] \`unset\` at "${dotted || "<root>"}" is a no-op \u2014 unset only works at primitive leaves (string / number / boolean / bigint, plus their optional / nullable variants), got "${slimType}". The slim default was written but the path is NOT marked blank. (TypeScript catches this at compile time; this warn covers plain-JS callers.)`
2074
- );
2238
+ function isSlimNumericPrimitive(value) {
2239
+ return value === 0 || value === 0n;
2240
+ }
2241
+ function blankForKind(slimDefault) {
2242
+ if (typeof slimDefault === "string") return "";
2243
+ if (typeof slimDefault === "number") return 0;
2244
+ if (typeof slimDefault === "bigint") return 0n;
2245
+ if (typeof slimDefault === "boolean") return false;
2246
+ if (slimDefault === null) return null;
2247
+ return void 0;
2248
+ }
2249
+ function expandUnsetAt(segments, schema, paths) {
2250
+ const du = schema.getUnionDiscriminatorAtPath(segments);
2251
+ if (du !== void 0) {
2252
+ const discPath = [...segments, du.discriminatorKey];
2253
+ const discSlim = schema.getEmptyValueAtPath(discPath);
2254
+ paths.push(canonicalizePath(discPath).key);
2255
+ return { [du.discriminatorKey]: blankForKind(discSlim) };
2256
+ }
2257
+ const slim = schema.getEmptyValueAtPath(segments);
2258
+ if (isPrimitiveOrEmpty(slim)) {
2259
+ paths.push(canonicalizePath(segments).key);
2260
+ return slim;
2261
+ }
2262
+ if (slim instanceof Date || slim instanceof RegExp || slim instanceof Map || slim instanceof Set || typeof slim === "function") {
2263
+ paths.push(canonicalizePath(segments).key);
2264
+ return slim;
2265
+ }
2266
+ if (Array.isArray(slim)) return slim;
2267
+ const result = {};
2268
+ for (const key of Object.keys(slim)) {
2269
+ result[key] = expandUnsetAt([...segments, key], schema, paths);
2270
+ }
2271
+ return result;
2075
2272
  }
2076
2273
 
2077
2274
  function buildValuesProxy(form) {
@@ -2141,28 +2338,6 @@ function buildValuesProxy(form) {
2141
2338
  });
2142
2339
  }
2143
2340
 
2144
- function blankForKind(slimDefault) {
2145
- if (typeof slimDefault === "string") return "";
2146
- if (typeof slimDefault === "number") return 0;
2147
- if (typeof slimDefault === "bigint") return 0n;
2148
- if (typeof slimDefault === "boolean") return false;
2149
- if (slimDefault === null) return null;
2150
- return void 0;
2151
- }
2152
- function readonlySetSnapshot(source) {
2153
- const snapshot = new Set(source);
2154
- return new Proxy(snapshot, {
2155
- get(target, prop) {
2156
- if (prop === "add" || prop === "delete" || prop === "clear") {
2157
- return () => {
2158
- throw new TypeError(`Cannot mutate readonly Set: '${String(prop)}' is not allowed.`);
2159
- };
2160
- }
2161
- const value = Reflect.get(target, prop, target);
2162
- return typeof value === "function" ? value.bind(target) : value;
2163
- }
2164
- });
2165
- }
2166
2341
  function buildFormApi(state, formInstanceId, options = {}) {
2167
2342
  const instanceMeta = (() => {
2168
2343
  const bag = {};
@@ -2185,6 +2360,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2185
2360
  Object.keys(registerConfig).length > 0 ? registerConfig : void 0
2186
2361
  );
2187
2362
  const processOptions = options.onInvalidSubmit !== void 0 ? { onInvalidSubmit: options.onInvalidSubmit } : {};
2363
+ const defaultInvalidSubmitPolicy = options.onInvalidSubmit ?? "focus-first-error";
2188
2364
  const {
2189
2365
  validate: validateBuilt,
2190
2366
  validateAsync: validateAsyncBuilt,
@@ -2205,49 +2381,53 @@ function buildFormApi(state, formInstanceId, options = {}) {
2205
2381
  next,
2206
2382
  state.schema
2207
2383
  );
2208
- const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
2209
- if (!ok2) return false;
2210
2384
  for (const pathKey of walked2.paths) {
2211
- const segments2 = segmentsForPathKey(pathKey);
2212
- if (segments2 === null) continue;
2213
- state.setValueAtPath(
2214
- segments2,
2215
- state.schema.getDefaultAtPath(segments2),
2216
- withInstanceMeta({ blank: true })
2217
- );
2385
+ state.blankPaths.add(pathKey);
2218
2386
  }
2219
- return true;
2387
+ return state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
2220
2388
  }
2221
2389
  const segments = canonicalizePath(pathOrValue).segments;
2222
- if (isUnset(maybeValue)) {
2390
+ const writeUnsetAt = () => {
2223
2391
  const last = segments.length > 0 ? segments[segments.length - 1] : void 0;
2224
2392
  if (typeof last === "string") {
2225
2393
  const parent = segments.slice(0, -1);
2226
2394
  const parentDU = state.schema.getUnionDiscriminatorAtPath(parent);
2227
2395
  if (parentDU?.discriminatorKey === last) {
2228
- const slimDefault = state.schema.getDefaultAtPath(segments);
2396
+ const slimDefault = state.schema.getEmptyValueAtPath(segments);
2229
2397
  const blank = blankForKind(slimDefault);
2230
2398
  return state.setValueAtPath(segments, blank, withInstanceMeta({ blank: true }));
2231
2399
  }
2232
2400
  }
2233
- return state.setValueAtPath(
2401
+ const blankPaths = [];
2402
+ const expanded = expandUnsetAt(
2234
2403
  segments,
2235
- state.schema.getDefaultAtPath(segments),
2236
- withInstanceMeta({ blank: true })
2404
+ state.schema,
2405
+ blankPaths
2237
2406
  );
2238
- }
2407
+ const segmentsKey = canonicalizePath(segments).key;
2408
+ if (blankPaths.length === 1 && blankPaths[0] === segmentsKey) {
2409
+ return state.setValueAtPath(segments, expanded, withInstanceMeta({ blank: true }));
2410
+ }
2411
+ const ok2 = state.setValueAtPath(segments, expanded, withInstanceMeta());
2412
+ if (!ok2) return false;
2413
+ for (const pathKey of blankPaths) {
2414
+ const blankSegments = segmentsForPathKey(pathKey);
2415
+ if (blankSegments === null) continue;
2416
+ state.setValueAtPath(
2417
+ blankSegments,
2418
+ state.getValueAtPath(blankSegments),
2419
+ withInstanceMeta({ blank: true })
2420
+ );
2421
+ }
2422
+ return true;
2423
+ };
2424
+ if (isUnset(maybeValue)) return writeUnsetAt();
2239
2425
  let resolvedValue;
2240
2426
  if (typeof maybeValue === "function") {
2241
2427
  const current = state.getValueAtPath(segments);
2242
2428
  const prev = current === void 0 ? state.schema.getDefaultAtPath(segments) : current;
2243
2429
  resolvedValue = maybeValue(prev);
2244
- if (isUnset(resolvedValue)) {
2245
- return state.setValueAtPath(
2246
- segments,
2247
- state.schema.getDefaultAtPath(segments),
2248
- withInstanceMeta({ blank: true })
2249
- );
2250
- }
2430
+ if (isUnset(resolvedValue)) return writeUnsetAt();
2251
2431
  } else {
2252
2432
  resolvedValue = maybeValue;
2253
2433
  }
@@ -2263,7 +2443,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2263
2443
  if (blankSegments === null) continue;
2264
2444
  state.setValueAtPath(
2265
2445
  blankSegments,
2266
- state.schema.getDefaultAtPath(blankSegments),
2446
+ state.getValueAtPath(blankSegments),
2267
2447
  withInstanceMeta({ blank: true })
2268
2448
  );
2269
2449
  }
@@ -2327,8 +2507,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
2327
2507
  state.userErrors.delete(FORM_ERRORS_PATH_KEY);
2328
2508
  }
2329
2509
  const submitting = computed(() => state.submitting.value);
2330
- const submitCount = computed(() => state.submitCount.value);
2510
+ const submissionAttempts = computed(() => state.submissionAttempts.value);
2511
+ const submitted = computed(() => state.submitted.value);
2331
2512
  const submitError = computed(() => state.submitError.value);
2513
+ const departAttempts = computed(() => state.departAttempts.value);
2332
2514
  const validating = computed(() => state.activeValidations.value > 0);
2333
2515
  const valid = computed(
2334
2516
  () => state.firstValidationDone.value && state.schemaErrors.size === 0 && state.userErrors.size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
@@ -2353,8 +2535,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
2353
2535
  return {
2354
2536
  ...rootBase,
2355
2537
  submitting: state.submitting.value,
2356
- submitCount: state.submitCount.value,
2538
+ submissionAttempts: state.submissionAttempts.value,
2539
+ departAttempts: state.departAttempts.value,
2357
2540
  submitError: state.submitError.value,
2541
+ errorCount: rootBase.errors.length,
2542
+ submitted: state.submitted.value,
2358
2543
  instanceId: formInstanceId
2359
2544
  };
2360
2545
  };
@@ -2414,8 +2599,14 @@ function buildFormApi(state, formInstanceId, options = {}) {
2414
2599
  meta: computed(() => rootFieldState.value.meta),
2415
2600
  // Lifecycle (form-level only — not on FieldState).
2416
2601
  submitting,
2417
- submitCount,
2602
+ submissionAttempts,
2603
+ departAttempts,
2418
2604
  submitError,
2605
+ // Scalar mirror over the array — meta is a single sticky surface
2606
+ // for both templates and `useWizard`'s `FormStatus`, so the
2607
+ // projection lives here.
2608
+ errorCount: computed(() => metaErrors.value.length),
2609
+ submitted,
2419
2610
  // Per-`useForm()`-call identity. Stable for one mount; new on
2420
2611
  // re-mount; orthogonal to `form.key` (which is the user-supplied
2421
2612
  // shared identifier). Useful for devtools panels disambiguating
@@ -2435,13 +2626,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2435
2626
  );
2436
2627
  state.reset(walked.cleanedValues);
2437
2628
  for (const pathKey of walked.paths) {
2438
- const segments = segmentsForPathKey(pathKey);
2439
- if (segments === null) continue;
2440
- state.setValueAtPath(
2441
- segments,
2442
- state.schema.getDefaultAtPath(segments),
2443
- withInstanceMeta({ blank: true })
2444
- );
2629
+ state.blankPaths.add(pathKey);
2445
2630
  state.originalBlankPaths.add(pathKey);
2446
2631
  }
2447
2632
  }
@@ -2491,56 +2676,141 @@ function buildFormApi(state, formInstanceId, options = {}) {
2491
2676
  target.element.scrollIntoView(options2);
2492
2677
  return true;
2493
2678
  };
2679
+ const applyInvalidSubmitPolicyPublic = (policy) => {
2680
+ applyInvalidSubmitPolicy(state, formInstanceId, policy ?? defaultInvalidSubmitPolicy);
2681
+ };
2494
2682
  const fieldArrays = buildFieldArrayApi(state);
2495
2683
  const blankPathsView = computed(() => {
2496
- return readonlySetSnapshot(state.blankPaths);
2684
+ const keys = /* @__PURE__ */ new Set();
2685
+ const paths = [];
2686
+ for (const pk of state.blankPaths) {
2687
+ keys.add(pk);
2688
+ const segs = segmentsForPathKey(pk);
2689
+ if (segs !== null) paths.push(segs);
2690
+ }
2691
+ Object.freeze(paths);
2692
+ const view = {
2693
+ get size() {
2694
+ return keys.size;
2695
+ },
2696
+ has(input) {
2697
+ const { key } = canonicalizePath(input);
2698
+ return keys.has(key);
2699
+ },
2700
+ values() {
2701
+ return paths;
2702
+ },
2703
+ [Symbol.iterator]() {
2704
+ return paths[Symbol.iterator]();
2705
+ }
2706
+ };
2707
+ return Object.freeze(view);
2497
2708
  });
2498
2709
  const valuesProxy = buildValuesProxy(state.form);
2499
2710
  const fieldStateProxy = buildFieldStateProxy(state, getFormMetaBase, fieldStateAccessorOptions);
2711
+ function gated(fn) {
2712
+ return ((...args) => {
2713
+ void state.activate();
2714
+ return fn(...args);
2715
+ });
2716
+ }
2500
2717
  return {
2501
- handleSubmit,
2502
- // `values` is the callable readonly Proxy. Each `get` trap reads
2503
- // through `inner.value` (a `computed(() => readonly(form.value))`)
2504
- // so reactivity tracking propagates at the call site. Identity-
2505
- // stable across whole-form swaps (the inner readonly proxy
2506
- // re-keys; the outer callable proxy stays the same instance).
2507
- values: valuesProxy,
2508
- fields: fieldStateProxy,
2509
- setValue: setValueImpl,
2510
- validate,
2511
- validateAsync,
2512
- process,
2513
- register,
2718
+ handleSubmit: gated(handleSubmit),
2719
+ // Callable readonly Proxies (`values`, `fields`, `errors`) and the
2720
+ // reactive containers (`meta`, `history`, `blankPaths`) are exposed
2721
+ // through getters so reading them activates the form on first
2722
+ // touch. Each underlying object is identity-stable across reads.
2723
+ get values() {
2724
+ void state.activate();
2725
+ return valuesProxy;
2726
+ },
2727
+ get fields() {
2728
+ void state.activate();
2729
+ return fieldStateProxy;
2730
+ },
2731
+ setValue: gated(setValueImpl),
2732
+ validate: gated(validate),
2733
+ validateAsync: gated(validateAsync),
2734
+ process: gated(process),
2735
+ register: gated(register),
2514
2736
  key: state.formKey,
2515
- errors: errorsProxy,
2516
- toRef: pathToRef,
2517
- setFieldErrors,
2518
- addFieldErrors,
2519
- clearFieldErrors,
2520
- setFormErrors,
2521
- clearFormErrors,
2522
- meta: formMeta,
2523
- reset,
2524
- resetField,
2525
- clear,
2526
- persist,
2527
- clearPersistedDraft,
2528
- focusFirstError,
2529
- scrollToFirstError,
2530
- touch,
2531
- history: formHistory,
2532
- append: fieldArrays.append,
2533
- prepend: fieldArrays.prepend,
2534
- insert: fieldArrays.insert,
2535
- remove: fieldArrays.remove,
2536
- swap: fieldArrays.swap,
2537
- move: fieldArrays.move,
2538
- replace: fieldArrays.replace,
2539
- blankPaths: blankPathsView
2737
+ // Auto-unwrapping views over the per-store async-defaults lifecycle
2738
+ // refs (see FormStore.hydrating / hydrateError). Reading either
2739
+ // activates the form — observing factory state implies use.
2740
+ get hydrating() {
2741
+ void state.activate();
2742
+ return state.hydrating.value;
2743
+ },
2744
+ get hydrateError() {
2745
+ void state.activate();
2746
+ return state.hydrateError.value;
2747
+ },
2748
+ // Orthogonal to `hydrating` and `hydrateError`: `ready` flips true
2749
+ // once defaults are applied (sync at construction or async factory
2750
+ // resolved successfully). One-way latch — stays true through later
2751
+ // refetches even when those refetches fail, so stale-while-
2752
+ // revalidate UIs keep rendering the prior values while
2753
+ // `hydrateError` surfaces the refresh failure.
2754
+ get ready() {
2755
+ void state.activate();
2756
+ return state.defaultsResolved.value;
2757
+ },
2758
+ // `rehydrate` and `activate` are themselves activation entry points
2759
+ // — they fire the factory by design. Wrapping them with `gated`
2760
+ // would double-fire (`state.activate()` plus the underlying call),
2761
+ // so they call `state` directly.
2762
+ rehydrate: () => state.rehydrate(),
2763
+ activate: () => state.activate(),
2764
+ get errors() {
2765
+ void state.activate();
2766
+ return errorsProxy;
2767
+ },
2768
+ toRef: gated(pathToRef),
2769
+ setFieldErrors: gated(setFieldErrors),
2770
+ addFieldErrors: gated(addFieldErrors),
2771
+ clearFieldErrors: gated(clearFieldErrors),
2772
+ setFormErrors: gated(setFormErrors),
2773
+ clearFormErrors: gated(clearFormErrors),
2774
+ get meta() {
2775
+ void state.activate();
2776
+ return formMeta;
2777
+ },
2778
+ reset: gated(reset),
2779
+ resetField: gated(resetField),
2780
+ clear: gated(clear),
2781
+ persist: gated(persist),
2782
+ clearPersistedDraft: gated(clearPersistedDraft),
2783
+ focusFirstError: gated(focusFirstError),
2784
+ scrollToFirstError: gated(scrollToFirstError),
2785
+ applyInvalidSubmitPolicy: gated(applyInvalidSubmitPolicyPublic),
2786
+ touch: gated(touch),
2787
+ get history() {
2788
+ void state.activate();
2789
+ return formHistory;
2790
+ },
2791
+ append: gated(fieldArrays.append),
2792
+ prepend: gated(fieldArrays.prepend),
2793
+ insert: gated(fieldArrays.insert),
2794
+ remove: gated(fieldArrays.remove),
2795
+ swap: gated(fieldArrays.swap),
2796
+ move: gated(fieldArrays.move),
2797
+ replace: gated(fieldArrays.replace),
2798
+ get blankPaths() {
2799
+ void state.activate();
2800
+ return blankPathsView;
2801
+ }
2540
2802
  };
2541
2803
  }
2542
2804
 
2543
- const defaultShouldShowErrors = (field, formMeta) => formMeta.submitCount > 0 || field.touched === true && field.dirty;
2805
+ const defaultShouldShowErrors = (field, formMeta) => {
2806
+ const hasOwnError = field.errors.some(
2807
+ (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
2808
+ );
2809
+ if (!hasOwnError) return false;
2810
+ if (field.validating === true) return false;
2811
+ if (formMeta.submissionAttempts > 0) return true;
2812
+ return field.touched === true && field.focused !== true;
2813
+ };
2544
2814
  const SHOW_ALWAYS = () => true;
2545
2815
  const SHOW_NEVER = () => false;
2546
2816
  function resolveShouldShowErrors(config) {
@@ -2553,7 +2823,7 @@ function resolveShouldShowErrors(config) {
2553
2823
  function isHydratedFieldRecord(value) {
2554
2824
  if (typeof value !== "object" || value === null) return false;
2555
2825
  const r = value;
2556
- return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.connected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) && (typeof r.touched === "boolean" || r.touched === null);
2826
+ return Array.isArray(r.path) && (typeof r.updatedAt === "string" || r.updatedAt === null) && typeof r.connected === "boolean" && (typeof r.focused === "boolean" || r.focused === null) && (typeof r.blurred === "boolean" || r.blurred === null) && typeof r.touched === "boolean";
2557
2827
  }
2558
2828
  function isHydratedValidationErrorArray(value) {
2559
2829
  if (!Array.isArray(value)) return false;
@@ -2590,7 +2860,8 @@ function walkDuStubs(schema, value, path, warned) {
2590
2860
  if (du !== void 0) {
2591
2861
  const discValue = rec[du.discriminatorKey];
2592
2862
  if (discValue !== void 0 && !du.isVariantSelected(discValue)) {
2593
- if (warned !== void 0 && __DEV__) {
2863
+ const isKindBlank = discValue === "" || discValue === 0 || discValue === 0n || discValue === false || discValue === null;
2864
+ if (!isKindBlank && warned !== void 0 && __DEV__) {
2594
2865
  const dotted = path.map((s) => String(s)).join(".") || "(root)";
2595
2866
  const key = `${dotted}::${String(discValue)}`;
2596
2867
  if (!warned.has(key)) {
@@ -2669,9 +2940,45 @@ function cloneVariantSnapshot(value) {
2669
2940
  for (const k of Object.keys(src)) out[k] = cloneVariantSnapshot(src[k]);
2670
2941
  return out;
2671
2942
  }
2943
+ function walkAuthoredFromConstraints(value, prefix, out) {
2944
+ if (prefix.length > 0) out.add(canonicalizePath(prefix).key);
2945
+ if (isPlainRecord(value)) {
2946
+ for (const k of Object.keys(value)) {
2947
+ walkAuthoredFromConstraints(value[k], [...prefix, k], out);
2948
+ }
2949
+ return;
2950
+ }
2951
+ if (Array.isArray(value)) {
2952
+ for (let i = 0; i < value.length; i++) {
2953
+ walkAuthoredFromConstraints(value[i], [...prefix, i], out);
2954
+ }
2955
+ }
2956
+ }
2957
+ function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out) {
2958
+ if (isPlainRecord(withDefaults) && isPlainRecord(withoutDefaults)) {
2959
+ const left = withDefaults;
2960
+ const right = withoutDefaults;
2961
+ const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
2962
+ for (const k of keys) {
2963
+ walkAuthoredFromSchemaDiff(left[k], right[k], [...prefix, k], out);
2964
+ }
2965
+ return;
2966
+ }
2967
+ if (Array.isArray(withDefaults) && Array.isArray(withoutDefaults)) {
2968
+ const len = Math.max(withDefaults.length, withoutDefaults.length);
2969
+ for (let i = 0; i < len; i++) {
2970
+ walkAuthoredFromSchemaDiff(withDefaults[i], withoutDefaults[i], [...prefix, i], out);
2971
+ }
2972
+ return;
2973
+ }
2974
+ if (!Object.is(withDefaults, withoutDefaults) && prefix.length > 0) {
2975
+ out.add(canonicalizePath(prefix).key);
2976
+ }
2977
+ }
2672
2978
  function createFormStore(options) {
2673
2979
  const { formKey, schema, defaultValues, strict = true, hydration } = options;
2674
2980
  const ssr = options.ssr === true;
2981
+ const ssrPrefetch = options.ssrPrefetch;
2675
2982
  const rememberVariants = options.rememberVariants !== false;
2676
2983
  const fieldValidationMode = options.validateOn ?? "change";
2677
2984
  const fieldValidationDebounceMs = normalizeNumericOption({
@@ -2717,6 +3024,29 @@ function createFormStore(options) {
2717
3024
  strict
2718
3025
  });
2719
3026
  const schemaInitialData = schemaResponse.data;
3027
+ const authoredPaths = /* @__PURE__ */ new Set();
3028
+ function rebuildAuthoredPaths(constraints, schemaWithDefaultsData) {
3029
+ authoredPaths.clear();
3030
+ if (constraints !== void 0) {
3031
+ walkAuthoredFromConstraints(constraints, [], authoredPaths);
3032
+ }
3033
+ const slimResponse = schema.getDefaultValues({
3034
+ useDefaultSchemaValues: false,
3035
+ strict
3036
+ });
3037
+ walkAuthoredFromSchemaDiff(schemaWithDefaultsData, slimResponse.data, [], authoredPaths);
3038
+ }
3039
+ rebuildAuthoredPaths(defaultValues, schemaInitialData);
3040
+ function filterAuthoredErrors(errors) {
3041
+ return errors.filter((err) => {
3042
+ const pathSegments = err.path;
3043
+ if (pathSegments.length === 0) return true;
3044
+ const value = getAtPath(form.value, pathSegments);
3045
+ if (value !== void 0) return true;
3046
+ if (authoredPaths.has(canonicalizePath(pathSegments).key)) return true;
3047
+ return !schema.isPreprocessOrCoerceLeaf(pathSegments);
3048
+ });
3049
+ }
2720
3050
  const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
2721
3051
  const stubbedInitialData = applyDuStubs(schema, initialData, {
2722
3052
  warn: true
@@ -2733,8 +3063,9 @@ function createFormStore(options) {
2733
3063
  const blankPaths = reactive(/* @__PURE__ */ new Set());
2734
3064
  const originalBlankPaths = /* @__PURE__ */ new Set();
2735
3065
  for (const raw of initialTransientList) {
2736
- blankPaths.add(raw);
2737
- originalBlankPaths.add(raw);
3066
+ const key = coerceToPathKey(raw);
3067
+ blankPaths.add(key);
3068
+ originalBlankPaths.add(key);
2738
3069
  }
2739
3070
  const variantMemory = /* @__PURE__ */ new Map();
2740
3071
  function clearVariantMemoryUnderPath(arrayPath) {
@@ -2802,10 +3133,18 @@ function createFormStore(options) {
2802
3133
  });
2803
3134
  const submitting = ref(false);
2804
3135
  const activeSubmissions = ref(0);
2805
- const submitCount = ref(0);
3136
+ const submissionAttempts = ref(0);
3137
+ const submitted = ref(false);
2806
3138
  const submitError = ref(null);
3139
+ const departAttempts = ref(0);
2807
3140
  const submissionGeneration = ref(0);
2808
3141
  const activeValidations = ref(0);
3142
+ const hydrating = ref(false);
3143
+ const hydrateError = ref(null);
3144
+ const defaultValuesFactory = ref(void 0);
3145
+ const defaultsResolved = ref(false);
3146
+ const activated = ref(false);
3147
+ const activationPromise = ref(void 0);
2809
3148
  const firstValidationDone = ref(!strict || schema.needsAsyncValidation?.() !== true);
2810
3149
  watch(activeValidations, (now, prev) => {
2811
3150
  if (prev > 0 && now === 0) {
@@ -2870,7 +3209,7 @@ function createFormStore(options) {
2870
3209
  connected: false,
2871
3210
  focused: null,
2872
3211
  blurred: null,
2873
- touched: null
3212
+ touched: false
2874
3213
  });
2875
3214
  });
2876
3215
  if (strict && !schemaResponse.success) {
@@ -2890,9 +3229,16 @@ function createFormStore(options) {
2890
3229
  path,
2891
3230
  updatedAt: patch.updatedAt ?? current?.updatedAt ?? null,
2892
3231
  connected: patch.connected ?? current?.connected ?? false,
2893
- focused: patch.focused ?? current?.focused ?? null,
2894
- blurred: patch.blurred ?? current?.blurred ?? null,
2895
- touched: patch.touched ?? current?.touched ?? null
3232
+ // focused/blurred use an explicit-undefined guard because
3233
+ // patches legitimately carry `null` to mark a disconnect — the
3234
+ // `??` operator would short-circuit on null and fall through to
3235
+ // `current`, losing the intent. `!== undefined` honours an
3236
+ // explicit null and preserves current only on absence.
3237
+ focused: patch.focused !== void 0 ? patch.focused : current?.focused ?? null,
3238
+ blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
3239
+ // touched is plain `boolean`; `??` is equivalent to the explicit
3240
+ // guard here because `false` is not nullish.
3241
+ touched: patch.touched ?? current?.touched ?? false
2896
3242
  });
2897
3243
  }
2898
3244
  function applyFormReplacement(next, meta) {
@@ -2923,7 +3269,6 @@ function createFormStore(options) {
2923
3269
  }
2924
3270
  function setValueAtPath(path, value, meta) {
2925
3271
  value = stripSymbolsDeep(value);
2926
- value = schema.normalizeWriteValueAtPath(value, path);
2927
3272
  if (!isSlimPrimitiveValid(schema, form, path, value)) {
2928
3273
  return false;
2929
3274
  }
@@ -3022,9 +3367,21 @@ function createFormStore(options) {
3022
3367
  } else if (blankPaths.has(pathKey)) {
3023
3368
  blankPaths.delete(pathKey);
3024
3369
  }
3370
+ const wasAuthoredBefore = authoredPaths.has(pathKey);
3371
+ walkAuthoredFromConstraints(value, path, authoredPaths);
3372
+ const newlyAuthored = !wasAuthoredBefore && authoredPaths.has(pathKey);
3025
3373
  const completedValue = mergeStructural(schema, path, value);
3026
3374
  const currentValue = getAtPath(form.value, path);
3027
3375
  if (Object.is(currentValue, completedValue)) {
3376
+ if (newlyAuthored && schema.isPreprocessOrCoerceLeaf(path)) {
3377
+ const modeForAuthoringTransition = meta?.instance?.validateOn ?? fieldValidationMode;
3378
+ if (modeForAuthoringTransition === "change") {
3379
+ scheduleFieldValidation(path, false, {
3380
+ ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
3381
+ ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
3382
+ });
3383
+ }
3384
+ }
3028
3385
  return true;
3029
3386
  }
3030
3387
  const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
@@ -3137,12 +3494,11 @@ function createFormStore(options) {
3137
3494
  prev.controller.abort();
3138
3495
  }
3139
3496
  const controller = new AbortController();
3140
- const fresh = { controller, timer: null };
3497
+ const fresh = { controller, timer: null, settled: false };
3141
3498
  fieldValidationState.set(key, fresh);
3142
3499
  const run = () => {
3143
3500
  fresh.timer = null;
3144
3501
  if (controller.signal.aborted) return;
3145
- const data = getAtPath(form.value, path);
3146
3502
  let activeIncremented = false;
3147
3503
  try {
3148
3504
  activeValidations.value += 1;
@@ -3154,17 +3510,16 @@ function createFormStore(options) {
3154
3510
  }
3155
3511
  throw err;
3156
3512
  }
3157
- void Promise.resolve().then(() => schema.validateAtPath(data, path)).then((response) => {
3513
+ void Promise.resolve().then(() => schema.validateAtPath(form.value, void 0)).then((response) => {
3158
3514
  if (controller.signal.aborted) return;
3159
- const reStamped = response.success ? [] : response.errors.map((err) => ({
3160
- ...err,
3161
- path: [...path, ...err.path]
3162
- }));
3163
- applySchemaErrorsForSubtree(path, reStamped);
3515
+ const errors = response.success ? [] : response.errors;
3516
+ const filtered = filterAuthoredErrors(errors);
3517
+ applySchemaErrorsForSubtree([], filtered);
3164
3518
  }).catch(() => {
3165
3519
  }).finally(() => {
3166
3520
  activeValidations.value = Math.max(0, activeValidations.value - 1);
3167
3521
  decFieldValidation(key);
3522
+ fresh.settled = true;
3168
3523
  });
3169
3524
  };
3170
3525
  if (immediate || effectiveDebounce === 0) {
@@ -3174,8 +3529,13 @@ function createFormStore(options) {
3174
3529
  }
3175
3530
  }
3176
3531
  function cancelFieldValidation() {
3177
- for (const entry of fieldValidationState.values()) {
3178
- if (entry.timer !== null) clearTimeout(entry.timer);
3532
+ for (const [pkey, entry] of fieldValidationState) {
3533
+ if (entry.timer !== null) {
3534
+ clearTimeout(entry.timer);
3535
+ } else if (!entry.settled) {
3536
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
3537
+ decFieldValidation(pkey);
3538
+ }
3179
3539
  entry.controller.abort();
3180
3540
  }
3181
3541
  fieldValidationState.clear();
@@ -3344,7 +3704,12 @@ function createFormStore(options) {
3344
3704
  }
3345
3705
  elementToFormInstance.set(element, formInstanceId);
3346
3706
  sortedRegistrationsCache = null;
3347
- touchFieldRecord(key, path, { connected: true });
3707
+ const current = fields.get(key);
3708
+ touchFieldRecord(key, path, {
3709
+ connected: true,
3710
+ focused: current?.focused ?? false,
3711
+ blurred: current?.blurred ?? true
3712
+ });
3348
3713
  return true;
3349
3714
  }
3350
3715
  function deregisterElement(path, element) {
@@ -3359,7 +3724,7 @@ function createFormStore(options) {
3359
3724
  const remaining = record.elements.size;
3360
3725
  if (remaining === 0) {
3361
3726
  elements.delete(key);
3362
- touchFieldRecord(key, path, { connected: false });
3727
+ touchFieldRecord(key, path, { connected: false, focused: null, blurred: null });
3363
3728
  }
3364
3729
  return remaining;
3365
3730
  }
@@ -3368,7 +3733,11 @@ function createFormStore(options) {
3368
3733
  const { key } = canonicalizePath(path);
3369
3734
  const current = fields.get(key);
3370
3735
  if (current?.connected === true) return;
3371
- touchFieldRecord(key, path, { connected: true });
3736
+ touchFieldRecord(key, path, {
3737
+ connected: true,
3738
+ focused: current?.focused ?? false,
3739
+ blurred: current?.blurred ?? true
3740
+ });
3372
3741
  }
3373
3742
  function markFocused(path, focused, meta) {
3374
3743
  const { key } = canonicalizePath(path);
@@ -3377,7 +3746,7 @@ function createFormStore(options) {
3377
3746
  blurred: !focused,
3378
3747
  // `touched` flips to true on blur and stays true thereafter; while
3379
3748
  // a field is currently focused we keep whatever value it held.
3380
- touched: focused ? fields.get(key)?.touched ?? null : true
3749
+ touched: focused ? fields.get(key)?.touched ?? false : true
3381
3750
  });
3382
3751
  const focusMode = meta?.instance?.validateOn ?? fieldValidationMode;
3383
3752
  if (!focused && focusMode === "blur") {
@@ -3412,6 +3781,84 @@ function createFormStore(options) {
3412
3781
  function clear(path) {
3413
3782
  return setValueAtPath(path, schema.getEmptyValueAtPath(path));
3414
3783
  }
3784
+ function rehydrate() {
3785
+ const factory = defaultValuesFactory.value;
3786
+ if (factory === void 0) {
3787
+ throw new Error(
3788
+ "[attaform] form.rehydrate(): no defaultValues factory was captured. Configure useForm({ defaultValues: () => ... }) to enable rehydrate."
3789
+ );
3790
+ }
3791
+ return fireFactory(factory);
3792
+ }
3793
+ function fireFactory(factory) {
3794
+ activated.value = true;
3795
+ const promise = runFactoryAndApply(factory);
3796
+ activationPromise.value = promise;
3797
+ void promise.finally(() => {
3798
+ if (activationPromise.value === promise) activationPromise.value = void 0;
3799
+ });
3800
+ return promise;
3801
+ }
3802
+ function activate() {
3803
+ if (ssrPrefetch !== void 0) {
3804
+ ssrPrefetch.enqueue();
3805
+ if (!ssrPrefetch.shouldFire()) return Promise.resolve();
3806
+ }
3807
+ if (defaultsResolved.value === true) return Promise.resolve();
3808
+ if (activationPromise.value !== void 0) return activationPromise.value;
3809
+ if (activated.value === true) return Promise.resolve();
3810
+ const factory = defaultValuesFactory.value;
3811
+ if (factory === void 0) return Promise.resolve();
3812
+ return fireFactory(factory);
3813
+ }
3814
+ async function runFactoryAndApply(factory) {
3815
+ hydrating.value = true;
3816
+ try {
3817
+ const value = await factory();
3818
+ walkAuthoredFromConstraints(value, [], authoredPaths);
3819
+ const full = mergeSparseHydration(
3820
+ toRaw(form.value),
3821
+ value,
3822
+ schema
3823
+ );
3824
+ applyFormReplacement(full, { hydration: true });
3825
+ scheduleFieldValidation(
3826
+ [],
3827
+ true
3828
+ /* immediate */
3829
+ );
3830
+ clearHydrationFailedEntry();
3831
+ hydrateError.value = null;
3832
+ defaultsResolved.value = true;
3833
+ } catch (error) {
3834
+ clearHydrationFailedEntry();
3835
+ hydrateError.value = appendHydrationFailedEntry(error);
3836
+ } finally {
3837
+ hydrating.value = false;
3838
+ }
3839
+ }
3840
+ function clearHydrationFailedEntry() {
3841
+ const existing = schemaErrors.get(FORM_ERRORS_PATH_KEY);
3842
+ if (existing === void 0) return;
3843
+ const filtered = existing.filter((e) => e.code !== AttaformErrorCode.HydrationFailed);
3844
+ if (filtered.length === 0) {
3845
+ schemaErrors.delete(FORM_ERRORS_PATH_KEY);
3846
+ } else {
3847
+ schemaErrors.set(FORM_ERRORS_PATH_KEY, filtered);
3848
+ }
3849
+ }
3850
+ function appendHydrationFailedEntry(error) {
3851
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Hydration failed";
3852
+ const entry = {
3853
+ message,
3854
+ path: [...FORM_ERRORS_PATH],
3855
+ formKey,
3856
+ code: AttaformErrorCode.HydrationFailed
3857
+ };
3858
+ const existing = schemaErrors.get(FORM_ERRORS_PATH_KEY) ?? [];
3859
+ schemaErrors.set(FORM_ERRORS_PATH_KEY, [...existing, entry]);
3860
+ return entry;
3861
+ }
3415
3862
  function reset(nextDefaultValues) {
3416
3863
  const resetSource = nextDefaultValues ?? defaultValues;
3417
3864
  const completedResetConstraints = resetSource === void 0 ? void 0 : mergeStructural(schema, [], resetSource);
@@ -3421,6 +3868,7 @@ function createFormStore(options) {
3421
3868
  strict
3422
3869
  });
3423
3870
  const next = resetResponse.data;
3871
+ rebuildAuthoredPaths(resetSource, next);
3424
3872
  applyFormReplacement(next);
3425
3873
  originals.clear();
3426
3874
  diffAndApply({}, next, [], (patch) => {
@@ -3463,16 +3911,18 @@ function createFormStore(options) {
3463
3911
  path: record.path,
3464
3912
  updatedAt: now,
3465
3913
  connected: record.connected,
3466
- focused: null,
3467
- blurred: null,
3468
- touched: null
3914
+ focused: record.focused,
3915
+ blurred: record.blurred,
3916
+ touched: false
3469
3917
  });
3470
3918
  }
3471
3919
  submissionGeneration.value += 1;
3472
3920
  submitting.value = false;
3473
3921
  activeSubmissions.value = 0;
3474
- submitCount.value = 0;
3922
+ submissionAttempts.value = 0;
3923
+ submitted.value = false;
3475
3924
  submitError.value = null;
3925
+ departAttempts.value = 0;
3476
3926
  cancelFieldValidation();
3477
3927
  variantMemory.clear();
3478
3928
  for (const listener of resetListeners) {
@@ -3545,9 +3995,9 @@ function createFormStore(options) {
3545
3995
  path: record.path,
3546
3996
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3547
3997
  connected: record.connected,
3548
- focused: null,
3549
- blurred: null,
3550
- touched: null
3998
+ focused: record.focused,
3999
+ blurred: record.blurred,
4000
+ touched: false
3551
4001
  });
3552
4002
  }
3553
4003
  function isPathPrefix(prefix, candidate) {
@@ -3610,8 +4060,18 @@ function createFormStore(options) {
3610
4060
  shouldShowErrors: resolvedShouldShowErrors,
3611
4061
  submitting,
3612
4062
  activeSubmissions,
3613
- submitCount,
4063
+ submissionAttempts,
4064
+ submitted,
3614
4065
  submitError,
4066
+ departAttempts,
4067
+ hydrating,
4068
+ hydrateError,
4069
+ defaultValuesFactory,
4070
+ defaultsResolved,
4071
+ activated,
4072
+ activationPromise,
4073
+ rehydrate,
4074
+ activate,
3615
4075
  submissionGeneration,
3616
4076
  activeValidations,
3617
4077
  firstValidationDone,
@@ -3626,6 +4086,7 @@ function createFormStore(options) {
3626
4086
  setSchemaErrorsForPath,
3627
4087
  setAllSchemaErrors,
3628
4088
  clearSchemaErrors,
4089
+ applySchemaErrorsForSubtree,
3629
4090
  setAllUserErrors,
3630
4091
  addUserErrors,
3631
4092
  clearUserErrors,
@@ -3871,6 +4332,11 @@ const PROTOCOL_VERSION = 1;
3871
4332
  const JOIN_COLLECTION_WINDOW_MS = 50;
3872
4333
  const SNAPSHOT_TIMEOUT_MS = 200;
3873
4334
  const MAX_LEADER_ATTEMPTS = 3;
4335
+ function isFileLikeValue(value) {
4336
+ if (typeof File !== "undefined" && value instanceof File) return true;
4337
+ if (typeof Blob !== "undefined" && value instanceof Blob) return true;
4338
+ return false;
4339
+ }
3874
4340
  function isDangerousSegment(s) {
3875
4341
  return s === "__proto__" || s === "constructor" || s === "prototype";
3876
4342
  }
@@ -3880,6 +4346,32 @@ function pathContainsDangerousSegment(path) {
3880
4346
  }
3881
4347
  return false;
3882
4348
  }
4349
+ function isInboundShapeAcceptable(schema, path, value) {
4350
+ if (isFileLikeValue(value)) return false;
4351
+ let kind;
4352
+ if (Array.isArray(value)) {
4353
+ kind = "array";
4354
+ } else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
4355
+ kind = "object";
4356
+ } else {
4357
+ kind = slimKindOf(value);
4358
+ }
4359
+ const accepted = schema.getSlimPrimitiveTypesAtPath(path);
4360
+ if (!accepted.has(kind)) return false;
4361
+ if (Array.isArray(value)) {
4362
+ for (let i = 0; i < value.length; i++) {
4363
+ if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
4364
+ }
4365
+ return true;
4366
+ }
4367
+ if (isPlainRecord(value)) {
4368
+ for (const key of Object.keys(value)) {
4369
+ if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
4370
+ }
4371
+ return true;
4372
+ }
4373
+ return true;
4374
+ }
3883
4375
  function diffBlankPaths(prev, curr) {
3884
4376
  const added = [];
3885
4377
  const removed = [];
@@ -3892,6 +4384,7 @@ function snapshotForm(form) {
3892
4384
  return structuralSnapshot(form);
3893
4385
  }
3894
4386
  function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
4387
+ if (isFileLikeValue(value)) return void 0;
3895
4388
  if (value === null || typeof value !== "object") return value;
3896
4389
  if (Array.isArray(value)) {
3897
4390
  return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
@@ -3996,6 +4489,7 @@ function createMultiTabSyncModule(state, channelName, options) {
3996
4489
  const safePatches = [];
3997
4490
  for (const p of rawPatches) {
3998
4491
  if (isPathLocallySuppressed(p.path)) continue;
4492
+ if ("value" in p && isFileLikeValue(p.value)) continue;
3999
4493
  safePatches.push(p);
4000
4494
  }
4001
4495
  const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
@@ -4035,6 +4529,9 @@ function createMultiTabSyncModule(state, channelName, options) {
4035
4529
  for (const p of msg.formPatches) {
4036
4530
  if (!Array.isArray(p.path)) continue;
4037
4531
  if (isPathLocallySuppressed(p.path)) continue;
4532
+ if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
4533
+ continue;
4534
+ }
4038
4535
  safePatches.push(p);
4039
4536
  }
4040
4537
  const safeBlankAdded = [];
@@ -4069,11 +4566,7 @@ function createMultiTabSyncModule(state, channelName, options) {
4069
4566
  }
4070
4567
  function handleSnapshot(msg) {
4071
4568
  if (lifecycle !== "joining") return;
4072
- try {
4073
- options.validateForm(msg.form);
4074
- } catch {
4075
- return;
4076
- }
4569
+ if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
4077
4570
  if (snapshotTimeoutTimer !== null) {
4078
4571
  clearTimeout(snapshotTimeoutTimer);
4079
4572
  snapshotTimeoutTimer = null;
@@ -4212,15 +4705,26 @@ function isSecureContext() {
4212
4705
  return typeof window !== "undefined" && window.isSecureContext === true;
4213
4706
  }
4214
4707
 
4215
- function useAbstractForm(configuration) {
4708
+ function resolveTrichotomy(input) {
4709
+ if (typeof input === "function") {
4710
+ return { kind: "async", factory: input };
4711
+ }
4712
+ return { kind: "sync", value: input };
4713
+ }
4714
+
4715
+ function useAbstractForm(configuration, options) {
4216
4716
  if (configuration === void 0 || configuration === null || configuration.schema === void 0) {
4217
4717
  throw new InvalidUseFormConfigError();
4218
4718
  }
4219
4719
  const key = resolveFormKey(configuration.key);
4220
4720
  const instance = getCurrentInstance();
4221
4721
  if (instance !== null) ensureAttaformInstalled(instance.appContext.app);
4222
- const registry = useRegistry();
4223
- const merged = mergeWithDefaults(registry.defaults, configuration);
4722
+ const registry = options?.registry ?? useRegistry();
4723
+ const resolvedDefaults = resolveTrichotomy(configuration.defaultValues);
4724
+ const materialisedDefaults = resolvedDefaults.kind === "sync" ? resolvedDefaults.value : void 0;
4725
+ const { defaultValues: _droppedDefaults, ...configWithoutDefaults } = configuration;
4726
+ const trichotomyOverride = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
4727
+ const merged = mergeWithDefaults(registry.defaults, trichotomyOverride);
4224
4728
  const maxRecursionDepth = normalizeNumericOption({
4225
4729
  value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
4226
4730
  source: "useForm.maxRecursionDepth",
@@ -4241,7 +4745,27 @@ function useAbstractForm(configuration) {
4241
4745
  warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
4242
4746
  warnOnPersistDivergence(key, existing, configuration.persist);
4243
4747
  }
4748
+ const hadPendingHydration = registry.pendingHydration.has(key);
4244
4749
  const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
4750
+ if (existing !== void 0) ; else if (resolvedDefaults.kind === "sync") {
4751
+ state.defaultsResolved.value = true;
4752
+ }
4753
+ if (existing === void 0 && resolvedDefaults.kind === "async") {
4754
+ const factory = resolvedDefaults.factory;
4755
+ state.defaultValuesFactory.value = factory;
4756
+ if (hadPendingHydration) {
4757
+ state.hydrating.value = false;
4758
+ state.defaultsResolved.value = true;
4759
+ } else if (registry.ssr) {
4760
+ if (configuration.__ssrAccessed === true) {
4761
+ registry.enqueuePrefetch(key);
4762
+ }
4763
+ onServerPrefetch(() => {
4764
+ if (!registry.shouldPrefetch(key)) return;
4765
+ return state.activate();
4766
+ });
4767
+ }
4768
+ }
4245
4769
  if (getCurrentScope() !== void 0) {
4246
4770
  const releaseConsumer = registry.trackConsumer(key);
4247
4771
  onScopeDispose(releaseConsumer);
@@ -4269,7 +4793,7 @@ function useAbstractForm(configuration) {
4269
4793
  void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
4270
4794
  }
4271
4795
  }
4272
- if (existing === void 0 && merged.multiTab !== false && configuration.key !== void 0 && !registry.ssr) {
4796
+ if (existing === void 0 && merged.multiTab === true && configuration.key !== void 0 && !registry.ssr) {
4273
4797
  const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
4274
4798
  const secureContext = isSecureContext();
4275
4799
  if (hasBroadcastChannel && secureContext) {
@@ -4376,7 +4900,10 @@ function buildFreshState(key, schema, configuration, registry) {
4376
4900
  configuration.defaultValues,
4377
4901
  schema
4378
4902
  );
4379
- const initialBlankPaths = pending === void 0 ? walked.paths : void 0;
4903
+ let initialBlankPaths;
4904
+ if (pending === void 0) {
4905
+ initialBlankPaths = walked.paths;
4906
+ }
4380
4907
  const resolvedSensitiveNames = configuration.sensitiveNames;
4381
4908
  const resolvedIsSensitivePath = resolvedSensitiveNames === void 0 ? void 0 : createIsSensitivePath(resolvedSensitiveNames);
4382
4909
  const resolvedSegmentMatchesSensitive = resolvedSensitiveNames === void 0 ? void 0 : createSegmentMatchesSensitive(resolvedSensitiveNames);
@@ -4389,6 +4916,21 @@ function buildFreshState(key, schema, configuration, registry) {
4389
4916
  ...configuration.validateOn !== void 0 ? { validateOn: configuration.validateOn } : {},
4390
4917
  ...configuration.debounceMs !== void 0 ? { debounceMs: configuration.debounceMs } : {},
4391
4918
  ssr: registry.ssr,
4919
+ // Server-only: bind the SSR prefetch coordination handles. `enqueue`
4920
+ // records intent on every `state.activate()` so a wizard skip-list
4921
+ // override or a future transform mark has a consistent set to diff
4922
+ // against; `shouldFire` lets the activate path bail when the
4923
+ // wizard explicitly skipped this key — even an explicit
4924
+ // `form.activate()` defers to the wizard's render-efficiency
4925
+ // skip-list on the server.
4926
+ ...registry.ssr ? {
4927
+ ssrPrefetch: {
4928
+ enqueue: () => {
4929
+ registry.enqueuePrefetch(key);
4930
+ },
4931
+ shouldFire: () => registry.shouldPrefetch(key)
4932
+ }
4933
+ } : {},
4392
4934
  ...configuration.rememberVariants !== void 0 ? { rememberVariants: configuration.rememberVariants } : {},
4393
4935
  ...configuration.coerce !== void 0 ? { coerce: configuration.coerce } : {},
4394
4936
  ...configuration.shouldShowErrors !== void 0 ? { shouldShowErrors: configuration.shouldShowErrors } : {},
@@ -4569,8 +5111,9 @@ function wirePersistence(state, config) {
4569
5111
  state.originalBlankPaths.delete(k);
4570
5112
  }
4571
5113
  for (const k of payload.data.blankPaths ?? []) {
4572
- state.blankPaths.add(k);
4573
- state.originalBlankPaths.add(k);
5114
+ const key2 = coerceToPathKey(k);
5115
+ state.blankPaths.add(key2);
5116
+ state.originalBlankPaths.add(key2);
4574
5117
  }
4575
5118
  if (include === "form+errors") {
4576
5119
  if (payload.data.schemaErrors !== void 0) {
@@ -4751,7 +5294,9 @@ function isDescendantPathKey(candidate, ancestor) {
4751
5294
  }
4752
5295
 
4753
5296
  let injectedInstanceCounter = 0;
4754
- function injectForm(key) {
5297
+ function injectForm(input) {
5298
+ const key = typeof input === "string" ? input : input?.key;
5299
+ const ssrAccessed = typeof input === "object" && input !== null ? input.__ssrAccessed === true : false;
4755
5300
  const instance = getCurrentInstance();
4756
5301
  if (instance !== null) ensureAttaformInstalled(instance.appContext.app);
4757
5302
  const registry = useRegistry();
@@ -4761,6 +5306,10 @@ function injectForm(key) {
4761
5306
  const releaseConsumer = registry.trackConsumer(state.formKey);
4762
5307
  onScopeDispose(releaseConsumer);
4763
5308
  }
5309
+ if (registry.ssr && ssrAccessed) {
5310
+ registry.enqueuePrefetch(state.formKey);
5311
+ onServerPrefetch(() => state.activate());
5312
+ }
4764
5313
  const apiOptions = {};
4765
5314
  const history = state.modules.get("history");
4766
5315
  if (history !== void 0) {
@@ -4778,20 +5327,17 @@ function resolveState(key, registry) {
4778
5327
  if (key !== void 0) {
4779
5328
  const stored = registry.forms.get(key);
4780
5329
  if (stored === void 0) {
4781
- warnMiss(`no form registered for key '${key}'`, registry.ssr);
5330
+ warnMiss$1(`no form registered for key '${key}'`, registry.ssr);
4782
5331
  return null;
4783
5332
  }
4784
5333
  return stored;
4785
5334
  }
4786
5335
  const ambient = inject(kFormContext, null);
4787
- if (ambient === null) {
4788
- warnMiss("no ambient form context", registry.ssr);
4789
- return null;
4790
- }
5336
+ if (ambient === null) return null;
4791
5337
  warnIfAmbientProviderHadDuplicates();
4792
5338
  return ambient;
4793
5339
  }
4794
- function warnMiss(detail, ssr) {
5340
+ function warnMiss$1(detail, ssr) {
4795
5341
  if (!__DEV__ || ssr) return;
4796
5342
  const frame = captureUserCallSite();
4797
5343
  console.warn(
@@ -4816,5 +5362,1044 @@ function warnIfAmbientProviderHadDuplicates() {
4816
5362
  }
4817
5363
  }
4818
5364
 
4819
- export { AttaformErrorCode as A, isUnset as a, defaultShouldShowErrors as b, defineCoercion as c, defaultCoercionRules as d, useAbstractForm as e, setAtPath as f, getAtPath as g, humanize as h, injectForm as i, isPlainRecord as j, normalizeNumericOption as n, slimKindOf as s, unset as u };
4820
- //# sourceMappingURL=attaform.B3ZaPIzS.mjs.map
5365
+ const LAZY_BRAND = Symbol.for("attaform/wizard-lazy");
5366
+ function lazy(resolve) {
5367
+ return { [LAZY_BRAND]: true, resolve };
5368
+ }
5369
+ function isLazyMarker(value) {
5370
+ return typeof value === "object" && value !== null && LAZY_BRAND in value;
5371
+ }
5372
+
5373
+ const NOOP_WIZARD_HISTORY = {
5374
+ push() {
5375
+ },
5376
+ replace() {
5377
+ },
5378
+ read() {
5379
+ return void 0;
5380
+ },
5381
+ subscribe() {
5382
+ },
5383
+ dispose() {
5384
+ }
5385
+ };
5386
+ function createWizardHistory(param) {
5387
+ if (typeof window === "undefined") return NOOP_WIZARD_HISTORY;
5388
+ const subscribers = [];
5389
+ let disposed = false;
5390
+ function buildUrl(key) {
5391
+ const url = new URL(window.location.href);
5392
+ url.searchParams.set(param, key);
5393
+ return url.toString();
5394
+ }
5395
+ function handlePopstate() {
5396
+ if (disposed) return;
5397
+ const url = new URL(window.location.href);
5398
+ const value = url.searchParams.get(param) ?? void 0;
5399
+ for (const subscriber of subscribers) subscriber(value);
5400
+ }
5401
+ window.addEventListener("popstate", handlePopstate);
5402
+ function safeWriteState(key, op) {
5403
+ try {
5404
+ const fn = op === "push" ? window.history.pushState : window.history.replaceState;
5405
+ fn.call(window.history, {}, "", buildUrl(key));
5406
+ } catch {
5407
+ }
5408
+ }
5409
+ return {
5410
+ push(key) {
5411
+ if (disposed) return;
5412
+ safeWriteState(key, "push");
5413
+ },
5414
+ replace(key) {
5415
+ if (disposed) return;
5416
+ safeWriteState(key, "replace");
5417
+ },
5418
+ read() {
5419
+ const url = new URL(window.location.href);
5420
+ return url.searchParams.get(param) ?? void 0;
5421
+ },
5422
+ subscribe(callback) {
5423
+ if (disposed) return;
5424
+ subscribers.push(callback);
5425
+ },
5426
+ dispose() {
5427
+ if (disposed) return;
5428
+ disposed = true;
5429
+ subscribers.length = 0;
5430
+ window.removeEventListener("popstate", handlePopstate);
5431
+ }
5432
+ };
5433
+ }
5434
+
5435
+ const NOOP_FINGERPRINT = "attaform:wizard-noop";
5436
+ const EMPTY_SLIM_KINDS = /* @__PURE__ */ new Set();
5437
+ function buildNoopWizardSchema(formKey) {
5438
+ const emptyValue = {};
5439
+ const success = {
5440
+ success: true,
5441
+ data: emptyValue,
5442
+ errors: void 0,
5443
+ formKey
5444
+ };
5445
+ const defaultsResponse = {
5446
+ success: true,
5447
+ data: emptyValue,
5448
+ errors: void 0,
5449
+ formKey
5450
+ };
5451
+ return {
5452
+ fingerprint: () => NOOP_FINGERPRINT,
5453
+ getDefaultValues: () => defaultsResponse,
5454
+ getDefaultAtPath: () => void 0,
5455
+ getEmptyValueAtPath: () => void 0,
5456
+ isPreprocessOrCoerceLeaf: () => false,
5457
+ arrayShapeAtPath: () => void 0,
5458
+ getSchemasAtPath: () => [],
5459
+ validateAtPath: () => success,
5460
+ getSlimPrimitiveTypesAtPath: () => new Set(EMPTY_SLIM_KINDS),
5461
+ isLeafAtPath: () => false,
5462
+ isRequiredAtPath: () => false,
5463
+ getUnionDiscriminatorAtPath: () => void 0
5464
+ };
5465
+ }
5466
+
5467
+ function buildWizardStatusesProxy(statuses) {
5468
+ const snapshot = computed(() => {
5469
+ const result = {};
5470
+ for (const key of Object.keys(statuses)) {
5471
+ result[key] = statuses[key].value;
5472
+ }
5473
+ return result;
5474
+ });
5475
+ const target = (() => {
5476
+ });
5477
+ const proxyToString = () => JSON.stringify(snapshot.value);
5478
+ const proxyToPrimitive = (hint) => hint === "number" ? NaN : proxyToString();
5479
+ return new Proxy(target, {
5480
+ apply(_, __, args) {
5481
+ const key = args[0];
5482
+ if (key === void 0) return snapshot.value;
5483
+ const computedEntry = statuses[key];
5484
+ if (computedEntry === void 0) return void 0;
5485
+ return computedEntry.value;
5486
+ },
5487
+ get(_, key) {
5488
+ if (typeof key === "symbol") {
5489
+ if (key === Symbol.toPrimitive) return proxyToPrimitive;
5490
+ return Reflect.get(target, key);
5491
+ }
5492
+ if (key === "toJSON") return () => snapshot.value;
5493
+ if (key === "toString") return proxyToString;
5494
+ if (key === "valueOf")
5495
+ return function() {
5496
+ return this;
5497
+ };
5498
+ const computedEntry = statuses[key];
5499
+ if (computedEntry === void 0) return void 0;
5500
+ return computedEntry.value;
5501
+ },
5502
+ has(_, key) {
5503
+ if (typeof key === "symbol") return Reflect.has(target, key);
5504
+ return Object.hasOwn(statuses, key);
5505
+ },
5506
+ ownKeys() {
5507
+ return Object.keys(statuses);
5508
+ },
5509
+ getOwnPropertyDescriptor(_, key) {
5510
+ if (typeof key === "symbol") return void 0;
5511
+ const computedEntry = statuses[key];
5512
+ if (computedEntry === void 0) return void 0;
5513
+ return {
5514
+ configurable: true,
5515
+ enumerable: true,
5516
+ writable: false,
5517
+ value: computedEntry.value
5518
+ };
5519
+ },
5520
+ set(_, key) {
5521
+ if (__DEV__) {
5522
+ console.warn(
5523
+ `[attaform] wizard.statuses is read-only \u2014 write to "${String(key)}" was ignored. Statuses derive from each form's meta; mutate the underlying form instead.`
5524
+ );
5525
+ }
5526
+ return true;
5527
+ },
5528
+ deleteProperty(_, key) {
5529
+ if (__DEV__) {
5530
+ console.warn(
5531
+ `[attaform] wizard.statuses is read-only \u2014 delete of "${String(key)}" was ignored.`
5532
+ );
5533
+ }
5534
+ return true;
5535
+ },
5536
+ defineProperty: () => true
5537
+ });
5538
+ }
5539
+
5540
+ const DEFAULT_STEP_PARAM = "step";
5541
+ const PENDING_STATUS = {
5542
+ valid: false,
5543
+ dirty: false,
5544
+ submitted: false,
5545
+ errorCount: 0
5546
+ };
5547
+ const NOOP_VALID_STATUS = {
5548
+ valid: true,
5549
+ dirty: false,
5550
+ submitted: false,
5551
+ errorCount: 0
5552
+ };
5553
+ function useWizard(options) {
5554
+ const rawSteps = Array.isArray(options.steps) ? options.steps : [];
5555
+ if (rawSteps.length === 0 && __DEV__) {
5556
+ console.error(
5557
+ "[attaform] useWizard({ steps }): expected a non-empty array of step slots. Continuing with an empty step list \u2014 wizard.currentStep reads as undefined, navigation refuses, handleSubmit no-ops."
5558
+ );
5559
+ }
5560
+ const registry = useRegistry();
5561
+ const noopForms = /* @__PURE__ */ new Map();
5562
+ const lazyNoopScope = effectScope(true);
5563
+ for (const slot of rawSteps) {
5564
+ if (typeof slot !== "string") continue;
5565
+ if (noopForms.has(slot)) continue;
5566
+ const noop = useAbstractForm({
5567
+ schema: buildNoopWizardSchema(slot),
5568
+ key: slot
5569
+ });
5570
+ noopForms.set(slot, noop);
5571
+ }
5572
+ function getOrBuildNoop(key) {
5573
+ const existing2 = noopForms.get(key);
5574
+ if (existing2 !== void 0) return existing2;
5575
+ const noop = lazyNoopScope.run(
5576
+ () => useAbstractForm(
5577
+ {
5578
+ schema: buildNoopWizardSchema(key),
5579
+ key
5580
+ },
5581
+ { registry }
5582
+ )
5583
+ );
5584
+ if (noop === void 0) {
5585
+ const stub = { key };
5586
+ return stub;
5587
+ }
5588
+ noopForms.set(key, noop);
5589
+ formsAccumulator.set(key, noop);
5590
+ return noop;
5591
+ }
5592
+ const trackedKeys = /* @__PURE__ */ new Set();
5593
+ function trackOnce(form) {
5594
+ if (trackedKeys.has(form.key)) return;
5595
+ trackedKeys.add(form.key);
5596
+ if (getCurrentScope() !== void 0) {
5597
+ const release = registry.trackConsumer(form.key);
5598
+ onScopeDispose(release);
5599
+ }
5600
+ }
5601
+ for (const slot of rawSteps) {
5602
+ if (typeof slot === "string") {
5603
+ const noop = noopForms.get(slot);
5604
+ if (noop !== void 0) trackOnce(noop);
5605
+ } else if (isAnyForm(slot)) {
5606
+ trackOnce(slot);
5607
+ }
5608
+ }
5609
+ const lazyEpoch = ref(0);
5610
+ const lazyComputeds = /* @__PURE__ */ new Map();
5611
+ const activeKey = ref("");
5612
+ const formsAccumulator = /* @__PURE__ */ new Map();
5613
+ for (const slot of rawSteps) {
5614
+ if (typeof slot === "string") {
5615
+ const noop = noopForms.get(slot);
5616
+ if (noop !== void 0) formsAccumulator.set(noop.key, noop);
5617
+ } else if (isAnyForm(slot)) {
5618
+ formsAccumulator.set(slot.key, slot);
5619
+ }
5620
+ }
5621
+ const slotForms = new Proxy({}, {
5622
+ get(_, key) {
5623
+ if (typeof key !== "string") return void 0;
5624
+ return formsAccumulator.get(key);
5625
+ },
5626
+ has(_, key) {
5627
+ if (typeof key !== "string") return false;
5628
+ return formsAccumulator.has(key);
5629
+ },
5630
+ ownKeys() {
5631
+ return [...formsAccumulator.keys()];
5632
+ },
5633
+ getOwnPropertyDescriptor(_, key) {
5634
+ if (typeof key !== "string") return void 0;
5635
+ const form = formsAccumulator.get(key);
5636
+ if (form === void 0) return void 0;
5637
+ return { configurable: true, enumerable: true, writable: false, value: form };
5638
+ }
5639
+ });
5640
+ const slotCtx = computed(() => ({
5641
+ forms: slotForms,
5642
+ currentKey: activeKey.value === "" ? void 0 : activeKey.value
5643
+ }));
5644
+ function resolveSlot(slot, index, ctx) {
5645
+ if (typeof slot === "string") {
5646
+ return getOrBuildNoop(slot);
5647
+ }
5648
+ if (isLazyMarker(slot)) {
5649
+ const c = lazyComputeds.get(index);
5650
+ return c === void 0 ? void 0 : resolveSlotResult(c.value);
5651
+ }
5652
+ if (typeof slot === "function") {
5653
+ const result = slot(ctx);
5654
+ return resolveSlotResult(result);
5655
+ }
5656
+ if (isAnyForm(slot)) return slot;
5657
+ return void 0;
5658
+ }
5659
+ function resolveSlotResult(result) {
5660
+ if (result === void 0) return void 0;
5661
+ if (typeof result === "string") {
5662
+ return getOrBuildNoop(result);
5663
+ }
5664
+ return result;
5665
+ }
5666
+ const lazyCtx = {
5667
+ forms: slotForms,
5668
+ get currentKey() {
5669
+ return activeKey.value === "" ? void 0 : activeKey.value;
5670
+ }
5671
+ };
5672
+ for (let i = 0; i < rawSteps.length; i++) {
5673
+ const slot = rawSteps[i];
5674
+ if (isLazyMarker(slot)) {
5675
+ const idx = i;
5676
+ const marker = slot;
5677
+ lazyComputeds.set(
5678
+ idx,
5679
+ computed(() => {
5680
+ void lazyEpoch.value;
5681
+ return marker.resolve(lazyCtx);
5682
+ })
5683
+ );
5684
+ }
5685
+ }
5686
+ const compiledSteps = computed(() => {
5687
+ const ctx = slotCtx.value;
5688
+ const out = [];
5689
+ const seen = /* @__PURE__ */ new Set();
5690
+ for (let i = 0; i < rawSteps.length; i++) {
5691
+ const slot = rawSteps[i];
5692
+ const form = resolveSlot(slot, i, ctx);
5693
+ if (form === void 0) continue;
5694
+ if (seen.has(form.key)) {
5695
+ if (__DEV__) {
5696
+ console.warn(
5697
+ `[attaform] useWizard: step "${form.key}" appears in more than one slot. The wizard treats the first occurrence as canonical and drops later duplicates.`
5698
+ );
5699
+ }
5700
+ continue;
5701
+ }
5702
+ seen.add(form.key);
5703
+ trackOnce(form);
5704
+ out.push({ key: form.key, form });
5705
+ }
5706
+ return out;
5707
+ });
5708
+ const activeIndex = computed(() => {
5709
+ const key = activeKey.value;
5710
+ if (key === "") return -1;
5711
+ const list = compiledSteps.value;
5712
+ for (let i = 0; i < list.length; i++) {
5713
+ if (list[i].key === key) return i;
5714
+ }
5715
+ return -1;
5716
+ });
5717
+ const currentStep = computed(() => {
5718
+ const key = activeKey.value;
5719
+ if (key !== "") return key;
5720
+ const first = compiledSteps.value[0];
5721
+ return first === void 0 ? void 0 : first.key;
5722
+ });
5723
+ const activeForm = computed(() => {
5724
+ const list = compiledSteps.value;
5725
+ const idx = activeIndex.value;
5726
+ if (idx >= 0 && idx < list.length) {
5727
+ return list[idx].form;
5728
+ }
5729
+ const first = list[0];
5730
+ return first === void 0 ? void 0 : first.form;
5731
+ });
5732
+ const isFinalStep = computed(() => {
5733
+ const list = compiledSteps.value;
5734
+ const idx = activeIndex.value;
5735
+ return list.length > 0 && idx === list.length - 1;
5736
+ });
5737
+ const count = computed(() => compiledSteps.value.length);
5738
+ const formsRecord = computed(() => {
5739
+ const out = {};
5740
+ for (const step of compiledSteps.value) out[step.key] = step.form;
5741
+ return out;
5742
+ });
5743
+ const allValues = computed(() => {
5744
+ const out = {};
5745
+ for (const step of compiledSteps.value) {
5746
+ const source = step.form;
5747
+ out[step.key] = source.values;
5748
+ }
5749
+ return out;
5750
+ });
5751
+ const allErrors = computed(() => {
5752
+ const out = {};
5753
+ for (const step of compiledSteps.value) {
5754
+ const source = step.form;
5755
+ const list = [];
5756
+ const store = registry.forms.get(step.key);
5757
+ const resolved = store?.defaultsResolved.value === true;
5758
+ if (resolved) {
5759
+ const errors = source.meta?.errors ?? [];
5760
+ for (const err of errors) {
5761
+ const entry = {
5762
+ formKey: step.key,
5763
+ path: err.path,
5764
+ message: err.message
5765
+ };
5766
+ if (err.code !== void 0) entry.code = err.code;
5767
+ list.push(entry);
5768
+ }
5769
+ }
5770
+ out[step.key] = list;
5771
+ }
5772
+ return out;
5773
+ });
5774
+ const seedRef = ref(void 0);
5775
+ const seedInput = options.defaultStatuses;
5776
+ if (seedInput !== void 0) {
5777
+ const resolved = resolveTrichotomy(seedInput);
5778
+ if (resolved.kind === "sync") {
5779
+ seedRef.value = resolved.value;
5780
+ } else {
5781
+ const eager = resolved.factory();
5782
+ if (eager instanceof Promise) {
5783
+ void eager.then((value) => {
5784
+ seedRef.value = value;
5785
+ });
5786
+ } else {
5787
+ seedRef.value = eager;
5788
+ }
5789
+ }
5790
+ }
5791
+ const statusCache = /* @__PURE__ */ new Map();
5792
+ function statusFor(form) {
5793
+ const cached = statusCache.get(form.key);
5794
+ if (cached !== void 0) return cached;
5795
+ const source = form;
5796
+ const computedStatus = computed(() => {
5797
+ const store = registry.forms.get(form.key);
5798
+ const resolved = store?.defaultsResolved.value === true;
5799
+ if (resolved) {
5800
+ const meta = source.meta;
5801
+ if (meta !== void 0 && meta !== null) {
5802
+ return {
5803
+ valid: meta.valid,
5804
+ dirty: meta.dirty,
5805
+ submitted: meta.submitted,
5806
+ errorCount: meta.errorCount
5807
+ };
5808
+ }
5809
+ }
5810
+ if (noopForms.has(form.key)) return NOOP_VALID_STATUS;
5811
+ const seedMap = seedRef.value;
5812
+ if (seedMap !== void 0 && Object.hasOwn(seedMap, form.key)) {
5813
+ return seedMap[form.key];
5814
+ }
5815
+ return PENDING_STATUS;
5816
+ });
5817
+ statusCache.set(form.key, computedStatus);
5818
+ return computedStatus;
5819
+ }
5820
+ const statusesRecord = new Proxy({}, {
5821
+ get(_, key) {
5822
+ if (typeof key !== "string") return void 0;
5823
+ const form = formsRecord.value[key];
5824
+ if (form === void 0) {
5825
+ const cached = statusCache.get(key);
5826
+ if (cached !== void 0) return cached;
5827
+ return void 0;
5828
+ }
5829
+ return statusFor(form);
5830
+ },
5831
+ ownKeys() {
5832
+ return Object.keys(formsRecord.value);
5833
+ },
5834
+ has(_, key) {
5835
+ if (typeof key !== "string") return false;
5836
+ return formsRecord.value[key] !== void 0;
5837
+ },
5838
+ getOwnPropertyDescriptor(_, key) {
5839
+ if (typeof key !== "string") return void 0;
5840
+ const form = formsRecord.value[key];
5841
+ if (form === void 0) return void 0;
5842
+ return {
5843
+ configurable: true,
5844
+ enumerable: true,
5845
+ writable: false,
5846
+ value: statusFor(form)
5847
+ };
5848
+ }
5849
+ });
5850
+ const statuses = buildWizardStatusesProxy(statusesRecord);
5851
+ if (__DEV__ && seedRef.value !== void 0) {
5852
+ const seedMap = seedRef.value;
5853
+ const known = new Set(compiledSteps.value.map((s) => s.key));
5854
+ const unknown = [];
5855
+ for (const key of Object.keys(seedMap)) {
5856
+ if (!known.has(key)) unknown.push(key);
5857
+ }
5858
+ if (unknown.length > 0) {
5859
+ console.warn(
5860
+ `[attaform] useWizard.defaultStatuses: seed contains unknown key(s) ${unknown.map((k) => `"${k}"`).join(", ")}. Known step keys: ${[...known].map((k) => `"${k}"`).join(", ")}.`
5861
+ );
5862
+ }
5863
+ }
5864
+ const progressOverride = options.progress;
5865
+ const progress = computed(() => {
5866
+ if (progressOverride !== void 0) {
5867
+ return progressOverride(compiledSteps.value);
5868
+ }
5869
+ const list = compiledSteps.value;
5870
+ if (list.length === 0) return 0;
5871
+ let valid = 0;
5872
+ for (const step of list) {
5873
+ const status = statusFor(step.form).value;
5874
+ if (status.valid === true) valid += 1;
5875
+ }
5876
+ return valid / list.length;
5877
+ });
5878
+ const complete = computed(() => {
5879
+ if (!isFinalStep.value) return false;
5880
+ for (const step of compiledSteps.value) {
5881
+ if (statusFor(step.form).value.valid !== true) return false;
5882
+ }
5883
+ return true;
5884
+ });
5885
+ const canAdvance = computed(() => activeIndex.value < count.value - 1);
5886
+ const canGoBack = computed(() => activeIndex.value > 0);
5887
+ const visited = ref([]);
5888
+ const wantsDefaultUrlSync = options.restore !== false || options.persist !== false;
5889
+ const historyHandle = wantsDefaultUrlSync ? createWizardHistory(DEFAULT_STEP_PARAM) : NOOP_WIZARD_HISTORY;
5890
+ const injectedResolver = inject(kAttaformWizardActiveStepResolver, null);
5891
+ const urlMirror = ref(void 0);
5892
+ const initialUrlValue = injectedResolver !== null ? injectedResolver(DEFAULT_STEP_PARAM) : historyHandle.read();
5893
+ urlMirror.value = initialUrlValue;
5894
+ historyHandle.subscribe((value) => {
5895
+ urlMirror.value = value;
5896
+ });
5897
+ const restoreCallback = options.restore === false ? void 0 : options.restore !== void 0 ? options.restore : () => {
5898
+ const value = urlMirror.value;
5899
+ return value === void 0 ? void 0 : { step: value };
5900
+ };
5901
+ const persistCallback = options.persist === false ? void 0 : options.persist !== void 0 ? options.persist : (state) => {
5902
+ if (state.step === void 0) return;
5903
+ historyHandle.replace(state.step);
5904
+ };
5905
+ function isCompiledKey(key) {
5906
+ const list = compiledSteps.value;
5907
+ for (const step of list) if (step.key === key) return true;
5908
+ return false;
5909
+ }
5910
+ function firstKey() {
5911
+ const first = compiledSteps.value[0];
5912
+ return first === void 0 ? void 0 : first.key;
5913
+ }
5914
+ let initialKey;
5915
+ const restoredAtSetup = restoreCallback?.();
5916
+ const restoredStep = restoredAtSetup?.step;
5917
+ if (restoredStep !== void 0 && isCompiledKey(restoredStep)) {
5918
+ initialKey = restoredStep;
5919
+ } else {
5920
+ if (__DEV__ && restoredStep !== void 0 && restoredStep !== "" && !isCompiledKey(restoredStep)) {
5921
+ console.warn(
5922
+ `[attaform] useWizard: restore() yielded step "${restoredStep}" which is not in the compiled step list. Falling back to the first step.`
5923
+ );
5924
+ }
5925
+ initialKey = firstKey();
5926
+ }
5927
+ if (initialKey !== void 0) {
5928
+ activeKey.value = initialKey;
5929
+ visited.value = [initialKey];
5930
+ }
5931
+ if (registry.ssr) {
5932
+ for (const step of compiledSteps.value) {
5933
+ if (step.key === initialKey) {
5934
+ registry.enqueuePrefetch(step.key);
5935
+ } else {
5936
+ registry.skipPrefetch(step.key);
5937
+ }
5938
+ }
5939
+ }
5940
+ if (!registry.ssr) {
5941
+ for (const step of compiledSteps.value) {
5942
+ const source = step.form;
5943
+ if (typeof source.activate === "function") void source.activate();
5944
+ }
5945
+ }
5946
+ let lastPersisted = initialUrlValue;
5947
+ if (restoreCallback !== void 0) {
5948
+ watch(
5949
+ () => restoreCallback()?.step,
5950
+ (step) => {
5951
+ if (step === void 0) return;
5952
+ if (!isCompiledKey(step)) {
5953
+ if (__DEV__) {
5954
+ console.warn(
5955
+ `[attaform] useWizard: restore() yielded step "${step}" which is not in the compiled step list. Ignoring.`
5956
+ );
5957
+ }
5958
+ return;
5959
+ }
5960
+ if (step === activeKey.value) return;
5961
+ activeKey.value = step;
5962
+ if (!visited.value.includes(step)) visited.value.push(step);
5963
+ }
5964
+ );
5965
+ }
5966
+ if (persistCallback !== void 0) {
5967
+ watch(
5968
+ () => activeKey.value,
5969
+ (next2) => {
5970
+ if (next2 === lastPersisted) return;
5971
+ lastPersisted = next2;
5972
+ persistCallback({ step: next2 });
5973
+ urlMirror.value = next2;
5974
+ }
5975
+ );
5976
+ if (initialKey !== void 0 && initialKey !== initialUrlValue && initialUrlValue === void 0) {
5977
+ lastPersisted = initialKey;
5978
+ persistCallback({ step: initialKey });
5979
+ urlMirror.value = initialKey;
5980
+ }
5981
+ }
5982
+ const submitting = ref(false);
5983
+ const submissionAttempts = ref(0);
5984
+ const done = ref(false);
5985
+ function activateForm(form) {
5986
+ const source = form;
5987
+ if (typeof source.activate === "function") {
5988
+ void source.activate();
5989
+ }
5990
+ }
5991
+ function moveTo(key, options2) {
5992
+ if (activeKey.value === key) return;
5993
+ activeKey.value = key;
5994
+ if (!visited.value.includes(key)) visited.value.push(key);
5995
+ const list = compiledSteps.value;
5996
+ for (const step of list) {
5997
+ if (step.key === key) {
5998
+ activateForm(step.form);
5999
+ return;
6000
+ }
6001
+ }
6002
+ }
6003
+ function recordDeparture(key) {
6004
+ const store = registry.forms.get(key);
6005
+ if (store !== void 0) store.departAttempts.value += 1;
6006
+ }
6007
+ async function next() {
6008
+ if (submitting.value) {
6009
+ if (__DEV__) {
6010
+ console.warn(
6011
+ `[attaform] wizard.next(): blocked while a submit is in flight. Wait for handleSubmit to settle.`
6012
+ );
6013
+ }
6014
+ return;
6015
+ }
6016
+ const list = compiledSteps.value;
6017
+ if (list.length === 0) {
6018
+ if (__DEV__) {
6019
+ console.warn(`[attaform] wizard.next(): wizard has no compiled steps; no-op.`);
6020
+ }
6021
+ return;
6022
+ }
6023
+ const idx = activeIndex.value;
6024
+ if (idx < 0 || idx >= list.length - 1) {
6025
+ if (__DEV__) {
6026
+ console.warn(
6027
+ `[attaform] wizard.next(): already on the final step ("${activeKey.value}"). Use wizard.handleSubmit() to submit.`
6028
+ );
6029
+ }
6030
+ return;
6031
+ }
6032
+ recordDeparture(activeKey.value);
6033
+ const target = list[idx + 1];
6034
+ moveTo(target.key);
6035
+ }
6036
+ function back() {
6037
+ if (submitting.value) {
6038
+ if (__DEV__) {
6039
+ console.warn(`[attaform] wizard.back(): blocked while a submit is in flight.`);
6040
+ }
6041
+ return;
6042
+ }
6043
+ if (compiledSteps.value.length === 0) {
6044
+ if (__DEV__) {
6045
+ console.warn(`[attaform] wizard.back(): wizard has no compiled steps; no-op.`);
6046
+ }
6047
+ return;
6048
+ }
6049
+ const idx = activeIndex.value;
6050
+ if (idx <= 0) {
6051
+ if (__DEV__) {
6052
+ console.warn(`[attaform] wizard.back(): already on the first step ("${activeKey.value}").`);
6053
+ }
6054
+ return;
6055
+ }
6056
+ recordDeparture(activeKey.value);
6057
+ const target = compiledSteps.value[idx - 1];
6058
+ moveTo(target.key);
6059
+ }
6060
+ function goTo(key) {
6061
+ if (submitting.value) {
6062
+ if (__DEV__) {
6063
+ console.warn(`[attaform] wizard.goTo(): blocked while a submit is in flight.`);
6064
+ }
6065
+ return;
6066
+ }
6067
+ if (!isCompiledKey(key)) {
6068
+ if (__DEV__) {
6069
+ const known = compiledSteps.value.map((s) => `"${s.key}"`).join(", ");
6070
+ console.warn(`[attaform] wizard.goTo("${key}"): unknown step key. Known keys: ${known}.`);
6071
+ }
6072
+ return;
6073
+ }
6074
+ if (key !== activeKey.value) recordDeparture(activeKey.value);
6075
+ moveTo(key);
6076
+ }
6077
+ function buildSubmitContext(valuesMap, currentKey, isFinal) {
6078
+ return {
6079
+ values: valuesMap,
6080
+ get: ((form) => valuesMap[form.key]),
6081
+ currentKey,
6082
+ isFinal
6083
+ };
6084
+ }
6085
+ async function processOne(form) {
6086
+ const full = form;
6087
+ let activationFailure;
6088
+ try {
6089
+ if (typeof full.activate === "function") await full.activate();
6090
+ } catch (err) {
6091
+ activationFailure = err?.message ?? String(err);
6092
+ }
6093
+ if (activationFailure === void 0 && full.hydrateError != null) {
6094
+ activationFailure = full.hydrateError.message;
6095
+ }
6096
+ if (activationFailure !== void 0) {
6097
+ return {
6098
+ success: false,
6099
+ data: void 0,
6100
+ errors: [
6101
+ {
6102
+ formKey: form.key,
6103
+ path: [],
6104
+ message: `Form '${form.key}' failed to activate: ${activationFailure}`,
6105
+ code: AttaformErrorCode.ActivationFailed
6106
+ }
6107
+ ],
6108
+ formKey: form.key
6109
+ };
6110
+ }
6111
+ return full.process();
6112
+ }
6113
+ function collectErrors(results) {
6114
+ const out = [];
6115
+ for (const step of compiledSteps.value) {
6116
+ const processed = results.get(step.key);
6117
+ if (processed === void 0 || processed.success === true) continue;
6118
+ for (const err of processed.errors) {
6119
+ const entry = {
6120
+ formKey: err.formKey,
6121
+ path: err.path,
6122
+ message: err.message
6123
+ };
6124
+ if (err.code !== void 0) entry.code = err.code;
6125
+ out.push(entry);
6126
+ }
6127
+ }
6128
+ return out;
6129
+ }
6130
+ function handleSubmit(onSubmit, onError) {
6131
+ return async function submitHandler(event) {
6132
+ if (event !== void 0 && typeof event.preventDefault === "function") {
6133
+ event.preventDefault();
6134
+ }
6135
+ if (compiledSteps.value.length === 0) {
6136
+ if (__DEV__) {
6137
+ console.warn(`[attaform] wizard.handleSubmit: wizard has no compiled steps; no-op.`);
6138
+ }
6139
+ return;
6140
+ }
6141
+ if (submitting.value) {
6142
+ if (__DEV__) {
6143
+ console.warn(
6144
+ `[attaform] wizard.handleSubmit: re-entrant submit while a prior call is still in flight; resolving no-op.`
6145
+ );
6146
+ }
6147
+ return;
6148
+ }
6149
+ submitting.value = true;
6150
+ try {
6151
+ const currentKey = activeKey.value;
6152
+ const final = isFinalStep.value;
6153
+ const list = compiledSteps.value;
6154
+ const results = /* @__PURE__ */ new Map();
6155
+ if (final) {
6156
+ await Promise.all(
6157
+ list.map(async (step) => {
6158
+ const result = await processOne(step.form);
6159
+ results.set(step.key, result);
6160
+ })
6161
+ );
6162
+ } else {
6163
+ const active = activeForm.value;
6164
+ const result = await processOne(active);
6165
+ results.set(active.key, result);
6166
+ }
6167
+ for (const key of results.keys()) {
6168
+ const store = registry.forms.get(key);
6169
+ if (store !== void 0) store.submissionAttempts.value += 1;
6170
+ }
6171
+ submissionAttempts.value += 1;
6172
+ const errors = collectErrors(results);
6173
+ if (errors.length === 0) {
6174
+ const valuesMap = {};
6175
+ for (const step of list) {
6176
+ const processed = results.get(step.key);
6177
+ if (processed !== void 0 && processed.success === true) {
6178
+ valuesMap[step.key] = processed.data;
6179
+ } else {
6180
+ const source = step.form;
6181
+ valuesMap[step.key] = source.values;
6182
+ }
6183
+ }
6184
+ const ctx = buildSubmitContext(valuesMap, currentKey, final);
6185
+ await onSubmit(ctx);
6186
+ if (final) {
6187
+ done.value = true;
6188
+ } else {
6189
+ recordDeparture(currentKey);
6190
+ const idx = activeIndex.value;
6191
+ const target = list[idx + 1];
6192
+ if (target !== void 0) moveTo(target.key);
6193
+ }
6194
+ } else {
6195
+ if (onError !== void 0) await onError(errors);
6196
+ if (options.focusFirstError !== false) {
6197
+ const firstFailedKey = errors[0]?.formKey;
6198
+ if (firstFailedKey !== void 0 && isCompiledKey(firstFailedKey)) {
6199
+ moveTo(firstFailedKey);
6200
+ await nextTick();
6201
+ const failedForm = formsRecord.value[firstFailedKey];
6202
+ if (failedForm !== void 0 && typeof failedForm.applyInvalidSubmitPolicy === "function") {
6203
+ failedForm.applyInvalidSubmitPolicy();
6204
+ }
6205
+ }
6206
+ }
6207
+ }
6208
+ } finally {
6209
+ submitting.value = false;
6210
+ }
6211
+ };
6212
+ }
6213
+ function reset() {
6214
+ submissionAttempts.value = 0;
6215
+ done.value = false;
6216
+ lazyEpoch.value += 1;
6217
+ for (const step of compiledSteps.value) {
6218
+ const full = step.form;
6219
+ if (typeof full.reset === "function") full.reset();
6220
+ }
6221
+ const firstStep = compiledSteps.value[0];
6222
+ if (firstStep !== void 0) {
6223
+ activeKey.value = firstStep.key;
6224
+ visited.value = [firstStep.key];
6225
+ if (persistCallback !== void 0) {
6226
+ lastPersisted = firstStep.key;
6227
+ persistCallback({ step: firstStep.key });
6228
+ }
6229
+ }
6230
+ }
6231
+ if (getCurrentScope() !== void 0) {
6232
+ onScopeDispose(() => {
6233
+ historyHandle.dispose();
6234
+ lazyNoopScope.stop();
6235
+ });
6236
+ }
6237
+ const explicitKey = options.key;
6238
+ const wizardKey = resolveWizardKey(explicitKey);
6239
+ const handle = {
6240
+ key: wizardKey,
6241
+ next,
6242
+ back,
6243
+ goTo,
6244
+ handleSubmit,
6245
+ reset,
6246
+ get currentStep() {
6247
+ return currentStep.value;
6248
+ },
6249
+ get activeForm() {
6250
+ return activeForm.value;
6251
+ },
6252
+ get activeIndex() {
6253
+ return activeIndex.value;
6254
+ },
6255
+ get isFinalStep() {
6256
+ return isFinalStep.value;
6257
+ },
6258
+ get steps() {
6259
+ return compiledSteps.value;
6260
+ },
6261
+ get forms() {
6262
+ return formsRecord.value;
6263
+ },
6264
+ get count() {
6265
+ return count.value;
6266
+ },
6267
+ statuses,
6268
+ get allValues() {
6269
+ return allValues.value;
6270
+ },
6271
+ get allErrors() {
6272
+ return allErrors.value;
6273
+ },
6274
+ get progress() {
6275
+ return progress.value;
6276
+ },
6277
+ get canAdvance() {
6278
+ return canAdvance.value;
6279
+ },
6280
+ get canGoBack() {
6281
+ return canGoBack.value;
6282
+ },
6283
+ get complete() {
6284
+ return complete.value;
6285
+ },
6286
+ get done() {
6287
+ return done.value;
6288
+ },
6289
+ get submitting() {
6290
+ return submitting.value;
6291
+ },
6292
+ get submissionAttempts() {
6293
+ return submissionAttempts.value;
6294
+ },
6295
+ get visited() {
6296
+ return visited.value;
6297
+ }
6298
+ };
6299
+ const existing = registry.wizards.get(wizardKey);
6300
+ if (existing === void 0) {
6301
+ registry.wizards.set(wizardKey, handle);
6302
+ } else if (__DEV__ && explicitKey !== void 0) {
6303
+ console.warn(
6304
+ `[attaform] useWizard({ key: "${wizardKey}" }): a wizard with this key is already registered. Keeping the existing handle. Pass a unique key to each useWizard call, or share the original handle via injectWizard("${wizardKey}").`
6305
+ );
6306
+ }
6307
+ if (getCurrentScope() !== void 0) {
6308
+ const releaseWizard = registry.trackWizardConsumer(wizardKey);
6309
+ onScopeDispose(releaseWizard);
6310
+ }
6311
+ if (getCurrentInstance() !== null && explicitKey === void 0) {
6312
+ recordAmbientWizardProvide(registry.ssr);
6313
+ provide(kAttaformAncestorWizard, handle);
6314
+ }
6315
+ return handle;
6316
+ }
6317
+ let anonWizardCounter = 0;
6318
+ const ambientWizardProvideHistory = __DEV__ ? /* @__PURE__ */ new WeakMap() : null;
6319
+ function recordAmbientWizardProvide(ssr) {
6320
+ if (!__DEV__ || ssr || ambientWizardProvideHistory === null) return;
6321
+ const instance = getCurrentInstance();
6322
+ if (instance === null) return;
6323
+ const instanceKey = instance;
6324
+ const entry = {
6325
+ source: captureUserCallSite()
6326
+ };
6327
+ const existing = ambientWizardProvideHistory.get(instanceKey);
6328
+ if (existing === void 0) {
6329
+ ambientWizardProvideHistory.set(instanceKey, [entry]);
6330
+ return;
6331
+ }
6332
+ existing.push(entry);
6333
+ }
6334
+ function resolveWizardKey(key) {
6335
+ if (key !== void 0 && key !== null && key !== "") return key;
6336
+ if (getCurrentInstance() !== null) {
6337
+ return `${ANONYMOUS_WIZARD_KEY_PREFIX}${useId()}`;
6338
+ }
6339
+ return `${ANONYMOUS_WIZARD_KEY_PREFIX}${anonWizardCounter++}`;
6340
+ }
6341
+ function isAnyForm(value) {
6342
+ if (value === null || typeof value !== "object") return false;
6343
+ if (typeof value.key !== "string") return false;
6344
+ return true;
6345
+ }
6346
+
6347
+ function injectWizard(input) {
6348
+ const key = typeof input === "string" ? input : input?.key;
6349
+ const instance = getCurrentInstance();
6350
+ if (instance !== null) ensureAttaformInstalled(instance.appContext.app);
6351
+ const registry = useRegistry();
6352
+ if (key !== void 0) {
6353
+ const handle = registry.wizards.get(key);
6354
+ if (handle === void 0) {
6355
+ warnMiss(
6356
+ `no wizard registered for key '${key}'`,
6357
+ registry.ssr,
6358
+ availableKeysHint(registry.wizards)
6359
+ );
6360
+ return null;
6361
+ }
6362
+ if (getCurrentScope() !== void 0) {
6363
+ const release = registry.trackWizardConsumer(key);
6364
+ onScopeDispose(release);
6365
+ }
6366
+ return handle;
6367
+ }
6368
+ const ambient = inject(kAttaformAncestorWizard, null);
6369
+ if (ambient === null) return null;
6370
+ warnIfAmbientWizardProviderHadDuplicates();
6371
+ return ambient;
6372
+ }
6373
+ function availableKeysHint(wizards) {
6374
+ if (wizards.size === 0) return void 0;
6375
+ const keys = [...wizards.keys()].map((k) => `"${k}"`).join(", ");
6376
+ return `Registered keys: ${keys}.`;
6377
+ }
6378
+ function warnMiss(detail, ssr, hint) {
6379
+ if (!__DEV__ || ssr) return;
6380
+ const frame = captureUserCallSite();
6381
+ const parts = [`[attaform] injectWizard: ${detail}. Returning null.`];
6382
+ if (hint !== void 0) parts.push(hint);
6383
+ if (frame !== void 0) parts.push(frame);
6384
+ console.warn(parts.join(" "));
6385
+ }
6386
+ function warnIfAmbientWizardProviderHadDuplicates() {
6387
+ if (!__DEV__ || ambientWizardProvideHistory === null) return;
6388
+ let ancestor = getCurrentInstance()?.parent ?? null;
6389
+ while (ancestor !== null) {
6390
+ const history = ambientWizardProvideHistory.get(ancestor);
6391
+ if (history !== void 0) {
6392
+ if (history.length > 1) {
6393
+ const lines = history.map((entry) => ` - ${entry.source ?? "<unknown location>"}`);
6394
+ console.warn(
6395
+ "[attaform] injectWizard() (no key) resolved against an ancestor with multiple anonymous useWizard() calls; descendants only see the last-provided wizard. Anonymous useWizard() calls were:\n" + lines.join("\n") + '\nFix: pass a key to each call (e.g. useWizard({ steps, key: "x" })) and reach them via injectWizard("x"), or split the wizards across separate components.'
6396
+ );
6397
+ }
6398
+ return;
6399
+ }
6400
+ ancestor = ancestor.parent;
6401
+ }
6402
+ }
6403
+
6404
+ export { AttaformErrorCode as A, injectWizard as a, isUnset as b, useWizard as c, defaultCoercionRules as d, defaultShouldShowErrors as e, defineCoercion as f, useAbstractForm as g, getAtPath as h, injectForm as i, humanize as j, setAtPath as k, lazy as l, isPlainRecord as m, normalizeNumericOption as n, slimKindOf as s, unset as u };
6405
+ //# sourceMappingURL=attaform.DsC3rZHG.mjs.map