@tanstack/form-core 0.44.1 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"mergeForm.js","sources":["../../src/mergeForm.ts"],"sourcesContent":["import type { FormApi } from './FormApi'\nimport type { NoInfer } from './util-types'\n\nfunction isValidKey(key: string | number | symbol): boolean {\n const dangerousProps = ['__proto__', 'constructor', 'prototype']\n return !dangerousProps.includes(String(key))\n}\n\n/**\n * @private\n */\nexport function mutateMergeDeep(\n target: object | null | undefined,\n source: object | null | undefined,\n): object {\n // Early return if either is not an object\n if (target === null || target === undefined || typeof target !== 'object')\n return {} as object\n if (source === null || source === undefined || typeof source !== 'object')\n return target\n\n const targetKeys = Object.keys(target)\n const sourceKeys = Object.keys(source)\n const keySet = new Set([...targetKeys, ...sourceKeys])\n\n for (const key of keySet) {\n if (!isValidKey(key)) continue\n\n const targetKey = key as keyof typeof target\n const sourceKey = key as keyof typeof source\n\n if (!Object.hasOwn(source, sourceKey)) continue\n\n const sourceValue = source[sourceKey] as unknown\n const targetValue = target[targetKey] as unknown\n\n // Handle arrays\n if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {\n Object.defineProperty(target, key, {\n value: [...sourceValue],\n enumerable: true,\n writable: true,\n configurable: true,\n })\n continue\n }\n\n // Handle nested objects (type assertion to satisfy ESLint)\n const isTargetObj = typeof targetValue === 'object' && targetValue !== null\n const isSourceObj = typeof sourceValue === 'object' && sourceValue !== null\n const areObjects =\n isTargetObj &&\n isSourceObj &&\n !Array.isArray(targetValue) &&\n !Array.isArray(sourceValue)\n\n if (areObjects) {\n mutateMergeDeep(targetValue as object, sourceValue as object)\n continue\n }\n\n // Handle all other cases\n Object.defineProperty(target, key, {\n value: sourceValue,\n enumerable: true,\n writable: true,\n configurable: true,\n })\n }\n\n return target\n}\n\nexport function mergeForm<TFormData>(\n baseForm: FormApi<NoInfer<TFormData>, any, any, any, any, any, any, any, any>,\n state: Partial<\n FormApi<TFormData, any, any, any, any, any, any, any, any>['state']\n >,\n) {\n mutateMergeDeep(baseForm.state, state)\n return baseForm\n}\n"],"names":[],"mappings":"AAGA,SAAS,WAAW,KAAwC;AAC1D,QAAM,iBAAiB,CAAC,aAAa,eAAe,WAAW;AAC/D,SAAO,CAAC,eAAe,SAAS,OAAO,GAAG,CAAC;AAC7C;AAKgB,SAAA,gBACd,QACA,QACQ;AAER,MAAI,WAAW,QAAQ,WAAW,UAAa,OAAO,WAAW;AAC/D,WAAO,CAAC;AACV,MAAI,WAAW,QAAQ,WAAW,UAAa,OAAO,WAAW;AACxD,WAAA;AAEH,QAAA,aAAa,OAAO,KAAK,MAAM;AAC/B,QAAA,aAAa,OAAO,KAAK,MAAM;AAC/B,QAAA,6BAAa,IAAI,CAAC,GAAG,YAAY,GAAG,UAAU,CAAC;AAErD,aAAW,OAAO,QAAQ;AACpB,QAAA,CAAC,WAAW,GAAG,EAAG;AAEtB,UAAM,YAAY;AAClB,UAAM,YAAY;AAElB,QAAI,CAAC,OAAO,OAAO,QAAQ,SAAS,EAAG;AAEjC,UAAA,cAAc,OAAO,SAAS;AAC9B,UAAA,cAAc,OAAO,SAAS;AAGpC,QAAI,MAAM,QAAQ,WAAW,KAAK,MAAM,QAAQ,WAAW,GAAG;AACrD,aAAA,eAAe,QAAQ,KAAK;AAAA,QACjC,OAAO,CAAC,GAAG,WAAW;AAAA,QACtB,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,cAAc;AAAA,MAAA,CACf;AACD;AAAA,IAAA;AAIF,UAAM,cAAc,OAAO,gBAAgB,YAAY,gBAAgB;AACvE,UAAM,cAAc,OAAO,gBAAgB,YAAY,gBAAgB;AACjE,UAAA,aACJ,eACA,eACA,CAAC,MAAM,QAAQ,WAAW,KAC1B,CAAC,MAAM,QAAQ,WAAW;AAE5B,QAAI,YAAY;AACd,sBAAgB,aAAuB,WAAqB;AAC5D;AAAA,IAAA;AAIK,WAAA,eAAe,QAAQ,KAAK;AAAA,MACjC,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,cAAc;AAAA,IAAA,CACf;AAAA,EAAA;AAGI,SAAA;AACT;AAEgB,SAAA,UACd,UACA,OAGA;AACgB,kBAAA,SAAS,OAAO,KAAK;AAC9B,SAAA;AACT;"}
1
+ {"version":3,"file":"mergeForm.js","sources":["../../src/mergeForm.ts"],"sourcesContent":["import type { FormApi } from './FormApi'\nimport type { NoInfer } from './util-types'\n\nfunction isValidKey(key: string | number | symbol): boolean {\n const dangerousProps = ['__proto__', 'constructor', 'prototype']\n return !dangerousProps.includes(String(key))\n}\n\n/**\n * @private\n */\nexport function mutateMergeDeep(\n target: object | null | undefined,\n source: object | null | undefined,\n): object {\n // Early return if either is not an object\n if (target === null || target === undefined || typeof target !== 'object')\n return {} as object\n if (source === null || source === undefined || typeof source !== 'object')\n return target\n\n const targetKeys = Object.keys(target)\n const sourceKeys = Object.keys(source)\n const keySet = new Set([...targetKeys, ...sourceKeys])\n\n for (const key of keySet) {\n if (!isValidKey(key)) continue\n\n const targetKey = key as keyof typeof target\n const sourceKey = key as keyof typeof source\n\n if (!Object.hasOwn(source, sourceKey)) continue\n\n const sourceValue = source[sourceKey] as unknown\n const targetValue = target[targetKey] as unknown\n\n // Handle arrays\n if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {\n Object.defineProperty(target, key, {\n value: [...sourceValue],\n enumerable: true,\n writable: true,\n configurable: true,\n })\n continue\n }\n\n // Handle nested objects (type assertion to satisfy ESLint)\n const isTargetObj = typeof targetValue === 'object' && targetValue !== null\n const isSourceObj = typeof sourceValue === 'object' && sourceValue !== null\n const areObjects =\n isTargetObj &&\n isSourceObj &&\n !Array.isArray(targetValue) &&\n !Array.isArray(sourceValue)\n\n if (areObjects) {\n mutateMergeDeep(targetValue as object, sourceValue as object)\n continue\n }\n\n // Handle all other cases\n Object.defineProperty(target, key, {\n value: sourceValue,\n enumerable: true,\n writable: true,\n configurable: true,\n })\n }\n\n return target\n}\n\nexport function mergeForm<TFormData>(\n baseForm: FormApi<\n NoInfer<TFormData>,\n any,\n any,\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >,\n state: Partial<\n FormApi<TFormData, any, any, any, any, any, any, any, any, any>['state']\n >,\n) {\n mutateMergeDeep(baseForm.state, state)\n return baseForm\n}\n"],"names":[],"mappings":"AAGA,SAAS,WAAW,KAAwC;AAC1D,QAAM,iBAAiB,CAAC,aAAa,eAAe,WAAW;AAC/D,SAAO,CAAC,eAAe,SAAS,OAAO,GAAG,CAAC;AAC7C;AAKgB,SAAA,gBACd,QACA,QACQ;AAER,MAAI,WAAW,QAAQ,WAAW,UAAa,OAAO,WAAW;AAC/D,WAAO,CAAC;AACV,MAAI,WAAW,QAAQ,WAAW,UAAa,OAAO,WAAW;AACxD,WAAA;AAEH,QAAA,aAAa,OAAO,KAAK,MAAM;AAC/B,QAAA,aAAa,OAAO,KAAK,MAAM;AAC/B,QAAA,6BAAa,IAAI,CAAC,GAAG,YAAY,GAAG,UAAU,CAAC;AAErD,aAAW,OAAO,QAAQ;AACpB,QAAA,CAAC,WAAW,GAAG,EAAG;AAEtB,UAAM,YAAY;AAClB,UAAM,YAAY;AAElB,QAAI,CAAC,OAAO,OAAO,QAAQ,SAAS,EAAG;AAEjC,UAAA,cAAc,OAAO,SAAS;AAC9B,UAAA,cAAc,OAAO,SAAS;AAGpC,QAAI,MAAM,QAAQ,WAAW,KAAK,MAAM,QAAQ,WAAW,GAAG;AACrD,aAAA,eAAe,QAAQ,KAAK;AAAA,QACjC,OAAO,CAAC,GAAG,WAAW;AAAA,QACtB,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,cAAc;AAAA,MAAA,CACf;AACD;AAAA,IAAA;AAIF,UAAM,cAAc,OAAO,gBAAgB,YAAY,gBAAgB;AACvE,UAAM,cAAc,OAAO,gBAAgB,YAAY,gBAAgB;AACjE,UAAA,aACJ,eACA,eACA,CAAC,MAAM,QAAQ,WAAW,KAC1B,CAAC,MAAM,QAAQ,WAAW;AAE5B,QAAI,YAAY;AACd,sBAAgB,aAAuB,WAAqB;AAC5D;AAAA,IAAA;AAIK,WAAA,eAAe,QAAQ,KAAK;AAAA,MACjC,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,cAAc;AAAA,IAAA,CACf;AAAA,EAAA;AAGI,SAAA;AACT;AAEgB,SAAA,UACd,UAYA,OAGA;AACgB,kBAAA,SAAS,OAAO,KAAK;AAC9B,SAAA;AACT;"}
@@ -1,7 +1,7 @@
1
1
  import { FormApi, FormAsyncValidateOrFn, FormValidateOrFn } from './FormApi.js';
