attaform 0.21.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/chunks/dev-key-collision-warnings.cjs +1 -1
  2. package/dist/chunks/dev-key-collision-warnings.mjs +1 -1
  3. package/dist/chunks/devtools.cjs +1 -1
  4. package/dist/chunks/devtools.mjs +1 -1
  5. package/dist/chunks/fingerprint2.cjs +1 -1
  6. package/dist/chunks/fingerprint2.mjs +1 -1
  7. package/dist/chunks/indexeddb.cjs +1 -1
  8. package/dist/chunks/indexeddb.mjs +1 -1
  9. package/dist/chunks/local-storage.cjs +1 -1
  10. package/dist/chunks/local-storage.mjs +1 -1
  11. package/dist/chunks/multi-tab-sync.cjs +2 -2
  12. package/dist/chunks/multi-tab-sync.mjs +2 -2
  13. package/dist/chunks/session-storage.cjs +1 -1
  14. package/dist/chunks/session-storage.mjs +1 -1
  15. package/dist/chunks/wire-persistence.cjs +2 -2
  16. package/dist/chunks/wire-persistence.mjs +2 -2
  17. package/dist/index.cjs +3 -3
  18. package/dist/index.d.cts +15 -14
  19. package/dist/index.d.mts +15 -14
  20. package/dist/index.d.ts +15 -14
  21. package/dist/index.mjs +5 -5
  22. package/dist/nuxt.d.cts +1 -1
  23. package/dist/nuxt.d.mts +1 -1
  24. package/dist/nuxt.d.ts +1 -1
  25. package/dist/runtime/plugins/attaform.cjs +2 -2
  26. package/dist/runtime/plugins/attaform.mjs +2 -2
  27. package/dist/shared/{attaform.BzvOdiSI.cjs → attaform.BSkvn43g.cjs} +4 -4
  28. package/dist/shared/{attaform.BzvOdiSI.cjs.map → attaform.BSkvn43g.cjs.map} +1 -1
  29. package/dist/shared/{attaform.F8LMHHWV.d.cts → attaform.BWfliRIK.d.cts} +78 -2
  30. package/dist/shared/{attaform.r3PePkDR.mjs → attaform.Be8NZG9M.mjs} +9 -3
  31. package/dist/shared/attaform.Be8NZG9M.mjs.map +1 -0
  32. package/dist/shared/{attaform.CEf6wYfD.cjs → attaform.Bq5sX7TF.cjs} +2 -2
  33. package/dist/shared/{attaform.CEf6wYfD.cjs.map → attaform.Bq5sX7TF.cjs.map} +1 -1
  34. package/dist/shared/{attaform.7lzO9pdM.d.mts → attaform.Bv7dRDWK.d.ts} +78 -2
  35. package/dist/shared/{attaform._rsCZy2j.cjs → attaform.CICFZ1iS.cjs} +9 -3
  36. package/dist/shared/attaform.CICFZ1iS.cjs.map +1 -0
  37. package/dist/shared/{attaform.BUszFoKq.cjs → attaform.ClXwitZj.cjs} +366 -63
  38. package/dist/shared/attaform.ClXwitZj.cjs.map +1 -0
  39. package/dist/shared/{attaform.B1nyO4ec.d.cts → attaform.D0dWZsJt.d.cts} +269 -49
  40. package/dist/shared/{attaform.B1nyO4ec.d.mts → attaform.D0dWZsJt.d.mts} +269 -49
  41. package/dist/shared/{attaform.B1nyO4ec.d.ts → attaform.D0dWZsJt.d.ts} +269 -49
  42. package/dist/shared/{attaform.BA3vRDos.cjs → attaform.D32WwKk6.cjs} +214 -33
  43. package/dist/shared/attaform.D32WwKk6.cjs.map +1 -0
  44. package/dist/shared/{attaform.BnK_bfcb.mjs → attaform.DMEP_ENr.mjs} +4 -4
  45. package/dist/shared/{attaform.PnqML3xW.cjs.map → attaform.DMEP_ENr.mjs.map} +1 -1
  46. package/dist/shared/{attaform.DSqO6Db7.mjs → attaform.DR6RmxWZ.mjs} +367 -64
  47. package/dist/shared/attaform.DR6RmxWZ.mjs.map +1 -0
  48. package/dist/shared/{attaform.CkjTapyq.mjs → attaform.DozgVlCE.mjs} +4 -4
  49. package/dist/shared/{attaform.CkjTapyq.mjs.map → attaform.DozgVlCE.mjs.map} +1 -1
  50. package/dist/shared/{attaform.BK1RE2ha.d.ts → attaform.Duecg2NO.d.mts} +2 -2
  51. package/dist/shared/{attaform.BDIEq9qP.d.cts → attaform.FudOcHaa.d.cts} +2 -2
  52. package/dist/shared/{attaform.BQ6drorq.d.mts → attaform.MtrpT6Ki.d.ts} +2 -2
  53. package/dist/shared/{attaform.CRsXyy-Y.d.ts → attaform.NQ8mybyW.d.mts} +78 -2
  54. package/dist/shared/{attaform.PnqML3xW.cjs → attaform.S-pYLSo4.cjs} +4 -4
  55. package/dist/shared/{attaform.BnK_bfcb.mjs.map → attaform.S-pYLSo4.cjs.map} +1 -1
  56. package/dist/shared/{attaform.ezb5Nh2t.mjs → attaform.Y1ZGhM4k.mjs} +2 -2
  57. package/dist/shared/{attaform.ezb5Nh2t.mjs.map → attaform.Y1ZGhM4k.mjs.map} +1 -1
  58. package/dist/shared/{attaform.Y_Mgg0Yp.mjs → attaform.pmtahXKy.mjs} +214 -34
  59. package/dist/shared/attaform.pmtahXKy.mjs.map +1 -0
  60. package/dist/zod-v3.cjs +3 -3
  61. package/dist/zod-v3.d.cts +3 -3
  62. package/dist/zod-v3.d.mts +3 -3
  63. package/dist/zod-v3.d.ts +3 -3
  64. package/dist/zod-v3.mjs +3 -3
  65. package/dist/zod-v4.cjs +3 -3
  66. package/dist/zod-v4.d.cts +4 -4
  67. package/dist/zod-v4.d.mts +4 -4
  68. package/dist/zod-v4.d.ts +4 -4
  69. package/dist/zod-v4.mjs +3 -3
  70. package/dist/zod.cjs +5 -5
  71. package/dist/zod.cjs.map +1 -1
  72. package/dist/zod.d.cts +52 -10
  73. package/dist/zod.d.mts +52 -10
  74. package/dist/zod.d.ts +52 -10
  75. package/dist/zod.mjs +6 -6
  76. package/dist/zod.mjs.map +1 -1
  77. package/package.json +1 -1
  78. package/dist/shared/attaform.BA3vRDos.cjs.map +0 -1
  79. package/dist/shared/attaform.BUszFoKq.cjs.map +0 -1
  80. package/dist/shared/attaform.DSqO6Db7.mjs.map +0 -1
  81. package/dist/shared/attaform.Y_Mgg0Yp.mjs.map +0 -1
  82. package/dist/shared/attaform._rsCZy2j.cjs.map +0 -1
  83. package/dist/shared/attaform.r3PePkDR.mjs.map +0 -1
