attaform 0.20.2 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) 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 +65 -70
  39. package/dist/index.d.mts +65 -70
  40. package/dist/index.d.ts +65 -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.ceGEAEMk.d.ts → attaform.7lzO9pdM.d.mts} +95 -1
  62. package/dist/shared/{attaform.99cfHcIt.d.cts → attaform.B1nyO4ec.d.cts} +82 -30
  63. package/dist/shared/{attaform.99cfHcIt.d.mts → attaform.B1nyO4ec.d.mts} +82 -30
  64. package/dist/shared/{attaform.99cfHcIt.d.ts → attaform.B1nyO4ec.d.ts} +82 -30
  65. package/dist/shared/{attaform.z5j3LwJz.cjs → attaform.BA3vRDos.cjs} +3 -3
  66. package/dist/shared/attaform.BA3vRDos.cjs.map +1 -0
  67. package/dist/shared/{attaform.BXinSW2T.d.mts → attaform.BDIEq9qP.d.cts} +1 -1
  68. package/dist/shared/attaform.BJGA_UOS.mjs +37 -0
  69. package/dist/shared/attaform.BJGA_UOS.mjs.map +1 -0
  70. package/dist/shared/{attaform.DN5CvZrg.d.ts → attaform.BK1RE2ha.d.ts} +1 -1
  71. package/dist/shared/{attaform.CywE4y8x.d.cts → attaform.BQ6drorq.d.mts} +1 -1
  72. package/dist/shared/attaform.BRGIpZo4.cjs +26 -0
  73. package/dist/shared/attaform.BRGIpZo4.cjs.map +1 -0
  74. package/dist/shared/{attaform.CwLjUqmQ.cjs → attaform.BUszFoKq.cjs} +383 -911
  75. package/dist/shared/attaform.BUszFoKq.cjs.map +1 -0
  76. package/dist/shared/{attaform.C5aYC_T8.mjs → attaform.BnK_bfcb.mjs} +39 -392
  77. package/dist/shared/attaform.BnK_bfcb.mjs.map +1 -0
  78. package/dist/shared/{attaform.DAKrGhxc.cjs → attaform.BzvOdiSI.cjs} +101 -417
  79. package/dist/shared/attaform.BzvOdiSI.cjs.map +1 -0
  80. package/dist/shared/attaform.C3Doa9Pt.mjs +24 -0
  81. package/dist/shared/attaform.C3Doa9Pt.mjs.map +1 -0
  82. package/dist/shared/{attaform.D2SCCd4O.cjs → attaform.CEf6wYfD.cjs} +2 -2
  83. package/dist/shared/{attaform.D2SCCd4O.cjs.map → attaform.CEf6wYfD.cjs.map} +1 -1
  84. package/dist/shared/attaform.CQN9R62B.cjs +39 -0
  85. package/dist/shared/attaform.CQN9R62B.cjs.map +1 -0
  86. package/dist/shared/{attaform.sWm8B15V.d.mts → attaform.CRsXyy-Y.d.ts} +95 -1
  87. package/dist/shared/{attaform.Dt7dEcHk.mjs → attaform.CkjTapyq.mjs} +89 -405
  88. package/dist/shared/attaform.CkjTapyq.mjs.map +1 -0
  89. package/dist/shared/{attaform.tiWEVznj.mjs → attaform.DSqO6Db7.mjs} +372 -912
  90. package/dist/shared/attaform.DSqO6Db7.mjs.map +1 -0
  91. package/dist/shared/attaform.DuzQYscR.d.cts +41 -0
  92. package/dist/shared/attaform.DuzQYscR.d.mts +41 -0
  93. package/dist/shared/attaform.DuzQYscR.d.ts +41 -0
  94. package/dist/shared/{attaform.DbRgDFa7.d.cts → attaform.F8LMHHWV.d.cts} +95 -1
  95. package/dist/shared/attaform.LEWUFqUw.cjs +54 -0
  96. package/dist/shared/attaform.LEWUFqUw.cjs.map +1 -0
  97. package/dist/shared/{attaform.Cd4AOfwu.cjs → attaform.PnqML3xW.cjs} +68 -402
  98. package/dist/shared/attaform.PnqML3xW.cjs.map +1 -0
  99. package/dist/shared/{attaform.QG5TG8lB.mjs → attaform.Y_Mgg0Yp.mjs} +3 -3
  100. package/dist/shared/attaform.Y_Mgg0Yp.mjs.map +1 -0
  101. package/dist/shared/{attaform.B_hph5AE.cjs → attaform._rsCZy2j.cjs} +172 -20
  102. package/dist/shared/attaform._rsCZy2j.cjs.map +1 -0
  103. package/dist/shared/{attaform.CnrxbkB6.mjs → attaform.ezb5Nh2t.mjs} +2 -2
  104. package/dist/shared/{attaform.CnrxbkB6.mjs.map → attaform.ezb5Nh2t.mjs.map} +1 -1
  105. package/dist/shared/{attaform.BGk8cfw2.mjs → attaform.r3PePkDR.mjs} +172 -21
  106. package/dist/shared/attaform.r3PePkDR.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 +5 -5
  136. package/dist/zod.d.mts +5 -5
  137. package/dist/zod.d.ts +5 -5
  138. package/dist/zod.mjs +6 -6
  139. package/package.json +19 -5
  140. package/dist/shared/attaform.BGk8cfw2.mjs.map +0 -1
  141. package/dist/shared/attaform.B_hph5AE.cjs.map +0 -1
  142. package/dist/shared/attaform.C5aYC_T8.mjs.map +0 -1
  143. package/dist/shared/attaform.Cd4AOfwu.cjs.map +0 -1
  144. package/dist/shared/attaform.CwLjUqmQ.cjs.map +0 -1
  145. package/dist/shared/attaform.DAKrGhxc.cjs.map +0 -1
  146. package/dist/shared/attaform.Dt7dEcHk.mjs.map +0 -1
  147. package/dist/shared/attaform.QG5TG8lB.mjs.map +0 -1
  148. package/dist/shared/attaform.tiWEVznj.mjs.map +0 -1
  149. 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.BA3vRDos.cjs');