2
2
  import { DeepKeys } from './util-types.js';
3
3
  type ArrayFieldMode = 'insert' | 'remove' | 'swap' | 'move';
4
- export declare function metaHelper<TFormData, TOnMount extends undefined | FormValidateOrFn<TFormData>, TOnChange extends undefined | FormValidateOrFn<TFormData>, TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>, TOnBlur extends undefined | FormValidateOrFn<TFormData>, TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>, TOnSubmit extends undefined | FormValidateOrFn<TFormData>, TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>, TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>>(formApi: FormApi<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnServer>): {
4
+ export declare function metaHelper<TFormData, TOnMount extends undefined | FormValidateOrFn<TFormData>, TOnChange extends undefined | FormValidateOrFn<TFormData>, TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>, TOnBlur extends undefined | FormValidateOrFn<TFormData>, TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>, TOnSubmit extends undefined | FormValidateOrFn<TFormData>, TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>, TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>, TSubmitMeta>(formApi: FormApi<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnServer, TSubmitMeta>): {
5
5
  handleArrayFieldMetaShift: (field: DeepKeys<TFormData>, index: number, mode: ArrayFieldMode, secondIndex?: number) => void;
6
6
  };
7
7
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"metaHelper.js","sources":["../../src/metaHelper.ts"],"sourcesContent":["import type {\n FormApi,\n FormAsyncValidateOrFn,\n FormValidateOrFn,\n} from './FormApi'\nimport type { AnyFieldMeta } from './FieldApi'\nimport type { DeepKeys } from './util-types'\n\ntype ArrayFieldMode = 'insert' | 'remove' | 'swap' | 'move'\n\nexport function metaHelper<\n TFormData,\n TOnMount extends undefined | FormValidateOrFn<TFormData>,\n TOnChange extends undefined | FormValidateOrFn<TFormData>,\n TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,\n TOnBlur extends undefined | FormValidateOrFn<TFormData>,\n TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,\n TOnSubmit extends undefined | FormValidateOrFn<TFormData>,\n TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,\n TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,\n>(\n formApi: FormApi<\n TFormData,\n TOnMount,\n TOnChange,\n TOnChangeAsync,\n TOnBlur,\n TOnBlurAsync,\n TOnSubmit,\n TOnSubmitAsync,\n TOnServer\n >,\n) {\n function handleArrayFieldMetaShift(\n field: DeepKeys<TFormData>,\n index: number,\n mode: ArrayFieldMode,\n secondIndex?: number,\n ) {\n const affectedFields = getAffectedFields(field, index, mode, secondIndex)\n\n const handlers = {\n insert: () => handleInsertMode(affectedFields, field, index),\n remove: () => handleRemoveMode(affectedFields),\n swap: () =>\n secondIndex !== undefined &&\n handleSwapMode(affectedFields, field, index, secondIndex),\n move: () =>\n secondIndex !== undefined &&\n handleMoveMode(affectedFields, field, index, secondIndex),\n }\n\n handlers[mode]()\n }\n\n function getFieldPath(field: DeepKeys<TFormData>, index: number): string {\n return `${field}[${index}]`\n }\n\n function getAffectedFields(\n field: DeepKeys<TFormData>,\n index: number,\n mode: ArrayFieldMode,\n secondIndex?: number,\n ): DeepKeys<TFormData>[] {\n const affectedFieldKeys = [getFieldPath(field, index)]\n\n if (mode === 'swap') {\n affectedFieldKeys.push(getFieldPath(field, secondIndex!))\n } else if (mode === 'move') {\n const [startIndex, endIndex] = [\n Math.min(index, secondIndex!),\n Math.max(index, secondIndex!),\n ]\n for (let i = startIndex; i <= endIndex; i++) {\n affectedFieldKeys.push(getFieldPath(field, i))\n }\n } else {\n const currentValue = formApi.getFieldValue(field)\n const fieldItems = Array.isArray(currentValue) ? currentValue.length : 0\n for (let i = index + 1; i < fieldItems; i++) {\n affectedFieldKeys.push(getFieldPath(field, i))\n }\n }\n\n return Object.keys(formApi.fieldInfo).filter((fieldKey) =>\n affectedFieldKeys.some((key) => fieldKey.startsWith(key)),\n ) as DeepKeys<TFormData>[]\n }\n\n function updateIndex(\n fieldKey: string,\n direction: 'up' | 'down',\n ): DeepKeys<TFormData> {\n return fieldKey.replace(/\\[(\\d+)\\]/, (_, num) => {\n const currIndex = parseInt(num, 10)\n const newIndex =\n direction === 'up' ? currIndex + 1 : Math.max(0, currIndex - 1)\n return `[${newIndex}]`\n }) as DeepKeys<TFormData>\n }\n\n function shiftMeta(fields: DeepKeys<TFormData>[], direction: 'up' | 'down') {\n const sortedFields = direction === 'up' ? fields : [...fields].reverse()\n\n sortedFields.forEach((fieldKey) => {\n const nextFieldKey = updateIndex(fieldKey.toString(), direction)\n const nextFieldMeta = formApi.getFieldMeta(nextFieldKey)\n if (nextFieldMeta) {\n formApi.setFieldMeta(fieldKey, nextFieldMeta)\n }\n })\n }\n\n const getEmptyFieldMeta = (): AnyFieldMeta => ({\n isValidating: false,\n isTouched: false,\n isBlurred: false,\n isDirty: false,\n isPristine: true,\n errors: [],\n errorMap: {},\n })\n\n const handleInsertMode = (\n fields: DeepKeys<TFormData>[],\n field: DeepKeys<TFormData>,\n insertIndex: number,\n ) => {\n shiftMeta(fields, 'down')\n\n fields.forEach((fieldKey) => {\n if (fieldKey.toString().startsWith(getFieldPath(field, insertIndex))) {\n formApi.setFieldMeta(fieldKey, getEmptyFieldMeta())\n }\n })\n }\n\n const handleRemoveMode = (fields: DeepKeys<TFormData>[]) => {\n shiftMeta(fields, 'up')\n }\n\n const handleMoveMode = (\n fields: DeepKeys<TFormData>[],\n field: DeepKeys<TFormData>,\n fromIndex: number,\n toIndex: number,\n ) => {\n // Store the original field meta that will be reapplied at the destination index\n const fromFields = new Map(\n Object.keys(formApi.fieldInfo)\n .filter((fieldKey) =>\n fieldKey.startsWith(getFieldPath(field, fromIndex)),\n )\n .map((fieldKey) => [\n fieldKey as DeepKeys<TFormData>,\n formApi.getFieldMeta(fieldKey as DeepKeys<TFormData>),\n ]),\n )\n\n shiftMeta(fields, fromIndex < toIndex ? 'up' : 'down')\n\n // Reapply the stored field meta at the destination index\n Object.keys(formApi.fieldInfo)\n .filter((fieldKey) => fieldKey.startsWith(getFieldPath(field, toIndex)))\n .forEach((fieldKey) => {\n const fromKey = fieldKey.replace(\n getFieldPath(field, toIndex),\n getFieldPath(field, fromIndex),\n ) as DeepKeys<TFormData>\n\n const fromMeta = fromFields.get(fromKey)\n if (fromMeta) {\n formApi.setFieldMeta(fieldKey as DeepKeys<TFormData>, fromMeta)\n }\n })\n }\n\n const handleSwapMode = (\n fields: DeepKeys<TFormData>[],\n field: DeepKeys<TFormData>,\n index: number,\n secondIndex: number,\n ) => {\n fields.forEach((fieldKey) => {\n if (!fieldKey.toString().startsWith(getFieldPath(field, index))) return\n\n const swappedKey = fieldKey\n .toString()\n .replace(\n getFieldPath(field, index),\n getFieldPath(field, secondIndex),\n ) as DeepKeys<TFormData>\n\n const [meta1, meta2] = [\n formApi.getFieldMeta(fieldKey),\n formApi.getFieldMeta(swappedKey),\n ]\n\n if (meta1) formApi.setFieldMeta(swappedKey, meta1)\n if (meta2) formApi.setFieldMeta(fieldKey, meta2)\n })\n }\n\n return { handleArrayFieldMetaShift }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"metaHelper.js","sources":["../../src/metaHelper.ts"],"sourcesContent":["import type {\n FormApi,\n FormAsyncValidateOrFn,\n FormValidateOrFn,\n} from './FormApi'\nimport type { AnyFieldMeta } from './FieldApi'\nimport type { DeepKeys } from './util-types'\n\ntype ArrayFieldMode = 'insert' | 'remove' | 'swap' | 'move'\n\nexport function metaHelper<\n TFormData,\n TOnMount extends undefined | FormValidateOrFn<TFormData>,\n TOnChange extends undefined | FormValidateOrFn<TFormData>,\n TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,\n TOnBlur extends undefined | FormValidateOrFn<TFormData>,\n TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,\n TOnSubmit extends undefined | FormValidateOrFn<TFormData>,\n TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,\n TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,\n TSubmitMeta,\n>(\n formApi: FormApi<\n TFormData,\n TOnMount,\n TOnChange,\n TOnChangeAsync,\n TOnBlur,\n TOnBlurAsync,\n TOnSubmit,\n TOnSubmitAsync,\n TOnServer,\n TSubmitMeta\n >,\n) {\n function handleArrayFieldMetaShift(\n field: DeepKeys<TFormData>,\n index: number,\n mode: ArrayFieldMode,\n secondIndex?: number,\n ) {\n const affectedFields = getAffectedFields(field, index, mode, secondIndex)\n\n const handlers = {\n insert: () => handleInsertMode(affectedFields, field, index),\n remove: () => handleRemoveMode(affectedFields),\n swap: () =>\n secondIndex !== undefined &&\n handleSwapMode(affectedFields, field, index, secondIndex),\n move: () =>\n secondIndex !== undefined &&\n handleMoveMode(affectedFields, field, index, secondIndex),\n }\n\n handlers[mode]()\n }\n\n function getFieldPath(field: DeepKeys<TFormData>, index: number): string {\n return `${field}[${index}]`\n }\n\n function getAffectedFields(\n field: DeepKeys<TFormData>,\n index: number,\n mode: ArrayFieldMode,\n secondIndex?: number,\n ): DeepKeys<TFormData>[] {\n const affectedFieldKeys = [getFieldPath(field, index)]\n\n if (mode === 'swap') {\n affectedFieldKeys.push(getFieldPath(field, secondIndex!))\n } else if (mode === 'move') {\n const [startIndex, endIndex] = [\n Math.min(index, secondIndex!),\n Math.max(index, secondIndex!),\n ]\n for (let i = startIndex; i <= endIndex; i++) {\n affectedFieldKeys.push(getFieldPath(field, i))\n }\n } else {\n const currentValue = formApi.getFieldValue(field)\n const fieldItems = Array.isArray(currentValue) ? currentValue.length : 0\n for (let i = index + 1; i < fieldItems; i++) {\n affectedFieldKeys.push(getFieldPath(field, i))\n }\n }\n\n return Object.keys(formApi.fieldInfo).filter((fieldKey) =>\n affectedFieldKeys.some((key) => fieldKey.startsWith(key)),\n ) as DeepKeys<TFormData>[]\n }\n\n function updateIndex(\n fieldKey: string,\n direction: 'up' | 'down',\n ): DeepKeys<TFormData> {\n return fieldKey.replace(/\\[(\\d+)\\]/, (_, num) => {\n const currIndex = parseInt(num, 10)\n const newIndex =\n direction === 'up' ? currIndex + 1 : Math.max(0, currIndex - 1)\n return `[${newIndex}]`\n }) as DeepKeys<TFormData>\n }\n\n function shiftMeta(fields: DeepKeys<TFormData>[], direction: 'up' | 'down') {\n const sortedFields = direction === 'up' ? fields : [...fields].reverse()\n\n sortedFields.forEach((fieldKey) => {\n const nextFieldKey = updateIndex(fieldKey.toString(), direction)\n const nextFieldMeta = formApi.getFieldMeta(nextFieldKey)\n if (nextFieldMeta) {\n formApi.setFieldMeta(fieldKey, nextFieldMeta)\n }\n })\n }\n\n const getEmptyFieldMeta = (): AnyFieldMeta => ({\n isValidating: false,\n isTouched: false,\n isBlurred: false,\n isDirty: false,\n isPristine: true,\n errors: [],\n errorMap: {},\n })\n\n const handleInsertMode = (\n fields: DeepKeys<TFormData>[],\n field: DeepKeys<TFormData>,\n insertIndex: number,\n ) => {\n shiftMeta(fields, 'down')\n\n fields.forEach((fieldKey) => {\n if (fieldKey.toString().startsWith(getFieldPath(field, insertIndex))) {\n formApi.setFieldMeta(fieldKey, getEmptyFieldMeta())\n }\n })\n }\n\n const handleRemoveMode = (fields: DeepKeys<TFormData>[]) => {\n shiftMeta(fields, 'up')\n }\n\n const handleMoveMode = (\n fields: DeepKeys<TFormData>[],\n field: DeepKeys<TFormData>,\n fromIndex: number,\n toIndex: number,\n ) => {\n // Store the original field meta that will be reapplied at the destination index\n const fromFields = new Map(\n Object.keys(formApi.fieldInfo)\n .filter((fieldKey) =>\n fieldKey.startsWith(getFieldPath(field, fromIndex)),\n )\n .map((fieldKey) => [\n fieldKey as DeepKeys<TFormData>,\n formApi.getFieldMeta(fieldKey as DeepKeys<TFormData>),\n ]),\n )\n\n shiftMeta(fields, fromIndex < toIndex ? 'up' : 'down')\n\n // Reapply the stored field meta at the destination index\n Object.keys(formApi.fieldInfo)\n .filter((fieldKey) => fieldKey.startsWith(getFieldPath(field, toIndex)))\n .forEach((fieldKey) => {\n const fromKey = fieldKey.replace(\n getFieldPath(field, toIndex),\n getFieldPath(field, fromIndex),\n ) as DeepKeys<TFormData>\n\n const fromMeta = fromFields.get(fromKey)\n if (fromMeta) {\n formApi.setFieldMeta(fieldKey as DeepKeys<TFormData>, fromMeta)\n }\n })\n }\n\n const handleSwapMode = (\n fields: DeepKeys<TFormData>[],\n field: DeepKeys<TFormData>,\n index: number,\n secondIndex: number,\n ) => {\n fields.forEach((fieldKey) => {\n if (!fieldKey.toString().startsWith(getFieldPath(field, index))) return\n\n const swappedKey = fieldKey\n .toString()\n .replace(\n getFieldPath(field, index),\n getFieldPath(field, secondIndex),\n ) as DeepKeys<TFormData>\n\n const [meta1, meta2] = [\n formApi.getFieldMeta(fieldKey),\n formApi.getFieldMeta(swappedKey),\n ]\n\n if (meta1) formApi.setFieldMeta(swappedKey, meta1)\n if (meta2) formApi.setFieldMeta(fieldKey, meta2)\n })\n }\n\n return { handleArrayFieldMetaShift }\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": "0.44.1",
3
+ "version": "0.45.1",
4
4
  "description": "Powerful, type-safe, framework agnostic forms.",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
package/src/FieldApi.ts CHANGED
@@ -96,6 +96,7 @@ export type FieldValidateFn<
96
96
  any,
97
97
  any,
98
98
  any,
99
+ any,
99
100
  any
100
101
  >
101
102
  }) => unknown
@@ -179,6 +180,7 @@ export type FieldValidateAsyncFn<
179
180
  any,
180
181
  any,
181
182
  any,
183
+ any,
182
184
  any
183
185
  >
184
186
  signal: AbortSignal
@@ -261,6 +263,7 @@ export type FieldListenerFn<
261
263
  any,
262
264
  any,
263
265
  any,
266
+ any,
264
267
  any
265
268
  >
266
269
  }) => void
