attaform 0.20.2 → 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 (150) hide show
  1. package/dist/chunks/dev-key-collision-warnings.cjs +58 -0
  2. package/dist/chunks/dev-key-collision-warnings.cjs.map +1 -0
  3. package/dist/chunks/dev-key-collision-warnings.mjs +55 -0
  4. package/dist/chunks/dev-key-collision-warnings.mjs.map +1 -0
  5. package/dist/chunks/devtools.cjs +1 -1
  6. package/dist/chunks/devtools.mjs +1 -1
  7. package/dist/chunks/fingerprint.cjs +186 -0
  8. package/dist/chunks/fingerprint.cjs.map +1 -0
  9. package/dist/chunks/fingerprint.mjs +184 -0
  10. package/dist/chunks/fingerprint.mjs.map +1 -0
  11. package/dist/chunks/fingerprint2.cjs +162 -0
  12. package/dist/chunks/fingerprint2.cjs.map +1 -0
  13. package/dist/chunks/fingerprint2.mjs +160 -0
  14. package/dist/chunks/fingerprint2.mjs.map +1 -0
  15. package/dist/chunks/indexeddb.cjs +1 -1
  16. package/dist/chunks/indexeddb.mjs +1 -1
  17. package/dist/chunks/local-storage.cjs +1 -1
  18. package/dist/chunks/local-storage.mjs +1 -1
  19. package/dist/chunks/multi-tab-sync.cjs +367 -0
  20. package/dist/chunks/multi-tab-sync.cjs.map +1 -0
  21. package/dist/chunks/multi-tab-sync.mjs +364 -0
  22. package/dist/chunks/multi-tab-sync.mjs.map +1 -0
  23. package/dist/chunks/session-storage.cjs +1 -1
  24. package/dist/chunks/session-storage.mjs +1 -1
  25. package/dist/chunks/wire-persistence.cjs +396 -0
  26. package/dist/chunks/wire-persistence.cjs.map +1 -0
  27. package/dist/chunks/wire-persistence.mjs +394 -0
  28. package/dist/chunks/wire-persistence.mjs.map +1 -0
  29. package/dist/esbuild.cjs +28 -0
  30. package/dist/esbuild.cjs.map +1 -0
  31. package/dist/esbuild.d.cts +56 -0
  32. package/dist/esbuild.d.mts +56 -0
  33. package/dist/esbuild.d.ts +56 -0
  34. package/dist/esbuild.mjs +26 -0
  35. package/dist/esbuild.mjs.map +1 -0
  36. package/dist/index.cjs +5 -3
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +66 -70
  39. package/dist/index.d.mts +66 -70
  40. package/dist/index.d.ts +66 -70
  41. package/dist/index.mjs +5 -5
  42. package/dist/nuxt.d.cts +1 -1
  43. package/dist/nuxt.d.mts +1 -1
  44. package/dist/nuxt.d.ts +1 -1
  45. package/dist/rollup.cjs +24 -0
  46. package/dist/rollup.cjs.map +1 -0
  47. package/dist/rollup.d.cts +35 -0
  48. package/dist/rollup.d.mts +35 -0
  49. package/dist/rollup.d.ts +35 -0
  50. package/dist/rollup.mjs +22 -0
  51. package/dist/rollup.mjs.map +1 -0
  52. package/dist/rspack.cjs +10 -0
  53. package/dist/rspack.cjs.map +1 -0
  54. package/dist/rspack.d.cts +40 -0
  55. package/dist/rspack.d.mts +40 -0
  56. package/dist/rspack.d.ts +40 -0
  57. package/dist/rspack.mjs +8 -0
  58. package/dist/rspack.mjs.map +1 -0
  59. package/dist/runtime/plugins/attaform.cjs +2 -2
  60. package/dist/runtime/plugins/attaform.mjs +2 -2
  61. package/dist/shared/attaform.BJGA_UOS.mjs +37 -0
  62. package/dist/shared/attaform.BJGA_UOS.mjs.map +1 -0
  63. package/dist/shared/attaform.BRGIpZo4.cjs +26 -0
  64. package/dist/shared/attaform.BRGIpZo4.cjs.map +1 -0
  65. package/dist/shared/{attaform.DAKrGhxc.cjs → attaform.BSkvn43g.cjs} +101 -417
  66. package/dist/shared/attaform.BSkvn43g.cjs.map +1 -0
  67. package/dist/shared/{attaform.sWm8B15V.d.mts → attaform.BWfliRIK.d.cts} +172 -2
  68. package/dist/shared/{attaform.BGk8cfw2.mjs → attaform.Be8NZG9M.mjs} +178 -21
  69. package/dist/shared/attaform.Be8NZG9M.mjs.map +1 -0
  70. package/dist/shared/{attaform.D2SCCd4O.cjs → attaform.Bq5sX7TF.cjs} +2 -2
  71. package/dist/shared/{attaform.D2SCCd4O.cjs.map → attaform.Bq5sX7TF.cjs.map} +1 -1
  72. package/dist/shared/{attaform.ceGEAEMk.d.ts → attaform.Bv7dRDWK.d.ts} +172 -2
  73. package/dist/shared/attaform.C3Doa9Pt.mjs +24 -0
  74. package/dist/shared/attaform.C3Doa9Pt.mjs.map +1 -0
  75. package/dist/shared/{attaform.B_hph5AE.cjs → attaform.CICFZ1iS.cjs} +178 -20
  76. package/dist/shared/attaform.CICFZ1iS.cjs.map +1 -0
  77. package/dist/shared/attaform.CQN9R62B.cjs +39 -0
  78. package/dist/shared/attaform.CQN9R62B.cjs.map +1 -0
  79. package/dist/shared/{attaform.CwLjUqmQ.cjs → attaform.ClXwitZj.cjs} +735 -960
  80. package/dist/shared/attaform.ClXwitZj.cjs.map +1 -0
  81. package/dist/shared/{attaform.99cfHcIt.d.cts → attaform.D0dWZsJt.d.cts} +349 -77
  82. package/dist/shared/{attaform.99cfHcIt.d.mts → attaform.D0dWZsJt.d.mts} +349 -77
  83. package/dist/shared/{attaform.99cfHcIt.d.ts → attaform.D0dWZsJt.d.ts} +349 -77
  84. package/dist/shared/{attaform.z5j3LwJz.cjs → attaform.D32WwKk6.cjs} +216 -35
  85. package/dist/shared/attaform.D32WwKk6.cjs.map +1 -0
  86. package/dist/shared/{attaform.C5aYC_T8.mjs → attaform.DMEP_ENr.mjs} +39 -392
  87. package/dist/shared/attaform.DMEP_ENr.mjs.map +1 -0
  88. package/dist/shared/{attaform.tiWEVznj.mjs → attaform.DR6RmxWZ.mjs} +725 -962
  89. package/dist/shared/attaform.DR6RmxWZ.mjs.map +1 -0
  90. package/dist/shared/{attaform.Dt7dEcHk.mjs → attaform.DozgVlCE.mjs} +89 -405
  91. package/dist/shared/attaform.DozgVlCE.mjs.map +1 -0
  92. package/dist/shared/{attaform.DN5CvZrg.d.ts → attaform.Duecg2NO.d.mts} +2 -2
  93. package/dist/shared/attaform.DuzQYscR.d.cts +41 -0
  94. package/dist/shared/attaform.DuzQYscR.d.mts +41 -0
  95. package/dist/shared/attaform.DuzQYscR.d.ts +41 -0
  96. package/dist/shared/{attaform.BXinSW2T.d.mts → attaform.FudOcHaa.d.cts} +2 -2
  97. package/dist/shared/attaform.LEWUFqUw.cjs +54 -0
  98. package/dist/shared/attaform.LEWUFqUw.cjs.map +1 -0
  99. package/dist/shared/{attaform.CywE4y8x.d.cts → attaform.MtrpT6Ki.d.ts} +2 -2
  100. package/dist/shared/{attaform.DbRgDFa7.d.cts → attaform.NQ8mybyW.d.mts} +172 -2
  101. package/dist/shared/{attaform.Cd4AOfwu.cjs → attaform.S-pYLSo4.cjs} +68 -402
  102. package/dist/shared/attaform.S-pYLSo4.cjs.map +1 -0
  103. package/dist/shared/{attaform.CnrxbkB6.mjs → attaform.Y1ZGhM4k.mjs} +2 -2
  104. package/dist/shared/{attaform.CnrxbkB6.mjs.map → attaform.Y1ZGhM4k.mjs.map} +1 -1
  105. package/dist/shared/{attaform.QG5TG8lB.mjs → attaform.pmtahXKy.mjs} +216 -36
  106. package/dist/shared/attaform.pmtahXKy.mjs.map +1 -0
  107. package/dist/shared/attaform.sHkHv_98.mjs +51 -0
  108. package/dist/shared/attaform.sHkHv_98.mjs.map +1 -0
  109. package/dist/vite.cjs +9 -45
  110. package/dist/vite.cjs.map +1 -1
  111. package/dist/vite.d.cts +36 -0
  112. package/dist/vite.d.mts +36 -0
  113. package/dist/vite.d.ts +36 -0
  114. package/dist/vite.mjs +8 -44
  115. package/dist/vite.mjs.map +1 -1
  116. package/dist/webpack.cjs +10 -0
  117. package/dist/webpack.cjs.map +1 -0
  118. package/dist/webpack.d.cts +37 -0
  119. package/dist/webpack.d.mts +37 -0
  120. package/dist/webpack.d.ts +37 -0
  121. package/dist/webpack.mjs +8 -0
  122. package/dist/webpack.mjs.map +1 -0
  123. package/dist/zod-v3.cjs +3 -3
  124. package/dist/zod-v3.d.cts +3 -3
  125. package/dist/zod-v3.d.mts +3 -3
  126. package/dist/zod-v3.d.ts +3 -3
  127. package/dist/zod-v3.mjs +3 -3
  128. package/dist/zod-v4.cjs +3 -3
  129. package/dist/zod-v4.d.cts +4 -4
  130. package/dist/zod-v4.d.mts +4 -4
  131. package/dist/zod-v4.d.ts +4 -4
  132. package/dist/zod-v4.mjs +3 -3
  133. package/dist/zod.cjs +8 -8
  134. package/dist/zod.cjs.map +1 -1
  135. package/dist/zod.d.cts +52 -10
  136. package/dist/zod.d.mts +52 -10
  137. package/dist/zod.d.ts +52 -10
  138. package/dist/zod.mjs +6 -6
  139. package/dist/zod.mjs.map +1 -1
  140. package/package.json +19 -5
  141. package/dist/shared/attaform.BGk8cfw2.mjs.map +0 -1
  142. package/dist/shared/attaform.B_hph5AE.cjs.map +0 -1
  143. package/dist/shared/attaform.C5aYC_T8.mjs.map +0 -1
  144. package/dist/shared/attaform.Cd4AOfwu.cjs.map +0 -1
  145. package/dist/shared/attaform.CwLjUqmQ.cjs.map +0 -1
  146. package/dist/shared/attaform.DAKrGhxc.cjs.map +0 -1
  147. package/dist/shared/attaform.Dt7dEcHk.mjs.map +0 -1
  148. package/dist/shared/attaform.QG5TG8lB.mjs.map +0 -1
  149. package/dist/shared/attaform.tiWEVznj.mjs.map +0 -1
  150. package/dist/shared/attaform.z5j3LwJz.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const vue = require('vue');
4
- const paths = require('./attaform.z5j3LwJz.cjs');
4
+ const paths = require('./attaform.D32WwKk6.cjs');
5
5
 
6
6
  function safeAssign(target, key, value) {
7
7
  if (key === "__proto__") {
@@ -15,10 +15,14 @@ function safeAssign(target, key, value) {
15
15
  }
16
16
  target[key] = value;
17
17
  }
18
+ function isShadowedKey(key) {
19
+ return key in Object.prototype;
20
+ }
18
21
  function safeOwnRead(target, key) {
19
- if (key === "__proto__") {
20
- const desc = Object.getOwnPropertyDescriptor(target, "__proto__");
21
- return desc?.value;
22
+ if (isShadowedKey(key)) {
23
+ const desc = Object.getOwnPropertyDescriptor(target, key);
24
+ if (desc === void 0) return void 0;
25
+ return "value" in desc ? desc.value : target[key];
22
26
  }
23
27
  return target[key];
24
28
  }
@@ -37,6 +41,10 @@ function descendStep(value, segment) {
37
41
  }
38
42
  const record = value;
39
43
  const key = typeof segment === "number" ? String(segment) : segment;
44
+ if (isShadowedKey(key)) {
45
+ if (!safeOwnHas(record, key)) return NOT_FOUND;
46
+ return safeOwnRead(record, key);
47
+ }
40
48
  if (!(key in record)) return NOT_FOUND;
41
49
  return record[key];
42
50
  }
@@ -66,6 +74,7 @@ function hasAtPath(root, path) {
66
74
  return typeof last === "number" && last >= 0 && last < current.length;
67
75
  }
68
76
  const key = typeof last === "number" ? String(last) : last;
77
+ if (isShadowedKey(key)) return safeOwnHas(current, key);
69
78
  return key in current;
70
79
  }
