@tanstack/form-core 0.41.4 → 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.
@@ -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
+ }