attaform 0.28.0 → 0.30.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 (66) hide show
  1. package/dist/abstract.d.mts +5 -5
  2. package/dist/abstract.mjs +4 -4
  3. package/dist/dev/abstract.mjs +4 -4
  4. package/dist/dev/chunks/dev-key-collision-warnings.mjs +47 -8
  5. package/dist/dev/chunks/devtools.mjs +1 -1
  6. package/dist/dev/directive.mjs +1 -1
  7. package/dist/dev/history.mjs +1 -2
  8. package/dist/dev/index.mjs +5 -5
  9. package/dist/dev/runtime/plugins/attaform.mjs +3 -3
  10. package/dist/dev/shared/{attaform-dev.oYrkP9dj.mjs → attaform-dev.BK6Ge2qR.mjs} +80 -341
  11. package/dist/dev/shared/{attaform-dev.Dr2YzS5B.mjs → attaform-dev.BQc5QV3Y.mjs} +11 -19
  12. package/dist/dev/shared/{attaform-dev.BR8u0YOZ.mjs → attaform-dev.BcNtyOmf.mjs} +4 -4
  13. package/dist/{shared/attaform.h0i6LijT.mjs → dev/shared/attaform-dev.BcYT6tFh.mjs} +152 -20
  14. package/dist/dev/shared/{attaform-dev.6qxD-Yqo.mjs → attaform-dev.BisFstxh.mjs} +227 -88
  15. package/dist/dev/shared/{attaform-dev.WmuhD_CZ.mjs → attaform-dev.CQ2rJiAX.mjs} +833 -656
  16. package/dist/dev/shared/{attaform-dev.P7cseiEG.mjs → attaform-dev.D6CLvaQT.mjs} +20 -11
  17. package/dist/dev/shared/{attaform-dev.D2I8e_Mm.mjs → attaform-dev.DTrHonpc.mjs} +225 -238
  18. package/dist/dev/shared/{attaform-dev.BXE2cr0L.mjs → attaform-dev.DUZckyV8.mjs} +1 -3
  19. package/dist/dev/zod-v3.mjs +6 -6
  20. package/dist/dev/zod-v4.mjs +6 -6
  21. package/dist/dev/zod.mjs +5 -5
  22. package/dist/directive.d.mts +2 -2
  23. package/dist/directive.mjs +1 -1
  24. package/dist/history.d.mts +1 -1
  25. package/dist/history.mjs +1 -2
  26. package/dist/index.d.mts +9 -7
  27. package/dist/index.mjs +5 -5
  28. package/dist/nuxt.d.mts +11 -20
  29. package/dist/nuxt.mjs +0 -1
  30. package/dist/runtime/plugins/attaform.mjs +3 -3
  31. package/dist/shared/{attaform.uLbun31C.mjs → attaform.B3resMQ1.mjs} +19 -11
  32. package/dist/shared/{attaform.Cs2Rbdlo.mjs → attaform.BK0cgu2K.mjs} +123 -139
  33. package/dist/{dev/shared/attaform-dev.By4PRjt0.mjs → shared/attaform.BRDDQeHV.mjs} +136 -26
  34. package/dist/shared/{attaform.NLmerepI.mjs → attaform.BXneBxEj.mjs} +71 -334
  35. package/dist/shared/{attaform.D6DCnnQu.mjs → attaform.ByWP6kSx.mjs} +4 -4
  36. package/dist/shared/{attaform.CJP3RkGa.mjs → attaform.CJ8DpRGj.mjs} +223 -238
  37. package/dist/shared/{attaform.C09fpdLS.mjs → attaform.CUfrMK7t.mjs} +227 -88
  38. package/dist/shared/{attaform.wfY8XFvm.mjs → attaform.CdZIEf7_.mjs} +11 -19
  39. package/dist/shared/{attaform.JfSWdEcb.d.mts → attaform.Clz0ArIi.d.mts} +1 -1
  40. package/dist/shared/{attaform.B8k8JrmK.mjs → attaform.DNJOWjQw.mjs} +826 -612
  41. package/dist/shared/{attaform.B4VkS43-.d.mts → attaform.Rc9u4Vfw.d.mts} +1 -1
  42. package/dist/shared/{attaform.D5V4tZWq.d.mts → attaform.UGyOK83T.d.mts} +200 -218
  43. package/dist/shared/{attaform.Dq4vWxuG.mjs → attaform.WiGdIqgx.mjs} +1 -3
  44. package/dist/shared/{attaform.Cgu-hdSb.d.mts → attaform.bKLzMU0o.d.mts} +321 -523
  45. package/dist/transforms.d.mts +0 -22
  46. package/dist/transforms.mjs +1 -1
  47. package/dist/vite.d.mts +13 -4
  48. package/dist/vite.mjs +8 -2
  49. package/dist/zod-v3.d.mts +6 -4
  50. package/dist/zod-v3.mjs +6 -6
  51. package/dist/zod-v4.d.mts +18 -31
  52. package/dist/zod-v4.mjs +6 -6
  53. package/dist/zod.d.mts +4 -4
  54. package/dist/zod.mjs +5 -5
  55. package/package.json +19 -15
  56. package/skills/attaform/SKILL.md +6 -3
  57. package/skills/attaform/references/custom-components.md +8 -3
  58. package/skills/attaform/references/errors.md +8 -3
  59. package/skills/attaform/references/saving.md +77 -0
  60. package/skills/attaform/references/wizards.md +10 -8
  61. package/dist/chunks/fingerprint.mjs +0 -183
  62. package/dist/chunks/fingerprint2.mjs +0 -159
  63. package/dist/dev/chunks/fingerprint.mjs +0 -183
  64. package/dist/dev/chunks/fingerprint2.mjs +0 -159
  65. package/dist/dev/shared/attaform-dev.BJGA_UOS.mjs +0 -36
  66. package/dist/shared/attaform.BJGA_UOS.mjs +0 -36
@@ -359,6 +359,48 @@ type JoinSegments<S extends ReadonlyArray<string | number>, Acc extends string =
359
359
  infer Head extends string | number,
360
360
  ...infer Rest extends ReadonlyArray<string | number>
361
361
  ] ? Acc extends '' ? JoinSegments<Rest, `${Head}`> : JoinSegments<Rest, `${Acc}.${Head}`> : Acc;
362
+ /**
363
+ * The advice half of a segment-array rejection, used when `S` never
364
+ * inferred a tuple at all.
365
+ *
366
+ * A caller who passes a plain `string` reaches this branch, because
367
+ * inference falls back to the constraint and `JoinSegments` of a
368
+ * non-tuple array is `''`. Naming a path would be a lie there, so the
369
+ * message names the remedies instead. This is the case behind #568: a
370
+ * row component received its path prefix as an untyped `string` prop,
371
+ * every concatenation widened to `string`, and the only signal was
372
+ * `segments: never`.
373
+ */
374
+ type PlainStringAdvice = 'attaform: a plain string cannot be checked against the schema. Pass a literal path, type the dynamic prefix, or use the segment-array form.';
375
+ /**
376
+ * Why a segment array was rejected, as a type the compiler prints.
377
+ *
378
+ * Every segment-array overload brands its parameter
379
+ * `S & (<joined path is valid> ? unknown : <this>)`. The brand used to
380
+ * be `never`, and `S & never` is `never`, so TypeScript reported the
381
+ * parameter as `never` and said nothing about what was wrong. A string
382
+ * literal intersects without collapsing, so the sentence survives into
383
+ * the diagnostic and the argument keeps its inferred tuple type:
384
+ *
385
+ * ```
386
+ * Argument of type '["boxes", 3, "nope"]' is not assignable to parameter
387
+ * of type 'readonly ["boxes", 3, "nope"] &
388
+ * "attaform: 'boxes.3.nope' is not a path in this form's schema"'.
389
+ * ```
390
+ *
391
+ * Nothing can satisfy the brand, so a wrong path is still a compile
392
+ * error; only the message changes.
393
+ */
394
+ type SegmentPathRejection<Joined extends string> = [Joined] extends [''] ? PlainStringAdvice : `attaform: '${Joined}' is not a path in this form's schema`;
395
+ /**
396
+ * `SegmentPathRejection` for `register`, whose accepted set is
397
+ * `RegisterFlatPath` rather than `FlatPath`.
398
+ *
399
+ * The distinction is worth its own sentence: a container path is in the
400
+ * schema and still not registrable, so "not a path in this form's
401
+ * schema" would be false where it is most likely to be read.
402
+ */
403
+ type SegmentRegisterRejection<Joined extends string> = [Joined] extends [''] ? PlainStringAdvice : `attaform: '${Joined}' is not a registrable path. v-register binds a leaf input, so container paths are excluded.`;
362
404
  /**
363
405
  * `true` when `T` is a union (multiple members), `false` when it's a
364
406
  * single type. Used to gate non-homomorphic mapped-type forms so
@@ -523,44 +565,89 @@ type IsTuple<T extends readonly unknown[]> = number extends T['length'] ? false
523
565
  * runtime possibility of a missing array position.
524
566
  */
525
567
  type NestedReadType<RootValue, FlattenedPath extends string> = NestedTypeBuilder<RootValue, FlattenedPath, true>;