5
5
 
6
6
  function safeAssign(target, key, value) {
7
7
  if (key === "__proto__") {
@@ -460,17 +460,51 @@ function structuralSnapshot(value) {
460
460
  return out;
461
461
  }
462
462
 
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";
463
+ function isGateOpen(field, formMeta) {
464
+ return formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
465
+ }
466
+ function computeVerdict(field, formMeta) {
467
+ if (!isGateOpen(field, formMeta)) return "idle";
467
468
  const hasOwnError = field.errors.some(
468
469
  (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
469
470
  );
470
471
  if (hasOwnError) return "error";
471
472
  if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
472
473
  return "idle";
473
- };
474
+ }
475
+ const DEFAULT_TIMINGS = { showDelay: 100, minVisible: 120 };
476
+ const FOCUS_OUT_GRACE = 16;
477
+ const defaultFamily = /* @__PURE__ */ new WeakSet();
478
+ function isDefaultDisplayState(fn) {
479
+ return defaultFamily.has(fn);
480
+ }
481
+ function makeDefaultDisplayState({
482
+ showDelay,
483
+ minVisible
484
+ }) {
485
+ const reducer = (prev, { field, formMeta, validatingSince, now }) => {
486
+ const verdict = computeVerdict(field, formMeta);
487
+ if (!isGateOpen(field, formMeta)) return { display: verdict };
488
+ if (validatingSince === null) {
489
+ if (prev.display === "pending") {
490
+ const shownAt = prev.pendingShownAt ?? now;
491
+ if (now < shownAt + minVisible)
492
+ return { display: "pending", pendingShownAt: shownAt, reviewAt: shownAt + minVisible };
493
+ }
494
+ return { display: verdict };
495
+ }
496
+ if (prev.display === "pending")
497
+ return { display: "pending", pendingShownAt: prev.pendingShownAt ?? now };
498
+ const window = field.focused === false ? Math.min(showDelay, FOCUS_OUT_GRACE) : showDelay;
499
+ if (now - validatingSince < window) {
500
+ return { display: prev.display, reviewAt: validatingSince + window };
501
+ }
502
+ return { display: "pending", pendingShownAt: now, reviewAt: now + minVisible };
503
+ };
504
+ defaultFamily.add(reducer);
505
+ return reducer;
506
+ }
507
+ const defaultDisplayState = makeDefaultDisplayState(DEFAULT_TIMINGS);
474
508
  function resolveGetDisplayState(config) {
475
509
  return config ?? defaultDisplayState;
476
510
  }
@@ -627,7 +661,19 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
627
661
  }
628
662
  function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
629
663
  const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
630
- return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
664
+ const validatingSince = state.fieldValidatingSince.get(key) ?? null;
665
+ return decorateWithDerivedProps(
666
+ base,
667
+ state,
668
+ getFormMetaBase,
669
+ key,
670
+ validatingSince,
671
+ false,
672
+ // revealedDescendantError: leaves have no descendants
673
+ false,
674
+ // isRoot: a leaf is never the form root
675
+ getDisplayState
676
+ );
631
677
  }
632
678
  function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
633
679
  const formValue = state.form.value;
@@ -643,8 +689,11 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
643
689
  let blurredAfterInteraction = false;
644
690
  let connected = false;
645
691
  let validating = false;
692
+ let validatingSince = null;
646
693
  let updatedAt = null;
647
694
  let asyncPending = false;
695
+ const submissionAttempts = state.submissionAttempts.value;
696
+ const blurredLeafSegments = [];
648
697
  for (const [leafKey, entry] of state.originals) {
649
698
  if (!paths.isPathPrefix(segments, entry.segments)) continue;
650
699
  if (segments.length === entry.segments.length) continue;
@@ -659,9 +708,18 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
659
708
  if (leafRecord?.blurred === true) blurred = true;
660
709
  if (leafRecord?.touched === true) touched = true;
661
710
  if (leafRecord?.interacted === true) interacted = true;
662
- if (leafRecord?.blurredAfterInteraction === true) blurredAfterInteraction = true;
711
+ if (leafRecord?.blurredAfterInteraction === true) {
712
+ blurredAfterInteraction = true;
713
+ blurredLeafSegments.push(entry.segments);
714
+ }
663
715
  if (leafRecord?.connected === true) connected = true;
664
- if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) validating = true;
716
+ if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) {
717
+ validating = true;
718
+ const leafGateOpen = submissionAttempts > 0 || leafRecord?.blurredAfterInteraction === true;
719
+ const since = state.fieldValidatingSince.get(leafKey);
720
+ if (leafGateOpen && since !== void 0 && (validatingSince === null || since < validatingSince))
721
+ validatingSince = since;
722
+ }
665
723
  if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
