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,5 +1,5 @@
1
1
  import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, reactive, toRaw, watch, markRaw, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, effectScope, nextTick } from 'vue';
2
- import { _ as __DEV__, j as canonicalizePath, G as segmentsForPathKey, t as isPathPrefix, b as FORM_ERRORS_PATH_KEY, S as SubmitErrorHandlerError, A as AnonPersistError, k as captureUserCallSite, I as INTERACTIVE_TAG_NAMES, r as getOrAssignElementId, e as ROOT_PATH_KEY, R as ROOT_PATH, h as allowSensitivePersist, F as FORM_ERRORS_PATH, l as coerceToPathKey, v as isSensitivePath, o as createPersistOptInRegistry, d as InvalidUseFormConfigError, q as ensureAttaformInstalled, J as useRegistry, z as kFormContext, B as kFormInstanceId, g as ReservedFormKeyError, n as createIsSensitivePath, y as kAttaformWizardActiveStepResolver, w as kAttaformAncestorWizard } from './attaform.QG5TG8lB.mjs';
2
+ import { _ as __DEV__, j as canonicalizePath, G as segmentsForPathKey, t as isPathPrefix, b as FORM_ERRORS_PATH_KEY, S as SubmitErrorHandlerError, A as AnonPersistError, k as captureUserCallSite, I as INTERACTIVE_TAG_NAMES, r as getOrAssignElementId, e as ROOT_PATH_KEY, R as ROOT_PATH, h as allowSensitivePersist, F as FORM_ERRORS_PATH, l as coerceToPathKey, v as isSensitivePath, o as createPersistOptInRegistry, d as InvalidUseFormConfigError, q as ensureAttaformInstalled, J as useRegistry, z as kFormContext, B as kFormInstanceId, g as ReservedFormKeyError, n as createIsSensitivePath, y as kAttaformWizardActiveStepResolver, w as kAttaformAncestorWizard } from './attaform.Y_Mgg0Yp.mjs';
3
3
 
4
4
  function safeAssign(target, key, value) {
5
5
  if (key === "__proto__") {
@@ -458,17 +458,51 @@ function structuralSnapshot(value) {
458
458
  return out;
459
459
  }
460
460
 
461
- const defaultDisplayState = (field, formMeta) => {
462
- const gateOpen = formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
463
- if (!gateOpen) return "idle";
464
- if (field.validating === true) return "pending";
461
+ function isGateOpen(field, formMeta) {
462
+ return formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
463
+ }
464
+ function computeVerdict(field, formMeta) {
465
+ if (!isGateOpen(field, formMeta)) return "idle";
465
466
  const hasOwnError = field.errors.some(
466
467
  (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
467
468
  );
468
469
  if (hasOwnError) return "error";
469
470
  if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
470
471
  return "idle";
471
- };
472
+ }
473
+ const DEFAULT_TIMINGS = { showDelay: 100, minVisible: 120 };
474
+ const FOCUS_OUT_GRACE = 16;
475
+ const defaultFamily = /* @__PURE__ */ new WeakSet();
476
+ function isDefaultDisplayState(fn) {
477
+ return defaultFamily.has(fn);
478
+ }
479
+ function makeDefaultDisplayState({
480
+ showDelay,
481
+ minVisible
482
+ }) {
483
+ const reducer = (prev, { field, formMeta, validatingSince, now }) => {
484
+ const verdict = computeVerdict(field, formMeta);
485
+ if (!isGateOpen(field, formMeta)) return { display: verdict };
486
+ if (validatingSince === null) {
487
+ if (prev.display === "pending") {
488
+ const shownAt = prev.pendingShownAt ?? now;
489
+ if (now < shownAt + minVisible)
490
+ return { display: "pending", pendingShownAt: shownAt, reviewAt: shownAt + minVisible };
491
+ }
492
+ return { display: verdict };
493
+ }
494
+ if (prev.display === "pending")
495
+ return { display: "pending", pendingShownAt: prev.pendingShownAt ?? now };
496
+ const window = field.focused === false ? Math.min(showDelay, FOCUS_OUT_GRACE) : showDelay;
497
+ if (now - validatingSince < window) {
498
+ return { display: prev.display, reviewAt: validatingSince + window };
499
+ }
500
+ return { display: "pending", pendingShownAt: now, reviewAt: now + minVisible };
501
+ };
502
+ defaultFamily.add(reducer);
503
+ return reducer;
504
+ }
505
+ const defaultDisplayState = makeDefaultDisplayState(DEFAULT_TIMINGS);
472
506
  function resolveGetDisplayState(config) {
473
507
  return config ?? defaultDisplayState;
474
508
  }
@@ -625,7 +659,19 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
625
659
  }
626
660
  function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
627
661
  const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
628
- return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
662
+ const validatingSince = state.fieldValidatingSince.get(key) ?? null;
663
+ return decorateWithDerivedProps(
664
+ base,
665
+ state,
666
+ getFormMetaBase,
667
+ key,
668
+ validatingSince,
669
+ false,
670
+ // revealedDescendantError: leaves have no descendants
671
+ false,
672
+ // isRoot: a leaf is never the form root
673
+ getDisplayState
674
+ );
629
675
  }
630
676
  function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
631
677
  const formValue = state.form.value;
@@ -641,8 +687,11 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
641
687
  let blurredAfterInteraction = false;
642
688
  let connected = false;
643
689
  let validating = false;
690
+ let validatingSince = null;
644
691
  let updatedAt = null;
645
692
  let asyncPending = false;
693
+ const submissionAttempts = state.submissionAttempts.value;
694
+ const blurredLeafSegments = [];
646
695
  for (const [leafKey, entry] of state.originals) {
647
696
  if (!isPathPrefix(segments, entry.segments)) continue;
648
697
  if (segments.length === entry.segments.length) continue;
@@ -657,9 +706,18 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
657
706
  if (leafRecord?.blurred === true) blurred = true;
658
707
  if (leafRecord?.touched === true) touched = true;
659
708
  if (leafRecord?.interacted === true) interacted = true;
660
- if (leafRecord?.blurredAfterInteraction === true) blurredAfterInteraction = true;
709
+ if (leafRecord?.blurredAfterInteraction === true) {
710
+ blurredAfterInteraction = true;
711
+ blurredLeafSegments.push(entry.segments);
712
+ }
661
713
  if (leafRecord?.connected === true) connected = true;
662
- if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) validating = true;
714
+ if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) {
715
+ validating = true;
716
+ const leafGateOpen = submissionAttempts > 0 || leafRecord?.blurredAfterInteraction === true;
717
+ const since = state.fieldValidatingSince.get(leafKey);
718
+ if (leafGateOpen && since !== void 0 && (validatingSince === null || since < validatingSince))
719
+ validatingSince = since;
720
+ }
663
721
  if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
664
722
  const ts = leafRecord?.updatedAt;
665
723
  if (ts !== void 0 && ts !== null) {
@@ -671,6 +729,13 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
671
729
  dirty = true;
672
730
  }
673
731
  const errors = aggregateErrorsAt(state, segments);
732
+ const revealedDescendantError = errors.length > 0 && errors.some((e) => {
733
+ const ePath = e.path;
734
+ if (ePath.length === segments.length && ePath.every((s, i) => s === segments[i])) return false;
735
+ if (submissionAttempts > 0) return true;
736
+ if (ePath.length === 1 && ePath[0] === "") return blurredAfterInteraction;
737
+ return blurredLeafSegments.some((s) => isPathPrefix(ePath, s));
738
+ });
674
739
  if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
675
740
  const gated = asyncPending && !state.firstValidationDone.value;
676
741
  const valid = !gated && errors.length === 0 && !validating;
@@ -678,43 +743,70 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
678
743
  const lastSegment = segments.length === 0 ? "" : segments[segments.length - 1] ?? "";
679
744
  const label = resolved.label || humanize(lastSegment);
680
745
  return {
681
- value,
682
- original,
683
- pristine,
684
- dirty,
685
- focused,
686
- blurred,
687
- touched,
688
- interacted,
689
- blurredAfterInteraction,
690
- connected,
691
- element: null,
692
- elements: EMPTY_ELEMENTS,
693
- updatedAt,
694
- errors,
695
- validating,
696
- valid,
697
- path: segments,
698
- ...computeFieldIdentity(formInstanceId, state.formKey, key),
699
- key: state.arrayElementKey(segments),
700
- blank,
701
- label,
702
- description: resolved.description,
703
- placeholder: resolved.placeholder,
704
- meta: resolved.meta
746
+ base: {
747
+ value,
748
+ original,
749
+ pristine,
750
+ dirty,
751
+ focused,
752
+ blurred,
753
+ touched,
754
+ interacted,
755
+ blurredAfterInteraction,
756
+ connected,
757
+ element: null,
758
+ elements: EMPTY_ELEMENTS,
759
+ updatedAt,
760
+ errors,
761
+ validating,
762
+ valid,
763
+ path: segments,
764
+ ...computeFieldIdentity(formInstanceId, state.formKey, key),
765
+ key: state.arrayElementKey(segments),
766
+ blank,
767
+ label,
768
+ description: resolved.description,
769
+ placeholder: resolved.placeholder,
770
+ meta: resolved.meta
771
+ },
772
+ validatingSince,
773
+ revealedDescendantError
705
774
  };