71
80
  function isPlainRecord(value) {
@@ -460,17 +469,57 @@ function structuralSnapshot(value) {
460
469
  return out;
461
470
  }
462
471
 
463
- const defaultDisplayState = (field, formMeta) => {
464
- const gateOpen = formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
465
- if (!gateOpen) return "idle";
466
- if (field.validating === true) return "pending";
472
+ function isGateOpen(field, formMeta) {
473
+ return formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
474
+ }
475
+ function computeVerdict(field, formMeta) {
476
+ if (!isGateOpen(field, formMeta)) return "idle";
467
477
  const hasOwnError = field.errors.some(
468
478
  (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
469
479
  );
470
480
  if (hasOwnError) return "error";
471
481
  if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
472
482
  return "idle";
473
- };
483
+ }
484
+ function earliestNonNull(a, b) {
485
+ if (a === null) return b;
486
+ if (b === null) return a;
487
+ return a < b ? a : b;
488
+ }
489
+ const DEFAULT_TIMINGS = { showDelay: 120, minVisible: 120 };
490
+ const FOCUS_OUT_GRACE = 16;
491
+ const defaultFamily = /* @__PURE__ */ new WeakSet();
492
+ function isDefaultDisplayState(fn) {
493
+ return defaultFamily.has(fn);
494
+ }
495
+ function makeDefaultDisplayState({
496
+ showDelay,
497
+ minVisible
498
+ }) {
499
+ const reducer = (prev, { field, formMeta, validatingSince, transformingSince, now }) => {
500
+ const verdict = computeVerdict(field, formMeta);
501
+ if (!isGateOpen(field, formMeta)) return { display: verdict };
502
+ const inFlightSince = earliestNonNull(validatingSince, transformingSince);
503
+ if (inFlightSince === null) {
504
+ if (prev.display === "pending") {
505
+ const shownAt = prev.pendingShownAt ?? now;
506
+ if (now < shownAt + minVisible)
507
+ return { display: "pending", pendingShownAt: shownAt, reviewAt: shownAt + minVisible };
508
+ }
509
+ return { display: verdict };
510
+ }
511
+ if (prev.display === "pending")
512
+ return { display: "pending", pendingShownAt: prev.pendingShownAt ?? now };
513
+ const window = field.focused === false ? Math.min(showDelay, FOCUS_OUT_GRACE) : showDelay;
514
+ if (now - inFlightSince < window) {
515
+ return { display: prev.display, reviewAt: inFlightSince + window };
516
+ }
517
+ return { display: "pending", pendingShownAt: now, reviewAt: now + minVisible };
518
+ };
519
+ defaultFamily.add(reducer);
520
+ return reducer;
521
+ }
522
+ const defaultDisplayState = makeDefaultDisplayState(DEFAULT_TIMINGS);
474
523
  function resolveGetDisplayState(config) {
475
524
  return config ?? defaultDisplayState;
476
525
  }
@@ -589,6 +638,8 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
589
638
  if (blankForKey !== void 0) errors.push(...blankForKey);
590
639
  if (userForKey !== void 0) errors.push(...userForKey);
591
640
  const validating = (state.fieldValidationCounts.get(key) ?? 0) > 0;
641
+ const transforming = (state.fieldTransformCounts.get(key) ?? 0) > 0;
642
+ const transformError = state.transformErrors.get(key) ?? null;
592
643
  const gated = state.pathHasAsyncValidation(segments) && !state.firstValidationDone.value;
593
644
  const isOrphan = segments.length > 0 && !hasAtPath(state.form.value, segments) && isUnderStubAncestor(state, segments);
594
645
  const valid = !gated && errors.length === 0 && !validating && !isOrphan;
@@ -615,6 +666,9 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
615
666
  errors,
616
667
  validating,
617
668
  valid,
669
+ transforming,
670
+ busy: transforming || validating,
671
+ transformError,
618
672
  path: segments,
619
673
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
620
674
  key: state.arrayElementKey(segments),
@@ -627,7 +681,21 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
627
681
  }
628
682
  function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
629
683
  const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
630
- return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
684
+ const validatingSince = state.fieldValidatingSince.get(key) ?? null;
685
+ const transformingSince = state.fieldTransformingSince.get(key) ?? null;
686
+ return decorateWithDerivedProps(
687
+ base,
688
+ state,
689
+ getFormMetaBase,
690
+ key,
691
+ validatingSince,
692
+ transformingSince,
693
+ false,
694
+ // revealedDescendantError: leaves have no descendants
695
+ false,
696
+ // isRoot: a leaf is never the form root
697
+ getDisplayState
698
+ );
631
699
  }
632
700
  function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
633
701
  const formValue = state.form.value;
@@ -643,8 +711,13 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
643
711
  let blurredAfterInteraction = false;
644
712
  let connected = false;
645
713
  let validating = false;
714
+ let validatingSince = null;
715
+ let transforming = false;
716
+ let transformingSince = null;
646
717
  let updatedAt = null;
647
718
  let asyncPending = false;
719
+ const submissionAttempts = state.submissionAttempts.value;
720
+ const blurredLeafSegments = [];
648
721
  for (const [leafKey, entry] of state.originals) {
649
722
  if (!paths.isPathPrefix(segments, entry.segments)) continue;
650
723
  if (segments.length === entry.segments.length) continue;
@@ -659,9 +732,25 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
659
732
  if (leafRecord?.blurred === true) blurred = true;
660
733
  if (leafRecord?.touched === true) touched = true;
661
734
  if (leafRecord?.interacted === true) interacted = true;
662
- if (leafRecord?.blurredAfterInteraction === true) blurredAfterInteraction = true;
735
+ if (leafRecord?.blurredAfterInteraction === true) {
736
+ blurredAfterInteraction = true;
737
+ blurredLeafSegments.push(entry.segments);
738
+ }
663
739
  if (leafRecord?.connected === true) connected = true;
664
- if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) validating = true;
740
+ if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) {
741
+ validating = true;
742
+ const leafGateOpen = submissionAttempts > 0 || leafRecord?.blurredAfterInteraction === true;
743
+ const since = state.fieldValidatingSince.get(leafKey);
744
+ if (leafGateOpen && since !== void 0 && (validatingSince === null || since < validatingSince))
745
+ validatingSince = since;
746
+ }
747
+ if ((state.fieldTransformCounts.get(leafKey) ?? 0) > 0) {
748
+ transforming = true;
749
+ const leafGateOpen = submissionAttempts > 0 || leafRecord?.blurredAfterInteraction === true;
750
+ const since = state.fieldTransformingSince.get(leafKey);
751
+ if (leafGateOpen && since !== void 0 && (transformingSince === null || since < transformingSince))
752
+ transformingSince = since;
753
+ }
665
754
  if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
666
755
  const ts = leafRecord?.updatedAt;
667
756
  if (ts !== void 0 && ts !== null) {
@@ -673,50 +762,101 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
673
762
  dirty = true;
674
763
  }
675
764
  const errors = aggregateErrorsAt(state, segments);
765
+ const revealedDescendantError = errors.length > 0 && errors.some((e) => {
766
+ const ePath = e.path;
767
+ if (ePath.length === segments.length && ePath.every((s, i) => s === segments[i])) return false;
768
+ if (submissionAttempts > 0) return true;
769
+ if (ePath.length === 1 && ePath[0] === "") return blurredAfterInteraction;
770
+ return blurredLeafSegments.some((s) => paths.isPathPrefix(ePath, s));
771
+ });
676
772
  if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
773
+ if ((state.fieldValidationCounts.get(key) ?? 0) > 0) {
774
+ validating = true;
775
+ const since = state.fieldValidatingSince.get(key);
776
+ if (since !== void 0 && (validatingSince === null || since < validatingSince))
777
+ validatingSince = since;
778
+ }
779
+ if ((state.fieldTransformCounts.get(key) ?? 0) > 0) {
780
+ transforming = true;
781
+ const since = state.fieldTransformingSince.get(key);
782
+ if (since !== void 0 && (transformingSince === null || since < transformingSince))
783
+ transformingSince = since;
784
+ }
785
+ const ownTransformError = state.transformErrors.get(key) ?? null;
677
786
  const gated = asyncPending && !state.firstValidationDone.value;
678
787
  const valid = !gated && errors.length === 0 && !validating;
679
788
  const resolved = state.schema.getFieldMetaAtPath ? state.schema.getFieldMetaAtPath(segments) : EMPTY_RESOLVED_FIELD_META;
680
789
  const lastSegment = segments.length === 0 ? "" : segments[segments.length - 1] ?? "";
681
790
  const label = resolved.label || humanize(lastSegment);
682
791
  return {
683
- value,
684
- original,
685
- pristine,
686
- dirty,
687
- focused,
688
- blurred,
689
- touched,
690
- interacted,
691
- blurredAfterInteraction,
692
- connected,
693
- element: null,
694
- elements: EMPTY_ELEMENTS,
695
- updatedAt,
696
- errors,
697
- validating,
698
- valid,
699
- path: segments,
700
- ...computeFieldIdentity(formInstanceId, state.formKey, key),
701
- key: state.arrayElementKey(segments),
702
- blank,
703
- label,
704
- description: resolved.description,
705
- placeholder: resolved.placeholder,
706
- meta: resolved.meta
792
+ base: {
793
+ value,
794
+ original,
795
+ pristine,
796
+ dirty,
797
+ focused,
798
+ blurred,
799
+ touched,
800
+ interacted,
801
+ blurredAfterInteraction,
802
+ connected,
803
+ element: null,
804
+ elements: EMPTY_ELEMENTS,
805
+ updatedAt,
806
+ errors,
807
+ validating,
808
+ valid,
809
+ transforming,
810
+ busy: transforming || validating,
811
+ // A container surfaces its OWN transform failure (a transform registered
812
+ // on the container path, e.g. a file normalizer) but never rolls up a
813
+ // descendant leaf's failure — that stays a per-field channel.
814
+ transformError: ownTransformError,
815
+ path: segments,
816
+ ...computeFieldIdentity(formInstanceId, state.formKey, key),
817
+ key: state.arrayElementKey(segments),
818
+ blank,
819
+ label,
820
+ description: resolved.description,
821
+ placeholder: resolved.placeholder,
822
+ meta: resolved.meta
823
+ },
824
+ validatingSince,
825
+ transformingSince,
826
+ revealedDescendantError
707
827
  };
708
828
  }
709
829
  function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
710
- const base = buildContainerFieldStateBase(state, segments, key, formInstanceId);
711
- return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
830
+ const { base, validatingSince, transformingSince, revealedDescendantError } = buildContainerFieldStateBase(state, segments, key, formInstanceId);
831
+ return decorateWithDerivedProps(
832
+ base,
833
+ state,
834
+ getFormMetaBase,
835
+ key,
836
+ validatingSince,
837
+ transformingSince,
838
+ revealedDescendantError,
839
+ segments.length === 0,
840
+ getDisplayState
841
+ );
712
842
  }
713
- function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState) {
843
+ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, transformingSince, revealedDescendantError, isRoot, getDisplayState) {
714
844
  const firstError = base.errors[0];
715
845
  const predicate = getDisplayState ?? state.getDisplayState;
716
846
  const formMeta = getFormMetaBase();
717
- let displayState;
847
+ const ctx = {
848
+ field: base,
849
+ formMeta,
850
+ validatingSince,
851
+ transformingSince,
852
+ // The engine's clock. Frozen to 0 under SSR (no clock, nothing in
853
+ // flight) so the reducer returns the plain verdict and hydration matches.
854
+ now: state.ssr ? 0 : Date.now()
855
+ };
856
+ let machine;
857
+ let rollupApplies = isDefaultDisplayState(predicate);
718
858
  try {
719
- displayState = predicate(base, formMeta);
859
+ machine = state.displayEngine.resolve(key, ctx, predicate);
720
860
  } catch (err) {
721
861
  if (paths.__DEV__ && !warnedDisplayStatePredicates.has(predicate)) {
722
862
  warnedDisplayStatePredicates.add(predicate);
@@ -725,8 +865,11 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState)
725
865
  err
726
866
  );
727
867
  }
728
- displayState = defaultDisplayState(base, formMeta);
868
+ machine = state.displayEngine.resolve(key, ctx, defaultDisplayState);
869
+ rollupApplies = true;
729
870
  }