666
724
  const ts = leafRecord?.updatedAt;
667
725
  if (ts !== void 0 && ts !== null) {
@@ -673,6 +731,13 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
673
731
  dirty = true;
674
732
  }
675
733
  const errors = aggregateErrorsAt(state, segments);
734
+ const revealedDescendantError = errors.length > 0 && errors.some((e) => {
735
+ const ePath = e.path;
736
+ if (ePath.length === segments.length && ePath.every((s, i) => s === segments[i])) return false;
737
+ if (submissionAttempts > 0) return true;
738
+ if (ePath.length === 1 && ePath[0] === "") return blurredAfterInteraction;
739
+ return blurredLeafSegments.some((s) => paths.isPathPrefix(ePath, s));
740
+ });
676
741
  if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
677
742
  const gated = asyncPending && !state.firstValidationDone.value;
678
743
  const valid = !gated && errors.length === 0 && !validating;
@@ -680,43 +745,70 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
680
745
  const lastSegment = segments.length === 0 ? "" : segments[segments.length - 1] ?? "";
681
746
  const label = resolved.label || humanize(lastSegment);
682
747
  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
748
+ base: {
749
+ value,
750
+ original,
751
+ pristine,
752
+ dirty,
753
+ focused,
754
+ blurred,
755
+ touched,
756
+ interacted,
757
+ blurredAfterInteraction,
758
+ connected,
759
+ element: null,
760
+ elements: EMPTY_ELEMENTS,
761
+ updatedAt,
762
+ errors,
763
+ validating,
764
+ valid,
765
+ path: segments,
766
+ ...computeFieldIdentity(formInstanceId, state.formKey, key),
767
+ key: state.arrayElementKey(segments),
768
+ blank,
769
+ label,
770
+ description: resolved.description,
771
+ placeholder: resolved.placeholder,
772
+ meta: resolved.meta
773
+ },
774
+ validatingSince,
775
+ revealedDescendantError
707
776
  };
708
777
  }
709
778
  function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
710
- const base = buildContainerFieldStateBase(state, segments, key, formInstanceId);
711
- return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
779
+ const { base, validatingSince, revealedDescendantError } = buildContainerFieldStateBase(
780
+ state,
781
+ segments,
782
+ key,
783
+ formInstanceId
784
+ );
785
+ return decorateWithDerivedProps(
786
+ base,
787
+ state,
788
+ getFormMetaBase,
789
+ key,
790
+ validatingSince,
791
+ revealedDescendantError,
792
+ segments.length === 0,
793
+ getDisplayState
794
+ );
712
795
  }
713
- function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState) {
796
+ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, revealedDescendantError, isRoot, getDisplayState) {
714
797
  const firstError = base.errors[0];
715
798
  const predicate = getDisplayState ?? state.getDisplayState;
716
799
  const formMeta = getFormMetaBase();
717
- let displayState;
800
+ const ctx = {
801
+ field: base,
802
+ formMeta,
803
+ validatingSince,
804
+ // The engine's clock. Frozen to 0 under SSR (no clock, nothing in
805
+ // flight) so the reducer returns the plain verdict and hydration matches.
806
+ now: state.ssr ? 0 : Date.now()
807
+ };
808
+ let machine;
809
+ let rollupApplies = isDefaultDisplayState(predicate);
718
810
  try {
719
- displayState = predicate(base, formMeta);
811
+ machine = state.displayEngine.resolve(key, ctx, predicate);
720
812
  } catch (err) {
721
813
  if (paths.__DEV__ && !warnedDisplayStatePredicates.has(predicate)) {
722
814
  warnedDisplayStatePredicates.add(predicate);
@@ -725,8 +817,11 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState)
725
817
  err
726
818
  );
727
819
  }
728
- displayState = defaultDisplayState(base, formMeta);
820
+ machine = state.displayEngine.resolve(key, ctx, defaultDisplayState);
821
+ rollupApplies = true;
729
822
  }
823
+ const submitValidating = rollupApplies && isRoot && state.submitting.value && state.activeValidations.value > 0;
824
+ const displayState = machine.display === "pending" || submitValidating ? "pending" : rollupApplies && revealedDescendantError ? "error" : machine.display;
730
825
  return {
731
826
  ...base,
732
827
  displayState,
@@ -1419,90 +1514,6 @@ async function getStorageAdapter(storage) {
1419
1514
  }
1420
1515
  }
1421
1516
  }
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
1517
  function resolveStorageKeyBase(config, formKey) {
1507
1518
  return config.key ?? `${PERSISTENCE_KEY_PREFIX}${formKey}`;
1508
1519
  }
@@ -1549,47 +1560,6 @@ async function sweepNonConfiguredStandardStoresForOrphans(configured, base) {
1549
1560
  }
1550
1561
  }