@@ -230,6 +230,21 @@ type KeyofUnion<T> = T extends unknown ? keyof T : never;
230
230
  * variants and the runtime returns a stable stub there.
231
231
  */
232
232
  type ValueOfUnion<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T[K] : undefined : never;
233
+ /**
234
+ * Value at key `K` across union members of `T`, dropping members that
235
+ * LACK `K` entirely (they contribute `never`, not `undefined`). The
236
+ * counterpart to `ValueOfUnion`: where that injects a SYNTHETIC
237
+ * `undefined` for absent-variant keys (so chained reads stay safe),
238
+ * this yields only the PRESENT value type.
239
+ *
240
+ * `form.fields` uses it at discriminated-union keys so a variant-only
241
+ * field types as node-optional `FieldState<X> | undefined` (the node is
242
+ * absent when its variant isn't active) rather than value-optional
243
+ * `FieldState<X | undefined>` (which would falsely promise a readable
244
+ * node). A genuine `undefined` from an OPTIONAL declaration survives —
245
+ * only the synthetic absent-variant `undefined` is stripped.
246
+ */
247
+ type PresentValueOfUnion<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T[K] : never : never;
233
248
  /**
234
249
  * Apply the discriminated-union "lift" to a value shape (i.e., a
235
250
  * shape carrying actual values, not metadata leaves like
@@ -944,6 +959,29 @@ type AbstractSchema<Form, GetValueFormType> = {
944
959
  * `optional(z.tuple([...]))` reports its tuple length.
945
960
  */
946
961
  arrayShapeAtPath(path: Path): number | null | undefined;
962
+ /**
963
+ * Whether the schema at `path` is a FIXED object: a closed set of
964
+ * declared keys (`z.object`), as opposed to an open or union container
965
+ * (array / record / map / set / union / discriminated union) whose
966
+ * element schema matches any segment.
967
+ *
968
+ * The surface proxies (`form.fields` / `form.errors`) use this to
969
+ * resolve a collision: a fixed object's declared keys are known ahead
970
+ * of any data, so a key the schema owns descends to a real terminal
971
+ * even when the live value hasn't been populated yet (a declared-but-
972
+ * absent `optional` field stays registrable). An open container can't
973
+ * make that promise — its element schema accepts ANY segment, so the
974
+ * proxy must fall back to the keys the data currently holds and read
975
+ * a genuinely-absent key (out-of-bounds index, missing record key,
976
+ * inactive variant key) as `undefined` rather than a phantom node.
977
+ *
978
+ * The empty path (the root form) is always a fixed object. Wrappers
979
+ * (optional / nullable / default / readonly / catch / pipe / lazy)
980
+ * are peeled before the kind check, so `z.object({...}).optional()`
981
+ * still reports `true`. A path the schema doesn't declare reports
982
+ * `false`.
983
+ */
984
+ isFixedObjectAtPath(path: Path): boolean;
947
985
  /**
948
986
  * Return every sub-schema that could resolve at the given structured
949
987
  * path. Multiple results are only expected for discriminated / union
@@ -2270,6 +2308,11 @@ type DisplayMachine = {
2270
2308
  * not `field.validating`, is the timing anchor: the elapsed wait is
2271
2309
  * `now - validatingSince`. Pinned to the start of the streak, so
2272
2310
  * overlapping sub-runs do not reset it.
2311
+ * - `transformingSince` — the same anchor for an in-flight async
2312
+ * `register` transform, or `null` when none is running. Folds into the
2313
+ * one in-flight clock the reducer already runs for validation, so a
2314
+ * deferred transform rides the anti-flash spinner timing identically.
2315
+ * `null` for a sync-only chain, which never defers.
2273
2316
  * - `now` — the engine's clock, injected so the reducer stays pure and
2274
2317
  * deterministic (and frozen to `0` under SSR, where there is no clock).
2275
2318
  */
