@tanstack/form-core 0.23.2 → 0.24.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 +6 -4
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +220 -2
- package/dist/cjs/FormApi.cjs +6 -0
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +228 -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 -2
- package/dist/esm/FieldApi.js +6 -4
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +228 -0
- package/dist/esm/FormApi.js +6 -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 +1 -1
- package/src/FieldApi.ts +222 -9
- package/src/FormApi.ts +228 -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,47 @@ 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
|
+
* Constructs a new `FormApi` instance with the given form options.
|
|
343
|
+
*/
|
|
186
344
|
constructor(opts?: FormOptions<TFormData, TFormValidator>) {
|
|
187
345
|
this.store = new Store<FormState<TFormData>>(
|
|
188
346
|
getDefaultFormState({
|
|
@@ -260,6 +418,9 @@ export class FormApi<
|
|
|
260
418
|
this.update(opts || {})
|
|
261
419
|
}
|
|
262
420
|
|
|
421
|
+
/**
|
|
422
|
+
* @private
|
|
423
|
+
*/
|
|
263
424
|
runValidator<
|
|
264
425
|
TValue extends { value: TFormData; formApi: FormApi<any, any> },
|
|
265
426
|
TType extends 'validate' | 'validateAsync',
|
|
@@ -297,6 +458,9 @@ export class FormApi<
|
|
|
297
458
|
}
|
|
298
459
|
}
|
|
299
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Updates the form options and form state.
|
|
463
|
+
*/
|
|
300
464
|
update = (options?: FormOptions<TFormData, TFormValidator>) => {
|
|
301
465
|
if (!options) return
|
|
302
466
|
|
|
@@ -334,6 +498,9 @@ export class FormApi<
|
|
|
334
498
|
})
|
|
335
499
|
}
|
|
336
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Resets the form state to the default values.
|
|
503
|
+
*/
|
|
337
504
|
reset = () => {
|
|
338
505
|
const { fieldMeta: currentFieldMeta } = this.state
|
|
339
506
|
const fieldMeta = this.resetFieldMeta(currentFieldMeta)
|
|
@@ -346,6 +513,9 @@ export class FormApi<
|
|
|
346
513
|
)
|
|
347
514
|
}
|
|
348
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Validates all fields in the form using the correct handlers for a given validation type.
|
|
518
|
+
*/
|
|
349
519
|
validateAllFields = async (cause: ValidationCause) => {
|
|
350
520
|
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
|
|
351
521
|
this.store.batch(() => {
|
|
@@ -370,6 +540,9 @@ export class FormApi<
|
|
|
370
540
|
return fieldErrorMapMap.flat()
|
|
371
541
|
}
|
|
372
542
|
|
|
543
|
+
/**
|
|
544
|
+
* 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.
|
|
545
|
+
*/
|
|
373
546
|
validateArrayFieldsStartingFrom = async <TField extends DeepKeys<TFormData>>(
|
|
374
547
|
field: TField,
|
|
375
548
|
index: number,
|
|
@@ -406,6 +579,9 @@ export class FormApi<
|
|
|
406
579
|
return fieldErrorMapMap.flat()
|
|
407
580
|
}
|
|
408
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Validates a specified field in the form using the correct handlers for a given validation type.
|
|
584
|
+
*/
|
|
409
585
|
validateField = <TField extends DeepKeys<TFormData>>(
|
|
410
586
|
field: TField,
|
|
411
587
|
cause: ValidationCause,
|
|
@@ -423,7 +599,10 @@ export class FormApi<
|
|
|
423
599
|
return fieldInstance.validate(cause)
|
|
424
600
|
}
|
|
425
601
|
|
|
426
|
-
|
|
602
|
+
/**
|
|
603
|
+
* TODO: This code is copied from FieldApi, we should refactor to share
|
|
604
|
+
* @private
|
|
605
|
+
*/
|
|
427
606
|
validateSync = (cause: ValidationCause) => {
|
|
428
607
|
const validates = getSyncValidatorArray(cause, this.options)
|
|
429
608
|
let hasErrored = false as boolean
|
|
@@ -480,6 +659,9 @@ export class FormApi<
|
|
|
480
659
|
return { hasErrored }
|
|
481
660
|
}
|
|
482
661
|
|
|
662
|
+
/**
|
|
663
|
+
* @private
|
|
664
|
+
*/
|
|
483
665
|
validateAsync = async (
|
|
484
666
|
cause: ValidationCause,
|
|
485
667
|
): Promise<ValidationError[]> => {
|
|
@@ -561,6 +743,9 @@ export class FormApi<
|
|
|
561
743
|
return results.filter(Boolean)
|
|
562
744
|
}
|
|
563
745
|
|
|
746
|
+
/**
|
|
747
|
+
* @private
|
|
748
|
+
*/
|
|
564
749
|
validate = (
|
|
565
750
|
cause: ValidationCause,
|
|
566
751
|
): ValidationError[] | Promise<ValidationError[]> => {
|
|
@@ -575,12 +760,10 @@ export class FormApi<
|
|
|
575
760
|
return this.validateAsync(cause)
|
|
576
761
|
}
|
|
577
762
|
|
|
763
|
+
/**
|
|
764
|
+
* Handles the form submission, performs validation, and calls the appropriate onSubmit or onInvalidSubmit callbacks.
|
|
765
|
+
*/
|
|
578
766
|
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
767
|
this.store.setState((old) => ({
|
|
585
768
|
...old,
|
|
586
769
|
// Submission attempts mark the form as not submitted
|
|
@@ -637,16 +820,25 @@ export class FormApi<
|
|
|
637
820
|
}
|
|
638
821
|
}
|
|
639
822
|
|
|
823
|
+
/**
|
|
824
|
+
* Gets the value of the specified field.
|
|
825
|
+
*/
|
|
640
826
|
getFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
641
827
|
field: TField,
|
|
642
828
|
): DeepValue<TFormData, TField> => getBy(this.state.values, field)
|
|
643
829
|
|
|
830
|
+
/**
|
|
831
|
+
* Gets the metadata of the specified field.
|
|
832
|
+
*/
|
|
644
833
|
getFieldMeta = <TField extends DeepKeys<TFormData>>(
|
|
645
834
|
field: TField,
|
|
646
835
|
): FieldMeta | undefined => {
|
|
647
836
|
return this.state.fieldMeta[field]
|
|
648
837
|
}
|
|
649
838
|
|
|
839
|
+
/**
|
|
840
|
+
* Gets the field info of the specified field.
|
|
841
|
+
*/
|
|
650
842
|
getFieldInfo = <TField extends DeepKeys<TFormData>>(
|
|
651
843
|
field: TField,
|
|
652
844
|
): FieldInfo<TFormData, TFormValidator> => {
|
|
@@ -663,6 +855,9 @@ export class FormApi<
|
|
|
663
855
|
})
|
|
664
856
|
}
|
|
665
857
|
|
|
858
|
+
/**
|
|
859
|
+
* Updates the metadata of the specified field.
|
|
860
|
+
*/
|
|
666
861
|
setFieldMeta = <TField extends DeepKeys<TFormData>>(
|
|
667
862
|
field: TField,
|
|
668
863
|
updater: Updater<FieldMeta>,
|
|
@@ -699,6 +894,9 @@ export class FormApi<
|
|
|
699
894
|
)
|
|
700
895
|
}
|
|
701
896
|
|
|
897
|
+
/**
|
|
898
|
+
* Sets the value of the specified field and optionally updates the touched state.
|
|
899
|
+
*/
|
|
702
900
|
setFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
703
901
|
field: TField,
|
|
704
902
|
updater: Updater<DeepValue<TFormData, TField>>,
|
|
@@ -735,6 +933,9 @@ export class FormApi<
|
|
|
735
933
|
delete this.fieldInfo[field]
|
|
736
934
|
}
|
|
737
935
|
|
|
936
|
+
/**
|
|
937
|
+
* Pushes a value into an array field.
|
|
938
|
+
*/
|
|
738
939
|
pushFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
739
940
|
field: TField,
|
|
740
941
|
value: DeepValue<TFormData, TField> extends any[]
|
|
@@ -750,6 +951,9 @@ export class FormApi<
|
|
|
750
951
|
this.validateField(field, 'change')
|
|
751
952
|
}
|
|
752
953
|
|
|
954
|
+
/**
|
|
955
|
+
* Inserts a value into an array field at the specified index, shifting the subsequent values to the right.
|
|
956
|
+
*/
|
|
753
957
|
insertFieldValue = async <TField extends DeepKeys<TFormData>>(
|
|
754
958
|
field: TField,
|
|
755
959
|
index: number,
|
|
@@ -774,6 +978,9 @@ export class FormApi<
|
|
|
774
978
|
await this.validateField(field, 'change')
|
|
775
979
|
}
|
|
776
980
|
|
|
981
|
+
/**
|
|
982
|
+
* Replaces a value into an array field at the specified index.
|
|
983
|
+
*/
|
|
777
984
|
replaceFieldValue = async <TField extends DeepKeys<TFormData>>(
|
|
778
985
|
field: TField,
|
|
779
986
|
index: number,
|
|
@@ -797,6 +1004,9 @@ export class FormApi<
|
|
|
797
1004
|
await this.validateArrayFieldsStartingFrom(field, index, 'change')
|
|
798
1005
|
}
|
|
799
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* Removes a value from an array field at the specified index.
|
|
1009
|
+
*/
|
|
800
1010
|
removeFieldValue = async <TField extends DeepKeys<TFormData>>(
|
|
801
1011
|
field: TField,
|
|
802
1012
|
index: number,
|
|
@@ -833,6 +1043,9 @@ export class FormApi<
|
|
|
833
1043
|
await this.validateArrayFieldsStartingFrom(field, index, 'change')
|
|
834
1044
|
}
|
|
835
1045
|
|
|
1046
|
+
/**
|
|
1047
|
+
* Swaps the values at the specified indices within an array field.
|
|
1048
|
+
*/
|
|
836
1049
|
swapFieldValues = <TField extends DeepKeys<TFormData>>(
|
|
837
1050
|
field: TField,
|
|
838
1051
|
index1: number,
|
|
@@ -856,6 +1069,9 @@ export class FormApi<
|
|
|
856
1069
|
this.validateField(`${field}[${index2}]` as DeepKeys<TFormData>, 'change')
|
|
857
1070
|
}
|
|
858
1071
|
|
|
1072
|
+
/**
|
|
1073
|
+
* Moves the value at the first specified index to the second specified index within an array field.
|
|
1074
|
+
*/
|
|
859
1075
|
moveFieldValues = <TField extends DeepKeys<TFormData>>(
|
|
860
1076
|
field: TField,
|
|
861
1077
|
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,
|
package/src/utils.ts
CHANGED
|
@@ -8,6 +8,9 @@ export type Updater<TInput, TOutput = TInput> =
|
|
|
8
8
|
| TOutput
|
|
9
9
|
| UpdaterFn<TInput, TOutput>
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
11
14
|
export function functionalUpdate<TInput, TOutput = TInput>(
|
|
12
15
|
updater: Updater<TInput, TOutput>,
|
|
13
16
|
input: TInput,
|
|
@@ -19,6 +22,7 @@ export function functionalUpdate<TInput, TOutput = TInput>(
|
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* Get a value from an object using a path, including dot notation.
|
|
25
|
+
* @private
|
|
22
26
|
*/
|
|
23
27
|
export function getBy(obj: any, path: any) {
|
|
24
28
|
const pathObj = makePathArray(path)
|
|
@@ -33,6 +37,7 @@ export function getBy(obj: any, path: any) {
|
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* Set a value on an object using a path, including dot notation.
|
|
40
|
+
* @private
|
|
36
41
|
*/
|
|
37
42
|
export function setBy(obj: any, _path: any, updater: Updater<any>) {
|
|
38
43
|
const path = makePathArray(_path)
|
|
@@ -75,6 +80,7 @@ export function setBy(obj: any, _path: any, updater: Updater<any>) {
|
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
82
|
* Delete a field on an object using a path, including dot notation.
|
|
83
|
+
* @private
|
|
78
84
|
*/
|
|
79
85
|
export function deleteBy(obj: any, _path: any) {
|
|
80
86
|
const path = makePathArray(_path)
|
|
@@ -130,6 +136,9 @@ const reFindMultiplePeriods = /\.{2,}/gm
|
|
|
130
136
|
const intPrefix = '__int__'
|
|
131
137
|
const intReplace = `${intPrefix}$1`
|
|
132
138
|
|
|
139
|
+
/**
|
|
140
|
+
* @private
|
|
141
|
+
*/
|
|
133
142
|
export function makePathArray(str: string) {
|
|
134
143
|
if (typeof str !== 'string') {
|
|
135
144
|
throw new Error('Path must be a string.')
|
|
@@ -152,6 +161,9 @@ export function makePathArray(str: string) {
|
|
|
152
161
|
})
|
|
153
162
|
}
|
|
154
163
|
|
|
164
|
+
/**
|
|
165
|
+
* @private
|
|
166
|
+
*/
|
|
155
167
|
export function isNonEmptyArray(obj: any) {
|
|
156
168
|
return !(Array.isArray(obj) && obj.length === 0)
|
|
157
169
|
}
|
|
@@ -161,12 +173,18 @@ interface AsyncValidatorArrayPartialOptions<T> {
|
|
|
161
173
|
asyncDebounceMs?: number
|
|
162
174
|
}
|
|
163
175
|
|
|
176
|
+
/**
|
|
177
|
+
* @private
|
|
178
|
+
*/
|
|
164
179
|
export interface AsyncValidator<T> {
|
|
165
180
|
cause: ValidationCause
|
|
166
181
|
validate: T
|
|
167
182
|
debounceMs: number
|
|
168
183
|
}
|
|
169
184
|
|
|
185
|
+
/**
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
170
188
|
export function getAsyncValidatorArray<T>(
|
|
171
189
|
cause: ValidationCause,
|
|
172
190
|
options: AsyncValidatorArrayPartialOptions<T>,
|
|
@@ -240,11 +258,17 @@ interface SyncValidatorArrayPartialOptions<T> {
|
|
|
240
258
|
validators?: T
|
|
241
259
|
}
|
|
242
260
|
|
|
261
|
+
/**
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
243
264
|
export interface SyncValidator<T> {
|
|
244
265
|
cause: ValidationCause
|
|
245
266
|
validate: T
|
|
246
267
|
}
|
|
247
268
|
|
|
269
|
+
/**
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
248
272
|
export function getSyncValidatorArray<T>(
|
|
249
273
|
cause: ValidationCause,
|
|
250
274
|
options: SyncValidatorArrayPartialOptions<T>,
|