@tanstack/form-core 0.23.1 → 0.23.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.
- package/dist/cjs/FieldApi.cjs +17 -0
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +220 -1
- package/dist/cjs/FormApi.cjs +14 -0
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +238 -0
- package/dist/cjs/mergeForm.cjs.map +1 -1
- package/dist/cjs/mergeForm.d.cts +3 -0
- package/dist/cjs/types.d.cts +14 -0
- package/dist/cjs/util-types.d.cts +18 -3
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +24 -0
- package/dist/esm/FieldApi.d.ts +220 -1
- package/dist/esm/FieldApi.js +17 -0
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +238 -0
- package/dist/esm/FormApi.js +14 -0
- package/dist/esm/FormApi.js.map +1 -1
- package/dist/esm/mergeForm.d.ts +3 -0
- package/dist/esm/mergeForm.js.map +1 -1
- package/dist/esm/types.d.ts +14 -0
- package/dist/esm/util-types.d.ts +18 -3
- package/dist/esm/utils.d.ts +24 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +7 -3
- package/src/FieldApi.ts +249 -4
- package/src/FormApi.ts +249 -12
- package/src/mergeForm.ts +3 -0
- package/src/types.ts +14 -2
- package/src/util-types.ts +18 -4
- package/src/utils.ts +24 -0
- package/src/tests/FieldApi.spec.ts +0 -1184
- package/src/tests/FieldApi.test-d.ts +0 -149
- package/src/tests/FormApi.spec.ts +0 -1523
- package/src/tests/formOptions.test.ts +0 -25
- package/src/tests/mutateMergeDeep.spec.ts +0 -32
- package/src/tests/util-types.test-d.ts +0 -171
- package/src/tests/utils.spec.ts +0 -131
- package/src/tests/utils.ts +0 -5
package/src/FormApi.ts
CHANGED
|
@@ -19,6 +19,9 @@ import type {
|
|
|
19
19
|
Validator,
|
|
20
20
|
} from './types'
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
22
25
|
export type FormValidateFn<
|
|
23
26
|
TFormData,
|
|
24
27
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
@@ -27,6 +30,9 @@ export type FormValidateFn<
|
|
|
27
30
|
formApi: FormApi<TFormData, TFormValidator>
|
|
28
31
|
}) => ValidationError
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
30
36
|
export type FormValidateOrFn<
|
|
31
37
|
TFormData,
|
|
32
38
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
@@ -34,6 +40,9 @@ export type FormValidateOrFn<
|
|
|
34
40
|
? TFN
|
|
35
41
|
: FormValidateFn<TFormData, TFormValidator>
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
37
46
|
export type FormValidateAsyncFn<
|
|
38
47
|
TFormData,
|
|
39
48
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
@@ -43,6 +52,9 @@ export type FormValidateAsyncFn<
|
|
|
43
52
|
signal: AbortSignal
|
|
44
53
|
}) => ValidationError | Promise<ValidationError>
|
|
45
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
46
58
|
export type FormAsyncValidateOrFn<
|
|
47
59
|
TFormData,
|
|
48
60
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
@@ -54,17 +66,41 @@ export interface FormValidators<
|
|
|
54
66
|
TFormData,
|
|
55
67
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
56
68
|
> {
|
|
69
|
+
/**
|
|
70
|
+
* Optional function that fires as soon as the component mounts.
|
|
71
|
+
*/
|
|
57
72
|
onMount?: FormValidateOrFn<TFormData, TFormValidator>
|
|
73
|
+
/**
|
|
74
|
+
* Optional function that checks the validity of your data whenever a value changes
|
|
75
|
+
*/
|
|
58
76
|
onChange?: FormValidateOrFn<TFormData, TFormValidator>
|
|
77
|
+
/**
|
|
78
|
+
* Optional onChange asynchronous counterpart to onChange. Useful for more complex validation logic that might involve server requests.
|
|
79
|
+
*/
|
|
59
80
|
onChangeAsync?: FormAsyncValidateOrFn<TFormData, TFormValidator>
|
|
81
|
+
/**
|
|
82
|
+
* The default time in milliseconds that if set to a number larger than 0, will debounce the async validation event by this length of time in milliseconds.
|
|
83
|
+
*/
|
|
60
84
|
onChangeAsyncDebounceMs?: number
|
|
85
|
+
/**
|
|
86
|
+
* Optional function that validates the form data when a field loses focus, returns a ValidationError
|
|
87
|
+
*/
|
|
61
88
|
onBlur?: FormValidateOrFn<TFormData, TFormValidator>
|
|
89
|
+
/**
|
|
90
|
+
* Optional onBlur asynchronous validation method for when a field loses focus return a `ValidationError` or a promise of `Promise<ValidationError>`
|
|
91
|
+
*/
|
|
62
92
|
onBlurAsync?: FormAsyncValidateOrFn<TFormData, TFormValidator>
|
|
93
|
+
/**
|
|
94
|
+
* The default time in milliseconds that if set to a number larger than 0, will debounce the async validation event by this length of time in milliseconds.
|
|
95
|
+
*/
|
|
63
96
|
onBlurAsyncDebounceMs?: number
|
|
64
97
|
onSubmit?: FormValidateOrFn<TFormData, TFormValidator>
|
|
65
98
|
onSubmitAsync?: FormAsyncValidateOrFn<TFormData, TFormValidator>
|
|
66
99
|
}
|
|
67
100
|
|
|
101
|
+
/**
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
68
104
|
export interface FormTransform<
|
|
69
105
|
TFormData,
|
|
70
106
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
@@ -75,20 +111,47 @@ export interface FormTransform<
|
|
|
75
111
|
deps: unknown[]
|
|
76
112
|
}
|
|
77
113
|
|
|
114
|
+
/**
|
|
115
|
+
* An object representing the options for a form.
|
|
116
|
+
*/
|
|
78
117
|
export interface FormOptions<
|
|
79
118
|
TFormData,
|
|
80
119
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
81
120
|
> {
|
|
121
|
+
/**
|
|
122
|
+
* Set initial values for your form.
|
|
123
|
+
*/
|
|
82
124
|
defaultValues?: TFormData
|
|
125
|
+
/**
|
|
126
|
+
* The default state for the form.
|
|
127
|
+
*/
|
|
83
128
|
defaultState?: Partial<FormState<TFormData>>
|
|
129
|
+
/**
|
|
130
|
+
* If true, always run async validation, even when sync validation has produced an error. Defaults to undefined.
|
|
131
|
+
*/
|
|
84
132
|
asyncAlways?: boolean
|
|
133
|
+
/**
|
|
134
|
+
* Optional time in milliseconds if you want to introduce a delay before firing off an async action.
|
|
135
|
+
*/
|
|
85
136
|
asyncDebounceMs?: number
|
|
137
|
+
/**
|
|
138
|
+
* A validator adapter to support usage of extra validation types (IE: Zod, Yup, or Valibot usage)
|
|
139
|
+
*/
|
|
86
140
|
validatorAdapter?: TFormValidator
|
|
141
|
+
/**
|
|
142
|
+
* A list of validators to pass to the form
|
|
143
|
+
*/
|
|
87
144
|
validators?: FormValidators<TFormData, TFormValidator>
|
|
145
|
+
/**
|
|
146
|
+
* 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>`
|
|
147
|
+
*/
|
|
88
148
|
onSubmit?: (props: {
|
|
89
149
|
value: TFormData
|
|
90
150
|
formApi: FormApi<TFormData, TFormValidator>
|
|
91
151
|
}) => any | Promise<any>
|
|
152
|
+
/**
|
|
153
|
+
* Specify an action for scenarios where the user tries to submit an invalid form.
|
|
154
|
+
*/
|
|
92
155
|
onSubmitInvalid?: (props: {
|
|
93
156
|
value: TFormData
|
|
94
157
|
formApi: FormApi<TFormData, TFormValidator>
|
|
@@ -96,44 +159,113 @@ export interface FormOptions<
|
|
|
96
159
|
transform?: FormTransform<TFormData, TFormValidator>
|
|
97
160
|
}
|
|
98
161
|
|
|
162
|
+
/**
|
|
163
|
+
* An object representing the validation metadata for a field. Not intended for public usage.
|
|
164
|
+
*/
|
|
99
165
|
export type ValidationMeta = {
|
|
166
|
+
/**
|
|
167
|
+
* An abort controller stored in memory to cancel previous async validation attempts.
|
|
168
|
+
*/
|
|
100
169
|
lastAbortController: AbortController
|
|
101
170
|
}
|
|
102
171
|
|
|
172
|
+
/**
|
|
173
|
+
* An object representing the field information for a specific field within the form.
|
|
174
|
+
*/
|
|
103
175
|
export type FieldInfo<
|
|
104
176
|
TFormData,
|
|
105
177
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
106
178
|
> = {
|
|
179
|
+
/**
|
|
180
|
+
* An instance of the FieldAPI.
|
|
181
|
+
*/
|
|
107
182
|
instance: FieldApi<
|
|
108
183
|
TFormData,
|
|
109
184
|
any,
|
|
110
185
|
Validator<unknown, unknown> | undefined,
|
|
111
186
|
TFormValidator
|
|
112
187
|
> | null
|
|
188
|
+
/**
|
|
189
|
+
* A record of field validation internal handling.
|
|
190
|
+
*/
|
|
113
191
|
validationMetaMap: Record<ValidationErrorMapKeys, ValidationMeta | undefined>
|
|
114
192
|
}
|
|
115
193
|
|
|
194
|
+
/**
|
|
195
|
+
* An object representing the current state of the form.
|
|
196
|
+
*/
|
|
116
197
|
export type FormState<TFormData> = {
|
|
198
|
+
/**
|
|
199
|
+
* The current values of the form fields.
|
|
200
|
+
*/
|
|
117
201
|
values: TFormData
|
|
118
|
-
|
|
202
|
+
/**
|
|
203
|
+
* A boolean indicating if the form is currently validating.
|
|
204
|
+
*/
|
|
119
205
|
isFormValidating: boolean
|
|
206
|
+
/**
|
|
207
|
+
* A boolean indicating if the form is valid.
|
|
208
|
+
*/
|
|
120
209
|
isFormValid: boolean
|
|
210
|
+
/**
|
|
211
|
+
* The error array for the form itself.
|
|
212
|
+
*/
|
|
121
213
|
errors: ValidationError[]
|
|
214
|
+
/**
|
|
215
|
+
* The error map for the form itself.
|
|
216
|
+
*/
|
|
122
217
|
errorMap: ValidationErrorMap
|
|
218
|
+
/**
|
|
219
|
+
* An internal mechanism used for keeping track of validation logic in a form.
|
|
220
|
+
*/
|
|
123
221
|
validationMetaMap: Record<ValidationErrorMapKeys, ValidationMeta | undefined>
|
|
124
|
-
|
|
222
|
+
/**
|
|
223
|
+
* A record of field metadata for each field in the form.
|
|
224
|
+
*/
|
|
125
225
|
fieldMeta: Record<DeepKeys<TFormData>, FieldMeta>
|
|
226
|
+
/**
|
|
227
|
+
* A boolean indicating if any of the form fields are currently validating.
|
|
228
|
+
*/
|
|
126
229
|
isFieldsValidating: boolean
|
|
230
|
+
/**
|
|
231
|
+
* A boolean indicating if all the form fields are valid.
|
|
232
|
+
*/
|
|
127
233
|
isFieldsValid: boolean
|
|
234
|
+
/**
|
|
235
|
+
* A boolean indicating if the form is currently submitting.
|
|
236
|
+
*/
|
|
128
237
|
isSubmitting: boolean
|
|
129
|
-
|
|
238
|
+
/**
|
|
239
|
+
* A boolean indicating if any of the form fields have been touched.
|
|
240
|
+
*/
|
|
130
241
|
isTouched: boolean
|
|
242
|
+
/**
|
|
243
|
+
* A boolean indicating if any of the form's fields' values have been modified by the user. `True` if the user have modified at least one of the fields. Opposite of `isPristine`.
|
|
244
|
+
*/
|
|
131
245
|
isDirty: boolean
|
|
246
|
+
/**
|
|
247
|
+
* A boolean indicating if none of the form's fields' values have been modified by the user. `True` if the user have not modified any of the fields. Opposite of `isDirty`.
|
|
248
|
+
*/
|
|
132
249
|
isPristine: boolean
|
|
250
|
+
/**
|
|
251
|
+
* A boolean indicating if the form has been submitted.
|
|
252
|
+
*/
|
|
133
253
|
isSubmitted: boolean
|
|
254
|
+
/**
|
|
255
|
+
* A boolean indicating if the form or any of its fields are currently validating.
|
|
256
|
+
*/
|
|
134
257
|
isValidating: boolean
|
|
258
|
+
/**
|
|
259
|
+
* A boolean indicating if the form and all its fields are valid.
|
|
260
|
+
*/
|
|
135
261
|
isValid: boolean
|
|
262
|
+
/**
|
|
263
|
+
* A boolean indicating if the form can be submitted based on its current state.
|
|
264
|
+
*/
|
|
136
265
|
canSubmit: boolean
|
|
266
|
+
/**
|
|
267
|
+
* A counter for tracking the number of submission attempts.
|
|
268
|
+
*/
|
|
137
269
|
submissionAttempts: number
|
|
138
270
|
}
|
|
139
271
|
|
|
@@ -168,21 +300,60 @@ function getDefaultFormState<TFormData>(
|
|
|
168
300
|
}
|
|
169
301
|
}
|
|
170
302
|
|
|
303
|
+
/**
|
|
304
|
+
* A class representing the Form API. It handles the logic and interactions with the form state.
|
|
305
|
+
*
|
|
306
|
+
* Normally, you will not need to create a new `FormApi` instance directly. Instead, you will use a framework
|
|
307
|
+
* hook/function like `useForm` or `createForm` to create a new instance for you that uses your framework's reactivity model.
|
|
308
|
+
* However, if you need to create a new instance manually, you can do so by calling the `new FormApi` constructor.
|
|
309
|
+
*/
|
|
171
310
|
export class FormApi<
|
|
172
311
|
TFormData,
|
|
173
312
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
|
174
313
|
> {
|
|
314
|
+
/**
|
|
315
|
+
* The options for the form.
|
|
316
|
+
*/
|
|
175
317
|
options: FormOptions<TFormData, TFormValidator> = {}
|
|
318
|
+
/**
|
|
319
|
+
* A [TanStack Store instance](https://tanstack.com/store/latest/docs/reference/Store) that keeps track of the form's state.
|
|
320
|
+
*/
|
|
176
321
|
store!: Store<FormState<TFormData>>
|
|
177
|
-
|
|
178
|
-
|
|
322
|
+
/**
|
|
323
|
+
* The current state of the form.
|
|
324
|
+
*
|
|
325
|
+
* **Note:**
|
|
326
|
+
* Do not use `state` directly, as it is not reactive.
|
|
327
|
+
* Please use form.useStore() utility to subscribe to state
|
|
328
|
+
*/
|
|
179
329
|
state!: FormState<TFormData>
|
|
180
|
-
|
|
330
|
+
/**
|
|
331
|
+
* A record of field information for each field in the form.
|
|
332
|
+
*/
|
|
181
333
|
fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, TFormValidator>> =
|
|
182
334
|
{} as any
|
|
183
335
|
|
|
336
|
+
/**
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
184
339
|
prevTransformArray: unknown[] = []
|
|
185
340
|
|
|
341
|
+
/**
|
|
342
|
+
* @private
|
|
343
|
+
* Used to handle the edgecase of `pushFieldValue` not adding a `defaultValue` to the child `FieldAPI`s that are
|
|
344
|
+
* subsequently generated from the `pushFieldValue` (and friends)
|
|
345
|
+
* @see https://github.com/TanStack/form/issues/704#issuecomment-2184080607
|
|
346
|
+
*/
|
|
347
|
+
_tempDefaultValue:
|
|
348
|
+
| undefined
|
|
349
|
+
| {
|
|
350
|
+
field: string
|
|
351
|
+
value: unknown
|
|
352
|
+
} = undefined
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Constructs a new `FormApi` instance with the given form options.
|
|
356
|
+
*/
|
|
186
357
|
constructor(opts?: FormOptions<TFormData, TFormValidator>) {
|
|
187
358
|
this.store = new Store<FormState<TFormData>>(
|
|
188
359
|
getDefaultFormState({
|
|
@@ -260,6 +431,9 @@ export class FormApi<
|
|
|
260
431
|
this.update(opts || {})
|
|
261
432
|
}
|
|
262
433
|
|
|
434
|
+
/**
|
|
435
|
+
* @private
|
|
436
|
+
*/
|
|
263
437
|
runValidator<
|
|
264
438
|
TValue extends { value: TFormData; formApi: FormApi<any, any> },
|
|
265
439
|
TType extends 'validate' | 'validateAsync',
|
|
@@ -297,6 +471,9 @@ export class FormApi<
|
|
|
297
471
|
}
|
|
298
472
|
}
|
|
299
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Updates the form options and form state.
|
|
476
|
+
*/
|
|
300
477
|
update = (options?: FormOptions<TFormData, TFormValidator>) => {
|
|
301
478
|
if (!options) return
|
|
302
479
|
|
|
@@ -334,6 +511,9 @@ export class FormApi<
|
|
|
334
511
|
})
|
|
335
512
|
}
|
|
336
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Resets the form state to the default values.
|
|
516
|
+
*/
|
|
337
517
|
reset = () => {
|
|
338
518
|
const { fieldMeta: currentFieldMeta } = this.state
|
|
339
519
|
const fieldMeta = this.resetFieldMeta(currentFieldMeta)
|
|
@@ -346,6 +526,9 @@ export class FormApi<
|
|
|
346
526
|
)
|
|
347
527
|
}
|
|
348
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Validates all fields in the form using the correct handlers for a given validation type.
|
|
531
|
+
*/
|
|
349
532
|
validateAllFields = async (cause: ValidationCause) => {
|
|
350
533
|
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
|
|
351
534
|
this.store.batch(() => {
|
|
@@ -370,6 +553,9 @@ export class FormApi<
|
|
|
370
553
|
return fieldErrorMapMap.flat()
|
|
371
554
|
}
|
|
372
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Validates the children of a specified array in the form starting from a given index until the end using the correct handlers for a given validation type.
|
|
558
|
+
*/
|
|
373
559
|
validateArrayFieldsStartingFrom = async <TField extends DeepKeys<TFormData>>(
|
|
374
560
|
field: TField,
|
|
375
561
|
index: number,
|
|
@@ -406,6 +592,9 @@ export class FormApi<
|
|
|
406
592
|
return fieldErrorMapMap.flat()
|
|
407
593
|
}
|
|
408
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Validates a specified field in the form using the correct handlers for a given validation type.
|
|
597
|
+
*/
|
|
409
598
|
validateField = <TField extends DeepKeys<TFormData>>(
|
|
410
599
|
field: TField,
|
|
411
600
|
cause: ValidationCause,
|
|
@@ -423,7 +612,10 @@ export class FormApi<
|
|
|
423
612
|
return fieldInstance.validate(cause)
|
|
424
613
|
}
|
|
425
614
|
|
|
426
|
-
|
|
615
|
+
/**
|
|
616
|
+
* TODO: This code is copied from FieldApi, we should refactor to share
|
|
617
|
+
* @private
|
|
618
|
+
*/
|
|
427
619
|
validateSync = (cause: ValidationCause) => {
|
|
428
620
|
const validates = getSyncValidatorArray(cause, this.options)
|
|
429
621
|
let hasErrored = false as boolean
|
|
@@ -480,6 +672,9 @@ export class FormApi<
|
|
|
480
672
|
return { hasErrored }
|
|
481
673
|
}
|
|
482
674
|
|
|
675
|
+
/**
|
|
676
|
+
* @private
|
|
677
|
+
*/
|
|
483
678
|
validateAsync = async (
|
|
484
679
|
cause: ValidationCause,
|
|
485
680
|
): Promise<ValidationError[]> => {
|
|
@@ -561,6 +756,9 @@ export class FormApi<
|
|
|
561
756
|
return results.filter(Boolean)
|
|
562
757
|
}
|
|
563
758
|
|
|
759
|
+
/**
|
|
760
|
+
* @private
|
|
761
|
+
*/
|
|
564
762
|
validate = (
|
|
565
763
|
cause: ValidationCause,
|
|
566
764
|
): ValidationError[] | Promise<ValidationError[]> => {
|
|
@@ -575,12 +773,10 @@ export class FormApi<
|
|
|
575
773
|
return this.validateAsync(cause)
|
|
576
774
|
}
|
|
577
775
|
|
|
776
|
+
/**
|
|
777
|
+
* Handles the form submission, performs validation, and calls the appropriate onSubmit or onInvalidSubmit callbacks.
|
|
778
|
+
*/
|
|
578
779
|
handleSubmit = async () => {
|
|
579
|
-
// Check to see that the form and all fields have been touched
|
|
580
|
-
// If they have not, touch them all and run validation
|
|
581
|
-
// Run form validation
|
|
582
|
-
// Submit the form
|
|
583
|
-
|
|
584
780
|
this.store.setState((old) => ({
|
|
585
781
|
...old,
|
|
586
782
|
// Submission attempts mark the form as not submitted
|
|
@@ -637,16 +833,25 @@ export class FormApi<
|
|
|
637
833
|
}
|
|
638
834
|
}
|
|
639
835
|
|
|
836
|
+
/**
|
|
837
|
+
* Gets the value of the specified field.
|
|
838
|
+
*/
|
|
640
839
|
getFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
641
840
|
field: TField,
|
|
642
841
|
): DeepValue<TFormData, TField> => getBy(this.state.values, field)
|
|
643
842
|
|
|
843
|
+
/**
|
|
844
|
+
* Gets the metadata of the specified field.
|
|
845
|
+
*/
|
|
644
846
|
getFieldMeta = <TField extends DeepKeys<TFormData>>(
|
|
645
847
|
field: TField,
|
|
646
848
|
): FieldMeta | undefined => {
|
|
647
849
|
return this.state.fieldMeta[field]
|
|
648
850
|
}
|
|
649
851
|
|
|
852
|
+
/**
|
|
853
|
+
* Gets the field info of the specified field.
|
|
854
|
+
*/
|
|
650
855
|
getFieldInfo = <TField extends DeepKeys<TFormData>>(
|
|
651
856
|
field: TField,
|
|
652
857
|
): FieldInfo<TFormData, TFormValidator> => {
|
|
@@ -663,6 +868,9 @@ export class FormApi<
|
|
|
663
868
|
})
|
|
664
869
|
}
|
|
665
870
|
|
|
871
|
+
/**
|
|
872
|
+
* Updates the metadata of the specified field.
|
|
873
|
+
*/
|
|
666
874
|
setFieldMeta = <TField extends DeepKeys<TFormData>>(
|
|
667
875
|
field: TField,
|
|
668
876
|
updater: Updater<FieldMeta>,
|
|
@@ -699,6 +907,9 @@ export class FormApi<
|
|
|
699
907
|
)
|
|
700
908
|
}
|
|
701
909
|
|
|
910
|
+
/**
|
|
911
|
+
* Sets the value of the specified field and optionally updates the touched state.
|
|
912
|
+
*/
|
|
702
913
|
setFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
703
914
|
field: TField,
|
|
704
915
|
updater: Updater<DeepValue<TFormData, TField>>,
|
|
@@ -735,6 +946,9 @@ export class FormApi<
|
|
|
735
946
|
delete this.fieldInfo[field]
|
|
736
947
|
}
|
|
737
948
|
|
|
949
|
+
/**
|
|
950
|
+
* Pushes a value into an array field.
|
|
951
|
+
*/
|
|
738
952
|
pushFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
739
953
|
field: TField,
|
|
740
954
|
value: DeepValue<TFormData, TField> extends any[]
|
|
@@ -742,6 +956,12 @@ export class FormApi<
|
|
|
742
956
|
: never,
|
|
743
957
|
opts?: { touch?: boolean },
|
|
744
958
|
) => {
|
|
959
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
960
|
+
const fieldVal = (this.getFieldValue(field) as unknown[]) ?? []
|
|
961
|
+
this._tempDefaultValue = {
|
|
962
|
+
value,
|
|
963
|
+
field: `${field}[${fieldVal.length}]`,
|
|
964
|
+
} as never
|
|
745
965
|
this.setFieldValue(
|
|
746
966
|
field,
|
|
747
967
|
(prev) => [...(Array.isArray(prev) ? prev : []), value] as any,
|
|
@@ -750,6 +970,9 @@ export class FormApi<
|
|
|
750
970
|
this.validateField(field, 'change')
|
|
751
971
|
}
|
|
752
972
|
|
|
973
|
+
/**
|
|
974
|
+
* Inserts a value into an array field at the specified index, shifting the subsequent values to the right.
|
|
975
|
+
*/
|
|
753
976
|
insertFieldValue = async <TField extends DeepKeys<TFormData>>(
|
|
754
977
|
field: TField,
|
|
755
978
|
index: number,
|
|
@@ -758,6 +981,7 @@ export class FormApi<
|
|
|
758
981
|
: never,
|
|
759
982
|
opts?: { touch?: boolean },
|
|
760
983
|
) => {
|
|
984
|
+
this._tempDefaultValue = { value, field: `${field}[${index}]` } as never
|
|
761
985
|
this.setFieldValue(
|
|
762
986
|
field,
|
|
763
987
|
(prev) => {
|
|
@@ -774,6 +998,9 @@ export class FormApi<
|
|
|
774
998
|
await this.validateField(field, 'change')
|
|
775
999
|
}
|
|
776
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* Replaces a value into an array field at the specified index.
|
|
1003
|
+
*/
|
|
777
1004
|
replaceFieldValue = async <TField extends DeepKeys<TFormData>>(
|
|
778
1005
|
field: TField,
|
|
779
1006
|
index: number,
|
|
@@ -782,6 +1009,7 @@ export class FormApi<
|
|
|
782
1009
|
: never,
|
|
783
1010
|
opts?: { touch?: boolean },
|
|
784
1011
|
) => {
|
|
1012
|
+
this._tempDefaultValue = { value, field: `${field}[${index}]` } as never
|
|
785
1013
|
this.setFieldValue(
|
|
786
1014
|
field,
|
|
787
1015
|
(prev) => {
|
|
@@ -797,6 +1025,9 @@ export class FormApi<
|
|
|
797
1025
|
await this.validateArrayFieldsStartingFrom(field, index, 'change')
|
|
798
1026
|
}
|
|
799
1027
|
|
|
1028
|
+
/**
|
|
1029
|
+
* Removes a value from an array field at the specified index.
|
|
1030
|
+
*/
|
|
800
1031
|
removeFieldValue = async <TField extends DeepKeys<TFormData>>(
|
|
801
1032
|
field: TField,
|
|
802
1033
|
index: number,
|
|
@@ -833,6 +1064,9 @@ export class FormApi<
|
|
|
833
1064
|
await this.validateArrayFieldsStartingFrom(field, index, 'change')
|
|
834
1065
|
}
|
|
835
1066
|
|
|
1067
|
+
/**
|
|
1068
|
+
* Swaps the values at the specified indices within an array field.
|
|
1069
|
+
*/
|
|
836
1070
|
swapFieldValues = <TField extends DeepKeys<TFormData>>(
|
|
837
1071
|
field: TField,
|
|
838
1072
|
index1: number,
|
|
@@ -856,6 +1090,9 @@ export class FormApi<
|
|
|
856
1090
|
this.validateField(`${field}[${index2}]` as DeepKeys<TFormData>, 'change')
|
|
857
1091
|
}
|
|
858
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Moves the value at the first specified index to the second specified index within an array field.
|
|
1095
|
+
*/
|
|
859
1096
|
moveFieldValues = <TField extends DeepKeys<TFormData>>(
|
|
860
1097
|
field: TField,
|
|
861
1098
|
index1: number,
|
package/src/mergeForm.ts
CHANGED
|
@@ -2,6 +2,9 @@ import type { FormApi } from './FormApi'
|
|
|
2
2
|
import type { Validator } from './types'
|
|
3
3
|
import type { NoInfer } from './util-types'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @private
|
|
7
|
+
*/
|
|
5
8
|
export function mutateMergeDeep(target: object, source: object): object {
|
|
6
9
|
const targetKeys = Object.keys(target)
|
|
7
10
|
const sourceKeys = Object.keys(source)
|
package/src/types.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
export type ValidationError = undefined | false | null | string
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* If/when TypeScript supports higher-kinded types, this should not be `unknown` anymore
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
4
7
|
export type Validator<Type, Fn = unknown> = () => {
|
|
5
8
|
validate(options: { value: Type }, fn: Fn): ValidationError
|
|
6
9
|
validateAsync(options: { value: Type }, fn: Fn): Promise<ValidationError>
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
/**
|
|
13
|
+
* "server" is only intended for SSR/SSG validation and should not execute anything
|
|
14
|
+
* @private
|
|
15
|
+
*/
|
|
10
16
|
export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount' | 'server'
|
|
11
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
12
21
|
export type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`
|
|
13
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
14
26
|
export type ValidationErrorMap = {
|
|
15
27
|
[K in ValidationErrorMapKeys]?: ValidationError
|
|
16
28
|
}
|
package/src/util-types.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
type Nullable<T> = T | null
|
|
2
2
|
type IsNullable<T> = [null] extends [T] ? true : false
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
4
7
|
export type RequiredByKey<T, K extends keyof T> = Omit<T, K> &
|
|
5
8
|
Required<Pick<T, K>>
|
|
6
9
|
|
|
@@ -13,13 +16,22 @@ type NarrowRaw<A> =
|
|
|
13
16
|
[K in keyof A]: A[K] extends Function ? A[K] : NarrowRaw<A[K]>
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
16
22
|
export type NoInfer<T> = [T][T extends any ? 0 : never]
|
|
17
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
18
27
|
export type Narrow<A> = Try<A, [], NarrowRaw<A>>
|
|
19
28
|
|
|
20
29
|
type Try<A1, A2, Catch = never> = A1 extends A2 ? A1 : Catch
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Hack to get TypeScript to show simplified types in error messages
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
23
35
|
export type Pretty<T> = { [K in keyof T]: T[K] } & {}
|
|
24
36
|
|
|
25
37
|
type ComputeRange<
|
|
@@ -67,6 +79,9 @@ type PrefixObjectAccessor<T extends object, TDepth extends any[]> = {
|
|
|
67
79
|
: never
|
|
68
80
|
}[keyof T]
|
|
69
81
|
|
|
82
|
+
/**
|
|
83
|
+
* The keys of an object or array, deeply nested.
|
|
84
|
+
*/
|
|
70
85
|
export type DeepKeys<T, TDepth extends any[] = []> = TDepth['length'] extends 5
|
|
71
86
|
? never
|
|
72
87
|
: unknown extends T
|
|
@@ -89,9 +104,8 @@ type PrefixFromDepth<
|
|
|
89
104
|
> = TDepth['length'] extends 0 ? T : `.${T}`
|
|
90
105
|
|
|
91
106
|
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
*/
|
|
107
|
+
* Infer the type of a deeply nested property within an object or an array.
|
|
108
|
+
*/
|
|
95
109
|
export type DeepValue<
|
|
96
110
|
// The object or array in which we have the property whose type we're trying to infer
|
|
97
111
|
TValue,
|