@@ -2277,6 +2320,7 @@ type DisplayCtx = {
2277
2320
  readonly field: Omit<FieldState, FieldStateDerivedKey>;
2278
2321
  readonly formMeta: Omit<FormMeta, FieldStateDerivedKey>;
2279
2322
  readonly validatingSince: number | null;
2323
+ readonly transformingSince: number | null;
2280
2324
  readonly now: number;
2281
2325
  };
2282
2326
  /**
@@ -2392,7 +2436,7 @@ type MetaTrackerValue = {
2392
2436
  };
2393
2437
  type RegisterFlatPath<Form, Key extends keyof Form = keyof Form> = FlatPathBuilder<Form, 'register', Key>;
2394
2438
  /**
2395
- * Sync transformation applied to a field's value as user input flows
2439
+ * A transformation applied to a field's value as user input flows
2396
2440
  * from DOM through the directive's assigner. Composes left-to-right
2397
2441
  * via the `transforms: [...]` array on `register()`.
2398
2442
  *
@@ -2412,19 +2456,33 @@ type RegisterFlatPath<Form, Key extends keyof Form = keyof Form> = FlatPathBuild
2412
2456
  * doesn't accept gets rejected at write time with a standard
2413
2457
  * diagnostic.
2414
2458
  *
2415
- * Transforms must be sync. A `Promise` return is treated as a
2416
- * pipeline failure: the write is aborted and a console.error is
2417
- * logged. Use async field validation for canonicalize-before-write
2418
- * patterns; use sync transforms for fire-and-forget side effects
2419
- * (`void doIt(value); return value`).
2420
- *
2421
- * Throws are caught and aborted: attaform wraps each transform call in
2422
- * try/catch so a buggy or defensive-throw transform doesn't crash
2423
- * the host app. On throw the pipeline aborts (subsequent transforms
2424
- * don't run), nothing is written to form state, and the assigner
2425
- * returns `false`.
2459
+ * Transforms may be sync or async. The chain stays fully synchronous
2460
+ * the value reaches form state in the same tick until a transform
2461
+ * returns a thenable; from there the write defers, the field reads
2462
+ * `busy` / `transforming` while the chain settles, and the resolved
2463
+ * value commits to canonical state once it lands. Rapid edits discard
2464
+ * all but the latest (latest-request-wins), and a rejection surfaces on
2465
+ * `field.transformError` rather than throwing or logging:
2466
+ *
2467
+ * ```ts
2468
+ * export const normalize: RegisterTransform = async (v, ctx) => {
2469
+ * const res = await fetch(`/normalize?q=${v}`, { signal: ctx?.signal })
2470
+ * return res.text()
2471
+ * }
2472
+ * ```
2473
+ *
2474
+ * `ctx.signal` is an `AbortSignal` aborted when the run is superseded by
2475
+ * a newer edit, or torn down by `reset()` / unmount — thread it into
2476
+ * cancellable I/O so a stale request is dropped. A sync chain never
2477
+ * touches it and allocates no controller.
2478
+ *
2479
+ * A synchronous throw is caught and aborts the pipeline: subsequent
2480
+ * transforms don't run, nothing is written to form state, and the
2481
+ * assigner returns `false` — so a buggy or defensive-throw transform
2482
+ * never crashes the host app. (An async rejection is the
2483
+ * `transformError` channel above, not a throw into the host app.)
2426
2484
  */
2427
- type RegisterTransform = (value: unknown) => unknown;
2485
+ type RegisterTransform = (value: unknown, ctx?: TransformContext) => unknown;
2428
2486
  /**
2429
2487
  * Runtime type for a slim primitive kind. Used to narrow the
2430
2488
  * `transform` parameter and return value on a `CoercionEntry` so
@@ -2549,9 +2607,11 @@ type RegisterOptions = {
2549
2607
  * form.setValue('email', slugify(lowercase(rawValue)))
2550
2608
  * ```
2551
2609
  *
2552
- * Transforms must be sync. Throws and Promise returns abort the
2553
- * write and log to `console.error` (see `RegisterTransform` for
2554
- * the failure-mode contract).
2610
+ * Transforms may be sync or async: the chain stays synchronous until
2611
+ * one returns a thenable, then the write defers and commits the
2612
+ * resolved value (the field reads `busy` meanwhile). A sync throw
2613
+ * aborts the write; an async rejection lands on `field.transformError`
2614
+ * (see `RegisterTransform` for the full contract).
2555
2615
  *
2556
2616
  * For patterns that need to inspect the `RegisterValue` itself
2557
2617
  * (rejection-with-side-effect, redirection to other fields, custom
@@ -2869,6 +2929,64 @@ type RegisterValue<Value = unknown> = Readonly<{
2869
2929
  */
