@tanstack/form-core 1.25.0 → 1.27.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/src/metaHelper.ts CHANGED
@@ -6,7 +6,7 @@ import type {
6
6
  import type { AnyFieldMeta } from './FieldApi'
7
7
  import type { DeepKeys } from './util-types'
8
8
 
9
- type ArrayFieldMode = 'insert' | 'remove' | 'swap' | 'move'
9
+ type ValueFieldMode = 'insert' | 'remove' | 'swap' | 'move'
10
10
 
11
11
  export const defaultFieldMeta: AnyFieldMeta = {
12
12
  isValidating: false,
@@ -33,7 +33,7 @@ export function metaHelper<
33
33
  TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
34
34
  TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
35
35
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
36
- TSubmitMeta,
36
+ TSubmitMeta = never,
37
37
  >(
38
38
  formApi: FormApi<
39
39
  TFormData,
@@ -50,57 +50,148 @@ export function metaHelper<
50
50
  TSubmitMeta
51
51
  >,
52
52
  ) {
53
- function handleArrayFieldMetaShift(
53
+ /**
54
+ * Handle the meta shift caused from moving a field from one index to another.
55
+ */
56
+ function handleArrayMove(
54
57
  field: DeepKeys<TFormData>,
55
- index: number,
56
- mode: ArrayFieldMode,
57
- secondIndex?: number,
58
+ fromIndex: number,
59
+ toIndex: number,
58
60
  ) {
59
- const affectedFields = getAffectedFields(field, index, mode, secondIndex)
60
-
61
- const handlers = {
62
- insert: () => handleInsertMode(affectedFields, field, index),
63
- remove: () => handleRemoveMode(affectedFields),
64
- swap: () =>
65
- secondIndex !== undefined &&
66
- handleSwapMode(affectedFields, field, index, secondIndex),
67
- move: () =>
68
- secondIndex !== undefined &&
69
- handleMoveMode(affectedFields, field, index, secondIndex),
61
+ const affectedFields = getAffectedFields(field, fromIndex, 'move', toIndex)
62
+
63
+ const startIndex = Math.min(fromIndex, toIndex)
64
+ const endIndex = Math.max(fromIndex, toIndex)
65
+ for (let i = startIndex; i <= endIndex; i++) {
66
+ affectedFields.push(getFieldPath(field, i))
70
67
  }
71
68
 
72
- handlers[mode]()
69
+ // Store the original field meta that will be reapplied at the destination index
70
+ const fromFields = Object.keys(formApi.fieldInfo).reduce(
71
+ (fieldMap, fieldKey) => {
72
+ if (fieldKey.startsWith(getFieldPath(field, fromIndex))) {
73
+ fieldMap.set(
74
+ fieldKey as DeepKeys<TFormData>,
75
+ formApi.getFieldMeta(fieldKey as DeepKeys<TFormData>),
76
+ )
77
+ }
78
+ return fieldMap
79
+ },
80
+ new Map<DeepKeys<TFormData>, AnyFieldMeta | undefined>(),
81
+ )
82
+
83
+ shiftMeta(affectedFields, fromIndex < toIndex ? 'up' : 'down')
84
+
85
+ // Reapply the stored field meta at the destination index
86
+ Object.keys(formApi.fieldInfo)
87
+ .filter((fieldKey) => fieldKey.startsWith(getFieldPath(field, toIndex)))
88
+ .forEach((fieldKey) => {
89
+ const fromKey = fieldKey.replace(
90
+ getFieldPath(field, toIndex),
91
+ getFieldPath(field, fromIndex),
92
+ ) as DeepKeys<TFormData>
93
+
94
+ const fromMeta = fromFields.get(fromKey)
95
+ if (fromMeta) {
96
+ formApi.setFieldMeta(fieldKey as DeepKeys<TFormData>, fromMeta)
97
+ }
98
+ })
99
+ }
100
+
101
+ /**
102
+ * Handle the meta shift from removing a field at the specified index.
103
+ */
104
+ function handleArrayRemove(field: DeepKeys<TFormData>, index: number) {
105
+ const affectedFields = getAffectedFields(field, index, 'remove')
106
+
107
+ shiftMeta(affectedFields, 'up')
108
+ }
109
+
110
+ /**
111
+ * Handle the meta shift from swapping two fields at the specified indeces.
112
+ */
113
+ function handleArraySwap(
114
+ field: DeepKeys<TFormData>,
115
+ index: number,
116
+ secondIndex: number,
117
+ ) {
118
+ const affectedFields = getAffectedFields(field, index, 'swap', secondIndex)
119
+
120
+ affectedFields.forEach((fieldKey) => {
121
+ if (!fieldKey.toString().startsWith(getFieldPath(field, index))) {
122
+ return
123
+ }
124
+
125
+ const swappedKey = fieldKey
126
+ .toString()
127
+ .replace(
128
+ getFieldPath(field, index),
129
+ getFieldPath(field, secondIndex),
130
+ ) as DeepKeys<TFormData>
131
+
132
+ const [meta1, meta2] = [
133
+ formApi.getFieldMeta(fieldKey),
134
+ formApi.getFieldMeta(swappedKey),
135
+ ]
136
+
137
+ if (meta1) formApi.setFieldMeta(swappedKey, meta1)
138
+ if (meta2) formApi.setFieldMeta(fieldKey, meta2)
139
+ })
140
+ }
141
+
142
+ /**
143
+ * Handle the meta shift from inserting a field at the specified index.
144
+ */
145
+ function handleArrayInsert(field: DeepKeys<TFormData>, insertIndex: number) {
146
+ const affectedFields = getAffectedFields(field, insertIndex, 'insert')
147
+
148
+ shiftMeta(affectedFields, 'down')
149
+
150
+ affectedFields.forEach((fieldKey) => {
151
+ if (fieldKey.toString().startsWith(getFieldPath(field, insertIndex))) {
152
+ formApi.setFieldMeta(fieldKey, getEmptyFieldMeta())
153
+ }
154
+ })
73
155
  }
74
156
 
75
- function getFieldPath(field: DeepKeys<TFormData>, index: number): string {
76
- return `${field}[${index}]`
157
+ function getFieldPath(
158
+ field: DeepKeys<TFormData>,
159
+ index: number,
160
+ ): DeepKeys<TFormData> {
161
+ return `${field}[${index}]` as DeepKeys<TFormData>
77
162
  }
78
163
 
79
164
  function getAffectedFields(
80
165
  field: DeepKeys<TFormData>,
81
166
  index: number,
82
- mode: ArrayFieldMode,
167
+ mode: ValueFieldMode,
83
168
  secondIndex?: number,
84
169
  ): DeepKeys<TFormData>[] {
85
170
  const affectedFieldKeys = [getFieldPath(field, index)]
86
171
 
87
- if (mode === 'swap') {
88
- affectedFieldKeys.push(getFieldPath(field, secondIndex!))
89
- } else if (mode === 'move') {
90
- const [startIndex, endIndex] = [
91
- Math.min(index, secondIndex!),
92
- Math.max(index, secondIndex!),
93
- ]
94
- for (let i = startIndex; i <= endIndex; i++) {
95
- affectedFieldKeys.push(getFieldPath(field, i))
172
+ switch (mode) {
173
+ case 'swap':
174
+ affectedFieldKeys.push(getFieldPath(field, secondIndex!))
175
+ break
176
+ case 'move': {
177
+ const [startIndex, endIndex] = [
178
+ Math.min(index, secondIndex!),
179
+ Math.max(index, secondIndex!),
180
+ ]
181
+ for (let i = startIndex; i <= endIndex; i++) {
182
+ affectedFieldKeys.push(getFieldPath(field, i))
183
+ }
184
+ break
96
185
  }
97
- } else {
98
- const currentValue = formApi.getFieldValue(field)
99
- const fieldItems = Array.isArray(currentValue)
100
- ? (currentValue as Array<unknown>).length
101
- : 0
102
- for (let i = index + 1; i < fieldItems; i++) {
103
- affectedFieldKeys.push(getFieldPath(field, i))
186
+ default: {
187
+ const currentValue = formApi.getFieldValue(field)
188
+ const fieldItems = Array.isArray(currentValue)
189
+ ? (currentValue as Array<unknown>).length
190
+ : 0
191
+ for (let i = index + 1; i < fieldItems; i++) {
192
+ affectedFieldKeys.push(getFieldPath(field, i))
193
+ }
194
+ break
104
195
  }
105
196
  }
106
197
 
@@ -137,85 +228,10 @@ export function metaHelper<
137
228
 
138
229
  const getEmptyFieldMeta = (): AnyFieldMeta => defaultFieldMeta
139
230
 
140
- const handleInsertMode = (
141
- fields: DeepKeys<TFormData>[],
142
- field: DeepKeys<TFormData>,
143
- insertIndex: number,
144
- ) => {
145
- shiftMeta(fields, 'down')
146
-
147
- fields.forEach((fieldKey) => {
148
- if (fieldKey.toString().startsWith(getFieldPath(field, insertIndex))) {
149
- formApi.setFieldMeta(fieldKey, getEmptyFieldMeta())
150
- }
151
- })
231
+ return {
232
+ handleArrayMove,
233
+ handleArrayRemove,
234
+ handleArraySwap,
235
+ handleArrayInsert,
152
236
  }
153
-
154
- const handleRemoveMode = (fields: DeepKeys<TFormData>[]) => {
155
- shiftMeta(fields, 'up')
156
- }
157
-
158
- const handleMoveMode = (
159
- fields: DeepKeys<TFormData>[],
160
- field: DeepKeys<TFormData>,
161
- fromIndex: number,
162
- toIndex: number,
163
- ) => {
164
- // Store the original field meta that will be reapplied at the destination index
165
- const fromFields = new Map(
166
- Object.keys(formApi.fieldInfo)
167
- .filter((fieldKey) =>
168
- fieldKey.startsWith(getFieldPath(field, fromIndex)),
169
- )
170
- .map((fieldKey) => [
171
- fieldKey as DeepKeys<TFormData>,
172
- formApi.getFieldMeta(fieldKey as DeepKeys<TFormData>),
173
- ]),
174
- )
175
-
176
- shiftMeta(fields, fromIndex < toIndex ? 'up' : 'down')
177
-
178
- // Reapply the stored field meta at the destination index
179
- Object.keys(formApi.fieldInfo)
180
- .filter((fieldKey) => fieldKey.startsWith(getFieldPath(field, toIndex)))
181
- .forEach((fieldKey) => {
182
- const fromKey = fieldKey.replace(
183
- getFieldPath(field, toIndex),
184
- getFieldPath(field, fromIndex),
185
- ) as DeepKeys<TFormData>
186
-
187
- const fromMeta = fromFields.get(fromKey)
188
- if (fromMeta) {
189
- formApi.setFieldMeta(fieldKey as DeepKeys<TFormData>, fromMeta)
190
- }
191
- })
192
- }
193
-
194
- const handleSwapMode = (
195
- fields: DeepKeys<TFormData>[],
196
- field: DeepKeys<TFormData>,
197
- index: number,
198
- secondIndex: number,
199
- ) => {
200
- fields.forEach((fieldKey) => {
201
- if (!fieldKey.toString().startsWith(getFieldPath(field, index))) return
202
-
203
- const swappedKey = fieldKey
204
- .toString()
205
- .replace(
206
- getFieldPath(field, index),
207
- getFieldPath(field, secondIndex),
208
- ) as DeepKeys<TFormData>
209
-
210
- const [meta1, meta2] = [
211
- formApi.getFieldMeta(fieldKey),
212
- formApi.getFieldMeta(swappedKey),
213
- ]
214
-
215
- if (meta1) formApi.setFieldMeta(swappedKey, meta1)
216
- if (meta2) formApi.setFieldMeta(fieldKey, meta2)
217
- })
218
- }
219
-
220
- return { handleArrayFieldMetaShift }
221
237
  }
package/src/utils.ts CHANGED
@@ -31,9 +31,9 @@ export function functionalUpdate<TInput, TOutput = TInput>(
31
31
  * Get a value from an object using a path, including dot notation.
32
32
  * @private
33
33
  */
34
- export function getBy(obj: any, path: any) {
34
+ export function getBy(obj: unknown, path: string | (string | number)[]): any {
35
35
  const pathObj = makePathArray(path)
36
- return pathObj.reduce((current: any, pathPart: any) => {
36
+ return pathObj.reduce((current: any, pathPart) => {
37
37
  if (current === null) return null
38
38
  if (typeof current !== 'undefined') {
39
39
  return current[pathPart]
@@ -108,7 +108,10 @@ export function deleteBy(obj: any, _path: any) {
108
108
 
109
109
  const key = path.shift()
110
110
 
111
- if (typeof key === 'string') {
111
+ if (
112
+ typeof key === 'string' ||
113
+ (typeof key === 'number' && !Array.isArray(parent))
114
+ ) {
112
115
  if (typeof parent === 'object') {
113
116
  return {
114
117
  ...parent,