attaform 0.21.0 → 0.21.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 (83) hide show
  1. package/dist/chunks/dev-key-collision-warnings.cjs +1 -1
  2. package/dist/chunks/dev-key-collision-warnings.mjs +1 -1
  3. package/dist/chunks/devtools.cjs +1 -1
  4. package/dist/chunks/devtools.mjs +1 -1
  5. package/dist/chunks/fingerprint2.cjs +1 -1
  6. package/dist/chunks/fingerprint2.mjs +1 -1
  7. package/dist/chunks/indexeddb.cjs +1 -1
  8. package/dist/chunks/indexeddb.mjs +1 -1
  9. package/dist/chunks/local-storage.cjs +1 -1
  10. package/dist/chunks/local-storage.mjs +1 -1
  11. package/dist/chunks/multi-tab-sync.cjs +2 -2
  12. package/dist/chunks/multi-tab-sync.mjs +2 -2
  13. package/dist/chunks/session-storage.cjs +1 -1
  14. package/dist/chunks/session-storage.mjs +1 -1
  15. package/dist/chunks/wire-persistence.cjs +2 -2
  16. package/dist/chunks/wire-persistence.mjs +2 -2
  17. package/dist/index.cjs +3 -3
  18. package/dist/index.d.cts +15 -14
  19. package/dist/index.d.mts +15 -14
  20. package/dist/index.d.ts +15 -14
  21. package/dist/index.mjs +5 -5
  22. package/dist/nuxt.d.cts +1 -1
  23. package/dist/nuxt.d.mts +1 -1
  24. package/dist/nuxt.d.ts +1 -1
  25. package/dist/runtime/plugins/attaform.cjs +2 -2
  26. package/dist/runtime/plugins/attaform.mjs +2 -2
  27. package/dist/shared/{attaform.BzvOdiSI.cjs → attaform.BSkvn43g.cjs} +4 -4
  28. package/dist/shared/{attaform.BzvOdiSI.cjs.map → attaform.BSkvn43g.cjs.map} +1 -1
  29. package/dist/shared/{attaform.F8LMHHWV.d.cts → attaform.BWfliRIK.d.cts} +78 -2
  30. package/dist/shared/{attaform.r3PePkDR.mjs → attaform.Be8NZG9M.mjs} +9 -3
  31. package/dist/shared/attaform.Be8NZG9M.mjs.map +1 -0
  32. package/dist/shared/{attaform.CEf6wYfD.cjs → attaform.Bq5sX7TF.cjs} +2 -2
  33. package/dist/shared/{attaform.CEf6wYfD.cjs.map → attaform.Bq5sX7TF.cjs.map} +1 -1
  34. package/dist/shared/{attaform.7lzO9pdM.d.mts → attaform.Bv7dRDWK.d.ts} +78 -2
  35. package/dist/shared/{attaform._rsCZy2j.cjs → attaform.CICFZ1iS.cjs} +9 -3
  36. package/dist/shared/attaform.CICFZ1iS.cjs.map +1 -0
  37. package/dist/shared/{attaform.BUszFoKq.cjs → attaform.ClXwitZj.cjs} +366 -63
  38. package/dist/shared/attaform.ClXwitZj.cjs.map +1 -0
  39. package/dist/shared/{attaform.B1nyO4ec.d.cts → attaform.D0dWZsJt.d.cts} +269 -49
  40. package/dist/shared/{attaform.B1nyO4ec.d.mts → attaform.D0dWZsJt.d.mts} +269 -49
  41. package/dist/shared/{attaform.B1nyO4ec.d.ts → attaform.D0dWZsJt.d.ts} +269 -49
  42. package/dist/shared/{attaform.BA3vRDos.cjs → attaform.D32WwKk6.cjs} +214 -33
  43. package/dist/shared/attaform.D32WwKk6.cjs.map +1 -0
  44. package/dist/shared/{attaform.BnK_bfcb.mjs → attaform.DMEP_ENr.mjs} +4 -4
  45. package/dist/shared/{attaform.PnqML3xW.cjs.map → attaform.DMEP_ENr.mjs.map} +1 -1
  46. package/dist/shared/{attaform.DSqO6Db7.mjs → attaform.DR6RmxWZ.mjs} +367 -64
  47. package/dist/shared/attaform.DR6RmxWZ.mjs.map +1 -0
  48. package/dist/shared/{attaform.CkjTapyq.mjs → attaform.DozgVlCE.mjs} +4 -4
  49. package/dist/shared/{attaform.CkjTapyq.mjs.map → attaform.DozgVlCE.mjs.map} +1 -1
  50. package/dist/shared/{attaform.BK1RE2ha.d.ts → attaform.Duecg2NO.d.mts} +2 -2
  51. package/dist/shared/{attaform.BDIEq9qP.d.cts → attaform.FudOcHaa.d.cts} +2 -2
  52. package/dist/shared/{attaform.BQ6drorq.d.mts → attaform.MtrpT6Ki.d.ts} +2 -2
  53. package/dist/shared/{attaform.CRsXyy-Y.d.ts → attaform.NQ8mybyW.d.mts} +78 -2
  54. package/dist/shared/{attaform.PnqML3xW.cjs → attaform.S-pYLSo4.cjs} +4 -4
  55. package/dist/shared/{attaform.BnK_bfcb.mjs.map → attaform.S-pYLSo4.cjs.map} +1 -1
  56. package/dist/shared/{attaform.ezb5Nh2t.mjs → attaform.Y1ZGhM4k.mjs} +2 -2
  57. package/dist/shared/{attaform.ezb5Nh2t.mjs.map → attaform.Y1ZGhM4k.mjs.map} +1 -1
  58. package/dist/shared/{attaform.Y_Mgg0Yp.mjs → attaform.pmtahXKy.mjs} +214 -34
  59. package/dist/shared/attaform.pmtahXKy.mjs.map +1 -0
  60. package/dist/zod-v3.cjs +3 -3
  61. package/dist/zod-v3.d.cts +3 -3
  62. package/dist/zod-v3.d.mts +3 -3
  63. package/dist/zod-v3.d.ts +3 -3
  64. package/dist/zod-v3.mjs +3 -3
  65. package/dist/zod-v4.cjs +3 -3
  66. package/dist/zod-v4.d.cts +4 -4
  67. package/dist/zod-v4.d.mts +4 -4
  68. package/dist/zod-v4.d.ts +4 -4
  69. package/dist/zod-v4.mjs +3 -3
  70. package/dist/zod.cjs +5 -5
  71. package/dist/zod.cjs.map +1 -1
  72. package/dist/zod.d.cts +52 -10
  73. package/dist/zod.d.mts +52 -10
  74. package/dist/zod.d.ts +52 -10
  75. package/dist/zod.mjs +6 -6
  76. package/dist/zod.mjs.map +1 -1
  77. package/package.json +1 -1
  78. package/dist/shared/attaform.BA3vRDos.cjs.map +0 -1
  79. package/dist/shared/attaform.BUszFoKq.cjs.map +0 -1
  80. package/dist/shared/attaform.DSqO6Db7.mjs.map +0 -1
  81. package/dist/shared/attaform.Y_Mgg0Yp.mjs.map +0 -1
  82. package/dist/shared/attaform._rsCZy2j.cjs.map +0 -1
  83. package/dist/shared/attaform.r3PePkDR.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, reactive, toRaw, watch, markRaw, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, effectScope, nextTick } from 'vue';