568
+ /**
569
+ * The array a path actually addresses, nullish stripped, or `never`
570
+ * when the path does not address an array. Shared by `ArrayPath`
571
+ * (membership) and `ArrayItem` (element type) so the filter and the
572
+ * extractor cannot disagree about what counts as one.
573
+ *
574
+ * The strip is the load-bearing part. `Form` is the schema's INPUT
575
+ * shape, so `z.array(row).default([])` resolves to `row[] | undefined`,
576
+ * and `NestedType` tags a discriminated union's per-variant keys the
577
+ * same way on purpose: that tagging is what keeps `NestedType` in
578
+ * lockstep with `FlatPath`, so every path `FlatPath` offers resolves to
579
+ * a useful value type. A bare `extends readonly unknown[]` predicate
580
+ * read the tag as "not an array", which is how every optional,
581
+ * defaulted, nullable, and DU-variant array fell out of the field-array
582
+ * helpers while the runtime went on accepting all of them (#541).
583
+ *
584
+ * `extends infer Leaf` binds the stripped leaf once so the guards below
585
+ * do not re-instantiate `NestedType`, and the tuple wraps suppress
586
+ * distribution so a union leaf is judged whole: `A[] | B[]` is an
587
+ * array, `A[] | string` is not.
588
+ *
589
+ * Exported so `rollup-plugin-dts` keeps it as a named alias in the
590
+ * bundled `.d.ts` instead of inlining the body at every reference.
591
+ * Consumers reach for `ArrayPath` / `ArrayItem`; this alias is not part
592
+ * of the stable surface.
593
+ */
594
+ type ArrayLeafOf<Form, P extends string> = Exclude<NestedType<Form, P>, undefined | null> extends infer Leaf ? [Leaf] extends [never] ? never : [Leaf] extends [readonly unknown[]] ? Leaf : never : never;
526
595
  /**
527
596
  * Filter FlatPath<Form> down to the subset of paths whose resolved leaf
528
597
  * is an array. Used by the typed field-array helpers (append / remove /
529
598
  * swap / ...) so those helpers only accept paths that actually address
530
599
  * an array — calling `append('email', ...)` on a `{ email: string }`
531
- * is a compile error.
600
+ * is a compile error. Optionality is not a disqualifier; see
601
+ * `ArrayLeafOf` for what "is an array" means here.
532
602
  *
533
603
  * `P extends string` re-triggers distribution over the `FlatPath<Form>`
534
604
  * union so the conditional evaluates per member. Without it, the
535
605
  * branch would reduce against the union as a whole and collapse to
536
606
  * `never` whenever a single member failed the predicate.
537
607
  */
538
- type ArrayPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends string ? NestedType<Form, P> extends readonly unknown[] ? P : never : never;
608
+ type ArrayPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends string ? [ArrayLeafOf<Form, P>] extends [never] ? never : P : never;
539
609
  /**
540
610
  * Extract the element type of the array addressed by `Path`. Callers
541
611
  * constrain `Path extends ArrayPath<Form>` so this is always well-defined.
612
+ * Rides on `ArrayLeafOf`, the same alias `ArrayPath` admits the path
613
+ * with, so an accepted path never hands its helper a `never` value slot.
614
+ */
615
+ type ArrayItem<Form, Path extends ArrayPath<Form>> = ArrayLeafOf<Form, Path> extends ReadonlyArray<infer Item> ? Item : never;
616
+ /**
617
+ * The record a path actually addresses, nullish stripped, or `never`
618
+ * when the path does not address one. Shared by `RecordPath`
619
+ * (membership) and `RecordValue` (value type).
620
+ *
621
+ * `string extends keyof Leaf` is the index-signature probe: it holds for
622
+ * `Record<string, V>` (`keyof` is `string`) and fails for a fixed object
623
+ * (`keyof` is the literal key union). The leading array guard keeps
624
+ * arrays (which also satisfy the object check) out of the record set.
625
+ *
626
+ * Nullish is stripped first for the reason `ArrayLeafOf` gives, with an
627
+ * extra bite here: `keyof (Record<string, V> | undefined)` is `never`,
628
+ * so on a `.default({})` or `.optional()` record the probe could not
629
+ * fire at all and `form.record(path)` rejected the path (#541).
630
+ *
631
+ * Exported for the same `.d.ts` reason as `ArrayLeafOf`, and equally
632
+ * not part of the stable surface.
542
633
  */
543
- type ArrayItem<Form, Path extends ArrayPath<Form>> = NestedType<Form, Path> extends ReadonlyArray<infer Item> ? Item : never;
634
+ type RecordLeafOf<Form, P extends string> = Exclude<NestedType<Form, P>, undefined | null> extends infer Leaf ? [Leaf] extends [never] ? never : [Leaf] extends [readonly unknown[]] ? never : [Leaf] extends [Record<string, unknown>] ? string extends keyof Leaf ? Leaf : never : never : never;
544
635
  /**
545
636
  * Companion to `ArrayPath`: filter `FlatPath<Form>` down to the subset
546
637
  * of paths whose resolved leaf is a record (an object with an open
547
638
  * string-keyed index signature, e.g. `z.record(z.string(), V)`). A
548
639
  * fixed-shape object (`z.object({ ... })`) is excluded — its keys are
549
640
  * statically known, so it has no `string` index signature.
550
- *
551
- * `string extends keyof T` is the index-signature probe: it holds for
552
- * `Record<string, V>` (`keyof` is `string`) and fails for a fixed object
553
- * (`keyof` is the literal key union). The leading array guard keeps
554
- * arrays (which also satisfy the object check) out of the record set.
555
641
  */
556
- type RecordPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends string ? NestedType<Form, P> extends readonly unknown[] ? never : NestedType<Form, P> extends Record<string, unknown> ? string extends keyof NestedType<Form, P> ? P : never : never : never;
642
+ type RecordPath<Form, P extends FlatPath<Form> = FlatPath<Form>> = P extends string ? [RecordLeafOf<Form, P>] extends [never] ? never : P : never;
557
643
  /**
558
644
  * Value type of the record addressed by `Path` — the `V` in a
559
645
  * `Record<string, V>`. Callers constrain `Path extends RecordPath<Form>`,
560
646
  * so the leaf is always an open string-keyed record and this is
561
- * well-defined.
647
+ * well-defined. Rides on `RecordLeafOf`, the same alias `RecordPath`
648
+ * admits the path with, so the two cannot disagree.
562
649
  */
563
- type RecordValue<Form, Path extends RecordPath<Form>> = NestedType<Form, Path> extends Record<string, infer Value> ? Value : never;
650
+ type RecordValue<Form, Path extends RecordPath<Form>> = RecordLeafOf<Form, Path> extends Record<string, infer Value> ? Value : never;
564
651
  /**
565
652
  * Widens primitive-literal leaves to their primitive supertype to
566
653
  * match the runtime "slim-primitive write contract."
@@ -751,12 +838,12 @@ type AcceptableDefaultsOf<DVI, SchemaInput> = DVI | SchemaInput | (() => DVI | S
751
838
  type FormKey = string;
752
839
  /**
753
840
  * Per-form options threaded from `useForm` into the adapter factory.
754
- * Today carries the resolved `maxRecursionDepth` so adapter walks can
755
- * cap their descent through recursive schemas; future per-form runtime
756
- * knobs land here too.
841
+ * Today carries `maxRecursionDepth` so adapter walks can cap their
842
+ * descent through recursive schemas; future per-form runtime knobs
843
+ * land here too.
757
844
  */
758
845
  interface SchemaFactoryOptions {
759
- /** Resolved recursion ceiling (per-form > app-default > library default). */
846
+ /** Recursion ceiling for walks through recursive schemas. */
760
847
  maxRecursionDepth: number;
761
848
  }
