attaform 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/devtools.cjs +3 -3
- package/dist/chunks/devtools.cjs.map +1 -1
- package/dist/chunks/devtools.mjs +3 -3
- 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 +5 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +6 -6
- 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 +1 -1
- package/dist/runtime/plugins/attaform.mjs +1 -1
- package/dist/shared/{attaform.DDXrY-1Q.d.mts → attaform.0Gxd_OOx.d.cts} +558 -174
- package/dist/shared/{attaform.DDXrY-1Q.d.ts → attaform.0Gxd_OOx.d.mts} +558 -174
- package/dist/shared/{attaform.DDXrY-1Q.d.cts → attaform.0Gxd_OOx.d.ts} +558 -174
- package/dist/shared/{attaform.xKWYHMdq.cjs → attaform.BOi138GE.cjs} +10 -2
- package/dist/shared/{attaform.xKWYHMdq.cjs.map → attaform.BOi138GE.cjs.map} +1 -1
- package/dist/shared/{attaform.CRgix6_n.cjs → attaform.BgYBU8gV.cjs} +18 -17
- package/dist/shared/attaform.BgYBU8gV.cjs.map +1 -0
- package/dist/shared/attaform.Bubm_slq.cjs.map +1 -1
- package/dist/shared/{attaform.CNJO3mME.cjs → attaform.CDJVeoJU.cjs} +633 -236
- package/dist/shared/attaform.CDJVeoJU.cjs.map +1 -0
- package/dist/shared/{attaform.DlgKK10S.mjs → attaform.CRk8NhlD.mjs} +18 -17
- package/dist/shared/attaform.CRk8NhlD.mjs.map +1 -0
- package/dist/shared/{attaform.CXZgUECn.d.cts → attaform.CVv9Oh0a.d.mts} +41 -9
- package/dist/shared/{attaform.BYc9kugA.d.ts → attaform.CWCx2r0x.d.ts} +41 -9
- package/dist/shared/attaform.CXpzmj38.mjs.map +1 -1
- package/dist/shared/{attaform.Cc93zNzD.mjs → attaform.DXye3JKf.mjs} +10 -3
- package/dist/shared/{attaform.Cc93zNzD.mjs.map → attaform.DXye3JKf.mjs.map} +1 -1
- package/dist/shared/{attaform.DOKOyb3Y.d.mts → attaform.Dq5BabH1.d.cts} +41 -9
- package/dist/shared/{attaform.B5GWYl76.cjs → attaform.RypIkgVy.cjs} +38 -7
- package/dist/shared/attaform.RypIkgVy.cjs.map +1 -0
- package/dist/shared/{attaform.al_rpt7_.mjs → attaform.a99dQV7Q.mjs} +39 -8
- package/dist/shared/attaform.a99dQV7Q.mjs.map +1 -0
- package/dist/shared/{attaform.BRTxpA3q.mjs → attaform.qxyip_aN.mjs} +634 -238
- package/dist/shared/attaform.qxyip_aN.mjs.map +1 -0
- package/dist/transforms.d.cts +2 -2
- package/dist/transforms.d.mts +2 -2
- package/dist/transforms.d.ts +2 -2
- package/dist/zod-v3.cjs +55 -3
- package/dist/zod-v3.cjs.map +1 -1
- package/dist/zod-v3.d.cts +77 -4
- package/dist/zod-v3.d.mts +77 -4
- package/dist/zod-v3.d.ts +77 -4
- package/dist/zod-v3.mjs +56 -6
- package/dist/zod-v3.mjs.map +1 -1
- package/dist/zod.cjs +372 -5
- package/dist/zod.cjs.map +1 -1
- package/dist/zod.d.cts +120 -4
- package/dist/zod.d.mts +120 -4
- package/dist/zod.d.ts +120 -4
- package/dist/zod.mjs +371 -8
- package/dist/zod.mjs.map +1 -1
- package/package.json +3 -1
- package/dist/shared/attaform.B5GWYl76.cjs.map +0 -1
- package/dist/shared/attaform.BRTxpA3q.mjs.map +0 -1
- package/dist/shared/attaform.CNJO3mME.cjs.map +0 -1
- package/dist/shared/attaform.CRgix6_n.cjs.map +0 -1
- package/dist/shared/attaform.DlgKK10S.mjs.map +0 -1
- package/dist/shared/attaform.al_rpt7_.mjs.map +0 -1
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
import { Ref, ObjectDirective, ComputedRef } from 'vue';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Schema-attached field metadata — the shared types used by both Zod
|
|
5
|
+
* adapters (`attaform/zod` for v4 and `attaform/zod-v3` for v3) so a
|
|
6
|
+
* consumer's data flow reads the same shape regardless of adapter.
|
|
7
|
+
*
|
|
8
|
+
* The Zod 4 adapter creates a typed `z.registry<FieldMetaPayload>()`
|
|
9
|
+
* and writes through `schema.register(fieldMeta, payload)` (native) or
|
|
10
|
+
* the `withMeta(schema, payload)` helper. The Zod 3 adapter has no
|
|
11
|
+
* native registry — it shims a `WeakMap<ZodTypeAny, FieldMetaPayload>`
|
|
12
|
+
* with the same write API via `withMeta`.
|
|
13
|
+
*
|
|
14
|
+
* Reads are unified through `AbstractSchema.getFieldMetaAtPath(path)`,
|
|
15
|
+
* which returns a fully-resolved `ResolvedFieldMeta` (label /
|
|
16
|
+
* description / placeholder / meta) so the per-leaf and per-container
|
|
17
|
+
* `FieldState` producers in core never see the version split.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The metadata a consumer attaches to a schema node — short label
|
|
21
|
+
* (presentational), longer description (helper text), placeholder
|
|
22
|
+
* (input affordance). Declared as `interface` (not `type`) so
|
|
23
|
+
* downstream apps can extend the shape via TypeScript declaration
|
|
24
|
+
* merging when they want to register richer payloads (tooltips,
|
|
25
|
+
* icons, badge counts, etc.):
|
|
26
|
+
*
|
|
27
|
+
* declare module 'attaform/zod' {
|
|
28
|
+
* interface FieldMetaPayload {
|
|
29
|
+
* tooltip?: string
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* After augmentation, `withMeta(schema, { tooltip: '…' })` is typed
|
|
34
|
+
* and `state.meta.tooltip` reads back as `string | undefined`.
|
|
35
|
+
*
|
|
36
|
+
* Every key is optional. Empty payloads (no keys registered) are
|
|
37
|
+
* indistinguishable from "not registered at all" — both surface as
|
|
38
|
+
* fallbacks (humanize for label, undefined for the rest).
|
|
39
|
+
*/
|
|
40
|
+
interface FieldMetaPayload {
|
|
41
|
+
label?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The fully-resolved metadata returned by
|
|
47
|
+
* `AbstractSchema.getFieldMetaAtPath(path)`. Adapters apply the
|
|
48
|
+
* precedence rules:
|
|
49
|
+
*
|
|
50
|
+
* - `label`: registry payload → `humanize(lastSegment)`
|
|
51
|
+
* - `description`: registry payload → schema's `.describe()` value → `undefined`
|
|
52
|
+
* - `placeholder`: registry payload → `undefined`
|
|
53
|
+
* - `meta`: full registered payload, frozen — empty object if nothing registered
|
|
54
|
+
*
|
|
55
|
+
* `label` is always a non-empty string at leaves (humanize fallback
|
|
56
|
+
* guarantees this for any non-numeric segment). For containers it
|
|
57
|
+
* may collapse to the empty string when the path is empty (root) or
|
|
58
|
+
* the segment is a numeric index — callers display "" or substitute
|
|
59
|
+
* a context-appropriate fallback.
|
|
60
|
+
*/
|
|
61
|
+
type ResolvedFieldMeta = {
|
|
62
|
+
readonly label: string;
|
|
63
|
+
readonly description: string | undefined;
|
|
64
|
+
readonly placeholder: string | undefined;
|
|
65
|
+
readonly meta: Readonly<FieldMetaPayload>;
|
|
66
|
+
};
|
|
67
|
+
|
|
3
68
|
/**
|
|
4
69
|
* Path primitives for advanced integrations. The form library accepts
|
|
5
70
|
* paths in dotted-string form (`'user.email'`) at every public API.
|
|
@@ -60,6 +125,21 @@ declare function canonicalizePath(input: string | Path): {
|
|
|
60
125
|
declare const ROOT_PATH: Path;
|
|
61
126
|
/** Stable string key for the root path. */
|
|
62
127
|
declare const ROOT_PATH_KEY: PathKey;
|
|
128
|
+
/**
|
|
129
|
+
* `true` when `path` starts with every segment of `prefix` (in order).
|
|
130
|
+
* The empty `prefix` matches every path — ROOT prefix is universal.
|
|
131
|
+
*
|
|
132
|
+
* Walks segments rather than `PathKey` strings because the data this
|
|
133
|
+
* helper operates on (e.g. `meta.errors[].path`) carries segment
|
|
134
|
+
* arrays directly.
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* isPathPrefix(['cargo'], ['cargo', 'items', 0, 'sku']) // true
|
|
138
|
+
* isPathPrefix(['cargo', 'items'], ['cargo']) // false (path shorter)
|
|
139
|
+
* isPathPrefix([], ['anything']) // true (root prefix)
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare function isPathPrefix(prefix: readonly Segment[], path: readonly Segment[]): boolean;
|
|
63
143
|
|
|
64
144
|
/** Internal brand for the `Unset` type. Never exposed at runtime. */
|
|
65
145
|
declare const _unsetBrand: unique symbol;
|
|
@@ -102,7 +182,7 @@ type Unset = typeof _unsetBrand;
|
|
|
102
182
|
* every realm gets the same sentinel.
|
|
103
183
|
*
|
|
104
184
|
* @see {@link isUnset} — type guard that narrows a value back to {@link Unset}.
|
|
105
|
-
* @see `docs/blank.md` — the conceptual model behind blank-aware fields.
|
|
185
|
+
* @see `docs/recipes/blank-inputs.md` — the conceptual model behind blank-aware fields.
|
|
106
186
|
*/
|
|
107
187
|
declare const unset: Unset;
|
|
108
188
|
/**
|
|
@@ -140,6 +220,87 @@ type CompleteFlatPath<Form, Key extends keyof Form = keyof Form> = IsObjectOrArr
|
|
|
140
220
|
* (no intermediate container paths).
|
|
141
221
|
*/
|
|
142
222
|
type FlatPath<Form, Key extends keyof Form = keyof Form, ForceFullPath extends boolean = false> = ForceFullPath extends true ? CompleteFlatPath<Form, Key> : PartialFlatPath<Form, Key>;
|
|
223
|
+
/**
|
|
224
|
+
* Convert a tuple of path segments to its dotted-string equivalent.
|
|
225
|
+
*
|
|
226
|
+
* `JoinSegments<['cargo', 'items', 0, 'sku']>` → `'cargo.items.0.sku'`
|
|
227
|
+
*
|
|
228
|
+
* Recursion depth is bounded by the tuple length (typically 3–4),
|
|
229
|
+
* not by form depth — the cost does not scale with `FlatPath<Form>`.
|
|
230
|
+
* Template literal types distribute over union members, so segments
|
|
231
|
+
* containing unions like `'pickup' | 'delivery'` propagate through
|
|
232
|
+
* to the joined path's union: `JoinSegments<['pickup' | 'delivery', 'line1']>`
|
|
233
|
+
* → `'pickup.line1' | 'delivery.line1'`. This is what makes
|
|
234
|
+
* tuple-form path APIs work cleanly inside `v-for` over a prefix
|
|
235
|
+
* variable: the joined result is checked against `FlatPath<Form>` /
|
|
236
|
+
* `RegisterFlatPath<Form>` (which already exist), so we don't
|
|
237
|
+
* enumerate a separate tuple-path union.
|
|
238
|
+
*/
|
|
239
|
+
type JoinSegments<S extends ReadonlyArray<string | number>, Acc extends string = ''> = S extends readonly [
|
|
240
|
+
infer Head extends string | number,
|
|
241
|
+
...infer Rest extends ReadonlyArray<string | number>
|
|
242
|
+
] ? Acc extends '' ? JoinSegments<Rest, `${Head}`> : JoinSegments<Rest, `${Acc}.${Head}`> : Acc;
|
|
243
|
+
/**
|
|
244
|
+
* `true` when `T` is a union (multiple members), `false` when it's a
|
|
245
|
+
* single type. Used to gate non-homomorphic mapped-type forms so
|
|
246
|
+
* single-object types retain their homomorphic `[K in keyof T]`
|
|
247
|
+
* lookup (preserving literal keys instead of widening to an index
|
|
248
|
+
* signature).
|
|
249
|
+
*/
|
|
250
|
+
type IsUnion<T, U = T> = T extends T ? ([U] extends [T] ? false : true) : never;
|
|
251
|
+
/**
|
|
252
|
+
* Union of all keys across all members of `T`. For a single object
|
|
253
|
+
* type this equals `keyof T`; for a discriminated union `A | B`, it
|
|
254
|
+
* produces `keyof A | keyof B` (whereas naked `keyof (A | B)` would
|
|
255
|
+
* intersect to common keys only).
|
|
256
|
+
*
|
|
257
|
+
* Paired with `ValueOfUnion` to merge variant key sets in chained
|
|
258
|
+
* metadata proxies (`form.fields`, `form.errors`) so per-variant
|
|
259
|
+
* leaves are addressable through one chained-access shape, regardless
|
|
260
|
+
* of which discriminant is currently active.
|
|
261
|
+
*/
|
|
262
|
+
type KeyofUnion<T> = T extends unknown ? keyof T : never;
|
|
263
|
+
/**
|
|
264
|
+
* Value at key `K` across union members of `T`. Members containing
|
|
265
|
+
* `K` contribute `T[K]`; members lacking `K` contribute `undefined`.
|
|
266
|
+
*
|
|
267
|
+
* The resulting union mirrors the runtime semantics of metadata
|
|
268
|
+
* proxies: chained access works at every union member, with the leaf
|
|
269
|
+
* carrying `T | undefined` to reflect that the key is absent in some
|
|
270
|
+
* variants and the runtime returns a stable stub there.
|
|
271
|
+
*/
|
|
272
|
+
type ValueOfUnion<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T[K] : undefined : never;
|
|
273
|
+
/**
|
|
274
|
+
* Apply the discriminated-union "lift" to a value shape (i.e., a
|
|
275
|
+
* shape carrying actual values, not metadata leaves like
|
|
276
|
+
* `FieldState`). Single-object types map homomorphically;
|
|
277
|
+
* discriminated unions of objects merge keys via
|
|
278
|
+
* `KeyofUnion` / `ValueOfUnion` so per-variant fields are reachable
|
|
279
|
+
* through one chained-access shape.
|
|
280
|
+
*
|
|
281
|
+
* Used by `ValuesSurface` to make `form.values.cargo.permitNumber`
|
|
282
|
+
* (oversized-only) typecheck regardless of the active variant —
|
|
283
|
+
* matching the runtime, where plain JS object access on a missing
|
|
284
|
+
* variant key returns `undefined` rather than throwing.
|
|
285
|
+
*
|
|
286
|
+
* Distinct from `FieldStateMapEntry`: that variant carries
|
|
287
|
+
* `FieldState<T>` at the leaf; this one carries the leaf VALUE
|
|
288
|
+
* directly. They share the same union-merging logic but differ in
|
|
289
|
+
* what the recursion bottoms out at.
|
|
290
|
+
*
|
|
291
|
+
* Date / Map / Set / RegExp / function leaves stay opaque (not
|
|
292
|
+
* recursed into) — value reads of those types should preserve the
|
|
293
|
+
* platform shape unchanged.
|
|
294
|
+
*/
|
|
295
|
+
type LiftedValueShape<T> = [T] extends [
|
|
296
|
+
string | number | boolean | bigint | symbol | null | undefined
|
|
297
|
+
] ? T : [T] extends [
|
|
298
|
+
Date | RegExp | Map<unknown, unknown> | Set<unknown> | ((...args: never) => unknown)
|
|
299
|
+
] ? T : [T] extends [ReadonlyArray<unknown>] ? T : [T] extends [object] ? [IsUnion<T>] extends [true] ? {
|
|
300
|
+
[K in KeyofUnion<T>]: LiftedValueShape<ValueOfUnion<T, K>>;
|
|
301
|
+
} : {
|
|
302
|
+
[K in keyof T]: LiftedValueShape<T[K]>;
|
|
303
|
+
} : T;
|
|
143
304
|
/**
|
|
144
305
|
* Recursive `Partial` — every property at every depth is optional.
|
|
145
306
|
* Used as the parameter type of `defaultValues` and `reset()` so
|
|
@@ -154,11 +315,18 @@ type DeepPartial<T> = T extends Primitive ? T : T extends Array<infer ArrayItem>
|
|
|
154
315
|
*
|
|
155
316
|
* `NestedType<{ user: { email: string } }, 'user.email'>` → `string`
|
|
156
317
|
*
|
|
318
|
+
* On discriminated-union descents (e.g. `cargo` is `A | B | C`), uses
|
|
319
|
+
* `KeyofUnion` / `ValueOfUnion` so per-variant keys resolve to
|
|
320
|
+
* `T | undefined` instead of `never`. This keeps NestedType in lockstep
|
|
321
|
+
* with `FlatPath`: any path FlatPath says is reachable resolves to a
|
|
322
|
+
* useful value type (vs. silently collapsing to `never` because
|
|
323
|
+
* `keyof (A|B|C)` would be the intersection of all variants' keys).
|
|
324
|
+
*
|
|
157
325
|
* TypeScript caps conditional-type recursion at around 50 levels;
|
|
158
326
|
* paths deeper than that resolve to `never`. Real form schemas
|
|
159
327
|
* never reach this depth.
|
|
160
328
|
*/
|
|
161
|
-
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
|
|
329
|
+
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;
|
|
162
330
|
type Primitive = string | number | boolean | symbol | bigint | null | undefined;
|
|
163
331
|
/**
|
|
164
332
|
* Distinguish a tuple from a regular array.
|
|
@@ -174,13 +342,15 @@ type IsTuple<T extends readonly unknown[]> = number extends T['length'] ? false
|
|
|
174
342
|
* Path-resolved type for read-side APIs. Like `NestedType`, but once
|
|
175
343
|
* the walk crosses an array index segment the resulting type is
|
|
176
344
|
* tagged `| undefined` (the runtime can return undefined for
|
|
177
|
-
* out-of-bounds reads).
|
|
345
|
+
* out-of-bounds reads). Discriminated-union descents follow the same
|
|
346
|
+
* `KeyofUnion`/`ValueOfUnion` rule as `NestedType` — per-variant
|
|
347
|
+
* keys resolve to `T | undefined`, agreeing with `FlatPath`.
|
|
178
348
|
*
|
|
179
349
|
* Used by `form.values.<path>` reads, `form.toRef(path)`, and
|
|
180
350
|
* `register(path).innerRef` so the compile-time type honours the
|
|
181
351
|
* runtime possibility of a missing array position.
|
|
182
352
|
*/
|
|
183
|
-
type NestedReadType<RootValue, FlattenedPath extends string, _Tainted extends boolean = false, _RootValue = NonNullable<RootValue>> = IsObjectOrArray<_RootValue> extends false ? never : FlattenedPath extends `${infer Key}.${infer Rest}` ? Key extends `${number}` ? Key extends
|
|
353
|
+
type NestedReadType<RootValue, FlattenedPath extends string, _Tainted extends boolean = false, _RootValue = NonNullable<RootValue>> = IsObjectOrArray<_RootValue> extends false ? never : FlattenedPath extends `${infer Key}.${infer Rest}` ? Key extends `${number}` ? Key extends KeyofUnion<_RootValue> ? NestedReadType<ValueOfUnion<_RootValue, Key>, Rest, true> : Key extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? NestedReadType<ValueOfUnion<_RootValue, NumericKey>, Rest, true> : never : never : Key extends KeyofUnion<_RootValue> ? NestedReadType<ValueOfUnion<_RootValue, Key>, Rest, _Tainted> : never : FlattenedPath extends `${number}` ? FlattenedPath extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, FlattenedPath> | undefined : FlattenedPath extends `${infer NumericKey extends number}` ? NumericKey extends KeyofUnion<_RootValue> ? ValueOfUnion<_RootValue, NumericKey> | undefined : never : never : FlattenedPath extends KeyofUnion<_RootValue> ? _Tainted extends true ? ValueOfUnion<_RootValue, FlattenedPath> | undefined : ValueOfUnion<_RootValue, FlattenedPath> : never;
|
|
184
354
|
/**
|
|
185
355
|
* Filter FlatPath<Form> down to the subset of paths whose resolved leaf
|
|
186
356
|
* is an array. Used by the typed field-array helpers (append / remove /
|
|
@@ -485,8 +655,8 @@ type AbstractSchema<Form, GetValueFormType> = {
|
|
|
485
655
|
* length and reuses one element default for every position.
|
|
486
656
|
* - `undefined` → the path doesn't resolve to an array OR the
|
|
487
657
|
* adapter can't determine the shape. The runtime falls back to
|
|
488
|
-
*
|
|
489
|
-
*
|
|
658
|
+
* a probe loop in this case (defensive — every built-in adapter
|
|
659
|
+
* returns `number` or `null`).
|
|
490
660
|
*
|
|
491
661
|
* Wrappers (optional / nullable / default / readonly / catch /
|
|
492
662
|
* pipe / lazy) are peeled transparently before the type check, so
|
|
@@ -572,7 +742,7 @@ type AbstractSchema<Form, GetValueFormType> = {
|
|
|
572
742
|
*
|
|
573
743
|
* The leaf-aware branching is what kills the FIELD_STATE_KEYS
|
|
574
744
|
* shadowing problem: reserved leaf-prop names (`dirty`, `errors`,
|
|
575
|
-
* `
|
|
745
|
+
* `valid`, …) inject only at the FieldState terminal, not at
|
|
576
746
|
* every depth. A schema field literally named `dirty` at depth ≥ 2
|
|
577
747
|
* stays reachable as a sub-proxy or leaf in its own right.
|
|
578
748
|
*
|
|
@@ -670,6 +840,34 @@ type AbstractSchema<Form, GetValueFormType> = {
|
|
|
670
840
|
* without this hook.
|
|
671
841
|
*/
|
|
672
842
|
getUnionDiscriminatorAtPath(path: Path): UnionDiscriminatorContext | undefined;
|
|
843
|
+
/**
|
|
844
|
+
* Return the resolved field metadata for the schema node at `path`
|
|
845
|
+
* — label, description, placeholder, plus the full registered
|
|
846
|
+
* payload as `meta` for consumer-augmented keys. Reads through the
|
|
847
|
+
* adapter's metadata mechanism (Zod 4: `z.registry()`; Zod 3:
|
|
848
|
+
* a WeakMap shim) and applies these one-way fallbacks:
|
|
849
|
+
*
|
|
850
|
+
* - `label`: registry payload → `humanize(lastSegment)`
|
|
851
|
+
* - `description`: registry payload → `schema.description`
|
|
852
|
+
* (`.describe()` value) → `undefined`
|
|
853
|
+
* - `placeholder`: registry payload → `undefined`
|
|
854
|
+
* - `meta`: registry payload (frozen) — empty object when
|
|
855
|
+
* nothing was registered
|
|
856
|
+
*
|
|
857
|
+
* `path` is the canonical `Segment[]`. The empty path resolves to
|
|
858
|
+
* the root schema's metadata. Multiple candidates (DU branches)
|
|
859
|
+
* resolve against the first candidate to match the existing
|
|
860
|
+
* first-success precedent in `getDefaultAtPath` /
|
|
861
|
+
* `validateAtPath` — schema authors register on the union root
|
|
862
|
+
* for shared metadata, on individual branches for variant-
|
|
863
|
+
* specific metadata.
|
|
864
|
+
*
|
|
865
|
+
* Optional. The runtime treats a missing implementation as a
|
|
866
|
+
* stub that returns `EMPTY_RESOLVED_FIELD_META` — so adapters
|
|
867
|
+
* that don't model field metadata yet can omit it; consumers
|
|
868
|
+
* see humanized fallbacks for `label`, undefined elsewhere.
|
|
869
|
+
*/
|
|
870
|
+
getFieldMetaAtPath?(path: Path): ResolvedFieldMeta;
|
|
673
871
|
/**
|
|
674
872
|
* Return `true` if `validateAtPath` MAY have to run asynchronously
|
|
675
873
|
* to surface every error this schema can produce. The runtime uses
|
|
@@ -686,6 +884,12 @@ type AbstractSchema<Form, GetValueFormType> = {
|
|
|
686
884
|
* don't yet support detection — can omit it; async-only errors
|
|
687
885
|
* then fall back to firing on first user mutation, matching the
|
|
688
886
|
* pre-detection behavior. Detection is best-effort.
|
|
887
|
+
*
|
|
888
|
+
* For per-path queries, compose with `getSchemasAtPath(path)`:
|
|
889
|
+
* each candidate sub-schema exposes its own
|
|
890
|
+
* `needsAsyncValidation`, so a caller asking "does the cargo
|
|
891
|
+
* subtree contain async work?" can union the per-candidate
|
|
892
|
+
* answers without a separate top-level overload.
|
|
689
893
|
*/
|
|
690
894
|
needsAsyncValidation?(): boolean;
|
|
691
895
|
};
|
|
@@ -1288,14 +1492,18 @@ type SubmitHandler = (event?: Event) => Promise<void>;
|
|
|
1288
1492
|
*/
|
|
1289
1493
|
type HandleSubmit<Form extends GenericForm> = (onSubmit: OnSubmit<Form>, onError?: OnError) => SubmitHandler;
|
|
1290
1494
|
/**
|
|
1291
|
-
* Per-leaf
|
|
1292
|
-
*
|
|
1495
|
+
* Per-leaf internal tracker record. Distinct from `FieldState.meta`
|
|
1496
|
+
* (which surfaces as `Readonly<FieldMetaPayload>` — the registry-
|
|
1497
|
+
* attached label / description / placeholder payload). Surfaced for
|
|
1498
|
+
* custom-adapter authors threading metadata through their own
|
|
1499
|
+
* pipelines; most consumers don't reach for it directly — the
|
|
1500
|
+
* matching fields appear with friendlier shape on `FieldState`.
|
|
1293
1501
|
*
|
|
1294
1502
|
* - `updatedAt` — ISO timestamp of the most recent write at this path,
|
|
1295
1503
|
* or `null` if the field has never been written.
|
|
1296
1504
|
* - `rawValue` — the value as it arrived (before any transform);
|
|
1297
1505
|
* useful for distinguishing parse-coerced reads from raw user input.
|
|
1298
|
-
* - `
|
|
1506
|
+
* - `connected` — whether at least one DOM element bound to this
|
|
1299
1507
|
* path is currently mounted. Flips to `false` when every binding
|
|
1300
1508
|
* unmounts.
|
|
1301
1509
|
* - `formKey` — identifier of the form this metadata belongs to.
|
|
@@ -1307,7 +1515,7 @@ type MetaTrackerValue = {
|
|
|
1307
1515
|
/** Value as it arrived, before any transforms. */
|
|
1308
1516
|
rawValue: unknown;
|
|
1309
1517
|
/** `true` while at least one binding to this path is currently mounted. */
|
|
1310
|
-
|
|
1518
|
+
connected: boolean;
|
|
1311
1519
|
/** Form this metadata belongs to. */
|
|
1312
1520
|
formKey: FormKey;
|
|
1313
1521
|
/** Dotted-string path to this leaf. */
|
|
@@ -1336,7 +1544,7 @@ type MetaTrackerValue = {
|
|
|
1336
1544
|
* want pre-error introspection ("the user hasn't decided yet"
|
|
1337
1545
|
* indicator, "review unanswered fields" hint).
|
|
1338
1546
|
*
|
|
1339
|
-
* See `docs/blank.md` for the full conceptual model.
|
|
1547
|
+
* See `docs/recipes/blank-inputs.md` for the full conceptual model.
|
|
1340
1548
|
*/
|
|
1341
1549
|
blank: boolean;
|
|
1342
1550
|
};
|
|
@@ -1500,11 +1708,17 @@ type RegisterOptions = {
|
|
|
1500
1708
|
*
|
|
1501
1709
|
* Or read `innerRef` directly when integrating with custom components.
|
|
1502
1710
|
*
|
|
1503
|
-
* The
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1711
|
+
* The returned value is a `shallowReadonly` reactive proxy: top-level
|
|
1712
|
+
* reads (`rv.path`, `rv.formKey`, `rv.persist`, …) track in reactive
|
|
1713
|
+
* scopes, mutations are blocked, and inner refs (`innerRef`,
|
|
1714
|
+
* `displayValue`) keep their `Ref` shape.
|
|
1715
|
+
*
|
|
1716
|
+
* `path`, `formKey`, and `formInstanceId` are the wrapper-component
|
|
1717
|
+
* primitives — a generic component using `useRegister()` can derive
|
|
1718
|
+
* field state and form identity from them without re-threading props
|
|
1719
|
+
* from the parent.
|
|
1506
1720
|
*/
|
|
1507
|
-
type RegisterValue<Value = unknown> = {
|
|
1721
|
+
type RegisterValue<Value = unknown> = Readonly<{
|
|
1508
1722
|
/**
|
|
1509
1723
|
* Live, read-only reactive value at this path. Watch it to drive
|
|
1510
1724
|
* UI that depends on the field's current value.
|
|
@@ -1528,6 +1742,44 @@ type RegisterValue<Value = unknown> = {
|
|
|
1528
1742
|
* directives signal whether the write should be persisted.
|
|
1529
1743
|
*/
|
|
1530
1744
|
setValueWithInternalPath: (value: unknown, meta?: WriteMeta) => boolean;
|
|
1745
|
+
/**
|
|
1746
|
+
* Canonical, JSON-encoded path key for this binding (e.g.
|
|
1747
|
+
* `'["items",0,"name"]'`). Useful for stable Map / Set keys, log
|
|
1748
|
+
* messages, and equality checks against another `RegisterValue`'s
|
|
1749
|
+
* path. Treat as opaque — for `form.fields(...)` / `form.values(...)`
|
|
1750
|
+
* lookups inside wrapper components, use `segments` instead.
|
|
1751
|
+
*/
|
|
1752
|
+
path: PathKey;
|
|
1753
|
+
/**
|
|
1754
|
+
* Structured path segments for this binding (e.g.
|
|
1755
|
+
* `['items', 0, 'name']`). The consumer-friendly form for
|
|
1756
|
+
* `form.fields(...)` / `form.values(...)` lookups in generic
|
|
1757
|
+
* wrapper components:
|
|
1758
|
+
*
|
|
1759
|
+
* ```ts
|
|
1760
|
+
* const rv = useRegister()
|
|
1761
|
+
* const form = injectForm()
|
|
1762
|
+
* const field = computed(() => form.fields(rv.value?.segments ?? []))
|
|
1763
|
+
* ```
|
|
1764
|
+
*
|
|
1765
|
+
* Frozen at runtime so wrapper components can read it without
|
|
1766
|
+
* defensive copying.
|
|
1767
|
+
*/
|
|
1768
|
+
segments: Path;
|
|
1769
|
+
/**
|
|
1770
|
+
* The form's user-supplied (or auto-allocated) `key`, mirroring
|
|
1771
|
+
* `form.key` on the public form API. Useful in wrapper components
|
|
1772
|
+
* that target a specific form by key without prop-drilling.
|
|
1773
|
+
*/
|
|
1774
|
+
formKey: string;
|
|
1775
|
+
/**
|
|
1776
|
+
* Per-mount runtime identifier for the form instance. Stable across
|
|
1777
|
+
* the form's lifetime. Used by the directive to scope element
|
|
1778
|
+
* registrations to a single mount and exposed here for wrapper
|
|
1779
|
+
* components that need to disambiguate sibling forms with the same
|
|
1780
|
+
* `key`.
|
|
1781
|
+
*/
|
|
1782
|
+
formInstanceId: string;
|
|
1531
1783
|
/**
|
|
1532
1784
|
* Read-only, string-form view of the field's current value — what
|
|
1533
1785
|
* the compile-time `:value` injection reads on every input /
|
|
@@ -1540,7 +1792,7 @@ type RegisterValue<Value = unknown> = {
|
|
|
1540
1792
|
* patching `el.value` back to `'0'` (the slim default).
|
|
1541
1793
|
*/
|
|
1542
1794
|
displayValue: Readonly<Ref<string>>;
|
|
1543
|
-
}
|
|
1795
|
+
}>;
|
|
1544
1796
|
/**
|
|
1545
1797
|
* Custom assigner installed on an element via the directive's
|
|
1546
1798
|
* `[assignKey]` slot OR an `@update:registerValue` listener. Called
|
|
@@ -1738,78 +1990,21 @@ type SetValueCallback<Read, Write = Read> = (prev: Read) => Read | Write;
|
|
|
1738
1990
|
* array elements as possibly-undefined to reflect runtime reality.
|
|
1739
1991
|
*/
|
|
1740
1992
|
type SetValuePayload<Write, Read = Write> = Write | SetValueCallback<Read, Write>;
|
|
1741
|
-
type DeepFlatten<T> = T extends object ? {
|
|
1742
|
-
[K in keyof T]: DeepFlatten<T[K]>;
|
|
1743
|
-
} : T;
|
|
1744
|
-
/**
|
|
1745
|
-
* Focus / blur / touched flags for a registered field.
|
|
1746
|
-
*
|
|
1747
|
-
* - `focused` — `true` while the user is interacting with the field;
|
|
1748
|
-
* `false` after blur. `null` until the field has ever been focused.
|
|
1749
|
-
* - `blurred` — `true` after the field has lost focus at least once.
|
|
1750
|
-
* `null` before any blur event.
|
|
1751
|
-
* - `touched` — flips to `true` on the first blur after a focus and
|
|
1752
|
-
* stays `true` thereafter. Useful for "show errors only after the
|
|
1753
|
-
* user has interacted" UX.
|
|
1754
|
-
*/
|
|
1755
|
-
type DOMFieldState = {
|
|
1756
|
-
/** `true` while focused; `false` after blur; `null` before first focus. */
|
|
1757
|
-
focused: boolean | null;
|
|
1758
|
-
/** `true` once the field has lost focus at least once; `null` before. */
|
|
1759
|
-
blurred: boolean | null;
|
|
1760
|
-
/** Flips to `true` on the first blur after a focus and stays there. */
|
|
1761
|
-
touched: boolean | null;
|
|
1762
|
-
};
|
|
1763
1993
|
/**
|
|
1764
|
-
*
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1994
|
+
* Per-field reactive shape returned by `form.fields.<leaf-path>` and
|
|
1995
|
+
* `form.fields(path)`. Slim, readonly across the board. The unified
|
|
1996
|
+
* shape replaces the older split between `FieldState` /
|
|
1997
|
+
* `FieldStateBranch`: one type lives at every path, with aggregations
|
|
1998
|
+
* rolled up at containers.
|
|
1769
1999
|
*
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
1772
|
-
* `
|
|
2000
|
+
* Leaf-aware: this shape only injects these keys at LEAF paths via
|
|
2001
|
+
* dot-access. At container paths the proxy descends without
|
|
2002
|
+
* injecting, so a schema field literally named `dirty` at depth 2+
|
|
2003
|
+
* stays reachable as a descent target — no shadowing. Container
|
|
2004
|
+
* call-form (`form.fields('address')`) returns a `FieldState`
|
|
2005
|
+
* surface where the keys are aggregations of the descendant leaves.
|
|
1773
2006
|
*/
|
|
1774
|
-
type FieldState<Value = unknown> =
|
|
1775
|
-
/** Per-field metadata (timestamps, raw value, connection state). */
|
|
1776
|
-
meta: MetaTrackerValue;
|
|
1777
|
-
/**
|
|
1778
|
-
* Validation errors for this path. Populated automatically by
|
|
1779
|
-
* `handleSubmit` and per-field validation; also writable via
|
|
1780
|
-
* `setFieldErrors` / `addFieldErrors`. Empty when valid — safe
|
|
1781
|
-
* to read without a null check.
|
|
1782
|
-
*/
|
|
1783
|
-
errors: ValidationError[];
|
|
1784
|
-
/** The value the field was initialised with. */
|
|
1785
|
-
originalValue: Value;
|
|
1786
|
-
/** The value before the most recent write. */
|
|
1787
|
-
previousValue: Value;
|
|
1788
|
-
/** The current value at this path. */
|
|
1789
|
-
currentValue: Value;
|
|
1790
|
-
/** `true` when `currentValue` matches `originalValue`. */
|
|
1791
|
-
pristine: boolean;
|
|
1792
|
-
/** `true` when `currentValue` differs from `originalValue`. */
|
|
1793
|
-
dirty: boolean;
|
|
1794
|
-
/**
|
|
1795
|
-
* `true` when this field is **blank** — the side-channel for
|
|
1796
|
-
* storage / display divergence (numeric leaves where storage
|
|
1797
|
-
* holds `0` / `0n` but the DOM shows `''`, plus any primitive
|
|
1798
|
-
* leaf the consumer explicitly opted in via `unset`). Surfaces
|
|
1799
|
-
* both as a top-level field here AND via `meta.blank` (the meta
|
|
1800
|
-
* projection mirrors the same value). See `docs/blank.md`.
|
|
1801
|
-
*/
|
|
1802
|
-
blank: boolean;
|
|
1803
|
-
}>;
|
|
1804
|
-
/**
|
|
1805
|
-
* Per-field reactive shape returned by `form.fields.<leaf-path>`.
|
|
1806
|
-
* Slim, readonly across the board. Leaf-aware: this shape only
|
|
1807
|
-
* appears at LEAF paths (primitives, dates). At container paths
|
|
1808
|
-
* the proxy descends without injecting these keys, so a schema
|
|
1809
|
-
* field literally named `dirty` at depth 2+ stays reachable as a
|
|
1810
|
-
* descent target — no shadowing.
|
|
1811
|
-
*/
|
|
1812
|
-
type FieldStateLeaf<Value = unknown> = {
|
|
2007
|
+
type FieldState<Value = unknown> = {
|
|
1813
2008
|
readonly value: Value;
|
|
1814
2009
|
readonly original: Value;
|
|
1815
2010
|
readonly pristine: boolean;
|
|
@@ -1817,22 +2012,121 @@ type FieldStateLeaf<Value = unknown> = {
|
|
|
1817
2012
|
readonly focused: boolean | null;
|
|
1818
2013
|
readonly blurred: boolean | null;
|
|
1819
2014
|
readonly touched: boolean | null;
|
|
1820
|
-
readonly
|
|
2015
|
+
readonly connected: boolean;
|
|
2016
|
+
/**
|
|
2017
|
+
* The first DOM element bound to this path via `v-register`, or
|
|
2018
|
+
* `null` when none is registered (initial mount, post-unmount,
|
|
2019
|
+
* SSR). "First" means first by registration order. Reach for it
|
|
2020
|
+
* when you need to call a native DOM method on a field's input —
|
|
2021
|
+
* `focus()`, `scrollIntoView()`, `select()`, `setSelectionRange()`,
|
|
2022
|
+
* etc. — without the library having to verb every imperative:
|
|
2023
|
+
*
|
|
2024
|
+
* ```ts
|
|
2025
|
+
* form.fields.email.element?.focus()
|
|
2026
|
+
* form.fields.email.element?.scrollIntoView({ block: 'center' })
|
|
2027
|
+
* ```
|
|
2028
|
+
*
|
|
2029
|
+
* For paths with multiple bindings (input syncing, mirrored
|
|
2030
|
+
* shadow inputs), prefer `elements` and pick the right target
|
|
2031
|
+
* yourself. Reactive: register / deregister triggers
|
|
2032
|
+
* re-evaluation.
|
|
2033
|
+
*/
|
|
2034
|
+
readonly element: HTMLElement | null;
|
|
2035
|
+
/**
|
|
2036
|
+
* Every DOM element currently bound to this path via `v-register`,
|
|
2037
|
+
* in registration order. Empty array when none is registered.
|
|
2038
|
+
* Two bindings to the same path are intentional — input syncing,
|
|
2039
|
+
* mirrored shadow inputs:
|
|
2040
|
+
*
|
|
2041
|
+
* ```ts
|
|
2042
|
+
* for (const el of form.fields.email.elements) el.blur()
|
|
2043
|
+
* ```
|
|
2044
|
+
*
|
|
2045
|
+
* For the common single-binding case, reach for `element` — sugar
|
|
2046
|
+
* over `elements[0] ?? null`.
|
|
2047
|
+
*/
|
|
2048
|
+
readonly elements: readonly HTMLElement[];
|
|
1821
2049
|
readonly updatedAt: string | null;
|
|
1822
2050
|
readonly errors: readonly ValidationError[];
|
|
2051
|
+
/**
|
|
2052
|
+
* `true` while a per-field validation run is in flight at this path.
|
|
2053
|
+
* Reflects field-level debounced runs (`validate-on-change`) and
|
|
2054
|
+
* cross-field re-validations targeting this path. Whole-form
|
|
2055
|
+
* `validate()` / `validateAsync()` calls drive `form.meta.validating`
|
|
2056
|
+
* only — they don't flip per-field flags.
|
|
2057
|
+
*
|
|
2058
|
+
* Per-field analogue of `form.meta.validating`. Use for a tight
|
|
2059
|
+
* "Checking…" indicator next to a single async-validated input
|
|
2060
|
+
* without commandeering the whole-form spinner.
|
|
2061
|
+
*/
|
|
2062
|
+
readonly validating: boolean;
|
|
2063
|
+
/**
|
|
2064
|
+
* `true` when this field has no errors AND no per-field validation
|
|
2065
|
+
* is in flight (`errors.length === 0 && !validating`). Confidence
|
|
2066
|
+
* that "we've checked, and we have no problems right now." Use for
|
|
2067
|
+
* green-checkmark / `aria-invalid` UX.
|
|
2068
|
+
*/
|
|
2069
|
+
readonly valid: boolean;
|
|
1823
2070
|
readonly path: ReadonlyArray<string | number>;
|
|
1824
2071
|
readonly blank: boolean;
|
|
2072
|
+
/**
|
|
2073
|
+
* Presentational label for this field. Resolves through the
|
|
2074
|
+
* adapter's metadata mechanism — Zod 4's `z.registry()` (typed
|
|
2075
|
+
* payload via `schema.register(fieldMeta, {...})` or the
|
|
2076
|
+
* `withMeta()` helper); Zod 3's WeakMap shim — and falls back to
|
|
2077
|
+
* a humanized form of the path's last segment when nothing has
|
|
2078
|
+
* been registered. Always a string.
|
|
2079
|
+
*
|
|
2080
|
+
* ```ts
|
|
2081
|
+
* z.string().register(fieldMeta, { label: 'Reference' })
|
|
2082
|
+
* // template: <label>{{ form.fields.reference.label }}</label>
|
|
2083
|
+
* ```
|
|
2084
|
+
*
|
|
2085
|
+
* Numeric segments (array indices) collapse to the empty string;
|
|
2086
|
+
* consumers wanting "Item 3" substitute their own format.
|
|
2087
|
+
*/
|
|
2088
|
+
readonly label: string;
|
|
2089
|
+
/**
|
|
2090
|
+
* Helper-text description for this field. Reads from the
|
|
2091
|
+
* registered `description` first; falls back to the schema's own
|
|
2092
|
+
* `.describe('...')` value (both Zod 3 and Zod 4 expose that as
|
|
2093
|
+
* `schema.description`); `undefined` when neither is set.
|
|
2094
|
+
*
|
|
2095
|
+
* Useful for `aria-describedby`-linked help text. Distinct from
|
|
2096
|
+
* `label` — descriptions are longer prose, labels are short
|
|
2097
|
+
* presentational nouns.
|
|
2098
|
+
*/
|
|
2099
|
+
readonly description: string | undefined;
|
|
2100
|
+
/**
|
|
2101
|
+
* Placeholder hint for input affordance. Reads from the
|
|
2102
|
+
* registered `placeholder`; `undefined` otherwise.
|
|
2103
|
+
*/
|
|
2104
|
+
readonly placeholder: string | undefined;
|
|
2105
|
+
/**
|
|
2106
|
+
* Full registered metadata payload, frozen — empty object when
|
|
2107
|
+
* nothing has been registered. Use as an escape hatch for
|
|
2108
|
+
* consumer-augmented keys (declared via TypeScript module
|
|
2109
|
+
* augmentation on `FieldMetaPayload`):
|
|
2110
|
+
*
|
|
2111
|
+
* ```ts
|
|
2112
|
+
* declare module 'attaform/zod' {
|
|
2113
|
+
* interface FieldMetaPayload { tooltip?: string }
|
|
2114
|
+
* }
|
|
2115
|
+
* // template: {{ form.fields.email.meta.tooltip }}
|
|
2116
|
+
* ```
|
|
2117
|
+
*/
|
|
2118
|
+
readonly meta: Readonly<FieldMetaPayload>;
|
|
1825
2119
|
};
|
|
1826
2120
|
/**
|
|
1827
2121
|
* Recursive type behind `form.fields`. Leaf-aware branching: at
|
|
1828
2122
|
* primitive paths (string, number, boolean, bigint, Date, …) the
|
|
1829
|
-
* proxy returns a `
|
|
2123
|
+
* proxy returns a `FieldState`; at container paths (object,
|
|
1830
2124
|
* array, …) the proxy descends without injecting leaf-keys.
|
|
1831
2125
|
*
|
|
1832
2126
|
* Field-name collisions at depth 2+ resolve unambiguously: a schema
|
|
1833
2127
|
* field literally named `dirty` at depth 2 is reachable as a
|
|
1834
2128
|
* descent target (`form.fields.address.dirty` returns the
|
|
1835
|
-
*
|
|
2129
|
+
* FieldState for `address.dirty`). Reading `dirty` AT the
|
|
1836
2130
|
* leaf-view (`form.fields.address.dirty.dirty`) reads the leaf's
|
|
1837
2131
|
* own dirty boolean — path-segment and leaf-prop occupy different
|
|
1838
2132
|
* proxy depths.
|
|
@@ -1842,22 +2136,43 @@ type FieldStateLeaf<Value = unknown> = {
|
|
|
1842
2136
|
* "T extends primitive". The two stay in sync for typical schemas;
|
|
1843
2137
|
* exotic adapter-defined leaf kinds (custom `Date`-like) may need
|
|
1844
2138
|
* a runtime check (the runtime is authoritative).
|
|
1845
|
-
|
|
1846
|
-
|
|
2139
|
+
*
|
|
2140
|
+
* The mapped type strips optional flags (`-?:`) because the field-
|
|
2141
|
+
* state surface always exposes a record per known leaf, regardless
|
|
2142
|
+
* of whether the schema field is declared `.optional()`. Optional
|
|
2143
|
+
* schemas mean the VALUE can be undefined — `FieldState<string |
|
|
2144
|
+
* undefined>` carries that — but the FieldState wrapper itself
|
|
2145
|
+
* always exists. Without the strip, `form.fields.notes` (where
|
|
2146
|
+
* `notes?: string`) would type as `FieldState<...> | undefined`,
|
|
2147
|
+
* forcing consumers to optional-chain through every reactive read.
|
|
2148
|
+
*
|
|
2149
|
+
* For discriminated-union containers the object branch uses
|
|
2150
|
+
* `[T] extends [object]` (non-distributive) plus
|
|
2151
|
+
* `KeyofUnion`/`ValueOfUnion` to merge variant key sets — so
|
|
2152
|
+
* `form.fields.cargo.tempMinC` (refrigerated-only) is reachable
|
|
2153
|
+
* regardless of the active variant, with the leaf typed as
|
|
2154
|
+
* `FieldState<number | undefined>`. Matches the runtime's stub
|
|
2155
|
+
* `FieldState` for inactive-variant paths.
|
|
2156
|
+
*/
|
|
2157
|
+
type FieldStateMapEntry<T> = [T] extends [
|
|
2158
|
+
string | number | boolean | bigint | symbol | null | undefined | Date
|
|
2159
|
+
] ? FieldState<T> : [T] extends [ReadonlyArray<infer U>] ? {
|
|
1847
2160
|
readonly [K: number]: FieldStateMapEntry<U>;
|
|
1848
|
-
} : T extends object ? {
|
|
1849
|
-
readonly [K in
|
|
1850
|
-
} :
|
|
2161
|
+
} : [T] extends [object] ? [IsUnion<T>] extends [true] ? {
|
|
2162
|
+
readonly [K in KeyofUnion<T>]-?: FieldStateMapEntry<ValueOfUnion<T, K>>;
|
|
2163
|
+
} : {
|
|
2164
|
+
readonly [K in keyof T]-?: FieldStateMapEntry<T[K]>;
|
|
2165
|
+
} : FieldState<T>;
|
|
1851
2166
|
/**
|
|
1852
2167
|
* Type of `form.fields` — leaf-aware drillable callable Proxy. At
|
|
1853
|
-
* a leaf path the proxy resolves to a `
|
|
2168
|
+
* a leaf path the proxy resolves to a `FieldState<Value>`; at
|
|
1854
2169
|
* a container path it returns a sub-proxy you can keep drilling.
|
|
1855
2170
|
*
|
|
1856
2171
|
* Augmented with the callable signatures so dot-access and function-
|
|
1857
2172
|
* call coexist on the same identifier:
|
|
1858
2173
|
*
|
|
1859
2174
|
* ```ts
|
|
1860
|
-
* form.fields.email.value // string (leaf-prop on
|
|
2175
|
+
* form.fields.email.value // string (leaf-prop on FieldState)
|
|
1861
2176
|
* form.fields('email').value // function-call (dynamic / programmatic)
|
|
1862
2177
|
* form.fields(['users', 0, 'name']) // path-array form
|
|
1863
2178
|
* form.fields() // root proxy
|
|
@@ -1868,12 +2183,38 @@ type FieldStateMapEntry<T> = T extends string | number | boolean | bigint | symb
|
|
|
1868
2183
|
* string as a single key. Use chained dot/bracket or the callable
|
|
1869
2184
|
* form.
|
|
1870
2185
|
*/
|
|
1871
|
-
type FieldStateMap<Form extends GenericForm> = {
|
|
1872
|
-
readonly [K in
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2186
|
+
type FieldStateMap<Form extends GenericForm> = ([IsUnion<Form>] extends [true] ? {
|
|
2187
|
+
readonly [K in KeyofUnion<Form>]-?: FieldStateMapEntry<ValueOfUnion<Form, K>>;
|
|
2188
|
+
} : {
|
|
2189
|
+
readonly [K in keyof Form]-?: FieldStateMapEntry<Form[K]>;
|
|
2190
|
+
}) & {
|
|
2191
|
+
/**
|
|
2192
|
+
* Dotted-string fallback for dynamic paths. Returns
|
|
2193
|
+
* `FieldState<unknown>` — the runtime always lands on a FieldState
|
|
2194
|
+
* terminal at any depth (leaf or container). Cast to
|
|
2195
|
+
* `FieldState<TypedValue>` when the caller knows the leaf type.
|
|
2196
|
+
*/
|
|
2197
|
+
(path: string): FieldState<unknown>;
|
|
2198
|
+
/**
|
|
2199
|
+
* Tuple-segment form. Returns the typed `FieldStateMapEntry` for
|
|
2200
|
+
* the resolved path when the tuple resolves to a known path.
|
|
2201
|
+
* Equivalent to `form.fields[a][b][...]` but useful when the path
|
|
2202
|
+
* is built from variables.
|
|
2203
|
+
*/
|
|
2204
|
+
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): FieldStateMapEntry<NestedType<Form, JoinSegments<S>>>;
|
|
2205
|
+
/**
|
|
2206
|
+
* Dynamic-array fallback for callers passing `Path`-typed (runtime)
|
|
2207
|
+
* segment arrays — e.g. forwarding `RegisterValue.segments` to
|
|
2208
|
+
* resolve a field view. Returns `FieldState<unknown>`; cast when
|
|
2209
|
+
* the value type is known.
|
|
2210
|
+
*/
|
|
2211
|
+
(segments: ReadonlyArray<string | number>): FieldState<unknown>;
|
|
2212
|
+
/**
|
|
2213
|
+
* No-arg call returns the root FieldState — same as
|
|
2214
|
+
* `form.fields([])`. Aggregates over the whole form (one
|
|
2215
|
+
* conjunction over every active-variant leaf).
|
|
2216
|
+
*/
|
|
2217
|
+
(): FieldState<Form>;
|
|
1877
2218
|
};
|
|
1878
2219
|
/**
|
|
1879
2220
|
* Untyped error map keyed by dotted-string path. The same data
|
|
@@ -1919,12 +2260,27 @@ type FormErrorRecord = Record<string, ValidationError[]>;
|
|
|
1919
2260
|
*/
|
|
1920
2261
|
type FormErrorsSurface<Form> = ErrorsProxyShape<Form> & {
|
|
1921
2262
|
(path: string): readonly ValidationError[] | undefined;
|
|
1922
|
-
|
|
1923
|
-
|
|
2263
|
+
/**
|
|
2264
|
+
* Tuple-segment form. Validated against `FlatPath<Form>` so literal
|
|
2265
|
+
* tuples that don't resolve to a known path fail at the call site.
|
|
2266
|
+
* Dynamic `Path`-typed inputs hit the untyped fallback overload below.
|
|
2267
|
+
*/
|
|
2268
|
+
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): readonly ValidationError[] | undefined;
|
|
2269
|
+
(segments: ReadonlyArray<string | number>): readonly ValidationError[] | undefined;
|
|
2270
|
+
/**
|
|
2271
|
+
* No-arg call returns the form-level error aggregate — same as
|
|
2272
|
+
* `form.errors([])` and `form.meta.errors`. `undefined` when the
|
|
2273
|
+
* form has no errors; readonly array otherwise.
|
|
2274
|
+
*/
|
|
2275
|
+
(): readonly ValidationError[] | undefined;
|
|
1924
2276
|
};
|
|
1925
|
-
type ErrorsProxyShape<T> = T extends
|
|
2277
|
+
type ErrorsProxyShape<T> = [T] extends [
|
|
2278
|
+
string | number | boolean | bigint | symbol | null | undefined | Date
|
|
2279
|
+
] ? readonly ValidationError[] | undefined : [T] extends [ReadonlyArray<infer U>] ? {
|
|
1926
2280
|
readonly [K: number]: ErrorsProxyShape<U>;
|
|
1927
|
-
} : T extends object ? {
|
|
2281
|
+
} : [T] extends [object] ? [IsUnion<T>] extends [true] ? {
|
|
2282
|
+
readonly [K in KeyofUnion<T>]: ErrorsProxyShape<ValueOfUnion<T, K>>;
|
|
2283
|
+
} : {
|
|
1928
2284
|
readonly [K in keyof T]: ErrorsProxyShape<T[K]>;
|
|
1929
2285
|
} : readonly ValidationError[] | undefined;
|
|
1930
2286
|
/**
|
|
@@ -1948,8 +2304,18 @@ type ErrorsProxyShape<T> = T extends string | number | boolean | bigint | symbol
|
|
|
1948
2304
|
* intentionally NOT supported — JS object semantics treat the dotted
|
|
1949
2305
|
* string as a single key. Use chained dot/bracket or the callable
|
|
1950
2306
|
* form.
|
|
1951
|
-
|
|
1952
|
-
|
|
2307
|
+
*
|
|
2308
|
+
* The chained shape applies the discriminated-union lift via
|
|
2309
|
+
* `LiftedValueShape<F>` so per-variant keys are reachable without
|
|
2310
|
+
* narrowing first (e.g. `form.values.cargo.permitNumber` types as
|
|
2311
|
+
* `string | undefined` regardless of which cargo variant is active —
|
|
2312
|
+
* matching the runtime, where plain JS object access on a missing
|
|
2313
|
+
* variant key returns `undefined`). The strict-variant shape is
|
|
2314
|
+
* still required at the WRITE side: `setValue` and `defaultValues`
|
|
2315
|
+
* use the un-lifted `WriteShape` so consumers can't accidentally
|
|
2316
|
+
* hand the form a partial / cross-variant object.
|
|
2317
|
+
*/
|
|
2318
|
+
type ValuesSurface<F> = Readonly<LiftedValueShape<F>> & {
|
|
1953
2319
|
(path: string): unknown;
|
|
1954
2320
|
(path: ReadonlyArray<string | number>): unknown;
|
|
1955
2321
|
(): Readonly<F>;
|
|
@@ -2021,13 +2387,13 @@ type ApiErrorEnvelope = {
|
|
|
2021
2387
|
* the reactive object:
|
|
2022
2388
|
*
|
|
2023
2389
|
* ```vue
|
|
2024
|
-
* <button :disabled="form.meta.
|
|
2390
|
+
* <button :disabled="form.meta.submitting">Save</button>
|
|
2025
2391
|
* ```
|
|
2026
2392
|
*
|
|
2027
2393
|
* Watch a single field via the getter form:
|
|
2028
2394
|
*
|
|
2029
2395
|
* ```ts
|
|
2030
|
-
* watch(() => form.meta.
|
|
2396
|
+
* watch(() => form.meta.submitting, (value) => …)
|
|
2031
2397
|
* ```
|
|
2032
2398
|
*
|
|
2033
2399
|
* Per-field state (touched, dirty, errors) lives behind
|
|
@@ -2038,33 +2404,13 @@ type ApiErrorEnvelope = {
|
|
|
2038
2404
|
* the current values; use `toRefs()` if you need reactive handles
|
|
2039
2405
|
* to individual fields.
|
|
2040
2406
|
*/
|
|
2041
|
-
|
|
2042
|
-
/**
|
|
2043
|
-
* `true` when any field's current value differs from its initial
|
|
2044
|
-
* value. `false` for a pristine form and for one where every change
|
|
2045
|
-
* has been undone. Restore the pristine baseline via `reset()`.
|
|
2046
|
-
*
|
|
2047
|
-
* Note: object/array leaves are compared by reference, so replacing
|
|
2048
|
-
* an array with an equal copy still reads as dirty.
|
|
2049
|
-
*/
|
|
2050
|
-
readonly isDirty: boolean;
|
|
2051
|
-
/**
|
|
2052
|
-
* `true` when the form currently has no validation errors. Flips
|
|
2053
|
-
* with every `validate()` / `handleSubmit` outcome.
|
|
2054
|
-
*/
|
|
2055
|
-
readonly isValid: boolean;
|
|
2407
|
+
type FormMeta<F = unknown> = FieldState<F> & {
|
|
2056
2408
|
/**
|
|
2057
2409
|
* `true` while a `handleSubmit`-produced submit handler is running.
|
|
2058
2410
|
* Covers both the validation phase and your async submit callback.
|
|
2059
2411
|
* Useful for disabling the submit button.
|
|
2060
2412
|
*/
|
|
2061
|
-
readonly
|
|
2062
|
-
/**
|
|
2063
|
-
* `true` while any validation run is in flight (the reactive
|
|
2064
|
-
* `validate()` re-run, an imperative `validateAsync()`, or the
|
|
2065
|
-
* pre-submit validation inside `handleSubmit`).
|
|
2066
|
-
*/
|
|
2067
|
-
readonly isValidating: boolean;
|
|
2413
|
+
readonly submitting: boolean;
|
|
2068
2414
|
/**
|
|
2069
2415
|
* How many times the submit handler has been invoked, regardless of
|
|
2070
2416
|
* outcome (validation failure, callback success, callback throw).
|
|
@@ -2091,33 +2437,6 @@ interface FormMeta {
|
|
|
2091
2437
|
* `canUndo` / `canRedo` instead.
|
|
2092
2438
|
*/
|
|
2093
2439
|
readonly historySize: number;
|
|
2094
|
-
/**
|
|
2095
|
-
* Flat aggregate of EVERY validation error in the form — schema-
|
|
2096
|
-
* keyed entries, form-level errors (path: []), unmapped server
|
|
2097
|
-
* errors (paths not in `FlatPath`), and cross-field-refine errors
|
|
2098
|
-
* (paths at containers). Reads as English: "the form's errors."
|
|
2099
|
-
*
|
|
2100
|
-
* Unlike `form.errors.<path>` (per-leaf, active-path-filtered),
|
|
2101
|
-
* `form.meta.errors` is unfiltered — inactive-variant errors stay
|
|
2102
|
-
* in the array. Consumers who want only addressable errors filter
|
|
2103
|
-
* the array themselves (`form.meta.errors.filter(e => …)`).
|
|
2104
|
-
*
|
|
2105
|
-
* Common patterns:
|
|
2106
|
-
*
|
|
2107
|
-
* ```vue
|
|
2108
|
-
* <p v-if="form.meta.errors.length">{{ form.meta.errors.length }} issue(s)</p>
|
|
2109
|
-
* <ul>
|
|
2110
|
-
* <li v-for="err in form.meta.errors" :key="err.path.join('.')">
|
|
2111
|
-
* {{ err.path.join('.') || 'form' }}: {{ err.message }}
|
|
2112
|
-
* </li>
|
|
2113
|
-
* </ul>
|
|
2114
|
-
* ```
|
|
2115
|
-
*
|
|
2116
|
-
* The array re-allocates on any underlying store change (schema /
|
|
2117
|
-
* derived-blank / user); reactivity propagates through the standard
|
|
2118
|
-
* Vue computed graph.
|
|
2119
|
-
*/
|
|
2120
|
-
readonly errors: readonly ValidationError[];
|
|
2121
2440
|
/**
|
|
2122
2441
|
* Per-`useForm()`-call identity. Stable for the lifetime of one
|
|
2123
2442
|
* `useForm()` call; new on every fresh mount. Orthogonal to
|
|
@@ -2141,7 +2460,7 @@ interface FormMeta {
|
|
|
2141
2460
|
* (drag-reorder, etc.) — stable identity per useForm() call.
|
|
2142
2461
|
*/
|
|
2143
2462
|
readonly instanceId: string;
|
|
2144
|
-
}
|
|
2463
|
+
};
|
|
2145
2464
|
/**
|
|
2146
2465
|
* The object returned by `useForm`. Holds every reactive ref, write
|
|
2147
2466
|
* helper, and lifecycle method bound to one form.
|
|
@@ -2154,7 +2473,7 @@ interface FormMeta {
|
|
|
2154
2473
|
* form.errors.email // ValidationError[] | undefined
|
|
2155
2474
|
* form.setValue('email', 'a@b.c')
|
|
2156
2475
|
* form.handleSubmit(onSubmit) // returns a submit handler
|
|
2157
|
-
* form.meta.
|
|
2476
|
+
* form.meta.submitting // form-level reactive flag
|
|
2158
2477
|
* ```
|
|
2159
2478
|
*/
|
|
2160
2479
|
type UseFormReturnType<Form extends GenericForm, GetValueFormType extends GenericForm = Form> = {
|
|
@@ -2212,16 +2531,16 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2212
2531
|
* ```
|
|
2213
2532
|
*
|
|
2214
2533
|
* The same proxy supports descent at every level — `address` reads
|
|
2215
|
-
* the
|
|
2534
|
+
* the FieldState for the address object, and `address.city`
|
|
2216
2535
|
* descends into the nested leaf.
|
|
2217
2536
|
*
|
|
2218
2537
|
* Leaf values follow the slim WriteShape contract: enum-typed leaves
|
|
2219
2538
|
* widen to their primitive supertype. The errors array, dirty flag,
|
|
2220
2539
|
* focus state, etc. are unaffected.
|
|
2221
2540
|
*
|
|
2222
|
-
* Shadowing: at depth 2+,
|
|
2541
|
+
* Shadowing: at depth 2+, FieldState keys (`dirty`, `touched`,
|
|
2223
2542
|
* `errors`, `blank`, `focused`, `blurred`, `value`,
|
|
2224
|
-
* `original`, `pristine`, `
|
|
2543
|
+
* `original`, `pristine`, `connected`, `updatedAt`, `path`) win
|
|
2225
2544
|
* over schema field names. Top-level fields are NOT shadowed.
|
|
2226
2545
|
* Document edge case; rename the offending schema field if the
|
|
2227
2546
|
* collision matters.
|
|
@@ -2283,6 +2602,13 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2283
2602
|
* empty; submit raises "No value supplied" for required schemas).
|
|
2284
2603
|
*/
|
|
2285
2604
|
<Path extends FlatPath<Form>, Value extends SetValuePayload<DefaultValuesShape<NestedType<Form, Path>>, NonNullable<WriteShape<NestedType<Form, Path>>>>>(path: Path, value: Value): boolean;
|
|
2605
|
+
/**
|
|
2606
|
+
* Tuple-segment form. Equivalent to the dotted-string overload —
|
|
2607
|
+
* useful when paths are built from variables or arrays:
|
|
2608
|
+
* `form.setValue([prefix, 'line1'], 'value')`. The resolved leaf
|
|
2609
|
+
* type is exact, matching the dotted-string form.
|
|
2610
|
+
*/
|
|
2611
|
+
<const S extends ReadonlyArray<string | number>, Value extends SetValuePayload<DefaultValuesShape<NestedType<Form, JoinSegments<S>>>, NonNullable<WriteShape<NestedType<Form, JoinSegments<S>>>>>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never), value: Value): boolean;
|
|
2286
2612
|
};
|
|
2287
2613
|
/**
|
|
2288
2614
|
* Reactive validation status. Re-runs whenever the form (or the
|
|
@@ -2311,7 +2637,7 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2311
2637
|
* if (!result.success) showErrors(result.errors)
|
|
2312
2638
|
* ```
|
|
2313
2639
|
*
|
|
2314
|
-
* Pass a path to validate a subtree. `state.
|
|
2640
|
+
* Pass a path to validate a subtree. `state.validating` flips
|
|
2315
2641
|
* `true` while the promise is in flight.
|
|
2316
2642
|
*/
|
|
2317
2643
|
validateAsync: (path?: FlatPath<Form>) => Promise<ValidationResponseWithoutValue<Form>>;
|
|
@@ -2328,11 +2654,25 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2328
2654
|
* />
|
|
2329
2655
|
* ```
|
|
2330
2656
|
*
|
|
2657
|
+
* Also accepts a segment-array form for callers building paths
|
|
2658
|
+
* dynamically — particularly inside a `v-for` over a prefix variable
|
|
2659
|
+
* where dotted-string concatenation widens the prefix's literal
|
|
2660
|
+
* union to plain `string`:
|
|
2661
|
+
*
|
|
2662
|
+
* ```vue
|
|
2663
|
+
* <fieldset v-for="block in [{ prefix: 'pickup' }, { prefix: 'delivery' }] as const">
|
|
2664
|
+
* <input v-register="form.register([block.prefix, 'line1'])" />
|
|
2665
|
+
* </fieldset>
|
|
2666
|
+
* ```
|
|
2667
|
+
*
|
|
2331
2668
|
* Pass `options.persist` to opt into the form's persistence
|
|
2332
2669
|
* pipeline. Persistence requires `useForm({ persist })` configured
|
|
2333
2670
|
* for storage activity to actually happen.
|
|
2334
2671
|
*/
|
|
2335
|
-
register:
|
|
2672
|
+
register: {
|
|
2673
|
+
<Path extends RegisterFlatPath<Form, keyof Form>>(path: Path, options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<Form>, Path>>;
|
|
2674
|
+
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [RegisterFlatPath<Form, keyof Form>] ? unknown : never), options?: RegisterOptions): RegisterValue<NestedReadType<WriteShape<Form>, JoinSegments<S>>>;
|
|
2675
|
+
};
|
|
2336
2676
|
/**
|
|
2337
2677
|
* The form's identifier — either the explicit `key` passed to
|
|
2338
2678
|
* `useForm` or an auto-generated unique id when `key` was omitted.
|
|
@@ -2385,7 +2725,10 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2385
2725
|
* Prefer `form.values.email` for direct reads in templates +
|
|
2386
2726
|
* scripts; `toRef` is for ref-shaped interop only.
|
|
2387
2727
|
*/
|
|
2388
|
-
toRef:
|
|
2728
|
+
toRef: {
|
|
2729
|
+
<Path extends FlatPath<Form>>(path: Path): Readonly<Ref<NestedReadType<WriteShape<GetValueFormType>, Path>>>;
|
|
2730
|
+
<const S extends ReadonlyArray<string | number>>(segments: S & ([JoinSegments<S>] extends [FlatPath<Form>] ? unknown : never)): Readonly<Ref<NestedReadType<WriteShape<GetValueFormType>, JoinSegments<S>>>>;
|
|
2731
|
+
};
|
|
2389
2732
|
/**
|
|
2390
2733
|
* Replace every field error for this form with the provided list.
|
|
2391
2734
|
* Useful after `parseApiErrors` produces a fresh batch from a
|
|
@@ -2414,15 +2757,56 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2414
2757
|
*/
|
|
2415
2758
|
clearFieldErrors: (path?: string | (string | number)[]) => void;
|
|
2416
2759
|
/**
|
|
2417
|
-
*
|
|
2418
|
-
* `
|
|
2760
|
+
* Replace the form-level errors — the entries at the empty path
|
|
2761
|
+
* (`path: []`) — without disturbing any field-level errors. Pass an
|
|
2762
|
+
* empty array to clear them all.
|
|
2763
|
+
*
|
|
2764
|
+
* ```ts
|
|
2765
|
+
* form.setFormErrors([{ message: 'Capacity exceeded' }])
|
|
2766
|
+
* form.setFormErrors([
|
|
2767
|
+
* { message: 'Capacity exceeded', code: 'capacity:exceeded' },
|
|
2768
|
+
* { message: 'Pickup window full' },
|
|
2769
|
+
* ])
|
|
2770
|
+
* form.setFormErrors([]) // clear
|
|
2771
|
+
* ```
|
|
2772
|
+
*
|
|
2773
|
+
* Only `message` is required. `code` defaults to `'atta:form-error'`.
|
|
2774
|
+
* Any caller-provided `path` or `formKey` is ignored — `path` is
|
|
2775
|
+
* always forced to `[]` (this API is form-level-only by definition)
|
|
2776
|
+
* and `formKey` is filled in from the form instance. The lenient
|
|
2777
|
+
* input shape lets you pipe `parseApiErrors` output (or any
|
|
2778
|
+
* `ValidationError[]`) straight in:
|
|
2779
|
+
*
|
|
2780
|
+
* ```ts
|
|
2781
|
+
* const result = parseApiErrors(payload, { formKey: form.key })
|
|
2782
|
+
* if (result.ok) form.setFormErrors(result.errors)
|
|
2783
|
+
* ```
|
|
2784
|
+
*
|
|
2785
|
+
* Form-level errors surface in `form.meta.errors` (alongside field
|
|
2786
|
+
* errors) but are intentionally excluded from the path-keyed
|
|
2787
|
+
* `form.errors` proxy (no key represents `[]` in a nested object) —
|
|
2788
|
+
* read them via `meta.errors.filter(e => e.path.length === 0)` or
|
|
2789
|
+
* `form.errors([])` (the call-form aggregates everywhere, including
|
|
2790
|
+
* form-level errors at `path: []`).
|
|
2791
|
+
*/
|
|
2792
|
+
setFormErrors: (errors: ReadonlyArray<Partial<ValidationError> & {
|
|
2793
|
+
message: string;
|
|
2794
|
+
}>) => void;
|
|
2795
|
+
/**
|
|
2796
|
+
* Clear every form-level error. Equivalent to `setFormErrors([])`;
|
|
2797
|
+
* field errors are untouched.
|
|
2798
|
+
*/
|
|
2799
|
+
clearFormErrors: () => void;
|
|
2800
|
+
/**
|
|
2801
|
+
* Form-level reactive flags, counters, and aggregates (`dirty`,
|
|
2802
|
+
* `valid`, `submitting`, `submitCount`, `canUndo`,
|
|
2419
2803
|
* `historySize`, and the flat `errors` array). See `FormMeta` for
|
|
2420
2804
|
* the full shape. Read leaves directly with no `.value`.
|
|
2421
2805
|
*
|
|
2422
2806
|
* For per-field state (touched, focused, blurred, errors at one
|
|
2423
2807
|
* path), use `form.fields.<path>` instead.
|
|
2424
2808
|
*/
|
|
2425
|
-
meta: FormMeta
|
|
2809
|
+
meta: FormMeta<Form>;
|
|
2426
2810
|
/**
|
|
2427
2811
|
* Restore the form to its initial state. Without arguments,
|
|
2428
2812
|
* re-applies the schema defaults (and any `defaultValues` passed
|
|
@@ -2431,10 +2815,10 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2431
2815
|
*
|
|
2432
2816
|
* Resets:
|
|
2433
2817
|
* - the form value back to defaults;
|
|
2434
|
-
* - the dirty baseline (so the next edit flips `
|
|
2818
|
+
* - the dirty baseline (so the next edit flips `dirty` correctly);
|
|
2435
2819
|
* - field errors;
|
|
2436
2820
|
* - touched / focused / blurred per-field flags;
|
|
2437
|
-
* - submission state (`
|
|
2821
|
+
* - submission state (`submitting` / `submitCount` / `submitError`);
|
|
2438
2822
|
* - the persisted draft, if persistence is configured.
|
|
2439
2823
|
*
|
|
2440
2824
|
* The next edit on a still-mounted opted-in input will start
|
|
@@ -2564,5 +2948,5 @@ type UseFormReturnType<Form extends GenericForm, GetValueFormType extends Generi
|
|
|
2564
2948
|
blankPaths: ComputedRef<ReadonlySet<string>>;
|
|
2565
2949
|
};
|
|
2566
2950
|
|
|
2567
|
-
export { ROOT_PATH as
|
|
2568
|
-
export type {
|
|
2951
|
+
export { ROOT_PATH as Z, ROOT_PATH_KEY as _, canonicalizePath as ak, isPathPrefix as al, isUnset as am, parseDottedPath as an, unset as ao };
|
|
2952
|
+
export type { ReactiveValidationStatus as $, AttaformDefaults as A, OnInvalidSubmitPolicy as B, CoercionRegistry as C, DeepPartial as D, OnSubmit 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, Path as P, PathKey as Q, RegisterValue as R, SlimPrimitiveKind as S, PendingValidationStatus as T, UseFormConfiguration as U, ValidationError as V, PersistConfig as W, PersistConfigOptions as X, PersistIncludeMode as Y, CoercionEntry as a, RegisterDirective as a0, RegisterFlatPath as a1, RegisterOptions as a2, RegisterSelectModifier as a3, RegisterTextModifier as a4, RegisterTransform as a5, Segment as a6, SetValueCallback as a7, SetValuePayload as a8, SettledValidationStatus as a9, SlimRuntimeOf as aa, SubmitHandler as ab, Unset as ac, ValidateOn as ad, ValidateOnConfig as ae, ValidationResponse as af, ValidationResponseWithoutValue as ag, ValueOfUnion as ah, WriteMeta as ai, WriteShape as aj, AbstractSchema as b, DefaultValuesShape as c, UseFormReturnType as d, RegisterModelDynamicCustomDirective as e, ApiErrorEnvelope as f, ApiErrorDetails as g, ApiErrorEntry as h, ArrayItem as i, ArrayPath as j, CoercionResult as k, CustomDirectiveRegisterAssignerFn as l, DefaultValuesResponse as m, FieldMetaPayload as n, FieldState as o, FieldStateMap as p, FieldStateMapEntry as q, FlatPath as r, FormErrorRecord as s, FormErrorsSurface as t, FormMeta as u, FormStorage as v, FormStorageKind as w, HistoryConfig as x, IsUnion as y, NestedType as z };
|