@@ -471,6 +474,7 @@ export interface FieldApiOptions<
471
474
  TFormOnSubmit extends undefined | FormValidateOrFn<TParentData>,
472
475
  TFormOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TParentData>,
473
476
  TFormOnServer extends undefined | FormAsyncValidateOrFn<TParentData>,
477
+ TParentSubmitMeta,
474
478
  > extends FieldOptions<
475
479
  TParentData,
476
480
  TName,
@@ -492,7 +496,8 @@ export interface FieldApiOptions<
492
496
  TFormOnBlurAsync,
493
497
  TFormOnSubmit,
494
498
  TFormOnSubmitAsync,
495
- TFormOnServer
499
+ TFormOnServer,
500
+ TParentSubmitMeta
496
501
  >
497
502
  }
498
503
 
@@ -843,6 +848,7 @@ export type AnyFieldApi = FieldApi<
843
848
  any,
844
849
  any,
845
850
  any,
851
+ any,
846
852
  any
847
853
  >
848
854
 
@@ -880,6 +886,7 @@ export class FieldApi<
880
886
  TFormOnSubmit extends undefined | FormValidateOrFn<TParentData>,
881
887
  TFormOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TParentData>,
882
888
  TFormOnServer extends undefined | FormAsyncValidateOrFn<TParentData>,
