attaform 0.16.4 → 0.17.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 +4 -2
- package/dist/chunks/devtools.cjs +19 -12
- package/dist/chunks/devtools.cjs.map +1 -1
- package/dist/chunks/devtools.mjs +19 -12
- package/dist/chunks/devtools.mjs.map +1 -1
- package/dist/chunks/indexeddb.cjs +1 -1
- package/dist/chunks/indexeddb.mjs +1 -1
- package/dist/chunks/local-storage.cjs +1 -1
- package/dist/chunks/local-storage.mjs +1 -1
- package/dist/chunks/session-storage.cjs +1 -1
- package/dist/chunks/session-storage.mjs +1 -1
- package/dist/index.cjs +26 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -8
- package/dist/index.d.mts +51 -8
- package/dist/index.d.ts +51 -8
- package/dist/index.mjs +28 -9
- package/dist/index.mjs.map +1 -1
- package/dist/nuxt.d.cts +1 -1
- package/dist/nuxt.d.mts +1 -1
- package/dist/nuxt.d.ts +1 -1
- package/dist/runtime/plugins/attaform.cjs +3 -3
- package/dist/runtime/plugins/attaform.cjs.map +1 -1
- package/dist/runtime/plugins/attaform.mjs +3 -3
- package/dist/runtime/plugins/attaform.mjs.map +1 -1
- package/dist/shared/{attaform.Dd_pWnmn.cjs → attaform.0Wg7UEeX.cjs} +51 -10
- package/dist/shared/attaform.0Wg7UEeX.cjs.map +1 -0
- package/dist/shared/attaform.AOgGyRoI.d.cts +65 -0
- package/dist/shared/{attaform.CCQkY4Ta.d.ts → attaform.B0zue7zt.d.ts} +1 -1
- package/dist/shared/{attaform.CIwZtbGV.cjs → attaform.BBM2muQ9.cjs} +2 -2
- package/dist/shared/{attaform.CIwZtbGV.cjs.map → attaform.BBM2muQ9.cjs.map} +1 -1
- package/dist/shared/{attaform.fegmBJaq.cjs → attaform.BFumZXY2.cjs} +1422 -389
- package/dist/shared/attaform.BFumZXY2.cjs.map +1 -0
- package/dist/shared/attaform.BQ-iGGWd.d.mts +65 -0
- package/dist/shared/{attaform.DyV1O4tI.mjs → attaform.BT55rDNN.mjs} +1423 -391
- package/dist/shared/attaform.BT55rDNN.mjs.map +1 -0
- package/dist/shared/{attaform.CU3JperC.d.cts → attaform.BYbsV2Wv.d.cts} +574 -132
- package/dist/shared/{attaform.CU3JperC.d.mts → attaform.BYbsV2Wv.d.mts} +574 -132
- package/dist/shared/{attaform.CU3JperC.d.ts → attaform.BYbsV2Wv.d.ts} +574 -132
- package/dist/shared/{attaform.keLBaHB6.cjs → attaform.C6_zOf8x.cjs} +228 -113
- package/dist/shared/attaform.C6_zOf8x.cjs.map +1 -0
- package/dist/shared/{attaform.CJttVxRj.cjs → attaform.C8LVFVVe.cjs} +2 -2
- package/dist/shared/{attaform.CJttVxRj.cjs.map → attaform.C8LVFVVe.cjs.map} +1 -1
- package/dist/shared/{attaform.BfMxsfmE.mjs → attaform.CIEQgJnM.mjs} +143 -78
- package/dist/shared/attaform.CIEQgJnM.mjs.map +1 -0
- package/dist/shared/attaform.CX9v2M8k.d.ts +65 -0
- package/dist/shared/{attaform.g7rfuXdz.mjs → attaform.Cj0pCNVn.mjs} +228 -113
- package/dist/shared/attaform.Cj0pCNVn.mjs.map +1 -0
- package/dist/shared/{attaform.CMRmwGDt.d.cts → attaform.ClfCi1i2.d.mts} +1 -1
- package/dist/shared/{attaform.UA19EF3J.mjs → attaform.D6Q5ZP8L.mjs} +51 -10
- package/dist/shared/attaform.D6Q5ZP8L.mjs.map +1 -0
- package/dist/shared/{attaform.CXMOheyZ.d.mts → attaform.D7lomopc.d.cts} +1 -1
- package/dist/shared/{attaform.rIRYSUI1.cjs → attaform.Dee2rU1P.cjs} +145 -77
- package/dist/shared/attaform.Dee2rU1P.cjs.map +1 -0
- package/dist/shared/{attaform.CINUMjPq.mjs → attaform.Vo-Kft0t.mjs} +2 -2
- package/dist/shared/{attaform.CINUMjPq.mjs.map → attaform.Vo-Kft0t.mjs.map} +1 -1
- package/dist/shared/{attaform.DZRj9s0s.mjs → attaform.h1sq3BFu.mjs} +2 -2
- package/dist/shared/{attaform.DZRj9s0s.mjs.map → attaform.h1sq3BFu.mjs.map} +1 -1
- package/dist/zod-v3.cjs +3 -3
- package/dist/zod-v3.d.cts +5 -5
- package/dist/zod-v3.d.mts +5 -5
- package/dist/zod-v3.d.ts +5 -5
- package/dist/zod-v3.mjs +3 -3
- package/dist/zod-v4.cjs +3 -3
- package/dist/zod-v4.d.cts +16 -42
- package/dist/zod-v4.d.mts +16 -42
- package/dist/zod-v4.d.ts +16 -42
- package/dist/zod-v4.mjs +3 -3
- package/dist/zod.cjs +4 -4
- package/dist/zod.cjs.map +1 -1
- package/dist/zod.d.cts +6 -5
- package/dist/zod.d.mts +6 -5
- package/dist/zod.d.ts +6 -5
- package/dist/zod.mjs +5 -5
- package/dist/zod.mjs.map +1 -1
- package/package.json +3 -8
- package/dist/shared/attaform.BfMxsfmE.mjs.map +0 -1
- package/dist/shared/attaform.Dd_pWnmn.cjs.map +0 -1
- package/dist/shared/attaform.DyV1O4tI.mjs.map +0 -1
- package/dist/shared/attaform.UA19EF3J.mjs.map +0 -1
- package/dist/shared/attaform.fegmBJaq.cjs.map +0 -1
- package/dist/shared/attaform.g7rfuXdz.mjs.map +0 -1
- package/dist/shared/attaform.keLBaHB6.cjs.map +0 -1
- package/dist/shared/attaform.rIRYSUI1.cjs.map +0 -1
|
@@ -73,87 +73,6 @@ type ResolvedFieldMeta = {
|
|
|
73
73
|
readonly meta: Readonly<FieldMetaPayload>;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
/**
|
|
77
|
-
* Path primitives for advanced integrations. The form library accepts
|
|
78
|
-
* paths in dotted-string form (`'user.email'`) at every public API.
|
|
79
|
-
* These primitives are exposed for adapter authors who need to
|
|
80
|
-
* canonicalise user-provided paths.
|
|
81
|
-
*/
|
|
82
|
-
declare const pathKeyBrand: unique symbol;
|
|
83
|
-
/**
|
|
84
|
-
* Branded string identifier for a canonicalised path. Useful as a
|
|
85
|
-
* `Map` key — two paths that resolve to the same canonical form
|
|
86
|
-
* produce the same `PathKey`. Treat as opaque; don't try to parse.
|
|
87
|
-
*/
|
|
88
|
-
type PathKey = string & {
|
|
89
|
-
readonly [pathKeyBrand]: 'PathKey';
|
|
90
|
-
};
|
|
91
|
-
/** A single path segment — a property name or array index. */
|
|
92
|
-
type Segment = string | number;
|
|
93
|
-
/** A structured path as a read-only sequence of segments. */
|
|
94
|
-
type Path = readonly Segment[];
|
|
95
|
-
/**
|
|
96
|
-
* Parse a dotted-string path into structured segments.
|
|
97
|
-
*
|
|
98
|
-
* ```ts
|
|
99
|
-
* parseDottedPath('user.address.line1') // ['user', 'address', 'line1']
|
|
100
|
-
* parseDottedPath('items.0.name') // ['items', 0, 'name']
|
|
101
|
-
* parseDottedPath('') // [''] (the empty-string key)
|
|
102
|
-
* ```
|
|
103
|
-
*
|
|
104
|
-
* The empty-string input `''` is the **literal empty-key path**, not
|
|
105
|
-
* the root. Use the array form `[]` for root. Form-level errors
|
|
106
|
-
* (root `.refine()`) live at the empty-string path bucket so
|
|
107
|
-
* `errors('')` returns them without sweeping every field error too.
|
|
108
|
-
*
|
|
109
|
-
* Throws `InvalidPathError` for paths with empty INTERNAL segments
|
|
110
|
-
* (`'a..b'`, leading or trailing dots). For keys containing literal
|
|
111
|
-
* dots, pass an array form (`['user.name']`) instead.
|
|
112
|
-
*/
|
|
113
|
-
declare function parseDottedPath(path: string): Segment[];
|
|
114
|
-
/**
|
|
115
|
-
* Canonicalise a path into structured segments plus a stable string
|
|
116
|
-
* key. Accepts either dotted-string or array form; integer-looking
|
|
117
|
-
* segments normalise to numbers.
|
|
118
|
-
*
|
|
119
|
-
* ```ts
|
|
120
|
-
* canonicalizePath('items.0.name')
|
|
121
|
-
* // { segments: ['items', 0, 'name'], key: '["items",0,"name"]' as PathKey }
|
|
122
|
-
*
|
|
123
|
-
* canonicalizePath(['items', 0, 'name'])
|
|
124
|
-
* // → same result
|
|
125
|
-
* ```
|
|
126
|
-
*
|
|
127
|
-
* The returned `key` is suitable as a `Map`/`Set` key — equal paths
|
|
128
|
-
* produce equal keys regardless of input form.
|
|
129
|
-
*/
|
|
130
|
-
declare function canonicalizePath(input: string | Path): {
|
|
131
|
-
segments: readonly Segment[];
|
|
132
|
-
key: PathKey;
|
|
133
|
-
};
|
|
134
|
-
/**
|
|
135
|
-
* The root path — an empty segment tuple. Pass to APIs that accept
|
|
136
|
-
* a `Path` to address the form value as a whole.
|
|
137
|
-
*/
|
|
138
|
-
declare const ROOT_PATH: Path;
|
|
139
|
-
/** Stable string key for the root path. */
|
|
140
|
-
declare const ROOT_PATH_KEY: PathKey;
|
|
141
|
-
/**
|
|
142
|
-
* `true` when `path` starts with every segment of `prefix` (in order).
|
|
143
|
-
* The empty `prefix` matches every path — ROOT prefix is universal.
|
|
144
|
-
*
|
|
145
|
-
* Walks segments rather than `PathKey` strings because the data this
|
|
146
|
-
* helper operates on (e.g. `meta.errors[].path`) carries segment
|
|
147
|
-
* arrays directly.
|
|
148
|
-
*
|
|
149
|
-
* ```ts
|
|
150
|
-
* isPathPrefix(['cargo'], ['cargo', 'items', 0, 'sku']) // true
|
|
151
|
-
* isPathPrefix(['cargo', 'items'], ['cargo']) // false (path shorter)
|
|
152
|
-
* isPathPrefix([], ['anything']) // true (root prefix)
|
|
153
|
-
* ```
|
|
154
|
-
*/
|
|
155
|
-
declare function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean;
|
|
156
|
-
|
|
157
76
|
/** Internal brand for the `Unset` type. Never exposed at runtime. */
|
|
158
77
|
declare const _unsetBrand: unique symbol;
|
|
159
78
|
/**
|
|
@@ -458,6 +377,98 @@ type DefaultValuesShape<T> = T extends string | number | boolean | bigint | symb
|
|
|
458
377
|
[K in keyof T]: DefaultValuesShape<T[K]>;
|
|
459
378
|
} : T;
|
|
460
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Per-form options threaded from `useForm` into the adapter factory.
|
|
382
|
+
* Today carries the resolved `maxRecursionDepth` so adapter walks can
|
|
383
|
+
* cap their descent through recursive schemas; future per-form runtime
|
|
384
|
+
* knobs land here too.
|
|
385
|
+
*/
|
|
386
|
+
interface SchemaFactoryOptions {
|
|
387
|
+
/** Resolved recursion ceiling (per-form > app-default > library default). */
|
|
388
|
+
maxRecursionDepth: number;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Path primitives for advanced integrations. The form library accepts
|
|
393
|
+
* paths in dotted-string form (`'user.email'`) at every public API.
|
|
394
|
+
* These primitives are exposed for adapter authors who need to
|
|
395
|
+
* canonicalise user-provided paths.
|
|
396
|
+
*/
|
|
397
|
+
declare const pathKeyBrand: unique symbol;
|
|
398
|
+
/**
|
|
399
|
+
* Branded string identifier for a canonicalised path. Useful as a
|
|
400
|
+
* `Map` key — two paths that resolve to the same canonical form
|
|
401
|
+
* produce the same `PathKey`. Treat as opaque; don't try to parse.
|
|
402
|
+
*/
|
|
403
|
+
type PathKey = string & {
|
|
404
|
+
readonly [pathKeyBrand]: 'PathKey';
|
|
405
|
+
};
|
|
406
|
+
/** A single path segment — a property name or array index. */
|
|
407
|
+
type Segment = string | number;
|
|
408
|
+
/** A structured path as a read-only sequence of segments. */
|
|
409
|
+
type Path = readonly Segment[];
|
|
410
|
+
/**
|
|
411
|
+
* Parse a dotted-string path into structured segments.
|
|
412
|
+
*
|
|
413
|
+
* ```ts
|
|
414
|
+
* parseDottedPath('user.address.line1') // ['user', 'address', 'line1']
|
|
415
|
+
* parseDottedPath('items.0.name') // ['items', 0, 'name']
|
|
416
|
+
* parseDottedPath('') // [''] (the empty-string key)
|
|
417
|
+
* ```
|
|
418
|
+
*
|
|
419
|
+
* The empty-string input `''` is the **literal empty-key path**, not
|
|
420
|
+
* the root. Use the array form `[]` for root. Form-level errors
|
|
421
|
+
* (root `.refine()`) live at the empty-string path bucket so
|
|
422
|
+
* `errors('')` returns them without sweeping every field error too.
|
|
423
|
+
*
|
|
424
|
+
* Throws `InvalidPathError` for paths with empty INTERNAL segments
|
|
425
|
+
* (`'a..b'`, leading or trailing dots). For keys containing literal
|
|
426
|
+
* dots, pass an array form (`['user.name']`) instead.
|
|
427
|
+
*/
|
|
428
|
+
declare function parseDottedPath(path: string): Segment[];
|
|
429
|
+
/**
|
|
430
|
+
* Canonicalise a path into structured segments plus a stable string
|
|
431
|
+
* key. Accepts either dotted-string or array form; integer-looking
|
|
432
|
+
* segments normalise to numbers.
|
|
433
|
+
*
|
|
434
|
+
* ```ts
|
|
435
|
+
* canonicalizePath('items.0.name')
|
|
436
|
+
* // { segments: ['items', 0, 'name'], key: '["items",0,"name"]' as PathKey }
|
|
437
|
+
*
|
|
438
|
+
* canonicalizePath(['items', 0, 'name'])
|
|
439
|
+
* // → same result
|
|
440
|
+
* ```
|
|
441
|
+
*
|
|
442
|
+
* The returned `key` is suitable as a `Map`/`Set` key — equal paths
|
|
443
|
+
* produce equal keys regardless of input form.
|
|
444
|
+
*/
|
|
445
|
+
declare function canonicalizePath(input: string | Path): {
|
|
446
|
+
segments: readonly Segment[];
|
|
447
|
+
key: PathKey;
|
|
448
|
+
};
|
|
449
|
+
/**
|
|
450
|
+
* The root path — an empty segment tuple. Pass to APIs that accept
|
|
451
|
+
* a `Path` to address the form value as a whole.
|
|
452
|
+
*/
|
|
453
|
+
declare const ROOT_PATH: Path;
|
|
454
|
+
/** Stable string key for the root path. */
|
|
455
|
+
declare const ROOT_PATH_KEY: PathKey;
|
|
456
|
+
/**
|
|
457
|
+
* `true` when `path` starts with every segment of `prefix` (in order).
|
|
458
|
+
* The empty `prefix` matches every path — ROOT prefix is universal.
|
|
459
|
+
*
|
|
460
|
+
* Walks segments rather than `PathKey` strings because the data this
|
|
461
|
+
* helper operates on (e.g. `meta.errors[].path`) carries segment
|
|
462
|
+
* arrays directly.
|
|
463
|
+
*
|
|
464
|
+
* ```ts
|
|
465
|
+
* isPathPrefix(['cargo'], ['cargo', 'items', 0, 'sku']) // true
|
|
466
|
+
* isPathPrefix(['cargo', 'items'], ['cargo']) // false (path shorter)
|
|
467
|
+
* isPathPrefix([], ['anything']) // true (root prefix)
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
declare function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean;
|
|
471
|
+
|
|
461
472
|
/**
|
|
462
473
|
* Identifier for a form. A `FormKey` is the string passed via
|
|
463
474
|
* `useForm({ key })`, used to look up a form by name from a distant
|
|
@@ -659,6 +670,38 @@ type AbstractSchema<Form, GetValueFormType> = {
|
|
|
659
670
|
* callers treat that as "don't fill" and fall back to existing data.
|
|
660
671
|
*/
|
|
661
672
|
getDefaultAtPath(path: Path): unknown;
|
|
673
|
+
/**
|
|
674
|
+
* Give the schema a chance to normalize the consumer's write value
|
|
675
|
+
* before it lands in storage / hits the slim-primitive gate. Each
|
|
676
|
+
* schema library exposes this concept differently — Zod calls it
|
|
677
|
+
* `z.preprocess(fn, inner)`, Yup calls it `.transform()`, Valibot
|
|
678
|
+
* spells it `pipe(transform(fn), inner)` — but the shape is the
|
|
679
|
+
* same: "this input shape gets coerced into that storage shape at
|
|
680
|
+
* the boundary."
|
|
681
|
+
*
|
|
682
|
+
* Runs SYNCHRONOUSLY at the write boundary so storage holds the
|
|
683
|
+
* post-normalization shape. Without this, a schema like `notify:
|
|
684
|
+
* z.preprocess(v => v == null ? defaultVar : v, innerDU)` would
|
|
685
|
+
* let the consumer write `null` and lock storage into `null` —
|
|
686
|
+
* because the gate sees the raw input (which the preprocess wrapper
|
|
687
|
+
* accepts as `unknown`) and storage holds a shape no variant
|
|
688
|
+
* matches.
|
|
689
|
+
*
|
|
690
|
+
* Adapters MUST:
|
|
691
|
+
* - Return `value` unchanged when no normalization is declared at
|
|
692
|
+
* the path.
|
|
693
|
+
* - Return `value` unchanged when the user's normalization fn
|
|
694
|
+
* returns a `Promise` (async coercion can't run at write time —
|
|
695
|
+
* validation handles it during parse).
|
|
696
|
+
* - Let user-thrown errors propagate (the user wrote the fn; we
|
|
697
|
+
* just tag the path in the wrapper error for diagnostics).
|
|
698
|
+
*
|
|
699
|
+
* Normalization runs when `path` equals the wrapper's exact
|
|
700
|
+
* location. Writes deeper than the wrapper bypass it (a wrapper
|
|
701
|
+
* over the whole subtree can't be invoked from a partial leaf
|
|
702
|
+
* write).
|
|
703
|
+
*/
|
|
704
|
+
normalizeWriteValueAtPath(value: unknown, path: Path): unknown;
|
|
662
705
|
/**
|
|
663
706
|
* Distinguish a tuple (fixed-length, position-typed) from an
|
|
664
707
|
* unbounded array at `path`. The runtime calls this on every
|
|
@@ -719,7 +762,7 @@ type AbstractSchema<Form, GetValueFormType> = {
|
|
|
719
762
|
* returned as a `success: false` response with a populated
|
|
720
763
|
* `errors` array.
|
|
721
764
|
*/
|
|
722
|
-
validateAtPath(data: unknown, path: Path | undefined, options?: ValidateOptions): MaybePromise<ValidationResponse<
|
|
765
|
+
validateAtPath(data: unknown, path: Path | undefined, options?: ValidateOptions): MaybePromise<ValidationResponse<GetValueFormType>>;
|
|
723
766
|
/**
|
|
724
767
|
* Sync sister to `getSchemasAtPath` / `validateAtPath`. Returns the
|
|
725
768
|
* set of primitive `typeof`-style kinds the path's leaf schema
|
|
@@ -931,6 +974,14 @@ type UnionDiscriminatorContext = {
|
|
|
931
974
|
* skips the reshape and falls back to a plain write.
|
|
932
975
|
*/
|
|
933
976
|
getVariantDefault(value: unknown): unknown;
|
|
977
|
+
/**
|
|
978
|
+
* Returns `true` iff `value` is a literal recognised by one of the
|
|
979
|
+
* discriminator's variants. Used by reshape to decide whether to
|
|
980
|
+
* seek a variant default or emit a stub state. NOT used at the
|
|
981
|
+
* runtime write gate — consumer-side value validity is a
|
|
982
|
+
* validation-time concern.
|
|
983
|
+
*/
|
|
984
|
+
isVariantSelected(value: unknown): boolean;
|
|
934
985
|
};
|
|
935
986
|
/**
|
|
936
987
|
* The set of primitive "kinds" the slim-primitive write contract
|
|
@@ -1130,19 +1181,136 @@ type WriteMeta = {
|
|
|
1130
1181
|
* an infinite loop. Don't set from consumer code.
|
|
1131
1182
|
*/
|
|
1132
1183
|
readonly skipDiscriminatorReshape?: boolean;
|
|
1184
|
+
/**
|
|
1185
|
+
* Hint about an array structural mutation, set by `field-arrays.ts`
|
|
1186
|
+
* helpers so `setValueAtPath` can surgically clear variant memory
|
|
1187
|
+
* for indices the operation invalidated. Without this hint, a raw
|
|
1188
|
+
* whole-array `setValue(arrayPath, [...])` clears all memory under
|
|
1189
|
+
* the array (the runtime can't tell which indices stayed put).
|
|
1190
|
+
* Internal — don't set from consumer code.
|
|
1191
|
+
*/
|
|
1192
|
+
readonly arrayOp?: {
|
|
1193
|
+
readonly kind: 'shift-from';
|
|
1194
|
+
readonly index: number;
|
|
1195
|
+
} | {
|
|
1196
|
+
readonly kind: 'shift-range';
|
|
1197
|
+
readonly fromIndex: number;
|
|
1198
|
+
readonly toIndex: number;
|
|
1199
|
+
} | {
|
|
1200
|
+
readonly kind: 'swap';
|
|
1201
|
+
readonly a: number;
|
|
1202
|
+
readonly b: number;
|
|
1203
|
+
} | {
|
|
1204
|
+
readonly kind: 'replace-at';
|
|
1205
|
+
readonly index: number;
|
|
1206
|
+
};
|
|
1207
|
+
/**
|
|
1208
|
+
* Per-instance config overrides threaded through writes so each
|
|
1209
|
+
* `useForm({ key })` callsite honors its own `validateOn` /
|
|
1210
|
+
* `debounceMs` / `rememberVariants` even when sharing a FormStore
|
|
1211
|
+
* with sibling calls (e.g., a modal and main form rendering the
|
|
1212
|
+
* same logical form). Internal — set by `buildFormApi` from
|
|
1213
|
+
* the per-instance options bag; the store reads each field with
|
|
1214
|
+
* fallback to its construction-time defaults.
|
|
1215
|
+
*/
|
|
1216
|
+
readonly instance?: {
|
|
1217
|
+
readonly validateOn?: ValidateOn;
|
|
1218
|
+
readonly debounceMs?: number;
|
|
1219
|
+
readonly rememberVariants?: boolean;
|
|
1220
|
+
};
|
|
1221
|
+
/**
|
|
1222
|
+
* When `true`, marks this `applyFormReplacement` call as the
|
|
1223
|
+
* persistence hydration step. Modules that snapshot the form state
|
|
1224
|
+
* (notably the history module) treat hydration as the baseline:
|
|
1225
|
+
* stacks reset to a single seed of the post-hydration value, so a
|
|
1226
|
+
* subsequent `undo()` can't recover the transient pre-hydration
|
|
1227
|
+
* default. Internal — set by `wirePersistence`. Don't set from
|
|
1228
|
+
* consumer code.
|
|
1229
|
+
*/
|
|
1230
|
+
readonly hydration?: boolean;
|
|
1231
|
+
/**
|
|
1232
|
+
* When `true`, this write originated from a sibling tab's
|
|
1233
|
+
* BroadcastChannel broadcast (the multi-tab sync module's inbound
|
|
1234
|
+
* apply). Listeners that initiate side effects check this flag to
|
|
1235
|
+
* avoid amplification loops and spurious side effects:
|
|
1236
|
+
*
|
|
1237
|
+
* - The multi-tab sync OUTBOUND broadcaster skips so a remote-driven
|
|
1238
|
+
* write doesn't echo back across the channel.
|
|
1239
|
+
* - The history module updates its diff anchor but does NOT push a
|
|
1240
|
+
* delta — remote writes aren't part of the local user's undo
|
|
1241
|
+
* timeline.
|
|
1242
|
+
* - The persistence writer skips so the receiving tab doesn't
|
|
1243
|
+
* double-persist a value the originating tab already wrote.
|
|
1244
|
+
*
|
|
1245
|
+
* Internal — set by `createMultiTabSyncModule`. Don't set from
|
|
1246
|
+
* consumer code.
|
|
1247
|
+
*/
|
|
1248
|
+
readonly crossTab?: boolean;
|
|
1133
1249
|
};
|
|
1134
1250
|
/**
|
|
1135
1251
|
* Undo/redo configuration passed via `useForm({ history })`.
|
|
1136
1252
|
*
|
|
1137
|
-
* - `true` — enable with the default
|
|
1138
|
-
* - `{ max }` — enable and tune the bounded
|
|
1253
|
+
* - `true` — enable with the default position cap (`max: 128`).
|
|
1254
|
+
* - `{ max }` — enable and tune the bounded history size.
|
|
1139
1255
|
*
|
|
1140
|
-
* When enabled, every mutation
|
|
1141
|
-
* `redo()` walk the
|
|
1256
|
+
* When enabled, every mutation records a forward delta; `form.history.undo()`
|
|
1257
|
+
* / `form.history.redo()` walk the chain. `reset()` is itself a mutation —
|
|
1258
|
+
* the pre-reset state stays one undo away. Persistence hydration is the
|
|
1259
|
+
* floor: after hydrate applies, the chain reseeds with the hydrated value
|
|
1260
|
+
* and `undo()` cannot reach the transient pre-hydration default.
|
|
1142
1261
|
*/
|
|
1143
1262
|
type HistoryConfig = true | {
|
|
1144
1263
|
max?: number;
|
|
1145
1264
|
};
|
|
1265
|
+
/**
|
|
1266
|
+
* Consolidated undo/redo namespace at `form.history`. All history-related
|
|
1267
|
+
* surface lives here — methods and reactive flags both — so consumers
|
|
1268
|
+
* have one canonical address to read from.
|
|
1269
|
+
*
|
|
1270
|
+
* Always present on `useForm()` return whether or not `history` was
|
|
1271
|
+
* configured. When history isn't enabled, methods are no-ops returning
|
|
1272
|
+
* `false` (or `void`), `canUndo` / `canRedo` read `false`, and `size`
|
|
1273
|
+
* reads `0`. Consumer templates don't need conditional logic.
|
|
1274
|
+
*
|
|
1275
|
+
* Reactivity: built as `readonly(reactive({...}))`, so `canUndo` / `canRedo`
|
|
1276
|
+
* / `size` auto-unwrap on access (plain `boolean` / `number`, not refs).
|
|
1277
|
+
* Method fields (`undo`, `redo`, `clear`) pass through as plain functions.
|
|
1278
|
+
*/
|
|
1279
|
+
type FormHistoryNamespace = {
|
|
1280
|
+
/**
|
|
1281
|
+
* Step back one position in the history chain. Returns `true` when a
|
|
1282
|
+
* step was taken, `false` when already at the oldest reachable
|
|
1283
|
+
* position (or when history isn't configured).
|
|
1284
|
+
*/
|
|
1285
|
+
readonly undo: () => boolean;
|
|
1286
|
+
/**
|
|
1287
|
+
* Replay the next step forward in the chain. Returns `true` on
|
|
1288
|
+
* success, `false` when there's nothing queued (or history isn't
|
|
1289
|
+
* configured). The forward branch is dropped as soon as a new
|
|
1290
|
+
* mutation lands.
|
|
1291
|
+
*/
|
|
1292
|
+
readonly redo: () => boolean;
|
|
1293
|
+
/**
|
|
1294
|
+
* Wipe the undo and redo branches; reseed the chain with the current
|
|
1295
|
+
* form state as the new baseline. The form value, errors, and
|
|
1296
|
+
* blankPaths all stay where they are — only the past/future history
|
|
1297
|
+
* resets. After `clear()`: `canUndo === false`, `canRedo === false`,
|
|
1298
|
+
* `size === 1`. No-op when history isn't configured.
|
|
1299
|
+
*/
|
|
1300
|
+
readonly clear: () => void;
|
|
1301
|
+
/** `true` when there is at least one undo step available. */
|
|
1302
|
+
readonly canUndo: boolean;
|
|
1303
|
+
/** `true` when `undo()` has been called and a `redo()` would replay. */
|
|
1304
|
+
readonly canRedo: boolean;
|
|
1305
|
+
/**
|
|
1306
|
+
* Total reachable positions in the history chain (the current
|
|
1307
|
+
* position plus everything reachable via `undo()` / `redo()`).
|
|
1308
|
+
* Useful for debug overlays; UI driving undo/redo buttons should
|
|
1309
|
+
* gate on `canUndo` / `canRedo` instead. Reads `0` when history
|
|
1310
|
+
* isn't configured.
|
|
1311
|
+
*/
|
|
1312
|
+
readonly size: number;
|
|
1313
|
+
};
|
|
1146
1314
|
/**
|
|
1147
1315
|
* Full options bag for `useForm({ persist })`. Use this when you need
|
|
1148
1316
|
* to override defaults beyond picking the backend.
|
|
@@ -1240,10 +1408,15 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1240
1408
|
* abstract entry point accepts any object implementing
|
|
1241
1409
|
* `AbstractSchema`.
|
|
1242
1410
|
*
|
|
1243
|
-
* For schemas that depend on the form's identity
|
|
1244
|
-
* `(key) => schema` instead — the
|
|
1411
|
+
* For schemas that depend on the form's identity or per-form
|
|
1412
|
+
* options, pass a factory `(key, options) => schema` instead — the
|
|
1413
|
+
* library calls it once per form, after `mergeWithDefaults` has
|
|
1414
|
+
* resolved the options bag (`maxRecursionDepth`, etc.). Most
|
|
1415
|
+
* adapters ignore the options argument; the typed Zod entry points
|
|
1416
|
+
* use it to thread the resolved recursion cap into the adapter
|
|
1417
|
+
* closure.
|
|
1245
1418
|
*/
|
|
1246
|
-
schema: Schema | ((key: FormKey) => Schema);
|
|
1419
|
+
schema: Schema | ((key: FormKey, options: SchemaFactoryOptions) => Schema);
|
|
1247
1420
|
/**
|
|
1248
1421
|
* Optional identifier for this form. Omit for one-off forms; the
|
|
1249
1422
|
* library allocates a unique key automatically (SSR-safe, stable
|
|
@@ -1353,13 +1526,14 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1353
1526
|
*/
|
|
1354
1527
|
persist?: PersistConfig;
|
|
1355
1528
|
/**
|
|
1356
|
-
* Opt-in undo/redo. Off by default. `true` enables with a
|
|
1529
|
+
* Opt-in undo/redo. Off by default. `true` enables with a 128-position
|
|
1357
1530
|
* cap; `{ max: N }` tunes the cap.
|
|
1358
1531
|
*
|
|
1359
|
-
* Every mutation
|
|
1360
|
-
*
|
|
1361
|
-
* `
|
|
1362
|
-
*
|
|
1532
|
+
* Every mutation records a forward delta. `form.history.undo()` walks
|
|
1533
|
+
* one step back; `form.history.redo()` walks one step forward.
|
|
1534
|
+
* `reset()` is itself a mutation, so the pre-reset state stays one
|
|
1535
|
+
* undo away. The consolidated `form.history` namespace also exposes
|
|
1536
|
+
* `clear()`, `canUndo`, `canRedo`, and `size`.
|
|
1363
1537
|
*/
|
|
1364
1538
|
history?: HistoryConfig;
|
|
1365
1539
|
/**
|
|
@@ -1416,6 +1590,71 @@ type UseFormConfiguration<Form extends GenericForm, GetValueFormType, Schema ext
|
|
|
1416
1590
|
* `false` → never show.
|
|
1417
1591
|
*/
|
|
1418
1592
|
shouldShowErrors?: ShouldShowErrorsConfig;
|
|
1593
|
+
/**
|
|
1594
|
+
* Recursion ceiling for schema walks that descend through recursive
|
|
1595
|
+
* schemas (Zod's `z.lazy(...)` today). Default `64`. Per-form value
|
|
1596
|
+
* overrides `AttaformDefaults.maxRecursionDepth`, which overrides
|
|
1597
|
+
* the library default.
|
|
1598
|
+
*
|
|
1599
|
+
* Schemas that don't include a recursive boundary ignore this knob
|
|
1600
|
+
* entirely — it's read only at the descent step through a recursive
|
|
1601
|
+
* wrapper. Set it on the specific form whose schema is recursive
|
|
1602
|
+
* (a comment tree, a category tree, a nested-rule editor):
|
|
1603
|
+
*
|
|
1604
|
+
* ```ts
|
|
1605
|
+
* useForm({ schema: commentTreeSchema, maxRecursionDepth: 128 })
|
|
1606
|
+
* ```
|
|
1607
|
+
*
|
|
1608
|
+
* Past the cap, the slim-primitive type gate falls back to permissive
|
|
1609
|
+
* (write-time type checks skip; full schema validation still runs).
|
|
1610
|
+
* Storage and reads work at any depth; only the per-write type gate
|
|
1611
|
+
* stops short of the cap. Raise the cap if you regularly edit nodes
|
|
1612
|
+
* beyond the default depth.
|
|
1613
|
+
*
|
|
1614
|
+
* See `AttaformDefaults.maxRecursionDepth` for the resolution rules
|
|
1615
|
+
* and the broader description of where the cap is read.
|
|
1616
|
+
*/
|
|
1617
|
+
maxRecursionDepth?: number;
|
|
1618
|
+
/**
|
|
1619
|
+
* Override the path-segment name stems treated as sensitive for this
|
|
1620
|
+
* form. Sensitive paths are excluded from persistence writes,
|
|
1621
|
+
* multi-tab sync broadcasts, AND the DevTools redact walk.
|
|
1622
|
+
*
|
|
1623
|
+
* Resolution: per-form value (this field) > global default
|
|
1624
|
+
* (`createAttaform({ defaults: { sensitiveNames } })`) > library
|
|
1625
|
+
* default (`DEFAULT_SENSITIVE_NAMES`).
|
|
1626
|
+
*
|
|
1627
|
+
* Pass an empty array `[]` as the explicit opt-out — "nothing is
|
|
1628
|
+
* sensitive on this form" — for fully-trusted internal tooling.
|
|
1629
|
+
* See `AttaformDefaults.sensitiveNames` for composition examples.
|
|
1630
|
+
*/
|
|
1631
|
+
sensitiveNames?: readonly string[];
|
|
1632
|
+
/**
|
|
1633
|
+
* Cross-tab synchronisation via BroadcastChannel. Defaults to `true`
|
|
1634
|
+
* (when the browser supports it and the page is in a secure
|
|
1635
|
+
* context): a keyed `useForm` callsite auto-pairs with same-keyed
|
|
1636
|
+
* siblings in other same-origin tabs and mirrors their mutations
|
|
1637
|
+
* in near real-time.
|
|
1638
|
+
*
|
|
1639
|
+
* **Resolution order (per-register override > per-form > global > library):**
|
|
1640
|
+
*
|
|
1641
|
+
* register(path, { multiTab }) > useForm({ multiTab }) > AttaformDefaults.multiTab > library default (`true`)
|
|
1642
|
+
*
|
|
1643
|
+
* **When to set `false`:** forms holding PII / PHI, contexts where
|
|
1644
|
+
* tab isolation is required by policy, or any flow where conflicting
|
|
1645
|
+
* tab edits could corrupt user intent. Sensitive-named paths (via
|
|
1646
|
+
* `sensitiveNames`) are always stripped from outbound broadcasts
|
|
1647
|
+
* regardless of this setting.
|
|
1648
|
+
*
|
|
1649
|
+
* **Secure-context requirement.** Multi-tab sync is silently disabled
|
|
1650
|
+
* outside `window.isSecureContext === true` (HTTPS or localhost). On
|
|
1651
|
+
* plain HTTP a one-shot dev warning fires and the module noops.
|
|
1652
|
+
*
|
|
1653
|
+
* **Anonymous (auto-keyed) forms skip sync entirely** — without a
|
|
1654
|
+
* consumer-supplied `key`, cross-tab identity is undefined and the
|
|
1655
|
+
* channel would be solo by construction.
|
|
1656
|
+
*/
|
|
1657
|
+
multiTab?: boolean;
|
|
1419
1658
|
};
|
|
1420
1659
|
/**
|
|
1421
1660
|
* App-level defaults applied to every `useForm` call. Set these once
|
|
@@ -1509,6 +1748,102 @@ type AttaformDefaults = {
|
|
|
1509
1748
|
* predicate, so reading them inside would be a self-reference.
|
|
1510
1749
|
*/
|
|
1511
1750
|
shouldShowErrors?: ShouldShowErrorsConfig;
|
|
1751
|
+
/**
|
|
1752
|
+
* Default for `useForm({ maxRecursionDepth })`. Recursion ceiling
|
|
1753
|
+
* for schema walks that descend through recursive schemas (Zod's
|
|
1754
|
+
* `z.lazy(...)` today, equivalent constructs in any future adapter).
|
|
1755
|
+
* Library default: `64`.
|
|
1756
|
+
*
|
|
1757
|
+
* Resolution order (per-form wins):
|
|
1758
|
+
*
|
|
1759
|
+
* useForm({ maxRecursionDepth }) > AttaformDefaults > library default (64)
|
|
1760
|
+
*
|
|
1761
|
+
* Read at every step of a schema walk that crosses a recursive
|
|
1762
|
+
* boundary — default-value derivation at construction, slim-primitive
|
|
1763
|
+
* type gates on each write, path-by-path schema resolution. Walks
|
|
1764
|
+
* track their descent depth and switch to a permissive fallback once
|
|
1765
|
+
* `depth > maxRecursionDepth`.
|
|
1766
|
+
*
|
|
1767
|
+
* "Permissive fallback" means storage and reads keep working at any
|
|
1768
|
+
* depth; only the per-write type gate stops checking past the cap.
|
|
1769
|
+
* Full schema validation (`validateAsync`, `handleSubmit`) still runs
|
|
1770
|
+
* against the real schema, so refinement errors at any depth still
|
|
1771
|
+
* surface — the cap only affects the *write-time gate*.
|
|
1772
|
+
*
|
|
1773
|
+
* Forms with no recursive schemas ignore this entirely — the cap is
|
|
1774
|
+
* read only at the descent step through a recursive wrapper. Setting
|
|
1775
|
+
* it app-wide is the right move when you have multiple recursive
|
|
1776
|
+
* forms that should share one ceiling:
|
|
1777
|
+
*
|
|
1778
|
+
* ```ts
|
|
1779
|
+
* createAttaform({
|
|
1780
|
+
* defaults: { maxRecursionDepth: 128 },
|
|
1781
|
+
* })
|
|
1782
|
+
* ```
|
|
1783
|
+
*
|
|
1784
|
+
* Per-form override stays available for the one tree-shaped form
|
|
1785
|
+
* whose depth is unusual:
|
|
1786
|
+
*
|
|
1787
|
+
* ```ts
|
|
1788
|
+
* useForm({ schema: deepCategoryTreeSchema, maxRecursionDepth: 256 })
|
|
1789
|
+
* ```
|
|
1790
|
+
*
|
|
1791
|
+
* Setting this app-wide costs nothing for non-recursive forms — the
|
|
1792
|
+
* walks that read the cap never run for them.
|
|
1793
|
+
*
|
|
1794
|
+
* Pass `Infinity` to disable the cap entirely. Walks will then
|
|
1795
|
+
* descend through recursive boundaries until they terminate
|
|
1796
|
+
* structurally; a schema with no structural terminator will exhaust
|
|
1797
|
+
* the JS call stack. Reserve for schemas whose authors are
|
|
1798
|
+
* confident the recursion is bounded by the actual data shape.
|
|
1799
|
+
*/
|
|
1800
|
+
maxRecursionDepth?: number;
|
|
1801
|
+
/**
|
|
1802
|
+
* Override the path-segment name stems treated as sensitive.
|
|
1803
|
+
* Sensitive paths are excluded from persistence writes, multi-tab
|
|
1804
|
+
* sync broadcasts, AND the DevTools redact walk — one configurable
|
|
1805
|
+
* source of truth across every surface.
|
|
1806
|
+
*
|
|
1807
|
+
* Library default is `DEFAULT_SENSITIVE_NAMES` (exported from
|
|
1808
|
+
* `attaform`); compose to extend:
|
|
1809
|
+
*
|
|
1810
|
+
* ```ts
|
|
1811
|
+
* import { DEFAULT_SENSITIVE_NAMES, createAttaform } from 'attaform'
|
|
1812
|
+
*
|
|
1813
|
+
* createAttaform({
|
|
1814
|
+
* defaults: { sensitiveNames: [...DEFAULT_SENSITIVE_NAMES, 'mrn', 'tax_id'] }
|
|
1815
|
+
* })
|
|
1816
|
+
* ```
|
|
1817
|
+
*
|
|
1818
|
+
* Pass an empty array `[]` as the explicit opt-out — "nothing is
|
|
1819
|
+
* sensitive" — for fully-trusted internal tooling. When present at
|
|
1820
|
+
* the per-form level via `useForm({ sensitiveNames })`, the per-form
|
|
1821
|
+
* list REPLACES the global one (consumers compose their own
|
|
1822
|
+
* additive lists via the exported default).
|
|
1823
|
+
*/
|
|
1824
|
+
sensitiveNames?: readonly string[];
|
|
1825
|
+
/**
|
|
1826
|
+
* App-wide default for `useForm({ multiTab })`. Default `true` when
|
|
1827
|
+
* the runtime supports `BroadcastChannel` AND `window.isSecureContext`
|
|
1828
|
+
* is true (HTTPS in production, localhost in development) — same gate
|
|
1829
|
+
* browsers apply to other sensitive APIs (clipboard, geolocation,
|
|
1830
|
+
* push, web crypto subtle).
|
|
1831
|
+
*
|
|
1832
|
+
* Set to `false` once at the plugin level for a multi-tenant
|
|
1833
|
+
* deployment that prefers tab-isolation by default; individual forms
|
|
1834
|
+
* can still opt back in via `useForm({ multiTab: true })`.
|
|
1835
|
+
*
|
|
1836
|
+
* **Resolution order (per-form wins):**
|
|
1837
|
+
*
|
|
1838
|
+
* useForm({ multiTab }) > AttaformDefaults.multiTab > library default (`true`)
|
|
1839
|
+
*
|
|
1840
|
+
* **Secure-context gate.** Multi-tab sync only activates over HTTPS
|
|
1841
|
+
* or localhost. On plain HTTP, the module silently noops with a
|
|
1842
|
+
* one-shot dev-mode warning — production deployments MUST be served
|
|
1843
|
+
* over HTTPS for sync to function. See the multi-tab-sync recipe's
|
|
1844
|
+
* Security section for the threat model.
|
|
1845
|
+
*/
|
|
1846
|
+
multiTab?: boolean;
|
|
1512
1847
|
};
|
|
1513
1848
|
/**
|
|
1514
1849
|
* Callback invoked by `handleSubmit` after the form parses successfully.
|
|
@@ -1765,6 +2100,25 @@ type RegisterOptions = {
|
|
|
1765
2100
|
* client-side storage for this user's session.
|
|
1766
2101
|
*/
|
|
1767
2102
|
acknowledgeSensitive?: boolean;
|
|
2103
|
+
/**
|
|
2104
|
+
* Opt this field OUT of multi-tab sync. The form-level cascade
|
|
2105
|
+
* activates sync by default; passing `multiTab: false` on a single
|
|
2106
|
+
* register call keeps that path tab-local — outbound patches at
|
|
2107
|
+
* the path are stripped, and inbound patches at the path are
|
|
2108
|
+
* rejected (symmetric tab-local behaviour).
|
|
2109
|
+
*
|
|
2110
|
+
* The opt-out is downgrade-only — you cannot pass `multiTab: true`
|
|
2111
|
+
* to bring sync back on a form whose form-level `multiTab` is
|
|
2112
|
+
* `false` (in that case the sync module never instantiated; there's
|
|
2113
|
+
* no broadcaster to opt back into).
|
|
2114
|
+
*
|
|
2115
|
+
* Use for fields that hold transient per-tab UI state inside an
|
|
2116
|
+
* otherwise-synced form (e.g. an editor's cursor position field
|
|
2117
|
+
* mirrored into the form for save-on-blur), or for individual
|
|
2118
|
+
* paths the consumer wants to scope to the originating tab without
|
|
2119
|
+
* disabling sync globally.
|
|
2120
|
+
*/
|
|
2121
|
+
multiTab?: boolean;
|
|
1768
2122
|
/**
|
|
1769
2123
|
* Sync transformation pipeline applied to user-typed values before
|
|
1770
2124
|
* they reach form state. Composes left-to-right: each transform
|
|
@@ -2088,6 +2442,59 @@ type SetValueCallback<Read, Write = Read> = (prev: Read) => Read | Write;
|
|
|
2088
2442
|
* array elements as possibly-undefined to reflect runtime reality.
|
|
2089
2443
|
*/
|
|
2090
2444
|
type SetValuePayload<Write, Read = Write> = Write | SetValueCallback<Read, Write>;
|
|
2445
|
+
/**
|
|
2446
|
+
* Detect `any` distinctly from `unknown`. The trick: `1 & any` is `any`
|
|
2447
|
+
* and `0 extends any` is `true`; `1 & unknown` is `1` and `0 extends 1`
|
|
2448
|
+
* is `false`. Used to fork `PathSetValuePayload` so `z.any()` paths
|
|
2449
|
+
* resolve to `any` (matching the read-side surface) and `z.unknown()` /
|
|
2450
|
+
* preprocess paths resolve to `unknown` (matching Zod's input typing).
|
|
2451
|
+
*/
|
|
2452
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
2453
|
+
/**
|
|
2454
|
+
* Resolves `setValue`'s `value` argument type at a single `Path` leaf.
|
|
2455
|
+
*
|
|
2456
|
+
* Three branches, one per Zod input-typing case:
|
|
2457
|
+
*
|
|
2458
|
+
* 1. **`any` leaf (`z.any()`)** — schema input type is `any`; the
|
|
2459
|
+
* whole form API surface (read, register, fields) is `any` at
|
|
2460
|
+
* this path. This branch returns raw `any` so `setValue` stays
|
|
2461
|
+
* consistent with the rest. Callsites that pass an unannotated
|
|
2462
|
+
* `(prev) => ...` may surface `noImplicitAny` under the
|
|
2463
|
+
* consumer's tsconfig — annotate `(prev: any) => ...` to opt
|
|
2464
|
+
* into the looser shape explicitly.
|
|
2465
|
+
*
|
|
2466
|
+
* 2. **`unknown` leaf (`z.unknown()`, `z.preprocess()` input)** —
|
|
2467
|
+
* schema input is unconstrained; consumers narrow before use.
|
|
2468
|
+
* The branch returns `({} | null | undefined) | ((prev: unknown)
|
|
2469
|
+
* => unknown)` instead of a `SetValuePayload<unknown, ...>`-style
|
|
2470
|
+
* union for three reasons:
|
|
2471
|
+
*
|
|
2472
|
+
* a. **Union absorption** — `unknown | X` collapses to `unknown`,
|
|
2473
|
+
* erasing the callback union member. With the callback shape
|
|
2474
|
+
* gone, TS has no contextual type for `prev` and decays it to
|
|
2475
|
+
* implicit `any` under `noImplicitAny`. The triple
|
|
2476
|
+
* `{} | null | undefined` is structurally equivalent to
|
|
2477
|
+
* `unknown` (covers the same value space) but is NOT subject
|
|
2478
|
+
* to absorption — the callback branch survives the union and
|
|
2479
|
+
* `prev` infers cleanly to `unknown`.
|
|
2480
|
+
*
|
|
2481
|
+
* b. **`NonNullable<unknown> = {}`** — applying `NonNullable` to
|
|
2482
|
+
* the read slot for an unknown leaf narrows `prev` to `{}`,
|
|
2483
|
+
* which is looser than `unknown` (allows ad-hoc property
|
|
2484
|
+
* access). This branch keeps the read slot as `unknown`
|
|
2485
|
+
* directly so the consumer is forced to narrow.
|
|
2486
|
+
*
|
|
2487
|
+
* c. **`Unset`-widening doesn't apply** — `DefaultValuesShape`
|
|
2488
|
+
* widens primitive leaves to admit `unset`; for an unknown
|
|
2489
|
+
* leaf there's no primitive to widen. The open-form triple
|
|
2490
|
+
* covers the same value space the runtime accepts (any
|
|
2491
|
+
* value, including `unset` — symbols are `{}`).
|
|
2492
|
+
*
|
|
2493
|
+
* 3. **All other leaves** — flow through unchanged via
|
|
2494
|
+
* `SetValuePayload<DefaultValuesShape<Leaf>, NonNullable<WriteShape<Leaf>>>`.
|
|
2495
|
+
*/
|
|
2496
|
+
type PathSetValuePayload<Leaf> = IsAny<Leaf> extends true ? any : unknown extends Leaf ? // eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
2497
|
+
({} | null | undefined) | ((prev: unknown) => unknown) : SetValuePayload<DefaultValuesShape<Leaf>, NonNullable<WriteShape<Leaf>>>;
|
|
2091
2498
|
/**
|
|
2092
2499
|
* Per-field reactive shape returned by `form.fields.<leaf-path>` and
|
|
2093
2500
|
* `form.fields(path)`. Slim, readonly across the board. The unified
|
|
@@ -2570,16 +2977,6 @@ type FormMeta<F = unknown> = FieldState<F> & {
|
|
|
2570
2977
|
* `try { await onSubmit() }` instead.
|
|
2571
2978
|
*/
|
|
2572
2979
|
readonly submitError: unknown;
|
|
2573
|
-
/** `true` when there is at least one undo step available. Always present (false when history is disabled). */
|
|
2574
|
-
readonly canUndo: boolean;
|
|
2575
|
-
/** `true` when `undo()` has been called and a `redo()` would replay. Always present (false when history is disabled). */
|
|
2576
|
-
readonly canRedo: boolean;
|
|
2577
|
-
/**
|
|
2578
|
-
* Total snapshots across the undo and redo stacks. Useful for
|
|
2579
|
-
* debug overlays; UI driving undo/redo buttons should gate on
|
|
2580
|
-
* `canUndo` / `canRedo` instead.
|
|
2581
|
-
*/
|
|
2582
|
-
readonly historySize: number;
|
|
2583
2980
|
/**
|
|
2584
2981
|
* Per-`useForm()`-call identity. Stable for the lifetime of one
|
|
2585
2982
|
* `useForm()` call; new on every fresh mount. Orthogonal to
|
|
@@ -2618,6 +3015,22 @@ type FormMeta<F = unknown> = FieldState<F> & {
|
|
|
2618
3015
|
* form.handleSubmit(onSubmit) // returns a submit handler
|
|
2619
3016
|
* form.meta.submitting // form-level reactive flag
|
|
2620
3017
|
* ```
|
|
3018
|
+
*
|
|
3019
|
+
* Two generic slots split the input view from the output view:
|
|
3020
|
+
*
|
|
3021
|
+
* - `Form` — the **input / storage shape** (`z.input<Schema>`). Used
|
|
3022
|
+
* by `setValue`, `defaultValues`, `values`, `fields`, `register`,
|
|
3023
|
+
* `toRef`, and every path-addressed API. Storage holds values as
|
|
3024
|
+
* the consumer wrote them; preprocess normalization runs at the
|
|
3025
|
+
* write boundary, but `.transform()`s are deferred to parse-time.
|
|
3026
|
+
*
|
|
3027
|
+
* - `GetValueFormType` — the **output / parsed shape**
|
|
3028
|
+
* (`z.output<Schema>`). Used by `handleSubmit`'s `onSubmit`
|
|
3029
|
+
* callback and by `form.process()`'s success payload. This is the
|
|
3030
|
+
* shape after refinements have fired and transforms have run.
|
|
3031
|
+
*
|
|
3032
|
+
* For schemas without transforms the two are identical, and the
|
|
3033
|
+
* default `GetValueFormType = Form` keeps the surface ergonomic.
|
|
2621
3034
|
*/
|
|
2622
3035
|
type UseFormReturnType<Form extends GenericForm, GetValueFormType extends GenericForm = Form> = {
|
|
2623
3036
|
/**
|
|
@@ -2631,10 +3044,13 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2631
3044
|
* ```
|
|
2632
3045
|
*
|
|
2633
3046
|
* `data` is the strictly-typed parsed value — refinements have
|
|
2634
|
-
* fired
|
|
2635
|
-
*
|
|
3047
|
+
* fired and `.transform()`s have run, so the payload matches
|
|
3048
|
+
* `z.output<Schema>` (the post-parse output shape). For schemas
|
|
3049
|
+
* where the input type differs from the output type (e.g.
|
|
3050
|
+
* `z.string().transform(v => v.length > 10)`), `data` is the
|
|
3051
|
+
* output shape while `form.values` stays the input shape.
|
|
2636
3052
|
*/
|
|
2637
|
-
handleSubmit: HandleSubmit<
|
|
3053
|
+
handleSubmit: HandleSubmit<GetValueFormType>;
|
|
2638
3054
|
/**
|
|
2639
3055
|
* Reactive readonly proxy over the form's storage value. Read
|
|
2640
3056
|
* identically in script and template — no `.value`, no auto-unwrap
|
|
@@ -2657,10 +3073,13 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2657
3073
|
*
|
|
2658
3074
|
* Reads reflect what's storable: enum-typed slots widen to their
|
|
2659
3075
|
* primitive supertype (`string`), so refinement-invalid but
|
|
2660
|
-
* structurally-valid values are visible.
|
|
2661
|
-
* `
|
|
3076
|
+
* structurally-valid values are visible. Storage holds the
|
|
3077
|
+
* `z.input<Schema>` shape — `.transform()`s have NOT run, so for
|
|
3078
|
+
* a schema like `z.string().transform(v => v.length > 10)` the
|
|
3079
|
+
* value reads as `string`, not `boolean`. Use `handleSubmit` or
|
|
3080
|
+
* `form.process()` when you need the post-transform output shape.
|
|
2662
3081
|
*/
|
|
2663
|
-
values: ValuesSurface<WriteShape<
|
|
3082
|
+
values: ValuesSurface<WriteShape<Form>>;
|
|
2664
3083
|
/**
|
|
2665
3084
|
* Reactive per-field state proxy. Pinia-style nested object — read
|
|
2666
3085
|
* leaf properties (`value`, `dirty`, `touched`, `errors`, `blurred`,
|
|
@@ -2678,8 +3097,9 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2678
3097
|
* descends into the nested leaf.
|
|
2679
3098
|
*
|
|
2680
3099
|
* Leaf values follow the slim WriteShape contract: enum-typed leaves
|
|
2681
|
-
* widen to their primitive supertype
|
|
2682
|
-
*
|
|
3100
|
+
* widen to their primitive supertype, and the leaf value reflects
|
|
3101
|
+
* the `z.input<Schema>` shape (transforms deferred until parse).
|
|
3102
|
+
* The errors array, dirty flag, focus state, etc. are unaffected.
|
|
2683
3103
|
*
|
|
2684
3104
|
* Shadowing: at depth 2+, FieldState keys (`dirty`, `touched`,
|
|
2685
3105
|
* `errors`, `blank`, `focused`, `blurred`, `value`,
|
|
@@ -2688,7 +3108,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2688
3108
|
* Document edge case; rename the offending schema field if the
|
|
2689
3109
|
* collision matters.
|
|
2690
3110
|
*/
|
|
2691
|
-
fields: FieldStateMap<WriteShape<
|
|
3111
|
+
fields: FieldStateMap<WriteShape<Form>>;
|
|
2692
3112
|
/**
|
|
2693
3113
|
* Write to the form programmatically. Two forms:
|
|
2694
3114
|
*
|
|
@@ -2744,14 +3164,14 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2744
3164
|
* it blank (storage holds the slim default; UI displays
|
|
2745
3165
|
* empty; submit raises "No value supplied" for required schemas).
|
|
2746
3166
|
*/
|
|
2747
|
-
<Path extends FlatPath<Form>, Value extends
|
|
3167
|
+
<Path extends FlatPath<Form>, Value extends PathSetValuePayload<NestedType<Form, Path>>>(path: Path, value: Value): boolean;
|
|
2748
3168
|
/**
|
|
2749
3169
|
* Tuple-segment form. Equivalent to the dotted-string overload —
|
|
2750
3170
|
* useful when paths are built from variables or arrays:
|
|
2751
3171
|
* `form.setValue([prefix, 'line1'], 'value')`. The resolved leaf
|
|
2752
3172
|
* type is exact, matching the dotted-string form.
|
|
2753
3173
|
*/
|
|
2754
|
-
<const S extends ReadonlyArray<string | number>, Value extends
|
|
3174
|
+
<const S extends ReadonlyArray<string | number>, Value extends PathSetValuePayload<NestedType<Form, JoinSegments<S>>>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never), value: Value): boolean;
|
|
2755
3175
|
};
|
|
2756
3176
|
/**
|
|
2757
3177
|
* Reactive validation status. Re-runs whenever the form (or the
|
|
@@ -2784,6 +3204,32 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2784
3204
|
* `true` while the promise is in flight.
|
|
2785
3205
|
*/
|
|
2786
3206
|
validateAsync: (path?: FlatPath<Form>) => Promise<ValidationResponseWithoutValue<Form>>;
|
|
3207
|
+
/**
|
|
3208
|
+
* Imperative one-shot parse. Same pipeline as `validateAsync` —
|
|
3209
|
+
* runs refinements, applies `.transform()`s, composes blank-required
|
|
3210
|
+
* errors — but RETAINS the parsed data instead of stripping it.
|
|
3211
|
+
*
|
|
3212
|
+
* Storage holds the "honest input view" — values you wrote, with
|
|
3213
|
+
* preprocess normalization applied but `.transform()` deferred. For
|
|
3214
|
+
* schemas where the input type differs from the output type (e.g.,
|
|
3215
|
+
* `z.string().transform(v => v.length > 10)`), `form.values.X` is
|
|
3216
|
+
* the input shape and `(await form.process()).data?.X` is the
|
|
3217
|
+
* output shape.
|
|
3218
|
+
*
|
|
3219
|
+
* ```ts
|
|
3220
|
+
* const result = await form.process()
|
|
3221
|
+
* if (result.success) {
|
|
3222
|
+
* // result.data matches z.output<typeof schema>
|
|
3223
|
+
* } else {
|
|
3224
|
+
* // result.errors is the validation failure list
|
|
3225
|
+
* }
|
|
3226
|
+
* ```
|
|
3227
|
+
*
|
|
3228
|
+
* Pass a path to parse a subtree only. Async because refinements may
|
|
3229
|
+
* be async. `meta.validating` flips `true` while the promise is in
|
|
3230
|
+
* flight (shared with validateAsync).
|
|
3231
|
+
*/
|
|
3232
|
+
process: (path?: FlatPath<Form>) => Promise<ValidationResponse<GetValueFormType>>;
|
|
2787
3233
|
/**
|
|
2788
3234
|
* Bind a path to a native input via `v-register`. Returns a
|
|
2789
3235
|
* `RegisterValue` carrying the live ref and event handlers the
|
|
@@ -2869,8 +3315,8 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2869
3315
|
* scripts; `toRef` is for ref-shaped interop only.
|
|
2870
3316
|
*/
|
|
2871
3317
|
toRef: {
|
|
2872
|
-
<Path extends FlatPath<Form>>(path: Path): Readonly<Ref<NestedReadType<WriteShape<
|
|
2873
|
-
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): Readonly<Ref<NestedReadType<WriteShape<
|
|
3318
|
+
<Path extends FlatPath<Form>>(path: Path): Readonly<Ref<NestedReadType<WriteShape<Form>, Path>>>;
|
|
3319
|
+
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): Readonly<Ref<NestedReadType<WriteShape<Form>, JoinSegments<S>>>>;
|
|
2874
3320
|
};
|
|
2875
3321
|
/**
|
|
2876
3322
|
* Replace every field error for this form with the provided list.
|
|
@@ -2945,12 +3391,13 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2945
3391
|
clearFormErrors: () => void;
|
|
2946
3392
|
/**
|
|
2947
3393
|
* Form-level reactive flags, counters, and aggregates (`dirty`,
|
|
2948
|
-
* `valid`, `submitting`, `submitCount`, `
|
|
2949
|
-
*
|
|
2950
|
-
*
|
|
3394
|
+
* `valid`, `submitting`, `submitCount`, and the flat `errors`
|
|
3395
|
+
* array). See `FormMeta` for the full shape. Read leaves directly
|
|
3396
|
+
* with no `.value`.
|
|
2951
3397
|
*
|
|
2952
3398
|
* For per-field state (touched, focused, blurred, errors at one
|
|
2953
|
-
* path), use `form.fields.<path>` instead.
|
|
3399
|
+
* path), use `form.fields.<path>` instead. Undo/redo state lives at
|
|
3400
|
+
* `form.history` (see `FormHistoryNamespace`).
|
|
2954
3401
|
*/
|
|
2955
3402
|
meta: FormMeta<Form>;
|
|
2956
3403
|
/**
|
|
@@ -3011,17 +3458,12 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
3011
3458
|
*/
|
|
3012
3459
|
clearPersistedDraft: (path?: FlatPath<Form>) => Promise<void>;
|
|
3013
3460
|
/**
|
|
3014
|
-
*
|
|
3015
|
-
*
|
|
3016
|
-
*
|
|
3017
|
-
|
|
3018
|
-
undo: () => boolean;
|
|
3019
|
-
/**
|
|
3020
|
-
* Replay a previously-undone snapshot. Returns `true` on success,
|
|
3021
|
-
* `false` when the redo stack is empty. The redo stack clears as
|
|
3022
|
-
* soon as a new mutation lands.
|
|
3461
|
+
* Consolidated undo/redo namespace — `form.history.{undo, redo,
|
|
3462
|
+
* clear, canUndo, canRedo, size}`. Always present; inert when
|
|
3463
|
+
* `useForm({ history })` wasn't configured. See `FormHistoryNamespace`
|
|
3464
|
+
* for field-by-field semantics.
|
|
3023
3465
|
*/
|
|
3024
|
-
|
|
3466
|
+
history: FormHistoryNamespace;
|
|
3025
3467
|
/**
|
|
3026
3468
|
* Focus the first errored field's first visible element. Returns
|
|
3027
3469
|
* `true` when an element was focused, `false` when no candidate
|
|
@@ -3115,4 +3557,4 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
3115
3557
|
};
|
|
3116
3558
|
|
|
3117
3559
|
export { ROOT_PATH_KEY as $, ROOT_PATH as _, canonicalizePath as am, isPathPrefix as an, isUnset as ao, parseDottedPath as ap, unset as aq };
|
|
3118
|
-
export type { AttaformDefaults as A, NestedType as B, CoercionRegistry as C, DeepPartial as D, OnInvalidSubmitPolicy as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, OnSubmit as P, Path as Q, RegisterValue as R, SlimPrimitiveKind as S, PathKey as T, UseFormConfiguration as U, ValidationError as V, PendingValidationStatus as W, PersistConfig as X, PersistConfigOptions as Y, PersistIncludeMode as Z, CoercionEntry as a, ReactiveValidationStatus as a0, RegisterDirective as a1, RegisterFlatPath as a2, RegisterOptions as a3, RegisterSelectModifier as a4, RegisterTextModifier as a5, RegisterTransform as a6, Segment as a7, SetValueCallback as a8, SetValuePayload as a9, SettledValidationStatus as aa, ShouldShowErrorsConfig as ab, SlimRuntimeOf as ac, SubmitHandler as ad, Unset as ae, ValidateOn as af, ValidateOnConfig as ag, ValidationResponse as ah, ValidationResponseWithoutValue as ai, ValueOfUnion as aj, WriteMeta as ak, WriteShape as al, AbstractSchema as b, DefaultValuesShape as c, UseFormReturnType as d, RegisterModelDynamicCustomDirective as e, ShouldShowErrors as f, ApiErrorEnvelope as g, ApiErrorDetails as h, ApiErrorEntry as i, ArrayItem as j, ArrayPath as k, CoercionResult as l, CustomDirectiveRegisterAssignerFn as m, DefaultValuesResponse as n, FieldMetaPayload as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormMeta as v, FormStorage as w, FormStorageKind as x, HistoryConfig as y, IsUnion as z };
|
|
3560
|
+
export type { AttaformDefaults as A, NestedType as B, CoercionRegistry as C, DeepPartial as D, OnInvalidSubmitPolicy as E, FormKey as F, GenericForm as G, HandleSubmit as H, IsTuple as I, JoinSegments as J, KeyofUnion as K, LiftedValueShape as L, MetaTrackerValue as M, NestedReadType as N, OnError as O, OnSubmit as P, Path as Q, RegisterValue as R, SlimPrimitiveKind as S, PathKey as T, UseFormConfiguration as U, ValidationError as V, PendingValidationStatus as W, PersistConfig as X, PersistConfigOptions as Y, PersistIncludeMode as Z, CoercionEntry as a, ReactiveValidationStatus as a0, RegisterDirective as a1, RegisterFlatPath as a2, RegisterOptions as a3, RegisterSelectModifier as a4, RegisterTextModifier as a5, RegisterTransform as a6, Segment as a7, SetValueCallback as a8, SetValuePayload as a9, SettledValidationStatus as aa, ShouldShowErrorsConfig as ab, SlimRuntimeOf as ac, SubmitHandler as ad, Unset as ae, ValidateOn as af, ValidateOnConfig as ag, ValidationResponse as ah, ValidationResponseWithoutValue as ai, ValueOfUnion as aj, WriteMeta as ak, WriteShape as al, SchemaFactoryOptions as ar, AbstractSchema as b, DefaultValuesShape as c, UseFormReturnType as d, RegisterModelDynamicCustomDirective as e, ShouldShowErrors as f, ApiErrorEnvelope as g, ApiErrorDetails as h, ApiErrorEntry as i, ArrayItem as j, ArrayPath as k, CoercionResult as l, CustomDirectiveRegisterAssignerFn as m, DefaultValuesResponse as n, FieldMetaPayload as o, FieldState as p, FieldStateMap as q, FieldStateMapEntry as r, FlatPath as s, FormErrorRecord as t, FormErrorsSurface as u, FormMeta as v, FormStorage as w, FormStorageKind as x, HistoryConfig as y, IsUnion as z };
|