1551
1562
  }
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
1563
  function mergeSparseHydration(schemaDefaults, sparse, schema) {
1594
1564
  return mergeDeep(schemaDefaults, sparse, [], schema);
1595
1565
  }
@@ -1827,6 +1797,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1827
1797
  state.submitting.value = true;
1828
1798
  state.submitError.value = null;
1829
1799
  state.cancelFieldValidation();
1800
+ state.displayEngine.clear();
1830
1801
  state.activeValidations.value += 1;
1831
1802
  const refinement = await runRefinementValidation(state.form.value, void 0);
1832
1803
  const merged = composeWithDerivedBlank(refinement, void 0);
@@ -2643,7 +2614,12 @@ function buildFormApi(state, formInstanceId, options = {}) {
2643
2614
  return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
2644
2615
  };
2645
2616
  const getFormMetaBase = () => {
2646
- const rootBase = buildContainerFieldStateBase(state, paths.ROOT_PATH, paths.ROOT_PATH_KEY, formInstanceId);
2617
+ const { base: rootBase } = buildContainerFieldStateBase(
2618
+ state,
2619
+ paths.ROOT_PATH,
2620
+ paths.ROOT_PATH_KEY,
2621
+ formInstanceId
2622
+ );
2647
2623
  return {
2648
2624
  ...rootBase,
2649
2625
  submitting: state.submitting.value,
@@ -2978,7 +2954,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2978
2954
  instanceId: formInstanceId
2979
2955
  })
2980
2956
  );
