@tanstack/form-core 0.3.7 → 0.4.1
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/build/legacy/FieldApi.cjs +46 -26
- package/build/legacy/FieldApi.cjs.map +1 -1
- package/build/legacy/FieldApi.d.cts +1 -0
- package/build/legacy/FieldApi.d.ts +1 -0
- package/build/legacy/FieldApi.js +46 -18
- package/build/legacy/FieldApi.js.map +1 -1
- package/build/legacy/FormApi.cjs +10 -12
- package/build/legacy/FormApi.cjs.map +1 -1
- package/build/legacy/FormApi.d.cts +2 -1
- package/build/legacy/FormApi.d.ts +2 -1
- package/build/legacy/FormApi.js +10 -14
- package/build/legacy/FormApi.js.map +1 -1
- package/build/legacy/index.cjs +3 -1
- package/build/legacy/index.cjs.map +1 -1
- package/build/legacy/index.d.cts +48 -52
- package/build/legacy/index.d.ts +48 -52
- package/build/legacy/index.js +1 -0
- package/build/legacy/index.js.map +1 -1
- package/build/legacy/types.cjs +19 -0
- package/build/legacy/types.cjs.map +1 -0
- package/build/legacy/types.d.cts +7 -0
- package/build/legacy/types.d.ts +7 -0
- package/build/legacy/types.js +1 -0
- package/build/legacy/utils.js +0 -2
- package/build/legacy/utils.js.map +1 -1
- package/build/modern/FieldApi.cjs +44 -9
- package/build/modern/FieldApi.cjs.map +1 -1
- package/build/modern/FieldApi.d.cts +1 -0
- package/build/modern/FieldApi.d.ts +1 -0
- package/build/modern/FieldApi.js +44 -9
- package/build/modern/FieldApi.js.map +1 -1
- package/build/modern/FormApi.cjs +10 -12
- package/build/modern/FormApi.cjs.map +1 -1
- package/build/modern/FormApi.d.cts +2 -1
- package/build/modern/FormApi.d.ts +2 -1
- package/build/modern/FormApi.js +10 -12
- package/build/modern/FormApi.js.map +1 -1
- package/build/modern/index.cjs +3 -1
- package/build/modern/index.cjs.map +1 -1
- package/build/modern/index.d.cts +48 -52
- package/build/modern/index.d.ts +48 -52
- package/build/modern/index.js +1 -0
- package/build/modern/index.js.map +1 -1
- package/build/modern/types.cjs +19 -0
- package/build/modern/types.cjs.map +1 -0
- package/build/modern/types.d.cts +7 -0
- package/build/modern/types.d.ts +7 -0
- package/build/modern/types.js +1 -0
- package/build/modern/types.js.map +1 -0
- package/package.json +1 -1
- package/src/FieldApi.ts +171 -44
- package/src/FormApi.ts +61 -46
- package/src/index.ts +1 -0
- package/src/tests/FieldApi.spec.ts +1 -1
- package/src/tests/FieldApi.test-d.ts +26 -11
- package/src/tests/FormApi.spec.ts +1 -1
- package/src/types.ts +7 -0
- package/build/legacy/chunk-4QZDOMDG.js +0 -19
- /package/build/legacy/{chunk-4QZDOMDG.js.map → types.js.map} +0 -0
package/src/FieldApi.ts
CHANGED
|
@@ -1,57 +1,126 @@
|
|
|
1
1
|
import { type DeepKeys, type DeepValue, type Updater } from './utils'
|
|
2
|
-
import type { FormApi,
|
|
2
|
+
import type { FormApi, ValidationErrorMap } from './FormApi'
|
|
3
3
|
import { Store } from '@tanstack/store'
|
|
4
|
+
import type { Validator, ValidationError } from './types'
|
|
4
5
|
|
|
5
6
|
export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount'
|
|
6
7
|
|
|
7
|
-
type ValidateFn<
|
|
8
|
+
type ValidateFn<
|
|
9
|
+
TParentData,
|
|
10
|
+
TName extends DeepKeys<TParentData>,
|
|
11
|
+
ValidatorType,
|
|
12
|
+
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
13
|
+
> = (
|
|
8
14
|
value: TData,
|
|
9
|
-
fieldApi: FieldApi<TParentData, TName>,
|
|
15
|
+
fieldApi: FieldApi<TParentData, TName, ValidatorType, TData>,
|
|
10
16
|
) => ValidationError
|
|
11
17
|
|
|
18
|
+
type ValidateOrFn<
|
|
19
|
+
TParentData,
|
|
20
|
+
TName extends DeepKeys<TParentData>,
|
|
21
|
+
ValidatorType,
|
|
22
|
+
FormValidator,
|
|
23
|
+
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
24
|
+
> = ValidatorType extends Validator<TData>
|
|
25
|
+
?
|
|
26
|
+
| Parameters<ReturnType<ValidatorType>['validate']>[1]
|
|
27
|
+
| ValidateFn<TParentData, TName, ValidatorType, TData>
|
|
28
|
+
: FormValidator extends Validator<TData>
|
|
29
|
+
?
|
|
30
|
+
| Parameters<ReturnType<FormValidator>['validate']>[1]
|
|
31
|
+
| ValidateFn<TParentData, TName, ValidatorType, TData>
|
|
32
|
+
: ValidateFn<TParentData, TName, ValidatorType, TData>
|
|
33
|
+
|
|
12
34
|
type ValidateAsyncFn<
|
|
13
35
|
TParentData,
|
|
14
36
|
TName extends DeepKeys<TParentData>,
|
|
15
|
-
|
|
37
|
+
ValidatorType,
|
|
38
|
+
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
16
39
|
> = (
|
|
17
40
|
value: TData,
|
|
18
|
-
fieldApi: FieldApi<TParentData, TName>,
|
|
41
|
+
fieldApi: FieldApi<TParentData, TName, ValidatorType, TData>,
|
|
19
42
|
) => ValidationError | Promise<ValidationError>
|
|
20
43
|
|
|
44
|
+
type AsyncValidateOrFn<
|
|
45
|
+
TParentData,
|
|
46
|
+
TName extends DeepKeys<TParentData>,
|
|
47
|
+
ValidatorType,
|
|
48
|
+
FormValidator,
|
|
49
|
+
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
50
|
+
> = ValidatorType extends Validator<TData>
|
|
51
|
+
?
|
|
52
|
+
| Parameters<ReturnType<ValidatorType>['validate']>[1]
|
|
53
|
+
| ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
|
|
54
|
+
: FormValidator extends Validator<TData>
|
|
55
|
+
?
|
|
56
|
+
| Parameters<ReturnType<FormValidator>['validate']>[1]
|
|
57
|
+
| ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
|
|
58
|
+
: ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
|
|
59
|
+
|
|
21
60
|
export interface FieldOptions<
|
|
22
61
|
TParentData,
|
|
23
|
-
/**
|
|
24
|
-
* This allows us to restrict the name to only be a valid field name while
|
|
25
|
-
* also assigning it to a generic
|
|
26
|
-
*/
|
|
27
62
|
TName extends DeepKeys<TParentData>,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
TData = DeepValue<TParentData, TName>,
|
|
63
|
+
ValidatorType,
|
|
64
|
+
FormValidator,
|
|
65
|
+
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
32
66
|
> {
|
|
33
|
-
name:
|
|
67
|
+
name: TName
|
|
34
68
|
index?: TData extends any[] ? number : never
|
|
35
69
|
defaultValue?: TData
|
|
36
70
|
asyncDebounceMs?: number
|
|
37
71
|
asyncAlways?: boolean
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
validator?: ValidatorType
|
|
73
|
+
onMount?: (
|
|
74
|
+
formApi: FieldApi<TParentData, TName, ValidatorType, TData>,
|
|
75
|
+
) => void
|
|
76
|
+
onChange?: ValidateOrFn<
|
|
77
|
+
TParentData,
|
|
78
|
+
TName,
|
|
79
|
+
ValidatorType,
|
|
80
|
+
FormValidator,
|
|
81
|
+
TData
|
|
82
|
+
>
|
|
83
|
+
onChangeAsync?: AsyncValidateOrFn<
|
|
84
|
+
TParentData,
|
|
85
|
+
TName,
|
|
86
|
+
ValidatorType,
|
|
87
|
+
FormValidator,
|
|
88
|
+
TData
|
|
89
|
+
>
|
|
41
90
|
onChangeAsyncDebounceMs?: number
|
|
42
|
-
onBlur?:
|
|
43
|
-
onBlurAsync?:
|
|
91
|
+
onBlur?: ValidateOrFn<TParentData, TName, ValidatorType, FormValidator, TData>
|
|
92
|
+
onBlurAsync?: AsyncValidateOrFn<
|
|
93
|
+
TParentData,
|
|
94
|
+
TName,
|
|
95
|
+
ValidatorType,
|
|
96
|
+
FormValidator,
|
|
97
|
+
TData
|
|
98
|
+
>
|
|
44
99
|
onBlurAsyncDebounceMs?: number
|
|
45
|
-
onSubmitAsync?:
|
|
100
|
+
onSubmitAsync?: AsyncValidateOrFn<
|
|
101
|
+
TParentData,
|
|
102
|
+
TName,
|
|
103
|
+
ValidatorType,
|
|
104
|
+
FormValidator,
|
|
105
|
+
TData
|
|
106
|
+
>
|
|
46
107
|
defaultMeta?: Partial<FieldMeta>
|
|
47
108
|
}
|
|
48
109
|
|
|
49
110
|
export interface FieldApiOptions<
|
|
50
111
|
TParentData,
|
|
51
112
|
TName extends DeepKeys<TParentData>,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
113
|
+
ValidatorType,
|
|
114
|
+
FormValidator,
|
|
115
|
+
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
116
|
+
> extends FieldOptions<
|
|
117
|
+
TParentData,
|
|
118
|
+
TName,
|
|
119
|
+
ValidatorType,
|
|
120
|
+
FormValidator,
|
|
121
|
+
TData
|
|
122
|
+
> {
|
|
123
|
+
form: FormApi<TParentData, FormValidator>
|
|
55
124
|
}
|
|
56
125
|
|
|
57
126
|
export type FieldMeta = {
|
|
@@ -76,22 +145,28 @@ export type ResolveName<TParentData> = unknown extends TParentData
|
|
|
76
145
|
export class FieldApi<
|
|
77
146
|
TParentData,
|
|
78
147
|
TName extends DeepKeys<TParentData>,
|
|
79
|
-
|
|
148
|
+
ValidatorType,
|
|
149
|
+
FormValidator,
|
|
150
|
+
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
80
151
|
> {
|
|
81
152
|
uid: number
|
|
82
|
-
form: FieldApiOptions<TParentData, TName, TData>['form']
|
|
153
|
+
form: FieldApiOptions<TParentData, TName, ValidatorType, TData>['form']
|
|
83
154
|
name!: DeepKeys<TParentData>
|
|
84
|
-
options: FieldApiOptions<TParentData, TName> = {} as any
|
|
155
|
+
options: FieldApiOptions<TParentData, TName, ValidatorType, TData> = {} as any
|
|
85
156
|
store!: Store<FieldState<TData>>
|
|
86
157
|
state!: FieldState<TData>
|
|
87
158
|
prevState!: FieldState<TData>
|
|
88
159
|
|
|
89
160
|
constructor(
|
|
90
|
-
opts: FieldApiOptions<
|
|
91
|
-
|
|
92
|
-
|
|
161
|
+
opts: FieldApiOptions<
|
|
162
|
+
TParentData,
|
|
163
|
+
TName,
|
|
164
|
+
ValidatorType,
|
|
165
|
+
FormValidator,
|
|
166
|
+
TData
|
|
167
|
+
>,
|
|
93
168
|
) {
|
|
94
|
-
this.form = opts.form
|
|
169
|
+
this.form = opts.form as never
|
|
95
170
|
this.uid = uid++
|
|
96
171
|
// Support field prefixing from FieldScope
|
|
97
172
|
// let fieldPrefix = ''
|
|
@@ -99,7 +174,7 @@ export class FieldApi<
|
|
|
99
174
|
// fieldPrefix = `${this.form.fieldName}.`
|
|
100
175
|
// }
|
|
101
176
|
|
|
102
|
-
this.name = opts.name as
|
|
177
|
+
this.name = opts.name as never
|
|
103
178
|
|
|
104
179
|
if (opts.defaultValue !== undefined) {
|
|
105
180
|
this.form.setFieldValue(this.name, opts.defaultValue as never)
|
|
@@ -167,12 +242,14 @@ export class FieldApi<
|
|
|
167
242
|
unsubscribe()
|
|
168
243
|
delete info.instances[this.uid]
|
|
169
244
|
if (!Object.keys(info.instances).length) {
|
|
170
|
-
delete this.form.fieldInfo[this.name]
|
|
245
|
+
delete this.form.fieldInfo[this.name as never]
|
|
171
246
|
}
|
|
172
247
|
}
|
|
173
248
|
}
|
|
174
249
|
|
|
175
|
-
update = (
|
|
250
|
+
update = (
|
|
251
|
+
opts: FieldApiOptions<TParentData, TName, ValidatorType, TData>,
|
|
252
|
+
) => {
|
|
176
253
|
// Default Value
|
|
177
254
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
178
255
|
if (this.state.value === undefined) {
|
|
@@ -238,10 +315,10 @@ export class FieldApi<
|
|
|
238
315
|
|
|
239
316
|
getSubField = <
|
|
240
317
|
TSubName extends DeepKeys<TData>,
|
|
241
|
-
TSubData = DeepValue<TData, TSubName>,
|
|
318
|
+
TSubData extends DeepValue<TData, TSubName> = DeepValue<TData, TSubName>,
|
|
242
319
|
>(
|
|
243
320
|
name: TSubName,
|
|
244
|
-
): FieldApi<TData, TSubName, TSubData> =>
|
|
321
|
+
): FieldApi<TData, TSubName, ValidatorType, TSubData> =>
|
|
245
322
|
new FieldApi({
|
|
246
323
|
name: `${this.name}.${name}` as never,
|
|
247
324
|
form: this.form,
|
|
@@ -251,13 +328,36 @@ export class FieldApi<
|
|
|
251
328
|
const { onChange, onBlur } = this.options
|
|
252
329
|
const validate =
|
|
253
330
|
cause === 'submit' ? undefined : cause === 'change' ? onChange : onBlur
|
|
331
|
+
|
|
254
332
|
if (!validate) return
|
|
255
333
|
|
|
256
334
|
// Use the validationCount for all field instances to
|
|
257
335
|
// track freshness of the validation
|
|
258
336
|
const validationCount = (this.getInfo().validationCount || 0) + 1
|
|
259
337
|
this.getInfo().validationCount = validationCount
|
|
260
|
-
|
|
338
|
+
|
|
339
|
+
const doValidate = () => {
|
|
340
|
+
if (this.options.validator && typeof validate !== 'function') {
|
|
341
|
+
return (this.options.validator as Validator<TData>)().validate(
|
|
342
|
+
value,
|
|
343
|
+
validate,
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (this.form.options.validator && typeof validate !== 'function') {
|
|
348
|
+
return (this.form.options.validator as Validator<TData>)().validate(
|
|
349
|
+
value,
|
|
350
|
+
validate,
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return (validate as ValidateFn<TParentData, TName, ValidatorType, TData>)(
|
|
355
|
+
value,
|
|
356
|
+
this as never,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const error = normalizeError(doValidate())
|
|
261
361
|
const errorMapKey = getErrorMapKey(cause)
|
|
262
362
|
if (this.state.meta.errorMap[errorMapKey] !== error) {
|
|
263
363
|
this.setMeta((prev) => ({
|
|
@@ -275,7 +375,7 @@ export class FieldApi<
|
|
|
275
375
|
}
|
|
276
376
|
}
|
|
277
377
|
|
|
278
|
-
|
|
378
|
+
__leaseValidateAsync = () => {
|
|
279
379
|
const count = (this.getInfo().validationAsyncCount || 0) + 1
|
|
280
380
|
this.getInfo().validationAsyncCount = count
|
|
281
381
|
return count
|
|
@@ -283,7 +383,7 @@ export class FieldApi<
|
|
|
283
383
|
|
|
284
384
|
cancelValidateAsync = () => {
|
|
285
385
|
// Lease a new validation count to ignore any pending validations
|
|
286
|
-
this
|
|
386
|
+
this.__leaseValidateAsync()
|
|
287
387
|
// Cancel any pending validation state
|
|
288
388
|
this.setMeta((prev) => ({
|
|
289
389
|
...prev,
|
|
@@ -317,12 +417,13 @@ export class FieldApi<
|
|
|
317
417
|
asyncDebounceMs ??
|
|
318
418
|
0
|
|
319
419
|
|
|
320
|
-
if (this.state.meta.isValidating !== true)
|
|
420
|
+
if (this.state.meta.isValidating !== true) {
|
|
321
421
|
this.setMeta((prev) => ({ ...prev, isValidating: true }))
|
|
422
|
+
}
|
|
322
423
|
|
|
323
424
|
// Use the validationCount for all field instances to
|
|
324
425
|
// track freshness of the validation
|
|
325
|
-
const validationAsyncCount = this
|
|
426
|
+
const validationAsyncCount = this.__leaseValidateAsync()
|
|
326
427
|
|
|
327
428
|
const checkLatest = () =>
|
|
328
429
|
validationAsyncCount === this.getInfo().validationAsyncCount
|
|
@@ -338,11 +439,31 @@ export class FieldApi<
|
|
|
338
439
|
await new Promise((r) => setTimeout(r, debounceMs))
|
|
339
440
|
}
|
|
340
441
|
|
|
442
|
+
const doValidate = () => {
|
|
443
|
+
if (this.options.validator && typeof validate !== 'function') {
|
|
444
|
+
return (this.options.validator as Validator<TData>)().validateAsync(
|
|
445
|
+
value,
|
|
446
|
+
validate,
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (this.form.options.validator && typeof validate !== 'function') {
|
|
451
|
+
return (
|
|
452
|
+
this.form.options.validator as Validator<TData>
|
|
453
|
+
)().validateAsync(value, validate)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return (validate as ValidateFn<TParentData, TName, ValidatorType, TData>)(
|
|
457
|
+
value,
|
|
458
|
+
this as never,
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
|
|
341
462
|
// Only kick off validation if this validation is the latest attempt
|
|
342
463
|
if (checkLatest()) {
|
|
343
464
|
const prevErrors = this.getMeta().errors
|
|
344
465
|
try {
|
|
345
|
-
const rawError = await
|
|
466
|
+
const rawError = await doValidate()
|
|
346
467
|
if (checkLatest()) {
|
|
347
468
|
const error = normalizeError(rawError)
|
|
348
469
|
this.setMeta((prev) => ({
|
|
@@ -378,12 +499,18 @@ export class FieldApi<
|
|
|
378
499
|
): ValidationError[] | Promise<ValidationError[]> => {
|
|
379
500
|
// If the field is pristine and validatePristine is false, do not validate
|
|
380
501
|
if (!this.state.meta.isTouched) return []
|
|
502
|
+
|
|
503
|
+
// Store the previous error for the errorMapKey (eg. onChange, onBlur, onSubmit)
|
|
504
|
+
const errorMapKey = getErrorMapKey(cause)
|
|
505
|
+
const prevError = this.getMeta().errorMap[errorMapKey]
|
|
506
|
+
|
|
381
507
|
// Attempt to sync validate first
|
|
382
508
|
this.validateSync(value, cause)
|
|
383
509
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
510
|
+
// If there is a new error mapped to the errorMapKey (eg. onChange, onBlur, onSubmit), return the errors array, do not attempt async validation
|
|
511
|
+
const newError = this.getMeta().errorMap[errorMapKey]
|
|
512
|
+
|
|
513
|
+
if (prevError !== newError) {
|
|
387
514
|
if (!this.options.asyncAlways) {
|
|
388
515
|
return this.state.meta.errors
|
|
389
516
|
}
|
package/src/FormApi.ts
CHANGED
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
|
-
//
|
|
3
2
|
import type { DeepKeys, DeepValue, Updater } from './utils'
|
|
4
3
|
import { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'
|
|
5
4
|
import type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'
|
|
5
|
+
import type { ValidationError, Validator } from './types'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type ValidateFn<TData, ValidatorType> = (
|
|
8
|
+
values: TData,
|
|
9
|
+
formApi: FormApi<TData, ValidatorType>,
|
|
10
|
+
) => ValidationError
|
|
11
|
+
|
|
12
|
+
type ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>
|
|
13
|
+
? Parameters<ReturnType<ValidatorType>['validate']>[1]
|
|
14
|
+
: ValidateFn<TData, ValidatorType>
|
|
15
|
+
|
|
16
|
+
type ValidateAsyncFn<TData, ValidatorType> = (
|
|
17
|
+
value: TData,
|
|
18
|
+
fieldApi: FormApi<TData, ValidatorType>,
|
|
19
|
+
) => ValidationError | Promise<ValidationError>
|
|
20
|
+
|
|
21
|
+
export type FormOptions<TData, ValidatorType> = {
|
|
8
22
|
defaultValues?: TData
|
|
9
23
|
defaultState?: Partial<FormState<TData>>
|
|
10
24
|
asyncDebounceMs?: number
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
formApi: FormApi<TData>,
|
|
15
|
-
) => ValidationError | Promise<ValidationError>
|
|
25
|
+
validator?: ValidatorType
|
|
26
|
+
onMount?: ValidateOrFn<TData, ValidatorType>
|
|
27
|
+
onMountAsync?: ValidateAsyncFn<TData, ValidatorType>
|
|
16
28
|
onMountAsyncDebounceMs?: number
|
|
17
|
-
onChange?:
|
|
18
|
-
onChangeAsync?:
|
|
19
|
-
values: TData,
|
|
20
|
-
formApi: FormApi<TData>,
|
|
21
|
-
) => ValidationError | Promise<ValidationError>
|
|
29
|
+
onChange?: ValidateOrFn<TData, ValidatorType>
|
|
30
|
+
onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>
|
|
22
31
|
onChangeAsyncDebounceMs?: number
|
|
23
|
-
onBlur?:
|
|
24
|
-
onBlurAsync?:
|
|
25
|
-
values: TData,
|
|
26
|
-
formApi: FormApi<TData>,
|
|
27
|
-
) => ValidationError | Promise<ValidationError>
|
|
32
|
+
onBlur?: ValidateOrFn<TData, ValidatorType>
|
|
33
|
+
onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>
|
|
28
34
|
onBlurAsyncDebounceMs?: number
|
|
29
|
-
onSubmit?: (
|
|
30
|
-
|
|
35
|
+
onSubmit?: (
|
|
36
|
+
values: TData,
|
|
37
|
+
formApi: FormApi<TData, ValidatorType>,
|
|
38
|
+
) => any | Promise<any>
|
|
39
|
+
onSubmitInvalid?: (
|
|
40
|
+
values: TData,
|
|
41
|
+
formApi: FormApi<TData, ValidatorType>,
|
|
42
|
+
) => void
|
|
31
43
|
}
|
|
32
44
|
|
|
33
|
-
export type FieldInfo<TFormData> = {
|
|
34
|
-
instances: Record<string, FieldApi<TFormData, any,
|
|
45
|
+
export type FieldInfo<TFormData, ValidatorType> = {
|
|
46
|
+
instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>
|
|
35
47
|
} & ValidationMeta
|
|
36
48
|
|
|
37
49
|
export type ValidationMeta = {
|
|
@@ -42,8 +54,6 @@ export type ValidationMeta = {
|
|
|
42
54
|
validationReject?: (errors: unknown) => void
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
export type ValidationError = undefined | false | null | string
|
|
46
|
-
|
|
47
57
|
export type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`
|
|
48
58
|
|
|
49
59
|
export type ValidationErrorMap = {
|
|
@@ -92,18 +102,19 @@ function getDefaultFormState<TData>(
|
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
|
|
95
|
-
export class FormApi<TFormData> {
|
|
105
|
+
export class FormApi<TFormData, ValidatorType> {
|
|
96
106
|
// // This carries the context for nested fields
|
|
97
|
-
options: FormOptions<TFormData> = {}
|
|
107
|
+
options: FormOptions<TFormData, ValidatorType> = {}
|
|
98
108
|
store!: Store<FormState<TFormData>>
|
|
99
109
|
// Do not use __state directly, as it is not reactive.
|
|
100
110
|
// Please use form.useStore() utility to subscribe to state
|
|
101
111
|
state!: FormState<TFormData>
|
|
102
|
-
fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData>> =
|
|
112
|
+
fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =
|
|
113
|
+
{} as any
|
|
103
114
|
fieldName?: string
|
|
104
115
|
validationMeta: ValidationMeta = {}
|
|
105
116
|
|
|
106
|
-
constructor(opts?: FormOptions<TFormData>) {
|
|
117
|
+
constructor(opts?: FormOptions<TFormData, ValidatorType>) {
|
|
107
118
|
this.store = new Store<FormState<TFormData>>(
|
|
108
119
|
getDefaultFormState({
|
|
109
120
|
...(opts?.defaultState as any),
|
|
@@ -157,7 +168,7 @@ export class FormApi<TFormData> {
|
|
|
157
168
|
this.update(opts || {})
|
|
158
169
|
}
|
|
159
170
|
|
|
160
|
-
update = (options?: FormOptions<TFormData>) => {
|
|
171
|
+
update = (options?: FormOptions<TFormData, ValidatorType>) => {
|
|
161
172
|
if (!options) return
|
|
162
173
|
|
|
163
174
|
this.store.batch(() => {
|
|
@@ -202,21 +213,21 @@ export class FormApi<TFormData> {
|
|
|
202
213
|
validateAllFields = async (cause: ValidationCause) => {
|
|
203
214
|
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
|
|
204
215
|
this.store.batch(() => {
|
|
205
|
-
void (
|
|
206
|
-
(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
)
|
|
216
|
+
void (
|
|
217
|
+
Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]
|
|
218
|
+
).forEach((field) => {
|
|
219
|
+
Object.values(field.instances).forEach((instance) => {
|
|
220
|
+
// If any fields are not touched
|
|
221
|
+
if (!instance.state.meta.isTouched) {
|
|
222
|
+
// Mark them as touched
|
|
223
|
+
instance.setMeta((prev) => ({ ...prev, isTouched: true }))
|
|
224
|
+
// Validate the field
|
|
225
|
+
fieldValidationPromises.push(
|
|
226
|
+
Promise.resolve().then(() => instance.validate(cause)),
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
})
|
|
220
231
|
})
|
|
221
232
|
|
|
222
233
|
return Promise.all(fieldValidationPromises)
|
|
@@ -290,7 +301,7 @@ export class FormApi<TFormData> {
|
|
|
290
301
|
|
|
291
302
|
getFieldInfo = <TField extends DeepKeys<TFormData>>(
|
|
292
303
|
field: TField,
|
|
293
|
-
): FieldInfo<TFormData> => {
|
|
304
|
+
): FieldInfo<TFormData, ValidatorType> => {
|
|
294
305
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
295
306
|
return (this.fieldInfo[field] ||= {
|
|
296
307
|
instances: {},
|
|
@@ -338,7 +349,9 @@ export class FormApi<TFormData> {
|
|
|
338
349
|
|
|
339
350
|
pushFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
340
351
|
field: TField,
|
|
341
|
-
value: DeepValue<TFormData, TField>[
|
|
352
|
+
value: DeepValue<TFormData, TField> extends any[]
|
|
353
|
+
? DeepValue<TFormData, TField>[number]
|
|
354
|
+
: never,
|
|
342
355
|
opts?: { touch?: boolean },
|
|
343
356
|
) => {
|
|
344
357
|
return this.setFieldValue(
|
|
@@ -351,7 +364,9 @@ export class FormApi<TFormData> {
|
|
|
351
364
|
insertFieldValue = <TField extends DeepKeys<TFormData>>(
|
|
352
365
|
field: TField,
|
|
353
366
|
index: number,
|
|
354
|
-
value: DeepValue<TFormData, TField>[
|
|
367
|
+
value: DeepValue<TFormData, TField> extends any[]
|
|
368
|
+
? DeepValue<TFormData, TField>[number]
|
|
369
|
+
: never,
|
|
355
370
|
opts?: { touch?: boolean },
|
|
356
371
|
) => {
|
|
357
372
|
this.setFieldValue(
|
package/src/index.ts
CHANGED
|
@@ -2,24 +2,21 @@ import { assertType } from 'vitest'
|
|
|
2
2
|
import { FormApi } from '../FormApi'
|
|
3
3
|
import { FieldApi } from '../FieldApi'
|
|
4
4
|
|
|
5
|
-
it('should type
|
|
5
|
+
it('should type value properly', () => {
|
|
6
6
|
const form = new FormApi({
|
|
7
7
|
defaultValues: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
} as const,
|
|
13
|
-
})
|
|
8
|
+
name: 'test',
|
|
9
|
+
},
|
|
10
|
+
} as const)
|
|
14
11
|
|
|
15
12
|
const field = new FieldApi({
|
|
16
13
|
form,
|
|
17
|
-
name: '
|
|
14
|
+
name: 'name',
|
|
18
15
|
})
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
assertType<'
|
|
17
|
+
assertType<'test'>(field.state.value)
|
|
18
|
+
assertType<'name'>(field.options.name)
|
|
19
|
+
assertType<'test'>(field.getValue())
|
|
23
20
|
})
|
|
24
21
|
|
|
25
22
|
it('should type onChange properly', () => {
|
|
@@ -39,3 +36,21 @@ it('should type onChange properly', () => {
|
|
|
39
36
|
},
|
|
40
37
|
})
|
|
41
38
|
})
|
|
39
|
+
|
|
40
|
+
it('should type onChangeAsync properly', () => {
|
|
41
|
+
const form = new FormApi({
|
|
42
|
+
defaultValues: {
|
|
43
|
+
name: 'test',
|
|
44
|
+
},
|
|
45
|
+
} as const)
|
|
46
|
+
|
|
47
|
+
const field = new FieldApi({
|
|
48
|
+
form,
|
|
49
|
+
name: 'name',
|
|
50
|
+
onChangeAsync: async (value) => {
|
|
51
|
+
assertType<'test'>(value)
|
|
52
|
+
|
|
53
|
+
return undefined
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
})
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type ValidationError = undefined | false | null | string
|
|
2
|
+
|
|
3
|
+
// If/when TypeScript supports higher-kinded types, this should not be `unknown` anymore
|
|
4
|
+
export type Validator<Type, Fn = unknown> = () => {
|
|
5
|
+
validate(value: Type, fn: Fn): ValidationError
|
|
6
|
+
validateAsync(value: Type, fn: Fn): Promise<ValidationError>
|
|
7
|
+
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
var __accessCheck = (obj, member, msg) => {
|
|
2
|
-
if (!member.has(obj))
|
|
3
|
-
throw TypeError("Cannot " + msg);
|
|
4
|
-
};
|
|
5
|
-
var __privateGet = (obj, member, getter) => {
|
|
6
|
-
__accessCheck(obj, member, "read from private field");
|
|
7
|
-
return getter ? getter.call(obj) : member.get(obj);
|
|
8
|
-
};
|
|
9
|
-
var __privateAdd = (obj, member, value) => {
|
|
10
|
-
if (member.has(obj))
|
|
11
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
12
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export {
|
|
16
|
-
__privateGet,
|
|
17
|
-
__privateAdd
|
|
18
|
-
};
|
|
19
|
-
//# sourceMappingURL=chunk-4QZDOMDG.js.map
|
|
File without changes
|