2870
2930
  ariaDisplayState?: Readonly<Ref<DisplayState>>;
2871
2931
  }>;
2932
+ /**
2933
+ * Internal extension of `RegisterValue` that includes directive-private
2934
+ * coordination state. Imported by the directive runtime; not part of
2935
+ * the public surface.
2936
+ *
2937
+ * `lastTypedForm` is the user's most recently typed string form for a
2938
+ * numeric field while mid-typing, or `null` once the field has been
2939
+ * blurred / cleared. The directive populates it on every committable
2940
+ * input event and clears it on the change (blur) event so:
2941
+ *
2942
+ * - Mid-typing: `displayValue` returns the typed form (e.g.
2943
+ * `'1e2'`) when it parses back to current storage. Vue's
2944
+ * `:value` patch then targets the typed form, which already
2945
+ * equals the DOM — idempotent, no cursor reset.
2946
+ * - On blur: `displayValue` falls back to `String(storage)`
2947
+ * (`'100'`), Vue patches the DOM to match. The user sees
2948
+ * exactly what's stored.
2949
+ *
2950
+ * Why a separate field: JavaScript's Number carries no representation
2951
+ * info — `1e2 === 100`, so `String(parseFloat('1e2'))` yields `'100'`.
2952
+ * Tracking the typed form lets us avoid Vue's mid-typing DOM yank
2953
+ * without lying about storage. Only meaningful for `.number` text
2954
+ * inputs and `<input type="number">`; other bindings ignore it.
2955
+ *
2956
+ * @internal
2957
+ */
2958
+ /**
2959
+ * Mutable holder for an async-transform run's `AbortController`, shared
2960
+ * between the directive — which lazily creates the controller the first
2961
+ * time a transform reaches for `ctx.signal` — and the store, which
2962
+ * aborts it when the run is superseded, cancelled, or reset.
2963
+ * `controller` stays `null` until `ctx.signal` is actually touched, so a
2964
+ * purely-sync chain never allocates one. `aborted` latches `true` the
2965
+ * moment the store tears the run down, so a signal accessed AFTER
2966
+ * teardown still resolves to an already-aborted signal rather than a
2967
+ * live one.
2968
+ */
2969
+ type TransformAbortHolder = {
2970
+ controller: AbortController | null;
2971
+ aborted: boolean;
2972
+ };
2973
+ /**
2974
+ * The second argument handed to every transform in a `transforms: [...]`
2975
+ * chain. `signal` is an `AbortSignal` that aborts when the run is
2976
+ * superseded by a newer input, or torn down by a reset / cancel — so a
2977
+ * transform doing cancellable I/O (a `fetch`, a worker round-trip) can
2978
+ * pass `ctx.signal` through and bail the moment its result is no longer
2979
+ * wanted.
2980
+ *
2981
+ * The signal is lazy: the backing `AbortController` is allocated only on
2982
+ * first access, so a purely-synchronous chain that never reaches for
2983
+ * `ctx.signal` allocates nothing. It is meaningful for async transforms;
2984
+ * a sync chain has no in-flight I/O to cancel, so its `signal` simply
2985
+ * never aborts.
2986
+ */
2987
+ type TransformContext = {
2988
+ readonly signal: AbortSignal;
2989
+ };
2872
2990
  /**
2873
2991
  * Custom assigner installed on an element via the directive's
2874
2992
  * `[assignKey]` slot OR an `@update:registerValue` listener. Called
@@ -2928,6 +3046,15 @@ type CustomRegisterDirective<T, Modifiers extends string = string> = ObjectDirec
2928
3046
  * from the user's input.
2929
3047
  */
2930
3048
  _lastAppliedModel?: unknown;
3049
+ /**
3050
+ * Variant-specific "repaint the DOM from current storage" closure,
3051
+ * stashed by each input directive's `created` hook (it mirrors that
3052
+ * variant's post-write force-sync block). The deferred async-transform
3053
+ * orchestrator calls it once the resolved value has committed, so a
3054
+ * bare `<input v-register>` with no other reactive reader still paints
3055
+ * the normalized result without depending on a parent re-render.
3056
+ */
3057
+ _syncFromStorage?: () => void;
2931
3058
  [S: symbol]: CustomDirectiveRegisterAssignerFn;
2932
3059
  }, RegisterValue | undefined, Modifiers, string>;