2981
- const persistence = state.modules.get(PERSISTENCE_MODULE_KEY);
2957
+ const persistenceHandle = state.modules.get(PERSISTENCE_MODULE_KEY);
2982
2958
  const reset = (nextDefaultValues) => {
2983
2959
  if (nextDefaultValues === void 0) {
2984
2960
  state.reset();
@@ -2993,15 +2969,15 @@ function buildFormApi(state, formInstanceId, options = {}) {
2993
2969
  state.originalBlankPaths.add(pathKey);
2994
2970
  }
2995
2971
  }
2996
- if (persistence !== void 0) {
2997
- void persistence.clearPersistedDraft().catch(() => void 0);
2972
+ if (persistenceHandle !== void 0) {
2973
+ void persistenceHandle.ready.then((m) => m?.clearPersistedDraft()).catch(() => void 0);
2998
2974
  }
2999
2975
  };
3000
2976
  const resetField = (pathInput) => {
3001
2977
  const segments = paths.canonicalizePath(pathInput).segments;
3002
2978
  state.resetField(segments);
3003
- if (persistence !== void 0) {
3004
- void persistence.clearPersistedDraft(segments).catch(() => void 0);
2979
+ if (persistenceHandle !== void 0) {
2980
+ void persistenceHandle.ready.then((m) => m?.clearPersistedDraft(segments)).catch(() => void 0);
3005
2981
  }
3006
2982
  };
3007
2983
  function clear(pathInput) {
@@ -3019,10 +2995,14 @@ function buildFormApi(state, formInstanceId, options = {}) {
3019
2995
  )) {
3020
2996
  return;
3021
2997
  }
2998
+ if (persistenceHandle === void 0) return;
2999
+ const persistence = await persistenceHandle.ready;
3022
3000
  if (persistence === void 0) return;
3023
3001
  await persistence.writePathImmediately(segments);
3024
3002
  };
3025
3003
  const clearPersistedDraft = async (pathInput) => {
3004
+ if (persistenceHandle === void 0) return;
3005
+ const persistence = await persistenceHandle.ready;
3026
3006
  if (persistence === void 0) return;
3027
3007
  if (pathInput === void 0) {
3028
3008
  await persistence.clearPersistedDraft();
@@ -3205,6 +3185,93 @@ function buildFormApi(state, formInstanceId, options = {}) {
3205
3185
  };
3206
3186
  }
3207
3187
 
3188
+ const IDLE = Object.freeze({ display: "idle" });
3189
+ const MAX_DELAY = 2147483647;
3190
+ function createDisplayEngine(ssr) {
3191
+ const machines = /* @__PURE__ */ new Map();
3192
+ const tick = vue.ref(0);
3193
+ let timer = null;
3194
+ let timerTarget = null;
3195
+ let lastFiredTarget = null;
3196
+ function clearTimer() {
3197
+ if (timer !== null) {
3198
+ clearTimeout(timer);
3199
+ timer = null;
3200
+ timerTarget = null;
3201
+ }
3202
+ }
3203
+ function nearestReviewAt() {
3204
+ let min = null;
3205
+ for (const m of machines.values()) {
3206
+ if (m.reviewAt === void 0 || !Number.isFinite(m.reviewAt)) continue;
3207
+ if (min === null || m.reviewAt < min) min = m.reviewAt;
3208
+ }
3209
+ return min;
3210
+ }
3211
+ function rearm(now) {
3212
+ const target = nearestReviewAt();
3213
+ if (target === null) {
3214
+ clearTimer();
3215
+ return;
3216
+ }
3217
+ if (timer !== null && timerTarget === target) return;
3218
+ if (target === lastFiredTarget) {
3219
+ clearTimer();
3220
+ return;
3221
+ }
3222
+ clearTimer();
3223
+ timerTarget = target;
3224
+ timer = setTimeout(
3225
+ () => {
3226
+ timer = null;
3227
+ timerTarget = null;
3228
+ lastFiredTarget = target;
3229
+ tick.value++;
3230
+ },
3231
+ Math.min(MAX_DELAY, Math.max(0, target - now))
3232
+ );
3233
+ }
3234
+ function resolve(key, ctx, reducer) {
3235
+ void tick.value;
3236
+ const prev = machines.get(key) ?? IDLE;
3237
+ const machine = reducer(prev, ctx);
3238
+ if (ssr) return machine;
3239
+ const active = machine.display !== "idle" || machine.reviewAt !== void 0 && Number.isFinite(machine.reviewAt);
3240
+ if (active) machines.set(key, machine);
3241
+ else machines.delete(key);
3242
+ rearm(ctx.now);
3243
+ return machine;
3244
+ }
3245
+ function clear() {
3246
+ machines.clear();
3247
+ clearTimer();
3248
+ lastFiredTarget = null;
3249
+ }
3250
+ let detachVisibility = null;
3251
+ if (!ssr && typeof document !== "undefined") {
3252
+ const onVisible = () => {
3253
+ if (document.visibilityState === "visible" && machines.size > 0) tick.value++;
3254
+ };
3255
+ document.addEventListener("visibilitychange", onVisible);
3256
+ detachVisibility = () => document.removeEventListener("visibilitychange", onVisible);
3257
+ }
3258
+ function dispose() {
3259
+ clear();
3260
+ if (detachVisibility !== null) {
3261
+ detachVisibility();
3262
+ detachVisibility = null;
3263
+ }
3264
+ }
3265
+ return {
3266
+ resolve,
3267
+ clear,
3268
+ dispose,
3269
+ size: () => machines.size,
3270
+ has: (key) => machines.has(key),
3271
+ hasTimer: () => timer !== null
3272
+ };
3273
+ }
3274
+
3208
3275
  function applyDuStubs(schema, data, options = {}) {
3209
3276
  const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
3210
3277
  return walkDuStubs(schema, data, options.basePath ?? [], warned);
@@ -3543,6 +3610,7 @@ function createArrayBookkeeping(deps) {
3543
3610
  blankPaths,
3544
3611
  originalBlankPaths,
3545
3612
  fieldValidationCounts,
3613
+ fieldValidatingSince,
3546
3614
  fieldValidationState,
3547
3615
  schemaErrors,
3548
3616
  activeValidations,
@@ -3568,6 +3636,7 @@ function createArrayBookkeeping(deps) {
3568
3636
  }));
3569
3637
  migrateSetSubtree(blankPaths, arrayPath, remap);
3570
3638
  migrateSetSubtree(originalBlankPaths, arrayPath, remap);
3639
+ migrateMapSubtree(fieldValidatingSince, arrayPath, remap, (since) => since);
3571
3640
  migrateMapSubtree(fieldValidationCounts, arrayPath, remap, (count) => count);
3572
3641
  arrayIdentity.applyRemap(arrayPath, remap);
3573
3642
  }
@@ -3627,6 +3696,18 @@ function isHydratedFieldRecord(value) {
3627
3696
  const r = value;
3628
3697
  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
3698
  }
3699
+ function withClearedHistoryFlags(record, now) {
3700
+ return {
3701
+ path: record.path,
3702
+ updatedAt: now,
3703
+ connected: record.connected,
3704
+ focused: record.focused,
3705
+ blurred: record.blurred,
3706
+ touched: false,
3707
+ interacted: false,
3708
+ blurredAfterInteraction: false
3709
+ };
3710
+ }
3630
3711
  function isHydratedValidationErrorArray(value) {
3631
3712
  if (!Array.isArray(value)) return false;
3632
3713
  for (const entry of value) {
@@ -3754,6 +3835,9 @@ function createFormStore(options) {
3754
3835
  const resolvedIsSensitivePath = options.isSensitivePath ?? paths.isSensitivePath;
3755
3836
  const cleanupHooks = [];
3756
3837
  const modules = /* @__PURE__ */ new Map();
3838
+ const fieldValidatingSince = vue.reactive(/* @__PURE__ */ new Map());
3839
+ const displayEngine = createDisplayEngine(ssr);
3840
+ registerCleanup(() => displayEngine.dispose());
3757
3841
  const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
3758
3842
  const schemaResponse = schema.getDefaultValues({
3759
3843
  useDefaultSchemaValues: true,
@@ -3882,12 +3966,18 @@ function createFormStore(options) {
3882
3966
  }
3883
3967
  const fieldValidationCounts = vue.reactive(/* @__PURE__ */ new Map());
3884
3968
  function incFieldValidation(key) {
3885
- fieldValidationCounts.set(key, (fieldValidationCounts.get(key) ?? 0) + 1);
3969
+ fieldValidatingSince.set(key, ssr ? 0 : Date.now());
3970
+ const prevCount = fieldValidationCounts.get(key) ?? 0;
3971
+ fieldValidationCounts.set(key, prevCount + 1);
3886
3972
  }
3887
3973
  function decFieldValidation(key) {
3888
3974
  const next = (fieldValidationCounts.get(key) ?? 0) - 1;
3889
- if (next <= 0) fieldValidationCounts.delete(key);
3890
- else fieldValidationCounts.set(key, next);
3975
+ if (next <= 0) {
3976
+ fieldValidationCounts.delete(key);
3977
+ fieldValidatingSince.delete(key);
3978
+ } else {
3979
+ fieldValidationCounts.set(key, next);
3980
+ }
3891
3981
  }
3892
3982
  const initStamp = (/* @__PURE__ */ new Date()).toISOString();
3893
3983
  diffAndApply({}, schemaInitialData, [], (patch) => {
@@ -3975,6 +4065,7 @@ function createFormStore(options) {
3975
4065
  blankPaths,
3976
4066
  originalBlankPaths,
3977
4067
  fieldValidationCounts,
4068
+ fieldValidatingSince,
3978
4069
  fieldValidationState,
3979
4070
  schemaErrors,
3980
4071
  activeValidations,
@@ -4242,7 +4333,7 @@ function createFormStore(options) {
4242
4333
  prev.controller.abort();
4243
4334
  }
4244
4335
  const controller = new AbortController();
4245
- const fresh = { controller, timer: null, settled: false };
4336
+ const fresh = { controller, timer: null, settled: false, released: false };
4246
4337
  fieldValidationState.set(key, fresh);
4247
4338
  const myEpoch = ++scheduleEpoch;
4248
4339
  const run = () => {
@@ -4280,8 +4371,10 @@ function createFormStore(options) {
4280
4371
  applySchemaErrorsForSubtree(scopePath ?? [], restamped);
4281
4372
  }).catch(() => {
4282
4373
  }).finally(() => {
4283
- activeValidations.value = Math.max(0, activeValidations.value - 1);
4284
- decFieldValidation(key);
4374
+ if (!fresh.released) {
4375
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
4376
+ decFieldValidation(key);
4377
+ }
4285
4378
  fresh.settled = true;
4286
4379
  });
4287
4380
  };
@@ -4303,6 +4396,22 @@ function createFormStore(options) {
4303
4396
  }
4304
4397
  fieldValidationState.clear();
4305
4398
  }
4399
+ function cancelFieldValidationUnder(prefix) {
4400
+ for (const [key, entry] of [...fieldValidationState]) {
4401
+ const segs = paths.segmentsForPathKey(key);
4402
+ if (segs === null) continue;
4403
+ if (!paths.isPathPrefix(prefix, segs)) continue;
4404
+ if (entry.timer !== null) {
4405
+ clearTimeout(entry.timer);
4406
+ } else if (!entry.settled && !entry.released) {
4407
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
4408
+ decFieldValidation(key);
4409
+ entry.released = true;
4410
+ }
4411
+ entry.controller.abort();
4412
+ fieldValidationState.delete(key);
4413
+ }
4414
+ }
4306
4415
  function onFormChange(listener) {
4307
4416
  formChangeListeners.add(listener);
4308
4417
  return () => {
@@ -4353,6 +4462,7 @@ function createFormStore(options) {
4353
4462
  drainHooks.length = 0;
4354
4463
  modules.clear();
4355
4464
  cancelFieldValidation();
4465
+ fieldValidatingSince.clear();
4356
4466
  formChangeListeners.clear();
4357
4467
  submitSuccessListeners.clear();
4358
4468
  resetListeners.clear();
@@ -4699,16 +4809,7 @@ function createFormStore(options) {
4699
4809
  }
4700
4810
  const now = (/* @__PURE__ */ new Date()).toISOString();
4701
4811
  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
- });
4812
+ fields.set(pathKey, withClearedHistoryFlags(record, now));
4712
4813
  }
4713
4814
  submissionGeneration.value += 1;
4714
4815
  submitting.value = false;
@@ -4718,6 +4819,8 @@ function createFormStore(options) {
4718
4819
  submitError.value = null;
4719
4820
  departAttempts.value = 0;
4720
4821
  cancelFieldValidation();
4822
+ displayEngine.clear();
4823
+ fieldValidatingSince.clear();
4721
4824
  pathSnapshots.clear();
4722
4825
  scheduleEpoch = 0;
4723
4826
  lastCommittedEpoch = 0;
@@ -4733,6 +4836,12 @@ function createFormStore(options) {
4733
4836
  function resetField(path) {
4734
4837
  const { key: targetKey, segments: targetSegments } = paths.canonicalizePath(path);
4735
4838
  variantMemory.clearUnderPath(targetSegments);
4839
+ cancelFieldValidationUnder(targetSegments);
4840
+ for (const [snapKey] of [...pathSnapshots]) {
4841
+ const segs = paths.segmentsForPathKey(snapKey);
4842
+ if (segs === null) continue;
4843
+ if (paths.isPathPrefix(targetSegments, segs)) pathSnapshots.delete(snapKey);
4844
+ }
4736
4845
  const leafEntry = originals.get(targetKey);
4737
4846
  if (leafEntry !== void 0) {
4738
4847
  const wrote = setValueAtPath(targetSegments, leafEntry.value);
@@ -4782,16 +4891,7 @@ function createFormStore(options) {
4782
4891
  function clearFieldRecordFlags(pathKey) {
4783
4892
  const record = fields.get(pathKey);
4784
4893
  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
- });
4894
+ fields.set(pathKey, withClearedHistoryFlags(record, (/* @__PURE__ */ new Date()).toISOString()));
4795
4895
  }
4796
4896
  function isPristineAtPath(path) {
4797
4897
  const { key, segments } = paths.canonicalizePath(path);
@@ -4871,6 +4971,8 @@ function createFormStore(options) {
4871
4971
  pathHasAsyncValidation,
4872
4972
  pathHasAsyncValidationByKey,
4873
4973
  fieldValidationCounts,
4974
+ fieldValidatingSince,
4975
+ displayEngine,
4874
4976
  applyFormReplacement,
4875
4977
  setValueAtPath,
4876
4978
  getValueAtPath,
@@ -4955,7 +5057,7 @@ function errorsEqual(a, b) {
4955
5057
  }
4956
5058
  return true;
4957
5059
  }
4958
- function diffBlankPaths$1(prev, curr) {
5060
+ function diffBlankPaths(prev, curr) {
4959
5061
  const added = [];
4960
5062
  const removed = [];
4961
5063
  for (const k of curr) if (!prev.has(k)) added.push(k);
@@ -5043,7 +5145,7 @@ function createHistoryModule(state, config) {
5043
5145
  diffAndApply(prevSnap.form, newSnap.form, [], (p) => formPatches.push(p));
5044
5146
  const prevBlankSet = new Set(prevSnap.blankPaths);
5045
5147
  const currBlankSet = new Set(newSnap.blankPaths);
5046
- const blankDiff = diffBlankPaths$1(prevBlankSet, currBlankSet);
5148
+ const blankDiff = diffBlankPaths(prevBlankSet, currBlankSet);
5047
5149
  const delta = {
5048
5150
  formPatches,
5049
5151
  blankPathsAdded: blankDiff.added,
@@ -5110,643 +5212,6 @@ function createHistoryModule(state, config) {
5110
5212
  };
5111
5213
  }
5112
5214
 
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
5215
  const warned = /* @__PURE__ */ new Set();
5751
5216
  function warnOnceInsecureContext(feature) {
5752
5217
  if (!paths.__DEV__) return;
@@ -5806,8 +5271,10 @@ function useAbstractForm(configuration, options) {
5806
5271
  }
5807
5272
  const existing = registry.forms.get(key);
5808
5273
  if (paths.__DEV__ && existing !== void 0) {
5809
- warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
5810
- warnOnPersistDivergence(key, existing, configuration.persist);
5274
+ void import('../chunks/dev-key-collision-warnings.cjs').then((m) => {
5275
+ void m.warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
5276
+ m.warnOnPersistDivergence(key, existing, configuration.persist);
5277
+ });
5811
5278
  }
5812
5279
  const hadPendingHydration = registry.pendingHydration.has(key);
5813
5280
  const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
@@ -5848,10 +5315,33 @@ function useAbstractForm(configuration, options) {
5848
5315
  } else {
5849
5316
  const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
5850
5317
  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());
5318
+ const adapterPromise = getStorageAdapter(resolvedPersist.storage);
5319
+ let persistDisposed = false;
5320
+ state.registerCleanup(() => {
5321
+ persistDisposed = true;
5322
+ });
5323
+ const ready = (async () => {
5324
+ try {
5325
+ const [{ wirePersistence }, fingerprintToken] = await Promise.all([
5326
+ import('../chunks/wire-persistence.cjs'),
5327
+ resolvePersistFingerprintToken(state)
5328
+ ]);
5329
+ if (persistDisposed) return void 0;
5330
+ const persistenceModule = wirePersistence(
5331
+ state,
5332
+ resolvedPersist,
5333
+ adapterPromise,
5334
+ fingerprintToken
5335
+ );
5336
+ state.registerDrain(() => persistenceModule.awaitPendingWrites());
5337
+ state.registerCleanup(() => persistenceModule.dispose());
5338
+ return persistenceModule;
5339
+ } catch {
5340
+ return void 0;
5341
+ }
5342
+ })();
5343
+ const persistenceHandle = { config: resolvedPersist, ready };
5344
+ state.modules.set(PERSISTENCE_MODULE_KEY, persistenceHandle);
5855
5345
  }
5856
5346
  } else {
5857
5347
  void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
@@ -5861,27 +5351,31 @@ function useAbstractForm(configuration, options) {
5861
5351
  const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
5862
5352
  const secureContext = isSecureContext();
5863
5353
  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");
5354
+ let formDisposed = false;
5355
+ state.registerCleanup(() => {
5356
+ formDisposed = true;
5357
+ });
5358
+ void (async () => {
5359
+ try {
5360
+ const [{ createMultiTabSyncModule, MULTI_TAB_SYNC_MODULE_KEY }, fingerprint] = await Promise.all([import('../chunks/multi-tab-sync.cjs'), state.schema.fingerprint()]);
5361
+ if (formDisposed) return;
5362
+ const channelName = `attaform:sync:${state.formKey}:${hashStableString(fingerprint)}`;
5363
+ const syncModule = createMultiTabSyncModule(state, channelName, {
5364
+ isSensitivePath: state.isSensitivePath,
5365
+ noSyncPaths: state.noSyncPaths,
5366
+ validateForm: (form) => {
5367
+ const result = state.schema.validateAtPath(form, void 0, { sync: true });
5368
+ if (result instanceof Promise) return;
5369
+ if (!result.success) {
5370
+ throw new Error("attaform multi-tab sync: post-apply schema validation failed");
5371
+ }
5879
5372
  }
5880
- }
5881
- });
5882
- state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
5883
- state.registerCleanup(() => syncModule.dispose());
5884
- }
5373
+ });
5374
+ state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
5375
+ state.registerCleanup(() => syncModule.dispose());
5376
+ } catch {
5377
+ }
5378
+ })();
5885
5379
  } else if (hasBroadcastChannel && !secureContext) {
5886
5380
  warnOnceInsecureContext("multiTab");
5887
5381
  }
@@ -6042,55 +5536,17 @@ function resolveFormKey(key) {
6042
5536
  }
6043
5537
  return `${ANONYMOUS_FORM_KEY_PREFIX}${anonCounter++}`;
6044
5538
  }
6045
- function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
6046
- let existingFp;
6047
- let incomingFp;
5539
+ async function resolvePersistFingerprintToken(state) {
6048
5540
  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;
5541
+ return hashStableString(await state.schema.fingerprint());
5542
+ } catch (err) {
5543
+ if (paths.__DEV__) {
5544
+ console.warn(
5545
+ `[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.`
5546
+ );
5547
+ }
5548
+ return "unfingerprinted";
6074
5549
  }
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
5550
  }
6095
5551
  const warnedAnonPersistKeys = /* @__PURE__ */ new Set();
6096
5552
  function enforceAnonPersistRule(formKey, ssr) {
@@ -6258,7 +5714,7 @@ function buildNoopWizardSchema(formKey) {
6258
5714
  formKey
6259
5715
  };
6260
5716
  return {
6261
- fingerprint: () => NOOP_FINGERPRINT,
5717
+ fingerprint: () => Promise.resolve(NOOP_FINGERPRINT),
6262
5718
  getDefaultValues: () => defaultsResponse,
6263
5719
  getDefaultAtPath: () => void 0,
6264
5720
  getEmptyValueAtPath: () => void 0,
@@ -6975,7 +6431,11 @@ function useWizard(options) {
6975
6431
  }
6976
6432
  for (const key of results.keys()) {
6977
6433
  const store = registry.forms.get(key);
6978
- if (store !== void 0) store.submissionAttempts.value += 1;
6434
+ if (store !== void 0) {
6435
+ store.submissionAttempts.value += 1;
6436
+ store.cancelFieldValidation();
6437
+ store.displayEngine.clear();
6438
+ }
6979
6439
  }
6980
6440
  submissionAttempts.value += 1;
6981
6441
  const errors = collectErrors(results);
@@ -7209,9 +6669,16 @@ function warnIfAmbientWizardProviderHadDuplicates() {
7209
6669
  }
7210
6670
 
7211
6671
  exports.AttaformErrorCode = AttaformErrorCode;
6672
+ exports.DEFAULT_PERSISTENCE_DEBOUNCE_MS = DEFAULT_PERSISTENCE_DEBOUNCE_MS;
6673
+ exports.DEFAULT_TIMINGS = DEFAULT_TIMINGS;
6674
+ exports.PERSISTENCE_MODULE_KEY = PERSISTENCE_MODULE_KEY;
6675
+ exports.applyPatchesForward = applyPatchesForward;
6676
+ exports.cleanupOrphanKeys = cleanupOrphanKeys;
7212
6677
  exports.defaultCoercionRules = defaultCoercionRules;
7213
6678
  exports.defaultDisplayState = defaultDisplayState;
7214
6679
  exports.defineCoercion = defineCoercion;
6680
+ exports.deleteAtPath = deleteAtPath;
6681
+ exports.diffAndApply = diffAndApply;
7215
6682
  exports.getAtPath = getAtPath;
7216
6683
  exports.humanize = humanize;
7217
6684
  exports.injectForm = injectForm;
@@ -7219,12 +6686,17 @@ exports.injectWizard = injectWizard;
7219
6686
  exports.isPlainRecord = isPlainRecord;
7220
6687
  exports.isUnset = isUnset;
7221
6688
  exports.lazy = lazy;
6689
+ exports.makeDefaultDisplayState = makeDefaultDisplayState;
6690
+ exports.mergeSparseHydration = mergeSparseHydration;
7222
6691
  exports.normalizeNumericOption = normalizeNumericOption;
6692
+ exports.normalizePersistConfig = normalizePersistConfig;
6693
+ exports.resolveStorageKeyBase = resolveStorageKeyBase;
7223
6694
  exports.safeAssign = safeAssign;
7224
6695
  exports.safeOwnRead = safeOwnRead;
7225
6696
  exports.setAtPath = setAtPath;
7226
6697
  exports.slimKindOf = slimKindOf;
6698
+ exports.structuralSnapshot = structuralSnapshot;
7227
6699
  exports.unset = unset;
7228
6700
  exports.useAbstractForm = useAbstractForm;
7229
6701
  exports.useWizard = useWizard;
7230
- //# sourceMappingURL=attaform.CwLjUqmQ.cjs.map
6702
+ //# sourceMappingURL=attaform.BUszFoKq.cjs.map