attaform 0.24.3 → 0.25.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 (89) hide show
  1. package/dist/chunks/devtools.cjs +1 -1
  2. package/dist/chunks/devtools.mjs +1 -1
  3. package/dist/chunks/fingerprint2.cjs +1 -1
  4. package/dist/chunks/fingerprint2.mjs +1 -1
  5. package/dist/index.cjs +4 -4
  6. package/dist/index.d.cts +4 -4
  7. package/dist/index.d.mts +4 -4
  8. package/dist/index.d.ts +4 -4
  9. package/dist/index.mjs +4 -4
  10. package/dist/nuxt.d.cts +1 -1
  11. package/dist/nuxt.d.mts +1 -1
  12. package/dist/nuxt.d.ts +1 -1
  13. package/dist/runtime/plugins/attaform.cjs +2 -2
  14. package/dist/runtime/plugins/attaform.mjs +2 -2
  15. package/dist/shared/{attaform.DQcdvxKZ.cjs → attaform.3heDHayA.cjs} +146 -3
  16. package/dist/shared/attaform.3heDHayA.cjs.map +1 -0
  17. package/dist/shared/{attaform.yqcYwt0d.d.ts → attaform.8vmdOURQ.d.mts} +124 -56
  18. package/dist/shared/{attaform.BjiHiYAZ.d.ts → attaform.B3cgZE3c.d.ts} +1 -1
  19. package/dist/shared/{attaform.DHjRN5JP.d.cts → attaform.B5GFS3Su.d.cts} +124 -56
  20. package/dist/shared/{attaform.BVjueUPu.cjs → attaform.B5YcWrKi.cjs} +3 -3
  21. package/dist/shared/{attaform.BVjueUPu.cjs.map → attaform.B5YcWrKi.cjs.map} +1 -1
  22. package/dist/shared/{attaform.Bx6pgrIy.d.cts → attaform.BJt4ZCkM.d.mts} +1 -1
  23. package/dist/shared/{attaform.DK6_Ctv7.cjs → attaform.BRzKBj2g.cjs} +182 -7
  24. package/dist/shared/attaform.BRzKBj2g.cjs.map +1 -0
  25. package/dist/shared/{attaform.CB1kYKUB.cjs → attaform.BkUzunyB.cjs} +180 -38
  26. package/dist/shared/attaform.BkUzunyB.cjs.map +1 -0
  27. package/dist/shared/{attaform.Dbzwtn4g.mjs → attaform.CN4ZEGo6.mjs} +4 -4
  28. package/dist/shared/{attaform.Dbzwtn4g.mjs.map → attaform.CN4ZEGo6.mjs.map} +1 -1
  29. package/dist/shared/{attaform.C5qkSN6t.mjs → attaform.CqwcTExO.mjs} +146 -3
  30. package/dist/shared/attaform.CqwcTExO.mjs.map +1 -0
  31. package/dist/shared/{attaform.D4ebpNbS.mjs → attaform.CwXXbZIQ.mjs} +3 -3
  32. package/dist/shared/{attaform.D4ebpNbS.mjs.map → attaform.CwXXbZIQ.mjs.map} +1 -1
  33. package/dist/shared/{attaform.B_F61Rx1.mjs → attaform.DK3xUBkj.mjs} +181 -39
  34. package/dist/shared/attaform.DK3xUBkj.mjs.map +1 -0
  35. package/dist/shared/{attaform.ByIB7i15.mjs → attaform.DMNnfQq_.mjs} +4 -4
  36. package/dist/shared/{attaform.ByIB7i15.mjs.map → attaform.DMNnfQq_.mjs.map} +1 -1
  37. package/dist/shared/{attaform.0zueP54D.d.mts → attaform.DS_zBcdr.d.ts} +124 -56
  38. package/dist/shared/{attaform.DvqdxvNU.d.mts → attaform.DmKLa4tL.d.cts} +1 -1
  39. package/dist/shared/{attaform.DgbHNLf_.mjs → attaform.OLtKd9gM.mjs} +2 -2
  40. package/dist/shared/{attaform.DgbHNLf_.mjs.map → attaform.OLtKd9gM.mjs.map} +1 -1
  41. package/dist/shared/{attaform.WvcckZMD.mjs → attaform.Vp2-CWZB.mjs} +3 -2
  42. package/dist/shared/attaform.Vp2-CWZB.mjs.map +1 -0
  43. package/dist/shared/{attaform.CqlLImc1.cjs → attaform.Xe8dATED.cjs} +4 -4
  44. package/dist/shared/{attaform.CqlLImc1.cjs.map → attaform.Xe8dATED.cjs.map} +1 -1
  45. package/dist/shared/{attaform.CjdepGnw.cjs → attaform.ZXZ6dH72.cjs} +3 -1
  46. package/dist/shared/attaform.ZXZ6dH72.cjs.map +1 -0
  47. package/dist/shared/{attaform.DUc6cetN.d.cts → attaform.i_LA3X9o.d.cts} +54 -2
  48. package/dist/shared/{attaform.DUc6cetN.d.ts → attaform.i_LA3X9o.d.mts} +54 -2
  49. package/dist/shared/{attaform.DUc6cetN.d.mts → attaform.i_LA3X9o.d.ts} +54 -2
  50. package/dist/shared/{attaform.DwCtxauq.mjs → attaform.k9V0py0s.mjs} +182 -8
  51. package/dist/shared/attaform.k9V0py0s.mjs.map +1 -0
  52. package/dist/shared/{attaform.DfnwaGW2.cjs → attaform.sks1MFVO.cjs} +4 -4
  53. package/dist/shared/{attaform.DfnwaGW2.cjs.map → attaform.sks1MFVO.cjs.map} +1 -1
  54. package/dist/shared/{attaform.DRWrVSkH.cjs → attaform.xBDo0Ko0.cjs} +2 -2
  55. package/dist/shared/{attaform.DRWrVSkH.cjs.map → attaform.xBDo0Ko0.cjs.map} +1 -1
  56. package/dist/transforms.cjs +2 -1
  57. package/dist/transforms.cjs.map +1 -1
  58. package/dist/transforms.d.cts +13 -1
  59. package/dist/transforms.d.mts +13 -1
  60. package/dist/transforms.d.ts +13 -1
  61. package/dist/transforms.mjs +1 -1
  62. package/dist/vite.cjs +2 -1
  63. package/dist/vite.cjs.map +1 -1
  64. package/dist/vite.mjs +2 -1
  65. package/dist/vite.mjs.map +1 -1
  66. package/dist/zod-v3.cjs +2 -2
  67. package/dist/zod-v3.d.cts +3 -3
  68. package/dist/zod-v3.d.mts +3 -3
  69. package/dist/zod-v3.d.ts +3 -3
  70. package/dist/zod-v3.mjs +2 -2
  71. package/dist/zod-v4.cjs +2 -2
  72. package/dist/zod-v4.d.cts +5 -5
  73. package/dist/zod-v4.d.mts +5 -5
  74. package/dist/zod-v4.d.ts +5 -5
  75. package/dist/zod-v4.mjs +2 -2
  76. package/dist/zod.cjs +5 -5
  77. package/dist/zod.d.cts +5 -5
  78. package/dist/zod.d.mts +5 -5
  79. package/dist/zod.d.ts +5 -5
  80. package/dist/zod.mjs +5 -5
  81. package/package.json +6 -2
  82. package/dist/shared/attaform.B_F61Rx1.mjs.map +0 -1
  83. package/dist/shared/attaform.C5qkSN6t.mjs.map +0 -1
  84. package/dist/shared/attaform.CB1kYKUB.cjs.map +0 -1
  85. package/dist/shared/attaform.CjdepGnw.cjs.map +0 -1
  86. package/dist/shared/attaform.DK6_Ctv7.cjs.map +0 -1
  87. package/dist/shared/attaform.DQcdvxKZ.cjs.map +0 -1
  88. package/dist/shared/attaform.DwCtxauq.mjs.map +0 -1
  89. package/dist/shared/attaform.WvcckZMD.mjs.map +0 -1
