attaform 0.21.2 → 0.22.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 (79) hide show
  1. package/dist/chunks/dev-key-collision-warnings.cjs +1 -1
  2. package/dist/chunks/dev-key-collision-warnings.mjs +1 -1
  3. package/dist/chunks/devtools.cjs +1 -1
  4. package/dist/chunks/devtools.mjs +1 -1
  5. package/dist/chunks/fingerprint2.cjs +1 -1
  6. package/dist/chunks/fingerprint2.mjs +1 -1
  7. package/dist/chunks/indexeddb.cjs +1 -1
  8. package/dist/chunks/indexeddb.mjs +1 -1
  9. package/dist/chunks/local-storage.cjs +1 -1
  10. package/dist/chunks/local-storage.mjs +1 -1
  11. package/dist/chunks/multi-tab-sync.cjs +2 -2
  12. package/dist/chunks/multi-tab-sync.mjs +2 -2
  13. package/dist/chunks/session-storage.cjs +1 -1
  14. package/dist/chunks/session-storage.mjs +1 -1
  15. package/dist/chunks/wire-persistence.cjs +2 -2
  16. package/dist/chunks/wire-persistence.mjs +2 -2
  17. package/dist/index.cjs +3 -3
  18. package/dist/index.d.cts +4 -4
  19. package/dist/index.d.mts +4 -4
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.mjs +5 -5
  22. package/dist/nuxt.d.cts +1 -1
  23. package/dist/nuxt.d.mts +1 -1
  24. package/dist/nuxt.d.ts +1 -1
  25. package/dist/runtime/plugins/attaform.cjs +2 -2
  26. package/dist/runtime/plugins/attaform.mjs +2 -2
  27. package/dist/shared/{attaform.B5LNzqQh.cjs → attaform.01iKS_lz.cjs} +18 -5
  28. package/dist/shared/{attaform.DP-u7_tk.mjs.map → attaform.01iKS_lz.cjs.map} +1 -1
  29. package/dist/shared/{attaform.C41gjp-a.mjs → attaform.6xE0Lcfd.mjs} +2 -2
  30. package/dist/shared/{attaform.C41gjp-a.mjs.map → attaform.6xE0Lcfd.mjs.map} +1 -1
  31. package/dist/shared/{attaform.BBDIKtKY.cjs → attaform.AyujQoHp.cjs} +4 -4
  32. package/dist/shared/{attaform.BBDIKtKY.cjs.map → attaform.AyujQoHp.cjs.map} +1 -1
  33. package/dist/shared/{attaform.BGf_J22U.d.ts → attaform.BGwNZ9GV.d.cts} +10 -2
  34. package/dist/shared/{attaform.DP-u7_tk.mjs → attaform.BKFwekY2.mjs} +16 -6
  35. package/dist/shared/{attaform.B5LNzqQh.cjs.map → attaform.BKFwekY2.mjs.map} +1 -1
  36. package/dist/shared/{attaform.CcnF1AKJ.cjs → attaform.C-RtnCJM.cjs} +116 -47
  37. package/dist/shared/attaform.C-RtnCJM.cjs.map +1 -0
  38. package/dist/shared/{attaform.BkjJfMvJ.d.cts → attaform.CCCeEPwa.d.mts} +10 -2
  39. package/dist/shared/{attaform.BwLp9KM7.cjs → attaform.CRzpFCjV.cjs} +2 -2
  40. package/dist/shared/{attaform.BwLp9KM7.cjs.map → attaform.CRzpFCjV.cjs.map} +1 -1
  41. package/dist/shared/{attaform.D2ZuIOCf.cjs → attaform.CjMcwV7W.cjs} +557 -126
  42. package/dist/shared/attaform.CjMcwV7W.cjs.map +1 -0
  43. package/dist/shared/{attaform.CTheKoTc.mjs → attaform.CsB-iKbU.mjs} +557 -126
  44. package/dist/shared/attaform.CsB-iKbU.mjs.map +1 -0
  45. package/dist/shared/{attaform.BCcrLApm.d.mts → attaform.D4XYaasQ.d.ts} +10 -2
  46. package/dist/shared/{attaform.D6GYGshL.mjs → attaform.DCjgGir_.mjs} +3 -3
  47. package/dist/shared/{attaform.D6GYGshL.mjs.map → attaform.DCjgGir_.mjs.map} +1 -1
  48. package/dist/shared/{attaform.BVeLgfEh.mjs → attaform.DNuiFCXG.mjs} +4 -4
  49. package/dist/shared/{attaform.BVeLgfEh.mjs.map → attaform.DNuiFCXG.mjs.map} +1 -1
  50. package/dist/shared/{attaform.BYgioWLF.d.ts → attaform.DUMWQefY.d.ts} +1 -1
  51. package/dist/shared/{attaform.CrD73S4m.mjs → attaform.DgCfLqay.mjs} +116 -47
  52. package/dist/shared/attaform.DgCfLqay.mjs.map +1 -0
  53. package/dist/shared/{attaform.ory-3WhV.d.ts → attaform.DvUH4a3o.d.cts} +181 -6
  54. package/dist/shared/{attaform.ory-3WhV.d.cts → attaform.DvUH4a3o.d.mts} +181 -6
  55. package/dist/shared/{attaform.ory-3WhV.d.mts → attaform.DvUH4a3o.d.ts} +181 -6
  56. package/dist/shared/{attaform.BoY6RZUl.d.cts → attaform.FN0vaQAg.d.mts} +1 -1
  57. package/dist/shared/{attaform.BwrowMp2.cjs → attaform.Q3eAD2wD.cjs} +3 -3
  58. package/dist/shared/{attaform.BwrowMp2.cjs.map → attaform.Q3eAD2wD.cjs.map} +1 -1
  59. package/dist/shared/{attaform.CnEl--PF.d.mts → attaform.aekT7mMx.d.cts} +1 -1
  60. package/dist/zod-v3.cjs +2 -2
  61. package/dist/zod-v3.d.cts +3 -3
  62. package/dist/zod-v3.d.mts +3 -3
  63. package/dist/zod-v3.d.ts +3 -3
  64. package/dist/zod-v3.mjs +2 -2
  65. package/dist/zod-v4.cjs +2 -2
  66. package/dist/zod-v4.d.cts +4 -4
  67. package/dist/zod-v4.d.mts +4 -4
  68. package/dist/zod-v4.d.ts +4 -4
  69. package/dist/zod-v4.mjs +2 -2
  70. package/dist/zod.cjs +5 -5
  71. package/dist/zod.d.cts +5 -5
  72. package/dist/zod.d.mts +5 -5
  73. package/dist/zod.d.ts +5 -5
  74. package/dist/zod.mjs +5 -5
  75. package/package.json +2 -2
  76. package/dist/shared/attaform.CTheKoTc.mjs.map +0 -1
  77. package/dist/shared/attaform.CcnF1AKJ.cjs.map +0 -1
  78. package/dist/shared/attaform.CrD73S4m.mjs.map +0 -1
  79. package/dist/shared/attaform.D2ZuIOCf.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, toRaw, reactive, watch, markRaw, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope, nextTick } from 'vue';