762
849
  /**
@@ -861,27 +948,49 @@ type ErrorInput = Error | {
861
948
  code?: string;
862
949
  data?: Json | null;
863
950
  };
864
- /** Settled validation result when the form (or subtree) parsed successfully. */
865
- type ValidationResponseSuccess<TData> = {
951
+ /**
952
+ * A parse verdict as an `AbstractSchema` produces it.
953
+ *
954
+ * A schema knows nothing about which form asked: it is a pure function
955
+ * of the schema it wraps, which is what lets one `AbstractSchema` serve
956
+ * every form built on the same schema. The owning store stamps its own
957
+ * `formKey` on the way out, producing the `ValidationResponse*` arms
958
+ * below. Adapter authors implement THESE.
959
+ */
960
+ type SchemaParseSuccess<TData> = {
866
961
  /** The parsed value at the validated subtree (whole form when `validate()` was called without a path). */
867
962
  data: TData;
868
963
  errors: undefined;
869
964
  success: true;
870
- formKey: FormKey;
871
965
  };
872
- /** Settled validation result when no data could be produced (e.g. a top-level type mismatch). */
873
- type ValidationResponseErrorWithoutData = {
966
+ /** Schema-level verdict when no data could be produced (e.g. a top-level type mismatch). */
967
+ type SchemaParseErrorWithoutData = {
874
968
  data: undefined;
875
969
  /** Non-empty list of failures. */
876
970
  errors: ValidationError[];
877
971
  success: false;
878
- formKey: FormKey;
879
972
  };
880
- /** Settled validation result when the parser produced partial data alongside failures. */
881
- type ValidationResponseErrorWithData<TData> = {
973
+ /** Schema-level verdict when the parser produced partial data alongside failures. */
974
+ type SchemaParseErrorWithData<TData> = {
882
975
  data: TData;
883
976
  errors: ValidationError[];
884
977
  success: false;
978
+ };
979
+ /** Settled schema-level verdict. Discriminate on `success`. */
980
+ type SchemaParseResult<TData> = SchemaParseSuccess<TData> | SchemaParseErrorWithData<TData> | SchemaParseErrorWithoutData;
981
+ /** Schema-level `getDefaultValues` verdict: defaults always come back. */
982
+ type SchemaDefaultsResult<TData> = SchemaParseSuccess<TData> | SchemaParseErrorWithData<TData>;
983
+ /** Settled validation result when the form (or subtree) parsed successfully. */
984
+ type ValidationResponseSuccess<TData> = SchemaParseSuccess<TData> & {
985
+ /** The form this verdict belongs to. Stamped by the store, not the schema. */
986
+ formKey: FormKey;
987
+ };
988
+ /** Settled validation result when no data could be produced (e.g. a top-level type mismatch). */
989
+ type ValidationResponseErrorWithoutData = SchemaParseErrorWithoutData & {
990
+ formKey: FormKey;
991
+ };
992
+ /** Settled validation result when the parser produced partial data alongside failures. */
993
+ type ValidationResponseErrorWithData<TData> = SchemaParseErrorWithData<TData> & {
885
994
  formKey: FormKey;
886
995
  };
887
996
  /**
@@ -896,12 +1005,6 @@ type ValidationResponseErrorWithData<TData> = {
896
1005
  * ```
897
1006
  */
898
1007
  type ValidationResponse<TData> = ValidationResponseSuccess<TData> | ValidationResponseErrorWithData<TData> | ValidationResponseErrorWithoutData;
899
- /**
900
- * Result of resolving the form's default values. Always returns at
901
- * least the shape derived from the schema; `errors` carry any
902
- * failures from validating those defaults against the schema.
903
- */
904
- type DefaultValuesResponse<TData> = ValidationResponseSuccess<TData> | ValidationResponseErrorWithData<TData>;
905
1008
  /**
906
1009
  * Trimmed `ValidationResponse` that omits the `data` payload. Used by
907
1010
  * the reactive `validate()` status, whose consumers only need the
@@ -949,21 +1052,13 @@ type ValidateOptions = {
949
1052
  /**
950
1053
  * Configuration passed to `AbstractSchema.getDefaultValues`. Adapters
951
1054
  * receive `useDefaultSchemaValues` (honor `.default(x)` wrappers vs.
952
- * empty/falsy fallbacks), an optional `strict` mode (refinement
953
- * preservation), and an optional `constraints` overlay merged into the
954
- * derived defaults so the runtime can stamp user-supplied defaults at
955
- * construction. Exported so adapter authors can co-implement the
956
- * service contract.
1055
+ * empty/falsy fallbacks) and an optional `constraints` overlay merged
1056
+ * into the derived defaults so the runtime can stamp user-supplied
1057
+ * defaults at construction. Exported so adapter authors can
1058
+ * co-implement the service contract.
957
1059
  */
958
1060
  type GetDefaultValuesConfig<Form> = {
959
1061
  useDefaultSchemaValues: boolean;
960
- /**
961
- * Whether to keep schema refinements when deriving slim defaults.
962
- * `true` (default) — preserve refinements; `false` — strip them so
963
- * placeholder data lands without immediate construction-time
964
- * errors. Mirrors `useForm({ strict })`.
965
- */
966
- strict?: boolean;
967
1062
  constraints?: DeepPartial<WriteShape<Form>> | undefined;
968
1063
  };
969
1064
  /**
@@ -977,45 +1072,7 @@ type GetDefaultValuesConfig<Form> = {
977
1072
  * adding support for a new schema library (Valibot, ArkType, custom).
978
1073
  */
979
1074
  type AbstractSchema<Form, GetValueFormType> = {
980
- /**
981
- * Structural fingerprint of the schema. Same shape → same string;
982
- * different shape → (best-effort) different string.
983
- *
984
- * Resolves a `Promise` so adapters can defer the structural walk (and
985
- * its `canonicalStringify` helper) onto a dynamic import. The framework
986
- * only ever needs the fingerprint for the dev-only shared-key schema
987
- * mismatch warning, so none of those bytes belong on the eager
988
- * `useForm` path.
989
- *
990
- * The library uses this to detect schema mismatches at a shared
991
- * form key: two `useForm({ key: 'x', schema })` calls are allowed
992
- * to land on the same `FormStore` (the "shared store" semantic),
993
- * but only when their schemas agree. If the second call's
994
- * fingerprint differs from the first's, the library emits a
995
- * dev-mode warning — the first call's schema stays canonical and
996
- * the second call's schema is silently ignored.
997
- *
998
- * Guarantees adapter authors should provide:
999
- * - **Determinism:** equal shapes at different memory addresses
1000
- * must produce the same fingerprint. Referential equality fails
1001
- * 99% of the time across files, so reference-identity is not a
1002
- * substitute.
1003
- * - **Key-order-insensitivity** for record-like shapes (object,
1004
- * struct) — two shapes with the same keys but different iteration
1005
- * order must match.
1006
- * - **Order-insensitivity for unbounded unions** — `a | b` and
1007
- * `b | a` must match (the set of members is what matters, not
1008
- * their source order).
1009
- *
1010
- * Compromises adapter authors may accept:
1011
- * - Function-valued metadata (refinements, transforms, lazy
1012
- * defaults) is not stably hashable. Represent it as an opaque
1013
- * sentinel; two schemas differing only in refinement logic will
1014
- * look identical. The warning is a footgun catcher, not a
1015
- * soundness guarantee.
1016
- */
1017
- fingerprint(): Promise<string>;
1018
- getDefaultValues(config: GetDefaultValuesConfig<Form>): DefaultValuesResponse<Form>;
1075
+ getDefaultValues(config: GetDefaultValuesConfig<Form>): SchemaDefaultsResult<Form>;
1019
1076
  /**
1020
1077
  * Return the schema-prescribed default value at the given path. The
1021
1078
  * runtime uses this to fill structural gaps so every `setValue` write
@@ -1088,6 +1145,28 @@ type AbstractSchema<Form, GetValueFormType> = {
1088
1145
  * the result by canonicalized path key.
1089
1146
  */
1090
1147
  isPreprocessOrCoerceLeaf(path: Path): boolean;
1148
+ /**
1149
+ * Reports whether `path` resolves to an opaque leaf: a schema that
1150
+ * declares a value without describing its shape. In Zod v4 that's
1151
+ * `z.any()`, `z.unknown()`, and `z.custom(...)` (the kind both
1152
+ * `z.instanceof(X)` and `z.custom<T>()` compile to); in Zod v3 it's
1153
+ * `z.any()` / `z.unknown()`, with `z.instanceof(X)` peeling through
1154
+ * `ZodEffects` to `ZodAny`.
1155
+ *
1156
+ * Consulted by the slim-primitive write gate. An opaque leaf admits
1157
+ * every kind, INCLUDING containers, and it declares no sub-paths.
1158
+ * Without this the gate accepts an array at the leaf and then walks
1159
+ * into it, checking `files.0` against a schema that never declared
1160
+ * that path; the empty accept set there reads as "not in your
1161
+ * schema" and no-ops the whole write (#542).
1162
+ *
1163
+ * Exact-path semantic, unlike `isPreprocessOrCoerceLeaf`: it answers
1164
+ * for the node at `path` only. Descendants stay unwritable on their
1165
+ * own, because the schema genuinely does not declare them and
1166
+ * fabricating them would put phantom nodes on `form.fields`. Writing
1167
+ * the whole value at the opaque leaf is the supported move.
1168
+ */
1169
+ isOpaqueLeafAtPath(path: Path): boolean;
1091
1170
  /**
1092
1171
  * Distinguish a tuple (fixed-length, position-typed) from an
1093
1172
  * unbounded array at `path`. The runtime calls this on every
@@ -1133,6 +1212,29 @@ type AbstractSchema<Form, GetValueFormType> = {
1133
1212
  * `false`.
1134
1213
  */
1135
1214
  isFixedObjectAtPath(path: Path): boolean;
1215
+ /**
1216
+ * How the container at `path` spells the keys of its own entries:
1217
+ * `'number'` for an array or a tuple, `'string'` for an object or a
1218
+ * record, and for a `z.map` the kind its declared key type accepts.
1219
+ * `undefined` for anything else, a leaf and an undeclared path
1220
+ * included.
1221
+ *
1222
+ * The write walkers consult it at a `Map`, where a new entry has to
1223
+ * be filed under a key of the declared type and the path segment
1224
+ * alone cannot say which: an integer-looking segment canonicalises
1225
+ * to a number, so `scores.42` against a `z.map(z.string(), V)` would
1226
+ * otherwise take the number `42` as its key and fail the map's own
1227
+ * parse. An entry the map already holds needs no ruling — its
1228
+ * existing key wins.
1229
+ *
1230
+ * A map whose key type is neither string-ish nor number-ish, or
1231
+ * which admits both, reports `undefined`: no segment can spell such
1232
+ * a key, so the map stays a whole value with no addressable entries.
1233
+ *
1234
+ * Adapters MAY cache per path; the runtime calls this on the write
1235
+ * path.
1236
+ */
1237
+ entryKeyKindAtPath(path: Path): 'string' | 'number' | undefined;
1136
1238
  /**
1137
1239
  * Return every sub-schema that could resolve at the given structured
1138
1240
  * path. Multiple results are only expected for discriminated / union
@@ -1170,7 +1272,7 @@ type AbstractSchema<Form, GetValueFormType> = {
1170
1272
  * returned as a `success: false` response with a populated
1171
1273
  * `errors` array.
1172
1274
  */
1173
- validateAtPath(data: unknown, path: Path | undefined, options?: ValidateOptions): MaybePromise<ValidationResponse<GetValueFormType>>;
1275
+ validateAtPath(data: unknown, path: Path | undefined, options?: ValidateOptions): MaybePromise<SchemaParseResult<GetValueFormType>>;
1174
1276
  /**
1175
1277
  * Sync sister to `getSchemasAtPath` / `validateAtPath`. Returns the
1176
1278
  * set of primitive `typeof`-style kinds the path's leaf schema
@@ -1201,11 +1303,14 @@ type AbstractSchema<Form, GetValueFormType> = {
1201
1303
  * - For nullable / optional wrappers: adds `'null'` / `'undefined'`
1202
1304
  * to the inner's set.
1203
1305
  */
1204
- getSlimPrimitiveTypesAtPath(path: Path): Set<SlimPrimitiveKind>;
1306
+ getSlimPrimitiveTypesAtPath(path: Path): ReadonlySet<SlimPrimitiveKind>;
1205
1307
  /**
1206
- * Return `true` iff `path` resolves to a **leaf** in the schema — a
1207
- * path whose slim primitive set contains only primitive kinds (no
1208
- * `object`, `array`, `map`, `set`). The runtime proxies (`form.values`,
1308
+ * Return `true` iff `path` resolves to a **leaf** in the schema: a
1309
+ * path the schema declares no sub-paths under. That is every path
1310
+ * whose slim primitive set contains only primitive kinds (no
1311
+ * `object`, `array`, `map`, `set`), plus the opaque leaves, which
1312
+ * admit every kind and are classified by kind instead (below). The
1313
+ * runtime proxies (`form.values`,
1209
1314
  * `form.errors`, `form.fields`) query this at every step to decide
1210
1315
  * between **descend into a sub-proxy** (container) and **terminate
1211
1316
  * with a leaf value** (leaf).
@@ -1223,6 +1328,14 @@ type AbstractSchema<Form, GetValueFormType> = {
1223
1328
  * date/function) → `true`. `'date'` counts as a leaf (don't drill
1224
1329
  * into `Date`). `'function'` is a leaf for the same reason — opaque
1225
1330
  * value.
1331
+ * - **Opaque leaf** (`z.any()`, `z.unknown()`, `z.custom()`, and the
1332
+ * `z.instanceof(X)` that compiles to it) → `true`, and it answers
1333
+ * ahead of the kind test. An opaque leaf admits every kind
1334
+ * INCLUDING the container ones, so reading it off the slim
1335
+ * primitive set alone classifies it as a container and puts
1336
+ * phantom nodes under it on `form.fields` — see
1337
+ * `isOpaqueLeafAtPath`. What leafness asks is whether the schema
1338
+ * declares sub-paths here, and an opaque leaf declares none.
1226
1339
  * - **Optional / Nullable / Default / Catch** wrappers transparent —
1227
1340
  * adds `'null'` / `'undefined'` to the inner kind set without
1228
1341
  * changing the leaf classification.
@@ -1470,38 +1583,6 @@ type SettledValidationStatus<Form> = {
1470
1583
  * `pending` to switch between in-flight and settled states.
1471
1584
  */
1472
1585
  type ReactiveValidationStatus<Form> = PendingValidationStatus | SettledValidationStatus<Form>;
1473
- /**
1474
- * What to do when a submit attempt fails validation. The library can
1475
- * focus and/or scroll the first errored field into view without you
1476
- * wiring an `onError` callback yourself. Defaults to
1477
- * `'focus-first-error'` because moving keyboard / screen-reader focus
1478
- * to the broken field on submit is an accessibility baseline; opt out
1479
- * with `'none'` if you're managing focus elsewhere.
1480
- *
1481
- * - `'focus-first-error'` (default): focus the first errored field's
1482
- * first visible element. Modern browsers scroll the focused element
1483
- * into view by default; pair with `'both'` if you want an explicit
1484
- * scroll alongside.
1485
- * - `'scroll-to-first-error'`: scroll that element into view without
1486
- * moving focus.
1487
- * - `'both'`: scroll first, then focus (with `preventScroll: true` so
1488
- * the browser doesn't undo the explicit scroll).
1489
- * - `'none'`: no automatic UI nudge; the dev handles focus / scroll
1490
- * manually via `form.focusFirstError()` or `form.scrollToFirstError()`
1491
- * from an `onError` callback.
1492
- *
1493
- * Both focusing policies request `focusVisible: true`, so the browser
1494
- * paints a focus ring even though the move is programmatic. Where a UA
1495
- * doesn't yet support that hint, non-text controls (radio, checkbox,
1496
- * custom widgets) may end up focused without a visible ring after a
1497
- * pointer-driven submit; if you target those browsers, pair the policy
1498
- * with your own indicator (e.g. a `:focus-within` ring on the option
1499
- * wrapper).
1500
- *
1501
- * If no errored field has a currently mounted, visible element, the
1502
- * policy silently no-ops.
1503
- */
1504
- type OnInvalidSubmitPolicy = 'none' | 'focus-first-error' | 'scroll-to-first-error' | 'both';
1505
1586
  /**
1506
1587
  * When per-field VALIDATION runs. Only validation timing varies per
1507
1588
  * mode; storage commit timing is the directive's concern (the
@@ -1695,8 +1776,7 @@ type HistoryModule = {
1695
1776
  * and `undo()` cannot reach the transient pre-hydration default.
1696
1777
  *
1697
1778
  * One plugin instance is a reusable configuration, not per-form state:
1698
- * passing the same instance to several forms (or setting it once via
1699
- * `createAttaform({ defaults: { history } })`) gives each form its own
1779
+ * passing the same instance to several forms gives each form its own
1700
1780
  * independent chain.
1701
1781
  */
1702
1782
  type HistoryPlugin = {
@@ -1780,11 +1860,9 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1780
1860
  *
1781
1861
  * For schemas that depend on the form's identity or per-form
1782
1862
  * options, pass a factory `(key, options) => schema` instead — the
1783
- * library calls it once per form, after `mergeWithDefaults` has
1784
- * resolved the options bag (`maxRecursionDepth`, etc.). Most
1785
- * adapters ignore the options argument; the typed Zod entry points
1786
- * use it to thread the resolved recursion cap into the adapter
1787
- * closure.
1863
+ * library calls it once per form. Most adapters ignore the options
1864
+ * argument; the typed Zod entry points use it to thread the
1865
+ * recursion cap into the adapter closure.
1788
1866
  */
1789
1867
  schema: Schema | ((key: FormKey, options: SchemaFactoryOptions) => Schema);
1790
1868
  /**
@@ -1842,40 +1920,29 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1842
1920
  */
1843
1921
  defaultValues?: DefaultValues | (() => DefaultValues) | (() => Promise<DefaultValues>);
1844
1922
  /**
1845
- * Whether to validate default values at construction. Default
1846
- * `true`.
1923
+ * Move keyboard / screen-reader focus to the first errored field when
1924
+ * a submit attempt fails validation. Fires after errors are populated
1925
+ * and before your `onError` callback runs.
1847
1926
  *
1848
- * - `true` (default): the schema is run against the derived
1849
- * defaults immediately; any failures populate `form.errors` from
1850
- * the first frame. The UI decides when to *show* errors — gate
1851
- * on `form.fields.<path>.touched`, `form.meta.submissionAttempts`, etc.
1852
- * - `false`: refinements are stripped during defaults derivation
1853
- * and construction-time validation is skipped. Useful for
1854
- * multi-step wizards, field arrays seeded with placeholder
1855
- * rows, or any form intentionally mounting with incomplete data.
1927
+ * Default `true`: focusing the broken field on submit is an
1928
+ * accessibility baseline, and modern browsers scroll the focused
1929
+ * element into view as part of the move. Pass `false` when you drive
1930
+ * the nudge yourself from an `onError` callback with
1931
+ * `form.focusFirstError()` or `form.scrollToFirstError()`, both of
1932
+ * which stay available either way.
1856
1933
  *
1857
- * Runtime validation (per-field on edit, full-form on submit) is
1858
- * identical regardless of this flag.
1859
- */
1860
- strict?: boolean;
1861
- /**
1862
- * Automatic UI nudge on submit-validation failure. Fires after
1863
- * errors are populated and before your `onError` callback runs.
1864
- * Default `'focus-first-error'`, which moves keyboard / screen-reader
1865
- * focus to the broken field as an accessibility baseline.
1866
- *
1867
- * - `'focus-first-error'` (default): focus the first errored field's
1868
- * first visible element.
1869
- * - `'scroll-to-first-error'`: scroll it into view without focusing.
1870
- * - `'both'`: scroll, then focus.
1871
- * - `'none'`: opt out entirely; handle focus / scroll yourself in an
1872
- * `onError` callback via `form.focusFirstError()` or
1873
- * `form.scrollToFirstError()`.
1934
+ * The focus requests `focusVisible: true`, so the browser paints a
1935
+ * focus ring even though the move is programmatic. Where a UA doesn't
1936
+ * yet support that hint, non-text controls (radio, checkbox, custom
1937
+ * widgets) may end up focused without a visible ring after a
1938
+ * pointer-driven submit; if you target those browsers, pair this with
1939
+ * your own indicator (e.g. a `:focus-within` ring on the option
1940
+ * wrapper).
1874
1941
  *
1875
- * If no errored field has a currently-mounted, visible element,
1876
- * the policy silently no-ops.
1942
+ * If no errored field has a currently-mounted, visible element, the
1943
+ * focus silently no-ops.
1877
1944
  */
1878
- onInvalidSubmit?: OnInvalidSubmitPolicy;
1945
+ focusOnInvalidSubmit?: boolean;
1879
1946
  /**
1880
1947
  * Freeze the form's data. When this resolves truthy every value write
1881
1948
  * no-ops at the store's write chokepoint (programmatic `setValue`, the
@@ -1893,7 +1960,6 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1893
1960
  *
1894
1961
  * Accepts a boolean, a ref, a computed, or a getter, read live so the
1895
1962
  * freeze tracks a reactive source. `undefined` resolves to `false`.
1896
- * Falls back to `AttaformDefaults.disabled`.
1897
1963
  *
1898
1964
  * A shared FormStore resolves `disabled` from its first `useForm({ key })`
1899
1965
  * call; a later caller passing a different value is ignored.
@@ -1960,68 +2026,18 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
1960
2026
  rememberVariants?: boolean;
1961
2027
  /**
1962
2028
  * Schema-driven coercion of user-typed DOM values at the v-register
1963
- * directive layer. Per-form override of the plugin-level
1964
- * `AttaformDefaults.coerce`.
2029
+ * directive layer.
1965
2030
  *
1966
- * - `true` / `undefined` — runs the built-in `defaultCoercionRules`.
1967
- * - `false` — disables coercion; the slim gate rejects mismatches.
1968
- * - `CoercionRegistry` — a custom array of entries (REPLACES, not
1969
- * merges, the plugin defaults). Spread `defaultCoercionRules` to
1970
- * extend.
2031
+ * Two rules ship: string→number and string→boolean, each firing
2032
+ * only when the schema declares that single type at the path.
2033
+ * Defaults to on; `false` disables coercion form-wide and the slim
2034
+ * gate rejects mismatches instead.
1971
2035
  *
1972
2036
  * Coercion applies ONLY to user-typed DOM values. Programmatic
1973
2037
  * writes (`form.setValue`, `setValueWithInternalPath`) are NEVER
1974
2038
  * coerced.
1975
2039
  */
1976
- coerce?: boolean | CoercionRegistry;
1977
- /**
1978
- * Per-form override of the `getDisplayState` heuristic that drives
1979
- * `field.displayState` and the `show*` booleans (and their `form.meta`
1980
- * rollups). Falls back to `AttaformDefaults.getDisplayState`, then to
1981
- * the library default (`defaultDisplayState`). See
1982
- * `AttaformDefaults.getDisplayState` for the resolution rules and
1983
- * predicate signature.
1984
- */
1985
- getDisplayState?: GetDisplayState;
1986
- /**
1987
- * Recursion ceiling for schema walks that descend through recursive
1988
- * schemas (Zod's `z.lazy(...)` today). Default `64`. Per-form value
1989
- * overrides `AttaformDefaults.maxRecursionDepth`, which overrides
1990
- * the library default.
1991
- *
1992
- * Schemas that don't include a recursive boundary ignore this knob
1993
- * entirely — it's read only at the descent step through a recursive
1994
- * wrapper. Set it on the specific form whose schema is recursive
1995
- * (a comment tree, a category tree, a nested-rule editor):
1996
- *
1997
- * ```ts
1998
- * useForm({ schema: commentTreeSchema, maxRecursionDepth: 128 })
1999
- * ```
2000
- *
2001
- * Past the cap, the slim-primitive type gate falls back to permissive
2002
- * (write-time type checks skip; full schema validation still runs).
2003
- * Storage and reads work at any depth; only the per-write type gate
2004
- * stops short of the cap. Raise the cap if you regularly edit nodes
2005
- * beyond the default depth.
2006
- *
2007
- * See `AttaformDefaults.maxRecursionDepth` for the resolution rules
2008
- * and the broader description of where the cap is read.
2009
- */
2010
- maxRecursionDepth?: number;
2011
- /**
2012
- * Whether `v-register` automatically manages aria attributes
2013
- * (`aria-invalid`, `aria-busy`, `aria-required`, `aria-describedby`)
2014
- * from the field's display state. **Defaults to `true`.**
2015
- *
2016
- * **Resolution order (per-register override > per-form > global > library):**
2017
- *
2018
- * register(path, { autoAria }) > useForm({ autoAria }) > AttaformDefaults.autoAria > library default (`true`)
2019
- *
2020
- * Set `false` to leave all aria wiring to your own markup form-wide.
2021
- * Any aria attribute you author yourself is always left untouched,
2022
- * independent of this flag.
2023
- */
2024
- autoAria?: boolean;
2040
+ coerce?: boolean;
2025
2041
  /**
2026
2042
  * @internal
2027
2043
  * SSR prefetch mark — set by the `attaform/vite` compile-time
@@ -2038,176 +2054,6 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
2038
2054
  */
2039
2055
  __ssrAccessed?: boolean;
2040
2056
  };
2041
- /**
2042
- * App-level defaults applied to every `useForm` call. Set these once
2043
- * per app via `createAttaform({ defaults })` (bare Vue) or
2044
- * `attaform.defaults` (Nuxt module).
2045
- *
2046
- * Resolution order (per-form wins):
2047
- *
2048
- * useForm({ ... }) > createAttaform({ defaults }) > library default
2049
- *
2050
- * `validateOn` and `debounceMs` resolve per-field — set the debounce
2051
- * globally while still overriding the trigger per form:
2052
- *
2053
- * ```ts
2054
- * createAttaform({
2055
- * defaults: { debounceMs: 100 },
2056
- * })
2057
- * // later
2058
- * useForm({ schema, validateOn: 'blur' })
2059
- * // → { validateOn: 'blur', debounceMs: <ignored under blur> }
2060
- * ```
2061
- *
2062
- * Note: per the discriminated union, `debounceMs` only takes effect
2063
- * when `validateOn` is `'change'` (or omitted). Setting it as an
2064
- * app-level default is fine — forms that switch to `'blur'` /
2065
- * `'submit'` simply ignore the inherited `debounceMs`.
2066
- *
2067
- * `schema`, `key`, and `defaultValues` are not configurable here —
2068
- * they belong on the per-form call.
2069
- */
2070
- type AttaformDefaults = {
2071
- /** Default for `useForm({ strict })`. Default `true`. */
2072
- strict?: boolean;
2073
- /** Default for `useForm({ onInvalidSubmit })`. */
2074
- onInvalidSubmit?: OnInvalidSubmitPolicy;
2075
- /** Default for `useForm({ validateOn })` — when validation runs. */
2076
- validateOn?: ValidateOn;
2077
- /**
2078
- * Default for `useForm({ debounceMs })` — ms to wait after the last
2079
- * input event before re-running validation. Only meaningful when
2080
- * `validateOn` resolves to `'change'`. Default `0` (synchronous).
2081
- */
2082
- debounceMs?: number;
2083
- /** Default for `useForm({ history })` — a `historyPlugin()` instance. */
2084
- history?: HistoryPlugin;
2085
- /** Default for `useForm({ rememberVariants })`. */
2086
- rememberVariants?: boolean;
2087
- /** Default for `useForm({ disabled })` — freeze the form's data. */
2088
- disabled?: MaybeRefOrGetter<boolean | undefined>;
2089
- /**
2090
- * Default for `useForm({ coerce })`. Schema-driven coercion of
2091
- * user-typed DOM values at the v-register directive layer.
2092
- *
2093
- * - `true` (default) — runs the built-in `defaultCoercionRules`
2094
- * (`string→number`, `string→boolean`).
2095
- * - `false` — disables coercion globally; the slim-primitive gate
2096
- * rejects type mismatches with its existing dev-warn instead.
2097
- * - `CoercionRegistry` — a custom array of `CoercionEntry` records.
2098
- * Spread `defaultCoercionRules` to extend rather than replace:
2099
- * `[...defaultCoercionRules, defineCoercion({ ... })]`.
2100
- *
2101
- * Coercion applies ONLY to user-typed DOM values flowing through
2102
- * the directive's assigner. Programmatic writes (`form.setValue`,
2103
- * `setValueWithInternalPath`) are NEVER coerced — they're
2104
- * authoritative writes whose strict typing is on the caller.
2105
- */
2106
- coerce?: boolean | CoercionRegistry;
2107
- /**
2108
- * Default for `useForm({ getDisplayState })`. The centralised
2109
- * heuristic that resolves every path's `field.displayState` — and thus
2110
- * the `show*` booleans and their `form.meta` rollups — to one of
2111
- * `'idle' | 'pending' | 'error' | 'success'`.
2112
- *
2113
- * Resolution order (per-form wins):
2114
- *
2115
- * useForm({ getDisplayState }) > AttaformDefaults > library default
2116
- *
2117
- * The library default opens one timing gate, then resolves by
2118
- * precedence: gate closed → `'idle'`; a run in flight → a delayed
2119
- * `'pending'` (held briefly to smooth fast validations, then held a
2120
- * minimum so it never flashes); an own-path error → `'error'`;
2121
- * otherwise earned `valid` → `'success'`, else `'idle'`. The gate opens
2122
- * after the first submit attempt OR once the field is edited and left:
2123
- *
2124
- * ```ts
2125
- * (prev, ctx) => {
2126
- * const gateOpen =
2127
- * ctx.formMeta.submissionAttempts > 0 ||
2128
- * ctx.field.blurredAfterInteraction === true
2129
- * if (!gateOpen) return { display: 'idle' }
2130
- * // ...timed 'pending' while validating; own-path error → 'error';
2131
- * // earned valid → 'success'; else 'idle'
2132
- * }
2133
- * ```
2134
- *
2135
- * Compose with the library default via the public `defaultDisplayState`
2136
- * export, or retune its timing via `makeDefaultDisplayState`. The
2137
- * reducer runs on every field-state read, so it owns the
2138
- * idle / pending / error / success decision outright.
2139
- *
2140
- * The reducer's `ctx.field` / `ctx.formMeta` are `Omit`'d of the
2141
- * derived `displayState` / `show*` / `firstError` keys (see
2142
- * `FieldStateDerivedKey`) to prevent a self-referential reducer.
2143
- */
2144
- getDisplayState?: GetDisplayState;
2145
- /**
2146
- * Default for `useForm({ maxRecursionDepth })`. Recursion ceiling
2147
- * for schema walks that descend through recursive schemas (Zod's
2148
- * `z.lazy(...)` today, equivalent constructs in any future adapter).
2149
- * Library default: `64`.
2150
- *
2151
- * Resolution order (per-form wins):
2152
- *
2153
- * useForm({ maxRecursionDepth }) > AttaformDefaults > library default (64)
2154
- *
2155
- * Read at every step of a schema walk that crosses a recursive
2156
- * boundary — default-value derivation at construction, slim-primitive
2157
- * type gates on each write, path-by-path schema resolution. Walks
2158
- * track their descent depth and switch to a permissive fallback once
2159
- * `depth > maxRecursionDepth`.
2160
- *
2161
- * "Permissive fallback" means storage and reads keep working at any
2162
- * depth; only the per-write type gate stops checking past the cap.
2163
- * Full schema validation (`parse`, `handleSubmit`) still runs
2164
- * against the real schema, so refinement errors at any depth still
2165
- * surface — the cap only affects the *write-time gate*.
2166
- *
2167
- * Forms with no recursive schemas ignore this entirely — the cap is
2168
- * read only at the descent step through a recursive wrapper. Setting
2169
- * it app-wide is the right move when you have multiple recursive
2170
- * forms that should share one ceiling:
2171
- *
2172
- * ```ts
2173
- * createAttaform({
2174
- * defaults: { maxRecursionDepth: 128 },
2175
- * })
2176
- * ```
2177
- *
2178
- * Per-form override stays available for the one tree-shaped form
2179
- * whose depth is unusual:
2180
- *
2181
- * ```ts
2182
- * useForm({ schema: deepCategoryTreeSchema, maxRecursionDepth: 256 })
2183
- * ```
2184
- *
2185
- * Setting this app-wide costs nothing for non-recursive forms — the
2186
- * walks that read the cap never run for them.
2187
- *
2188
- * Pass `Infinity` to disable the cap entirely. Walks will then
2189
- * descend through recursive boundaries until they terminate
2190
- * structurally; a schema with no structural terminator will exhaust
2191
- * the JS call stack. Reserve for schemas whose authors are
2192
- * confident the recursion is bounded by the actual data shape.
2193
- */
2194
- maxRecursionDepth?: number;
2195
- /**
2196
- * App-wide default for `useForm({ autoAria })`. Library default is
2197
- * `true`: `v-register` keeps `aria-invalid` / `aria-busy` /
2198
- * `aria-required` / `aria-describedby` in sync with each field's
2199
- * display state out of the box.
2200
- *
2201
- * **Resolution order (per-form wins):**
2202
- *
2203
- * useForm({ autoAria }) > AttaformDefaults.autoAria > library default (`true`)
2204
- *
2205
- * Set `false` once at the plugin level to make every form manage its
2206
- * own aria markup. Authored aria attributes are always preserved
2207
- * regardless of this setting.
2208
- */
2209
- autoAria?: boolean;
2210
- };
2211
2057
  /**
2212
2058
  * Callback invoked by `handleSubmit` after the form parses successfully.
2213
2059
  * Receives the strictly-typed parsed value — refinements have run, so
@@ -2220,7 +2066,7 @@ type OnSubmit<Form extends GenericForm> = (form: Form) => void | Promise<void>;
2220
2066
  * the user-error layer (the `setErrors(...); return` server-rejection
2221
2067
  * pattern). Receives the full list of errors. Bind this when you want to
2222
2068
  * react to submit failures explicitly (alongside or instead of the
2223
- * automatic `onInvalidSubmit` UI nudge).
2069
+ * automatic `focusOnInvalidSubmit` nudge).
2224
2070
  */
2225
2071
  type OnError = (error: ValidationError[]) => void | Promise<void>;
2226
2072
  /**
@@ -2274,11 +2120,11 @@ type DisplayMachine = {
2274
2120
  readonly pendingShownAt?: number;
2275
2121
  };
2276
2122
  /**
2277
- * Inputs to a `getDisplayState` reducer. `field` and `formMeta` are the
2278
- * same reactive snapshots a predicate has always received (still minus
2279
- * the derived `displayState` / `show*` / `firstError` keys — see
2280
- * `FieldStateDerivedKey` — so a reducer can never read its own output and
2281
- * form a cycle), now joined by:
2123
+ * Inputs to the display reducer. `field` and `formMeta` are the
2124
+ * reactive snapshots it resolves against (minus the derived
2125
+ * `displayState` / `show*` / `firstError` keys — see
2126
+ * `FieldStateDerivedKey` — so the reducer can never read its own output
2127
+ * and form a cycle), joined by:
2282
2128
  *
2283
2129
  * - `validatingSince` — `Date.now()` at which the field's current
2284
2130
  * validation streak opened, or `null` when nothing is in flight. This,
@@ -2306,26 +2152,9 @@ type DisplayCtx = {
2306
2152
  * returns the next machine; the engine owns the clock and the timers, the
2307
2153
  * reducer owns the timing policy. Runs on every field-state read (and
2308
2154
  * again whenever a `reviewAt` deadline fires), so the whole app's
2309
- * validation-display behavior flows from this one function.
2310
- *
2311
- * The library default — `defaultDisplayState` — is publicly exported so a
2312
- * layered reducer can compose with it, and `makeDefaultDisplayState`
2313
- * builds a default with custom anti-flash timings:
2314
- *
2315
- * ```ts
2316
- * import { defaultDisplayState } from 'attaform'
2317
- *
2318
- * useForm({
2319
- * schema,
2320
- * // Defer to the default everywhere, but never show a success check on `username`.
2321
- * getDisplayState: (prev, ctx) => {
2322
- * const next = defaultDisplayState(prev, ctx)
2323
- * return next.display === 'success' && ctx.field.path[0] === 'username'
2324
- * ? { display: 'idle' }
2325
- * : next
2326
- * },
2327
- * })
2328
- * ```
2155
+ * validation-display behavior flows from this one function
2156
+ * (`core/display-state.ts`).
2157
+ * @internal
2329
2158
  */
2330
2159
  type GetDisplayState = (prev: DisplayMachine, ctx: DisplayCtx) => DisplayMachine;
2331
2160
  /**
@@ -2461,60 +2290,6 @@ type RegisterFlatPath<Form> = Form extends unknown ? FlatPathBuilder<Form, 'regi
2461
2290
  * `transformError` channel above, not a throw into the host app.)
2462
2291
  */
2463
2292
  type RegisterTransform = (value: unknown, ctx?: TransformContext) => unknown;
2464
- /**
2465
- * Runtime type for a slim primitive kind. Used to narrow the
2466
- * `transform` parameter and return value on a `CoercionEntry` so
2467
- * authors writing rules don't have to cast `unknown`.
2468
- *
2469
- * Exhaustive over `SlimPrimitiveKind` — adding a new kind to that
2470
- * union must add a corresponding branch here.
2471
- */
2472
- type SlimRuntimeOf<K extends SlimPrimitiveKind> = K extends 'string' ? string : K extends 'number' ? number : K extends 'boolean' ? boolean : K extends 'bigint' ? bigint : K extends 'date' ? Date : K extends 'null' ? null : K extends 'undefined' ? undefined : K extends 'array' ? readonly unknown[] : K extends 'set' ? ReadonlySet<unknown> : K extends 'map' ? ReadonlyMap<unknown, unknown> : K extends 'object' ? Record<string, unknown> : K extends 'symbol' ? symbol : K extends 'function' ? (...args: never[]) => unknown : never;
2473
- /**
2474
- * Outcome of a coercion attempt.
2475
- *
2476
- * - `coerced: true` — the rule produced `value`, which the directive
2477
- * forwards to the slim gate (the gate may still reject if the
2478
- * value doesn't satisfy the path's accept set).
2479
- * - `coerced: false` — the rule decided it can't coerce this input.
2480
- * The directive passes the original value through; the slim gate
2481
- * decides downstream.
2482
- *
2483
- * Discriminated rather than `O | undefined` so rules with
2484
- * `output: 'undefined'` or `output: 'null'` don't conflict with the
2485
- * "skip" signal.
2486
- */
2487
- type CoercionResult<O> = {
2488
- coerced: true;
2489
- value: O;
2490
- } | {
2491
- coerced: false;
2492
- };
2493
- /**
2494
- * A single coercion rule. `input` and `output` are
2495
- * `SlimPrimitiveKind` literals; `transform` receives a value already
2496
- * narrowed to `SlimRuntimeOf<input>` and returns
2497
- * `CoercionResult<SlimRuntimeOf<output>>`.
2498
- *
2499
- * Rules MUST be sync. They SHOULD NOT throw — wrap internal
2500
- * try/catch when the conversion can fail (e.g. `BigInt(s)` throws
2501
- * for non-numeric strings). The library wraps each invocation in
2502
- * try/catch as defense in depth; throws are caught, logged once per
2503
- * `(input, output)`, and the original value passes through.
2504
- */
2505
- type CoercionEntry<I extends SlimPrimitiveKind = SlimPrimitiveKind, O extends SlimPrimitiveKind = SlimPrimitiveKind> = {
2506
- readonly input: I;
2507
- readonly output: O;
2508
- readonly transform: (value: SlimRuntimeOf<I>) => CoercionResult<SlimRuntimeOf<O>>;
2509
- };
2510
- /**
2511
- * A registry is an ordered array of `CoercionEntry` records.
2512
- * Consumers compose by spreading `defaultCoercionRules` and
2513
- * appending their own entries. Order is observable only when two
2514
- * entries share the same `(input, output)` pair — the library emits
2515
- * a one-shot dev-warn and the LATER entry wins.
2516
- */
2517
- type CoercionRegistry = readonly CoercionEntry[];
2518
2293
  /**
2519
2294
  * Options for `register(path, options)`. Per-field configuration
2520
2295
  * applied at the binding's own call site.
@@ -2552,22 +2327,6 @@ type RegisterOptions = {
2552
2327
  * instead — see the "Custom assigners" section in the API docs.
2553
2328
  */
2554
2329
  transforms?: ReadonlyArray<RegisterTransform>;
2555
- /**
2556
- * Per-binding override for automatic aria management, the narrowest
2557
- * tier of the `autoAria` cascade. By default the directive keeps
2558
- * `aria-invalid` / `aria-busy` / `aria-required` / `aria-describedby`
2559
- * in sync with the field's display state. Pass `autoAria: false` to
2560
- * leave every aria attribute on this element to you (the directive
2561
- * still manages value binding and registration), or `autoAria: true`
2562
- * to re-enable management on one binding even when the form set
2563
- * `useForm({ autoAria: false })`.
2564
- *
2565
- * Overrides `useForm({ autoAria })` and
2566
- * `createAttaform({ defaults: { autoAria } })`. Writing an aria
2567
- * attribute yourself also locks the directive out of that one
2568
- * attribute, regardless of this flag.
2569
- */
2570
- autoAria?: boolean;
2571
2330
  };
2572
2331
  /**
2573
2332
  * The narrow kernel surface the DOM binding drives. Structurally a
@@ -2905,14 +2664,6 @@ type RegisterValue<Value = unknown> = Readonly<{
2905
2664
  * @internal
2906
2665
  */
2907
2666
  isRequired?: boolean;
2908
- /**
2909
- * Whether the directive should auto-manage aria attributes for this
2910
- * binding. Resolves the per-register `autoAria` override against the
2911
- * form-level value: `options.autoAria ?? formAutoAria`. The directive
2912
- * treats an absent value as off.
2913
- * @internal
2914
- */
2915
- ariaEnabled?: boolean;
2916
2667
  /**
2917
2668
  * The gated display-state verdict for this path, reusing the same
2918
2669
  * field-state identity as `form.fields`. The directive watches it to
@@ -3213,9 +2964,11 @@ declare module 'vue' {
3213
2964
  * form.setValue((prev) => ({ ...prev, name: 'Ada' }))
3214
2965
  * ```
3215
2966
  *
3216
- * The library fills any missing structural slots (e.g. nested
3217
- * objects) against the schema's defaults after the callback returns,
3218
- * so partial returns are safe.
2967
+ * A whole-form callback replaces the whole form. Any key the return
2968
+ * omits is refilled from the SCHEMA's declared defaults (the slim
2969
+ * value where none is declared), so an omitted key is not carried
2970
+ * over from the previous value or from `defaultValues` — spread
2971
+ * `prev` to keep the rest of the form.
3219
2972
  */
3220
2973
  type SetValueCallback<Read, Write = Read> = (prev: Read) => Read | Write;
3221
2974
  /**
@@ -3459,11 +3212,11 @@ type FieldState<Value = unknown> = {
3459
3212
  * <FieldStatusIcon :state="form.fields.email.displayState" />
3460
3213
  * ```
3461
3214
  *
3462
- * Resolved by the `getDisplayState` heuristic:
3463
- * `useForm({ getDisplayState })` →
3464
- * `createAttaform({ defaults: { getDisplayState } })` → library
3465
- * default (`defaultDisplayState`). Override per form, app-wide, or
3466
- * compose with `defaultDisplayState` for a layered predicate.
3215
+ * Resolved by the display heuristic: gate closed → `'idle'`; a run
3216
+ * in flight → a delayed `'pending'`; an own-path error → `'error'`;
3217
+ * otherwise earned `valid` → `'success'`, else `'idle'`. The gate
3218
+ * opens after the first submit attempt OR once the field is edited
3219
+ * and left.
3467
3220
  *
3468
3221
  * Available on container paths too: `form.fields.users[0].displayState`
3469
3222
  * rolls up over the row's descendants.
@@ -3848,7 +3601,7 @@ type FieldStateMap<Form extends GenericForm> = LeafWalker<Form, 'field'> & {
3848
3601
  * dot/bracket access (`form.fields.users[i].email`), which is what
3849
3602
  * descends.
3850
3603
  */
3851
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldState<NestedType<Form, JoinSegments<S>>>;
3604
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>)): FieldState<NestedType<Form, JoinSegments<S>>>;
3852
3605
  /**
3853
3606
  * Dynamic-array fallback for callers passing `Path`-typed (runtime)
3854
3607
  * segment arrays — e.g. forwarding `RegisterValue.segments` to
@@ -3919,7 +3672,7 @@ type FormErrorsSurface<Form> = ErrorsProxyShape<Form> & {
3919
3672
  * tuples that don't resolve to a known path fail at the call site.
3920
3673
  * Dynamic `Path`-typed inputs hit the untyped fallback overload below.
3921
3674
  */
3922
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): readonly ValidationError[];
3675
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>)): readonly ValidationError[];
3923
3676
  (segments: ReadonlyArray<string | number>): readonly ValidationError[];
