@coherent.js/forms 1.0.0-rc.6 → 1.0.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/form-builder.js +77 -24
- package/dist/form-builder.js.map +2 -2
- package/dist/form-hydration.js.map +2 -2
- package/dist/index.js +261 -207
- package/dist/index.js.map +4 -4
- package/dist/validators.js +84 -9
- package/dist/validators.js.map +4 -4
- package/package.json +5 -4
- package/types/index.d.ts +273 -153
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coherent.js/forms",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "SSR + Hydration form system for Coherent.js applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"author": "Coherent.js Team",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@coherent.js/
|
|
27
|
-
"@coherent.js/
|
|
26
|
+
"@coherent.js/state": "1.0.1",
|
|
27
|
+
"@coherent.js/core": "1.0.1"
|
|
28
28
|
},
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"build": "node build.mjs",
|
|
53
53
|
"clean": "rm -rf dist",
|
|
54
54
|
"test": "vitest run",
|
|
55
|
-
"test:watch": "vitest"
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
|
|
56
57
|
}
|
|
57
58
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module @coherent.js/forms
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { CoherentNode
|
|
6
|
+
import type { CoherentNode } from '@coherent.js/core';
|
|
7
7
|
|
|
8
8
|
// ============================================================================
|
|
9
9
|
// Form Field Types
|
|
@@ -113,80 +113,126 @@ export interface FieldValidation<T = unknown> {
|
|
|
113
113
|
* Form configuration options
|
|
114
114
|
*/
|
|
115
115
|
export interface FormConfig {
|
|
116
|
-
/**
|
|
117
|
-
fields?: FormField[]
|
|
116
|
+
/** Fields, as an array of `{ name, ...config }` or keyed by field name */
|
|
117
|
+
fields?: FormField[] | Record<string, Omit<FormField, 'name'>>;
|
|
118
118
|
/** Form action URL */
|
|
119
119
|
action?: string;
|
|
120
120
|
/** Form submission method */
|
|
121
|
-
method?: 'get' | 'post';
|
|
121
|
+
method?: 'get' | 'post' | (string & {});
|
|
122
|
+
/** Form name attribute; defaults to `'form'` */
|
|
123
|
+
name?: string;
|
|
122
124
|
/** Form CSS class name */
|
|
123
125
|
className?: string;
|
|
124
126
|
/** Submit button text */
|
|
125
127
|
submitText?: string;
|
|
126
128
|
/** Form submit handler */
|
|
127
|
-
onSubmit?: (data:
|
|
129
|
+
onSubmit?: (data: Record<string, unknown>) => void | Promise<void>;
|
|
128
130
|
/** Form encoding type */
|
|
129
131
|
enctype?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain';
|
|
130
132
|
/** Whether to disable browser validation */
|
|
131
133
|
novalidate?: boolean;
|
|
132
134
|
/** Form ID */
|
|
133
135
|
id?: string;
|
|
136
|
+
/** Validate a field as it changes; defaults to `true` */
|
|
137
|
+
validateOnChange?: boolean;
|
|
138
|
+
/** Validate a field when it loses focus; defaults to `true` */
|
|
139
|
+
validateOnBlur?: boolean;
|
|
140
|
+
[option: string]: unknown;
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
/**
|
|
137
|
-
*
|
|
144
|
+
* Accumulates fields and renders them as a Coherent component.
|
|
145
|
+
*
|
|
146
|
+
* Every mutator is chainable, and `build()`, `render()` and `buildForm()` all
|
|
147
|
+
* return the same node — `toHTML()` is that node rendered to a string.
|
|
148
|
+
*
|
|
149
|
+
* ```ts
|
|
150
|
+
* const form = new FormBuilder({ name: 'signup' })
|
|
151
|
+
* .field('email', { type: 'email', label: 'Email', required: true })
|
|
152
|
+
* .setAction('/subscribe')
|
|
153
|
+
* .setMethod('post')
|
|
154
|
+
* .build();
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
138
157
|
* @template T - The shape of the form data
|
|
139
158
|
*/
|
|
140
|
-
export
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
export class FormBuilder<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
160
|
+
constructor(options?: FormConfig);
|
|
161
|
+
|
|
162
|
+
options: FormConfig;
|
|
163
|
+
fields: Map<string, FormField>;
|
|
164
|
+
values: Partial<T>;
|
|
165
|
+
errors: Record<string, string>;
|
|
166
|
+
touched: Record<string, boolean>;
|
|
167
|
+
|
|
168
|
+
/** Define a field */
|
|
169
|
+
field<K extends keyof T & string>(name: K, config?: Omit<FormField<T[K]>, 'name'>): this;
|
|
170
|
+
/** Alias of {@link FormBuilder.field} */
|
|
171
|
+
addField<K extends keyof T & string>(name: K, config?: Omit<FormField<T[K]>, 'name'>): this;
|
|
172
|
+
removeField(name: keyof T & string): this;
|
|
173
|
+
/** Merge changes into an existing field */
|
|
174
|
+
updateField(name: keyof T & string, config: Partial<FormField>): this;
|
|
175
|
+
|
|
176
|
+
/** Every field definition, in insertion order */
|
|
177
|
+
getFields(): FormField[];
|
|
178
|
+
getField(name: keyof T & string): FormField | undefined;
|
|
151
179
|
|
|
152
|
-
/**
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
setAction(action: string): FormBuilder<T>;
|
|
180
|
+
/** Group fields for layout */
|
|
181
|
+
addGroup(name: string, config?: Record<string, unknown>): this;
|
|
182
|
+
getGroup(name: string): Record<string, unknown> | undefined;
|
|
156
183
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
184
|
+
setValue<K extends keyof T & string>(name: K, value: T[K]): this;
|
|
185
|
+
setValues(values: Partial<T>): this;
|
|
186
|
+
getValue<K extends keyof T & string>(name: K): T[K] | undefined;
|
|
187
|
+
getValues(): Partial<T>;
|
|
161
188
|
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
*/
|
|
165
|
-
onSubmit(handler: (data: T) => void | Promise<void>): FormBuilder<T>;
|
|
189
|
+
/** Run one field's validators; returns the error or `null` */
|
|
190
|
+
validateField(name: keyof T & string): string | null;
|
|
166
191
|
|
|
167
192
|
/**
|
|
168
|
-
*
|
|
193
|
+
* Validate every visible field and store the result. Returns the errors
|
|
194
|
+
* keyed by field name — empty when the form is valid.
|
|
169
195
|
*/
|
|
170
|
-
|
|
196
|
+
validate(): Record<string, string>;
|
|
171
197
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
198
|
+
getFieldError(name: keyof T & string): string | null;
|
|
199
|
+
hasErrors(): boolean;
|
|
200
|
+
clearErrors(): this;
|
|
201
|
+
isValid(): boolean;
|
|
202
|
+
/** Mark a field as touched */
|
|
203
|
+
touch(name: keyof T & string): void;
|
|
204
|
+
/** Whether any value differs from its initial value */
|
|
205
|
+
isDirty(): boolean;
|
|
176
206
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
validate(data: unknown): { valid: boolean; errors: Record<keyof T, string[]> };
|
|
207
|
+
onSubmit(handler: (data: Partial<T>) => void | Promise<void>): this;
|
|
208
|
+
onError(handler: (error: unknown) => void): this;
|
|
209
|
+
isSubmitting(): boolean;
|
|
181
210
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
211
|
+
setAction(action: string): this;
|
|
212
|
+
setMethod(method: 'get' | 'post' | (string & {})): this;
|
|
213
|
+
|
|
214
|
+
/** Build the form component */
|
|
215
|
+
buildForm(options?: FormConfig): CoherentNode;
|
|
216
|
+
/** Alias of {@link FormBuilder.buildForm} */
|
|
217
|
+
build(options?: FormConfig): CoherentNode;
|
|
218
|
+
/** Alias of {@link FormBuilder.buildForm} */
|
|
219
|
+
render(options?: FormConfig): CoherentNode;
|
|
220
|
+
/** The built form, rendered to an HTML string */
|
|
221
|
+
toHTML(options?: FormConfig): string;
|
|
222
|
+
|
|
223
|
+
/** Build the node for one field, including its label and error */
|
|
224
|
+
buildField(name: keyof T & string): CoherentNode;
|
|
225
|
+
|
|
226
|
+
/** Copy of the current values */
|
|
227
|
+
serialize(): Partial<T>;
|
|
228
|
+
/** Whether a field's `showWhen`/`showIf` condition currently holds */
|
|
229
|
+
isFieldVisible(name: keyof T & string): boolean;
|
|
230
|
+
/** Restore default values and clear errors and touched state */
|
|
231
|
+
reset(): this;
|
|
186
232
|
}
|
|
187
233
|
|
|
188
234
|
/**
|
|
189
|
-
* Create a
|
|
235
|
+
* Create a form builder, optionally seeding it from `config.fields`.
|
|
190
236
|
* @template T - The shape of the form data
|
|
191
237
|
*/
|
|
192
238
|
export function createFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
@@ -194,35 +240,12 @@ export function createFormBuilder<T extends Record<string, unknown> = Record<str
|
|
|
194
240
|
): FormBuilder<T>;
|
|
195
241
|
|
|
196
242
|
/**
|
|
197
|
-
* Build a form from configuration
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Validate a single field value
|
|
203
|
-
* @returns Error message or null if valid
|
|
204
|
-
*/
|
|
205
|
-
export function validateField<T>(field: FormField<T>, value: unknown): string | null;
|
|
206
|
-
|
|
207
|
-
// ============================================================================
|
|
208
|
-
// Form Builder Class
|
|
209
|
-
// ============================================================================
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Form builder class implementation
|
|
243
|
+
* Build a form component from configuration, in one call.
|
|
244
|
+
*
|
|
245
|
+
* `fields` may be an array of `{ name, ...config }` objects or an object
|
|
246
|
+
* keyed by field name; passing a bare array is shorthand for `{ fields }`.
|
|
213
247
|
*/
|
|
214
|
-
export
|
|
215
|
-
constructor(config?: FormConfig);
|
|
216
|
-
addField<K extends keyof T>(name: K, field: Omit<FormField<T[K]>, 'name'>): this;
|
|
217
|
-
removeField(name: keyof T): this;
|
|
218
|
-
setAction(action: string): this;
|
|
219
|
-
setMethod(method: 'get' | 'post'): this;
|
|
220
|
-
onSubmit(handler: (data: T) => void | Promise<void>): this;
|
|
221
|
-
build(): CoherentNode;
|
|
222
|
-
render(): CoherentNode;
|
|
223
|
-
validate(data: unknown): { valid: boolean; errors: Record<keyof T, string[]> };
|
|
224
|
-
getFields(): FormField[];
|
|
225
|
-
}
|
|
248
|
+
export function buildForm(config?: FormConfig | FormField[]): CoherentNode;
|
|
226
249
|
|
|
227
250
|
// ============================================================================
|
|
228
251
|
// Form Hydration Types
|
|
@@ -232,51 +255,75 @@ export class FormBuilder<T extends Record<string, unknown> = Record<string, unkn
|
|
|
232
255
|
* Options for hydrating a form on the client
|
|
233
256
|
*/
|
|
234
257
|
export interface HydrationOptions {
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
|
|
258
|
+
/** Validate a field when it loses focus; defaults to `true` */
|
|
259
|
+
validateOnBlur?: boolean;
|
|
260
|
+
/** Validate a field as it changes; defaults to `false` */
|
|
261
|
+
validateOnChange?: boolean;
|
|
262
|
+
/** Validate everything on submit; defaults to `true` */
|
|
263
|
+
validateOnSubmit?: boolean;
|
|
264
|
+
/** Only show a field's error once it has been touched; defaults to `true` */
|
|
265
|
+
showErrorsOnTouch?: boolean;
|
|
266
|
+
/** Debounce window for change validation, in ms; defaults to `300` */
|
|
267
|
+
debounce?: number;
|
|
268
|
+
/**
|
|
269
|
+
* Called instead of the browser's native submit. Return `false` to cancel,
|
|
270
|
+
* or a promise to defer completion.
|
|
271
|
+
*/
|
|
272
|
+
onSubmit?: (data: Record<string, unknown>, event: Event) => unknown;
|
|
273
|
+
/** Called with the field errors on a failed submit, or a rejected `onSubmit` */
|
|
274
|
+
onError?: (errors: ValidationErrors | unknown) => void;
|
|
275
|
+
/** Called after a promise returned by `onSubmit` resolves */
|
|
276
|
+
onSuccess?: (data: Record<string, unknown>) => void;
|
|
277
|
+
[option: string]: unknown;
|
|
245
278
|
}
|
|
246
279
|
|
|
247
280
|
/**
|
|
248
|
-
*
|
|
281
|
+
* Controller returned by {@link hydrateForm}.
|
|
249
282
|
*/
|
|
250
283
|
export interface HydratedForm {
|
|
251
|
-
/**
|
|
252
|
-
|
|
253
|
-
/** Validate all
|
|
254
|
-
|
|
255
|
-
|
|
284
|
+
/** Validate one field and record the result */
|
|
285
|
+
validateField(name: string): string | null;
|
|
286
|
+
/** Validate every field; `true` when all pass */
|
|
287
|
+
validateForm(): boolean;
|
|
288
|
+
|
|
289
|
+
setFieldValue(name: string, value: unknown): void;
|
|
290
|
+
getFieldValue(name: string): unknown;
|
|
291
|
+
|
|
292
|
+
/** Error currently shown for a field, or `undefined` */
|
|
293
|
+
getError(name: string): string | undefined;
|
|
294
|
+
/** Copy of the current errors */
|
|
295
|
+
getErrors(): ValidationErrors;
|
|
296
|
+
/** Copy of the current values */
|
|
297
|
+
getValues(): Record<string, unknown>;
|
|
298
|
+
|
|
299
|
+
setTouched(name: string, touched?: boolean): void;
|
|
300
|
+
|
|
301
|
+
/** Restore initial values and clear errors */
|
|
256
302
|
reset(): void;
|
|
257
|
-
/**
|
|
258
|
-
getData(): FormData;
|
|
259
|
-
/** Get form data as object */
|
|
260
|
-
getValues<T = Record<string, unknown>>(): T;
|
|
261
|
-
/** Set form field values */
|
|
262
|
-
setData(data: Record<string, unknown>): void;
|
|
263
|
-
/** Set a single field value */
|
|
264
|
-
setValue(name: string, value: unknown): void;
|
|
265
|
-
/** Destroy hydration and clean up event listeners */
|
|
303
|
+
/** Detach every listener and cancel pending debounces */
|
|
266
304
|
destroy(): void;
|
|
267
|
-
|
|
305
|
+
|
|
268
306
|
isValid(): boolean;
|
|
269
|
-
|
|
270
|
-
|
|
307
|
+
isSubmitting(): boolean;
|
|
308
|
+
|
|
309
|
+
/** Snapshot of values, errors, touched flags and submit state */
|
|
310
|
+
getState(): {
|
|
311
|
+
values: Record<string, unknown>;
|
|
312
|
+
errors: ValidationErrors;
|
|
313
|
+
touched: Record<string, boolean>;
|
|
314
|
+
isSubmitting: boolean;
|
|
315
|
+
};
|
|
271
316
|
}
|
|
272
317
|
|
|
273
318
|
/**
|
|
274
|
-
*
|
|
319
|
+
* Attach client-side behavior to a server-rendered form.
|
|
320
|
+
*
|
|
321
|
+
* Returns `null` outside a browser, or when the selector matches nothing.
|
|
275
322
|
*/
|
|
276
323
|
export function hydrateForm(
|
|
277
|
-
|
|
324
|
+
formSelector: HTMLFormElement | string,
|
|
278
325
|
options?: HydrationOptions
|
|
279
|
-
): HydratedForm;
|
|
326
|
+
): HydratedForm | null;
|
|
280
327
|
|
|
281
328
|
// ============================================================================
|
|
282
329
|
// Validation Types
|
|
@@ -286,9 +333,9 @@ export function hydrateForm(
|
|
|
286
333
|
* Validation result
|
|
287
334
|
*/
|
|
288
335
|
export interface ValidationResult {
|
|
289
|
-
/** Whether
|
|
290
|
-
|
|
291
|
-
/**
|
|
336
|
+
/** Whether every field passed */
|
|
337
|
+
isValid: boolean;
|
|
338
|
+
/** The first error per failing field; passing fields are absent */
|
|
292
339
|
errors: ValidationErrors;
|
|
293
340
|
}
|
|
294
341
|
|
|
@@ -296,86 +343,159 @@ export interface ValidationResult {
|
|
|
296
343
|
* Validation errors mapped by field name
|
|
297
344
|
*/
|
|
298
345
|
export interface ValidationErrors {
|
|
299
|
-
[fieldName: string]: string
|
|
346
|
+
[fieldName: string]: string;
|
|
300
347
|
}
|
|
301
348
|
|
|
302
349
|
/**
|
|
303
|
-
*
|
|
350
|
+
* A field check: returns an error message, or `null` when the value passes.
|
|
351
|
+
*
|
|
352
|
+
* The second argument is the whole form, so validators like
|
|
353
|
+
* `validators.matches` can compare fields.
|
|
304
354
|
*/
|
|
305
355
|
export interface Validator {
|
|
306
|
-
(value: unknown
|
|
356
|
+
(value: unknown, formData?: Record<string, unknown>): string | null;
|
|
307
357
|
}
|
|
308
358
|
|
|
309
359
|
/**
|
|
310
|
-
*
|
|
360
|
+
* Per-field validators. A field maps to one validator or a list run in order,
|
|
361
|
+
* stopping at the first error.
|
|
362
|
+
*/
|
|
363
|
+
export type ValidationSchema = Record<string, Validator | Validator[]>;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Runs a {@link ValidationSchema} and tracks errors and touched fields.
|
|
367
|
+
*
|
|
368
|
+
* ```ts
|
|
369
|
+
* const validator = new FormValidator({
|
|
370
|
+
* email: [validators.required(), validators.email()]
|
|
371
|
+
* });
|
|
372
|
+
* const { isValid, errors } = validator.validate({ email: '' });
|
|
373
|
+
* ```
|
|
311
374
|
*/
|
|
312
375
|
export class FormValidator {
|
|
313
|
-
constructor(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
376
|
+
constructor(schema?: ValidationSchema);
|
|
377
|
+
|
|
378
|
+
schema: ValidationSchema;
|
|
379
|
+
/** Errors from the last `validate()` call */
|
|
380
|
+
errors: ValidationErrors;
|
|
381
|
+
touched: Record<string, boolean>;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Check one field. Returns the first error, or `null` when it passes or has
|
|
385
|
+
* no validators.
|
|
386
|
+
*/
|
|
387
|
+
validateField(
|
|
388
|
+
name: string,
|
|
389
|
+
value: unknown,
|
|
390
|
+
formData?: Record<string, unknown>
|
|
391
|
+
): string | null;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Check every field in `formData` plus any schema field it omits, and store
|
|
395
|
+
* the result in `errors`.
|
|
396
|
+
*/
|
|
397
|
+
validate(formData: Record<string, unknown>): ValidationResult;
|
|
398
|
+
|
|
399
|
+
/** Mark a field as touched */
|
|
400
|
+
touch(name: string): void;
|
|
401
|
+
isTouched(name: string): boolean;
|
|
402
|
+
|
|
403
|
+
/** Error recorded for a field by the last `validate()`, or `null` */
|
|
404
|
+
getError(name: string): string | null;
|
|
405
|
+
hasError(name: string): boolean;
|
|
406
|
+
|
|
407
|
+
clearErrors(): void;
|
|
408
|
+
clearTouched(): void;
|
|
409
|
+
/** Clear both errors and touched state */
|
|
410
|
+
reset(): void;
|
|
324
411
|
}
|
|
325
412
|
|
|
326
413
|
/**
|
|
327
414
|
* Create a form validator
|
|
328
415
|
*/
|
|
329
|
-
export function createValidator(
|
|
416
|
+
export function createValidator(schema?: ValidationSchema): FormValidator;
|
|
330
417
|
|
|
331
418
|
/**
|
|
332
|
-
* Validate data against
|
|
419
|
+
* Validate data against a schema with a throwaway validator
|
|
333
420
|
*/
|
|
334
421
|
export function validate(
|
|
335
|
-
|
|
336
|
-
|
|
422
|
+
formData: Record<string, unknown>,
|
|
423
|
+
schema?: ValidationSchema
|
|
337
424
|
): ValidationResult;
|
|
338
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Run a list of validators against one value, returning the first error or
|
|
428
|
+
* `null`.
|
|
429
|
+
*/
|
|
430
|
+
export function validateField(
|
|
431
|
+
value: unknown,
|
|
432
|
+
validatorList: Validator[],
|
|
433
|
+
formData?: Record<string, unknown>
|
|
434
|
+
): string | null;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Run per-field validator lists over a whole form. Returns `null` when
|
|
438
|
+
* everything passes, rather than an empty object.
|
|
439
|
+
*/
|
|
440
|
+
export function validateForm(
|
|
441
|
+
formData: Record<string, unknown>,
|
|
442
|
+
fieldValidators: Record<string, Validator[]>
|
|
443
|
+
): ValidationErrors | null;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Add a validator to {@link validators} under `name`.
|
|
447
|
+
*
|
|
448
|
+
* Unlike the built-ins, which are factories, this stores `validatorFn`
|
|
449
|
+
* directly — so use it as `validators[name]`, not `validators[name]()`.
|
|
450
|
+
* Registering over a built-in therefore changes that name's calling
|
|
451
|
+
* convention.
|
|
452
|
+
*/
|
|
453
|
+
export function registerValidator(name: string, validatorFn: Validator): void;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Combine validators into one that returns the first error, or `null`.
|
|
457
|
+
*/
|
|
458
|
+
export function composeValidators(...validatorFns: Validator[]): Validator;
|
|
459
|
+
|
|
339
460
|
// ============================================================================
|
|
340
461
|
// Built-in Validators
|
|
341
462
|
// ============================================================================
|
|
342
463
|
|
|
343
464
|
/**
|
|
344
|
-
* Built-in validator
|
|
465
|
+
* Built-in validator factories. Each returns a {@link Validator}, so call it
|
|
466
|
+
* before putting it in a schema: `validators.required()`, not
|
|
467
|
+
* `validators.required`.
|
|
468
|
+
*
|
|
469
|
+
* Validators added with {@link registerValidator} also appear here, but are
|
|
470
|
+
* stored as bare validators rather than factories.
|
|
345
471
|
*/
|
|
346
472
|
export const validators: {
|
|
347
|
-
/**
|
|
473
|
+
/** Reject `null`, `undefined` and the empty string */
|
|
348
474
|
required(message?: string): Validator;
|
|
349
|
-
/** Validate email format */
|
|
475
|
+
/** Validate email format; empty values pass */
|
|
350
476
|
email(message?: string): Validator;
|
|
351
|
-
/** Minimum
|
|
352
|
-
minLength(
|
|
353
|
-
/** Maximum
|
|
354
|
-
maxLength(
|
|
477
|
+
/** Minimum length; empty values pass */
|
|
478
|
+
minLength(min: number, message?: string): Validator;
|
|
479
|
+
/** Maximum length; empty values pass */
|
|
480
|
+
maxLength(max: number, message?: string): Validator;
|
|
355
481
|
/** Minimum numeric value */
|
|
356
|
-
min(
|
|
482
|
+
min(min: number, message?: string): Validator;
|
|
357
483
|
/** Maximum numeric value */
|
|
358
|
-
max(
|
|
359
|
-
/**
|
|
360
|
-
pattern(regex: RegExp, message?: string): Validator;
|
|
361
|
-
/** Match another field's value */
|
|
362
|
-
matches(field: string, message?: string): Validator;
|
|
363
|
-
/** Validate URL format */
|
|
484
|
+
max(max: number, message?: string): Validator;
|
|
485
|
+
/** Parseable as a URL; empty values pass */
|
|
364
486
|
url(message?: string): Validator;
|
|
365
|
-
/**
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
/** Async validation function */
|
|
378
|
-
async(fn: (value: unknown) => Promise<boolean | string>): Validator;
|
|
487
|
+
/** Match a regular expression; empty values pass */
|
|
488
|
+
pattern(regex: RegExp, message?: string): Validator;
|
|
489
|
+
/** Equal another field's value */
|
|
490
|
+
matches(fieldName: string, message?: string): Validator;
|
|
491
|
+
/** One of a fixed set; empty values pass */
|
|
492
|
+
oneOf(options: unknown[], message?: string): Validator;
|
|
493
|
+
/** Fail when `fn` returns falsy */
|
|
494
|
+
custom(
|
|
495
|
+
fn: (value: unknown, formData?: Record<string, unknown>) => boolean,
|
|
496
|
+
message?: string
|
|
497
|
+
): Validator;
|
|
498
|
+
[name: string]: Validator | ((...args: never[]) => Validator);
|
|
379
499
|
};
|
|
380
500
|
|
|
381
501
|
// ============================================================================
|