889
+ TParentSubmitMeta,
883
890
  > {
884
891
  /**
885
892
  * A reference to the form API instance.
@@ -902,7 +909,8 @@ export class FieldApi<
902
909
  TFormOnBlurAsync,
903
910
  TFormOnSubmit,
904
911
  TFormOnSubmitAsync,
905
- TFormOnServer
912
+ TFormOnServer,
913
+ TParentSubmitMeta
906
914
  >['form']
907
915
  /**
908
916
  * The field name.
@@ -929,7 +937,8 @@ export class FieldApi<
929
937
  TFormOnBlurAsync,
930
938
  TFormOnSubmit,
931
939
  TFormOnSubmitAsync,
932
- TFormOnServer
940
+ TFormOnServer,
941
+ TParentSubmitMeta
933
942
  > = {} as any
934
943
  /**
935
944
  * The field state store.
@@ -985,7 +994,8 @@ export class FieldApi<
985
994
  TFormOnBlurAsync,
986
995
  TFormOnSubmit,
987
996
  TFormOnSubmitAsync,
988
- TFormOnServer
997
+ TFormOnServer,
998
+ TParentSubmitMeta
989
999
  >,
990
1000
  ) {
991
1001
  this.form = opts.form as never
@@ -1130,7 +1140,8 @@ export class FieldApi<
1130
1140
  TFormOnBlurAsync,
1131
1141
  TFormOnSubmit,
1132
1142
  TFormOnSubmitAsync,
1133
- TFormOnServer
1143
+ TFormOnServer,
1144
+ TParentSubmitMeta
1134
1145
  >,
1135
1146
  ) => {
1136
1147
  // Default Value
@@ -0,0 +1,31 @@
1
+ import { assertType, it } from 'vitest'
2
+ import { FormApi } from '../src/index'
3
+
4
+ it('should type handleSubmit as never when onSubmitMeta is not passed', () => {
5
+ const form = new FormApi({
6
+ defaultValues: {
7
+ name: 'test',
8
+ },
9
+ } as const)
10
+
11
+ assertType<() => Promise<void>>(form.handleSubmit)
12
+ })
13
+
14
+ type OnSubmitMeta = {
15
+ group: string
16
+ }
17
+
18
+ it('should type handleChange correctly', () => {
19
+ const form = new FormApi({
20
+ defaultValues: {
21
+ name: 'test',
22
+ },
23
+ onSubmitMeta: {} as OnSubmitMeta,
24
+ } as const)
25
+
26
+ form.handleSubmit({ group: 'track' })
27
+
28
+ assertType<(submitMeta: { group: string }) => Promise<void>>(
29
+ form.handleSubmit,
30
+ )
31
+ })
package/src/FormApi.ts CHANGED
@@ -74,6 +74,7 @@ export type FormValidateFn<TFormData> = (props: {
74
74
  any,
75
75
  any,
76
76
  any,
77
+ any,
77
78
  any
78
79
  >
79
80
  }) => unknown
@@ -109,6 +110,7 @@ export type FormValidateAsyncFn<TFormData> = (props: {
109
110
  any,
110
111
  any,
111
112
  any,
113
+ any,
112
114
  any
113
115
  >
114
116
  signal: AbortSignal
@@ -199,6 +201,7 @@ export interface FormTransform<
199
201
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
200
202
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
201
203
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
204
+ TSubmitMeta = never,
202
205
  > {
203
206
  fn: (
204
207
  formBase: FormApi<
@@ -210,7 +213,8 @@ export interface FormTransform<
210
213
  TOnBlurAsync,
211
214
  TOnSubmit,
212
215
  TOnSubmitAsync,
213
- TOnServer
216
+ TOnServer,
217
+ TSubmitMeta
214
218
  >,
215
219
  ) => FormApi<
216
220
  TFormData,
@@ -221,7 +225,8 @@ export interface FormTransform<
221
225
  TOnBlurAsync,
222
226
  TOnSubmit,
223
227
  TOnSubmitAsync,
224
- TOnServer
228
+ TOnServer,
229
+ TSubmitMeta
225
230
  >
226
231
  deps: unknown[]
227
232
  }
@@ -239,6 +244,7 @@ export interface FormOptions<
239
244
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
240
245
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
241
246
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
247
+ TSubmitMeta = never,
242
248
  > {
243
249
  /**
244
250
  * Set initial values for your form.
@@ -281,6 +287,12 @@ export interface FormOptions<
281
287
  TOnSubmit,
282
288
  TOnSubmitAsync
283
289
  >
290
+
291
+ /**
292
+ * onSubmitMeta, the data passed from the handleSubmit handler, to the onSubmit function props
293
+ */
294
+ onSubmitMeta?: TSubmitMeta
295
+
284
296
  /**
285
297
  * A function to be called when the form is submitted, what should happen once the user submits a valid form returns `any` or a promise `Promise<any>`
286
298
  */
@@ -295,8 +307,10 @@ export interface FormOptions<
295
307
  TOnBlurAsync,
296
308
  TOnSubmit,
297
309
  TOnSubmitAsync,
298
- TOnServer
310
+ TOnServer,
311
+ TSubmitMeta
299
312
  >
313
+ meta: TSubmitMeta
300
314
  }) => any | Promise<any>