3924
3677
  /**
3925
3678
  * No-arg call returns the whole-form error aggregate, the same as
@@ -3964,6 +3717,27 @@ type ErrorsProxyShape<T> = LeafWalker<T, 'errors', false>;
3964
3717
  * form.values() // the whole form value (root)
3965
3718
  * ```
3966
3719
  *
3720
+ * The two shapes answer different questions. Dot access is the
3721
+ * REACTIVE view: per-key tracking, no copying, always the live value.
3722
+ * The call form returns a SNAPSHOT: a detached plain object that keeps
3723
+ * what it held at capture time, so it is the shape to hand an async
3724
+ * call, a serialiser, or a diff.
3725
+ *
3726
+ * ```ts
3727
+ * const before = form.values() // detached; survives later writes
3728
+ * await api.save(before) // cannot mutate underneath the call
3729
+ * form.values.email // reactive read; use this in computed
3730
+ * ```
3731
+ *
3732
+ * Prefer dot access inside a `computed`, `watchEffect`, or template:
3733
+ * it tracks the one key you read, where the call form depends on the
3734
+ * whole form and re-runs the consumer on any change.
3735
+ *
3736
+ * The copy is deep across plain objects and arrays. Non-plain
3737
+ * instances (Map, Set, File, Date) are shared by reference, matching
3738
+ * `JSON.stringify` behaviour; `structuredClone(form.values())` gives a
3739
+ * fully detached copy when one is needed.
3740
+ *
3967
3741
  * Single-bracket dotted access (`form.values['address.city']`) is
3968
3742
  * intentionally NOT supported — JS object semantics treat the dotted
3969
3743
  * string as a single key. Use chained dot/bracket or the callable
@@ -4007,7 +3781,9 @@ type ValuesSurface<F> = Readonly<LiftedValueShape<F>> & {
4007
3781
  * `form.fields.<path>`; this is the aggregate view across the
4008
3782
  * whole form.
4009
3783
  *
4010
- * Read-only at runtime — assignments throw. Destructuring snapshots
3784
+ * Read-only at runtime — an assignment is refused with a dev-console
3785
+ * warning and never lands (Attaform does not throw from a read
3786
+ * surface). Destructuring snapshots
4011
3787
  * the current values; use `toRefs()` if you need reactive handles
4012
3788
  * to individual fields.
4013
3789
  */