2933
3060
  /**
@@ -3263,8 +3390,38 @@ type FieldState<Value = unknown> = {
3263
3390
  * is in flight (`errors.length === 0 && !validating`). Confidence
3264
3391
  * that "we've checked, and we have no problems right now." Use for
3265
3392
  * green-checkmark / `aria-invalid` UX.
3393
+ *
3394
+ * Validation-only: an in-flight async transform does NOT clamp
3395
+ * `valid` to `false`. `valid` is the verdict on the last committed
3396
+ * value; `busy` is the union "work in flight" signal.
3266
3397
  */
3267
3398
  readonly valid: boolean;
3399
+ /**
3400
+ * `true` while an async `register` transform is in flight at this
3401
+ * path: a transform returned a thenable and the resolved value has
3402
+ * not yet committed to form state. Always `false` for a sync-only
3403
+ * chain, which reaches form state in the same tick with no deferral.
3404
+ * Containers roll it up as a disjunction (any descendant transforming).
3405
+ */
3406
+ readonly transforming: boolean;
3407
+ /**
3408
+ * `transforming || validating` — the union "work is in flight at this
3409
+ * path" signal. Drives `aria-busy` through `displayState` on a
3410
+ * revealed field, and is the surface to bind for a busy indicator on a
3411
+ * field not yet revealed (where `displayState` stays idle by the
3412
+ * reveal gate). Containers roll it up as a disjunction.
3413
+ */
3414
+ readonly busy: boolean;
3415
+ /**
3416
+ * The `Error` from the most recent async transform that rejected at
3417
+ * this path, else `null`. A per-field normalization-failure channel
3418
+ * separate from validation `errors`: a transform that rejects (a
3419
+ * failed fetch, a parse error) surfaces here instead of crashing the
3420
+ * host app or logging. Cleared when a fresh transform starts or a
3421
+ * write supersedes it. Leaf-only — containers do not roll it up (it is
3422
+ * always `null` at a container path).
3423
+ */
3424
+ readonly transformError: Error | null;
3268
3425
  /**
3269
3426
  * The single display-state verdict at this path: `'idle'`,
3270
3427
  * `'pending'`, `'error'`, or `'success'`. The source of truth the
@@ -3533,15 +3690,33 @@ interface LeafSchemeFor<T> {
3533
3690
  */
3534
3691
  type LeafWalker<T, Kind extends keyof LeafSchemeFor<unknown>, StripOptional extends boolean = true> = [T] extends [string | number | boolean | bigint | symbol | null | undefined | Date | File] ? LeafSchemeFor<T>[Kind] : [T] extends [ReadonlyArray<infer U>] ? {
3535
3692
  readonly [K: number]: LeafWalker<U, Kind, StripOptional>;
3536
- } & ContainerSelfErrorsSlot<T, Kind> : [T] extends [object] ? [IsUnion<T>] extends [true] ? StripOptional extends true ? {
3537
- readonly [K in KeyofUnion<T>]-?: LeafWalker<ValueOfUnion<T, K>, Kind, StripOptional>;
3693
+ } & ContainerSelfErrorsSlot<T, Kind> : [T] extends [object] ? string extends keyof T ? {
3694
+ readonly [K in keyof T]: LeafWalker<T[K], Kind, StripOptional>;
3695
+ } & ContainerSelfErrorsSlot<T, Kind> : [IsUnion<T>] extends [true] ? StripOptional extends true ? {
3696
+ readonly [K in KeyofUnion<T>]-?: DiscriminatedLeaf<T, K, Kind, StripOptional>;
3538
3697
  } & ContainerSelfErrorsSlot<T, Kind> : {
3539
- readonly [K in KeyofUnion<T>]: LeafWalker<ValueOfUnion<T, K>, Kind, StripOptional>;
3698
+ readonly [K in KeyofUnion<T>]: DiscriminatedLeaf<T, K, Kind, StripOptional>;
3540
3699
  } & ContainerSelfErrorsSlot<T, Kind> : StripOptional extends true ? {
3541
3700
  readonly [K in keyof T]-?: LeafWalker<T[K], Kind, StripOptional>;
3542
3701
  } & ContainerSelfErrorsSlot<T, Kind> : {
3543
3702
  readonly [K in keyof T]: LeafWalker<T[K], Kind, StripOptional>;
3544
3703
  } & ContainerSelfErrorsSlot<T, Kind> : LeafSchemeFor<T>[Kind];
