react-f0rm 1.2.0 → 1.4.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 +90 -1062
- package/devtools.d.ts +1 -0
- package/devtools.js +1 -0
- package/dist/array-3hAjl6Bz.d.ts +160 -0
- package/dist/devtools/index.cjs.js +1 -1
- package/dist/devtools/index.cjs.js.map +1 -1
- package/dist/devtools/index.d.cts +34 -0
- package/dist/devtools/index.d.mts +34 -0
- package/dist/devtools/index.d.ts +3 -2
- package/dist/devtools/index.mjs +1 -1
- package/dist/devtools/index.mjs.map +1 -1
- package/dist/errors-8oPux7GR.cjs.js +2 -0
- package/dist/errors-8oPux7GR.cjs.js.map +1 -0
- package/dist/errors-ClH3eV1R.mjs +2 -0
- package/dist/errors-ClH3eV1R.mjs.map +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.cts +1833 -0
- package/dist/index.d.mts +1833 -0
- package/dist/index.d.ts +678 -128
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1210 -480
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +2 -2
- package/dist/index.umd.min.js.map +1 -1
- package/dist/persist.cjs.js +1 -1
- package/dist/persist.cjs.js.map +1 -1
- package/dist/persist.d.cts +49 -0
- package/dist/persist.d.mts +49 -0
- package/dist/persist.mjs +1 -1
- package/dist/persist.mjs.map +1 -1
- package/dist/resolvers/standard-schema.cjs.js +1 -1
- package/dist/resolvers/standard-schema.cjs.js.map +1 -1
- package/dist/resolvers/standard-schema.d.cts +2 -0
- package/dist/resolvers/standard-schema.d.mts +2 -0
- package/dist/resolvers/standard-schema.d.ts +1 -67
- package/dist/resolvers/standard-schema.mjs +1 -1
- package/dist/resolvers/standard-schema.mjs.map +1 -1
- package/dist/resolvers/yup.cjs.js +1 -1
- package/dist/resolvers/yup.cjs.js.map +1 -1
- package/dist/resolvers/yup.d.cts +6 -0
- package/dist/resolvers/yup.d.mts +6 -0
- package/dist/resolvers/yup.d.ts +0 -1
- package/dist/resolvers/yup.mjs +1 -1
- package/dist/resolvers/yup.mjs.map +1 -1
- package/dist/resolvers/zod.cjs.js +1 -1
- package/dist/resolvers/zod.cjs.js.map +1 -1
- package/dist/resolvers/zod.d.cts +6 -0
- package/dist/resolvers/zod.d.mts +6 -0
- package/dist/resolvers/zod.d.ts +0 -1
- package/dist/resolvers/zod.mjs +1 -1
- package/dist/resolvers/zod.mjs.map +1 -1
- package/dist/server/index.cjs.js +1 -1
- package/dist/server/index.cjs.js.map +1 -1
- package/dist/server/index.d.cts +106 -0
- package/dist/server/index.d.mts +106 -0
- package/dist/server/index.d.ts +31 -2
- package/dist/server/index.mjs +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/standard-schema-CYbnugL7.d.ts +1184 -0
- package/dist/standardSchema-BpZvg0tZ.mjs +2 -0
- package/dist/standardSchema-BpZvg0tZ.mjs.map +1 -0
- package/dist/standardSchema-CxV_DhBO.cjs.js +2 -0
- package/dist/standardSchema-CxV_DhBO.cjs.js.map +1 -0
- package/dist/validate-BEjNsNvd.mjs +2 -0
- package/dist/validate-BEjNsNvd.mjs.map +1 -0
- package/dist/validate-BvN6hiEG.cjs.js +2 -0
- package/dist/validate-BvN6hiEG.cjs.js.map +1 -0
- package/dist/values-Bod74Kzu.mjs +2 -0
- package/dist/values-Bod74Kzu.mjs.map +1 -0
- package/dist/values-DKpOCD5J.cjs.js +2 -0
- package/dist/values-DKpOCD5J.cjs.js.map +1 -0
- package/package.json +90 -34
- package/persist.d.ts +1 -0
- package/persist.js +1 -0
- package/resolvers/standard-schema.d.ts +1 -0
- package/resolvers/standard-schema.js +1 -0
- package/resolvers/yup.d.ts +1 -0
- package/resolvers/yup.js +1 -0
- package/resolvers/zod.d.ts +1 -0
- package/resolvers/zod.js +1 -0
- package/server.d.ts +1 -0
- package/server.js +1 -0
- package/dist/errors-CxSjrWJO.cjs.js +0 -2
- package/dist/errors-CxSjrWJO.cjs.js.map +0 -1
- package/dist/errors-CzWtwjO0.mjs +0 -2
- package/dist/errors-CzWtwjO0.mjs.map +0 -1
- package/dist/form-CvmWHUrd.d.ts +0 -423
- package/dist/validate-B1Gdjeaq.mjs +0 -2
- package/dist/validate-B1Gdjeaq.mjs.map +0 -1
- package/dist/validate-CUmNZqg6.d.ts +0 -238
- package/dist/validate-DAfz8Nbb.cjs.js +0 -2
- package/dist/validate-DAfz8Nbb.cjs.js.map +0 -1
- package/dist/values-B1IV-6V4.mjs +0 -2
- package/dist/values-B1IV-6V4.mjs.map +0 -1
- package/dist/values-CDNAYEOB.cjs.js +0 -2
- package/dist/values-CDNAYEOB.cjs.js.map +0 -1
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
import { EventEmitter } from '@for-fun/event-emitter';
|
|
2
|
+
|
|
3
|
+
/** A single Standard Schema issue. */
|
|
4
|
+
type StandardSchemaIssue = {
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly path?: ReadonlyArray<PropertyKey | {
|
|
7
|
+
readonly key: PropertyKey;
|
|
8
|
+
}> | undefined;
|
|
9
|
+
};
|
|
10
|
+
/** The union Standard Schema's `validate` resolves to. */
|
|
11
|
+
type StandardSchemaResult<Output = unknown> = {
|
|
12
|
+
readonly value: Output;
|
|
13
|
+
readonly issues?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
readonly issues: ReadonlyArray<StandardSchemaIssue>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Structural Standard Schema v1. `types` is optional in the spec but
|
|
19
|
+
* implemented by zod v3.24+/v4, valibot v1 and arktype — it is what
|
|
20
|
+
* {@link InferSchemaValues} and `createForm`'s `TValues` inference read.
|
|
21
|
+
*/
|
|
22
|
+
type StandardSchemaV1<Input = unknown, Output = Input> = {
|
|
23
|
+
readonly '~standard': {
|
|
24
|
+
readonly version: 1;
|
|
25
|
+
readonly vendor: string;
|
|
26
|
+
readonly validate: (value: Input) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;
|
|
27
|
+
readonly types?: {
|
|
28
|
+
readonly input: Input;
|
|
29
|
+
readonly output: Output;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The values type a Standard Schema produces: the `Output` of its
|
|
35
|
+
* `~standard.types`, resolved structurally without importing the schema
|
|
36
|
+
* library. Coercions/transforms land in the output — `z.coerce.date()`
|
|
37
|
+
* infers `Date`, not `string`. `never` when the schema does not expose
|
|
38
|
+
* `types` (pass an explicit `TValues` then).
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const schema = z.object({email: z.string().email()});
|
|
43
|
+
* type Values = InferSchemaValues<typeof schema>; // {email: string}
|
|
44
|
+
* const form = useForm<InferSchemaValues<typeof schema>>();
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
type InferSchemaValues<S> = S extends {
|
|
48
|
+
readonly '~standard': {
|
|
49
|
+
readonly types: {
|
|
50
|
+
readonly output: infer Out;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
} ? Out : never;
|
|
54
|
+
/**
|
|
55
|
+
* Does the value implement the Standard Schema v1 props? A plain boolean
|
|
56
|
+
* predicate, not a type guard: narrowing a
|
|
57
|
+
* `Fn | StandardSchemaV1<In, Out>` union through the guard fails either
|
|
58
|
+
* way (a generics-narrowing guard is contravariance-blocked, an
|
|
59
|
+
* intersection guard keeps both members) — call sites cast after the
|
|
60
|
+
* check instead.
|
|
61
|
+
*/
|
|
62
|
+
declare function hasStandardProps(schema: any): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Field-level Standard Schema adapter: validate a single value with any
|
|
65
|
+
* schema implementing '~standard' and map every issue to a FieldError, so
|
|
66
|
+
* a value breaking several rules surfaces all of them (setErrorByPath
|
|
67
|
+
* stores the array; error/errorObject readers still see the first).
|
|
68
|
+
* The schema's parsed output is validation-only here — field validators
|
|
69
|
+
* never rewrite the value; form-level schemas own coercion.
|
|
70
|
+
*/
|
|
71
|
+
declare function schemaToFieldValidator(schema: StandardSchemaV1<any, any>): Validator;
|
|
72
|
+
/**
|
|
73
|
+
* Form-level Standard Schema adapter: validate the whole values object and
|
|
74
|
+
* return a ValidationOutcome. On failure `errors` carries the nested shape
|
|
75
|
+
* Options.validate expects ({a: {b: FieldError[]}}; ensureValidate flattens
|
|
76
|
+
* it back to per-field errors, keeping every issue of a path). Issues
|
|
77
|
+
* without a path are form-level errors and land on the FORM_ERROR key.
|
|
78
|
+
* On success `values` carries the schema's parsed output (coerce/transform
|
|
79
|
+
* results included), which the form stores as its parsedValues baseline —
|
|
80
|
+
* the layer getValues reads above initialValues.
|
|
81
|
+
*
|
|
82
|
+
* `createForm({validate: schema})` wraps the schema through this adapter
|
|
83
|
+
* automatically (no resolver import needed), and `TValues` infers from
|
|
84
|
+
* the schema's output type.
|
|
85
|
+
*/
|
|
86
|
+
declare function schemaToFormValidator<T extends Record<string, any>>(schema: StandardSchemaV1<any, T>): (values: T) => Promise<ValidationOutcome<T>>;
|
|
87
|
+
|
|
88
|
+
type PathSegments = (string | number)[];
|
|
89
|
+
type Name = string | PathSegments;
|
|
90
|
+
type Path = {
|
|
91
|
+
value: PathSegments;
|
|
92
|
+
key: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compile-time field path utilities: `FieldPath<T>` enumerates the valid
|
|
97
|
+
* path strings for a values shape `T` ('a', 'a.b', 'a[0]', 'a[b]', ...),
|
|
98
|
+
* and `PathValue<T, P>` resolves the leaf type a path points at.
|
|
99
|
+
* The grammar mirrors the paths accepted at runtime by `normalizePath`:
|
|
100
|
+
* numeric segments are bracket-only ('a[0]', never 'a.0' — dotted
|
|
101
|
+
* numerics throw a TypeError at runtime).
|
|
102
|
+
*/
|
|
103
|
+
/** `true` only for the `any` type (`0 extends 1 & any`). */
|
|
104
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
105
|
+
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
|
|
106
|
+
/**
|
|
107
|
+
* Opt-in registry of value types the path types treat as opaque leaves.
|
|
108
|
+
* `FieldPath<T>` stops descending into a registered type — a `Date`,
|
|
109
|
+
* `Dayjs` or class instance in the values shape is a value, not a field
|
|
110
|
+
* tree — and `PathValue` resolves the field itself to that type. Merge
|
|
111
|
+
* entries in via declaration merging:
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* // app.d.ts
|
|
115
|
+
* declare module 'react-f0rm' {
|
|
116
|
+
* interface OpaqueTypes {
|
|
117
|
+
* dayjs: Dayjs;
|
|
118
|
+
* date: Date;
|
|
119
|
+
* }
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* Empty by default: every object-typed leaf keeps its historical,
|
|
124
|
+
* navigable behavior (react-hook-form's same-named registry is opt-in
|
|
125
|
+
* too). `keyof` of an empty interface is `never`, so the registry reads
|
|
126
|
+
* as a no-op until an entry lands.
|
|
127
|
+
*/
|
|
128
|
+
interface OpaqueTypes {
|
|
129
|
+
}
|
|
130
|
+
/** The union of every leaf type registered in {@link OpaqueTypes} —
|
|
131
|
+
* `never` until an application merges entries in. */
|
|
132
|
+
type OpaqueLeaf = OpaqueTypes[keyof OpaqueTypes];
|
|
133
|
+
/** Depth countdown: Prev[9] = 8 ... Prev[1] = 0, Prev[0] = never stops recursion. */
|
|
134
|
+
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
135
|
+
/** Paths are capped at 10 segments to keep instantiation depth bounded. */
|
|
136
|
+
type MaxDepth = 9;
|
|
137
|
+
/**
|
|
138
|
+
* Valid path continuations after a segment: `.k` / `[k]` / `[0]`,
|
|
139
|
+
* optionally followed by deeper continuations into the child node.
|
|
140
|
+
* Numeric segments are bracket-only (`.0` throws at runtime); object
|
|
141
|
+
* keys that are themselves numeric strings likewise enumerate just the
|
|
142
|
+
* bracket subscript.
|
|
143
|
+
*/
|
|
144
|
+
type Continue<T, D extends number> = [D] extends [never] ? never : IsAny<T> extends true ? string : T extends OpaqueLeaf ? never : T extends Primitive | Function ? never : T extends readonly (infer U)[] ? `[${number}]` | `[${number}]${Continue<U, Prev[D]>}` : {
|
|
145
|
+
[K in Extract<keyof T, string>]: (K extends `${number}` ? never : `.${K}`) | `[${K}]` | (K extends `${number}` ? never : `.${K}${Continue<T[K], Prev[D]>}`) | `[${K}]${Continue<T[K], Prev[D]>}`;
|
|
146
|
+
}[Extract<keyof T, string>];
|
|
147
|
+
/**
|
|
148
|
+
* Every valid field path string for a values shape `T`.
|
|
149
|
+
*
|
|
150
|
+
* Enumeration is capped at 10 segments ({@link MaxDepth}) to keep
|
|
151
|
+
* instantiation depth bounded: paths below the cap are not part of this
|
|
152
|
+
* type, so their call sites fall back to `any` through
|
|
153
|
+
* {@link PathValueOf} instead of erroring. Types registered in
|
|
154
|
+
* {@link OpaqueTypes} stop recursion entirely — register deep object
|
|
155
|
+
* leaves (Date, Dayjs, class instances) there instead of relying on the
|
|
156
|
+
* cap.
|
|
157
|
+
* @example FieldPath<{a: {b: string}}> // 'a' | 'a.b' | 'a[b]'
|
|
158
|
+
*/
|
|
159
|
+
type FieldPath<T> = IsAny<T> extends true ? string : T extends OpaqueLeaf ? never : T extends Primitive | Function ? never : T extends readonly (infer U)[] ? `[${number}]` | `[${number}]${Continue<U, MaxDepth>}` : {
|
|
160
|
+
[K in Extract<keyof T, string>]: K extends `${number}` ? never : K | `${K}${Continue<T[K], MaxDepth>}`;
|
|
161
|
+
}[Extract<keyof T, string>];
|
|
162
|
+
/** Resolve `T[K]` for one bare segment: array index -> element, object key -> value. */
|
|
163
|
+
type Lookup<T, K extends string> = K extends `${number}` ? T extends readonly (infer U)[] ? U : never : K extends keyof T ? T[K] : never;
|
|
164
|
+
/** One dot-separated chunk: a bare segment plus any `[k]` / `[0]` suffixes. */
|
|
165
|
+
type ChunkValue<T, C extends string> = C extends `${infer Key}[${infer Tail}` ? ChunkSuffix<Lookup<T, Key>, `[${Tail}`> : Lookup<T, C>;
|
|
166
|
+
type ChunkSuffix<T, S extends string> = S extends `[${infer Key}]${infer Rest}` ? Rest extends '' ? Lookup<T, Key> : PathOf<Lookup<T, Key>, Rest> : never;
|
|
167
|
+
/** Resolve the value type the path string `P` points at inside `T`. */
|
|
168
|
+
type PathOf<T, P extends string> = P extends '' ? never : P extends `[${infer Key}]${infer Rest}` ? Rest extends '' ? Lookup<T, Key> : PathOf<Lookup<T, Key>, Rest> : P extends `.${infer Rest}` ? PathOf<T, Rest> : P extends `${infer Chunk}.${infer Rest}` ? PathOf<ChunkValue<T, Chunk>, Rest> : ChunkValue<T, P>;
|
|
169
|
+
/**
|
|
170
|
+
* The value type at path `P` of a values shape `T`.
|
|
171
|
+
* @example PathValue<{a: {b: string}}, 'a.b'> // string
|
|
172
|
+
*/
|
|
173
|
+
type PathValue<T, P extends FieldPath<T>> = PathOf<T, P & string>;
|
|
174
|
+
/**
|
|
175
|
+
* The value type at path `P` of `T`, or `any` when `P` is not a known
|
|
176
|
+
* field path (plain `string` / segment-array calls keep their old behavior).
|
|
177
|
+
*/
|
|
178
|
+
type PathValueOf<T, P> = P extends FieldPath<T> ? PathValue<T, Extract<P, FieldPath<T>>> : any;
|
|
179
|
+
/**
|
|
180
|
+
* The element type of the array a path points at inside a values shape:
|
|
181
|
+
* `ArrayItemOf<{tags: Item[]}, 'tags'>` is `Item`. Non-array leaves
|
|
182
|
+
* resolve to the leaf type itself; unknown paths (plain `string`,
|
|
183
|
+
* segment arrays) fall back to `any` — the same wide-name escape hatch
|
|
184
|
+
* {@link PathValueOf} keeps.
|
|
185
|
+
*/
|
|
186
|
+
type ArrayItemOf<T, P> = P extends FieldPath<T> ? PathValue<T, Extract<P, FieldPath<T>>> extends readonly (infer U)[] ? U : PathValue<T, Extract<P, FieldPath<T>>> : any;
|
|
187
|
+
|
|
188
|
+
/** Type tag of a failed rule, as stored on the resulting FieldError. */
|
|
189
|
+
type RuleType = 'required' | 'min' | 'max' | 'minLength' | 'maxLength' | 'pattern';
|
|
190
|
+
/**
|
|
191
|
+
* Declarative field rules — a subset of react-hook-form's `register` rules.
|
|
192
|
+
*
|
|
193
|
+
* Failed rules land in the form's error state as FieldErrors (`type` is the
|
|
194
|
+
* rule name) instead of only surfacing through the browser's validity
|
|
195
|
+
* bubble, so any design system can render the messages uniformly.
|
|
196
|
+
*/
|
|
197
|
+
type FieldRules = {
|
|
198
|
+
/**
|
|
199
|
+
* Fails on empty values: `''`, `undefined`, `null` or an empty array
|
|
200
|
+
* (`0` and `false` count as filled) — react-hook-form's `required`
|
|
201
|
+
* semantics. A string is the error message; `true` uses the default.
|
|
202
|
+
* When it fails, the remaining rules are skipped — an empty value
|
|
203
|
+
* reports only its required error.
|
|
204
|
+
*/
|
|
205
|
+
required?: string | true;
|
|
206
|
+
/** Fails when `Number(value)` is below this bound; `NaN` values skip. */
|
|
207
|
+
min?: number;
|
|
208
|
+
/** Fails when `Number(value)` is above this bound; `NaN` values skip. */
|
|
209
|
+
max?: number;
|
|
210
|
+
/** Fails when a string value is shorter than this, or an array has
|
|
211
|
+
* fewer entries; other values skip. */
|
|
212
|
+
minLength?: number;
|
|
213
|
+
/** Fails when a string value is longer than this, or an array has more
|
|
214
|
+
* entries; other values skip. */
|
|
215
|
+
maxLength?: number;
|
|
216
|
+
/** Fails when the value does not match `pattern.value`. */
|
|
217
|
+
pattern?: {
|
|
218
|
+
value: RegExp;
|
|
219
|
+
message: string;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Custom rule callbacks — react-hook-form's `register({validate})`
|
|
223
|
+
* shape: one function, or a record of named functions. Each runs after
|
|
224
|
+
* the declarative checks, and only when they passed (`required` failing
|
|
225
|
+
* short-circuits the rest, RHF's first-error semantics). A returned
|
|
226
|
+
* error keeps its message; its `type` becomes the record key
|
|
227
|
+
* (`'validate'` for the single-function form) so consumers can switch
|
|
228
|
+
* on `error.type`. Sync-only — async checks belong in the field's
|
|
229
|
+
* `validate` option.
|
|
230
|
+
*/
|
|
231
|
+
validate?: SyncValidator | Record<string, SyncValidator>;
|
|
232
|
+
/**
|
|
233
|
+
* Overrides the message per rule type — `min`, `max`, `minLength`,
|
|
234
|
+
* `maxLength` defaults and pattern's inline `message` alike — e.g. for
|
|
235
|
+
* centralizing or localizing messages.
|
|
236
|
+
*/
|
|
237
|
+
messages?: Partial<Record<Exclude<RuleType, 'required'>, string>>;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/** Options accepted by {@link setFocus}. All flags default to `false`. */
|
|
241
|
+
type SetFocusOptions = {
|
|
242
|
+
/** Select the field's text after focusing it. Bound fields call
|
|
243
|
+
* `select()` on their element; elements without one (custom `as`
|
|
244
|
+
* components) just focus. */
|
|
245
|
+
shouldSelect?: boolean;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Programmatically focus a bound field's element (e.g. the <Field>'s
|
|
249
|
+
* input).
|
|
250
|
+
*
|
|
251
|
+
* Rides the same 'focusError' event channel a failed handleSubmit uses to
|
|
252
|
+
* focus the first errored field: the payload is the target's path key,
|
|
253
|
+
* with the focus options as a second, backward-compatible argument (older
|
|
254
|
+
* subscribers declared with a single `key` parameter simply ignore it).
|
|
255
|
+
* Being event-driven, it is a silent no-op when the field is unmounted or
|
|
256
|
+
* nothing subscribes — unknown names never throw.
|
|
257
|
+
*
|
|
258
|
+
* @param form form instance
|
|
259
|
+
* @param name field name (dot path or segments path)
|
|
260
|
+
* @param options focus options
|
|
261
|
+
*/
|
|
262
|
+
declare function setFocus(form: Form, name: Name, options?: SetFocusOptions): void;
|
|
263
|
+
|
|
264
|
+
/** Reserved top-level path segment for form-level errors. The Standard
|
|
265
|
+
* Schema form-level adapter lands path-less issues under this key; the
|
|
266
|
+
* exported constant replaces the magic string, and readers consume it via
|
|
267
|
+
* getError(form, FORM_ERROR) / getFieldErrors(form, FORM_ERROR). */
|
|
268
|
+
declare const FORM_ERROR = "_form";
|
|
269
|
+
/** When a field is validated:
|
|
270
|
+
* - `'onSubmit'` (default): only on submit
|
|
271
|
+
* - `'onBlur'`: when the field loses focus
|
|
272
|
+
* - `'onChange'`: on every change
|
|
273
|
+
* - `'onTouched'`: on first blur, then on every change
|
|
274
|
+
* - `'all'`: on both change and blur
|
|
275
|
+
*/
|
|
276
|
+
/** Brand marking a form-level validate result as a structured
|
|
277
|
+
* {@link ValidationOutcome} (parsed values and/or errors) rather than a
|
|
278
|
+
* plain nested error record. Symbols cannot collide with user error
|
|
279
|
+
* records, so detection is an exact `VALIDATION_OUTCOME in result`. */
|
|
280
|
+
declare const VALIDATION_OUTCOME: unique symbol;
|
|
281
|
+
/** Structured form-level validate result: `errors` uses the same nested
|
|
282
|
+
* shape a plain error record uses, `values` is the schema's parsed output
|
|
283
|
+
* (coerce/transform results included). Either side may be omitted. */
|
|
284
|
+
/**
|
|
285
|
+
* Get field error
|
|
286
|
+
* @param form
|
|
287
|
+
* @param name
|
|
288
|
+
* @return FieldError object or undefined
|
|
289
|
+
*/
|
|
290
|
+
declare function getError<T extends Record<string, any> = any, P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments>(form: Form<T>, name: P): FieldError | undefined;
|
|
291
|
+
/**
|
|
292
|
+
* Get field error by path
|
|
293
|
+
* @param form
|
|
294
|
+
* @param path
|
|
295
|
+
* @return first FieldError of the field, or undefined
|
|
296
|
+
*/
|
|
297
|
+
declare function getErrorByPath({ errors }: Form, path: Path): FieldError | undefined;
|
|
298
|
+
/**
|
|
299
|
+
* Get all errors of a field
|
|
300
|
+
* @param form
|
|
301
|
+
* @param name
|
|
302
|
+
* @return every error registered for the field (insertion order); an empty
|
|
303
|
+
* array when the field has none
|
|
304
|
+
*/
|
|
305
|
+
declare function getFieldErrors<T extends Record<string, any> = any, P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments>(form: Form<T>, name: P): FieldError[];
|
|
306
|
+
/**
|
|
307
|
+
* Get all errors of a field by path
|
|
308
|
+
* @param form
|
|
309
|
+
* @param path
|
|
310
|
+
* @return every error registered for the field (insertion order); an empty
|
|
311
|
+
* array when the field has none
|
|
312
|
+
*/
|
|
313
|
+
declare function getFieldErrorsByPath({ errors }: Form, path: Path): FieldError[];
|
|
314
|
+
/**
|
|
315
|
+
* Get all errors
|
|
316
|
+
* @param form
|
|
317
|
+
* @return array of {path, type, message} entries, in insertion order; path
|
|
318
|
+
* is the user-facing dotted field path ('a.b', 'list.0'), and a
|
|
319
|
+
* field holding several errors contributes one entry per error
|
|
320
|
+
*/
|
|
321
|
+
declare function getErrors({ errors }: Form): FieldErrorEntry[];
|
|
322
|
+
/**
|
|
323
|
+
* Convert a {@link FieldPath} string to the key form error records use at
|
|
324
|
+
* runtime: paths spell array access with brackets ('items[0].name') while
|
|
325
|
+
* {@link getErrorsRecord} keys are dot-joined segments ('items.0.name').
|
|
326
|
+
* The declared keys follow the runtime, so typed reads
|
|
327
|
+
* (`errors['items.0.name']`) match what the record actually holds. Quoted
|
|
328
|
+
* segments ('items["0"]') drop their quotes like the parser does.
|
|
329
|
+
*
|
|
330
|
+
* Public so consumers can name the key style in their own types —
|
|
331
|
+
* `const key: DottedPath<'items[0].name'> = 'items.0.name'`.
|
|
332
|
+
*/
|
|
333
|
+
type DottedPath<P extends string> = P extends `${infer H}[${infer N}]${infer R}` ? `${H extends '' ? '' : `${H}.`}${N extends `"${infer K}"` | `'${infer K}'` ? K : N}${DottedPath<R>}` : P;
|
|
334
|
+
/**
|
|
335
|
+
* Every error as one record keyed by user-facing dotted path, typed
|
|
336
|
+
* against the values shape — react-hook-form's `FieldErrors<T>` shape
|
|
337
|
+
* (per-key values are optional there too). Keys follow the runtime form:
|
|
338
|
+
* dotted paths ('a.b', 'list.0'), plus the {@link FORM_ERROR} slot for
|
|
339
|
+
* form-level errors. Values are the stored FieldError[] arrays shared
|
|
340
|
+
* with the form, so treat the whole result as read-only.
|
|
341
|
+
*/
|
|
342
|
+
type FieldErrors<T extends Record<string, any> = any> = Partial<Record<DottedPath<Extract<FieldPath<T>, string>>, FieldError[]>> & {
|
|
343
|
+
[FORM_ERROR]?: FieldError[];
|
|
344
|
+
};
|
|
345
|
+
/** `true` only for the `any` type (the same probe {@link FieldPath}
|
|
346
|
+
* uses), so the tree of an untyped form degrades to `any` instead of an
|
|
347
|
+
* infinite mapped-type expansion. */
|
|
348
|
+
type IsAnyTree<T> = 0 extends 1 & T ? true : false;
|
|
349
|
+
type TreePrimitive = null | undefined | string | number | boolean | symbol | bigint;
|
|
350
|
+
/** One level of the nested error tree: arrays become arrays of the item's
|
|
351
|
+
* tree, objects recurse per key, everything else — primitives, functions,
|
|
352
|
+
* registered {@link OpaqueTypes} leaves, and the common opaque DOM/date
|
|
353
|
+
* containers (Date, File, FileList, Map, Set) — is a leaf holding the
|
|
354
|
+
* stored FieldError[] (shared with the form — treat as read-only). */
|
|
355
|
+
type FieldErrorsTreeNode<T> = IsAnyTree<T> extends true ? any : T extends OpaqueTypes[keyof OpaqueTypes] ? FieldError[] : T extends TreePrimitive | Function | Date | File | FileList | Map<any, any> | Set<any> ? FieldError[] : T extends ReadonlyArray<infer E> ? FieldErrorsTreeNode<E>[] : {
|
|
356
|
+
[K in keyof T]?: FieldErrorsTreeNode<T[K]>;
|
|
357
|
+
};
|
|
358
|
+
/**
|
|
359
|
+
* Every error as one nested object following the values tree —
|
|
360
|
+
* react-hook-form's `formState.errors` shape with typed optional chains
|
|
361
|
+
* (`errors.items?.[0]?.name`), the readable counterpart of
|
|
362
|
+
* {@link FieldErrors}' flat dotted keys. Array positions become array
|
|
363
|
+
* indices (holes stay absent), object fields become optional keys,
|
|
364
|
+
* leaves hold the stored FieldError[] arrays shared with the form — treat
|
|
365
|
+
* the whole result as read-only. The {@link FORM_ERROR} slot holds
|
|
366
|
+
* form-level errors at the top level.
|
|
367
|
+
*
|
|
368
|
+
* One conflict is resolved by insertion order: a row-level error at
|
|
369
|
+
* `items[0]` and a field error at `items[0].name` cannot both occupy the
|
|
370
|
+
* `items[0]` slot, so whichever landed later owns it (react-hook-form's
|
|
371
|
+
* nested `setError` clobbers the same way). The flat record never
|
|
372
|
+
* conflicts — read it when both coexist.
|
|
373
|
+
*/
|
|
374
|
+
type FieldErrorsTree<T = any> = FieldErrorsTreeNode<T> & {
|
|
375
|
+
[FORM_ERROR]?: FieldError[];
|
|
376
|
+
};
|
|
377
|
+
/**
|
|
378
|
+
* Get every error as one record keyed by user-facing dotted path
|
|
379
|
+
* ('a.b', 'list.0') — react-hook-form's `formState.errors` shape. Values
|
|
380
|
+
* are the stored FieldError[] arrays shared with the form, so treat the
|
|
381
|
+
* whole result as read-only. Memoized per form with the same
|
|
382
|
+
* version-bump/read pattern {@link getValues} uses: every error write
|
|
383
|
+
* bumps {@link bumpErrorsVersion}, consecutive reads hand back one stable
|
|
384
|
+
* reference, so {@link useErrors} / `useFormState().errors` only re-render
|
|
385
|
+
* when an error actually changed.
|
|
386
|
+
*
|
|
387
|
+
* @param form
|
|
388
|
+
*/
|
|
389
|
+
declare function getErrorsRecord<T extends Record<string, any> = any>(form: Form<T>): FieldErrors<T>;
|
|
390
|
+
/**
|
|
391
|
+
* Get every error as one nested object following the values tree
|
|
392
|
+
* (`errors.items[0].name` reads — the shape react-hook-form's
|
|
393
|
+
* `formState.errors` uses), the optional-chaining counterpart of
|
|
394
|
+
* {@link getErrorsRecord}'s flat dotted keys. Array positions become
|
|
395
|
+
* array indices, leaves hold the stored FieldError[] arrays shared with
|
|
396
|
+
* the form — treat the whole result as read-only. Memoized alongside the
|
|
397
|
+
* record through the same version-bump/read pattern, so {@link
|
|
398
|
+
* useErrorsTree} only re-renders when an error actually changed.
|
|
399
|
+
*
|
|
400
|
+
* @param form
|
|
401
|
+
*/
|
|
402
|
+
declare function getErrorsTree<T extends Record<string, any> = any>(form: Form<T>): FieldErrorsTree<T>;
|
|
403
|
+
/**
|
|
404
|
+
* Convert a field path (bracket spelling, the `name` every API takes) into
|
|
405
|
+
* the dotted key {@link getErrorsRecord} / {@link getErrors} use:
|
|
406
|
+
* `'items[0].name'` → `'items.0.name'`. Useful for error-record reads —
|
|
407
|
+
* `errors[fieldPathToDottedKey(name)]` — and for centralizing the
|
|
408
|
+
* translation when the two spellings meet in one component.
|
|
409
|
+
*/
|
|
410
|
+
declare function fieldPathToDottedKey(name: Name): string;
|
|
411
|
+
/**
|
|
412
|
+
* Convert a dotted errors-record key back into bracket path spelling:
|
|
413
|
+
* `'items.0.name'` → `'items[0].name'` — the form every API takes, so the
|
|
414
|
+
* result feeds straight into `getError(form, …)` / `<Field name=…>`.
|
|
415
|
+
*
|
|
416
|
+
* Numeric-shaped segments become bracket segments; everything else stays
|
|
417
|
+
* dot-joined. Inherently lossy by the record's own convention: the dotted
|
|
418
|
+
* key cannot distinguish a segment containing a literal dot (a quoted path
|
|
419
|
+
* like `a["b.c"]` joins to the same `'a.b.c'` as `a.b.c`) — for such paths
|
|
420
|
+
* keep the bracket spelling and use {@link fieldPathToDottedKey} for the
|
|
421
|
+
* record read instead.
|
|
422
|
+
*/
|
|
423
|
+
declare function dottedKeyToFieldPath(key: string): string;
|
|
424
|
+
/**
|
|
425
|
+
* Get first error message
|
|
426
|
+
* @param form
|
|
427
|
+
* @return first error's message string, or undefined when there are no errors
|
|
428
|
+
*/
|
|
429
|
+
declare function getFirstError({ errors }: Form): string | undefined;
|
|
430
|
+
/** Snapshot of one field's aggregated state, as {@link getFieldState}
|
|
431
|
+
* returns it. `errors` is the stored array shared with the form — treat it
|
|
432
|
+
* as read-only, like every {@link getFieldErrors} result. */
|
|
433
|
+
/** Options accepted by {@link setError}. */
|
|
434
|
+
type SetErrorOptions = {
|
|
435
|
+
/**
|
|
436
|
+
* Focus the named field's element after the error lands (react-hook-form's
|
|
437
|
+
* `setError` `shouldFocus`). Rides the same 'focusError' channel
|
|
438
|
+
* `setFocus` and a failed submit's auto-focus use: only mounted bound
|
|
439
|
+
* fields react, unmounted ones are silent no-ops.
|
|
440
|
+
*/
|
|
441
|
+
shouldFocus?: boolean;
|
|
442
|
+
};
|
|
443
|
+
/**
|
|
444
|
+
* Set field error
|
|
445
|
+
* @param form
|
|
446
|
+
* @param name
|
|
447
|
+
* @param error string is normalized to {type: 'custom', message}; a
|
|
448
|
+
* FieldError object is stored as-is; an array holds several errors
|
|
449
|
+
* (falsy items dropped, strings normalized); undefined clears
|
|
450
|
+
* @param options {@link SetErrorOptions} — `shouldFocus` focuses the field
|
|
451
|
+
* after the error lands
|
|
452
|
+
*/
|
|
453
|
+
declare function setError<T extends Record<string, any> = any, P extends FieldPath<T> | PathSegments = FieldPath<T> | PathSegments>(form: Form<T>, name: P, error: string | FieldError | (string | FieldError)[] | undefined, options?: SetErrorOptions): void;
|
|
454
|
+
/**
|
|
455
|
+
* Set field error
|
|
456
|
+
* @param form
|
|
457
|
+
* @param path
|
|
458
|
+
* @param error string is normalized to {type: 'custom', message}; a
|
|
459
|
+
* FieldError object is stored as-is; an array holds several errors
|
|
460
|
+
* (falsy items dropped, strings normalized); undefined clears
|
|
461
|
+
* @param options {@link SetErrorOptions} — `shouldFocus` focuses the field
|
|
462
|
+
* after the error lands
|
|
463
|
+
*/
|
|
464
|
+
declare function setErrorByPath(form: Form, path: Path, error: string | FieldError | (string | FieldError)[] | undefined, options?: SetErrorOptions): void;
|
|
465
|
+
/**
|
|
466
|
+
* Clear errors
|
|
467
|
+
* @param form
|
|
468
|
+
* @param name a single path or a list of paths; omit to clear every error
|
|
469
|
+
*/
|
|
470
|
+
declare function clearErrors(form: Form, name?: Name | Name[]): void;
|
|
471
|
+
/** Options accepted by {@link setServerErrors}. */
|
|
472
|
+
type SetServerErrorsOptions = {
|
|
473
|
+
/** Keep existing field errors instead of clearing them first. Defaults
|
|
474
|
+
* to `false`: a fresh server response replaces the prior error state. */
|
|
475
|
+
keepExisting?: boolean;
|
|
476
|
+
};
|
|
477
|
+
/**
|
|
478
|
+
* Land a server-side error response on the form: each entry becomes the
|
|
479
|
+
* named field's error(s) with `type: 'server'`, ready for the same
|
|
480
|
+
* renderError/`useError` channel client-side validation uses. Takes the
|
|
481
|
+
* flat `Record<string, string | string[]>` shape REST APIs commonly
|
|
482
|
+
* return (RealWorld: `422 {errors: {email: ['has already been taken']}}`)
|
|
483
|
+
* without a hand-rolled `Object.entries` + `setError` loop.
|
|
484
|
+
*
|
|
485
|
+
* A string value lands as one error, a string array as several (first one
|
|
486
|
+
* is what `getError`/`error` expose); an empty array clears that field's
|
|
487
|
+
* errors. By default every existing error is cleared first — a fresh
|
|
488
|
+
* response describes the current state, not a patch onto stale client
|
|
489
|
+
* errors; pass `keepExisting: true` to layer instead.
|
|
490
|
+
* @param form
|
|
491
|
+
* @param errors field errors keyed by name
|
|
492
|
+
* @param options
|
|
493
|
+
*/
|
|
494
|
+
declare function setServerErrors(form: Form, errors: Record<string, string | string[]>, options?: SetServerErrorsOptions): void;
|
|
495
|
+
/**
|
|
496
|
+
* Drop every `type: 'server'` error from the form — the round-trip state
|
|
497
|
+
* a previous submit landed. {@link handleSubmit} runs this before its
|
|
498
|
+
* validation round so a retry is judged on the fresh attempt, not on the
|
|
499
|
+
* server's verdict for the last payload (client errors are untouched:
|
|
500
|
+
* they describe the current form state). Emits payload-less 'errors' when
|
|
501
|
+
* anything changed.
|
|
502
|
+
*/
|
|
503
|
+
declare function clearServerErrors(form: Form): void;
|
|
504
|
+
/**
|
|
505
|
+
* Set field touched state
|
|
506
|
+
* @param form
|
|
507
|
+
* @param name
|
|
508
|
+
*/
|
|
509
|
+
/**
|
|
510
|
+
* @param form
|
|
511
|
+
*/
|
|
512
|
+
declare function hasErrors({ errors }: Form): boolean;
|
|
513
|
+
|
|
514
|
+
declare function unsetValidatingByPath({ emitter, validating }: Form, path: Path): void;
|
|
515
|
+
declare function setValidatingByPath({ emitter, validating }: Form, path: Path): void;
|
|
516
|
+
/**
|
|
517
|
+
* Field validator. Returns an error (a string, a FieldError, or an array
|
|
518
|
+
* mixing both) or undefined when valid; may return a Promise for async
|
|
519
|
+
* validation.
|
|
520
|
+
*
|
|
521
|
+
* The second argument carries the validation context. `meta.signal` is
|
|
522
|
+
* aborted as soon as the round is superseded — a newer round started, or
|
|
523
|
+
* the field unregistered — so async validators can cancel their underlying
|
|
524
|
+
* work (fetch, timers) instead of racing a stale result home. Stale
|
|
525
|
+
* results are dropped independently by the registration's lock
|
|
526
|
+
* ({@link registerValidatorByPath}), so validators that ignore the signal
|
|
527
|
+
* stay correct too. Validators written against the older two-argument
|
|
528
|
+
* signature keep working.
|
|
529
|
+
*/
|
|
530
|
+
type Validator = (value: any, meta: {
|
|
531
|
+
form: Form;
|
|
532
|
+
path: Path;
|
|
533
|
+
signal: AbortSignal;
|
|
534
|
+
}) => string | FieldError | (string | FieldError)[] | undefined | Promise<string | FieldError | (string | FieldError)[] | undefined>;
|
|
535
|
+
/**
|
|
536
|
+
* Synchronous pre-validator for {@link registerValidatorByPath}'s `sync`
|
|
537
|
+
* accessor — declarative `required` rules compiled by `rulesToValidator`
|
|
538
|
+
* in practice, but any sync-only check works. Runs on every kick, never
|
|
539
|
+
* debounced: its errors land immediately and, while present,
|
|
540
|
+
* short-circuit the debounced validator for that kick (the expensive
|
|
541
|
+
* check never sees a value the gate already rejects). Must be synchronous
|
|
542
|
+
* — unlike a {@link Validator} it may not return a Promise — and its meta
|
|
543
|
+
* carries no `signal`: there is nothing to abort in a synchronous check.
|
|
544
|
+
*/
|
|
545
|
+
type SyncValidator = (value: any, meta: {
|
|
546
|
+
form: Form;
|
|
547
|
+
path: Path;
|
|
548
|
+
}) => string | FieldError | (string | FieldError)[] | undefined;
|
|
549
|
+
/** Live options for {@link registerValidatorByPath}: read at every kick
|
|
550
|
+
* through accessors, so callers (React's `useValidate`) can swap the
|
|
551
|
+
* validator/debounce/sync-gate per render without re-subscribing the
|
|
552
|
+
* registration mid-flight. */
|
|
553
|
+
type ValidatorRegistration = {
|
|
554
|
+
/** Current debounced validator (or undefined — a sync-only
|
|
555
|
+
* registration). */
|
|
556
|
+
validate: () => Validator | undefined;
|
|
557
|
+
/** Debounce delay in milliseconds; 0 (default) runs immediately. */
|
|
558
|
+
debounce: () => number;
|
|
559
|
+
/** Synchronous pre-validator, run on every kick (never debounced). */
|
|
560
|
+
sync: () => SyncValidator | undefined;
|
|
561
|
+
/** Whether the debounced validator still runs when the sync gate
|
|
562
|
+
* failed — TanStack Form's `asyncAlways`: the gate's errors land
|
|
563
|
+
* immediately (never debounced), then the validator's own result lands
|
|
564
|
+
* alongside them (per-source semantics) instead of the gate
|
|
565
|
+
* short-circuiting the whole kick. Optional — absent means false
|
|
566
|
+
* (gate failure owns the kick's outcome), so pre-existing
|
|
567
|
+
* framework-free registrations keep working unchanged. */
|
|
568
|
+
asyncAlways?: () => boolean;
|
|
569
|
+
};
|
|
570
|
+
/**
|
|
571
|
+
* Register a field validator's kick at `path` in {@link Form.validators}
|
|
572
|
+
* — the framework-free machinery behind `useValidate`. Returns a
|
|
573
|
+
* disposer that drops the registration and cancels any pending debounce
|
|
574
|
+
* window or in-flight round (its signal aborts and its validating mark
|
|
575
|
+
* is released).
|
|
576
|
+
*
|
|
577
|
+
* Contract of the registered kick (the same contract `trigger` /
|
|
578
|
+
* `ensureValidate` rely on when they run every entry, and the
|
|
579
|
+
* user-change gate relies on when it runs the changed path's entry):
|
|
580
|
+
* - the `sync` gate runs immediately on every kick — never debounced —
|
|
581
|
+
* and while it returns errors, the debounced validator is skipped for
|
|
582
|
+
* that kick and any pending window or in-flight round is superseded —
|
|
583
|
+
* unless `asyncAlways` is set, in which case the validator still runs
|
|
584
|
+
* and its result lands alongside the gate's errors (per-source);
|
|
585
|
+
* - a positive `debounce` merges kicks inside the window: only the last
|
|
586
|
+
* one runs the validator, and while the timer is pending the field
|
|
587
|
+
* counts as validating so `trigger`/`ensureValidate` wait it out;
|
|
588
|
+
* - async results land under a lock: a superseded round's outcome —
|
|
589
|
+
* rejection included — is dropped, and only the owning round releases
|
|
590
|
+
* the validating mark;
|
|
591
|
+
* - a synchronous throw inside the validator propagates to the caller
|
|
592
|
+
* (the validating mark is not left stuck behind it).
|
|
593
|
+
*
|
|
594
|
+
* Registering at a path already registered by another mount replaces it
|
|
595
|
+
* (last-wins, the historical `useValidate` behavior); the disposer drops
|
|
596
|
+
* its own registration unconditionally.
|
|
597
|
+
*
|
|
598
|
+
* @param form
|
|
599
|
+
* @param path
|
|
600
|
+
* @param registration live validator/debounce/sync accessors
|
|
601
|
+
* @return disposer: unregister and cancel pending work
|
|
602
|
+
*/
|
|
603
|
+
declare function registerValidatorByPath(form: Form, path: Path, registration: ValidatorRegistration): () => void;
|
|
604
|
+
/**
|
|
605
|
+
* Set field error
|
|
606
|
+
* @param form
|
|
607
|
+
* @param name
|
|
608
|
+
* @param error string is normalized to {type: 'custom', message}; a
|
|
609
|
+
* FieldError object is stored as-is; an array holds several errors
|
|
610
|
+
* (falsy items dropped, strings normalized); undefined clears
|
|
611
|
+
*/
|
|
612
|
+
/** Options accepted by {@link trigger}. `shouldTouch` defaults to `false`;
|
|
613
|
+
* omitting the options object entirely keeps the plain validate-only
|
|
614
|
+
* behavior, so the historical two-argument calls are untouched. */
|
|
615
|
+
type TriggerOptions = {
|
|
616
|
+
/** Mark every path in the triggered scope as touched — even when
|
|
617
|
+
* validation fails — once the round settles. Mirrors react-hook-form's
|
|
618
|
+
* trigger `shouldTouch`. Defaults to `false`. */
|
|
619
|
+
shouldTouch?: boolean;
|
|
620
|
+
/**
|
|
621
|
+
* Focus the first errored field in the triggered scope once the round
|
|
622
|
+
* settles (and only when the round left errors) — react-hook-form's
|
|
623
|
+
* trigger `shouldFocus` counterpart. Rides the 'focusError' event
|
|
624
|
+
* channel like a failed submit's auto-focus: only mounted bound fields
|
|
625
|
+
* react, unmounted ones are silent no-ops. Without `name` the first key
|
|
626
|
+
* of the errors Map wins (the same rule handleSubmit applies); with
|
|
627
|
+
* `name` the first errored triggered key does. Defaults to `false`.
|
|
628
|
+
*/
|
|
629
|
+
shouldFocus?: boolean;
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* Trigger field validation.
|
|
633
|
+
*
|
|
634
|
+
* Without `name` every registered field validator runs. A single `name` —
|
|
635
|
+
* dotted string or segments array — runs only that field's validator, and
|
|
636
|
+
* an array of names runs each one in order. An empty array is a no-op, as
|
|
637
|
+
* is any name with no registered validator. An array argument counts as
|
|
638
|
+
* one segments path only when it mixes in numbers (`['items', 0]`); pure
|
|
639
|
+
* string arrays are name lists, so `['a', 'b']` triggers fields `a` and
|
|
640
|
+
* `b`, not the nested path `a.b`.
|
|
641
|
+
*
|
|
642
|
+
* `options.shouldTouch` marks the triggered scope — the given names, or
|
|
643
|
+
* every registered field when `name` is omitted — as touched after the
|
|
644
|
+
* round settles, whether validation passed or failed. The wait/settle
|
|
645
|
+
* logic is untouched: the marking rides on top of the settled round, so
|
|
646
|
+
* subscribers observe errors and touched together rather than mid-flight.
|
|
647
|
+
*
|
|
648
|
+
* The returned promise waits for the triggered validation to settle —
|
|
649
|
+
* async validators included — so their errors have already landed in
|
|
650
|
+
* `form.errors` when it resolves. It never rejects: landing errors is the
|
|
651
|
+
* expected outcome here, not a failure. Resolves `true` when the triggered
|
|
652
|
+
* scope is error-free, `false` otherwise. Without `name` the scope is all
|
|
653
|
+
* fields plus the form-level `validate` result (which runs after field
|
|
654
|
+
* validators settle, same pipeline as {@link ensureValidate}); with `name`
|
|
655
|
+
* only those fields' own errors count and form-level `validate` is
|
|
656
|
+
* skipped (RHF semantics).
|
|
657
|
+
*
|
|
658
|
+
* Fire-and-forget callers may ignore the promise: the validator kicks
|
|
659
|
+
* still happen synchronously, matching the pre-promise behavior.
|
|
660
|
+
*
|
|
661
|
+
* @param form
|
|
662
|
+
* @param name field name(s) to trigger, or all fields when omitted
|
|
663
|
+
* @param options extra behavior toggles ({@link TriggerOptions}); omitted,
|
|
664
|
+
* validation alone runs — no touched marking
|
|
665
|
+
* @return whether the triggered scope is error-free once validation settles
|
|
666
|
+
*/
|
|
667
|
+
declare function trigger(form: Form, name?: Name | Name[], options?: TriggerOptions): Promise<boolean>;
|
|
668
|
+
/**
|
|
669
|
+
* Run the form-level `validate` and land its result, honoring the form's
|
|
670
|
+
* `validateDebounce` option.
|
|
671
|
+
*
|
|
672
|
+
* Undebounced (`0`/undefined) the caller's await *is* the validate call —
|
|
673
|
+
* the long-standing pipeline, unchanged: no validating mark, no round
|
|
674
|
+
* gating, immediate values snapshot, rejection propagating to the caller.
|
|
675
|
+
*
|
|
676
|
+
* Debounced, the kick opens (or restarts — kicks inside the window merge)
|
|
677
|
+
* a window during which the form counts as validating, so `trigger` /
|
|
678
|
+
* `ensureValidate` / submit wait the window out exactly like a field's
|
|
679
|
+
* `validateDebounce` window. When the timer fires, the round reads the
|
|
680
|
+
* then-current values, supersedes (aborts) any in-flight round, and lands
|
|
681
|
+
* its result. The returned promise settles once the window group's final
|
|
682
|
+
* round has landed — rejecting when that round's validate callback threw
|
|
683
|
+
* or its promise rejected, mirroring the undebounced propagation — so
|
|
684
|
+
* merged callers all observe the same outcome.
|
|
685
|
+
*
|
|
686
|
+
* Only called under `if (form.validate)`. Public: the form-level-only
|
|
687
|
+
* round (field validators excluded) — `useForm`'s `validateOnMount` run
|
|
688
|
+
* and cross-cutting re-checks use it; `trigger`/`ensureValidate` compose
|
|
689
|
+
* it after the field validators settle.
|
|
690
|
+
*/
|
|
691
|
+
declare function runFormValidate(form: Form): Promise<void>;
|
|
692
|
+
/**
|
|
693
|
+
* Form-level twin of the gated validator kick in `useField`'s onChange:
|
|
694
|
+
* re-run the form-level `validate` after a user change to a field listed
|
|
695
|
+
* in `validateDeps`. Called from the field's own change pipeline (typing
|
|
696
|
+
* and `changeValue` alike — both route through the mounted field's
|
|
697
|
+
* onChange), so programmatic `setValue` writes do not re-run it, exactly
|
|
698
|
+
* like they do not re-run field validators.
|
|
699
|
+
*
|
|
700
|
+
* The gate mirrors the per-field matrix with the *changed field's*
|
|
701
|
+
* effective `mode` (a per-field override governs when its changes may
|
|
702
|
+
* fire validation) and the form-level `reValidateMode` against the last
|
|
703
|
+
* round's error footprint ({@link hasFormValidateErrors} — field
|
|
704
|
+
* validators' errors never arm this kick):
|
|
705
|
+
* - `mode` `'onChange'`/`'all'` — every dep change re-runs;
|
|
706
|
+
* - `mode` `'onTouched'` — dep changes re-run once the field was touched;
|
|
707
|
+
* - otherwise the re-run waits for `reValidateMode: 'onChange'` (the
|
|
708
|
+
* default) while the last round's error is still live — the
|
|
709
|
+
* submit-then-fix flow: the mismatch lands on submit, editing the
|
|
710
|
+
* dependency re-runs the validate and clears it.
|
|
711
|
+
* `reValidateMode: 'onBlur'`/`'onSubmit'` never re-run on a change (a
|
|
712
|
+
* change is not a blur; submit re-runs are the submit pipeline's job).
|
|
713
|
+
*
|
|
714
|
+
* The kick is fire-and-forget: async round rejections are swallowed
|
|
715
|
+
* (nothing in an event handler can await them), while a synchronous
|
|
716
|
+
* throw inside the validate callback propagates to the caller exactly
|
|
717
|
+
* like a field validator's does.
|
|
718
|
+
*
|
|
719
|
+
* A no-op unless the form set `validateDeps` listing `path` — forms
|
|
720
|
+
* without the option pay one property check here.
|
|
721
|
+
*/
|
|
722
|
+
declare function revalidateFormOnChange(form: Form, path: Path, mode: ValidationMode): void;
|
|
723
|
+
/** Register one field's validateDeps declaration: `key` re-validates when
|
|
724
|
+
* any path in `depKeys` takes a user change. Idempotent per (key, dep)
|
|
725
|
+
* pair, so StrictMode's double effect is harmless. */
|
|
726
|
+
declare function registerFieldValidateDeps(form: Form, key: string, depKeys: string[]): void;
|
|
727
|
+
/** Drop one field's validateDeps registration ({@link
|
|
728
|
+
* registerFieldValidateDeps}). Entries nobody lists anymore are removed so
|
|
729
|
+
* the registry never outlives its fields. */
|
|
730
|
+
declare function unregisterFieldValidateDeps(form: Form, key: string, depKeys: string[]): void;
|
|
731
|
+
/**
|
|
732
|
+
* Field-level twin of {@link revalidateFormOnChange}: after a user change
|
|
733
|
+
* to `path`, re-run every field validator that declared `path` in its
|
|
734
|
+
* `validateDeps` (useField option). Same channel, same gate: the kick
|
|
735
|
+
* rides the changed field's own onChange pipeline (typing and
|
|
736
|
+
* `changeValue` alike), so programmatic `setValue` writes never fire it —
|
|
737
|
+
* exactly like field validators and the form-level `validateDeps`.
|
|
738
|
+
*
|
|
739
|
+
* The gate mirrors the form-level matrix with the *changed field's*
|
|
740
|
+
* effective `mode` and the form-level `reValidateMode` against each
|
|
741
|
+
* dependent's live error:
|
|
742
|
+
* - `mode` `'onChange'`/`'all'` — every dep change re-runs the dependent;
|
|
743
|
+
* - `mode` `'onTouched'` — once the changed field was touched;
|
|
744
|
+
* - otherwise the re-run waits for `reValidateMode: 'onChange'` (the
|
|
745
|
+
* default) while the dependent still shows an error — the
|
|
746
|
+
* submit-then-fix flow: the mismatch lands on submit, editing the
|
|
747
|
+
* dependency re-validates the dependent and a passing round clears it
|
|
748
|
+
* (a field validator owns its whole key, so the re-run's result
|
|
749
|
+
* replaces whatever the previous round wrote — the field-level shape
|
|
750
|
+
* of the form-level footprint reclaim).
|
|
751
|
+
*
|
|
752
|
+
* The kick is an ordinary validator kick: the dependent's own
|
|
753
|
+
* `validateDebounce` window applies, and a synchronous throw inside its
|
|
754
|
+
* validate propagates to the caller like any field validator's would.
|
|
755
|
+
*
|
|
756
|
+
* A no-op unless some field declared `path` as a dep — forms without any
|
|
757
|
+
* field-level `validateDeps` pay one property check here.
|
|
758
|
+
*/
|
|
759
|
+
declare function revalidateDependentsOnChange(form: Form, path: Path, mode: ValidationMode): void;
|
|
760
|
+
/** The Error {@link ensureValidate} rejects with: `message` is the first
|
|
761
|
+
* error's display text ({@link getFirstError}) — the long-standing shape
|
|
762
|
+
* — and `.errors` carries the complete flattened error list ({@link
|
|
763
|
+
* getErrors}: `{path, type, message}` entries, dotted display paths) so
|
|
764
|
+
* catchers can branch on types and locate fields without re-reading the
|
|
765
|
+
* form. */
|
|
766
|
+
type FormValidationError = Error & {
|
|
767
|
+
errors: FieldErrorEntry[];
|
|
768
|
+
};
|
|
769
|
+
/**
|
|
770
|
+
* Validate and throw if any field error.
|
|
771
|
+
* @param form
|
|
772
|
+
* @return resolve if no error; reject and stop validate if has an error
|
|
773
|
+
*/
|
|
774
|
+
declare function ensureValidate(form: Form): Promise<void>;
|
|
775
|
+
/**
|
|
776
|
+
* Validate and return if any field error.
|
|
777
|
+
* @param form
|
|
778
|
+
* @return error message string or void
|
|
779
|
+
*/
|
|
780
|
+
declare function validate(form: Form): Promise<void | string>;
|
|
781
|
+
|
|
782
|
+
/** A field error: `type` identifies the error kind ('custom' for plain
|
|
783
|
+
* string errors), `message` is the display text. */
|
|
784
|
+
type FieldError = {
|
|
785
|
+
type: string;
|
|
786
|
+
message: string;
|
|
787
|
+
};
|
|
788
|
+
/** A flattened entry from {@link getErrors}. */
|
|
789
|
+
type FieldErrorEntry = {
|
|
790
|
+
path: string;
|
|
791
|
+
type: string;
|
|
792
|
+
message: string;
|
|
793
|
+
};
|
|
794
|
+
/** When a field is validated:
|
|
795
|
+
* - `'onSubmit'` (default): only on submit
|
|
796
|
+
* - `'onBlur'`: when the field loses focus
|
|
797
|
+
* - `'onChange'`: on every change
|
|
798
|
+
* - `'onTouched'`: on first blur, then on every change
|
|
799
|
+
* - `'all'`: on both change and blur
|
|
800
|
+
*/
|
|
801
|
+
type ValidationMode = 'onSubmit' | 'onBlur' | 'onChange' | 'onTouched' | 'all';
|
|
802
|
+
/** When a field is re-validated after it already has an error:
|
|
803
|
+
* - `'onChange'` (default): on every change
|
|
804
|
+
* - `'onBlur'`: when the field loses focus
|
|
805
|
+
* - `'onSubmit'`: only on submit (no live re-validation)
|
|
806
|
+
*/
|
|
807
|
+
type ReValidateMode = 'onChange' | 'onBlur' | 'onSubmit';
|
|
808
|
+
/** When the form-level {@link Options.validate} re-runs outside
|
|
809
|
+
* submit/`trigger`/`validateOnMount`:
|
|
810
|
+
* - `'onSubmit'` (default): only on submit/trigger — cross-field linkage
|
|
811
|
+
* goes through {@link Options.validateDeps} instead
|
|
812
|
+
* - `'onChange'`: every user change to a bound field re-runs it
|
|
813
|
+
* - `'onBlur'`: every user blur of a bound field re-runs it
|
|
814
|
+
*
|
|
815
|
+
* TanStack Form's `validators.onChange`/`validators.onBlur` counterpart:
|
|
816
|
+
* a cadence declaration instead of enumerating deps. The re-run rides the
|
|
817
|
+
* changed field's own user-change pipeline (typing and `changeValue`
|
|
818
|
+
* alike, never programmatic `setValue`), honors {@link
|
|
819
|
+
* Options.validateDebounce}, and reuses the round-scoped error footprint —
|
|
820
|
+
* a passing re-run clears what the previous round wrote.
|
|
821
|
+
*/
|
|
822
|
+
type FormValidateMode = 'onSubmit' | 'onChange' | 'onBlur';
|
|
823
|
+
/**
|
|
824
|
+
* Options accepted by {@link Form.register} — the non-hook binding for
|
|
825
|
+
* uncontrolled fields (react-hook-form's `register` contract: the bound
|
|
826
|
+
* element never re-renders; the store carries every write).
|
|
827
|
+
*/
|
|
828
|
+
type RegisterOptions = {
|
|
829
|
+
/**
|
|
830
|
+
* Field-level validation mode for this binding (see {@link
|
|
831
|
+
* ValidationMode}): typing gates on it exactly like a mounted
|
|
832
|
+
* `useField`. Defaults to the form's `mode`.
|
|
833
|
+
*/
|
|
834
|
+
mode?: ValidationMode;
|
|
835
|
+
/**
|
|
836
|
+
* Unmount behavior: `true` (the default, this library's historical
|
|
837
|
+
* default) tombstones the path when the element unmounts, `false` keeps
|
|
838
|
+
* the value. Falls back to the form-level
|
|
839
|
+
* `createForm({shouldUnregister})` when omitted.
|
|
840
|
+
*/
|
|
841
|
+
shouldUnregister?: boolean;
|
|
842
|
+
/**
|
|
843
|
+
* DOM event → value extractor for the returned `onChange`. Defaults to
|
|
844
|
+
* the element's own protocol: `target.files` for file inputs,
|
|
845
|
+
* `target.checked` for checkboxes, `target.valueAsNumber` /
|
|
846
|
+
* `target.valueAsDate` under those flags, `target.value` otherwise —
|
|
847
|
+
* the same extraction `<Field>` performs.
|
|
848
|
+
*/
|
|
849
|
+
eventToValue?: (e: any) => any;
|
|
850
|
+
/** Store `e.target.valueAsNumber` instead of the string value
|
|
851
|
+
* (`<input type="number">`, RHF's `register({valueAsNumber})`). An
|
|
852
|
+
* explicit `eventToValue` takes precedence. */
|
|
853
|
+
valueAsNumber?: boolean;
|
|
854
|
+
/** Store `e.target.valueAsDate` instead of the string value (RHF's
|
|
855
|
+
* `register({valueAsDate})`). An explicit `eventToValue` takes
|
|
856
|
+
* precedence; combining with `valueAsNumber` is a TypeError. */
|
|
857
|
+
valueAsDate?: boolean;
|
|
858
|
+
/**
|
|
859
|
+
* Declarative rules for this binding — the same {@link FieldRules}
|
|
860
|
+
* `useField`/`<Field>` take (`required` runs as the synchronous gate,
|
|
861
|
+
* `validate` callbacks included). Wired through the framework-free
|
|
862
|
+
* `registerValidatorByPath`, so `trigger`/submit/`mode` gating see it
|
|
863
|
+
* exactly like a hook-registered validator. `validateDebounce` is
|
|
864
|
+
* fixed at 0 — the rules run immediately on every kick.
|
|
865
|
+
*/
|
|
866
|
+
rules?: FieldRules;
|
|
867
|
+
};
|
|
868
|
+
/**
|
|
869
|
+
* What {@link Form.register} returns: spread these props onto an
|
|
870
|
+
* uncontrolled DOM element (`<input {...form.register('name')} />`).
|
|
871
|
+
* The bound element never re-renders — read live state through
|
|
872
|
+
* `useValue`/`useError`/`getValues`, exactly like react-hook-form's
|
|
873
|
+
* `register` contract. `name` is the store's path key; `onChange` writes
|
|
874
|
+
* the extracted value through the gated user-change pipeline; `ref`
|
|
875
|
+
* attaches the element (seeds its initial DOM content into the store,
|
|
876
|
+
* wires the 'focusError' channel and bulk-reset DOM sync) and detaches it
|
|
877
|
+
* on unmount (tombstone unless `shouldUnregister: false`).
|
|
878
|
+
*/
|
|
879
|
+
type RegisterProps = {
|
|
880
|
+
name: string;
|
|
881
|
+
onChange: (e: any) => void;
|
|
882
|
+
onBlur: () => void;
|
|
883
|
+
ref: (el: any) => void;
|
|
884
|
+
};
|
|
885
|
+
/** Structured form-level validate result: `errors` uses the same nested
|
|
886
|
+
* shape a plain error record uses, `values` is the schema's parsed output
|
|
887
|
+
* (coerce/transform results included). Either side may be omitted.
|
|
888
|
+
*
|
|
889
|
+
* The brand constant itself lives in the errors module (the leaf module of
|
|
890
|
+
* the core dependency graph — every consumer imports it from there) and is
|
|
891
|
+
* re-exported below with `export *`. */
|
|
892
|
+
type ValidationOutcome<T> = {
|
|
893
|
+
[VALIDATION_OUTCOME]: true;
|
|
894
|
+
errors?: Record<string, any>;
|
|
895
|
+
values?: T;
|
|
896
|
+
};
|
|
897
|
+
/** What a form-level validate function may return: a plain nested error
|
|
898
|
+
* record (flattened into field errors — the long-standing shape), or a
|
|
899
|
+
* branded {@link ValidationOutcome} whose `values` become the form's
|
|
900
|
+
* parsedValues baseline. */
|
|
901
|
+
type ValidateResult<T> = Record<string, any> | ValidationOutcome<T> | Promise<Record<string, any> | ValidationOutcome<T>>;
|
|
902
|
+
/** Context passed to a form-level `validate` function's second argument.
|
|
903
|
+
* `signal` aborts as soon as the round is superseded — a newer round
|
|
904
|
+
* started (which only happens under a positive `validateDebounce`, where
|
|
905
|
+
* kicks merge into windows) — so async validators can cancel their
|
|
906
|
+
* underlying work instead of racing a stale result home. Stale results
|
|
907
|
+
* are dropped independently by the round gate, so validators that ignore
|
|
908
|
+
* the signal stay correct too; the same contract field-level validators
|
|
909
|
+
* get through their own `meta`. */
|
|
910
|
+
type FormValidateMeta<T extends Record<string, any> = any> = {
|
|
911
|
+
form: Form<T>;
|
|
912
|
+
signal: AbortSignal;
|
|
913
|
+
};
|
|
914
|
+
/** Form-level validator: receives all values (plus {@link
|
|
915
|
+
* FormValidateMeta} as an optional second argument) and returns a
|
|
916
|
+
* {@link ValidateResult} — sync or async — or `undefined`/nothing when
|
|
917
|
+
* valid (the runtime skips falsy results, so implicit-return callbacks
|
|
918
|
+
* type-check). */
|
|
919
|
+
type FormValidateFn<T extends Record<string, any> = any> = (values: T, meta: FormValidateMeta<T>) => ValidateResult<T> | undefined;
|
|
920
|
+
/**
|
|
921
|
+
* The emitter event table for {@link Form.emitter}: each event's payload
|
|
922
|
+
* tuple. Path-carrying events declare an optional single `Path` payload —
|
|
923
|
+
* emit sites send it for single-field mutations and omit it for bulk
|
|
924
|
+
* payload-less broadcasts (reset, setInitialValues, clear-all), both of
|
|
925
|
+
* which subscribers handle. `focusError` carries the target's path key
|
|
926
|
+
* plus optional {@link SetFocusOptions}.
|
|
927
|
+
*/
|
|
928
|
+
type FormEvents = ['change', [path?: Path]] | ['errors', [path?: Path]] | ['touched', [path?: Path]] | ['validating', [path?: Path]] | ['submitting', []] | ['submitCount', []] | ['submitSuccessful', []] | ['reset', []] | ['disabled', [path?: Path]] | ['status', []] | ['loading', []] | ['focusError', [key: string, options?: SetFocusOptions]];
|
|
929
|
+
type Form<T extends Record<string, any> = any> = {
|
|
930
|
+
emitter: EventEmitter<FormEvents>;
|
|
931
|
+
mode: ValidationMode;
|
|
932
|
+
reValidateMode: ReValidateMode;
|
|
933
|
+
initialValues: T;
|
|
934
|
+
values: Map<string, any>;
|
|
935
|
+
/** Tombstones of unregistered field paths (JSON path keys): reading or
|
|
936
|
+
* merging values must not fall back to initialValues for these paths. */
|
|
937
|
+
deleted: Set<string>;
|
|
938
|
+
/** Every error registered for a field, as a non-empty array (the
|
|
939
|
+
* write-side {@link setErrorByPath} normalizes to this invariant, so
|
|
940
|
+
* readers never need to guard against an empty list). Readers wanting
|
|
941
|
+
* the display error take the first entry ({@link getError}); readers
|
|
942
|
+
* wanting all of them use {@link getFieldErrors}. */
|
|
943
|
+
errors: Map<string, FieldError[]>;
|
|
944
|
+
touched: Set<string>;
|
|
945
|
+
/** Per-field validation kicks, registered by {@link
|
|
946
|
+
* registerValidatorByPath} (`useValidate` is the React-side
|
|
947
|
+
* registration): each is the field's debounce/lock-aware kick —
|
|
948
|
+
* invoking it validates the field's current value. `trigger` /
|
|
949
|
+
* `ensureValidate` run every entry; the user-change gate ({@link
|
|
950
|
+
* userChangeByPath}) runs the entry at the changed path. */
|
|
951
|
+
validators: Map<string, () => void>;
|
|
952
|
+
validating: Set<string>;
|
|
953
|
+
/** Parsed values from the last successful schema validation: the
|
|
954
|
+
* schema's complete output tree (coerced/transformed values included).
|
|
955
|
+
* Sits between initialValues and the values Map in {@link getValues}
|
|
956
|
+
* until `reset`/`setInitialValues` clears it. Never affects dirty
|
|
957
|
+
* state — that compares live edits against initialValues only. */
|
|
958
|
+
parsedValues: T | undefined;
|
|
959
|
+
/** Form-level validator, seeded from {@link Options.validate}. May
|
|
960
|
+
* receive a second {@link FormValidateMeta} argument. */
|
|
961
|
+
validate?: FormValidateFn<T>;
|
|
962
|
+
/** Delay in milliseconds before the form-level `validate` runs; seeded
|
|
963
|
+
* from {@link Options.validateDebounce} and fixed at create time. */
|
|
964
|
+
validateDebounce?: number;
|
|
965
|
+
/** Path keys (JSON-stringified segments) of the fields whose user
|
|
966
|
+
* changes re-run the form-level `validate`; normalized from {@link
|
|
967
|
+
* Options.validateDeps} at create time and fixed thereafter. */
|
|
968
|
+
validateDeps?: ReadonlySet<string>;
|
|
969
|
+
/** When the form-level `validate` re-runs outside submit/trigger —
|
|
970
|
+
* the cadence declared by {@link Options.validateMode}, seeded at
|
|
971
|
+
* create time and fixed thereafter ('onSubmit' by default; with
|
|
972
|
+
* 'onChange'/'onBlur' every user change/blur to a bound field re-runs
|
|
973
|
+
* it, no dep list required). */
|
|
974
|
+
validateMode: FormValidateMode;
|
|
975
|
+
isSubmitting: boolean;
|
|
976
|
+
/** Whether a submit has been attempted — set by `handleSubmit` on every
|
|
977
|
+
* attempt (validation outcome aside), cleared by `reset`.
|
|
978
|
+
* `useFormState().isSubmitted` reads it (react-hook-form's
|
|
979
|
+
* `formState.isSubmitted` semantics). */
|
|
980
|
+
isSubmitted: boolean;
|
|
981
|
+
submitCount: number;
|
|
982
|
+
isSubmitSuccessful: boolean | undefined;
|
|
983
|
+
/** True while an async {@link Options.initialValues} source (a Promise,
|
|
984
|
+
* or a thunk returning one) is still pending — the form starts empty
|
|
985
|
+
* and the resolved values become the baseline via setInitialValues when
|
|
986
|
+
* it lands. Flips through the payload-less 'loading' event
|
|
987
|
+
* (`useIsLoading` / `useFormState().isLoading`). */
|
|
988
|
+
isLoading: boolean;
|
|
989
|
+
/** Form-level default for a bound field's unmount behavior, seeded from
|
|
990
|
+
* {@link Options.shouldUnregister}: `true` (the default) tombstones an
|
|
991
|
+
* unmounted field, `false` keeps its value (react-hook-form's
|
|
992
|
+
* `shouldUnregister` semantics). A field's own `shouldUnregister` option
|
|
993
|
+
* overrides this. */
|
|
994
|
+
shouldUnregister?: boolean;
|
|
995
|
+
/** Form-level disabled flag, OR-ed into every bound field's `disabled`
|
|
996
|
+
* (form flag || the field's own option). Seeded from
|
|
997
|
+
* {@link Options}.disabled at create time and toggled at runtime with
|
|
998
|
+
* {@link setDisabled}, which emits a payload-less 'disabled' event so
|
|
999
|
+
* subscribed fields re-render. */
|
|
1000
|
+
disabled: boolean;
|
|
1001
|
+
/** Form-level default for mount validation, seeded from
|
|
1002
|
+
* {@link Options.validateOnMount}: `true` makes every mounted field
|
|
1003
|
+
* with a validator kick once after mount (deferred until an async
|
|
1004
|
+
* {@link Options.initialValues} source lands), and makes `useForm` run
|
|
1005
|
+
* the form-level `validate` once. A field's own `validateOnMount`
|
|
1006
|
+
* option overrides this flag in either direction. */
|
|
1007
|
+
validateOnMount: boolean;
|
|
1008
|
+
/** Form-level default for {@link UseValidateOptions.asyncAlways}:
|
|
1009
|
+
* whether a field's debounced validator still runs when its `required`
|
|
1010
|
+
* gate failed. A field's own `asyncAlways` option overrides this flag
|
|
1011
|
+
* in either direction. Seeded from {@link Options.asyncAlways}. */
|
|
1012
|
+
asyncAlways: boolean;
|
|
1013
|
+
/**
|
|
1014
|
+
* Whether native constraint validation gates submission (the submitted
|
|
1015
|
+
* element's checkValidity, skipped for targets without it — React
|
|
1016
|
+
* Native, toolbar buttons) and skips a bound `<Field>`'s custom
|
|
1017
|
+
* validators on a native-failing kick. Seeded from
|
|
1018
|
+
* {@link Options.shouldUseNativeValidation} — default `true`; a submit
|
|
1019
|
+
* may override per attempt via
|
|
1020
|
+
* {@link HandleSubmitOptions.shouldUseNativeValidation}.
|
|
1021
|
+
*/
|
|
1022
|
+
shouldUseNativeValidation: boolean;
|
|
1023
|
+
/**
|
|
1024
|
+
* User-owned metadata slot for non-field state — session flags, server
|
|
1025
|
+
* backfill that belongs to no field, step indices (Formik's `status`
|
|
1026
|
+
* role). Written with {@link setStatus}, which emits the payload-less
|
|
1027
|
+
* 'status' event; read directly or reactively through {@link useStatus}.
|
|
1028
|
+
* Starts `undefined`.
|
|
1029
|
+
*/
|
|
1030
|
+
status: any;
|
|
1031
|
+
/**
|
|
1032
|
+
* Non-hook field binding — react-hook-form's `register` contract:
|
|
1033
|
+
* spread the returned props onto an uncontrolled DOM element
|
|
1034
|
+
* (`<input {...form.register('name')} />`) and the element never
|
|
1035
|
+
* re-renders, while the store carries every write and `trigger`/submit
|
|
1036
|
+
* validate it. Seeding, the 'focusError' channel, bulk-reset DOM sync
|
|
1037
|
+
* and unmount tombstoning ride the `ref` callback's attach/detach —
|
|
1038
|
+
* no React state involved, so `register` works anywhere (dynamic
|
|
1039
|
+
* lists, conditional fields, non-React adapters). See {@link
|
|
1040
|
+
* RegisterOptions} / {@link RegisterProps}.
|
|
1041
|
+
*/
|
|
1042
|
+
register: (name: Name, options?: RegisterOptions) => RegisterProps;
|
|
1043
|
+
};
|
|
1044
|
+
type Options<T extends Record<string, any> = any> = {
|
|
1045
|
+
/**
|
|
1046
|
+
* The values baseline. Sync objects seed immediately (SSR renders
|
|
1047
|
+
* them). Async sources — a Promise, or a thunk returning a value or
|
|
1048
|
+
* Promise (react-hook-form's async `defaultValues` shape) — start the
|
|
1049
|
+
* form empty with `isLoading: true` and land the resolved values as
|
|
1050
|
+
* the baseline via setInitialValues once they resolve: value
|
|
1051
|
+
* subscribers re-sync, dirty/touched state starts clean, and a later
|
|
1052
|
+
* `reset()` returns to the resolved baseline. A rejected source flips
|
|
1053
|
+
* isLoading back to false, keeps the form empty, and logs the error in
|
|
1054
|
+
* DEV — attach a `.catch` on the source itself to handle it. The thunk
|
|
1055
|
+
* runs at create time: keep its identity stable (module scope or
|
|
1056
|
+
* useMemo) when passing it inline, and note StrictMode double-invokes
|
|
1057
|
+
* it in development, like every render-phase call.
|
|
1058
|
+
*/
|
|
1059
|
+
initialValues?: T | Promise<T> | (() => T | Promise<T>);
|
|
1060
|
+
/** When fields are validated. Defaults to `'onSubmit'`. See
|
|
1061
|
+
* {@link ValidationMode}. */
|
|
1062
|
+
mode?: ValidationMode;
|
|
1063
|
+
/** When a field is re-validated after it already has an error — it only
|
|
1064
|
+
* takes effect once the field has an error. Defaults to `'onChange'`. See
|
|
1065
|
+
* {@link ReValidateMode}. */
|
|
1066
|
+
reValidateMode?: ReValidateMode;
|
|
1067
|
+
/**
|
|
1068
|
+
* Form-level validator. Returns a record of errors keyed by field path;
|
|
1069
|
+
* nested objects are flattened ('a.b' style) and array values contribute
|
|
1070
|
+
* every non-empty string they hold as separate errors (zod `flatten()`
|
|
1071
|
+
* formErrors style). Schema adapters instead return a branded
|
|
1072
|
+
* {@link ValidationOutcome}: `errors` flattens the same way, `values`
|
|
1073
|
+
* (the schema's parsed output) becomes the form's parsedValues baseline
|
|
1074
|
+
* that {@link getValues} layers over initialValues.
|
|
1075
|
+
*
|
|
1076
|
+
* Alternatively pass a Standard Schema v1 object directly (zod
|
|
1077
|
+
* v3.24+/v4, valibot v1, arktype, …) — it is wrapped into a form-level
|
|
1078
|
+
* validator automatically, no resolver import needed, and `TValues`
|
|
1079
|
+
* infers from the schema's output type:
|
|
1080
|
+
* `createForm({validate: schema})` → `Form<InferSchemaValues<typeof
|
|
1081
|
+
* schema>>`.
|
|
1082
|
+
*/
|
|
1083
|
+
validate?: FormValidateFn<T> | StandardSchemaV1<unknown, T>;
|
|
1084
|
+
/**
|
|
1085
|
+
* Milliseconds to debounce the form-level `validate`: kicks from
|
|
1086
|
+
* `trigger`/`ensureValidate`/submit inside the window merge into one
|
|
1087
|
+
* run, and while the timer is pending the form counts as validating,
|
|
1088
|
+
* so `trigger` and submit wait the window out — the same contract the
|
|
1089
|
+
* per-field `validateDebounce` gives field validators. The merged run
|
|
1090
|
+
* reads the values current when its timer fires. Defaults to `0`
|
|
1091
|
+
* (validate runs immediately, exactly as before this option existed).
|
|
1092
|
+
*/
|
|
1093
|
+
validateDebounce?: number;
|
|
1094
|
+
/** Fields whose user changes re-run the form-level `validate` — the
|
|
1095
|
+
* cross-field dependency list (password-confirm mismatch and friends).
|
|
1096
|
+
* Each entry is a field path ('password', 'user.email', 'items.0.qty');
|
|
1097
|
+
* a user change to a listed field re-runs the form-level `validate`
|
|
1098
|
+
* under the same mode/`reValidateMode` gating the field's own
|
|
1099
|
+
* validator gets. Omit it and the form-level `validate` only runs on
|
|
1100
|
+
* `trigger`/submit, exactly as before this option existed.
|
|
1101
|
+
*
|
|
1102
|
+
* Opting in also changes what a re-run may clear: each round first
|
|
1103
|
+
* drops the errors the previous round wrote (paths it flattened onto),
|
|
1104
|
+
* so a dep change that fixes the cross-field error makes it disappear.
|
|
1105
|
+
* Errors the round never wrote — field validators', `setServerErrors`,
|
|
1106
|
+
* manual `setError` — are never touched. TanStack Form's counterpart is
|
|
1107
|
+
* `onChangeListenTo` (v1) / validator `triggers` (v2 alpha). */
|
|
1108
|
+
validateDeps?: FieldPath<T>[];
|
|
1109
|
+
/**
|
|
1110
|
+
* When the form-level `validate` re-runs outside submit/`trigger`/
|
|
1111
|
+
* `validateOnMount` — a cadence declaration instead of enumerating
|
|
1112
|
+
* {@link Options.validateDeps}. `'onSubmit'` (the default) keeps the
|
|
1113
|
+
* historical behavior (submit/trigger only, deps for cross-field
|
|
1114
|
+
* linkage). `'onChange'` re-runs the form-level validate on every user
|
|
1115
|
+
* change to a bound field; `'onBlur'` on every user blur. The re-run
|
|
1116
|
+
* rides the changed field's own user-change pipeline (typing and
|
|
1117
|
+
* `changeValue` alike, never programmatic `setValue`), honors
|
|
1118
|
+
* {@link Options.validateDebounce}, and clears what the previous round
|
|
1119
|
+
* wrote — TanStack Form's `validators.onChange`/`validators.onBlur`
|
|
1120
|
+
* counterpart. See {@link FormValidateMode}.
|
|
1121
|
+
*/
|
|
1122
|
+
validateMode?: FormValidateMode;
|
|
1123
|
+
/**
|
|
1124
|
+
* Form-level default for a bound field's unmount behavior. `true` (the
|
|
1125
|
+
* default) tombstones an unmounted field — it drops out of
|
|
1126
|
+
* `getValues()` instead of reviving its initial value (this library's
|
|
1127
|
+
* historical default); `false` keeps the value, matching
|
|
1128
|
+
* react-hook-form's `shouldUnregister`. A field's own
|
|
1129
|
+
* `useField({shouldUnregister})` option overrides the form-level flag
|
|
1130
|
+
* in either direction.
|
|
1131
|
+
*/
|
|
1132
|
+
shouldUnregister?: boolean;
|
|
1133
|
+
/** Start the form with every bound field disabled — the flag bound
|
|
1134
|
+
* fields OR with their own `disabled` option (a field cannot opt out
|
|
1135
|
+
* of a disabled form). Toggle later with {@link setDisabled}.
|
|
1136
|
+
* Defaults to `false`. */
|
|
1137
|
+
disabled?: boolean;
|
|
1138
|
+
/**
|
|
1139
|
+
* Form-level default for field validation's `asyncAlways`: when true,
|
|
1140
|
+
* a field whose `required` gate failed still runs its debounced
|
|
1141
|
+
* validator (the gate's errors land immediately, the validator's own
|
|
1142
|
+
* result lands alongside them per-source). TanStack Form's
|
|
1143
|
+
* `asyncAlways` counterpart. A field's own
|
|
1144
|
+
* `useField({asyncAlways})` option overrides the form-level flag in
|
|
1145
|
+
* either direction. Defaults to `false`.
|
|
1146
|
+
*/
|
|
1147
|
+
asyncAlways?: boolean;
|
|
1148
|
+
/**
|
|
1149
|
+
* Whether native constraint validation gates submission and skips a
|
|
1150
|
+
* bound `<Field>`'s custom validators when its native constraints fail
|
|
1151
|
+
* that kick (react-hook-form's `shouldUseNativeValidation`): pass
|
|
1152
|
+
* `false` for forms where custom validators are the only source of
|
|
1153
|
+
* truth — the browser's checkValidity/reportValidity gate (and the
|
|
1154
|
+
* per-kick native gate in `<Field>`) stop running, while declarative
|
|
1155
|
+
* `rules` keep producing store-side errors and native constraint
|
|
1156
|
+
* attributes keep rendering for a11y. Defaults to `true`. Fixed at
|
|
1157
|
+
* create time; a single submit may override it through
|
|
1158
|
+
* {@link HandleSubmitOptions.shouldUseNativeValidation}.
|
|
1159
|
+
*/
|
|
1160
|
+
shouldUseNativeValidation?: boolean;
|
|
1161
|
+
/**
|
|
1162
|
+
* Validate on mount: `true` makes every mounted field with a validator
|
|
1163
|
+
* (declarative `rules` or a `validate` callback) run it once after
|
|
1164
|
+
* mount, instead of waiting for the first submit/change — errors show
|
|
1165
|
+
* immediately for an untouched form (Formik's `validateOnMount` /
|
|
1166
|
+
* TanStack Form's per-field `validateOnMount`). The form-level
|
|
1167
|
+
* `validate` also runs once after mount. Mount kicks are deferred
|
|
1168
|
+
* while an async `initialValues` source is still pending: validating
|
|
1169
|
+
* the empty shell would land spurious required errors, so the kicks
|
|
1170
|
+
* fire after the resolved baseline lands instead. A field's own
|
|
1171
|
+
* `useField({validateOnMount})` option overrides the form-level flag
|
|
1172
|
+
* in either direction. Defaults to `false`.
|
|
1173
|
+
*/
|
|
1174
|
+
validateOnMount?: boolean;
|
|
1175
|
+
};
|
|
1176
|
+
/**
|
|
1177
|
+
* Create form instance
|
|
1178
|
+
* @param options
|
|
1179
|
+
* @return form instance
|
|
1180
|
+
*/
|
|
1181
|
+
declare function create<T extends Record<string, any> = any>(options?: Options<T>): Form<T>;
|
|
1182
|
+
|
|
1183
|
+
export { registerFieldValidateDeps as $, clearErrors as C, clearServerErrors as E, create as G, dottedKeyToFieldPath as H, ensureValidate as J, fieldPathToDottedKey as K, getError as L, getErrorByPath as M, getErrors as Q, getErrorsRecord as U, getErrorsTree as W, getFieldErrors as X, getFieldErrorsByPath as Y, getFirstError as Z, hasErrors as _, registerValidatorByPath as a0, revalidateDependentsOnChange as a1, revalidateFormOnChange as a2, runFormValidate as a3, setError as a4, setErrorByPath as a5, setFocus as a6, setServerErrors as a7, setValidatingByPath as a8, trigger as a9, unregisterFieldValidateDeps as aa, unsetValidatingByPath as ab, validate as ac, hasStandardProps as ae, schemaToFormValidator as af, schemaToFieldValidator as ag, VALIDATION_OUTCOME as b, FORM_ERROR as n };
|
|
1184
|
+
export type { ArrayItemOf as A, ValidatorRegistration as B, DottedPath as D, Form as F, InferSchemaValues as I, Name as N, Options as O, PathSegments as P, RegisterOptions as R, StandardSchemaV1 as S, TriggerOptions as T, Validator as V, FieldErrorEntry as a, StandardSchemaIssue as ad, ValidationOutcome as c, FieldPath as d, Path as e, FieldError as f, PathValueOf as g, ValidationMode as h, RegisterProps as i, FieldRules as j, FieldErrors as k, FieldErrorsTree as l, FormEvents as m, FormValidateFn as o, FormValidateMeta as p, FormValidateMode as q, FormValidationError as r, OpaqueTypes as s, PathValue as t, ReValidateMode as u, SetErrorOptions as v, SetFocusOptions as w, SetServerErrorsOptions as x, SyncValidator as y, ValidateResult as z };
|