@@ -4032,9 +3808,8 @@ type FormMeta<F = unknown> = FieldState<F> & {
4032
3808
  * the counter at its prior value.
4033
3809
  *
4034
3810
  * Pure introspection counter — useful for "this form has been
4035
- * visited and left" UX (analytics, prior-step badges, layered
4036
- * `getDisplayState` predicates) but does NOT drive the library's
4037
- * default `getDisplayState` heuristic. The reveal-on-submit story
3811
+ * visited and left" UX (analytics, prior-step badges) but does NOT
3812
+ * drive the display heuristic. The reveal-on-submit story
4038
3813
  * runs entirely through `submissionAttempts`, which
4039
3814
  * `wizard.handleSubmit` bumps on every form (it always validates the
4040
3815
  * whole step list).
@@ -4351,7 +4126,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4351
4126
  * `form.setValue([prefix, 'line1'], 'value')`. The resolved leaf
4352
4127
  * type is exact, matching the dotted-string form.
4353
4128
  */
4354
- <const S extends ReadonlyArray<string | number>, Value extends PathSetValuePayload<NestedType<Form, JoinSegments<S>>>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never), value: Value): boolean;
4129
+ <const S extends ReadonlyArray<string | number>, Value extends PathSetValuePayload<NestedType<Form, JoinSegments<S>>>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>), value: Value): boolean;
4355
4130
  };
