@tanstack/form-core 0.41.3 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/FieldApi.cjs +3 -3
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +3 -1
- package/dist/cjs/FormApi.cjs +21 -4
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +1 -4
- package/dist/cjs/metaHelper.cjs +116 -0
- package/dist/cjs/metaHelper.cjs.map +1 -0
- package/dist/cjs/metaHelper.d.cts +8 -0
- package/dist/esm/FieldApi.d.ts +3 -1
- package/dist/esm/FieldApi.js +3 -3
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +1 -4
- package/dist/esm/FormApi.js +21 -4
- package/dist/esm/FormApi.js.map +1 -1
- package/dist/esm/metaHelper.d.ts +8 -0
- package/dist/esm/metaHelper.js +116 -0
- package/dist/esm/metaHelper.js.map +1 -0
- package/package.json +1 -1
- package/src/FieldApi.ts +7 -2
- package/src/FormApi.ts +30 -6
- package/src/metaHelper.ts +184 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FormApi } from './FormApi.js';
|
|
2
|
+
import { Validator } from './types.js';
|
|
3
|
+
import { DeepKeys } from './util-types.js';
|
|
4
|
+
type ArrayFieldMode = 'insert' | 'remove' | 'swap' | 'move';
|
|
5
|
+
export declare function metaHelper<TFormData, TFormValidator extends Validator<TFormData, unknown> | undefined = undefined>(formApi: FormApi<TFormData, TFormValidator>): {
|
|
6
|
+
handleArrayFieldMetaShift: (field: DeepKeys<TFormData>, index: number, mode: ArrayFieldMode, secondIndex?: number) => void;
|
|
7
|
+
};
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
function metaHelper(formApi) {
|
|
2
|
+
function handleArrayFieldMetaShift(field, index, mode, secondIndex) {
|
|
3
|
+
const affectedFields = getAffectedFields(field, index, mode, secondIndex);
|
|
4
|
+
const handlers = {
|
|
5
|
+
insert: () => handleInsertMode(affectedFields, field, index),
|
|
6
|
+
remove: () => handleRemoveMode(affectedFields),
|
|
7
|
+
swap: () => secondIndex !== void 0 && handleSwapMode(affectedFields, field, index, secondIndex),
|
|
8
|
+
move: () => secondIndex !== void 0 && handleMoveMode(affectedFields, field, index, secondIndex)
|
|
9
|
+
};
|
|
10
|
+
handlers[mode]();
|
|
11
|
+
}
|
|
12
|
+
function getFieldPath(field, index) {
|
|
13
|
+
return `${field}[${index}]`;
|
|
14
|
+
}
|
|
15
|
+
function getAffectedFields(field, index, mode, secondIndex) {
|
|
16
|
+
const affectedFieldKeys = [getFieldPath(field, index)];
|
|
17
|
+
if (mode === "swap") {
|
|
18
|
+
affectedFieldKeys.push(getFieldPath(field, secondIndex));
|
|
19
|
+
} else if (mode === "move") {
|
|
20
|
+
const [startIndex, endIndex] = [
|
|
21
|
+
Math.min(index, secondIndex),
|
|
22
|
+
Math.max(index, secondIndex)
|
|
23
|
+
];
|
|
24
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
25
|
+
affectedFieldKeys.push(getFieldPath(field, i));
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
const currentValue = formApi.getFieldValue(field);
|
|
29
|
+
const fieldItems = Array.isArray(currentValue) ? currentValue.length : 0;
|
|
30
|
+
for (let i = index + 1; i < fieldItems; i++) {
|
|
31
|
+
affectedFieldKeys.push(getFieldPath(field, i));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return Object.keys(formApi.fieldInfo).filter(
|
|
35
|
+
(fieldKey) => affectedFieldKeys.some((key) => fieldKey.startsWith(key))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
function updateIndex(fieldKey, direction) {
|
|
39
|
+
return fieldKey.replace(/\[(\d+)\]/, (_, num) => {
|
|
40
|
+
const currIndex = parseInt(num, 10);
|
|
41
|
+
const newIndex = direction === "up" ? currIndex + 1 : Math.max(0, currIndex - 1);
|
|
42
|
+
return `[${newIndex}]`;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function shiftMeta(fields, direction) {
|
|
46
|
+
const sortedFields = direction === "up" ? fields : [...fields].reverse();
|
|
47
|
+
sortedFields.forEach((fieldKey) => {
|
|
48
|
+
const nextFieldKey = updateIndex(fieldKey.toString(), direction);
|
|
49
|
+
const nextFieldMeta = formApi.getFieldMeta(nextFieldKey);
|
|
50
|
+
if (nextFieldMeta) {
|
|
51
|
+
formApi.setFieldMeta(fieldKey, nextFieldMeta);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const getEmptyFieldMeta = () => ({
|
|
56
|
+
isValidating: false,
|
|
57
|
+
isTouched: false,
|
|
58
|
+
isBlurred: false,
|
|
59
|
+
isDirty: false,
|
|
60
|
+
isPristine: true,
|
|
61
|
+
errors: [],
|
|
62
|
+
errorMap: {}
|
|
63
|
+
});
|
|
64
|
+
const handleInsertMode = (fields, field, insertIndex) => {
|
|
65
|
+
shiftMeta(fields, "down");
|
|
66
|
+
fields.forEach((fieldKey) => {
|
|
67
|
+
if (fieldKey.toString().startsWith(getFieldPath(field, insertIndex))) {
|
|
68
|
+
formApi.setFieldMeta(fieldKey, getEmptyFieldMeta());
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const handleRemoveMode = (fields) => {
|
|
73
|
+
shiftMeta(fields, "up");
|
|
74
|
+
};
|
|
75
|
+
const handleMoveMode = (fields, field, fromIndex, toIndex) => {
|
|
76
|
+
const fromFields = new Map(
|
|
77
|
+
Object.keys(formApi.fieldInfo).filter(
|
|
78
|
+
(fieldKey) => fieldKey.startsWith(getFieldPath(field, fromIndex))
|
|
79
|
+
).map((fieldKey) => [
|
|
80
|
+
fieldKey,
|
|
81
|
+
formApi.getFieldMeta(fieldKey)
|
|
82
|
+
])
|
|
83
|
+
);
|
|
84
|
+
shiftMeta(fields, fromIndex < toIndex ? "up" : "down");
|
|
85
|
+
Object.keys(formApi.fieldInfo).filter((fieldKey) => fieldKey.startsWith(getFieldPath(field, toIndex))).forEach((fieldKey) => {
|
|
86
|
+
const fromKey = fieldKey.replace(
|
|
87
|
+
getFieldPath(field, toIndex),
|
|
88
|
+
getFieldPath(field, fromIndex)
|
|
89
|
+
);
|
|
90
|
+
const fromMeta = fromFields.get(fromKey);
|
|
91
|
+
if (fromMeta) {
|
|
92
|
+
formApi.setFieldMeta(fieldKey, fromMeta);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
const handleSwapMode = (fields, field, index, secondIndex) => {
|
|
97
|
+
fields.forEach((fieldKey) => {
|
|
98
|
+
if (!fieldKey.toString().startsWith(getFieldPath(field, index))) return;
|
|
99
|
+
const swappedKey = fieldKey.toString().replace(
|
|
100
|
+
getFieldPath(field, index),
|
|
101
|
+
getFieldPath(field, secondIndex)
|
|
102
|
+
);
|
|
103
|
+
const [meta1, meta2] = [
|
|
104
|
+
formApi.getFieldMeta(fieldKey),
|
|
105
|
+
formApi.getFieldMeta(swappedKey)
|
|
106
|
+
];
|
|
107
|
+
if (meta1) formApi.setFieldMeta(swappedKey, meta1);
|
|
108
|
+
if (meta2) formApi.setFieldMeta(fieldKey, meta2);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
return { handleArrayFieldMetaShift };
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
metaHelper
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=metaHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metaHelper.js","sources":["../../src/metaHelper.ts"],"sourcesContent":["import type { FieldMeta } from './FieldApi'\nimport type { FormApi } from './FormApi'\nimport type { Validator } from './types'\nimport type { DeepKeys } from './util-types'\n\ntype ArrayFieldMode = 'insert' | 'remove' | 'swap' | 'move'\n\nexport function metaHelper<\n TFormData,\n TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,\n>(formApi: FormApi<TFormData, TFormValidator>) {\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 = (): FieldMeta => ({\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
package/src/FieldApi.ts
CHANGED
|
@@ -989,12 +989,15 @@ export class FieldApi<
|
|
|
989
989
|
*/
|
|
990
990
|
validate = (
|
|
991
991
|
cause: ValidationCause,
|
|
992
|
+
opts?: { skipFormValidation?: boolean },
|
|
992
993
|
): ValidationError[] | Promise<ValidationError[]> => {
|
|
993
994
|
// If the field is pristine, do not validate
|
|
994
995
|
if (!this.state.meta.isTouched) return []
|
|
995
996
|
|
|
996
997
|
// Attempt to sync validate first
|
|
997
|
-
const { fieldsErrorMap } =
|
|
998
|
+
const { fieldsErrorMap } = opts?.skipFormValidation
|
|
999
|
+
? { fieldsErrorMap: {} as never }
|
|
1000
|
+
: this.form.validateSync(cause)
|
|
998
1001
|
const { hasErrored } = this.validateSync(
|
|
999
1002
|
cause,
|
|
1000
1003
|
fieldsErrorMap[this.name] ?? {},
|
|
@@ -1008,7 +1011,9 @@ export class FieldApi<
|
|
|
1008
1011
|
}
|
|
1009
1012
|
|
|
1010
1013
|
// No error? Attempt async validation
|
|
1011
|
-
const formValidationResultPromise =
|
|
1014
|
+
const formValidationResultPromise = opts?.skipFormValidation
|
|
1015
|
+
? Promise.resolve({})
|
|
1016
|
+
: this.form.validateAsync(cause)
|
|
1012
1017
|
return this.validateAsync(cause, formValidationResultPromise)
|
|
1013
1018
|
}
|
|
1014
1019
|
|
package/src/FormApi.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
isStandardSchemaValidator,
|
|
14
14
|
standardSchemaValidator,
|
|
15
15
|
} from './standardSchemaValidator'
|
|
16
|
+
import { metaHelper } from './metaHelper'
|
|
16
17
|
import type { StandardSchemaV1 } from './standardSchemaValidator'
|
|
17
18
|
import type { FieldApi, FieldMeta, FieldMetaBase } from './FieldApi'
|
|
18
19
|
import type {
|
|
@@ -722,7 +723,7 @@ export class FormApi<
|
|
|
722
723
|
}
|
|
723
724
|
|
|
724
725
|
/**
|
|
725
|
-
* Validates
|
|
726
|
+
* Validates all fields using the correct handlers for a given validation cause.
|
|
726
727
|
*/
|
|
727
728
|
validateAllFields = async (cause: ValidationCause) => {
|
|
728
729
|
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
|
|
@@ -735,7 +736,9 @@ export class FormApi<
|
|
|
735
736
|
// Validate the field
|
|
736
737
|
fieldValidationPromises.push(
|
|
737
738
|
// Remember, `validate` is either a sync operation or a promise
|
|
738
|
-
Promise.resolve().then(() =>
|
|
739
|
+
Promise.resolve().then(() =>
|
|
740
|
+
fieldInstance.validate(cause, { skipFormValidation: true }),
|
|
741
|
+
),
|
|
739
742
|
)
|
|
740
743
|
// If any fields are not touched
|
|
741
744
|
if (!field.instance.state.meta.isTouched) {
|
|
@@ -1076,9 +1079,19 @@ export class FormApi<
|
|
|
1076
1079
|
this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false }))
|
|
1077
1080
|
}
|
|
1078
1081
|
|
|
1079
|
-
// Validate form and all fields
|
|
1080
1082
|
await this.validateAllFields('submit')
|
|
1081
1083
|
|
|
1084
|
+
if (!this.state.isFieldsValid) {
|
|
1085
|
+
done()
|
|
1086
|
+
this.options.onSubmitInvalid?.({
|
|
1087
|
+
value: this.state.values,
|
|
1088
|
+
formApi: this,
|
|
1089
|
+
})
|
|
1090
|
+
return
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
await this.validate('submit')
|
|
1094
|
+
|
|
1082
1095
|
// Fields are invalid, do not submit
|
|
1083
1096
|
if (!this.state.isValid) {
|
|
1084
1097
|
done()
|
|
@@ -1253,9 +1266,6 @@ export class FormApi<
|
|
|
1253
1266
|
this.validateField(field, 'change')
|
|
1254
1267
|
}
|
|
1255
1268
|
|
|
1256
|
-
/**
|
|
1257
|
-
* Inserts a value into an array field at the specified index, shifting the subsequent values to the right.
|
|
1258
|
-
*/
|
|
1259
1269
|
insertFieldValue = async <TField extends DeepKeys<TFormData>>(
|
|
1260
1270
|
field: TField,
|
|
1261
1271
|
index: number,
|
|
@@ -1278,6 +1288,11 @@ export class FormApi<
|
|
|
1278
1288
|
|
|
1279
1289
|
// Validate the whole array + all fields that have shifted
|
|
1280
1290
|
await this.validateField(field, 'change')
|
|
1291
|
+
|
|
1292
|
+
// Shift down all meta after validating to make sure the new field has been mounted
|
|
1293
|
+
metaHelper(this).handleArrayFieldMetaShift(field, index, 'insert')
|
|
1294
|
+
|
|
1295
|
+
await this.validateArrayFieldsStartingFrom(field, index, 'change')
|
|
1281
1296
|
}
|
|
1282
1297
|
|
|
1283
1298
|
/**
|
|
@@ -1330,6 +1345,9 @@ export class FormApi<
|
|
|
1330
1345
|
opts,
|
|
1331
1346
|
)
|
|
1332
1347
|
|
|
1348
|
+
// Shift up all meta
|
|
1349
|
+
metaHelper(this).handleArrayFieldMetaShift(field, index, 'remove')
|
|
1350
|
+
|
|
1333
1351
|
if (lastIndex !== null) {
|
|
1334
1352
|
const start = `${field}[${lastIndex}]`
|
|
1335
1353
|
const fieldsToDelete = Object.keys(this.fieldInfo).filter((f) =>
|
|
@@ -1364,6 +1382,9 @@ export class FormApi<
|
|
|
1364
1382
|
opts,
|
|
1365
1383
|
)
|
|
1366
1384
|
|
|
1385
|
+
// Swap meta
|
|
1386
|
+
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'swap', index2)
|
|
1387
|
+
|
|
1367
1388
|
// Validate the whole array
|
|
1368
1389
|
this.validateField(field, 'change')
|
|
1369
1390
|
// Validate the swapped fields
|
|
@@ -1389,6 +1410,9 @@ export class FormApi<
|
|
|
1389
1410
|
opts,
|
|
1390
1411
|
)
|
|
1391
1412
|
|
|
1413
|
+
// Move meta between index1 and index2
|
|
1414
|
+
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'move', index2)
|
|
1415
|
+
|
|
1392
1416
|
// Validate the whole array
|
|
1393
1417
|
this.validateField(field, 'change')
|
|
1394
1418
|
// Validate the moved fields
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { FieldMeta } from './FieldApi'
|
|
2
|
+
import type { FormApi } from './FormApi'
|
|
3
|
+
import type { Validator } from './types'
|
|
4
|
+
import type { DeepKeys } from './util-types'
|
|
5
|
+
|
|
6
|
+
type ArrayFieldMode = 'insert' | 'remove' | 'swap' | 'move'
|
|
7
|
+
|
|
8
|
+
export function metaHelper<
|
|
9
|
+
TFormData,
|
|
10
|
+
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
11
|
+
>(formApi: FormApi<TFormData, TFormValidator>) {
|
|
12
|
+
function handleArrayFieldMetaShift(
|
|
13
|
+
field: DeepKeys<TFormData>,
|
|
14
|
+
index: number,
|
|
15
|
+
mode: ArrayFieldMode,
|
|
16
|
+
secondIndex?: number,
|
|
17
|
+
) {
|
|
18
|
+
const affectedFields = getAffectedFields(field, index, mode, secondIndex)
|
|
19
|
+
|
|
20
|
+
const handlers = {
|
|
21
|
+
insert: () => handleInsertMode(affectedFields, field, index),
|
|
22
|
+
remove: () => handleRemoveMode(affectedFields),
|
|
23
|
+
swap: () =>
|
|
24
|
+
secondIndex !== undefined &&
|
|
25
|
+
handleSwapMode(affectedFields, field, index, secondIndex),
|
|
26
|
+
move: () =>
|
|
27
|
+
secondIndex !== undefined &&
|
|
28
|
+
handleMoveMode(affectedFields, field, index, secondIndex),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
handlers[mode]()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getFieldPath(field: DeepKeys<TFormData>, index: number): string {
|
|
35
|
+
return `${field}[${index}]`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getAffectedFields(
|
|
39
|
+
field: DeepKeys<TFormData>,
|
|
40
|
+
index: number,
|
|
41
|
+
mode: ArrayFieldMode,
|
|
42
|
+
secondIndex?: number,
|
|
43
|
+
): DeepKeys<TFormData>[] {
|
|
44
|
+
const affectedFieldKeys = [getFieldPath(field, index)]
|
|
45
|
+
|
|
46
|
+
if (mode === 'swap') {
|
|
47
|
+
affectedFieldKeys.push(getFieldPath(field, secondIndex!))
|
|
48
|
+
} else if (mode === 'move') {
|
|
49
|
+
const [startIndex, endIndex] = [
|
|
50
|
+
Math.min(index, secondIndex!),
|
|
51
|
+
Math.max(index, secondIndex!),
|
|
52
|
+
]
|
|
53
|
+
for (let i = startIndex; i <= endIndex; i++) {
|
|
54
|
+
affectedFieldKeys.push(getFieldPath(field, i))
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
const currentValue = formApi.getFieldValue(field)
|
|
58
|
+
const fieldItems = Array.isArray(currentValue) ? currentValue.length : 0
|
|
59
|
+
for (let i = index + 1; i < fieldItems; i++) {
|
|
60
|
+
affectedFieldKeys.push(getFieldPath(field, i))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return Object.keys(formApi.fieldInfo).filter((fieldKey) =>
|
|
65
|
+
affectedFieldKeys.some((key) => fieldKey.startsWith(key)),
|
|
66
|
+
) as DeepKeys<TFormData>[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function updateIndex(
|
|
70
|
+
fieldKey: string,
|
|
71
|
+
direction: 'up' | 'down',
|
|
72
|
+
): DeepKeys<TFormData> {
|
|
73
|
+
return fieldKey.replace(/\[(\d+)\]/, (_, num) => {
|
|
74
|
+
const currIndex = parseInt(num, 10)
|
|
75
|
+
const newIndex =
|
|
76
|
+
direction === 'up' ? currIndex + 1 : Math.max(0, currIndex - 1)
|
|
77
|
+
return `[${newIndex}]`
|
|
78
|
+
}) as DeepKeys<TFormData>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function shiftMeta(fields: DeepKeys<TFormData>[], direction: 'up' | 'down') {
|
|
82
|
+
const sortedFields = direction === 'up' ? fields : [...fields].reverse()
|
|
83
|
+
|
|
84
|
+
sortedFields.forEach((fieldKey) => {
|
|
85
|
+
const nextFieldKey = updateIndex(fieldKey.toString(), direction)
|
|
86
|
+
const nextFieldMeta = formApi.getFieldMeta(nextFieldKey)
|
|
87
|
+
if (nextFieldMeta) {
|
|
88
|
+
formApi.setFieldMeta(fieldKey, nextFieldMeta)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const getEmptyFieldMeta = (): FieldMeta => ({
|
|
94
|
+
isValidating: false,
|
|
95
|
+
isTouched: false,
|
|
96
|
+
isBlurred: false,
|
|
97
|
+
isDirty: false,
|
|
98
|
+
isPristine: true,
|
|
99
|
+
errors: [],
|
|
100
|
+
errorMap: {},
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const handleInsertMode = (
|
|
104
|
+
fields: DeepKeys<TFormData>[],
|
|
105
|
+
field: DeepKeys<TFormData>,
|
|
106
|
+
insertIndex: number,
|
|
107
|
+
) => {
|
|
108
|
+
shiftMeta(fields, 'down')
|
|
109
|
+
|
|
110
|
+
fields.forEach((fieldKey) => {
|
|
111
|
+
if (fieldKey.toString().startsWith(getFieldPath(field, insertIndex))) {
|
|
112
|
+
formApi.setFieldMeta(fieldKey, getEmptyFieldMeta())
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const handleRemoveMode = (fields: DeepKeys<TFormData>[]) => {
|
|
118
|
+
shiftMeta(fields, 'up')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const handleMoveMode = (
|
|
122
|
+
fields: DeepKeys<TFormData>[],
|
|
123
|
+
field: DeepKeys<TFormData>,
|
|
124
|
+
fromIndex: number,
|
|
125
|
+
toIndex: number,
|
|
126
|
+
) => {
|
|
127
|
+
// Store the original field meta that will be reapplied at the destination index
|
|
128
|
+
const fromFields = new Map(
|
|
129
|
+
Object.keys(formApi.fieldInfo)
|
|
130
|
+
.filter((fieldKey) =>
|
|
131
|
+
fieldKey.startsWith(getFieldPath(field, fromIndex)),
|
|
132
|
+
)
|
|
133
|
+
.map((fieldKey) => [
|
|
134
|
+
fieldKey as DeepKeys<TFormData>,
|
|
135
|
+
formApi.getFieldMeta(fieldKey as DeepKeys<TFormData>),
|
|
136
|
+
]),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
shiftMeta(fields, fromIndex < toIndex ? 'up' : 'down')
|
|
140
|
+
|
|
141
|
+
// Reapply the stored field meta at the destination index
|
|
142
|
+
Object.keys(formApi.fieldInfo)
|
|
143
|
+
.filter((fieldKey) => fieldKey.startsWith(getFieldPath(field, toIndex)))
|
|
144
|
+
.forEach((fieldKey) => {
|
|
145
|
+
const fromKey = fieldKey.replace(
|
|
146
|
+
getFieldPath(field, toIndex),
|
|
147
|
+
getFieldPath(field, fromIndex),
|
|
148
|
+
) as DeepKeys<TFormData>
|
|
149
|
+
|
|
150
|
+
const fromMeta = fromFields.get(fromKey)
|
|
151
|
+
if (fromMeta) {
|
|
152
|
+
formApi.setFieldMeta(fieldKey as DeepKeys<TFormData>, fromMeta)
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const handleSwapMode = (
|
|
158
|
+
fields: DeepKeys<TFormData>[],
|
|
159
|
+
field: DeepKeys<TFormData>,
|
|
160
|
+
index: number,
|
|
161
|
+
secondIndex: number,
|
|
162
|
+
) => {
|
|
163
|
+
fields.forEach((fieldKey) => {
|
|
164
|
+
if (!fieldKey.toString().startsWith(getFieldPath(field, index))) return
|
|
165
|
+
|
|
166
|
+
const swappedKey = fieldKey
|
|
167
|
+
.toString()
|
|
168
|
+
.replace(
|
|
169
|
+
getFieldPath(field, index),
|
|
170
|
+
getFieldPath(field, secondIndex),
|
|
171
|
+
) as DeepKeys<TFormData>
|
|
172
|
+
|
|
173
|
+
const [meta1, meta2] = [
|
|
174
|
+
formApi.getFieldMeta(fieldKey),
|
|
175
|
+
formApi.getFieldMeta(swappedKey),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
if (meta1) formApi.setFieldMeta(swappedKey, meta1)
|
|
179
|
+
if (meta2) formApi.setFieldMeta(fieldKey, meta2)
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { handleArrayFieldMetaShift }
|
|
184
|
+
}
|