@tanstack/form-core 1.6.2 → 1.6.3
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 +51 -32
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +5 -1
- package/dist/cjs/FormApi.cjs +80 -46
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +0 -6
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/metaHelper.cjs +2 -1
- package/dist/cjs/metaHelper.cjs.map +1 -1
- package/dist/cjs/types.d.cts +10 -0
- package/dist/cjs/utils.cjs +30 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +24 -1
- package/dist/esm/FieldApi.d.ts +5 -1
- package/dist/esm/FieldApi.js +52 -33
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +0 -6
- package/dist/esm/FormApi.js +81 -47
- package/dist/esm/FormApi.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/metaHelper.js +2 -1
- package/dist/esm/metaHelper.js.map +1 -1
- package/dist/esm/types.d.ts +10 -0
- package/dist/esm/utils.d.ts +24 -1
- package/dist/esm/utils.js +30 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/FieldApi.ts +67 -32
- package/src/FormApi.ts +88 -68
- package/src/metaHelper.ts +1 -0
- package/src/types.ts +11 -0
- package/src/utils.ts +68 -1
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type { GlobalFormValidationError, ValidationCause } from './types'\nimport type { FormValidators } from './FormApi'\nimport type { AnyFieldMeta, 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 (\n typeof key === 'string' ||\n (typeof key === 'number' && !Array.isArray(parent))\n ) {\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) && typeof key === 'number') {\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 .replace(/\\[/g, '.')\n .replace(/\\]/g, '')\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, any, any, any, any, any, any, any, any>\n ? Array<\n AsyncValidator<T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']>\n >\n : T extends FormValidators<any, any, any, any, any, any, 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, any, any, any, any, any, any, any, any>\n | FormValidators<any, any, any, any, any, any, 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, any, any, any, any, any, any, any, any>\n ? Array<\n SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit'] | T['onMount']>\n >\n : T extends FormValidators<any, any, any, any, any, any, any, any>\n ? Array<\n SyncValidator<\n T['onChange'] | T['onBlur'] | T['onSubmit'] | T['onMount']\n >\n >\n : never {\n const { onChange, onBlur, onSubmit, onMount } = (options.validators || {}) as\n | FieldValidators<any, any, any, any, any, any, any, any, any, any>\n | FormValidators<any, any, any, any, any, any, 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 const mountValidator = { cause: 'mount', validate: onMount } 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 'mount':\n return [mountValidator] as never\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\nexport const isGlobalFormValidationError = (\n error: unknown,\n): error is GlobalFormValidationError<unknown> => {\n return !!error && typeof error === 'object' && 'fields' in error\n}\n\nexport function shallow<T>(objA: T, objB: T) {\n if (Object.is(objA, objB)) {\n return true\n }\n\n if (\n typeof objA !== 'object' ||\n objA === null ||\n typeof objB !== 'object' ||\n objB === null\n ) {\n return false\n }\n\n if (objA instanceof Map && objB instanceof Map) {\n if (objA.size !== objB.size) return false\n for (const [k, v] of objA) {\n if (!objB.has(k) || !Object.is(v, objB.get(k))) return false\n }\n return true\n }\n\n if (objA instanceof Set && objB instanceof Set) {\n if (objA.size !== objB.size) return false\n for (const v of objA) {\n if (!objB.has(v)) return false\n }\n return true\n }\n\n const keysA = Object.keys(objA)\n if (keysA.length !== Object.keys(objB).length) {\n return false\n }\n\n for (let i = 0; i < keysA.length; i++) {\n if (\n !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||\n !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])\n ) {\n return false\n }\n }\n return true\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type {\n GlobalFormValidationError,\n ValidationCause,\n ValidationError,\n ValidationSource,\n} from './types'\nimport type { FormValidators } from './FormApi'\nimport type { AnyFieldMeta, 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 (\n typeof key === 'string' ||\n (typeof key === 'number' && !Array.isArray(parent))\n ) {\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) && typeof key === 'number') {\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 .replace(/\\[/g, '.')\n .replace(/\\]/g, '')\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, any, any, any, any, any, any, any, any>\n ? Array<\n AsyncValidator<T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']>\n >\n : T extends FormValidators<any, any, any, any, any, any, 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, any, any, any, any, any, any, any, any>\n | FormValidators<any, any, any, any, any, any, 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, any, any, any, any, any, any, any, any>\n ? Array<\n SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit'] | T['onMount']>\n >\n : T extends FormValidators<any, any, any, any, any, any, any, any>\n ? Array<\n SyncValidator<\n T['onChange'] | T['onBlur'] | T['onSubmit'] | T['onMount']\n >\n >\n : never {\n const { onChange, onBlur, onSubmit, onMount } = (options.validators || {}) as\n | FieldValidators<any, any, any, any, any, any, any, any, any, any>\n | FormValidators<any, any, any, any, any, any, 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 const mountValidator = { cause: 'mount', validate: onMount } 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 'mount':\n return [mountValidator] as never\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\nexport const isGlobalFormValidationError = (\n error: unknown,\n): error is GlobalFormValidationError<unknown> => {\n return !!error && typeof error === 'object' && 'fields' in error\n}\n\nexport function shallow<T>(objA: T, objB: T) {\n if (Object.is(objA, objB)) {\n return true\n }\n\n if (\n typeof objA !== 'object' ||\n objA === null ||\n typeof objB !== 'object' ||\n objB === null\n ) {\n return false\n }\n\n if (objA instanceof Map && objB instanceof Map) {\n if (objA.size !== objB.size) return false\n for (const [k, v] of objA) {\n if (!objB.has(k) || !Object.is(v, objB.get(k))) return false\n }\n return true\n }\n\n if (objA instanceof Set && objB instanceof Set) {\n if (objA.size !== objB.size) return false\n for (const v of objA) {\n if (!objB.has(v)) return false\n }\n return true\n }\n\n const keysA = Object.keys(objA)\n if (keysA.length !== Object.keys(objB).length) {\n return false\n }\n\n for (let i = 0; i < keysA.length; i++) {\n if (\n !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) ||\n !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T])\n ) {\n return false\n }\n }\n return true\n}\n\n/**\n * Determines the logic for determining the error source and value to set on the field meta within the form level sync/async validation.\n * @private\n */\nexport const determineFormLevelErrorSourceAndValue = ({\n newFormValidatorError,\n isPreviousErrorFromFormValidator,\n previousErrorValue,\n}: {\n newFormValidatorError: ValidationError\n isPreviousErrorFromFormValidator: boolean\n previousErrorValue: ValidationError\n}): {\n newErrorValue: ValidationError\n newSource: ValidationSource | undefined\n} => {\n // All falsy values are not considered errors\n if (newFormValidatorError) {\n return { newErrorValue: newFormValidatorError, newSource: 'form' }\n }\n\n // Clears form level error since it's now stale\n if (isPreviousErrorFromFormValidator) {\n return { newErrorValue: undefined, newSource: undefined }\n }\n\n // At this point, we have a preivous error which must have been set by the field validator, keep as is\n if (previousErrorValue) {\n return { newErrorValue: previousErrorValue, newSource: 'field' }\n }\n\n // No new or previous error, clear the error\n return { newErrorValue: undefined, newSource: undefined }\n}\n\n/**\n * Determines the logic for determining the error source and value to set on the field meta within the field level sync/async validation.\n * @private\n */\nexport const determineFieldLevelErrorSourceAndValue = ({\n formLevelError,\n fieldLevelError,\n}: {\n formLevelError: ValidationError\n fieldLevelError: ValidationError\n}): {\n newErrorValue: ValidationError\n newSource: ValidationSource | undefined\n} => {\n // At field level, we prioritize the field level error\n if (fieldLevelError) {\n return { newErrorValue: fieldLevelError, newSource: 'field' }\n }\n\n // If there is no field level error, and there is a form level error, we set the form level error\n if (formLevelError) {\n return { newErrorValue: formLevelError, newSource: 'form' }\n }\n\n return { newErrorValue: undefined, newSource: undefined }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/FieldApi.ts
CHANGED
|
@@ -4,7 +4,12 @@ import {
|
|
|
4
4
|
standardSchemaValidators,
|
|
5
5
|
} from './standardSchemaValidator'
|
|
6
6
|
import { defaultFieldMeta } from './metaHelper'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
determineFieldLevelErrorSourceAndValue,
|
|
9
|
+
getAsyncValidatorArray,
|
|
10
|
+
getBy,
|
|
11
|
+
getSyncValidatorArray,
|
|
12
|
+
} from './utils'
|
|
8
13
|
import type { DeepKeys, DeepValue, UnwrapOneLevelOfArray } from './util-types'
|
|
9
14
|
import type {
|
|
10
15
|
StandardSchemaV1,
|
|
@@ -25,6 +30,7 @@ import type {
|
|
|
25
30
|
ValidationCause,
|
|
26
31
|
ValidationError,
|
|
27
32
|
ValidationErrorMap,
|
|
33
|
+
ValidationErrorMapSource,
|
|
28
34
|
} from './types'
|
|
29
35
|
import type { AsyncValidator, SyncValidator, Updater } from './utils'
|
|
30
36
|
|
|
@@ -561,6 +567,10 @@ export type FieldMetaBase<
|
|
|
561
567
|
UnwrapFieldValidateOrFn<TName, TOnSubmit, TFormOnSubmit>,
|
|
562
568
|
UnwrapFieldAsyncValidateOrFn<TName, TOnSubmitAsync, TFormOnSubmitAsync>
|
|
563
569
|
>
|
|
570
|
+
/**
|
|
571
|
+
* @private allows tracking the source of the errors in the error map
|
|
572
|
+
*/
|
|
573
|
+
errorSourceMap: ValidationErrorMapSource
|
|
564
574
|
/**
|
|
565
575
|
* A flag indicating whether the field is currently being validated.
|
|
566
576
|
*/
|
|
@@ -1101,6 +1111,11 @@ export class FieldApi<
|
|
|
1101
1111
|
...prev,
|
|
1102
1112
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1103
1113
|
errorMap: { ...prev?.errorMap, onMount: error },
|
|
1114
|
+
errorSourceMap: {
|
|
1115
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1116
|
+
...prev?.errorSourceMap,
|
|
1117
|
+
onMount: 'field',
|
|
1118
|
+
},
|
|
1104
1119
|
}) as never,
|
|
1105
1120
|
)
|
|
1106
1121
|
}
|
|
@@ -1345,39 +1360,43 @@ export class FieldApi<
|
|
|
1345
1360
|
) => {
|
|
1346
1361
|
const errorMapKey = getErrorMapKey(validateObj.cause)
|
|
1347
1362
|
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
validationSource: 'field',
|
|
1362
|
-
fieldApi: field,
|
|
1363
|
-
},
|
|
1364
|
-
type: 'validate',
|
|
1365
|
-
}),
|
|
1366
|
-
)
|
|
1367
|
-
: errorFromForm[errorMapKey]
|
|
1363
|
+
const fieldLevelError = validateObj.validate
|
|
1364
|
+
? normalizeError(
|
|
1365
|
+
field.runValidator({
|
|
1366
|
+
validate: validateObj.validate,
|
|
1367
|
+
value: {
|
|
1368
|
+
value: field.store.state.value,
|
|
1369
|
+
validationSource: 'field',
|
|
1370
|
+
fieldApi: field,
|
|
1371
|
+
},
|
|
1372
|
+
type: 'validate',
|
|
1373
|
+
}),
|
|
1374
|
+
)
|
|
1375
|
+
: undefined
|
|
1368
1376
|
|
|
1369
|
-
|
|
1377
|
+
const formLevelError = errorFromForm[errorMapKey]
|
|
1378
|
+
|
|
1379
|
+
const { newErrorValue, newSource } =
|
|
1380
|
+
determineFieldLevelErrorSourceAndValue({
|
|
1381
|
+
formLevelError,
|
|
1382
|
+
fieldLevelError,
|
|
1383
|
+
})
|
|
1384
|
+
|
|
1385
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1386
|
+
if (field.state.meta.errorMap?.[errorMapKey] !== newErrorValue) {
|
|
1370
1387
|
field.setMeta((prev) => ({
|
|
1371
1388
|
...prev,
|
|
1372
1389
|
errorMap: {
|
|
1373
1390
|
...prev.errorMap,
|
|
1374
|
-
[
|
|
1375
|
-
|
|
1376
|
-
|
|
1391
|
+
[errorMapKey]: newErrorValue,
|
|
1392
|
+
},
|
|
1393
|
+
errorSourceMap: {
|
|
1394
|
+
...prev.errorSourceMap,
|
|
1395
|
+
[errorMapKey]: newSource,
|
|
1377
1396
|
},
|
|
1378
1397
|
}))
|
|
1379
1398
|
}
|
|
1380
|
-
if (
|
|
1399
|
+
if (newErrorValue) {
|
|
1381
1400
|
hasErrored = true
|
|
1382
1401
|
}
|
|
1383
1402
|
}
|
|
@@ -1398,7 +1417,8 @@ export class FieldApi<
|
|
|
1398
1417
|
const submitErrKey = getErrorMapKey('submit')
|
|
1399
1418
|
|
|
1400
1419
|
if (
|
|
1401
|
-
|
|
1420
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1421
|
+
this.state.meta.errorMap?.[submitErrKey] &&
|
|
1402
1422
|
cause !== 'submit' &&
|
|
1403
1423
|
!hasErrored
|
|
1404
1424
|
) {
|
|
@@ -1408,6 +1428,10 @@ export class FieldApi<
|
|
|
1408
1428
|
...prev.errorMap,
|
|
1409
1429
|
[submitErrKey]: undefined,
|
|
1410
1430
|
},
|
|
1431
|
+
errorSourceMap: {
|
|
1432
|
+
...prev.errorSourceMap,
|
|
1433
|
+
[submitErrKey]: undefined,
|
|
1434
|
+
},
|
|
1411
1435
|
}))
|
|
1412
1436
|
}
|
|
1413
1437
|
|
|
@@ -1521,22 +1545,33 @@ export class FieldApi<
|
|
|
1521
1545
|
rawError = e as ValidationError
|
|
1522
1546
|
}
|
|
1523
1547
|
if (controller.signal.aborted) return resolve(undefined)
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1548
|
+
|
|
1549
|
+
const fieldLevelError = normalizeError(rawError)
|
|
1550
|
+
const formLevelError =
|
|
1526
1551
|
asyncFormValidationResults[this.name]?.[errorMapKey]
|
|
1527
|
-
|
|
1552
|
+
|
|
1553
|
+
const { newErrorValue, newSource } =
|
|
1554
|
+
determineFieldLevelErrorSourceAndValue({
|
|
1555
|
+
formLevelError,
|
|
1556
|
+
fieldLevelError,
|
|
1557
|
+
})
|
|
1558
|
+
|
|
1528
1559
|
field.setMeta((prev) => {
|
|
1529
1560
|
return {
|
|
1530
1561
|
...prev,
|
|
1531
1562
|
errorMap: {
|
|
1532
1563
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1533
1564
|
...prev?.errorMap,
|
|
1534
|
-
[errorMapKey]:
|
|
1565
|
+
[errorMapKey]: newErrorValue,
|
|
1566
|
+
},
|
|
1567
|
+
errorSourceMap: {
|
|
1568
|
+
...prev.errorSourceMap,
|
|
1569
|
+
[errorMapKey]: newSource,
|
|
1535
1570
|
},
|
|
1536
1571
|
}
|
|
1537
1572
|
})
|
|
1538
1573
|
|
|
1539
|
-
resolve(
|
|
1574
|
+
resolve(newErrorValue)
|
|
1540
1575
|
}),
|
|
1541
1576
|
)
|
|
1542
1577
|
}
|
package/src/FormApi.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Derived, Store, batch } from '@tanstack/store'
|
|
2
2
|
import {
|
|
3
3
|
deleteBy,
|
|
4
|
+
determineFormLevelErrorSourceAndValue,
|
|
4
5
|
functionalUpdate,
|
|
5
6
|
getAsyncValidatorArray,
|
|
6
7
|
getBy,
|
|
@@ -733,22 +734,6 @@ export class FormApi<
|
|
|
733
734
|
*/
|
|
734
735
|
prevTransformArray: unknown[] = []
|
|
735
736
|
|
|
736
|
-
/**
|
|
737
|
-
* @private Persistent store of all field validation errors originating from form-level validators.
|
|
738
|
-
* Maintains the cumulative state across validation cycles, including cleared errors (undefined values).
|
|
739
|
-
* This map preserves the complete validation state for all fields.
|
|
740
|
-
*/
|
|
741
|
-
cumulativeFieldsErrorMap: FormErrorMapFromValidator<
|
|
742
|
-
TFormData,
|
|
743
|
-
TOnMount,
|
|
744
|
-
TOnChange,
|
|
745
|
-
TOnChangeAsync,
|
|
746
|
-
TOnBlur,
|
|
747
|
-
TOnBlurAsync,
|
|
748
|
-
TOnSubmit,
|
|
749
|
-
TOnSubmitAsync
|
|
750
|
-
> = {}
|
|
751
|
-
|
|
752
737
|
/**
|
|
753
738
|
* Constructs a new `FormApi` instance with the given form options.
|
|
754
739
|
*/
|
|
@@ -1306,56 +1291,56 @@ export class FormApi<
|
|
|
1306
1291
|
|
|
1307
1292
|
const errorMapKey = getErrorMapKey(validateObj.cause)
|
|
1308
1293
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1294
|
+
for (const field of Object.keys(
|
|
1295
|
+
this.state.fieldMeta,
|
|
1296
|
+
) as DeepKeys<TFormData>[]) {
|
|
1297
|
+
const fieldMeta = this.getFieldMeta(field)
|
|
1298
|
+
if (!fieldMeta) continue
|
|
1299
|
+
|
|
1300
|
+
const {
|
|
1301
|
+
errorMap: currentErrorMap,
|
|
1302
|
+
errorSourceMap: currentErrorMapSource,
|
|
1303
|
+
} = fieldMeta
|
|
1304
|
+
|
|
1305
|
+
const newFormValidatorError = fieldErrors?.[field]
|
|
1306
|
+
|
|
1307
|
+
const { newErrorValue, newSource } =
|
|
1308
|
+
determineFormLevelErrorSourceAndValue({
|
|
1309
|
+
newFormValidatorError,
|
|
1310
|
+
isPreviousErrorFromFormValidator:
|
|
1311
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1312
|
+
currentErrorMapSource?.[errorMapKey] === 'form',
|
|
1313
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1314
|
+
previousErrorValue: currentErrorMap?.[errorMapKey],
|
|
1315
|
+
})
|
|
1321
1316
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
errorMap: {
|
|
1327
|
-
...prev.errorMap,
|
|
1328
|
-
[errorMapKey]: fieldError,
|
|
1329
|
-
},
|
|
1330
|
-
}))
|
|
1317
|
+
if (newSource === 'form') {
|
|
1318
|
+
currentValidationErrorMap[field] = {
|
|
1319
|
+
...currentValidationErrorMap[field],
|
|
1320
|
+
[errorMapKey]: newFormValidatorError,
|
|
1331
1321
|
}
|
|
1332
1322
|
}
|
|
1333
|
-
}
|
|
1334
1323
|
|
|
1335
|
-
for (const field of Object.keys(this.cumulativeFieldsErrorMap) as Array<
|
|
1336
|
-
DeepKeys<TFormData>
|
|
1337
|
-
>) {
|
|
1338
|
-
const fieldMeta = this.getFieldMeta(field)
|
|
1339
1324
|
if (
|
|
1340
|
-
|
|
1341
|
-
|
|
1325
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1326
|
+
currentErrorMap?.[errorMapKey] !== newErrorValue
|
|
1342
1327
|
) {
|
|
1343
|
-
this.cumulativeFieldsErrorMap[field] = {
|
|
1344
|
-
...this.cumulativeFieldsErrorMap[field],
|
|
1345
|
-
[errorMapKey]: undefined,
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
1328
|
this.setFieldMeta(field, (prev) => ({
|
|
1349
1329
|
...prev,
|
|
1350
1330
|
errorMap: {
|
|
1351
1331
|
...prev.errorMap,
|
|
1352
|
-
[errorMapKey]:
|
|
1332
|
+
[errorMapKey]: newErrorValue,
|
|
1333
|
+
},
|
|
1334
|
+
errorSourceMap: {
|
|
1335
|
+
...prev.errorSourceMap,
|
|
1336
|
+
[errorMapKey]: newSource,
|
|
1353
1337
|
},
|
|
1354
1338
|
}))
|
|
1355
1339
|
}
|
|
1356
1340
|
}
|
|
1357
1341
|
|
|
1358
|
-
|
|
1342
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1343
|
+
if (this.state.errorMap?.[errorMapKey] !== formError) {
|
|
1359
1344
|
this.baseStore.setState((prev) => ({
|
|
1360
1345
|
...prev,
|
|
1361
1346
|
errorMap: {
|
|
@@ -1376,7 +1361,8 @@ export class FormApi<
|
|
|
1376
1361
|
*/
|
|
1377
1362
|
const submitErrKey = getErrorMapKey('submit')
|
|
1378
1363
|
if (
|
|
1379
|
-
|
|
1364
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1365
|
+
this.state.errorMap?.[submitErrKey] &&
|
|
1380
1366
|
cause !== 'submit' &&
|
|
1381
1367
|
!hasErrored
|
|
1382
1368
|
) {
|
|
@@ -1422,7 +1408,7 @@ export class FormApi<
|
|
|
1422
1408
|
*/
|
|
1423
1409
|
const promises: Promise<ValidationPromiseResult<TFormData>>[] = []
|
|
1424
1410
|
|
|
1425
|
-
let
|
|
1411
|
+
let fieldErrorsFromFormValidators:
|
|
1426
1412
|
| Partial<Record<DeepKeys<TFormData>, ValidationError>>
|
|
1427
1413
|
| undefined
|
|
1428
1414
|
|
|
@@ -1473,26 +1459,56 @@ export class FormApi<
|
|
|
1473
1459
|
normalizeError<TFormData>(rawError)
|
|
1474
1460
|
|
|
1475
1461
|
if (fieldErrorsFromNormalizeError) {
|
|
1476
|
-
|
|
1477
|
-
? {
|
|
1462
|
+
fieldErrorsFromFormValidators = fieldErrorsFromFormValidators
|
|
1463
|
+
? {
|
|
1464
|
+
...fieldErrorsFromFormValidators,
|
|
1465
|
+
...fieldErrorsFromNormalizeError,
|
|
1466
|
+
}
|
|
1478
1467
|
: fieldErrorsFromNormalizeError
|
|
1479
1468
|
}
|
|
1480
1469
|
const errorMapKey = getErrorMapKey(validateObj.cause)
|
|
1481
1470
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1471
|
+
for (const field of Object.keys(
|
|
1472
|
+
this.state.fieldMeta,
|
|
1473
|
+
) as DeepKeys<TFormData>[]) {
|
|
1474
|
+
const fieldMeta = this.getFieldMeta(field)
|
|
1475
|
+
if (!fieldMeta) continue
|
|
1476
|
+
|
|
1477
|
+
const {
|
|
1478
|
+
errorMap: currentErrorMap,
|
|
1479
|
+
errorSourceMap: currentErrorMapSource,
|
|
1480
|
+
} = fieldMeta
|
|
1481
|
+
|
|
1482
|
+
const newFormValidatorError = fieldErrorsFromFormValidators?.[field]
|
|
1483
|
+
|
|
1484
|
+
const { newErrorValue, newSource } =
|
|
1485
|
+
determineFormLevelErrorSourceAndValue({
|
|
1486
|
+
newFormValidatorError,
|
|
1487
|
+
isPreviousErrorFromFormValidator:
|
|
1488
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1489
|
+
currentErrorMapSource?.[errorMapKey] === 'form',
|
|
1490
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1491
|
+
previousErrorValue: currentErrorMap?.[errorMapKey],
|
|
1492
|
+
})
|
|
1493
|
+
|
|
1494
|
+
if (
|
|
1495
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1496
|
+
currentErrorMap?.[errorMapKey] !== newErrorValue
|
|
1497
|
+
) {
|
|
1498
|
+
this.setFieldMeta(field, (prev) => ({
|
|
1499
|
+
...prev,
|
|
1500
|
+
errorMap: {
|
|
1501
|
+
...prev.errorMap,
|
|
1502
|
+
[errorMapKey]: newErrorValue,
|
|
1503
|
+
},
|
|
1504
|
+
errorSourceMap: {
|
|
1505
|
+
...prev.errorSourceMap,
|
|
1506
|
+
[errorMapKey]: newSource,
|
|
1507
|
+
},
|
|
1508
|
+
}))
|
|
1494
1509
|
}
|
|
1495
1510
|
}
|
|
1511
|
+
|
|
1496
1512
|
this.baseStore.setState((prev) => ({
|
|
1497
1513
|
...prev,
|
|
1498
1514
|
errorMap: {
|
|
@@ -1501,7 +1517,11 @@ export class FormApi<
|
|
|
1501
1517
|
},
|
|
1502
1518
|
}))
|
|
1503
1519
|
|
|
1504
|
-
resolve(
|
|
1520
|
+
resolve(
|
|
1521
|
+
fieldErrorsFromFormValidators
|
|
1522
|
+
? { fieldErrors: fieldErrorsFromFormValidators, errorMapKey }
|
|
1523
|
+
: undefined,
|
|
1524
|
+
)
|
|
1505
1525
|
}),
|
|
1506
1526
|
)
|
|
1507
1527
|
}
|
package/src/metaHelper.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -40,6 +40,17 @@ export type ValidationErrorMap<
|
|
|
40
40
|
onServer?: TOnServerReturn
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @private allows tracking the source of the errors in the error map
|
|
45
|
+
*/
|
|
46
|
+
export type ValidationErrorMapSource = {
|
|
47
|
+
onMount?: ValidationSource
|
|
48
|
+
onChange?: ValidationSource
|
|
49
|
+
onBlur?: ValidationSource
|
|
50
|
+
onSubmit?: ValidationSource
|
|
51
|
+
onServer?: ValidationSource
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
/**
|
|
44
55
|
* @private
|
|
45
56
|
*/
|
package/src/utils.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
GlobalFormValidationError,
|
|
3
|
+
ValidationCause,
|
|
4
|
+
ValidationError,
|
|
5
|
+
ValidationSource,
|
|
6
|
+
} from './types'
|
|
2
7
|
import type { FormValidators } from './FormApi'
|
|
3
8
|
import type { AnyFieldMeta, FieldValidators } from './FieldApi'
|
|
4
9
|
|
|
@@ -376,3 +381,65 @@ export function shallow<T>(objA: T, objB: T) {
|
|
|
376
381
|
}
|
|
377
382
|
return true
|
|
378
383
|
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Determines the logic for determining the error source and value to set on the field meta within the form level sync/async validation.
|
|
387
|
+
* @private
|
|
388
|
+
*/
|
|
389
|
+
export const determineFormLevelErrorSourceAndValue = ({
|
|
390
|
+
newFormValidatorError,
|
|
391
|
+
isPreviousErrorFromFormValidator,
|
|
392
|
+
previousErrorValue,
|
|
393
|
+
}: {
|
|
394
|
+
newFormValidatorError: ValidationError
|
|
395
|
+
isPreviousErrorFromFormValidator: boolean
|
|
396
|
+
previousErrorValue: ValidationError
|
|
397
|
+
}): {
|
|
398
|
+
newErrorValue: ValidationError
|
|
399
|
+
newSource: ValidationSource | undefined
|
|
400
|
+
} => {
|
|
401
|
+
// All falsy values are not considered errors
|
|
402
|
+
if (newFormValidatorError) {
|
|
403
|
+
return { newErrorValue: newFormValidatorError, newSource: 'form' }
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Clears form level error since it's now stale
|
|
407
|
+
if (isPreviousErrorFromFormValidator) {
|
|
408
|
+
return { newErrorValue: undefined, newSource: undefined }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// At this point, we have a preivous error which must have been set by the field validator, keep as is
|
|
412
|
+
if (previousErrorValue) {
|
|
413
|
+
return { newErrorValue: previousErrorValue, newSource: 'field' }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// No new or previous error, clear the error
|
|
417
|
+
return { newErrorValue: undefined, newSource: undefined }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Determines the logic for determining the error source and value to set on the field meta within the field level sync/async validation.
|
|
422
|
+
* @private
|
|
423
|
+
*/
|
|
424
|
+
export const determineFieldLevelErrorSourceAndValue = ({
|
|
425
|
+
formLevelError,
|
|
426
|
+
fieldLevelError,
|
|
427
|
+
}: {
|
|
428
|
+
formLevelError: ValidationError
|
|
429
|
+
fieldLevelError: ValidationError
|
|
430
|
+
}): {
|
|
431
|
+
newErrorValue: ValidationError
|
|
432
|
+
newSource: ValidationSource | undefined
|
|
433
|
+
} => {
|
|
434
|
+
// At field level, we prioritize the field level error
|
|
435
|
+
if (fieldLevelError) {
|
|
436
|
+
return { newErrorValue: fieldLevelError, newSource: 'field' }
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// If there is no field level error, and there is a form level error, we set the form level error
|
|
440
|
+
if (formLevelError) {
|
|
441
|
+
return { newErrorValue: formLevelError, newSource: 'form' }
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return { newErrorValue: undefined, newSource: undefined }
|
|
445
|
+
}
|