4356
4131
  /**
4357
4132
  * Reactive validation status. Re-runs whenever the form (or the
@@ -4463,7 +4238,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4463
4238
  */
4464
4239
  register: {
4465
4240
  <Path extends RegisterFlatPath<Form>>(path: Path, options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, Path>>;
4466
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [RegisterFlatPath<Form>] ? unknown : never), options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>;
4241
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [RegisterFlatPath<Form>] ? unknown : SegmentRegisterRejection<JoinSegments<S>>), options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>;
4467
4242
  };
4468
4243
  /**
4469
4244
  * The form's identifier — either the explicit `key` passed to
@@ -4544,8 +4319,12 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4544
4319
  *
4545
4320
  * Resolves after `hydrating` flips back to `false`. Throws
4546
4321
  * synchronously when the form was constructed with a plain-value
4547
- * `defaultValues` (nothing to re-fire). Does NOT clear dirty /
4548
- * touched / submit state — chain `form.reset()` for that.
4322
+ * `defaultValues` (nothing to re-fire).
4323
+ *
4324
+ * The payload becomes the form's defaults, the same way a
4325
+ * `reset(next)` argument does, so `form.reset()` afterwards lands on
4326
+ * the values just fetched. Does NOT clear touched / submit state;
4327
+ * chain `form.reset()` for that.
4549
4328
  */
