@tanstack/form-core 0.10.3 → 0.12.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.d.ts +95 -0
- package/dist/cjs/FormApi.d.ts +118 -0
- package/dist/cjs/index.cjs +926 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +5 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +926 -0
- package/dist/cjs/mergeForm.d.ts +4 -0
- package/dist/cjs/tests/FieldApi.spec.d.ts +1 -0
- package/dist/cjs/tests/FieldApi.test-d.d.ts +1 -0
- package/dist/cjs/tests/FormApi.spec.d.ts +1 -0
- package/dist/cjs/tests/mutateMergeDeep.spec.d.ts +1 -0
- package/dist/cjs/tests/utils.d.ts +1 -0
- package/dist/cjs/tests/utils.spec.d.ts +1 -0
- package/dist/cjs/types.d.ts +14 -0
- package/dist/cjs/utils.d.ts +57 -0
- package/dist/mjs/FieldApi.d.ts +95 -0
- package/dist/mjs/FormApi.d.ts +118 -0
- package/dist/mjs/index.d.mts +5 -0
- package/dist/mjs/index.d.ts +5 -0
- package/dist/mjs/index.js +926 -0
- package/dist/mjs/index.mjs +926 -0
- package/dist/mjs/index.mjs.map +1 -0
- package/dist/mjs/mergeForm.d.ts +4 -0
- package/dist/mjs/tests/FieldApi.spec.d.ts +1 -0
- package/dist/mjs/tests/FieldApi.test-d.d.ts +1 -0
- package/dist/mjs/tests/FormApi.spec.d.ts +1 -0
- package/dist/mjs/tests/mutateMergeDeep.spec.d.ts +1 -0
- package/dist/mjs/tests/utils.d.ts +1 -0
- package/dist/mjs/tests/utils.spec.d.ts +1 -0
- package/dist/mjs/types.d.ts +14 -0
- package/dist/mjs/utils.d.ts +57 -0
- package/package.json +16 -21
- package/src/FieldApi.ts +328 -236
- package/src/FormApi.ts +302 -216
- package/src/index.ts +1 -0
- package/src/mergeForm.ts +42 -0
- package/src/tests/FieldApi.spec.ts +135 -48
- package/src/tests/FieldApi.test-d.ts +10 -6
- package/src/tests/FormApi.spec.ts +171 -62
- package/src/tests/mutateMergeDeep.spec.ts +32 -0
- package/src/tests/utils.ts +1 -1
- package/src/types.ts +11 -2
- package/src/utils.ts +137 -14
- package/build/legacy/FieldApi.cjs +0 -340
- package/build/legacy/FieldApi.cjs.map +0 -1
- package/build/legacy/FieldApi.d.cts +0 -4
- package/build/legacy/FieldApi.d.ts +0 -4
- package/build/legacy/FieldApi.js +0 -315
- package/build/legacy/FieldApi.js.map +0 -1
- package/build/legacy/FormApi.cjs +0 -438
- package/build/legacy/FormApi.cjs.map +0 -1
- package/build/legacy/FormApi.d.cts +0 -4
- package/build/legacy/FormApi.d.ts +0 -4
- package/build/legacy/FormApi.js +0 -419
- package/build/legacy/FormApi.js.map +0 -1
- package/build/legacy/index.cjs +0 -31
- package/build/legacy/index.cjs.map +0 -1
- package/build/legacy/index.d.cts +0 -170
- package/build/legacy/index.d.ts +0 -170
- package/build/legacy/index.js +0 -6
- package/build/legacy/index.js.map +0 -1
- package/build/legacy/types.cjs +0 -19
- package/build/legacy/types.cjs.map +0 -1
- package/build/legacy/types.d.cts +0 -7
- package/build/legacy/types.d.ts +0 -7
- package/build/legacy/types.js +0 -1
- package/build/legacy/types.js.map +0 -1
- package/build/legacy/utils.cjs +0 -132
- package/build/legacy/utils.cjs.map +0 -1
- package/build/legacy/utils.d.cts +0 -37
- package/build/legacy/utils.d.ts +0 -37
- package/build/legacy/utils.js +0 -103
- package/build/legacy/utils.js.map +0 -1
- package/build/modern/FieldApi.cjs +0 -337
- package/build/modern/FieldApi.cjs.map +0 -1
- package/build/modern/FieldApi.d.cts +0 -4
- package/build/modern/FieldApi.d.ts +0 -4
- package/build/modern/FieldApi.js +0 -312
- package/build/modern/FieldApi.js.map +0 -1
- package/build/modern/FormApi.cjs +0 -431
- package/build/modern/FormApi.cjs.map +0 -1
- package/build/modern/FormApi.d.cts +0 -4
- package/build/modern/FormApi.d.ts +0 -4
- package/build/modern/FormApi.js +0 -412
- package/build/modern/FormApi.js.map +0 -1
- package/build/modern/index.cjs +0 -31
- package/build/modern/index.cjs.map +0 -1
- package/build/modern/index.d.cts +0 -170
- package/build/modern/index.d.ts +0 -170
- package/build/modern/index.js +0 -6
- package/build/modern/index.js.map +0 -1
- package/build/modern/types.cjs +0 -19
- package/build/modern/types.cjs.map +0 -1
- package/build/modern/types.d.cts +0 -7
- package/build/modern/types.d.ts +0 -7
- package/build/modern/types.js +0 -1
- package/build/modern/types.js.map +0 -1
- package/build/modern/utils.cjs +0 -132
- package/build/modern/utils.cjs.map +0 -1
- package/build/modern/utils.d.cts +0 -37
- package/build/modern/utils.d.ts +0 -37
- package/build/modern/utils.js +0 -103
- package/build/modern/utils.js.map +0 -1
|
@@ -24,7 +24,12 @@ describe('form api', () => {
|
|
|
24
24
|
isValid: true,
|
|
25
25
|
isValidating: false,
|
|
26
26
|
submissionAttempts: 0,
|
|
27
|
-
|
|
27
|
+
validationMetaMap: {
|
|
28
|
+
onChange: undefined,
|
|
29
|
+
onBlur: undefined,
|
|
30
|
+
onSubmit: undefined,
|
|
31
|
+
onMount: undefined,
|
|
32
|
+
},
|
|
28
33
|
})
|
|
29
34
|
})
|
|
30
35
|
|
|
@@ -53,7 +58,12 @@ describe('form api', () => {
|
|
|
53
58
|
isValid: true,
|
|
54
59
|
isValidating: false,
|
|
55
60
|
submissionAttempts: 0,
|
|
56
|
-
|
|
61
|
+
validationMetaMap: {
|
|
62
|
+
onChange: undefined,
|
|
63
|
+
onBlur: undefined,
|
|
64
|
+
onSubmit: undefined,
|
|
65
|
+
onMount: undefined,
|
|
66
|
+
},
|
|
57
67
|
})
|
|
58
68
|
})
|
|
59
69
|
|
|
@@ -80,7 +90,12 @@ describe('form api', () => {
|
|
|
80
90
|
isValid: true,
|
|
81
91
|
isValidating: false,
|
|
82
92
|
submissionAttempts: 30,
|
|
83
|
-
|
|
93
|
+
validationMetaMap: {
|
|
94
|
+
onChange: undefined,
|
|
95
|
+
onBlur: undefined,
|
|
96
|
+
onSubmit: undefined,
|
|
97
|
+
onMount: undefined,
|
|
98
|
+
},
|
|
84
99
|
})
|
|
85
100
|
})
|
|
86
101
|
|
|
@@ -118,7 +133,13 @@ describe('form api', () => {
|
|
|
118
133
|
isValid: true,
|
|
119
134
|
isValidating: false,
|
|
120
135
|
submissionAttempts: 300,
|
|
121
|
-
|
|
136
|
+
validationMetaMap: {
|
|
137
|
+
onChange: undefined,
|
|
138
|
+
onBlur: undefined,
|
|
139
|
+
onSubmit: undefined,
|
|
140
|
+
onMount: undefined,
|
|
141
|
+
onServer: undefined,
|
|
142
|
+
},
|
|
122
143
|
})
|
|
123
144
|
})
|
|
124
145
|
|
|
@@ -152,7 +173,12 @@ describe('form api', () => {
|
|
|
152
173
|
isValid: true,
|
|
153
174
|
isValidating: false,
|
|
154
175
|
submissionAttempts: 0,
|
|
155
|
-
|
|
176
|
+
validationMetaMap: {
|
|
177
|
+
onChange: undefined,
|
|
178
|
+
onBlur: undefined,
|
|
179
|
+
onSubmit: undefined,
|
|
180
|
+
onMount: undefined,
|
|
181
|
+
},
|
|
156
182
|
})
|
|
157
183
|
})
|
|
158
184
|
|
|
@@ -234,7 +260,7 @@ describe('form api', () => {
|
|
|
234
260
|
employees: Partial<Employee>[]
|
|
235
261
|
}
|
|
236
262
|
|
|
237
|
-
const form = new FormApi<Form
|
|
263
|
+
const form = new FormApi<Form>()
|
|
238
264
|
|
|
239
265
|
const field = new FieldApi({
|
|
240
266
|
form,
|
|
@@ -262,7 +288,7 @@ describe('form api', () => {
|
|
|
262
288
|
employees: Partial<Employee>[]
|
|
263
289
|
}
|
|
264
290
|
|
|
265
|
-
const form = new FormApi<Form
|
|
291
|
+
const form = new FormApi<Form>()
|
|
266
292
|
|
|
267
293
|
const field = new FieldApi({
|
|
268
294
|
form,
|
|
@@ -362,7 +388,9 @@ describe('form api', () => {
|
|
|
362
388
|
const field = new FieldApi({
|
|
363
389
|
form,
|
|
364
390
|
name: 'name',
|
|
365
|
-
|
|
391
|
+
validators: {
|
|
392
|
+
onChange: ({ value }) => (value.length > 0 ? undefined : 'required'),
|
|
393
|
+
},
|
|
366
394
|
})
|
|
367
395
|
|
|
368
396
|
form.mount()
|
|
@@ -390,9 +418,11 @@ describe('form api', () => {
|
|
|
390
418
|
defaultValues: {
|
|
391
419
|
name: 'test',
|
|
392
420
|
},
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
421
|
+
validators: {
|
|
422
|
+
onChange: ({ value }) => {
|
|
423
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
424
|
+
return
|
|
425
|
+
},
|
|
396
426
|
},
|
|
397
427
|
})
|
|
398
428
|
|
|
@@ -418,10 +448,12 @@ describe('form api', () => {
|
|
|
418
448
|
defaultValues: {
|
|
419
449
|
name: 'test',
|
|
420
450
|
},
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
451
|
+
validators: {
|
|
452
|
+
onChangeAsync: async ({ value }) => {
|
|
453
|
+
await sleep(1000)
|
|
454
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
455
|
+
return
|
|
456
|
+
},
|
|
425
457
|
},
|
|
426
458
|
})
|
|
427
459
|
const field = new FieldApi({
|
|
@@ -449,11 +481,13 @@ describe('form api', () => {
|
|
|
449
481
|
defaultValues: {
|
|
450
482
|
name: 'test',
|
|
451
483
|
},
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
484
|
+
validators: {
|
|
485
|
+
onChangeAsyncDebounceMs: 1000,
|
|
486
|
+
onChangeAsync: async ({ value }) => {
|
|
487
|
+
await sleepMock(1000)
|
|
488
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
489
|
+
return
|
|
490
|
+
},
|
|
457
491
|
},
|
|
458
492
|
})
|
|
459
493
|
const field = new FieldApi({
|
|
@@ -485,10 +519,12 @@ describe('form api', () => {
|
|
|
485
519
|
name: 'test',
|
|
486
520
|
},
|
|
487
521
|
asyncDebounceMs: 1000,
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
522
|
+
validators: {
|
|
523
|
+
onChangeAsync: async ({ value }) => {
|
|
524
|
+
await sleepMock(1000)
|
|
525
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
526
|
+
return
|
|
527
|
+
},
|
|
492
528
|
},
|
|
493
529
|
})
|
|
494
530
|
const field = new FieldApi({
|
|
@@ -516,9 +552,11 @@ describe('form api', () => {
|
|
|
516
552
|
defaultValues: {
|
|
517
553
|
name: 'other',
|
|
518
554
|
},
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
555
|
+
validators: {
|
|
556
|
+
onBlur: ({ value }) => {
|
|
557
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
558
|
+
return
|
|
559
|
+
},
|
|
522
560
|
},
|
|
523
561
|
})
|
|
524
562
|
const field = new FieldApi({
|
|
@@ -544,10 +582,12 @@ describe('form api', () => {
|
|
|
544
582
|
defaultValues: {
|
|
545
583
|
name: 'test',
|
|
546
584
|
},
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
585
|
+
validators: {
|
|
586
|
+
onBlurAsync: async ({ value }) => {
|
|
587
|
+
await sleep(1000)
|
|
588
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
589
|
+
return
|
|
590
|
+
},
|
|
551
591
|
},
|
|
552
592
|
})
|
|
553
593
|
const field = new FieldApi({
|
|
@@ -575,11 +615,13 @@ describe('form api', () => {
|
|
|
575
615
|
defaultValues: {
|
|
576
616
|
name: 'test',
|
|
577
617
|
},
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
618
|
+
validators: {
|
|
619
|
+
onBlurAsyncDebounceMs: 1000,
|
|
620
|
+
onBlurAsync: async ({ value }) => {
|
|
621
|
+
await sleepMock(10)
|
|
622
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
623
|
+
return
|
|
624
|
+
},
|
|
583
625
|
},
|
|
584
626
|
})
|
|
585
627
|
const field = new FieldApi({
|
|
@@ -612,10 +654,12 @@ describe('form api', () => {
|
|
|
612
654
|
name: 'test',
|
|
613
655
|
},
|
|
614
656
|
asyncDebounceMs: 1000,
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
657
|
+
validators: {
|
|
658
|
+
onBlurAsync: async ({ value }) => {
|
|
659
|
+
await sleepMock(10)
|
|
660
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
661
|
+
return
|
|
662
|
+
},
|
|
619
663
|
},
|
|
620
664
|
})
|
|
621
665
|
const field = new FieldApi({
|
|
@@ -644,13 +688,15 @@ describe('form api', () => {
|
|
|
644
688
|
defaultValues: {
|
|
645
689
|
name: 'other',
|
|
646
690
|
},
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
691
|
+
validators: {
|
|
692
|
+
onBlur: ({ value }) => {
|
|
693
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
694
|
+
return
|
|
695
|
+
},
|
|
696
|
+
onChange: ({ value }) => {
|
|
697
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
698
|
+
return
|
|
699
|
+
},
|
|
654
700
|
},
|
|
655
701
|
})
|
|
656
702
|
const field = new FieldApi({
|
|
@@ -678,9 +724,11 @@ describe('form api', () => {
|
|
|
678
724
|
defaultValues: {
|
|
679
725
|
name: 'other',
|
|
680
726
|
},
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
727
|
+
validators: {
|
|
728
|
+
onChange: ({ value }) => {
|
|
729
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
730
|
+
return
|
|
731
|
+
},
|
|
684
732
|
},
|
|
685
733
|
})
|
|
686
734
|
const field = new FieldApi({
|
|
@@ -706,9 +754,11 @@ describe('form api', () => {
|
|
|
706
754
|
defaultValues: {
|
|
707
755
|
name: 'other',
|
|
708
756
|
},
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
757
|
+
validators: {
|
|
758
|
+
onMount: ({ value }) => {
|
|
759
|
+
if (value.name === 'other') return 'Please enter a different value'
|
|
760
|
+
return
|
|
761
|
+
},
|
|
712
762
|
},
|
|
713
763
|
})
|
|
714
764
|
const field = new FieldApi({
|
|
@@ -736,13 +786,19 @@ describe('form api', () => {
|
|
|
736
786
|
const field = new FieldApi({
|
|
737
787
|
form,
|
|
738
788
|
name: 'firstName',
|
|
739
|
-
|
|
789
|
+
validators: {
|
|
790
|
+
onChange: ({ value }) =>
|
|
791
|
+
value.length > 0 ? undefined : 'first name is required',
|
|
792
|
+
},
|
|
740
793
|
})
|
|
741
794
|
|
|
742
795
|
const lastNameField = new FieldApi({
|
|
743
796
|
form,
|
|
744
797
|
name: 'lastName',
|
|
745
|
-
|
|
798
|
+
validators: {
|
|
799
|
+
onChange: ({ value }) =>
|
|
800
|
+
value.length > 0 ? undefined : 'last name is required',
|
|
801
|
+
},
|
|
746
802
|
})
|
|
747
803
|
|
|
748
804
|
field.mount()
|
|
@@ -770,11 +826,14 @@ describe('form api', () => {
|
|
|
770
826
|
const field = new FieldApi({
|
|
771
827
|
form,
|
|
772
828
|
name: 'firstName',
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
829
|
+
validators: {
|
|
830
|
+
onChange: ({ value }) =>
|
|
831
|
+
value.length > 0 ? undefined : 'first name is required',
|
|
832
|
+
onBlur: ({ value }) =>
|
|
833
|
+
value.length > 3
|
|
834
|
+
? undefined
|
|
835
|
+
: 'first name must be longer than 3 characters',
|
|
836
|
+
},
|
|
778
837
|
})
|
|
779
838
|
|
|
780
839
|
field.mount()
|
|
@@ -798,7 +857,10 @@ describe('form api', () => {
|
|
|
798
857
|
const field = new FieldApi({
|
|
799
858
|
form,
|
|
800
859
|
name: 'firstName',
|
|
801
|
-
|
|
860
|
+
validators: {
|
|
861
|
+
onSubmit: ({ value }) =>
|
|
862
|
+
value.length > 0 ? undefined : 'first name is required',
|
|
863
|
+
},
|
|
802
864
|
})
|
|
803
865
|
|
|
804
866
|
field.mount()
|
|
@@ -828,7 +890,10 @@ describe('form api', () => {
|
|
|
828
890
|
const field = new FieldApi({
|
|
829
891
|
form,
|
|
830
892
|
name: 'firstName',
|
|
831
|
-
|
|
893
|
+
validators: {
|
|
894
|
+
onChange: ({ value }) =>
|
|
895
|
+
value.length > 0 ? undefined : 'first name is required',
|
|
896
|
+
},
|
|
832
897
|
})
|
|
833
898
|
|
|
834
899
|
field.mount()
|
|
@@ -839,4 +904,48 @@ describe('form api', () => {
|
|
|
839
904
|
await form.validateAllFields('change')
|
|
840
905
|
expect(field.getMeta().errorMap.onChange).toEqual('first name is required')
|
|
841
906
|
})
|
|
907
|
+
|
|
908
|
+
it('should show onSubmit errors', async () => {
|
|
909
|
+
const form = new FormApi({
|
|
910
|
+
defaultValues: {
|
|
911
|
+
firstName: '',
|
|
912
|
+
},
|
|
913
|
+
validators: {
|
|
914
|
+
onSubmit: ({ value }) =>
|
|
915
|
+
value.firstName.length > 0 ? undefined : 'first name is required',
|
|
916
|
+
},
|
|
917
|
+
})
|
|
918
|
+
|
|
919
|
+
const field = new FieldApi({
|
|
920
|
+
form,
|
|
921
|
+
name: 'firstName',
|
|
922
|
+
})
|
|
923
|
+
|
|
924
|
+
field.mount()
|
|
925
|
+
|
|
926
|
+
await form.handleSubmit()
|
|
927
|
+
expect(form.state.errors).toStrictEqual(['first name is required'])
|
|
928
|
+
})
|
|
929
|
+
|
|
930
|
+
it('should run onChange validation during submit', async () => {
|
|
931
|
+
const form = new FormApi({
|
|
932
|
+
defaultValues: {
|
|
933
|
+
firstName: '',
|
|
934
|
+
},
|
|
935
|
+
validators: {
|
|
936
|
+
onChange: ({ value }) =>
|
|
937
|
+
value.firstName.length > 0 ? undefined : 'first name is required',
|
|
938
|
+
},
|
|
939
|
+
})
|
|
940
|
+
|
|
941
|
+
const field = new FieldApi({
|
|
942
|
+
form,
|
|
943
|
+
name: 'firstName',
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
field.mount()
|
|
947
|
+
|
|
948
|
+
await form.handleSubmit()
|
|
949
|
+
expect(form.state.errors).toStrictEqual(['first name is required'])
|
|
950
|
+
})
|
|
842
951
|
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe } from 'vitest'
|
|
2
|
+
import { mutateMergeDeep } from '../mergeForm'
|
|
3
|
+
|
|
4
|
+
describe('mutateMergeDeep', () => {
|
|
5
|
+
test('Should merge two objects by mutating', () => {
|
|
6
|
+
const a = { a: 1 }
|
|
7
|
+
const b = { b: 2 }
|
|
8
|
+
mutateMergeDeep(a, b)
|
|
9
|
+
expect(a).toStrictEqual({ a: 1, b: 2 })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('Should merge two objects including overwriting with undefined', () => {
|
|
13
|
+
const a = { a: 1 }
|
|
14
|
+
const b = { a: undefined }
|
|
15
|
+
mutateMergeDeep(a, b)
|
|
16
|
+
expect(a).toStrictEqual({ a: undefined })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('Should merge two object by merging arrays', () => {
|
|
20
|
+
const a = { a: [1] }
|
|
21
|
+
const b = { a: [2] }
|
|
22
|
+
mutateMergeDeep(a, b)
|
|
23
|
+
expect(a).toStrictEqual({ a: [1, 2] })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('Should merge two deeply nested objects', () => {
|
|
27
|
+
const a = { a: { a: 1 } }
|
|
28
|
+
const b = { a: { b: 2 } }
|
|
29
|
+
mutateMergeDeep(a, b)
|
|
30
|
+
expect(a).toStrictEqual({ a: { a: 1, b: 2 } })
|
|
31
|
+
})
|
|
32
|
+
})
|
package/src/tests/utils.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -2,6 +2,15 @@ export type ValidationError = undefined | false | null | string
|
|
|
2
2
|
|
|
3
3
|
// If/when TypeScript supports higher-kinded types, this should not be `unknown` anymore
|
|
4
4
|
export type Validator<Type, Fn = unknown> = () => {
|
|
5
|
-
validate(value: Type, fn: Fn): ValidationError
|
|
6
|
-
validateAsync(value: Type, fn: Fn): Promise<ValidationError>
|
|
5
|
+
validate(options: { value: Type }, fn: Fn): ValidationError
|
|
6
|
+
validateAsync(options: { value: Type }, fn: Fn): Promise<ValidationError>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// "server" is only intended for SSR/SSG validation and should not execute anything
|
|
10
|
+
export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount' | 'server'
|
|
11
|
+
|
|
12
|
+
export type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`
|
|
13
|
+
|
|
14
|
+
export type ValidationErrorMap = {
|
|
15
|
+
[K in ValidationErrorMapKeys]?: ValidationError
|
|
7
16
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { ValidationCause, Validator } from './types'
|
|
2
|
+
import type { FormValidators } from './FormApi'
|
|
3
|
+
import type { FieldValidators } from './FieldApi'
|
|
4
|
+
|
|
1
5
|
export type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput
|
|
2
6
|
|
|
3
7
|
export type Updater<TInput, TOutput = TInput> =
|
|
@@ -141,6 +145,125 @@ export function isNonEmptyArray(obj: any) {
|
|
|
141
145
|
return !(Array.isArray(obj) && obj.length === 0)
|
|
142
146
|
}
|
|
143
147
|
|
|
148
|
+
interface AsyncValidatorArrayPartialOptions<T> {
|
|
149
|
+
validators?: T
|
|
150
|
+
asyncDebounceMs?: number
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface AsyncValidator<T> {
|
|
154
|
+
cause: ValidationCause
|
|
155
|
+
validate: T
|
|
156
|
+
debounceMs: number
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function getAsyncValidatorArray<T>(
|
|
160
|
+
cause: ValidationCause,
|
|
161
|
+
options: AsyncValidatorArrayPartialOptions<T>,
|
|
162
|
+
): T extends FieldValidators<any, any>
|
|
163
|
+
? Array<
|
|
164
|
+
AsyncValidator<T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']>
|
|
165
|
+
>
|
|
166
|
+
: T extends FormValidators<any, any>
|
|
167
|
+
? Array<
|
|
168
|
+
AsyncValidator<
|
|
169
|
+
T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']
|
|
170
|
+
>
|
|
171
|
+
>
|
|
172
|
+
: never {
|
|
173
|
+
const { asyncDebounceMs } = options
|
|
174
|
+
const {
|
|
175
|
+
onChangeAsync,
|
|
176
|
+
onBlurAsync,
|
|
177
|
+
onSubmitAsync,
|
|
178
|
+
onBlurAsyncDebounceMs,
|
|
179
|
+
onChangeAsyncDebounceMs,
|
|
180
|
+
onSubmitAsyncDebounceMs,
|
|
181
|
+
} = (options.validators || {}) as
|
|
182
|
+
| FieldValidators<any, any>
|
|
183
|
+
| FormValidators<any, any>
|
|
184
|
+
|
|
185
|
+
const defaultDebounceMs = asyncDebounceMs ?? 0
|
|
186
|
+
|
|
187
|
+
const changeValidator = {
|
|
188
|
+
cause: 'change',
|
|
189
|
+
validate: onChangeAsync,
|
|
190
|
+
debounceMs: onChangeAsyncDebounceMs ?? defaultDebounceMs,
|
|
191
|
+
} as const
|
|
192
|
+
|
|
193
|
+
const blurValidator = {
|
|
194
|
+
cause: 'blur',
|
|
195
|
+
validate: onBlurAsync,
|
|
196
|
+
debounceMs: onBlurAsyncDebounceMs ?? defaultDebounceMs,
|
|
197
|
+
} as const
|
|
198
|
+
|
|
199
|
+
const submitValidator = {
|
|
200
|
+
cause: 'submit',
|
|
201
|
+
validate: onSubmitAsync,
|
|
202
|
+
debounceMs: onSubmitAsyncDebounceMs ?? defaultDebounceMs,
|
|
203
|
+
} as const
|
|
204
|
+
|
|
205
|
+
switch (cause) {
|
|
206
|
+
case 'submit':
|
|
207
|
+
return [changeValidator, blurValidator, submitValidator] as never
|
|
208
|
+
case 'server':
|
|
209
|
+
return [] as never
|
|
210
|
+
case 'blur':
|
|
211
|
+
return [blurValidator] as never
|
|
212
|
+
case 'change':
|
|
213
|
+
default:
|
|
214
|
+
return [changeValidator] as never
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
interface SyncValidatorArrayPartialOptions<T> {
|
|
219
|
+
validators?: T
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface SyncValidator<T> {
|
|
223
|
+
cause: ValidationCause
|
|
224
|
+
validate: T
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function getSyncValidatorArray<T>(
|
|
228
|
+
cause: ValidationCause,
|
|
229
|
+
options: SyncValidatorArrayPartialOptions<T>,
|
|
230
|
+
): T extends FieldValidators<any, any>
|
|
231
|
+
? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>>
|
|
232
|
+
: T extends FormValidators<any, any>
|
|
233
|
+
? Array<SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit']>>
|
|
234
|
+
: never {
|
|
235
|
+
const { onChange, onBlur, onSubmit } = (options.validators || {}) as
|
|
236
|
+
| FieldValidators<any, any>
|
|
237
|
+
| FormValidators<any, any>
|
|
238
|
+
|
|
239
|
+
const changeValidator = { cause: 'change', validate: onChange } as const
|
|
240
|
+
const blurValidator = { cause: 'blur', validate: onBlur } as const
|
|
241
|
+
const submitValidator = { cause: 'submit', validate: onSubmit } as const
|
|
242
|
+
|
|
243
|
+
// Allows us to clear onServer errors
|
|
244
|
+
const serverValidator = {
|
|
245
|
+
cause: 'server',
|
|
246
|
+
validate: () => undefined,
|
|
247
|
+
} as const
|
|
248
|
+
|
|
249
|
+
switch (cause) {
|
|
250
|
+
case 'submit':
|
|
251
|
+
return [
|
|
252
|
+
changeValidator,
|
|
253
|
+
blurValidator,
|
|
254
|
+
submitValidator,
|
|
255
|
+
serverValidator,
|
|
256
|
+
] as never
|
|
257
|
+
case 'server':
|
|
258
|
+
return [serverValidator] as never
|
|
259
|
+
case 'blur':
|
|
260
|
+
return [blurValidator, serverValidator] as never
|
|
261
|
+
case 'change':
|
|
262
|
+
default:
|
|
263
|
+
return [changeValidator, serverValidator] as never
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
144
267
|
export type RequiredByKey<T, K extends keyof T> = Omit<T, K> &
|
|
145
268
|
Required<Pick<T, K>>
|
|
146
269
|
|
|
@@ -166,24 +289,24 @@ type AllowedIndexes<
|
|
|
166
289
|
> = Tuple extends readonly []
|
|
167
290
|
? Keys
|
|
168
291
|
: Tuple extends readonly [infer _, ...infer Tail]
|
|
169
|
-
|
|
170
|
-
|
|
292
|
+
? AllowedIndexes<Tail, Keys | Tail['length']>
|
|
293
|
+
: Keys
|
|
171
294
|
|
|
172
295
|
export type DeepKeys<T, TDepth extends any[] = []> = TDepth['length'] extends 5
|
|
173
296
|
? never
|
|
174
297
|
: unknown extends T
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
298
|
+
? string
|
|
299
|
+
: object extends T
|
|
300
|
+
? string
|
|
301
|
+
: T extends readonly any[] & IsTuple<T>
|
|
302
|
+
? AllowedIndexes<T> | DeepKeysPrefix<T, AllowedIndexes<T>, TDepth>
|
|
303
|
+
: T extends any[]
|
|
304
|
+
? DeepKeys<T[number], [...TDepth, any]>
|
|
305
|
+
: T extends Date
|
|
306
|
+
? never
|
|
307
|
+
: T extends object
|
|
308
|
+
? (keyof T & string) | DeepKeysPrefix<T, keyof T, TDepth>
|
|
309
|
+
: never
|
|
187
310
|
|
|
188
311
|
type DeepKeysPrefix<
|
|
189
312
|
T,
|