706
775
  }
707
776
  function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
708
- const base = buildContainerFieldStateBase(state, segments, key, formInstanceId);
709
- return decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState);
777
+ const { base, validatingSince, revealedDescendantError } = buildContainerFieldStateBase(
778
+ state,
779
+ segments,
780
+ key,
781
+ formInstanceId
782
+ );
783
+ return decorateWithDerivedProps(
784
+ base,
785
+ state,
786
+ getFormMetaBase,
787
+ key,
788
+ validatingSince,
789
+ revealedDescendantError,
790
+ segments.length === 0,
791
+ getDisplayState
792
+ );
710
793
  }
711
- function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState) {
794
+ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, revealedDescendantError, isRoot, getDisplayState) {
712
795
  const firstError = base.errors[0];
713
796
  const predicate = getDisplayState ?? state.getDisplayState;
714
797
  const formMeta = getFormMetaBase();
715
- let displayState;
798
+ const ctx = {
799
+ field: base,
800
+ formMeta,
801
+ validatingSince,
802
+ // The engine's clock. Frozen to 0 under SSR (no clock, nothing in
803
+ // flight) so the reducer returns the plain verdict and hydration matches.
804
+ now: state.ssr ? 0 : Date.now()
805
+ };
806
+ let machine;
807
+ let rollupApplies = isDefaultDisplayState(predicate);
716
808
  try {
717
- displayState = predicate(base, formMeta);
809
+ machine = state.displayEngine.resolve(key, ctx, predicate);
718
810
  } catch (err) {
719
811
  if (__DEV__ && !warnedDisplayStatePredicates.has(predicate)) {
720
812
  warnedDisplayStatePredicates.add(predicate);
@@ -723,8 +815,11 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, getDisplayState)
723
815
  err
724
816
  );
725
817
  }
726
- displayState = defaultDisplayState(base, formMeta);
818
+ machine = state.displayEngine.resolve(key, ctx, defaultDisplayState);
819
+ rollupApplies = true;
727
820
  }
821
+ const submitValidating = rollupApplies && isRoot && state.submitting.value && state.activeValidations.value > 0;
822
+ const displayState = machine.display === "pending" || submitValidating ? "pending" : rollupApplies && revealedDescendantError ? "error" : machine.display;
728
823
  return {
729
824
  ...base,
730
825
  displayState,
@@ -1417,90 +1512,6 @@ async function getStorageAdapter(storage) {
1417
1512
  }
1418
1513
  }
1419
1514
  }
1420
- const PERSISTED_ENVELOPE_VERSION = 6;
1421
- function readPersistedPayload(value) {
1422
- if (value === null || value === void 0 || typeof value !== "object") return null;
1423
- const envelope = value;
1424
- if (typeof envelope.v !== "number") return null;
1425
- if (envelope.v !== PERSISTED_ENVELOPE_VERSION) {
1426
- warnVersionMismatch(envelope.v);
1427
- return null;
1428
- }
1429
- if (envelope.data === void 0 || typeof envelope.data !== "object") return null;
1430
- return envelope;
1431
- }
1432
- const warnedVersions = __DEV__ ? /* @__PURE__ */ new Set() : null;
1433
- function warnVersionMismatch(observedVersion) {
1434
- if (warnedVersions === null) return;
1435
- if (warnedVersions.has(observedVersion)) return;
1436
- warnedVersions.add(observedVersion);
1437
- console.warn(
1438
- `[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.`
1439
- );
1440
- }
1441
- function buildPersistedPayload(form, include, schemaErrors, userErrors, blankPaths) {
1442
- let transientList;
1443
- if (blankPaths !== void 0 && blankPaths.size > 0) {
1444
- transientList = [...blankPaths];
1445
- }
1446
- if (include === "form") {
1447
- if (transientList === void 0) return { v: PERSISTED_ENVELOPE_VERSION, data: { form } };
1448
- return {
1449
- v: PERSISTED_ENVELOPE_VERSION,
1450
- data: { form, blankPaths: transientList }
1451
- };
1452
- }
1453
- return {
1454
- v: PERSISTED_ENVELOPE_VERSION,
1455
- data: {
1456
- form,
1457
- schemaErrors: [...schemaErrors.entries()].map(([k, v]) => [k, [...v]]),
1458
- userErrors: [...userErrors.entries()].map(([k, v]) => [k, [...v]]),
1459
- ...transientList !== void 0 ? { blankPaths: transientList } : {}
1460
- }
1461
- };
1462
- }
1463
- function createDebouncedWriter(write, debounceMs) {
1464
- let timer = null;
1465
- let pending = null;
1466
- let writeGeneration = 0;
1467
- function runWrite() {
1468
- const gen = ++writeGeneration;
1469
- pending = write().finally(() => {
1470
- if (writeGeneration === gen) pending = null;
1471
- });
1472
- }
1473
- function schedule() {
1474
- if (timer !== null) clearTimeout(timer);
1475
- if (debounceMs === 0) {
1476
- runWrite();
1477
- return;
1478
- }
1479
- timer = setTimeout(() => {
1480
- timer = null;
1481
- runWrite();
1482
- }, debounceMs);
1483
- }
1484
- async function flush() {
1485
- if (timer !== null) {
1486
- clearTimeout(timer);
1487
- timer = null;
1488
- runWrite();
1489
- }
1490
- while (pending !== null) {
1491
- const awaited = pending;
1492
- await awaited;
1493
- if (pending === awaited) break;
1494
- }
1495
- }
1496
- function cancel() {
1497
- if (timer !== null) {
1498
- clearTimeout(timer);
1499
- timer = null;
1500
- }
1501
- }
1502
- return { schedule, flush, cancel };
1503
- }
1504
1515
  function resolveStorageKeyBase(config, formKey) {
1505
1516
  return config.key ?? `${PERSISTENCE_KEY_PREFIX}${formKey}`;
1506
1517
  }
@@ -1547,47 +1558,6 @@ async function sweepNonConfiguredStandardStoresForOrphans(configured, base) {
1547
1558
  }
1548
1559
  }
1549
1560
  }
1550
- function pluckPaths(form, pathKeys) {
1551
- let sparse = void 0;
1552
- for (const pathKey of pathKeys) {
1553
- const segments = segmentsForPathKey(pathKey);
1554
- if (segments === null) continue;
1555
- const value = getAtPath(form, segments);
1556
- if (value === void 0) continue;
1557
- sparse = setAtPath(sparse ?? {}, segments, value);
1558
- }
1559
- return sparse ?? {};
1560
- }
1561
- function stripUnacknowledgedSensitiveLeaves(form, optedInPaths, isSensitivePath) {
1562
- const acknowledgedSensitive = [];
1563
- for (const key of optedInPaths) {
1564
- const segs = segmentsForPathKey(key);
1565
- if (segs !== null && isSensitivePath(segs)) acknowledgedSensitive.push(segs);
1566
- }
1567
- const coveredByAcknowledged = (path) => acknowledgedSensitive.some((prefix) => isPathPrefix(prefix, path));
1568
- const walk = (path, value) => {
1569
- if (path.length > 0 && isSensitivePath(path) && !coveredByAcknowledged(path)) {
1570
- return void 0;
1571
- }
1572
- if (value === null || typeof value !== "object") return value;
1573
- if (Array.isArray(value)) return value.map((item, i) => walk([...path, i], item));
1574
- if (!isPlainRecord(value)) return value;
1575
- const out = {};
1576
- for (const key of Object.keys(value)) {
1577
- const walked = walk([...path, key], value[key]);
1578
- if (walked !== void 0) safeAssign(out, key, walked);
1579
- }
1580
- return out;
1581
- };
1582
- return walk([], form);
1583
- }
1584
- function filterErrorsByPaths(errors, pathKeys) {
1585
- const out = /* @__PURE__ */ new Map();
1586
- for (const [key, value] of errors) {
1587
- if (pathKeys.has(key)) out.set(key, value);
1588
- }
1589
- return out;
1590
- }
1591
1561
  function mergeSparseHydration(schemaDefaults, sparse, schema) {
1592
1562
  return mergeDeep(schemaDefaults, sparse, [], schema);
1593
1563
  }
@@ -1825,6 +1795,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1825
1795
  state.submitting.value = true;
1826
1796
  state.submitError.value = null;
1827
1797
  state.cancelFieldValidation();
1798
+ state.displayEngine.clear();
1828
1799
  state.activeValidations.value += 1;
1829
1800
  const refinement = await runRefinementValidation(state.form.value, void 0);
1830
1801
  const merged = composeWithDerivedBlank(refinement, void 0);