4550
4329
  rehydrate(): Promise<void>;
4551
4330
  /**
@@ -4603,7 +4382,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4603
4382
  */
4604
4383
  toRef: {
4605
4384
  <Path extends FlatPath<Form>>(path: Path): Readonly<Ref<NestedReadType<WriteShape<ReadForm>, Path>>>;
4606
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): Readonly<Ref<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>>;
4385
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : SegmentPathRejection<JoinSegments<S>>)): Readonly<Ref<NestedReadType<WriteShape<ReadForm>, JoinSegments<S>>>>;
4607
4386
  };
4608
4387
  /**
4609
4388
  * Set the form's manual error layer. One surface for server-side
@@ -4673,10 +4452,10 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4673
4452
  */
4674
4453
  meta: FormMeta<Form>;
4675
4454
  /**
4676
- * Restore the form to its initial state. Without arguments,
4677
- * re-applies the schema defaults (and any `defaultValues` passed
4678
- * to `useForm`). Pass `nextDefaultValues` to seed the reset with
4679
- * a fresh set of overrides.
4455
+ * Restore the form to its defaults. Without arguments, re-applies
4456
+ * whatever the defaults currently are: `useForm({ defaultValues })`
4457
+ * on a fresh form, or the last values a `reset(next)` or an async
4458
+ * `defaultValues` factory settled on.
4680
4459
  *
4681
4460
  * Resets:
4682
4461
  * - the form value back to defaults;
@@ -4685,6 +4464,26 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4685
4464
  * - touched / focused / blurred per-field flags;
4686
4465
  * - submission state (`submitting` / `submissionAttempts` /
4687
4466
  * `submitted` / `submitError`).
4467
+ *
4468
+ * `nextDefaultValues` RE-SEATS the defaults rather than applying for
4469
+ * one call, so the form has a single set of defaults that `reset()`,
4470
+ * `resetField(path)`, and `dirty` all read. That is what makes the
4471
+ * save-then-discard shape work: reset from the resource the server
4472
+ * returned, and a later Discard returns to the save instead of
4473
+ * rolling back across it.
4474
+ *
4475
+ * ```ts
4476
+ * const saved = await api.save(form.values())
4477
+ * form.reset(toValues(saved)) // the saved resource is now the baseline
4478
+ * // …later…
4479
+ * form.reset() // back to the saved resource
4480
+ * ```
4481
+ *
4482
+ * The argument is sparse: it folds over the defaults already in
4483
+ * force, so paths it does not name keep the value they had, and
4484
+ * `reset({})` changes nothing. Arrays are replaced wholesale rather
4485
+ * than merged element-wise. Pass `unset` at a path to withdraw its
4486
+ * value and mark it blank.
4688
4487
  */
