attaform 0.29.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/abstract.d.mts +15 -21
  2. package/dist/abstract.mjs +4 -4
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/dev-key-collision-warnings.mjs +47 -8
  5. package/dist/dev/chunks/devtools.mjs +2 -2
  6. package/dist/dev/directive.mjs +1 -1
  7. package/dist/dev/history.mjs +1 -2
  8. package/dist/dev/index.mjs +5 -5
  9. package/dist/dev/runtime/plugins/attaform.mjs +3 -3
  10. package/dist/dev/shared/{attaform-dev.BO-71R54.mjs → attaform-dev.BJK1kqHY.mjs} +44 -198
  11. package/dist/dev/shared/{attaform-dev.D8aoKb89.mjs → attaform-dev.BMQ3GcgD.mjs} +51 -64
  12. package/dist/dev/shared/{attaform-dev.BgM6FdtA.mjs → attaform-dev.BUIFcTYf.mjs} +94 -62
  13. package/dist/dev/shared/{attaform-dev.q6UutkCx.mjs → attaform-dev.CT7MDjPy.mjs} +92 -152
  14. package/dist/dev/shared/{attaform-dev.ButTkwHd.mjs → attaform-dev.CZz4GYZ6.mjs} +26 -15
  15. package/dist/dev/shared/{attaform-dev.DD5m2W51.mjs → attaform-dev.Chm4D6SJ.mjs} +2 -4
  16. package/dist/dev/shared/{attaform-dev.DrzObAZo.mjs → attaform-dev.DuEE4GU1.mjs} +4 -4
  17. package/dist/dev/shared/{attaform-dev.DHuydLAC.mjs → attaform-dev.DxHdkkPG.mjs} +674 -734
  18. package/dist/dev/shared/{attaform-dev.A-TpJW0B.mjs → attaform-dev.zlvDr0WB.mjs} +3 -21
  19. package/dist/dev/zod-v3.mjs +6 -6
  20. package/dist/dev/zod-v4.mjs +6 -6
  21. package/dist/dev/zod.mjs +5 -5
  22. package/dist/directive.d.mts +4 -4
  23. package/dist/directive.mjs +1 -1
  24. package/dist/esbuild.d.mts +11 -0
  25. package/dist/esbuild.mjs +1 -1
  26. package/dist/history.d.mts +3 -32
  27. package/dist/history.mjs +1 -2
  28. package/dist/index.d.mts +67 -70
  29. package/dist/index.mjs +5 -5
  30. package/dist/nuxt.d.mts +6 -14
  31. package/dist/nuxt.mjs +3 -7
  32. package/dist/rollup.d.mts +11 -0
  33. package/dist/rollup.mjs +1 -1
  34. package/dist/rspack.d.mts +19 -11
  35. package/dist/rspack.mjs +1 -1
  36. package/dist/runtime/components/AttaformDevtoolsPanel.vue +12 -12
  37. package/dist/runtime/components/DevtoolsValueTree.vue +2 -2
  38. package/dist/runtime/plugins/attaform.mjs +3 -3
  39. package/dist/shared/{attaform.CIWLMY-R.d.mts → attaform.2FslhB9R.d.mts} +83 -95
  40. package/dist/shared/{attaform.Cvao8bW5.mjs → attaform.8mfKZCwH.mjs} +7 -7
  41. package/dist/shared/{attaform.DRUT703x.d.mts → attaform.B-Tb62qV.d.mts} +12 -12
  42. package/dist/shared/{attaform.v0fQtWyA.mjs → attaform.B1rH9ht6.mjs} +1 -1
  43. package/dist/shared/{attaform.BndjUUYX.mjs → attaform.B2iebkPA.mjs} +4 -4
  44. package/dist/shared/{attaform.DhnPb6nK.mjs → attaform.BAf3UNn4.mjs} +1 -1
  45. package/dist/shared/{attaform.CZMAYwSK.mjs → attaform.BIWicztt.mjs} +651 -678
  46. package/dist/shared/attaform.BcQwgWaq.d.mts +2153 -0
  47. package/dist/shared/attaform.Br8EBn5B.d.mts +4553 -0
  48. package/dist/shared/{attaform.C2MVyuxC.mjs → attaform.CO918TdU.mjs} +44 -198
  49. package/dist/shared/{attaform.CZYt_5N_.mjs → attaform.CdZIEf7_.mjs} +1 -19
  50. package/dist/shared/{attaform.B0dKL8ST.mjs → attaform.DRsANMok.mjs} +94 -62
  51. package/dist/shared/{attaform.DffRL5pP.mjs → attaform.D_f1WgYf.mjs} +46 -59
  52. package/dist/shared/{attaform.D1fnTifF.mjs → attaform.D_pt1gOj.mjs} +92 -150
  53. package/dist/shared/attaform.L0kUn3vL.d.mts +42 -0
  54. package/dist/shared/{attaform.D0x-qSXm.mjs → attaform.WiGdIqgx.mjs} +1 -3
  55. package/dist/shared/{attaform.BK0cgu2K.mjs → attaform.ZYn2ahzJ.mjs} +3 -3
  56. package/dist/transforms.d.mts +153 -26
  57. package/dist/transforms.mjs +1 -1
  58. package/dist/vite.d.mts +29 -52
  59. package/dist/vite.mjs +8 -3
  60. package/dist/webpack.d.mts +20 -12
  61. package/dist/webpack.mjs +1 -1
  62. package/dist/zod-v3.d.mts +90 -96
  63. package/dist/zod-v3.mjs +6 -6
  64. package/dist/zod-v4.d.mts +119 -119
  65. package/dist/zod-v4.mjs +6 -6
  66. package/dist/zod.d.mts +5 -5
  67. package/dist/zod.mjs +5 -5
  68. package/package.json +9 -7
  69. package/skills/attaform/SKILL.md +1 -1
  70. package/skills/attaform/references/custom-components.md +1 -1
  71. package/dist/chunks/fingerprint.mjs +0 -183
  72. package/dist/chunks/fingerprint2.mjs +0 -159
  73. package/dist/dev/chunks/fingerprint.mjs +0 -183
  74. package/dist/dev/chunks/fingerprint2.mjs +0 -159
  75. package/dist/dev/shared/attaform-dev.BxfsuwBR.mjs +0 -44
  76. package/dist/shared/attaform.BxfsuwBR.mjs +0 -44
  77. package/dist/shared/attaform.CsoeRi3P.d.mts +0 -5155
  78. package/dist/shared/attaform.DDtqHYv1.d.mts +0 -2465
  79. package/dist/shared/attaform.axHXVQO7.d.mts +0 -44
@@ -1,11 +1,11 @@
1
- import { computed, readonly, toRaw, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, nextTick, warn, reactive, shallowRef, toValue, watch, triggerRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope } from 'vue';
2
- import { A as ANONYMOUS_FORM_KEY_PREFIX, d as consumerHas, i as isShadowedKey, e as safeOwnHas, a as safeOwnRead, f as spreadConsumerRecord, g as copyConsumerArray, r as readConsumerIndex, b as safeAssign, h as consumerKeys, j as readConsumerProp, k as diffAndApply, p as pickDefined, s as structuralSnapshot, n as normalizeNumericOption, l as DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS, m as consumerSymbolKeys, o as applyChangedKeys, R as RESERVED_KEY_PREFIX, q as DEFAULT_MAX_RECURSION_DEPTH, t as ANONYMOUS_WIZARD_KEY_PREFIX } from './attaform-dev.ButTkwHd.mjs';
3
- import { e as ensureAttaformInstalled, u as useRegistry, d as kFormContext, f as kFormInstanceId, b as kAttaformWizardActiveStepResolver, i as kAttaformAncestorWizard } from './attaform-dev.DD5m2W51.mjs';
4
- import { R as REGISTER_OWNER_MARKER, d as armDomBinding, V as V_REGISTER_MARKER } from './attaform-dev.D8aoKb89.mjs';
5
- import { i as isPathPrefix, e as canonicalizePath, j as cellEntriesFor, s as segmentsForPathKey, k as keyForSegments, m as mapSegmentKeys, b as ROOT_PATH_KEY, S as SubmitErrorHandlerError, t as toError, l as groupErrorsByKey, n as normalizeErrorInput, h as SET_MEMBER_SEGMENT, R as ROOT_PATH, o as normalizeErrorInputs, q as coerceToPathKey, a as InvalidUseFormConfigError, d as ReservedFormKeyError } from './attaform-dev.A-TpJW0B.mjs';
1
+ import { computed, readonly, toRaw, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, nextTick, warn, reactive, shallowReactive, shallowRef, toValue, watch, triggerRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope } from 'vue';
2
+ import { A as ANONYMOUS_FORM_KEY_PREFIX, i as isShadowedKey, d as safeOwnHas, a as safeOwnRead, e as consumerHas, f as spreadConsumerRecord, g as copyConsumerArray, r as readConsumerIndex, b as safeAssign, h as consumerKeys, j as readConsumerProp, k as diffAndApply, p as pickDefined, s as structuralSnapshot, n as normalizeNumericOption, l as DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS, m as consumerSymbolKeys, o as applyChangedKeys, R as RESERVED_KEY_PREFIX, q as DEFAULT_MAX_RECURSION_DEPTH, c as callConsumerFn, t as reportConsumerThrow, u as ANONYMOUS_WIZARD_KEY_PREFIX } from './attaform-dev.CZz4GYZ6.mjs';
3
+ import { e as ensureAttaformInstalled, u as useRegistry, d as kFormContext, f as kFormInstanceId, b as kAttaformWizardActiveStepResolver, i as kAttaformAncestorWizard } from './attaform-dev.Chm4D6SJ.mjs';
4
+ import { R as REGISTER_OWNER_MARKER, d as armDomBinding, V as V_REGISTER_MARKER } from './attaform-dev.BMQ3GcgD.mjs';
5
+ import { i as isPathPrefix, e as canonicalizePath, k as keyForSegments, m as mapSegmentKeys, b as ROOT_PATH_KEY, s as segmentsForPathKey, S as SubmitErrorHandlerError, t as toError, j as groupErrorsByKey, n as normalizeErrorInput, h as SET_MEMBER_SEGMENT, R as ROOT_PATH, l as normalizeErrorInputs, a as InvalidUseFormConfigError, d as ReservedFormKeyError } from './attaform-dev.zlvDr0WB.mjs';
6
6
 