301
315
  /**
302
316
  * Specify an action for scenarios where the user tries to submit an invalid form.
@@ -312,7 +326,8 @@ export interface FormOptions<
312
326
  TOnBlurAsync,
313
327
  TOnSubmit,
314
328
  TOnSubmitAsync,
315
- TOnServer
329
+ TOnServer,
330
+ TSubmitMeta
316
331
  >
317
332
  }) => void
318
333
  transform?: FormTransform<
@@ -324,7 +339,8 @@ export interface FormOptions<
324
339
  TOnBlurAsync,
325
340
  TOnSubmit,
326
341
  TOnSubmitAsync,
327
- TOnServer
342
+ TOnServer,
343
+ TSubmitMeta
328
344
  >
329
345
  }
330
346
 
@@ -363,6 +379,7 @@ export type FieldInfo<TFormData> = {
363
379
  any,
364
380
  any,
365
381
  any,
382
+ any,
366
383
  any
367
384
  > | null
368
385
  /**
@@ -610,7 +627,18 @@ function getDefaultFormState<
610
627
  *
611
628
  * A type representing the Form API with all generics set to `any` for convenience.
612
629
  */
613
- export type AnyFormApi = FormApi<any, any, any, any, any, any, any, any, any>
630
+ export type AnyFormApi = FormApi<
631
+ any,
632
+ any,
633
+ any,
634
+ any,
635
+ any,
636
+ any,
637
+ any,
638
+ any,
639
+ any,
640
+ any
641
+ >
614
642
 
