attaform 0.30.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 (67) hide show
  1. package/dist/abstract.d.mts +15 -21
  2. package/dist/abstract.mjs +2 -2
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/devtools.mjs +2 -2
  5. package/dist/dev/directive.mjs +1 -1
  6. package/dist/dev/history.mjs +1 -1
  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.BK6Ge2qR.mjs → attaform-dev.BJK1kqHY.mjs} +14 -14
  10. package/dist/dev/shared/{attaform-dev.D6CLvaQT.mjs → attaform-dev.BMQ3GcgD.mjs} +46 -59
  11. package/dist/dev/shared/{attaform-dev.BisFstxh.mjs → attaform-dev.BUIFcTYf.mjs} +6 -6
  12. package/dist/dev/shared/{attaform-dev.DTrHonpc.mjs → attaform-dev.CT7MDjPy.mjs} +27 -36
  13. package/dist/dev/shared/{attaform-dev.BcYT6tFh.mjs → attaform-dev.CZz4GYZ6.mjs} +21 -9
  14. package/dist/dev/shared/{attaform-dev.DUZckyV8.mjs → attaform-dev.Chm4D6SJ.mjs} +2 -2
  15. package/dist/dev/shared/{attaform-dev.BcNtyOmf.mjs → attaform-dev.DuEE4GU1.mjs} +4 -4
  16. package/dist/dev/shared/{attaform-dev.CQ2rJiAX.mjs → attaform-dev.DxHdkkPG.mjs} +126 -115
  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 +1 -1
  27. package/dist/index.d.mts +67 -70
  28. package/dist/index.mjs +3 -3
  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 +12 -12
  36. package/dist/runtime/components/DevtoolsValueTree.vue +2 -2
  37. package/dist/shared/{attaform.Rc9u4Vfw.d.mts → attaform.2FslhB9R.d.mts} +83 -95
  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.ByWP6kSx.mjs → attaform.B2iebkPA.mjs} +3 -3
  42. package/dist/shared/{attaform.DhnPb6nK.mjs → attaform.BAf3UNn4.mjs} +1 -1
  43. package/dist/shared/{attaform.DNJOWjQw.mjs → attaform.BIWicztt.mjs} +102 -100
  44. package/dist/shared/attaform.BcQwgWaq.d.mts +2153 -0
  45. package/dist/shared/attaform.Br8EBn5B.d.mts +4553 -0
  46. package/dist/shared/{attaform.BXneBxEj.mjs → attaform.CO918TdU.mjs} +13 -13
  47. package/dist/shared/{attaform.CUfrMK7t.mjs → attaform.DRsANMok.mjs} +5 -5
  48. package/dist/shared/{attaform.B3resMQ1.mjs → attaform.D_f1WgYf.mjs} +40 -53
  49. package/dist/shared/{attaform.CJ8DpRGj.mjs → attaform.D_pt1gOj.mjs} +26 -33
  50. package/dist/shared/attaform.L0kUn3vL.d.mts +42 -0
  51. package/dist/shared/{attaform.BK0cgu2K.mjs → attaform.ZYn2ahzJ.mjs} +3 -3
  52. package/dist/transforms.d.mts +153 -26
  53. package/dist/transforms.mjs +1 -1
  54. package/dist/vite.d.mts +29 -52
  55. package/dist/vite.mjs +3 -3
  56. package/dist/webpack.d.mts +20 -12
  57. package/dist/webpack.mjs +1 -1
  58. package/dist/zod-v3.d.mts +90 -96
  59. package/dist/zod-v3.mjs +4 -4
  60. package/dist/zod-v4.d.mts +118 -118
  61. package/dist/zod-v4.mjs +4 -4
  62. package/dist/zod.d.mts +5 -5
  63. package/dist/zod.mjs +3 -3
  64. package/package.json +7 -6
  65. package/dist/shared/attaform.Clz0ArIi.d.mts +0 -44
  66. package/dist/shared/attaform.UGyOK83T.d.mts +0 -2366
  67. package/dist/shared/attaform.bKLzMU0o.d.mts +0 -4761
@@ -1,7 +1,7 @@
1
- import { j as humanize, c as useAbstractForm } from './attaform.DNJOWjQw.mjs';
1
+ import { j as humanize, c as useAbstractForm } from './attaform.BIWicztt.mjs';
2
2
  import { A as AttaformError, e as canonicalizePath, a as InvalidUseFormConfigError } from './attaform.CdZIEf7_.mjs';
3
- import { s as slimPrimitivesWalk, d as deriveDefaultWalk, m as mergeDeep, f as fixStructuralDefaults, w as walkPathSegments, c as createAbstractSchema, a as createSharedSchemaStore, b as buildFieldMetaPathMap, e as getFieldMetaListForSchema, g as getFieldMetaForSchema } from './attaform.CUfrMK7t.mjs';
4
- import { c as callConsumerSchemaFn } from './attaform.BRDDQeHV.mjs';
3
+ import { s as slimPrimitivesWalk, d as deriveDefaultWalk, m as mergeDeep, f as fixStructuralDefaults, w as walkPathSegments, c as createAbstractSchema, a as createSharedSchemaStore, b as buildFieldMetaPathMap, e as getFieldMetaListForSchema, g as getFieldMetaForSchema } from './attaform.DRsANMok.mjs';
4
+ import { c as callConsumerFn } from './attaform.8mfKZCwH.mjs';
5
5
 
