@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/form-core",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "Powerful, type-safe, framework agnostic forms.",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
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 { getAsyncValidatorArray, getBy, getSyncValidatorArray } from './utils'
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 error =
1349
- /*
1350
- If `validateObj.validate` is `undefined`, then the field doesn't have
1351
- a validator for this event, but there still could be an error that
1352
- needs to be cleaned up related to the current event left by the
1353
- form's validator.
1354
- */
1355
- validateObj.validate
1356
- ? normalizeError(
1357
- field.runValidator({
1358
- validate: validateObj.validate,
1359
- value: {
1360
- value: field.store.state.value,
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
- if (field.state.meta.errorMap[errorMapKey] !== error) {
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
- [getErrorMapKey(validateObj.cause)]:
1375
- // Prefer the error message from the field validators if they exist
1376
- error ? error : errorFromForm[errorMapKey],
1391
+ [errorMapKey]: newErrorValue,
1392
+ },
1393
+ errorSourceMap: {
1394
+ ...prev.errorSourceMap,
1395
+ [errorMapKey]: newSource,
1377
1396
  },
1378
1397
  }))
1379
1398
  }
1380
- if (error || errorFromForm[errorMapKey]) {
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
- this.state.meta.errorMap[submitErrKey] &&
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
- const error = normalizeError(rawError)
1525
- const fieldErrorFromForm =
1548
+
1549
+ const fieldLevelError = normalizeError(rawError)
1550
+ const formLevelError =
1526
1551
  asyncFormValidationResults[this.name]?.[errorMapKey]
1527
- const fieldError = error || fieldErrorFromForm
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]: fieldError,
1565
+ [errorMapKey]: newErrorValue,
1566
+ },
1567
+ errorSourceMap: {
1568
+ ...prev.errorSourceMap,
1569
+ [errorMapKey]: newSource,
1535
1570
  },
1536
1571
  }
1537
1572
  })
1538
1573
 
1539
- resolve(fieldError)
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
- if (fieldErrors) {
1310
- for (const [field, fieldError] of Object.entries(fieldErrors) as [
1311
- DeepKeys<TFormData>,
1312
- ValidationError,
1313
- ][]) {
1314
- const oldErrorMap = this.cumulativeFieldsErrorMap[field] || {}
1315
- const newErrorMap = {
1316
- ...oldErrorMap,
1317
- [errorMapKey]: fieldError,
1318
- }
1319
- currentValidationErrorMap[field] = newErrorMap
1320
- this.cumulativeFieldsErrorMap[field] = newErrorMap
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
- const fieldMeta = this.getFieldMeta(field)
1323
- if (fieldMeta && fieldMeta.errorMap[errorMapKey] !== fieldError) {
1324
- this.setFieldMeta(field, (prev) => ({
1325
- ...prev,
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
- fieldMeta?.errorMap[errorMapKey] &&
1341
- !currentValidationErrorMap[field]?.[errorMapKey]
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]: undefined,
1332
+ [errorMapKey]: newErrorValue,
1333
+ },
1334
+ errorSourceMap: {
1335
+ ...prev.errorSourceMap,
1336
+ [errorMapKey]: newSource,
1353
1337
  },
1354
1338
  }))
1355
1339
  }
1356
1340
  }
1357
1341
 
1358
- if (this.state.errorMap[errorMapKey] !== formError) {
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
- this.state.errorMap[submitErrKey] &&
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 fieldErrors:
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
- fieldErrors = fieldErrors
1477
- ? { ...fieldErrors, ...fieldErrorsFromNormalizeError }
1462
+ fieldErrorsFromFormValidators = fieldErrorsFromFormValidators
1463
+ ? {
1464
+ ...fieldErrorsFromFormValidators,
1465
+ ...fieldErrorsFromNormalizeError,
1466
+ }
1478
1467
  : fieldErrorsFromNormalizeError
1479
1468
  }
1480
1469
  const errorMapKey = getErrorMapKey(validateObj.cause)
1481
1470
 
1482
- if (fieldErrors) {
1483
- for (const [field, fieldError] of Object.entries(fieldErrors)) {
1484
- const fieldMeta = this.getFieldMeta(field as DeepKeys<TFormData>)
1485
- if (fieldMeta && fieldMeta.errorMap[errorMapKey] !== fieldError) {
1486
- this.setFieldMeta(field as DeepKeys<TFormData>, (prev) => ({
1487
- ...prev,
1488
- errorMap: {
1489
- ...prev.errorMap,
1490
- [errorMapKey]: fieldError,
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(fieldErrors ? { fieldErrors, errorMapKey } : undefined)
1520
+ resolve(
1521
+ fieldErrorsFromFormValidators
1522
+ ? { fieldErrors: fieldErrorsFromFormValidators, errorMapKey }
1523
+ : undefined,
1524
+ )
1505
1525
  }),
1506
1526
  )
1507
1527
  }
package/src/metaHelper.ts CHANGED
@@ -16,6 +16,7 @@ export const defaultFieldMeta: AnyFieldMeta = {
16
16
  isPristine: true,
17
17
  errors: [],
18
18
  errorMap: {},
19
+ errorSourceMap: {},
19
20
  }
20
21
 
21
22
  export function metaHelper<
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 { GlobalFormValidationError, ValidationCause } from './types'
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
+ }