@tanstack/form-core 0.30.0 → 0.33.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/dist/cjs/FieldApi.cjs +14 -3
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +7 -2
- package/dist/cjs/FormApi.cjs +30 -5
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +7 -2
- package/dist/cjs/types.d.cts +11 -2
- package/dist/cjs/utils.cjs +3 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -1
- package/dist/esm/FieldApi.d.ts +7 -2
- package/dist/esm/FieldApi.js +14 -3
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +7 -2
- package/dist/esm/FormApi.js +30 -5
- package/dist/esm/FormApi.js.map +1 -1
- package/dist/esm/types.d.ts +11 -2
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js +3 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/FieldApi.ts +24 -3
- package/src/FormApi.ts +56 -8
- package/src/types.ts +18 -3
- package/src/utils.ts +5 -1
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DeepKeys } from './util-types.js';
|
|
2
2
|
export type ValidationError = undefined | false | null | string;
|
|
3
|
+
export type ValidationSource = 'form' | 'field';
|
|
3
4
|
/**
|
|
4
5
|
* If/when TypeScript supports higher-kinded types, this should not be `unknown` anymore
|
|
5
6
|
* @private
|
|
@@ -7,10 +8,12 @@ export type ValidationError = undefined | false | null | string;
|
|
|
7
8
|
export type Validator<Type, Fn = unknown> = () => {
|
|
8
9
|
validate(options: {
|
|
9
10
|
value: Type;
|
|
10
|
-
|
|
11
|
+
validationSource: ValidationSource;
|
|
12
|
+
}, fn: Fn): ValidationError | FormValidationError<unknown>;
|
|
11
13
|
validateAsync(options: {
|
|
12
14
|
value: Type;
|
|
13
|
-
|
|
15
|
+
validationSource: ValidationSource;
|
|
16
|
+
}, fn: Fn): Promise<ValidationError | FormValidationError<unknown>>;
|
|
14
17
|
};
|
|
15
18
|
/**
|
|
16
19
|
* Parameters in common for all validator adapters, making it easier to swap adapter
|
|
@@ -34,6 +37,12 @@ export type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`;
|
|
|
34
37
|
export type ValidationErrorMap = {
|
|
35
38
|
[K in ValidationErrorMapKeys]?: ValidationError;
|
|
36
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
export type FormValidationErrorMap = {
|
|
44
|
+
[K in ValidationErrorMapKeys]?: ValidationError | FormValidationError<unknown>;
|
|
45
|
+
};
|
|
37
46
|
/**
|
|
38
47
|
* @private
|
|
39
48
|
*
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export declare function deleteBy(obj: any, _path: any): any;
|
|
|
25
25
|
/**
|
|
26
26
|
* @private
|
|
27
27
|
*/
|
|
28
|
-
export declare function makePathArray(str: string): (string | number)[];
|
|
28
|
+
export declare function makePathArray(str: string | Array<string | number>): (string | number)[];
|
|
29
29
|
/**
|
|
30
30
|
* @private
|
|
31
31
|
*/
|
package/dist/esm/utils.js
CHANGED
|
@@ -90,6 +90,9 @@ const reFindMultiplePeriods = /\.{2,}/gm;
|
|
|
90
90
|
const intPrefix = "__int__";
|
|
91
91
|
const intReplace = `${intPrefix}$1`;
|
|
92
92
|
function makePathArray(str) {
|
|
93
|
+
if (Array.isArray(str)) {
|
|
94
|
+
return [...str];
|
|
95
|
+
}
|
|
93
96
|
if (typeof str !== "string") {
|
|
94
97
|
throw new Error("Path must be a string.");
|
|
95
98
|
}
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type { ValidationCause } from './types'\nimport type { FormValidators } from './FormApi'\nimport type { FieldValidators } from './FieldApi'\n\nexport type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput\n\nexport type Updater<TInput, TOutput = TInput> =\n | TOutput\n | UpdaterFn<TInput, TOutput>\n\n/**\n * @private\n */\nexport function functionalUpdate<TInput, TOutput = TInput>(\n updater: Updater<TInput, TOutput>,\n input: TInput,\n): TOutput {\n return typeof updater === 'function'\n ? (updater as UpdaterFn<TInput, TOutput>)(input)\n : updater\n}\n\n/**\n * Get a value from an object using a path, including dot notation.\n * @private\n */\nexport function getBy(obj: any, path: any) {\n const pathObj = makePathArray(path)\n return pathObj.reduce((current: any, pathPart: any) => {\n if (current === null) return null\n if (typeof current !== 'undefined') {\n return current[pathPart]\n }\n return undefined\n }, obj)\n}\n\n/**\n * Set a value on an object using a path, including dot notation.\n * @private\n */\nexport function setBy(obj: any, _path: any, updater: Updater<any>) {\n const path = makePathArray(_path)\n\n function doSet(parent?: any): any {\n if (!path.length) {\n return functionalUpdate(updater, parent)\n }\n\n const key = path.shift()\n\n if (typeof key === 'string') {\n if (typeof parent === 'object') {\n if (parent === null) {\n parent = {}\n }\n return {\n ...parent,\n [key]: doSet(parent[key]),\n }\n }\n return {\n [key]: doSet(),\n }\n }\n\n if (Array.isArray(parent) && key !== undefined) {\n const prefix = parent.slice(0, key)\n return [\n ...(prefix.length ? prefix : new Array(key)),\n doSet(parent[key]),\n ...parent.slice(key + 1),\n ]\n }\n return [...new Array(key), doSet()]\n }\n\n return doSet(obj)\n}\n\n/**\n * Delete a field on an object using a path, including dot notation.\n * @private\n */\nexport function deleteBy(obj: any, _path: any) {\n const path = makePathArray(_path)\n\n function doDelete(parent: any): any {\n if (!parent) return\n if (path.length === 1) {\n const finalPath = path[0]!\n if (Array.isArray(parent) && typeof finalPath === 'number') {\n return parent.filter((_, i) => i !== finalPath)\n }\n const { [finalPath]: remove, ...rest } = parent\n return rest\n }\n\n const key = path.shift()\n\n if (typeof key === 'string') {\n if (typeof parent === 'object') {\n return {\n ...parent,\n [key]: doDelete(parent[key]),\n }\n }\n }\n\n if (typeof key === 'number') {\n if (Array.isArray(parent)) {\n if (key >= parent.length) {\n return parent\n }\n const prefix = parent.slice(0, key)\n return [\n ...(prefix.length ? prefix : new Array(key)),\n doDelete(parent[key]),\n ...parent.slice(key + 1),\n ]\n }\n }\n\n throw new Error('It seems we have created an infinite loop in deleteBy. ')\n }\n\n return doDelete(obj)\n}\n\nconst reFindNumbers0 = /^(\\d*)$/gm\nconst reFindNumbers1 = /\\.(\\d*)\\./gm\nconst reFindNumbers2 = /^(\\d*)\\./gm\nconst reFindNumbers3 = /\\.(\\d*$)/gm\nconst reFindMultiplePeriods = /\\.{2,}/gm\n\nconst intPrefix = '__int__'\nconst intReplace = `${intPrefix}$1`\n\n/**\n * @private\n */\nexport function makePathArray(str: string) {\n if (typeof str !== 'string') {\n throw new Error('Path must be a string.')\n }\n\n return str\n .replaceAll('[', '.')\n .replaceAll(']', '')\n .replace(reFindNumbers0, intReplace)\n .replace(reFindNumbers1, `.${intReplace}.`)\n .replace(reFindNumbers2, `${intReplace}.`)\n .replace(reFindNumbers3, `.${intReplace}`)\n .replace(reFindMultiplePeriods, '.')\n .split('.')\n .map((d) => {\n if (d.indexOf(intPrefix) === 0) {\n return parseInt(d.substring(intPrefix.length), 10)\n }\n return d\n })\n}\n\n/**\n * @private\n */\nexport function isNonEmptyArray(obj: any) {\n return !(Array.isArray(obj) && obj.length === 0)\n}\n\ninterface AsyncValidatorArrayPartialOptions<T> {\n validators?: T\n asyncDebounceMs?: number\n}\n\n/**\n * @private\n */\nexport interface AsyncValidator<T> {\n cause: ValidationCause\n validate: T\n debounceMs: number\n}\n\n/**\n * @private\n */\nexport function getAsyncValidatorArray<T>(\n cause: ValidationCause,\n options: AsyncValidatorArrayPartialOptions<T>,\n): T extends FieldValidators<any, any>\n ? Array<\n AsyncValidator<T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']>\n >\n : T extends FormValidators<any, any>\n ? Array<\n AsyncValidator<\n T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']\n >\n >\n : never {\n const { asyncDebounceMs } = options\n const {\n onChangeAsync,\n onBlurAsync,\n onSubmitAsync,\n onBlurAsyncDebounceMs,\n onChangeAsyncDebounceMs,\n } = (options.validators || {}) as\n | FieldValidators<any, any>\n | FormValidators<any, any>\n\n const defaultDebounceMs = asyncDebounceMs ?? 0\n\n const changeValidator = {\n cause: 'change',\n validate: onChangeAsync,\n debounceMs: onChangeAsyncDebounceMs ?? defaultDebounceMs,\n } as const\n\n const blurValidator = {\n cause: 'blur',\n validate: onBlurAsync,\n debounceMs: onBlurAsyncDebounceMs ?? defaultDebounceMs,\n } as const\n\n const submitValidator = {\n cause: 'submit',\n validate: onSubmitAsync,\n debounceMs: 0,\n } as const\n\n const noopValidator = (\n validator:\n | typeof changeValidator\n | typeof blurValidator\n | typeof submitValidator,\n ) => ({ ...validator, debounceMs: 0 }) as const\n\n switch (cause) {\n case 'submit':\n return [\n noopValidator(changeValidator),\n noopValidator(blurValidator),\n submitValidator,\n ] as never\n case 'blur':\n return [blurValidator] as never\n case 'change':\n return [changeValidator] as never\n case 'server':\n default:\n return [] as never\n }\n}\n\ninterface SyncValidatorArrayPartialOptions<T> {\n validators?: T\n}\n\n/**\n * @private\n */\nexport interface SyncValidator<T> {\n cause: ValidationCause\n validate: T\n}\n\n/**\n * @private\n */\nexport function getSyncValidatorArray<T>(\n cause: ValidationCause,\n options: SyncValidatorArrayPartialOptions<T>,\n): T extends FieldValidators<any, any>\n ? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>>\n : T extends FormValidators<any, any>\n ? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>>\n : never {\n const { onChange, onBlur, onSubmit } = (options.validators || {}) as\n | FieldValidators<any, any>\n | FormValidators<any, any>\n\n const changeValidator = { cause: 'change', validate: onChange } as const\n const blurValidator = { cause: 'blur', validate: onBlur } as const\n const submitValidator = { cause: 'submit', validate: onSubmit } as const\n\n // Allows us to clear onServer errors\n const serverValidator = {\n cause: 'server',\n validate: () => undefined,\n } as const\n\n switch (cause) {\n case 'submit':\n return [\n changeValidator,\n blurValidator,\n submitValidator,\n serverValidator,\n ] as never\n case 'server':\n return [serverValidator] as never\n case 'blur':\n return [blurValidator, serverValidator] as never\n case 'change':\n default:\n return [changeValidator, serverValidator] as never\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type { ValidationCause } from './types'\nimport type { FormValidators } from './FormApi'\nimport type { FieldValidators } from './FieldApi'\n\nexport type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput\n\nexport type Updater<TInput, TOutput = TInput> =\n | TOutput\n | UpdaterFn<TInput, TOutput>\n\n/**\n * @private\n */\nexport function functionalUpdate<TInput, TOutput = TInput>(\n updater: Updater<TInput, TOutput>,\n input: TInput,\n): TOutput {\n return typeof updater === 'function'\n ? (updater as UpdaterFn<TInput, TOutput>)(input)\n : updater\n}\n\n/**\n * Get a value from an object using a path, including dot notation.\n * @private\n */\nexport function getBy(obj: any, path: any) {\n const pathObj = makePathArray(path)\n return pathObj.reduce((current: any, pathPart: any) => {\n if (current === null) return null\n if (typeof current !== 'undefined') {\n return current[pathPart]\n }\n return undefined\n }, obj)\n}\n\n/**\n * Set a value on an object using a path, including dot notation.\n * @private\n */\nexport function setBy(obj: any, _path: any, updater: Updater<any>) {\n const path = makePathArray(_path)\n\n function doSet(parent?: any): any {\n if (!path.length) {\n return functionalUpdate(updater, parent)\n }\n\n const key = path.shift()\n\n if (typeof key === 'string') {\n if (typeof parent === 'object') {\n if (parent === null) {\n parent = {}\n }\n return {\n ...parent,\n [key]: doSet(parent[key]),\n }\n }\n return {\n [key]: doSet(),\n }\n }\n\n if (Array.isArray(parent) && key !== undefined) {\n const prefix = parent.slice(0, key)\n return [\n ...(prefix.length ? prefix : new Array(key)),\n doSet(parent[key]),\n ...parent.slice(key + 1),\n ]\n }\n return [...new Array(key), doSet()]\n }\n\n return doSet(obj)\n}\n\n/**\n * Delete a field on an object using a path, including dot notation.\n * @private\n */\nexport function deleteBy(obj: any, _path: any) {\n const path = makePathArray(_path)\n\n function doDelete(parent: any): any {\n if (!parent) return\n if (path.length === 1) {\n const finalPath = path[0]!\n if (Array.isArray(parent) && typeof finalPath === 'number') {\n return parent.filter((_, i) => i !== finalPath)\n }\n const { [finalPath]: remove, ...rest } = parent\n return rest\n }\n\n const key = path.shift()\n\n if (typeof key === 'string') {\n if (typeof parent === 'object') {\n return {\n ...parent,\n [key]: doDelete(parent[key]),\n }\n }\n }\n\n if (typeof key === 'number') {\n if (Array.isArray(parent)) {\n if (key >= parent.length) {\n return parent\n }\n const prefix = parent.slice(0, key)\n return [\n ...(prefix.length ? prefix : new Array(key)),\n doDelete(parent[key]),\n ...parent.slice(key + 1),\n ]\n }\n }\n\n throw new Error('It seems we have created an infinite loop in deleteBy. ')\n }\n\n return doDelete(obj)\n}\n\nconst reFindNumbers0 = /^(\\d*)$/gm\nconst reFindNumbers1 = /\\.(\\d*)\\./gm\nconst reFindNumbers2 = /^(\\d*)\\./gm\nconst reFindNumbers3 = /\\.(\\d*$)/gm\nconst reFindMultiplePeriods = /\\.{2,}/gm\n\nconst intPrefix = '__int__'\nconst intReplace = `${intPrefix}$1`\n\n/**\n * @private\n */\nexport function makePathArray(str: string | Array<string | number>) {\n if (Array.isArray(str)) {\n return [...str]\n }\n\n if (typeof str !== 'string') {\n throw new Error('Path must be a string.')\n }\n\n return str\n .replaceAll('[', '.')\n .replaceAll(']', '')\n .replace(reFindNumbers0, intReplace)\n .replace(reFindNumbers1, `.${intReplace}.`)\n .replace(reFindNumbers2, `${intReplace}.`)\n .replace(reFindNumbers3, `.${intReplace}`)\n .replace(reFindMultiplePeriods, '.')\n .split('.')\n .map((d) => {\n if (d.indexOf(intPrefix) === 0) {\n return parseInt(d.substring(intPrefix.length), 10)\n }\n return d\n })\n}\n\n/**\n * @private\n */\nexport function isNonEmptyArray(obj: any) {\n return !(Array.isArray(obj) && obj.length === 0)\n}\n\ninterface AsyncValidatorArrayPartialOptions<T> {\n validators?: T\n asyncDebounceMs?: number\n}\n\n/**\n * @private\n */\nexport interface AsyncValidator<T> {\n cause: ValidationCause\n validate: T\n debounceMs: number\n}\n\n/**\n * @private\n */\nexport function getAsyncValidatorArray<T>(\n cause: ValidationCause,\n options: AsyncValidatorArrayPartialOptions<T>,\n): T extends FieldValidators<any, any>\n ? Array<\n AsyncValidator<T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']>\n >\n : T extends FormValidators<any, any>\n ? Array<\n AsyncValidator<\n T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']\n >\n >\n : never {\n const { asyncDebounceMs } = options\n const {\n onChangeAsync,\n onBlurAsync,\n onSubmitAsync,\n onBlurAsyncDebounceMs,\n onChangeAsyncDebounceMs,\n } = (options.validators || {}) as\n | FieldValidators<any, any>\n | FormValidators<any, any>\n\n const defaultDebounceMs = asyncDebounceMs ?? 0\n\n const changeValidator = {\n cause: 'change',\n validate: onChangeAsync,\n debounceMs: onChangeAsyncDebounceMs ?? defaultDebounceMs,\n } as const\n\n const blurValidator = {\n cause: 'blur',\n validate: onBlurAsync,\n debounceMs: onBlurAsyncDebounceMs ?? defaultDebounceMs,\n } as const\n\n const submitValidator = {\n cause: 'submit',\n validate: onSubmitAsync,\n debounceMs: 0,\n } as const\n\n const noopValidator = (\n validator:\n | typeof changeValidator\n | typeof blurValidator\n | typeof submitValidator,\n ) => ({ ...validator, debounceMs: 0 }) as const\n\n switch (cause) {\n case 'submit':\n return [\n noopValidator(changeValidator),\n noopValidator(blurValidator),\n submitValidator,\n ] as never\n case 'blur':\n return [blurValidator] as never\n case 'change':\n return [changeValidator] as never\n case 'server':\n default:\n return [] as never\n }\n}\n\ninterface SyncValidatorArrayPartialOptions<T> {\n validators?: T\n}\n\n/**\n * @private\n */\nexport interface SyncValidator<T> {\n cause: ValidationCause\n validate: T\n}\n\n/**\n * @private\n */\nexport function getSyncValidatorArray<T>(\n cause: ValidationCause,\n options: SyncValidatorArrayPartialOptions<T>,\n): T extends FieldValidators<any, any>\n ? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>>\n : T extends FormValidators<any, any>\n ? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>>\n : never {\n const { onChange, onBlur, onSubmit } = (options.validators || {}) as\n | FieldValidators<any, any>\n | FormValidators<any, any>\n\n const changeValidator = { cause: 'change', validate: onChange } as const\n const blurValidator = { cause: 'blur', validate: onBlur } as const\n const submitValidator = { cause: 'submit', validate: onSubmit } as const\n\n // Allows us to clear onServer errors\n const serverValidator = {\n cause: 'server',\n validate: () => undefined,\n } as const\n\n switch (cause) {\n case 'submit':\n return [\n changeValidator,\n blurValidator,\n submitValidator,\n serverValidator,\n ] as never\n case 'server':\n return [serverValidator] as never\n case 'blur':\n return [blurValidator, serverValidator] as never\n case 'change':\n default:\n return [changeValidator, serverValidator] as never\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/FieldApi.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ValidationCause,
|
|
7
7
|
ValidationError,
|
|
8
8
|
ValidationErrorMap,
|
|
9
|
+
ValidationSource,
|
|
9
10
|
Validator,
|
|
10
11
|
} from './types'
|
|
11
12
|
import type { AsyncValidator, SyncValidator, Updater } from './utils'
|
|
@@ -335,6 +336,10 @@ export type FieldMeta = {
|
|
|
335
336
|
* A flag indicating whether the field has been touched.
|
|
336
337
|
*/
|
|
337
338
|
isTouched: boolean
|
|
339
|
+
/**
|
|
340
|
+
* A flag indicating whether the field has been blurred.
|
|
341
|
+
*/
|
|
342
|
+
isBlurred: boolean
|
|
338
343
|
/**
|
|
339
344
|
* A flag that is `true` if the field's value has not been modified by the user. Opposite of `isDirty`.
|
|
340
345
|
*/
|
|
@@ -456,6 +461,7 @@ export class FieldApi<
|
|
|
456
461
|
meta: this._getMeta() ?? {
|
|
457
462
|
isValidating: false,
|
|
458
463
|
isTouched: false,
|
|
464
|
+
isBlurred: false,
|
|
459
465
|
isDirty: false,
|
|
460
466
|
isPristine: true,
|
|
461
467
|
errors: [],
|
|
@@ -488,7 +494,11 @@ export class FieldApi<
|
|
|
488
494
|
* @private
|
|
489
495
|
*/
|
|
490
496
|
runValidator<
|
|
491
|
-
TValue extends {
|
|
497
|
+
TValue extends {
|
|
498
|
+
value: TData
|
|
499
|
+
fieldApi: FieldApi<any, any, any, any>
|
|
500
|
+
validationSource: ValidationSource
|
|
501
|
+
},
|
|
492
502
|
TType extends 'validate' | 'validateAsync',
|
|
493
503
|
>(props: {
|
|
494
504
|
validate: TType extends 'validate'
|
|
@@ -496,7 +506,8 @@ export class FieldApi<
|
|
|
496
506
|
: FieldAsyncValidateOrFn<any, any, any, any>
|
|
497
507
|
value: TValue
|
|
498
508
|
type: TType
|
|
499
|
-
|
|
509
|
+
// When `api` is 'field', the return type cannot be `FormValidationError`
|
|
510
|
+
}): TType extends 'validate' ? ValidationError : Promise<ValidationError> {
|
|
500
511
|
const adapters = [
|
|
501
512
|
this.form.options.validatorAdapter,
|
|
502
513
|
this.options.validatorAdapter,
|
|
@@ -543,6 +554,7 @@ export class FieldApi<
|
|
|
543
554
|
value: {
|
|
544
555
|
value: this.state.value,
|
|
545
556
|
fieldApi: this,
|
|
557
|
+
validationSource: 'field',
|
|
546
558
|
},
|
|
547
559
|
type: 'validate',
|
|
548
560
|
})
|
|
@@ -625,6 +637,7 @@ export class FieldApi<
|
|
|
625
637
|
({
|
|
626
638
|
isValidating: false,
|
|
627
639
|
isTouched: false,
|
|
640
|
+
isBlurred: false,
|
|
628
641
|
isDirty: false,
|
|
629
642
|
isPristine: true,
|
|
630
643
|
errors: [],
|
|
@@ -757,7 +770,11 @@ export class FieldApi<
|
|
|
757
770
|
? normalizeError(
|
|
758
771
|
field.runValidator({
|
|
759
772
|
validate: validateObj.validate,
|
|
760
|
-
value: {
|
|
773
|
+
value: {
|
|
774
|
+
value: field.getValue(),
|
|
775
|
+
validationSource: 'field',
|
|
776
|
+
fieldApi: field,
|
|
777
|
+
},
|
|
761
778
|
type: 'validate',
|
|
762
779
|
}),
|
|
763
780
|
)
|
|
@@ -884,6 +901,7 @@ export class FieldApi<
|
|
|
884
901
|
value: field.getValue(),
|
|
885
902
|
fieldApi: field,
|
|
886
903
|
signal: controller.signal,
|
|
904
|
+
validationSource: 'field',
|
|
887
905
|
},
|
|
888
906
|
type: 'validateAsync',
|
|
889
907
|
}),
|
|
@@ -1001,6 +1019,9 @@ export class FieldApi<
|
|
|
1001
1019
|
this.setMeta((prev) => ({ ...prev, isTouched: true }))
|
|
1002
1020
|
this.validate('change')
|
|
1003
1021
|
}
|
|
1022
|
+
if (!this.state.meta.isBlurred) {
|
|
1023
|
+
this.setMeta((prev) => ({ ...prev, isBlurred: true }))
|
|
1024
|
+
}
|
|
1004
1025
|
this.validate('blur')
|
|
1005
1026
|
}
|
|
1006
1027
|
|
package/src/FormApi.ts
CHANGED
|
@@ -13,11 +13,13 @@ import type { DeepKeys, DeepValue } from './util-types'
|
|
|
13
13
|
import type { FieldApi, FieldMeta } from './FieldApi'
|
|
14
14
|
import type {
|
|
15
15
|
FormValidationError,
|
|
16
|
+
FormValidationErrorMap,
|
|
16
17
|
UpdateMetaOptions,
|
|
17
18
|
ValidationCause,
|
|
18
19
|
ValidationError,
|
|
19
20
|
ValidationErrorMap,
|
|
20
21
|
ValidationErrorMapKeys,
|
|
22
|
+
ValidationSource,
|
|
21
23
|
Validator,
|
|
22
24
|
} from './types'
|
|
23
25
|
|
|
@@ -234,7 +236,7 @@ export type FormState<TFormData> = {
|
|
|
234
236
|
/**
|
|
235
237
|
* The error map for the form itself.
|
|
236
238
|
*/
|
|
237
|
-
errorMap:
|
|
239
|
+
errorMap: FormValidationErrorMap
|
|
238
240
|
/**
|
|
239
241
|
* An internal mechanism used for keeping track of validation logic in a form.
|
|
240
242
|
*/
|
|
@@ -259,6 +261,10 @@ export type FormState<TFormData> = {
|
|
|
259
261
|
* A boolean indicating if any of the form fields have been touched.
|
|
260
262
|
*/
|
|
261
263
|
isTouched: boolean
|
|
264
|
+
/**
|
|
265
|
+
* A boolean indicating if any of the form fields have been blurred.
|
|
266
|
+
*/
|
|
267
|
+
isBlurred: boolean
|
|
262
268
|
/**
|
|
263
269
|
* A boolean indicating if any of the form's fields' values have been modified by the user. `True` if the user have modified at least one of the fields. Opposite of `isPristine`.
|
|
264
270
|
*/
|
|
@@ -305,6 +311,7 @@ function getDefaultFormState<TFormData>(
|
|
|
305
311
|
isSubmitted: defaultState.isSubmitted ?? false,
|
|
306
312
|
isSubmitting: defaultState.isSubmitting ?? false,
|
|
307
313
|
isTouched: defaultState.isTouched ?? false,
|
|
314
|
+
isBlurred: defaultState.isBlurred ?? false,
|
|
308
315
|
isPristine: defaultState.isPristine ?? true,
|
|
309
316
|
isDirty: defaultState.isDirty ?? false,
|
|
310
317
|
isValid: defaultState.isValid ?? false,
|
|
@@ -320,6 +327,12 @@ function getDefaultFormState<TFormData>(
|
|
|
320
327
|
}
|
|
321
328
|
}
|
|
322
329
|
|
|
330
|
+
const isFormValidationError = (
|
|
331
|
+
error: unknown,
|
|
332
|
+
): error is FormValidationError<unknown> => {
|
|
333
|
+
return typeof error === 'object'
|
|
334
|
+
}
|
|
335
|
+
|
|
323
336
|
/**
|
|
324
337
|
* A class representing the Form API. It handles the logic and interactions with the form state.
|
|
325
338
|
*
|
|
@@ -388,14 +401,24 @@ export class FormApi<
|
|
|
388
401
|
)
|
|
389
402
|
|
|
390
403
|
const isTouched = fieldMetaValues.some((field) => field?.isTouched)
|
|
404
|
+
const isBlurred = fieldMetaValues.some((field) => field?.isBlurred)
|
|
391
405
|
|
|
392
406
|
const isDirty = fieldMetaValues.some((field) => field?.isDirty)
|
|
393
407
|
const isPristine = !isDirty
|
|
394
408
|
|
|
395
409
|
const isValidating = isFieldsValidating || state.isFormValidating
|
|
396
|
-
state.errors = Object.values(state.errorMap).
|
|
397
|
-
|
|
398
|
-
|
|
410
|
+
state.errors = Object.values(state.errorMap).reduce((prev, curr) => {
|
|
411
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
412
|
+
if (curr === undefined) return prev
|
|
413
|
+
if (typeof curr === 'string') {
|
|
414
|
+
prev.push(curr)
|
|
415
|
+
return prev
|
|
416
|
+
} else if (curr && isFormValidationError(curr)) {
|
|
417
|
+
prev.push(curr.form)
|
|
418
|
+
return prev
|
|
419
|
+
}
|
|
420
|
+
return prev
|
|
421
|
+
}, [] as ValidationError[])
|
|
399
422
|
const isFormValid = state.errors.length === 0
|
|
400
423
|
const isValid = isFieldsValid && isFormValid
|
|
401
424
|
const canSubmit =
|
|
@@ -410,6 +433,7 @@ export class FormApi<
|
|
|
410
433
|
isValid,
|
|
411
434
|
canSubmit,
|
|
412
435
|
isTouched,
|
|
436
|
+
isBlurred,
|
|
413
437
|
isPristine,
|
|
414
438
|
isDirty,
|
|
415
439
|
}
|
|
@@ -442,7 +466,11 @@ export class FormApi<
|
|
|
442
466
|
* @private
|
|
443
467
|
*/
|
|
444
468
|
runValidator<
|
|
445
|
-
TValue extends {
|
|
469
|
+
TValue extends {
|
|
470
|
+
value: TFormData
|
|
471
|
+
formApi: FormApi<any, any>
|
|
472
|
+
validationSource: ValidationSource
|
|
473
|
+
},
|
|
446
474
|
TType extends 'validate' | 'validateAsync',
|
|
447
475
|
>(props: {
|
|
448
476
|
validate: TType extends 'validate'
|
|
@@ -467,6 +495,7 @@ export class FormApi<
|
|
|
467
495
|
value: {
|
|
468
496
|
value: this.state.values,
|
|
469
497
|
formApi: this,
|
|
498
|
+
validationSource: 'form',
|
|
470
499
|
},
|
|
471
500
|
type: 'validate',
|
|
472
501
|
})
|
|
@@ -553,6 +582,12 @@ export class FormApi<
|
|
|
553
582
|
// Mark them as touched
|
|
554
583
|
field.instance.setMeta((prev) => ({ ...prev, isTouched: true }))
|
|
555
584
|
}
|
|
585
|
+
|
|
586
|
+
// If any fields are not blurred
|
|
587
|
+
if (!field.instance.state.meta.isBlurred) {
|
|
588
|
+
// Mark them as blurred
|
|
589
|
+
field.instance.setMeta((prev) => ({ ...prev, isBlurred: true }))
|
|
590
|
+
}
|
|
556
591
|
})
|
|
557
592
|
})
|
|
558
593
|
|
|
@@ -616,6 +651,12 @@ export class FormApi<
|
|
|
616
651
|
fieldInstance.setMeta((prev) => ({ ...prev, isTouched: true }))
|
|
617
652
|
}
|
|
618
653
|
|
|
654
|
+
// If the field is not blurred (same logic as in validateAllFields)
|
|
655
|
+
if (!fieldInstance.state.meta.isBlurred) {
|
|
656
|
+
// Mark it as blurred
|
|
657
|
+
fieldInstance.setMeta((prev) => ({ ...prev, isBlurred: true }))
|
|
658
|
+
}
|
|
659
|
+
|
|
619
660
|
return fieldInstance.validate(cause)
|
|
620
661
|
}
|
|
621
662
|
|
|
@@ -643,6 +684,7 @@ export class FormApi<
|
|
|
643
684
|
value: {
|
|
644
685
|
value: this.state.values,
|
|
645
686
|
formApi: this,
|
|
687
|
+
validationSource: 'form',
|
|
646
688
|
},
|
|
647
689
|
type: 'validate',
|
|
648
690
|
})
|
|
@@ -748,7 +790,10 @@ export class FormApi<
|
|
|
748
790
|
|
|
749
791
|
promises.push(
|
|
750
792
|
new Promise<ValidationPromiseResult<TFormData>>(async (resolve) => {
|
|
751
|
-
let rawError!:
|
|
793
|
+
let rawError!:
|
|
794
|
+
| ValidationError
|
|
795
|
+
| FormValidationError<unknown>
|
|
796
|
+
| undefined
|
|
752
797
|
try {
|
|
753
798
|
rawError = await new Promise((rawResolve, rawReject) => {
|
|
754
799
|
setTimeout(async () => {
|
|
@@ -760,6 +805,7 @@ export class FormApi<
|
|
|
760
805
|
value: {
|
|
761
806
|
value: this.state.values,
|
|
762
807
|
formApi: this,
|
|
808
|
+
validationSource: 'form',
|
|
763
809
|
signal: controller.signal,
|
|
764
810
|
},
|
|
765
811
|
type: 'validateAsync',
|
|
@@ -983,6 +1029,7 @@ export class FormApi<
|
|
|
983
1029
|
acc[fieldKey] = {
|
|
984
1030
|
isValidating: false,
|
|
985
1031
|
isTouched: false,
|
|
1032
|
+
isBlurred: false,
|
|
986
1033
|
isDirty: false,
|
|
987
1034
|
isPristine: true,
|
|
988
1035
|
errors: [],
|
|
@@ -1009,6 +1056,7 @@ export class FormApi<
|
|
|
1009
1056
|
this.setFieldMeta(field, (prev) => ({
|
|
1010
1057
|
...prev,
|
|
1011
1058
|
isTouched: true,
|
|
1059
|
+
isBlurred: true,
|
|
1012
1060
|
isDirty: true,
|
|
1013
1061
|
}))
|
|
1014
1062
|
}
|
|
@@ -1207,7 +1255,7 @@ export class FormApi<
|
|
|
1207
1255
|
}
|
|
1208
1256
|
}
|
|
1209
1257
|
|
|
1210
|
-
function normalizeError<TFormData>(rawError?: FormValidationError<
|
|
1258
|
+
function normalizeError<TFormData>(rawError?: FormValidationError<unknown>): {
|
|
1211
1259
|
formError: ValidationError
|
|
1212
1260
|
fieldErrors?: Partial<Record<DeepKeys<TFormData>, ValidationError>>
|
|
1213
1261
|
} {
|
|
@@ -1215,7 +1263,7 @@ function normalizeError<TFormData>(rawError?: FormValidationError<TFormData>): {
|
|
|
1215
1263
|
if (typeof rawError === 'object') {
|
|
1216
1264
|
const formError = normalizeError(rawError.form).formError
|
|
1217
1265
|
const fieldErrors = rawError.fields
|
|
1218
|
-
return { formError, fieldErrors }
|
|
1266
|
+
return { formError, fieldErrors } as never
|
|
1219
1267
|
}
|
|
1220
1268
|
|
|
1221
1269
|
if (typeof rawError !== 'string') {
|
package/src/types.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DeepKeys } from './util-types'
|
|
2
2
|
|
|
3
3
|
export type ValidationError = undefined | false | null | string
|
|
4
4
|
|
|
5
|
+
export type ValidationSource = 'form' | 'field'
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* If/when TypeScript supports higher-kinded types, this should not be `unknown` anymore
|
|
7
9
|
* @private
|
|
8
10
|
*/
|
|
9
11
|
export type Validator<Type, Fn = unknown> = () => {
|
|
10
|
-
validate(
|
|
11
|
-
|
|
12
|
+
validate(
|
|
13
|
+
options: { value: Type; validationSource: ValidationSource },
|
|
14
|
+
fn: Fn,
|
|
15
|
+
): ValidationError | FormValidationError<unknown>
|
|
16
|
+
validateAsync(
|
|
17
|
+
options: { value: Type; validationSource: ValidationSource },
|
|
18
|
+
fn: Fn,
|
|
19
|
+
): Promise<ValidationError | FormValidationError<unknown>>
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
/**
|
|
@@ -37,6 +45,13 @@ export type ValidationErrorMap = {
|
|
|
37
45
|
[K in ValidationErrorMapKeys]?: ValidationError
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
export type FormValidationErrorMap = {
|
|
52
|
+
[K in ValidationErrorMapKeys]?: ValidationError | FormValidationError<unknown>
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
/**
|
|
41
56
|
* @private
|
|
42
57
|
*
|
package/src/utils.ts
CHANGED
|
@@ -139,7 +139,11 @@ const intReplace = `${intPrefix}$1`
|
|
|
139
139
|
/**
|
|
140
140
|
* @private
|
|
141
141
|
*/
|
|
142
|
-
export function makePathArray(str: string) {
|
|
142
|
+
export function makePathArray(str: string | Array<string | number>) {
|
|
143
|
+
if (Array.isArray(str)) {
|
|
144
|
+
return [...str]
|
|
145
|
+
}
|
|
146
|
+
|
|
143
147
|
if (typeof str !== 'string') {
|
|
144
148
|
throw new Error('Path must be a string.')
|
|
145
149
|
}
|