6
6
  function readDef(schema) {
7
7
  if (schema === null || typeof schema !== "object") return void 0;
@@ -131,7 +131,7 @@ function unwrapLazy(schema) {
131
131
  const def = readDef(schema);
132
132
  const getter = def?.getter;
133
133
  if (typeof getter !== "function") return void 0;
134
- return callConsumerSchemaFn(() => getter(), void 0);
134
+ return callConsumerFn(() => getter(), void 0);
135
135
  }
136
136
  function getIntersectionLeft(schema) {
137
137
  const def = readDef(schema);
@@ -145,7 +145,7 @@ function getCatchDefault(schema) {
145
145
  const def = readDef(schema);
146
146
  const cv = def?.catchValue;
147
147
  if (typeof cv !== "function") return void 0;
148
- return callConsumerSchemaFn(
148
+ return callConsumerFn(
149
149
  () => cv({ error: new Error("atta:default-values"), input: void 0 }),
150
150
  void 0);
151
151
  }
@@ -155,7 +155,7 @@ function hasCatchValue(schema) {
155
155
  }
156
156
  function getDefaultValue(schema) {
157
157
  const def = readDef(schema);
158
- return callConsumerSchemaFn(() => def?.defaultValue, void 0);
158
+ return callConsumerFn(() => def?.defaultValue, void 0);
159
159
  }
160
160
  function getNativeEnumValues(_schema) {
161
161
  return void 0;
@@ -335,10 +335,10 @@ function zodIssuesToValidationErrors(issues) {
335
335
  }
336
336
  return {
337
337
  message: issue.message,
338
- // Adapter-side paths stay schema-relative — the validation
339
- // pipeline in `create-form-store.ts` prepends the parent path
340
- // to absolutise, then routes form-level (absolute path length 0)
341
- // entries to the empty-string bucket at storage time.
338
+ // Adapter-side paths stay schema-RELATIVE: the validation pipeline
339
+ // in `core/create-form-store.ts` prepends the parent path to
340
+ // absolutise, then routes form-level entries, those whose absolute
341
+ // path is empty, to the empty-string bucket at storage time.
342
342
  path: issue.path.map((seg) => typeof seg === "number" ? seg : String(seg)),
343
343
  code
344
344
  };
@@ -533,9 +533,9 @@ const sharedV4Schemas = createSharedSchemaStore();
533
533
  function buildV4Services() {
534
534
  return {
535
535
  getNestedSchemasAtPath: (schema, path, maxRecursionDepth) => getNestedZodSchemasAtPath(schema, path, maxRecursionDepth),
536
- // v4 doesn't pre-strip for the slim-mode walk — its path walker
537
- // already peels every transparent wrapper inline, so the slim and
538
- // unstripped walks coincide.
536
+ // No pre-strip for the slim-mode walk: the path walker peels every
537
+ // transparent wrapper inline, so the slim and unstripped walks
538
+ // coincide.
539
539
  getNestedSchemasInSlimMode: (schema, path, maxRecursionDepth) => getNestedZodSchemasAtPath(schema, path, maxRecursionDepth),
540
540
  slimPrimitivesOf: (schema, maxRecursionDepth) => slimPrimitivesOf(schema, maxRecursionDepth),
541
541
  deriveDefault: (schema, useDefault, maxRecursionDepth) => deriveDefault(schema, useDefault, maxRecursionDepth),
@@ -1,6 +1,6 @@
1
- import { A as AttaformErrorCode, k as isPlainRecord, s as slimKindOf } from './attaform.DNJOWjQw.mjs';
1
+ import { A as AttaformErrorCode, k as isPlainRecord, s as slimKindOf } from './attaform.BIWicztt.mjs';
2
2
  import { e as canonicalizePath, h as SET_MEMBER_SEGMENT } from './attaform.CdZIEf7_.mjs';
3
- import { a as safeOwnRead, b as safeAssign } from './attaform.BRDDQeHV.mjs';
3
+ import { a as safeOwnRead, b as safeAssign } from './attaform.8mfKZCwH.mjs';
4
4
 
5
5
  const PATH_SEPARATOR = ".";
6
6
  function isOpaqueKind(kind) {
@@ -425,8 +425,8 @@ function walkPathSegments(schema, segments, intro, maxDepth, lazyDepth) {
425
425
  const rightResults = right === void 0 ? [] : walkPathSegments(right, segments, intro, maxDepth, lazyDepth);
426
426
  return [...leftResults, ...rightResults];
427
427
  }
428
- // Leaves — can't descend further. Opaque leaves (`any` /
429
- // `unknown` / `custom`) land here too and `[]` is the truthful
428
+ // Leaves, with nothing further to descend. An opaque leaf (`any` /
429
+ // `unknown` / `custom`) lands here too and `[]` is the truthful
430
430
  // answer: the schema declares no sub-paths under them, so none are
431
431
  // fabricated. `symbol` / `function` / `promise` hold a value
432
432
  // without declaring anything inside it and land here for the same
@@ -1017,7 +1017,7 @@ function slimPrimitivesWalk(schema, intro, maxDepth, lazyDepth = 0) {
1017
1017
  case "unknown":
1018
1018
  case "custom":
1019
1019
  return PERMISSIVE_SLIM_KINDS;
1020
- // A kind this walker has no case for — in practice a kind a newer
1020
+ // A kind this walker has no case for, in practice one a newer
1021
1021
  // Zod introduced. Be permissive rather than reject: the adapter
1022
1022
  // carries the value opaquely and the schema's own parse is what
1023
1023
  // decides whether it was legal.
@@ -931,20 +931,15 @@ const vRegisterCheckbox = {
931
931
  el._lastAppliedModel = value.innerRef.value;
932
932
  });
933
933
  },
934
- // Skip the DOM sync when the model is identity-unchanged from the
935
- // last application. Pre-fix the scalar branch in `setChecked`
936
- // gated on `originalValue === oldValue`, comparing a primitive
937
- // scalar against the wrapper RegisterValue object — always !==,
938
- // so the guard was a silent no-op. Array / Set branches lacked
939
- // any guard. The per-render re-apply mirrors the just-fixed
940
- // `vRegisterSelect` shape: a sibling's reactive write triggers
941
- // `beforeUpdate` mid-click, `setChecked` re-applies the prior
942
- // model state, and the in-flight user toggle is clobbered before
943
- // the browser fires `change`. Identity comparison on
944
- // `innerRef.value` is sound for the same reason as multi-select —
945
- // every form write produces a fresh value at the path (new
946
- // primitives; new array/Set references along the spine), so
947
- // reference equality tracks "did the model move" exactly.
934
+ // Skip the DOM sync when the model is identity-unchanged since the last
935
+ // application. Without the guard, a sibling's reactive write triggers
936
+ // `beforeUpdate` mid-click, `setChecked` re-applies the prior model state,
937
+ // and the in-flight toggle is clobbered before the browser fires `change`.
938
+ // Compare `innerRef.value` by identity: every form write produces a fresh
939
+ // value at the path, new primitives and new array or Set references along
940
+ // the spine, so reference equality tracks "did the model move" exactly. The
941
+ // guard must NOT compare against `oldValue`, which is the previous binding's
942
+ // wrapper RegisterValue and never equals a model scalar.
948
943
  beforeUpdate(el, binding, vnode) {
949
944
  setAssignFunction(el, vnode, binding.value);
950
945
  if (!isRegisterValue(binding.value)) return;
@@ -994,13 +989,11 @@ const vRegisterRadio = {
994
989
  }
995
990
  });
996
991
  },
997
- // Initial checked-state sync runs in `mounted`, NOT `created` —
998
- // Vue's directive lifecycle fires `created` BEFORE the element's
999
- // attributes are patched (`type`, `value`, `_value` etc. aren't on
1000
- // the element yet), so `getValue(el)` would return `undefined` and
1001
- // every radio in a group would mount unchecked regardless of the
1002
- // model. Checkbox already uses `mounted: setChecked` for the same
1003
- // reason.
992
+ // The initial checked sync runs in `mounted`, NOT `created`: Vue fires
993
+ // `created` before the element's attributes are patched, so `type`, `value`
994
+ // and `_value` are absent, `getValue(el)` returns undefined, and every radio
995
+ // in a group mounts unchecked whatever the model says. Checkbox uses
996
+ // `mounted` for the same reason.
1004
997
  mounted(el, { value }) {
1005
998
  if (!isRegisterValue(value)) return;
1006
999
  el.checked = looseEqual(value.innerRef.value, applyCoerce(getValue(el), value));
@@ -1010,15 +1003,12 @@ const vRegisterRadio = {
1010
1003
  el._lastAppliedModel = value.innerRef.value;
1011
1004
  });
1012
1005
  },
1013
- // Skip the DOM sync when the model is identity-unchanged from the
1014
- // last application. Pre-fix the guard read `value.innerRef.value
1015
- // !== oldValue`, comparing a primitive scalar against the previous
1016
- // binding's wrapper RegisterValue object — always !==, so the
1017
- // guard was a silent no-op and `el.checked = …` re-applied on
1018
- // every parent re-render. Same shape as the just-fixed
1019
- // `vRegisterSelect` and `setChecked` bugs: a sibling's reactive
1020
- // write triggers `beforeUpdate` mid-click and writes back the
1021
- // prior model state, clobbering the in-flight selection.
1006
+ // Skip the DOM sync when the model is identity-unchanged since the last
1007
+ // application, tracked on `_lastAppliedModel`. Comparing against `oldValue`
1008
+ // instead compares a primitive scalar to the previous binding's wrapper
1009
+ // RegisterValue, never equal, so `el.checked` re-applies on every parent
1010
+ // re-render and a sibling's reactive write clobbers an in-flight selection
1011
+ // mid-click.
1022
1012
  beforeUpdate(el, { value }, vnode) {
1023
1013
  if (!isRegisterValue(value)) return;
1024
1014
  setAssignFunction(el, vnode, value);
@@ -1077,22 +1067,20 @@ const vRegisterSelect = {
1077
1067
  beforeUpdate(el, binding, vnode) {
1078
1068
  setAssignFunction(el, vnode, binding.value);
1079
1069
  },
1080
- // Skip the DOM sync when the model is identity-unchanged from the
1081
- // last application. Parent re-renders fire `updated` whether or not
1082
- // the bound model actually moved (a typed character in a sibling,
1083
- // an async-validation tick, any reactive read elsewhere on the
1084
- // page). Without this guard, every such render unconditionally re-
1085
- // applies `setSelected` against the prior model, which on a
1086
- // `<select multiple>` clobbers any in-progress user selection
1087
- // between mousedown and the browser's change-event decision — the
1088
- // browser then sees no net change, never fires `change`, and the
1089
- // model never updates. Identity comparison is sound: every form
1090
- // write produces a new array/Set reference at the path (the diff-
1091
- // apply replacement of `form.value` rolls forward fresh structures
1092
- // along the spine), so reference equality on `innerRef.value`
1093
- // tracks "did the model move" exactly. The `_assigning` gate stays
1094
- // — it short-circuits the immediate post-write render where the
1095
- // DOM is already in sync from the user's click.
1070
+ // Skip the DOM sync when the model is identity-unchanged since the last
1071
+ // application. A parent re-render fires `updated` whether or not the bound
1072
+ // model moved: a typed character in a sibling, an async-validation tick, any
1073
+ // reactive read elsewhere on the page. Re-applying `setSelected` against the
1074
+ // prior model on each of those clobbers an in-progress `<select multiple>`
1075
+ // selection between mousedown and the browser's change decision, after which
1076
+ // the browser sees no net change, never fires `change`, and the model never
1077
+ // updates.
1078
+ //
1079
+ // Identity comparison is sound: every form write produces a new array or Set
1080
+ // reference at the path, the replacement of `form.value` rolling fresh
1081
+ // structures forward along the spine. The `_assigning` gate stays as well,
1082
+ // short-circuiting the immediate post-write render where the DOM is already
1083
+ // in sync from the user's click.
1096
1084
  updated(el, { value }) {
1097
1085
  if (el._assigning === true) return;
1098
1086
  if (!isRegisterValue(value)) return;
@@ -1306,13 +1294,12 @@ const vRegisterDynamic = {
1306
1294
  delete el._syncFromStorage;
1307
1295
  delete el[assignKey];
1308
1296
  },
1309
- // The lifecycle hooks above don't run on the server (Vue skips
1310
- // directive lifecycle during SSR), so emit the same aria attributes
1311
- // here from the SSR-time gated display state. Honors authored attrs
1312
- // (vnode-level lockout), touches no DOM, and shares
1313
- // `resolveAriaValue` with the client path. Ids are SSR-stable
1314
- // (formInstanceId derives from Vue's useId), so a server-rendered
1315
- // describedby matches the client after hydration.
1297
+ // Vue skips directive lifecycle during SSR, so the hooks above never run on
1298
+ // the server and the same aria attributes are emitted here from the SSR-time
1299
+ // gated display state. Honours authored attrs through the vnode-level
1300
+ // lockout, touches no DOM, and shares `resolveAriaValue` with the client
1301
+ // path. Ids are SSR-stable, `formInstanceId` deriving from Vue's `useId`, so
1302
+ // a server-rendered describedby matches the client after hydration.
1316
1303
  getSSRProps(binding, vnode) {
1317
1304
  const rv = binding.value;
1318
1305
  if (!isRegisterValue(rv)) return void 0;
@@ -1,6 +1,6 @@
1
- import { w as walkPathSegments, s as slimPrimitivesWalk, c as createAbstractSchema, a as createSharedSchemaStore, b as buildFieldMetaPathMap, g as getFieldMetaForSchema, f as fixStructuralDefaults, m as mergeDeep, d as deriveDefaultWalk, e as getFieldMetaListForSchema } from './attaform.CUfrMK7t.mjs';
2
- import { h as getAtPath, j as humanize, c as useAbstractForm } from './attaform.DNJOWjQw.mjs';
3
- import { c as callConsumerSchemaFn } from './attaform.BRDDQeHV.mjs';
1
+ import { w as walkPathSegments, s as slimPrimitivesWalk, c as createAbstractSchema, a as createSharedSchemaStore, b as buildFieldMetaPathMap, g as getFieldMetaForSchema, f as fixStructuralDefaults, m as mergeDeep, d as deriveDefaultWalk, e as getFieldMetaListForSchema } from './attaform.DRsANMok.mjs';
2
+ import { h as getAtPath, j as humanize, c as useAbstractForm } from './attaform.BIWicztt.mjs';
3
+ import { c as callConsumerFn } from './attaform.8mfKZCwH.mjs';
4
4
  import { A as AttaformError, e as canonicalizePath, a as InvalidUseFormConfigError } from './attaform.CdZIEf7_.mjs';
5
5
 
6
6
  function isZodSchemaType(schema, expectedType) {
@@ -306,7 +306,7 @@ function unwrapLazy(schema) {
306
306
  const def = readDef(schema);
307
307
  const getter = def?.getter;
308
308
  if (typeof getter !== "function") return void 0;
309
- return callConsumerSchemaFn(() => getter(), void 0);
309
+ return callConsumerFn(() => getter(), void 0);
310
310
  }
311
311
  function getLiteralValues(schema) {
312
312
  const def = readDef(schema);
@@ -323,15 +323,13 @@ function getDefaultValue(schema) {
323
323
  const def = readDef(schema);
324
324
  const thunk = def?.defaultValue;
325
325
  if (typeof thunk !== "function") return void 0;
326
- return callConsumerSchemaFn(() => thunk(), void 0);
326
+ return callConsumerFn(() => thunk(), void 0);
327
327
  }
328
328
  function getCatchDefault(schema) {
329
329
  const def = readDef(schema);
330
330
  const cv = def?.catchValue;
331
331
  if (typeof cv !== "function") return void 0;
332
- return callConsumerSchemaFn(
333
- () => cv({ error: null, input: void 0 }),
334
- void 0);
332
+ return callConsumerFn(() => cv({ error: null, input: void 0 }), void 0);
335
333
  }
336
334
  function hasCatchValue(schema) {
337
335
  const def = readDef(schema);
@@ -830,13 +828,9 @@ function buildV3Services(maxRecursionDepth) {
830
828
  };
831
829
  return {
832
830
  getNestedSchemasAtPath: (schema, path, maxRecursionDepth2) => getNestedZodSchemasAtPath(schema, path, maxRecursionDepth2),
833
- // v3 pre-strips refinements / defaults / wrappers off the root for
834
- // slim-mode walks — `getSlimPrimitiveTypesAtPath` and
835
- // `getSchemasAtPath` both consume this variant so the yielded
836
- // candidates reflect the slim shape.
837
- // The slim-root projection is gone (size-teardown P7): the shared
838
- // path walker peels wrappers / effects inline, so the slim and
839
- // unstripped walks coincide — same aliasing v4 always had.
831
+ // The shared path walker peels wrappers and effects inline, so the
832
+ // slim and unstripped walks coincide and the root needs no slim
833
+ // projection of its own. Same aliasing v4 has.
840
834
  getNestedSchemasInSlimMode: (schema, path, maxRecursionDepth2) => getNestedZodSchemasAtPath(schema, path, maxRecursionDepth2),
841
835
  slimPrimitivesOf: (schema, _maxRecursionDepth) => slimPrimitivesV3(schema),
842
836
  deriveDefault: (schema, useDefault) => getDefaultValuesFromZodSchema(schema, useDefault),
@@ -855,10 +849,10 @@ function buildV3Services(maxRecursionDepth) {
855
849
  const result = await schema.safeParseAsync(data);
856
850
  return result.success ? { success: true, data: result.data } : { success: false, issues: refileIssues(schema, data, result.error.issues) };
857
851
  },
858
- // v3 returns the full recursive AbstractSchema for sub-schemas (the
859
- // historical shape) — `getSchemasAtPath` consumers may probe any
860
- // method on the result. The factory call rebuilds the full surface
861
- // against the sub-schema with its own per-form caches.
852
+ // A `getSchemasAtPath` consumer may probe any method on the result,
853
+ // so a sub-schema comes back as a full recursive AbstractSchema: the
854
+ // factory call rebuilds the whole surface against it, with its own
855
+ // per-form caches.
862
856
  makeSubSchema: (sub, maxRecursionDepth2) => createAbstractSchema(
863
857
  sub,
864
858
  V3_INTROSPECTOR,
@@ -878,17 +872,16 @@ function zodIssuesToValidationErrors(issues) {
878
872
  }
879
873
  validationErrors.push({
880
874
  message: issue.message,
881
- // `ValidationError.path` is `(string | number)[]` per the
882
- // public type. v3's `issue.path` is the same in the standard
883
- // case, but a custom check via `ctx.addIssue({ path: [...] })`
884
- // can smuggle a Symbol through — the public surface promised
885
- // strings/numbers, so coerce defensively to keep the contract.
886
- // Mirrors v4's behaviour at the same site.
875
+ // `ValidationError.path` is `(string | number)[]` publicly, and
876
+ // v3's `issue.path` matches in the standard case, but a custom
877
+ // check calling `ctx.addIssue({ path: [...] })` can smuggle a
878
+ // Symbol through. Coerce to keep the promise. v4 does the same
879
+ // here.
887
880
  //
888
- // Adapter-side paths stay schema-relative — the validation
889
- // pipeline in `create-form-store.ts` prepends the parent path
890
- // to absolutise, then routes form-level (absolute path length 0)
891
- // entries to the empty-string bucket at storage time.
881
+ // Adapter-side paths stay schema-RELATIVE: the validation pipeline
882
+ // in `core/create-form-store.ts` prepends the parent path to
883
+ // absolutise, then routes form-level entries, those whose absolute
884
+ // path is empty, to the empty-string bucket at storage time.
892
885
  path: coercePathSegments(issue.path),
893
886
  code
894
887
  });
@@ -920,10 +913,10 @@ function unwrapStructuralLeafV3(schema) {
920
913
  return current;
921
914
  }
922
915
  function isStructuralV3Kind(schema) {
923
- return isZodSchemaType(schema, "ZodObject") || isZodSchemaType(schema, "ZodArray") || isZodSchemaType(schema, "ZodRecord") || isZodSchemaType(schema, "ZodTuple") || isZodSchemaType(schema, "ZodUnion") || isZodSchemaType(schema, "ZodDiscriminatedUnion") || // Wrappers that themselves resolve to a structural type — keep
924
- // peeling at the next iteration.
925
- isZodSchemaType(schema, "ZodOptional") || isZodSchemaType(schema, "ZodNullable") || isZodSchemaType(schema, "ZodDefault") || isZodSchemaType(schema, "ZodEffects") || // Newer transparent wrappers (v3.23+). Each wraps a single inner
926
- // schema with no structural impact — `peelV3Wrappers` resolves them.
916
+ return isZodSchemaType(schema, "ZodObject") || isZodSchemaType(schema, "ZodArray") || isZodSchemaType(schema, "ZodRecord") || isZodSchemaType(schema, "ZodTuple") || isZodSchemaType(schema, "ZodUnion") || isZodSchemaType(schema, "ZodDiscriminatedUnion") || // Wrappers that themselves resolve to a structural type; keep
917
+ // peeling next iteration.
918
+ isZodSchemaType(schema, "ZodOptional") || isZodSchemaType(schema, "ZodNullable") || isZodSchemaType(schema, "ZodDefault") || isZodSchemaType(schema, "ZodEffects") || // Newer transparent wrappers (v3.23+), each wrapping one inner
919
+ // schema with no structural impact. `peelV3Wrappers` resolves them.
927
920
  isZodSchemaType(schema, "ZodPipeline") || isZodSchemaType(schema, "ZodReadonly") || isZodSchemaType(schema, "ZodBranded");
928
921
  }
929
922
  function peelV3Wrappers(schema) {
@@ -0,0 +1,42 @@
1
+ import { App } from 'vue';
2
+ import { ao as RegisterModelDynamicCustomDirective } from './attaform.Br8EBn5B.mjs';
3
+
4
+ /**
5
+ * The `v-register` directive. Bind a form field to a native input, select,
6
+ * textarea, checkbox or radio:
7
+ *
8
+ * ```vue
9
+ * <input v-register="form.register('email')" />
10
+ * <select v-register="form.register('country')">
11
+ * <option value="us">US</option>
12
+ * <option value="uk">UK</option>
13
+ * </select>
14
+ * ```
15
+ *
16
+ * The binding strategy follows the element's `tagName` and `type`. Delivered by
17
+ * the Vite and Nuxt plugins' compile-time binding, or by
18
+ * `installVRegister(app)` everywhere else. Most consumers never import it
19
+ * directly; it is exposed for integrations that wire directives by hand.
20
+ */
21
+ declare const vRegister: RegisterModelDynamicCustomDirective;
22
+ /**
23
+ * Register the `v-register` directive app-wide. The one-line setup for an app
24
+ * whose templates are compiled without Attaform's Vite or Nuxt plugin: a
25
+ * webpack-family bundler, a no-build page, or runtime-compiled templates.
26
+ *
27
+ * ```ts
28
+ * import { createApp } from 'vue'
29
+ * import { installVRegister } from 'attaform/directive'
30
+ *
31
+ * const app = createApp(App)
32
+ * installVRegister(app)
33
+ * ```
34
+ *
35
+ * Vite and Nuxt consumers do not call this; their build plugin binds each
36
+ * template's `v-register` at compile time. Idempotent: a second call for the
37
+ * same app is a no-op, and a directive the consumer registered under the same
38
+ * name is left in place.
39
+ */
40
+ declare function installVRegister(app: App): void;
41
+
42
+ export { installVRegister as i, vRegister as v };
@@ -756,9 +756,9 @@ function injectPreamble(element, captured) {
756
756
  ),
757
757
  exp,
758
758
  modifiers: [],
759
- // Reuse the host element's source location so any runtime error
760
- // in the synthesized expression points at the consumer's template
761
- // line, not at the dummyLoc that pre-fix was line 0.
759
+ // The host element's source location, so a runtime error in the
760
+ // synthesized expression points at the consumer's template line
761
+ // rather than at line 0.
762
762
  loc: element.loc
763
763
  };
764
764
  element.props.unshift(directive);
@@ -1,50 +1,177 @@
1
1
  import { NodeTransform } from '@vue/compiler-core';
2
2
 
3
+ /**
4
+ * Vue compiler node transform that bridges `v-register` into the binding
5
+ * shapes its consumers expect, on two kinds of host.
6
+ *
7
+ * - `<select v-register>` gets `:value` on a single-select plus a
8
+ * per-`<option>` `:selected`, so the runtime directive can pre-mark
9
+ * selected options at SSR time.
10
+ * - `<MyComponent v-register>`, and a kebab-case custom-element host,
11
+ * gets a `:registerValue` bridge prop, so `useRegister` inside the
12
+ * child sees the parent's RegisterValue. Any parent-authored slotted
13
+ * `<option>` is marked exactly as the native path marks it, so a
14
+ * `<select>` wrapped in a styled component keeps its SSR-selected
15
+ * option (#394).
16
+ *
17
+ * `attaform/vite` and `attaform/nuxt` wire it for you; reach for it
18
+ * directly only when integrating a custom bundler.
19
+ */
20
+
3
21
  declare const componentBridgeTransform: NodeTransform;
4
22
 
5
23
  /**
6
- * Vue compiler node transform for `<input v-register>` and
7
- * `<textarea v-register>`. Injects the `:value` / `:checked`
8
- * bindings required for SSR-correct initial render.
24
+ * Vue compiler node transform for `<input v-register>` and `<textarea
25
+ * v-register>`, injecting the `:value` and `:checked` bindings an
26
+ * SSR-correct initial render needs.
9
27
  *
10
- * Wired automatically by `attaform/vite` and
11
- * `attaform/nuxt`. Use directly only when integrating with
12
- * a custom bundler.
28
+ * `attaform/vite` and `attaform/nuxt` wire it for you; reach for it
29
+ * directly only when integrating a custom bundler.
13
30
  */
14
31
  declare const inputTextAreaNodeTransform: NodeTransform;
15
32
 
16
33
  /**
17
- * Vue compiler node transform that warns about a redundant state
18
- * binding co-located with `v-register`, and stamps the
19
- * compile-active marker so the runtime diagnostic stands down.
34
+ * The compile-time half of the redundant-binding guard (#464). On every
35
+ * element carrying `v-register` it does two things.
36
+ *
37
+ * 1. Warns at build time when a `v-model` sits beside `v-register` on
38
+ * a native `<input>`, `<select>` or `<textarea>`, which installs
39
+ * Vue's own model directive next to Attaform's: two writers driving
40
+ * one element with no fallback story between them.
41
+ * `findRedundantStateBinding` below carries the full reasoning for
42
+ * what does and does not count.
43
+ *
44
+ * 2. Stamps `V_REGISTER_COMPILED_MODIFIER` on the directive so the
45
+ * runtime diagnostic in `core/directive.ts` stands down. This
46
+ * transform runs BEFORE `inputTextAreaNodeTransform` and
47
+ * `componentBridgeTransform` strip and inject the value channel, so
48
+ * it sees the author's props verbatim where the runtime, seeing
49
+ * only the post-injection props, cannot. Exactly one layer fires
50
+ * per consumer.
51
+ *
52
+ * The carve-out: a `:value`, or a static `value=`, is the legitimate
53
+ * IDENTITY channel for a radio and an `<option>`, and `v-register` READS
54
+ * it. Those never warn; only the STATE attrs do.
55
+ *
56
+ * A dynamic `:type` cannot be classified at compile time, so that input
57
+ * is skipped for the warn, best-effort as in `inputTextAreaNodeTransform`
58
+ * and the runtime `resolveDynamicModel`. The marker is still stamped, so
59
+ * the runtime layer, which sees the resolved type, does not double-report.
60
+ *
61
+ * It is deliberately NOT `__DEV__`-gated: firing on every compile,
62
+ * production and CI included, is what lets a consumer retire a bespoke
63
+ * SFC-lint gate. Warnings only. A redundant binding never fails the
64
+ * build, the Vue compiler giving transforms no error channel and a
65
+ * library having no business nuking a consumer's build over a
66
+ * lint-level issue.
67
+ *
68
+ * `attaform/vite` and `attaform/nuxt` wire it first in `nodeTransforms`;
69
+ * reach for it directly only when integrating a custom bundler.
70
+ */
71
+
72
+ /**
73
+ * Vue compiler node transform that warns about a redundant state binding
74
+ * sitting beside `v-register`, and stamps the compile-active marker so
75
+ * the runtime diagnostic stands down.
20
76
  *
21
- * Must run BEFORE `inputTextAreaNodeTransform` and
22
- * `componentBridgeTransform` so it reads the author's props before they
23
- * are stripped / injected. Wired first by `attaform/vite` and
24
- * `attaform/nuxt`.
77
+ * It MUST run before `inputTextAreaNodeTransform` and
78
+ * `componentBridgeTransform`, so it reads the author's props before they
79
+ * are stripped and injected. `attaform/vite` and `attaform/nuxt` wire it
80
+ * first.
25
81
  */
26
82
  declare const redundantBindingWarnTransform: NodeTransform;
27
83
 
28
84
  /**
29
- * Vue compiler node transform that wraps every `v-register`
30
- * expression in a small IIFE so the directive can flag a field as
31
- * connected during SSR. Eliminates the `false → true` flicker on
32
- * `getFieldState(path).connected` after hydration.
85
+ * Rewrites every `<element v-register="<expr>">` so the binding
86
+ * expression wraps `<expr>` in an IIFE that calls
87
+ * `markConnectedOptimistically()` on the resulting `RegisterValue` and
88
+ * hands back the same object:
89
+ *
90
+ * ((__attaRv) => (__attaRv?.markConnectedOptimistically?.(), __attaRv))(<expr>)
91
+ *
92
+ * Vue deliberately skips directive lifecycle hooks during SSR (see the
93
+ * header of `core/directive.ts`), so the `v-register` directive's
94
+ * `created` hook, the one that flips `connected: true`, never fires on
95
+ * the server. Every SSR'd FieldState therefore serialises `connected:
96
+ * false`, then flickers to `true` when the directive runs on hydration,
97
+ * and anything reading `getFieldState(path).connected` in a
98
+ * server-rendered template sees the stale value baked into the HTML.
33
99
  *
34
- * Must run after `vRegisterPreambleTransform`. Wired automatically
35
- * by `attaform/vite` and `attaform/nuxt`.
100
+ * The IIFE captures the `RegisterValue` `<expr>` produced, fires the
101
+ * optimistic mark, itself guarded by `state.ssr` so it is a free no-op
102
+ * on the client, and returns the same object, so the directive receives
103
+ * exactly what the author wrote.
104
+ *
105
+ * It is agnostic to the shape of `<expr>`, inline, hoisted into a
106
+ * variable, or dynamically built: all three produce a `RegisterValue` at
107
+ * runtime and the wrapper never needs the path string. A setup-time
108
+ * `register()` call NEVER bound to `v-register` gets no wrapper, no
109
+ * mark, and stays `connected: false` after hydration, which is correct:
110
+ * it represents no rendered DOM element.
111
+ *
112
+ * Idempotent. Running twice on one AST, as some bundler configurations
113
+ * do, detects the marker on the second pass and skips re-wrapping.
114
+ */
115
+
116
+ /**
117
+ * Vue compiler node transform that wraps every `v-register` expression
118
+ * in a small IIFE, so the directive can flag a field connected during
119
+ * SSR and `getFieldState(path).connected` does not flicker after
120
+ * hydration.
121
+ *
122
+ * It must run after `vRegisterPreambleTransform`. `attaform/vite` and
123
+ * `attaform/nuxt` wire both.
36
124
  */
37
125
  declare const vRegisterHintTransform: NodeTransform;
38
126
 
39
127
  /**
40
- * Vue compiler node transform that hoists `v-register`'s SSR
41
- * connection marks to the root of the template. Together with
42
- * `vRegisterHintTransform`, ensures expressions earlier in the
43
- * template that read `getFieldState(path).connected` see the
44
- * correct value during the server's single-pass render.
128
+ * Closes the render-order edge `vRegisterHintTransform` alone leaves
129
+ * open.
130
+ *
131
+ * The hint transform wraps each `v-register` expression in an IIFE that
132
+ * calls `markConnectedOptimistically()` when the element's vnode is
133
+ * created, which covers any expression evaluated AT or AFTER the input
134
+ * in render order. Vue's SSR is single-pass top to bottom, though, so in
135
+ *
136
+ * <pre>{{ form.fields.password.connected }}</pre>
137
+ * <input v-register="form.register('password')" />
138
+ *
139
+ * the `<pre>` evaluates BEFORE the wrapper has fired. The serialized
140
+ * HTML carries `connected: false`, the post-hydration steady state says
141
+ * `true`, and the user sees a one-tick flicker.
142
+ *
143
+ * So the marks hoist one level up. The walk collects every static
144
+ * `v-register` binding, skipping `v-for` descendants, whose expressions
145
+ * reference loop locals unavailable at root scope, and prepends a
146
+ * synthetic `:data-atta-pre-mark` directive to the first root element.
147
+ * Vue evaluates an element's prop bindings before recursing into its
148
+ * children, so that IIFE fires every collected mark before any
149
+ * descendant expression runs. Its own expression resolves to
150
+ * `undefined`, which the SSR renderer drops, so no attribute appears and
151
+ * the marks are the only output.
152
+ *
153
+ * Register it BEFORE `vRegisterHintTransform`. The pre-order pass here
154
+ * captures each expression's original, un-wrapped text into a
155
+ * per-template state map, the hint transform then wraps the in-place
156
+ * directive expression, and the exit hook on the first root element
157
+ * builds the preamble from the captured originals. An element's exit
158
+ * hooks fire before `transformElement`'s codegen exit, so the injected
159
+ * prop lands in the rendered output.
160
+ *
161
+ * For a `v-for` descendant the hint transform's per-element wrapper
162
+ * stays load-bearing: those bindings cannot hoist, their path
163
+ * expressions referencing loop-scoped identifiers.
164
+ */
165
+
166
+ /**
167
+ * Vue compiler node transform that hoists `v-register`'s SSR connection
168
+ * marks to the root of the template. With `vRegisterHintTransform`, it
169
+ * is what lets an expression EARLIER in the template read
170
+ * `getFieldState(path).connected` correctly during the server's
171
+ * single-pass render.
45
172
  *
46
- * Must run before `vRegisterHintTransform`. Wired automatically
47
- * by `attaform/vite` and `attaform/nuxt`.
173
+ * It must run before `vRegisterHintTransform`. `attaform/vite` and
174
+ * `attaform/nuxt` wire both.
48
175
  */
49
176
  declare const vRegisterPreambleTransform: NodeTransform;
50
177
 
@@ -1 +1 @@
1
- export { c as componentBridgeTransform, i as inputTextAreaNodeTransform, r as redundantBindingWarnTransform, v as vRegisterHintTransform, a as vRegisterPreambleTransform } from './shared/attaform.BK0cgu2K.mjs';
1
+ export { c as componentBridgeTransform, i as inputTextAreaNodeTransform, r as redundantBindingWarnTransform, v as vRegisterHintTransform, a as vRegisterPreambleTransform } from './shared/attaform.ZYn2ahzJ.mjs';