871
+ const submitValidating = rollupApplies && isRoot && state.submitting.value && state.activeValidations.value > 0;
872
+ const displayState = machine.display === "pending" || submitValidating ? "pending" : rollupApplies && revealedDescendantError ? "error" : machine.display;
730
873
  return {
731
874
  ...base,
732
875
  displayState,
@@ -772,6 +915,15 @@ function liveKeysAtPath(state, segments) {
772
915
  if (typeof value === "object") return Object.keys(value);
773
916
  return [];
774
917
  }
918
+ function liveContainerHasKey(state, segments, key) {
919
+ const value = getAtPath(state.form.value, segments);
920
+ if (value === null || value === void 0 || typeof value !== "object") return false;
921
+ if (Array.isArray(value)) {
922
+ const index = Number(key);
923
+ return Number.isInteger(index) && index >= 0 && index < value.length && String(index) === key;
924
+ }
925
+ return Object.hasOwn(value, key);
926
+ }
775
927
  function isArrayPath(state, segments) {
776
928
  if (segments.length === 0) return false;
777
929
  return Array.isArray(getAtPath(state.form.value, segments));
@@ -800,6 +952,21 @@ const INTEGER_SEGMENT = /^(?:0|[1-9]\d*)$/;
800
952
  function keyToSegment(key) {
801
953
  return INTEGER_SEGMENT.test(key) ? Number(key) : key;
802
954
  }
955
+ function callableInvokeShim(method, surface, getDescent) {
956
+ const fnMethod = Reflect.get(Function.prototype, method);
957
+ return new Proxy((() => {
958
+ }), {
959
+ apply: (_target, _thisArg, args) => Reflect.apply(fnMethod, surface, args),
960
+ get: (_target, key) => Reflect.get(getDescent(), key),
961
+ has: (_target, key) => Reflect.has(getDescent(), key),
962
+ ownKeys: () => Reflect.ownKeys(getDescent()),
963
+ getOwnPropertyDescriptor: (_target, key) => {
964
+ const descriptor = Reflect.getOwnPropertyDescriptor(getDescent(), key);
965
+ if (descriptor !== void 0) descriptor.configurable = true;
966
+ return descriptor;
967
+ }
968
+ });
969
+ }
803
970
  function buildSurfaceProxy(opts) {
804
971
  const containerCache = /* @__PURE__ */ new Map();
805
972
  const leafViewCache = /* @__PURE__ */ new Map();
@@ -826,6 +993,8 @@ function buildSurfaceProxy(opts) {
826
993
  const cacheKey = `${JSON.stringify(segments)}+${isArrayLike ? "A" : "O"}`;
827
994
  const existing = containerCache.get(cacheKey);
828
995
  if (existing !== void 0) return existing;
996
+ const isFixedObject = opts.schema.isFixedObjectAtPath(segments);
997
+ const containerHasKey = (k) => opts.containerHasOwnKey !== void 0 ? opts.containerHasOwnKey(segments, k) : opts.containerOwnKeys?.(segments).includes(k) === true;
829
998
  const snapshotContainer = () => opts.materializeContainer === void 0 ? {} : opts.materializeContainer(segments);
830
999
  const {
831
1000
  toString: containerToString,
@@ -833,8 +1002,9 @@ function buildSurfaceProxy(opts) {
833
1002
  toJSON: containerToJSON,
834
1003
  toPrimitive: containerToPrimitive
835
1004
  } = makeReadonlyCoercion(snapshotContainer);
836
- const target = isArrayLike ? [] : (() => {
837
- });
1005
+ const isRoot = segments.length === 0;
1006
+ const target = isRoot ? (() => {
1007
+ }) : isArrayLike ? [] : {};
838
1008
  const proxy = new Proxy(target, {
839
1009
  apply(_, __, args) {
840
1010
  const arg = args[0];
@@ -865,7 +1035,16 @@ function buildSurfaceProxy(opts) {
865
1035
  return key === "toString" ? containerToString : containerValueOf;
866
1036
  }
867
1037
  }
868
- return descendOrTerminate(childSegs);
1038
+ if (key === "hasOwnProperty" && !schemaHasPath(childSegs)) {
1039
+ return Object.prototype.hasOwnProperty;
1040
+ }
1041
+ if (isRoot && (key === "call" || key === "apply" || key === "bind")) {
1042
+ return callableInvokeShim(key, proxy, () => descendOrTerminate(childSegs));
1043
+ }
1044
+ if (opts.isTerminalAt?.(childSegs) === true || isFixedObject && schemaHasPath(childSegs) || containerHasKey(key)) {
1045
+ return descendOrTerminate(childSegs);
1046
+ }
1047
+ return void 0;
869
1048
  },
870
1049
  has(_, key) {
871
1050
  if (typeof key === "symbol") return Reflect.has(target, key);
@@ -950,15 +1129,8 @@ function buildSurfaceProxy(opts) {
950
1129
  toJSON: leafToJSONHandler,
951
1130
  toPrimitive: leafToPrimitive
952
1131
  } = makeReadonlyCoercion(snapshotLeaf);
953
- const target = (() => {
954
- });
1132
+ const target = {};
955
1133
  const proxy = new Proxy(target, {
956
- apply(_, __, args) {
957
- const arg = args[0];
958
- if (arg === void 0) return opts.resolveCallTarget(segments);
959
- const { segments: argSegs } = paths.canonicalizePath(arg);
960
- return opts.resolveCallTarget(argSegs);
961
- },
962
1134
  get(_, key) {
963
1135
  if (typeof key === "symbol") {
964
1136
  if (key === Symbol.toPrimitive) return leafToPrimitive;
@@ -968,6 +1140,9 @@ function buildSurfaceProxy(opts) {
968
1140
  if (key === "toString") return leafToString;
969
1141
  if (key === "valueOf") return leafValueOf;
970
1142
  if (key === "toJSON") return leafToJSONHandler;
1143
+ if (key === "hasOwnProperty" && !schemaHasPath([...segments, keyToSegment(key)])) {
1144
+ return Object.prototype.hasOwnProperty;
1145
+ }
971
1146
  if (leafKeys.has(key)) {
972
1147
  const leaf = opts.resolveLeaf(segments);
973
1148
  return readLeafKey(leaf, key);
@@ -980,8 +1155,8 @@ function buildSurfaceProxy(opts) {
980
1155
  return true;
981
1156
  },
982
1157
  // Iteration: leaf-views expose the leaf-key set so
983
- // `JSON.stringify(form.fields.email)` produces a FieldState
984
- // snapshot rather than the function-target placeholder.
1158
+ // `Object.keys(form.fields.email)` / spread enumerate the
1159
+ // FieldState props. (`JSON.stringify` routes through `toJSON` above.)
985
1160
  ownKeys: () => Array.from(leafKeys),
986
1161
  getOwnPropertyDescriptor(_, key) {
987
1162
  if (typeof key !== "string") return void 0;
@@ -1090,6 +1265,12 @@ function buildErrorsProxy(state) {
1090
1265
  // library-produced verdicts (schema + derived-blank) at unreachable
1091
1266
  // paths stay hidden; user-supplied errors are unconditional.
1092
1267
  containerOwnKeys: (segments) => errorAwareContainerKeys(state, segments),
1268
+ // Fast path: a key the live form data holds short-circuits before the
1269
+ // O(n) error-store scan, so iterating `form.errors.<array>` over live
1270
+ // indices stays linear. The scan still runs for a key with no live
1271
+ // home — a server error at a non-schema key (`form.errors.ghost`) —
1272
+ // so it keeps surfacing while a genuinely-absent key reads undefined.
1273
+ containerHasOwnKey: (segments, key) => liveContainerHasKey(state, segments, key) || errorAwareContainerKeys(state, segments).includes(key),
1093
1274
  isArrayContainer: (segments) => isArrayPath(state, segments)
1094
1275
  });
1095
1276
  }
@@ -1263,6 +1444,9 @@ const FIELD_STATE_KEYS = /* @__PURE__ */ new Set([
1263
1444
  "errors",
1264
1445
  "validating",
1265
1446
  "valid",
1447
+ "transforming",
1448
+ "busy",
1449
+ "transformError",
1266
1450
  "displayState",
1267
1451
  "showErrors",
1268
1452
  "showPending",
@@ -1365,14 +1549,22 @@ function buildFieldStateProxy(state, formInstanceId, getFormMetaBase, options) {
1365
1549
  terminalCache.set(cacheKey, proxy);
1366
1550
  return proxy;
1367
1551
  }
1552
+ const surfaceSchema = state.schema;
1368
1553
  return buildSurfaceProxy({
1369
- schema: state.schema,
1554
+ schema: surfaceSchema,
1370
1555
  resolveLeaf: (path) => getFieldStateAt(path),
1371
1556
  leafKeys: FIELD_STATE_KEYS,
1372
1557
  readLeafKey: (computed, key) => computed.value[key],
1373
1558
  materializeContainer: (segments) => materializeFields(state, segments, snapshotFieldStateAt),
1374
- resolveCallTarget: (path) => fieldStateTerminalAt(path),
1559
+ // `form.fields(path)` resolves a FieldState for any path the SCHEMA
1560
+ // declares — a leaf, a container, an inactive discriminated-union
1561
+ // variant key, or an out-of-bounds array index (the element schema
1562
+ // admits any index). A path the schema doesn't have is a typo, not a
1563
+ // field, so it reads `undefined` rather than a phantom stub. The
1564
+ // empty path (`form.fields()`) is the root object, always valid.
1565
+ resolveCallTarget: (path) => surfaceSchema.getSlimPrimitiveTypesAtPath(path).size > 0 ? fieldStateTerminalAt(path) : void 0,
1375
1566
  containerOwnKeys: (segments) => liveKeysAtPath(state, segments),
1567
+ containerHasOwnKey: (segments, key) => liveContainerHasKey(state, segments, key),
1376
1568
  isArrayContainer: (segments) => isArrayPath(state, segments)
1377
1569
  });
1378
1570
  }
@@ -1419,90 +1611,6 @@ async function getStorageAdapter(storage) {
1419
1611
  }
1420
1612
  }
1421
1613
  }
1422
- const PERSISTED_ENVELOPE_VERSION = 6;
1423
- function readPersistedPayload(value) {
1424
- if (value === null || value === void 0 || typeof value !== "object") return null;
1425
- const envelope = value;
1426
- if (typeof envelope.v !== "number") return null;
1427
- if (envelope.v !== PERSISTED_ENVELOPE_VERSION) {
1428
- warnVersionMismatch(envelope.v);
1429
- return null;
1430
- }
1431
- if (envelope.data === void 0 || typeof envelope.data !== "object") return null;
1432
- return envelope;
1433
- }
1434
- const warnedVersions = paths.__DEV__ ? /* @__PURE__ */ new Set() : null;
1435
- function warnVersionMismatch(observedVersion) {
1436
- if (warnedVersions === null) return;
1437
- if (warnedVersions.has(observedVersion)) return;
1438
- warnedVersions.add(observedVersion);
1439
- console.warn(
1440
- `[attaform] Dropping persisted draft \u2014 envelope v=${observedVersion}, but this version of the library expects v=${PERSISTED_ENVELOPE_VERSION}. The persisted shape changed across releases; older drafts can't be restored. New drafts saved this session will use the current envelope.`
1441
- );
1442
- }
1443
- function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPaths) {
1444
- let transientList;
1445
- if (blankPaths !== void 0 && blankPaths.size > 0) {
1446
- transientList = [...blankPaths];
1447
- }
1448
- if (include === "form") {
1449
- if (transientList === void 0) return { v: PERSISTED_ENVELOPE_VERSION, data: { form } };
1450
- return {
1451
- v: PERSISTED_ENVELOPE_VERSION,
1452
- data: { form, blankPaths: transientList }
1453
- };
1454
- }
1455
- return {
1456
- v: PERSISTED_ENVELOPE_VERSION,
1457
- data: {
1458
- form,
1459
- schemaErrors: [...schemaErrors.entries()].map(([k, v]) => [k, [...v]]),
1460
- userErrors: [...userErrors.entries()].map(([k, v]) => [k, [...v]]),
1461
- ...transientList !== void 0 ? { blankPaths: transientList } : {}
1462
- }
1463
- };
1464
- }
1465
- function createDebouncedWriter(write, debounceMs) {
1466
- let timer = null;
1467
- let pending = null;
1468
- let writeGeneration = 0;
1469
- function runWrite() {
1470
- const gen = ++writeGeneration;
1471
- pending = write().finally(() => {
1472
- if (writeGeneration === gen) pending = null;
1473
- });
1474
- }
1475
- function schedule() {
1476
- if (timer !== null) clearTimeout(timer);
1477
- if (debounceMs === 0) {
1478
- runWrite();
1479
- return;
1480
- }
1481
- timer = setTimeout(() => {
1482
- timer = null;
1483
- runWrite();
1484
- }, debounceMs);
1485
- }
1486
- async function flush() {
1487
- if (timer !== null) {
1488
- clearTimeout(timer);
1489
- timer = null;
1490
- runWrite();
1491
- }
1492
- while (pending !== null) {
1493
- const awaited = pending;
1494
- await awaited;
1495
- if (pending === awaited) break;
1496
- }
1497
- }
1498
- function cancel() {
1499
- if (timer !== null) {
1500
- clearTimeout(timer);
1501
- timer = null;
1502
- }
1503
- }
1504
- return { schedule, flush, cancel };
1505
- }
1506
1614
  function resolveStorageKeyBase(config, formKey) {
1507
1615
  return config.key ?? `${PERSISTENCE_KEY_PREFIX}${formKey}`;
1508
1616
  }
@@ -1549,47 +1657,6 @@ async function sweepNonConfiguredStandardStoresForOrphans(configured, base) {
1549
1657
  }
1550
1658
  }
1551
1659
  }
1552
- function pluckPaths(form, pathKeys) {
1553
- let sparse = void 0;
1554
- for (const pathKey of pathKeys) {
1555
- const segments = paths.segmentsForPathKey(pathKey);
1556
- if (segments === null) continue;
1557
- const value = getAtPath(form, segments);
1558
- if (value === void 0) continue;
1559
- sparse = setAtPath(sparse ?? {}, segments, value);
1560
- }
1561
- return sparse ?? {};
1562
- }
1563
- function stripUnacknowledgedSensitiveLeaves(form, optedInPaths, isSensitivePath) {
1564
- const acknowledgedSensitive = [];
1565
- for (const key of optedInPaths) {
1566
- const segs = paths.segmentsForPathKey(key);
1567
- if (segs !== null && isSensitivePath(segs)) acknowledgedSensitive.push(segs);
1568
- }
1569
- const coveredByAcknowledged = (path) => acknowledgedSensitive.some((prefix) => paths.isPathPrefix(prefix, path));
1570
- const walk = (path, value) => {
1571
- if (path.length > 0 && isSensitivePath(path) && !coveredByAcknowledged(path)) {
1572
- return void 0;
1573
- }
1574
- if (value === null || typeof value !== "object") return value;
1575
- if (Array.isArray(value)) return value.map((item, i) => walk([...path, i], item));
1576
- if (!isPlainRecord(value)) return value;
1577
- const out = {};
1578
- for (const key of Object.keys(value)) {
1579
- const walked = walk([...path, key], value[key]);
1580
- if (walked !== void 0) safeAssign(out, key, walked);
1581
- }
1582
- return out;
1583
- };
1584
- return walk([], form);
1585
- }
1586
- function filterErrorsByPaths(errors, pathKeys) {
1587
- const out = /* @__PURE__ */ new Map();
1588
- for (const [key, value] of errors) {
1589
- if (pathKeys.has(key)) out.set(key, value);
1590
- }
1591
- return out;
1592
- }
1593
1660
  function mergeSparseHydration(schemaDefaults, sparse, schema) {
1594
1661
  return mergeDeep(schemaDefaults, sparse, [], schema);
1595
1662
  }
@@ -1773,7 +1840,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1773
1840
  if (!result.ok) return result.error;
1774
1841
  return stripData(composeWithDerivedBlank(result.refinement, result.segments));
1775
1842
  }
1776
- async function process(pathInput) {
1843
+ async function parse(pathInput) {
1777
1844
  const result = await runImperativeValidation(pathInput, {
1778
1845
  cancelInFlight: false,
1779
1846
  commitToSchemaErrors: false
@@ -1826,7 +1893,10 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1826
1893
  state.activeSubmissions.value += 1;
1827
1894
  state.submitting.value = true;
1828
1895
  state.submitError.value = null;
1896
+ state.clearUserErrors();
1897
+ while (state.activeTransforms.value > 0) await state.settleTransforms();
1829
1898
  state.cancelFieldValidation();
1899
+ state.displayEngine.clear();
1830
1900
  state.activeValidations.value += 1;
1831
1901
  const refinement = await runRefinementValidation(state.form.value, void 0);
1832
1902
  const merged = composeWithDerivedBlank(refinement, void 0);
@@ -1863,9 +1933,8 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1863
1933
  state.emitSubmitSuccess();
1864
1934
  } catch (err) {
1865
1935
  if (state.submissionGeneration.value === genAtEntry) {
1866
- state.submitError.value = err;
1936
+ state.submitError.value = paths.toError(err);
1867
1937
  }
1868
- throw err;
1869
1938
  } finally {
1870
1939
  if (!validationSettled) {
1871
1940
  state.activeValidations.value = Math.max(0, state.activeValidations.value - 1);
@@ -1879,7 +1948,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1879
1948
  };
1880
1949
  return submitHandler;
1881
1950
  };
1882
- return { validate, validateAsync, process, handleSubmit };
1951
+ return { validate, validateAsync, parse, handleSubmit };
1883
1952
  }
1884
1953
  function toSegments(pathInput) {
1885
1954
  return paths.canonicalizePath(pathInput).segments;
@@ -2352,6 +2421,23 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2352
2421
  markConnectedOptimistically: () => {
2353
2422
  state.markConnectedOptimistically(segments);
2354
2423
  },
2424
+ // --- Async transform lifecycle (internal; the directive's
2425
+ // deferred orchestrator is the only legitimate consumer). Thin
2426
+ // path-bound delegates to the store's per-path token / counter
2427
+ // machinery — same pattern as `markBlank` / `setValueWithInternalPath`,
2428
+ // so the directive (which holds only this RegisterValue, never the
2429
+ // store) can drive the busy/discard/error bookkeeping. ---
2430
+ beginTransform: (holder) => state.beginTransform(pathKey, holder),
2431
+ isCurrentTransform: (token) => state.isCurrentTransform(pathKey, token),
2432
+ endTransform: (token) => state.endTransform(pathKey, token),
2433
+ setTransformError: (err) => state.setTransformError(pathKey, err),
2434
+ // Synchronous read of "is a transform in flight at this path". The
2435
+ // orchestrator's `beginTransform` bumps the count before the
2436
+ // listener's force-sync block runs, so the directive reads this to
2437
+ // skip reverting the DOM to stale storage mid-flight.
2438
+ get transforming() {
2439
+ return (state.fieldTransformCounts.get(pathKey) ?? 0) > 0;
2440
+ },
2355
2441
  path: pathKey,
2356
2442
  // Frozen so a wrapper component can pass `rv.segments` directly
2357
2443
  // to `form.fields(...)` without defensive copying — and so test
@@ -2550,7 +2636,9 @@ function expandUnsetAt(segments, schema, paths$1) {
2550
2636
  function buildCallableReadonlySnapshotProxy(opts) {
2551
2637
  const target = (() => {
2552
2638
  });
2553
- const { toString, valueOf, toJSON, toPrimitive } = makeReadonlyCoercion(opts.snapshot);
2639
+ const { toString, valueOf, toJSON, toPrimitive } = makeReadonlyCoercion(
2640
+ opts.coercionSnapshot ?? opts.snapshot
2641
+ );
2554
2642
  const callResolve = opts.resolveCall ?? ((arg) => opts.resolveKey(String(arg)));
2555
2643
  return new Proxy(target, {
2556
2644
  apply(_, __, args) {
@@ -2599,27 +2687,52 @@ function buildCallableReadonlySnapshotProxy(opts) {
2599
2687
  });
2600
2688
  }
2601
2689
 
2690
+ function materializeFormValue(node) {
2691
+ if (node === null || typeof node !== "object") return node;
2692
+ if (Array.isArray(node)) {
2693
+ const out2 = new Array(node.length);
2694
+ for (let i = 0; i < node.length; i++) out2[i] = materializeFormValue(node[i]);
2695
+ return out2;
2696
+ }
2697
+ if (!isPlainRecord(node)) return vue.toRaw(node);
2698
+ const rec = node;
2699
+ const out = {};
2700
+ for (const key of Object.keys(rec)) {
2701
+ safeAssign(out, key, materializeFormValue(safeOwnRead(rec, key)));
2702
+ }
2703
+ return out;
2704
+ }
2602
2705
  function buildValuesProxy(form) {
2603
2706
  const inner = vue.computed(() => vue.readonly(form.value));
2604
2707
  return buildCallableReadonlySnapshotProxy({
2605
2708
  surface: "form.values",
2606
2709
  snapshot: () => inner.value,
2710
+ // Faithful, reactivity-preserving serialisation: walk the reactive
2711
+ // proxy with own-safe reads so `JSON.stringify(form.values)` /
2712
+ // `String(form.values)` reflect the stored data — including a field
2713
+ // literally named `hasOwnProperty` that Vue would otherwise shim —
2714
+ // while still tracking the per-key reads that drive re-render.
2715
+ coercionSnapshot: () => materializeFormValue(inner.value),
2607
2716
  // Read through the readonly proxy at access time so Vue's
2608
2717
  // dependency tracking lands inside the consumer's active effect
2609
2718
  // — `inner.value[key]` is what triggers per-key tracking.
2610
- resolveKey: (key) => inner.value[key],
2611
- // Dynamic path: walk segments through the readonly proxy. Each
2612
- // step reads through the proxy's own get traps so dependency
2613
- // tracking propagates at every level.
2614
- resolveCall: (arg) => {
2615
- const { segments } = paths.canonicalizePath(arg);
2616
- let cursor = inner.value;
2617
- for (const seg of segments) {
2618
- if (cursor === null || cursor === void 0) return void 0;
2619
- cursor = cursor[seg];
2620
- }
2621
- return cursor;
2622
- },
2719
+ //
2720
+ // Prototype-shadowed names (`hasOwnProperty`, `constructor`, …) read
2721
+ // off the RAW target instead: own-shadows-inherited semantics still
2722
+ // hold (a data field by that name returns its stored value), but
2723
+ // when there's no such field the inherited member resolves — so
2724
+ // `form.values.hasOwnProperty('x')` keeps working as the real
2725
+ // method. The raw read dodges Vue's `hasOwnProperty` proxy shim,
2726
+ // which would otherwise mask a data field of that name. (`toString`
2727
+ // / `valueOf` / `toJSON` never reach here the base get trap
2728
+ // intercepts them as coercion handlers first.)
2729
+ resolveKey: (key) => isShadowedKey(key) ? vue.toRaw(inner.value)[key] : inner.value[key],
2730
+ // Dynamic path: walk segments through the readonly proxy with the
2731
+ // same own-property-safe descent the rest of the runtime uses
2732
+ // (`getAtPath`), so `form.values('a.hasOwnProperty')` resolves the
2733
+ // stored value. Per-level reads still propagate Vue's tracking for
2734
+ // ordinary keys.
2735
+ resolveCall: (arg) => getAtPath(inner.value, paths.canonicalizePath(arg).segments),
2623
2736
  ownKeys: () => Reflect.ownKeys(inner.value),
2624
2737
  hasKey: (key) => Reflect.has(inner.value, key),
2625
2738
  describeKey: (key) => {
@@ -2643,7 +2756,12 @@ function buildFormApi(state, formInstanceId, options = {}) {
2643
2756
  return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
2644
2757
  };
2645
2758
  const getFormMetaBase = () => {
2646
- const rootBase = buildContainerFieldStateBase(state, paths.ROOT_PATH, paths.ROOT_PATH_KEY, formInstanceId);
2759
+ const { base: rootBase } = buildContainerFieldStateBase(
2760
+ state,
2761
+ paths.ROOT_PATH,
2762
+ paths.ROOT_PATH_KEY,
2763
+ formInstanceId
2764
+ );
2647
2765
  return {
2648
2766
  ...rootBase,
2649
2767
  submitting: state.submitting.value,
@@ -2675,12 +2793,12 @@ function buildFormApi(state, formInstanceId, options = {}) {
2675
2793
  const {
2676
2794
  validate: validateBuilt,
2677
2795
  validateAsync: validateAsyncBuilt,
2678
- process: processBuilt,
2796
+ parse: parseBuilt,
2679
2797
  handleSubmit
2680
2798
  } = buildProcessForm(state, formInstanceId, processOptions);
2681
2799
  const validate = (pathInput) => validateBuilt(pathInput);
2682
2800
  const validateAsync = (pathInput) => validateAsyncBuilt(pathInput);
2683
- const process = (pathInput) => processBuilt(pathInput);
2801
+ const parse = (pathInput) => parseBuilt(pathInput);
2684
2802
  function pathToRef(pathInput) {
2685
2803
  const segments = paths.canonicalizePath(pathInput).segments;
2686
2804
  return vue.computed(() => getAtPath(state.form.value, segments));
@@ -2908,6 +3026,21 @@ function buildFormApi(state, formInstanceId, options = {}) {
2908
3026
  // keep the explicit form-level computation for the gate.
2909
3027
  valid,
2910
3028
  errors: metaErrors,
3029
+ // Whole-form transforming mirrors the global `activeTransforms`
3030
+ // counter ORed with any per-leaf transform in flight (the root
3031
+ // rollup), exactly as `validating` composes its lifecycle and
3032
+ // per-field sources. `busy` is the union of both work signals at
3033
+ // the form level. `transformError` is leaf-only, so the root
3034
+ // rollup reads it as `null` (kept for FieldState-shape parity).
3035
+ transforming: vue.computed(
3036
+ () => state.activeTransforms.value > 0 || rootFieldState.value.transforming
3037
+ ),
3038
+ busy: vue.computed(
3039
+ () => state.activeValidations.value > 0 || state.activeTransforms.value > 0 || rootFieldState.value.validating || rootFieldState.value.transforming
3040
+ ),
3041
+ get transformError() {
3042
+ return rootFieldState.value.transformError;
3043
+ },
2911
3044
  // `displayState` / the `show*` booleans / `firstError` flow
2912
3045
  // through the same root field-state computed as the rest of the
2913
3046
  // FieldState surface, so `form.meta.displayState` matches
@@ -2978,7 +3111,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2978
3111
  instanceId: formInstanceId
2979
3112
  })
2980
3113
  );
2981
- const persistence = state.modules.get(PERSISTENCE_MODULE_KEY);
3114
+ const persistenceHandle = state.modules.get(PERSISTENCE_MODULE_KEY);
2982
3115
  const reset = (nextDefaultValues) => {
2983
3116
  if (nextDefaultValues === void 0) {
2984
3117
  state.reset();
@@ -2993,15 +3126,15 @@ function buildFormApi(state, formInstanceId, options = {}) {
2993
3126
  state.originalBlankPaths.add(pathKey);
2994
3127
  }
2995
3128
  }
2996
- if (persistence !== void 0) {
2997
- void persistence.clearPersistedDraft().catch(() => void 0);
3129
+ if (persistenceHandle !== void 0) {
3130
+ void persistenceHandle.ready.then((m) => m?.clearPersistedDraft()).catch(() => void 0);
2998
3131
  }
2999
3132
  };
3000
3133
  const resetField = (pathInput) => {
3001
3134
  const segments = paths.canonicalizePath(pathInput).segments;
3002
3135
  state.resetField(segments);
3003
- if (persistence !== void 0) {
3004
- void persistence.clearPersistedDraft(segments).catch(() => void 0);
3136
+ if (persistenceHandle !== void 0) {
3137
+ void persistenceHandle.ready.then((m) => m?.clearPersistedDraft(segments)).catch(() => void 0);
3005
3138
  }
3006
3139
  };
3007
3140
  function clear(pathInput) {
@@ -3019,10 +3152,14 @@ function buildFormApi(state, formInstanceId, options = {}) {
3019
3152
  )) {
3020
3153
  return;
3021
3154
  }
3155
+ if (persistenceHandle === void 0) return;
3156
+ const persistence = await persistenceHandle.ready;
3022
3157
  if (persistence === void 0) return;
3023
3158
  await persistence.writePathImmediately(segments);
3024
3159
  };
3025
3160
  const clearPersistedDraft = async (pathInput) => {
3161
+ if (persistenceHandle === void 0) return;
3162
+ const persistence = await persistenceHandle.ready;
3026
3163
  if (persistence === void 0) return;
3027
3164
  if (pathInput === void 0) {
3028
3165
  await persistence.clearPersistedDraft();
@@ -3132,7 +3269,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
3132
3269
  setValue: gated(setValueImpl),
3133
3270
  validate: gated(validate),
3134
3271
  validateAsync: gated(validateAsync),
3135
- process: gated(process),
3272
+ parse: gated(parse),
3273
+ settleTransforms: gated(state.settleTransforms),
3136
3274
  register: gated(register),
3137
3275
  key: state.formKey,
3138
3276
  // Auto-unwrapping views over the per-store async-defaults lifecycle
@@ -3205,6 +3343,93 @@ function buildFormApi(state, formInstanceId, options = {}) {
3205
3343
  };
3206
3344
  }
3207
3345
 
3346
+ const IDLE = Object.freeze({ display: "idle" });
3347
+ const MAX_DELAY = 2147483647;
3348
+ function createDisplayEngine(ssr) {
3349
+ const machines = /* @__PURE__ */ new Map();
3350
+ const tick = vue.ref(0);
3351
+ let timer = null;
3352
+ let timerTarget = null;
3353
+ let lastFiredTarget = null;
3354
+ function clearTimer() {
3355
+ if (timer !== null) {
3356
+ clearTimeout(timer);
3357
+ timer = null;
3358
+ timerTarget = null;
3359
+ }
3360
+ }
3361
+ function nearestReviewAt() {
3362
+ let min = null;
3363
+ for (const m of machines.values()) {
3364
+ if (m.reviewAt === void 0 || !Number.isFinite(m.reviewAt)) continue;
3365
+ if (min === null || m.reviewAt < min) min = m.reviewAt;
3366
+ }
3367
+ return min;
3368
+ }
3369
+ function rearm(now) {
3370
+ const target = nearestReviewAt();
3371
+ if (target === null) {
3372
+ clearTimer();
3373
+ return;
3374
+ }
3375
+ if (timer !== null && timerTarget === target) return;
3376
+ if (target === lastFiredTarget) {
3377
+ clearTimer();
3378
+ return;
3379
+ }
3380
+ clearTimer();
3381
+ timerTarget = target;
3382
+ timer = setTimeout(
3383
+ () => {
3384
+ timer = null;
3385
+ timerTarget = null;
3386
+ lastFiredTarget = target;
3387
+ tick.value++;
3388
+ },
3389
+ Math.min(MAX_DELAY, Math.max(0, target - now))
3390
+ );
3391
+ }
3392
+ function resolve(key, ctx, reducer) {
3393
+ void tick.value;
3394
+ const prev = machines.get(key) ?? IDLE;
3395
+ const machine = reducer(prev, ctx);
3396
+ if (ssr) return machine;
3397
+ const active = machine.display !== "idle" || machine.reviewAt !== void 0 && Number.isFinite(machine.reviewAt);
3398
+ if (active) machines.set(key, machine);
3399
+ else machines.delete(key);
3400
+ rearm(ctx.now);
3401
+ return machine;
3402
+ }
3403
+ function clear() {
3404
+ machines.clear();
3405
+ clearTimer();
3406
+ lastFiredTarget = null;
3407
+ }
3408
+ let detachVisibility = null;
3409
+ if (!ssr && typeof document !== "undefined") {
3410
+ const onVisible = () => {
3411
+ if (document.visibilityState === "visible" && machines.size > 0) tick.value++;
3412
+ };
3413
+ document.addEventListener("visibilitychange", onVisible);
3414
+ detachVisibility = () => document.removeEventListener("visibilitychange", onVisible);
3415
+ }
3416
+ function dispose() {
3417
+ clear();
3418
+ if (detachVisibility !== null) {
3419
+ detachVisibility();
3420
+ detachVisibility = null;
3421
+ }
3422
+ }
3423
+ return {
3424
+ resolve,
3425
+ clear,
3426
+ dispose,
3427
+ size: () => machines.size,
3428
+ has: (key) => machines.has(key),
3429
+ hasTimer: () => timer !== null
3430
+ };
3431
+ }
3432
+
3208
3433
  function applyDuStubs(schema, data, options = {}) {
3209
3434
  const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
3210
3435
  return walkDuStubs(schema, data, options.basePath ?? [], warned);
@@ -3543,6 +3768,7 @@ function createArrayBookkeeping(deps) {
3543
3768
  blankPaths,
3544
3769
  originalBlankPaths,
3545
3770
  fieldValidationCounts,
3771
+ fieldValidatingSince,
3546
3772
  fieldValidationState,
3547
3773
  schemaErrors,
3548
3774
  activeValidations,
@@ -3568,6 +3794,7 @@ function createArrayBookkeeping(deps) {
3568
3794
  }));
3569
3795
  migrateSetSubtree(blankPaths, arrayPath, remap);
3570
3796
  migrateSetSubtree(originalBlankPaths, arrayPath, remap);
3797
+ migrateMapSubtree(fieldValidatingSince, arrayPath, remap, (since) => since);
3571
3798
  migrateMapSubtree(fieldValidationCounts, arrayPath, remap, (count) => count);
3572
3799
  arrayIdentity.applyRemap(arrayPath, remap);
3573
3800
  }
@@ -3627,6 +3854,18 @@ function isHydratedFieldRecord(value) {
3627
3854
  const r = value;
3628
3855
  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" && typeof r.interacted === "boolean" && typeof r.blurredAfterInteraction === "boolean";
3629
3856
  }
3857
+ function withClearedHistoryFlags(record, now) {
3858
+ return {
3859
+ path: record.path,
3860
+ updatedAt: now,
3861
+ connected: record.connected,
3862
+ focused: record.focused,
3863
+ blurred: record.blurred,
3864
+ touched: false,
3865
+ interacted: false,
3866
+ blurredAfterInteraction: false
3867
+ };
3868
+ }
3630
3869
  function isHydratedValidationErrorArray(value) {
3631
3870
  if (!Array.isArray(value)) return false;
3632
3871
  for (const entry of value) {
@@ -3754,6 +3993,9 @@ function createFormStore(options) {
3754
3993
  const resolvedIsSensitivePath = options.isSensitivePath ?? paths.isSensitivePath;
3755
3994
  const cleanupHooks = [];
3756
3995
  const modules = /* @__PURE__ */ new Map();
3996
+ const fieldValidatingSince = vue.reactive(/* @__PURE__ */ new Map());
3997
+ const displayEngine = createDisplayEngine(ssr);
3998
+ registerCleanup(() => displayEngine.dispose());
3757
3999
  const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
3758
4000
  const schemaResponse = schema.getDefaultValues({
3759
4001
  useDefaultSchemaValues: true,
@@ -3882,12 +4124,118 @@ function createFormStore(options) {
3882
4124
  }
3883
4125
  const fieldValidationCounts = vue.reactive(/* @__PURE__ */ new Map());
3884
4126
  function incFieldValidation(key) {
3885
- fieldValidationCounts.set(key, (fieldValidationCounts.get(key) ?? 0) + 1);
4127
+ fieldValidatingSince.set(key, ssr ? 0 : Date.now());
4128
+ const prevCount = fieldValidationCounts.get(key) ?? 0;
4129
+ fieldValidationCounts.set(key, prevCount + 1);
3886
4130
  }
3887
4131
  function decFieldValidation(key) {
3888
4132
  const next = (fieldValidationCounts.get(key) ?? 0) - 1;
3889
- if (next <= 0) fieldValidationCounts.delete(key);
3890
- else fieldValidationCounts.set(key, next);
4133
+ if (next <= 0) {
4134
+ fieldValidationCounts.delete(key);
4135
+ fieldValidatingSince.delete(key);
4136
+ } else {
4137
+ fieldValidationCounts.set(key, next);
4138
+ }
4139
+ }
4140
+ const fieldTransformCounts = vue.reactive(/* @__PURE__ */ new Map());
4141
+ const fieldTransformingSince = vue.reactive(/* @__PURE__ */ new Map());
4142
+ const transformErrors = vue.reactive(/* @__PURE__ */ new Map());
4143
+ const activeTransforms = vue.ref(0);
4144
+ const transformRuns = /* @__PURE__ */ new Map();
4145
+ let transformTokenSeq = 0;
4146
+ const transformWaiters = [];
4147
+ function incFieldTransform(key) {
4148
+ fieldTransformingSince.set(key, ssr ? 0 : Date.now());
4149
+ fieldTransformCounts.set(key, (fieldTransformCounts.get(key) ?? 0) + 1);
4150
+ }
4151
+ function decFieldTransform(key) {
4152
+ const next = (fieldTransformCounts.get(key) ?? 0) - 1;
4153
+ if (next <= 0) {
4154
+ fieldTransformCounts.delete(key);
4155
+ fieldTransformingSince.delete(key);
4156
+ } else {
4157
+ fieldTransformCounts.set(key, next);
4158
+ }
4159
+ }
4160
+ function flushSettledTransformWaiters() {
4161
+ if (transformWaiters.length === 0) return;
4162
+ const globalIdle = activeTransforms.value === 0;
4163
+ for (let i = transformWaiters.length - 1; i >= 0; i--) {
4164
+ const w = transformWaiters[i];
4165
+ if (w === void 0) continue;
4166
+ const idle = w.key === null ? globalIdle : (fieldTransformCounts.get(w.key) ?? 0) === 0;
4167
+ if (idle) {
4168
+ transformWaiters.splice(i, 1);
4169
+ w.resolve();
4170
+ }
4171
+ }
4172
+ }
4173
+ function releaseTransformRun(key, run) {
4174
+ if (run.released) return;
4175
+ run.released = true;
4176
+ run.holder.aborted = true;
4177
+ run.holder.controller?.abort();
4178
+ activeTransforms.value = Math.max(0, activeTransforms.value - 1);
4179
+ decFieldTransform(key);
4180
+ }
4181
+ function beginTransform(key, holder) {
4182
+ const prior = transformRuns.get(key);
4183
+ if (prior !== void 0) releaseTransformRun(key, prior);
4184
+ const token = ++transformTokenSeq;
4185
+ transformRuns.set(key, { token, holder, released: false });
4186
+ incFieldTransform(key);
4187
+ activeTransforms.value += 1;
4188
+ if (transformErrors.has(key)) transformErrors.delete(key);
4189
+ return token;
4190
+ }
4191
+ function isCurrentTransform(key, token) {
4192
+ return transformRuns.get(key)?.token === token;
4193
+ }
4194
+ function endTransform(key, token) {
4195
+ const run = transformRuns.get(key);
4196
+ if (run?.token === token) {
4197
+ if (!run.released) {
4198
+ activeTransforms.value = Math.max(0, activeTransforms.value - 1);
4199
+ decFieldTransform(key);
4200
+ }
4201
+ transformRuns.delete(key);
4202
+ }
4203
+ flushSettledTransformWaiters();
4204
+ }
4205
+ function setTransformError(key, err) {
4206
+ transformErrors.set(key, err);
4207
+ }
4208
+ function cancelTransforms() {
4209
+ for (const [key, run] of [...transformRuns]) {
4210
+ releaseTransformRun(key, run);
4211
+ transformRuns.delete(key);
4212
+ }
4213
+ if (transformErrors.size > 0) transformErrors.clear();
4214
+ flushSettledTransformWaiters();
4215
+ }
4216
+ function cancelTransformsUnder(prefix) {
4217
+ for (const [key, run] of [...transformRuns]) {
4218
+ const segs = paths.segmentsForPathKey(key);
4219
+ if (segs === null) continue;
4220
+ if (!paths.isPathPrefix(prefix, segs)) continue;
4221
+ releaseTransformRun(key, run);
4222
+ transformRuns.delete(key);
4223
+ transformErrors.delete(key);
4224
+ }
4225
+ flushSettledTransformWaiters();
4226
+ }
4227
+ function settleTransforms(path) {
4228
+ if (path === void 0) {
4229
+ if (activeTransforms.value === 0) return Promise.resolve();
4230
+ return new Promise((resolve) => {
4231
+ transformWaiters.push({ key: null, resolve });
4232
+ });
4233
+ }
4234
+ const { key } = paths.canonicalizePath(path);
4235
+ if ((fieldTransformCounts.get(key) ?? 0) === 0) return Promise.resolve();
4236
+ return new Promise((resolve) => {
4237
+ transformWaiters.push({ key, resolve });
4238
+ });
3891
4239
  }
3892
4240
  const initStamp = (/* @__PURE__ */ new Date()).toISOString();
3893
4241
  diffAndApply({}, schemaInitialData, [], (patch) => {
@@ -3975,6 +4323,7 @@ function createFormStore(options) {
3975
4323
  blankPaths,
3976
4324
  originalBlankPaths,
3977
4325
  fieldValidationCounts,
4326
+ fieldValidatingSince,
3978
4327
  fieldValidationState,
3979
4328
  schemaErrors,
3980
4329
  activeValidations,
@@ -4037,6 +4386,7 @@ function createFormStore(options) {
4037
4386
  }
4038
4387
  }
4039
4388
  }
4389
+ if (transformRuns.size !== 0) cancelTransformsUnder(path);
4040
4390
  if (meta?.skipDiscriminatorReshape !== true) {
4041
4391
  if (path.length > 0) {
4042
4392
  const last = path[path.length - 1];
@@ -4242,7 +4592,7 @@ function createFormStore(options) {
4242
4592
  prev.controller.abort();
4243
4593
  }
4244
4594
  const controller = new AbortController();
4245
- const fresh = { controller, timer: null, settled: false };
4595
+ const fresh = { controller, timer: null, settled: false, released: false };
4246
4596
  fieldValidationState.set(key, fresh);
4247
4597
  const myEpoch = ++scheduleEpoch;
4248
4598
  const run = () => {
@@ -4280,8 +4630,10 @@ function createFormStore(options) {
4280
4630
  applySchemaErrorsForSubtree(scopePath ?? [], restamped);
4281
4631
  }).catch(() => {
4282
4632
  }).finally(() => {
4283
- activeValidations.value = Math.max(0, activeValidations.value - 1);
4284
- decFieldValidation(key);
4633
+ if (!fresh.released) {
4634
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
4635
+ decFieldValidation(key);
4636
+ }
4285
4637
  fresh.settled = true;
4286
4638
  });
4287
4639
  };
@@ -4303,6 +4655,22 @@ function createFormStore(options) {
4303
4655
  }
4304
4656
  fieldValidationState.clear();
4305
4657
  }
4658
+ function cancelFieldValidationUnder(prefix) {
4659
+ for (const [key, entry] of [...fieldValidationState]) {
4660
+ const segs = paths.segmentsForPathKey(key);
4661
+ if (segs === null) continue;
4662
+ if (!paths.isPathPrefix(prefix, segs)) continue;
4663
+ if (entry.timer !== null) {
4664
+ clearTimeout(entry.timer);
4665
+ } else if (!entry.settled && !entry.released) {
4666
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
4667
+ decFieldValidation(key);
4668
+ entry.released = true;
4669
+ }
4670
+ entry.controller.abort();
4671
+ fieldValidationState.delete(key);
4672
+ }
4673
+ }
4306
4674
  function onFormChange(listener) {
4307
4675
  formChangeListeners.add(listener);
4308
4676
  return () => {
@@ -4353,6 +4721,8 @@ function createFormStore(options) {
4353
4721
  drainHooks.length = 0;
4354
4722
  modules.clear();
4355
4723
  cancelFieldValidation();
4724
+ cancelTransforms();
4725
+ fieldValidatingSince.clear();
4356
4726
  formChangeListeners.clear();
4357
4727
  submitSuccessListeners.clear();
4358
4728
  resetListeners.clear();
@@ -4488,6 +4858,7 @@ function createFormStore(options) {
4488
4858
  if (remaining === 0) {
4489
4859
  elements.delete(key);
4490
4860
  touchFieldRecord(key, path, { connected: false, focused: null, blurred: null });
4861
+ if (transformRuns.size !== 0) cancelTransformsUnder(path);
4491
4862
  }
4492
4863
  return remaining;
4493
4864
  }
@@ -4699,16 +5070,7 @@ function createFormStore(options) {
4699
5070
  }
4700
5071
  const now = (/* @__PURE__ */ new Date()).toISOString();
4701
5072
  for (const [pathKey, record] of fields) {
4702
- fields.set(pathKey, {
4703
- path: record.path,
4704
- updatedAt: now,
4705
- connected: record.connected,
4706
- focused: record.focused,
4707
- blurred: record.blurred,
4708
- touched: false,
4709
- interacted: false,
4710
- blurredAfterInteraction: false
4711
- });
5073
+ fields.set(pathKey, withClearedHistoryFlags(record, now));
4712
5074
  }
4713
5075
  submissionGeneration.value += 1;
4714
5076
  submitting.value = false;
@@ -4718,6 +5080,9 @@ function createFormStore(options) {
4718
5080
  submitError.value = null;
4719
5081
  departAttempts.value = 0;
4720
5082
  cancelFieldValidation();
5083
+ cancelTransforms();
5084
+ displayEngine.clear();
5085
+ fieldValidatingSince.clear();
4721
5086
  pathSnapshots.clear();
4722
5087
  scheduleEpoch = 0;
4723
5088
  lastCommittedEpoch = 0;
@@ -4733,6 +5098,13 @@ function createFormStore(options) {
4733
5098
  function resetField(path) {
4734
5099
  const { key: targetKey, segments: targetSegments } = paths.canonicalizePath(path);
4735
5100
  variantMemory.clearUnderPath(targetSegments);
5101
+ cancelFieldValidationUnder(targetSegments);
5102
+ cancelTransformsUnder(targetSegments);
5103
+ for (const [snapKey] of [...pathSnapshots]) {
5104
+ const segs = paths.segmentsForPathKey(snapKey);
5105
+ if (segs === null) continue;
5106
+ if (paths.isPathPrefix(targetSegments, segs)) pathSnapshots.delete(snapKey);
5107
+ }
4736
5108
  const leafEntry = originals.get(targetKey);
4737
5109
  if (leafEntry !== void 0) {
4738
5110
  const wrote = setValueAtPath(targetSegments, leafEntry.value);
@@ -4782,16 +5154,7 @@ function createFormStore(options) {
4782
5154
  function clearFieldRecordFlags(pathKey) {
4783
5155
  const record = fields.get(pathKey);
4784
5156
  if (record === void 0) return;
4785
- fields.set(pathKey, {
4786
- path: record.path,
4787
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4788
- connected: record.connected,
4789
- focused: record.focused,
4790
- blurred: record.blurred,
4791
- touched: false,
4792
- interacted: false,
4793
- blurredAfterInteraction: false
4794
- });
5157
+ fields.set(pathKey, withClearedHistoryFlags(record, (/* @__PURE__ */ new Date()).toISOString()));
4795
5158
  }
4796
5159
  function isPristineAtPath(path) {
4797
5160
  const { key, segments } = paths.canonicalizePath(path);
@@ -4871,6 +5234,12 @@ function createFormStore(options) {
4871
5234
  pathHasAsyncValidation,
4872
5235
  pathHasAsyncValidationByKey,
4873
5236
  fieldValidationCounts,
5237
+ fieldValidatingSince,
5238
+ fieldTransformCounts,
5239
+ fieldTransformingSince,
5240
+ transformErrors,
5241
+ activeTransforms,
5242
+ displayEngine,
4874
5243
  applyFormReplacement,
4875
5244
  setValueAtPath,
4876
5245
  getValueAtPath,
@@ -4899,6 +5268,13 @@ function createFormStore(options) {
4899
5268
  getOriginalAtPath,
4900
5269
  getFirstErrorElement,
4901
5270
  cancelFieldValidation,
5271
+ beginTransform,
5272
+ isCurrentTransform,
5273
+ endTransform,
5274
+ setTransformError,
5275
+ cancelTransforms,
5276
+ cancelTransformsUnder,
5277
+ settleTransforms,
4902
5278
  scheduleFieldValidation,
4903
5279
  onFormChange,
4904
5280
  onSubmitSuccess,
@@ -4955,7 +5331,7 @@ function errorsEqual(a, b) {
4955
5331
  }
4956
5332
  return true;
4957
5333
  }
4958
- function diffBlankPaths$1(prev, curr) {
5334
+ function diffBlankPaths(prev, curr) {
4959
5335
  const added = [];
4960
5336
  const removed = [];
4961
5337
  for (const k of curr) if (!prev.has(k)) added.push(k);
@@ -5043,7 +5419,7 @@ function createHistoryModule(state, config) {
5043
5419
  diffAndApply(prevSnap.form, newSnap.form, [], (p) => formPatches.push(p));
5044
5420
  const prevBlankSet = new Set(prevSnap.blankPaths);
5045
5421
  const currBlankSet = new Set(newSnap.blankPaths);
5046
- const blankDiff = diffBlankPaths$1(prevBlankSet, currBlankSet);
5422
+ const blankDiff = diffBlankPaths(prevBlankSet, currBlankSet);
5047
5423
  const delta = {
5048
5424
  formPatches,
5049
5425
  blankPathsAdded: blankDiff.added,
@@ -5110,643 +5486,6 @@ function createHistoryModule(state, config) {
5110
5486
  };
5111
5487
  }
5112
5488
 
5113
- function wirePersistence(state, config) {
5114
- let fingerprint;
5115
- try {
5116
- fingerprint = hashStableString(state.schema.fingerprint());
5117
- } catch (err) {
5118
- if (paths.__DEV__) {
5119
- console.warn(
5120
- `[attaform] Could not fingerprint the schema for form '${state.formKey}': ${err instanceof Error ? err.message : String(err)}. Persistence falls back to a fingerprint-free key, so a schema change won't auto-invalidate a saved draft.`
5121
- );
5122
- }
5123
- fingerprint = "unfingerprinted";
5124
- }
5125
- const base = resolveStorageKeyBase(config, state.formKey);
5126
- const key = `${base}:${fingerprint}`;
5127
- const debounceMs = normalizeNumericOption({
5128
- value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
5129
- source: "useForm.persist.debounceMs",
5130
- allowInfinity: false,
5131
- min: 0,
5132
- defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
5133
- });
5134
- const include = config.include ?? "form";
5135
- const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
5136
- const adapterPromise = getStorageAdapter(config.storage);
5137
- let disposed = false;
5138
- const isDisposed = () => disposed;
5139
- let inFlightFinalFlush = null;
5140
- let pendingOptedInPaths = null;
5141
- const writer = createDebouncedWriter(async () => {
5142
- const optedInPaths = pendingOptedInPaths ?? new Set(state.persistOptIns.optedInPaths());
5143
- pendingOptedInPaths = null;
5144
- const adapter = await adapterPromise;
5145
- if (isDisposed()) return;
5146
- if (optedInPaths.size === 0) {
5147
- await adapter.removeItem(key);
5148
- return;
5149
- }
5150
- const rawForm = vue.toRaw(state.form.value);
5151
- const filteredForm = stripUnacknowledgedSensitiveLeaves(
5152
- pluckPaths(rawForm, optedInPaths),
5153
- optedInPaths,
5154
- state.isSensitivePath
5155
- );
5156
- const filteredSchemaErrors = filterErrorsByPaths(state.schemaErrors, optedInPaths);
5157
- const filteredUserErrors = filterErrorsByPaths(state.userErrors, optedInPaths);
5158
- const filteredTransientEmpty = /* @__PURE__ */ new Set();
5159
- for (const tk of state.blankPaths) {
5160
- if (optedInPaths.has(tk)) filteredTransientEmpty.add(tk);
5161
- }
5162
- const payload = buildPersistedPayload(
5163
- filteredForm,
5164
- include,
5165
- filteredSchemaErrors,
5166
- filteredUserErrors,
5167
- filteredTransientEmpty
5168
- );
5169
- await adapter.setItem(key, payload);
5170
- }, debounceMs);
5171
- const unsubscribeChange = state.onFormChange((_next, meta) => {
5172
- if (isDisposed() || inFlightFinalFlush !== null) return;
5173
- if (meta?.crossTab === true) return;
5174
- if (meta?.persist !== true) return;
5175
- pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
5176
- writer.schedule();
5177
- });
5178
- const unsubscribeSuccess = clearOnSubmitSuccess ? state.onSubmitSuccess(() => {
5179
- if (isDisposed()) return;
5180
- void (async () => {
5181
- await writer.flush();
5182
- if (isDisposed()) return;
5183
- const adapter = await adapterPromise;
5184
- if (isDisposed()) return;
5185
- await adapter.removeItem(key);
5186
- })();
5187
- }) : () => void 0;
5188
- const handlePageHide = () => {
5189
- if (isDisposed()) return;
5190
- void writer.flush();
5191
- };
5192
- if (typeof window !== "undefined") {
5193
- window.addEventListener("pagehide", handlePageHide);
5194
- }
5195
- void (async () => {
5196
- const adapter = await adapterPromise;
5197
- if (isDisposed()) return;
5198
- void cleanupOrphanKeys(adapter, base, key);
5199
- try {
5200
- const raw = await adapter.getItem(key);
5201
- const payload = readPersistedPayload(raw);
5202
- if (payload === null) {
5203
- if (raw !== null && raw !== void 0) {
5204
- await adapter.removeItem(key);
5205
- }
5206
- return;
5207
- }
5208
- if (isDisposed()) return;
5209
- const merged = mergeSparseHydration(
5210
- vue.toRaw(state.form.value),
5211
- payload.data.form,
5212
- state.schema
5213
- );
5214
- state.applyFormReplacement(merged, { hydration: true });
5215
- const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
5216
- for (const k of persistedLeafPaths) {
5217
- state.blankPaths.delete(k);
5218
- state.originalBlankPaths.delete(k);
5219
- }
5220
- for (const k of payload.data.blankPaths ?? []) {
5221
- state.blankPaths.add(k);
5222
- state.originalBlankPaths.add(k);
5223
- }
5224
- if (include === "form+errors") {
5225
- if (payload.data.schemaErrors !== void 0) {
5226
- const flat = payload.data.schemaErrors.flatMap(([, errs]) => errs);
5227
- state.setAllSchemaErrors(flat);
5228
- }
5229
- if (payload.data.userErrors !== void 0) {
5230
- const flat = payload.data.userErrors.flatMap(([, errs]) => errs);
5231
- state.setAllUserErrors(flat);
5232
- }
5233
- }
5234
- state.scheduleFieldValidation(
5235
- [],
5236
- true
5237
- /* immediate */
5238
- );
5239
- } catch {
5240
- }
5241
- })();
5242
- async function writePathImmediately(path) {
5243
- if (isDisposed()) return;
5244
- await writer.flush();
5245
- if (isDisposed()) return;
5246
- const adapter = await adapterPromise;
5247
- if (isDisposed()) return;
5248
- const raw = await adapter.getItem(key);
5249
- const existing = readPersistedPayload(raw);
5250
- const value = getAtPath(vue.toRaw(state.form.value), path);
5251
- const nextForm = setAtPath(existing?.data.form ?? {}, path, value);
5252
- const { key: pathKey } = paths.canonicalizePath(path);
5253
- const transientSet = new Set(
5254
- (existing?.data.blankPaths ?? []).filter(
5255
- (k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
5256
- )
5257
- );
5258
- for (const liveKey of state.blankPaths) {
5259
- if (liveKey === pathKey || isDescendantPathKey(liveKey, pathKey)) {
5260
- transientSet.add(liveKey);
5261
- }
5262
- }
5263
- if (include === "form") {
5264
- await adapter.setItem(
5265
- key,
5266
- buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
5267
- );
5268
- return;
5269
- }
5270
- const schemaMap = new Map(existing?.data.schemaErrors ?? []);
5271
- const userMap = new Map(existing?.data.userErrors ?? []);
5272
- const currentSchema = state.schemaErrors.get(pathKey);
5273
- const currentUser = state.userErrors.get(pathKey);
5274
- if (currentSchema !== void 0 && currentSchema.length > 0) {
5275
- schemaMap.set(pathKey, [...currentSchema]);
5276
- } else {
5277
- schemaMap.delete(pathKey);
5278
- }
5279
- if (currentUser !== void 0 && currentUser.length > 0) {
5280
- userMap.set(pathKey, [...currentUser]);
5281
- } else {
5282
- userMap.delete(pathKey);
5283
- }
5284
- await adapter.setItem(
5285
- key,
5286
- buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
5287
- );
5288
- }
5289
- async function clearPersistedDraft(path) {
5290
- if (isDisposed()) return;
5291
- await writer.flush();
5292
- if (isDisposed()) return;
5293
- const adapter = await adapterPromise;
5294
- if (isDisposed()) return;
5295
- if (path === void 0) {
5296
- await adapter.removeItem(key);
5297
- return;
5298
- }
5299
- const raw = await adapter.getItem(key);
5300
- const existing = readPersistedPayload(raw);
5301
- if (existing === null) return;
5302
- const nextForm = deleteAtPath(existing.data.form, path);
5303
- if (isEmptyContainer(nextForm)) {
5304
- await adapter.removeItem(key);
5305
- return;
5306
- }
5307
- const { key: pathKey } = paths.canonicalizePath(path);
5308
- const transientSet = new Set(
5309
- (existing.data.blankPaths ?? []).filter(
5310
- (k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
5311
- )
5312
- );
5313
- if (include === "form") {
5314
- await adapter.setItem(
5315
- key,
5316
- buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
5317
- );
5318
- return;
5319
- }
5320
- const schemaErrors = (existing.data.schemaErrors ?? []).filter(([k]) => k !== pathKey);
5321
- const userErrors = (existing.data.userErrors ?? []).filter(([k]) => k !== pathKey);
5322
- const schemaMap = new Map(schemaErrors.map(([k, v]) => [k, [...v]]));
5323
- const userMap = new Map(userErrors.map(([k, v]) => [k, [...v]]));
5324
- await adapter.setItem(
5325
- key,
5326
- buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
5327
- );
5328
- }
5329
- function awaitPendingWrites() {
5330
- if (inFlightFinalFlush !== null) return inFlightFinalFlush;
5331
- if (isDisposed()) return Promise.resolve();
5332
- return writer.flush().catch(() => void 0);
5333
- }
5334
- function dispose() {
5335
- if (isDisposed() || inFlightFinalFlush !== null) return;
5336
- unsubscribeChange();
5337
- unsubscribeSuccess();
5338
- if (typeof window !== "undefined") {
5339
- window.removeEventListener("pagehide", handlePageHide);
5340
- }
5341
- inFlightFinalFlush = writer.flush().catch(() => void 0).finally(() => {
5342
- disposed = true;
5343
- inFlightFinalFlush = null;
5344
- });
5345
- }
5346
- return {
5347
- wiredConfig: config,
5348
- writePathImmediately,
5349
- clearPersistedDraft,
5350
- awaitPendingWrites,
5351
- dispose
5352
- };
5353
- }
5354
- function isEmptyContainer(value) {
5355
- if (value === void 0 || value === null) return true;
5356
- if (Array.isArray(value)) return value.length === 0;
5357
- if (isPlainRecord(value)) return Object.keys(value).length === 0;
5358
- return false;
5359
- }
5360
- function collectPersistedLeafPaths(form) {
5361
- const out = [];
5362
- walk(form, []);
5363
- return out;
5364
- function walk(node, prefix) {
5365
- if (Array.isArray(node)) {
5366
- for (let i = 0; i < node.length; i++) {
5367
- walk(node[i], [...prefix, i]);
5368
- }
5369
- return;
5370
- }
5371
- if (isPlainRecord(node)) {
5372
- for (const key of Object.keys(node)) {
5373
- walk(node[key], [...prefix, key]);
5374
- }
5375
- return;
5376
- }
5377
- if (prefix.length === 0) return;
5378
- out.push(paths.canonicalizePath(prefix).key);
5379
- }
5380
- }
5381
- function isDescendantPathKey(candidate, ancestor) {
5382
- if (candidate.length <= ancestor.length) return false;
5383
- if (!ancestor.endsWith("]")) return false;
5384
- const childPrefix = `${ancestor.slice(0, -1)},`;
5385
- return candidate.startsWith(childPrefix);
5386
- }
5387
-
5388
- const PROTOCOL_VERSION = 1;
5389
- const JOIN_COLLECTION_WINDOW_MS = 50;
5390
- const SNAPSHOT_TIMEOUT_MS = 200;
5391
- const MAX_LEADER_ATTEMPTS = 3;
5392
- const SNAPSHOT_RESPONSE_MIN_INTERVAL_MS = 500;
5393
- function isFileLikeValue(value) {
5394
- if (typeof File !== "undefined" && value instanceof File) return true;
5395
- if (typeof Blob !== "undefined" && value instanceof Blob) return true;
5396
- return false;
5397
- }
5398
- function isInboundShapeAcceptable(schema, path, value) {
5399
- if (isFileLikeValue(value)) return false;
5400
- let kind;
5401
- if (Array.isArray(value)) {
5402
- kind = "array";
5403
- } else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
5404
- kind = "object";
5405
- } else {
5406
- kind = slimKindOf(value);
5407
- }
5408
- const accepted = schema.getSlimPrimitiveTypesAtPath(path);
5409
- if (!accepted.has(kind)) return false;
5410
- if (Array.isArray(value)) {
5411
- for (let i = 0; i < value.length; i++) {
5412
- if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
5413
- }
5414
- return true;
5415
- }
5416
- if (isPlainRecord(value)) {
5417
- for (const key of Object.keys(value)) {
5418
- if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
5419
- }
5420
- return true;
5421
- }
5422
- return true;
5423
- }
5424
- function diffBlankPaths(prev, curr) {
5425
- const added = [];
5426
- const removed = [];
5427
- const prevSet = new Set(prev);
5428
- for (const k of curr) if (!prevSet.has(k)) added.push(k);
5429
- for (const k of prev) if (!curr.has(k)) removed.push(k);
5430
- return { added, removed };
5431
- }
5432
- function snapshotForm(form) {
5433
- return structuralSnapshot(form);
5434
- }
5435
- function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
5436
- if (isFileLikeValue(value)) return void 0;
5437
- if (value === null || typeof value !== "object") return value;
5438
- if (Array.isArray(value)) {
5439
- return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
5440
- }
5441
- const proto = Object.getPrototypeOf(value);
5442
- if (proto !== Object.prototype && proto !== null) return value;
5443
- const out = {};
5444
- const src = value;
5445
- for (const key of Object.keys(src)) {
5446
- const childPath = [...pathSoFar, key];
5447
- if (isSensitivePath(childPath)) {
5448
- safeAssign(out, key, void 0);
5449
- continue;
5450
- }
5451
- safeAssign(out, key, stripSensitivePathsDeep(src[key], childPath, isSensitivePath));
5452
- }
5453
- return out;
5454
- }
5455
- function isStringArray(value) {
5456
- return Array.isArray(value) && value.every((item) => typeof item === "string");
5457
- }
5458
- function isPatchArray(value) {
5459
- return Array.isArray(value) && value.every(
5460
- (p) => p !== null && typeof p === "object" && Array.isArray(p.path)
5461
- );
5462
- }
5463
- function isValidSyncMessage(data) {
5464
- if (data === null || typeof data !== "object") return false;
5465
- const m = data;
5466
- if (m["v"] !== PROTOCOL_VERSION) return false;
5467
- if (typeof m["senderId"] !== "string") return false;
5468
- if (typeof m["kind"] !== "string") return false;
5469
- switch (m["kind"]) {
5470
- case "hello":
5471
- case "announce":
5472
- return true;
5473
- case "requestSnapshot":
5474
- return typeof m["targetId"] === "string";
5475
- case "snapshot":
5476
- return isStringArray(m["blankPaths"]) && "form" in m;
5477
- case "patches":
5478
- return isPatchArray(m["formPatches"]) && isStringArray(m["blankPathsAdded"]) && isStringArray(m["blankPathsRemoved"]);
5479
- default:
5480
- return false;
5481
- }
5482
- }
5483
- function generateSenderId() {
5484
- try {
5485
- return globalThis.crypto.randomUUID();
5486
- } catch {
5487
- return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
5488
- }
5489
- }
5490
- function createMultiTabSyncModule(state, channelName, options) {
5491
- if (typeof BroadcastChannel === "undefined") {
5492
- return {
5493
- dispose: () => void 0,
5494
- lifecycle: () => "established",
5495
- senderId: "",
5496
- channelName
5497
- };
5498
- }
5499
- let channel;
5500
- try {
5501
- channel = new BroadcastChannel(channelName);
5502
- } catch {
5503
- return {
5504
- dispose: () => void 0,
5505
- lifecycle: () => "established",
5506
- senderId: "",
5507
- channelName
5508
- };
5509
- }
5510
- const senderId = generateSenderId();
5511
- let lifecycle = "joining";
5512
- let disposed = false;
5513
- const peerIds = /* @__PURE__ */ new Set();
5514
- const lastSnapshotResponseAt = /* @__PURE__ */ new Map();
5515
- let joinCollectionTimer = null;
5516
- let snapshotTimeoutTimer = null;
5517
- let leaderAttempts = 0;
5518
- let prior = {
5519
- form: snapshotForm(state.form.value),
5520
- blankPathsSnapshot: [...state.blankPaths]
5521
- };
5522
- function safePost(msg) {
5523
- if (disposed) return;
5524
- try {
5525
- channel.postMessage(msg);
5526
- } catch {
5527
- }
5528
- }
5529
- function refreshPrior() {
5530
- prior = {
5531
- form: snapshotForm(state.form.value),
5532
- blankPathsSnapshot: [...state.blankPaths]
5533
- };
5534
- }
5535
- function isPathLocallySuppressed(path) {
5536
- if (options.isSensitivePath(path)) return true;
5537
- const { key } = paths.canonicalizePath([...path]);
5538
- if (options.noSyncPaths.has(key)) return true;
5539
- return false;
5540
- }
5541
- function postPatches() {
5542
- if (lifecycle !== "established") return;
5543
- const next = snapshotForm(state.form.value);
5544
- const rawPatches = [];
5545
- diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
5546
- const safePatches = [];
5547
- for (const p of rawPatches) {
5548
- if (isPathLocallySuppressed(p.path)) continue;
5549
- if ("value" in p && isFileLikeValue(p.value)) continue;
5550
- safePatches.push(p);
5551
- }
5552
- const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
5553
- if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
5554
- prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
5555
- return;
5556
- }
5557
- safePost({
5558
- v: PROTOCOL_VERSION,
5559
- kind: "patches",
5560
- senderId,
5561
- formPatches: safePatches,
5562
- blankPathsAdded: added,
5563
- blankPathsRemoved: removed
5564
- });
5565
- prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
5566
- }
5567
- const unsubscribeChange = state.onFormChange((_next, meta) => {
5568
- if (disposed) return;
5569
- if (lifecycle !== "established") return;
5570
- if (meta?.crossTab === true) return;
5571
- if (meta?.hydration === true) {
5572
- refreshPrior();
5573
- return;
5574
- }
5575
- postPatches();
5576
- });
5577
- function applyIncomingForm(form, blankPaths) {
5578
- state.blankPaths.clear();
5579
- for (const k of blankPaths) state.blankPaths.add(k);
5580
- state.applyFormReplacement(form, { crossTab: true, persist: false });
5581
- refreshPrior();
5582
- }
5583
- function handlePatches(msg) {
5584
- if (lifecycle !== "established") return;
5585
- const safePatches = [];
5586
- for (const p of msg.formPatches) {
5587
- if (!Array.isArray(p.path)) continue;
5588
- if (isPathLocallySuppressed(p.path)) continue;
5589
- if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
5590
- continue;
5591
- }
5592
- safePatches.push(p);
5593
- }
5594
- const safeBlankAdded = [];
5595
- for (const k of msg.blankPathsAdded) {
5596
- const segs = paths.canonicalizePath(k).segments;
5597
- if (isPathLocallySuppressed(segs)) continue;
5598
- safeBlankAdded.push(k);
5599
- }
5600
- const safeBlankRemoved = [];
5601
- for (const k of msg.blankPathsRemoved) {
5602
- const segs = paths.canonicalizePath(k).segments;
5603
- if (isPathLocallySuppressed(segs)) continue;
5604
- safeBlankRemoved.push(k);
5605
- }
5606
- if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
5607
- return;
5608
- }
5609
- const candidate = applyPatchesForward(state.form.value, safePatches);
5610
- try {
5611
- options.validateForm(state.form.value);
5612
- try {
5613
- options.validateForm(candidate);
5614
- } catch {
5615
- return;
5616
- }
5617
- } catch {
5618
- }
5619
- const nextBlankPaths = new Set(state.blankPaths);
5620
- for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
5621
- for (const k of safeBlankAdded) nextBlankPaths.add(k);
5622
- applyIncomingForm(candidate, [...nextBlankPaths]);
5623
- }
5624
- function handleSnapshot(msg) {
5625
- if (lifecycle !== "joining") return;
5626
- if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
5627
- if (snapshotTimeoutTimer !== null) {
5628
- clearTimeout(snapshotTimeoutTimer);
5629
- snapshotTimeoutTimer = null;
5630
- }
5631
- if (joinCollectionTimer !== null) {
5632
- clearTimeout(joinCollectionTimer);
5633
- joinCollectionTimer = null;
5634
- }
5635
- applyIncomingForm(msg.form, msg.blankPaths);
5636
- lifecycle = "established";
5637
- peerIds.clear();
5638
- }
5639
- function respondToHello() {
5640
- safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
5641
- }
5642
- function respondToSnapshotRequest(requesterId) {
5643
- const now = Date.now();
5644
- const last = lastSnapshotResponseAt.get(requesterId);
5645
- if (last !== void 0 && now - last < SNAPSHOT_RESPONSE_MIN_INTERVAL_MS) return;
5646
- lastSnapshotResponseAt.set(requesterId, now);
5647
- const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
5648
- safePost({
5649
- v: PROTOCOL_VERSION,
5650
- kind: "snapshot",
5651
- senderId,
5652
- form: scrubbedForm,
5653
- blankPaths: [...state.blankPaths]
5654
- });
5655
- }
5656
- channel.onmessage = (event) => {
5657
- if (disposed) return;
5658
- try {
5659
- const data = event.data;
5660
- if (!isValidSyncMessage(data)) return;
5661
- const msg = data;
5662
- if (msg.senderId === senderId) return;
5663
- switch (msg.kind) {
5664
- case "hello":
5665
- if (lifecycle !== "established") return;
5666
- respondToHello();
5667
- break;
5668
- case "announce":
5669
- if (lifecycle === "joining") peerIds.add(msg.senderId);
5670
- break;
5671
- case "requestSnapshot":
5672
- if (lifecycle !== "established") return;
5673
- if (msg.targetId !== senderId) return;
5674
- respondToSnapshotRequest(msg.senderId);
5675
- break;
5676
- case "snapshot":
5677
- handleSnapshot(msg);
5678
- break;
5679
- case "patches":
5680
- handlePatches(msg);
5681
- break;
5682
- }
5683
- } catch {
5684
- }
5685
- };
5686
- function electLeaderAndRequest() {
5687
- if (disposed) return;
5688
- if (peerIds.size === 0) {
5689
- lifecycle = "established";
5690
- refreshPrior();
5691
- return;
5692
- }
5693
- const sorted = [...peerIds].sort();
5694
- const leaderId = sorted[0];
5695
- peerIds.delete(leaderId);
5696
- leaderAttempts++;
5697
- safePost({
5698
- v: PROTOCOL_VERSION,
5699
- kind: "requestSnapshot",
5700
- senderId,
5701
- targetId: leaderId
5702
- });
5703
- snapshotTimeoutTimer = setTimeout(() => {
5704
- snapshotTimeoutTimer = null;
5705
- if (disposed) return;
5706
- if (lifecycle === "established") return;
5707
- if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
5708
- lifecycle = "established";
5709
- refreshPrior();
5710
- return;
5711
- }
5712
- electLeaderAndRequest();
5713
- }, SNAPSHOT_TIMEOUT_MS);
5714
- }
5715
- function joinFlow() {
5716
- safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
5717
- joinCollectionTimer = setTimeout(() => {
5718
- joinCollectionTimer = null;
5719
- if (disposed) return;
5720
- if (lifecycle === "established") return;
5721
- electLeaderAndRequest();
5722
- }, JOIN_COLLECTION_WINDOW_MS);
5723
- }
5724
- joinFlow();
5725
- return {
5726
- dispose: () => {
5727
- if (disposed) return;
5728
- disposed = true;
5729
- if (joinCollectionTimer !== null) {
5730
- clearTimeout(joinCollectionTimer);
5731
- joinCollectionTimer = null;
5732
- }
5733
- if (snapshotTimeoutTimer !== null) {
5734
- clearTimeout(snapshotTimeoutTimer);
5735
- snapshotTimeoutTimer = null;
5736
- }
5737
- unsubscribeChange();
5738
- try {
5739
- channel.close();
5740
- } catch {
5741
- }
5742
- },
5743
- lifecycle: () => lifecycle,
5744
- senderId,
5745
- channelName
5746
- };
5747
- }
5748
- const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
5749
-
5750
5489
  const warned = /* @__PURE__ */ new Set();
5751
5490
  function warnOnceInsecureContext(feature) {
5752
5491
  if (!paths.__DEV__) return;
@@ -5806,8 +5545,10 @@ function useAbstractForm(configuration, options) {
5806
5545
  }
5807
5546
  const existing = registry.forms.get(key);
5808
5547
  if (paths.__DEV__ && existing !== void 0) {
5809
- warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
5810
- warnOnPersistDivergence(key, existing, configuration.persist);
5548
+ void import('../chunks/dev-key-collision-warnings.cjs').then((m) => {
5549
+ void m.warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
5550
+ m.warnOnPersistDivergence(key, existing, configuration.persist);
5551
+ });
5811
5552
  }
5812
5553
  const hadPendingHydration = registry.pendingHydration.has(key);
5813
5554
  const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
@@ -5848,10 +5589,33 @@ function useAbstractForm(configuration, options) {
5848
5589
  } else {
5849
5590
  const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
5850
5591
  void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
5851
- const persistenceModule = wirePersistence(state, resolvedPersist);
5852
- state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
5853
- state.registerDrain(() => persistenceModule.awaitPendingWrites());
5854
- state.registerCleanup(() => persistenceModule.dispose());
5592
+ const adapterPromise = getStorageAdapter(resolvedPersist.storage);
5593
+ let persistDisposed = false;
5594
+ state.registerCleanup(() => {
5595
+ persistDisposed = true;
5596
+ });
5597
+ const ready = (async () => {
5598
+ try {
5599
+ const [{ wirePersistence }, fingerprintToken] = await Promise.all([
5600
+ import('../chunks/wire-persistence.cjs'),
5601
+ resolvePersistFingerprintToken(state)
5602
+ ]);
5603
+ if (persistDisposed) return void 0;
5604
+ const persistenceModule = wirePersistence(
5605
+ state,
5606
+ resolvedPersist,
5607
+ adapterPromise,
5608
+ fingerprintToken
5609
+ );
5610
+ state.registerDrain(() => persistenceModule.awaitPendingWrites());
5611
+ state.registerCleanup(() => persistenceModule.dispose());
5612
+ return persistenceModule;
5613
+ } catch {
5614
+ return void 0;
5615
+ }
5616
+ })();
5617
+ const persistenceHandle = { config: resolvedPersist, ready };
5618
+ state.modules.set(PERSISTENCE_MODULE_KEY, persistenceHandle);
5855
5619
  }
5856
5620
  } else {
5857
5621
  void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
@@ -5861,27 +5625,31 @@ function useAbstractForm(configuration, options) {
5861
5625
  const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
5862
5626
  const secureContext = isSecureContext();
5863
5627
  if (hasBroadcastChannel && secureContext) {
5864
- let channelName;
5865
- try {
5866
- channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
5867
- } catch {
5868
- channelName = null;
5869
- }
5870
- if (channelName !== null) {
5871
- const syncModule = createMultiTabSyncModule(state, channelName, {
5872
- isSensitivePath: state.isSensitivePath,
5873
- noSyncPaths: state.noSyncPaths,
5874
- validateForm: (form) => {
5875
- const result = state.schema.validateAtPath(form, void 0, { sync: true });
5876
- if (result instanceof Promise) return;
5877
- if (!result.success) {
5878
- throw new Error("attaform multi-tab sync: post-apply schema validation failed");
5628
+ let formDisposed = false;
5629
+ state.registerCleanup(() => {
5630
+ formDisposed = true;
5631
+ });
5632
+ void (async () => {
5633
+ try {
5634
+ const [{ createMultiTabSyncModule, MULTI_TAB_SYNC_MODULE_KEY }, fingerprint] = await Promise.all([import('../chunks/multi-tab-sync.cjs'), state.schema.fingerprint()]);
5635
+ if (formDisposed) return;
5636
+ const channelName = `attaform:sync:${state.formKey}:${hashStableString(fingerprint)}`;
5637
+ const syncModule = createMultiTabSyncModule(state, channelName, {
5638
+ isSensitivePath: state.isSensitivePath,
5639
+ noSyncPaths: state.noSyncPaths,
5640
+ validateForm: (form) => {
5641
+ const result = state.schema.validateAtPath(form, void 0, { sync: true });
5642
+ if (result instanceof Promise) return;
5643
+ if (!result.success) {
5644
+ throw new Error("attaform multi-tab sync: post-apply schema validation failed");
5645
+ }
5879
5646
  }
5880
- }
5881
- });
5882
- state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
5883
- state.registerCleanup(() => syncModule.dispose());
5884
- }
5647
+ });
5648
+ state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
5649
+ state.registerCleanup(() => syncModule.dispose());
5650
+ } catch {
5651
+ }
5652
+ })();
5885
5653
  } else if (hasBroadcastChannel && !secureContext) {
5886
5654
  warnOnceInsecureContext("multiTab");
5887
5655
  }
@@ -6042,55 +5810,17 @@ function resolveFormKey(key) {
6042
5810
  }
6043
5811
  return `${ANONYMOUS_FORM_KEY_PREFIX}${anonCounter++}`;
6044
5812
  }
6045
- function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
6046
- let existingFp;
6047
- let incomingFp;
5813
+ async function resolvePersistFingerprintToken(state) {
6048
5814
  try {
6049
- existingFp = existing.fingerprint();
6050
- incomingFp = incoming.fingerprint();
6051
- } catch (error) {
6052
- console.error(
6053
- `[attaform] fingerprint() threw for key "${key}"; skipping mismatch check.`,
6054
- error
6055
- );
6056
- return;
6057
- }
6058
- if (existingFp === incomingFp) return;
6059
- console.warn(
6060
- `[attaform] useForm() calls with key "${key}" use different schemas; first wins, second is ignored. Use identical schemas or unique keys.
6061
- existing: ${existingFp}
6062
- incoming: ${incomingFp}`
6063
- );
6064
- }
6065
- function warnOnPersistDivergence(key, existing, incomingPersist) {
6066
- if (incomingPersist === void 0) return;
6067
- const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
6068
- const incomingNormalized = normalizePersistConfig(incomingPersist);
6069
- if (wired === void 0) {
6070
- console.warn(
6071
- `[attaform] useForm({ key: "${key}" }) passed a persist config but the first useForm({ key }) call didn't wire persistence; the new config is silently dropped. Pass persist on the first call, or remove persist here to make the inheritance explicit.`
6072
- );
6073
- return;
5815
+ return hashStableString(await state.schema.fingerprint());
5816
+ } catch (err) {
5817
+ if (paths.__DEV__) {
5818
+ console.warn(
5819
+ `[attaform] Could not fingerprint the schema for form '${state.formKey}': ${err instanceof Error ? err.message : String(err)}. Persistence falls back to a fingerprint-free key, so a schema change won't auto-invalidate a saved draft.`
5820
+ );
5821
+ }
5822
+ return "unfingerprinted";
6074
5823
  }
6075
- if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
6076
- console.warn(
6077
- `[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
6078
- wired: ${describePersist(wired.wiredConfig)}
6079
- incoming: ${describePersist(incomingNormalized)}`
6080
- );
6081
- }
6082
- function persistConfigsEquivalent(a, b) {
6083
- if (a.storage !== b.storage) return false;
6084
- if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
6085
- if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
6086
- return true;
6087
- }
6088
- function describePersist(config) {
6089
- const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
6090
- const parts = [`storage=${storage}`];
6091
- if (config.key !== void 0) parts.push(`key=${config.key}`);
6092
- if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
6093
- return `{ ${parts.join(", ")} }`;
6094
5824
  }
6095
5825
  const warnedAnonPersistKeys = /* @__PURE__ */ new Set();
6096
5826
  function enforceAnonPersistRule(formKey, ssr) {
@@ -6187,6 +5917,8 @@ function isLazyMarker(value) {
6187
5917
  }
6188
5918
 
6189
5919
  const NOOP_WIZARD_HISTORY = {
5920
+ push() {
5921
+ },
6190
5922
  replace() {
6191
5923
  },
6192
5924
  read() {
@@ -6201,6 +5933,9 @@ function createWizardHistory(param) {
6201
5933
  if (typeof window === "undefined") return NOOP_WIZARD_HISTORY;
6202
5934
  const subscribers = [];
6203
5935
  let disposed = false;
5936
+ function currentKey() {
5937
+ return new URL(window.location.href).searchParams.get(param) ?? void 0;
5938
+ }
6204
5939
  function buildUrl(key) {
6205
5940
  const url = new URL(window.location.href);
6206
5941
  url.searchParams.set(param, key);
@@ -6208,25 +5943,29 @@ function createWizardHistory(param) {
6208
5943
  }
6209
5944
  function handlePopstate() {
6210
5945
  if (disposed) return;
6211
- const url = new URL(window.location.href);
6212
- const value = url.searchParams.get(param) ?? void 0;
5946
+ const value = currentKey();
6213
5947
  for (const subscriber of subscribers) subscriber(value);
6214
5948
  }
6215
5949
  window.addEventListener("popstate", handlePopstate);
6216
- function safeReplaceState(key) {
5950
+ function safeWrite(key, mode) {
6217
5951
  try {
6218
- window.history.replaceState({}, "", buildUrl(key));
5952
+ if (mode === "push") window.history.pushState({}, "", buildUrl(key));
5953
+ else window.history.replaceState({}, "", buildUrl(key));
6219
5954
  } catch {
6220
5955
  }
6221
5956
  }
6222
5957
  return {
5958
+ push(key) {
5959
+ if (disposed) return;
5960
+ if (currentKey() === key) return;
5961
+ safeWrite(key, "push");
5962
+ },
6223
5963
  replace(key) {
6224
5964
  if (disposed) return;
6225
- safeReplaceState(key);
5965
+ safeWrite(key, "replace");
6226
5966
  },
6227
5967
  read() {
6228
- const url = new URL(window.location.href);
6229
- return url.searchParams.get(param) ?? void 0;
5968
+ return currentKey();
6230
5969
  },
6231
5970
  subscribe(callback) {
6232
5971
  if (disposed) return;
@@ -6258,12 +5997,13 @@ function buildNoopWizardSchema(formKey) {
6258
5997
  formKey
6259
5998
  };
6260
5999
  return {
6261
- fingerprint: () => NOOP_FINGERPRINT,
6000
+ fingerprint: () => Promise.resolve(NOOP_FINGERPRINT),
6262
6001
  getDefaultValues: () => defaultsResponse,
6263
6002
  getDefaultAtPath: () => void 0,
6264
6003
  getEmptyValueAtPath: () => void 0,
6265
6004
  isPreprocessOrCoerceLeaf: () => false,
6266
6005
  arrayShapeAtPath: () => void 0,
6006
+ isFixedObjectAtPath: (path) => path.length === 0,
6267
6007
  getSchemasAtPath: () => [],
6268
6008
  validateAtPath: () => success,
6269
6009
  getSlimPrimitiveTypesAtPath: () => new Set(EMPTY_SLIM_KINDS),
@@ -6717,7 +6457,10 @@ function useWizard(options) {
6717
6457
  };
6718
6458
  const persistCallback = options.persist === false ? void 0 : options.persist !== void 0 ? options.persist : (state) => {
6719
6459
  if (state.step === void 0) return;
6720
- historyHandle.replace(state.step);
6460
+ const current = historyHandle.read();
6461
+ const effectiveCurrent = current !== void 0 && isCompiledKey(current) ? current : firstKey();
6462
+ if (state.step === effectiveCurrent) historyHandle.replace(state.step);
6463
+ else historyHandle.push(state.step);
6721
6464
  };
6722
6465
  function isCompiledKey(key) {
6723
6466
  const list = compiledSteps.value;
@@ -6798,6 +6541,7 @@ function useWizard(options) {
6798
6541
  }
6799
6542
  const submitting = vue.ref(false);
6800
6543
  const submissionAttempts = vue.ref(0);
6544
+ const submitError = vue.ref(null);
6801
6545
  const done = vue.ref(false);
6802
6546
  function activateForm(form) {
6803
6547
  const source = asSubmissionSource(form);
@@ -6925,7 +6669,7 @@ function useWizard(options) {
6925
6669
  formKey: form.key
6926
6670
  };
6927
6671
  }
6928
- return full.process();
6672
+ return full.parse();
6929
6673
  }
6930
6674
  function collectErrors(results) {
6931
6675
  const out = [];
@@ -6956,6 +6700,7 @@ function useWizard(options) {
6956
6700
  return;
6957
6701
  }
6958
6702
  submitting.value = true;
6703
+ submitError.value = null;
6959
6704
  try {
6960
6705
  const currentKey = activeKey.value;
6961
6706
  const final = isFinalStep.value;
@@ -6964,18 +6709,24 @@ function useWizard(options) {
6964
6709
  if (final) {
6965
6710
  await Promise.all(
6966
6711
  list.map(async (step) => {
6712
+ registry.forms.get(step.key)?.clearUserErrors();
6967
6713
  const result = await processOne(step.form);
6968
6714
  results.set(step.key, result);
6969
6715
  })
6970
6716
  );
6971
6717
  } else {
6972
6718
  const active = activeForm.value;
6719
+ registry.forms.get(active.key)?.clearUserErrors();
6973
6720
  const result = await processOne(active);
6974
6721
  results.set(active.key, result);
6975
6722
  }
6976
6723
  for (const key of results.keys()) {
6977
6724
  const store = registry.forms.get(key);
6978
- if (store !== void 0) store.submissionAttempts.value += 1;
6725
+ if (store !== void 0) {
6726
+ store.submissionAttempts.value += 1;
6727
+ store.cancelFieldValidation();
6728
+ store.displayEngine.clear();
6729
+ }
6979
6730
  }
6980
6731
  submissionAttempts.value += 1;
6981
6732
  const errors = collectErrors(results);
@@ -7000,7 +6751,6 @@ function useWizard(options) {
7000
6751
  if (target !== void 0) moveTo(target.key);
7001
6752
  }
7002
6753
  } else {
7003
- if (onError !== void 0) await onError(errors);
7004
6754
  if (options.focusFirstError !== false) {
7005
6755
  const firstFailedKey = errors[0]?.formKey;
7006
6756
  if (firstFailedKey !== void 0 && isCompiledKey(firstFailedKey)) {
@@ -7015,7 +6765,16 @@ function useWizard(options) {
7015
6765
  }
7016
6766
  }
7017
6767
  }
6768
+ if (onError !== void 0) {
6769
+ try {
6770
+ await onError(errors);
6771
+ } catch (cause) {
6772
+ throw new paths.SubmitErrorHandlerError("User-provided onError threw", { cause });
6773
+ }
6774
+ }
7018
6775
  }
6776
+ } catch (err) {
6777
+ submitError.value = paths.toError(err);
7019
6778
  } finally {
7020
6779
  submitting.value = false;
7021
6780
  }
@@ -7024,6 +6783,7 @@ function useWizard(options) {
7024
6783
  function reset() {
7025
6784
  submissionAttempts.value = 0;
7026
6785
  done.value = false;
6786
+ submitError.value = null;
7027
6787
  lazyEpoch.value += 1;
7028
6788
  for (const step of compiledSteps.value) {
7029
6789
  const full = asSubmissionSource(step.form);
@@ -7099,6 +6859,9 @@ function useWizard(options) {
7099
6859
  get submissionAttempts() {
7100
6860
  return submissionAttempts.value;
7101
6861
  },
6862
+ get submitError() {
6863
+ return submitError.value;
6864
+ },
7102
6865
  get visited() {
7103
6866
  return visited.value;
7104
6867
  }
@@ -7209,9 +6972,16 @@ function warnIfAmbientWizardProviderHadDuplicates() {
7209
6972
  }
7210
6973
 
7211
6974
  exports.AttaformErrorCode = AttaformErrorCode;
6975
+ exports.DEFAULT_PERSISTENCE_DEBOUNCE_MS = DEFAULT_PERSISTENCE_DEBOUNCE_MS;
6976
+ exports.DEFAULT_TIMINGS = DEFAULT_TIMINGS;
6977
+ exports.PERSISTENCE_MODULE_KEY = PERSISTENCE_MODULE_KEY;
6978
+ exports.applyPatchesForward = applyPatchesForward;
6979
+ exports.cleanupOrphanKeys = cleanupOrphanKeys;
7212
6980
  exports.defaultCoercionRules = defaultCoercionRules;
7213
6981
  exports.defaultDisplayState = defaultDisplayState;
7214
6982
  exports.defineCoercion = defineCoercion;
6983
+ exports.deleteAtPath = deleteAtPath;
6984
+ exports.diffAndApply = diffAndApply;
7215
6985
  exports.getAtPath = getAtPath;
7216
6986
  exports.humanize = humanize;
7217
6987
  exports.injectForm = injectForm;
@@ -7219,12 +6989,17 @@ exports.injectWizard = injectWizard;
7219
6989
  exports.isPlainRecord = isPlainRecord;
7220
6990
  exports.isUnset = isUnset;
7221
6991
  exports.lazy = lazy;
6992
+ exports.makeDefaultDisplayState = makeDefaultDisplayState;
6993
+ exports.mergeSparseHydration = mergeSparseHydration;
7222
6994
  exports.normalizeNumericOption = normalizeNumericOption;
6995
+ exports.normalizePersistConfig = normalizePersistConfig;
6996
+ exports.resolveStorageKeyBase = resolveStorageKeyBase;
7223
6997
  exports.safeAssign = safeAssign;
7224
6998
  exports.safeOwnRead = safeOwnRead;
7225
6999
  exports.setAtPath = setAtPath;
7226
7000
  exports.slimKindOf = slimKindOf;
7001
+ exports.structuralSnapshot = structuralSnapshot;
7227
7002
  exports.unset = unset;
7228
7003
  exports.useAbstractForm = useAbstractForm;
7229
7004
  exports.useWizard = useWizard;
7230
- //# sourceMappingURL=attaform.CwLjUqmQ.cjs.map
7005
+ //# sourceMappingURL=attaform.ClXwitZj.cjs.map