@@ -5,6 +5,7 @@ const vue = require('vue');
5
5
  const V_REGISTER_MARKER = Symbol.for("attaform:v-register-directive");
6
6
  const REGISTER_OWNER_MARKER = Symbol.for("attaform:register-owner-marker");
7
7
  const SSR_COMPONENT_HOST_MODIFIER = "attaformComponentHost";
8
+ const V_REGISTER_COMPILED_MODIFIER = "attaformCompiled";
8
9
  function isRegisterValue(val) {
9
10
  if (typeof val !== "object" || val === null) return false;
10
11
  if (!("innerRef" in val)) return false;
@@ -21,7 +22,8 @@ function isTransforming(value) {
21
22
 
22
23
  exports.REGISTER_OWNER_MARKER = REGISTER_OWNER_MARKER;
23
24
  exports.SSR_COMPONENT_HOST_MODIFIER = SSR_COMPONENT_HOST_MODIFIER;
25
+ exports.V_REGISTER_COMPILED_MODIFIER = V_REGISTER_COMPILED_MODIFIER;
24
26
  exports.V_REGISTER_MARKER = V_REGISTER_MARKER;
25
27
  exports.isRegisterValue = isRegisterValue;
26
28
  exports.isTransforming = isTransforming;
27
- //# sourceMappingURL=attaform.CjdepGnw.cjs.map
29
+ //# sourceMappingURL=attaform.ZXZ6dH72.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attaform.ZXZ6dH72.cjs","sources":["../../src/runtime/core/register-protocol.ts"],"sourcesContent":["import { isRef } from 'vue'\nimport type { InternalRegisterValue, RegisterValue } from '../types/types-api'\n\n// Shared register-protocol primitives: the markers and value guards that\n// the v-register directive, its file / lifecycle satellites, and\n// useRegister all build on. They live in this leaf (depending only on vue\n// and the types module) so those modules don't import directive.ts back,\n// which would form an import cycle.\n\n/**\n * Marker installed on the v-register directive object so consumers\n * (notably `useRegister`) can identify it in a child vnode's\n * directive list even when the consumer's bundler hasn't installed\n * attaform's compile-time transforms. `Symbol.for(...)` round-trips\n * across duplicate bundle copies: `attaform` and `attaform/zod` can\n * land on different `vRegister` references in the playground or\n * under pnpm-hoist edge cases, but both carry the same marker.\n */\nexport const V_REGISTER_MARKER: unique symbol = Symbol.for('attaform:v-register-directive')\n\n/**\n * Marker on the rendered root DOM element. Set by `useRegister`'s\n * `onMounted` hook; read by the directive's deferred warn check to\n * skip the \"is a no-op\" warn for components that handle binding via\n * an inner v-register.\n *\n * `Symbol.for(...)` so the marker round-trips across duplicate copies\n * of attaform — see `assignKey` in core/directive.ts for the same\n * reasoning. `useRegister` and the directive are typically loaded\n * from the same module copy, but a consumer importing from\n * `attaform/zod` (Vite-optimized bundle) and the Nuxt\n * plugin's relative-path import (live ESM) can land on different\n * copies; a global symbol means the marker check still works.\n */\nexport const REGISTER_OWNER_MARKER: unique symbol = Symbol.for('attaform:register-owner-marker')\n\n/**\n * Directive modifier the `componentBridgeTransform` stamps onto a\n * `v-register` that lands on a component host (or kebab custom element).\n * It is the compile-time -> runtime signal the directive's `getSSRProps`\n * reads under compiled SSR, where Vue passes a `null` vnode and the host\n * is otherwise indistinguishable from a native control. autoAria then\n * suppresses its attrs on the host root (the inner control the component\n * re-binds via useRegister carries them). Namespaced to avoid collision\n * with any author-written modifier. (#404)\n */\nexport const SSR_COMPONENT_HOST_MODIFIER = 'attaformComponentHost'\n\n/**\n * Directive modifier the `redundantBindingWarnTransform` stamps onto\n * every `v-register` it processes. It is the compile-time -> runtime\n * signal that Attaform's template transforms are active, and it lets the\n * runtime redundant-binding diagnostic (in the directive's `created`\n * hook) stand down.\n *\n * The hand-off matters because `inputTextAreaNodeTransform` strips the\n * author's `value` / `checked` and injects its own. Once that has run,\n * `vnode.props` no longer reflects what the author wrote, so a runtime\n * check would flag every field. With this marker present the compile\n * layer owns detection (it reads the authored props BEFORE injection);\n * with it absent (no bundler plugin) the runtime layer reads the\n * untouched `vnode.props` directly. Exactly one layer fires per\n * consumer, never both.\n *\n * Namespaced to avoid collision with any author-written modifier, like\n * `SSR_COMPONENT_HOST_MODIFIER` above.\n */\nexport const V_REGISTER_COMPILED_MODIFIER = 'attaformCompiled'\n\n/**\n * Type guard for a `RegisterValue`. Returns `true` when `val` looks\n * like the object returned from `form.register(path)`.\n *\n * ```ts\n * if (isRegisterValue(slotValue)) {\n * // slotValue.innerRef is now a Ref<unknown>\n * }\n * ```\n *\n * Useful when building wrapper components that accept either a\n * `RegisterValue` or a plain ref via the same prop.\n */\nexport function isRegisterValue<Value = unknown>(val: unknown): val is RegisterValue<Value> {\n if (typeof val !== 'object' || val === null) return false\n if (!('innerRef' in val)) return false\n if (!isRef(val.innerRef)) return false\n if (!('registerElement' in val)) return false\n if (typeof val.registerElement !== 'function') return false\n if (!('setValueWithInternalPath' in val)) return false\n if (typeof val.setValueWithInternalPath !== 'function') return false\n return true\n}\n\n/**\n * `true` while a deferred async transform is in flight at this path.\n * `beginTransform` flips it synchronously inside the assigner, so a\n * listener's post-write force-sync block reads it (right after the\n * assigner returns) to skip snapping the DOM back to stale storage —\n * the resolved value is painted in by the orchestrator's `syncDom`\n * once the run lands.\n */\nexport function isTransforming(value: unknown): boolean {\n return isRegisterValue(value) && (value as InternalRegisterValue).transforming\n}\n"],"names":["isRef"],"mappings":";;;;AAkBO,MAAM,iBAAA,GAAmC,MAAA,CAAO,GAAA,CAAI,+BAA+B;AAgBnF,MAAM,qBAAA,GAAuC,MAAA,CAAO,GAAA,CAAI,gCAAgC;AAYxF,MAAM,2BAAA,GAA8B;AAqBpC,MAAM,4BAAA,GAA+B;AAerC,SAAS,gBAAiC,GAAA,EAA2C;AAC1F,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,MAAM,OAAO,KAAA;AACpD,EAAA,IAAI,EAAE,UAAA,IAAc,GAAA,CAAA,EAAM,OAAO,KAAA;AACjC,EAAA,IAAI,CAACA,SAAA,CAAM,GAAA,CAAI,QAAQ,GAAG,OAAO,KAAA;AACjC,EAAA,IAAI,EAAE,iBAAA,IAAqB,GAAA,CAAA,EAAM,OAAO,KAAA;AACxC,EAAA,IAAI,OAAO,GAAA,CAAI,eAAA,KAAoB,UAAA,EAAY,OAAO,KAAA;AACtD,EAAA,IAAI,EAAE,0BAAA,IAA8B,GAAA,CAAA,EAAM,OAAO,KAAA;AACjD,EAAA,IAAI,OAAO,GAAA,CAAI,wBAAA,KAA6B,UAAA,EAAY,OAAO,KAAA;AAC/D,EAAA,OAAO,IAAA;AACT;AAUO,SAAS,eAAe,KAAA,EAAyB;AACtD,EAAA,OAAO,eAAA,CAAgB,KAAK,CAAA,IAAM,KAAA,CAAgC,YAAA;AACpE;;;;;;;;;"}
@@ -1396,6 +1396,14 @@ type ReactiveValidationStatus<Form> = PendingValidationStatus | SettledValidatio
1396
1396
  * manually via `form.focusFirstError()` or `form.scrollToFirstError()`
1397
1397
  * from an `onError` callback.
1398
1398
  *
1399
+ * Both focusing policies request `focusVisible: true`, so the browser
1400
+ * paints a focus ring even though the move is programmatic. Where a UA
1401
+ * doesn't yet support that hint, non-text controls (radio, checkbox,
1402
+ * custom widgets) may end up focused without a visible ring after a
1403
+ * pointer-driven submit; if you target those browsers, pair the policy
1404
+ * with your own indicator (e.g. a `:focus-within` ring on the option
1405
+ * wrapper).
1406
+ *
1399
1407
  * If no errored field has a currently mounted, visible element, the
1400
1408
  * policy silently no-ops.
1401
1409
  */
@@ -2442,6 +2450,16 @@ type RegisterValue<Value = unknown> = Readonly<{
2442
2450
  * unchanged.
2443
2451
  */
2444
2452
  setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
2453
+ /**
2454
+ * Commit a value emitted by a third-party component bound through
2455
+ * `v-register`'s compile-time v-model desugar. Writes the value (the
2456
+ * component's typed model output, authoritative -- no coercion) AND marks
2457
+ * the field interacted, since a v-model host has no DOM input listener to
2458
+ * flip the sticky `interacted` bit. The injected `onUpdate:modelValue`
2459
+ * handler is the only caller. Returns `true` when the write was accepted.
2460
+ * @internal
2461
+ */
2462
+ setValueFromHost: (value: unknown) => boolean;
2445
2463
  /**
2446
2464
  * Mark this field as DOM-connected during SSR so a server-rendered
2447
2465
  * template that reads `form.fields.<path>.connected` doesn't
@@ -2450,6 +2468,34 @@ type RegisterValue<Value = unknown> = Readonly<{
2450
2468
  * @internal
2451
2469
  */
2452
2470
  markConnectedOptimistically: () => void;
2471
+ /**
2472
+ * Mark this field DOM-connected (`true`) or disconnected (`false`) for a
2473
+ * `v-register` component host that binds value through the v-model desugar
2474
+ * but exposes no single inner control to register -- a composite widget,
2475
+ * or none found. The directive calls it on mount / unmount; distinct from
2476
+ * the SSR-only `markConnectedOptimistically`.
2477
+ * @internal
2478
+ */
2479
+ markHostConnected: (connected: boolean) => void;
2480
+ /**
2481
+ * Mark this field focused (`true`) or blurred (`false`) for a `v-register`
2482
+ * component host with no single latched control -- a composite widget whose
2483
+ * focus moves between inner segments, or a control-less one. The directive
2484
+ * tracks focusin / focusout on the widget root and forwards here, so focus
2485
+ * state and blur-validation still arm without an element-level focus
2486
+ * listener. Carries this binding's instance meta (its `validateOn`).
2487
+ * @internal
2488
+ */
2489
+ markFocused: (focused: boolean) => void;
2490
+ /**
2491
+ * `true` when an element already registered for this binding's path is
2492
+ * contained within (or equal to) `hostElement`. The directive's
2493
+ * component-host branch reads it to tell a `useRegister` wrapper (whose
2494
+ * inner control self-registered) from a third-party component (which
2495
+ * registered nothing), so the latch only runs for the latter.
2496
+ * @internal
2497
+ */
2498
+ hasRegisteredDescendant: (hostElement: HTMLElement) => boolean;
2453
2499
  /**
2454
2500
  * Canonical, JSON-encoded path key for this binding (e.g.
2455
2501
  * `'["items",0,"name"]'`). Useful for stable Map / Set keys, log
@@ -3667,8 +3713,8 @@ type FormMeta<F = unknown> = FieldState<F> & {
3667
3713
  * `getDisplayState` predicates) but does NOT drive the library's
3668
3714
  * default `getDisplayState` heuristic. The reveal-on-submit story
3669
3715
  * runs entirely through `submissionAttempts`, which
3670
- * `wizard.handleSubmit` bumps on the active form at intermediate
3671
- * steps and on every form at the final step.
3716
+ * `wizard.handleSubmit` bumps on every form (it always validates the
3717
+ * whole step list).
3672
3718
  *
3673
3719
  * Distinct from `submissionAttempts`, which counts `handleSubmit`
3674
3720
  * passes only — wizard departures and form submissions are tracked
@@ -4385,6 +4431,12 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4385
4431
  * Pass `{ preventScroll: true }` if you're scrolling separately
4386
4432
  * (e.g. via `scrollToFirstError`) and don't want the browser to
4387
4433
  * fight the explicit scroll.
4434
+ *
4435
+ * Requests `focusVisible: true` so the UA paints a focus ring even
4436
+ * though the move is programmatic. Honored where supported; on
4437
+ * browsers without it, non-text controls focused after a pointer
4438
+ * submit may show no ring, so you may still want an app-side
4439
+ * indicator.
4388
4440
  */
4389
4441
  focusFirstError: (options?: {
4390
4442
  preventScroll?: boolean;
@@ -1396,6 +1396,14 @@ type ReactiveValidationStatus<Form> = PendingValidationStatus | SettledValidatio
1396
1396
  * manually via `form.focusFirstError()` or `form.scrollToFirstError()`
1397
1397
  * from an `onError` callback.
1398
1398
  *
1399
+ * Both focusing policies request `focusVisible: true`, so the browser
1400
+ * paints a focus ring even though the move is programmatic. Where a UA
1401
+ * doesn't yet support that hint, non-text controls (radio, checkbox,
1402
+ * custom widgets) may end up focused without a visible ring after a
1403
+ * pointer-driven submit; if you target those browsers, pair the policy
1404
+ * with your own indicator (e.g. a `:focus-within` ring on the option
1405
+ * wrapper).
1406
+ *
1399
1407
  * If no errored field has a currently mounted, visible element, the
1400
1408
  * policy silently no-ops.
1401
1409
  */
@@ -2442,6 +2450,16 @@ type RegisterValue<Value = unknown> = Readonly<{
2442
2450
  * unchanged.
2443
2451
  */
2444
2452
  setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
2453
+ /**
2454
+ * Commit a value emitted by a third-party component bound through
2455
+ * `v-register`'s compile-time v-model desugar. Writes the value (the
2456
+ * component's typed model output, authoritative -- no coercion) AND marks
2457
+ * the field interacted, since a v-model host has no DOM input listener to
2458
+ * flip the sticky `interacted` bit. The injected `onUpdate:modelValue`
2459
+ * handler is the only caller. Returns `true` when the write was accepted.
2460
+ * @internal
2461
+ */
2462
+ setValueFromHost: (value: unknown) => boolean;
2445
2463
  /**
2446
2464
  * Mark this field as DOM-connected during SSR so a server-rendered
2447
2465
  * template that reads `form.fields.<path>.connected` doesn't
@@ -2450,6 +2468,34 @@ type RegisterValue<Value = unknown> = Readonly<{
2450
2468
  * @internal
2451
2469
  */
2452
2470
  markConnectedOptimistically: () => void;
2471
+ /**
2472
+ * Mark this field DOM-connected (`true`) or disconnected (`false`) for a
2473
+ * `v-register` component host that binds value through the v-model desugar
2474
+ * but exposes no single inner control to register -- a composite widget,
2475
+ * or none found. The directive calls it on mount / unmount; distinct from
2476
+ * the SSR-only `markConnectedOptimistically`.
2477
+ * @internal
2478
+ */
2479
+ markHostConnected: (connected: boolean) => void;
2480
+ /**
2481
+ * Mark this field focused (`true`) or blurred (`false`) for a `v-register`
2482
+ * component host with no single latched control -- a composite widget whose
2483
+ * focus moves between inner segments, or a control-less one. The directive
2484
+ * tracks focusin / focusout on the widget root and forwards here, so focus
2485
+ * state and blur-validation still arm without an element-level focus
2486
+ * listener. Carries this binding's instance meta (its `validateOn`).
2487
+ * @internal
2488
+ */
2489
+ markFocused: (focused: boolean) => void;
2490
+ /**
2491
+ * `true` when an element already registered for this binding's path is
2492
+ * contained within (or equal to) `hostElement`. The directive's
2493
+ * component-host branch reads it to tell a `useRegister` wrapper (whose
2494
+ * inner control self-registered) from a third-party component (which
2495
+ * registered nothing), so the latch only runs for the latter.
2496
+ * @internal
2497
+ */
2498
+ hasRegisteredDescendant: (hostElement: HTMLElement) => boolean;
2453
2499
  /**
2454
2500
  * Canonical, JSON-encoded path key for this binding (e.g.
2455
2501
  * `'["items",0,"name"]'`). Useful for stable Map / Set keys, log
@@ -3667,8 +3713,8 @@ type FormMeta<F = unknown> = FieldState<F> & {
3667
3713
  * `getDisplayState` predicates) but does NOT drive the library's
3668
3714
  * default `getDisplayState` heuristic. The reveal-on-submit story
3669
3715
  * runs entirely through `submissionAttempts`, which
3670
- * `wizard.handleSubmit` bumps on the active form at intermediate
3671
- * steps and on every form at the final step.
3716
+ * `wizard.handleSubmit` bumps on every form (it always validates the
3717
+ * whole step list).
3672
3718
  *
3673
3719
  * Distinct from `submissionAttempts`, which counts `handleSubmit`
3674
3720
  * passes only — wizard departures and form submissions are tracked
@@ -4385,6 +4431,12 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4385
4431
  * Pass `{ preventScroll: true }` if you're scrolling separately
4386
4432
  * (e.g. via `scrollToFirstError`) and don't want the browser to
4387
4433
  * fight the explicit scroll.
4434
+ *
4435
+ * Requests `focusVisible: true` so the UA paints a focus ring even
4436
+ * though the move is programmatic. Honored where supported; on
4437
+ * browsers without it, non-text controls focused after a pointer
4438
+ * submit may show no ring, so you may still want an app-side
4439
+ * indicator.
4388
4440
  */
4389
4441
  focusFirstError: (options?: {
4390
4442
  preventScroll?: boolean;
@@ -1396,6 +1396,14 @@ type ReactiveValidationStatus<Form> = PendingValidationStatus | SettledValidatio
1396
1396
  * manually via `form.focusFirstError()` or `form.scrollToFirstError()`
1397
1397
  * from an `onError` callback.
1398
1398
  *
1399
+ * Both focusing policies request `focusVisible: true`, so the browser
1400
+ * paints a focus ring even though the move is programmatic. Where a UA
1401
+ * doesn't yet support that hint, non-text controls (radio, checkbox,
1402
+ * custom widgets) may end up focused without a visible ring after a
1403
+ * pointer-driven submit; if you target those browsers, pair the policy
1404
+ * with your own indicator (e.g. a `:focus-within` ring on the option
1405
+ * wrapper).
1406
+ *
1399
1407
  * If no errored field has a currently mounted, visible element, the
1400
1408
  * policy silently no-ops.
1401
1409
  */
@@ -2442,6 +2450,16 @@ type RegisterValue<Value = unknown> = Readonly<{
2442
2450
  * unchanged.
2443
2451
  */
2444
2452
  setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
2453
+ /**
2454
+ * Commit a value emitted by a third-party component bound through
2455
+ * `v-register`'s compile-time v-model desugar. Writes the value (the
2456
+ * component's typed model output, authoritative -- no coercion) AND marks
2457
+ * the field interacted, since a v-model host has no DOM input listener to
2458
+ * flip the sticky `interacted` bit. The injected `onUpdate:modelValue`
2459
+ * handler is the only caller. Returns `true` when the write was accepted.
2460
+ * @internal
2461
+ */
2462
+ setValueFromHost: (value: unknown) => boolean;
2445
2463
  /**
2446
2464
  * Mark this field as DOM-connected during SSR so a server-rendered
2447
2465
  * template that reads `form.fields.<path>.connected` doesn't
@@ -2450,6 +2468,34 @@ type RegisterValue<Value = unknown> = Readonly<{
2450
2468
  * @internal
2451
2469
  */
2452
2470
  markConnectedOptimistically: () => void;
2471
+ /**
2472
+ * Mark this field DOM-connected (`true`) or disconnected (`false`) for a
2473
+ * `v-register` component host that binds value through the v-model desugar
2474
+ * but exposes no single inner control to register -- a composite widget,
2475
+ * or none found. The directive calls it on mount / unmount; distinct from
2476
+ * the SSR-only `markConnectedOptimistically`.
2477
+ * @internal
2478
+ */
2479
+ markHostConnected: (connected: boolean) => void;
2480
+ /**
2481
+ * Mark this field focused (`true`) or blurred (`false`) for a `v-register`
2482
+ * component host with no single latched control -- a composite widget whose
2483
+ * focus moves between inner segments, or a control-less one. The directive
2484
+ * tracks focusin / focusout on the widget root and forwards here, so focus
2485
+ * state and blur-validation still arm without an element-level focus
2486
+ * listener. Carries this binding's instance meta (its `validateOn`).
2487
+ * @internal
2488
+ */
2489
+ markFocused: (focused: boolean) => void;
2490
+ /**
2491
+ * `true` when an element already registered for this binding's path is
2492
+ * contained within (or equal to) `hostElement`. The directive's
2493
+ * component-host branch reads it to tell a `useRegister` wrapper (whose
2494
+ * inner control self-registered) from a third-party component (which
2495
+ * registered nothing), so the latch only runs for the latter.
2496
+ * @internal
2497
+ */
2498
+ hasRegisteredDescendant: (hostElement: HTMLElement) => boolean;
2453
2499
  /**
2454
2500
  * Canonical, JSON-encoded path key for this binding (e.g.
2455
2501
  * `'["items",0,"name"]'`). Useful for stable Map / Set keys, log
@@ -3667,8 +3713,8 @@ type FormMeta<F = unknown> = FieldState<F> & {
3667
3713
  * `getDisplayState` predicates) but does NOT drive the library's
3668
3714
  * default `getDisplayState` heuristic. The reveal-on-submit story
3669
3715
  * runs entirely through `submissionAttempts`, which
3670
- * `wizard.handleSubmit` bumps on the active form at intermediate
3671
- * steps and on every form at the final step.
3716
+ * `wizard.handleSubmit` bumps on every form (it always validates the
3717
+ * whole step list).
3672
3718
  *
3673
3719
  * Distinct from `submissionAttempts`, which counts `handleSubmit`
3674
3720
  * passes only — wizard departures and form submissions are tracked
@@ -4385,6 +4431,12 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4385
4431
  * Pass `{ preventScroll: true }` if you're scrolling separately
4386
4432
  * (e.g. via `scrollToFirstError`) and don't want the browser to
4387
4433
  * fight the explicit scroll.
4434
+ *
4435
+ * Requests `focusVisible: true` so the UA paints a focus ring even
4436
+ * though the move is programmatic. Honored where supported; on
4437
+ * browsers without it, non-text controls focused after a pointer
4438
+ * submit may show no ring, so you may still want an app-side
4439
+ * indicator.
4388
4440
  */
4389
4441
  focusFirstError: (options?: {
4390
4442
  preventScroll?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { NodeTypes, createCompoundExpression, createSimpleExpression, ElementTypes, processExpression } from '@vue/compiler-core';
2
- import { S as SSR_COMPONENT_HOST_MODIFIER } from './attaform.WvcckZMD.mjs';
2
+ import { S as SSR_COMPONENT_HOST_MODIFIER, V as V_REGISTER_COMPILED_MODIFIER } from './attaform.Vp2-CWZB.mjs';
3
3
 
4
4
  function getSummarizedProps(node) {
5
5
  if (!("props" in node)) return [];
@@ -143,7 +143,7 @@ function extractMultipleFromSelectSummarizedProps(props) {
143
143
  const value = props[priorityIndex]?.value;
144
144
  return typeof value === "string" ? value.replace(/'|"/g, "") : value ?? "true";
145
145
  }
146
- const RECURSABLE_NODE_TYPES = /* @__PURE__ */ new Set([
146
+ const RECURSABLE_NODE_TYPES$1 = /* @__PURE__ */ new Set([
147
147
  NodeTypes.ELEMENT,
148
148
  NodeTypes.FOR,
149
149
  NodeTypes.IF,
@@ -180,7 +180,7 @@ const componentBridgeTransform = (node, context) => {
180
180
  let traverseSelectNode = function(_node, previousOptionExpressions2) {
181
181
  const isOption = _node.type === NodeTypes.ELEMENT && _node.tag === "option";
182
182
  if (!isOption) {
183
- if (!RECURSABLE_NODE_TYPES.has(_node.type)) return;
183
+ if (!RECURSABLE_NODE_TYPES$1.has(_node.type)) return;
184
184
  const hasChildren = "children" in _node;
185
185
  if (!hasChildren) return;
186
186
  for (const child of _node.children) {
@@ -190,6 +190,7 @@ const componentBridgeTransform = (node, context) => {
190
190
  }
191
191
  return;
192
192
  }
193
+ hasSlottedOptions = true;
193
194
  const optionProps = getSummarizedProps(_node);
194
195
  const valueIndex = optionProps.findIndex((p) => isExactKey(p.key, "value"));
195
196
  let optionValueSummarizedProp;
@@ -231,13 +232,20 @@ const componentBridgeTransform = (node, context) => {
231
232
  return;
232
233
  const registerSummarizedProp = selectSummarizedProps[registerIndex];
233
234
  const selectLoc = node.loc;
235
+ let hasSlottedOptions = false;
234
236
  const multipleExpression = extractMultipleFromSelectSummarizedProps(selectSummarizedProps);
235
237
  const previousOptionExpressions = typeof multipleExpression === "string" ? [[multipleExpression]] : [multipleExpression];
238
+ for (const child of node.children) {
239
+ traverseSelectNode(child, previousOptionExpressions);
240
+ }
236
241
  const isStaticallyNonMultiple = multipleExpression === "false";
242
+ const isComponentHost = isCustomComponent || isKebabCustomElement;
243
+ const isSelectLikeHost = isSelect || isComponentHost && hasSlottedOptions;
244
+ const isPlainComponentHost = isComponentHost && !hasSlottedOptions;
237
245
  const selectProps = node.props;
238
246
  snapshotProps(selectProps);
239
247
  removePropsByName(selectProps, ["value"]);
240
- if (isStaticallyNonMultiple) {
248
+ if (isStaticallyNonMultiple && isSelectLikeHost) {
241
249
  const valuePropExpArray = Array.isArray(registerSummarizedProp?.value) ? registerSummarizedProp.value : [registerSummarizedProp?.value ?? "undefined"];
242
250
  const initExpression = createCompoundExpression([
243
251
  "(",
@@ -266,8 +274,78 @@ const componentBridgeTransform = (node, context) => {
266
274
  };
267
275
  node.props.push(valueProp);
268
276
  }
269
- for (const child of node.children) {
270
- traverseSelectNode(child, previousOptionExpressions);
277
+ if (isPlainComponentHost) {
278
+ removePropsByName(node.props, [
279
+ "model",
280
+ "modelValue",
281
+ "onUpdate:modelValue",
282
+ "update:modelValue"
283
+ ]);
284
+ const modelValuePropExpArray = Array.isArray(registerSummarizedProp?.value) ? registerSummarizedProp.value : [registerSummarizedProp?.value ?? "undefined"];
285
+ const modelInitExpression = createCompoundExpression([
286
+ "(",
287
+ ...modelValuePropExpArray,
288
+ ")?.innerRef?.value"
289
+ ]);
290
+ const modelSimpleExpression = createSimpleExpression(
291
+ flattenExpression(modelInitExpression),
292
+ false
293
+ );
294
+ let modelOutputExp;
295
+ try {
296
+ modelOutputExp = processExpression(modelSimpleExpression, {
297
+ ...context,
298
+ prefixIdentifiers: false
299
+ });
300
+ } catch (err) {
301
+ console.error(
302
+ "[attaform] component-bridge transform: processExpression failed for :modelValue; falling back to the unprocessed expression.",
303
+ err
304
+ );
305
+ modelOutputExp = modelSimpleExpression;
306
+ }
307
+ const modelValueProp = {
308
+ rawName: ":modelValue",
309
+ arg: createSimpleExpression("modelValue", true),
310
+ exp: modelOutputExp,
311
+ name: "bind",
312
+ modifiers: [],
313
+ type: NodeTypes.DIRECTIVE,
314
+ loc: selectLoc
315
+ };
316
+ node.props.push(modelValueProp);
317
+ const updateInitExpression = createCompoundExpression([
318
+ "$event => (",
319
+ ...modelValuePropExpArray,
320
+ ")?.setValueFromHost?.($event)"
321
+ ]);
322
+ const updateSimpleExpression = createSimpleExpression(
323
+ flattenExpression(updateInitExpression),
324
+ false
325
+ );
326
+ let updateOutputExp;
327
+ try {
328
+ updateOutputExp = processExpression(updateSimpleExpression, {
329
+ ...context,
330
+ prefixIdentifiers: false
331
+ });
332
+ } catch (err) {
333
+ console.error(
334
+ "[attaform] component-bridge transform: processExpression failed for onUpdate:modelValue; falling back to the unprocessed expression.",
335
+ err
336
+ );
337
+ updateOutputExp = updateSimpleExpression;
338
+ }
339
+ const updateModelValueProp = {
340
+ rawName: "@update:modelValue",
341
+ arg: createSimpleExpression("onUpdate:modelValue", true),
342
+ exp: updateOutputExp,
343
+ name: "bind",
344
+ modifiers: [],
345
+ type: NodeTypes.DIRECTIVE,
346
+ loc: selectLoc
347
+ };
348
+ node.props.push(updateModelValueProp);
271
349
  }
272
350
  if (isSelect) {
273
351
  return;
@@ -436,6 +514,102 @@ const inputTextAreaNodeTransform = (node) => {
436
514
  }
437
515
  };
438
516
 
517
+ const RECURSABLE_NODE_TYPES = /* @__PURE__ */ new Set([
518
+ NodeTypes.ELEMENT,
519
+ NodeTypes.FOR,
520
+ NodeTypes.IF,
521
+ NodeTypes.IF_BRANCH
522
+ ]);
523
+ function findRedundantStateBinding(props, stateNames) {
524
+ for (const prop of props) {
525
+ if (prop.type === NodeTypes.ATTRIBUTE) {
526
+ if (stateNames.includes(prop.name)) return prop.name;
527
+ continue;
528
+ }
529
+ if (prop.name === "model") return "v-model";
530
+ if (prop.name === "bind" && prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic && stateNames.includes(prop.arg.content)) {
531
+ return `:${prop.arg.content}`;
532
+ }
533
+ }
534
+ return null;
535
+ }
536
+ function classifyInput(props) {
537
+ let staticType = null;
538
+ for (const prop of props) {
539
+ if (prop.type === NodeTypes.ATTRIBUTE) {
540
+ if (prop.name === "type") staticType = prop.value?.content ?? null;
541
+ continue;
542
+ }
543
+ if (prop.name === "bind" && prop.arg?.type === NodeTypes.SIMPLE_EXPRESSION && prop.arg.isStatic && prop.arg.content === "type") {
544
+ return "dynamic";
545
+ }
546
+ }
547
+ if (staticType === "checkbox") return "checkbox";
548
+ if (staticType === "radio") return "radio";
549
+ if (staticType === "file") return "file";
550
+ return "text";
551
+ }
552
+ function emitRedundantWarning(tag, binding) {
553
+ console.warn(
554
+ `[attaform] \`${binding}\` is redundant beside v-register on <${tag}>. v-register already drives this field's value, so keep v-register alone and drop \`${binding}\`. (An identity \`:value\` on a radio or <option> is expected and stays silent.)`
555
+ );
556
+ }
557
+ function walkOptionsForSelected(selectNode) {
558
+ const visit = (candidate) => {
559
+ if (candidate.type === NodeTypes.ELEMENT && candidate.tag === "option") {
560
+ const found = findRedundantStateBinding(candidate.props, ["selected"]);
561
+ if (found !== null) emitRedundantWarning("option", found);
562
+ return;
563
+ }
564
+ if (!RECURSABLE_NODE_TYPES.has(candidate.type)) return;
565
+ if (!("children" in candidate)) return;
566
+ for (const child of candidate.children) {
567
+ if (typeof child === "string" || typeof child === "symbol") continue;
568
+ if (child.type === NodeTypes.SIMPLE_EXPRESSION) continue;
569
+ visit(child);
570
+ }
571
+ };
572
+ for (const child of selectNode.children) visit(child);
573
+ }
574
+ function warnIfRedundant(node) {
575
+ if (node.tagType !== ElementTypes.ELEMENT) return;
576
+ const tag = node.tag;
577
+ if (tag === "select") {
578
+ const found2 = findRedundantStateBinding(node.props, ["value"]);
579
+ if (found2 !== null) emitRedundantWarning("select", found2);
580
+ walkOptionsForSelected(node);
581
+ return;
582
+ }
583
+ if (tag === "textarea") {
584
+ const found2 = findRedundantStateBinding(node.props, ["value"]);
585
+ if (found2 !== null) emitRedundantWarning("textarea", found2);
586
+ return;
587
+ }
588
+ if (tag !== "input") return;
589
+ const kind = classifyInput(node.props);
590
+ if (kind === "dynamic" || kind === "file") return;
591
+ const stateNames = kind === "checkbox" || kind === "radio" ? ["checked"] : ["value"];
592
+ const found = findRedundantStateBinding(node.props, stateNames);
593
+ if (found !== null) emitRedundantWarning("input", found);
594
+ }
595
+ const redundantBindingWarnTransform = (node) => {
596
+ try {
597
+ if (node.type !== NodeTypes.ELEMENT) return;
598
+ const registerProp = node.props.find(
599
+ (prop) => prop.type === NodeTypes.DIRECTIVE && prop.name === "register"
600
+ );
601
+ if (registerProp === void 0) return;
602
+ const alreadyProcessed = registerProp.modifiers.some(
603
+ (modifier) => modifier.type === NodeTypes.SIMPLE_EXPRESSION && modifier.content === V_REGISTER_COMPILED_MODIFIER
604
+ );
605
+ if (alreadyProcessed) return;
606
+ warnIfRedundant(node);
607
+ registerProp.modifiers.push(createSimpleExpression(V_REGISTER_COMPILED_MODIFIER, true));
608
+ } catch (err) {
609
+ console.error("[attaform] redundant-binding warn transform failed, skipping:", err);
610
+ }
611
+ };
612
+
439
613
  const HINT_MARKER = "__attaRv";
440
614
  const HINT_PREFIX = `((${HINT_MARKER}) => (${HINT_MARKER}?.markConnectedOptimistically?.(), ${HINT_MARKER}))(`;
441
615
  const HINT_SUFFIX = `)`;
@@ -568,5 +742,5 @@ function hasPreamble(element) {
568
742
  return false;
569
743
  }
570
744
 
571
- export { vRegisterPreambleTransform as a, componentBridgeTransform as c, inputTextAreaNodeTransform as i, vRegisterHintTransform as v };
572
- //# sourceMappingURL=attaform.DwCtxauq.mjs.map
745
+ export { vRegisterPreambleTransform as a, componentBridgeTransform as c, inputTextAreaNodeTransform as i, redundantBindingWarnTransform as r, vRegisterHintTransform as v };
746
+ //# sourceMappingURL=attaform.k9V0py0s.mjs.map