3704
+ /**
3705
+ * One key of a discriminated-union container in `LeafWalker`. A key
3706
+ * present (and required) in EVERY variant is universal — its node is
3707
+ * always reachable. A variant-only or optional-in-some key is a dynamic
3708
+ * hop: its node is `undefined` when its variant isn't active, so it
3709
+ * carries node-optionality (`LeafWalker<…> | undefined`), NOT value-
3710
+ * optionality (`LeafWalker<… | undefined>`). `PresentValueOfUnion`
3711
+ * strips the synthetic absent-variant `undefined` so the present node
3712
+ * resolves to the precise value type; a genuine `undefined` from an
3713
+ * `optional` declaration survives.
3714
+ *
3715
+ * Universality is `[T] extends [Record<K, unknown>]` — true iff `T`
3716
+ * (the whole union) satisfies "has K, required", which holds only when
3717
+ * every variant declares K as a required property.
3718
+ */
3719
+ type DiscriminatedLeaf<T, K extends PropertyKey, Kind extends keyof LeafSchemeFor<unknown>, StripOptional extends boolean> = [T] extends [Record<K, unknown>] ? LeafWalker<PresentValueOfUnion<T, K>, Kind, StripOptional> : LeafWalker<PresentValueOfUnion<T, K>, Kind, StripOptional> | undefined;
3545
3720
  /**
3546
3721
  * Intersection augmenting every container in the `form.errors` walker
3547
3722
  * with a `''` sentinel slot — the per-container home for cross-field
@@ -3556,6 +3731,22 @@ type ContainerSelfErrorsSlot<T, Kind> = Kind extends 'errors' ? '' extends keyof
3556
3731
  readonly ['']: readonly ValidationError[];
3557
3732
  } : unknown;
3558
3733
  type FieldStateMapEntry<T> = LeafWalker<T, 'field'>;
3734
+ /**
3735
+ * Result of the `form.fields(path)` string call-form. A path the schema
3736
+ * declares resolves to its `FieldState` — a leaf's value type, or a
3737
+ * container's rolled-up aggregate (every FieldState property exists
3738
+ * regardless of the value type). A path the schema lacks resolves to
3739
+ * `undefined`, because a typo is not a field and the runtime hands back
3740
+ * `undefined` rather than a phantom stub. A non-literal `string` could
3741
+ * be either, so it widens to `FieldState<unknown> | undefined`.
3742
+ *
3743
+ * Named (rather than inlined into the `FieldStateMap` call signature) so
3744
+ * the conditional is one cached type — keeps two structurally-identical
3745
+ * `FieldStateMap` instantiations (e.g. the unified `attaform/zod` return
3746
+ * and `UseFormReturnV4`) relatable instead of collapsing to a nominal
3747
+ * "two different types with this name" mismatch.
3748
+ */
3749
+ type FieldCallResult<Form, P extends string> = [P] extends [FlatPath<Form>] ? FieldState<NestedType<Form, P>> : string extends P ? FieldState<unknown> | undefined : undefined;
3559
3750
  /**
3560
3751
  * Type of `form.fields` — leaf-aware drillable callable Proxy. At
3561
3752
  * a leaf path the proxy resolves to a `FieldState<Value>`; at
@@ -3576,18 +3767,14 @@ type FieldStateMapEntry<T> = LeafWalker<T, 'field'>;
3576
3767
  * string as a single key. Use chained dot/bracket or the callable
3577
3768
  * form.
3578
3769
  */