2
- import { _ as __DEV__, j as canonicalizePath, G as segmentsForPathKey, t as isPathPrefix, b as FORM_ERRORS_PATH_KEY, S as SubmitErrorHandlerError, A as AnonPersistError, k as captureUserCallSite, I as INTERACTIVE_TAG_NAMES, r as getOrAssignElementId, e as ROOT_PATH_KEY, R as ROOT_PATH, h as allowSensitivePersist, F as FORM_ERRORS_PATH, l as coerceToPathKey, v as isSensitivePath, o as createPersistOptInRegistry, d as InvalidUseFormConfigError, q as ensureAttaformInstalled, J as useRegistry, z as kFormContext, B as kFormInstanceId, g as ReservedFormKeyError, n as createIsSensitivePath, y as kAttaformWizardActiveStepResolver, w as kAttaformAncestorWizard } from './attaform.Y_Mgg0Yp.mjs';
1
+ import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, toRaw, reactive, watch, markRaw, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, effectScope, nextTick } from 'vue';
2
+ import { _ as __DEV__, j as canonicalizePath, G as segmentsForPathKey, t as isPathPrefix, b as FORM_ERRORS_PATH_KEY, S as SubmitErrorHandlerError, H as toError, A as AnonPersistError, k as captureUserCallSite, I as INTERACTIVE_TAG_NAMES, r as getOrAssignElementId, e as ROOT_PATH_KEY, R as ROOT_PATH, h as allowSensitivePersist, F as FORM_ERRORS_PATH, l as coerceToPathKey, v as isSensitivePath, o as createPersistOptInRegistry, d as InvalidUseFormConfigError, q as ensureAttaformInstalled, K as useRegistry, z as kFormContext, B as kFormInstanceId, g as ReservedFormKeyError, n as createIsSensitivePath, y as kAttaformWizardActiveStepResolver, w as kAttaformAncestorWizard } from './attaform.pmtahXKy.mjs';
3
3
 
4
4
  function safeAssign(target, key, value) {
5
5
  if (key === "__proto__") {
@@ -13,10 +13,14 @@ function safeAssign(target, key, value) {
13
13
  }
14
14
  target[key] = value;
15
15
  }
16
+ function isShadowedKey(key) {
17
+ return key in Object.prototype;
18
+ }
16
19
  function safeOwnRead(target, key) {
17
- if (key === "__proto__") {
18
- const desc = Object.getOwnPropertyDescriptor(target, "__proto__");
19
- return desc?.value;
20
+ if (isShadowedKey(key)) {
21
+ const desc = Object.getOwnPropertyDescriptor(target, key);
22
+ if (desc === void 0) return void 0;
23
+ return "value" in desc ? desc.value : target[key];
20
24
  }
21
25
  return target[key];
22
26
  }
@@ -35,6 +39,10 @@ function descendStep(value, segment) {
35
39
  }
36
40
  const record = value;
37
41
  const key = typeof segment === "number" ? String(segment) : segment;
42
+ if (isShadowedKey(key)) {
43
+ if (!safeOwnHas(record, key)) return NOT_FOUND;
44
+ return safeOwnRead(record, key);
45
+ }
38
46
  if (!(key in record)) return NOT_FOUND;
39
47
  return record[key];
40
48
  }
@@ -64,6 +72,7 @@ function hasAtPath(root, path) {
64
72
  return typeof last === "number" && last >= 0 && last < current.length;
65
73
  }
66
74
  const key = typeof last === "number" ? String(last) : last;
75
+ if (isShadowedKey(key)) return safeOwnHas(current, key);
67
76
  return key in current;
68
77
  }
