attaform 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/abstract.d.mts +15 -21
  2. package/dist/abstract.mjs +3 -3
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/devtools.mjs +4 -4
  5. package/dist/dev/directive.mjs +1 -1
  6. package/dist/dev/history.mjs +2 -2
  7. package/dist/dev/index.mjs +5 -5
  8. package/dist/dev/runtime/plugins/attaform.mjs +1 -1
  9. package/dist/dev/shared/{attaform-dev.DUZckyV8.mjs → attaform-dev.B1q-x9R9.mjs} +4 -4
  10. package/dist/dev/shared/{attaform-dev.BcYT6tFh.mjs → attaform-dev.CZz4GYZ6.mjs} +21 -9
  11. package/dist/dev/shared/{attaform-dev.BisFstxh.mjs → attaform-dev.Caibt7sb.mjs} +16 -15
  12. package/dist/dev/shared/{attaform-dev.DTrHonpc.mjs → attaform-dev.CtyqGia7.mjs} +176 -260
  13. package/dist/dev/shared/{attaform-dev.BK6Ge2qR.mjs → attaform-dev.DJ3nsWw6.mjs} +86 -122
  14. package/dist/dev/shared/{attaform-dev.BcNtyOmf.mjs → attaform-dev.DlaT4xGd.mjs} +4 -4
  15. package/dist/dev/shared/{attaform-dev.D6CLvaQT.mjs → attaform-dev.k5X47GMD.mjs} +520 -395
  16. package/dist/dev/shared/{attaform-dev.CQ2rJiAX.mjs → attaform-dev.y9x9Vp9F.mjs} +324 -234
  17. package/dist/dev/shared/{attaform-dev.BQc5QV3Y.mjs → attaform-dev.zlvDr0WB.mjs} +2 -2
  18. package/dist/dev/zod-v3.mjs +6 -6
  19. package/dist/dev/zod-v4.mjs +6 -6
  20. package/dist/dev/zod.mjs +5 -5
  21. package/dist/directive.d.mts +4 -4
  22. package/dist/directive.mjs +1 -1
  23. package/dist/esbuild.d.mts +11 -0
  24. package/dist/esbuild.mjs +1 -1
  25. package/dist/history.d.mts +3 -32
  26. package/dist/history.mjs +2 -2
  27. package/dist/index.d.mts +67 -70
  28. package/dist/index.mjs +4 -4
  29. package/dist/nuxt.d.mts +4 -3
  30. package/dist/nuxt.mjs +3 -6
  31. package/dist/rollup.d.mts +11 -0
  32. package/dist/rollup.mjs +1 -1
  33. package/dist/rspack.d.mts +19 -11
  34. package/dist/rspack.mjs +1 -1
  35. package/dist/runtime/components/AttaformDevtoolsPanel.vue +15 -15
  36. package/dist/runtime/components/DevtoolsValueTree.vue +2 -2
  37. package/dist/runtime/plugins/attaform.mjs +1 -1
  38. package/dist/shared/{attaform.BRDDQeHV.mjs → attaform.8mfKZCwH.mjs} +2 -2
  39. package/dist/shared/{attaform.DRUT703x.d.mts → attaform.B-Tb62qV.d.mts} +12 -12
  40. package/dist/shared/{attaform.v0fQtWyA.mjs → attaform.B1rH9ht6.mjs} +1 -1
  41. package/dist/shared/{attaform.DhnPb6nK.mjs → attaform.BAf3UNn4.mjs} +1 -1
  42. package/dist/shared/{attaform.BXneBxEj.mjs → attaform.BWUn8Kjy.mjs} +85 -121
  43. package/dist/shared/{attaform.WiGdIqgx.mjs → attaform.C4shmwyJ.mjs} +2 -2
  44. package/dist/shared/{attaform.Rc9u4Vfw.d.mts → attaform.CRVe8s82.d.mts} +83 -95
  45. package/dist/shared/attaform.D9RqPgGu.d.mts +42 -0
  46. package/dist/shared/attaform.DAo0QBmS.d.mts +2173 -0
  47. package/dist/shared/{attaform.BK0cgu2K.mjs → attaform.DBCbxlyk.mjs} +62 -4
  48. package/dist/shared/{attaform.CUfrMK7t.mjs → attaform.DL2nl_QR.mjs} +15 -14
  49. package/dist/shared/{attaform.DNJOWjQw.mjs → attaform.DNPJPOO7.mjs} +299 -218
  50. package/dist/shared/attaform.DZnrDrNb.d.mts +4657 -0
  51. package/dist/shared/{attaform.B3resMQ1.mjs → attaform.DxKQsqFZ.mjs} +496 -371
  52. package/dist/shared/{attaform.ByWP6kSx.mjs → attaform.L011tHgJ.mjs} +3 -3
  53. package/dist/shared/{attaform.CJ8DpRGj.mjs → attaform.LUPC-2Qg.mjs} +175 -257
  54. package/dist/transforms.d.mts +153 -26
  55. package/dist/transforms.mjs +1 -1
  56. package/dist/vite.d.mts +29 -52
  57. package/dist/vite.mjs +3 -3
  58. package/dist/webpack.d.mts +20 -12
  59. package/dist/webpack.mjs +1 -1
  60. package/dist/zod-v3.d.mts +90 -96
  61. package/dist/zod-v3.mjs +5 -5
  62. package/dist/zod-v4.d.mts +120 -118
  63. package/dist/zod-v4.mjs +5 -5
  64. package/dist/zod.d.mts +5 -5
  65. package/dist/zod.mjs +4 -4
  66. package/package.json +7 -6
  67. package/skills/attaform/SKILL.md +1 -1
  68. package/skills/attaform/references/custom-components.md +4 -0
  69. package/dist/shared/attaform.Clz0ArIi.d.mts +0 -44
  70. package/dist/shared/attaform.UGyOK83T.d.mts +0 -2366
  71. package/dist/shared/attaform.bKLzMU0o.d.mts +0 -4761
@@ -1,12 +1,12 @@
1
- import { computed, readonly, toRaw, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, reactive, shallowReactive, shallowRef, toValue, watch, triggerRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, onBeforeMount, onBeforeUpdate, onMounted, effectScope, nextTick } from 'vue';
2
- import { e as ensureAttaformInstalled, u as useRegistry, d as kFormContext, f as kFormInstanceId, b as kAttaformWizardActiveStepResolver, i as kAttaformAncestorWizard } from './attaform.WiGdIqgx.mjs';
3
- import { c as armDomBinding } from './attaform.B3resMQ1.mjs';
1
+ import { computed, readonly, toRaw, ref, watchEffect, getCurrentScope, onScopeDispose, shallowReadonly, reactive, shallowReactive, shallowRef, toValue, watch, triggerRef, getCurrentInstance, onServerPrefetch, provide, useId, inject, effectScope, onBeforeMount, onBeforeUpdate, onMounted, nextTick } from 'vue';
2
+ import { e as ensureAttaformInstalled, u as useRegistry, d as kFormContext, f as kFormInstanceId, b as kAttaformWizardActiveStepResolver, i as kAttaformAncestorWizard } from './attaform.C4shmwyJ.mjs';
3
+ import { c as armDomBinding, r as recordInheritedModifiers, w as withInheritedModifiers } from './attaform.DxKQsqFZ.mjs';
4
4
  import { R as REGISTER_OWNER_MARKER, b as V_REGISTER_MARKER } from './attaform.KvlEedlF.mjs';
5
- 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, t as ANONYMOUS_WIZARD_KEY_PREFIX } from './attaform.BRDDQeHV.mjs';
5
+ 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 ANONYMOUS_WIZARD_KEY_PREFIX } from './attaform.8mfKZCwH.mjs';
6
6
  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.CdZIEf7_.mjs';
7
7
 
8
8
  const AttaformErrorCode = {
9
- /** A required field is in the blank set — user hasn't supplied a value. */
9
+ /** A required field is in the blank set: no value was supplied. */
10
10
  NoValueSupplied: "atta:no-value-supplied",
11
11
  /** The schema adapter's `validateAtPath` threw synchronously. */
12
12
  AdapterThrew: "atta:adapter-threw",
@@ -532,7 +532,7 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
532
532
  const value = state.getValueAtPath(segments);
533
533
  const original = state.originals.get(key)?.value;
534
534
  const pristine = state.isPristineAtPathByKey(key, segments);
535
- const cell = state.errorCells.get(key);
535
+ const cell = state.errorCells().get(key);
536
536
  const schemaForKey = cell !== void 0 && cell.schema.length > 0 ? cell.schema : void 0;
537
537
  const blankForKey = state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments) ? [makeBlankRequiredError(segments)] : void 0;
538
538
  const userForKey = cell !== void 0 && cell.user.length > 0 ? cell.user : void 0;