2
- import { _ as __DEV__, a as canonicalizePath, s as segmentsForPathKey, l as isPathPrefix, F as FORM_ERRORS_PATH_KEY, S as SubmitErrorHandlerError, t as toError, A as AnonPersistError, q as INTERACTIVE_TAG_NAMES, r as getOrAssignElementId, e as ROOT_PATH_KEY, R as ROOT_PATH, w as allowSensitivePersist, x as FORM_ERRORS_PATH, y as coerceToPathKey, z as isSensitivePath, B as createPersistOptInRegistry, d as InvalidUseFormConfigError, C as ensureAttaformInstalled, u as useRegistry, E as kFormContext, G as kFormInstanceId, h as ReservedFormKeyError, H as createIsSensitivePath, J as REGISTER_OWNER_MARKER, V as V_REGISTER_MARKER, k as kAttaformWizardActiveStepResolver, K as kAttaformAncestorWizard } from './attaform.DP-u7_tk.mjs';
1
+ import { computed, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, readonly, toRaw, reactive, isRef, toValue, watch, markRaw, triggerRef, shallowRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope, nextTick } from 'vue';
2
+ import { q as pathsEqual, l as isPathPrefix, _ as __DEV__, a as canonicalizePath, s as segmentsForPathKey, F as FORM_ERRORS_PATH_KEY, r as keyForSegments, S as SubmitErrorHandlerError, t as toError, A as AnonPersistError, w as INTERACTIVE_TAG_NAMES, x as getOrAssignElementId, R as ROOT_PATH, y as allowSensitivePersist, z as FORM_ERRORS_PATH, e as ROOT_PATH_KEY, B as segmentsToDotted, C as coerceToPathKey, E as isSensitivePath, G as createPersistOptInRegistry, d as InvalidUseFormConfigError, H as ensureAttaformInstalled, u as useRegistry, J as kFormContext, K as kFormInstanceId, h as ReservedFormKeyError, L as createIsSensitivePath, M as REGISTER_OWNER_MARKER, V as V_REGISTER_MARKER, k as kAttaformWizardActiveStepResolver, N as kAttaformAncestorWizard } from './attaform.BKFwekY2.mjs';
3
3
 