@@ -2641,7 +2612,12 @@ function buildFormApi(state, formInstanceId, options = {}) {
2641
2612
  return meta === void 0 ? { instance: instanceMeta } : { ...meta, instance: instanceMeta };
2642
2613
  };
2643
2614
  const getFormMetaBase = () => {
2644
- const rootBase = buildContainerFieldStateBase(state, ROOT_PATH, ROOT_PATH_KEY, formInstanceId);
2615
+ const { base: rootBase } = buildContainerFieldStateBase(
2616
+ state,
2617
+ ROOT_PATH,
2618
+ ROOT_PATH_KEY,
2619
+ formInstanceId
2620
+ );
2645
2621
  return {
2646
2622
  ...rootBase,
2647
2623
  submitting: state.submitting.value,
@@ -2976,7 +2952,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2976
2952
  instanceId: formInstanceId
2977
2953
  })
2978
2954
  );
2979
- const persistence = state.modules.get(PERSISTENCE_MODULE_KEY);
2955
+ const persistenceHandle = state.modules.get(PERSISTENCE_MODULE_KEY);
2980
2956
  const reset = (nextDefaultValues) => {
2981
2957
  if (nextDefaultValues === void 0) {
2982
2958
  state.reset();
@@ -2991,15 +2967,15 @@ function buildFormApi(state, formInstanceId, options = {}) {
2991
2967
  state.originalBlankPaths.add(pathKey);
2992
2968
  }
2993
2969
  }
2994
- if (persistence !== void 0) {
2995
- void persistence.clearPersistedDraft().catch(() => void 0);
2970
+ if (persistenceHandle !== void 0) {
2971
+ void persistenceHandle.ready.then((m) => m?.clearPersistedDraft()).catch(() => void 0);
2996
2972
  }
2997
2973
  };
2998
2974
  const resetField = (pathInput) => {
2999
2975
  const segments = canonicalizePath(pathInput).segments;
3000
2976
  state.resetField(segments);
3001
- if (persistence !== void 0) {
3002
- void persistence.clearPersistedDraft(segments).catch(() => void 0);
2977
+ if (persistenceHandle !== void 0) {
2978
+ void persistenceHandle.ready.then((m) => m?.clearPersistedDraft(segments)).catch(() => void 0);
3003
2979
  }
3004
2980
  };
3005
2981
  function clear(pathInput) {
@@ -3017,10 +2993,14 @@ function buildFormApi(state, formInstanceId, options = {}) {
3017
2993
  )) {
3018
2994
  return;
3019
2995
  }
2996
+ if (persistenceHandle === void 0) return;
2997
+ const persistence = await persistenceHandle.ready;
3020
2998
  if (persistence === void 0) return;
3021
2999
  await persistence.writePathImmediately(segments);
3022
3000
  };
3023
3001
  const clearPersistedDraft = async (pathInput) => {
3002
+ if (persistenceHandle === void 0) return;
3003
+ const persistence = await persistenceHandle.ready;
3024
3004
  if (persistence === void 0) return;
3025
3005
  if (pathInput === void 0) {
3026
3006
  await persistence.clearPersistedDraft();
@@ -3203,6 +3183,93 @@ function buildFormApi(state, formInstanceId, options = {}) {
3203
3183
  };
3204
3184
  }
3205
3185
 
3186
+ const IDLE = Object.freeze({ display: "idle" });
3187
+ const MAX_DELAY = 2147483647;
3188
+ function createDisplayEngine(ssr) {
3189
+ const machines = /* @__PURE__ */ new Map();
3190
+ const tick = ref(0);
3191
+ let timer = null;
3192
+ let timerTarget = null;
3193
+ let lastFiredTarget = null;
3194
+ function clearTimer() {
3195
+ if (timer !== null) {
3196
+ clearTimeout(timer);
3197
+ timer = null;
3198
+ timerTarget = null;
3199
+ }
3200
+ }
3201
+ function nearestReviewAt() {
3202
+ let min = null;
3203
+ for (const m of machines.values()) {
3204
+ if (m.reviewAt === void 0 || !Number.isFinite(m.reviewAt)) continue;
3205
+ if (min === null || m.reviewAt < min) min = m.reviewAt;
3206
+ }
3207
+ return min;
3208
+ }
3209
+ function rearm(now) {
3210
+ const target = nearestReviewAt();
3211
+ if (target === null) {
3212
+ clearTimer();
3213
+ return;
3214
+ }
3215
+ if (timer !== null && timerTarget === target) return;
3216
+ if (target === lastFiredTarget) {
3217
+ clearTimer();
3218
+ return;
3219
+ }
3220
+ clearTimer();
3221
+ timerTarget = target;
3222
+ timer = setTimeout(
3223
+ () => {
3224
+ timer = null;
3225
+ timerTarget = null;
3226
+ lastFiredTarget = target;
3227
+ tick.value++;
3228
+ },
3229
+ Math.min(MAX_DELAY, Math.max(0, target - now))
3230
+ );
3231
+ }
3232
+ function resolve(key, ctx, reducer) {
3233
+ void tick.value;
3234
+ const prev = machines.get(key) ?? IDLE;
3235
+ const machine = reducer(prev, ctx);
3236
+ if (ssr) return machine;
3237
+ const active = machine.display !== "idle" || machine.reviewAt !== void 0 && Number.isFinite(machine.reviewAt);
3238
+ if (active) machines.set(key, machine);
3239
+ else machines.delete(key);
3240
+ rearm(ctx.now);
3241
+ return machine;
3242
+ }
3243
+ function clear() {
3244
+ machines.clear();
3245
+ clearTimer();
3246
+ lastFiredTarget = null;
3247
+ }
3248
+ let detachVisibility = null;
3249
+ if (!ssr && typeof document !== "undefined") {
3250
+ const onVisible = () => {
3251
+ if (document.visibilityState === "visible" && machines.size > 0) tick.value++;
3252
+ };
3253
+ document.addEventListener("visibilitychange", onVisible);
3254
+ detachVisibility = () => document.removeEventListener("visibilitychange", onVisible);
3255
+ }
3256
+ function dispose() {
3257
+ clear();
3258
+ if (detachVisibility !== null) {
3259
+ detachVisibility();
3260
+ detachVisibility = null;
3261
+ }
3262
+ }
3263
+ return {
3264
+ resolve,
3265
+ clear,
3266
+ dispose,
3267
+ size: () => machines.size,
3268
+ has: (key) => machines.has(key),
3269
+ hasTimer: () => timer !== null
3270
+ };
3271
+ }
3272
+
3206
3273
  function applyDuStubs(schema, data, options = {}) {
3207
3274
  const warned = options.warn === true ? /* @__PURE__ */ new Set() : void 0;
3208
3275
  return walkDuStubs(schema, data, options.basePath ?? [], warned);
@@ -3541,6 +3608,7 @@ function createArrayBookkeeping(deps) {
3541
3608
  blankPaths,
3542
3609
  originalBlankPaths,
3543
3610
  fieldValidationCounts,
3611
+ fieldValidatingSince,
3544
3612
  fieldValidationState,
3545
3613
  schemaErrors,
3546
3614
  activeValidations,
@@ -3566,6 +3634,7 @@ function createArrayBookkeeping(deps) {
3566
3634
  }));
3567
3635
  migrateSetSubtree(blankPaths, arrayPath, remap);
3568
3636
  migrateSetSubtree(originalBlankPaths, arrayPath, remap);
3637
+ migrateMapSubtree(fieldValidatingSince, arrayPath, remap, (since) => since);
3569
3638
  migrateMapSubtree(fieldValidationCounts, arrayPath, remap, (count) => count);
3570
3639
  arrayIdentity.applyRemap(arrayPath, remap);
3571
3640
  }
@@ -3625,6 +3694,18 @@ function isHydratedFieldRecord(value) {
3625
3694
  const r = value;
3626
3695
  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";
3627
3696
  }
3697
+ function withClearedHistoryFlags(record, now) {
3698
+ return {
3699
+ path: record.path,
3700
+ updatedAt: now,
3701
+ connected: record.connected,
3702
+ focused: record.focused,
3703
+ blurred: record.blurred,
3704
+ touched: false,
3705
+ interacted: false,
3706
+ blurredAfterInteraction: false
3707
+ };
3708
+ }
3628
3709
  function isHydratedValidationErrorArray(value) {
3629
3710
  if (!Array.isArray(value)) return false;
3630
3711
  for (const entry of value) {
@@ -3752,6 +3833,9 @@ function createFormStore(options) {
3752
3833
  const resolvedIsSensitivePath = options.isSensitivePath ?? isSensitivePath;
3753
3834
  const cleanupHooks = [];
3754
3835
  const modules = /* @__PURE__ */ new Map();
3836
+ const fieldValidatingSince = reactive(/* @__PURE__ */ new Map());
3837
+ const displayEngine = createDisplayEngine(ssr);
3838
+ registerCleanup(() => displayEngine.dispose());
3755
3839
  const completedConstraints = defaultValues === void 0 ? void 0 : mergeStructural(schema, [], defaultValues);
3756
3840
  const schemaResponse = schema.getDefaultValues({
3757
3841
  useDefaultSchemaValues: true,
@@ -3880,12 +3964,18 @@ function createFormStore(options) {
3880
3964
  }
3881
3965
  const fieldValidationCounts = reactive(/* @__PURE__ */ new Map());
3882
3966
  function incFieldValidation(key) {
3883
- fieldValidationCounts.set(key, (fieldValidationCounts.get(key) ?? 0) + 1);
3967
+ fieldValidatingSince.set(key, ssr ? 0 : Date.now());
3968
+ const prevCount = fieldValidationCounts.get(key) ?? 0;
3969
+ fieldValidationCounts.set(key, prevCount + 1);
3884
3970
  }
3885
3971
  function decFieldValidation(key) {
3886
3972
  const next = (fieldValidationCounts.get(key) ?? 0) - 1;
3887
- if (next <= 0) fieldValidationCounts.delete(key);
3888
- else fieldValidationCounts.set(key, next);
3973
+ if (next <= 0) {
3974
+ fieldValidationCounts.delete(key);
3975
+ fieldValidatingSince.delete(key);
3976
+ } else {
3977
+ fieldValidationCounts.set(key, next);
3978
+ }
3889
3979
  }
3890
3980
  const initStamp = (/* @__PURE__ */ new Date()).toISOString();
3891
3981
  diffAndApply({}, schemaInitialData, [], (patch) => {
@@ -3973,6 +4063,7 @@ function createFormStore(options) {
3973
4063
  blankPaths,
3974
4064
  originalBlankPaths,
3975
4065
  fieldValidationCounts,
4066
+ fieldValidatingSince,
3976
4067
  fieldValidationState,
3977
4068
  schemaErrors,
3978
4069
  activeValidations,
@@ -4240,7 +4331,7 @@ function createFormStore(options) {
4240
4331
  prev.controller.abort();
4241
4332
  }
4242
4333
  const controller = new AbortController();
4243
- const fresh = { controller, timer: null, settled: false };
4334
+ const fresh = { controller, timer: null, settled: false, released: false };
4244
4335
  fieldValidationState.set(key, fresh);
4245
4336
  const myEpoch = ++scheduleEpoch;
4246
4337
  const run = () => {
@@ -4278,8 +4369,10 @@ function createFormStore(options) {
4278
4369
  applySchemaErrorsForSubtree(scopePath ?? [], restamped);
4279
4370
  }).catch(() => {
4280
4371
  }).finally(() => {
4281
- activeValidations.value = Math.max(0, activeValidations.value - 1);
4282
- decFieldValidation(key);
4372
+ if (!fresh.released) {
4373
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
4374
+ decFieldValidation(key);
4375
+ }
4283
4376
  fresh.settled = true;
4284
4377
  });
4285
4378
  };
@@ -4301,6 +4394,22 @@ function createFormStore(options) {
4301
4394
  }
4302
4395
  fieldValidationState.clear();
4303
4396
  }
4397
+ function cancelFieldValidationUnder(prefix) {
4398
+ for (const [key, entry] of [...fieldValidationState]) {
4399
+ const segs = segmentsForPathKey(key);
4400
+ if (segs === null) continue;
4401
+ if (!isPathPrefix(prefix, segs)) continue;
4402
+ if (entry.timer !== null) {
4403
+ clearTimeout(entry.timer);
4404
+ } else if (!entry.settled && !entry.released) {
4405
+ activeValidations.value = Math.max(0, activeValidations.value - 1);
4406
+ decFieldValidation(key);
4407
+ entry.released = true;
4408
+ }
4409
+ entry.controller.abort();
4410
+ fieldValidationState.delete(key);
4411
+ }
4412
+ }
4304
4413
  function onFormChange(listener) {
4305
4414
  formChangeListeners.add(listener);
4306
4415
  return () => {
@@ -4351,6 +4460,7 @@ function createFormStore(options) {
4351
4460
  drainHooks.length = 0;
4352
4461
  modules.clear();
4353
4462
  cancelFieldValidation();
4463
+ fieldValidatingSince.clear();
4354
4464
  formChangeListeners.clear();
4355
4465
  submitSuccessListeners.clear();
4356
4466
  resetListeners.clear();
@@ -4697,16 +4807,7 @@ function createFormStore(options) {
4697
4807
  }
4698
4808
  const now = (/* @__PURE__ */ new Date()).toISOString();
4699
4809
  for (const [pathKey, record] of fields) {
4700
- fields.set(pathKey, {
4701
- path: record.path,
4702
- updatedAt: now,
4703
- connected: record.connected,
4704
- focused: record.focused,
4705
- blurred: record.blurred,
4706
- touched: false,
4707
- interacted: false,
4708
- blurredAfterInteraction: false
4709
- });
4810
+ fields.set(pathKey, withClearedHistoryFlags(record, now));
4710
4811
  }
4711
4812
  submissionGeneration.value += 1;
4712
4813
  submitting.value = false;
@@ -4716,6 +4817,8 @@ function createFormStore(options) {
4716
4817
  submitError.value = null;
4717
4818
  departAttempts.value = 0;
4718
4819
  cancelFieldValidation();
4820
+ displayEngine.clear();
4821
+ fieldValidatingSince.clear();
4719
4822
  pathSnapshots.clear();
4720
4823
  scheduleEpoch = 0;
4721
4824
  lastCommittedEpoch = 0;
@@ -4731,6 +4834,12 @@ function createFormStore(options) {
4731
4834
  function resetField(path) {
4732
4835
  const { key: targetKey, segments: targetSegments } = canonicalizePath(path);
4733
4836
  variantMemory.clearUnderPath(targetSegments);
4837
+ cancelFieldValidationUnder(targetSegments);
4838
+ for (const [snapKey] of [...pathSnapshots]) {
4839
+ const segs = segmentsForPathKey(snapKey);
4840
+ if (segs === null) continue;
4841
+ if (isPathPrefix(targetSegments, segs)) pathSnapshots.delete(snapKey);
4842
+ }
4734
4843
  const leafEntry = originals.get(targetKey);
4735
4844
  if (leafEntry !== void 0) {
4736
4845
  const wrote = setValueAtPath(targetSegments, leafEntry.value);
@@ -4780,16 +4889,7 @@ function createFormStore(options) {
4780
4889
  function clearFieldRecordFlags(pathKey) {
4781
4890
  const record = fields.get(pathKey);
4782
4891
  if (record === void 0) return;
4783
- fields.set(pathKey, {
4784
- path: record.path,
4785
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4786
- connected: record.connected,
4787
- focused: record.focused,
4788
- blurred: record.blurred,
4789
- touched: false,
4790
- interacted: false,
4791
- blurredAfterInteraction: false
4792
- });
4892
+ fields.set(pathKey, withClearedHistoryFlags(record, (/* @__PURE__ */ new Date()).toISOString()));
4793
4893
  }
4794
4894
  function isPristineAtPath(path) {
4795
4895
  const { key, segments } = canonicalizePath(path);
@@ -4869,6 +4969,8 @@ function createFormStore(options) {
4869
4969
  pathHasAsyncValidation,
4870
4970
  pathHasAsyncValidationByKey,
4871
4971
  fieldValidationCounts,
4972
+ fieldValidatingSince,
4973
+ displayEngine,
4872
4974
  applyFormReplacement,
4873
4975
  setValueAtPath,
4874
4976
  getValueAtPath,
@@ -4953,7 +5055,7 @@ function errorsEqual(a, b) {
4953
5055
  }
4954
5056
  return true;
4955
5057
  }
4956
- function diffBlankPaths$1(prev, curr) {
5058
+ function diffBlankPaths(prev, curr) {
4957
5059
  const added = [];
4958
5060
  const removed = [];
4959
5061
  for (const k of curr) if (!prev.has(k)) added.push(k);
@@ -5041,7 +5143,7 @@ function createHistoryModule(state, config) {
5041
5143
  diffAndApply(prevSnap.form, newSnap.form, [], (p) => formPatches.push(p));
5042
5144
  const prevBlankSet = new Set(prevSnap.blankPaths);
5043
5145
  const currBlankSet = new Set(newSnap.blankPaths);
5044
- const blankDiff = diffBlankPaths$1(prevBlankSet, currBlankSet);
5146
+ const blankDiff = diffBlankPaths(prevBlankSet, currBlankSet);
5045
5147
  const delta = {
5046
5148
  formPatches,
5047
5149
  blankPathsAdded: blankDiff.added,
@@ -5108,643 +5210,6 @@ function createHistoryModule(state, config) {
5108
5210
  };
5109
5211
  }
5110
5212
 
5111
- function wirePersistence(state, config) {
5112
- let fingerprint;
5113
- try {
5114
- fingerprint = hashStableString(state.schema.fingerprint());
5115
- } catch (err) {
5116
- if (__DEV__) {
5117
- console.warn(
5118
- `[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.`
5119
- );
5120
- }
5121
- fingerprint = "unfingerprinted";
5122
- }
5123
- const base = resolveStorageKeyBase(config, state.formKey);
5124
- const key = `${base}:${fingerprint}`;
5125
- const debounceMs = normalizeNumericOption({
5126
- value: config.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS,
5127
- source: "useForm.persist.debounceMs",
5128
- allowInfinity: false,
5129
- min: 0,
5130
- defaultValue: DEFAULT_PERSISTENCE_DEBOUNCE_MS
5131
- });
5132
- const include = config.include ?? "form";
5133
- const clearOnSubmitSuccess = config.clearOnSubmitSuccess ?? true;
5134
- const adapterPromise = getStorageAdapter(config.storage);
5135
- let disposed = false;
5136
- const isDisposed = () => disposed;
5137
- let inFlightFinalFlush = null;
5138
- let pendingOptedInPaths = null;
5139
- const writer = createDebouncedWriter(async () => {
5140
- const optedInPaths = pendingOptedInPaths ?? new Set(state.persistOptIns.optedInPaths());
5141
- pendingOptedInPaths = null;
5142
- const adapter = await adapterPromise;
5143
- if (isDisposed()) return;
5144
- if (optedInPaths.size === 0) {
5145
- await adapter.removeItem(key);
5146
- return;
5147
- }
5148
- const rawForm = toRaw(state.form.value);
5149
- const filteredForm = stripUnacknowledgedSensitiveLeaves(
5150
- pluckPaths(rawForm, optedInPaths),
5151
- optedInPaths,
5152
- state.isSensitivePath
5153
- );
5154
- const filteredSchemaErrors = filterErrorsByPaths(state.schemaErrors, optedInPaths);
5155
- const filteredUserErrors = filterErrorsByPaths(state.userErrors, optedInPaths);
5156
- const filteredTransientEmpty = /* @__PURE__ */ new Set();
5157
- for (const tk of state.blankPaths) {
5158
- if (optedInPaths.has(tk)) filteredTransientEmpty.add(tk);
5159
- }
5160
- const payload = buildPersistedPayload(
5161
- filteredForm,
5162
- include,
5163
- filteredSchemaErrors,
5164
- filteredUserErrors,
5165
- filteredTransientEmpty
5166
- );
5167
- await adapter.setItem(key, payload);
5168
- }, debounceMs);
5169
- const unsubscribeChange = state.onFormChange((_next, meta) => {
5170
- if (isDisposed() || inFlightFinalFlush !== null) return;
5171
- if (meta?.crossTab === true) return;
5172
- if (meta?.persist !== true) return;
5173
- pendingOptedInPaths = new Set(state.persistOptIns.optedInPaths());
5174
- writer.schedule();
5175
- });
5176
- const unsubscribeSuccess = clearOnSubmitSuccess ? state.onSubmitSuccess(() => {
5177
- if (isDisposed()) return;
5178
- void (async () => {
5179
- await writer.flush();
5180
- if (isDisposed()) return;
5181
- const adapter = await adapterPromise;
5182
- if (isDisposed()) return;
5183
- await adapter.removeItem(key);
5184
- })();
5185
- }) : () => void 0;
5186
- const handlePageHide = () => {
5187
- if (isDisposed()) return;
5188
- void writer.flush();
5189
- };
5190
- if (typeof window !== "undefined") {
5191
- window.addEventListener("pagehide", handlePageHide);
5192
- }
5193
- void (async () => {
5194
- const adapter = await adapterPromise;
5195
- if (isDisposed()) return;
5196
- void cleanupOrphanKeys(adapter, base, key);
5197
- try {
5198
- const raw = await adapter.getItem(key);
5199
- const payload = readPersistedPayload(raw);
5200
- if (payload === null) {
5201
- if (raw !== null && raw !== void 0) {
5202
- await adapter.removeItem(key);
5203
- }
5204
- return;
5205
- }
5206
- if (isDisposed()) return;
5207
- const merged = mergeSparseHydration(
5208
- toRaw(state.form.value),
5209
- payload.data.form,
5210
- state.schema
5211
- );
5212
- state.applyFormReplacement(merged, { hydration: true });
5213
- const persistedLeafPaths = collectPersistedLeafPaths(payload.data.form);
5214
- for (const k of persistedLeafPaths) {
5215
- state.blankPaths.delete(k);
5216
- state.originalBlankPaths.delete(k);
5217
- }
5218
- for (const k of payload.data.blankPaths ?? []) {
5219
- state.blankPaths.add(k);
5220
- state.originalBlankPaths.add(k);
5221
- }
5222
- if (include === "form+errors") {
5223
- if (payload.data.schemaErrors !== void 0) {
5224
- const flat = payload.data.schemaErrors.flatMap(([, errs]) => errs);
5225
- state.setAllSchemaErrors(flat);
5226
- }
5227
- if (payload.data.userErrors !== void 0) {
5228
- const flat = payload.data.userErrors.flatMap(([, errs]) => errs);
5229
- state.setAllUserErrors(flat);
5230
- }
5231
- }
5232
- state.scheduleFieldValidation(
5233
- [],
5234
- true
5235
- /* immediate */
5236
- );
5237
- } catch {
5238
- }
5239
- })();
5240
- async function writePathImmediately(path) {
5241
- if (isDisposed()) return;
5242
- await writer.flush();
5243
- if (isDisposed()) return;
5244
- const adapter = await adapterPromise;
5245
- if (isDisposed()) return;
5246
- const raw = await adapter.getItem(key);
5247
- const existing = readPersistedPayload(raw);
5248
- const value = getAtPath(toRaw(state.form.value), path);
5249
- const nextForm = setAtPath(existing?.data.form ?? {}, path, value);
5250
- const { key: pathKey } = canonicalizePath(path);
5251
- const transientSet = new Set(
5252
- (existing?.data.blankPaths ?? []).filter(
5253
- (k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
5254
- )
5255
- );
5256
- for (const liveKey of state.blankPaths) {
5257
- if (liveKey === pathKey || isDescendantPathKey(liveKey, pathKey)) {
5258
- transientSet.add(liveKey);
5259
- }
5260
- }
5261
- if (include === "form") {
5262
- await adapter.setItem(
5263
- key,
5264
- buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
5265
- );
5266
- return;
5267
- }
5268
- const schemaMap = new Map(existing?.data.schemaErrors ?? []);
5269
- const userMap = new Map(existing?.data.userErrors ?? []);
5270
- const currentSchema = state.schemaErrors.get(pathKey);
5271
- const currentUser = state.userErrors.get(pathKey);
5272
- if (currentSchema !== void 0 && currentSchema.length > 0) {
5273
- schemaMap.set(pathKey, [...currentSchema]);
5274
- } else {
5275
- schemaMap.delete(pathKey);
5276
- }
5277
- if (currentUser !== void 0 && currentUser.length > 0) {
5278
- userMap.set(pathKey, [...currentUser]);
5279
- } else {
5280
- userMap.delete(pathKey);
5281
- }
5282
- await adapter.setItem(
5283
- key,
5284
- buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
5285
- );
5286
- }
5287
- async function clearPersistedDraft(path) {
5288
- if (isDisposed()) return;
5289
- await writer.flush();
5290
- if (isDisposed()) return;
5291
- const adapter = await adapterPromise;
5292
- if (isDisposed()) return;
5293
- if (path === void 0) {
5294
- await adapter.removeItem(key);
5295
- return;
5296
- }
5297
- const raw = await adapter.getItem(key);
5298
- const existing = readPersistedPayload(raw);
5299
- if (existing === null) return;
5300
- const nextForm = deleteAtPath(existing.data.form, path);
5301
- if (isEmptyContainer(nextForm)) {
5302
- await adapter.removeItem(key);
5303
- return;
5304
- }
5305
- const { key: pathKey } = canonicalizePath(path);
5306
- const transientSet = new Set(
5307
- (existing.data.blankPaths ?? []).filter(
5308
- (k) => k !== pathKey && !isDescendantPathKey(k, pathKey)
5309
- )
5310
- );
5311
- if (include === "form") {
5312
- await adapter.setItem(
5313
- key,
5314
- buildPersistedPayload(nextForm, "form", /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), transientSet)
5315
- );
5316
- return;
5317
- }
5318
- const schemaErrors = (existing.data.schemaErrors ?? []).filter(([k]) => k !== pathKey);
5319
- const userErrors = (existing.data.userErrors ?? []).filter(([k]) => k !== pathKey);
5320
- const schemaMap = new Map(schemaErrors.map(([k, v]) => [k, [...v]]));
5321
- const userMap = new Map(userErrors.map(([k, v]) => [k, [...v]]));
5322
- await adapter.setItem(
5323
- key,
5324
- buildPersistedPayload(nextForm, "form+errors", schemaMap, userMap, transientSet)
5325
- );
5326
- }
5327
- function awaitPendingWrites() {
5328
- if (inFlightFinalFlush !== null) return inFlightFinalFlush;
5329
- if (isDisposed()) return Promise.resolve();
5330
- return writer.flush().catch(() => void 0);
5331
- }
5332
- function dispose() {
5333
- if (isDisposed() || inFlightFinalFlush !== null) return;
5334
- unsubscribeChange();
5335
- unsubscribeSuccess();
5336
- if (typeof window !== "undefined") {
5337
- window.removeEventListener("pagehide", handlePageHide);
5338
- }
5339
- inFlightFinalFlush = writer.flush().catch(() => void 0).finally(() => {
5340
- disposed = true;
5341
- inFlightFinalFlush = null;
5342
- });
5343
- }
5344
- return {
5345
- wiredConfig: config,
5346
- writePathImmediately,
5347
- clearPersistedDraft,
5348
- awaitPendingWrites,
5349
- dispose
5350
- };
5351
- }
5352
- function isEmptyContainer(value) {
5353
- if (value === void 0 || value === null) return true;
5354
- if (Array.isArray(value)) return value.length === 0;
5355
- if (isPlainRecord(value)) return Object.keys(value).length === 0;
5356
- return false;
5357
- }
5358
- function collectPersistedLeafPaths(form) {
5359
- const out = [];
5360
- walk(form, []);
5361
- return out;
5362
- function walk(node, prefix) {
5363
- if (Array.isArray(node)) {
5364
- for (let i = 0; i < node.length; i++) {
5365
- walk(node[i], [...prefix, i]);
5366
- }
5367
- return;
5368
- }
5369
- if (isPlainRecord(node)) {
5370
- for (const key of Object.keys(node)) {
5371
- walk(node[key], [...prefix, key]);
5372
- }
5373
- return;
5374
- }
5375
- if (prefix.length === 0) return;
5376
- out.push(canonicalizePath(prefix).key);
5377
- }
5378
- }
5379
- function isDescendantPathKey(candidate, ancestor) {
5380
- if (candidate.length <= ancestor.length) return false;
5381
- if (!ancestor.endsWith("]")) return false;
5382
- const childPrefix = `${ancestor.slice(0, -1)},`;
5383
- return candidate.startsWith(childPrefix);
5384
- }
5385
-
5386
- const PROTOCOL_VERSION = 1;
5387
- const JOIN_COLLECTION_WINDOW_MS = 50;
5388
- const SNAPSHOT_TIMEOUT_MS = 200;
5389
- const MAX_LEADER_ATTEMPTS = 3;
5390
- const SNAPSHOT_RESPONSE_MIN_INTERVAL_MS = 500;
5391
- function isFileLikeValue(value) {
5392
- if (typeof File !== "undefined" && value instanceof File) return true;
5393
- if (typeof Blob !== "undefined" && value instanceof Blob) return true;
5394
- return false;
5395
- }
5396
- function isInboundShapeAcceptable(schema, path, value) {
5397
- if (isFileLikeValue(value)) return false;
5398
- let kind;
5399
- if (Array.isArray(value)) {
5400
- kind = "array";
5401
- } else if (value !== null && typeof value === "object" && isPlainRecord(value)) {
5402
- kind = "object";
5403
- } else {
5404
- kind = slimKindOf(value);
5405
- }
5406
- const accepted = schema.getSlimPrimitiveTypesAtPath(path);
5407
- if (!accepted.has(kind)) return false;
5408
- if (Array.isArray(value)) {
5409
- for (let i = 0; i < value.length; i++) {
5410
- if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
5411
- }
5412
- return true;
5413
- }
5414
- if (isPlainRecord(value)) {
5415
- for (const key of Object.keys(value)) {
5416
- if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
5417
- }
5418
- return true;
5419
- }
5420
- return true;
5421
- }
5422
- function diffBlankPaths(prev, curr) {
5423
- const added = [];
5424
- const removed = [];
5425
- const prevSet = new Set(prev);
5426
- for (const k of curr) if (!prevSet.has(k)) added.push(k);
5427
- for (const k of prev) if (!curr.has(k)) removed.push(k);
5428
- return { added, removed };
5429
- }
5430
- function snapshotForm(form) {
5431
- return structuralSnapshot(form);
5432
- }
5433
- function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
5434
- if (isFileLikeValue(value)) return void 0;
5435
- if (value === null || typeof value !== "object") return value;
5436
- if (Array.isArray(value)) {
5437
- return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
5438
- }
5439
- const proto = Object.getPrototypeOf(value);
5440
- if (proto !== Object.prototype && proto !== null) return value;
5441
- const out = {};
5442
- const src = value;
5443
- for (const key of Object.keys(src)) {
5444
- const childPath = [...pathSoFar, key];
5445
- if (isSensitivePath(childPath)) {
5446
- safeAssign(out, key, void 0);
5447
- continue;
5448
- }
5449
- safeAssign(out, key, stripSensitivePathsDeep(src[key], childPath, isSensitivePath));
5450
- }
5451
- return out;
5452
- }
5453
- function isStringArray(value) {
5454
- return Array.isArray(value) && value.every((item) => typeof item === "string");
5455
- }
5456
- function isPatchArray(value) {
5457
- return Array.isArray(value) && value.every(
5458
- (p) => p !== null && typeof p === "object" && Array.isArray(p.path)
5459
- );
5460
- }
5461
- function isValidSyncMessage(data) {
5462
- if (data === null || typeof data !== "object") return false;
5463
- const m = data;
5464
- if (m["v"] !== PROTOCOL_VERSION) return false;
5465
- if (typeof m["senderId"] !== "string") return false;
5466
- if (typeof m["kind"] !== "string") return false;
5467
- switch (m["kind"]) {
5468
- case "hello":
5469
- case "announce":
5470
- return true;
5471
- case "requestSnapshot":
5472
- return typeof m["targetId"] === "string";
5473
- case "snapshot":
5474
- return isStringArray(m["blankPaths"]) && "form" in m;
5475
- case "patches":
5476
- return isPatchArray(m["formPatches"]) && isStringArray(m["blankPathsAdded"]) && isStringArray(m["blankPathsRemoved"]);
5477
- default:
5478
- return false;
5479
- }
5480
- }
5481
- function generateSenderId() {
5482
- try {
5483
- return globalThis.crypto.randomUUID();
5484
- } catch {
5485
- return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
5486
- }
5487
- }
5488
- function createMultiTabSyncModule(state, channelName, options) {
5489
- if (typeof BroadcastChannel === "undefined") {
5490
- return {
5491
- dispose: () => void 0,
5492
- lifecycle: () => "established",
5493
- senderId: "",
5494
- channelName
5495
- };
5496
- }
5497
- let channel;
5498
- try {
5499
- channel = new BroadcastChannel(channelName);
5500
- } catch {
5501
- return {
5502
- dispose: () => void 0,
5503
- lifecycle: () => "established",
5504
- senderId: "",
5505
- channelName
5506
- };
5507
- }
5508
- const senderId = generateSenderId();
5509
- let lifecycle = "joining";
5510
- let disposed = false;
5511
- const peerIds = /* @__PURE__ */ new Set();
5512
- const lastSnapshotResponseAt = /* @__PURE__ */ new Map();
5513
- let joinCollectionTimer = null;
5514
- let snapshotTimeoutTimer = null;
5515
- let leaderAttempts = 0;
5516
- let prior = {
5517
- form: snapshotForm(state.form.value),
5518
- blankPathsSnapshot: [...state.blankPaths]
5519
- };
5520
- function safePost(msg) {
5521
- if (disposed) return;
5522
- try {
5523
- channel.postMessage(msg);
5524
- } catch {
5525
- }
5526
- }
5527
- function refreshPrior() {
5528
- prior = {
5529
- form: snapshotForm(state.form.value),
5530
- blankPathsSnapshot: [...state.blankPaths]
5531
- };
5532
- }
5533
- function isPathLocallySuppressed(path) {
5534
- if (options.isSensitivePath(path)) return true;
5535
- const { key } = canonicalizePath([...path]);
5536
- if (options.noSyncPaths.has(key)) return true;
5537
- return false;
5538
- }
5539
- function postPatches() {
5540
- if (lifecycle !== "established") return;
5541
- const next = snapshotForm(state.form.value);
5542
- const rawPatches = [];
5543
- diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
5544
- const safePatches = [];
5545
- for (const p of rawPatches) {
5546
- if (isPathLocallySuppressed(p.path)) continue;
5547
- if ("value" in p && isFileLikeValue(p.value)) continue;
5548
- safePatches.push(p);
5549
- }
5550
- const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
5551
- if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
5552
- prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
5553
- return;
5554
- }
5555
- safePost({
5556
- v: PROTOCOL_VERSION,
5557
- kind: "patches",
5558
- senderId,
5559
- formPatches: safePatches,
5560
- blankPathsAdded: added,
5561
- blankPathsRemoved: removed
5562
- });
5563
- prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
5564
- }
5565
- const unsubscribeChange = state.onFormChange((_next, meta) => {
5566
- if (disposed) return;
5567
- if (lifecycle !== "established") return;
5568
- if (meta?.crossTab === true) return;
5569
- if (meta?.hydration === true) {
5570
- refreshPrior();
5571
- return;
5572
- }
5573
- postPatches();
5574
- });
5575
- function applyIncomingForm(form, blankPaths) {
5576
- state.blankPaths.clear();
5577
- for (const k of blankPaths) state.blankPaths.add(k);
5578
- state.applyFormReplacement(form, { crossTab: true, persist: false });
5579
- refreshPrior();
5580
- }
5581
- function handlePatches(msg) {
5582
- if (lifecycle !== "established") return;
5583
- const safePatches = [];
5584
- for (const p of msg.formPatches) {
5585
- if (!Array.isArray(p.path)) continue;
5586
- if (isPathLocallySuppressed(p.path)) continue;
5587
- if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
5588
- continue;
5589
- }
5590
- safePatches.push(p);
5591
- }
5592
- const safeBlankAdded = [];
5593
- for (const k of msg.blankPathsAdded) {
5594
- const segs = canonicalizePath(k).segments;
5595
- if (isPathLocallySuppressed(segs)) continue;
5596
- safeBlankAdded.push(k);
5597
- }
5598
- const safeBlankRemoved = [];
5599
- for (const k of msg.blankPathsRemoved) {
5600
- const segs = canonicalizePath(k).segments;
5601
- if (isPathLocallySuppressed(segs)) continue;
5602
- safeBlankRemoved.push(k);
5603
- }
5604
- if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
5605
- return;
5606
- }
5607
- const candidate = applyPatchesForward(state.form.value, safePatches);
5608
- try {
5609
- options.validateForm(state.form.value);
5610
- try {
5611
- options.validateForm(candidate);
5612
- } catch {
5613
- return;
5614
- }
5615
- } catch {
5616
- }
5617
- const nextBlankPaths = new Set(state.blankPaths);
5618
- for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
5619
- for (const k of safeBlankAdded) nextBlankPaths.add(k);
5620
- applyIncomingForm(candidate, [...nextBlankPaths]);
5621
- }
5622
- function handleSnapshot(msg) {
5623
- if (lifecycle !== "joining") return;
5624
- if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
5625
- if (snapshotTimeoutTimer !== null) {
5626
- clearTimeout(snapshotTimeoutTimer);
5627
- snapshotTimeoutTimer = null;
5628
- }
5629
- if (joinCollectionTimer !== null) {
5630
- clearTimeout(joinCollectionTimer);
5631
- joinCollectionTimer = null;
5632
- }
5633
- applyIncomingForm(msg.form, msg.blankPaths);
5634
- lifecycle = "established";
5635
- peerIds.clear();
5636
- }
5637
- function respondToHello() {
5638
- safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
5639
- }
5640
- function respondToSnapshotRequest(requesterId) {
5641
- const now = Date.now();
5642
- const last = lastSnapshotResponseAt.get(requesterId);
5643
- if (last !== void 0 && now - last < SNAPSHOT_RESPONSE_MIN_INTERVAL_MS) return;
5644
- lastSnapshotResponseAt.set(requesterId, now);
5645
- const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
5646
- safePost({
5647
- v: PROTOCOL_VERSION,
5648
- kind: "snapshot",
5649
- senderId,
5650
- form: scrubbedForm,
5651
- blankPaths: [...state.blankPaths]
5652
- });
5653
- }
5654
- channel.onmessage = (event) => {
5655
- if (disposed) return;
5656
- try {
5657
- const data = event.data;
5658
- if (!isValidSyncMessage(data)) return;
5659
- const msg = data;
5660
- if (msg.senderId === senderId) return;
5661
- switch (msg.kind) {
5662
- case "hello":
5663
- if (lifecycle !== "established") return;
5664
- respondToHello();
5665
- break;
5666
- case "announce":
5667
- if (lifecycle === "joining") peerIds.add(msg.senderId);
5668
- break;
5669
- case "requestSnapshot":
5670
- if (lifecycle !== "established") return;
5671
- if (msg.targetId !== senderId) return;
5672
- respondToSnapshotRequest(msg.senderId);
5673
- break;
5674
- case "snapshot":
5675
- handleSnapshot(msg);
5676
- break;
5677
- case "patches":
5678
- handlePatches(msg);
5679
- break;
5680
- }
5681
- } catch {
5682
- }
5683
- };
5684
- function electLeaderAndRequest() {
5685
- if (disposed) return;
5686
- if (peerIds.size === 0) {
5687
- lifecycle = "established";
5688
- refreshPrior();
5689
- return;
5690
- }
5691
- const sorted = [...peerIds].sort();
5692
- const leaderId = sorted[0];
5693
- peerIds.delete(leaderId);
5694
- leaderAttempts++;
5695
- safePost({
5696
- v: PROTOCOL_VERSION,
5697
- kind: "requestSnapshot",
5698
- senderId,
5699
- targetId: leaderId
5700
- });
5701
- snapshotTimeoutTimer = setTimeout(() => {
5702
- snapshotTimeoutTimer = null;
5703
- if (disposed) return;
5704
- if (lifecycle === "established") return;
5705
- if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
5706
- lifecycle = "established";
5707
- refreshPrior();
5708
- return;
5709
- }
5710
- electLeaderAndRequest();
5711
- }, SNAPSHOT_TIMEOUT_MS);
5712
- }
5713
- function joinFlow() {
5714
- safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
5715
- joinCollectionTimer = setTimeout(() => {
5716
- joinCollectionTimer = null;
5717
- if (disposed) return;
5718
- if (lifecycle === "established") return;
5719
- electLeaderAndRequest();
5720
- }, JOIN_COLLECTION_WINDOW_MS);
5721
- }
5722
- joinFlow();
5723
- return {
5724
- dispose: () => {
5725
- if (disposed) return;
5726
- disposed = true;
5727
- if (joinCollectionTimer !== null) {
5728
- clearTimeout(joinCollectionTimer);
5729
- joinCollectionTimer = null;
5730
- }
5731
- if (snapshotTimeoutTimer !== null) {
5732
- clearTimeout(snapshotTimeoutTimer);
5733
- snapshotTimeoutTimer = null;
5734
- }
5735
- unsubscribeChange();
5736
- try {
5737
- channel.close();
5738
- } catch {
5739
- }
5740
- },
5741
- lifecycle: () => lifecycle,
5742
- senderId,
5743
- channelName
5744
- };
5745
- }
5746
- const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
5747
-
5748
5213
  const warned = /* @__PURE__ */ new Set();
5749
5214
  function warnOnceInsecureContext(feature) {
5750
5215
  if (!__DEV__) return;
@@ -5804,8 +5269,10 @@ function useAbstractForm(configuration, options) {
5804
5269
  }
5805
5270
  const existing = registry.forms.get(key);
5806
5271
  if (__DEV__ && existing !== void 0) {
5807
- warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
5808
- warnOnPersistDivergence(key, existing, configuration.persist);
5272
+ void import('../chunks/dev-key-collision-warnings.mjs').then((m) => {
5273
+ void m.warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
5274
+ m.warnOnPersistDivergence(key, existing, configuration.persist);
5275
+ });
5809
5276
  }
5810
5277
  const hadPendingHydration = registry.pendingHydration.has(key);
5811
5278
  const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
@@ -5846,10 +5313,33 @@ function useAbstractForm(configuration, options) {
5846
5313
  } else {
5847
5314
  const persistenceBase = resolveStorageKeyBase(resolvedPersist, state.formKey);
5848
5315
  void sweepNonConfiguredStandardStoresForOrphans(resolvedPersist.storage, persistenceBase);
5849
- const persistenceModule = wirePersistence(state, resolvedPersist);
5850
- state.modules.set(PERSISTENCE_MODULE_KEY, persistenceModule);
5851
- state.registerDrain(() => persistenceModule.awaitPendingWrites());
5852
- state.registerCleanup(() => persistenceModule.dispose());
5316
+ const adapterPromise = getStorageAdapter(resolvedPersist.storage);
5317
+ let persistDisposed = false;
5318
+ state.registerCleanup(() => {
5319
+ persistDisposed = true;
5320
+ });
5321
+ const ready = (async () => {
5322
+ try {
5323
+ const [{ wirePersistence }, fingerprintToken] = await Promise.all([
5324
+ import('../chunks/wire-persistence.mjs'),
5325
+ resolvePersistFingerprintToken(state)
5326
+ ]);
5327
+ if (persistDisposed) return void 0;
5328
+ const persistenceModule = wirePersistence(
5329
+ state,
5330
+ resolvedPersist,
5331
+ adapterPromise,
5332
+ fingerprintToken
5333
+ );
5334
+ state.registerDrain(() => persistenceModule.awaitPendingWrites());
5335
+ state.registerCleanup(() => persistenceModule.dispose());
5336
+ return persistenceModule;
5337
+ } catch {
5338
+ return void 0;
5339
+ }
5340
+ })();
5341
+ const persistenceHandle = { config: resolvedPersist, ready };
5342
+ state.modules.set(PERSISTENCE_MODULE_KEY, persistenceHandle);
5853
5343
  }
5854
5344
  } else {
5855
5345
  void sweepAllOrphansAcrossStandardStores(`${PERSISTENCE_KEY_PREFIX}${state.formKey}`);
@@ -5859,27 +5349,31 @@ function useAbstractForm(configuration, options) {
5859
5349
  const hasBroadcastChannel = typeof BroadcastChannel !== "undefined";
5860
5350
  const secureContext = isSecureContext();
5861
5351
  if (hasBroadcastChannel && secureContext) {
5862
- let channelName;
5863
- try {
5864
- channelName = `attaform:sync:${state.formKey}:${hashStableString(state.schema.fingerprint())}`;
5865
- } catch {
5866
- channelName = null;
5867
- }
5868
- if (channelName !== null) {
5869
- const syncModule = createMultiTabSyncModule(state, channelName, {
5870
- isSensitivePath: state.isSensitivePath,
5871
- noSyncPaths: state.noSyncPaths,
5872
- validateForm: (form) => {
5873
- const result = state.schema.validateAtPath(form, void 0, { sync: true });
5874
- if (result instanceof Promise) return;
5875
- if (!result.success) {
5876
- throw new Error("attaform multi-tab sync: post-apply schema validation failed");
5352
+ let formDisposed = false;
5353
+ state.registerCleanup(() => {
5354
+ formDisposed = true;
5355
+ });
5356
+ void (async () => {
5357
+ try {
5358
+ const [{ createMultiTabSyncModule, MULTI_TAB_SYNC_MODULE_KEY }, fingerprint] = await Promise.all([import('../chunks/multi-tab-sync.mjs'), state.schema.fingerprint()]);
5359
+ if (formDisposed) return;
5360
+ const channelName = `attaform:sync:${state.formKey}:${hashStableString(fingerprint)}`;
5361
+ const syncModule = createMultiTabSyncModule(state, channelName, {
5362
+ isSensitivePath: state.isSensitivePath,
5363
+ noSyncPaths: state.noSyncPaths,
5364
+ validateForm: (form) => {
5365
+ const result = state.schema.validateAtPath(form, void 0, { sync: true });
5366
+ if (result instanceof Promise) return;
5367
+ if (!result.success) {
5368
+ throw new Error("attaform multi-tab sync: post-apply schema validation failed");
5369
+ }
5877
5370
  }
5878
- }
5879
- });
5880
- state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
5881
- state.registerCleanup(() => syncModule.dispose());
5882
- }
5371
+ });
5372
+ state.modules.set(MULTI_TAB_SYNC_MODULE_KEY, syncModule);
5373
+ state.registerCleanup(() => syncModule.dispose());
5374
+ } catch {
5375
+ }
5376
+ })();
5883
5377
  } else if (hasBroadcastChannel && !secureContext) {
5884
5378
  warnOnceInsecureContext("multiTab");
5885
5379
  }
@@ -6040,55 +5534,17 @@ function resolveFormKey(key) {
6040
5534
  }
6041
5535
  return `${ANONYMOUS_FORM_KEY_PREFIX}${anonCounter++}`;
6042
5536
  }
6043
- function warnOnSchemaFingerprintMismatch(key, existing, incoming) {
6044
- let existingFp;
6045
- let incomingFp;
5537
+ async function resolvePersistFingerprintToken(state) {
6046
5538
  try {
6047
- existingFp = existing.fingerprint();
6048
- incomingFp = incoming.fingerprint();
6049
- } catch (error) {
6050
- console.error(
6051
- `[attaform] fingerprint() threw for key "${key}"; skipping mismatch check.`,
6052
- error
6053
- );
6054
- return;
6055
- }
6056
- if (existingFp === incomingFp) return;
6057
- console.warn(
6058
- `[attaform] useForm() calls with key "${key}" use different schemas; first wins, second is ignored. Use identical schemas or unique keys.
6059
- existing: ${existingFp}
6060
- incoming: ${incomingFp}`
6061
- );
6062
- }
6063
- function warnOnPersistDivergence(key, existing, incomingPersist) {
6064
- if (incomingPersist === void 0) return;
6065
- const wired = existing.modules.get(PERSISTENCE_MODULE_KEY);
6066
- const incomingNormalized = normalizePersistConfig(incomingPersist);
6067
- if (wired === void 0) {
6068
- console.warn(
6069
- `[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.`
6070
- );
6071
- return;
5539
+ return hashStableString(await state.schema.fingerprint());
5540
+ } catch (err) {
5541
+ if (__DEV__) {
5542
+ console.warn(
5543
+ `[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.`
5544
+ );
5545
+ }
5546
+ return "unfingerprinted";
6072
5547
  }
6073
- if (persistConfigsEquivalent(wired.wiredConfig, incomingNormalized)) return;
6074
- console.warn(
6075
- `[attaform] useForm({ key: "${key}" }) passed a persist config that differs from the first useForm({ key }) call's; first wins, this one is ignored.
6076
- wired: ${describePersist(wired.wiredConfig)}
6077
- incoming: ${describePersist(incomingNormalized)}`
6078
- );
6079
- }
6080
- function persistConfigsEquivalent(a, b) {
6081
- if (a.storage !== b.storage) return false;
6082
- if ((a.key ?? void 0) !== (b.key ?? void 0)) return false;
6083
- if ((a.debounceMs ?? void 0) !== (b.debounceMs ?? void 0)) return false;
6084
- return true;
6085
- }
6086
- function describePersist(config) {
6087
- const storage = typeof config.storage === "string" ? config.storage : "custom-adapter";
6088
- const parts = [`storage=${storage}`];
6089
- if (config.key !== void 0) parts.push(`key=${config.key}`);
6090
- if (config.debounceMs !== void 0) parts.push(`debounceMs=${config.debounceMs}`);
6091
- return `{ ${parts.join(", ")} }`;
6092
5548
  }
6093
5549
  const warnedAnonPersistKeys = /* @__PURE__ */ new Set();
6094
5550
  function enforceAnonPersistRule(formKey, ssr) {
@@ -6256,7 +5712,7 @@ function buildNoopWizardSchema(formKey) {
6256
5712
  formKey
6257
5713
  };
6258
5714
  return {
6259
- fingerprint: () => NOOP_FINGERPRINT,
5715
+ fingerprint: () => Promise.resolve(NOOP_FINGERPRINT),
6260
5716
  getDefaultValues: () => defaultsResponse,
6261
5717
  getDefaultAtPath: () => void 0,
6262
5718
  getEmptyValueAtPath: () => void 0,
@@ -6973,7 +6429,11 @@ function useWizard(options) {
6973
6429
  }
6974
6430
  for (const key of results.keys()) {
6975
6431
  const store = registry.forms.get(key);
6976
- if (store !== void 0) store.submissionAttempts.value += 1;
6432
+ if (store !== void 0) {
6433
+ store.submissionAttempts.value += 1;
6434
+ store.cancelFieldValidation();
6435
+ store.displayEngine.clear();
6436
+ }
6977
6437
  }
6978
6438
  submissionAttempts.value += 1;
6979
6439
  const errors = collectErrors(results);
@@ -7206,5 +6666,5 @@ function warnIfAmbientWizardProviderHadDuplicates() {
7206
6666
  }
7207
6667
  }
7208
6668
 
7209
- export { AttaformErrorCode as A, defaultDisplayState as a, defineCoercion as b, injectWizard as c, defaultCoercionRules as d, isPlainRecord as e, isUnset as f, getAtPath as g, humanize as h, injectForm as i, safeOwnRead as j, setAtPath as k, lazy as l, slimKindOf as m, normalizeNumericOption as n, useAbstractForm as o, useWizard as p, safeAssign as s, unset as u };
7210
- //# sourceMappingURL=attaform.tiWEVznj.mjs.map
6669
+ export { AttaformErrorCode as A, useAbstractForm as B, useWizard as C, DEFAULT_PERSISTENCE_DEBOUNCE_MS as D, PERSISTENCE_MODULE_KEY as P, DEFAULT_TIMINGS as a, applyPatchesForward as b, cleanupOrphanKeys as c, defaultCoercionRules as d, defaultDisplayState as e, defineCoercion as f, deleteAtPath as g, diffAndApply as h, getAtPath as i, humanize as j, injectForm as k, injectWizard as l, isPlainRecord as m, isUnset as n, lazy as o, makeDefaultDisplayState as p, mergeSparseHydration as q, normalizeNumericOption as r, normalizePersistConfig as s, resolveStorageKeyBase as t, safeAssign as u, safeOwnRead as v, setAtPath as w, slimKindOf as x, structuralSnapshot as y, unset as z };
6670
+ //# sourceMappingURL=attaform.DSqO6Db7.mjs.map