@@ -566,9 +566,8 @@ function buildLeafFieldStateBase(state, segments, key, formInstanceId) {
566
566
  updatedAt: record?.updatedAt ?? null,
567
567
  errors,
568
568
  // A leaf has no descendants, so its own-bucket errors ARE its subtree
569
- // errors. Reuse the same array reference: referential `ownErrors ===
570
- // errors`, and no second read of the whole-form `derivedBlankErrors`
571
- // map (preserves the P3 blank-reactivity optimization above).
569
+ // errors. Reuse the array reference, which makes `ownErrors === errors`
570
+ // and avoids a second read of the whole-form `derivedBlankErrors` map.
572
571
  ownErrors: errors,
573
572
  validating,
574
573
  valid,
@@ -748,19 +747,19 @@ function buildContainerFieldStateBase(state, segments, key, formInstanceId) {
748
747
  elements: EMPTY_ELEMENTS,
749
748
  updatedAt,
750
749
  errors,
751
- // The container's OWN bucket only (this exact path), excluding every
752
- // descendant that `errors` rolls up: a cross-field `.refine()` here,
753
- // or `setErrors` pinned at this node. The same raw union the leaf
754
- // builds from the three stores; the container already depends on all
755
- // three via `aggregateErrorsAt`, so there's no new reactivity cost.
750
+ // The container's OWN bucket at this exact path, excluding every
751
+ // descendant `errors` rolls up: a cross-field `.refine()` here, or a
752
+ // `setErrors` pinned at this node. The same raw union the leaf builds
753
+ // from the three stores, and the container already depends on all three
754
+ // through `aggregateErrorsAt`, so it costs no new reactivity.
756
755
  ownErrors: state.getErrorsForPath(segments),
757
756
  validating,
758
757
  valid,
759
758
  transforming,
760
759
  busy: transforming || validating,
761
- // A container surfaces its OWN transform failure (a transform registered
762
- // on the container path, e.g. a file normalizer) but never rolls up a
763
- // descendant leaf's failure — that stays a per-field channel.
760
+ // A container surfaces its OWN transform failure, from a transform
761
+ // registered on the container path such as a file normalizer, and never
762
+ // rolls up a descendant's: that stays a per-field channel.
764
763
  transformError: ownTransformError,
765
764
  path: segments,
766
765
  ...computeFieldIdentity(formInstanceId, state.formKey, key),
@@ -822,7 +821,7 @@ function aggregateErrorsAt(state, prefix, prefixKey) {
822
821
  const candidates = state.errorWindowAt(prefix, prefixKey);
823
822
  if (candidates.length === 0) return [];
824
823
  const formValue = state.form.value;
825
- const cells = state.errorCells;
824
+ const cells = state.errorCells();
826
825
  const buckets = /* @__PURE__ */ new Map();
827
826
  for (const { key, segments } of candidates) {
828
827
  if (!isPathPrefix(prefix, segments)) continue;
@@ -1092,16 +1091,16 @@ function buildErrorsSurface(state, sweep) {
1092
1091
  // non-schema keys reachable while a genuinely-absent key stays out.
1093
1092
  hasOwn: (segments, key) => liveContainerHasKey(state, segments, key) || errorAwareContainerKeys(state, segments).includes(key),
1094
1093
  isArrayAt: (segments) => isArrayPath(state, segments),
1095
- // `errors()` / `errors([])` / `errors(path)` are all the subtree
1096
- // aggregate — the same helper `meta.errors` reads, so the surfaces
1097
- // never drift.
1094
+ // `errors()`, `errors([])` and `errors(path)` are all the subtree
1095
+ // aggregate, through the helper `meta.errors` reads, so they cannot
1096
+ // drift.
1098
1097
  call: (path) => aggregateErrorsAt(state, path, keyForSegments(path).key),
1099
1098
  surface: "form.errors",
1100
1099
  sweep
1101
1100
  });
1102
1101
  }
1103
1102
  function collectErrorsAt(state, key, segments, active, into) {
1104
- const cell = state.errorCells.get(key);
1103
+ const cell = state.errorCells().get(key);
1105
1104
  if (active) {
1106
1105
  if (cell !== void 0) into.push(...cell.schema);
1107
1106
  if (state.blankPaths.has(key) && state.schema.isRequiredAtPath(segments)) {
@@ -1682,7 +1681,7 @@ function createArrayBookkeeping(deps) {
1682
1681
  ...record,
1683
1682
  path: segments
1684
1683
  }));
1685
- migrateMapSubtree(errorCells, arrayPath, remap, (cell, segments) => ({
1684
+ migrateMapSubtree(errorCells(), arrayPath, remap, (cell, segments) => ({
1686
1685
  schema: NO_ERRORS,
1687
1686
  user: cell.user.map((error) => ({ ...error, path: [...segments] }))
1688
1687
  }));
@@ -1732,7 +1731,7 @@ function createArrayBookkeeping(deps) {
1732
1731
  migrateElementState(arrayPath, remap);
1733
1732
  for (const freshIndex of remap.fresh) seedFreshElement(arrayPath, freshIndex);
1734
1733
  const changed = changedIndices(remap);
1735
- evictSchemaVerdictsAtIndices(errorCells, arrayPath, changed);
1734
+ evictSchemaVerdictsAtIndices(errorCells(), arrayPath, changed);
1736
1735
  abortValidationAtVacatedIndices(arrayPath, remap);
1737
1736
  variantMemory.dropAtIndices(arrayPath, changed);
1738
1737
  arrayIdentity.applyOp(arrayPath, remap);
@@ -2037,7 +2036,7 @@ function adapterThrowMessage(err) {
2037
2036
  return "Adapter validateAtPath threw a non-Error value";
2038
2037
  }
2039
2038
  function hasUserErrorEntries(state) {
2040
- for (const cell of state.errorCells.values()) {
2039
+ for (const cell of state.errorCells().values()) {
2041
2040
  if (cell.user.length > 0) return true;
2042
2041
  }
2043
2042
  return false;
@@ -2257,6 +2256,7 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2257
2256
  const isRequired = state.schema.isRequiredAtPath(segments);
2258
2257
  const ariaDisplayState = getDisplayStateAt !== void 0 ? computed(() => getDisplayStateAt(segments)) : void 0;
2259
2258
  const markBlank = () => state.setValueAtPath(segments, slimDefault, withInstanceMeta({ blank: true }));
2259
+ const commitFromHost = (value) => value === "" && !acceptsString || value === null && !slimTypes.has("null") || value === void 0 && !acceptsUndefined ? markBlank() : state.setValueAtPath(segments, value, withInstanceMeta(void 0));
2260
2260
  const internalRv = {
2261
2261
  innerRef,
2262
2262
  displayValue,
@@ -2273,7 +2273,7 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2273
2273
  },
2274
2274
  ensureDomBinding: (factory) => {
2275
2275
  var _a;
2276
- (_a = state.domBinding).value ?? (_a.value = factory(state));
2276
+ return (_a = state.domBinding).value ?? (_a.value = factory(state));
2277
2277
  },
2278
2278
  registerElement: (element) => {
2279
2279
  const dom = state.domBinding.value;
@@ -2288,13 +2288,14 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2288
2288
  setValueWithInternalPath: (value, meta) => {
2289
2289
  return state.setValueAtPath(segments, value, withInstanceMeta(meta));
2290
2290
  },
2291
- setValueFromHost: (value) => {
2291
+ commitFromHost,
2292
+ setValueFromHost: (value, modifiers) => {
2292
2293
  state.markInteracted(segments);
2293
- const isBlankSignal = value === "" && !acceptsString || value === null && !slimTypes.has("null") || value === void 0 && !acceptsUndefined;
2294
- const accepted = isBlankSignal ? markBlank() : state.setValueAtPath(segments, value, withInstanceMeta(void 0));
2294
+ const accepted = state.domBinding.value?.hostChannel.write(internalRv, value, modifiers) ?? commitFromHost(value);
2295
2295
  return accepted;
2296
2296
  },
2297
- // Called by the `vRegisterHint` compile-time transform's wrapping
2297
+ setValueFromHostInput: (payload, modifiers) => state.domBinding.value?.hostChannel.writeInput(internalRv, payload, modifiers) ?? false,
2298
+ // Called by the `vRegisterHintTransform` compile-time transform's wrapping
2298
2299
  // IIFE on every server-side render of `<element v-register="…">`.
2299
2300
  // Without it, every SSR'd FieldState serialises `connected: false`
2300
2301
  // (because Vue skips directive lifecycle during SSR) and the client
@@ -2323,12 +2324,12 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2323
2324
  }
2324
2325
  return false;
2325
2326
  },
2326
- // --- Async transform lifecycle (internal; the directive's
2327
- // deferred orchestrator is the only legitimate consumer). Thin
2328
- // path-bound delegates to the store's per-path token / counter
2329
- // machinery — same pattern as `markBlank` / `setValueWithInternalPath`,
2330
- // so the directive (which holds only this RegisterValue, never the
2331
- // store) can drive the busy/discard/error bookkeeping. ---
2327
+ // --- Async transform lifecycle (internal; the directive's deferred
2328
+ // orchestrator is the only legitimate consumer). Thin path-bound
2329
+ // delegates to the store's per-path token and counter machinery,
2330
+ // the same pattern as `markBlank` and `setValueWithInternalPath`, so
2331
+ // the directive (which holds this RegisterValue and never the store)
2332
+ // can drive the busy / discard / error bookkeeping. ---
2332
2333
  beginTransform: (holder) => state.beginTransform(pathKey, holder),
2333
2334
  isCurrentTransform: (token) => state.isCurrentTransform(pathKey, token),
2334
2335
  endTransform: (token) => state.endTransform(pathKey, token),
@@ -2341,10 +2342,10 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2341
2342
  return (state.fieldTransformCounts.get(pathKey) ?? 0) > 0;
2342
2343
  },
2343
2344
  path: pathKey,
2344
- // Frozen so a wrapper component can pass `rv.segments` directly
2345
- // to `form.fields(...)` without defensive copying — and so test
2346
- // fixtures or downstream code can't mutate the canonical
2347
- // segment list out from under the directive.
2345
+ // Frozen so a wrapper component can pass `rv.segments` straight to
2346
+ // `form.fields(...)` with no defensive copy, and so neither a test
2347
+ // fixture nor downstream code can mutate the canonical segment list
2348
+ // out from under the directive.
2348
2349
  segments: Object.freeze(segments.slice()),
2349
2350
  formKey: state.formKey,
2350
2351
  formInstanceId,
@@ -2367,25 +2368,24 @@ function isUnset(value) {
2367
2368
  return value === unset;
2368
2369
  }
2369
2370
 
2370
- function walkUnsetSentinels(values, schema) {
2371
+ function walkUnsetSentinels(values, schema, rootDefault) {
2371
2372
  const paths = [];
2372
2373
  if (values === void 0) {
2373
- const rootSlim = schema.getDefaultAtPath([]);
2374
- walkUnspecified(rootSlim, [], paths);
2374
+ walkUnspecified(rootDefault, [], paths);
2375
2375
  return { cleanedValues: void 0, paths };
2376
2376
  }
2377
- const cleaned = walkCore(values, [], schema, paths, true);
2377
+ const cleaned = walkCore(values, [], schema, paths, { rootDefault });
2378
2378
  return { cleanedValues: cleaned, paths };
2379
2379
  }
2380
2380
  function isRecursable(value) {
2381
2381
  return Array.isArray(value) || isPlainRecord(value);
2382
2382
  }
2383
- function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2383
+ function walkCore(input, segments, schema, paths, synthesis) {
2384
2384
  if (isUnset(input)) {
2385
2385
  return expandUnsetAt(segments, schema, paths);
2386
2386
  }
2387
2387
  if (input === void 0) {
2388
- if (synthesizeSchemaKeys) {
2388
+ if (synthesis !== null) {
2389
2389
  const slim = schema.getDefaultAtPath(segments);
2390
2390
  return walkUnspecified(slim, segments, paths);
2391
2391
  }
@@ -2398,7 +2398,7 @@ function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2398
2398
  let mutated = false;
2399
2399
  for (let i = 0; i < input.length; i++) {
2400
2400
  const original = readConsumerIndex(input, i);
2401
- const walked = walkCore(original, [...segments, i], schema, paths, synthesizeSchemaKeys);
2401
+ const walked = walkCore(original, [...segments, i], schema, paths, synthesis);
2402
2402
  out[i] = walked;
2403
2403
  if (walked !== original) mutated = true;
2404
2404
  }
@@ -2410,10 +2410,10 @@ function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2410
2410
  let keys = inputKeys;
2411
2411
  let mutated = false;
2412
2412
  let inputKeysSet = null;
2413
- if (synthesizeSchemaKeys) {
2413
+ if (synthesis !== null) {
2414
2414
  inputKeysSet = new Set(inputKeys);
2415
2415
  const allKeys = new Set(inputKeys);
2416
- const slim = schema.getDefaultAtPath(segments);
2416
+ const slim = segments.length === 0 ? synthesis.rootDefault : schema.getDefaultAtPath(segments);
2417
2417
  if (isPlainRecord(slim)) {
2418
2418
  for (const k of Object.keys(slim)) allKeys.add(k);
2419
2419
  }
@@ -2423,12 +2423,12 @@ function walkCore(input, segments, schema, paths, synthesizeSchemaKeys) {
2423
2423
  const out = {};
2424
2424
  for (const key of keys) {
2425
2425
  const orig = readConsumerProp(obj, key);
2426
- if (synthesizeSchemaKeys && orig === void 0 && inputKeysSet?.has(key) === true) {
2426
+ if (synthesis !== null && orig === void 0 && inputKeysSet?.has(key) === true) {
2427
2427
  safeAssign(out, key, void 0);
2428
2428
  mutated = true;
2429
2429
  continue;
2430
2430
  }
2431
- const walked = walkCore(orig, [...segments, key], schema, paths, synthesizeSchemaKeys);
2431
+ const walked = walkCore(orig, [...segments, key], schema, paths, synthesis);
2432
2432
  safeAssign(out, key, walked);
2433
2433
  if (walked !== orig) mutated = true;
2434
2434
  }
@@ -2452,7 +2452,7 @@ function walkUnspecified(slim, segments, paths) {
2452
2452
  }
2453
2453
  function substituteUnsetSentinels(value, prefix, schema) {
2454
2454
  const paths = [];
2455
- const cleaned = walkCore(value, [...prefix], schema, paths, false);
2455
+ const cleaned = walkCore(value, [...prefix], schema, paths, null);
2456
2456
  return { cleanedValues: cleaned, paths };
2457
2457
  }
2458
2458
  function isPrimitiveOrEmpty(value) {
@@ -2550,15 +2550,15 @@ function buildMetaProxy(keys, read) {
2550
2550
  return new Proxy(target, {
2551
2551
  get: (_, key) => owns.has(key) ? read(key) : Reflect.get(target, key),
2552
2552
  has: (_, key) => owns.has(key) || Reflect.has(target, key),
2553
- // Safe to hand back the same array every time: the spec copies a
2554
- // trap's key list before using it.
2553
+ // Safe to hand back the same array every time: the spec copies a trap's
2554
+ // key list before using it.
2555
2555
  ownKeys: () => keys,
2556
2556
  getOwnPropertyDescriptor(_, key) {
2557
2557
  if (!owns.has(key)) return Reflect.getOwnPropertyDescriptor(target, key);
2558
2558
  return { configurable: true, enumerable: true, value: read(key), writable: false };
2559
2559
  },
2560
- // A getter with no setter throws on assignment under strict mode,
2561
- // which every module here is. `false` keeps that.
2560
+ // A getter with no setter throws on assignment under strict mode, which
2561
+ // every module here is; `false` keeps that.
2562
2562
  set: denyWrite,
2563
2563
  deleteProperty: denyWrite,
2564
2564
  defineProperty: denyWrite
@@ -2617,7 +2617,18 @@ function buildFormApi(state, formInstanceId, options = {}) {
2617
2617
  void state.submitted.value;
2618
2618
  return metaBase;
2619
2619
  };
2620
- const pathSweep = state.pathSweep;
2620
+ const releases = [];
2621
+ const lease = (release) => {
2622
+ releases.push(release);
2623
+ return release;
2624
+ };
2625
+ onScopeDispose(() => {
2626
+ for (const release of releases) release();
2627
+ }, true);
2628
+ const pathSweep = {
2629
+ track: state.pathSweep.track,
2630
+ onEvict: (evict) => lease(state.pathSweep.onEvict(evict))
2631
+ };
2621
2632
  const getRootFieldStateAt = buildFieldStateAccessor(
2622
2633
  state,
2623
2634
  formInstanceId,
@@ -2675,7 +2686,8 @@ function buildFormApi(state, formInstanceId, options = {}) {
2675
2686
  const next = typeof pathOrValue === "function" ? pathOrValue(structuralSnapshot(state.form.value)) : pathOrValue;
2676
2687
  const walked2 = walkUnsetSentinels(
2677
2688
  next,
2678
- state.schema
2689
+ state.schema,
2690
+ state.schema.getDefaultAtPath([])
2679
2691
  );
2680
2692
  const ok2 = state.setValueAtPath([], walked2.cleanedValues, writeMeta());
2681
2693
  if (!ok2) return false;
@@ -2740,7 +2752,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2740
2752
  const errorsProxy = buildErrorsSurface(state, pathSweep);
2741
2753
  function flattenUserErrors() {
2742
2754
  const all = [];
2743
- for (const cell of state.errorCells.values()) all.push(...cell.user);
2755
+ for (const cell of state.errorCells().values()) all.push(...cell.user);
2744
2756
  return all;
2745
2757
  }
2746
2758
  function setErrors(arg1, arg2) {
@@ -2748,7 +2760,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2748
2760
  if (isScoped) {
2749
2761
  const { segments, key } = canonicalizePath(arg1);
2750
2762
  const input2 = arg2;
2751
- const resolved2 = typeof input2 === "function" ? input2([...state.errorCells.get(key)?.user ?? []]) : input2;
2763
+ const resolved2 = typeof input2 === "function" ? input2([...state.errorCells().get(key)?.user ?? []]) : input2;
2752
2764
  state.setUserErrorsForPath(
2753
2765
  segments,
2754
2766
  normalizeErrorInputs(resolved2, segments, AttaformErrorCode.UserError)
@@ -2776,7 +2788,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2776
2788
  const departAttempts = computed(() => state.departAttempts.value);
2777
2789
  const validating = computed(() => state.activeValidations.value > 0);
2778
2790
  const valid = computed(
2779
- () => state.firstValidationDone.value && state.errorCells.size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
2791
+ () => state.firstValidationDone.value && state.errorCells().size === 0 && state.derivedBlankErrors.value.size === 0 && !validating.value
2780
2792
  );
2781
2793
  const history = options.history;
2782
2794
  const formHistory = history === void 0 ? inertHistory() : readonly(
@@ -2794,12 +2806,11 @@ function buildFormApi(state, formInstanceId, options = {}) {
2794
2806
  );
2795
2807
  const rootFieldState = getRootFieldStateAt([]);
2796
2808
  const metaOwn = {
2797
- // Whole-form work signals compose the LIFECYCLE counters with the
2798
- // per-leaf rollup: a submit-time validate shows up as
2799
- // activeValidations, per-field debounced validators as
2800
- // fieldValidationCounts — either flips the flag. `valid` keeps the
2801
- // form-level `firstValidationDone` mount gate; `errors` is the
2802
- // unfiltered whole-form aggregate.
2809
+ // Whole-form work signals compose the LIFECYCLE counters with the per-leaf
2810
+ // rollup: a submit-time validate shows up in `activeValidations` and a
2811
+ // per-field debounced validator in `fieldValidationCounts`, and either
2812
+ // flips the flag. `valid` keeps the form-level mount gate, and `errors` is
2813
+ // the unfiltered whole-form aggregate.
2803
2814
  validating: computed(
2804
2815
  () => state.activeValidations.value > 0 || rootFieldState.value.validating
2805
2816
  ),
@@ -2811,7 +2822,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2811
2822
  busy: computed(
2812
2823
  () => state.activeValidations.value > 0 || state.activeTransforms.value > 0 || rootFieldState.value.validating || rootFieldState.value.transforming
2813
2824
  ),
2814
- // Lifecycle (form-level only — not on FieldState).
2825
+ // Lifecycle, form-level only and never on FieldState.
2815
2826
  submitting,
2816
2827
  submissionAttempts,
2817
2828
  departAttempts,
@@ -2893,7 +2904,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
2893
2904
  };
2894
2905
  return Object.freeze(view);
2895
2906
  });
2896
- const valuesProxy = buildValuesSurface(state.form, state.onFormChange);
2907
+ const valuesProxy = buildValuesSurface(
2908
+ state.form,
2909
+ (listener) => lease(state.onFormChange(listener))
2910
+ );
2897
2911
  const fieldStateProxy = buildFieldsSurface(state, getRootFieldStateAt, pathSweep);
2898
2912
  const needsLazyGate = state.defaultValuesFactory.value !== void 0 || state.hasSsrPrefetch;
2899
2913
  function gated(fn) {
@@ -2928,10 +2942,10 @@ function buildFormApi(state, formInstanceId, options = {}) {
2928
2942
  }
2929
2943
  const api = {
2930
2944
  handleSubmit: gated(handleSubmit),
2931
- // Callable readonly Proxies (`values`, `fields`, `errors`) and the
2932
- // reactive containers (`meta`, `history`, `blankPaths`) are exposed
2933
- // through getters so reading them activates the form on first
2934
- // touch. Each underlying object is identity-stable across reads.
2945
+ // The callable readonly Proxies (`values`, `fields`, `errors`) and the
2946
+ // reactive containers (`meta`, `history`, `blankPaths`) are exposed through
2947
+ // getters, so reading one activates the form on first touch. Each
2948
+ // underlying object is identity-stable across reads.
2935
2949
  get values() {
2936
2950
  void state.activate();
2937
2951
  return valuesProxy;
@@ -2948,7 +2962,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
2948
2962
  key: state.formKey,
2949
2963
  // Auto-unwrapping views over the per-store async-defaults lifecycle
2950
2964
  // refs (see FormStore.hydrating / hydrateError). Reading either
2951
- // activates the form — observing factory state implies use.
2965
+ // activates the form: observing factory state implies use.
2952
2966
  get hydrating() {
2953
2967
  void state.activate();
2954
2968
  return state.hydrating.value;
@@ -2957,20 +2971,18 @@ function buildFormApi(state, formInstanceId, options = {}) {
2957
2971
  void state.activate();
2958
2972
  return state.hydrateError.value;
2959
2973
  },
2960
- // Orthogonal to `hydrating` and `hydrateError`: `ready` flips true
2961
- // once defaults are applied (sync at construction or async factory
2962
- // resolved successfully). One-way latch — stays true through later
2963
- // refetches even when those refetches fail, so stale-while-
2964
- // revalidate UIs keep rendering the prior values while
2965
- // `hydrateError` surfaces the refresh failure.
2974
+ // Orthogonal to `hydrating` and `hydrateError`. `ready` flips true once
2975
+ // defaults are applied, whether sync at construction or from a factory
2976
+ // that resolved, and it is a one-way latch: it stays true through later
2977
+ // refetches even when those fail, so a stale-while-revalidate UI keeps
2978
+ // rendering the prior values while `hydrateError` surfaces the failure.
2966
2979
  get ready() {
2967
2980
  void state.activate();
2968
2981
  return state.defaultsResolved.value;
2969
2982
  },
2970
- // `rehydrate` and `activate` are themselves activation entry points
2971
- // — they fire the factory by design. Wrapping them with `gated`
2972
- // would double-fire (`state.activate()` plus the underlying call),
2973
- // so they call `state` directly.
2983
+ // `rehydrate` and `activate` are activation entry points themselves and
2984
+ // fire the factory by design, so wrapping them in `gated` would
2985
+ // double-fire. They call `state` directly.
2974
2986
  rehydrate: () => state.rehydrate(),
2975
2987
  activate: () => state.activate(),
2976
2988
  get errors() {
@@ -3016,7 +3028,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
3016
3028
  const SWEEP_SLICE = 8;
3017
3029
  function createDynamicPathSweep(deps) {
3018
3030
  const dynamicKeys = /* @__PURE__ */ new Set();
3019
- const evictors = [];
3031
+ const evictors = /* @__PURE__ */ new Set();
3020
3032
  const isDynamicPath = (segments) => {
3021
3033
  for (let i = 0; i < segments.length; i++) {
3022
3034
  if (typeof segments[i] === "number") return true;
@@ -3047,7 +3059,10 @@ function createDynamicPathSweep(deps) {
3047
3059
  if (isDynamicPath(segments)) dynamicKeys.add(key);
3048
3060
  },
3049
3061
  onEvict(evict) {
3050
- evictors.push(evict);
3062
+ evictors.add(evict);
3063
+ return () => {
3064
+ evictors.delete(evict);
3065
+ };
3051
3066
  }
3052
3067
  };
3053
3068
  }
@@ -3246,7 +3261,7 @@ function mergeObjectKeys(target, source, path, schema) {
3246
3261
  async function runFactoryAndApply(st, factory) {
3247
3262
  try {
3248
3263
  const value = await factory();
3249
- walkAuthoredFromConstraints(value, [], st.authoredPaths);
3264
+ if (st.hasPreprocessOrCoerce) walkAuthoredFromConstraints(value, [], st.authoredPaths);
3250
3265
  const full = mergeSparseHydration(
3251
3266
  toRaw(st.form.value),
3252
3267
  value,
@@ -3270,7 +3285,7 @@ async function runFactoryAndApply(st, factory) {
3270
3285
  }
3271
3286
  }
3272
3287
  function clearHydrationFailedEntry(st) {
3273
- const existing = st.errorCells.get(ROOT_PATH_KEY)?.schema;
3288
+ const existing = st.errorCells().get(ROOT_PATH_KEY)?.schema;
3274
3289
  if (existing === void 0 || existing.length === 0) return;
3275
3290
  const filtered = existing.filter((e) => e.code !== AttaformErrorCode.HydrationFailed);
3276
3291
  if (filtered.length !== existing.length) {
@@ -3284,7 +3299,7 @@ function appendHydrationFailedEntry(st, error) {
3284
3299
  path: [...ROOT_PATH],
3285
3300
  code: AttaformErrorCode.HydrationFailed
3286
3301
  };
3287
- const existing = st.errorCells.get(ROOT_PATH_KEY)?.schema ?? [];
3302
+ const existing = st.errorCells().get(ROOT_PATH_KEY)?.schema ?? [];
3288
3303
  st.setSchemaErrorsForPath([...ROOT_PATH], [...existing, entry]);
3289
3304
  return entry;
3290
3305
  }
@@ -3402,23 +3417,24 @@ function walkAuthoredFromSchemaDiff(withDefaults, withoutDefaults, prefix, out)
3402
3417
  out.add(canonicalizePath(prefix).key);
3403
3418
  }
3404
3419
  }
3405
- function computeBaselineResponse(schema, source) {
3406
- const completed = source === void 0 ? void 0 : mergeStructural(schema, [], source);
3420
+ function computeBaseline(schema, source, schemaDefaults) {
3421
+ if (source === void 0) return schemaDefaults;
3407
3422
  return schema.getDefaultValues({
3408
3423
  useDefaultSchemaValues: true,
3409
- constraints: completed
3424
+ constraints: mergeStructural(schema, [], source, schemaDefaults)
3410
3425
  });
3411
3426
  }
3412
3427
  function initialFirstValidationGate(schema) {
3413
3428
  return schema.needsAsyncValidation?.() !== true;
3414
3429
  }
3415
- function seedOriginalsFromBaseline(st, baseline, ensureOrdinals) {
3430
+ function seedOriginalsFromBaseline(st, baseline) {
3416
3431
  st.originals.clear();
3417
3432
  diffAndApply({}, baseline, [], (patch) => {
3418
3433
  if (patch.kind !== "added") return;
3419
- const { key } = canonicalizePath(patch.path);
3420
- st.originals.set(key, { segments: patch.path, value: patch.newValue });
3421
- if (ensureOrdinals) ensurePathOrdinal(st, key);
3434
+ st.originals.set(canonicalizePath(patch.path).key, {
3435
+ segments: patch.path,
3436
+ value: patch.newValue
3437
+ });
3422
3438
  });
3423
3439
  }
3424
3440
  function queueInitialAsyncValidation(st) {
@@ -3432,6 +3448,7 @@ function queueInitialAsyncValidation(st) {
3432
3448
  }
3433
3449
  }
3434
3450
  function rebuildAuthoredPaths(st, constraints, schemaWithDefaultsData) {
3451
+ if (!st.hasPreprocessOrCoerce) return;
3435
3452
  st.authoredPaths.clear();
3436
3453
  if (constraints !== void 0) {
3437
3454
  walkAuthoredFromConstraints(constraints, [], st.authoredPaths);
@@ -3465,6 +3482,7 @@ function ensurePathOrdinal(st, key) {
3465
3482
  return ordinal;
3466
3483
  }
3467
3484
  function pathHasAsyncValidationByKey(st, key, segments) {
3485
+ if (st.schema.needsAsyncValidation?.() === false) return false;
3468
3486
  const cached = st.pathAsyncCache.get(key);
3469
3487
  if (cached !== void 0) return cached;
3470
3488
  const candidates = st.schema.getSchemasAtPath(segments);
@@ -3585,19 +3603,16 @@ function touchFieldRecord(st, pathKey, path, patch) {
3585
3603
  path,
3586
3604
  updatedAt: patch.updatedAt ?? current?.updatedAt ?? null,
3587
3605
  connected: patch.connected ?? current?.connected ?? false,
3588
- // focused/blurred use an explicit-undefined guard because
3589
- // patches legitimately carry `null` to mark a disconnect — the
3590
- // `??` operator would short-circuit on null and fall through to
3591
- // `current`, losing the intent. `!== undefined` honours an
3592
- // explicit null and preserves current only on absence.
3606
+ // `focused` / `blurred` need an explicit-undefined guard: a patch
3607
+ // legitimately carries `null` to mark a disconnect, and `??` would
3608
+ // short-circuit on it and fall through to `current`, losing the intent.
3593
3609
  focused: patch.focused !== void 0 ? patch.focused : current?.focused ?? null,
3594
3610
  blurred: patch.blurred !== void 0 ? patch.blurred : current?.blurred ?? null,
3595
- // touched is plain `boolean`; `??` is equivalent to the explicit
3596
- // guard here because `false` is not nullish.
3611
+ // `touched` is a plain boolean, so `??` is equivalent to the guard above.
3597
3612
  touched: patch.touched ?? current?.touched ?? false,
3598
- // interacted is sticky-true; a merge patch only ever sets it, so
3599
- // `??` preserves the current bit. It flips back to false solely
3600
- // through the reset paths, which reconstruct the record outright.
3613
+ // `interacted` is sticky-true and a merge patch only ever sets it, so `??`
3614
+ // preserves the current bit. Only the reset paths clear it, and they
3615
+ // reconstruct the record outright.
3601
3616
  interacted: patch.interacted ?? current?.interacted ?? false,
3602
3617
  blurredAfterInteraction: patch.blurredAfterInteraction ?? current?.blurredAfterInteraction ?? false
3603
3618
  });
@@ -3796,16 +3811,19 @@ function setValueAtPath(st, path, value, meta) {
3796
3811
  }
3797
3812
  }
3798
3813
  }
3799
- const wasAuthoredBefore = st.authoredPaths.has(pathKey);
3800
- if (arrayOpRemap !== null && Array.isArray(value)) {
3801
- if (path.length > 0) st.authoredPaths.add(pathKey);
3802
- for (const idx of arrayOpRemap.fresh) {
3803
- walkAuthoredFromConstraints(value[idx], [...path, idx], st.authoredPaths);
3814
+ let newlyAuthored = false;
3815
+ if (st.hasPreprocessOrCoerce) {
3816
+ const wasAuthoredBefore = st.authoredPaths.has(pathKey);
3817
+ if (arrayOpRemap !== null && Array.isArray(value)) {
3818
+ if (path.length > 0) st.authoredPaths.add(pathKey);
3819
+ for (const idx of arrayOpRemap.fresh) {
3820
+ walkAuthoredFromConstraints(value[idx], [...path, idx], st.authoredPaths);
3821
+ }
3822
+ } else {
3823
+ walkAuthoredFromConstraints(value, path, st.authoredPaths);
3804
3824
  }
3805
- } else {
3806
- walkAuthoredFromConstraints(value, path, st.authoredPaths);
3825
+ newlyAuthored = !wasAuthoredBefore && st.authoredPaths.has(pathKey);
3807
3826
  }
3808
- const newlyAuthored = !wasAuthoredBefore && st.authoredPaths.has(pathKey);
3809
3827
  let completedValue;
3810
3828
  if (arrayOpRemap !== null && Array.isArray(value)) {
3811
3829
  for (const idx of arrayOpRemap.fresh) {
@@ -4062,22 +4080,38 @@ function getValueAtPath(st, path) {
4062
4080
  return getAtPath(st.form.value, path);
4063
4081
  }
4064
4082
  const ERROR_SOURCES = ["schema", "user"];
4083
+ function createErrorCells(schema, pristine) {
4084
+ const raw = /* @__PURE__ */ new Map();
4085
+ const cells = shallowReactive(raw);
4086
+ return (seed) => {
4087
+ const data = pristine;
4088
+ if (data !== void 0) {
4089
+ pristine = void 0;
4090
+ if (seed) {
4091
+ for (const [key, list] of groupErrorsByKey(schema.validateSync(data))) {
4092
+ raw.set(key, { schema: list, user: NO_ERRORS });
4093
+ }
4094
+ }
4095
+ }
4096
+ return cells;
4097
+ };
4098
+ }
4065
4099
  function setErrorChannelForKey(st, key, src, entries) {
4066
- const current = st.errorCells.get(key);
4100
+ const current = st.errorCells().get(key);
4067
4101
  const schema = src === "schema" ? entries : current?.schema ?? NO_ERRORS;
4068
4102
  const user = src === "user" ? entries : current?.user ?? NO_ERRORS;
4069
4103
  if (schema.length === 0 && user.length === 0) {
4070
- if (current !== void 0) st.errorCells.delete(key);
4104
+ if (current !== void 0) st.errorCells().delete(key);
4071
4105
  return;
4072
4106
  }
4073
- st.errorCells.set(key, { schema, user });
4107
+ st.errorCells().set(key, { schema, user });
4074
4108
  }
4075
4109
  function replaceErrorChannel(st, src, entries) {
4076
4110
  const other = src === "schema" ? "user" : "schema";
4077
4111
  const grouped = groupErrorsByKey(entries);
4078
- for (const [key, cell] of st.errorCells) {
4112
+ for (const [key, cell] of st.errorCells()) {
4079
4113
  if (cell[other].length === 0) {
4080
- st.errorCells.delete(key);
4114
+ st.errorCells().delete(key);
4081
4115
  continue;
4082
4116
  }
4083
4117
  const fresh = grouped.get(key);
@@ -4102,8 +4136,9 @@ function clearErrorChannel(st, src, path) {
4102
4136
  function applySchemaErrorsForSubtree(st, path, entries) {
4103
4137
  const parentKey = canonicalizePath(path).key;
4104
4138
  const grouped = groupErrorsByKey(entries);
4139
+ if (path.length === 0) st.skipErrorSeed();
4105
4140
  if (!grouped.has(parentKey)) setErrorChannelForKey(st, parentKey, "schema", NO_ERRORS);
4106
- for (const [existingKey, cell] of st.errorCells) {
4141
+ for (const [existingKey, cell] of st.errorCells()) {
4107
4142
  if (existingKey === parentKey) continue;
4108
4143
  if (cell.schema.length === 0) continue;
4109
4144
  if (isPathKeyUnder(existingKey, path) && !grouped.has(existingKey)) {
@@ -4115,15 +4150,15 @@ function applySchemaErrorsForSubtree(st, path, entries) {
4115
4150
  }
4116
4151
  }
4117
4152
  function restoreErrorCells(st, entries) {
4118
- st.errorCells.clear();
4153
+ st.skipErrorSeed().clear();
4119
4154
  for (const [key, cell] of entries) {
4120
4155
  if (cell.schema.length === 0 && cell.user.length === 0) continue;
4121
- st.errorCells.set(key, { schema: [...cell.schema], user: [...cell.user] });
4156
+ st.errorCells().set(key, { schema: [...cell.schema], user: [...cell.user] });
4122
4157
  }
4123
4158
  }
4124
4159
  function getErrorsForPath(st, path) {
4125
4160
  const { key } = canonicalizePath(path);
4126
- const cell = st.errorCells.get(key);
4161
+ const cell = st.errorCells().get(key);
4127
4162
  const blankForKey = st.derivedBlankErrors.value.get(key);
4128
4163
  if (cell === void 0 && blankForKey === void 0) {
4129
4164
  return [];
@@ -4160,13 +4195,13 @@ function markFocused(st, path, focused, meta) {
4160
4195
  touchFieldRecord(st, key, path, {
4161
4196
  focused,
4162
4197
  blurred: !focused,
4163
- // `touched` flips to true on blur and stays true thereafter; while
4164
- // a field is currently focused we keep whatever value it held.
4198
+ // `touched` flips true on blur and stays; a focused field keeps whatever
4199
+ // it held.
4165
4200
  touched: focused ? current?.touched ?? false : true,
4166
- // `blurredAfterInteraction` flips true on the first blur that lands
4167
- // after a value edit and stays true. A tab-through blur before any
4168
- // edit leaves it false (`interacted` is still false at that blur),
4169
- // which is what keeps a clean tab-through from arming the gate.
4201
+ // `blurredAfterInteraction` flips true on the first blur after a value
4202
+ // edit and stays. A tab-through blur before any edit leaves it false,
4203
+ // since `interacted` is still false, which is what keeps a clean
4204
+ // tab-through from arming the gate.
4170
4205
  blurredAfterInteraction: !focused && current?.interacted === true ? true : current?.blurredAfterInteraction ?? false
4171
4206
  });
4172
4207
  const focusMode = meta?.instance?.validateOn ?? st.fieldValidationMode;
@@ -4273,7 +4308,14 @@ function adoptResolvedDefaults(st, value) {
4273
4308
  st.schema
4274
4309
  )
4275
4310
  );
4276
- seedOriginalsFromBaseline(st, computeBaselineResponse(st.schema, st.defaultValues).data, false);
4311
+ seedOriginalsFromBaseline(
4312
+ st,
4313
+ computeBaseline(
4314
+ st.schema,
4315
+ st.defaultValues,
4316
+ st.schema.getDefaultValues({ useDefaultSchemaValues: true })
4317
+ )
4318
+ );
4277
4319
  }
4278
4320
  function reset(st, nextDefaultValues) {
4279
4321
  const resetSource = nextDefaultValues === void 0 ? st.defaultValues : mergeSparseHydration(
@@ -4282,13 +4324,16 @@ function reset(st, nextDefaultValues) {
4282
4324
  st.schema
4283
4325
  );
4284
4326
  st.defaultValues = structuralSnapshot(resetSource);
4285
- const resetResponse = computeBaselineResponse(st.schema, resetSource);
4286
- const next = resetResponse.data;
4327
+ const next = computeBaseline(
4328
+ st.schema,
4329
+ resetSource,
4330
+ st.schema.getDefaultValues({ useDefaultSchemaValues: true })
4331
+ );
4287
4332
  rebuildAuthoredPaths(st, resetSource, next);
4288
4333
  applyFormReplacement(st, next);
4289
4334
  st.arrayIdentity.rebaselineAll();
4290
4335
  st.removedSubtrees.clear();
4291
- seedOriginalsFromBaseline(st, next, false);
4336
+ seedOriginalsFromBaseline(st, next);
4292
4337
  if (nextDefaultValues !== void 0) {
4293
4338
  const mentioned = /* @__PURE__ */ new Set();
4294
4339
  walkAuthoredFromConstraints(nextDefaultValues, [], mentioned);
@@ -4298,14 +4343,8 @@ function reset(st, nextDefaultValues) {
4298
4343
  for (const key of st.originalBlankPaths) {
4299
4344
  st.blankPaths.add(key);
4300
4345
  }
4301
- st.errorCells.clear();
4302
- if (!resetResponse.success) {
4303
- replaceErrorChannel(st, "schema", resetResponse.errors);
4304
- }
4305
- const syncResult = st.schema.validateAtPath(st.form.value, void 0, { sync: true });
4306
- if (!(syncResult instanceof Promise) && !syncResult.success) {
4307
- applySchemaErrorsForSubtree(st, [], syncResult.errors);
4308
- }
4346
+ st.skipErrorSeed().clear();
4347
+ replaceErrorChannel(st, "schema", st.schema.validateSync(next));
4309
4348
  st.firstValidationDone.value = initialFirstValidationGate(st.schema);
4310
4349
  queueInitialAsyncValidation(st);
4311
4350
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4382,7 +4421,7 @@ function resetField(st, path) {
4382
4421
  }
4383
4422
  }
4384
4423
  function deleteErrorCellsUnderPrefix(st, prefix) {
4385
- for (const [errorKey, cell] of st.errorCells) {
4424
+ for (const [errorKey, cell] of st.errorCells()) {
4386
4425
  for (const src of ERROR_SOURCES) {
4387
4426
  const first = cell[src][0];
4388
4427
  if (first !== void 0 && isPathPrefix(prefix, first.path)) {
@@ -4447,8 +4486,8 @@ function createFormStore(options) {
4447
4486
  const fieldValidatingSince = reactive(/* @__PURE__ */ new Map());
4448
4487
  const displayEngine = createDisplayEngine(ssr);
4449
4488
  cleanupHooks.push(() => displayEngine.dispose());
4450
- const schemaResponse = computeBaselineResponse(schema, defaultValues);
4451
- const schemaInitialData = schemaResponse.data;
4489
+ const ownDefaultValues = structuralSnapshot(defaultValues);
4490
+ const schemaInitialData = computeBaseline(schema, ownDefaultValues, options.schemaDefaults);
4452
4491
  const authoredPaths = /* @__PURE__ */ new Set();
4453
4492
  const initialData = hydration !== void 0 ? hydration.form : structuralSnapshot(schemaInitialData);
4454
4493
  const hasDU = schema.hasDiscriminatedUnions?.() !== false;
@@ -4462,7 +4501,11 @@ function createFormStore(options) {
4462
4501
  });
4463
4502
  const fields = shallowReactive(/* @__PURE__ */ new Map());
4464
4503
  const domBinding = shallowRef(null);
4465
- const errorCells = shallowReactive(/* @__PURE__ */ new Map());
4504
+ const errorStore = createErrorCells(
4505
+ schema,
4506
+ hydration === void 0 ? schemaInitialData : void 0
4507
+ );
4508
+ const errorCells = () => errorStore(true);
4466
4509
  const originals = shallowReactive(/* @__PURE__ */ new Map());
4467
4510
  const removedSubtrees = /* @__PURE__ */ new Set();
4468
4511
  const blankPaths = reactive(/* @__PURE__ */ new Set());
@@ -4486,7 +4529,7 @@ function createFormStore(options) {
4486
4529
  return result;
4487
4530
  });
4488
4531
  const errorPathIndex = computed(
4489
- () => buildErrorPathIndex(errorCells, derivedBlankErrors.value)
4532
+ () => buildErrorPathIndex(errorCells(), derivedBlankErrors.value)
4490
4533
  );
4491
4534
  const submitting = ref(false);
4492
4535
  const activeSubmissions = ref(0);
@@ -4618,21 +4661,16 @@ function createFormStore(options) {
4618
4661
  blankPaths,
4619
4662
  originalBlankPaths,
4620
4663
  // --- kernel-internal state ---
4621
- // Defensive copy, not a fix for an observed alias: today
4622
- // `getDefaultValues` happens to build a fresh tree, so form storage
4623
- // does not currently share structure with the consumer's object.
4624
- // That is an adapter implementation detail rather than a contract,
4625
- // and this field is now durable state that every `reset()` reads, so
4626
- // it should not depend on it. The copy also means a consumer who
4627
- // keeps a reference to the literal they passed cannot mutate the
4628
- // form's defaults from outside. `reset()` snapshots for a harder
4629
- // reason (see there) where an alias IS reachable.
4630
- defaultValues: structuralSnapshot(defaultValues),
4664
+ // The copy taken above, which also stops a consumer holding a reference to
4665
+ // the literal they passed from mutating the form's defaults from outside.
4666
+ // `reset()` snapshots for a harder reason, where an alias IS reachable.
4667
+ defaultValues: ownDefaultValues,
4631
4668
  ssrPrefetch,
4632
4669
  rememberVariants,
4633
4670
  fieldValidationMode,
4634
4671
  fieldValidationDebounceMs,
4635
4672
  hasDU,
4673
+ hasPreprocessOrCoerce: schema.hasPreprocessOrCoerce?.() !== false,
4636
4674
  fieldValidationState,
4637
4675
  formChangeListeners,
4638
4676
  submitSuccessListeners,
@@ -4648,6 +4686,7 @@ function createFormStore(options) {
4648
4686
  transformRuns,
4649
4687
  transformWaiters,
4650
4688
  arrayBookkeeping,
4689
+ skipErrorSeed: () => errorStore(false),
4651
4690
  nextOrdinal: 0,
4652
4691
  scheduleEpoch: 0,
4653
4692
  lastCommittedEpoch: 0,
@@ -4664,10 +4703,10 @@ function createFormStore(options) {
4664
4703
  arrayElementKey: bind(arrayElementKey),
4665
4704
  reset: bind(reset),
4666
4705
  resetField: bind(resetField),
4667
- // The schema/user pair below reads as a fold waiting to happen. It was
4668
- // tried: a `setErrorsForPathIn(channel)` factory measured 10 B LARGER,
4669
- // because gzip had already collected the rent on two adjacent copies
4670
- // and the helper added a name the original did not need.
4706
+ // The schema/user pair below reads as a fold waiting to happen. Folding
4707
+ // them into a `setErrorsForPathIn(channel)` factory measures 10 B LARGER:
4708
+ // gzip has already collected the rent on two adjacent copies, and the
4709
+ // helper adds a name the original does not need.
4671
4710
  setSchemaErrorsForPath: (path, entries) => setErrorChannelForKey(
4672
4711
  st,
4673
4712
  canonicalizePath(path).key,
@@ -4715,7 +4754,25 @@ function createFormStore(options) {
4715
4754
  dispose: bind(dispose)
4716
4755
  };
4717
4756
  rebuildAuthoredPaths(st, defaultValues, schemaInitialData);
4718
- seedOriginalsFromBaseline(st, schemaInitialData, true);
4757
+ const initStamp = (/* @__PURE__ */ new Date()).toISOString();
4758
+ diffAndApply({}, schemaInitialData, [], (patch) => {
4759
+ if (patch.kind !== "added") return;
4760
+ const { key } = canonicalizePath(patch.path);
4761
+ originals.set(key, { segments: patch.path, value: patch.newValue });
4762
+ ensurePathOrdinal(st, key);
4763
+ if (hydration === void 0) {
4764
+ fields.set(key, {
4765
+ path: patch.path,
4766
+ updatedAt: initStamp,
4767
+ connected: false,
4768
+ focused: null,
4769
+ blurred: null,
4770
+ touched: false,
4771
+ interacted: false,
4772
+ blurredAfterInteraction: false
4773
+ });
4774
+ }
4775
+ });
4719
4776
  if (hydration !== void 0) {
4720
4777
  for (const [rawKey, record] of hydration.fields) {
4721
4778
  if (typeof rawKey !== "string" || !isHydratedFieldRecord(record)) {
@@ -4735,25 +4792,6 @@ function createFormStore(options) {
4735
4792
  }
4736
4793
  setErrorChannelForKey(st, rawKey, "user", errs);
4737
4794
  }
4738
- } else {
4739
- const initStamp = (/* @__PURE__ */ new Date()).toISOString();
4740
- diffAndApply({}, initialData, [], (patch) => {
4741
- if (patch.kind !== "added") return;
4742
- const { key } = canonicalizePath(patch.path);
4743
- fields.set(key, {
4744
- path: patch.path,
4745
- updatedAt: initStamp,
4746
- connected: false,
4747
- focused: null,
4748
- blurred: null,
4749
- touched: false,
4750
- interacted: false,
4751
- blurredAfterInteraction: false
4752
- });
4753
- });
4754
- if (!schemaResponse.success) {
4755
- replaceErrorChannel(st, "schema", schemaResponse.errors);
4756
- }
4757
4795
  }
4758
4796
  queueInitialAsyncValidation(st);
4759
4797
  return st;
@@ -4846,9 +4884,11 @@ const HISTORY_MODULE_KEY = "history";
4846
4884
  function buildFreshState(key, schema, configuration, registry) {
4847
4885
  const pending = registry.pendingHydration.get(key);
4848
4886
  if (pending !== void 0) registry.pendingHydration.delete(key);
4887
+ const schemaDefaults = schema.getDefaultValues({ useDefaultSchemaValues: true });
4849
4888
  const walked = walkUnsetSentinels(
4850
4889
  configuration.defaultValues,
4851
- schema
4890
+ schema,
4891
+ schemaDefaults
4852
4892
  );
4853
4893
  let initialBlankPaths;
4854
4894
  if (pending === void 0) {
@@ -4858,6 +4898,7 @@ function buildFreshState(key, schema, configuration, registry) {
4858
4898
  formKey: key,
4859
4899
  schema,
4860
4900
  defaultValues: walked.cleanedValues,
4901
+ schemaDefaults,
4861
4902
  hydration: pending,
4862
4903
  ssr: registry.ssr,
4863
4904
  ...pickDefined({
@@ -4868,12 +4909,10 @@ function buildFreshState(key, schema, configuration, registry) {
4868
4909
  coerce: configuration.coerce,
4869
4910
  initialBlankPaths
4870
4911
  }),
4871
- // Server-only: bind the SSR prefetch coordination handles. `enqueue`
4872
- // records intent on every `state.activate()` so a wizard skip-list
4873
- // override or a future transform mark has a consistent set to diff
4874
- // against; `shouldFire` lets the activate path bail when the
4875
- // wizard explicitly skipped this key — even an explicit
4876
- // `form.activate()` defers to the wizard's render-efficiency
4912
+ // Server only. `enqueue` records intent on every `state.activate()`
4913
+ // so a wizard skip-list override has a consistent set to diff
4914
+ // against; `shouldFire` lets the activate path bail on a key the
4915
+ // wizard skipped. Even an explicit `form.activate()` defers to that
4877
4916
  // skip-list on the server.
4878
4917
  ...registry.ssr ? {
4879
4918
  ssrPrefetch: {
@@ -4910,6 +4949,7 @@ function resolveFormKey(key) {
4910
4949
  }
4911
4950
 
4912
4951
  let injectedInstanceCounter = 0;
4952
+ const sharedApis = /* @__PURE__ */ new WeakMap();
4913
4953
  function injectForm(input) {
4914
4954
  const key = typeof input === "string" ? input : input?.key;
4915
4955
  const ssrAccessed = typeof input === "object" && input !== null ? input.__ssrAccessed === true : false;
@@ -4932,12 +4972,37 @@ function injectForm(input) {
4932
4972
  apiOptions.history = history;
4933
4973
  }
4934
4974
  const ambientInstanceId = getCurrentInstance() !== null ? inject(kFormInstanceId, null) : null;
4935
- const formInstanceId = ambientInstanceId ?? (getCurrentInstance() !== null ? useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`);
4936
- return buildFormApi(
4975
+ const api = ambientInstanceId !== null ? shareApi(state, ambientInstanceId, apiOptions) : buildFormApi(
4937
4976
  state,
4938
- formInstanceId,
4977
+ getCurrentInstance() !== null ? useId() : `atta:form-instance-injected:${injectedInstanceCounter++}`,
4939
4978
  apiOptions
4940
4979
  );
4980
+ return api;
4981
+ }
4982
+ function shareApi(state, formInstanceId, options) {
4983
+ let instances = sharedApis.get(state);
4984
+ if (instances === void 0) {
4985
+ instances = /* @__PURE__ */ new Map();
4986
+ sharedApis.set(state, instances);
4987
+ }
4988
+ const byInstance = instances;
4989
+ let shared = byInstance.get(formInstanceId);
4990
+ if (shared === void 0) {
4991
+ const scope = effectScope(true);
4992
+ const api = scope.run(() => buildFormApi(state, formInstanceId, options));
4993
+ if (api === void 0) return buildFormApi(state, formInstanceId, options);
4994
+ shared = { api, scope, users: 0 };
4995
+ byInstance.set(formInstanceId, shared);
4996
+ }
4997
+ const entry = shared;
4998
+ entry.users += 1;
4999
+ onScopeDispose(() => {
5000
+ entry.users -= 1;
5001
+ if (entry.users > 0) return;
5002
+ entry.scope.stop();
5003
+ byInstance.delete(formInstanceId);
5004
+ });
5005
+ return entry.api;
4941
5006
  }
4942
5007
  function resolveState(key, registry) {
4943
5008
  if (key !== void 0) {
@@ -4997,22 +5062,23 @@ function useRegister(options) {
4997
5062
  const capturedRegisterValue = shallowRef(void 0);
4998
5063
  const refreshAndStripBridgeAttrs = () => {
4999
5064
  const rawAttrs = instance.attrs;
5065
+ const parentBinding = instance.vnode.dirs?.find(
5066
+ (dir) => dir.dir?.[V_REGISTER_MARKER] === true
5067
+ );
5000
5068
  if ("registerValue" in rawAttrs) {
5001
5069
  capturedRegisterValue.value = rawAttrs["registerValue"];
5002
5070
  delete rawAttrs["registerValue"];
5003
- if (capturedRegisterValue.value !== void 0) armDomBinding(capturedRegisterValue.value);
5004
- } else {
5005
- const dirs = instance.vnode.dirs;
5006
- if (dirs !== null && dirs !== void 0) {
5007
- for (const dir of dirs) {
5008
- const marked = dir.dir?.[V_REGISTER_MARKER];
5009
- if (marked === true) {
5010
- capturedRegisterValue.value = dir.value;
5011
- if (capturedRegisterValue.value !== void 0)
5012
- armDomBinding(capturedRegisterValue.value);
5013
- break;
5014
- }
5015
- }
5071
+ } else if (parentBinding !== void 0) {
5072
+ capturedRegisterValue.value = parentBinding.value;
5073
+ }
5074
+ const captured = capturedRegisterValue.value;
5075
+ if (captured !== void 0) {
5076
+ armDomBinding(captured);
5077
+ if (parentBinding !== void 0) {
5078
+ recordInheritedModifiers(
5079
+ captured,
5080
+ withInheritedModifiers(parentBinding.value, parentBinding.modifiers)
5081
+ );
5016
5082
  }
5017
5083
  }
5018
5084
  if ("value" in rawAttrs) delete rawAttrs["value"];
@@ -5119,11 +5185,11 @@ const NOOP_SUCCESS = {
5119
5185
  data: EMPTY_VALUE,
5120
5186
  errors: void 0
5121
5187
  };
5122
- const NOOP_DEFAULTS = NOOP_SUCCESS;
5123
5188
  let NOOP_SCHEMA;
5124
5189
  function buildNoopWizardSchema() {
5125
5190
  NOOP_SCHEMA ?? (NOOP_SCHEMA = {
5126
- getDefaultValues: () => NOOP_DEFAULTS,
5191
+ getDefaultValues: () => EMPTY_VALUE,
5192
+ validateSync: () => [],
5127
5193
  getDefaultAtPath: () => void 0,
5128
5194
  getEmptyValueAtPath: () => void 0,
5129
5195
  isPreprocessOrCoerceLeaf: () => false,
@@ -5139,7 +5205,8 @@ function buildNoopWizardSchema() {
5139
5205
  isLeafAtPath: () => false,
5140
5206
  isRequiredAtPath: () => false,
5141
5207
  getUnionDiscriminatorAtPath: () => void 0,
5142
- hasDiscriminatedUnions: () => false
5208
+ hasDiscriminatedUnions: () => false,
5209
+ hasPreprocessOrCoerce: () => false
5143
5210
  });
5144
5211
  return NOOP_SCHEMA;
5145
5212
  }
@@ -5335,27 +5402,33 @@ function useWizard(options) {
5335
5402
  return activeKey.value === "" ? void 0 : activeKey.value;
5336
5403
  }
5337
5404
  };
5338
- function lazyComputedFor(marker) {
5405
+ function lazyComputedFor(marker, index) {
5339
5406
  const cached = lazyComputeds.get(marker);
5340
5407
  if (cached !== void 0) return cached;
5341
5408
  const c = computed(() => {
5342
5409
  void lazyEpoch.value;
5343
- return marker.resolve(slotCtx);
5410
+ return callConsumerFn(
5411
+ () => marker.resolve(slotCtx),
5412
+ void 0);
5344
5413
  });
5345
5414
  lazyComputeds.set(marker, c);
5346
5415
  return c;
5347
5416
  }
5348
5417
  const MAX_SLOT_DEPTH = 32;
5349
- function normalizeSlot(slot, ctx, gated, depth) {
5418
+ function normalizeSlot(slot, ctx, gated, depth, index) {
5350
5419
  if (depth > MAX_SLOT_DEPTH) {
5351
5420
  return void 0;
5352
5421
  }
5353
5422
  if (slot === void 0 || slot === null) return void 0;
5354
5423
  if (typeof slot === "string") return { form: getOrBuildNoop(slot), gated };
5355
5424
  if (isGateMarker(slot)) return normalizeSlot(slot.inner, ctx, true, depth + 1);
5356
- if (isLazyMarker(slot)) return normalizeSlot(lazyComputedFor(slot).value, ctx, gated, depth + 1);
5425
+ if (isLazyMarker(slot)) {
5426
+ return normalizeSlot(lazyComputedFor(slot).value, ctx, gated, depth + 1);
5427
+ }
5357
5428
  if (typeof slot === "function") {
5358
- const result = slot(ctx);
5429
+ const result = callConsumerFn(
5430
+ () => slot(ctx),
5431
+ void 0);
5359
5432
  return normalizeSlot(result, ctx, gated, depth + 1);
5360
5433
  }
5361
5434
  if (isAnyForm(slot)) return { form: slot, gated };
@@ -5673,13 +5746,14 @@ function useWizard(options) {
5673
5746
  seedRef.value = resolved.value;
5674
5747
  applyGateSeed(resolved.value);
5675
5748
  } else {
5676
- const eager = resolved.factory();
5749
+ const eager = callConsumerFn(() => resolved.factory(), void 0);
5677
5750
  if (eager instanceof Promise) {
5678
5751
  void eager.then((value) => {
5679
5752
  seedRef.value = value;
5680
5753
  applyGateSeed(value);
5754
+ }).catch((err) => {
5681
5755
  });
5682
- } else {
5756
+ } else if (eager !== void 0) {
5683
5757
  seedRef.value = eager;
5684
5758
  applyGateSeed(eager);
5685
5759
  }
@@ -5752,10 +5826,13 @@ function useWizard(options) {
5752
5826
  const statuses = buildWizardStatusesProxy(statusesRecord);
5753
5827
  const progressOverride = options.progress;
5754
5828
  const progress = computed(() => {
5829
+ const list = compiledSteps.value;
5755
5830
  if (progressOverride !== void 0) {
5756
- return progressOverride(compiledSteps.value);
5831
+ const override = callConsumerFn(
5832
+ () => progressOverride(list),
5833
+ null);
5834
+ if (override !== null) return override;
5757
5835
  }
5758
- const list = compiledSteps.value;
5759
5836
  if (list.length === 0) return 0;
5760
5837
  let valid = 0;
5761
5838
  for (const step of list) {
@@ -5783,11 +5860,15 @@ function useWizard(options) {
5783
5860
  historyHandle.subscribe((value) => {
5784
5861
  urlMirror.value = value;
5785
5862
  });
5786
- const restoreCallback = options.restore === false ? void 0 : options.restore !== void 0 ? options.restore : () => {
5863
+ const consumerRestore = options.restore;
5864
+ const restoreCallback = consumerRestore === false ? void 0 : consumerRestore !== void 0 ? () => callConsumerFn(() => consumerRestore(), void 0) : () => {
5787
5865
  const value = urlMirror.value;
5788
5866
  return value === void 0 ? void 0 : { step: value };
5789
5867
  };
5790
- const persistCallback = options.persist === false ? void 0 : options.persist !== void 0 ? options.persist : (state) => {
5868
+ const consumerPersist = options.persist;
5869
+ const persistCallback = consumerPersist === false ? void 0 : consumerPersist !== void 0 ? (state) => {
5870
+ callConsumerFn(() => consumerPersist(state), void 0);
5871
+ } : (state) => {
5791
5872
  if (state.step === void 0) return;
5792
5873
  const current = historyHandle.read();
5793
5874
  const effectiveCurrent = current !== void 0 && isCompiledKey(current) ? current : firstKey();
@@ -6038,7 +6119,7 @@ function useWizard(options) {
6038
6119
  for (const key of keys) {
6039
6120
  const store = registry.forms.get(key);
6040
6121
  if (store === void 0) continue;
6041
- for (const cell of store.errorCells.values()) {
6122
+ for (const cell of store.errorCells().values()) {
6042
6123
  for (const err of cell.user) out.push(toWizardAggregateError(err, key));
6043
6124
  }
6044
6125
  }