attaform 0.27.4 → 0.27.6

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 (70) hide show
  1. package/dist/abstract.cjs +2 -2
  2. package/dist/abstract.d.cts +4 -4
  3. package/dist/abstract.d.mts +4 -4
  4. package/dist/abstract.d.ts +4 -4
  5. package/dist/abstract.mjs +2 -2
  6. package/dist/chunks/devtools.cjs +1 -1
  7. package/dist/chunks/devtools.mjs +1 -1
  8. package/dist/chunks/fingerprint2.cjs +1 -1
  9. package/dist/chunks/fingerprint2.mjs +1 -1
  10. package/dist/index.cjs +3 -3
  11. package/dist/index.d.cts +5 -5
  12. package/dist/index.d.mts +5 -5
  13. package/dist/index.d.ts +5 -5
  14. package/dist/index.mjs +3 -3
  15. package/dist/nuxt.d.cts +1 -1
  16. package/dist/nuxt.d.mts +1 -1
  17. package/dist/nuxt.d.ts +1 -1
  18. package/dist/runtime/plugins/attaform.cjs +1 -1
  19. package/dist/runtime/plugins/attaform.mjs +1 -1
  20. package/dist/shared/{attaform.BrVzB14N.mjs → attaform.B1ZYt2Li.mjs} +4 -4
  21. package/dist/shared/{attaform.BrVzB14N.mjs.map → attaform.B1ZYt2Li.mjs.map} +1 -1
  22. package/dist/shared/{attaform.C9arOC5p.cjs → attaform.B9Wyhfro.cjs} +5 -5
  23. package/dist/shared/{attaform.C9arOC5p.cjs.map → attaform.B9Wyhfro.cjs.map} +1 -1
  24. package/dist/shared/{attaform.C925L0WA.d.mts → attaform.BGDV42hI.d.cts} +1 -1
  25. package/dist/shared/{attaform.BBaMFa-k.cjs → attaform.Biytrv0-.cjs} +3 -3
  26. package/dist/shared/{attaform.CgsQZdy1.mjs.map → attaform.Biytrv0-.cjs.map} +1 -1
  27. package/dist/shared/{attaform.DKtXMaJB.d.ts → attaform.CHlJLAiv.d.mts} +18 -2
  28. package/dist/shared/{attaform.CuUx4jWI.mjs → attaform.CKH2lGdL.mjs} +90 -9
  29. package/dist/shared/attaform.CKH2lGdL.mjs.map +1 -0
  30. package/dist/shared/{attaform.a1GnhjVk.mjs → attaform.CoR4mWfv.mjs} +3 -3
  31. package/dist/shared/{attaform.a1GnhjVk.mjs.map → attaform.CoR4mWfv.mjs.map} +1 -1
  32. package/dist/shared/{attaform.CX2NHAFs.cjs → attaform.D6tk90Rc.cjs} +3 -3
  33. package/dist/shared/{attaform.CX2NHAFs.cjs.map → attaform.D6tk90Rc.cjs.map} +1 -1
  34. package/dist/shared/{attaform.B3spfqIA.cjs → attaform.D8kliaKw.cjs} +4 -4
  35. package/dist/shared/{attaform.B3spfqIA.cjs.map → attaform.D8kliaKw.cjs.map} +1 -1
  36. package/dist/shared/{attaform.CnvbLiIO.d.cts → attaform.DLknk9nF.d.mts} +1 -1
  37. package/dist/shared/{attaform.shkNCMEz.mjs → attaform.DMxM2kol.mjs} +4 -4
  38. package/dist/shared/{attaform.shkNCMEz.mjs.map → attaform.DMxM2kol.mjs.map} +1 -1
  39. package/dist/shared/{attaform.CC4KCx_q.mjs → attaform.DYEr28Pt.mjs} +5 -5
  40. package/dist/shared/{attaform.CC4KCx_q.mjs.map → attaform.DYEr28Pt.mjs.map} +1 -1
  41. package/dist/shared/{attaform.DXwJVUNS.cjs → attaform.DdQN7sgN.cjs} +4 -4
  42. package/dist/shared/{attaform.DXwJVUNS.cjs.map → attaform.DdQN7sgN.cjs.map} +1 -1
  43. package/dist/shared/{attaform.BIPSHY5U.d.cts → attaform.DniawQLP.d.cts} +68 -11
  44. package/dist/shared/{attaform.BIPSHY5U.d.mts → attaform.DniawQLP.d.mts} +68 -11
  45. package/dist/shared/{attaform.BIPSHY5U.d.ts → attaform.DniawQLP.d.ts} +68 -11
  46. package/dist/shared/{attaform.Ib2seKUE.cjs → attaform.DrvMscNX.cjs} +90 -9
  47. package/dist/shared/attaform.DrvMscNX.cjs.map +1 -0
  48. package/dist/shared/{attaform.Cjh6b6xU.d.cts → attaform.F2Q8iv38.d.ts} +18 -2
  49. package/dist/shared/{attaform.gj8dtOlx.d.mts → attaform.FmqKO1Ex.d.cts} +18 -2
  50. package/dist/shared/{attaform.C8zVmank.d.ts → attaform.Fp3sCrVs.d.ts} +1 -1
  51. package/dist/shared/{attaform.CgsQZdy1.mjs → attaform.nMaz2C5P.mjs} +3 -3
  52. package/dist/shared/{attaform.BBaMFa-k.cjs.map → attaform.nMaz2C5P.mjs.map} +1 -1
  53. package/dist/zod-v3.cjs +3 -3
  54. package/dist/zod-v3.d.cts +3 -3
  55. package/dist/zod-v3.d.mts +3 -3
  56. package/dist/zod-v3.d.ts +3 -3
  57. package/dist/zod-v3.mjs +3 -3
  58. package/dist/zod-v4.cjs +3 -3
  59. package/dist/zod-v4.d.cts +6 -6
  60. package/dist/zod-v4.d.mts +6 -6
  61. package/dist/zod-v4.d.ts +6 -6
  62. package/dist/zod-v4.mjs +3 -3
  63. package/dist/zod.cjs +3 -3
  64. package/dist/zod.d.cts +3 -3
  65. package/dist/zod.d.mts +3 -3
  66. package/dist/zod.d.ts +3 -3
  67. package/dist/zod.mjs +3 -3
  68. package/package.json +1 -1
  69. package/dist/shared/attaform.CuUx4jWI.mjs.map +0 -1
  70. package/dist/shared/attaform.Ib2seKUE.cjs.map +0 -1