615
643
  /**
616
644
  * A class representing the Form API. It handles the logic and interactions with the form state.
@@ -629,6 +657,7 @@ export class FormApi<
629
657
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
630
658
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
631
659
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
660
+ TSubmitMeta = never,
632
661
  > {
633
662
  /**
634
663
  * The options for the form.
@@ -642,7 +671,8 @@ export class FormApi<
642
671
  TOnBlurAsync,
643
672
  TOnSubmit,
644
673
  TOnSubmitAsync,
645
- TOnServer
674
+ TOnServer,
675
+ TSubmitMeta
646
676
  > = {}
647
677
  baseStore!: Store<
648
678
  BaseFormState<
@@ -685,6 +715,20 @@ export class FormApi<
685
715
  */
686
716
  prevTransformArray: unknown[] = []
687
717
 
718
+ /**
719
+ * @private map of errors originated from form level validators
720
+ */
721
+ prevFieldsErrorMap: FormErrorMapFromValidator<
722
+ TFormData,
723
+ TOnMount,
724
+ TOnChange,
725
+ TOnChangeAsync,
726
+ TOnBlur,
727
+ TOnBlurAsync,
728
+ TOnSubmit,
729
+ TOnSubmitAsync
730
+ > = {}
731
+
688
732
  /**
689
733
  * Constructs a new `FormApi` instance with the given form options.
690
734
  */
