attaform 0.17.2 → 0.18.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.
- package/README.md +77 -36
- package/dist/chunks/devtools.cjs +10 -37
- package/dist/chunks/devtools.cjs.map +1 -1
- package/dist/chunks/devtools.mjs +10 -37
- package/dist/chunks/devtools.mjs.map +1 -1
- package/dist/chunks/indexeddb.cjs +4 -4
- package/dist/chunks/indexeddb.cjs.map +1 -1
- package/dist/chunks/indexeddb.mjs +1 -1
- package/dist/chunks/local-storage.cjs +2 -2
- package/dist/chunks/local-storage.cjs.map +1 -1
- package/dist/chunks/local-storage.mjs +1 -1
- package/dist/chunks/session-storage.cjs +2 -2
- package/dist/chunks/session-storage.cjs.map +1 -1
- package/dist/chunks/session-storage.mjs +1 -1
- package/dist/index.cjs +42 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -196
- package/dist/index.d.mts +159 -196
- package/dist/index.d.ts +159 -196
- package/dist/index.mjs +5 -7
- package/dist/index.mjs.map +1 -1
- package/dist/nuxt.cjs +31 -40
- package/dist/nuxt.cjs.map +1 -1
- package/dist/nuxt.d.cts +8 -1
- package/dist/nuxt.d.mts +8 -1
- package/dist/nuxt.d.ts +8 -1
- package/dist/nuxt.mjs +32 -41
- package/dist/nuxt.mjs.map +1 -1
- package/dist/runtime/components/AttaformDevtoolsPanel.d.vue.ts +7 -0
- package/dist/runtime/components/AttaformDevtoolsPanel.vue +453 -0
- package/dist/runtime/components/AttaformDevtoolsPanel.vue.d.ts +7 -0
- package/dist/runtime/components/DevtoolsValueTree.d.vue.ts +37 -0
- package/dist/runtime/components/DevtoolsValueTree.vue +192 -0
- package/dist/runtime/components/DevtoolsValueTree.vue.d.ts +37 -0
- package/dist/runtime/plugins/attaform.cjs +17 -6
- package/dist/runtime/plugins/attaform.cjs.map +1 -1
- package/dist/runtime/plugins/attaform.mjs +15 -4
- package/dist/runtime/plugins/attaform.mjs.map +1 -1
- package/dist/shared/attaform.5UhpSVFI.cjs +63 -0
- package/dist/shared/attaform.5UhpSVFI.cjs.map +1 -0
- package/dist/shared/attaform.BDdFdjeX.mjs +57 -0
- package/dist/shared/attaform.BDdFdjeX.mjs.map +1 -0
- package/dist/shared/attaform.Bgu9l6OG.d.cts +1651 -0
- package/dist/shared/attaform.BmDBu4ql.d.ts +1651 -0
- package/dist/shared/{attaform.Dee2rU1P.cjs → attaform.BqK_L4gK.cjs} +310 -24
- package/dist/shared/attaform.BqK_L4gK.cjs.map +1 -0
- package/dist/shared/{attaform.C_5aB6EQ.d.ts → attaform.BsMdl-35.d.cts} +754 -146
- package/dist/shared/{attaform.C_5aB6EQ.d.mts → attaform.BsMdl-35.d.mts} +754 -146
- package/dist/shared/{attaform.C_5aB6EQ.d.cts → attaform.BsMdl-35.d.ts} +754 -146
- package/dist/shared/attaform.Bubm_slq.cjs.map +1 -1
- package/dist/shared/{attaform.C6lbmMUe.d.ts → attaform.C3x1hKJC.d.mts} +4 -4
- package/dist/shared/{attaform.CuE-bS1C.d.mts → attaform.CWs1Z3p7.d.ts} +57 -23
- package/dist/shared/attaform.CXpzmj38.mjs.map +1 -1
- package/dist/shared/{attaform.C5MH4lNh.d.mts → attaform.CjmJpfLH.d.ts} +4 -4
- package/dist/shared/{attaform.Drt6fivF.mjs → attaform.CtNUB9nf.mjs} +74 -76
- package/dist/shared/attaform.CtNUB9nf.mjs.map +1 -0
- package/dist/shared/{attaform.C0iFnTN0.d.ts → attaform.D-hDvb98.d.cts} +57 -23
- package/dist/shared/attaform.DAH3kvav.d.mts +1651 -0
- package/dist/shared/{attaform.BPRHR3Zs.cjs → attaform.DUHru0OF.cjs} +83 -85
- package/dist/shared/attaform.DUHru0OF.cjs.map +1 -0
- package/dist/shared/{attaform.BV40t5y2.cjs → attaform.Dlk1jMuv.cjs} +245 -108
- package/dist/shared/attaform.Dlk1jMuv.cjs.map +1 -0
- package/dist/shared/{attaform.B3ZaPIzS.mjs → attaform.DsC3rZHG.mjs} +1804 -219
- package/dist/shared/attaform.DsC3rZHG.mjs.map +1 -0
- package/dist/shared/{attaform.DtMN-MAm.d.cts → attaform.Dzi89x8N.d.cts} +4 -4
- package/dist/shared/{attaform.Cer8JO_P.cjs → attaform.II89Pcf4.cjs} +1860 -272
- package/dist/shared/attaform.II89Pcf4.cjs.map +1 -0
- package/dist/shared/{attaform.CIEQgJnM.mjs → attaform.Xhg0AYNa.mjs} +300 -26
- package/dist/shared/attaform.Xhg0AYNa.mjs.map +1 -0
- package/dist/shared/{attaform.CpERWz3u.mjs → attaform.Xt0A3QUd.mjs} +232 -95
- package/dist/shared/attaform.Xt0A3QUd.mjs.map +1 -0
- package/dist/shared/{attaform.CHorcsIU.d.cts → attaform.bH7WvNad.d.mts} +57 -23
- package/dist/vite.cjs +270 -2
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +266 -2
- package/dist/vite.mjs.map +1 -1
- package/dist/zod-v3.cjs +11 -8
- package/dist/zod-v3.cjs.map +1 -1
- package/dist/zod-v3.d.cts +6 -6
- package/dist/zod-v3.d.mts +6 -6
- package/dist/zod-v3.d.ts +6 -6
- package/dist/zod-v3.mjs +3 -3
- package/dist/zod-v4.cjs +11 -8
- package/dist/zod-v4.cjs.map +1 -1
- package/dist/zod-v4.d.cts +5 -5
- package/dist/zod-v4.d.mts +5 -5
- package/dist/zod-v4.d.ts +5 -5
- package/dist/zod-v4.mjs +3 -3
- package/dist/zod.cjs +15 -16
- package/dist/zod.cjs.map +1 -1
- package/dist/zod.d.cts +127 -40
- package/dist/zod.d.mts +127 -40
- package/dist/zod.d.ts +127 -40
- package/dist/zod.mjs +7 -11
- package/dist/zod.mjs.map +1 -1
- package/package.json +19 -5
- package/dist/shared/attaform.B1jvxsOF.d.mts +0 -156
- package/dist/shared/attaform.B3ZaPIzS.mjs.map +0 -1
- package/dist/shared/attaform.BBM2muQ9.cjs +0 -101
- package/dist/shared/attaform.BBM2muQ9.cjs.map +0 -1
- package/dist/shared/attaform.BPRHR3Zs.cjs.map +0 -1
- package/dist/shared/attaform.BV40t5y2.cjs.map +0 -1
- package/dist/shared/attaform.C6qzEdIM.d.cts +0 -156
- package/dist/shared/attaform.C8LVFVVe.cjs +0 -32
- package/dist/shared/attaform.C8LVFVVe.cjs.map +0 -1
- package/dist/shared/attaform.CIEQgJnM.mjs.map +0 -1
- package/dist/shared/attaform.CTwNcpLE.d.ts +0 -156
- package/dist/shared/attaform.Cer8JO_P.cjs.map +0 -1
- package/dist/shared/attaform.CpERWz3u.mjs.map +0 -1
- package/dist/shared/attaform.Dee2rU1P.cjs.map +0 -1
- package/dist/shared/attaform.Drt6fivF.mjs.map +0 -1
- package/dist/shared/attaform.Vo-Kft0t.mjs +0 -29
- package/dist/shared/attaform.Vo-Kft0t.mjs.map +0 -1
- package/dist/shared/attaform.h1sq3BFu.mjs +0 -92
- package/dist/shared/attaform.h1sq3BFu.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ObjectDirective, Ref, ComputedRef } from 'vue';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Schema-attached field metadata — the shared types used by both Zod
|
|
@@ -136,6 +136,16 @@ declare function isUnset(value: unknown): value is Unset;
|
|
|
136
136
|
type GenericForm = Record<string, unknown>;
|
|
137
137
|
/** Internal helper — `true` when `T` is an object or array. */
|
|
138
138
|
type IsObjectOrArray<T> = T extends GenericForm ? true : T extends Array<unknown> ? true : false;
|
|
139
|
+
/**
|
|
140
|
+
* Implementation detail backing `FlatPath` in its default
|
|
141
|
+
* (partial-path) mode. Exported so `rollup-plugin-dts` preserves it
|
|
142
|
+
* as a named alias in the bundled `.d.ts` rather than inlining the
|
|
143
|
+
* full template-literal recursion body at every reference site
|
|
144
|
+
* (`FlatPath`, `RegisterFlatPath`, every path-addressed API method).
|
|
145
|
+
* Inlining at consumer call sites compounds into TS2589 territory
|
|
146
|
+
* when multiple complex forms share a scope. Consumers should reach
|
|
147
|
+
* for `FlatPath` instead; this alias is not part of the stable surface.
|
|
148
|
+
*/
|
|
139
149
|
type PartialFlatPath<Form, Key extends keyof Form = keyof Form> = IsObjectOrArray<Form> extends true ? Key extends string ? Form[Key] extends infer Value ? Value extends Array<infer ArrayItem> ? `${Key}` | `${Key}.${number}` | `${Key}.${number}.${PartialFlatPath<ArrayItem>}` : Value extends GenericForm ? `${Key}` | `${Key}.${PartialFlatPath<Value>}` : `${Key}` : never : Key extends number ? `${Key}` | (Form[Key] extends GenericForm ? `${Key}.${PartialFlatPath<Form[Key]>}` : Form[Key] extends Array<infer ArrayItem> ? IsObjectOrArray<ArrayItem> extends true ? `${Key}.${number}` | `${Key}.${number}.${PartialFlatPath<ArrayItem>}` : `${Key}.${number}` : never) : never : never;
|
|
140
150
|
type CompleteFlatPath<Form, Key extends keyof Form = keyof Form> = IsObjectOrArray<Form> extends true ? Key extends string ? Form[Key] extends infer Value ? Value extends Array<infer ArrayItem> ? `${Key}.${number}.${CompleteFlatPath<ArrayItem>}` : Value extends GenericForm ? `${Key}.${CompleteFlatPath<Value>}` : `${Key}` : never : Key extends number ? `${Key}` | (Form[Key] extends GenericForm ? `${Key}.${CompleteFlatPath<Form[Key]>}` : Form[Key] extends Array<infer ArrayItem> ? IsObjectOrArray<ArrayItem> extends true ? `${Key}.${number}.${CompleteFlatPath<ArrayItem>}` : `${Key}.${number}` : never) : never : never;
|
|
141
151
|
/**
|
|
@@ -259,6 +269,14 @@ type DeepPartial<T> = T extends Primitive ? T : T extends Array<infer ArrayItem>
|
|
|
259
269
|
* never reach this depth.
|
|
260
270
|
*/
|
|
261
271
|
type NestedType<RootValue, FlattenedPath extends string, FilterOutNullishTypesDuringRecursion extends boolean = true, _RootValue = FilterOutNullishTypesDuringRecursion extends false ? RootValue : NonNullable<RootValue>> = IsObjectOrArray<_RootValue> extends false ? never : FlattenedPath extends `${infer Key}.${infer Rest}` ? Key extends `${number}` ? Key extends KeyofUnion<_RootValue> ? NestedType<ValueOfUnion<_RootValue, Key>, Rest, FilterOutNullishTypesDuringRecursion> : Key extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? NestedType<ValueOfUnion<_RootValue, NumericKey>, Rest, FilterOutNullishTypesDuringRecursion> : never : never : Key extends KeyofUnion<_RootValue> ? NestedType<ValueOfUnion<_RootValue, Key>, Rest, FilterOutNullishTypesDuringRecursion> : never : FlattenedPath extends `${number}` ? FlattenedPath extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, FlattenedPath> : FlattenedPath extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, NumericKey> : never : never : FlattenedPath extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, FlattenedPath> : never;
|
|
272
|
+
/**
|
|
273
|
+
* Implementation-detail primitive-leaf marker used by `DeepPartial`
|
|
274
|
+
* and sibling structural walkers. Exported so the bundled `.d.ts`
|
|
275
|
+
* references one alias instead of re-emitting the union at every
|
|
276
|
+
* recursion branch of every walker that depends on it. Not part of
|
|
277
|
+
* the stable consumer-facing surface — reach for `DeepPartial`
|
|
278
|
+
* instead.
|
|
279
|
+
*/
|
|
262
280
|
type Primitive = string | number | boolean | symbol | bigint | null | undefined;
|
|
263
281
|
/**
|
|
264
282
|
* Distinguish a tuple from a regular array.
|
|
@@ -334,10 +352,14 @@ type ArrayItem<Form, Path extends ArrayPath<Form>> = NestedType<Form, Path> exte
|
|
|
334
352
|
* the array-recursion branch so positionally-typed array literals
|
|
335
353
|
* survive intact.
|
|
336
354
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
355
|
+
* `WriteShape<T>` stays STRICT — no `| Unset` widening. It's used for
|
|
356
|
+
* the callback's prev-value argument in `setValue(path, prev => ...)`
|
|
357
|
+
* (which always receives a real value, never the sentinel), for
|
|
358
|
+
* read-side types where consumers expect a structural form shape, and
|
|
359
|
+
* by internal types like `FieldStateMap<T>`. The consumer-facing
|
|
360
|
+
* write-value type is `DefaultValuesShape<T>` (composed via
|
|
361
|
+
* `SetValuePayload`), which adds `| Unset` at every recursable
|
|
362
|
+
* position.
|
|
341
363
|
*/
|
|
342
364
|
type WriteShape<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? T extends string ? string : T extends number ? number : T extends boolean ? boolean : T extends bigint ? bigint : T extends symbol ? symbol : T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T : T extends readonly [unknown, ...unknown[]] ? {
|
|
343
365
|
-readonly [K in keyof T]: WriteShape<T[K]>;
|
|
@@ -352,30 +374,75 @@ type WriteShape<T> = T extends string | number | boolean | bigint | symbol | nul
|
|
|
352
374
|
* brand-typed sentinel consumers pass to indicate "this leaf starts
|
|
353
375
|
* displayed-empty" in `defaultValues`, `setValue`, and `reset`.
|
|
354
376
|
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
377
|
+
* `Unset` is admitted at every position — primitive leaves, opaque
|
|
378
|
+
* leaves (`Date`, `RegExp`, `Map`, `Set`, functions), and containers
|
|
379
|
+
* (objects, arrays, tuples, records, DUs, optional / nullable
|
|
380
|
+
* wrappers). A container `unset` recursively marks every primitive
|
|
381
|
+
* descendant blank, so `defaultValues: { profile: unset }` and
|
|
382
|
+
* `setValue('cargo', unset)` typecheck cleanly.
|
|
358
383
|
*
|
|
359
384
|
* The recursion mirrors `WriteShape<T>` exactly so `defaultValues`
|
|
360
|
-
* stays compatible at every nested position
|
|
361
|
-
*
|
|
362
|
-
* records all flow through unchanged.
|
|
385
|
+
* stays compatible at every nested position. Tuple positions,
|
|
386
|
+
* unbounded arrays, and nested records all flow through unchanged.
|
|
363
387
|
*
|
|
364
388
|
* Example:
|
|
365
389
|
*
|
|
366
390
|
* DefaultValuesShape<{ income: number; name: string; age: 21 }>
|
|
367
|
-
* // → { income: number | Unset; name: string | Unset; age: number | Unset }
|
|
391
|
+
* // → { income: number | Unset; name: string | Unset; age: number | Unset } | Unset
|
|
368
392
|
*
|
|
369
393
|
* Used by `UseFormConfiguration.defaultValues`, `setValue`'s value
|
|
370
|
-
* parameter, and `reset`'s parameter
|
|
394
|
+
* parameter, and `reset`'s parameter.
|
|
371
395
|
*/
|
|
372
|
-
type DefaultValuesShape<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? T extends string ? string | Unset : T extends number ? number | Unset : T extends boolean ? boolean | Unset : T extends bigint ? bigint | Unset : T extends symbol ? symbol : T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T : T extends readonly [unknown, ...unknown[]] ? {
|
|
396
|
+
type DefaultValuesShape<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? T extends string ? string | Unset : T extends number ? number | Unset : T extends boolean ? boolean | Unset : T extends bigint ? bigint | Unset : T extends symbol ? symbol : T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T | Unset : T extends readonly [unknown, ...unknown[]] ? {
|
|
373
397
|
-readonly [K in keyof T]: DefaultValuesShape<T[K]>;
|
|
374
|
-
} : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
|
|
398
|
+
} | Unset : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
|
|
375
399
|
-readonly [K in keyof T]: DefaultValuesShape<T[K]>;
|
|
376
|
-
} : Array<DefaultValuesShape<U>> : T extends object ? {
|
|
400
|
+
} | Unset : Array<DefaultValuesShape<U>> | Unset : T extends object ? {
|
|
377
401
|
[K in keyof T]: DefaultValuesShape<T[K]>;
|
|
378
|
-
} : T;
|
|
402
|
+
} | Unset : T;
|
|
403
|
+
/**
|
|
404
|
+
* Single-walker fusion of `DeepPartial` and `DefaultValuesShape` — the
|
|
405
|
+
* type accepted at `defaultValues`, `reset()`'s parameter, and every
|
|
406
|
+
* partial-shape consumer. Every level is optional and every position
|
|
407
|
+
* — primitive leaf, opaque leaf, or container — admits `| Unset`, in
|
|
408
|
+
* one tree walk where the prior `DeepPartial<DefaultValuesShape<F>>`
|
|
409
|
+
* composition walked twice.
|
|
410
|
+
*
|
|
411
|
+
* Both passes had identical topology (object → mapped, tuple →
|
|
412
|
+
* positional, array → recurse, primitive → terminal) — the doubled
|
|
413
|
+
* recursion exhausted the depth budget at consumer call sites that
|
|
414
|
+
* wire multiple complex forms into one scope. Collapsing them buys
|
|
415
|
+
* back the headroom plus a side fix: opaque leaves (`Date`, `Map`,
|
|
416
|
+
* `Set`, `RegExp`, functions) now stay intact when their containing
|
|
417
|
+
* property is optional, rather than getting structurally destructured
|
|
418
|
+
* by `DeepPartial`'s pass.
|
|
419
|
+
*
|
|
420
|
+
* Tuple positions, array elements, and discriminated-union variants
|
|
421
|
+
* all flow through unchanged from the prior semantics. Container
|
|
422
|
+
* positions widen with `| Unset` so `defaultValues: { profile: unset }`
|
|
423
|
+
* and `reset({ cargo: unset })` typecheck — the runtime recursively
|
|
424
|
+
* marks every primitive descendant blank.
|
|
425
|
+
*
|
|
426
|
+
* ```ts
|
|
427
|
+
* type T = DefaultValuesInput<{
|
|
428
|
+
* email: string
|
|
429
|
+
* joinedAt: Date
|
|
430
|
+
* profile: { name: string; age: number }
|
|
431
|
+
* }>
|
|
432
|
+
* // → {
|
|
433
|
+
* // email?: string | Unset
|
|
434
|
+
* // joinedAt?: Date | Unset
|
|
435
|
+
* // profile?: { name?: string | Unset; age?: number | Unset } | Unset
|
|
436
|
+
* // } | Unset
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
type DefaultValuesInput<T> = T extends string ? string | Unset : T extends number ? number | Unset : T extends boolean ? boolean | Unset : T extends bigint ? bigint | Unset : T extends symbol ? symbol : T extends null | undefined ? T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown) ? T | Unset : T extends readonly [unknown, ...unknown[]] ? {
|
|
440
|
+
-readonly [K in keyof T]?: DefaultValuesInput<T[K]>;
|
|
441
|
+
} | Unset : T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? {
|
|
442
|
+
-readonly [K in keyof T]?: DefaultValuesInput<T[K]>;
|
|
443
|
+
} | Unset : Array<DefaultValuesInput<U>> | Unset : T extends object ? {
|
|
444
|
+
[K in keyof T]?: DefaultValuesInput<T[K]>;
|
|
445
|
+
} | Unset : T;
|
|
379
446
|
|
|
380
447
|
/**
|
|
381
448
|
* Per-form options threaded from `useForm` into the adapter factory.
|
|
@@ -469,6 +536,42 @@ declare const ROOT_PATH_KEY: PathKey;
|
|
|
469
536
|
*/
|
|
470
537
|
declare function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean;
|
|
471
538
|
|
|
539
|
+
/**
|
|
540
|
+
* Per-FormStore registry tracking which DOM elements have opted into
|
|
541
|
+
* persistence for which paths. Lives on the FormStore so that two SFCs
|
|
542
|
+
* sharing a key share the registry — opt-ins are per-element, not
|
|
543
|
+
* per-component.
|
|
544
|
+
*
|
|
545
|
+
* The directive's input handler computes `meta.persist` for each write
|
|
546
|
+
* by calling `hasOptIn(elementId, path)` — only THIS element's writes
|
|
547
|
+
* persist if THIS element opted in. Other call sites that aren't tied
|
|
548
|
+
* to a single element (history undo/redo, field-array helpers, devtools
|
|
549
|
+
* edits) use `hasAnyOptInForPath(path)` — persist if any element has
|
|
550
|
+
* opted into that path.
|
|
551
|
+
*
|
|
552
|
+
* Internal data structure: `Map<PathKey, Set<elementId>>`. Small forms
|
|
553
|
+
* have ~10-50 paths; iteration is cheap. All operations are O(1) given
|
|
554
|
+
* (id, path).
|
|
555
|
+
*/
|
|
556
|
+
type PersistOptInRegistry = {
|
|
557
|
+
/** Add an opt-in entry; idempotent. */
|
|
558
|
+
add(elementId: string, path: PathKey): void;
|
|
559
|
+
/** Remove a single (element, path) entry. */
|
|
560
|
+
remove(elementId: string, path: PathKey): void;
|
|
561
|
+
/** Remove every opt-in for `elementId`. Called from directive's beforeUnmount. */
|
|
562
|
+
removeAllFor(elementId: string): void;
|
|
563
|
+
/** Check whether THIS element has opted into THIS path. */
|
|
564
|
+
hasOptIn(elementId: string, path: PathKey): boolean;
|
|
565
|
+
/** Check whether ANY element has opted into this path. */
|
|
566
|
+
hasAnyOptInForPath(path: PathKey): boolean;
|
|
567
|
+
/** Iterate every path that currently has at least one opt-in. */
|
|
568
|
+
optedInPaths(): IterableIterator<PathKey>;
|
|
569
|
+
/** True iff no element has opted into any path. */
|
|
570
|
+
isEmpty(): boolean;
|
|
571
|
+
/** Drop every entry. Called from FormStore.dispose. */
|
|
572
|
+
clear(): void;
|
|
573
|
+
};
|
|
574
|
+
|
|
472
575
|
/**
|
|
473
576
|
* Identifier for a form. A `FormKey` is the string passed via
|
|
474
577
|
* `useForm({ key })`, used to look up a form by name from a distant
|
|
@@ -699,37 +802,24 @@ type AbstractSchema<Form, GetValueFormType> = {
|
|
|
699
802
|
*/
|
|
700
803
|
getEmptyValueAtPath(path: Path): unknown;
|
|
701
804
|
/**
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
* `z.
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
* the
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
*
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
* the path.
|
|
721
|
-
* - Return `value` unchanged when the user's normalization fn
|
|
722
|
-
* returns a `Promise` (async coercion can't run at write time —
|
|
723
|
-
* validation handles it during parse).
|
|
724
|
-
* - Let user-thrown errors propagate (the user wrote the fn; we
|
|
725
|
-
* just tag the path in the wrapper error for diagnostics).
|
|
726
|
-
*
|
|
727
|
-
* Normalization runs when `path` equals the wrapper's exact
|
|
728
|
-
* location. Writes deeper than the wrapper bypass it (a wrapper
|
|
729
|
-
* over the whole subtree can't be invoked from a partial leaf
|
|
730
|
-
* write).
|
|
731
|
-
*/
|
|
732
|
-
normalizeWriteValueAtPath(value: unknown, path: Path): unknown;
|
|
805
|
+
* Reports whether `path` resolves to (or descends through) a
|
|
806
|
+
* schema-side normalizer that runs at parse, not at the write
|
|
807
|
+
* boundary. In Zod v4 that's `z.preprocess(fn, inner)` and
|
|
808
|
+
* `z.coerce.X()` (both desugar to `ZodPipe<ZodTransform, inner>`);
|
|
809
|
+
* in Zod v3 it's `ZodEffects` with `_def.effect.type === 'preprocess'`.
|
|
810
|
+
*
|
|
811
|
+
* Consulted by the slim-primitive write gate. When true at a path,
|
|
812
|
+
* the gate accepts the consumer's raw value verbatim and stops
|
|
813
|
+
* walking children — storage holds the user's input, and the
|
|
814
|
+
* normalizer fires during `safeParse` (handleSubmit / validate /
|
|
815
|
+
* validateAsync), not at `setValue` time.
|
|
816
|
+
*
|
|
817
|
+
* Path-prefix semantic: returns true if ANY ancestor of `path`
|
|
818
|
+
* resolves to such a wrapper, so descendants under a preprocess-
|
|
819
|
+
* wrapped container also short-circuit the gate. Adapters cache
|
|
820
|
+
* the result by canonicalized path key.
|
|
821
|
+
*/
|
|
822
|
+
isPreprocessOrCoerceLeaf(path: Path): boolean;
|
|
733
823
|
/**
|
|
734
824
|
* Distinguish a tuple (fixed-length, position-typed) from an
|
|
735
825
|
* unbounded array at `path`. The runtime calls this on every
|
|
@@ -1021,7 +1111,7 @@ type UnionDiscriminatorContext = {
|
|
|
1021
1111
|
* the set of kinds the path's leaf schema accepts. A write is gated
|
|
1022
1112
|
* by `accepted.has(slimKindOf(value))`.
|
|
1023
1113
|
*/
|
|
1024
|
-
type SlimPrimitiveKind = 'string' | 'number' | 'boolean' | 'bigint' | 'date' | 'null' | 'undefined' | 'object' | 'array' | 'symbol' | 'function' | 'map' | 'set';
|
|
1114
|
+
type SlimPrimitiveKind = 'string' | 'number' | 'boolean' | 'bigint' | 'date' | 'null' | 'undefined' | 'object' | 'array' | 'symbol' | 'function' | 'map' | 'set' | 'file';
|
|
1025
1115
|
/**
|
|
1026
1116
|
* The "no result yet" status returned by the reactive `validate()` ref
|
|
1027
1117
|
* while a validation run is in flight.
|
|
@@ -1053,15 +1143,23 @@ type SettledValidationStatus<Form> = {
|
|
|
1053
1143
|
type ReactiveValidationStatus<Form> = PendingValidationStatus | SettledValidationStatus<Form>;
|
|
1054
1144
|
/**
|
|
1055
1145
|
* What to do when a submit attempt fails validation. The library can
|
|
1056
|
-
* focus and/or scroll the first errored field into view without
|
|
1057
|
-
* wiring an `onError` callback yourself.
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1146
|
+
* focus and/or scroll the first errored field into view without you
|
|
1147
|
+
* wiring an `onError` callback yourself. Defaults to
|
|
1148
|
+
* `'focus-first-error'` because moving keyboard / screen-reader focus
|
|
1149
|
+
* to the broken field on submit is an accessibility baseline; opt out
|
|
1150
|
+
* with `'none'` if you're managing focus elsewhere.
|
|
1151
|
+
*
|
|
1152
|
+
* - `'focus-first-error'` (default): focus the first errored field's
|
|
1153
|
+
* first visible element. Modern browsers scroll the focused element
|
|
1154
|
+
* into view by default; pair with `'both'` if you want an explicit
|
|
1155
|
+
* scroll alongside.
|
|
1156
|
+
* - `'scroll-to-first-error'`: scroll that element into view without
|
|
1157
|
+
* moving focus.
|
|
1158
|
+
* - `'both'`: scroll first, then focus (with `preventScroll: true` so
|
|
1159
|
+
* the browser doesn't undo the explicit scroll).
|
|
1160
|
+
* - `'none'`: no automatic UI nudge; the dev handles focus / scroll
|
|
1161
|
+
* manually via `form.focusFirstError()` or `form.scrollToFirstError()`
|
|
1162
|
+
* from an `onError` callback.
|
|
1065
1163
|
*
|
|
1066
1164
|
* If no errored field has a currently mounted, visible element, the
|
|
1067
1165
|
* policy silently no-ops.
|
|
@@ -1428,7 +1526,7 @@ type PersistConfig = FormStorageKind | FormStorage | PersistConfigOptions;
|
|
|
1428
1526
|
* })
|
|
1429
1527
|
* ```
|
|
1430
1528
|
*/
|
|
1431
|
-
type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema extends AbstractSchema<Form, GetValueFormType>, DefaultValues extends
|
|
1529
|
+
type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema extends AbstractSchema<Form, GetValueFormType>, DefaultValues extends DefaultValuesInput<Form>, K extends FormKey = FormKey> = {
|
|
1432
1530
|
/**
|
|
1433
1531
|
* The schema describing the form's shape and validation rules.
|
|
1434
1532
|
* Typed entry points like `attaform/zod` accept the
|
|
@@ -1459,8 +1557,12 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1459
1557
|
*
|
|
1460
1558
|
* Keys starting with `__atta:` are reserved for internal use and
|
|
1461
1559
|
* throw `ReservedFormKeyError` if passed.
|
|
1560
|
+
*
|
|
1561
|
+
* When passed as a string literal, the literal is preserved on
|
|
1562
|
+
* `form.key` so `useWizard` and other consumers can discriminate
|
|
1563
|
+
* against the union of known keys at compile time.
|
|
1462
1564
|
*/
|
|
1463
|
-
key?:
|
|
1565
|
+
key?: K;
|
|
1464
1566
|
/**
|
|
1465
1567
|
* Initial values applied over the schema's defaults. Each field
|
|
1466
1568
|
* falls back to the schema default (or the primitive default for
|
|
@@ -1472,8 +1574,30 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1472
1574
|
* membership, length / range bounds). Refinement-invalid defaults
|
|
1473
1575
|
* pass through and surface as field errors — this lets you
|
|
1474
1576
|
* rehydrate stale saved data without losing the user's input.
|
|
1577
|
+
*
|
|
1578
|
+
* Accepts a plain value, a sync function, or an async function:
|
|
1579
|
+
*
|
|
1580
|
+
* ```ts
|
|
1581
|
+
* // Plain value — applies at construction.
|
|
1582
|
+
* defaultValues: { email: '' }
|
|
1583
|
+
*
|
|
1584
|
+
* // Sync function — invoked on a microtask after construction.
|
|
1585
|
+
* defaultValues: () => buildDraft()
|
|
1586
|
+
*
|
|
1587
|
+
* // Async function — form starts with the schema's slim defaults
|
|
1588
|
+
* // and `form.hydrating` flips true while the promise is
|
|
1589
|
+
* // in flight; on resolve the values apply and `hydrating` flips
|
|
1590
|
+
* // false. Under SSR the factory fires via `onServerPrefetch` so
|
|
1591
|
+
* // the resolved payload bakes into hydration transfer state and
|
|
1592
|
+
* // the client never re-fetches.
|
|
1593
|
+
* defaultValues: async () => api.fetchDraft(userId)
|
|
1594
|
+
* ```
|
|
1595
|
+
*
|
|
1596
|
+
* Errors thrown by a function-form factory surface on
|
|
1597
|
+
* `form.hydrateError`; the form stays usable with slim defaults.
|
|
1598
|
+
* Call `form.rehydrate()` to re-fire the factory.
|
|
1475
1599
|
*/
|
|
1476
|
-
defaultValues?: DefaultValues;
|
|
1600
|
+
defaultValues?: DefaultValues | (() => DefaultValues) | (() => Promise<DefaultValues>);
|
|
1477
1601
|
/**
|
|
1478
1602
|
* Whether to validate default values at construction. Default
|
|
1479
1603
|
* `true`.
|
|
@@ -1481,7 +1605,7 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1481
1605
|
* - `true` (default): the schema is run against the derived
|
|
1482
1606
|
* defaults immediately; any failures populate `form.errors` from
|
|
1483
1607
|
* the first frame. The UI decides when to *show* errors — gate
|
|
1484
|
-
* on `form.fields.<path>.touched`, `form.meta.
|
|
1608
|
+
* on `form.fields.<path>.touched`, `form.meta.submissionAttempts`, etc.
|
|
1485
1609
|
* - `false`: refinements are stripped during defaults derivation
|
|
1486
1610
|
* and construction-time validation is skipped. Useful for
|
|
1487
1611
|
* multi-step wizards, field arrays seeded with placeholder
|
|
@@ -1494,12 +1618,16 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1494
1618
|
/**
|
|
1495
1619
|
* Automatic UI nudge on submit-validation failure. Fires after
|
|
1496
1620
|
* errors are populated and before your `onError` callback runs.
|
|
1497
|
-
* Default `'
|
|
1621
|
+
* Default `'focus-first-error'`, which moves keyboard / screen-reader
|
|
1622
|
+
* focus to the broken field as an accessibility baseline.
|
|
1498
1623
|
*
|
|
1499
|
-
* - `'focus-first-error'
|
|
1500
|
-
* visible element
|
|
1501
|
-
* - `'scroll-to-first-error'`: scroll it into view.
|
|
1624
|
+
* - `'focus-first-error'` (default): focus the first errored field's
|
|
1625
|
+
* first visible element.
|
|
1626
|
+
* - `'scroll-to-first-error'`: scroll it into view without focusing.
|
|
1502
1627
|
* - `'both'`: scroll, then focus.
|
|
1628
|
+
* - `'none'`: opt out entirely; handle focus / scroll yourself in an
|
|
1629
|
+
* `onError` callback via `form.focusFirstError()` or
|
|
1630
|
+
* `form.scrollToFirstError()`.
|
|
1503
1631
|
*
|
|
1504
1632
|
* If no errored field has a currently-mounted, visible element,
|
|
1505
1633
|
* the policy silently no-ops.
|
|
@@ -1658,31 +1786,53 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1658
1786
|
*/
|
|
1659
1787
|
sensitiveNames?: readonly string[];
|
|
1660
1788
|
/**
|
|
1661
|
-
* Cross-tab synchronisation via BroadcastChannel. Defaults to
|
|
1662
|
-
* (
|
|
1663
|
-
*
|
|
1664
|
-
*
|
|
1665
|
-
* in near real-time.
|
|
1789
|
+
* Cross-tab synchronisation via BroadcastChannel. **Defaults to
|
|
1790
|
+
* `false` (opt-in).** Setting `true` on a keyed `useForm` callsite
|
|
1791
|
+
* auto-pairs the form with same-keyed siblings in other same-origin
|
|
1792
|
+
* tabs and mirrors their mutations in near real-time.
|
|
1666
1793
|
*
|
|
1667
1794
|
* **Resolution order (per-register override > per-form > global > library):**
|
|
1668
1795
|
*
|
|
1669
|
-
* register(path, { multiTab }) > useForm({ multiTab }) > AttaformDefaults.multiTab > library default (`
|
|
1796
|
+
* register(path, { multiTab }) > useForm({ multiTab }) > AttaformDefaults.multiTab > library default (`false`)
|
|
1670
1797
|
*
|
|
1671
|
-
* **
|
|
1672
|
-
*
|
|
1673
|
-
* tab
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1798
|
+
* **Why opt-in.** Same-keyed forms broadcasting by default leaks
|
|
1799
|
+
* surprise: a user editing in one tab sees their values appear in a
|
|
1800
|
+
* sibling tab they forgot was open, including PII / PHI for forms
|
|
1801
|
+
* that don't explicitly use `sensitiveNames`. Mirrors the `persist`
|
|
1802
|
+
* default (also opt-in): both opt-in surfaces compose into one
|
|
1803
|
+
* consistent "richer state needs explicit consent" rule.
|
|
1676
1804
|
*
|
|
1677
|
-
* **
|
|
1678
|
-
*
|
|
1679
|
-
*
|
|
1805
|
+
* **What's stripped even when opt-in.** `File` and `Blob` values
|
|
1806
|
+
* never traverse the channel regardless of this flag (security +
|
|
1807
|
+
* `structuredClone` cost). Sensitive-named paths (via
|
|
1808
|
+
* `sensitiveNames`) are stripped both directions. See the
|
|
1809
|
+
* multi-tab sync page for the full security model.
|
|
1810
|
+
*
|
|
1811
|
+
* **Secure-context requirement.** Even with `multiTab: true`, sync
|
|
1812
|
+
* is silently disabled outside `window.isSecureContext === true`
|
|
1813
|
+
* (HTTPS or localhost). On plain HTTP a one-shot dev warning fires
|
|
1814
|
+
* and the module noops.
|
|
1680
1815
|
*
|
|
1681
1816
|
* **Anonymous (auto-keyed) forms skip sync entirely** — without a
|
|
1682
1817
|
* consumer-supplied `key`, cross-tab identity is undefined and the
|
|
1683
1818
|
* channel would be solo by construction.
|
|
1684
1819
|
*/
|
|
1685
1820
|
multiTab?: boolean;
|
|
1821
|
+
/**
|
|
1822
|
+
* @internal
|
|
1823
|
+
* SSR prefetch mark — set by the `attaform/vite` compile-time
|
|
1824
|
+
* transform on `useForm` calls whose surrounding SFC template (or a
|
|
1825
|
+
* computed feeding it) reads the form's reactive state. The flag
|
|
1826
|
+
* enqueues the form on the registry's SSR prefetch queue so an
|
|
1827
|
+
* async `defaultValues` factory runs inside `onServerPrefetch` and
|
|
1828
|
+
* the resolved payload bakes into the hydration transfer state.
|
|
1829
|
+
*
|
|
1830
|
+
* Consumers do not write this directly — `form.activate()` is the
|
|
1831
|
+
* documented escape hatch when the transform's static analysis
|
|
1832
|
+
* can't see a reference (cross-module sharing, dynamic property
|
|
1833
|
+
* access, headless contexts).
|
|
1834
|
+
*/
|
|
1835
|
+
__ssrAccessed?: boolean;
|
|
1686
1836
|
};
|
|
1687
1837
|
/**
|
|
1688
1838
|
* App-level defaults applied to every `useForm` call. Set these once
|
|
@@ -1762,7 +1912,7 @@ type AttaformDefaults = {
|
|
|
1762
1912
|
*
|
|
1763
1913
|
* ```ts
|
|
1764
1914
|
* (field, formMeta) =>
|
|
1765
|
-
* formMeta.
|
|
1915
|
+
* formMeta.submissionAttempts > 0 || (field.touched === true && field.dirty)
|
|
1766
1916
|
* ```
|
|
1767
1917
|
*
|
|
1768
1918
|
* Compose with the library default via the public
|
|
@@ -1851,25 +2001,28 @@ type AttaformDefaults = {
|
|
|
1851
2001
|
*/
|
|
1852
2002
|
sensitiveNames?: readonly string[];
|
|
1853
2003
|
/**
|
|
1854
|
-
* App-wide default for `useForm({ multiTab })`.
|
|
1855
|
-
*
|
|
1856
|
-
*
|
|
1857
|
-
*
|
|
1858
|
-
*
|
|
1859
|
-
*
|
|
1860
|
-
* Set to `false` once at the plugin level for a multi-tenant
|
|
1861
|
-
* deployment that prefers tab-isolation by default; individual forms
|
|
1862
|
-
* can still opt back in via `useForm({ multiTab: true })`.
|
|
2004
|
+
* App-wide default for `useForm({ multiTab })`. Library default is
|
|
2005
|
+
* `false` (opt-in) — same posture as `persist`. Set to `true` once
|
|
2006
|
+
* at the plugin level to enable cross-tab sync for every form in
|
|
2007
|
+
* the app by default; individual forms can still opt out via
|
|
2008
|
+
* `useForm({ multiTab: false })`.
|
|
1863
2009
|
*
|
|
1864
2010
|
* **Resolution order (per-form wins):**
|
|
1865
2011
|
*
|
|
1866
|
-
* useForm({ multiTab }) > AttaformDefaults.multiTab > library default (`
|
|
2012
|
+
* useForm({ multiTab }) > AttaformDefaults.multiTab > library default (`false`)
|
|
1867
2013
|
*
|
|
1868
|
-
* **
|
|
1869
|
-
*
|
|
1870
|
-
*
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
2014
|
+
* **Why opt-in.** Auto-broadcasting same-keyed forms surprises users
|
|
2015
|
+
* (a value typed in one tab appearing in another they forgot was
|
|
2016
|
+
* open) and leaks state for forms that don't explicitly use
|
|
2017
|
+
* `sensitiveNames`. Paired with `persist` (also opt-in), the two
|
|
2018
|
+
* "richer state" surfaces follow one consistent rule: explicit
|
|
2019
|
+
* consent.
|
|
2020
|
+
*
|
|
2021
|
+
* **Secure-context gate.** Even with `multiTab: true`, sync only
|
|
2022
|
+
* activates over HTTPS or localhost. On plain HTTP, the module
|
|
2023
|
+
* silently noops with a one-shot dev-mode warning — production
|
|
2024
|
+
* deployments MUST be served over HTTPS for sync to function. See
|
|
2025
|
+
* the multi-tab-sync recipe's Security section for the threat model.
|
|
1873
2026
|
*/
|
|
1874
2027
|
multiTab?: boolean;
|
|
1875
2028
|
};
|
|
@@ -2222,6 +2375,14 @@ type RegisterValue<Value = unknown> = Readonly<{
|
|
|
2222
2375
|
* directives signal whether the write should be persisted.
|
|
2223
2376
|
*/
|
|
2224
2377
|
setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
|
|
2378
|
+
/**
|
|
2379
|
+
* Mark this field as DOM-connected during SSR so a server-rendered
|
|
2380
|
+
* template that reads `form.fields.<path>.connected` doesn't
|
|
2381
|
+
* flicker on hydration. The `v-register` directive calls this for
|
|
2382
|
+
* you; no-op on the client.
|
|
2383
|
+
* @internal
|
|
2384
|
+
*/
|
|
2385
|
+
markConnectedOptimistically: () => void;
|
|
2225
2386
|
/**
|
|
2226
2387
|
* Canonical, JSON-encoded path key for this binding (e.g.
|
|
2227
2388
|
* `'["items",0,"name"]'`). Useful for stable Map / Set keys, log
|
|
@@ -2260,6 +2421,90 @@ type RegisterValue<Value = unknown> = Readonly<{
|
|
|
2260
2421
|
* `key`.
|
|
2261
2422
|
*/
|
|
2262
2423
|
formInstanceId: string;
|
|
2424
|
+
/**
|
|
2425
|
+
* Whether this binding opted into persistence via `register(path, { persist: true })`.
|
|
2426
|
+
* @internal
|
|
2427
|
+
*/
|
|
2428
|
+
persist: boolean;
|
|
2429
|
+
/**
|
|
2430
|
+
* Whether this binding acknowledged a sensitive-name override.
|
|
2431
|
+
* @internal
|
|
2432
|
+
*/
|
|
2433
|
+
acknowledgeSensitive: boolean;
|
|
2434
|
+
/**
|
|
2435
|
+
* Per-element persistence opt-in registry. Used by directive integrations.
|
|
2436
|
+
* @internal
|
|
2437
|
+
*/
|
|
2438
|
+
persistOptIns: PersistOptInRegistry;
|
|
2439
|
+
/**
|
|
2440
|
+
* Resolved sensitive-path predicate honoring this form's
|
|
2441
|
+
* `sensitiveNames` cascade. The directive calls this through
|
|
2442
|
+
* `enforceSensitiveCheck` when a `register('path', { persist: true })`
|
|
2443
|
+
* binding mounts so a per-form custom list (e.g. extending with
|
|
2444
|
+
* `'mrn'`) gates persistence enrolment correctly.
|
|
2445
|
+
* @internal
|
|
2446
|
+
*/
|
|
2447
|
+
isSensitivePath: (path: Path | PathKey | string) => boolean;
|
|
2448
|
+
/**
|
|
2449
|
+
* Whether this binding declared `register('path', { multiTab: false })`.
|
|
2450
|
+
* Drives the directive's mount/unmount lifecycle: when `false`, the
|
|
2451
|
+
* directive's `created` hook bumps `state.noSyncPaths` for this
|
|
2452
|
+
* path, and `beforeUnmount` decrements. When `true` (the default),
|
|
2453
|
+
* the binding rides the form-level cascade.
|
|
2454
|
+
* @internal
|
|
2455
|
+
*/
|
|
2456
|
+
multiTab: boolean;
|
|
2457
|
+
/**
|
|
2458
|
+
* Pre-bound mount hook for `multiTab: false` bindings — calls
|
|
2459
|
+
* `state.incrementNoSyncOptOut(path)` with this binding's path.
|
|
2460
|
+
* `undefined` when `multiTab !== false`. The directive invokes
|
|
2461
|
+
* during the mount lifecycle.
|
|
2462
|
+
* @internal
|
|
2463
|
+
*/
|
|
2464
|
+
markNoSync?: () => void;
|
|
2465
|
+
/**
|
|
2466
|
+
* Pre-bound unmount hook for `multiTab: false` bindings — calls
|
|
2467
|
+
* `state.decrementNoSyncOptOut(path)`. Paired with `markNoSync`;
|
|
2468
|
+
* the directive invokes on `beforeUnmount` (and on `beforeUpdate`
|
|
2469
|
+
* when the binding transitions out of opt-out).
|
|
2470
|
+
* @internal
|
|
2471
|
+
*/
|
|
2472
|
+
unmarkNoSync?: () => void;
|
|
2473
|
+
/**
|
|
2474
|
+
* Sync transform pipeline applied by the directive's assigner to
|
|
2475
|
+
* user-typed values before they reach form state. See
|
|
2476
|
+
* `RegisterOptions.transforms` for the public contract; this is
|
|
2477
|
+
* the readonly internal handle the directive iterates. Optional
|
|
2478
|
+
* so hand-rolled `RegisterValue` mocks (test fixtures, custom
|
|
2479
|
+
* integrations) don't have to declare an empty array — the
|
|
2480
|
+
* directive falls back to a no-op pipeline.
|
|
2481
|
+
* @internal
|
|
2482
|
+
*/
|
|
2483
|
+
transforms?: ReadonlyArray<RegisterTransform>;
|
|
2484
|
+
/**
|
|
2485
|
+
* Schema-driven coercion closure baked at register-time. Captures
|
|
2486
|
+
* the path's slim accept set and the resolved coercion index so
|
|
2487
|
+
* the per-event hot path is a single function call. Identity
|
|
2488
|
+
* function when coercion is disabled or the path admits no
|
|
2489
|
+
* coercion target. Optional so hand-rolled `RegisterValue` mocks
|
|
2490
|
+
* (test fixtures, custom integrations) don't have to declare it —
|
|
2491
|
+
* the directive falls back to identity.
|
|
2492
|
+
* @internal
|
|
2493
|
+
*/
|
|
2494
|
+
coerce?: (value: unknown) => unknown;
|
|
2495
|
+
/**
|
|
2496
|
+
* Element-level coercion closure for container paths
|
|
2497
|
+
* (`z.array(...)` / `z.set(...)`). Coerces a scalar DOM-side
|
|
2498
|
+
* value (an option's `value` attribute, a checkbox's value)
|
|
2499
|
+
* against the container's element type. `undefined` when the
|
|
2500
|
+
* path isn't a container — scalar paths use `coerce` exclusively.
|
|
2501
|
+
*
|
|
2502
|
+
* Used by the directive's read-side comparisons in setChecked
|
|
2503
|
+
* (array/Set branches) and setSelected (multi-select) to keep
|
|
2504
|
+
* parity with the change handler's WRITE-side path-level coerce.
|
|
2505
|
+
* @internal
|
|
2506
|
+
*/
|
|
2507
|
+
coerceElement?: (value: unknown) => unknown;
|
|
2263
2508
|
/**
|
|
2264
2509
|
* Read-only, string-form view of the field's current value — what
|
|
2265
2510
|
* the compile-time `:value` injection reads on every input /
|
|
@@ -2272,6 +2517,50 @@ type RegisterValue<Value = unknown> = Readonly<{
|
|
|
2272
2517
|
* patching `el.value` back to `'0'` (the slim default).
|
|
2273
2518
|
*/
|
|
2274
2519
|
displayValue: Readonly<Ref<string>>;
|
|
2520
|
+
/**
|
|
2521
|
+
* Add this field's path to the form's `blankPaths` set,
|
|
2522
|
+
* writing the slim default to storage. Returns the `setValueAtPath`
|
|
2523
|
+
* boolean (`true` accepted, `false` rejected by the slim-primitive
|
|
2524
|
+
* gate). Inherits the binding's `persist` meta so the mark rides
|
|
2525
|
+
* the same persistence channel as user-typed writes.
|
|
2526
|
+
*
|
|
2527
|
+
* Called by the directive's input listener on numeric clear (commit
|
|
2528
|
+
* 5) and by the imperative `setValue(path, unset)` translation
|
|
2529
|
+
* (commit 7). Don't call from consumer code.
|
|
2530
|
+
* @internal
|
|
2531
|
+
*/
|
|
2532
|
+
markBlank: () => boolean;
|
|
2533
|
+
/**
|
|
2534
|
+
* `true` when the schema's slim primitive set at this path includes
|
|
2535
|
+
* `'undefined'` — i.e. the leaf was declared `.optional()` (or as
|
|
2536
|
+
* part of a union admitting `undefined`). Cached at register-time.
|
|
2537
|
+
*
|
|
2538
|
+
* Read by the directive's text-input listener to map a DOM clear
|
|
2539
|
+
* (`el.value === ''`) onto `undefined` storage instead of `''`, so
|
|
2540
|
+
* the `.optional()` "absent" semantic survives user interactions.
|
|
2541
|
+
* Without this, a user who typed an invalid value into an optional
|
|
2542
|
+
* field and then cleared it would be stuck with a permanent
|
|
2543
|
+
* validation error (storage holds `''`, which is neither
|
|
2544
|
+
* `undefined` nor a valid inner value).
|
|
2545
|
+
* @internal
|
|
2546
|
+
*/
|
|
2547
|
+
acceptsUndefined: boolean;
|
|
2548
|
+
/**
|
|
2549
|
+
* `true` when the schema's slim primitive set at this path includes
|
|
2550
|
+
* `'string'`. Cached at register-time alongside
|
|
2551
|
+
* [[acceptsUndefined]].
|
|
2552
|
+
*
|
|
2553
|
+
* Read by the directive's text-input listener so a DOM clear on a
|
|
2554
|
+
* numeric-only (or boolean-only, bigint-only) leaf takes the
|
|
2555
|
+
* `markBlank` path instead of writing `''` through the assigner:
|
|
2556
|
+
* the slim-primitive gate would reject the empty string anyway,
|
|
2557
|
+
* and the directive's post-write force-sync would then snap the
|
|
2558
|
+
* DOM back to the last accepted value, making the final character
|
|
2559
|
+
* undeletable. With `markBlank`, storage holds the slim default
|
|
2560
|
+
* with the blank meta and the DOM stays empty.
|
|
2561
|
+
* @internal
|
|
2562
|
+
*/
|
|
2563
|
+
acceptsString: boolean;
|
|
2275
2564
|
}>;
|
|
2276
2565
|
/**
|
|
2277
2566
|
* Custom assigner installed on an element via the directive's
|
|
@@ -2444,6 +2733,48 @@ type RegisterModelDynamicCustomDirective = ObjectDirective<HTMLInputElement | HT
|
|
|
2444
2733
|
* directives manually.
|
|
2445
2734
|
*/
|
|
2446
2735
|
type RegisterDirective = RegisterTextCustomDirective | RegisterCheckboxCustomDirective | RegisterSelectCustomDirective | RegisterRadioCustomDirective | RegisterModelDynamicCustomDirective;
|
|
2736
|
+
/**
|
|
2737
|
+
* Module augmentation: register `v-register` with Vue's template
|
|
2738
|
+
* type system. Lives in `types-api` because every public entry
|
|
2739
|
+
* (`attaform`, `attaform/zod`, `attaform/zod-v3`, `attaform/zod-v4`)
|
|
2740
|
+
* transitively reaches this file via the `useForm` return type, so
|
|
2741
|
+
* the augmentation propagates to consumer SFCs regardless of which
|
|
2742
|
+
* entry they import from — and regardless of whether they install
|
|
2743
|
+
* `attaform/nuxt` or the Vite plugin.
|
|
2744
|
+
*
|
|
2745
|
+
* Augmentation targets `vue` rather than `@vue/runtime-core`:
|
|
2746
|
+
* `GlobalDirectives` is originally declared in `@vue/runtime-core`,
|
|
2747
|
+
* but consumers and Volar's strict-template codegen both resolve
|
|
2748
|
+
* the interface through `vue`'s `export * from '@vue/runtime-dom'`
|
|
2749
|
+
* → `export * from '@vue/runtime-core'` chain. TypeScript merges
|
|
2750
|
+
* interfaces across re-exports, so augmenting `'vue'` reaches Volar
|
|
2751
|
+
* without needing `@vue/runtime-core` to be hoisted into the
|
|
2752
|
+
* library's own `node_modules` for its own typecheck.
|
|
2753
|
+
*/
|
|
2754
|
+
declare module 'vue' {
|
|
2755
|
+
interface GlobalDirectives {
|
|
2756
|
+
/**
|
|
2757
|
+
* The `v-register` directive. Binds a form field to a native
|
|
2758
|
+
* input, select, textarea, checkbox, or radio:
|
|
2759
|
+
*
|
|
2760
|
+
* ```vue
|
|
2761
|
+
* <input v-register="form.register('email')" />
|
|
2762
|
+
* ```
|
|
2763
|
+
*
|
|
2764
|
+
* Also works on custom components whose root is NOT a native
|
|
2765
|
+
* input — call `useRegister()` in the child's setup to read the
|
|
2766
|
+
* parent's binding, then re-bind `v-register` onto an inner
|
|
2767
|
+
* native element. (When the wrapper's root IS the input itself,
|
|
2768
|
+
* attribute fallthrough handles it; `useRegister` is unnecessary.)
|
|
2769
|
+
*
|
|
2770
|
+
* Modifier support varies by element:
|
|
2771
|
+
* - text / number / textarea: `.lazy`, `.trim`, `.number`
|
|
2772
|
+
* - select: `.number`
|
|
2773
|
+
* - checkbox / radio: none
|
|
2774
|
+
*/
|
|
2775
|
+
vRegister: RegisterDirective;
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2447
2778
|
/**
|
|
2448
2779
|
* Callback form of `setValue`'s value argument. Receives the previous
|
|
2449
2780
|
* value at the path and returns the next value:
|
|
@@ -2544,7 +2875,7 @@ type FieldState<Value = unknown> = {
|
|
|
2544
2875
|
readonly dirty: boolean;
|
|
2545
2876
|
readonly focused: boolean | null;
|
|
2546
2877
|
readonly blurred: boolean | null;
|
|
2547
|
-
readonly touched: boolean
|
|
2878
|
+
readonly touched: boolean;
|
|
2548
2879
|
readonly connected: boolean;
|
|
2549
2880
|
/**
|
|
2550
2881
|
* The first DOM element bound to this path via `v-register`, or
|
|
@@ -2732,15 +3063,89 @@ type FieldState<Value = unknown> = {
|
|
|
2732
3063
|
* `FieldState<number | undefined>`. Matches the runtime's stub
|
|
2733
3064
|
* `FieldState` for inactive-variant paths.
|
|
2734
3065
|
*/
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
3066
|
+
/**
|
|
3067
|
+
* Leaf-shape dispatch table for `LeafWalker`. Each entry maps a walker
|
|
3068
|
+
* kind to the leaf type that walker produces at primitive / Date /
|
|
3069
|
+
* non-recursable positions. The lookup `LeafSchemeFor<T>[Kind]`
|
|
3070
|
+
* threads `T` through the leaf type when the kind needs it
|
|
3071
|
+
* (`field` carries `FieldState<T>`); kinds that don't depend on
|
|
3072
|
+
* `T` simply ignore it (`errors` always produces
|
|
3073
|
+
* `readonly ValidationError[] | undefined`).
|
|
3074
|
+
*
|
|
3075
|
+
* Adding a new walker is one entry here plus a one-line wrapper
|
|
3076
|
+
* alias (`type FooShape<T> = LeafWalker<T, 'foo'>`). The walker
|
|
3077
|
+
* topology is shared; only the leaf changes.
|
|
3078
|
+
*
|
|
3079
|
+
* The `errors` entry threads `T` to preserve `| undefined` when the
|
|
3080
|
+
* value type itself includes undefined (DU variant-only fields whose
|
|
3081
|
+
* lifted shape resolves to `X | undefined`). Statically-known leaves
|
|
3082
|
+
* collapse to `readonly ValidationError[]` (no undefined); dynamic-key
|
|
3083
|
+
* boundaries (array indices, record keys) re-introduce `| undefined`
|
|
3084
|
+
* via the structural index-signature channels.
|
|
3085
|
+
*
|
|
3086
|
+
* Preprocess / coerce leaves (StorageShape = `unknown`) are
|
|
3087
|
+
* statically known too — the IsUnknown filter keeps them on the
|
|
3088
|
+
* non-optional branch instead of being swept into the dynamic
|
|
3089
|
+
* `| undefined` arm by `undefined extends unknown`.
|
|
3090
|
+
*
|
|
3091
|
+
* Implementation-detail surface — consumers reach for `FieldStateMap`
|
|
3092
|
+
* or `FormErrorsSurface` instead.
|
|
3093
|
+
*/
|
|
3094
|
+
type IsUnknown<T> = IsAny<T> extends true ? false : unknown extends T ? true : false;
|
|
3095
|
+
interface LeafSchemeFor<T> {
|
|
3096
|
+
field: FieldState<T>;
|
|
3097
|
+
errors: IsUnknown<T> extends true ? readonly ValidationError[] : undefined extends T ? readonly ValidationError[] | undefined : readonly ValidationError[];
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Generic walk that produces a proxy shape over `T` with leaves
|
|
3101
|
+
* dispatched via `LeafSchemeFor<T>[Kind]`. The walk topology
|
|
3102
|
+
* (object → mapped homomorphic, object-union → KeyofUnion merge,
|
|
3103
|
+
* array → indexed, primitive / Date → terminal) is identical
|
|
3104
|
+
* across walker kinds; only the leaf type differs.
|
|
3105
|
+
*
|
|
3106
|
+
* Replaces the duplicated bodies of `FieldStateMapEntry` and
|
|
3107
|
+
* `ErrorsProxyShape`. The prior duplication walked the same shape
|
|
3108
|
+
* twice per useForm return type — once for the fields proxy, once
|
|
3109
|
+
* for the errors proxy. Factoring lets the bundled `.d.ts` carry one
|
|
3110
|
+
* shared walker body plus per-kind one-line wrappers, halving the
|
|
3111
|
+
* recursive depth contribution from these two proxies on consumer
|
|
3112
|
+
* call sites.
|
|
3113
|
+
*
|
|
3114
|
+
* `StripOptional` controls whether optional modifiers on input
|
|
3115
|
+
* properties are stripped at every recursion level. `true` (default)
|
|
3116
|
+
* matches the fields proxy semantics — every known leaf carries a
|
|
3117
|
+
* `FieldState` wrapper regardless of source `?`. `false` matches the
|
|
3118
|
+
* errors proxy semantics — the proxy shape stays structurally
|
|
3119
|
+
* identical to the input form, optional keys included.
|
|
3120
|
+
*
|
|
3121
|
+
* Implementation-detail surface — consumers reach for `FieldStateMap`
|
|
3122
|
+
* or `FormErrorsSurface` instead.
|
|
3123
|
+
*/
|
|
3124
|
+
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>] ? {
|
|
3125
|
+
readonly [K: number]: LeafWalker<U, Kind, StripOptional>;
|
|
3126
|
+
} & ContainerSelfErrorsSlot<T, Kind> : [T] extends [object] ? [IsUnion<T>] extends [true] ? StripOptional extends true ? {
|
|
3127
|
+
readonly [K in KeyofUnion<T>]-?: LeafWalker<ValueOfUnion<T, K>, Kind, StripOptional>;
|
|
3128
|
+
} & ContainerSelfErrorsSlot<T, Kind> : {
|
|
3129
|
+
readonly [K in KeyofUnion<T>]: LeafWalker<ValueOfUnion<T, K>, Kind, StripOptional>;
|
|
3130
|
+
} & ContainerSelfErrorsSlot<T, Kind> : StripOptional extends true ? {
|
|
3131
|
+
readonly [K in keyof T]-?: LeafWalker<T[K], Kind, StripOptional>;
|
|
3132
|
+
} & ContainerSelfErrorsSlot<T, Kind> : {
|
|
3133
|
+
readonly [K in keyof T]: LeafWalker<T[K], Kind, StripOptional>;
|
|
3134
|
+
} & ContainerSelfErrorsSlot<T, Kind> : LeafSchemeFor<T>[Kind];
|
|
3135
|
+
/**
|
|
3136
|
+
* Intersection augmenting every container in the `form.errors` walker
|
|
3137
|
+
* with a `''` sentinel slot — the per-container home for cross-field
|
|
3138
|
+
* refine errors, server-side container marks, and (at root) form-level
|
|
3139
|
+
* errors. Gated on `Kind extends 'errors'` so `form.values` and
|
|
3140
|
+
* `form.fields` surfaces stay untouched. Carve-out for schemas that
|
|
3141
|
+
* legitimately declare a `''` field: the declared field type wins; at
|
|
3142
|
+
* runtime the two collide harmlessly (errors at the literal leaf and
|
|
3143
|
+
* any container-self errors share the slot via array concat).
|
|
3144
|
+
*/
|
|
3145
|
+
type ContainerSelfErrorsSlot<T, Kind> = Kind extends 'errors' ? '' extends keyof T ? unknown : {
|
|
3146
|
+
readonly ['']: readonly ValidationError[];
|
|
3147
|
+
} : unknown;
|
|
3148
|
+
type FieldStateMapEntry<T> = LeafWalker<T, 'field'>;
|
|
2744
3149
|
/**
|
|
2745
3150
|
* Type of `form.fields` — leaf-aware drillable callable Proxy. At
|
|
2746
3151
|
* a leaf path the proxy resolves to a `FieldState<Value>`; at
|
|
@@ -2803,14 +3208,18 @@ type FieldStateMap<Form extends GenericForm> = ([IsUnion<Form>] extends [true] ?
|
|
|
2803
3208
|
type FormErrorRecord = Record<string, ValidationError[]>;
|
|
2804
3209
|
/**
|
|
2805
3210
|
* Type of `form.errors`. Leaf-aware drillable callable Proxy. At a
|
|
2806
|
-
* leaf
|
|
2807
|
-
*
|
|
3211
|
+
* statically-known leaf the proxy resolves to `readonly ValidationError[]`
|
|
3212
|
+
* (empty array when no errors land); at dynamic boundaries (array
|
|
3213
|
+
* indices, record keys, DU variant-only fields) it resolves to
|
|
3214
|
+
* `readonly ValidationError[] | undefined`. At a container path it
|
|
3215
|
+
* returns a sub-proxy you can keep drilling.
|
|
2808
3216
|
*
|
|
2809
3217
|
* Dot/bracket access mirrors the schema shape:
|
|
2810
3218
|
*
|
|
2811
3219
|
* ```ts
|
|
2812
|
-
* form.errors.email // ValidationError[]
|
|
2813
|
-
* form.errors.user.profile.email // ValidationError[]
|
|
3220
|
+
* form.errors.email // readonly ValidationError[] (static leaf)
|
|
3221
|
+
* form.errors.user.profile.email // readonly ValidationError[] (chained static leaves)
|
|
3222
|
+
* form.errors.posts[3]?.title // readonly ValidationError[] | undefined (past array boundary)
|
|
2814
3223
|
* form.errors.address // sub-proxy (container — descend further)
|
|
2815
3224
|
* ```
|
|
2816
3225
|
*
|
|
@@ -2829,38 +3238,47 @@ type FormErrorRecord = Record<string, ValidationError[]>;
|
|
|
2829
3238
|
*/
|
|
2830
3239
|
/**
|
|
2831
3240
|
* Recursive shape of the `form.errors` proxy. Mirrors the schema:
|
|
2832
|
-
* primitive leaves expose `ValidationError[]
|
|
2833
|
-
*
|
|
2834
|
-
*
|
|
3241
|
+
* statically-known primitive leaves expose `readonly ValidationError[]`
|
|
3242
|
+
* (always an array; empty when no errors); leaves whose value type
|
|
3243
|
+
* itself includes `undefined` (DU variant-only fields) keep the
|
|
3244
|
+
* `| undefined` branch. Containers expose a sub-shape you can keep
|
|
3245
|
+
* drilling. Arrays expose numeric-indexed sub-shapes; reading a
|
|
3246
|
+
* numeric index introduces `| undefined` via noUncheckedIndexedAccess.
|
|
2835
3247
|
*
|
|
2836
3248
|
* Augmented with the callable signatures so dot-access and function-
|
|
2837
3249
|
* call coexist on the same identifier.
|
|
2838
3250
|
*/
|
|
2839
3251
|
type FormErrorsSurface<Form> = ErrorsProxyShape<Form> & {
|
|
2840
|
-
(path: string): readonly ValidationError[]
|
|
3252
|
+
(path: string): readonly ValidationError[];
|
|
2841
3253
|
/**
|
|
2842
3254
|
* Tuple-segment form. Validated against `FlatPath<Form>` so literal
|
|
2843
3255
|
* tuples that don't resolve to a known path fail at the call site.
|
|
2844
3256
|
* Dynamic `Path`-typed inputs hit the untyped fallback overload below.
|
|
2845
3257
|
*/
|
|
2846
|
-
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): readonly ValidationError[]
|
|
2847
|
-
(segments: ReadonlyArray<string | number>): readonly ValidationError[]
|
|
3258
|
+
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): readonly ValidationError[];
|
|
3259
|
+
(segments: ReadonlyArray<string | number>): readonly ValidationError[];
|
|
2848
3260
|
/**
|
|
2849
3261
|
* No-arg call returns the form-level error aggregate — same as
|
|
2850
|
-
* `form.errors([])` and `form.meta.errors`.
|
|
2851
|
-
* form has no errors
|
|
3262
|
+
* `form.errors([])` and `form.meta.errors`. Always a readonly array;
|
|
3263
|
+
* empty when the form has no errors.
|
|
2852
3264
|
*/
|
|
2853
|
-
(): readonly ValidationError[]
|
|
3265
|
+
(): readonly ValidationError[];
|
|
2854
3266
|
};
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
3267
|
+
/**
|
|
3268
|
+
* Implementation-detail walker backing `form.errors` typed proxy.
|
|
3269
|
+
* Thin alias over `LeafWalker<T, 'errors', false>` — the shared walker
|
|
3270
|
+
* topology is defined once at `LeafWalker` and parameterized via
|
|
3271
|
+
* `LeafSchemeFor`. `false` preserves optional-key modifiers (errors
|
|
3272
|
+
* proxy mirrors the input shape including `?`); contrast with the
|
|
3273
|
+
* fields proxy alias which strips them via the default `true`.
|
|
3274
|
+
*
|
|
3275
|
+
* Exported so the bundled `.d.ts` references a single alias body
|
|
3276
|
+
* rather than re-emitting the full union-aware recursion at every
|
|
3277
|
+
* consumer call site that types `form.errors`. Multiple useForm
|
|
3278
|
+
* instances in one scope otherwise compound this into TS2589
|
|
3279
|
+
* territory. Consumers should reach for `FormErrorsSurface` instead.
|
|
3280
|
+
*/
|
|
3281
|
+
type ErrorsProxyShape<T> = LeafWalker<T, 'errors', false>;
|
|
2864
3282
|
/**
|
|
2865
3283
|
* Type of `form.values`. Drillable readonly callable proxy. Unlike
|
|
2866
3284
|
* `form.errors` and `form.fields`, containers are USEFUL terminals:
|
|
@@ -2994,7 +3412,31 @@ type FormMeta<F = unknown> = FieldState<F> & {
|
|
|
2994
3412
|
* outcome (validation failure, callback success, callback throw).
|
|
2995
3413
|
* Useful for "show errors after first submit attempt" UX.
|
|
2996
3414
|
*/
|
|
2997
|
-
readonly
|
|
3415
|
+
readonly submissionAttempts: number;
|
|
3416
|
+
/**
|
|
3417
|
+
* How many times wizard navigation (`wizard.next`, `wizard.back`,
|
|
3418
|
+
* `wizard.goTo`) has actually departed this form. Bumped on real
|
|
3419
|
+
* departures only: no-ops like `back()` from the first step, a
|
|
3420
|
+
* same-key `goTo`, or a `next()` blocked by failed activation leave
|
|
3421
|
+
* the counter at its prior value.
|
|
3422
|
+
*
|
|
3423
|
+
* Pure introspection counter — useful for "this form has been
|
|
3424
|
+
* visited and left" UX (analytics, prior-step badges, layered
|
|
3425
|
+
* `shouldShowErrors` predicates) but does NOT drive the library's
|
|
3426
|
+
* default `shouldShowErrors` heuristic. The reveal-on-submit story
|
|
3427
|
+
* runs entirely through `submissionAttempts`, which
|
|
3428
|
+
* `wizard.handleSubmit` bumps on the active form at intermediate
|
|
3429
|
+
* steps and on every form at the final step.
|
|
3430
|
+
*
|
|
3431
|
+
* Distinct from `submissionAttempts`, which counts `handleSubmit`
|
|
3432
|
+
* passes only — wizard departures and form submissions are tracked
|
|
3433
|
+
* separately so consumers can introspect each cleanly. Distinct
|
|
3434
|
+
* from `form.validate()`, which is a read-only inspection primitive
|
|
3435
|
+
* that never bumps any counter.
|
|
3436
|
+
*
|
|
3437
|
+
* Cleared by `form.reset()`.
|
|
3438
|
+
*/
|
|
3439
|
+
readonly departAttempts: number;
|
|
2998
3440
|
/**
|
|
2999
3441
|
* The error thrown or rejected by the most recent submit callback
|
|
3000
3442
|
* (or its `onError` handler). Cleared to `null` at the start of
|
|
@@ -3005,6 +3447,27 @@ type FormMeta<F = unknown> = FieldState<F> & {
|
|
|
3005
3447
|
* `try { await onSubmit() }` instead.
|
|
3006
3448
|
*/
|
|
3007
3449
|
readonly submitError: unknown;
|
|
3450
|
+
/**
|
|
3451
|
+
* Scalar mirror of `meta.errors.length`. Read it from templates and
|
|
3452
|
+
* `watch()` without indexing the underlying array.
|
|
3453
|
+
*
|
|
3454
|
+
* Always tracks `errors.length` exactly — reactivity is wired through
|
|
3455
|
+
* the same computed graph, so a `watch(form.meta.errorCount, ...)`
|
|
3456
|
+
* fires when (and only when) the aggregate error count changes.
|
|
3457
|
+
*/
|
|
3458
|
+
readonly errorCount: number;
|
|
3459
|
+
/**
|
|
3460
|
+
* `true` once a `handleSubmit` callback has resolved without
|
|
3461
|
+
* throwing. Independent of `submissionAttempts` — a failed submit
|
|
3462
|
+
* (validation failure or callback rejection) increments attempts but
|
|
3463
|
+
* leaves `submitted` at `false`. Templates read it as "the form has
|
|
3464
|
+
* been submitted successfully at least once."
|
|
3465
|
+
*
|
|
3466
|
+
* Cleared by `form.reset()` alongside `submissionAttempts` and
|
|
3467
|
+
* `submitError`. For "the user has attempted a submit," read
|
|
3468
|
+
* `submissionAttempts > 0` directly.
|
|
3469
|
+
*/
|
|
3470
|
+
readonly submitted: boolean;
|
|
3008
3471
|
/**
|
|
3009
3472
|
* Per-`useForm()`-call identity. Stable for the lifetime of one
|
|
3010
3473
|
* `useForm()` call; new on every fresh mount. Orthogonal to
|
|
@@ -3038,7 +3501,7 @@ type FormMeta<F = unknown> = FieldState<F> & {
|
|
|
3038
3501
|
* form.register('email') // bind to <input v-register>
|
|
3039
3502
|
* form.values.email // current value (proxy, no .value)
|
|
3040
3503
|
* form.fields.email.dirty // per-field flags
|
|
3041
|
-
* form.errors.email // ValidationError[]
|
|
3504
|
+
* form.errors.email // readonly ValidationError[]
|
|
3042
3505
|
* form.setValue('email', 'a@b.c')
|
|
3043
3506
|
* form.handleSubmit(onSubmit) // returns a submit handler
|
|
3044
3507
|
* form.meta.submitting // form-level reactive flag
|
|
@@ -3067,7 +3530,45 @@ type FormMeta<F = unknown> = FieldState<F> & {
|
|
|
3067
3530
|
* For schemas without write-boundary wrappers or transforms the three
|
|
3068
3531
|
* shapes coincide.
|
|
3069
3532
|
*/
|
|
3070
|
-
|
|
3533
|
+
/**
|
|
3534
|
+
* Read-only view returned by `form.blankPaths.value`. Exposes lookup
|
|
3535
|
+
* (`.has`), aggregate (`.size`), and iteration over the form's
|
|
3536
|
+
* blank-marked paths.
|
|
3537
|
+
*
|
|
3538
|
+
* `.has(input)` and the iterator yield consistent results across both
|
|
3539
|
+
* input conventions the library accepts everywhere a path is named:
|
|
3540
|
+
*
|
|
3541
|
+
* - **Dotted string**: `'user.email'`, matching what `register('user.email')`
|
|
3542
|
+
* or `setValue('items.0.sku', …)` accept. Convenient when no segment
|
|
3543
|
+
* contains a literal dot.
|
|
3544
|
+
* - **Array form**: `['user', 'email']`, mirroring `register(['user', 'email'])`.
|
|
3545
|
+
* Required when a single segment contains literal dots (e.g.
|
|
3546
|
+
* `['address.primary']` for a top-level key named `address.primary` —
|
|
3547
|
+
* the dotted form `'address.primary'` would be parsed as two
|
|
3548
|
+
* segments).
|
|
3549
|
+
*
|
|
3550
|
+
* Iteration yields `Path` arrays so the structure is unambiguous —
|
|
3551
|
+
* consumers building debug UI or persisting the set never have to guess
|
|
3552
|
+
* whether a dot in a segment is a separator or part of the name.
|
|
3553
|
+
*
|
|
3554
|
+
* Mutating the view does nothing — writes still go through
|
|
3555
|
+
* `setValue(path, unset)`, `markBlank()` on a register binding, or the
|
|
3556
|
+
* directive's input listener on numeric clear.
|
|
3557
|
+
*/
|
|
3558
|
+
interface BlankPathsView {
|
|
3559
|
+
/** Number of blank-marked paths. */
|
|
3560
|
+
readonly size: number;
|
|
3561
|
+
/**
|
|
3562
|
+
* `true` when the path is in the blank set. Accepts dotted-string
|
|
3563
|
+
* form (parsed by [[parseDottedPath]]) or the array form.
|
|
3564
|
+
*/
|
|
3565
|
+
has(input: string | Path): boolean;
|
|
3566
|
+
/** Snapshot of all blank-marked paths as segment arrays. */
|
|
3567
|
+
values(): readonly Path[];
|
|
3568
|
+
/** Iterates the blank-marked paths as segment arrays. */
|
|
3569
|
+
[Symbol.iterator](): IterableIterator<Path>;
|
|
3570
|
+
}
|
|
3571
|
+
type UseFormReturnType<Form extends GenericForm, GetValueFormType extends GenericForm = Form, ReadForm extends GenericForm = Form, K extends FormKey = FormKey> = {
|
|
3071
3572
|
/**
|
|
3072
3573
|
* Wraps your submit logic with validation and error routing.
|
|
3073
3574
|
*
|
|
@@ -3306,8 +3807,96 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
3306
3807
|
* const result = parseApiErrors(serverPayload, { formKey: form.key })
|
|
3307
3808
|
* if (result.ok) form.setFieldErrors(result.errors)
|
|
3308
3809
|
* ```
|
|
3810
|
+
*
|
|
3811
|
+
* Typed as the literal `K` when an explicit `key` was passed; falls
|
|
3812
|
+
* back to `FormKey` when omitted (auto-generated id).
|
|
3813
|
+
*/
|
|
3814
|
+
key: K;
|
|
3815
|
+
/**
|
|
3816
|
+
* `true` while a function-form `defaultValues` factory is in flight
|
|
3817
|
+
* — between `useForm` construction and the moment the factory
|
|
3818
|
+
* resolves (sync function on the next microtask; async function when
|
|
3819
|
+
* its promise settles). `false` otherwise, including when
|
|
3820
|
+
* `defaultValues` is a plain value.
|
|
3821
|
+
*
|
|
3822
|
+
* The form is fully usable while `hydrating` is `true` — it holds
|
|
3823
|
+
* the schema's slim defaults. The flag exists so templates can show
|
|
3824
|
+
* a spinner / dim the form while real data loads:
|
|
3825
|
+
*
|
|
3826
|
+
* ```vue
|
|
3827
|
+
* <div :aria-busy="form.hydrating">…</div>
|
|
3828
|
+
* ```
|
|
3829
|
+
*
|
|
3830
|
+
* Exposed as an auto-unwrapping `boolean` (no `.value`); reactivity
|
|
3831
|
+
* is preserved via a getter that tracks the underlying ref at the
|
|
3832
|
+
* access site, so `watch(() => form.hydrating, …)` and template
|
|
3833
|
+
* reads both fire on change. Reading this property activates the
|
|
3834
|
+
* form's factory under the lazy-by-default rule.
|
|
3835
|
+
*/
|
|
3836
|
+
readonly hydrating: boolean;
|
|
3837
|
+
/**
|
|
3838
|
+
* The error from the most recent function-form `defaultValues` factory,
|
|
3839
|
+
* normalized to a `ValidationError` (code `atta:hydration-failed`) so the
|
|
3840
|
+
* shape matches every other surface in `form.errors` / `form.meta.errors`.
|
|
3841
|
+
* `null` on construction, on successful resolution, and whenever no
|
|
3842
|
+
* factory has fired. Updates with each `form.rehydrate()` call.
|
|
3843
|
+
*
|
|
3844
|
+
* Distinct from `meta.submitError` so retry buttons and recovery UX can
|
|
3845
|
+
* stay focused on the load-time failure without entangling the submit
|
|
3846
|
+
* pipeline. Read directly in templates and script (no `.value`);
|
|
3847
|
+
* reactivity is preserved via a getter:
|
|
3848
|
+
*
|
|
3849
|
+
* ```vue
|
|
3850
|
+
* <p v-if="form.hydrateError">{{ form.hydrateError.message }}</p>
|
|
3851
|
+
* ```
|
|
3309
3852
|
*/
|
|
3310
|
-
|
|
3853
|
+
readonly hydrateError: ValidationError | null;
|
|
3854
|
+
/**
|
|
3855
|
+
* `true` once the form's defaults have been applied — either a plain
|
|
3856
|
+
* `defaultValues` value at construction or an async factory whose
|
|
3857
|
+
* settle completed successfully. Stays `false` for dormant lazy
|
|
3858
|
+
* forms (factory not yet activated) and for failed activations
|
|
3859
|
+
* (`hydrateError` set). Once `true`, stays `true` through refetches
|
|
3860
|
+
* so stale-while-revalidate UIs can keep rendering the prior values
|
|
3861
|
+
* while a `rehydrate()` is in flight.
|
|
3862
|
+
*
|
|
3863
|
+
* Composes with `hydrating` and `hydrateError`:
|
|
3864
|
+
*
|
|
3865
|
+
* ```vue
|
|
3866
|
+
* <Spinner v-if="!form.ready && form.hydrating" />
|
|
3867
|
+
* <ErrorBanner v-if="!form.ready && form.hydrateError" :error="form.hydrateError" />
|
|
3868
|
+
* <form v-if="form.ready">…</form>
|
|
3869
|
+
* ```
|
|
3870
|
+
*
|
|
3871
|
+
* Exposed as a reactive `boolean` (no `.value`). Reading it activates
|
|
3872
|
+
* the factory under the lazy-by-default rule — observing readiness
|
|
3873
|
+
* implies use.
|
|
3874
|
+
*/
|
|
3875
|
+
readonly ready: boolean;
|
|
3876
|
+
/**
|
|
3877
|
+
* Re-fire the captured `defaultValues` factory and re-apply its
|
|
3878
|
+
* payload over the current form values. Useful when the upstream
|
|
3879
|
+
* source changes (the user picks a different draft, a background
|
|
3880
|
+
* sync indicates fresh server data, etc.).
|
|
3881
|
+
*
|
|
3882
|
+
* Resolves after `hydrating` flips back to `false`. Throws
|
|
3883
|
+
* synchronously when the form was constructed with a plain-value
|
|
3884
|
+
* `defaultValues` (nothing to re-fire). Does NOT clear dirty /
|
|
3885
|
+
* touched / submit state — chain `form.reset()` for that.
|
|
3886
|
+
*/
|
|
3887
|
+
rehydrate(): Promise<void>;
|
|
3888
|
+
/**
|
|
3889
|
+
* Idempotent activation. Forms are lazy-by-default: a function-form
|
|
3890
|
+
* `defaultValues` factory fires on the first reactive interaction
|
|
3891
|
+
* (reading `form.values`, calling `form.setValue`, etc.). Call
|
|
3892
|
+
* `form.activate()` to kick the factory explicitly — typically from
|
|
3893
|
+
* `setup` so SSR's `onServerPrefetch` hook awaits the resolution
|
|
3894
|
+
* before the page renders. Subsequent calls return the in-flight
|
|
3895
|
+
* promise until the factory settles, after which they resolve
|
|
3896
|
+
* immediately. Plain-value forms (no factory captured) always
|
|
3897
|
+
* return a resolved promise.
|
|
3898
|
+
*/
|
|
3899
|
+
activate(): Promise<void>;
|
|
3311
3900
|
/**
|
|
3312
3901
|
* Reactive map of field errors, keyed by dotted path. Populated
|
|
3313
3902
|
* automatically by `handleSubmit` and per-field validation; cleared
|
|
@@ -3426,7 +4015,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
3426
4015
|
clearFormErrors: () => void;
|
|
3427
4016
|
/**
|
|
3428
4017
|
* Form-level reactive flags, counters, and aggregates (`dirty`,
|
|
3429
|
-
* `valid`, `submitting`, `
|
|
4018
|
+
* `valid`, `submitting`, `submissionAttempts`, and the flat `errors`
|
|
3430
4019
|
* array). See `FormMeta` for the full shape. Read leaves directly
|
|
3431
4020
|
* with no `.value`.
|
|
3432
4021
|
*
|
|
@@ -3446,13 +4035,14 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
3446
4035
|
* - the dirty baseline (so the next edit flips `dirty` correctly);
|
|
3447
4036
|
* - field errors;
|
|
3448
4037
|
* - touched / focused / blurred per-field flags;
|
|
3449
|
-
* - submission state (`submitting` / `
|
|
4038
|
+
* - submission state (`submitting` / `submissionAttempts` /
|
|
4039
|
+
* `submitted` / `submitError`);
|
|
3450
4040
|
* - the persisted draft, if persistence is configured.
|
|
3451
4041
|
*
|
|
3452
4042
|
* The next edit on a still-mounted opted-in input will start
|
|
3453
4043
|
* persisting again automatically.
|
|
3454
4044
|
*/
|
|
3455
|
-
reset: (nextDefaultValues?:
|
|
4045
|
+
reset: (nextDefaultValues?: DefaultValuesInput<Form>) => void;
|
|
3456
4046
|
/**
|
|
3457
4047
|
* Restore a single field (or a sub-tree like `'user'`) to its
|
|
3458
4048
|
* initial value. Clears errors and touched flags for the field
|
|
@@ -3565,6 +4155,21 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
3565
4155
|
* `options` is forwarded to `Element.scrollIntoView` unchanged.
|
|
3566
4156
|
*/
|
|
3567
4157
|
scrollToFirstError: (options?: ScrollIntoViewOptions) => boolean;
|
|
4158
|
+
/**
|
|
4159
|
+
* Drive the form's `onInvalidSubmit` policy imperatively. The same
|
|
4160
|
+
* focus/scroll behavior `handleSubmit` runs after a failed submit,
|
|
4161
|
+
* but available standalone. Defaults to the policy configured via
|
|
4162
|
+
* `useForm({ onInvalidSubmit })` (or `'focus-first-error'` when
|
|
4163
|
+
* omitted). Pass an explicit policy to override for one call.
|
|
4164
|
+
*
|
|
4165
|
+
* Used by `useWizard` after navigating to the first failing form
|
|
4166
|
+
* during `wizard.handleSubmit`, so the failing form's own configured
|
|
4167
|
+
* policy fires once its DOM is in view.
|
|
4168
|
+
*
|
|
4169
|
+
* No-op when no errored field is currently registered or when the
|
|
4170
|
+
* resolved policy is `'none'`.
|
|
4171
|
+
*/
|
|
4172
|
+
applyInvalidSubmitPolicy: (policy?: OnInvalidSubmitPolicy) => void;
|
|
3568
4173
|
/**
|
|
3569
4174
|
* Programmatically mark fields as touched — the sticky flag the
|
|
3570
4175
|
* standard "show errors after interaction" pattern reads. Closes
|
|
@@ -3613,28 +4218,31 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
3613
4218
|
/** Replace the element at `index` with `value`. No-op when out of range. */
|
|
3614
4219
|
replace: <Path extends ArrayPath<Form>>(path: Path, index: number, value: ArrayItem<Form, Path>) => void;
|
|
3615
4220
|
/**
|
|
3616
|
-
* Read-only view of the form's blank path set.
|
|
3617
|
-
*
|
|
3618
|
-
*
|
|
3619
|
-
* tracks `.has()` / `for..of` / size accesses, so consumers can
|
|
3620
|
-
* drive conditional UI off it directly:
|
|
4221
|
+
* Read-only view of the form's blank path set. Reactive — Vue 3.5
|
|
4222
|
+
* tracks `.has()` / `for..of` / size accesses, so consumers can drive
|
|
4223
|
+
* conditional UI off it directly:
|
|
3621
4224
|
*
|
|
3622
4225
|
* ```ts
|
|
3623
4226
|
* watchEffect(() => {
|
|
3624
4227
|
* if (form.blankPaths.value.size > 0) {
|
|
3625
|
-
*
|
|
4228
|
+
* const paths = [...form.blankPaths.value] // Path[][] — array of segments per entry
|
|
4229
|
+
* console.warn('unanswered fields:', paths.map((p) => p.join('.')))
|
|
3626
4230
|
* }
|
|
3627
4231
|
* })
|
|
3628
4232
|
* ```
|
|
3629
4233
|
*
|
|
4234
|
+
* `.has(input)` accepts the dotted-string form (`'user.email'`) or
|
|
4235
|
+
* the array form (`['user', 'email']`). The array form disambiguates
|
|
4236
|
+
* keys with literal dots (e.g. `['address.primary']`). See
|
|
4237
|
+
* [[BlankPathsView]] for the full surface.
|
|
4238
|
+
*
|
|
3630
4239
|
* For per-path access, use `form.fields.<path>.blank`.
|
|
3631
4240
|
* Writes happen through `setValue(path, unset)`,
|
|
3632
4241
|
* `markBlank()` on a register binding, and the directive's
|
|
3633
|
-
* input listener on numeric clear.
|
|
3634
|
-
* here does nothing — it's `Object.freeze`-d.
|
|
4242
|
+
* input listener on numeric clear.
|
|
3635
4243
|
*/
|
|
3636
|
-
blankPaths: ComputedRef<
|
|
4244
|
+
blankPaths: ComputedRef<BlankPathsView>;
|
|
3637
4245
|
};
|
|
3638
4246
|
|
|
3639
|
-
export {
|
|
3640
|
-
export type { AttaformDefaults as A,
|
|
4247
|
+
export { ROOT_PATH as a2, ROOT_PATH_KEY as a3, canonicalizePath as aq, isPathPrefix as ar, isUnset as as, parseDottedPath as at, unset as au };
|
|
4248
|
+
export type { PersistConfigOptions as $, AttaformDefaults as A, IsUnion as B, CoercionEntry as C, DefaultValuesInput as D, ErrorsProxyShape 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, NestedType as O, OnError as P, OnInvalidSubmitPolicy as Q, RegisterValue as R, Segment as S, OnSubmit as T, UseFormConfiguration as U, ValidationError as V, PartialFlatPath as W, Path as X, PathKey as Y, PendingValidationStatus as Z, PersistConfig as _, AbstractSchema as a, PersistIncludeMode as a0, Primitive as a1, ReactiveValidationStatus as a4, RegisterDirective as a5, RegisterFlatPath as a6, RegisterOptions as a7, RegisterSelectModifier as a8, RegisterTextModifier as a9, RegisterTransform as aa, SetValueCallback as ab, SetValuePayload as ac, SettledValidationStatus as ad, ShouldShowErrorsConfig as ae, SlimPrimitiveKind as af, SlimRuntimeOf as ag, SubmitHandler as ah, Unset as ai, ValidateOn as aj, ValidateOnConfig as ak, ValidationResponse as al, ValidationResponseWithoutValue as am, ValueOfUnion as an, WriteMeta as ao, WriteShape as ap, SchemaFactoryOptions as av, PersistOptInRegistry as aw, UseFormReturnType as b, RegisterModelDynamicCustomDirective as c, ShouldShowErrors as d, ApiErrorEnvelope as e, ApiErrorDetails as f, ApiErrorEntry as g, ArrayItem as h, ArrayPath as i, CoercionRegistry as j, CoercionResult as k, CustomDirectiveRegisterAssignerFn as l, DeepPartial as m, DefaultValuesResponse as n, DefaultValuesShape as o, FieldMetaPayload as p, FieldState as q, FieldStateMap as r, FieldStateMapEntry as s, FlatPath as t, FormErrorRecord as u, FormErrorsSurface as v, FormMeta as w, FormStorage as x, FormStorageKind as y, HistoryConfig as z };
|