3579
- type FieldStateMap<Form extends GenericForm> = ([IsUnion<Form>] extends [true] ? {
3580
- readonly [K in KeyofUnion<Form>]-?: FieldStateMapEntry<ValueOfUnion<Form, K>>;
3581
- } : {
3582
- readonly [K in keyof Form]-?: FieldStateMapEntry<Form[K]>;
3583
- }) & {
3770
+ type FieldStateMap<Form extends GenericForm> = LeafWalker<Form, 'field'> & {
3584
3771
  /**
3585
- * Dotted-string fallback for dynamic paths. Returns
3586
- * `FieldState<unknown>` the runtime always lands on a FieldState
3587
- * terminal at any depth (leaf or container). Cast to
3588
- * `FieldState<TypedValue>` when the caller knows the leaf type.
3772
+ * String-path form (dynamic / programmatic). See {@link FieldCallResult}:
3773
+ * a path the schema declares resolves to its precise `FieldState`, a
3774
+ * path it lacks to `undefined`, and a non-literal `string` to
3775
+ * `FieldState<unknown> | undefined`.
3589
3776
  */
3590
- (path: string): FieldState<unknown>;
3777
+ <P extends string>(path: P): FieldCallResult<Form, P>;
3591
3778
  /**
3592
3779
  * Tuple-segment form. Returns the typed `FieldStateMapEntry` for
3593
3780
  * the resolved path when the tuple resolves to a known path.
@@ -3598,10 +3785,10 @@ type FieldStateMap<Form extends GenericForm> = ([IsUnion<Form>] extends [true] ?
3598
3785
  /**
3599
3786
  * Dynamic-array fallback for callers passing `Path`-typed (runtime)
3600
3787
  * segment arrays — e.g. forwarding `RegisterValue.segments` to
3601
- * resolve a field view. Returns `FieldState<unknown>`; cast when
3602
- * the value type is known.
3788
+ * resolve a field view. The path may not resolve, so the result
3789
+ * widens with `| undefined`; cast when the value type is known.
3603
3790
  */
3604
- (segments: ReadonlyArray<string | number>): FieldState<unknown>;
3791
+ (segments: ReadonlyArray<string | number>): FieldState<unknown> | undefined;
3605
3792
  /**
3606
3793
  * No-arg call returns the root FieldState — same as
3607
3794
  * `form.fields([])`. Aggregates over the whole form (one
@@ -3848,15 +4035,23 @@ type FormMeta<F = unknown> = FieldState<F> & {
3848
4035
  */
3849
4036
  readonly departAttempts: number;
3850
4037
  /**
3851
- * The error thrown or rejected by the most recent submit callback
3852
- * (or its `onError` handler). Cleared to `null` at the start of
3853
- * each new submission attempt; stays `null` on success.
4038
+ * The error thrown or rejected by the most recent submit callback (or
4039
+ * its `onError` handler), coerced to a real `Error` (a non-`Error`
4040
+ * throw keeps its origin on `.cause`). Cleared to `null` at the start
4041
+ * of each new submission attempt; stays `null` on success.
4042
+ *
4043
+ * The submit handler does NOT re-throw — its returned promise always
4044
+ * resolves, so binding it to `@submit.prevent` never manufactures a
4045
+ * `window` unhandledrejection. This is the single channel for "the
4046
+ * submit failed", read the same way in templates and after an
4047
+ * imperative `await submit()`. Like `hydrateError`, it stays distinct
4048
+ * from the curated user-error store: render it where you choose:
3854
4049
  *
3855
- * The submit handler still throws normally — this is the reactive
3856
- * mirror for templates. Imperative callers can use
3857
- * `try { await onSubmit() }` instead.
4050
+ * ```vue
4051
+ * <p v-if="form.meta.submitError">{{ form.meta.submitError.message }}</p>
4052
+ * ```
3858
4053
  */
3859
- readonly submitError: unknown;
4054
+ readonly submitError: Error | null;
3860
4055
  /**
3861
4056
  * Scalar mirror of `meta.errors.length`. Read it from templates and
3862
4057
  * `watch()` without indexing the underlying array.
@@ -3926,7 +4121,7 @@ type FormMeta<F = unknown> = FieldState<F> & {
3926
4121
  *
3927
4122
  * - `GetValueFormType` — the **output / parsed shape**
3928
4123
  * (`z.output<Schema>`). Used by `handleSubmit`'s `onSubmit`
3929
- * callback and by `form.process()`'s success payload. This is the
4124
+ * callback and by `form.parse()`'s success payload. This is the
3930
4125
  * shape after refinements have fired and transforms have run.
3931
4126
  *
3932
4127
  * - `ReadForm` — the **read / storage shape**. Used by `values`,
@@ -4023,7 +4218,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4023
4218
  * `z.input<Schema>` shape — `.transform()`s have NOT run, so for
4024
4219
  * a schema like `z.string().transform(v => v.length > 10)` the
4025
4220
  * value reads as `string`, not `boolean`. Use `handleSubmit` or
4026
- * `form.process()` when you need the post-transform output shape.
4221
+ * `form.parse()` when you need the post-transform output shape.
4027
4222
  */
4028
4223
  values: ValuesSurface<WriteShape<ReadForm>>;
4029
4224
  /**
@@ -4150,6 +4345,26 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4150
4345
  * `true` while the promise is in flight.
4151
4346
  */
4152
4347
  validateAsync: (path?: FlatPath<Form>) => Promise<ValidationResponseWithoutValue<Form>>;
4348
+ /**
4349
+ * Resolve once every in-flight async `register({ transforms })` run
4350
+ * has settled — globally, or (with `path`) only at-or-under that path.
4351
+ * Resolve-never-reject: a transform that throws still settles the
4352
+ * field (its failure lands on `field.transformError`), so the returned
4353
+ * promise always resolves.
4354
+ *
4355
+ * `handleSubmit` awaits this internally before parsing, so a submit
4356
+ * fired the instant after an async transform still validates the
4357
+ * resolved value. Reach for it directly when you need the same
4358
+ * guarantee outside submit — e.g. before reading `form.values` in an
4359
+ * imperative flow or a test:
4360
+ *
4361
+ * ```ts
4362
+ * input.value = ' a@b.com '
4363
+ * await form.settleTransforms('email')
4364
+ * // form.values.email is now the normalized value
4365
+ * ```
4366
+ */
4367
+ settleTransforms: (path?: FlatPath<Form>) => Promise<void>;
4153
4368
  /**
4154
4369
  * Imperative one-shot parse. Same pipeline as `validateAsync` —
4155
4370
  * runs refinements, applies `.transform()`s, composes blank-required
@@ -4159,11 +4374,11 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4159
4374
  * preprocess normalization applied but `.transform()` deferred. For
4160
4375
  * schemas where the input type differs from the output type (e.g.,
4161
4376
  * `z.string().transform(v => v.length > 10)`), `form.values.X` is
4162
- * the input shape and `(await form.process()).data?.X` is the
4377
+ * the input shape and `(await form.parse()).data?.X` is the
4163
4378
  * output shape.
4164
4379
  *
4165
4380
  * ```ts
4166
- * const result = await form.process()
4381
+ * const result = await form.parse()
4167
4382
  * if (result.success) {
4168
4383
  * // result.data matches z.output<typeof schema>
4169
4384
  * } else {
@@ -4171,11 +4386,16 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4171
4386
  * }
4172
4387
  * ```
4173
4388
  *
4174
- * Pass a path to parse a subtree only. Async because refinements may
4175
- * be async. `meta.validating` flips `true` while the promise is in
4176
- * flight (shared with validateAsync).
4389
+ * Always async, and there is no synchronous variant by design: a
4390
+ * schema can carry async refinements or transforms, so a sync parse
4391
+ * would silently miss them the moment one is added. One always-
4392
+ * awaited `parse` closes that category of bug entirely. The returned
4393
+ * promise never rejects (a thrown adapter lands as a `success: false`
4394
+ * response). Pass a path to parse a subtree only. `meta.validating`
4395
+ * flips `true` while the promise is in flight (shared with
4396
+ * validateAsync).
4177
4397
  */
4178
- process: (path?: FlatPath<Form>) => Promise<ValidationResponse<GetValueFormType>>;
4398
+ parse: (path?: FlatPath<Form>) => Promise<ValidationResponse<GetValueFormType>>;
4179
4399
  /**
4180
4400
  * Bind a path to a native input via `v-register`. Returns a
4181
4401
  * `RegisterValue` carrying the live ref and event handlers the
@@ -4696,5 +4916,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
4696
4916
  blankPaths: ComputedRef<BlankPathsView>;
4697
4917
  };
4698
4918
 
4699
- export { ROOT_PATH as a0, ROOT_PATH_KEY as a1, canonicalizePath as au, isPathPrefix as av, isUnset as aw, parseDottedPath as ax, unset as ay };
4700
- export type { Primitive as $, AbstractSchema as A, HistoryConfig as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload as F, GenericForm as G, HandleSubmit as H, IsTuple as I, IsUnion as J, JoinSegments as K, KeyofUnion as L, LiftedValueShape as M, MetaTrackerValue as N, NestedReadType as O, NestedType as P, OnError as Q, OnInvalidSubmitPolicy as R, OnSubmit as S, PartialFlatPath as T, Path as U, PathKey as V, PendingValidationStatus as W, PersistConfig as X, PersistConfigOptions as Y, PersistIncludeMode as Z, PersistOptInRegistry as _, ApiErrorDetails as a, ReactiveValidationStatus as a2, RegisterDirective as a3, RegisterFlatPath as a4, RegisterModelDynamicCustomDirective as a5, RegisterOptions as a6, RegisterSelectModifier as a7, RegisterTextModifier as a8, RegisterTransform as a9, RegisterValue as aa, SchemaFactoryOptions as ab, Segment as ac, SetValueCallback as ad, SetValuePayload as ae, SettledValidationStatus as af, SlimPrimitiveKind as ag, SlimRuntimeOf as ah, SubmitHandler as ai, Unset as aj, UseFormConfiguration as ak, UseFormReturnType as al, ValidateOn as am, ValidateOnConfig as an, ValidationError as ao, ValidationResponse as ap, ValidationResponseWithoutValue as aq, ValueOfUnion as ar, WriteMeta as as, WriteShape as at, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayCtx as m, DisplayMachine as n, DisplayState as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormKey as v, FormMeta as w, FormStorage as x, FormStorageKind as y, GetDisplayState as z };
4919
+ export { ROOT_PATH as a0, ROOT_PATH_KEY as a1, canonicalizePath as av, isPathPrefix as aw, isUnset as ax, parseDottedPath as ay, unset as az };
4920
+ export type { Primitive as $, AbstractSchema as A, HistoryConfig as B, CoercionEntry as C, DeepPartial as D, ErrorsProxyShape as E, FieldMetaPayload as F, GenericForm as G, HandleSubmit as H, IsTuple as I, IsUnion as J, JoinSegments as K, KeyofUnion as L, LiftedValueShape as M, MetaTrackerValue as N, NestedReadType as O, NestedType as P, OnError as Q, OnInvalidSubmitPolicy as R, OnSubmit as S, PartialFlatPath as T, Path as U, PathKey as V, PendingValidationStatus as W, PersistConfig as X, PersistConfigOptions as Y, PersistIncludeMode as Z, PersistOptInRegistry as _, ApiErrorDetails as a, ReactiveValidationStatus as a2, RegisterDirective as a3, RegisterFlatPath as a4, RegisterModelDynamicCustomDirective as a5, RegisterOptions as a6, RegisterSelectModifier as a7, RegisterTextModifier as a8, RegisterTransform as a9, RegisterValue as aa, SchemaFactoryOptions as ab, Segment as ac, SetValueCallback as ad, SetValuePayload as ae, SettledValidationStatus as af, SlimPrimitiveKind as ag, SlimRuntimeOf as ah, SubmitHandler as ai, TransformAbortHolder as aj, Unset as ak, UseFormConfiguration as al, UseFormReturnType as am, ValidateOn as an, ValidateOnConfig as ao, ValidationError as ap, ValidationResponse as aq, ValidationResponseWithoutValue as ar, ValueOfUnion as as, WriteMeta as at, WriteShape as au, ApiErrorEntry as b, ApiErrorEnvelope as c, ArrayItem as d, ArrayPath as e, AttaformDefaults as f, CoercionRegistry as g, CoercionResult as h, CustomDirectiveRegisterAssignerFn as i, DefaultValuesInput as j, DefaultValuesResponse as k, DefaultValuesShape as l, DisplayCtx as m, DisplayMachine as n, DisplayState as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormKey as v, FormMeta as w, FormStorage as x, FormStorageKind as y, GetDisplayState as z };