69
78
  function isPlainRecord(value) {
@@ -470,7 +479,12 @@ function computeVerdict(field, formMeta) {
470
479
  if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
471
480
  return "idle";
472
481
  }
473
- const DEFAULT_TIMINGS = { showDelay: 100, minVisible: 120 };
482
+ function earliestNonNull(a, b) {
483
+ if (a === null) return b;
484
+ if (b === null) return a;
485
+ return a < b ? a : b;
486
+ }
487
+ const DEFAULT_TIMINGS = { showDelay: 120, minVisible: 120 };
474
488
  const FOCUS_OUT_GRACE = 16;
475
489
  const defaultFamily = /* @__PURE__ */ new WeakSet();
476
490
  function isDefaultDisplayState(fn) {
@@ -480,10 +494,11 @@ function makeDefaultDisplayState({
480
494
  showDelay,
481
495
  minVisible
482
496
  }) {
483
- const reducer = (prev, { field, formMeta, validatingSince, now }) => {
497
+ const reducer = (prev, { field, formMeta, validatingSince, transformingSince, now }) => {
484
498
  const verdict = computeVerdict(field, formMeta);
485
499
  if (!isGateOpen(field, formMeta)) return { display: verdict };
486
- if (validatingSince === null) {
500
+ const inFlightSince = earliestNonNull(validatingSince, transformingSince);
501
+ if (inFlightSince === null) {
487
502
  if (prev.display === "pending") {
488
503
  const shownAt = prev.pendingShownAt ?? now;
489
504
  if (now < shownAt + minVisible)
@@ -494,8 +509,8 @@ function makeDefaultDisplayState({
494
509
  if (prev.display === "pending")
495
510
  return { display: "pending", pendingShownAt: prev.pendingShownAt ?? now };
496
511
  const window = field.focused === false ? Math.min(showDelay, FOCUS_OUT_GRACE) : showDelay;
497
- if (now - validatingSince < window) {
498
- return { display: prev.display, reviewAt: validatingSince + window };
512
+ if (now - inFlightSince < window) {
513
+ return { display: prev.display, reviewAt: inFlightSince + window };
499
514
  }
500
515
  return { display: "pending", pendingShownAt: now, reviewAt: now + minVisible };
501
516
  };
@@ -621,6 +636,8 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
621
636
  if (blankForKey !== void 0) errors.push(...blankForKey);
622
637
  if (userForKey !== void 0) errors.push(...userForKey);
623
638
  const validating = (state.fieldValidationCounts.get(key) ?? 0) > 0;
639
+ const transforming = (state.fieldTransformCounts.get(key) ?? 0) > 0;
640
+ const transformError = state.transformErrors.get(key) ?? null;
624
641
  const gated = state.pathHasAsyncValidation(segments) && !state.firstValidationDone.value;
625
642
  const isOrphan = segments.length > 0 && !hasAtPath(state.form.value, segments) && isUnderStubAncestor(state, segments);
626
643
  const valid = !gated && errors.length === 0 && !validating && !isOrphan;
@@ -647,6 +664,9 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
647
664
  errors,
648
665
  validating,
649
666
  valid,
667
+ transforming,
668
+ busy: transforming || validating,
669
+ transformError,
650
670
  path: segments,
651
671
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
652
672
  key: state.arrayElementKey(segments),
@@ -660,12 +680,14 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
660
680
  function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
661
681
  const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
662
682
  const validatingSince = state.fieldValidatingSince.get(key) ?? null;
683
+ const transformingSince = state.fieldTransformingSince.get(key) ?? null;
663
684
  return decorateWithDerivedProps(
664
685
  base,
665
686
  state,
666
687
  getFormMetaBase,
667
688
  key,
668
689
  validatingSince,
690
+ transformingSince,
669
691
  false,
670
692
  // revealedDescendantError: leaves have no descendants
671
693
  false,
@@ -688,6 +710,8 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
688
710
  let connected = false;
689
711
  let validating = false;
690
712
  let validatingSince = null;
713
+ let transforming = false;
714
+ let transformingSince = null;
691
715
  let updatedAt = null;
692
716
  let asyncPending = false;
693
717
  const submissionAttempts = state.submissionAttempts.value;
@@ -718,6 +742,13 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
718
742
  if (leafGateOpen && since !== void 0 && (validatingSince === null || since < validatingSince))
719
743
  validatingSince = since;
720
744
  }
745
+ if ((state.fieldTransformCounts.get(leafKey) ?? 0) > 0) {
746
+ transforming = true;
747
+ const leafGateOpen = submissionAttempts > 0 || leafRecord?.blurredAfterInteraction === true;
748
+ const since = state.fieldTransformingSince.get(leafKey);
749
+ if (leafGateOpen && since !== void 0 && (transformingSince === null || since < transformingSince))
750
+ transformingSince = since;
751
+ }
721
752
  if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
722
753
  const ts = leafRecord?.updatedAt;
723
754
  if (ts !== void 0 && ts !== null) {
@@ -737,6 +768,19 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
737
768
  return blurredLeafSegments.some((s) => isPathPrefix(ePath, s));
738
769
  });
739
770
  if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
771
+ if ((state.fieldValidationCounts.get(key) ?? 0) > 0) {
772
+ validating = true;
773
+ const since = state.fieldValidatingSince.get(key);
774
+ if (since !== void 0 && (validatingSince === null || since < validatingSince))
775
+ validatingSince = since;
776
+ }
777
+ if ((state.fieldTransformCounts.get(key) ?? 0) > 0) {
778
+ transforming = true;
779
+ const since = state.fieldTransformingSince.get(key);
780
+ if (since !== void 0 && (transformingSince === null || since < transformingSince))
781
+ transformingSince = since;
782
+ }
783
+ const ownTransformError = state.transformErrors.get(key) ?? null;
740
784
  const gated = asyncPending && !state.firstValidationDone.value;
741
785
  const valid = !gated && errors.length === 0 && !validating;
742
786
  const resolved = state.schema.getFieldMetaAtPath ? state.schema.getFieldMetaAtPath(segments) : EMPTY_RESOLVED_FIELD_META;
@@ -760,6 +804,12 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
760
804
  errors,
761
805
  validating,
762
806
  valid,
807
+ transforming,
808
+ busy: transforming || validating,
809
+ // A container surfaces its OWN transform failure (a transform registered
810
+ // on the container path, e.g. a file normalizer) but never rolls up a
811
+ // descendant leaf's failure — that stays a per-field channel.
812
+ transformError: ownTransformError,
763
813
  path: segments,
764
814
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
765
815
  key: state.arrayElementKey(segments),
@@ -770,28 +820,25 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
770
820
  meta: resolved.meta
771
821
  },
772
822
  validatingSince,
823
+ transformingSince,
773
824
  revealedDescendantError
774
825
  };
775
826
  }
776
827
  function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
777
- const { base, validatingSince, revealedDescendantError } = buildContainerFieldStateBase(
778
- state,
779
- segments,
780
- key,
781
- formInstanceId
782
- );
828
+ const { base, validatingSince, transformingSince, revealedDescendantError } = buildContainerFieldStateBase(state, segments, key, formInstanceId);
783
829
  return decorateWithDerivedProps(
784
830
  base,
785
831
  state,
786
832
  getFormMetaBase,
787
833
  key,
788
834
  validatingSince,
835
+ transformingSince,
789
836
  revealedDescendantError,
790
837
  segments.length === 0,
791
838
  getDisplayState
792
839
  );
793
840
  }
794
- function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, revealedDescendantError, isRoot, getDisplayState) {
841
+ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, transformingSince, revealedDescendantError, isRoot, getDisplayState) {
795
842
  const firstError = base.errors[0];
796
843
  const predicate = getDisplayState ?? state.getDisplayState;
797
844
  const formMeta = getFormMetaBase();
@@ -799,6 +846,7 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingS
799
846
  field: base,
800
847
  formMeta,
801
848
  validatingSince,
849
+ transformingSince,
802
850
  // The engine's clock. Frozen to 0 under SSR (no clock, nothing in
803
851
  // flight) so the reducer returns the plain verdict and hydration matches.
804
852
  now: state.ssr ? 0 : Date.now()
@@ -865,6 +913,15 @@ function liveKeysAtPath(state, segments) {
865
913
  if (typeof value === "object") return Object.keys(value);
866
914
  return [];
867
915
  }
916
+ function liveContainerHasKey(state, segments, key) {
917
+ const value = getAtPath(state.form.value, segments);
918
+ if (value === null || value === void 0 || typeof value !== "object") return false;
919
+ if (Array.isArray(value)) {
920
+ const index = Number(key);
921
+ return Number.isInteger(index) && index >= 0 && index < value.length && String(index) === key;
922
+ }
923
+ return Object.hasOwn(value, key);
924
+ }
868
925
  function isArrayPath(state, segments) {
869
926
  if (segments.length === 0) return false;
870
927
  return Array.isArray(getAtPath(state.form.value, segments));
@@ -893,6 +950,21 @@ const INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
893
950
  function keyToSegment(key) {
894
951
  return INTEGER_SEGMENT.test(key) ? Number(key) : key;
895
952
  }
953
+ function callableInvokeShim(method, surface, getDescent) {
954
+ const fnMethod = Reflect.get(Function.prototype, method);
955
+ return new Proxy((() => {
956
+ }), {
957
+ apply: (_target, _thisArg, args) => Reflect.apply(fnMethod, surface, args),
958
+ get: (_target, key) => Reflect.get(getDescent(), key),
959
+ has: (_target, key) => Reflect.has(getDescent(), key),
960
+ ownKeys: () => Reflect.ownKeys(getDescent()),
961
+ getOwnPropertyDescriptor: (_target, key) => {
962
+ const descriptor = Reflect.getOwnPropertyDescriptor(getDescent(), key);
963
+ if (descriptor !== void 0) descriptor.configurable = true;
964
+ return descriptor;
965
+ }
966
+ });
967
+ }
896
968
  function buildSurfaceProxy(opts) {
897
969
  const containerCache = /* @__PURE__ */ new Map();
898
970
  const leafViewCache = /* @__PURE__ */ new Map();
@@ -919,6 +991,8 @@ function buildSurfaceProxy(opts) {
919
991
  const cacheKey = `${JSON.stringify(segments)}+${isArrayLike ? "A" : "O"}`;
920
992
  const existing = containerCache.get(cacheKey);
921
993
  if (existing !== void 0) return existing;
994
+ const isFixedObject = opts.schema.isFixedObjectAtPath(segments);
995
+ const containerHasKey = (k) => opts.containerHasOwnKey !== void 0 ? opts.containerHasOwnKey(segments, k) : opts.containerOwnKeys?.(segments).includes(k) === true;
922
996
  const snapshotContainer = () => opts.materializeContainer === void 0 ? {} : opts.materializeContainer(segments);
923
997
  const {
924
998
  toString: containerToString,
@@ -926,8 +1000,9 @@ function buildSurfaceProxy(opts) {
926
1000
  toJSON: containerToJSON,
927
1001
  toPrimitive: containerToPrimitive
928
1002
  } = makeReadonlyCoercion(snapshotContainer);
929
- const target = isArrayLike ? [] : (() => {
930
- });
1003
+ const isRoot = segments.length === 0;
1004
+ const target = isRoot ? (() => {
1005
+ }) : isArrayLike ? [] : {};
931
1006
  const proxy = new Proxy(target, {
932
1007
  apply(_, __, args) {
933
1008
  const arg = args[0];
@@ -958,7 +1033,16 @@ function buildSurfaceProxy(opts) {
958
1033
  return key === "toString" ? containerToString : containerValueOf;
959
1034
  }
960
1035
  }
961
- return descendOrTerminate(childSegs);
1036
+ if (key === "hasOwnProperty" && !schemaHasPath(childSegs)) {
1037
+ return Object.prototype.hasOwnProperty;
1038
+ }
1039
+ if (isRoot && (key === "call" || key === "apply" || key === "bind")) {
1040
+ return callableInvokeShim(key, proxy, () => descendOrTerminate(childSegs));
1041
+ }
1042
+ if (opts.isTerminalAt?.(childSegs) === true || isFixedObject && schemaHasPath(childSegs) || containerHasKey(key)) {
1043
+ return descendOrTerminate(childSegs);
1044
+ }
1045
+ return void 0;
962
1046
  },
963
1047
  has(_, key) {
964
1048
  if (typeof key === "symbol") return Reflect.has(target, key);
@@ -1043,15 +1127,8 @@ function buildSurfaceProxy(opts) {
1043
1127
  toJSON: leafToJSONHandler,
1044
1128
  toPrimitive: leafToPrimitive
1045
1129
  } = makeReadonlyCoercion(snapshotLeaf);
1046
- const target = (() => {
1047
- });
1130
+ const target = {};
1048
1131
  const proxy = new Proxy(target, {
1049
- apply(_, __, args) {
1050
- const arg = args[0];
1051
- if (arg === void 0) return opts.resolveCallTarget(segments);
1052
- const { segments: argSegs } = canonicalizePath(arg);
1053
- return opts.resolveCallTarget(argSegs);
1054
- },
1055
1132
  get(_, key) {
1056
1133
  if (typeof key === "symbol") {
1057
1134
  if (key === Symbol.toPrimitive) return leafToPrimitive;
@@ -1061,6 +1138,9 @@ function buildSurfaceProxy(opts) {
1061
1138
  if (key === "toString") return leafToString;
1062
1139
  if (key === "valueOf") return leafValueOf;
1063
1140
  if (key === "toJSON") return leafToJSONHandler;
1141
+ if (key === "hasOwnProperty" && !schemaHasPath([...segments, keyToSegment(key)])) {
1142
+ return Object.prototype.hasOwnProperty;
1143
+ }
1064
1144
  if (leafKeys.has(key)) {
1065
1145
  const leaf = opts.resolveLeaf(segments);
1066
1146
  return readLeafKey(leaf, key);
@@ -1073,8 +1153,8 @@ function buildSurfaceProxy(opts) {
1073
1153
  return true;
1074
1154
  },
1075
1155
  // Iteration: leaf-views expose the leaf-key set so
1076
- // `JSON.stringify(form.fields.email)` produces a FieldState
1077
- // snapshot rather than the function-target placeholder.
1156
+ // `Object.keys(form.fields.email)` / spread enumerate the
1157
+ // FieldState props. (`JSON.stringify` routes through `toJSON` above.)
1078
1158
  ownKeys: () => Array.from(leafKeys),
1079
1159
  getOwnPropertyDescriptor(_, key) {
1080
1160
  if (typeof key !== "string") return void 0;
@@ -1183,6 +1263,12 @@ function buildErrorsProxy(state) {
1183
1263
  // library-produced verdicts (schema + derived-blank) at unreachable
1184
1264
  // paths stay hidden; user-supplied errors are unconditional.
1185
1265
  containerOwnKeys: (segments) => errorAwareContainerKeys(state, segments),
1266
+ // Fast path: a key the live form data holds short-circuits before the
1267
+ // O(n) error-store scan, so iterating `form.errors.<array>` over live
1268
+ // indices stays linear. The scan still runs for a key with no live
1269
+ // home — a server error at a non-schema key (`form.errors.ghost`) —
1270
+ // so it keeps surfacing while a genuinely-absent key reads undefined.
1271
+ containerHasOwnKey: (segments, key) => liveContainerHasKey(state, segments, key) || errorAwareContainerKeys(state, segments).includes(key),
1186
1272
  isArrayContainer: (segments) => isArrayPath(state, segments)
1187
1273
  });
1188
1274
  }
@@ -1356,6 +1442,9 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1356
1442
  "errors",
1357
1443
  "validating",
1358
1444
  "valid",
1445
+ "transforming",
1446
+ "busy",
1447
+ "transformError",
1359
1448
  "displayState",
1360
1449
  "showErrors",
1361
1450
  "showPending",
@@ -1458,14 +1547,22 @@ function buildFieldStateProxy(state, formInstanceId, getFormMetaBase, options) {
1458
1547
  terminalCache.set(cacheKey, proxy);
1459
1548
  return proxy;
1460
1549
  }
1550
+ const surfaceSchema = state.schema;
1461
1551
  return buildSurfaceProxy({
1462
- schema: state.schema,
1552
+ schema: surfaceSchema,
1463
1553
  resolveLeaf: (path) => getFieldStateAt(path),
1464
1554
  leafKeys: FIELD_STATE_KEYS,
1465
1555
  readLeafKey: (computed, key) => computed.value[key],
1466
1556
  materializeContainer: (segments) => materializeFields(state, segments, snapshotFieldStateAt),
1467
- resolveCallTarget: (path) => fieldStateTerminalAt(path),
1557
+ // `form.fields(path)` resolves a FieldState for any path the SCHEMA
1558
+ // declares — a leaf, a container, an inactive discriminated-union
1559
+ // variant key, or an out-of-bounds array index (the element schema
1560
+ // admits any index). A path the schema doesn't have is a typo, not a
1561
+ // field, so it reads `undefined` rather than a phantom stub. The
1562
+ // empty path (`form.fields()`) is the root object, always valid.
1563
+ resolveCallTarget: (path) => surfaceSchema.getSlimPrimitiveTypesAtPath(path).size > 0 ? fieldStateTerminalAt(path) : void 0,
1468
1564
  containerOwnKeys: (segments) => liveKeysAtPath(state, segments),
1565
+ containerHasOwnKey: (segments, key) => liveContainerHasKey(state, segments, key),
1469
1566
  isArrayContainer: (segments) => isArrayPath(state, segments)
1470
1567
  });
1471
1568
  }
@@ -1741,7 +1838,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1741
1838
  if (!result.ok) return result.error;
1742
1839
  return stripData(composeWithDerivedBlank(result.refinement, result.segments));
1743
1840
  }
1744
- async function process(pathInput) {
1841
+ async function parse(pathInput) {
1745
1842
  const result = await runImperativeValidation(pathInput, {
1746
1843
  cancelInFlight: false,
1747
1844
  commitToSchemaErrors: false
@@ -1794,6 +1891,8 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1794
1891
  state.activeSubmissions.value += 1;
1795
1892
  state.submitting.value = true;
1796
1893
  state.submitError.value = null;
1894
+ state.clearUserErrors();
1895
+ while (state.activeTransforms.value > 0) await state.settleTransforms();
1797
1896
  state.cancelFieldValidation();
1798
1897
  state.displayEngine.clear();
1799
1898
  state.activeValidations.value += 1;
@@ -1832,9 +1931,8 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1832
1931
  state.emitSubmitSuccess();
1833
1932
  } catch (err) {
1834
1933
  if (state.submissionGeneration.value === genAtEntry) {
1835
- state.submitError.value = err;
1934
+ state.submitError.value = toError(err);
1836
1935
  }
1837
- throw err;
1838
1936
  } finally {
1839
1937
  if (!validationSettled) {
1840
1938
  state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
@@ -1848,7 +1946,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1848
1946
  };
1849
1947
  return submitHandler;
1850
1948
  };
1851
- return { validate, validateAsync, process, handleSubmit };
1949
+ return { validate, validateAsync, parse, handleSubmit };
1852
1950
  }
1853
1951
  function toSegments(pathInput) {
1854
1952
  return canonicalizePath(pathInput).segments;
@@ -2321,6 +2419,23 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2321
2419
  markConnectedOptimistically: () => {
2322
2420
  state.markConnectedOptimistically(segments);
2323
2421
  },
2422
+ // --- Async transform lifecycle (internal; the directive's
2423
+ // deferred orchestrator is the only legitimate consumer). Thin
2424
+ // path-bound delegates to the store's per-path token / counter
2425
+ // machinery — same pattern as `markBlank` / `setValueWithInternalPath`,
2426
+ // so the directive (which holds only this RegisterValue, never the
2427
+ // store) can drive the busy/discard/error bookkeeping. ---
2428
+ beginTransform: (holder) => state.beginTransform(pathKey, holder),
2429
+ isCurrentTransform: (token) => state.isCurrentTransform(pathKey, token),
2430
+ endTransform: (token) => state.endTransform(pathKey, token),
2431
+ setTransformError: (err) => state.setTransformError(pathKey, err),
2432
+ // Synchronous read of "is a transform in flight at this path". The
2433
+ // orchestrator's `beginTransform` bumps the count before the
2434
+ // listener's force-sync block runs, so the directive reads this to
2435
+ // skip reverting the DOM to stale storage mid-flight.
2436
+ get transforming() {
2437
+ return (state.fieldTransformCounts.get(pathKey) ?? 0) > 0;
2438
+ },
2324
2439
  path: pathKey,
2325
2440
  // Frozen so a wrapper component can pass `rv.segments` directly
2326
2441
  // to `form.fields(...)` without defensive copying — and so test
@@ -2519,7 +2634,9 @@ function expandUnsetAt(segments, schema, paths) {
2519
2634
  function buildCallableReadonlySnapshotProxy(opts) {
2520
2635
  const target = (() => {
2521
2636
  });
2522
- const { toString, valueOf, toJSON, toPrimitive } = makeReadonlyCoercion(opts.snapshot);
2637
+ const { toString, valueOf, toJSON, toPrimitive } = makeReadonlyCoercion(
2638
+ opts.coercionSnapshot ?? opts.snapshot
2639
+ );
2523
2640
  const callResolve = opts.resolveCall ?? ((arg) => opts.resolveKey(String(arg)));
2524
2641
  return new Proxy(target, {
2525
2642
  apply(_, __, args) {
@@ -2568,27 +2685,52 @@ function buildCallableReadonlySnapshotProxy(opts) {
2568
2685
  });
2569
2686
  }
2570
2687
 
2688
+ function materializeFormValue(node) {
2689
+ if (node === null || typeof node !== "object") return node;
2690
+ if (Array.isArray(node)) {
2691
+ const out2 = new Array(node.length);
2692
+ for (let i = 0; i < node.length; i++) out2[i] = materializeFormValue(node[i]);
2693
+ return out2;
2694
+ }
2695
+ if (!isPlainRecord(node)) return toRaw(node);
2696
+ const rec = node;
2697
+ const out = {};
2698
+ for (const key of Object.keys(rec)) {
2699
+ safeAssign(out, key, materializeFormValue(safeOwnRead(rec, key)));
2700
+ }
2701
+ return out;
2702
+ }
2571
2703
  function buildValuesProxy(form) {
2572
2704
  const inner = computed(() => readonly(form.value));
2573
2705
  return buildCallableReadonlySnapshotProxy({
2574
2706
  surface: "form.values",
2575
2707
  snapshot: () => inner.value,
2708
+ // Faithful, reactivity-preserving serialisation: walk the reactive
2709
+ // proxy with own-safe reads so `JSON.stringify(form.values)` /
2710
+ // `String(form.values)` reflect the stored data — including a field
2711
+ // literally named `hasOwnProperty` that Vue would otherwise shim —
2712
+ // while still tracking the per-key reads that drive re-render.
2713
+ coercionSnapshot: () => materializeFormValue(inner.value),
2576
2714
  // Read through the readonly proxy at access time so Vue's
2577
2715
  // dependency tracking lands inside the consumer's active effect
2578
2716
  // — `inner.value[key]` is what triggers per-key tracking.
2579
- resolveKey: (key) => inner.value[key],
2580
- // Dynamic path: walk segments through the readonly proxy. Each
2581
- // step reads through the proxy's own get traps so dependency
2582
- // tracking propagates at every level.
2583
- resolveCall: (arg) => {
2584
- const { segments } = canonicalizePath(arg);
2585
- let cursor = inner.value;
2586
- for (const seg of segments) {
2587
- if (cursor === null || cursor === void 0) return void 0;
2588
- cursor = cursor[seg];
2589
- }
2590
- return cursor;
2591
- },
2717
+ //
2718
+ // Prototype-shadowed names (`hasOwnProperty`, `constructor`, …) read
2719
+ // off the RAW target instead: own-shadows-inherited semantics still
2720
+ // hold (a data field by that name returns its stored value), but
2721
+ // when there's no such field the inherited member resolves — so
2722
+ // `form.values.hasOwnProperty('x')` keeps working as the real
2723
+ // method. The raw read dodges Vue's `hasOwnProperty` proxy shim,
2724
+ // which would otherwise mask a data field of that name. (`toString`
2725
+ // / `valueOf` / `toJSON` never reach here the base get trap
2726
+ // intercepts them as coercion handlers first.)
2727
+ resolveKey: (key) => isShadowedKey(key) ? toRaw(inner.value)[key] : inner.value[key],
2728
+ // Dynamic path: walk segments through the readonly proxy with the
2729
+ // same own-property-safe descent the rest of the runtime uses
2730
+ // (`getAtPath`), so `form.values('a.hasOwnProperty')` resolves the
2731
+ // stored value. Per-level reads still propagate Vue's tracking for
2732
+ // ordinary keys.
2733
+ resolveCall: (arg) => getAtPath(inner.value, canonicalizePath(arg).segments),
2592
2734
  ownKeys: () => Reflect.ownKeys(inner.value),
2593
2735
  hasKey: (key) => Reflect.has(inner.value, key),
2594
2736
  describeKey: (key) => {
@@ -2649,12 +2791,12 @@ function buildFormApi(state, formInstanceId, options = {}) {
2649
2791
  const {
2650
2792
  validate: validateBuilt,
2651
2793
  validateAsync: validateAsyncBuilt,
2652
- process: processBuilt,
2794
+ parse: parseBuilt,
2653
2795
  handleSubmit
2654
2796
  } = buildProcessForm(state, formInstanceId, processOptions);
2655
2797
  const validate = (pathInput) => validateBuilt(pathInput);
2656
2798
  const validateAsync = (pathInput) => validateAsyncBuilt(pathInput);
2657
- const process = (pathInput) => processBuilt(pathInput);
2799
+ const parse = (pathInput) => parseBuilt(pathInput);
2658
2800
  function pathToRef(pathInput) {
2659
2801
  const segments = canonicalizePath(pathInput).segments;
2660
2802
  return computed(() => getAtPath(state.form.value, segments));
@@ -2882,6 +3024,21 @@ function buildFormApi(state, formInstanceId, options = {}) {
2882
3024
  // keep the explicit form-level computation for the gate.
2883
3025
  valid,
2884
3026
  errors: metaErrors,
3027
+ // Whole-form transforming mirrors the global `activeTransforms`
3028
+ // counter ORed with any per-leaf transform in flight (the root
3029
+ // rollup), exactly as `validating` composes its lifecycle and
3030
+ // per-field sources. `busy` is the union of both work signals at
3031
+ // the form level. `transformError` is leaf-only, so the root
3032
+ // rollup reads it as `null` (kept for FieldState-shape parity).
3033
+ transforming: computed(
3034
+ () => state.activeTransforms.value > 0 || rootFieldState.value.transforming
3035
+ ),
3036
+ busy: computed(
3037
+ () => state.activeValidations.value > 0 || state.activeTransforms.value > 0 || rootFieldState.value.validating || rootFieldState.value.transforming
3038
+ ),
3039
+ get transformError() {
3040
+ return rootFieldState.value.transformError;
3041
+ },
2885
3042
  // `displayState` / the `show*` booleans / `firstError` flow
2886
3043
  // through the same root field-state computed as the rest of the
2887
3044
  // FieldState surface, so `form.meta.displayState` matches
@@ -3110,7 +3267,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
3110
3267
  setValue: gated(setValueImpl),
3111
3268
  validate: gated(validate),
3112
3269
  validateAsync: gated(validateAsync),
3113
- process: gated(process),
3270
+ parse: gated(parse),
3271
+ settleTransforms: gated(state.settleTransforms),
3114
3272
  register: gated(register),
3115
3273
  key: state.formKey,
3116
3274
  // Auto-unwrapping views over the per-store async-defaults lifecycle
@@ -3977,6 +4135,106 @@ function createFormStore(options) {
3977
4135
  fieldValidationCounts.set(key, next);
3978
4136
  }
3979
4137
  }
4138
+ const fieldTransformCounts = reactive(/* @__PURE__ */ new Map());
4139
+ const fieldTransformingSince = reactive(/* @__PURE__ */ new Map());
4140
+ const transformErrors = reactive(/* @__PURE__ */ new Map());
4141
+ const activeTransforms = ref(0);
4142
+ const transformRuns = /* @__PURE__ */ new Map();
4143
+ let transformTokenSeq = 0;
4144
+ const transformWaiters = [];
4145
+ function incFieldTransform(key) {
4146
+ fieldTransformingSince.set(key, ssr ? 0 : Date.now());
4147
+ fieldTransformCounts.set(key, (fieldTransformCounts.get(key) ?? 0) + 1);
4148
+ }
4149
+ function decFieldTransform(key) {
4150
+ const next = (fieldTransformCounts.get(key) ?? 0) - 1;
4151
+ if (next <= 0) {
4152
+ fieldTransformCounts.delete(key);
4153
+ fieldTransformingSince.delete(key);
4154
+ } else {
4155
+ fieldTransformCounts.set(key, next);
4156
+ }
4157
+ }
4158
+ function flushSettledTransformWaiters() {
4159
+ if (transformWaiters.length === 0) return;
4160
+ const globalIdle = activeTransforms.value === 0;
4161
+ for (let i = transformWaiters.length - 1; i >= 0; i--) {
4162
+ const w = transformWaiters[i];
4163
+ if (w === void 0) continue;
4164
+ const idle = w.key === null ? globalIdle : (fieldTransformCounts.get(w.key) ?? 0) === 0;
4165
+ if (idle) {
4166
+ transformWaiters.splice(i, 1);
4167
+ w.resolve();
4168
+ }
4169
+ }
4170
+ }
4171
+ function releaseTransformRun(key, run) {
4172
+ if (run.released) return;
4173
+ run.released = true;
4174
+ run.holder.aborted = true;
4175
+ run.holder.controller?.abort();
4176
+ activeTransforms.value = Math.max(0, activeTransforms.value - 1);
4177
+ decFieldTransform(key);
4178
+ }
4179
+ function beginTransform(key, holder) {
4180
+ const prior = transformRuns.get(key);
4181
+ if (prior !== void 0) releaseTransformRun(key, prior);
4182
+ const token = ++transformTokenSeq;
4183
+ transformRuns.set(key, { token, holder, released: false });
4184
+ incFieldTransform(key);
4185
+ activeTransforms.value += 1;
4186
+ if (transformErrors.has(key)) transformErrors.delete(key);
4187
+ return token;
4188
+ }
4189
+ function isCurrentTransform(key, token) {
4190
+ return transformRuns.get(key)?.token === token;
4191
+ }
4192
+ function endTransform(key, token) {
4193
+ const run = transformRuns.get(key);
4194
+ if (run?.token === token) {
4195
+ if (!run.released) {
4196
+ activeTransforms.value = Math.max(0, activeTransforms.value - 1);
4197
+ decFieldTransform(key);
4198
+ }
4199
+ transformRuns.delete(key);
4200
+ }
4201
+ flushSettledTransformWaiters();
4202
+ }
4203
+ function setTransformError(key, err) {
4204
+ transformErrors.set(key, err);
4205
+ }
4206
+ function cancelTransforms() {
4207
+ for (const [key, run] of [...transformRuns]) {
4208
+ releaseTransformRun(key, run);
4209
+ transformRuns.delete(key);
4210
+ }
4211
+ if (transformErrors.size > 0) transformErrors.clear();
4212
+ flushSettledTransformWaiters();
4213
+ }
4214
+ function cancelTransformsUnder(prefix) {
4215
+ for (const [key, run] of [...transformRuns]) {
4216
+ const segs = segmentsForPathKey(key);
4217
+ if (segs === null) continue;
4218
+ if (!isPathPrefix(prefix, segs)) continue;
4219
+ releaseTransformRun(key, run);
4220
+ transformRuns.delete(key);
4221
+ transformErrors.delete(key);
4222
+ }
4223
+ flushSettledTransformWaiters();
4224
+ }
4225
+ function settleTransforms(path) {
4226
+ if (path === void 0) {
4227
+ if (activeTransforms.value === 0) return Promise.resolve();
4228
+ return new Promise((resolve) => {
4229
+ transformWaiters.push({ key: null, resolve });
4230
+ });
4231
+ }
4232
+ const { key } = canonicalizePath(path);
4233
+ if ((fieldTransformCounts.get(key) ?? 0) === 0) return Promise.resolve();
4234
+ return new Promise((resolve) => {
4235
+ transformWaiters.push({ key, resolve });
4236
+ });
4237
+ }
3980
4238
  const initStamp = (/* @__PURE__ */ new Date()).toISOString();
3981
4239
  diffAndApply({}, schemaInitialData, [], (patch) => {
3982
4240
  if (patch.kind !== "added") return;
@@ -4126,6 +4384,7 @@ function createFormStore(options) {
4126
4384
  }
4127
4385
  }
4128
4386
  }
4387
+ if (transformRuns.size !== 0) cancelTransformsUnder(path);
4129
4388
  if (meta?.skipDiscriminatorReshape !== true) {
4130
4389
  if (path.length > 0) {
4131
4390
  const last = path[path.length - 1];
@@ -4460,6 +4719,7 @@ function createFormStore(options) {
4460
4719
  drainHooks.length = 0;
4461
4720
  modules.clear();
4462
4721
  cancelFieldValidation();
4722
+ cancelTransforms();
4463
4723
  fieldValidatingSince.clear();
4464
4724
  formChangeListeners.clear();
4465
4725
  submitSuccessListeners.clear();
@@ -4596,6 +4856,7 @@ function createFormStore(options) {
4596
4856
  if (remaining === 0) {
4597
4857
  elements.delete(key);
4598
4858
  touchFieldRecord(key, path, { connected: false, focused: null, blurred: null });
4859
+ if (transformRuns.size !== 0) cancelTransformsUnder(path);
4599
4860
  }
4600
4861
  return remaining;
4601
4862
  }
@@ -4817,6 +5078,7 @@ function createFormStore(options) {
4817
5078
  submitError.value = null;
4818
5079
  departAttempts.value = 0;
4819
5080
  cancelFieldValidation();
5081
+ cancelTransforms();
4820
5082
  displayEngine.clear();
4821
5083
  fieldValidatingSince.clear();
4822
5084
  pathSnapshots.clear();
@@ -4835,6 +5097,7 @@ function createFormStore(options) {
4835
5097
  const { key: targetKey, segments: targetSegments } = canonicalizePath(path);
4836
5098
  variantMemory.clearUnderPath(targetSegments);
4837
5099
  cancelFieldValidationUnder(targetSegments);
5100
+ cancelTransformsUnder(targetSegments);
4838
5101
  for (const [snapKey] of [...pathSnapshots]) {
4839
5102
  const segs = segmentsForPathKey(snapKey);
4840
5103
  if (segs === null) continue;
@@ -4970,6 +5233,10 @@ function createFormStore(options) {
4970
5233
  pathHasAsyncValidationByKey,
4971
5234
  fieldValidationCounts,
4972
5235
  fieldValidatingSince,
5236
+ fieldTransformCounts,
5237
+ fieldTransformingSince,
5238
+ transformErrors,
5239
+ activeTransforms,
4973
5240
  displayEngine,
4974
5241
  applyFormReplacement,
4975
5242
  setValueAtPath,
@@ -4999,6 +5266,13 @@ function createFormStore(options) {
4999
5266
  getOriginalAtPath,
5000
5267
  getFirstErrorElement,
5001
5268
  cancelFieldValidation,
5269
+ beginTransform,
5270
+ isCurrentTransform,
5271
+ endTransform,
5272
+ setTransformError,
5273
+ cancelTransforms,
5274
+ cancelTransformsUnder,
5275
+ settleTransforms,
5002
5276
  scheduleFieldValidation,
5003
5277
  onFormChange,
5004
5278
  onSubmitSuccess,
@@ -5641,6 +5915,8 @@ function isLazyMarker(value) {
5641
5915
  }
5642
5916
 
5643
5917
  const NOOP_WIZARD_HISTORY = {
5918
+ push() {
5919
+ },
5644
5920
  replace() {
5645
5921
  },
5646
5922
  read() {
@@ -5655,6 +5931,9 @@ function createWizardHistory(param) {
5655
5931
  if (typeof window === "undefined") return NOOP_WIZARD_HISTORY;
5656
5932
  const subscribers = [];
5657
5933
  let disposed = false;
5934
+ function currentKey() {
5935
+ return new URL(window.location.href).searchParams.get(param) ?? void 0;
5936
+ }
5658
5937
  function buildUrl(key) {
5659
5938
  const url = new URL(window.location.href);
5660
5939
  url.searchParams.set(param, key);
@@ -5662,25 +5941,29 @@ function createWizardHistory(param) {
5662
5941
  }
5663
5942
  function handlePopstate() {
5664
5943
  if (disposed) return;
5665
- const url = new URL(window.location.href);
5666
- const value = url.searchParams.get(param) ?? void 0;
5944
+ const value = currentKey();
5667
5945
  for (const subscriber of subscribers) subscriber(value);
5668
5946
  }
5669
5947
  window.addEventListener("popstate", handlePopstate);
5670
- function safeReplaceState(key) {
5948
+ function safeWrite(key, mode) {
5671
5949
  try {
5672
- window.history.replaceState({}, "", buildUrl(key));
5950
+ if (mode === "push") window.history.pushState({}, "", buildUrl(key));
5951
+ else window.history.replaceState({}, "", buildUrl(key));
5673
5952
  } catch {
5674
5953
  }
5675
5954
  }
5676
5955
  return {
5956
+ push(key) {
5957
+ if (disposed) return;
5958
+ if (currentKey() === key) return;
5959
+ safeWrite(key, "push");
5960
+ },
5677
5961
  replace(key) {
5678
5962
  if (disposed) return;
5679
- safeReplaceState(key);
5963
+ safeWrite(key, "replace");
5680
5964
  },
5681
5965
  read() {
5682
- const url = new URL(window.location.href);
5683
- return url.searchParams.get(param) ?? void 0;
5966
+ return currentKey();
5684
5967
  },
5685
5968
  subscribe(callback) {
5686
5969
  if (disposed) return;
@@ -5718,6 +6001,7 @@ function buildNoopWizardSchema(formKey) {
5718
6001
  getEmptyValueAtPath: () => void 0,
5719
6002
  isPreprocessOrCoerceLeaf: () => false,
5720
6003
  arrayShapeAtPath: () => void 0,
6004
+ isFixedObjectAtPath: (path) => path.length === 0,
5721
6005
  getSchemasAtPath: () => [],
5722
6006
  validateAtPath: () => success,
5723
6007
  getSlimPrimitiveTypesAtPath: () => new Set(EMPTY_SLIM_KINDS),
@@ -6171,7 +6455,10 @@ function useWizard(options) {
6171
6455
  };
6172
6456
  const persistCallback = options.persist === false ? void 0 : options.persist !== void 0 ? options.persist : (state) => {
6173
6457
  if (state.step === void 0) return;
6174
- historyHandle.replace(state.step);
6458
+ const current = historyHandle.read();
6459
+ const effectiveCurrent = current !== void 0 && isCompiledKey(current) ? current : firstKey();
6460
+ if (state.step === effectiveCurrent) historyHandle.replace(state.step);
6461
+ else historyHandle.push(state.step);
6175
6462
  };
6176
6463
  function isCompiledKey(key) {
6177
6464
  const list = compiledSteps.value;
@@ -6252,6 +6539,7 @@ function useWizard(options) {
6252
6539
  }
6253
6540
  const submitting = ref(false);
6254
6541
  const submissionAttempts = ref(0);
6542
+ const submitError = ref(null);
6255
6543
  const done = ref(false);
6256
6544
  function activateForm(form) {
6257
6545
  const source = asSubmissionSource(form);
@@ -6379,7 +6667,7 @@ function useWizard(options) {
6379
6667
  formKey: form.key
6380
6668
  };
6381
6669
  }
6382
- return full.process();
6670
+ return full.parse();
6383
6671
  }
6384
6672
  function collectErrors(results) {
6385
6673
  const out = [];
@@ -6410,6 +6698,7 @@ function useWizard(options) {
6410
6698
  return;
6411
6699
  }
6412
6700
  submitting.value = true;
6701
+ submitError.value = null;
6413
6702
  try {
6414
6703
  const currentKey = activeKey.value;
6415
6704
  const final = isFinalStep.value;
@@ -6418,12 +6707,14 @@ function useWizard(options) {
6418
6707
  if (final) {
6419
6708
  await Promise.all(
6420
6709
  list.map(async (step) => {
6710
+ registry.forms.get(step.key)?.clearUserErrors();
6421
6711
  const result = await processOne(step.form);
6422
6712
  results.set(step.key, result);
6423
6713
  })
6424
6714
  );
6425
6715
  } else {
6426
6716
  const active = activeForm.value;
6717
+ registry.forms.get(active.key)?.clearUserErrors();
6427
6718
  const result = await processOne(active);
6428
6719
  results.set(active.key, result);
6429
6720
  }
@@ -6458,7 +6749,6 @@ function useWizard(options) {
6458
6749
  if (target !== void 0) moveTo(target.key);
6459
6750
  }
6460
6751
  } else {
6461
- if (onError !== void 0) await onError(errors);
6462
6752
  if (options.focusFirstError !== false) {
6463
6753
  const firstFailedKey = errors[0]?.formKey;
6464
6754
  if (firstFailedKey !== void 0 && isCompiledKey(firstFailedKey)) {
@@ -6473,7 +6763,16 @@ function useWizard(options) {
6473
6763
  }
6474
6764
  }
6475
6765
  }
6766
+ if (onError !== void 0) {
6767
+ try {
6768
+ await onError(errors);
6769
+ } catch (cause) {
6770
+ throw new SubmitErrorHandlerError("User-provided onError threw", { cause });
6771
+ }
6772
+ }
6476
6773
  }
6774
+ } catch (err) {
6775
+ submitError.value = toError(err);
6477
6776
  } finally {
6478
6777
  submitting.value = false;
6479
6778
  }
@@ -6482,6 +6781,7 @@ function useWizard(options) {
6482
6781
  function reset() {
6483
6782
  submissionAttempts.value = 0;
6484
6783
  done.value = false;
6784
+ submitError.value = null;
6485
6785
  lazyEpoch.value += 1;
6486
6786
  for (const step of compiledSteps.value) {
6487
6787
  const full = asSubmissionSource(step.form);
@@ -6557,6 +6857,9 @@ function useWizard(options) {
6557
6857
  get submissionAttempts() {
6558
6858
  return submissionAttempts.value;
6559
6859
  },
6860
+ get submitError() {
6861
+ return submitError.value;
6862
+ },
6560
6863
  get visited() {
6561
6864
  return visited.value;
6562
6865
  }
@@ -6667,4 +6970,4 @@ function warnIfAmbientWizardProviderHadDuplicates() {
6667
6970
  }
6668
6971
 
6669
6972
  export { AttaformErrorCode as A, useAbstractForm as B, useWizard as C, DEFAULT_PERSISTENCE_DEBOUNCE_MS as D, PERSISTENCE_MODULE_KEY as P, DEFAULT_TIMINGS as a, applyPatchesForward as b, cleanupOrphanKeys as c, defaultCoercionRules as d, defaultDisplayState as e, defineCoercion as f, deleteAtPath as g, diffAndApply as h, getAtPath as i, humanize as j, injectForm as k, injectWizard as l, isPlainRecord as m, isUnset as n, lazy as o, makeDefaultDisplayState as p, mergeSparseHydration as q, normalizeNumericOption as r, normalizePersistConfig as s, resolveStorageKeyBase as t, safeAssign as u, safeOwnRead as v, setAtPath as w, slimKindOf as x, structuralSnapshot as y, unset as z };
6670
- //# sourceMappingURL=attaform.DSqO6Db7.mjs.map
6973
+ //# sourceMappingURL=attaform.DR6RmxWZ.mjs.map