@@ -698,7 +742,8 @@ export class FormApi<
698
742
  TOnBlurAsync,
699
743
  TOnSubmit,
700
744
  TOnSubmitAsync,
701
- TOnServer
745
+ TOnServer,
746
+ TSubmitMeta
702
747
  >,
703
748
  ) {
704
749
  this.baseStore = new Store(
@@ -868,7 +913,7 @@ export class FormApi<
868
913
  >
869
914
  >((prev, curr) => {
870
915
  if (curr === undefined) return prev
871
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
916
+
872
917
  if (curr && isGlobalFormValidationError(curr)) {
873
918
  prev.push(curr.form as never)
874
919
  return prev
@@ -958,6 +1003,8 @@ export class FormApi<
958
1003
  },
959
1004
  })
960
1005
 
1006
+ this.handleSubmit = this.handleSubmit.bind(this)
1007
+
961
1008
  this.update(opts || {})
962
1009
  }
963
1010
 
@@ -1013,7 +1060,8 @@ export class FormApi<
1013
1060
  TOnBlurAsync,
1014
1061
  TOnSubmit,
1015
1062
  TOnSubmitAsync,
1016
- TOnServer
1063
+ TOnServer,
1064
+ TSubmitMeta
1017
1065
  >,
1018
1066
  ) => {
1019
1067
  if (!options) return
@@ -1195,7 +1243,7 @@ export class FormApi<
1195
1243
  const validates = getSyncValidatorArray(cause, this.options)
1196
1244
  let hasErrored = false as boolean
1197
1245
 
1198
- const fieldsErrorMap: FormErrorMapFromValidator<
1246
+ const newFieldsErrorMap: FormErrorMapFromValidator<
1199
1247
  TFormData,
1200
1248
  TOnMount,
1201
1249
  TOnChange,
@@ -1227,12 +1275,12 @@ export class FormApi<
1227
1275
  if (fieldErrors) {
1228
1276
  for (const [field, fieldError] of Object.entries(fieldErrors)) {
1229
1277
  const oldErrorMap =
1230
- fieldsErrorMap[field as DeepKeys<TFormData>] || {}
1278
+ newFieldsErrorMap[field as DeepKeys<TFormData>] || {}
1231
1279
  const newErrorMap = {
1232
1280
  ...oldErrorMap,
1233
1281
  [errorMapKey]: fieldError,
1234
1282
  }
1235
- fieldsErrorMap[field as DeepKeys<TFormData>] = newErrorMap
1283
+ newFieldsErrorMap[field as DeepKeys<TFormData>] = newErrorMap
1236
1284
 
1237
1285
  const fieldMeta = this.getFieldMeta(field as DeepKeys<TFormData>)
1238
1286
  if (fieldMeta && fieldMeta.errorMap[errorMapKey] !== fieldError) {
@@ -1247,6 +1295,24 @@ export class FormApi<
1247
1295
  }
1248
1296
  }
1249
1297
 
1298
+ for (const field of Object.keys(this.prevFieldsErrorMap) as Array<
1299
+ DeepKeys<TFormData>
1300
+ >) {
1301
+ const fieldMeta = this.getFieldMeta(field)
1302
+ if (
1303
+ fieldMeta?.errorMap[errorMapKey] &&
1304
+ !newFieldsErrorMap[field]?.[errorMapKey]
1305
+ ) {
1306
+ this.setFieldMeta(field, (prev) => ({
1307
+ ...prev,
1308
+ errorMap: {
1309
+ ...prev.errorMap,
1310
+ [errorMapKey]: undefined,
1311
+ },
1312
+ }))
1313
+ }
1314
+ }
1315
+
1250
1316
  if (this.state.errorMap[errorMapKey] !== formError) {
1251
1317
  this.baseStore.setState((prev) => ({
1252
1318
  ...prev,
@@ -1261,28 +1327,30 @@ export class FormApi<
1261
1327
  hasErrored = true
1262
1328
  }
1263
1329
  }
1330
+
1331
+ /**
1332
+ * when we have an error for onSubmit in the state, we want
1333
+ * to clear the error as soon as the user enters a valid value in the field
1334
+ */
1335
+ const submitErrKey = getErrorMapKey('submit')
1336
+ if (
1337
+ this.state.errorMap[submitErrKey] &&
1338
+ cause !== 'submit' &&
1339
+ !hasErrored
1340
+ ) {
1341
+ this.baseStore.setState((prev) => ({
1342
+ ...prev,
1343
+ errorMap: {
1344
+ ...prev.errorMap,
1345
+ [submitErrKey]: undefined,
1346
+ },
1347
+ }))
1348
+ }
1264
1349
  })
1265
1350
 
1266
- /**
1267
- * when we have an error for onSubmit in the state, we want
1268
- * to clear the error as soon as the user enters a valid value in the field
1269
- */
1270
- const submitErrKey = getErrorMapKey('submit')
1271
- if (
1272
- this.state.errorMap[submitErrKey] &&
1273
- cause !== 'submit' &&
1274
- !hasErrored
1275
- ) {
1276
- this.baseStore.setState((prev) => ({
1277
- ...prev,
1278
- errorMap: {
1279
- ...prev.errorMap,
1280
- [submitErrKey]: undefined,
1281
- },
1282
- }))
1283
- }
1351
+ this.prevFieldsErrorMap = newFieldsErrorMap
1284
1352
 
1285
- return { hasErrored, fieldsErrorMap }
1353
+ return { hasErrored, fieldsErrorMap: newFieldsErrorMap }
1286
1354
  }
1287
1355
 
1288
1356
  /**
@@ -1319,7 +1387,6 @@ export class FormApi<
1319
1387
  | undefined
1320
1388
 
1321
1389
  for (const validateObj of validates) {
1322
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1323
1390
  if (!validateObj.validate) continue
1324
1391
  const key = getErrorMapKey(validateObj.cause)
1325
1392
  const fieldValidatorMeta = this.state.validationMetaMap[key]
@@ -1482,7 +1549,9 @@ export class FormApi<
1482
1549
  /**
1483
1550
  * Handles the form submission, performs validation, and calls the appropriate onSubmit or onInvalidSubmit callbacks.
1484
1551
  */
1485
- handleSubmit = async () => {
1552
+ handleSubmit(): Promise<void>
1553
+ handleSubmit(submitMeta: TSubmitMeta): Promise<void>
1554
+ async handleSubmit(submitMeta?: TSubmitMeta): Promise<void> {
1486
1555
  this.baseStore.setState((old) => ({
1487
1556
  ...old,
1488
1557
  // Submission attempts mark the form as not submitted
@@ -1536,7 +1605,11 @@ export class FormApi<
1536
1605
 
1537
1606
  try {
1538
1607
  // Run the submit code
1539
- await this.options.onSubmit?.({ value: this.state.values, formApi: this })
1608
+ await this.options.onSubmit?.({
1609
+ value: this.state.values,
1610
+ formApi: this,
1611
+ meta: submitMeta ?? this.options.onSubmitMeta,
1612
+ } as any)
1540
1613
 
1541
1614
  batch(() => {
1542
1615
  this.baseStore.setState((prev) => ({ ...prev, isSubmitted: true }))
@@ -14,6 +14,7 @@ export function formOptions<
14
14
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
15
15
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
16
16
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
17
+ TSubmitMeta,
17
18
  >(
18
19
  defaultOpts?: FormOptions<
19
20
  TFormData,
@@ -24,7 +25,8 @@ export function formOptions<
24
25
  TOnBlurAsync,
25
26
  TOnSubmit,
26
27
  TOnSubmitAsync,
27
- TOnServer
28
+ TOnServer,
29
+ TSubmitMeta
28
30
  >,
29
31
  ) {
30
32
  return defaultOpts
package/src/mergeForm.ts CHANGED
@@ -72,9 +72,20 @@ export function mutateMergeDeep(
72
72
  }
73
73
 
74
74
  export function mergeForm<TFormData>(
75
- baseForm: FormApi<NoInfer<TFormData>, any, any, any, any, any, any, any, any>,
75
+ baseForm: FormApi<
76
+ NoInfer<TFormData>,
77
+ any,
78
+ any,
79
+ any,
80
+ any,
81
+ any,
82
+ any,
83
+ any,
84
+ any,
85
+ any
86
+ >,
76
87
  state: Partial<
77
- FormApi<TFormData, any, any, any, any, any, any, any, any>['state']
88
+ FormApi<TFormData, any, any, any, any, any, any, any, any, any>['state']
78
89
  >,
79
90
  ) {
80
91
  mutateMergeDeep(baseForm.state, state)
package/src/metaHelper.ts CHANGED
@@ -18,6 +18,7 @@ export function metaHelper<
18
18
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
19
19
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
20
20
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
21
+ TSubmitMeta,
21
22
  >(
22
23
  formApi: FormApi<
23
24
  TFormData,
@@ -28,7 +29,8 @@ export function metaHelper<
28
29
  TOnBlurAsync,
29
30
  TOnSubmit,
30
31
  TOnSubmitAsync,
31
- TOnServer
32
+ TOnServer,
33
+ TSubmitMeta
32
34
  >,
33
35
  ) {
34
36
  function handleArrayFieldMetaShift(