4689
4488
  reset: (nextDefaultValues?: DefaultValuesInput<Form>) => void;
4690
4489
  /**
@@ -4739,7 +4538,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4739
4538
  clear: {
4740
4539
  (): boolean;
4741
4540
  <Path extends FlatPath<Form> | ''>(path: Path): boolean;
4742
- <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form> | ''] ? unknown : never)): boolean;
4541
+ <const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form> | ''] ? unknown : SegmentPathRejection<JoinSegments<S>>)): boolean;
4743
4542
  };
4744
4543
  /**
4745
4544
  * Consolidated undo/redo namespace — `form.history.{undo, redo,
@@ -4776,20 +4575,19 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4776
4575
  */
4777
4576
  scrollToFirstError: (options?: ScrollIntoViewOptions) => boolean;
4778
4577
  /**
4779
- * Drive the form's `onInvalidSubmit` policy imperatively. The same
4780
- * focus/scroll behavior `handleSubmit` runs after a failed submit,
4781
- * but available standalone. Defaults to the policy configured via
4782
- * `useForm({ onInvalidSubmit })` (or `'focus-first-error'` when
4783
- * omitted). Pass an explicit policy to override for one call.
4578
+ * Run the form's own invalid-submit nudge imperatively: exactly what
4579
+ * `handleSubmit` does after a failed submit, but available standalone
4580
+ * and honoring this form's `useForm({ focusOnInvalidSubmit })` choice.
4784
4581
  *
4785
4582
  * Used by `useWizard` after navigating to the first failing form
4786
4583
  * during `wizard.handleSubmit`, so the failing form's own configured
4787
- * policy fires once its DOM is in view.
4584
+ * behavior fires once its DOM is in view.
4788
4585
  *
4789
- * No-op when no errored field is currently registered or when the
4790
- * resolved policy is `'none'`.
4586
+ * No-op when this form opted out, or when no errored field is
4587
+ * currently registered. Use `focusFirstError()` to focus regardless
4588
+ * of the configured choice.
4791
4589
  */
4792
- applyInvalidSubmitPolicy: (policy?: OnInvalidSubmitPolicy) => void;
4590
+ applyInvalidSubmitPolicy: () => void;
4793
4591
  /**
4794
4592
  * Programmatically mark fields as `touched` — the descriptive
4795
4593
  * "this field was visited" flag.
@@ -4805,9 +4603,9 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4805
4603
  * heuristic.** `touched` also flips on a bare focus → blur with no
4806
4604
  * edit, so the library-default gate deliberately ignores it and
4807
4605
  * reads `blurredAfterInteraction` instead — the stricter bit that
4808
- * only a blur *following* an edit sets. Reach for `touch()` when a
4809
- * custom `getDisplayState` reducer or your analytics reads
4810
- * `touched`; reach for {@link UseFormReturnType.interact} to make
4606
+ * only a blur *following* an edit sets. Reach for `touch()` when
4607
+ * your own analytics reads `touched`; reach for
4608
+ * {@link UseFormReturnType.interact} to make
4811
4609
  * seeded or imported values surface their errors.
4812
4610
  *
4813
4611
  * Pure flag write — does not mutate value, focused, blurred, or
@@ -4959,5 +4757,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4959
4757
  blankPaths: ComputedRef<BlankPathsView>;
4960
4758
  };
4961
4759
 
4962
- export { ROOT_PATH as Y, ROOT_PATH_KEY as Z, canonicalizePath as am, isPathPrefix as an, isUnset as ao, parseDottedPath as ap, unset as aq };
4963
- export type { RegisterDirective as $, AbstractSchema as A, NestedType as B, CoercionEntry as C, DefaultValuesInput as D, ErrorInput as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, OnInvalidSubmitPolicy as P, OnSubmit as Q, ParseOptions as R, PartialFlatPath as S, Path as T, UseFormConfiguration as U, PathKey as V, PendingValidationStatus as W, Primitive as X, ReactiveValidationStatus as _, UseFormReturnType as a, RegisterFlatPath as a0, RegisterOptions as a1, RegisterSelectModifier as a2, RegisterTextModifier as a3, RegisterTransform as a4, RegisterValue as a5, Segment as a6, SetValueCallback as a7, SetValuePayload as a8, SettledValidationStatus as a9, SlimPrimitiveKind as aa, SlimRuntimeOf as ab, SubmitHandler as ac, Unset as ad, ValidateOn as ae, ValidateOnConfig as af, ValidationError as ag, ValidationResponse as ah, ValidationResponseWithoutValue as ai, ValueOfUnion as aj, WriteMeta as ak, WriteShape as al, RegisterModelDynamicCustomDirective as ar, AcceptableDefaults as as, SchemaFactoryOptions as at, FieldRecord as au, ErrorCell as av, OriginalsRecord as aw, AttaformDomBinding as ax, TransformAbortHolder as ay, ArrayItem as b, ArrayPath as c, AttaformDefaults as d, CoercionRegistry as e, CoercionResult as f, CustomDirectiveRegisterAssignerFn as g, DeepPartial as h, DefaultValuesResponse as i, DefaultValuesShape as j, DisplayCtx as k, DisplayMachine as l, DisplayState as m, ErrorsProxyShape as n, FieldMetaPayload as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormMeta as v, GetDisplayState as w, HistoryPlugin as x, IsUnion as y, Json as z };
4760
+ export { ROOT_PATH as R, canonicalizePath as aj, isPathPrefix as ak, isUnset as al, parseDottedPath as am, unset as an, ROOT_PATH_KEY as z };
4761
+ export type { SchemaDefaultsResult as $, AbstractSchema as A, ReactiveValidationStatus as B, CustomDirectiveRegisterAssignerFn as C, DefaultValuesInput as D, ErrorInput as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, ParseOptions as P, RecordPath as Q, RecordValue as S, RegisterDirective as T, UseFormConfiguration as U, RegisterFlatPath as V, RegisterOptions as W, RegisterSelectModifier as X, RegisterTextModifier as Y, RegisterTransform as Z, RegisterValue as _, UseFormReturnType as a, SchemaParseErrorWithData as a0, SchemaParseErrorWithoutData as a1, SchemaParseResult as a2, SchemaParseSuccess as a3, Segment as a4, SetValueCallback as a5, SetValuePayload as a6, SettledValidationStatus as a7, SlimPrimitiveKind as a8, SubmitHandler as a9, Unset as aa, ValidateOn as ab, ValidateOnConfig as ac, ValidationError as ad, ValidationResponse as ae, ValidationResponseWithoutValue as af, ValueOfUnion as ag, WriteMeta as ah, WriteShape as ai, RegisterModelDynamicCustomDirective as ao, SchemaFactoryOptions as ap, AcceptableDefaults as aq, DisplayCtx as ar, GetDisplayState as as, DisplayMachine as at, FieldRecord as au, ErrorCell as av, OriginalsRecord as aw, AttaformDomBinding as ax, TransformAbortHolder as ay, ArrayItem as b, ArrayPath as c, DeepPartial as d, DefaultValuesShape as e, DisplayState as f, ErrorsProxyShape as g, FieldMetaPayload as h, FieldState as i, FieldStateMap as j, FieldStateMapEntry as k, FlatPath as l, FormErrorRecord as m, FormErrorsSurface as n, FormMeta as o, HistoryPlugin as p, IsUnion as q, Json as r, NestedType as s, OnSubmit as t, PartialFlatPath as u, Path as v, PathKey as w, PendingValidationStatus as x, Primitive as y };