@@ -2501,9 +2501,16 @@ type RegisterValue<Value = unknown> = Readonly<{
2501
2501
  * but exposes no single inner control to register -- a composite widget,
2502
2502
  * or none found. The directive calls it on mount / unmount; distinct from
2503
2503
  * the SSR-only `markConnectedOptimistically`.
2504
+ *
2505
+ * `hostEl` is the host root element, recorded on connect as the field's
2506
+ * focus-first-error anchor: a no-latch host registers no control, so it
2507
+ * would otherwise be invisible to `focusFirstError` / `scrollToFirstError`
2508
+ * (which walk the registered-element set). On an invalid submit the error
2509
+ * walk resolves the host root to its first focusable descendant. The
2510
+ * directive passes the same element on both edges.
2504
2511
  * @internal
2505
2512
  */
2506
- markHostConnected: (connected: boolean) => void;
2513
+ markHostConnected: (connected: boolean, hostEl: HTMLElement) => void;
2507
2514
  /**
2508
2515
  * Mark this field focused (`true`) or blurred (`false`) for a `v-register`
2509
2516
  * component host with no single latched control -- a composite widget whose
@@ -3630,12 +3637,20 @@ type FieldStateMap<Form extends GenericForm> = LeafWalker<Form, 'field'> & {
3630
3637
  */
3631
3638
  <P extends string>(path: P): FieldCallResult<Form, P>;
3632
3639
  /**
3633
- * Tuple-segment form. Returns the typed `FieldStateMapEntry` for
3634
- * the resolved path when the tuple resolves to a known path.
3635
- * Equivalent to `form.fields[a][b][...]` but useful when the path
3636
- * is built from variables.
3640
+ * Tuple-segment form. The segment-array spelling of the string
3641
+ * call-form above, for a path built from variables — the two resolve
3642
+ * to the same proxy, so `form.fields(['users', i, 'email'])` and
3643
+ * `form.fields(\`users.${i}.email\`)` are interchangeable.
3644
+ *
3645
+ * Resolves to that path's `FieldState`, which at a container is its
3646
+ * rolled-up aggregate (`displayState`, `valid`, `dirty`, ... all
3647
+ * exist regardless of the value type), exactly as the string form
3648
+ * does. Note this is the CALL form: it hands back the path's state,
3649
+ * not a drillable sub-proxy. To walk into a container's children use
3650
+ * dot/bracket access (`form.fields.users[i].email`), which is what
3651
+ * descends.
3637
3652
  */
3638
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldStateMapEntry<NestedType<Form, JoinSegments<S>>>;
3653
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldState<NestedType<Form, JoinSegments<S>>>;
3639
3654
  /**
3640
3655
  * Dynamic-array fallback for callers passing `Path`-typed (runtime)
3641
3656
  * segment arrays — e.g. forwarding `RegisterValue.segments` to
@@ -4576,11 +4591,8 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4576
4591
  */
4577
4592
  applyInvalidSubmitPolicy: (policy?: OnInvalidSubmitPolicy) => void;
4578
4593
  /**
4579
- * Programmatically mark fields as touched — the sticky flag the
4580
- * standard "show errors after interaction" pattern reads. Closes
4581
- * the gap when fields are populated without a DOM gesture (post-
4582
- * import, paste, autofill, server-seeded values you want to
4583
- * validate immediately).
4594
+ * Programmatically mark fields as `touched` — the descriptive
4595
+ * "this field was visited" flag.
4584
4596
  *
4585
4597
  * ```ts
4586
4598
  * form.touch('email') // one leaf
@@ -4589,12 +4601,57 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4589
4601
  * form.touch() // every leaf in the form
4590
4602
  * ```
4591
4603
  *
4604
+ * **This does not reveal errors under the default display
4605
+ * heuristic.** `touched` also flips on a bare focus → blur with no
4606
+ * edit, so the library-default gate deliberately ignores it and
4607
+ * reads `blurredAfterInteraction` instead — the stricter bit that
4608
+ * only a blur *following* an edit sets. Reach for `touch()` when a
4609
+ * custom `getDisplayState` reducer or your analytics reads
4610
+ * `touched`; reach for {@link UseFormReturnType.interact} to make
4611
+ * seeded or imported values surface their errors.
4612
+ *
4592
4613
  * Pure flag write — does not mutate value, focused, blurred, or
4593
4614
  * trigger validation. Idempotent: re-calling on an already-touched
4594
4615
  * field is a no-op. Touched is sticky-true; pair with
4595
4616
  * `form.reset()` / `form.resetField()` to clear.
4596
4617
  */
4597
4618
  touch: (path?: FlatPath<Form> | (string | number)[]) => void;
4619
+ /**
4620
+ * Simulate a complete focus → edit → blur over every leaf under
4621
+ * `path` (the whole form when omitted), so values that arrived
4622
+ * without a DOM gesture reveal their errors under the default
4623
+ * display heuristic.
4624
+ *
4625
+ * ```ts
4626
+ * form.interact('profile') // arm one subtree
4627
+ * await form.interact(['members', 2]) // errors committed on resolve
4628
+ * form.interact() // every leaf in the form
4629
+ * ```
4630
+ *
4631
+ * Flips the full interaction ladder (`touched`, `interacted`, and
4632
+ * `blurredAfterInteraction`) and runs the subtree's validation, so
4633
+ * the default gate — `submissionAttempts > 0 ||
4634
+ * blurredAfterInteraction` — opens through its front door. Use it
4635
+ * to arm one region's errors without a form-wide submit, which
4636
+ * would light up every other field on the page: a field-array row
4637
+ * edited in a modal, a server-seeded section, a pasted import.
4638
+ *
4639
+ * Walks schema leaves, so it reaches fields that are currently
4640
+ * `v-if`'d away or were never mounted; the flags are sticky, so
4641
+ * such a subtree stays revealed when it remounts.
4642
+ *
4643
+ * Flags land synchronously — the returned promise resolves once the
4644
+ * subtree's validation has committed, so an awaiting caller can read
4645
+ * `showErrors` / `errors` immediately after. It never rejects, and
4646
+ * ignoring it is fine.
4647
+ *
4648
+ * No-op on a disabled form, matching every other interaction-
4649
+ * lifecycle write. Does not mutate value, `focused`, or `blurred`:
4650
+ * those are DOM-owned, and the display gate reads none of them.
4651
+ * Sticky like `touched`; pair with `form.reset()` /
4652
+ * `form.resetField()` to clear.
4653
+ */
4654
+ interact: (path?: FlatPath<Form> | (string | number)[]) => Promise<void>;
4598
4655
  /**
4599
4656
  * Append `value` to the array at `path`.
4600
4657
  *
@@ -2501,9 +2501,16 @@ type RegisterValue<Value = unknown> = Readonly<{
2501
2501
  * but exposes no single inner control to register -- a composite widget,
2502
2502
  * or none found. The directive calls it on mount / unmount; distinct from
2503
2503
  * the SSR-only `markConnectedOptimistically`.
2504
+ *
2505
+ * `hostEl` is the host root element, recorded on connect as the field's
2506
+ * focus-first-error anchor: a no-latch host registers no control, so it
2507
+ * would otherwise be invisible to `focusFirstError` / `scrollToFirstError`
2508
+ * (which walk the registered-element set). On an invalid submit the error
2509
+ * walk resolves the host root to its first focusable descendant. The
2510
+ * directive passes the same element on both edges.
2504
2511
  * @internal
2505
2512
  */
2506
- markHostConnected: (connected: boolean) => void;
2513
+ markHostConnected: (connected: boolean, hostEl: HTMLElement) => void;
2507
2514
  /**
2508
2515
  * Mark this field focused (`true`) or blurred (`false`) for a `v-register`
2509
2516
  * component host with no single latched control -- a composite widget whose
@@ -3630,12 +3637,20 @@ type FieldStateMap<Form extends GenericForm> = LeafWalker<Form, 'field'> & {
3630
3637
  */
3631
3638
  <P extends string>(path: P): FieldCallResult<Form, P>;
3632
3639
  /**
3633
- * Tuple-segment form. Returns the typed `FieldStateMapEntry` for
3634
- * the resolved path when the tuple resolves to a known path.
3635
- * Equivalent to `form.fields[a][b][...]` but useful when the path
3636
- * is built from variables.
3640
+ * Tuple-segment form. The segment-array spelling of the string
3641
+ * call-form above, for a path built from variables — the two resolve
3642
+ * to the same proxy, so `form.fields(['users', i, 'email'])` and
3643
+ * `form.fields(\`users.${i}.email\`)` are interchangeable.
3644
+ *
3645
+ * Resolves to that path's `FieldState`, which at a container is its
3646
+ * rolled-up aggregate (`displayState`, `valid`, `dirty`, ... all
3647
+ * exist regardless of the value type), exactly as the string form
3648
+ * does. Note this is the CALL form: it hands back the path's state,
3649
+ * not a drillable sub-proxy. To walk into a container's children use
3650
+ * dot/bracket access (`form.fields.users[i].email`), which is what
3651
+ * descends.
3637
3652
  */
3638
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldStateMapEntry<NestedType<Form, JoinSegments<S>>>;
3653
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldState<NestedType<Form, JoinSegments<S>>>;
3639
3654
  /**
3640
3655
  * Dynamic-array fallback for callers passing `Path`-typed (runtime)
3641
3656
  * segment arrays — e.g. forwarding `RegisterValue.segments` to
@@ -4576,11 +4591,8 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4576
4591
  */
4577
4592
  applyInvalidSubmitPolicy: (policy?: OnInvalidSubmitPolicy) => void;
4578
4593
  /**
4579
- * Programmatically mark fields as touched — the sticky flag the
4580
- * standard "show errors after interaction" pattern reads. Closes
4581
- * the gap when fields are populated without a DOM gesture (post-
4582
- * import, paste, autofill, server-seeded values you want to
4583
- * validate immediately).
4594
+ * Programmatically mark fields as `touched` — the descriptive
4595
+ * "this field was visited" flag.
4584
4596
  *
4585
4597
  * ```ts
4586
4598
  * form.touch('email') // one leaf
@@ -4589,12 +4601,57 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4589
4601
  * form.touch() // every leaf in the form
4590
4602
  * ```
4591
4603
  *
4604
+ * **This does not reveal errors under the default display
4605
+ * heuristic.** `touched` also flips on a bare focus → blur with no
4606
+ * edit, so the library-default gate deliberately ignores it and
4607
+ * reads `blurredAfterInteraction` instead — the stricter bit that
4608
+ * only a blur *following* an edit sets. Reach for `touch()` when a
4609
+ * custom `getDisplayState` reducer or your analytics reads
4610
+ * `touched`; reach for {@link UseFormReturnType.interact} to make
4611
+ * seeded or imported values surface their errors.
4612
+ *
4592
4613
  * Pure flag write — does not mutate value, focused, blurred, or
4593
4614
  * trigger validation. Idempotent: re-calling on an already-touched
4594
4615
  * field is a no-op. Touched is sticky-true; pair with
4595
4616
  * `form.reset()` / `form.resetField()` to clear.
4596
4617
  */
4597
4618
  touch: (path?: FlatPath<Form> | (string | number)[]) => void;
4619
+ /**
4620
+ * Simulate a complete focus → edit → blur over every leaf under
4621
+ * `path` (the whole form when omitted), so values that arrived
4622
+ * without a DOM gesture reveal their errors under the default
4623
+ * display heuristic.
4624
+ *
4625
+ * ```ts
4626
+ * form.interact('profile') // arm one subtree
4627
+ * await form.interact(['members', 2]) // errors committed on resolve
4628
+ * form.interact() // every leaf in the form
4629
+ * ```
4630
+ *
4631
+ * Flips the full interaction ladder (`touched`, `interacted`, and
4632
+ * `blurredAfterInteraction`) and runs the subtree's validation, so
4633
+ * the default gate — `submissionAttempts > 0 ||
4634
+ * blurredAfterInteraction` — opens through its front door. Use it
4635
+ * to arm one region's errors without a form-wide submit, which
4636
+ * would light up every other field on the page: a field-array row
4637
+ * edited in a modal, a server-seeded section, a pasted import.
4638
+ *
4639
+ * Walks schema leaves, so it reaches fields that are currently
4640
+ * `v-if`'d away or were never mounted; the flags are sticky, so
4641
+ * such a subtree stays revealed when it remounts.
4642
+ *
4643
+ * Flags land synchronously — the returned promise resolves once the
4644
+ * subtree's validation has committed, so an awaiting caller can read
4645
+ * `showErrors` / `errors` immediately after. It never rejects, and
4646
+ * ignoring it is fine.
4647
+ *
4648
+ * No-op on a disabled form, matching every other interaction-
4649
+ * lifecycle write. Does not mutate value, `focused`, or `blurred`:
4650
+ * those are DOM-owned, and the display gate reads none of them.
4651
+ * Sticky like `touched`; pair with `form.reset()` /
4652
+ * `form.resetField()` to clear.
4653
+ */
4654
+ interact: (path?: FlatPath<Form> | (string | number)[]) => Promise<void>;
4598
4655
  /**
4599
4656
  * Append `value` to the array at `path`.
4600
4657
  *
@@ -2501,9 +2501,16 @@ type RegisterValue<Value = unknown> = Readonly<{
2501
2501
  * but exposes no single inner control to register -- a composite widget,
2502
2502
  * or none found. The directive calls it on mount / unmount; distinct from
2503
2503
  * the SSR-only `markConnectedOptimistically`.
2504
+ *
2505
+ * `hostEl` is the host root element, recorded on connect as the field's
2506
+ * focus-first-error anchor: a no-latch host registers no control, so it
2507
+ * would otherwise be invisible to `focusFirstError` / `scrollToFirstError`
2508
+ * (which walk the registered-element set). On an invalid submit the error
2509
+ * walk resolves the host root to its first focusable descendant. The
2510
+ * directive passes the same element on both edges.
2504
2511
  * @internal
2505
2512
  */
2506
- markHostConnected: (connected: boolean) => void;
2513
+ markHostConnected: (connected: boolean, hostEl: HTMLElement) => void;
2507
2514
  /**
2508
2515
  * Mark this field focused (`true`) or blurred (`false`) for a `v-register`
2509
2516
  * component host with no single latched control -- a composite widget whose
@@ -3630,12 +3637,20 @@ type FieldStateMap<Form extends GenericForm> = LeafWalker<Form, 'field'> & {
3630
3637
  */
3631
3638
  <P extends string>(path: P): FieldCallResult<Form, P>;
3632
3639
  /**
3633
- * Tuple-segment form. Returns the typed `FieldStateMapEntry` for
3634
- * the resolved path when the tuple resolves to a known path.
3635
- * Equivalent to `form.fields[a][b][...]` but useful when the path
3636
- * is built from variables.
3640
+ * Tuple-segment form. The segment-array spelling of the string
3641
+ * call-form above, for a path built from variables — the two resolve
3642
+ * to the same proxy, so `form.fields(['users', i, 'email'])` and
3643
+ * `form.fields(\`users.${i}.email\`)` are interchangeable.
3644
+ *
3645
+ * Resolves to that path's `FieldState`, which at a container is its
3646
+ * rolled-up aggregate (`displayState`, `valid`, `dirty`, ... all
3647
+ * exist regardless of the value type), exactly as the string form
3648
+ * does. Note this is the CALL form: it hands back the path's state,
3649
+ * not a drillable sub-proxy. To walk into a container's children use
3650
+ * dot/bracket access (`form.fields.users[i].email`), which is what
3651
+ * descends.
3637
3652
  */
3638
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldStateMapEntry<NestedType<Form, JoinSegments<S>>>;
3653
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldState<NestedType<Form, JoinSegments<S>>>;
3639
3654
  /**
3640
3655
  * Dynamic-array fallback for callers passing `Path`-typed (runtime)
3641
3656
  * segment arrays — e.g. forwarding `RegisterValue.segments` to
@@ -4576,11 +4591,8 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4576
4591
  */
4577
4592
  applyInvalidSubmitPolicy: (policy?: OnInvalidSubmitPolicy) => void;
4578
4593
  /**
4579
- * Programmatically mark fields as touched — the sticky flag the
4580
- * standard "show errors after interaction" pattern reads. Closes
4581
- * the gap when fields are populated without a DOM gesture (post-
4582
- * import, paste, autofill, server-seeded values you want to
4583
- * validate immediately).
4594
+ * Programmatically mark fields as `touched` — the descriptive
4595
+ * "this field was visited" flag.
4584
4596
  *
4585
4597
  * ```ts
4586
4598
  * form.touch('email') // one leaf
@@ -4589,12 +4601,57 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4589
4601
  * form.touch() // every leaf in the form
4590
4602
  * ```
4591
4603
  *
4604
+ * **This does not reveal errors under the default display
4605
+ * heuristic.** `touched` also flips on a bare focus → blur with no
4606
+ * edit, so the library-default gate deliberately ignores it and
4607
+ * reads `blurredAfterInteraction` instead — the stricter bit that
4608
+ * only a blur *following* an edit sets. Reach for `touch()` when a
4609
+ * custom `getDisplayState` reducer or your analytics reads
4610
+ * `touched`; reach for {@link UseFormReturnType.interact} to make
4611
+ * seeded or imported values surface their errors.
4612
+ *
4592
4613
  * Pure flag write — does not mutate value, focused, blurred, or
4593
4614
  * trigger validation. Idempotent: re-calling on an already-touched
4594
4615
  * field is a no-op. Touched is sticky-true; pair with
4595
4616
  * `form.reset()` / `form.resetField()` to clear.
4596
4617
  */
4597
4618
  touch: (path?: FlatPath<Form> | (string | number)[]) => void;
4619
+ /**
4620
+ * Simulate a complete focus → edit → blur over every leaf under
4621
+ * `path` (the whole form when omitted), so values that arrived
4622
+ * without a DOM gesture reveal their errors under the default
4623
+ * display heuristic.
4624
+ *
4625
+ * ```ts
4626
+ * form.interact('profile') // arm one subtree
4627
+ * await form.interact(['members', 2]) // errors committed on resolve
4628
+ * form.interact() // every leaf in the form
4629
+ * ```
4630
+ *
4631
+ * Flips the full interaction ladder (`touched`, `interacted`, and
4632
+ * `blurredAfterInteraction`) and runs the subtree's validation, so
4633
+ * the default gate — `submissionAttempts > 0 ||
4634
+ * blurredAfterInteraction` — opens through its front door. Use it
4635
+ * to arm one region's errors without a form-wide submit, which
4636
+ * would light up every other field on the page: a field-array row
4637
+ * edited in a modal, a server-seeded section, a pasted import.
4638
+ *
4639
+ * Walks schema leaves, so it reaches fields that are currently
4640
+ * `v-if`'d away or were never mounted; the flags are sticky, so
4641
+ * such a subtree stays revealed when it remounts.
4642
+ *
4643
+ * Flags land synchronously — the returned promise resolves once the
4644
+ * subtree's validation has committed, so an awaiting caller can read
4645
+ * `showErrors` / `errors` immediately after. It never rejects, and
4646
+ * ignoring it is fine.
4647
+ *
4648
+ * No-op on a disabled form, matching every other interaction-
4649
+ * lifecycle write. Does not mutate value, `focused`, or `blurred`:
4650
+ * those are DOM-owned, and the display gate reads none of them.
4651
+ * Sticky like `touched`; pair with `form.reset()` /
4652
+ * `form.resetField()` to clear.
4653
+ */
4654
+ interact: (path?: FlatPath<Form> | (string | number)[]) => Promise<void>;
4598
4655
  /**
4599
4656
  * Append `value` to the array at `path`.
4600
4657
  *
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const vue = require('vue');
4
- const devtoolsShared = require('./attaform.BBaMFa-k.cjs');
4
+ const devtoolsShared = require('./attaform.Biytrv0-.cjs');
5
5
  const registerProtocol = require('./attaform.yUrz7C2z.cjs');
6
6
 
7
7
  function safeAssign(target, key, value) {
@@ -626,7 +626,9 @@ function computeVerdict(field, formMeta) {
626
626
  (e) => e.path.length === field.path.length && e.path.every((s, i) => s === field.path[i])
627
627
  );
628
628
  if (hasOwnError) return "error";
629
- if (field.valid === true && field.blank !== true && field.dirty === true) return "success";
629
+ if (field.valid === true && field.blank !== true && (field.dirty === true || field.interacted === true)) {
630
+ return "success";
631
+ }
630
632
  return "idle";
631
633
  }
632
634
  function earliestNonNull(a, b) {
@@ -2509,8 +2511,8 @@ function buildRegister(state, formInstanceId, instanceConfig) {
2509
2511
  markConnectedOptimistically: () => {
2510
2512
  state.markConnectedOptimistically(segments);
2511
2513
  },
2512
- markHostConnected: (connected) => {
2513
- state.markHostConnected(segments, connected);
2514
+ markHostConnected: (connected, hostEl) => {
2515
+ state.markHostConnected(segments, connected, hostEl, formInstanceId);
2514
2516
  },
2515
2517
  markFocused: (focused) => {
2516
2518
  state.markFocused(
@@ -3314,6 +3316,14 @@ function buildFormApi(state, formInstanceId, options = {}) {
3314
3316
  const segments = pathInput === void 0 ? devtoolsShared.ROOT_PATH : devtoolsShared.canonicalizePath(pathInput).segments;
3315
3317
  state.touchAtPath(segments);
3316
3318
  }
3319
+ async function interact(pathInput) {
3320
+ const segments = pathInput === void 0 ? devtoolsShared.ROOT_PATH : devtoolsShared.canonicalizePath(pathInput).segments;
3321
+ if (!state.interactAtPath(segments)) return;
3322
+ try {
3323
+ await validateAsyncBuilt(pathInput === void 0 ? void 0 : segments);
3324
+ } catch {
3325
+ }
3326
+ }
3317
3327
  const focusFirstError = (options2) => {
3318
3328
  const target = state.getFirstErrorElement(formInstanceId);
3319
3329
  if (target === null) return false;
@@ -3460,6 +3470,7 @@ function buildFormApi(state, formInstanceId, options = {}) {
3460
3470
  scrollToFirstError: gated(scrollToFirstError),
3461
3471
  applyInvalidSubmitPolicy: gated(applyInvalidSubmitPolicyPublic),
3462
3472
  touch: gated(touch),
3473
+ interact: gated(interact),
3463
3474
  get history() {
3464
3475
  void state.activate();
3465
3476
  return formHistory;
@@ -4068,6 +4079,25 @@ function mergeObjectKeys(target, source, path, schema) {
4068
4079
  }
4069
4080
 
4070
4081
  const isContainer = (value) => Array.isArray(value) || isPlainRecord(value);
4082
+ const HOST_FOCUS_TARGET_SELECTOR = [
4083
+ 'input:not([type="hidden"])',
4084
+ "select",
4085
+ "textarea",
4086
+ "button",
4087
+ "a[href]",
4088
+ '[tabindex]:not([tabindex="-1"])',
4089
+ '[contenteditable="true"]'
4090
+ ].join(", ");
4091
+ function resolveHostFocusTarget(hostRoot) {
4092
+ const candidates = hostRoot.querySelectorAll(HOST_FOCUS_TARGET_SELECTOR);
4093
+ for (const el of candidates) {
4094
+ if (!el.isConnected) continue;
4095
+ if (el.offsetParent === null) continue;
4096
+ if (el.matches(":disabled")) continue;
4097
+ return el;
4098
+ }
4099
+ return hostRoot;
4100
+ }
4071
4101
  function isHydratedFieldRecord(value) {
4072
4102
  if (typeof value !== "object" || value === null) return false;
4073
4103
  const r = value;
@@ -4253,6 +4283,7 @@ function createFormStore(options) {
4253
4283
  const fields = vue.reactive(/* @__PURE__ */ new Map());