7
7
  const AttaformErrorCode = {
8
- /** A required field is in the blank set — user hasn't supplied a value. */
8
+ /** A required field is in the blank set: no value was supplied. */
9
9
  NoValueSupplied: "atta:no-value-supplied",
10
10
  /** The schema adapter's `validateAtPath` threw synchronously. */
11
11
  AdapterThrew: "atta:adapter-threw",
@@ -77,11 +77,6 @@ function makeBlankRequiredError(segments) {
77
77
  }
78
78
  const NO_ERRORS = Object.freeze([]);
79
79
 
80
- function crossCopyState(key, create) {
81
- const carrier = globalThis;
82
- return carrier[key] ?? (carrier[key] = create());
83
- }
84
-
85
80
  function isGateOpen(field, formMeta) {
86
81
  return formMeta.submissionAttempts > 0 || field.blurredAfterInteraction === true;
87
82
  }
@@ -103,45 +98,32 @@ function earliestNonNull(a, b) {
103
98
  return a < b ? a : b;
104
99
  }
105
100
  const DEFAULT_TIMINGS = { showDelay: 120, minVisible: 120 };
101
+ const { showDelay, minVisible } = DEFAULT_TIMINGS;
106
102
  const FOCUS_OUT_GRACE = 16;
107
- const defaultFamily = crossCopyState(
108
- /* @__PURE__ */ Symbol.for("attaform:default-display-state-family"),
109
- () => /* @__PURE__ */ new WeakSet()
110
- );
111
- function isDefaultDisplayState(fn) {
112
- return defaultFamily.has(fn);
113
- }
114
- function makeDefaultDisplayState({
115
- showDelay,
116
- minVisible
117
- }) {
118
- const reducer = (prev, { field, formMeta, validatingSince, transformingSince, now }) => {
119
- const verdict = computeVerdict(field, formMeta);
120
- if (!isGateOpen(field, formMeta)) return { display: verdict };
121
- const inFlightSince = earliestNonNull(validatingSince, transformingSince);
122
- if (inFlightSince === null) {
123
- if (prev.display === "pending") {
124
- const shownAt = prev.pendingShownAt ?? now;
125
- if (now < shownAt + minVisible)
126
- return { display: "pending", pendingShownAt: shownAt, reviewAt: shownAt + minVisible };
127
- }
128
- return { display: verdict };
129
- }
130
- if (prev.display === "pending")
131
- return { display: "pending", pendingShownAt: prev.pendingShownAt ?? now };
132
- const window = field.focused === false ? Math.min(showDelay, FOCUS_OUT_GRACE) : showDelay;
133
- if (now - inFlightSince < window) {
134
- return { display: prev.display, reviewAt: inFlightSince + window };
103
+ const defaultDisplayState = (prev, { field, formMeta, validatingSince, transformingSince, now }) => {
104
+ const verdict = computeVerdict(field, formMeta);
105
+ if (!isGateOpen(field, formMeta)) return { display: verdict };
106
+ const inFlightSince = earliestNonNull(validatingSince, transformingSince);
107
+ if (inFlightSince === null) {
108
+ if (prev.display === "pending") {
109
+ const shownAt = prev.pendingShownAt ?? now;
110
+ if (now < shownAt + minVisible)
111
+ return {
112
+ display: "pending",
113
+ pendingShownAt: shownAt,
114
+ reviewAt: shownAt + minVisible
115
+ };
135
116
  }
136
- return { display: "pending", pendingShownAt: now, reviewAt: now + minVisible };
137
- };
138
- defaultFamily.add(reducer);
139
- return reducer;
140
- }
141
- const defaultDisplayState = makeDefaultDisplayState(DEFAULT_TIMINGS);
142
- function resolveGetDisplayState(config) {
143
- return config ?? defaultDisplayState;
144
- }
117
+ return { display: verdict };
118
+ }
119
+ if (prev.display === "pending")
120
+ return { display: "pending", pendingShownAt: prev.pendingShownAt ?? now };
121
+ const window = field.focused === false ? Math.min(showDelay, FOCUS_OUT_GRACE) : showDelay;
122
+ if (now - inFlightSince < window) {
123
+ return { display: prev.display, reviewAt: inFlightSince + window };
124
+ }
125
+ return { display: "pending", pendingShownAt: now, reviewAt: now + minVisible };
126
+ };
145
127
 
146
128
  function hashStableString(input, seed = 0) {
147
129
  let h1 = 3735928559 ^ seed;
@@ -229,16 +211,27 @@ function descendStep(value, segment) {
229
211
  }
230
212
  function getAtPath(root, path) {
231
213
  if (path.length === 0) return root;
232
- let current = root;
233
- for (const segment of path) {
234
- const next = descendStep(current, segment);
235
- if (next === NOT_FOUND) return void 0;
236
- current = next;
214
+ try {
215
+ let current = root;
216
+ for (const segment of path) {
217
+ const next = descendStep(current, segment);
218
+ if (next === NOT_FOUND) return void 0;
219
+ current = next;
220
+ }
221
+ return current;
222
+ } catch {
223
+ return void 0;
237
224
  }
238
- return current;
239
225
  }
240
226
  function hasAtPath(root, path) {
241
227
  if (path.length === 0) return true;
228
+ try {
229
+ return hasAtPathUnguarded(root, path);
230
+ } catch {
231
+ return false;
232
+ }
233
+ }
234
+ function hasAtPathUnguarded(root, path) {
242
235
  let current = root;
243
236
  for (let i = 0; i < path.length - 1; i++) {
244
237
  const segment = path[i];
@@ -265,29 +258,15 @@ function isPlainRecord(value) {
265
258
  const proto = Object.getPrototypeOf(value);
266
259
  return proto === null || proto === Object.prototype;
267
260
  }
261
+ const NO_SCHEMA_DEFAULTS = /* @__PURE__ */ new Set();
262
+ const NO_SCHEMA_FILL = {
263
+ getDefaultAtPath: () => void 0,
264
+ arrayShapeAtPath: () => null,
265
+ getSlimPrimitiveTypesAtPath: () => NO_SCHEMA_DEFAULTS,
266
+ entryKeyKindAtPath: () => void 0
267
+ };
268
268
  function setAtPath(root, path, value) {
269
- return setAtPathOffset(root, path, value, 0);
270
- }
271
- function setAtPathOffset(root, path, value, offset) {
272
- if (offset >= path.length) return value;
273
- const head = path[offset];
274
- const nextOffset = offset + 1;
275
- if (root instanceof Map) {
276
- const next = new Map(root);
277
- const key = mapKeyForSegment(root, head);
278
- next.set(key, setAtPathOffset(root.get(key), path, value, nextOffset));
279
- return next;
280
- }
281
- if (!isRebuildableContainer(root, head)) return root;
282
- if (typeof head === "number") {
283
- const arr = Array.isArray(root) ? [...root] : [];
284
- while (arr.length <= head) arr.push(void 0);
285
- arr[head] = setAtPathOffset(arr[head], path, value, nextOffset);
286
- return arr;
287
- }
288
- const rec = isPlainRecord(root) ? { ...root } : {};
289
- safeAssign(rec, head, setAtPathOffset(safeOwnRead(rec, head), path, value, nextOffset));
290
- return rec;
269
+ return setAtPathWithSchemaFill(root, NO_SCHEMA_FILL, path, value);
291
270
  }
292
271
  const NO_IN_PLACE = { applied: false };
293
272
  function tryInPlaceLeafWrite(root, path, value) {
@@ -493,10 +472,10 @@ function setAtPathWithSchemaFillImpl(root, schema, fullPath, value, startIdx) {
493
472
  }
494
473
  const rec = isPlainRecord(root) ? { ...root } : {};
495
474
  if (isLeafStep) {
496
- rec[head] = value;
475
+ safeAssign(rec, head, value);
497
476
  return rec;
498
477
  }
499
- const existing = rec[head];
478
+ const existing = safeOwnRead(rec, head);
500
479
  let childRoot;
501
480
  if (existing === void 0 || existing !== null && typeof existing !== "object") {
502
481
  const intermPath = [...fullPath.slice(0, startIdx + 1)];
@@ -505,11 +484,14 @@ function setAtPathWithSchemaFillImpl(root, schema, fullPath, value, startIdx) {
505
484
  } else {
506
485
  childRoot = existing;
507
486
  }
508
- rec[head] = setAtPathWithSchemaFillImpl(childRoot, schema, fullPath, value, startIdx + 1);
487
+ safeAssign(
488
+ rec,
489
+ head,
490
+ setAtPathWithSchemaFillImpl(childRoot, schema, fullPath, value, startIdx + 1)
491
+ );
509
492
  return rec;
510
493
  }
511
494
 
512
- const warnedDisplayStatePredicates = /* @__PURE__ */ new WeakSet();
513
495
  function isUnderStubAncestor(state, segments) {
514
496
  for (let i = 0; i < segments.length; i++) {
515
497
  const ancestorPath = segments.slice(0, i);
@@ -523,16 +505,15 @@ function isUnderStubAncestor(state, segments) {
523
505
  }
524
506
  return false;
525
507
  }
526
- function buildFieldStateAccessor(state, formInstanceId, getFormMetaBase, sweep, options) {
508
+ function buildFieldStateAccessor(state, formInstanceId, getFormMetaBase, sweep) {
527
509
  const cache = /* @__PURE__ */ new Map();
528
- const predicate = options?.getDisplayState;
529
510
  sweep.onEvict((key) => cache.delete(key));
530
511
  return function getFieldState(pathInput) {
531
512
  const { segments, key } = canonicalizePath(pathInput);
532
513
  const cached = cache.get(key);
533
514
  if (cached !== void 0) return cached;
534
515
  const c = computed(
535
- () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate) : buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, predicate)
516
+ () => state.schema.isLeafAtPath(segments) ? buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase) : buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase)
536
517
  );
537
518
  cache.set(key, c);
538
519
  sweep.track(segments, key);
@@ -549,7 +530,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
549
530
  const record = state.fields.get(key);
550
531
  const value = state.getValueAtPath(segments);
551
532
  const original = state.originals.get(key)?.value;
552
- const pristine = state.isPristineAtPath(segments);
533
+ const pristine = state.isPristineAtPathByKey(key, segments);
553
534
  const cell = state.errorCells.get(key);
554
535
  const schemaForKey = cell !== void 0 && cell.schema.length > 0 ? cell.schema : void 0;
555
536
  const blankForKey = state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments) ? [makeBlankRequiredError(segments)] : void 0;
@@ -561,7 +542,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
561
542
  const validating = (state.fieldValidationCounts.get(key) ?? 0) > 0;
562
543
  const transforming = (state.fieldTransformCounts.get(key) ?? 0) > 0;
563
544
  const transformError = state.transformErrors.get(key) ?? null;
564
- const gated = state.pathHasAsyncValidation(segments) && !state.firstValidationDone.value;
545
+ const gated = state.pathHasAsyncValidationByKey(key, segments) && !state.firstValidationDone.value;
565
546
  const isOrphan = segments.length > 0 && !hasAtPath(state.form.value, segments) && isUnderStubAncestor(state, segments);
566
547
  const valid = !gated && errors.length === 0 && !validating && !isOrphan;
567
548
  const elementRecord = state.domBinding.value?.elements.get(key);
@@ -584,9 +565,8 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
584
565
  updatedAt: record?.updatedAt ?? null,
585
566
  errors,
586
567
  // A leaf has no descendants, so its own-bucket errors ARE its subtree
587
- // errors. Reuse the same array reference: referential `ownErrors ===
588
- // errors`, and no second read of the whole-form `derivedBlankErrors`
589
- // map (preserves the P3 blank-reactivity optimization above).
568
+ // errors. Reuse the array reference, which makes `ownErrors === errors`
569
+ // and avoids a second read of the whole-form `derivedBlankErrors` map.
590
570
  ownErrors: errors,
591
571
  validating,
592
572
  valid,
@@ -604,7 +584,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
604
584
  meta: resolved.meta
605
585
  };
606
586
  }
607
- function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
587
+ function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBase) {
608
588
  const base = buildLeafFieldStateBase(state, segments, key, formInstanceId);
609
589
  const validatingSince = state.fieldValidatingSince.get(key) ?? null;
610
590
  const transformingSince = state.fieldTransformingSince.get(key) ?? null;
@@ -617,9 +597,8 @@ function buildLeafFieldState(state, segments, key, formInstanceId, getFormMetaBa
617
597
  transformingSince,
618
598
  false,
619
599
  // revealedDescendantError: leaves have no descendants
620
- false,
600
+ false
621
601
  // isRoot: a leaf is never the form root
622
- getDisplayState
623
602
  );
624
603
  }
625
604
  function visitActiveLeafPaths(value, base, visit) {
@@ -717,7 +696,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
717
696
  pristine = false;
718
697
  dirty = true;
719
698
  }
720
- const errors = aggregateErrorsAt(state, segments);
699
+ const errors = aggregateErrorsAt(state, segments, key);
721
700
  const revealedDescendantError = errors.length > 0 && errors.some((e) => {
722
701
  const ePath = e.path;
723
702
  if (ePath.length === segments.length && ePath.every((s, i) => s === segments[i])) return false;
@@ -725,7 +704,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
725
704
  if (ePath.length === 1 && ePath[0] === "") return blurredAfterInteraction;
726
705
  return blurredLeafSegments.some((s) => isPathPrefix(ePath, s));
727
706
  });
728
- if (!asyncPending && state.pathHasAsyncValidation(segments)) asyncPending = true;
707
+ if (!asyncPending && state.pathHasAsyncValidationByKey(key, segments)) asyncPending = true;
729
708
  if ((state.fieldValidationCounts.get(key) ?? 0) > 0) {
730
709
  validating = true;
731
710
  const since = state.fieldValidatingSince.get(key);
@@ -767,19 +746,19 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
767
746
  elements: EMPTY_ELEMENTS,
768
747
  updatedAt,
769
748
  errors,
770
- // The container's OWN bucket only (this exact path), excluding every
771
- // descendant that `errors` rolls up: a cross-field `.refine()` here,
772
- // or `setErrors` pinned at this node. The same raw union the leaf
773
- // builds from the three stores; the container already depends on all
774
- // three via `aggregateErrorsAt`, so there's no new reactivity cost.
749
+ // The container's OWN bucket at this exact path, excluding every
750
+ // descendant `errors` rolls up: a cross-field `.refine()` here, or a
751
+ // `setErrors` pinned at this node. The same raw union the leaf builds
752
+ // from the three stores, and the container already depends on all three
753
+ // through `aggregateErrorsAt`, so it costs no new reactivity.
775
754
  ownErrors: state.getErrorsForPath(segments),
776
755
  validating,
777
756
  valid,
778
757
  transforming,
779
758
  busy: transforming || validating,
780
- // A container surfaces its OWN transform failure (a transform registered
781
- // on the container path, e.g. a file normalizer) but never rolls up a
782
- // descendant leaf's failure — that stays a per-field channel.
759
+ // A container surfaces its OWN transform failure, from a transform
760
+ // registered on the container path such as a file normalizer, and never
761
+ // rolls up a descendant's: that stays a per-field channel.
783
762
  transformError: ownTransformError,
784
763
  path: segments,
785
764
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
@@ -796,7 +775,7 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
796
775
  revealedDescendantError
797
776
  };
798
777
  }
799
- function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase, getDisplayState) {
778
+ function buildContainerFieldState(state, segments, key, formInstanceId, getFormMetaBase) {
800
779
  const { base, validatingSince, transformingSince, revealedDescendantError } = buildContainerFieldStateBase(state, segments, key, formInstanceId);
801
780
  return decorateWithDerivedProps(
802
781
  base,
@@ -806,14 +785,12 @@ function buildContainerFieldState(state, segments, key, formInstanceId, getFormM
806
785
  validatingSince,
807
786
  transformingSince,
808
787
  revealedDescendantError,
809
- segments.length === 0,
810
- getDisplayState
788
+ segments.length === 0
811
789
  );
812
790
  }
813
- function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, transformingSince, revealedDescendantError, isRoot, getDisplayState) {
791
+ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingSince, transformingSince, revealedDescendantError, isRoot) {
814
792
  const firstError = base.errors[0];
815
793
  const firstOwnError = base.ownErrors[0];
816
- const predicate = getDisplayState ?? state.getDisplayState;
817
794
  const formMeta = getFormMetaBase();
818
795
  const ctx = {
819
796
  field: base,
@@ -824,23 +801,9 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingS
824
801
  // flight) so the reducer returns the plain verdict and hydration matches.
825
802
  now: state.ssr ? 0 : Date.now()
826
803
  };
827
- let machine;
828
- let rollupApplies = isDefaultDisplayState(predicate);
829
- try {
830
- machine = state.displayEngine.resolve(key, ctx, predicate);
831
- } catch (err) {
832
- if (!warnedDisplayStatePredicates.has(predicate)) {
833
- warnedDisplayStatePredicates.add(predicate);
834
- console.warn(
835
- "[attaform] custom getDisplayState threw \u2014 falling back to defaultDisplayState. Subsequent throws from the same predicate will not warn again.",
836
- err
837
- );
838
- }
839
- machine = state.displayEngine.resolve(key, ctx, defaultDisplayState);
840
- rollupApplies = true;
841
- }
842
- const submitValidating = rollupApplies && isRoot && state.submitting.value && state.activeValidations.value > 0;
843
- const resolvedDisplayState = machine.display === "pending" || submitValidating ? "pending" : rollupApplies && revealedDescendantError ? "error" : machine.display;
804
+ const machine = state.displayEngine.resolve(key, ctx, defaultDisplayState);
805
+ const submitValidating = isRoot && state.submitting.value && state.activeValidations.value > 0;
806
+ const resolvedDisplayState = machine.display === "pending" || submitValidating ? "pending" : revealedDescendantError ? "error" : machine.display;
844
807
  const displayState = base.disabled === true ? "idle" : resolvedDisplayState;
845
808
  return {
846
809
  ...base,
@@ -853,26 +816,33 @@ function decorateWithDerivedProps(base, state, getFormMetaBase, key, validatingS
853
816
  firstOwnError
854
817
  };
855
818
  }
856
- function aggregateErrorsAt(state, prefix) {
819
+ function aggregateErrorsAt(state, prefix, prefixKey) {
820
+ const candidates = state.errorWindowAt(prefix, prefixKey);
821
+ if (candidates.length === 0) return [];
857
822
  const formValue = state.form.value;
823
+ const cells = state.errorCells;
858
824
  const buckets = /* @__PURE__ */ new Map();
859
- const collect = (errs) => {
860
- for (const [pathKey, list] of errs) {
861
- if (list.length === 0) continue;
862
- const segs = segmentsForPathKey(pathKey);
863
- if (segs === null) continue;
864
- if (!isPathPrefix(prefix, segs)) continue;
865
- if (segs.length > 0 && !hasAtPath(formValue, segs)) continue;
866
- const ordinal = state.ensurePathOrdinal(pathKey);
867
- const existing = buckets.get(ordinal);
868
- if (existing === void 0) buckets.set(ordinal, [...list]);
869
- else existing.push(...list);
870
- }
871
- };
872
- collect(cellEntriesFor(state.errorCells, "schema"));
873
- collect(state.derivedBlankErrors.value);
874
- collect(cellEntriesFor(state.errorCells, "user"));
825
+ for (const { key, segments } of candidates) {
826
+ if (!isPathPrefix(prefix, segments)) continue;
827
+ if (segments.length > 0 && !hasAtPath(formValue, segments)) continue;
828
+ const cell = cells.get(key);
829
+ const blankList = state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments) ? [makeBlankRequiredError(segments)] : void 0;
830
+ const schemaList = cell?.schema;
831
+ const userList = cell?.user;
832
+ const total = (schemaList?.length ?? 0) + (blankList?.length ?? 0) + (userList?.length ?? 0);
833
+ if (total === 0) continue;
834
+ const ordinal = state.ensurePathOrdinal(key);
835
+ let bucket = buckets.get(ordinal);
836
+ if (bucket === void 0) {
837
+ bucket = [];
838
+ buckets.set(ordinal, bucket);
839
+ }
840
+ if (schemaList !== void 0) bucket.push(...schemaList);
841
+ if (blankList !== void 0) bucket.push(...blankList);
842
+ if (userList !== void 0) bucket.push(...userList);
843
+ }
875
844
  if (buckets.size === 0) return [];
845
+ if (buckets.size === 1) return [...buckets.values()][0];
876
846
  return [...buckets.entries()].sort(([a], [b]) => a - b).flatMap(([, errs]) => errs);
877
847
  }
878
848
  const EMPTY_ELEMENTS = Object.freeze([]);
@@ -915,7 +885,7 @@ function isArrayPath(state, segments) {
915
885
  function warnReadOnly(surface, action, key) {
916
886
  const phrase = action === "write" ? `write to "${String(key)}"` : `${action} of "${String(key)}"`;
917
887
  console.warn(
918
- `[attaform] ${surface} is read-only \u2014 ${phrase} was ignored. Mutate the form via setValue / the directive / field-array helpers instead.`
888
+ `[attaform] ${surface} is read-only; ${phrase} was ignored. Mutate the form via setValue / the directive / field-array helpers instead.`
919
889
  );
920
890
  }
921
891
  function makeReadonlyCoercion(snapshot) {
@@ -967,7 +937,7 @@ function buildTree(spec) {
967
937
  existsCache.delete(key);
968
938
  });
969
939
  function schemaHasPath(segs) {
970
- const cacheKey = JSON.stringify(segs);
940
+ const cacheKey = keyForSegments(segs).key;
971
941
  const cached = existsCache.get(cacheKey);
972
942
  if (cached !== void 0) return cached;
973
943
  const result = spec.schema.getSlimPrimitiveTypesAtPath(segs).size > 0;
@@ -982,7 +952,7 @@ function buildTree(spec) {
982
952
  }
983
953
  function containerAt(segments) {
984
954
  const isArrayLike = spec.isArrayAt(segments);
985
- const pathKey = JSON.stringify(segments);
955
+ const pathKey = keyForSegments(segments).key;
986
956
  const cacheKey = `${pathKey}+${isArrayLike ? "A" : "O"}`;
987
957
  const existing = containerCache.get(cacheKey);
988
958
  if (existing !== void 0) return existing;
@@ -1082,7 +1052,7 @@ function buildErrorsSurface(state, sweep) {
1082
1052
  const treeCache = /* @__PURE__ */ new Map();
1083
1053
  sweep.onEvict((key) => treeCache.delete(key));
1084
1054
  const materialize = (segments) => {
1085
- const cacheKey = JSON.stringify(segments);
1055
+ const cacheKey = keyForSegments(segments).key;
1086
1056
  let tree = treeCache.get(cacheKey);
1087
1057
  if (tree === void 0) {
1088
1058
  const frozen = [...segments];
@@ -1094,28 +1064,21 @@ function buildErrorsSurface(state, sweep) {
1094
1064
  };
1095
1065
  const resolveLeaf = (path) => {
1096
1066
  const isContainerSelfAccess = path.length > 1 && path[path.length - 1] === "";
1097
- const collectAtKey = (key2, active2, into) => {
1098
- const cell = state.errorCells.get(key2);
1099
- if (active2) {
1100
- const b = state.derivedBlankErrors.value.get(key2);
1101
- if (cell !== void 0) into.push(...cell.schema);
1102
- if (b !== void 0) into.push(...b);
1103
- }
1104
- if (cell !== void 0) into.push(...cell.user);
1105
- };
1106
1067
  const merged = [];
1107
1068
  if (isContainerSelfAccess) {
1108
1069
  const containerPath = path.slice(0, -1);
1109
- const containerKey = canonicalizePath(containerPath).key;
1110
- const literalKey = canonicalizePath(path).key;
1070
+ const container = canonicalizePath(containerPath);
1071
+ const literal = canonicalizePath(path);
1111
1072
  const active2 = hasAtPath(state.form.value, containerPath);
1112
- collectAtKey(containerKey, active2, merged);
1113
- if (literalKey !== containerKey) collectAtKey(literalKey, active2, merged);
1073
+ collectErrorsAt(state, container.key, container.segments, active2, merged);
1074
+ if (literal.key !== container.key) {
1075
+ collectErrorsAt(state, literal.key, literal.segments, active2, merged);
1076
+ }
1114
1077
  return merged;
1115
1078
  }
1116
- const { key } = canonicalizePath(path);
1079
+ const { key, segments } = canonicalizePath(path);
1117
1080
  const active = hasAtPath(state.form.value, path);
1118
- collectAtKey(key, active, merged);
1081
+ collectErrorsAt(state, key, segments, active, merged);
1119
1082
  return merged;
1120
1083
  };
1121
1084
  return buildTree({
@@ -1130,68 +1093,83 @@ function buildErrorsSurface(state, sweep) {
1130
1093
  // non-schema keys reachable while a genuinely-absent key stays out.
1131
1094
  hasOwn: (segments, key) => liveContainerHasKey(state, segments, key) || errorAwareContainerKeys(state, segments).includes(key),
1132
1095
  isArrayAt: (segments) => isArrayPath(state, segments),
1133
- // `errors()` / `errors([])` / `errors(path)` are all the subtree
1134
- // aggregate — the same helper `meta.errors` reads, so the surfaces
1135
- // never drift.
1136
- call: (path) => aggregateErrorsAt(state, path),
1096
+ // `errors()`, `errors([])` and `errors(path)` are all the subtree
1097
+ // aggregate, through the helper `meta.errors` reads, so they cannot
1098
+ // drift.
1099
+ call: (path) => aggregateErrorsAt(state, path, keyForSegments(path).key),
1137
1100
  surface: "form.errors",
1138
1101
  sweep
1139
1102
  });
1140
1103
  }
1104
+ function collectErrorsAt(state, key, segments, active, into) {
1105
+ const cell = state.errorCells.get(key);
1106
+ if (active) {
1107
+ if (cell !== void 0) into.push(...cell.schema);
1108
+ if (state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments)) {
1109
+ into.push(makeBlankRequiredError(segments));
1110
+ }
1111
+ }
1112
+ if (cell !== void 0) into.push(...cell.user);
1113
+ }
1141
1114
  function errorAwareContainerKeys(state, segments) {
1142
1115
  const keys = new Set(liveKeysAtPath(state, segments));
1143
1116
  const formValue = state.form.value;
1144
- const walk = (store, applyActivePathFilter) => {
1145
- for (const [pathKey, errors] of store) {
1146
- if (errors.length === 0) continue;
1147
- const decoded = segmentsForPathKey(pathKey);
1148
- if (decoded === null) continue;
1149
- if (decoded.length <= segments.length) continue;
1150
- if (!isPathPrefix(segments, decoded)) continue;
1151
- if (applyActivePathFilter && !hasAtPath(formValue, decoded)) continue;
1152
- const nextSeg = decoded[segments.length];
1153
- keys.add(typeof nextSeg === "number" ? String(nextSeg) : nextSeg);
1154
- }
1155
- };
1156
- walk(cellEntriesFor(state.errorCells, "schema"), true);
1157
- walk(state.derivedBlankErrors.value, true);
1158
- walk(cellEntriesFor(state.errorCells, "user"), false);
1117
+ const errors = [];
1118
+ const found = [];
1119
+ for (const { key, segments: decoded } of state.errorWindowAt(
1120
+ segments,
1121
+ keyForSegments(segments).key
1122
+ )) {
1123
+ if (decoded.length <= segments.length) continue;
1124
+ if (!isPathPrefix(segments, decoded)) continue;
1125
+ errors.length = 0;
1126
+ collectErrorsAt(state, key, decoded, hasAtPath(formValue, decoded), errors);
1127
+ if (errors.length === 0) continue;
1128
+ const nextSeg = decoded[segments.length];
1129
+ found.push({
1130
+ childKey: typeof nextSeg === "number" ? String(nextSeg) : nextSeg,
1131
+ ordinal: state.ensurePathOrdinal(key)
1132
+ });
1133
+ }
1134
+ found.sort((a, b) => a.ordinal - b.ordinal);
1135
+ for (const { childKey } of found) keys.add(childKey);
1159
1136
  return [...keys];
1160
1137
  }
1161
1138
  function materializeErrors(state, containerSegments) {
1162
- const liveContainer = getAtPath(state.form.value, containerSegments);
1139
+ const formValue = state.form.value;
1140
+ const liveContainer = getAtPath(formValue, containerSegments);
1163
1141
  const tree = Array.isArray(liveContainer) ? [] : {};
1164
- const collect = (store, applyActivePathFilter) => {
1165
- entries: for (const [pathKey, errors] of store) {
1166
- if (errors.length === 0) continue;
1167
- const fullPath = segmentsForPathKey(pathKey);
1168
- if (fullPath === null) continue;
1169
- if (fullPath.length === 0) {
1170
- if (containerSegments.length === 0) placeAt(tree, [ROOT_PATH_KEY], errors);
1171
- continue;
1172
- }
1173
- if (fullPath.length < containerSegments.length) continue;
1174
- for (let i = 0; i < containerSegments.length; i++) {
1175
- if (fullPath[i] !== containerSegments[i]) continue entries;
1176
- }
1177
- if (applyActivePathFilter && !hasAtPath(state.form.value, fullPath)) continue;
1178
- const relativePath = fullPath.slice(containerSegments.length);
1179
- let placePath;
1180
- if (relativePath.length === 0) {
1181
- placePath = [""];
1182
- } else if (state.schema.isLeafAtPath(fullPath)) {
1183
- placePath = relativePath;
1184
- } else if (state.schema.getSlimPrimitiveTypesAtPath(fullPath).size > 0) {
1185
- placePath = [...relativePath, ""];
1186
- } else {
1187
- placePath = relativePath;
1188
- }
1189
- placeAt(tree, placePath, errors);
1142
+ const placements = [];
1143
+ for (const { key, segments: fullPath } of state.errorWindowAt(
1144
+ containerSegments,
1145
+ keyForSegments(containerSegments).key
1146
+ )) {
1147
+ const isRoot = fullPath.length === 0;
1148
+ if (isRoot) {
1149
+ if (containerSegments.length !== 0) continue;
1150
+ } else if (!isPathPrefix(containerSegments, fullPath)) {
1151
+ continue;
1190
1152
  }
1191
- };
1192
- collect(cellEntriesFor(state.errorCells, "schema"), true);
1193
- collect(state.derivedBlankErrors.value, true);
1194
- collect(cellEntriesFor(state.errorCells, "user"), false);
1153
+ const errors = [];
1154
+ collectErrorsAt(state, key, fullPath, isRoot || hasAtPath(formValue, fullPath), errors);
1155
+ if (errors.length === 0) continue;
1156
+ const relativePath = fullPath.slice(containerSegments.length);
1157
+ let placePath;
1158
+ if (isRoot) {
1159
+ placePath = [ROOT_PATH_KEY];
1160
+ } else if (relativePath.length === 0) {
1161
+ placePath = [""];
1162
+ } else if (state.schema.isLeafAtPath(fullPath)) {
1163
+ placePath = relativePath;
1164
+ } else if (state.schema.getSlimPrimitiveTypesAtPath(fullPath).size > 0) {
1165
+ placePath = [...relativePath, ""];
1166
+ } else {
1167
+ placePath = relativePath;
1168
+ }
1169
+ placements.push({ placePath, errors, ordinal: state.ensurePathOrdinal(key) });
1170
+ }
1171
+ placements.sort((a, b) => a.ordinal - b.ordinal);
1172
+ for (const { placePath, errors } of placements) placeAt(tree, placePath, errors);
1195
1173
  return tree;
1196
1174
  }
1197
1175
  function placeAt(tree, path, errors) {
@@ -1264,7 +1242,7 @@ function buildFieldsSurface(state, getFieldStateAt, sweep) {
1264
1242
  const viewCache = /* @__PURE__ */ new Map();
1265
1243
  sweep.onEvict((key) => viewCache.delete(key));
1266
1244
  function viewAt(segments) {
1267
- const cacheKey = JSON.stringify(segments);
1245
+ const cacheKey = keyForSegments(segments).key;
1268
1246
  const existing = viewCache.get(cacheKey);
1269
1247
  if (existing !== void 0) return existing;
1270
1248
  const { toString, valueOf, toJSON, toPrimitive } = makeReadonlyCoercion(
@@ -1832,45 +1810,6 @@ function buildFieldArrayApi(state) {
1832
1810
  };
1833
1811
  }
1834
1812
 
1835
- const SWEEP_SLICE = 8;
1836
- function createDynamicPathSweep(state) {
1837
- const dynamicKeys = /* @__PURE__ */ new Set();
1838
- const evictors = [];
1839
- const isDynamicPath = (segments) => {
1840
- for (let i = 0; i < segments.length; i++) {
1841
- if (typeof segments[i] === "number") return true;
1842
- if (!state.schema.isFixedObjectAtPath(segments.slice(0, i))) return true;
1843
- }
1844
- return false;
1845
- };
1846
- let sweepCursor = null;
1847
- state.onFormChange((next) => {
1848
- if (dynamicKeys.size === 0) return;
1849
- const raw = toRaw(next);
1850
- for (let i = 0; i < SWEEP_SLICE; i++) {
1851
- sweepCursor ?? (sweepCursor = dynamicKeys.values());
1852
- const step = sweepCursor.next();
1853
- if (step.done === true) {
1854
- sweepCursor = null;
1855
- break;
1856
- }
1857
- const segments = segmentsForPathKey(step.value);
1858
- if (segments !== null && hasAtPath(raw, segments)) continue;
1859
- dynamicKeys.delete(step.value);
1860
- for (const evict of evictors) evict(step.value);
1861
- }
1862
- });
1863
- return {
1864
- track(segments, key) {
1865
- if (dynamicKeys.has(key)) return;
1866
- if (isDynamicPath(segments)) dynamicKeys.add(key);
1867
- },
1868
- onEvict(evict) {
1869
- evictors.push(evict);
1870
- }
1871
- };
1872
- }
1873
-
1874
1813
  const warnedNoScopeStores = /* @__PURE__ */ new WeakSet() ;
1875
1814
  function isErrorInputLike(value) {
1876
1815
  if (value instanceof Error) return true;
@@ -1898,8 +1837,8 @@ function deriveSubmitErrors(err) {
1898
1837
  messageless: true
1899
1838
  };
1900
1839
  }
1901
- function buildProcessForm(state, formInstanceId, options = {}) {
1902
- const invalidPolicy = options.onInvalidSubmit ?? "focus-first-error";
1840
+ function buildProcessForm(state, options = {}) {
1841
+ const applyInvalidSubmit = options.applyInvalidSubmit;
1903
1842
  function validate(pathInput) {
1904
1843
  const result = ref({
1905
1844
  pending: true,
@@ -1997,7 +1936,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
1997
1936
  };
1998
1937
  }
1999
1938
  async function runRefinementValidation(data, path) {
2000
- return await state.schema.validateAtPath(data, path);
1939
+ return stampFormKey(await state.schema.validateAtPath(data, path), state.formKey);
2001
1940
  }
2002
1941
  function composeWithDerivedBlank(refinement, scope) {
2003
1942
  const blankErrors = collectScopedBlankErrors(state, scope);
@@ -2048,7 +1987,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
2048
1987
  }
2049
1988
  }
2050
1989
  if (generationStillValid) {
2051
- applyInvalidSubmitPolicy(state, formInstanceId, invalidPolicy);
1990
+ applyInvalidSubmit?.();
2052
1991
  }
2053
1992
  if (onError !== void 0) {
2054
1993
  try {
@@ -2065,7 +2004,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
2065
2004
  await onSubmit(merged.data);
2066
2005
  if (hasUserErrorEntries(state)) {
2067
2006
  if (state.submissionGeneration.value === genAtEntry) {
2068
- applyInvalidSubmitPolicy(state, formInstanceId, invalidPolicy);
2007
+ applyInvalidSubmit?.();
2069
2008
  }
2070
2009
  return;
2071
2010
  }
@@ -2083,7 +2022,7 @@ function buildProcessForm(state, formInstanceId, options = {}) {
2083
2022
  if (segments === null) continue;
2084
2023
  state.setUserErrorsForPath([...segments], bucket);
2085
2024
  }
2086
- applyInvalidSubmitPolicy(state, formInstanceId, invalidPolicy);
2025
+ applyInvalidSubmit?.();
2087
2026
  if (messageless) {
2088
2027
  console.warn(
2089
2028
  "[attaform] handleSubmit callback threw a non-Error value; throw an Error or a ValidationError ({ message, path? }) so the failure surfaces with a usable message."
@@ -2103,6 +2042,10 @@ function buildProcessForm(state, formInstanceId, options = {}) {
2103
2042
  };
2104
2043
  return { validate, parse, handleSubmit };
2105
2044
  }
2045
+ function stampFormKey(verdict, formKey) {
2046
+ if (verdict.success) return { ...verdict, formKey };
2047
+ return verdict.data === void 0 ? { data: void 0, errors: verdict.errors, success: false, formKey } : { data: verdict.data, errors: verdict.errors, success: false, formKey };
2048
+ }
2106
2049
  function toSegments(pathInput) {
2107
2050
  return canonicalizePath(pathInput).segments;
2108
2051
  }
@@ -2136,24 +2079,9 @@ function collectScopedBlankErrors(state, scope) {
2136
2079
  }
2137
2080
  return errors;
2138
2081
  }
2139
- function applyInvalidSubmitPolicy(state, formInstanceId, policy) {
2140
- if (policy === "none") return;
2141
- const target = state.domBinding.value?.getFirstErrorElement(formInstanceId) ?? null;
2142
- if (target === null) return;
2143
- if (policy === "scroll-to-first-error") {
2144
- target.element.scrollIntoView();
2145
- return;
2146
- }
2147
- if (policy === "focus-first-error") {
2148
- target.element.focus({ focusVisible: true });
2149
- return;
2150
- }
2151
- target.element.scrollIntoView();
2152
- target.element.focus({ preventScroll: true, focusVisible: true });
2153
- }
2154
2082
 
2155
2083
  const warnedRejections = /* @__PURE__ */ new WeakMap() ;
2156
- function shouldWarnOnce$1(store, key) {
2084
+ function shouldWarnOnce(store, key) {
2157
2085
  if (warnedRejections === null) return false;
2158
2086
  let set = warnedRejections.get(store);
2159
2087
  if (set === void 0) {
@@ -2229,12 +2157,12 @@ function walk(schema, store, path, value) {
2229
2157
  function reportRejection(store, path, kind, accepted) {
2230
2158
  const dotted = path.map((s) => String(s)).join(".") || "(root)";
2231
2159
  const key = `${dotted}::${kind}`;
2232
- if (!shouldWarnOnce$1(store, key)) return;
2160
+ if (!shouldWarnOnce(store, key)) return;
2233
2161
  if (accepted.size === 0) {
2234
2162
  console.warn(
2235
- `[attaform] Cannot write to '${dotted}' \u2014 this path is not in your schema.
2163
+ `[attaform] Cannot write to '${dotted}': this path is not in your schema.
2236
2164
  Fix: check for a typo in register('${dotted}'); it should match a leaf key in your schema.
2237
- (If the path resolves to a never-typed schema, it explicitly admits no values \u2014 relax the schema if intentional.)
2165
+ (If the path resolves to a never-typed schema, it explicitly admits no values; relax the schema if intentional.)
2238
2166
  The write was a no-op.`
2239
2167
  );
2240
2168
  return;
@@ -2242,7 +2170,7 @@ function reportRejection(store, path, kind, accepted) {
2242
2170
  const expected = formatExpectedKinds(accepted);
2243
2171
  if (kind === "string" && accepted.has("number")) {
2244
2172
  console.warn(
2245
- `[attaform] Cannot write a string to '${dotted}' \u2014 the schema expects ${expected}.
2173
+ `[attaform] Cannot write a string to '${dotted}': the schema expects ${expected}.
2246
2174
  Fix: add type="number" to the input, OR use the .number modifier on v-register:
2247
2175
  <input type="number" v-register="register('${dotted}')" />
2248
2176
  <input v-register.number="register('${dotted}')" />
@@ -2251,7 +2179,7 @@ function reportRejection(store, path, kind, accepted) {
2251
2179
  return;
2252
2180
  }
2253
2181
  console.warn(
2254
- `[attaform] Cannot write a ${kind} to '${dotted}' \u2014 the schema expects ${expected}.
2182
+ `[attaform] Cannot write a ${kind} to '${dotted}': the schema expects ${expected}.
2255
2183
  The write was a no-op.`
2256
2184
  );
2257
2185
  }
@@ -2262,56 +2190,9 @@ function formatExpectedKinds(accepted) {
2262
2190
  return `one of: ${list.join(", ")}`;
2263
2191
  }
2264
2192
 
2265
- function defineCoercion(entry) {
2266
- return entry;
2267
- }
2268
2193
  const IDENTITY = (v) => v;
2269
- const EMPTY_INDEX = /* @__PURE__ */ new Map();
2270
- const defaultCoercionRules = [
2271
- defineCoercion({
2272
- input: "string",
2273
- output: "number",
2274
- transform: (s) => {
2275
- const trimmed = s.trim();
2276
- if (trimmed === "") return { coerced: false };
2277
- const n = Number(trimmed);
2278
- if (!Number.isFinite(n)) return { coerced: false };
2279
- return { coerced: true, value: n };
2280
- }
2281
- }),
2282
- defineCoercion({
2283
- input: "string",
2284
- output: "boolean",
2285
- transform: (s) => {
2286
- const normalized = s.trim().toLowerCase();
2287
- if (normalized === "true") return { coerced: true, value: true };
2288
- if (normalized === "false") return { coerced: true, value: false };
2289
- return { coerced: false };
2290
- }
2291
- })
2292
- ];
2293
- function resolveCoercionIndex(config) {
2294
- if (config === false) return EMPTY_INDEX;
2295
- const rules = config === void 0 || config === true ? defaultCoercionRules : config;
2296
- return indexRules(rules);
2297
- }
2298
- function indexRules(rules) {
2299
- const idx = /* @__PURE__ */ new Map();
2300
- for (const entry of rules) {
2301
- const candidate = entry;
2302
- if (candidate === null || typeof candidate !== "object" || typeof candidate.transform !== "function") {
2303
- {
2304
- console.warn("[attaform] coercion entry missing or invalid `transform` \u2014 skipped.");
2305
- }
2306
- continue;
2307
- }
2308
- const key = `${entry.input}->${entry.output}`;
2309
- if (idx.has(key) && true) {
2310
- console.warn(`[attaform] duplicate coercion rule for '${key}' \u2014 last entry wins.`);
2311
- }
2312
- idx.set(key, entry);
2313
- }
2314
- return idx;
2194
+ function resolveCoerceEnabled(config) {
2195
+ return config !== false;
2315
2196
  }
2316
2197
  function memberSlimTypes(schema, segments, accepted) {
2317
2198
  if (accepted.has("array")) return schema.getSlimPrimitiveTypesAtPath([...segments, 0]);
@@ -2320,119 +2201,84 @@ function memberSlimTypes(schema, segments, accepted) {
2320
2201
  }
2321
2202
  return void 0;
2322
2203
  }
2323
- function buildCoerceFn(schema, segments, index) {
2324
- if (index === EMPTY_INDEX) return IDENTITY;
2325
- if (index.size === 0) return IDENTITY;
2204
+ function buildCoerceFn(schema, segments, enabled) {
2205
+ if (!enabled) return IDENTITY;
2326
2206
  const accepted = schema.getSlimPrimitiveTypesAtPath(segments);
2327
2207
  const elementAccepted = memberSlimTypes(schema, segments, accepted);
2328
- return (value) => coerceValue(value, accepted, elementAccepted, index);
2208
+ return (value) => coerceValue(value, accepted, elementAccepted);
2329
2209
  }
2330
- function buildElementCoerceFn(schema, segments, index) {
2331
- if (index === EMPTY_INDEX) return void 0;
2332
- if (index.size === 0) return void 0;
2210
+ function buildElementCoerceFn(schema, segments, enabled) {
2211
+ if (!enabled) return void 0;
2333
2212
  const accepted = schema.getSlimPrimitiveTypesAtPath(segments);
2334
2213
  const elementAccepted = memberSlimTypes(schema, segments, accepted);
2335
2214
  if (elementAccepted === void 0) return void 0;
2336
- return (value) => coerceScalar(value, elementAccepted, index);
2215
+ return (value) => coerceScalar(value, elementAccepted);
2337
2216
  }
2338
2217
  function pickScalarTarget(accepted) {
2339
2218
  if (accepted.has("string")) return null;
2340
2219
  if (accepted.has("number")) return "number";
2341
2220
  if (accepted.has("boolean")) return "boolean";
2342
- if (accepted.has("bigint")) return "bigint";
2343
2221
  return null;
2344
2222
  }
2345
- const warnedCoerce = /* @__PURE__ */ new WeakMap() ;
2346
- const sharedWarnStore = {};
2347
- function shouldWarnOnce(key) {
2348
- if (warnedCoerce === null) return false;
2349
- let set = warnedCoerce.get(sharedWarnStore);
2350
- if (set === void 0) {
2351
- set = /* @__PURE__ */ new Set();
2352
- warnedCoerce.set(sharedWarnStore, set);
2353
- }
2354
- if (set.has(key)) return false;
2355
- set.add(key);
2356
- return true;
2223
+ function toNumber(source) {
2224
+ const trimmed = source.trim();
2225
+ if (trimmed === "") return source;
2226
+ const n = Number(trimmed);
2227
+ return Number.isFinite(n) ? n : source;
2228
+ }
2229
+ function toBoolean(source) {
2230
+ const normalized = source.trim().toLowerCase();
2231
+ if (normalized === "true") return true;
2232
+ if (normalized === "false") return false;
2233
+ return source;
2357
2234
  }
2358
- function coerceScalar(value, accepted, index) {
2235
+ function coerceScalar(value, accepted) {
2359
2236
  if (accepted.size === 0) return value;
2360
2237
  const sourceKind = slimKindOf(value);
2361
2238
  if (accepted.has(sourceKind)) return value;
2239
+ if (sourceKind !== "string") return value;
2362
2240
  const target = pickScalarTarget(accepted);
2363
- if (target === null) return value;
2364
- const entry = index.get(`${sourceKind}->${target}`);
2365
- if (entry === void 0) return value;
2366
- let result;
2367
- try {
2368
- result = entry.transform(value);
2369
- } catch (err) {
2370
- if (shouldWarnOnce(`${entry.input}->${entry.output}::throw`)) {
2371
- console.warn(
2372
- `[attaform] coercion '${entry.input}->${entry.output}' threw \u2014 write passes through.`,
2373
- err
2374
- );
2375
- }
2376
- return value;
2377
- }
2378
- if (!result.coerced) return value;
2379
- const returnedKind = slimKindOf(result.value);
2380
- if (returnedKind !== entry.output) {
2381
- if (shouldWarnOnce(`${entry.input}->${entry.output}::wrong-kind:${returnedKind}`)) {
2382
- console.warn(
2383
- `[attaform] coercion '${entry.input}->${entry.output}' produced a ${returnedKind} \u2014 write passes through.`
2384
- );
2385
- }
2386
- return value;
2387
- }
2388
- if (entry.output === "number" && !Number.isFinite(result.value)) {
2389
- if (shouldWarnOnce(`${entry.input}->${entry.output}::nan`)) {
2390
- console.warn(
2391
- `[attaform] coercion '${entry.input}->${entry.output}' produced a non-finite number \u2014 write passes through.`
2392
- );
2393
- }
2394
- return value;
2395
- }
2396
- return result.value;
2241
+ if (target === "number") return toNumber(value);
2242
+ if (target === "boolean") return toBoolean(value);
2243
+ return value;
2397
2244
  }
2398
- function coerceArrayMembers(arr, elementAccepted, index) {
2245
+ function coerceArrayMembers(arr, elementAccepted) {
2399
2246
  let changed = false;
2400
2247
  const out = [];
2401
2248
  for (const el of arr) {
2402
- const next = coerceScalar(el, elementAccepted, index);
2249
+ const next = coerceScalar(el, elementAccepted);
2403
2250
  if (next !== el) changed = true;
2404
2251
  out.push(next);
2405
2252
  }
2406
2253
  return changed ? out : arr;
2407
2254
  }
2408
- function coerceSetMembers(set, elementAccepted, index) {
2255
+ function coerceSetMembers(set, elementAccepted) {
2409
2256
  let changed = false;
2410
2257
  const out = [];
2411
2258
  for (const el of set) {
2412
- const next = coerceScalar(el, elementAccepted, index);
2259
+ const next = coerceScalar(el, elementAccepted);
2413
2260
  if (next !== el) changed = true;
2414
2261
  out.push(next);
2415
2262
  }
2416
2263
  return changed ? new Set(out) : set;
2417
2264
  }
2418
- function coerceValue(value, accepted, elementAccepted, index) {
2265
+ function coerceValue(value, accepted, elementAccepted) {
2419
2266
  if (Array.isArray(value)) {
2420
2267
  if (!accepted.has("array") || elementAccepted === void 0) return value;
2421
- return coerceArrayMembers(value, elementAccepted, index);
2268
+ return coerceArrayMembers(value, elementAccepted);
2422
2269
  }
2423
2270
  if (value instanceof Set) {
2424
2271
  if (!accepted.has("set") || elementAccepted === void 0) return value;
2425
- return coerceSetMembers(value, elementAccepted, index);
2272
+ return coerceSetMembers(value, elementAccepted);
2426
2273
  }
2427
- return coerceScalar(value, accepted, index);
2274
+ return coerceScalar(value, accepted);
2428
2275
  }
2429
2276
 
2430
2277
  const warnedMultiRootHosts = /* @__PURE__ */ new Set();
2431
2278
  const EMPTY_TRANSFORMS = Object.freeze([]);
2432
2279
  function buildRegister(state, formInstanceId, instanceConfig) {
2433
- const coerceIndex = instanceConfig?.coerce !== void 0 ? resolveCoercionIndex(instanceConfig.coerce) : state.coerceIndex;
2280
+ const coerceEnabled = instanceConfig?.coerce !== void 0 ? resolveCoerceEnabled(instanceConfig.coerce) : state.coerceEnabled;
2434
2281
  const instanceMeta = instanceConfig?.instanceMeta;
2435
- const formAutoAria = instanceConfig?.autoAria ?? true;
2436
2282
  const getDisplayStateAt = instanceConfig?.getDisplayStateAt;
2437
2283
  const withInstanceMeta = (meta) => {
2438
2284
  if (instanceMeta === void 0) return meta;
@@ -2472,16 +2318,15 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2472
2318
  const coerce = buildCoerceFn(
2473
2319
  state.schema,
2474
2320
  segments,
2475
- coerceIndex
2321
+ coerceEnabled
2476
2322
  );
2477
2323
  const coerceElement = buildElementCoerceFn(
2478
2324
  state.schema,
2479
2325
  segments,
2480
- coerceIndex
2326
+ coerceEnabled
2481
2327
  );
2482
2328
  const { aria } = computeFieldIdentity(formInstanceId, state.formKey, pathKey);
2483
2329
  const isRequired = state.schema.isRequiredAtPath(segments);
2484
- const ariaEnabled = options?.autoAria ?? formAutoAria;
2485
2330
  const ariaDisplayState = getDisplayStateAt !== void 0 ? computed(() => getDisplayStateAt(segments)) : void 0;
2486
2331
  const markBlank = () => state.setValueAtPath(segments, slimDefault, withInstanceMeta({ blank: true }));
2487
2332
  const internalRv = {
@@ -2539,7 +2384,7 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2539
2384
  }
2540
2385
  return accepted;
2541
2386
  },
2542
- // Called by the `vRegisterHint` compile-time transform's wrapping
2387
+ // Called by the `vRegisterHintTransform` compile-time transform's wrapping
2543
2388
  // IIFE on every server-side render of `<element v-register="…">`.
2544
2389
  // Without it, every SSR'd FieldState serialises `connected: false`
2545
2390
  // (because Vue skips directive lifecycle during SSR) and the client
@@ -2568,12 +2413,12 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2568
2413
  }
2569
2414
  return false;
2570
2415
  },
2571
- // --- Async transform lifecycle (internal; the directive's
2572
- // deferred orchestrator is the only legitimate consumer). Thin
2573
- // path-bound delegates to the store's per-path token / counter
2574
- // machinery — same pattern as `markBlank` / `setValueWithInternalPath`,
2575
- // so the directive (which holds only this RegisterValue, never the
2576
- // store) can drive the busy/discard/error bookkeeping. ---
2416
+ // --- Async transform lifecycle (internal; the directive's deferred
2417
+ // orchestrator is the only legitimate consumer). Thin path-bound
2418
+ // delegates to the store's per-path token and counter machinery,
2419
+ // the same pattern as `markBlank` and `setValueWithInternalPath`, so
2420
+ // the directive (which holds this RegisterValue and never the store)
2421
+ // can drive the busy / discard / error bookkeeping. ---
2577
2422
  beginTransform: (holder) => state.beginTransform(pathKey, holder),
2578
2423
  isCurrentTransform: (token) => state.isCurrentTransform(pathKey, token),
2579
2424
  endTransform: (token) => state.endTransform(pathKey, token),
@@ -2586,10 +2431,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2586
2431
  return (state.fieldTransformCounts.get(pathKey) ?? 0) > 0;
2587
2432
  },
2588
2433
  path: pathKey,
2589
- // Frozen so a wrapper component can pass `rv.segments` directly
2590
- // to `form.fields(...)` without defensive copying — and so test
2591
- // fixtures or downstream code can't mutate the canonical
2592
- // segment list out from under the directive.
2434
+ // Frozen so a wrapper component can pass `rv.segments` straight to
2435
+ // `form.fields(...)` with no defensive copy, and so neither a test
2436
+ // fixture nor downstream code can mutate the canonical segment list
2437
+ // out from under the directive.
2593
2438
  segments: Object.freeze(segments.slice()),
2594
2439
  formKey: state.formKey,
2595
2440
  formInstanceId,
@@ -2601,7 +2446,6 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2601
2446
  // --- Aria (internal; consumed by the directive) ---
2602
2447
  aria,
2603
2448
  isRequired,
2604
- ariaEnabled,
2605
2449
  ...ariaDisplayState !== void 0 ? { ariaDisplayState } : {}
2606
2450
  };
2607
2451
  return shallowReadonly(internalRv);
@@ -2765,11 +2609,50 @@ const DERIVED_DISPLAY_KEYS = /* @__PURE__ */ new Set([
2765
2609
  "firstError",
2766
2610
  "firstOwnError"
2767
2611
  ]);
2768
- const META_SPECIAL_KEYS = /* @__PURE__ */ new Set(["errors", "validating", "valid", "transforming", "busy"]);
2769
- const BASE_MIRROR_KEYS = [...FIELD_STATE_KEYS].filter((k) => !DERIVED_DISPLAY_KEYS.has(k));
2770
- const META_MIRROR_KEYS = [...FIELD_STATE_KEYS].filter((k) => !META_SPECIAL_KEYS.has(k));
2771
- function defineGetter(target, key, get) {
2772
- Object.defineProperty(target, key, { get, enumerable: true, configurable: true });
2612
+ const META_KEYS = [
2613
+ ...FIELD_STATE_KEYS,
2614
+ "submitting",
2615
+ "submissionAttempts",
2616
+ "departAttempts",
2617
+ "submitError",
2618
+ "submitted",
2619
+ "instanceId",
2620
+ "errorCount"
2621
+ ];
2622
+ const BASE_META_KEYS = META_KEYS.filter((k) => !DERIVED_DISPLAY_KEYS.has(k));
2623
+ let INERT_HISTORY;
2624
+ function inertHistory() {
2625
+ INERT_HISTORY ?? (INERT_HISTORY = readonly({
2626
+ undo: () => false,
2627
+ redo: () => false,
2628
+ clear: () => {
2629
+ },
2630
+ canUndo: false,
2631
+ canRedo: false,
2632
+ size: 0
2633
+ }));
2634
+ return INERT_HISTORY;
2635
+ }
2636
+ const denyWrite = () => false;
2637
+ function buildMetaProxy(keys, read) {
2638
+ const target = {};
2639
+ const owns = new Set(keys);
2640
+ return new Proxy(target, {
2641
+ get: (_, key) => owns.has(key) ? read(key) : Reflect.get(target, key),
2642
+ has: (_, key) => owns.has(key) || Reflect.has(target, key),
2643
+ // Safe to hand back the same array every time: the spec copies a trap's
2644
+ // key list before using it.
2645
+ ownKeys: () => keys,
2646
+ getOwnPropertyDescriptor(_, key) {
2647
+ if (!owns.has(key)) return Reflect.getOwnPropertyDescriptor(target, key);
2648
+ return { configurable: true, enumerable: true, value: read(key), writable: false };
2649
+ },
2650
+ // A getter with no setter throws on assignment under strict mode, which
2651
+ // every module here is; `false` keeps that.
2652
+ set: denyWrite,
2653
+ deleteProperty: denyWrite,
2654
+ defineProperty: denyWrite
2655
+ });
2773
2656
  }
2774
2657
  function buildFormApi(state, formInstanceId, options = {}) {
2775
2658
  const instanceBag = pickDefined({
@@ -2796,32 +2679,26 @@ function buildFormApi(state, formInstanceId, options = {}) {
2796
2679
  const rootBaseComputed = computed(
2797
2680
  () => buildContainerFieldStateBase(state, ROOT_PATH, ROOT_PATH_KEY, formInstanceId).base
2798
2681
  );
2799
- const metaBase = {
2800
- instanceId: formInstanceId,
2801
- get submitting() {
2802
- return state.submitting.value;
2803
- },
2804
- get submissionAttempts() {
2805
- return state.submissionAttempts.value;
2806
- },
2807
- get departAttempts() {
2808
- return state.departAttempts.value;
2809
- },
2810
- get submitError() {
2811
- return state.submitError.value;
2812
- },
2813
- get submitted() {
2814
- return state.submitted.value;
2682
+ const metaBase = buildMetaProxy(BASE_META_KEYS, (key) => {
2683
+ switch (key) {
2684
+ case "instanceId":
2685
+ return formInstanceId;
2686
+ case "errorCount":
2687
+ return rootBaseComputed.value.errors.length;
2688
+ case "submitting":
2689
+ return state.submitting.value;
2690
+ case "submissionAttempts":
2691
+ return state.submissionAttempts.value;
2692
+ case "departAttempts":
2693
+ return state.departAttempts.value;
2694
+ case "submitError":
2695
+ return state.submitError.value;
2696
+ case "submitted":
2697
+ return state.submitted.value;
2698
+ default:
2699
+ return rootBaseComputed.value[key];
2815
2700
  }
2816
- };
2817
- for (const k of BASE_MIRROR_KEYS) {
2818
- defineGetter(
2819
- metaBase,
2820
- k,
2821
- () => rootBaseComputed.value[k]
2822
- );
2823
- }
2824
- defineGetter(metaBase, "errorCount", () => rootBaseComputed.value.errors.length);
2701
+ });
2825
2702
  const getFormMetaBase = () => {
2826
2703
  void state.submitting.value;
2827
2704
  void state.submissionAttempts.value;
@@ -2830,28 +2707,41 @@ function buildFormApi(state, formInstanceId, options = {}) {
2830
2707
  void state.submitted.value;
2831
2708
  return metaBase;
2832
2709
  };
2833
- const fieldStateAccessorOptions = options.getDisplayState !== void 0 ? { getDisplayState: options.getDisplayState } : void 0;
2834
- const pathSweep = createDynamicPathSweep(state);
2710
+ const pathSweep = state.pathSweep;
2835
2711
  const getRootFieldStateAt = buildFieldStateAccessor(
2836
2712
  state,
2837
2713
  formInstanceId,
2838
2714
  getFormMetaBase,
2839
- pathSweep,
2840
- fieldStateAccessorOptions
2715
+ pathSweep
2841
2716
  );
2842
2717
  const getDisplayStateAt = (segments) => getRootFieldStateAt(segments).value.displayState;
2843
2718
  const registerConfig = {
2844
- ...pickDefined({ instanceMeta, coerce: options.coerce, autoAria: options.autoAria }),
2719
+ ...pickDefined({ instanceMeta, coerce: options.coerce }),
2845
2720
  getDisplayStateAt
2846
2721
  };
2847
2722
  const register = buildRegister(state, formInstanceId, registerConfig);
2848
- const processOptions = pickDefined({ onInvalidSubmit: options.onInvalidSubmit });
2849
- const defaultInvalidSubmitPolicy = options.onInvalidSubmit ?? "focus-first-error";
2723
+ const focusFirstError = (options2) => {
2724
+ const target = state.domBinding.value?.getFirstErrorElement(formInstanceId) ?? null;
2725
+ if (target === null) return false;
2726
+ target.element.focus({ focusVisible: true, ...options2 });
2727
+ return true;
2728
+ };
2729
+ const scrollToFirstError = (options2) => {
2730
+ const target = state.domBinding.value?.getFirstErrorElement(formInstanceId) ?? null;
2731
+ if (target === null) return false;
2732
+ target.element.scrollIntoView(options2);
2733
+ return true;
2734
+ };
2735
+ const applyInvalidSubmitPolicyPublic = () => {
2736
+ if (options.focusOnInvalidSubmit !== false) focusFirstError();
2737
+ };
2850
2738
  const {
2851
2739
  validate: validateBuilt,
2852
2740
  parse: parseBuilt,
2853
2741
  handleSubmit
2854
- } = buildProcessForm(state, formInstanceId, processOptions);
2742
+ } = buildProcessForm(state, {
2743
+ applyInvalidSubmit: applyInvalidSubmitPolicyPublic
2744
+ });
2855
2745
  const validate = (pathInput) => validateBuilt(pathInput);
2856
2746
  const parse = (arg1, arg2) => {
2857
2747
  const isPathArg = typeof arg1 === "string" || Array.isArray(arg1);
@@ -2979,28 +2869,26 @@ function buildFormApi(state, formInstanceId, options = {}) {
2979
2869
  () => state.firstValidationDone.value && state.errorCells.size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
2980
2870
  );
2981
2871
  const history = options.history;
2982
- const formHistory = readonly(
2872
+ const formHistory = history === void 0 ? inertHistory() : readonly(
2983
2873
  reactive({
2984
- undo: history?.undo ?? (() => false),
2985
- redo: history?.redo ?? (() => false),
2986
- clear: history?.clear ?? (() => {
2987
- }),
2988
- canUndo: history?.canUndo ?? computed(() => false),
2989
- canRedo: history?.canRedo ?? computed(() => false),
2990
- size: history?.historySize ?? computed(() => 0)
2874
+ undo: history.undo,
2875
+ redo: history.redo,
2876
+ clear: history.clear,
2877
+ canUndo: history.canUndo,
2878
+ canRedo: history.canRedo,
2879
+ size: history.historySize
2991
2880
  })
2992
2881
  );
2993
2882
  const metaErrors = computed(
2994
- () => aggregateErrorsAt(state, [])
2883
+ () => aggregateErrorsAt(state, [], ROOT_PATH_KEY)
2995
2884
  );
2996
2885
  const rootFieldState = getRootFieldStateAt([]);
2997
- const metaTarget = {
2998
- // Whole-form work signals compose the LIFECYCLE counters with the
2999
- // per-leaf rollup: a submit-time validate shows up as
3000
- // activeValidations, per-field debounced validators as
3001
- // fieldValidationCounts — either flips the flag. `valid` keeps the
3002
- // form-level `firstValidationDone` mount gate; `errors` is the
3003
- // unfiltered whole-form aggregate.
2886
+ const metaOwn = {
2887
+ // Whole-form work signals compose the LIFECYCLE counters with the per-leaf
2888
+ // rollup: a submit-time validate shows up in `activeValidations` and a
2889
+ // per-field debounced validator in `fieldValidationCounts`, and either
2890
+ // flips the flag. `valid` keeps the form-level mount gate, and `errors` is
2891
+ // the unfiltered whole-form aggregate.
3004
2892
  validating: computed(
3005
2893
  () => state.activeValidations.value > 0 || rootFieldState.value.validating
3006
2894
  ),
@@ -3012,7 +2900,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
3012
2900
  busy: computed(
3013
2901
  () => state.activeValidations.value > 0 || state.activeTransforms.value > 0 || rootFieldState.value.validating || rootFieldState.value.transforming
3014
2902
  ),
3015
- // Lifecycle (form-level only — not on FieldState).
2903
+ // Lifecycle, form-level only and never on FieldState.
3016
2904
  submitting,
3017
2905
  submissionAttempts,
3018
2906
  departAttempts,
@@ -3023,14 +2911,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
3023
2911
  // identifier).
3024
2912
  instanceId: formInstanceId
3025
2913
  };
3026
- for (const k of META_MIRROR_KEYS) {
3027
- defineGetter(
3028
- metaTarget,
3029
- k,
3030
- () => rootFieldState.value[k]
3031
- );
3032
- }
3033
- defineGetter(metaTarget, "errorCount", () => metaErrors.value.length);
2914
+ const metaTarget = buildMetaProxy(META_KEYS, (key) => {
2915
+ if (key === "errorCount") return metaErrors.value.length;
2916
+ if (Object.hasOwn(metaOwn, key)) return metaOwn[key];
2917
+ return rootFieldState.value[key];
2918
+ });
3034
2919
  const formMeta = readonly(reactive(metaTarget));
3035
2920
  const reset = (nextDefaultValues) => {
3036
2921
  if (nextDefaultValues === void 0) {
@@ -3070,21 +2955,6 @@ function buildFormApi(state, formInstanceId, options = {}) {
3070
2955
  } catch {
3071
2956
  }
3072
2957
  }
3073
- const focusFirstError = (options2) => {
3074
- const target = state.domBinding.value?.getFirstErrorElement(formInstanceId) ?? null;
3075
- if (target === null) return false;
3076
- target.element.focus({ focusVisible: true, ...options2 });
3077
- return true;
3078
- };
3079
- const scrollToFirstError = (options2) => {
3080
- const target = state.domBinding.value?.getFirstErrorElement(formInstanceId) ?? null;
3081
- if (target === null) return false;
3082
- target.element.scrollIntoView(options2);
3083
- return true;
3084
- };
3085
- const applyInvalidSubmitPolicyPublic = (policy) => {
3086
- applyInvalidSubmitPolicy(state, formInstanceId, policy ?? defaultInvalidSubmitPolicy);
3087
- };
3088
2958
  const fieldArrays = buildFieldArrayApi(state);
3089
2959
  const blankPathsView = computed(() => {
3090
2960
  const keys = /* @__PURE__ */ new Set();
@@ -3147,10 +3017,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
3147
3017
  }
3148
3018
  const api = {
3149
3019
  handleSubmit: gated(handleSubmit),
3150
- // Callable readonly Proxies (`values`, `fields`, `errors`) and the
3151
- // reactive containers (`meta`, `history`, `blankPaths`) are exposed
3152
- // through getters so reading them activates the form on first
3153
- // touch. Each underlying object is identity-stable across reads.
3020
+ // The callable readonly Proxies (`values`, `fields`, `errors`) and the
3021
+ // reactive containers (`meta`, `history`, `blankPaths`) are exposed through
3022
+ // getters, so reading one activates the form on first touch. Each
3023
+ // underlying object is identity-stable across reads.
3154
3024
  get values() {
3155
3025
  void state.activate();
3156
3026
  return valuesProxy;
@@ -3167,7 +3037,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
3167
3037
  key: state.formKey,
3168
3038
  // Auto-unwrapping views over the per-store async-defaults lifecycle
3169
3039
  // refs (see FormStore.hydrating / hydrateError). Reading either
3170
- // activates the form — observing factory state implies use.
3040
+ // activates the form: observing factory state implies use.
3171
3041
  get hydrating() {
3172
3042
  void state.activate();
3173
3043
  return state.hydrating.value;
@@ -3176,20 +3046,18 @@ function buildFormApi(state, formInstanceId, options = {}) {
3176
3046
  void state.activate();
3177
3047
  return state.hydrateError.value;
3178
3048
  },
3179
- // Orthogonal to `hydrating` and `hydrateError`: `ready` flips true
3180
- // once defaults are applied (sync at construction or async factory
3181
- // resolved successfully). One-way latch — stays true through later
3182
- // refetches even when those refetches fail, so stale-while-
3183
- // revalidate UIs keep rendering the prior values while
3184
- // `hydrateError` surfaces the refresh failure.
3049
+ // Orthogonal to `hydrating` and `hydrateError`. `ready` flips true once
3050
+ // defaults are applied, whether sync at construction or from a factory
3051
+ // that resolved, and it is a one-way latch: it stays true through later
3052
+ // refetches even when those fail, so a stale-while-revalidate UI keeps
3053
+ // rendering the prior values while `hydrateError` surfaces the failure.
3185
3054
  get ready() {
3186
3055
  void state.activate();
3187
3056
  return state.defaultsResolved.value;
3188
3057
  },
3189
- // `rehydrate` and `activate` are themselves activation entry points
3190
- // — they fire the factory by design. Wrapping them with `gated`
3191
- // would double-fire (`state.activate()` plus the underlying call),
3192
- // so they call `state` directly.
3058
+ // `rehydrate` and `activate` are activation entry points themselves and
3059
+ // fire the factory by design, so wrapping them in `gated` would
3060
+ // double-fire. They call `state` directly.
3193
3061
  rehydrate: () => state.rehydrate(),
3194
3062
  activate: () => state.activate(),
3195
3063
  get errors() {
@@ -3259,6 +3127,45 @@ function shortenSourceFrame(frame) {
3259
3127
  return `(${path}:${line})`;
3260
3128
  }
3261
3129
 
3130
+ const SWEEP_SLICE = 8;
3131
+ function createDynamicPathSweep(deps) {
3132
+ const dynamicKeys = /* @__PURE__ */ new Set();
3133
+ const evictors = [];
3134
+ const isDynamicPath = (segments) => {
3135
+ for (let i = 0; i < segments.length; i++) {
3136
+ if (typeof segments[i] === "number") return true;
3137
+ if (!deps.isFixedObjectAtPath(segments.slice(0, i))) return true;
3138
+ }
3139
+ return false;
3140
+ };
3141
+ let sweepCursor = null;
3142
+ deps.onFormChange((next) => {
3143
+ if (dynamicKeys.size === 0) return;
3144
+ const raw = toRaw(next);
3145
+ for (let i = 0; i < SWEEP_SLICE; i++) {
3146
+ sweepCursor ?? (sweepCursor = dynamicKeys.values());
3147
+ const step = sweepCursor.next();
3148
+ if (step.done === true) {
3149
+ sweepCursor = null;
3150
+ break;
3151
+ }
3152
+ const segments = segmentsForPathKey(step.value);
3153
+ if (segments !== null && hasAtPath(raw, segments)) continue;
3154
+ dynamicKeys.delete(step.value);
3155
+ for (const evict of evictors) evict(step.value);
3156
+ }
3157
+ });
3158
+ return {
3159
+ track(segments, key) {
3160
+ if (dynamicKeys.has(key)) return;
3161
+ if (isDynamicPath(segments)) dynamicKeys.add(key);
3162
+ },
3163
+ onEvict(evict) {
3164
+ evictors.push(evict);
3165
+ }
3166
+ };
3167
+ }
3168
+
3262
3169
  const IDLE = Object.freeze({ display: "idle" });
3263
3170
  const MAX_DELAY = 2147483647;
3264
3171
  const PENDING_LIVENESS_MS = 250;
@@ -3352,6 +3259,58 @@ function createDisplayEngine(ssr) {
3352
3259
  return engine;
3353
3260
  }
3354
3261
 
3262
+ function buildErrorPathIndex(cells, blank) {
3263
+ const keys = /* @__PURE__ */ new Set();
3264
+ for (const [key, cell] of cells) {
3265
+ if (cell.schema.length > 0 || cell.user.length > 0) keys.add(key);
3266
+ }
3267
+ for (const [key, list] of blank) {
3268
+ if (list.length > 0) keys.add(key);
3269
+ }
3270
+ if (keys.size === 0) return EMPTY_INDEX;
3271
+ const entries = [];
3272
+ for (const key of keys) {
3273
+ const segments = segmentsForPathKey(key);
3274
+ if (segments !== null) entries.push({ key, segments });
3275
+ }
3276
+ entries.sort(compareByKey);
3277
+ return entries;
3278
+ }
3279
+ const EMPTY_INDEX = Object.freeze([]);
3280
+ function compareByKey(a, b) {
3281
+ return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
3282
+ }
3283
+ function windowUnder(index, prefix) {
3284
+ if (prefix.length === 0 || index.length === 0) return index;
3285
+ const prefixKey = keyForSegments(prefix).key;
3286
+ const marker = `${prefixKey.slice(0, -1)},`;
3287
+ const start = lowerBound(index, marker);
3288
+ if (start >= index.length) return EMPTY_INDEX;
3289
+ let end = start;
3290
+ for (; end < index.length; end += 1) {
3291
+ const entry = index[end];
3292
+ if (entry === void 0 || entry.key > prefixKey) break;
3293
+ }
3294
+ return start === 0 && end === index.length ? index : index.slice(start, end);
3295
+ }
3296
+ function isSameWindow(a, b) {
3297
+ if (a.length !== b.length) return false;
3298
+ for (let i = 0; i < a.length; i += 1) {
3299
+ if (a[i]?.key !== b[i]?.key) return false;
3300
+ }
3301
+ return true;
3302
+ }
3303
+ function lowerBound(index, target) {
3304
+ let lo = 0;
3305
+ let hi = index.length;
3306
+ while (lo < hi) {
3307
+ const mid = lo + hi >>> 1;
3308
+ if ((index[mid]?.key ?? target) < target) lo = mid + 1;
3309
+ else hi = mid;
3310
+ }
3311
+ return lo;
3312
+ }
3313
+
3355
3314
  function mergeSparseHydration(schemaDefaults, sparse, schema) {
3356
3315
  return mergeDeep(schemaDefaults, sparse, [], schema);
3357
3316
  }
@@ -3578,16 +3537,15 @@ function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out)
3578
3537
  out.add(canonicalizePath(prefix).key);
3579
3538
  }
3580
3539
  }
3581
- function computeBaselineResponse(schema, strict, source) {
3540
+ function computeBaselineResponse(schema, source) {
3582
3541
  const completed = source === void 0 ? void 0 : mergeStructural(schema, [], source);
3583
3542
  return schema.getDefaultValues({
3584
3543
  useDefaultSchemaValues: true,
3585
- constraints: completed,
3586
- strict
3544
+ constraints: completed
3587
3545
  });
3588
3546
  }
3589
- function initialFirstValidationGate(schema, strict) {
3590
- return !strict || schema.needsAsyncValidation?.() !== true;
3547
+ function initialFirstValidationGate(schema) {
3548
+ return schema.needsAsyncValidation?.() !== true;
3591
3549
  }
3592
3550
  function seedOriginalsFromBaseline(st, baseline, ensureOrdinals) {
3593
3551
  st.originals.clear();
@@ -3599,7 +3557,7 @@ function seedOriginalsFromBaseline(st, baseline, ensureOrdinals) {
3599
3557
  });
3600
3558
  }
3601
3559
  function queueInitialAsyncValidation(st) {
3602
- if (!st.ssr && st.strict && st.schema.needsAsyncValidation?.() === true) {
3560
+ if (!st.ssr && st.schema.needsAsyncValidation?.() === true) {
3603
3561
  queueMicrotask(() => scheduleFieldValidation(
3604
3562
  st,
3605
3563
  [],
@@ -3641,10 +3599,6 @@ function ensurePathOrdinal(st, key) {
3641
3599
  }
3642
3600
  return ordinal;
3643
3601
  }
3644
- function pathHasAsyncValidation(st, path) {
3645
- const { key } = canonicalizePath(path);
3646
- return pathHasAsyncValidationByKey(st, key, path);
3647
- }
3648
3602
  function pathHasAsyncValidationByKey(st, key, segments) {
3649
3603
  const cached = st.pathAsyncCache.get(key);
3650
3604
  if (cached !== void 0) return cached;
@@ -3766,19 +3720,16 @@ function touchFieldRecord(st, pathKey, path, patch) {
3766
3720
  path,
3767
3721
  updatedAt: patch.updatedAt ?? current?.updatedAt ?? null,
3768
3722
  connected: patch.connected ?? current?.connected ?? false,
3769
- // focused/blurred use an explicit-undefined guard because
3770
- // patches legitimately carry `null` to mark a disconnect — the
3771
- // `??` operator would short-circuit on null and fall through to
3772
- // `current`, losing the intent. `!== undefined` honours an
3773
- // explicit null and preserves current only on absence.
3723
+ // `focused` / `blurred` need an explicit-undefined guard: a patch
3724
+ // legitimately carries `null` to mark a disconnect, and `??` would
3725
+ // short-circuit on it and fall through to `current`, losing the intent.
3774
3726
  focused: patch.focused !== void 0 ? patch.focused : current?.focused ?? null,
3775
3727
  blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
3776
- // touched is plain `boolean`; `??` is equivalent to the explicit
3777
- // guard here because `false` is not nullish.
3728
+ // `touched` is a plain boolean, so `??` is equivalent to the guard above.
3778
3729
  touched: patch.touched ?? current?.touched ?? false,
3779
- // interacted is sticky-true; a merge patch only ever sets it, so
3780
- // `??` preserves the current bit. It flips back to false solely
3781
- // through the reset paths, which reconstruct the record outright.
3730
+ // `interacted` is sticky-true and a merge patch only ever sets it, so `??`
3731
+ // preserves the current bit. Only the reset paths clear it, and they
3732
+ // reconstruct the record outright.
3782
3733
  interacted: patch.interacted ?? current?.interacted ?? false,
3783
3734
  blurredAfterInteraction: patch.blurredAfterInteraction ?? current?.blurredAfterInteraction ?? false
3784
3735
  });
@@ -3787,10 +3738,15 @@ function commitWritePatches(st, patches, meta) {
3787
3738
  const now = (/* @__PURE__ */ new Date()).toISOString();
3788
3739
  for (const patch of patches) {
3789
3740
  const { key } = canonicalizePath(patch.path);
3790
- if (patch.kind === "added" && !st.originals.has(key)) {
3741
+ if (!st.originals.has(key)) {
3791
3742
  st.originals.set(key, { segments: patch.path, value: void 0 });
3792
3743
  }
3793
3744
  touchFieldRecord(st, key, patch.path, { updatedAt: now });
3745
+ st.pathSweep.track(patch.path, key);
3746
+ for (let i = 1; i < patch.path.length; i++) {
3747
+ const ancestor = patch.path.slice(0, i);
3748
+ st.pathSweep.track(ancestor, canonicalizePath(ancestor).key);
3749
+ }
3794
3750
  }
3795
3751
  for (const listener of st.formChangeListeners) {
3796
3752
  try {
@@ -4001,10 +3957,7 @@ function setValueAtPath(st, path, value, meta) {
4001
3957
  if (newlyAuthored && st.schema.isPreprocessOrCoerceLeaf(path)) {
4002
3958
  const modeForAuthoringTransition = meta?.instance?.validateOn ?? st.fieldValidationMode;
4003
3959
  if (modeForAuthoringTransition === "change") {
4004
- scheduleFieldValidation(st, path, false, {
4005
- ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
4006
- ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
4007
- });
3960
+ scheduleFieldValidation(st, path, false, meta?.instance);
4008
3961
  }
4009
3962
  }
4010
3963
  return true;
@@ -4020,15 +3973,12 @@ function setValueAtPath(st, path, value, meta) {
4020
3973
  st.arrayIdentity.realign(path);
4021
3974
  } else if (isContainer(currentValue) && !isContainer(value)) {
4022
3975
  if (subtreeHadRealBaseline(st, path, currentValue)) {
4023
- st.removedSubtrees.add(canonicalizePath(path).key);
3976
+ st.removedSubtrees.add(pathKey);
4024
3977
  }
4025
3978
  }
4026
3979
  const effectiveModeAfterWrite = meta?.instance?.validateOn ?? st.fieldValidationMode;
4027
3980
  if (effectiveModeAfterWrite === "change") {
4028
- scheduleFieldValidation(st, path, false, {
4029
- ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
4030
- ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
4031
- });
3981
+ scheduleFieldValidation(st, path, false, meta?.instance);
4032
3982
  }
4033
3983
  return true;
4034
3984
  }
@@ -4089,12 +4039,11 @@ function reshapeUnionVariant(st, parentPath, oldDiscValue, newDiscValue, variant
4089
4039
  path: [...parentPath, ...err.path]
4090
4040
  }));
4091
4041
  applySchemaErrorsForSubtree(st, parentPath, reStamped);
4092
- const { key: parentKey2 } = canonicalizePath(parentPath);
4093
- const prevValidation = st.fieldValidationState.get(parentKey2);
4042
+ const prevValidation = st.fieldValidationState.get(parentKey);
4094
4043
  if (prevValidation !== void 0) {
4095
4044
  if (prevValidation.timer !== null) clearTimeout(prevValidation.timer);
4096
4045
  prevValidation.aborted = true;
4097
- st.fieldValidationState.delete(parentKey2);
4046
+ st.fieldValidationState.delete(parentKey);
4098
4047
  }
4099
4048
  appliedSync = true;
4100
4049
  }
@@ -4102,17 +4051,14 @@ function reshapeUnionVariant(st, parentPath, oldDiscValue, newDiscValue, variant
4102
4051
  applyFormReplacement(st, nextForm, meta);
4103
4052
  for (const k of newBlankPaths) st.blankPaths.add(k);
4104
4053
  if (reshapeMode === "change" && !appliedSync) {
4105
- scheduleFieldValidation(st, parentPath, false, {
4106
- ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
4107
- ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
4108
- });
4054
+ scheduleFieldValidation(st, parentPath, false, meta?.instance);
4109
4055
  }
4110
4056
  return true;
4111
4057
  }
4112
- function scheduleFieldValidation(st, path, immediate, override) {
4113
- const effectiveMode = override?.mode ?? st.fieldValidationMode;
4058
+ function scheduleFieldValidation(st, path, immediate, instance) {
4059
+ const effectiveMode = instance?.validateOn ?? st.fieldValidationMode;
4114
4060
  if (effectiveMode === "submit") return;
4115
- const effectiveDebounce = override?.debounceMs ?? st.fieldValidationDebounceMs;
4061
+ const effectiveDebounce = instance?.debounceMs ?? st.fieldValidationDebounceMs;
4116
4062
  const { key } = canonicalizePath(path);
4117
4063
  const prev = st.fieldValidationState.get(key);
4118
4064
  if (prev !== void 0) {
@@ -4352,13 +4298,13 @@ function markFocused(st, path, focused, meta) {
4352
4298
  touchFieldRecord(st, key, path, {
4353
4299
  focused,
4354
4300
  blurred: !focused,
4355
- // `touched` flips to true on blur and stays true thereafter; while
4356
- // a field is currently focused we keep whatever value it held.
4301
+ // `touched` flips true on blur and stays; a focused field keeps whatever
4302
+ // it held.
4357
4303
  touched: focused ? current?.touched ?? false : true,
4358
- // `blurredAfterInteraction` flips true on the first blur that lands
4359
- // after a value edit and stays true. A tab-through blur before any
4360
- // edit leaves it false (`interacted` is still false at that blur),
4361
- // which is what keeps a clean tab-through from arming the gate.
4304
+ // `blurredAfterInteraction` flips true on the first blur after a value
4305
+ // edit and stays. A tab-through blur before any edit leaves it false,
4306
+ // since `interacted` is still false, which is what keeps a clean
4307
+ // tab-through from arming the gate.
4362
4308
  blurredAfterInteraction: !focused && current?.interacted === true ? true : current?.blurredAfterInteraction ?? false
4363
4309
  });
4364
4310
  const focusMode = meta?.instance?.validateOn ?? st.fieldValidationMode;
@@ -4386,10 +4332,7 @@ function markFocused(st, path, focused, meta) {
4386
4332
  });
4387
4333
  }
4388
4334
  if (changed) {
4389
- scheduleFieldValidation(st, path, true, {
4390
- ...meta?.instance?.validateOn !== void 0 ? { mode: meta.instance.validateOn } : {},
4391
- ...meta?.instance?.debounceMs !== void 0 ? { debounceMs: meta.instance.debounceMs } : {}
4392
- });
4335
+ scheduleFieldValidation(st, path, true, meta?.instance);
4393
4336
  }
4394
4337
  }
4395
4338
  }
@@ -4402,11 +4345,10 @@ function markInteracted(st, path) {
4402
4345
  function touchAtPath(st, segments) {
4403
4346
  const formValue = st.form.value;
4404
4347
  let touchedAny = false;
4405
- for (const [, entry] of st.originals) {
4348
+ for (const [leafKey, entry] of st.originals) {
4406
4349
  if (!isPathPrefix(segments, entry.segments)) continue;
4407
4350
  if (!hasAtPath(formValue, entry.segments)) continue;
4408
4351
  touchedAny = true;
4409
- const leafKey = canonicalizePath(entry.segments).key;
4410
4352
  const current = st.fields.get(leafKey);
4411
4353
  if (current?.touched === true) continue;
4412
4354
  touchFieldRecord(st, leafKey, entry.segments, { touched: true });
@@ -4421,11 +4363,10 @@ function interactAtPath(st, segments) {
4421
4363
  if (st.effectiveDisabled.value) return false;
4422
4364
  const formValue = st.form.value;
4423
4365
  let interactedAny = false;
4424
- for (const [, entry] of st.originals) {
4366
+ for (const [leafKey, entry] of st.originals) {
4425
4367
  if (!isPathPrefix(segments, entry.segments)) continue;
4426
4368
  if (!hasAtPath(formValue, entry.segments)) continue;
4427
4369
  interactedAny = true;
4428
- const leafKey = canonicalizePath(entry.segments).key;
4429
4370
  const current = st.fields.get(leafKey);
4430
4371
  if (current?.touched === true && current.interacted === true && current.blurredAfterInteraction === true) {
4431
4372
  continue;
@@ -4482,11 +4423,7 @@ function adoptResolvedDefaults(st, value) {
4482
4423
  st.schema
4483
4424
  )
4484
4425
  );
4485
- seedOriginalsFromBaseline(
4486
- st,
4487
- computeBaselineResponse(st.schema, st.strict, st.defaultValues).data,
4488
- false
4489
- );
4426
+ seedOriginalsFromBaseline(st, computeBaselineResponse(st.schema, st.defaultValues).data, false);
4490
4427
  }
4491
4428
  function reset(st, nextDefaultValues) {
4492
4429
  const resetSource = nextDefaultValues === void 0 ? st.defaultValues : mergeSparseHydration(
@@ -4495,7 +4432,7 @@ function reset(st, nextDefaultValues) {
4495
4432
  st.schema
4496
4433
  );
4497
4434
  st.defaultValues = structuralSnapshot(resetSource);
4498
- const resetResponse = computeBaselineResponse(st.schema, st.strict, resetSource);
4435
+ const resetResponse = computeBaselineResponse(st.schema, resetSource);
4499
4436
  const next = resetResponse.data;
4500
4437
  rebuildAuthoredPaths(st, resetSource, next);
4501
4438
  applyFormReplacement(st, next);
@@ -4512,16 +4449,14 @@ function reset(st, nextDefaultValues) {
4512
4449
  st.blankPaths.add(key);
4513
4450
  }
4514
4451
  st.errorCells.clear();
4515
- if (st.strict && !resetResponse.success) {
4452
+ if (!resetResponse.success) {
4516
4453
  replaceErrorChannel(st, "schema", resetResponse.errors);
4517
4454
  }
4518
- if (st.strict) {
4519
- const syncResult = st.schema.validateAtPath(st.form.value, void 0, { sync: true });
4520
- if (!(syncResult instanceof Promise) && !syncResult.success) {
4521
- applySchemaErrorsForSubtree(st, [], syncResult.errors);
4522
- }
4455
+ const syncResult = st.schema.validateAtPath(st.form.value, void 0, { sync: true });
4456
+ if (!(syncResult instanceof Promise) && !syncResult.success) {
4457
+ applySchemaErrorsForSubtree(st, [], syncResult.errors);
4523
4458
  }
4524
- st.firstValidationDone.value = initialFirstValidationGate(st.schema, st.strict);
4459
+ st.firstValidationDone.value = initialFirstValidationGate(st.schema);
4525
4460
  queueInitialAsyncValidation(st);
4526
4461
  const now = (/* @__PURE__ */ new Date()).toISOString();
4527
4462
  for (const [pathKey, record] of st.fields) {
@@ -4565,7 +4500,7 @@ function resetField(st, path) {
4565
4500
  const wrote = setValueAtPath(st, targetSegments, leafEntry.value);
4566
4501
  if (!wrote) {
4567
4502
  console.error(
4568
- `[attaform] resetField: leaf write rejected for path '${targetKey}' \u2014 originals contain a value that doesn't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4503
+ `[attaform] resetField: leaf write rejected for path '${targetKey}'. Originals contain a value that doesn't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4569
4504
  );
4570
4505
  }
4571
4506
  } else {
@@ -4586,7 +4521,7 @@ function resetField(st, path) {
4586
4521
  const wroteSubtree = setValueAtPath(st, targetSegments, subtree);
4587
4522
  if (!wroteSubtree) {
4588
4523
  console.error(
4589
- `[attaform] resetField: subtree write rejected at path '${targetKey}' \u2014 originals contain values that don't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4524
+ `[attaform] resetField: subtree write rejected at path '${targetKey}'. Originals contain values that don't satisfy the slim primitive shape. This is a bug in the construction pipeline.`
4590
4525
  );
4591
4526
  }
4592
4527
  }
@@ -4611,10 +4546,6 @@ function clearFieldRecordFlags(st, pathKey) {
4611
4546
  if (record === void 0) return;
4612
4547
  st.fields.set(pathKey, withClearedHistoryFlags(record, (/* @__PURE__ */ new Date()).toISOString()));
4613
4548
  }
4614
- function isPristineAtPath(st, path) {
4615
- const { key, segments } = canonicalizePath(path);
4616
- return isPristineAtPathByKey(st, key, segments);
4617
- }
4618
4549
  function isPristineAtPathByKey(st, key, segments) {
4619
4550
  if (st.blankPaths.has(key) !== st.originalBlankPaths.has(key)) return false;
4620
4551
  const entry = st.originals.get(key);
@@ -4648,12 +4579,8 @@ function getFieldRecord(st, path) {
4648
4579
  const { key } = canonicalizePath(path);
4649
4580
  return st.fields.get(key);
4650
4581
  }
4651
- function getOriginalAtPath(st, path) {
4652
- const { key } = canonicalizePath(path);
4653
- return st.originals.get(key)?.value;
4654
- }
4655
4582
  function createFormStore(options) {
4656
- const { formKey, schema, defaultValues, strict = true, hydration } = options;
4583
+ const { formKey, schema, defaultValues, hydration } = options;
4657
4584
  const ssr = options.ssr === true;
4658
4585
  const ssrPrefetch = options.ssrPrefetch;
4659
4586
  const rememberVariants = options.rememberVariants !== false;
@@ -4661,22 +4588,16 @@ function createFormStore(options) {
4661
4588
  const fieldValidationDebounceMs = normalizeNumericOption({
4662
4589
  value: options.debounceMs ?? DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS,
4663
4590
  source: "useForm.debounceMs",
4664
- allowInfinity: false,
4665
4591
  min: 0,
4666
4592
  defaultValue: DEFAULT_FIELD_VALIDATION_DEBOUNCE_MS
4667
4593
  });
4668
- const coerceIndex = resolveCoercionIndex(options.coerce);
4669
- const resolvedGetDisplayState = resolveGetDisplayState(options.getDisplayState);
4594
+ const coerceEnabled = resolveCoerceEnabled(options.coerce);
4670
4595
  const cleanupHooks = [];
4671
4596
  const modules = /* @__PURE__ */ new Map();
4672
4597
  const fieldValidatingSince = reactive(/* @__PURE__ */ new Map());
4673
4598
  const displayEngine = createDisplayEngine(ssr);
4674
4599
  cleanupHooks.push(() => displayEngine.dispose());
4675
- const schemaResponse = computeBaselineResponse(
4676
- schema,
4677
- strict,
4678
- defaultValues
4679
- );
4600
+ const schemaResponse = computeBaselineResponse(schema, defaultValues);
4680
4601
  const schemaInitialData = schemaResponse.data;
4681
4602
  const authoredPaths = /* @__PURE__ */ new Set();
4682
4603
  const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
@@ -4689,16 +4610,15 @@ function createFormStore(options) {
4689
4610
  const v = getAtPath(toRaw(form.value), arraySegs);
4690
4611
  return Array.isArray(v) ? v.length : 0;
4691
4612
  });
4692
- const fields = reactive(/* @__PURE__ */ new Map());
4613
+ const fields = shallowReactive(/* @__PURE__ */ new Map());
4693
4614
  const domBinding = shallowRef(null);
4694
- const errorCells = reactive(/* @__PURE__ */ new Map());
4695
- const originals = reactive(/* @__PURE__ */ new Map());
4615
+ const errorCells = shallowReactive(/* @__PURE__ */ new Map());
4616
+ const originals = shallowReactive(/* @__PURE__ */ new Map());
4696
4617
  const removedSubtrees = /* @__PURE__ */ new Set();
4697
- const initialTransientList = hydration?.blankPaths ?? options.initialBlankPaths ?? [];
4698
4618
  const blankPaths = reactive(/* @__PURE__ */ new Set());
4699
4619
  const originalBlankPaths = /* @__PURE__ */ new Set();
4700
- for (const raw of initialTransientList) {
4701
- const key = coerceToPathKey(raw);
4620
+ const seededBlankPaths = hydration !== void 0 ? (hydration.blankPaths ?? []).map((dotted) => canonicalizePath(dotted).key) : options.initialBlankPaths ?? [];
4621
+ for (const key of seededBlankPaths) {
4702
4622
  blankPaths.add(key);
4703
4623
  originalBlankPaths.add(key);
4704
4624
  }
@@ -4715,6 +4635,9 @@ function createFormStore(options) {
4715
4635
  }
4716
4636
  return result;
4717
4637
  });
4638
+ const errorPathIndex = computed(
4639
+ () => buildErrorPathIndex(errorCells, derivedBlankErrors.value)
4640
+ );
4718
4641
  const submitting = ref(false);
4719
4642
  const activeSubmissions = ref(0);
4720
4643
  const submissionAttempts = ref(0);
@@ -4747,7 +4670,7 @@ function createFormStore(options) {
4747
4670
  const defaultsResolved = ref(false);
4748
4671
  const activated = ref(false);
4749
4672
  const activationPromise = ref(void 0);
4750
- const firstValidationDone = ref(initialFirstValidationGate(schema, strict));
4673
+ const firstValidationDone = ref(initialFirstValidationGate(schema));
4751
4674
  watch(activeValidations, (now, prev) => {
4752
4675
  if (prev > 0 && now === 0) {
4753
4676
  firstValidationDone.value = true;
@@ -4765,6 +4688,33 @@ function createFormStore(options) {
4765
4688
  const activeTransforms = ref(0);
4766
4689
  const transformRuns = /* @__PURE__ */ new Map();
4767
4690
  const transformWaiters = [];
4691
+ const errorWindows = /* @__PURE__ */ new Map();
4692
+ const pathSweep = createDynamicPathSweep({
4693
+ onFormChange: (listener) => {
4694
+ formChangeListeners.add(listener);
4695
+ },
4696
+ isFixedObjectAtPath: (path) => schema.isFixedObjectAtPath(path)
4697
+ });
4698
+ pathSweep.onEvict((key) => {
4699
+ fields.delete(key);
4700
+ fieldValidationState.delete(key);
4701
+ authoredPaths.delete(key);
4702
+ if (originals.get(key)?.value === void 0) originals.delete(key);
4703
+ errorWindows.delete(key);
4704
+ });
4705
+ const errorWindowAt = (prefix, prefixKey) => {
4706
+ let cached = errorWindows.get(prefixKey);
4707
+ if (cached === void 0) {
4708
+ const frozen = [...prefix];
4709
+ cached = computed((prev) => {
4710
+ const next = windowUnder(errorPathIndex.value, frozen);
4711
+ return prev !== void 0 && isSameWindow(prev, next) ? prev : next;
4712
+ });
4713
+ errorWindows.set(prefixKey, cached);
4714
+ pathSweep.track(frozen, prefixKey);
4715
+ }
4716
+ return cached.value;
4717
+ };
4768
4718
  const arrayBookkeeping = createArrayBookkeeping({
4769
4719
  form,
4770
4720
  fields,
@@ -4782,6 +4732,7 @@ function createFormStore(options) {
4782
4732
  touchFieldRecord: (pathKey, path, patch) => touchFieldRecord(st, pathKey, path, patch),
4783
4733
  decFieldValidation: (key) => decFieldValidation(st, key)
4784
4734
  });
4735
+ const bind = (fn) => (...args) => fn(st, ...args);
4785
4736
  const st = {
4786
4737
  // --- public data (the FormStore contract's state members) ---
4787
4738
  formKey,
@@ -4789,10 +4740,11 @@ function createFormStore(options) {
4789
4740
  fields,
4790
4741
  errorCells,
4791
4742
  derivedBlankErrors,
4743
+ errorWindowAt,
4792
4744
  originals,
4745
+ pathSweep,
4793
4746
  schema,
4794
4747
  ssr,
4795
- getDisplayState: resolvedGetDisplayState,
4796
4748
  submitting,
4797
4749
  activeSubmissions,
4798
4750
  submissionAttempts,
@@ -4820,20 +4772,16 @@ function createFormStore(options) {
4820
4772
  displayEngine,
4821
4773
  domBinding,
4822
4774
  modules,
4823
- coerceIndex,
4775
+ coerceEnabled,
4824
4776
  blankPaths,
4825
4777
  originalBlankPaths,
4826
4778
  // --- kernel-internal state ---
4827
- strict,
4828
- // Defensive copy, not a fix for an observed alias: today
4829
- // `getDefaultValues` happens to build a fresh tree, so form storage
4830
- // does not currently share structure with the consumer's object.
4831
- // That is an adapter implementation detail rather than a contract,
4832
- // and this field is now durable state that every `reset()` reads, so
4833
- // it should not depend on it. The copy also means a consumer who
4834
- // keeps a reference to the literal they passed cannot mutate the
4835
- // form's defaults from outside. `reset()` snapshots for a harder
4836
- // reason (see there) where an alias IS reachable.
4779
+ // A defensive copy. `getDefaultValues` building a fresh tree is an adapter
4780
+ // implementation detail, not a contract, and this field is durable state
4781
+ // that every `reset()` reads, so it must not depend on one. The copy also
4782
+ // stops a consumer holding a reference to the literal they passed from
4783
+ // mutating the form's defaults from outside. `reset()` snapshots for a
4784
+ // harder reason, where an alias IS reachable.
4837
4785
  defaultValues: structuralSnapshot(defaultValues),
4838
4786
  ssrPrefetch,
4839
4787
  rememberVariants,
@@ -4861,17 +4809,20 @@ function createFormStore(options) {
4861
4809
  transformTokenSeq: 0,
4862
4810
  warnedDisabledWrite: false,
4863
4811
  // --- methods: thin per-instance skins over the module kernel ---
4864
- rehydrate: () => rehydrate(st),
4865
- activate: () => activate(st),
4866
- adoptResolvedDefaults: (value) => adoptResolvedDefaults(st, value),
4867
- pathHasAsyncValidation: (path) => pathHasAsyncValidation(st, path),
4868
- pathHasAsyncValidationByKey: (key, segments) => pathHasAsyncValidationByKey(st, key, segments),
4869
- applyFormReplacement: (next, meta) => applyFormReplacement(st, next, meta),
4870
- setValueAtPath: (path, value, meta) => setValueAtPath(st, path, value, meta),
4871
- getValueAtPath: (path) => getValueAtPath(st, path),
4872
- arrayElementKey: (path) => arrayElementKey(st, path),
4873
- reset: (nextDefaultValues) => reset(st, nextDefaultValues),
4874
- resetField: (path) => resetField(st, path),
4812
+ rehydrate: bind(rehydrate),
4813
+ activate: bind(activate),
4814
+ adoptResolvedDefaults: bind(adoptResolvedDefaults),
4815
+ pathHasAsyncValidationByKey: bind(pathHasAsyncValidationByKey),
4816
+ applyFormReplacement: bind(applyFormReplacement),
4817
+ setValueAtPath: bind(setValueAtPath),
4818
+ getValueAtPath: bind(getValueAtPath),
4819
+ arrayElementKey: bind(arrayElementKey),
4820
+ reset: bind(reset),
4821
+ resetField: bind(resetField),
4822
+ // The schema/user pair below reads as a fold waiting to happen. Folding
4823
+ // them into a `setErrorsForPathIn(channel)` factory measures 10 B LARGER:
4824
+ // gzip has already collected the rent on two adjacent copies, and the
4825
+ // helper adds a name the original does not need.
4875
4826
  setSchemaErrorsForPath: (path, entries) => setErrorChannelForKey(
4876
4827
  st,
4877
4828
  canonicalizePath(path).key,
@@ -4880,7 +4831,7 @@ function createFormStore(options) {
4880
4831
  ),
4881
4832
  setAllSchemaErrors: (entries) => replaceErrorChannel(st, "schema", entries),
4882
4833
  clearSchemaErrors: (path) => clearErrorChannel(st, "schema", path),
4883
- applySchemaErrorsForSubtree: (path, entries) => applySchemaErrorsForSubtree(st, path, entries),
4834
+ applySchemaErrorsForSubtree: bind(applySchemaErrorsForSubtree),
4884
4835
  setAllUserErrors: (entries) => replaceErrorChannel(st, "user", entries),
4885
4836
  setUserErrorsForPath: (path, entries) => setErrorChannelForKey(
4886
4837
  st,
@@ -4889,37 +4840,34 @@ function createFormStore(options) {
4889
4840
  entries.length === 0 ? NO_ERRORS : [...entries]
4890
4841
  ),
4891
4842
  clearUserErrors: (path) => clearErrorChannel(st, "user", path),
4892
- restoreErrorCells: (entries) => restoreErrorCells(st, entries),
4893
- getErrorsForPath: (path) => getErrorsForPath(st, path),
4894
- ensurePathOrdinal: (key) => ensurePathOrdinal(st, key),
4895
- noteDomConnected: (path) => noteDomConnected(st, path),
4896
- noteDomDisconnected: (path) => noteDomDisconnected(st, path),
4897
- markFocused: (path, focused, meta) => markFocused(st, path, focused, meta),
4898
- markInteracted: (path) => markInteracted(st, path),
4899
- touchAtPath: (segments) => touchAtPath(st, segments),
4900
- interactAtPath: (segments) => interactAtPath(st, segments),
4901
- markConnectedOptimistically: (path) => markConnectedOptimistically(st, path),
4902
- isPristineAtPath: (path) => isPristineAtPath(st, path),
4903
- isPristineAtPathByKey: (key, segments) => isPristineAtPathByKey(st, key, segments),
4904
- hasStructuralChangeUnder: (path) => hasStructuralChangeUnder(st, path),
4905
- hasRemovedSubtreeUnder: (path) => hasRemovedSubtreeUnder(st, path),
4906
- getFieldRecord: (path) => getFieldRecord(st, path),
4907
- getOriginalAtPath: (path) => getOriginalAtPath(st, path),
4908
- cancelFieldValidation: () => cancelFieldValidation(st),
4909
- beginTransform: (key, holder) => beginTransform(st, key, holder),
4910
- isCurrentTransform: (key, token) => isCurrentTransform(st, key, token),
4911
- endTransform: (key, token) => endTransform(st, key, token),
4912
- setTransformError: (key, err) => setTransformError(st, key, err),
4913
- cancelTransforms: () => cancelTransforms(st),
4914
- cancelTransformsUnder: (prefix) => cancelTransformsUnder(st, prefix),
4915
- settleTransforms: (path) => settleTransforms(st, path),
4916
- scheduleFieldValidation: (path, immediate, override) => scheduleFieldValidation(st, path, immediate, override),
4917
- onFormChange: (listener) => onFormChange(st, listener),
4918
- onSubmitSuccess: (listener) => onSubmitSuccess(st, listener),
4919
- onReset: (listener) => onReset(st, listener),
4920
- emitSubmitSuccess: () => emitSubmitSuccess(st),
4921
- registerCleanup: (fn) => registerCleanup(st, fn),
4922
- dispose: () => dispose(st)
4843
+ restoreErrorCells: bind(restoreErrorCells),
4844
+ getErrorsForPath: bind(getErrorsForPath),
4845
+ ensurePathOrdinal: bind(ensurePathOrdinal),
4846
+ noteDomConnected: bind(noteDomConnected),
4847
+ noteDomDisconnected: bind(noteDomDisconnected),
4848
+ markFocused: bind(markFocused),
4849
+ markInteracted: bind(markInteracted),
4850
+ touchAtPath: bind(touchAtPath),
4851
+ interactAtPath: bind(interactAtPath),
4852
+ markConnectedOptimistically: bind(markConnectedOptimistically),
4853
+ isPristineAtPathByKey: bind(isPristineAtPathByKey),
4854
+ hasStructuralChangeUnder: bind(hasStructuralChangeUnder),
4855
+ hasRemovedSubtreeUnder: bind(hasRemovedSubtreeUnder),
4856
+ getFieldRecord: bind(getFieldRecord),
4857
+ cancelFieldValidation: bind(cancelFieldValidation),
4858
+ beginTransform: bind(beginTransform),
4859
+ isCurrentTransform: bind(isCurrentTransform),
4860
+ endTransform: bind(endTransform),
4861
+ setTransformError: bind(setTransformError),
4862
+ cancelTransformsUnder: bind(cancelTransformsUnder),
4863
+ settleTransforms: bind(settleTransforms),
4864
+ scheduleFieldValidation: bind(scheduleFieldValidation),
4865
+ onFormChange: bind(onFormChange),
4866
+ onSubmitSuccess: bind(onSubmitSuccess),
4867
+ onReset: bind(onReset),
4868
+ emitSubmitSuccess: bind(emitSubmitSuccess),
4869
+ registerCleanup: bind(registerCleanup),
4870
+ dispose: bind(dispose)
4923
4871
  };
4924
4872
  rebuildAuthoredPaths(st, defaultValues, schemaInitialData);
4925
4873
  seedOriginalsFromBaseline(st, schemaInitialData, true);
@@ -4961,7 +4909,7 @@ function createFormStore(options) {
4961
4909
  blurredAfterInteraction: false
4962
4910
  });
4963
4911
  });
4964
- if (strict && !schemaResponse.success) {
4912
+ if (!schemaResponse.success) {
4965
4913
  replaceErrorChannel(st, "schema", schemaResponse.errors);
4966
4914
  }
4967
4915
  }
@@ -4992,24 +4940,23 @@ function useAbstractForm(configuration, options) {
4992
4940
  const resolvedDefaults = resolveTrichotomy(configuration.defaultValues);
4993
4941
  const materialisedDefaults = resolvedDefaults.kind === "sync" ? resolvedDefaults.value : void 0;
4994
4942
  const { defaultValues: _droppedDefaults, ...configWithoutDefaults } = configuration;
4995
- const trichotomyOverride = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
4996
- const merged = mergeWithDefaults(registry.defaults, trichotomyOverride);
4997
- const maxRecursionDepth = normalizeNumericOption({
4998
- value: merged.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH,
4999
- source: "useForm.maxRecursionDepth",
5000
- allowInfinity: true,
5001
- min: 0,
5002
- defaultValue: DEFAULT_MAX_RECURSION_DEPTH
5003
- });
5004
- const resolvedSchema = getComputedSchema(key, configuration.schema, { maxRecursionDepth });
4943
+ const materialisedConfiguration = materialisedDefaults === void 0 ? configWithoutDefaults : { ...configWithoutDefaults, defaultValues: materialisedDefaults };
5005
4944
  const existing = registry.forms.get(key);
4945
+ const resolvedSchema = getComputedSchema(key, configuration.schema, {
4946
+ maxRecursionDepth: DEFAULT_MAX_RECURSION_DEPTH
4947
+ }) ;
5006
4948
  if (existing !== void 0) {
5007
4949
  void import('../chunks/dev-key-collision-warnings.mjs').then((m) => {
5008
4950
  void m.warnOnSchemaFingerprintMismatch(key, existing.schema, resolvedSchema);
5009
4951
  });
5010
4952
  }
5011
4953
  const hadPendingHydration = registry.pendingHydration.has(key);
5012
- const state = existing ?? buildFreshState(key, resolvedSchema, merged, registry);
4954
+ const state = existing ?? buildFreshState(
4955
+ key,
4956
+ resolvedSchema,
4957
+ materialisedConfiguration,
4958
+ registry
4959
+ );
5013
4960
  if (existing !== void 0) ; else if (resolvedDefaults.kind === "sync") {
5014
4961
  state.defaultsResolved.value = true;
5015
4962
  }
@@ -5034,8 +4981,8 @@ function useAbstractForm(configuration, options) {
5034
4981
  const releaseConsumer = registry.trackConsumer(key);
5035
4982
  onScopeDispose(releaseConsumer);
5036
4983
  }
5037
- if (existing === void 0 && merged.history !== void 0) {
5038
- const historyModule = merged.history.attach(state);
4984
+ if (existing === void 0 && materialisedConfiguration.history !== void 0) {
4985
+ const historyModule = materialisedConfiguration.history.attach(state);
5039
4986
  state.modules.set(HISTORY_MODULE_KEY, historyModule);
5040
4987
  state.registerCleanup(() => historyModule.dispose());
5041
4988
  }
@@ -5048,36 +4995,16 @@ function useAbstractForm(configuration, options) {
5048
4995
  provide(kFormInstanceId, formInstanceId);
5049
4996
  }
5050
4997
  const apiOptions = pickDefined({
5051
- onInvalidSubmit: merged.onInvalidSubmit,
4998
+ focusOnInvalidSubmit: materialisedConfiguration.focusOnInvalidSubmit,
5052
4999
  history: state.modules.get(HISTORY_MODULE_KEY),
5053
- validateOn: merged.validateOn,
5054
- debounceMs: merged.debounceMs,
5055
- getDisplayState: merged.getDisplayState,
5056
- coerce: merged.coerce,
5057
- rememberVariants: merged.rememberVariants,
5058
- autoAria: merged.autoAria
5000
+ validateOn: materialisedConfiguration.validateOn,
5001
+ debounceMs: materialisedConfiguration.debounceMs,
5002
+ coerce: materialisedConfiguration.coerce,
5003
+ rememberVariants: materialisedConfiguration.rememberVariants
5059
5004
  });
5060
5005
  const api = buildFormApi(state, formInstanceId, apiOptions);
5061
5006
  return api;
5062
5007
  }
5063
- function mergeWithDefaults(defaults, configuration) {
5064
- return {
5065
- ...configuration,
5066
- ...pickDefined({
5067
- strict: configuration.strict ?? defaults.strict,
5068
- onInvalidSubmit: configuration.onInvalidSubmit ?? defaults.onInvalidSubmit,
5069
- history: configuration.history ?? defaults.history,
5070
- rememberVariants: configuration.rememberVariants ?? defaults.rememberVariants,
5071
- disabled: configuration.disabled ?? defaults.disabled,
5072
- coerce: configuration.coerce ?? defaults.coerce,
5073
- validateOn: configuration.validateOn ?? defaults.validateOn,
5074
- debounceMs: configuration.debounceMs ?? defaults.debounceMs,
5075
- getDisplayState: configuration.getDisplayState ?? defaults.getDisplayState,
5076
- maxRecursionDepth: configuration.maxRecursionDepth ?? defaults.maxRecursionDepth,
5077
- autoAria: configuration.autoAria ?? defaults.autoAria
5078
- })
5079
- };
5080
- }
5081
5008
  const HISTORY_MODULE_KEY = "history";
5082
5009
  function buildFreshState(key, schema, configuration, registry) {
5083
5010
  const pending = registry.pendingHydration.get(key);
@@ -5097,21 +5024,17 @@ function buildFreshState(key, schema, configuration, registry) {
5097
5024
  hydration: pending,
5098
5025
  ssr: registry.ssr,
5099
5026
  ...pickDefined({
5100
- strict: configuration.strict,
5101
5027
  validateOn: configuration.validateOn,
5102
5028
  debounceMs: configuration.debounceMs,
5103
5029
  rememberVariants: configuration.rememberVariants,
5104
5030
  disabled: configuration.disabled,
5105
5031
  coerce: configuration.coerce,
5106
- getDisplayState: configuration.getDisplayState,
5107
5032
  initialBlankPaths
5108
5033
  }),
5109
- // Server-only: bind the SSR prefetch coordination handles. `enqueue`
5110
- // records intent on every `state.activate()` so a wizard skip-list
5111
- // override or a future transform mark has a consistent set to diff
5112
- // against; `shouldFire` lets the activate path bail when the
5113
- // wizard explicitly skipped this key — even an explicit
5114
- // `form.activate()` defers to the wizard's render-efficiency
5034
+ // Server only. `enqueue` records intent on every `state.activate()`
5035
+ // so a wizard skip-list override has a consistent set to diff
5036
+ // against; `shouldFire` lets the activate path bail on a key the
5037
+ // wizard skipped. Even an explicit `form.activate()` defers to that
5115
5038
  // skip-list on the server.
5116
5039
  ...registry.ssr ? {
5117
5040
  ssrPrefetch: {
@@ -5324,7 +5247,7 @@ function warnOutsideSetup() {
5324
5247
  warnedOutsideSetup = true;
5325
5248
  const frame = captureUserCallSite();
5326
5249
  console.warn(
5327
- `[attaform] useRegister() called outside a component setup; returning an unbound RegisterValue proxy. Fix: call it inside <script setup> or a setup() function \u2014 not from an event handler or async callback.` + (frame !== void 0 ? ` ${frame}` : "")
5250
+ `[attaform] useRegister() called outside a component setup; returning an unbound RegisterValue proxy. Fix: call it inside <script setup> or a setup() function, not from an event handler or async callback.` + (frame !== void 0 ? ` ${frame}` : "")
5328
5251
  );
5329
5252
  }
5330
5253
  function warnNoParentRV(instance) {
@@ -5417,25 +5340,18 @@ function createWizardHistory(param) {
5417
5340
  };
5418
5341
  }
5419
5342
 
5420
- const NOOP_FINGERPRINT = "attaform:wizard-noop";
5421
5343
  const EMPTY_SLIM_KINDS = /* @__PURE__ */ new Set();
5422
- function buildNoopWizardSchema(formKey) {
5423
- const emptyValue = {};
5424
- const success = {
5425
- success: true,
5426
- data: emptyValue,
5427
- errors: void 0,
5428
- formKey
5429
- };
5430
- const defaultsResponse = {
5431
- success: true,
5432
- data: emptyValue,
5433
- errors: void 0,
5434
- formKey
5435
- };
5436
- return {
5437
- fingerprint: () => Promise.resolve(NOOP_FINGERPRINT),
5438
- getDefaultValues: () => defaultsResponse,
5344
+ const EMPTY_VALUE = {};
5345
+ const NOOP_SUCCESS = {
5346
+ success: true,
5347
+ data: EMPTY_VALUE,
5348
+ errors: void 0
5349
+ };
5350
+ const NOOP_DEFAULTS = NOOP_SUCCESS;
5351
+ let NOOP_SCHEMA;
5352
+ function buildNoopWizardSchema() {
5353
+ NOOP_SCHEMA ?? (NOOP_SCHEMA = {
5354
+ getDefaultValues: () => NOOP_DEFAULTS,
5439
5355
  getDefaultAtPath: () => void 0,
5440
5356
  getEmptyValueAtPath: () => void 0,
5441
5357
  isPreprocessOrCoerceLeaf: () => false,
@@ -5446,13 +5362,14 @@ function buildNoopWizardSchema(formKey) {
5446
5362
  // addressable, root included.
5447
5363
  entryKeyKindAtPath: () => void 0,
5448
5364
  getSchemasAtPath: () => [],
5449
- validateAtPath: () => success,
5365
+ validateAtPath: () => NOOP_SUCCESS,
5450
5366
  getSlimPrimitiveTypesAtPath: () => new Set(EMPTY_SLIM_KINDS),
5451
5367
  isLeafAtPath: () => false,
5452
5368
  isRequiredAtPath: () => false,
5453
5369
  getUnionDiscriminatorAtPath: () => void 0,
5454
5370
  hasDiscriminatedUnions: () => false
5455
- };
5371
+ });
5372
+ return NOOP_SCHEMA;
5456
5373
  }
5457
5374
 
5458
5375
  function buildCallableReadonlySnapshotProxy(opts) {
@@ -5561,7 +5478,7 @@ function useWizard(options) {
5561
5478
  const rawSteps = Array.isArray(options.steps) ? options.steps : [];
5562
5479
  if (rawSteps.length === 0 && true) {
5563
5480
  console.error(
5564
- "[attaform] useWizard({ steps }): expected a non-empty array of step slots. Continuing with an empty step list \u2014 wizard.currentStep reads as undefined, navigation refuses, handleSubmit no-ops."
5481
+ "[attaform] useWizard({ steps }): expected a non-empty array of step slots. Continuing with an empty step list: wizard.currentStep reads as undefined, navigation refuses, handleSubmit no-ops."
5565
5482
  );
5566
5483
  }
5567
5484
  const registry = useRegistry();
@@ -5571,7 +5488,7 @@ function useWizard(options) {
5571
5488
  if (typeof slot !== "string") continue;
5572
5489
  if (noopForms.has(slot)) continue;
5573
5490
  const noop = useAbstractForm({
5574
- schema: buildNoopWizardSchema(slot),
5491
+ schema: buildNoopWizardSchema(),
5575
5492
  key: slot
5576
5493
  });
5577
5494
  noopForms.set(slot, noop);
@@ -5582,7 +5499,7 @@ function useWizard(options) {
5582
5499
  const noop = lazyNoopScope.run(
5583
5500
  () => useAbstractForm(
5584
5501
  {
5585
- schema: buildNoopWizardSchema(key),
5502
+ schema: buildNoopWizardSchema(),
5586
5503
  key
5587
5504
  },
5588
5505
  { registry }
@@ -5650,33 +5567,45 @@ function useWizard(options) {
5650
5567
  return activeKey.value === "" ? void 0 : activeKey.value;
5651
5568
  }
5652
5569
  };
5653
- function lazyComputedFor(marker) {
5570
+ function lazyComputedFor(marker, index) {
5654
5571
  const cached = lazyComputeds.get(marker);
5655
5572
  if (cached !== void 0) return cached;
5656
5573
  const c = computed(() => {
5657
5574
  void lazyEpoch.value;
5658
- return marker.resolve(slotCtx);
5575
+ return callConsumerFn(
5576
+ () => marker.resolve(slotCtx),
5577
+ void 0,
5578
+ "wizard-lazy-slot",
5579
+ `steps[${index}]`
5580
+ );
5659
5581
  });
5660
5582
  lazyComputeds.set(marker, c);
5661
5583
  return c;
5662
5584
  }
5663
5585
  const MAX_SLOT_DEPTH = 32;
5664
- function normalizeSlot(slot, ctx, gated, depth) {
5586
+ function normalizeSlot(slot, ctx, gated, depth, index) {
5665
5587
  if (depth > MAX_SLOT_DEPTH) {
5666
5588
  {
5667
5589
  console.warn(
5668
- `[attaform] useWizard: a step slot nested past ${MAX_SLOT_DEPTH} levels (gate / lazy / function); dropping it. Check for a resolver that returns itself.`
5590
+ `[attaform] useWizard: steps[${index}] nested past ${MAX_SLOT_DEPTH} levels (gate / lazy / function); dropping it. Check for a resolver that returns itself.`
5669
5591
  );
5670
5592
  }
5671
5593
  return void 0;
5672
5594
  }
5673
5595
  if (slot === void 0 || slot === null) return void 0;
5674
5596
  if (typeof slot === "string") return { form: getOrBuildNoop(slot), gated };
5675
- if (isGateMarker(slot)) return normalizeSlot(slot.inner, ctx, true, depth + 1);
5676
- if (isLazyMarker(slot)) return normalizeSlot(lazyComputedFor(slot).value, ctx, gated, depth + 1);
5597
+ if (isGateMarker(slot)) return normalizeSlot(slot.inner, ctx, true, depth + 1, index);
5598
+ if (isLazyMarker(slot)) {
5599
+ return normalizeSlot(lazyComputedFor(slot, index).value, ctx, gated, depth + 1, index);
5600
+ }
5677
5601
  if (typeof slot === "function") {
5678
- const result = slot(ctx);
5679
- return normalizeSlot(result, ctx, gated, depth + 1);
5602
+ const result = callConsumerFn(
5603
+ () => slot(ctx),
5604
+ void 0,
5605
+ "wizard-slot",
5606
+ `steps[${index}]`
5607
+ );
5608
+ return normalizeSlot(result, ctx, gated, depth + 1, index);
5680
5609
  }
5681
5610
  if (isAnyForm(slot)) return { form: slot, gated };
5682
5611
  return void 0;
@@ -5685,7 +5614,7 @@ function useWizard(options) {
5685
5614
  const out = [];
5686
5615
  const seen = /* @__PURE__ */ new Set();
5687
5616
  for (let i = 0; i < rawSteps.length; i++) {
5688
- const norm = normalizeSlot(rawSteps[i], slotCtx, false, 0);
5617
+ const norm = normalizeSlot(rawSteps[i], slotCtx, false, 0, i);
5689
5618
  if (norm === void 0) continue;
5690
5619
  const { form, gated } = norm;
5691
5620
  if (seen.has(form.key)) {
@@ -5998,13 +5927,15 @@ function useWizard(options) {
5998
5927
  seedRef.value = resolved.value;
5999
5928
  applyGateSeed(resolved.value);
6000
5929
  } else {
6001
- const eager = resolved.factory();
5930
+ const eager = callConsumerFn(() => resolved.factory(), void 0, "wizard-default-statuses");
6002
5931
  if (eager instanceof Promise) {
6003
5932
  void eager.then((value) => {
6004
5933
  seedRef.value = value;
6005
5934
  applyGateSeed(value);
5935
+ }).catch((err) => {
5936
+ reportConsumerThrow("wizard-default-statuses", void 0, err);
6006
5937
  });
6007
- } else {
5938
+ } else if (eager !== void 0) {
6008
5939
  seedRef.value = eager;
6009
5940
  applyGateSeed(eager);
6010
5941
  }
@@ -6090,10 +6021,15 @@ function useWizard(options) {
6090
6021
  }
6091
6022
  const progressOverride = options.progress;
6092
6023
  const progress = computed(() => {
6024
+ const list = compiledSteps.value;
6093
6025
  if (progressOverride !== void 0) {
6094
- return progressOverride(compiledSteps.value);
6026
+ const override = callConsumerFn(
6027
+ () => progressOverride(list),
6028
+ null,
6029
+ "wizard-progress"
6030
+ );
6031
+ if (override !== null) return override;
6095
6032
  }
6096
- const list = compiledSteps.value;
6097
6033
  if (list.length === 0) return 0;
6098
6034
  let valid = 0;
6099
6035
  for (const step of list) {
@@ -6121,11 +6057,15 @@ function useWizard(options) {
6121
6057
  historyHandle.subscribe((value) => {
6122
6058
  urlMirror.value = value;
6123
6059
  });
6124
- const restoreCallback = options.restore === false ? void 0 : options.restore !== void 0 ? options.restore : () => {
6060
+ const consumerRestore = options.restore;
6061
+ const restoreCallback = consumerRestore === false ? void 0 : consumerRestore !== void 0 ? () => callConsumerFn(() => consumerRestore(), void 0, "wizard-restore") : () => {
6125
6062
  const value = urlMirror.value;
6126
6063
  return value === void 0 ? void 0 : { step: value };
6127
6064
  };
6128
- const persistCallback = options.persist === false ? void 0 : options.persist !== void 0 ? options.persist : (state) => {
6065
+ const consumerPersist = options.persist;
6066
+ const persistCallback = consumerPersist === false ? void 0 : consumerPersist !== void 0 ? (state) => {
6067
+ callConsumerFn(() => consumerPersist(state), void 0, "wizard-persist");
6068
+ } : (state) => {
6129
6069
  if (state.step === void 0) return;
6130
6070
  const current = historyHandle.read();
6131
6071
  const effectiveCurrent = current !== void 0 && isCompiledKey(current) ? current : firstKey();
@@ -6806,4 +6746,4 @@ function escapeForInlineScript(json) {
6806
6746
  });
6807
6747
  }
6808
6748
 
6809
- export { AttaformErrorCode as A, DEFAULT_TIMINGS as D, defaultDisplayState as a, defineCoercion as b, injectWizard as c, defaultCoercionRules as d, escapeForInlineScript as e, isUnset as f, gate as g, useAbstractForm as h, injectForm as i, useRegister as j, useWizard as k, lazy as l, makeDefaultDisplayState as m, isPlainRecord as n, crossCopyState as o, getAtPath as p, humanize as q, slimKindOf as s, unset as u };
6749
+ export { AttaformErrorCode as A, injectWizard as a, isUnset as b, useAbstractForm as c, useRegister as d, escapeForInlineScript as e, useWizard as f, gate as g, getAtPath as h, injectForm as i, humanize as j, isPlainRecord as k, lazy as l, slimKindOf as s, unset as u };