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,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const vue = require('vue');
4
- const paths = require('./attaform.B5LNzqQh.cjs');
4
+ const paths = require('./attaform.01iKS_lz.cjs');
5
5
 
6
6
  function safeAssign(target, key, value) {
7
7
  if (key === "__proto__") {
@@ -36,7 +36,7 @@ function descendStep(value, segment) {
36
36
  if (typeof value !== "object") return NOT_FOUND;
37
37
  if (Array.isArray(value)) {
38
38
  if (typeof segment !== "number") return NOT_FOUND;
39
- if (segment < 0 || segment >= value.length) return NOT_FOUND;
39
+ if (!(segment in value)) return NOT_FOUND;
40
40
  return value[segment];
41
41
  }
42
42
  const record = value;
@@ -71,7 +71,7 @@ function hasAtPath(root, path) {
71
71
  if (current === null || current === void 0) return false;
72
72
  if (typeof current !== "object") return false;
73
73
  if (Array.isArray(current)) {
74
- return typeof last === "number" && last >= 0 && last < current.length;
74
+ return typeof last === "number" && last in current;
75
75
  }
76
76
  const key = typeof last === "number" ? String(last) : last;
77
77
  if (isShadowedKey(key)) return safeOwnHas(current, key);
@@ -100,6 +100,31 @@ function setAtPathOffset(root, path, value, offset) {
100
100
  safeAssign(rec, head, setAtPathOffset(safeOwnRead(rec, head), path, value, nextOffset));
101
101
  return rec;
102
102
  }
103
+ const NO_IN_PLACE = { applied: false };
104
+ function tryInPlaceLeafWrite(root, path, value) {
105
+ if (path.length === 0) return NO_IN_PLACE;
106
+ let node = root;
107
+ for (let i = 0; i < path.length; i++) {
108
+ const seg = path[i];
109
+ if (Array.isArray(node)) {
110
+ if (typeof seg !== "number" || seg < 0 || seg >= node.length) return NO_IN_PLACE;
111
+ } else if (isPlainRecord(node)) {
112
+ if (typeof seg !== "string" || isShadowedKey(seg) || !(seg in node)) return NO_IN_PLACE;
113
+ } else {
114
+ return NO_IN_PLACE;
115
+ }
116
+ const container = node;
117
+ if (i < path.length - 1) {
118
+ node = container[seg];
119
+ continue;
120
+ }
121
+ const old = container[seg];
122
+ if (isPlainRecord(old) || Array.isArray(old)) return NO_IN_PLACE;
123
+ container[seg] = value;
124
+ return { applied: true, old };
125
+ }
126
+ return NO_IN_PLACE;
127
+ }
103
128
  function deleteAtPath(root, path) {
104
129
  return deleteAtPathOffset(root, path, 0);
105
130
  }
@@ -403,7 +428,7 @@ function diffObjectsLockstep(oldRec, newRec, prefix, visit) {
403
428
  diffAndApply(oldRec[k], newRec[k], appendSegment(prefix, k), visit);
404
429
  }
405
430
  }
406
- function applyChangedKeys(target, source) {
431
+ function applyChangedKeys(target, source, arrayOpPath, currentPath) {
407
432
  if (!isDescendable(target) || !isDescendable(source)) return false;
408
433
  const targetIsArray = Array.isArray(target);
409
434
  const sourceIsArray = Array.isArray(source);
@@ -421,11 +446,27 @@ function applyChangedKeys(target, source) {
421
446
  if (targetIsArray) {
422
447
  const t = target;
423
448
  const s = source;
424
- if (t.length > s.length) t.length = s.length;
425
- for (const idx of changedFirstSegments) {
426
- if (typeof idx === "symbol") continue;
427
- const i = typeof idx === "number" ? idx : Number(idx);
428
- t[i] = s[i];
449
+ if (arrayOpPath === null || paths.pathsEqual(currentPath, arrayOpPath)) {
450
+ if (t.length > s.length) t.length = s.length;
451
+ for (const idx of changedFirstSegments) {
452
+ if (typeof idx === "symbol") continue;
453
+ const i = typeof idx === "number" ? idx : Number(idx);
454
+ if (i >= s.length) continue;
455
+ t[i] = s[i];
456
+ }
457
+ } else {
458
+ for (const idx of changedFirstSegments) {
459
+ if (typeof idx === "symbol") continue;
460
+ const i = typeof idx === "number" ? idx : Number(idx);
461
+ if (i >= s.length) continue;
462
+ const childPath = appendSegment(currentPath, i);
463
+ const curEl = t[i];
464
+ const nextEl = s[i];
465
+ if (paths.isPathPrefix(childPath, arrayOpPath) && isDescendable(curEl) && isDescendable(nextEl) && applyChangedKeys(curEl, nextEl, arrayOpPath, childPath)) {
466
+ continue;
467
+ }
468
+ t[i] = nextEl;
469
+ }
429
470
  }
430
471
  } else {
431
472
  const t = target;
@@ -437,7 +478,14 @@ function applyChangedKeys(target, source) {
437
478
  for (const k of changedFirstSegments) {
438
479
  if (typeof k === "symbol") continue;
439
480
  const key = String(k);
440
- safeAssign(t, key, safeOwnRead(s, key));
481
+ const nextVal = safeOwnRead(s, key);
482
+ if (arrayOpPath !== null) {
483
+ const curVal = safeOwnRead(t, key);
484
+ if (isDescendable(curVal) && isDescendable(nextVal) && applyChangedKeys(curVal, nextVal, arrayOpPath, appendSegment(currentPath, key))) {
485
+ continue;
486
+ }
487
+ }
488
+ safeAssign(t, key, nextVal);
441
489
  }
442
490
  }
443
491
  return true;
@@ -549,6 +597,50 @@ function resolveGetDisplayState(config) {
549
597
  return config ?? defaultDisplayState;
550
598
  }
551
599
 
600
+ const AttaformErrorCode = {
601
+ /** A required field is in the blank set — user hasn't supplied a value. */
602
+ NoValueSupplied: "atta:no-value-supplied",
603
+ /** The schema adapter's `validateAtPath` threw synchronously. */
604
+ AdapterThrew: "atta:adapter-threw",
605
+ /**
606
+ * User code inside a `z.preprocess`, `.refine`, or `.transform`
607
+ * threw (sync or async). The adapter caught the throw and surfaced
608
+ * it as a `ValidationError` at the field path so the form's normal
609
+ * error pipeline handles it instead of leaking as an unhandled
610
+ * rejection or routing through `submitError`.
611
+ */
612
+ ValidatorThrew: "atta:validator-threw",
613
+ /**
614
+ * A function-form `defaultValues` factory threw or its promise
615
+ * rejected. The runtime captures the raw error on `form.hydrateError`
616
+ * and ALSO surfaces a form-level `ValidationError` (path `[]`) so
617
+ * the standard error pipeline carries the signal. Critical for the
618
+ * SSR round-trip: `hydrateError` itself does not ride the wire
619
+ * payload, but `schemaErrors` does, so the client sees the failure
620
+ * after rehydration without an extra channel.
621
+ */
622
+ HydrationFailed: "atta:hydration-failed",
623
+ /** The supplied path didn't resolve to any node in the schema. */
624
+ PathNotFound: "atta:path-not-found",
625
+ /**
626
+ * A walked form's `activate()` (async `defaultValues` factory) threw
627
+ * during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
628
+ * `ValidationError` at the form-level path (`[]`) so the wizard's
629
+ * aggregate error pipeline can carry the failure alongside ordinary
630
+ * validation errors. The raw factory error remains on
631
+ * `form.hydrateError` for retry UX.
632
+ */
633
+ ActivationFailed: "atta:activation-failed"
634
+ };
635
+ function makeBlankRequiredError(segments, formKey) {
636
+ return {
637
+ message: "No value supplied",
638
+ path: [...segments],
639
+ formKey,
640
+ code: AttaformErrorCode.NoValueSupplied
641
+ };
642
+ }
643
+
552
644
  const DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS = 0;
553
645
  const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 300;
554
646
  const DEFAULT_HISTORY_MAX_SNAPSHOTS = 128;
@@ -662,7 +754,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
662
754
  const original = state.originals.get(key)?.value;
663
755
  const pristine = state.isPristineAtPath(segments);
664
756
  const schemaForKey = state.schemaErrors.get(key);
665
- const blankForKey = state.derivedBlankErrors.value.get(key);
757
+ const blankForKey = state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments) ? [makeBlankRequiredError(segments, state.formKey)] : void 0;
666
758
  const userForKey = state.userErrors.get(key);
667
759
  const errors = [];
668
760
  if (schemaForKey !== void 0) errors.push(...schemaForKey);
@@ -726,9 +818,32 @@ function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBa
726
818
  getDisplayState
727
819
  );
728
820
  }
821
+ function visitActiveLeafPaths(value, base, visit) {
822
+ if (Array.isArray(value)) {
823
+ for (let i = 0; i < value.length; i++) {
824
+ const child = value[i];
825
+ if (Array.isArray(child) || isPlainRecord(child)) {
826
+ visitActiveLeafPaths(child, [...base, i], visit);
827
+ } else {
828
+ visit([...base, i]);
829
+ }
830
+ }
831
+ return;
832
+ }
833
+ if (isPlainRecord(value)) {
834
+ for (const k of Object.keys(value)) {
835
+ const child = value[k];
836
+ if (Array.isArray(child) || isPlainRecord(child)) {
837
+ visitActiveLeafPaths(child, [...base, k], visit);
838
+ } else {
839
+ visit([...base, k]);
840
+ }
841
+ }
842
+ }
843
+ }
729
844
  function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
730
845
  const formValue = state.form.value;
731
- const value = state.getValueAtPath(segments);
846
+ const value = getAtPath(formValue, segments);
732
847
  const original = state.originals.get(key)?.value;
733
848
  let pristine = true;
734
849
  let blank = true;
@@ -747,12 +862,16 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
747
862
  let asyncPending = false;
748
863
  const submissionAttempts = state.submissionAttempts.value;
749
864
  const blurredLeafSegments = [];
750
- for (const [leafKey, entry] of state.originals) {
751
- if (!paths.isPathPrefix(segments, entry.segments)) continue;
752
- if (segments.length === entry.segments.length) continue;
753
- if (!hasAtPath(formValue, entry.segments)) continue;
865
+ const descendantLeaves = [];
866
+ visitActiveLeafPaths(value, segments, (leafSegments) => {
867
+ const { key: leafKey } = paths.keyForSegments(leafSegments);
868
+ const entry = state.originals.get(leafKey);
869
+ if (entry === void 0) return;
870
+ descendantLeaves.push({ key: leafKey, segments: entry.segments });
871
+ });
872
+ for (const { key: leafKey, segments: leafSeg } of descendantLeaves) {
754
873
  const leafRecord = state.fields.get(leafKey);
755
- if (!state.isPristineAtPathByKey(leafKey, entry.segments)) {
874
+ if (!state.isPristineAtPathByKey(leafKey, leafSeg)) {
756
875
  pristine = false;
757
876
  dirty = true;
758
877
  }
@@ -763,7 +882,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
763
882
  if (leafRecord?.interacted === true) interacted = true;
764
883
  if (leafRecord?.blurredAfterInteraction === true) {
765
884
  blurredAfterInteraction = true;
766
- blurredLeafSegments.push(entry.segments);
885
+ blurredLeafSegments.push(leafSeg);
767
886
  }
768
887
  if (leafRecord?.connected === true) connected = true;
769
888
  if ((state.fieldValidationCounts.get(leafKey) ?? 0) > 0) {
@@ -780,7 +899,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
780
899
  if (leafGateOpen && since !== void 0 && (transformingSince === null || since < transformingSince))
781
900
  transformingSince = since;
782
901
  }
783
- if (state.pathHasAsyncValidationByKey(leafKey, entry.segments)) asyncPending = true;
902
+ if (state.pathHasAsyncValidationByKey(leafKey, leafSeg)) asyncPending = true;
784
903
  const ts = leafRecord?.updatedAt;
785
904
  if (ts !== void 0 && ts !== null) {
786
905
  if (updatedAt === null || ts > updatedAt) updatedAt = ts;
@@ -1407,7 +1526,7 @@ function buildFieldArrayApi(state) {
1407
1526
  append(path, value) {
1408
1527
  const next = readArray(path);
1409
1528
  next.push(value);
1410
- return writeArray(path, next);
1529
+ return writeArray(path, next, { kind: "insert", index: next.length - 1 });
1411
1530
  },
1412
1531
  prepend(path, value) {
1413
1532
  const next = readArray(path);
@@ -1735,42 +1854,6 @@ function mergeObjectKeys(target, source, path, schema) {
1735
1854
  return out;
1736
1855
  }
1737
1856
 
1738
- const AttaformErrorCode = {
1739
- /** A required field is in the blank set — user hasn't supplied a value. */
1740
- NoValueSupplied: "atta:no-value-supplied",
1741
- /** The schema adapter's `validateAtPath` threw synchronously. */
1742
- AdapterThrew: "atta:adapter-threw",
1743
- /**
1744
- * User code inside a `z.preprocess`, `.refine`, or `.transform`
1745
- * threw (sync or async). The adapter caught the throw and surfaced
1746
- * it as a `ValidationError` at the field path so the form's normal
1747
- * error pipeline handles it instead of leaking as an unhandled
1748
- * rejection or routing through `submitError`.
1749
- */
1750
- ValidatorThrew: "atta:validator-threw",
1751
- /**
1752
- * A function-form `defaultValues` factory threw or its promise
1753
- * rejected. The runtime captures the raw error on `form.hydrateError`
1754
- * and ALSO surfaces a form-level `ValidationError` (path `[]`) so
1755
- * the standard error pipeline carries the signal. Critical for the
1756
- * SSR round-trip: `hydrateError` itself does not ride the wire
1757
- * payload, but `schemaErrors` does, so the client sees the failure
1758
- * after rehydration without an extra channel.
1759
- */
1760
- HydrationFailed: "atta:hydration-failed",
1761
- /** The supplied path didn't resolve to any node in the schema. */
1762
- PathNotFound: "atta:path-not-found",
1763
- /**
1764
- * A walked form's `activate()` (async `defaultValues` factory) threw
1765
- * during `wizard.handleSubmit`'s path walk. Surfaced as a synthetic
1766
- * `ValidationError` at the form-level path (`[]`) so the wizard's
1767
- * aggregate error pipeline can carry the failure alongside ordinary
1768
- * validation errors. The raw factory error remains on
1769
- * `form.hydrateError` for retry UX.
1770
- */
1771
- ActivationFailed: "atta:activation-failed"
1772
- };
1773
-
1774
1857
  const warnedNoScopeStores = paths.__DEV__ ? /* @__PURE__ */ new WeakSet() : null;
1775
1858
  function buildProcessForm(state, formInstanceId, options = {}) {
1776
1859
  const invalidPolicy = options.onInvalidSubmit ?? "focus-first-error";
@@ -2796,19 +2879,116 @@ function buildFormApi(state, formInstanceId, options = {}) {
2796
2879
  }
2797
2880
  };
2798
2881
  const getFormMetaBase = () => {
2799
- const { base: rootBase } = buildContainerFieldStateBase(
2882
+ let rollup;
2883
+ const rootBase = () => rollup ?? (rollup = buildContainerFieldStateBase(
2800
2884
  state,
2801
2885
  paths.ROOT_PATH,
2802
2886
  paths.ROOT_PATH_KEY,
2803
2887
  formInstanceId
2804
- );
2888
+ ).base);
2805
2889
  return {
2806
- ...rootBase,
2890
+ // Rollup-derived (FieldStateBase) — the whole rollup builds once, on the
2891
+ // first access of any of these.
2892
+ get value() {
2893
+ return rootBase().value;
2894
+ },
2895
+ get original() {
2896
+ return rootBase().original;
2897
+ },
2898
+ get pristine() {
2899
+ return rootBase().pristine;
2900
+ },
2901
+ get dirty() {
2902
+ return rootBase().dirty;
2903
+ },
2904
+ get focused() {
2905
+ return rootBase().focused;
2906
+ },
2907
+ get blurred() {
2908
+ return rootBase().blurred;
2909
+ },
2910
+ get touched() {
2911
+ return rootBase().touched;
2912
+ },
2913
+ get interacted() {
2914
+ return rootBase().interacted;
2915
+ },
2916
+ get blurredAfterInteraction() {
2917
+ return rootBase().blurredAfterInteraction;
2918
+ },
2919
+ get connected() {
2920
+ return rootBase().connected;
2921
+ },
2922
+ get element() {
2923
+ return rootBase().element;
2924
+ },
2925
+ get elements() {
2926
+ return rootBase().elements;
2927
+ },
2928
+ get updatedAt() {
2929
+ return rootBase().updatedAt;
2930
+ },
2931
+ get errors() {
2932
+ return rootBase().errors;
2933
+ },
2934
+ get validating() {
2935
+ return rootBase().validating;
2936
+ },
2937
+ get valid() {
2938
+ return rootBase().valid;
2939
+ },
2940
+ get transforming() {
2941
+ return rootBase().transforming;
2942
+ },
2943
+ get busy() {
2944
+ return rootBase().busy;
2945
+ },
2946
+ get transformError() {
2947
+ return rootBase().transformError;
2948
+ },
2949
+ get path() {
2950
+ return rootBase().path;
2951
+ },
2952
+ get id() {
2953
+ return rootBase().id;
2954
+ },
2955
+ get aria() {
2956
+ return rootBase().aria;
2957
+ },
2958
+ get key() {
2959
+ return rootBase().key;
2960
+ },
2961
+ get blank() {
2962
+ return rootBase().blank;
2963
+ },
2964
+ get label() {
2965
+ return rootBase().label;
2966
+ },
2967
+ get description() {
2968
+ return rootBase().description;
2969
+ },
2970
+ get placeholder() {
2971
+ return rootBase().placeholder;
2972
+ },
2973
+ get meta() {
2974
+ return rootBase().meta;
2975
+ },
2976
+ get errorCount() {
2977
+ return rootBase().errors.length;
2978
+ },
2979
+ // Form-level scalars — EAGER reads, tracked on every field-state eval.
2980
+ // They are O(1) refs that never change on a keystroke, so tracking them
2981
+ // per field costs nothing on the hot path. Kept eager (NOT lazy like the
2982
+ // rollup) because behaviors beyond the predicate's own output depend on
2983
+ // every field re-evaluating when they flip — most notably, the display
2984
+ // engine is cleared on submit (revealing held spinners), and that
2985
+ // imperative reset only becomes visible if `submitting` is a tracked dep
2986
+ // of each field. Matches the pre-bust dependency set for these scalars
2987
+ // exactly.
2807
2988
  submitting: state.submitting.value,
2808
2989
  submissionAttempts: state.submissionAttempts.value,
2809
2990
  departAttempts: state.departAttempts.value,
2810
2991
  submitError: state.submitError.value,
2811
- errorCount: rootBase.errors.length,
2812
2992
  submitted: state.submitted.value,
2813
2993
  instanceId: formInstanceId
2814
2994
  };
@@ -2843,14 +3023,19 @@ function buildFormApi(state, formInstanceId, options = {}) {
2843
3023
  const segments = paths.canonicalizePath(pathInput).segments;
2844
3024
  return vue.computed(() => getAtPath(state.form.value, segments));
2845
3025
  }
2846
- function setValueImpl(pathOrValue, maybeValue) {
2847
- if (arguments.length === 1) {
3026
+ function setValueImpl(pathOrValue, maybeValue, maybeOptions) {
3027
+ const argc = arguments.length;
3028
+ const isPathForm = argc >= 2 && (typeof pathOrValue === "string" || Array.isArray(pathOrValue));
3029
+ const options2 = isPathForm ? maybeOptions : argc >= 2 ? maybeValue : void 0;
3030
+ const silent = options2?.silent === true;
3031
+ const writeMeta = (extra) => withInstanceMeta(silent ? { ...extra, silent: true } : extra);
3032
+ if (!isPathForm) {
2848
3033
  const next = typeof pathOrValue === "function" ? pathOrValue(structuralSnapshot(state.form.value)) : pathOrValue;
2849
3034
  const walked2 = walkUnsetSentinels(
2850
3035
  next,
2851
3036
  state.schema
2852
3037
  );
2853
- const ok2 = state.setValueAtPath([], walked2.cleanedValues, withInstanceMeta());
3038
+ const ok2 = state.setValueAtPath([], walked2.cleanedValues, writeMeta());
2854
3039
  if (!ok2) return false;
2855
3040
  reMarkBlanksAfterSubstitution(walked2.paths);
2856
3041
  return true;
@@ -2864,7 +3049,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2864
3049
  if (parentDU?.discriminatorKey === last) {
2865
3050
  const slimDefault = state.schema.getEmptyValueAtPath(segments);
2866
3051
  const blank = blankForKind(slimDefault);
2867
- return state.setValueAtPath(segments, blank, withInstanceMeta({ blank: true }));
3052
+ return state.setValueAtPath(segments, blank, writeMeta({ blank: true }));
2868
3053
  }
2869
3054
  }
2870
3055
  const blankPaths = [];
@@ -2875,9 +3060,9 @@ function buildFormApi(state, formInstanceId, options = {}) {
2875
3060
  );
2876
3061
  const segmentsKey = paths.canonicalizePath(segments).key;
2877
3062
  if (blankPaths.length === 1 && blankPaths[0] === segmentsKey) {
2878
- return state.setValueAtPath(segments, expanded, withInstanceMeta({ blank: true }));
3063
+ return state.setValueAtPath(segments, expanded, writeMeta({ blank: true }));
2879
3064
  }
2880
- const ok2 = state.setValueAtPath(segments, expanded, withInstanceMeta());
3065
+ const ok2 = state.setValueAtPath(segments, expanded, writeMeta());
2881
3066
  if (!ok2) return false;
2882
3067
  for (const pathKey of blankPaths) {
2883
3068
  const blankSegments = paths.segmentsForPathKey(pathKey);
@@ -2885,7 +3070,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2885
3070
  state.setValueAtPath(
2886
3071
  blankSegments,
2887
3072
  state.getValueAtPath(blankSegments),
2888
- withInstanceMeta({ blank: true })
3073
+ writeMeta({ blank: true })
2889
3074
  );
2890
3075
  }
2891
3076
  return true;
@@ -2905,7 +3090,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2905
3090
  segments,
2906
3091
  state.schema
2907
3092
  );
2908
- const ok = state.setValueAtPath(segments, walked.cleanedValues, withInstanceMeta());
3093
+ const ok = state.setValueAtPath(segments, walked.cleanedValues, writeMeta());
2909
3094
  if (!ok) return false;
2910
3095
  reMarkBlanksAfterSubstitution(walked.paths);
2911
3096
  return true;
@@ -3276,7 +3461,19 @@ function buildFormApi(state, formInstanceId, options = {}) {
3276
3461
  }
3277
3462
  return Object.freeze(out);
3278
3463
  }
3279
- return {
3464
+ const formHandle = {
3465
+ current: void 0
3466
+ };
3467
+ function onChangeImpl(a, b, c) {
3468
+ const sourced = typeof b === "function";
3469
+ const source = sourced ? a : void 0;
3470
+ const handler = sourced ? b : a;
3471
+ const options2 = sourced ? c : b;
3472
+ const stop = state.registerOnChange(source, handler, options2, () => formHandle.current);
3473
+ if (vue.getCurrentScope() !== void 0) vue.onScopeDispose(stop);
3474
+ return stop;
3475
+ }
3476
+ const api = {
3280
3477
  handleSubmit: gated(handleSubmit),
3281
3478
  // Callable readonly Proxies (`values`, `fields`, `errors`) and the
3282
3479
  // reactive containers (`meta`, `history`, `blankPaths`) are exposed
@@ -3363,8 +3560,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
3363
3560
  get blankPaths() {
3364
3561
  void state.activate();
3365
3562
  return blankPathsView;
3366
- }
3563
+ },
3564
+ onChange: onChangeImpl
3367
3565
  };
3566
+ formHandle.current = api;
3567
+ return api;
3368
3568
  }
3369
3569
 
3370
3570
  const IDLE = Object.freeze({ display: "idle" });
@@ -3791,6 +3991,7 @@ function createArrayBookkeeping(deps) {
3791
3991
  originals,
3792
3992
  blankPaths,
3793
3993
  originalBlankPaths,
3994
+ authoredPaths,
3794
3995
  fieldValidationCounts,
3795
3996
  fieldValidatingSince,
3796
3997
  fieldValidationState,
@@ -3818,6 +4019,7 @@ function createArrayBookkeeping(deps) {
3818
4019
  }));
3819
4020
  migrateSetSubtree(blankPaths, arrayPath, remap);
3820
4021
  migrateSetSubtree(originalBlankPaths, arrayPath, remap);
4022
+ migrateSetSubtree(authoredPaths, arrayPath, remap);
3821
4023
  migrateMapSubtree(fieldValidatingSince, arrayPath, remap, (since) => since);
3822
4024
  migrateMapSubtree(fieldValidationCounts, arrayPath, remap, (count) => count);
3823
4025
  arrayIdentity.applyRemap(arrayPath, remap);
@@ -3861,7 +4063,7 @@ function createArrayBookkeeping(deps) {
3861
4063
  activeValidations.value = Math.max(0, activeValidations.value - 1);
3862
4064
  decFieldValidation(key);
3863
4065
  }
3864
- entry.controller.abort();
4066
+ entry.aborted = true;
3865
4067
  fieldValidationState.delete(key);
3866
4068
  }
3867
4069
  }
@@ -3873,6 +4075,170 @@ function createArrayBookkeeping(deps) {
3873
4075
  };
3874
4076
  }
3875
4077
 
4078
+ const NOOP_STOP = () => {
4079
+ };
4080
+ function isThenable(value) {
4081
+ return value !== null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
4082
+ }
4083
+ function isSuppressed(meta) {
4084
+ return meta?.hydration === true || meta?.crossTab === true || meta?.silent === true;
4085
+ }
4086
+ function canonicalizeSourceList(raw) {
4087
+ const list = typeof raw === "string" ? [raw] : raw;
4088
+ const out = [];
4089
+ const seen = /* @__PURE__ */ new Set();
4090
+ for (const entry of list) {
4091
+ const { segments, key } = paths.canonicalizePath(entry);
4092
+ if (seen.has(key)) continue;
4093
+ seen.add(key);
4094
+ out.push({ segments, key, dotted: paths.segmentsToDotted(segments) });
4095
+ }
4096
+ return out;
4097
+ }
4098
+ function makeResolver(source) {
4099
+ if (source === void 0) {
4100
+ const root = [{ segments: paths.ROOT_PATH, key: paths.ROOT_PATH_KEY, dotted: "" }];
4101
+ return () => root;
4102
+ }
4103
+ if (typeof source !== "function" && !vue.isRef(source)) {
4104
+ const fixed = canonicalizeSourceList(source);
4105
+ return () => fixed;
4106
+ }
4107
+ return () => canonicalizeSourceList(vue.toValue(source));
4108
+ }
4109
+ function createOnChangeRegistry(deps) {
4110
+ const handlers = /* @__PURE__ */ new Set();
4111
+ function isCurrent(reg, key, token) {
4112
+ return reg.runs.get(key)?.token === token;
4113
+ }
4114
+ function routeError(reg, error, source, changed, value, previous, attempt, token) {
4115
+ if (reg.onError === void 0) {
4116
+ if (paths.__DEV__) {
4117
+ console.error(
4118
+ `[attaform] onChange handler threw for path '${source.dotted}' \u2014 error swallowed. Pass { onError } to handle it. Original error:`,
4119
+ error
4120
+ );
4121
+ }
4122
+ return;
4123
+ }
4124
+ const retry = () => {
4125
+ if (!isCurrent(reg, source.key, token)) return;
4126
+ runHandler(reg, source, changed, value, previous, attempt + 1);
4127
+ };
4128
+ const errCtx = {
4129
+ path: source.dotted,
4130
+ value,
4131
+ attempt,
4132
+ retry,
4133
+ form: reg.getForm()
4134
+ };
4135
+ try {
4136
+ reg.onError(error, errCtx);
4137
+ } catch (err) {
4138
+ if (paths.__DEV__) console.error("[attaform] onChange onError threw:", err);
4139
+ }
4140
+ }
4141
+ function runHandler(reg, source, changed, value, previous, attempt) {
4142
+ const prior = reg.runs.get(source.key);
4143
+ if (prior) prior.controller.abort();
4144
+ const controller = new AbortController();
4145
+ const token = ++reg.seq;
4146
+ reg.runs.set(source.key, { token, controller });
4147
+ const ctx = {
4148
+ path: source.dotted,
4149
+ previous,
4150
+ signal: controller.signal,
4151
+ attempt,
4152
+ form: reg.getForm(),
4153
+ changed
4154
+ };
4155
+ let result;
4156
+ try {
4157
+ result = reg.handler(value, ctx);
4158
+ } catch (error) {
4159
+ routeError(reg, error, source, changed, value, previous, attempt, token);
4160
+ return;
4161
+ }
4162
+ if (isThenable(result)) {
4163
+ result.then(void 0, (error) => {
4164
+ if (isCurrent(reg, source.key, token)) {
4165
+ routeError(reg, error, source, changed, value, previous, attempt, token);
4166
+ }
4167
+ });
4168
+ }
4169
+ }
4170
+ function dispatch(patches, meta) {
4171
+ if (handlers.size === 0 || isSuppressed(meta)) return;
4172
+ for (const reg of handlers) {
4173
+ let sources;
4174
+ try {
4175
+ sources = reg.resolve();
4176
+ } catch (error) {
4177
+ if (paths.__DEV__) console.error("[attaform] onChange source getter threw:", error);
4178
+ continue;
4179
+ }
4180
+ for (const source of sources) {
4181
+ let changed;
4182
+ for (const patch of patches) {
4183
+ if (paths.isPathPrefix(source.segments, patch.path) || paths.isPathPrefix(patch.path, source.segments)) {
4184
+ (changed ?? (changed = [])).push(paths.segmentsToDotted(patch.path));
4185
+ }
4186
+ }
4187
+ if (changed === void 0) continue;
4188
+ const value = deps.getValueAtPath(source.segments);
4189
+ const previous = reg.previous.has(source.key) ? reg.previous.get(source.key) : value;
4190
+ reg.previous.set(source.key, value);
4191
+ runHandler(reg, source, changed, value, previous, 0);
4192
+ }
4193
+ }
4194
+ }
4195
+ function register(source, handler, options, getForm) {
4196
+ if (deps.ssr) return NOOP_STOP;
4197
+ const reg = {
4198
+ handler,
4199
+ onError: options?.onError,
4200
+ getForm,
4201
+ resolve: makeResolver(source),
4202
+ previous: /* @__PURE__ */ new Map(),
4203
+ runs: /* @__PURE__ */ new Map(),
4204
+ seq: 0
4205
+ };
4206
+ try {
4207
+ for (const { key, segments } of reg.resolve()) {
4208
+ reg.previous.set(key, deps.getValueAtPath(segments));
4209
+ }
4210
+ } catch (error) {
4211
+ if (paths.__DEV__) console.error("[attaform] onChange source getter threw at registration:", error);
4212
+ }
4213
+ handlers.add(reg);
4214
+ let stopped = false;
4215
+ return () => {
4216
+ if (stopped) return;
4217
+ stopped = true;
4218
+ handlers.delete(reg);
4219
+ for (const run of reg.runs.values()) run.controller.abort();
4220
+ reg.runs.clear();
4221
+ reg.previous.clear();
4222
+ };
4223
+ }
4224
+ function dispose() {
4225
+ for (const reg of handlers) {
4226
+ for (const run of reg.runs.values()) run.controller.abort();
4227
+ reg.runs.clear();
4228
+ reg.previous.clear();
4229
+ }
4230
+ handlers.clear();
4231
+ }
4232
+ return {
4233
+ get active() {
4234
+ return handlers.size > 0;
4235
+ },
4236
+ register,
4237
+ dispatch,
4238
+ dispose
4239
+ };
4240
+ }
4241
+
3876
4242
  function isHydratedFieldRecord(value) {
3877
4243
  if (typeof value !== "object" || value === null) return false;
3878
4244
  const r = value;
@@ -3957,6 +4323,17 @@ function walkAuthoredFromConstraints(value, prefix, out) {
3957
4323
  }
3958
4324
  }
3959
4325
  }
4326
+ function freshElementIndices(op) {
4327
+ switch (op.kind) {
4328
+ case "insert":
4329
+ case "replace-at":
4330
+ return [op.index];
4331
+ case "remove":
4332
+ case "swap":
4333
+ case "move":
4334
+ return [];
4335
+ }
4336
+ }
3960
4337
  function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out) {
3961
4338
  if (isPlainRecord(withDefaults) && isPlainRecord(withoutDefaults)) {
3962
4339
  const left = withDefaults;
@@ -3995,6 +4372,7 @@ function createFormStore(options) {
3995
4372
  const formChangeListeners = /* @__PURE__ */ new Set();
3996
4373
  const submitSuccessListeners = /* @__PURE__ */ new Set();
3997
4374
  const resetListeners = /* @__PURE__ */ new Set();
4375
+ const onChangeRegistry = createOnChangeRegistry({ getValueAtPath, ssr });
3998
4376
  const persistOptIns = paths.createPersistOptInRegistry();
3999
4377
  const noSyncPaths = /* @__PURE__ */ new Set();
4000
4378
  const noSyncPathCounts = /* @__PURE__ */ new Map();
@@ -4033,11 +4411,8 @@ function createFormStore(options) {
4033
4411
  if (constraints !== void 0) {
4034
4412
  walkAuthoredFromConstraints(constraints, [], authoredPaths);
4035
4413
  }
4036
- const slimResponse = schema.getDefaultValues({
4037
- useDefaultSchemaValues: false,
4038
- strict
4039
- });
4040
- walkAuthoredFromSchemaDiff(schemaWithDefaultsData, slimResponse.data, [], authoredPaths);
4414
+ const slimBaseline = schema.getEmptyValueAtPath([]);
4415
+ walkAuthoredFromSchemaDiff(schemaWithDefaultsData, slimBaseline, [], authoredPaths);
4041
4416
  }
4042
4417
  rebuildAuthoredPaths(defaultValues, schemaInitialData);
4043
4418
  function filterAuthoredErrors(errors) {
@@ -4056,7 +4431,7 @@ function createFormStore(options) {
4056
4431
  });
4057
4432
  const form = vue.ref(stubbedInitialData);
4058
4433
  const arrayIdentity = createArrayIdentity((arraySegs) => {
4059
- const v = getAtPath(form.value, arraySegs);
4434
+ const v = getAtPath(vue.toRaw(form.value), arraySegs);
4060
4435
  return Array.isArray(v) ? v.length : 0;
4061
4436
  });
4062
4437
  function arrayElementKey(path) {
@@ -4099,14 +4474,7 @@ function createFormStore(options) {
4099
4474
  const segments = paths.segmentsForPathKey(pathKey);
4100
4475
  if (segments === null) continue;
4101
4476
  if (!schema.isRequiredAtPath(segments)) continue;
4102
- result.set(pathKey, [
4103
- {
4104
- message: "No value supplied",
4105
- path: [...segments],
4106
- formKey,
4107
- code: AttaformErrorCode.NoValueSupplied
4108
- }
4109
- ]);
4477
+ result.set(pathKey, [makeBlankRequiredError(segments, formKey)]);
4110
4478
  }
4111
4479
  return result;
4112
4480
  });
@@ -4346,6 +4714,7 @@ function createFormStore(options) {
4346
4714
  originals,
4347
4715
  blankPaths,
4348
4716
  originalBlankPaths,
4717
+ authoredPaths,
4349
4718
  fieldValidationCounts,
4350
4719
  fieldValidatingSince,
4351
4720
  fieldValidationState,
@@ -4355,17 +4724,8 @@ function createFormStore(options) {
4355
4724
  touchFieldRecord,
4356
4725
  decFieldValidation
4357
4726
  });
4358
- function applyFormReplacement(next, meta) {
4359
- const prev = form.value;
4360
- if (Object.is(prev, next)) return;
4727
+ function commitWritePatches(patches, meta) {
4361
4728
  const now = (/* @__PURE__ */ new Date()).toISOString();
4362
- const patches = [];
4363
- diffAndApply(prev, next, [], (patch) => {
4364
- patches.push(patch);
4365
- });
4366
- if (!applyChangedKeys(prev, next)) {
4367
- form.value = next;
4368
- }
4369
4729
  for (const patch of patches) {
4370
4730
  const { key } = paths.canonicalizePath(patch.path);
4371
4731
  if (patch.kind === "added" && !originals.has(key)) {
@@ -4380,10 +4740,63 @@ function createFormStore(options) {
4380
4740
  console.error("[attaform] onFormChange threw:", err);
4381
4741
  }
4382
4742
  }
4743
+ if (onChangeRegistry.active) onChangeRegistry.dispatch(patches, meta);
4744
+ }
4745
+ function applyFormReplacementWithPath(next, meta, arrayOpPath) {
4746
+ const prev = form.value;
4747
+ if (Object.is(prev, next)) return;
4748
+ const patches = [];
4749
+ diffAndApply(prev, next, [], (patch) => {
4750
+ patches.push(patch);
4751
+ });
4752
+ if (!applyChangedKeys(prev, next, arrayOpPath, [])) {
4753
+ form.value = next;
4754
+ } else if (patches.some(
4755
+ (p) => p.path.length > 0 && typeof p.path[0] === "string" && isShadowedKey(p.path[0])
4756
+ )) {
4757
+ vue.triggerRef(form);
4758
+ }
4759
+ commitWritePatches(patches, meta);
4760
+ }
4761
+ function applyFormReplacement(next, meta) {
4762
+ applyFormReplacementWithPath(next, meta, null);
4763
+ }
4764
+ function applyTargetedWrite(path, completedValue, meta) {
4765
+ const result = tryInPlaceLeafWrite(form.value, path, completedValue);
4766
+ if (!result.applied) {
4767
+ applyFormReplacementWithPath(
4768
+ setAtPathWithSchemaFill(form.value, schema, path, completedValue),
4769
+ meta,
4770
+ meta?.arrayOp !== void 0 ? path : null
4771
+ );
4772
+ return;
4773
+ }
4774
+ const patches = [];
4775
+ diffAndApply(result.old, completedValue, path, (patch) => {
4776
+ patches.push(patch);
4777
+ });
4778
+ commitWritePatches(patches, meta);
4383
4779
  }
4384
4780
  function setValueAtPath(path, value, meta) {
4385
- value = stripSymbolsDeep(value);
4386
- if (!isSlimPrimitiveValid(schema, form, path, value)) {
4781
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4782
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4783
+ value[idx] = stripSymbolsDeep(value[idx]);
4784
+ }
4785
+ } else {
4786
+ value = stripSymbolsDeep(value);
4787
+ }
4788
+ let slimOk = true;
4789
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4790
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4791
+ if (!isSlimPrimitiveValid(schema, form, [...path, idx], value[idx])) {
4792
+ slimOk = false;
4793
+ break;
4794
+ }
4795
+ }
4796
+ } else {
4797
+ slimOk = isSlimPrimitiveValid(schema, form, path, value);
4798
+ }
4799
+ if (!slimOk) {
4387
4800
  return false;
4388
4801
  }
4389
4802
  if (path.length >= 2) {
@@ -4476,22 +4889,37 @@ function createFormStore(options) {
4476
4889
  }
4477
4890
  }
4478
4891
  }
4892
+ const currentValue = getAtPath(form.value, path);
4479
4893
  const pathKey = paths.canonicalizePath(path).key;
4480
4894
  if (meta?.blank === true) {
4481
4895
  blankPaths.add(pathKey);
4482
4896
  } else {
4483
4897
  if (blankPaths.has(pathKey)) blankPaths.delete(pathKey);
4484
- if (meta?.arrayOp === void 0) {
4898
+ if (meta?.arrayOp === void 0 && (isPlainRecord(currentValue) || Array.isArray(currentValue))) {
4485
4899
  for (const existingKey of [...blankPaths]) {
4486
4900
  if (isPathKeyUnder(existingKey, path)) blankPaths.delete(existingKey);
4487
4901
  }
4488
4902
  }
4489
4903
  }
4490
4904
  const wasAuthoredBefore = authoredPaths.has(pathKey);
4491
- walkAuthoredFromConstraints(value, path, authoredPaths);
4905
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4906
+ if (path.length > 0) authoredPaths.add(pathKey);
4907
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4908
+ walkAuthoredFromConstraints(value[idx], [...path, idx], authoredPaths);
4909
+ }
4910
+ } else {
4911
+ walkAuthoredFromConstraints(value, path, authoredPaths);
4912
+ }
4492
4913
  const newlyAuthored = !wasAuthoredBefore && authoredPaths.has(pathKey);
4493
- const completedValue = mergeStructural(schema, path, value);
4494
- const currentValue = getAtPath(form.value, path);
4914
+ let completedValue;
4915
+ if (meta?.arrayOp !== void 0 && Array.isArray(value)) {
4916
+ for (const idx of freshElementIndices(meta.arrayOp)) {
4917
+ value[idx] = mergeStructural(schema, [...path, idx], value[idx]);
4918
+ }
4919
+ completedValue = value;
4920
+ } else {
4921
+ completedValue = mergeStructural(schema, path, value);
4922
+ }
4495
4923
  if (Object.is(currentValue, completedValue)) {
4496
4924
  if (newlyAuthored && schema.isPreprocessOrCoerceLeaf(path)) {
4497
4925
  const modeForAuthoringTransition = meta?.instance?.validateOn ?? fieldValidationMode;
@@ -4505,8 +4933,7 @@ function createFormStore(options) {
4505
4933
  return true;
4506
4934
  }
4507
4935
  const oldArrayLength = Array.isArray(currentValue) ? currentValue.length : 0;
4508
- const nextForm = setAtPathWithSchemaFill(form.value, schema, path, completedValue);
4509
- applyFormReplacement(nextForm, meta);
4936
+ applyTargetedWrite(path, completedValue, meta);
4510
4937
  if (meta?.arrayOp !== void 0) {
4511
4938
  const remap = remapForOp(meta.arrayOp, oldArrayLength);
4512
4939
  arrayBookkeeping.migrateElementState(path, remap);
@@ -4589,7 +5016,7 @@ function createFormStore(options) {
4589
5016
  const prevValidation = fieldValidationState.get(parentKey2);
4590
5017
  if (prevValidation !== void 0) {
4591
5018
  if (prevValidation.timer !== null) clearTimeout(prevValidation.timer);
4592
- prevValidation.controller.abort();
5019
+ prevValidation.aborted = true;
4593
5020
  fieldValidationState.delete(parentKey2);
4594
5021
  }
4595
5022
  appliedSync = true;
@@ -4613,15 +5040,19 @@ function createFormStore(options) {
4613
5040
  const prev = fieldValidationState.get(key);
4614
5041
  if (prev !== void 0) {
4615
5042
  if (prev.timer !== null) clearTimeout(prev.timer);
4616
- prev.controller.abort();
5043
+ prev.aborted = true;
4617
5044
  }
4618
- const controller = new AbortController();
4619
- const fresh = { controller, timer: null, settled: false, released: false };
5045
+ const fresh = {
5046
+ aborted: false,
5047
+ timer: null,
5048
+ settled: false,
5049
+ released: false
5050
+ };
4620
5051
  fieldValidationState.set(key, fresh);
4621
5052
  const myEpoch = ++scheduleEpoch;
4622
5053
  const run = () => {
4623
5054
  fresh.timer = null;
4624
- if (controller.signal.aborted) return;
5055
+ if (fresh.aborted) return;
4625
5056
  let activeIncremented = false;
4626
5057
  try {
4627
5058
  activeValidations.value += 1;
@@ -4638,7 +5069,7 @@ function createFormStore(options) {
4638
5069
  const dataAtScope = subtreeScope ? getAtPath(form.value, path) : form.value;
4639
5070
  const scopeKey = subtreeScope ? paths.canonicalizePath(path).key : paths.ROOT_PATH_KEY;
4640
5071
  void Promise.resolve().then(() => schema.validateAtPath(dataAtScope, scopePath)).then((response) => {
4641
- if (controller.signal.aborted) return;
5072
+ if (fresh.aborted) return;
4642
5073
  if (myEpoch <= lastCommittedEpoch) return;
4643
5074
  lastCommittedEpoch = myEpoch;
4644
5075
  if (effectiveMode === "blur") {
@@ -4675,7 +5106,7 @@ function createFormStore(options) {
4675
5106
  activeValidations.value = Math.max(0, activeValidations.value - 1);
4676
5107
  decFieldValidation(pkey);
4677
5108
  }
4678
- entry.controller.abort();
5109
+ entry.aborted = true;
4679
5110
  }
4680
5111
  fieldValidationState.clear();
4681
5112
  }
@@ -4691,7 +5122,7 @@ function createFormStore(options) {
4691
5122
  decFieldValidation(key);
4692
5123
  entry.released = true;
4693
5124
  }
4694
- entry.controller.abort();
5125
+ entry.aborted = true;
4695
5126
  fieldValidationState.delete(key);
4696
5127
  }
4697
5128
  }
@@ -4750,6 +5181,7 @@ function createFormStore(options) {
4750
5181
  formChangeListeners.clear();
4751
5182
  submitSuccessListeners.clear();
4752
5183
  resetListeners.clear();
5184
+ onChangeRegistry.dispose();
4753
5185
  persistOptIns.clear();
4754
5186
  noSyncPaths.clear();
4755
5187
  noSyncPathCounts.clear();
@@ -5055,7 +5487,7 @@ function createFormStore(options) {
5055
5487
  });
5056
5488
  const next = resetResponse.data;
5057
5489
  rebuildAuthoredPaths(resetSource, next);
5058
- applyFormReplacement(next);
5490
+ applyFormReplacement(next, { silent: true });
5059
5491
  arrayIdentity.rebaselineAll();
5060
5492
  originals.clear();
5061
5493
  diffAndApply({}, next, [], (patch) => {
@@ -5301,6 +5733,7 @@ function createFormStore(options) {
5301
5733
  settleTransforms,
5302
5734
  scheduleFieldValidation,
5303
5735
  onFormChange,
5736
+ registerOnChange: onChangeRegistry.register,
5304
5737
  onSubmitSuccess,
5305
5738
  onReset,
5306
5739
  emitSubmitSuccess,
@@ -5330,19 +5763,12 @@ function captureErrorEntries(map) {
5330
5763
  for (const [k, v] of map) out.push([k, [...v]]);
5331
5764
  return out;
5332
5765
  }
5333
- function pathsEqual(a, b) {
5334
- if (a.length !== b.length) return false;
5335
- for (let j = 0; j < a.length; j++) {
5336
- if (a[j] !== b[j]) return false;
5337
- }
5338
- return true;
5339
- }
5340
5766
  function errorFieldsEqual(av, bvi) {
5341
5767
  if (av === bvi) return true;
5342
5768
  if (av.message !== bvi.message) return false;
5343
5769
  if (av.code !== bvi.code) return false;
5344
5770
  if (av.formKey !== bvi.formKey) return false;
5345
- return av.path === bvi.path || pathsEqual(av.path, bvi.path);
5771
+ return av.path === bvi.path || paths.pathsEqual(av.path, bvi.path);
5346
5772
  }
5347
5773
  function errorsEqual(a, b) {
5348
5774
  if (a.length !== b.length) return false;
@@ -5721,11 +6147,16 @@ function useAbstractForm(configuration, options) {
5721
6147
  if (merged.autoAria !== void 0) {
5722
6148
  apiOptions.autoAria = merged.autoAria;
5723
6149
  }
5724
- return buildFormApi(
5725
- state,
5726
- formInstanceId,
5727
- apiOptions
5728
- );
6150
+ const api = buildFormApi(state, formInstanceId, apiOptions);
6151
+ const onChangeConfig = configuration.onChange;
6152
+ if (onChangeConfig !== void 0) {
6153
+ const handler = typeof onChangeConfig === "function" ? onChangeConfig : onChangeConfig.handler;
6154
+ const onError = typeof onChangeConfig === "function" ? void 0 : onChangeConfig.onError;
6155
+ const options2 = onError === void 0 ? void 0 : { onError };
6156
+ const stop = state.registerOnChange(void 0, handler, options2, () => api);
6157
+ if (vue.getCurrentScope() !== void 0) vue.onScopeDispose(stop);
6158
+ }
6159
+ return api;
5729
6160
  }
5730
6161
  function mergeWithDefaults(defaults, configuration) {
5731
6162
  const strict = configuration.strict ?? defaults.strict;
@@ -7123,4 +7554,4 @@ exports.unset = unset;
7123
7554
  exports.useAbstractForm = useAbstractForm;
7124
7555
  exports.useRegister = useRegister;
7125
7556
  exports.useWizard = useWizard;
7126
- //# sourceMappingURL=attaform.D2ZuIOCf.cjs.map
7557
+ //# sourceMappingURL=attaform.CjMcwV7W.cjs.map