4
4
  function safeAssign(target, key, value) {
5
5
  if (key === "__proto__") {
@@ -34,7 +34,7 @@ function descendStep(value, segment) {
34
34
  if (typeof value !== "object") return NOT_FOUND;
35
35
  if (Array.isArray(value)) {
36
36
  if (typeof segment !== "number") return NOT_FOUND;
37
- if (segment < 0 || segment >= value.length) return NOT_FOUND;
37
+ if (!(segment in value)) return NOT_FOUND;
38
38
  return value[segment];
39
39
  }
40
40
  const record = value;
@@ -69,7 +69,7 @@ function hasAtPath(root, path) {
69
69
  if (current === null || current === void 0) return false;
70
70
  if (typeof current !== "object") return false;
71
71
  if (Array.isArray(current)) {
72
- return typeof last === "number" && last >= 0 && last < current.length;
72
+ return typeof last === "number" && last in current;
73
73
  }
74
74
  const key = typeof last === "number" ? String(last) : last;
75
75
  if (isShadowedKey(key)) return safeOwnHas(current, key);
@@ -98,6 +98,31 @@ function setAtPathOffset(root, path, value, offset) {
98
98
  safeAssign(rec, head, setAtPathOffset(safeOwnRead(rec, head), path, value, nextOffset));
99
99
  return rec;
100
100
  }
101
+ const NO_IN_PLACE = { applied: false };
102
+ function tryInPlaceLeafWrite(root, path, value) {
103
+ if (path.length === 0) return NO_IN_PLACE;
104
+ let node = root;
105
+ for (let i = 0; i < path.length; i++) {
106
+ const seg = path[i];
107
+ if (Array.isArray(node)) {
108
+ if (typeof seg !== "number" || seg < 0 || seg >= node.length) return NO_IN_PLACE;
109
+ } else if (isPlainRecord(node)) {
110
+ if (typeof seg !== "string" || isShadowedKey(seg) || !(seg in node)) return NO_IN_PLACE;
111
+ } else {
112
+ return NO_IN_PLACE;
113
+ }
114
+ const container = node;
115
+ if (i < path.length - 1) {
116
+ node = container[seg];
117
+ continue;
118
+ }
119
+ const old = container[seg];
120
+ if (isPlainRecord(old) || Array.isArray(old)) return NO_IN_PLACE;
121
+ container[seg] = value;
122
+ return { applied: true, old };
123
+ }
124
+ return NO_IN_PLACE;
125
+ }
101
126
  function deleteAtPath(root, path) {
102
127
  return deleteAtPathOffset(root, path, 0);
103
128
  }
@@ -401,7 +426,7 @@ function diffObjectsLockstep(oldRec, newRec, prefix, visit) {
401
426
  diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
402
427
  }
403
428
  }
404
- function applyChangedKeys(target, source) {
429
+ function applyChangedKeys(target, source, arrayOpPath, currentPath) {
405
430
  if (!isDescendable(target) || !isDescendable(source)) return false;
406
431
  const targetIsArray = Array.isArray(target);
407
432
  const sourceIsArray = Array.isArray(source);
@@ -419,11 +444,27 @@ function applyChangedKeys(target, source) {
419
444
  if (targetIsArray) {
420
445
  const t = target;
421
446
  const s = source;
422
- if (t.length > s.length) t.length = s.length;
423
- for (const idx of changedFirstSegments) {
424
- if (typeof idx === "symbol") continue;
425
- const i = typeof idx === "number" ? idx : Number(idx);
426
- t[i] = s[i];
447
+ if (arrayOpPath === null || pathsEqual(currentPath, arrayOpPath)) {
448
+ if (t.length > s.length) t.length = s.length;
449
+ for (const idx of changedFirstSegments) {
450
+ if (typeof idx === "symbol") continue;
451
+ const i = typeof idx === "number" ? idx : Number(idx);
452
+ if (i >= s.length) continue;
453
+ t[i] = s[i];
454
+ }
455
+ } else {
456
+ for (const idx of changedFirstSegments) {
457
+ if (typeof idx === "symbol") continue;
458
+ const i = typeof idx === "number" ? idx : Number(idx);
459
+ if (i >= s.length) continue;
460
+ const childPath = appendSegment(currentPath, i);
461
+ const curEl = t[i];
462
+ const nextEl = s[i];
463
+ if (isPathPrefix(childPath, arrayOpPath) && isDescendable(curEl) && isDescendable(nextEl) && applyChangedKeys(curEl, nextEl, arrayOpPath, childPath)) {
464
+ continue;
465
+ }
466
+ t[i] = nextEl;
467
+ }
427
468
  }
428
469
  } else {
429
470
  const t = target;
@@ -435,7 +476,14 @@ function applyChangedKeys(target, source) {
435
476
  for (const k of changedFirstSegments) {
436
477
  if (typeof k === "symbol") continue;
437
478
  const key = String(k);
438
- safeAssign(t, key, safeOwnRead(s, key));
479
+ const nextVal = safeOwnRead(s, key);
480
+ if (arrayOpPath !== null) {
481
+ const curVal = safeOwnRead(t, key);
482
+ if (isDescendable(curVal) && isDescendable(nextVal) && applyChangedKeys(curVal, nextVal, arrayOpPath, appendSegment(currentPath, key))) {
483
+ continue;
484
+ }
485
+ }
486
+ safeAssign(t, key, nextVal);
439
487
  }
440
488
  }
441
489
  return true;
@@ -547,6 +595,50 @@ function resolveGetDisplayState(config) {
547
595
  return config ?? defaultDisplayState;
548
596
  }
549
597
 
598
+ const AttaformErrorCode = {
599
+ /** A required field is in the blank set — user hasn't supplied a value. */
600
+ NoValueSupplied: "atta:no-value-supplied",
601
+ /** The schema adapter's `validateAtPath` threw synchronously. */
602
+ AdapterThrew: "atta:adapter-threw",
603
+ /**
604
+ * User code inside a `z.preprocess`, `.refine`, or `.transform`
605
+ * threw (sync or async). The adapter caught the throw and surfaced
606
+ * it as a `ValidationError` at the field path so the form's normal
607
+ * error pipeline handles it instead of leaking as an unhandled
608
+ * rejection or routing through `submitError`.
609
+ */
610
+ ValidatorThrew: "atta:validator-threw",
611
+ /**
612
+ * A function-form `defaultValues` factory threw or its promise
613
+ * rejected. The runtime captures the raw error on `form.hydrateError`
614
+ * and ALSO surfaces a form-level `ValidationError` (path `[]`) so
615
+ * the standard error pipeline carries the signal. Critical for the
616
+ * SSR round-trip: `hydrateError` itself does not ride the wire
617
+ * payload, but `schemaErrors` does, so the client sees the failure
618
+ * after rehydration without an extra channel.
619
+ */
620
+ HydrationFailed: "atta:hydration-failed",
621
+ /** The supplied path didn't resolve to any node in the schema. */
622
+ PathNotFound: "atta:path-not-found",
623
+ /**
624
+ * A walked form's `activate()` (async `defaultValues` factory) threw
625
+ * during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
626
+ * `ValidationError` at the form-level path (`[]`) so the wizard's
627
+ * aggregate error pipeline can carry the failure alongside ordinary
628
+ * validation errors. The raw factory error remains on
629
+ * `form.hydrateError` for retry UX.
630
+ */
631
+ ActivationFailed: "atta:activation-failed"
632
+ };
633
+ function makeBlankRequiredError(segments, formKey) {
634
+ return {
635
+ message: "No value supplied",
636
+ path: [...segments],
637
+ formKey,
638
+ code: AttaformErrorCode.NoValueSupplied
639
+ };
640
+ }
641
+
550
642
  const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
551
643
  const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
552
644
  const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
@@ -660,7 +752,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
660
752
  const original = state.originals.get(key)?.value;
661
753
  const pristine = state.isPristineAtPath(segments);
662
754
  const schemaForKey = state.schemaErrors.get(key);
663
- const blankForKey = state.derivedBlankErrors.value.get(key);
755
+ const blankForKey = state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments) ? [makeBlankRequiredError(segments, state.formKey)] : void 0;
664
756
  const userForKey = state.userErrors.get(key);
665
757
  const errors = [];
666
758
  if (schemaForKey !== void 0) errors.push(...schemaForKey);
@@ -724,9 +816,32 @@ function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBa
724
816
  getDisplayState
725
817
  );
726
818
  }
819
+ function visitActiveLeafPaths(value, base, visit) {
820
+ if (Array.isArray(value)) {
821
+ for (let i = 0; i < value.length; i++) {
822
+ const child = value[i];
823
+ if (Array.isArray(child) || isPlainRecord(child)) {
824
+ visitActiveLeafPaths(child, [...base, i], visit);
825
+ } else {
826
+ visit([...base, i]);
827
+ }
828
+ }
829
+ return;
830
+ }
831
+ if (isPlainRecord(value)) {
832
+ for (const k of Object.keys(value)) {
833
+ const child = value[k];
834
+ if (Array.isArray(child) || isPlainRecord(child)) {
835
+ visitActiveLeafPaths(child, [...base, k], visit);
836
+ } else {
837
+ visit([...base, k]);
838
+ }
839
+ }
840
+ }
841
+ }
727
842
  function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
728
843
  const formValue = state.form.value;
729
- const value = state.getValueAtPath(segments);
844
+ const value = getAtPath(formValue, segments);
730
845
  const original = state.originals.get(key)?.value;
731
846
  let pristine = true;
732
847
  let blank = true;
@@ -745,12 +860,16 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
745
860
  let asyncPending = false;
746
861
  const submissionAttempts = state.submissionAttempts.value;
747
862
  const blurredLeafSegments = [];
748
- for (const [leafKey, entry] of state.originals) {
749
- if (!isPathPrefix(segments, entry.segments)) continue;
750
- if (segments.length === entry.segments.length) continue;
751
- if (!hasAtPath(formValue, entry.segments)) continue;
863
+ const descendantLeaves = [];
864
+ visitActiveLeafPaths(value, segments, (leafSegments) => {
865
+ const { key: leafKey } = keyForSegments(leafSegments);
866
+ const entry = state.originals.get(leafKey);
867
+ if (entry === void 0) return;
868
+ descendantLeaves.push({ key: leafKey, segments: entry.segments });
869
+ });
870
+ for (const { key: leafKey, segments: leafSeg } of descendantLeaves) {
752
871
  const leafRecord = state.fields.get(leafKey);
753
- if (!state.isPristineAtPathByKey(leafKey, entry.segments)) {
872
+ if (!state.isPristineAtPathByKey(leafKey, leafSeg)) {
754
873
  pristine = false;
755
874
  dirty = true;
756
875
  }
@@ -761,7 +880,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
761
880
  if (leafRecord?.interacted === true) interacted = true;
762
881
  if (leafRecord?.blurredAfterInteraction === true) {
763
882
  blurredAfterInteraction = true;
764
- blurredLeafSegments.push(entry.segments);
883
+ blurredLeafSegments.push(leafSeg);
765
884
  }
766
885
  if (leafRecord?.connected === true) connected = true;
767
886
  if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) {
@@ -778,7 +897,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
778
897
  if (leafGateOpen && since !== void 0 && (transformingSince === null || since < transformingSince))
779
898
  transformingSince = since;
780
899
  }
781
- if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
900
+ if (state.pathHasAsyncValidationByKey(leafKey, leafSeg)) asyncPending = true;
782
901
  const ts = leafRecord?.updatedAt;
783
902
  if (ts !== void 0 && ts !== null) {
784
903
  if (updatedAt === null || ts > updatedAt) updatedAt = ts;
@@ -1405,7 +1524,7 @@ function buildFieldArrayApi(state) {
1405
1524
  append(path, value) {
1406
1525
  const next = readArray(path);
1407
1526
  next.push(value);
1408
- return writeArray(path, next);
1527
+ return writeArray(path, next, { kind: "insert", index: next.length - 1 });
1409
1528
  },
1410
1529
  prepend(path, value) {
1411
1530
  const next = readArray(path);
@@ -1733,42 +1852,6 @@ function mergeObjectKeys(target, source, path, schema) {
1733
1852
  return out;
1734
1853
  }
1735
1854
 
1736
- const AttaformErrorCode = {
1737
- /** A required field is in the blank set — user hasn't supplied a value. */
1738
- NoValueSupplied: "atta:no-value-supplied",
1739
- /** The schema adapter's `validateAtPath` threw synchronously. */
1740
- AdapterThrew: "atta:adapter-threw",
1741
- /**
1742
- * User code inside a `z.preprocess`, `.refine`, or `.transform`
1743
- * threw (sync or async). The adapter caught the throw and surfaced
1744
- * it as a `ValidationError` at the field path so the form's normal
1745
- * error pipeline handles it instead of leaking as an unhandled
1746
- * rejection or routing through `submitError`.
1747
- */
1748
- ValidatorThrew: "atta:validator-threw",
1749
- /**
1750
- * A function-form `defaultValues` factory threw or its promise
1751
- * rejected. The runtime captures the raw error on `form.hydrateError`
1752
- * and ALSO surfaces a form-level `ValidationError` (path `[]`) so
1753
- * the standard error pipeline carries the signal. Critical for the
1754
- * SSR round-trip: `hydrateError` itself does not ride the wire
1755
- * payload, but `schemaErrors` does, so the client sees the failure
1756
- * after rehydration without an extra channel.
1757
- */
1758
- HydrationFailed: "atta:hydration-failed",
1759
- /** The supplied path didn't resolve to any node in the schema. */
1760
- PathNotFound: "atta:path-not-found",
1761
- /**
1762
- * A walked form's `activate()` (async `defaultValues` factory) threw
1763
- * during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
1764
- * `ValidationError` at the form-level path (`[]`) so the wizard's
1765
- * aggregate error pipeline can carry the failure alongside ordinary
1766
- * validation errors. The raw factory error remains on
1767
- * `form.hydrateError` for retry UX.
1768
- */
1769
- ActivationFailed: "atta:activation-failed"
1770
- };
1771
-
1772
1855
  const warnedNoScopeStores = __DEV__ ? /* @__PURE__ */ new WeakSet() : null;
1773
1856
  function buildProcessForm(state, formInstanceId, options = {}) {
1774
1857
  const invalidPolicy = options.onInvalidSubmit ?? "focus-first-error";
@@ -2794,19 +2877,116 @@ function buildFormApi(state, formInstanceId, options = {}) {
2794
2877
  }
2795
2878
  };
2796
2879
  const getFormMetaBase = () => {
2797
- const { base: rootBase } = buildContainerFieldStateBase(
2880
+ let rollup;
2881
+ const rootBase = () => rollup ?? (rollup = buildContainerFieldStateBase(
2798
2882
  state,
2799
2883
  ROOT_PATH,
2800
2884
  ROOT_PATH_KEY,
2801
2885
  formInstanceId
2802
- );
2886
+ ).base);
2803
2887
  return {
2804
- ...rootBase,
2888
+ // Rollup-derived (FieldStateBase) — the whole rollup builds once, on the
2889
+ // first access of any of these.
2890
+ get value() {
2891
+ return rootBase().value;
2892
+ },
2893
+ get original() {
2894
+ return rootBase().original;
2895
+ },
2896
+ get pristine() {
2897
+ return rootBase().pristine;
2898
+ },
2899
+ get dirty() {
2900
+ return rootBase().dirty;
2901
+ },
2902
+ get focused() {
2903
+ return rootBase().focused;
2904
+ },
2905
+ get blurred() {
2906
+ return rootBase().blurred;
2907
+ },
2908
+ get touched() {
2909
+ return rootBase().touched;
2910
+ },
2911
+ get interacted() {
2912
+ return rootBase().interacted;
2913
+ },
2914
+ get blurredAfterInteraction() {
2915
+ return rootBase().blurredAfterInteraction;
2916
+ },
2917
+ get connected() {
2918
+ return rootBase().connected;
2919
+ },
2920
+ get element() {
2921
+ return rootBase().element;
2922
+ },
2923
+ get elements() {
2924
+ return rootBase().elements;
2925
+ },
2926
+ get updatedAt() {
2927
+ return rootBase().updatedAt;
2928
+ },
2929
+ get errors() {
2930
+ return rootBase().errors;
2931
+ },
2932
+ get validating() {
2933
+ return rootBase().validating;
2934
+ },
2935
+ get valid() {
2936
+ return rootBase().valid;
2937
+ },
2938
+ get transforming() {
2939
+ return rootBase().transforming;
2940
+ },
2941
+ get busy() {
2942
+ return rootBase().busy;
2943
+ },
2944
+ get transformError() {
2945
+ return rootBase().transformError;
2946
+ },
2947
+ get path() {
2948
+ return rootBase().path;
2949
+ },
2950
+ get id() {
2951
+ return rootBase().id;
2952
+ },
2953
+ get aria() {
2954
+ return rootBase().aria;
2955
+ },
2956
+ get key() {
2957
+ return rootBase().key;
2958
+ },
2959
+ get blank() {
2960
+ return rootBase().blank;
2961
+ },
2962
+ get label() {
2963
+ return rootBase().label;
2964
+ },
2965
+ get description() {
2966
+ return rootBase().description;
2967
+ },
2968
+ get placeholder() {
2969
+ return rootBase().placeholder;
2970
+ },
2971
+ get meta() {
2972
+ return rootBase().meta;
2973
+ },
2974
+ get errorCount() {
2975
+ return rootBase().errors.length;
2976
+ },
2977
+ // Form-level scalars — EAGER reads, tracked on every field-state eval.
2978
+ // They are O(1) refs that never change on a keystroke, so tracking them
2979
+ // per field costs nothing on the hot path. Kept eager (NOT lazy like the
2980
+ // rollup) because behaviors beyond the predicate's own output depend on
2981
+ // every field re-evaluating when they flip — most notably, the display
2982
+ // engine is cleared on submit (revealing held spinners), and that
2983
+ // imperative reset only becomes visible if `submitting` is a tracked dep
2984
+ // of each field. Matches the pre-bust dependency set for these scalars
2985
+ // exactly.
2805
2986
  submitting: state.submitting.value,
2806
2987
  submissionAttempts: state.submissionAttempts.value,
2807
2988
  departAttempts: state.departAttempts.value,
2808
2989
  submitError: state.submitError.value,
2809
- errorCount: rootBase.errors.length,
2810
2990
  submitted: state.submitted.value,
2811
2991
  instanceId: formInstanceId
2812
2992
  };
@@ -2841,14 +3021,19 @@ function buildFormApi(state, formInstanceId, options = {}) {
2841
3021
  const segments = canonicalizePath(pathInput).segments;
2842
3022
  return computed(() => getAtPath(state.form.value, segments));
2843
3023
  }
2844
- function setValueImpl(pathOrValue, maybeValue) {
2845
- if (arguments.length === 1) {
3024
+ function setValueImpl(pathOrValue, maybeValue, maybeOptions) {
3025
+ const argc = arguments.length;
3026
+ const isPathForm = argc >= 2 && (typeof pathOrValue === "string" || Array.isArray(pathOrValue));
3027
+ const options2 = isPathForm ? maybeOptions : argc >= 2 ? maybeValue : void 0;
3028
+ const silent = options2?.silent === true;
3029
+ const writeMeta = (extra) => withInstanceMeta(silent ? { ...extra, silent: true } : extra);
3030
+ if (!isPathForm) {
2846
3031
  const next = typeof pathOrValue === "function" ? pathOrValue(structuralSnapshot(state.form.value)) : pathOrValue;
2847
3032
  const walked2 = walkUnsetSentinels(
2848
3033
  next,
2849
3034
  state.schema
2850
3035
  );
2851
- const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
3036
+ const ok2 = state.setValueAtPath([], walked2.cleanedValues, writeMeta());
2852
3037
  if (!ok2) return false;
2853
3038
  reMarkBlanksAfterSubstitution(walked2.paths);
2854
3039
  return true;
@@ -2862,7 +3047,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2862
3047
  if (parentDU?.discriminatorKey === last) {
2863
3048
  const slimDefault = state.schema.getEmptyValueAtPath(segments);
2864
3049
  const blank = blankForKind(slimDefault);
2865
- return state.setValueAtPath(segments, blank, withInstanceMeta({ blank: true }));
3050
+ return state.setValueAtPath(segments, blank, writeMeta({ blank: true }));
2866
3051
  }
2867
3052
  }
2868
3053
  const blankPaths = [];
@@ -2873,9 +3058,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
2873
3058
  );
2874
3059
  const segmentsKey = canonicalizePath(segments).key;
2875
3060
  if (blankPaths.length === 1 && blankPaths[0] === segmentsKey) {
2876
- return state.setValueAtPath(segments, expanded, withInstanceMeta({ blank: true }));
3061
+ return state.setValueAtPath(segments, expanded, writeMeta({ blank: true }));
2877
3062
  }
2878
- const ok2 = state.setValueAtPath(segments, expanded, withInstanceMeta());
3063
+ const ok2 = state.setValueAtPath(segments, expanded, writeMeta());
2879
3064
  if (!ok2) return false;
2880
3065
  for (const pathKey of blankPaths) {
2881
3066
  const blankSegments = segmentsForPathKey(pathKey);
@@ -2883,7 +3068,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2883
3068
  state.setValueAtPath(
2884
3069
  blankSegments,
2885
3070
  state.getValueAtPath(blankSegments),
2886
- withInstanceMeta({ blank: true })
3071
+ writeMeta({ blank: true })
2887
3072
  );
2888
3073
  }
2889
3074
  return true;
@@ -2903,7 +3088,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2903
3088
  segments,
2904
3089
  state.schema
2905
3090
  );
2906
- const ok = state.setValueAtPath(segments, walked.cleanedValues, withInstanceMeta());
3091
+ const ok = state.setValueAtPath(segments, walked.cleanedValues, writeMeta());
2907
3092
  if (!ok) return false;
2908
3093
  reMarkBlanksAfterSubstitution(walked.paths);
2909
3094
  return true;
@@ -3274,7 +3459,19 @@ function buildFormApi(state, formInstanceId, options = {}) {
3274
3459
  }
3275
3460
  return Object.freeze(out);
3276
3461
  }
3277
- return {
3462
+ const formHandle = {
3463
+ current: void 0
3464
+ };
3465
+ function onChangeImpl(a, b, c) {
3466
+ const sourced = typeof b === "function";
3467
+ const source = sourced ? a : void 0;
3468
+ const handler = sourced ? b : a;
3469
+ const options2 = sourced ? c : b;
3470
+ const stop = state.registerOnChange(source, handler, options2, () => formHandle.current);
3471
+ if (getCurrentScope() !== void 0) onScopeDispose(stop);
3472
+ return stop;
3473
+ }
3474
+ const api = {
3278
3475
  handleSubmit: gated(handleSubmit),
3279
3476
  // Callable readonly Proxies (`values`, `fields`, `errors`) and the
3280
3477
  // reactive containers (`meta`, `history`, `blankPaths`) are exposed
@@ -3361,8 +3558,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
3361
3558
  get blankPaths() {
3362
3559
  void state.activate();
3363
3560
  return blankPathsView;
3364
- }
3561
+ },
3562
+ onChange: onChangeImpl
3365
3563
  };
3564
+ formHandle.current = api;
3565
+ return api;
3366
3566
  }
3367
3567
 
3368
3568
  const IDLE = Object.freeze({ display: "idle" });
@@ -3789,6 +3989,7 @@ function createArrayBookkeeping(deps) {
3789
3989
  originals,
3790
3990
  blankPaths,
3791
3991
  originalBlankPaths,
3992
+ authoredPaths,
3792
3993
  fieldValidationCounts,
3793
3994
  fieldValidatingSince,
3794
3995
  fieldValidationState,
@@ -3816,6 +4017,7 @@ function createArrayBookkeeping(deps) {
3816
4017
  }));
3817
4018
  migrateSetSubtree(blankPaths, arrayPath, remap);
3818
4019
  migrateSetSubtree(originalBlankPaths, arrayPath, remap);
4020
+ migrateSetSubtree(authoredPaths, arrayPath, remap);
3819
4021
  migrateMapSubtree(fieldValidatingSince, arrayPath, remap, (since) => since);
3820
4022
  migrateMapSubtree(fieldValidationCounts, arrayPath, remap, (count) => count);
3821
4023
  arrayIdentity.applyRemap(arrayPath, remap);
@@ -3859,7 +4061,7 @@ function createArrayBookkeeping(deps) {
3859
4061
  activeValidations.value = Math.max(0, activeValidations.value - 1);
3860
4062
  decFieldValidation(key);
3861
4063
  }
3862
- entry.controller.abort();
4064
+ entry.aborted = true;
3863
4065
  fieldValidationState.delete(key);
3864
4066
  }
3865
4067
  }
@@ -3871,6 +4073,170 @@ function createArrayBookkeeping(deps) {
3871
4073
  };
3872
4074
  }
3873
4075
 
4076
+ const NOOP_STOP = () => {
4077
+ };
4078
+ function isThenable(value) {
4079
+ return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
4080
+ }
4081
+ function isSuppressed(meta) {
4082
+ return meta?.hydration === true || meta?.crossTab === true || meta?.silent === true;
4083
+ }
4084
+ function canonicalizeSourceList(raw) {
4085
+ const list = typeof raw === "string" ? [raw] : raw;
4086
+ const out = [];
4087
+ const seen = /* @__PURE__ */ new Set();
4088
+ for (const entry of list) {
4089
+ const { segments, key } = canonicalizePath(entry);
4090
+ if (seen.has(key)) continue;
4091
+ seen.add(key);
4092
+ out.push({ segments, key, dotted: segmentsToDotted(segments) });
4093
+ }
4094
+ return out;
4095
+ }
4096
+ function makeResolver(source) {
4097
+ if (source === void 0) {
4098
+ const root = [{ segments: ROOT_PATH, key: ROOT_PATH_KEY, dotted: "" }];
4099
+ return () => root;
4100
+ }
4101
+ if (typeof source !== "function" && !isRef(source)) {
4102
+ const fixed = canonicalizeSourceList(source);
4103
+ return () => fixed;
4104
+ }
4105
+ return () => canonicalizeSourceList(toValue(source));
4106
+ }
4107
+ function createOnChangeRegistry(deps) {
4108
+ const handlers = /* @__PURE__ */ new Set();
4109
+ function isCurrent(reg, key, token) {
4110
+ return reg.runs.get(key)?.token === token;
4111
+ }
4112
+ function routeError(reg, error, source, changed, value, previous, attempt, token) {
4113
+ if (reg.onError === void 0) {
4114
+ if (__DEV__) {
4115
+ console.error(
4116
+ `[attaform] onChange handler threw for path '${source.dotted}' \u2014 error swallowed. Pass { onError } to handle it. Original error:`,
4117
+ error
4118
+ );
4119
+ }
4120
+ return;
4121
+ }
4122
+ const retry = () => {
4123
+ if (!isCurrent(reg, source.key, token)) return;
4124
+ runHandler(reg, source, changed, value, previous, attempt + 1);
4125
+ };
4126
+ const errCtx = {
4127
+ path: source.dotted,
4128
+ value,
4129
+ attempt,
4130
+ retry,
4131
+ form: reg.getForm()
4132
+ };
4133
+ try {
4134
+ reg.onError(error, errCtx);
4135
+ } catch (err) {
4136
+ if (__DEV__) console.error("[attaform] onChange onError threw:", err);
4137
+ }
4138
+ }
4139
+ function runHandler(reg, source, changed, value, previous, attempt) {
4140
+ const prior = reg.runs.get(source.key);
4141
+ if (prior) prior.controller.abort();
4142
+ const controller = new AbortController();
4143
+ const token = ++reg.seq;
4144
+ reg.runs.set(source.key, { token, controller });
4145
+ const ctx = {
4146
+ path: source.dotted,
4147
+ previous,
4148
+ signal: controller.signal,
4149
+ attempt,
4150
+ form: reg.getForm(),
4151
+ changed
4152
+ };
4153
+ let result;
4154
+ try {
4155
+ result = reg.handler(value, ctx);
4156
+ } catch (error) {
4157
+ routeError(reg, error, source, changed, value, previous, attempt, token);
4158
+ return;
4159
+ }
4160
+ if (isThenable(result)) {
4161
+ result.then(void 0, (error) => {
4162
+ if (isCurrent(reg, source.key, token)) {
4163
+ routeError(reg, error, source, changed, value, previous, attempt, token);
4164
+ }
4165
+ });
4166
+ }
4167
+ }
4168
+ function dispatch(patches, meta) {
4169
+ if (handlers.size === 0 || isSuppressed(meta)) return;
4170
+ for (const reg of handlers) {
4171
+ let sources;
4172
+ try {
4173
+ sources = reg.resolve();
4174
+ } catch (error) {
4175
+ if (__DEV__) console.error("[attaform] onChange source getter threw:", error);
4176
+ continue;
4177
+ }
4178
+ for (const source of sources) {
4179
+ let changed;
4180
+ for (const patch of patches) {
4181
+ if (isPathPrefix(source.segments, patch.path) || isPathPrefix(patch.path, source.segments)) {
4182
+ (changed ?? (changed = [])).push(segmentsToDotted(patch.path));
4183
+ }
4184
+ }
4185
+ if (changed === void 0) continue;
4186
+ const value = deps.getValueAtPath(source.segments);
4187
+ const previous = reg.previous.has(source.key) ? reg.previous.get(source.key) : value;
4188
+ reg.previous.set(source.key, value);
4189
+ runHandler(reg, source, changed, value, previous, 0);
4190
+ }
4191
+ }
4192
+ }
4193
+ function register(source, handler, options, getForm) {
4194
+ if (deps.ssr) return NOOP_STOP;
4195
+ const reg = {
4196
+ handler,
4197
+ onError: options?.onError,
4198
+ getForm,
4199
+ resolve: makeResolver(source),
4200
+ previous: /* @__PURE__ */ new Map(),
4201
+ runs: /* @__PURE__ */ new Map(),
4202
+ seq: 0
4203
+ };
4204
+ try {
4205
+ for (const { key, segments } of reg.resolve()) {
4206
+ reg.previous.set(key, deps.getValueAtPath(segments));
4207
+ }
4208
+ } catch (error) {
4209
+ if (__DEV__) console.error("[attaform] onChange source getter threw at registration:", error);
4210
+ }
4211
+ handlers.add(reg);
4212
+ let stopped = false;
4213
+ return () => {
4214
+ if (stopped) return;
4215
+ stopped = true;
4216
+ handlers.delete(reg);
4217
+ for (const run of reg.runs.values()) run.controller.abort();
4218
+ reg.runs.clear();
4219
+ reg.previous.clear();
4220
+ };
4221
+ }
4222
+ function dispose() {
4223
+ for (const reg of handlers) {
4224
+ for (const run of reg.runs.values()) run.controller.abort();
4225
+ reg.runs.clear();
4226
+ reg.previous.clear();
4227
+ }
4228
+ handlers.clear();
4229
+ }
4230
+ return {
4231
+ get active() {
4232
+ return handlers.size > 0;
4233
+ },
4234
+ register,
4235
+ dispatch,
4236
+ dispose
4237
+ };
4238
+ }
4239
+
3874
4240
  function isHydratedFieldRecord(value) {
3875
4241
  if (typeof value !== "object" || value === null) return false;
3876
4242
  const r = value;
@@ -3955,6 +4321,17 @@ function walkAuthoredFromConstraints(value, prefix, out) {
3955
4321
  }
3956
4322
  }
3957
4323
  }
4324
+ function freshElementIndices(op) {
4325
+ switch (op.kind) {
4326
+ case "insert":
4327
+ case "replace-at":
4328
+ return [op.index];
4329
+ case "remove":
4330
+ case "swap":
4331
+ case "move":
4332
+ return [];
4333
+ }
4334
+ }
3958
4335
  function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out) {
3959
4336
  if (isPlainRecord(withDefaults) && isPlainRecord(withoutDefaults)) {
3960
4337
  const left = withDefaults;
@@ -3993,6 +4370,7 @@ function createFormStore(options) {
3993
4370
  const formChangeListeners = /* @__PURE__ */ new Set();
3994
4371
  const submitSuccessListeners = /* @__PURE__ */ new Set();
3995
4372
  const resetListeners = /* @__PURE__ */ new Set();
4373
+ const onChangeRegistry = createOnChangeRegistry({ getValueAtPath, ssr });
3996
4374
  const persistOptIns = createPersistOptInRegistry();
3997
4375
  const noSyncPaths = /* @__PURE__ */ new Set();
3998
4376
  const noSyncPathCounts = /* @__PURE__ */ new Map();
@@ -4031,11 +4409,8 @@ function createFormStore(options) {
4031
4409
  if (constraints !== void 0) {
4032
4410
  walkAuthoredFromConstraints(constraints, [], authoredPaths);
4033
4411
  }
4034
- const slimResponse = schema.getDefaultValues({
4035
- useDefaultSchemaValues: false,
4036
- strict
4037
- });
4038
- walkAuthoredFromSchemaDiff(schemaWithDefaultsData, slimResponse.data, [], authoredPaths);
4412
+ const slimBaseline = schema.getEmptyValueAtPath([]);
4413
+ walkAuthoredFromSchemaDiff(schemaWithDefaultsData, slimBaseline, [], authoredPaths);
4039
4414
  }
4040
4415
  rebuildAuthoredPaths(defaultValues, schemaInitialData);
4041
4416
  function filterAuthoredErrors(errors) {
@@ -4054,7 +4429,7 @@ function createFormStore(options) {
4054
4429
  });
4055
4430
  const form = ref(stubbedInitialData);
4056
4431
  const arrayIdentity = createArrayIdentity((arraySegs) => {
4057
- const v = getAtPath(form.value, arraySegs);
4432
+ const v = getAtPath(toRaw(form.value), arraySegs);
4058
4433
  return Array.isArray(v) ? v.length : 0;
4059
4434
  });
4060
4435
  function arrayElementKey(path) {
@@ -4097,14 +4472,7 @@ function createFormStore(options) {
4097
4472
  const segments = segmentsForPathKey(pathKey);
4098
4473
  if (segments === null) continue;
4099
4474
  if (!schema.isRequiredAtPath(segments)) continue;
4100
- result.set(pathKey, [
4101
- {
4102
- message: "No value supplied",
4103
- path: [...segments],
4104
- formKey,
4105
- code: AttaformErrorCode.NoValueSupplied
4106
- }
4107
- ]);
4475
+ result.set(pathKey, [makeBlankRequiredError(segments, formKey)]);
4108
4476
  }
4109
4477
  return result;
4110
4478
  });
@@ -4344,6 +4712,7 @@ function createFormStore(options) {
4344
4712
  originals,
4345
4713
  blankPaths,
4346
4714
  originalBlankPaths,
4715
+ authoredPaths,
4347
4716
  fieldValidationCounts,
4348
4717
  fieldValidatingSince,
4349
4718
  fieldValidationState,
@@ -4353,17 +4722,8 @@ function createFormStore(options) {
4353
4722
  touchFieldRecord,
4354
4723
  decFieldValidation
4355
4724
  });
4356
- function applyFormReplacement(next, meta) {
4357
- const prev = form.value;
4358
- if (Object.is(prev, next)) return;
4725
+ function commitWritePatches(patches, meta) {
4359
4726
  const now = (/* @__PURE__ */ new Date()).toISOString();
4360
- const patches = [];
4361
- diffAndApply(prev, next, [], (patch) => {
4362
- patches.push(patch);
4363
- });
4364
- if (!applyChangedKeys(prev, next)) {
4365
- form.value = next;
4366
- }
4367
4727
  for (const patch of patches) {
4368
4728
  const { key } = canonicalizePath(patch.path);
4369
4729
  if (patch.kind === "added" && !originals.has(key)) {
@@ -4378,10 +4738,63 @@ function createFormStore(options) {
4378
4738
  console.error("[attaform] onFormChange threw:", err);
4379
4739
  }
4380
4740
  }
4741
+ if (onChangeRegistry.active) onChangeRegistry.dispatch(patches, meta);
4742
+ }
4743
+ function applyFormReplacementWithPath(next, meta, arrayOpPath) {
4744
+ const prev = form.value;
4745
+ if (Object.is(prev, next)) return;
4746
+ const patches = [];
4747
+ diffAndApply(prev, next, [], (patch) => {
4748
+ patches.push(patch);
4749
+ });
4750
+ if (!applyChangedKeys(prev, next, arrayOpPath, [])) {
4751
+ form.value = next;
4752
+ } else if (patches.some(
4753
+ (p) => p.path.length > 0 && typeof p.path[0] === "string" && isShadowedKey(p.path[0])
4754
+ )) {
4755
+ triggerRef(form);
4756
+ }
4757
+ commitWritePatches(patches, meta);
4758
+ }
4759
+ function applyFormReplacement(next, meta) {
4760
+ applyFormReplacementWithPath(next, meta, null);
4761
+ }
4762
+ function applyTargetedWrite(path, completedValue, meta) {
4763
+ const result = tryInPlaceLeafWrite(form.value, path, completedValue);
4764
+ if (!result.applied) {
4765
+ applyFormReplacementWithPath(
4766
+ setAtPathWithSchemaFill(form.value, schema, path, completedValue),
4767
+ meta,
4768
+ meta?.arrayOp !== void 0 ? path : null
4769
+ );
4770
+ return;
4771
+ }
4772
+ const patches = [];
4773
+ diffAndApply(result.old, completedValue, path, (patch) => {
4774
+ patches.push(patch);
4775
+ });
4776
+ commitWritePatches(patches, meta);
4381
4777
  }
4382
4778
  function setValueAtPath(path, value, meta) {
4383
- value = stripSymbolsDeep(value);
4384
- if (!isSlimPrimitiveValid(schema, form, path, value)) {
4779
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4780
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4781
+ value[idx] = stripSymbolsDeep(value[idx]);
4782
+ }
4783
+ } else {
4784
+ value = stripSymbolsDeep(value);
4785
+ }
4786
+ let slimOk = true;
4787
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4788
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4789
+ if (!isSlimPrimitiveValid(schema, form, [...path, idx], value[idx])) {
4790
+ slimOk = false;
4791
+ break;
4792
+ }
4793
+ }
4794
+ } else {
4795
+ slimOk = isSlimPrimitiveValid(schema, form, path, value);
4796
+ }
4797
+ if (!slimOk) {
4385
4798
  return false;
4386
4799
  }
4387
4800
  if (path.length >= 2) {
@@ -4474,22 +4887,37 @@ function createFormStore(options) {
4474
4887
  }
4475
4888
  }
4476
4889
  }
4890
+ const currentValue = getAtPath(form.value, path);
4477
4891
  const pathKey = canonicalizePath(path).key;
4478
4892
  if (meta?.blank === true) {
4479
4893
  blankPaths.add(pathKey);
4480
4894
  } else {
4481
4895
  if (blankPaths.has(pathKey)) blankPaths.delete(pathKey);
4482
- if (meta?.arrayOp === void 0) {
4896
+ if (meta?.arrayOp === void 0 && (isPlainRecord(currentValue) || Array.isArray(currentValue))) {
4483
4897
  for (const existingKey of [...blankPaths]) {
4484
4898
  if (isPathKeyUnder(existingKey, path)) blankPaths.delete(existingKey);
4485
4899
  }
4486
4900
  }
4487
4901
  }
4488
4902
  const wasAuthoredBefore = authoredPaths.has(pathKey);
4489
- walkAuthoredFromConstraints(value, path, authoredPaths);
4903
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4904
+ if (path.length > 0) authoredPaths.add(pathKey);
4905
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4906
+ walkAuthoredFromConstraints(value[idx], [...path, idx], authoredPaths);
4907
+ }
4908
+ } else {
4909
+ walkAuthoredFromConstraints(value, path, authoredPaths);
4910
+ }
4490
4911
  const newlyAuthored = !wasAuthoredBefore && authoredPaths.has(pathKey);
4491
- const completedValue = mergeStructural(schema, path, value);
4492
- const currentValue = getAtPath(form.value, path);
4912
+ let completedValue;
4913
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4914
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4915
+ value[idx] = mergeStructural(schema, [...path, idx], value[idx]);
4916
+ }
4917
+ completedValue = value;
4918
+ } else {
4919
+ completedValue = mergeStructural(schema, path, value);
4920
+ }
4493
4921
  if (Object.is(currentValue, completedValue)) {
4494
4922
  if (newlyAuthored && schema.isPreprocessOrCoerceLeaf(path)) {
4495
4923
  const modeForAuthoringTransition = meta?.instance?.validateOn ?? fieldValidationMode;
@@ -4503,8 +4931,7 @@ function createFormStore(options) {
4503
4931
  return true;
4504
4932
  }
4505
4933
  const oldArrayLength = Array.isArray(currentValue) ? currentValue.length : 0;
4506
- const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
4507
- applyFormReplacement(nextForm, meta);
4934
+ applyTargetedWrite(path, completedValue, meta);
4508
4935
  if (meta?.arrayOp !== void 0) {
4509
4936
  const remap = remapForOp(meta.arrayOp, oldArrayLength);
4510
4937
  arrayBookkeeping.migrateElementState(path, remap);
@@ -4587,7 +5014,7 @@ function createFormStore(options) {
4587
5014
  const prevValidation = fieldValidationState.get(parentKey2);
4588
5015
  if (prevValidation !== void 0) {
4589
5016
  if (prevValidation.timer !== null) clearTimeout(prevValidation.timer);
4590
- prevValidation.controller.abort();
5017
+ prevValidation.aborted = true;
4591
5018
  fieldValidationState.delete(parentKey2);
4592
5019
  }
4593
5020
  appliedSync = true;
@@ -4611,15 +5038,19 @@ function createFormStore(options) {
4611
5038
  const prev = fieldValidationState.get(key);
4612
5039
  if (prev !== void 0) {
4613
5040
  if (prev.timer !== null) clearTimeout(prev.timer);
4614
- prev.controller.abort();
5041
+ prev.aborted = true;
4615
5042
  }
4616
- const controller = new AbortController();
4617
- const fresh = { controller, timer: null, settled: false, released: false };
5043
+ const fresh = {
5044
+ aborted: false,
5045
+ timer: null,
5046
+ settled: false,
5047
+ released: false
5048
+ };
4618
5049
  fieldValidationState.set(key, fresh);
4619
5050
  const myEpoch = ++scheduleEpoch;
4620
5051
  const run = () => {
4621
5052
  fresh.timer = null;
4622
- if (controller.signal.aborted) return;
5053
+ if (fresh.aborted) return;
4623
5054
  let activeIncremented = false;
4624
5055
  try {
4625
5056
  activeValidations.value += 1;
@@ -4636,7 +5067,7 @@ function createFormStore(options) {
4636
5067
  const dataAtScope = subtreeScope ? getAtPath(form.value, path) : form.value;
4637
5068
  const scopeKey = subtreeScope ? canonicalizePath(path).key : ROOT_PATH_KEY;
4638
5069
  void Promise.resolve().then(() => schema.validateAtPath(dataAtScope, scopePath)).then((response) => {
4639
- if (controller.signal.aborted) return;
5070
+ if (fresh.aborted) return;
4640
5071
  if (myEpoch <= lastCommittedEpoch) return;
4641
5072
  lastCommittedEpoch = myEpoch;
4642
5073
  if (effectiveMode === "blur") {
@@ -4673,7 +5104,7 @@ function createFormStore(options) {
4673
5104
  activeValidations.value = Math.max(0, activeValidations.value - 1);
4674
5105
  decFieldValidation(pkey);
4675
5106
  }
4676
- entry.controller.abort();
5107
+ entry.aborted = true;
4677
5108
  }
4678
5109
  fieldValidationState.clear();
4679
5110
  }
@@ -4689,7 +5120,7 @@ function createFormStore(options) {
4689
5120
  decFieldValidation(key);
4690
5121
  entry.released = true;
4691
5122
  }
4692
- entry.controller.abort();
5123
+ entry.aborted = true;
4693
5124
  fieldValidationState.delete(key);
4694
5125
  }
4695
5126
  }
@@ -4748,6 +5179,7 @@ function createFormStore(options) {
4748
5179
  formChangeListeners.clear();
4749
5180
  submitSuccessListeners.clear();
4750
5181
  resetListeners.clear();
5182
+ onChangeRegistry.dispose();
4751
5183
  persistOptIns.clear();
4752
5184
  noSyncPaths.clear();
4753
5185
  noSyncPathCounts.clear();
@@ -5053,7 +5485,7 @@ function createFormStore(options) {
5053
5485
  });
5054
5486
  const next = resetResponse.data;
5055
5487
  rebuildAuthoredPaths(resetSource, next);
5056
- applyFormReplacement(next);
5488
+ applyFormReplacement(next, { silent: true });
5057
5489
  arrayIdentity.rebaselineAll();
5058
5490
  originals.clear();
5059
5491
  diffAndApply({}, next, [], (patch) => {
@@ -5299,6 +5731,7 @@ function createFormStore(options) {
5299
5731
  settleTransforms,
5300
5732
  scheduleFieldValidation,
5301
5733
  onFormChange,
5734
+ registerOnChange: onChangeRegistry.register,
5302
5735
  onSubmitSuccess,
5303
5736
  onReset,
5304
5737
  emitSubmitSuccess,
@@ -5328,13 +5761,6 @@ function captureErrorEntries(map) {
5328
5761
  for (const [k, v] of map) out.push([k, [...v]]);
5329
5762
  return out;
5330
5763
  }
5331
- function pathsEqual(a, b) {
5332
- if (a.length !== b.length) return false;
5333
- for (let j = 0; j < a.length; j++) {
5334
- if (a[j] !== b[j]) return false;
5335
- }
5336
- return true;
5337
- }
5338
5764
  function errorFieldsEqual(av, bvi) {
5339
5765
  if (av === bvi) return true;
5340
5766
  if (av.message !== bvi.message) return false;
@@ -5719,11 +6145,16 @@ function useAbstractForm(configuration, options) {
5719
6145
  if (merged.autoAria !== void 0) {
5720
6146
  apiOptions.autoAria = merged.autoAria;
5721
6147
  }
5722
- return buildFormApi(
5723
- state,
5724
- formInstanceId,
5725
- apiOptions
5726
- );
6148
+ const api = buildFormApi(state, formInstanceId, apiOptions);
6149
+ const onChangeConfig = configuration.onChange;
6150
+ if (onChangeConfig !== void 0) {
6151
+ const handler = typeof onChangeConfig === "function" ? onChangeConfig : onChangeConfig.handler;
6152
+ const onError = typeof onChangeConfig === "function" ? void 0 : onChangeConfig.onError;
6153
+ const options2 = onError === void 0 ? void 0 : { onError };
6154
+ const stop = state.registerOnChange(void 0, handler, options2, () => api);
6155
+ if (getCurrentScope() !== void 0) onScopeDispose(stop);
6156
+ }
6157
+ return api;
5727
6158
  }
5728
6159
  function mergeWithDefaults(defaults, configuration) {
5729
6160
  const strict = configuration.strict ?? defaults.strict;
@@ -7090,4 +7521,4 @@ function warnIfAmbientWizardProviderHadDuplicates() {
7090
7521
  }
7091
7522
 
7092
7523
  export { AttaformErrorCode as A, deleteAtPath as B, safeOwnRead as C, DEFAULT_TIMINGS as D, humanize as E, PERSISTENCE_MODULE_KEY as P, injectWizard as a, isUnset as b, useRegister as c, useWizard as d, isPlainRecord as e, safeAssign as f, diffAndApply as g, slimKindOf as h, injectForm as i, applyPatchesForward as j, normalizeNumericOption as k, lazy as l, defaultCoercionRules as m, normalizePersistConfig as n, defaultDisplayState as o, defineCoercion as p, makeDefaultDisplayState as q, useAbstractForm as r, structuralSnapshot as s, getAtPath as t, unset as u, setAtPath as v, resolveStorageKeyBase as w, DEFAULT_PERSISTENCE_DEBOUNCE_MS as x, cleanupOrphanKeys as y, mergeSparseHydration as z };
7093
- //# sourceMappingURL=attaform.CTheKoTc.mjs.map
7524
+ //# sourceMappingURL=attaform.CsB-iKbU.mjs.map