4254
4284
  const elements = vue.reactive(/* @__PURE__ */ new Map());
4255
4285
  const elementToFormInstance = /* @__PURE__ */ new WeakMap();
4286
+ const hostTargets = /* @__PURE__ */ new Map();
4256
4287
  let sortedRegistrationsCache = null;
4257
4288
  const schemaErrors = vue.reactive(/* @__PURE__ */ new Map());
4258
4289
  const userErrors = vue.reactive(/* @__PURE__ */ new Map());
@@ -5125,6 +5156,7 @@ function createFormStore(options) {
5125
5156
  record.elements.add(raw);
5126
5157
  }
5127
5158
  elementToFormInstance.set(element, formInstanceId);
5159
+ hostTargets.delete(key);
5128
5160
  sortedRegistrationsCache = null;
5129
5161
  const current = fields.get(key);
5130
5162
  touchFieldRecord(key, path, {
@@ -5162,10 +5194,14 @@ function createFormStore(options) {
5162
5194
  blurred: current?.blurred ?? true
5163
5195
  });
5164
5196
  }
5165
- function markHostConnected(path, connected) {
5197
+ function markHostConnected(path, connected, hostEl, formInstanceId) {
5166
5198
  const { key } = devtoolsShared.canonicalizePath(path);
5167
5199
  const current = fields.get(key);
5168
5200
  if (connected) {
5201
+ if (!elements.has(key)) {
5202
+ hostTargets.set(key, { path, hostRoot: hostEl, formInstanceId });
5203
+ sortedRegistrationsCache = null;
5204
+ }
5169
5205
  if (current?.connected === true) return;
5170
5206
  touchFieldRecord(key, path, {
5171
5207
  connected: true,
@@ -5173,6 +5209,7 @@ function createFormStore(options) {
5173
5209
  blurred: current?.blurred ?? true
5174
5210
  });
5175
5211
  } else {
5212
+ if (hostTargets.delete(key)) sortedRegistrationsCache = null;
5176
5213
  if (current?.connected !== true) return;
5177
5214
  touchFieldRecord(key, path, { connected: false, focused: null, blurred: null });
5178
5215
  }
@@ -5249,6 +5286,32 @@ function createFormStore(options) {
5249
5286
  );
5250
5287
  }
5251
5288
  }
5289
+ function interactAtPath(segments) {
5290
+ if (effectiveDisabled.value) return false;
5291
+ const formValue = form.value;
5292
+ let interactedAny = false;
5293
+ for (const [, entry] of originals) {
5294
+ if (!devtoolsShared.isPathPrefix(segments, entry.segments)) continue;
5295
+ if (!hasAtPath(formValue, entry.segments)) continue;
5296
+ interactedAny = true;
5297
+ const leafKey = devtoolsShared.canonicalizePath(entry.segments).key;
5298
+ const current = fields.get(leafKey);
5299
+ if (current?.touched === true && current.interacted === true && current.blurredAfterInteraction === true) {
5300
+ continue;
5301
+ }
5302
+ touchFieldRecord(leafKey, entry.segments, {
5303
+ touched: true,
5304
+ interacted: true,
5305
+ blurredAfterInteraction: true
5306
+ });
5307
+ }
5308
+ if (!interactedAny && devtoolsShared.__DEV__) {
5309
+ console.warn(
5310
+ `[attaform] form.interact(): no fields resolved at path ${JSON.stringify(segments)}. Check the path matches an existing field or container.`
5311
+ );
5312
+ }
5313
+ return interactedAny;
5314
+ }
5252
5315
  function rehydrate() {
5253
5316
  const factory = defaultValuesFactory.value;
5254
5317
  if (factory === void 0) {
@@ -5507,18 +5570,35 @@ function createFormStore(options) {
5507
5570
  function getFirstErrorElement(formInstanceId) {
5508
5571
  sortedRegistrationsCache ?? (sortedRegistrationsCache = rebuildSortedRegistrations());
5509
5572
  for (const entry of sortedRegistrationsCache) {
5510
- if (elementToFormInstance.get(entry.element) !== formInstanceId) continue;
5573
+ if (entry.formInstanceId !== formInstanceId) continue;
5511
5574
  if (!entry.element.isConnected) continue;
5512
5575
  if (entry.element.offsetParent === null) continue;
5513
5576
  if (getErrorsForPath(entry.path).length === 0) continue;
5514
- return { path: entry.path, element: entry.element };
5577
+ const element = entry.host ? resolveHostFocusTarget(entry.element) : entry.element;
5578
+ return { path: entry.path, element };
5515
5579
  }
5516
5580
  return null;
5517
5581
  }
5518
5582
  function rebuildSortedRegistrations() {
5519
5583
  const flat = [];
5520
5584
  for (const [, record] of elements) {
5521
- for (const el of record.elements) flat.push({ path: record.path, element: el });
5585
+ for (const el of record.elements) {
5586
+ flat.push({
5587
+ path: record.path,
5588
+ element: el,
5589
+ host: false,
5590
+ formInstanceId: elementToFormInstance.get(el) ?? ""
5591
+ });
5592
+ }
5593
+ }
5594
+ for (const [key, target] of hostTargets) {
5595
+ if (elements.has(key)) continue;
5596
+ flat.push({
5597
+ path: target.path,
5598
+ element: target.hostRoot,
5599
+ host: true,
5600
+ formInstanceId: target.formInstanceId
5601
+ });
5522
5602
  }
5523
5603
  flat.sort(
5524
5604
  (a, b) => a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
@@ -5586,6 +5666,7 @@ function createFormStore(options) {
5586
5666
  markFocused,
5587
5667
  markInteracted,
5588
5668
  touchAtPath,
5669
+ interactAtPath,
5589
5670
  markConnectedOptimistically,
5590
5671
  markHostConnected,
5591
5672
  isPristineAtPath,
@@ -7613,4 +7694,4 @@ exports.unset = unset;
7613
7694
  exports.useAbstractForm = useAbstractForm;
7614
7695
  exports.useRegister = useRegister;
7615
7696
  exports.useWizard = useWizard;
7616
- //# sourceMappingURL=attaform.Ib2seKUE.cjs.map
7697
+ //# sourceMappingURL=attaform.DrvMscNX.cjs.map