@tanstack/form-core 1.16.0 → 1.17.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.
Files changed (61) hide show
  1. package/dist/cjs/FieldApi.cjs +48 -40
  2. package/dist/cjs/FieldApi.cjs.map +1 -1
  3. package/dist/cjs/FieldApi.d.cts +33 -30
  4. package/dist/cjs/FieldGroupApi.cjs.map +1 -1
  5. package/dist/cjs/FieldGroupApi.d.cts +6 -6
  6. package/dist/cjs/FormApi.cjs +79 -69
  7. package/dist/cjs/FormApi.cjs.map +1 -1
  8. package/dist/cjs/FormApi.d.cts +40 -35
  9. package/dist/cjs/ValidationLogic.cjs +106 -0
  10. package/dist/cjs/ValidationLogic.cjs.map +1 -0
  11. package/dist/cjs/ValidationLogic.d.cts +47 -0
  12. package/dist/cjs/formOptions.cjs.map +1 -1
  13. package/dist/cjs/formOptions.d.cts +1 -1
  14. package/dist/cjs/index.cjs +3 -0
  15. package/dist/cjs/index.cjs.map +1 -1
  16. package/dist/cjs/index.d.cts +1 -0
  17. package/dist/cjs/mergeForm.cjs.map +1 -1
  18. package/dist/cjs/mergeForm.d.cts +1 -1
  19. package/dist/cjs/metaHelper.cjs.map +1 -1
  20. package/dist/cjs/metaHelper.d.cts +1 -1
  21. package/dist/cjs/standardSchemaValidator.cjs.map +1 -1
  22. package/dist/cjs/types.d.cts +6 -3
  23. package/dist/cjs/utils.cjs +51 -63
  24. package/dist/cjs/utils.cjs.map +1 -1
  25. package/dist/cjs/utils.d.cts +11 -5
  26. package/dist/esm/FieldApi.d.ts +33 -30
  27. package/dist/esm/FieldApi.js +48 -40
  28. package/dist/esm/FieldApi.js.map +1 -1
  29. package/dist/esm/FieldGroupApi.d.ts +6 -6
  30. package/dist/esm/FieldGroupApi.js.map +1 -1
  31. package/dist/esm/FormApi.d.ts +40 -35
  32. package/dist/esm/FormApi.js +79 -69
  33. package/dist/esm/FormApi.js.map +1 -1
  34. package/dist/esm/ValidationLogic.d.ts +47 -0
  35. package/dist/esm/ValidationLogic.js +106 -0
  36. package/dist/esm/ValidationLogic.js.map +1 -0
  37. package/dist/esm/formOptions.d.ts +1 -1
  38. package/dist/esm/formOptions.js.map +1 -1
  39. package/dist/esm/index.d.ts +1 -0
  40. package/dist/esm/index.js +3 -0
  41. package/dist/esm/index.js.map +1 -1
  42. package/dist/esm/mergeForm.d.ts +1 -1
  43. package/dist/esm/mergeForm.js.map +1 -1
  44. package/dist/esm/metaHelper.d.ts +1 -1
  45. package/dist/esm/metaHelper.js.map +1 -1
  46. package/dist/esm/standardSchemaValidator.js.map +1 -1
  47. package/dist/esm/types.d.ts +6 -3
  48. package/dist/esm/utils.d.ts +11 -5
  49. package/dist/esm/utils.js +51 -63
  50. package/dist/esm/utils.js.map +1 -1
  51. package/package.json +16 -3
  52. package/src/FieldApi.ts +185 -14
  53. package/src/FieldGroupApi.ts +14 -0
  54. package/src/FormApi.ts +131 -3
  55. package/src/ValidationLogic.ts +200 -0
  56. package/src/formOptions.ts +1 -1
  57. package/src/index.ts +1 -0
  58. package/src/mergeForm.ts +16 -1
  59. package/src/metaHelper.ts +4 -0
  60. package/src/types.ts +17 -1
  61. package/src/utils.ts +159 -109
package/src/FormApi.ts CHANGED
@@ -11,12 +11,14 @@ import {
11
11
  isNonEmptyArray,
12
12
  setBy,
13
13
  } from './utils'
14
+ import { defaultValidationLogic } from './ValidationLogic'
14
15
 
15
16
  import {
16
17
  isStandardSchemaValidator,
17
18
  standardSchemaValidators,
18
19
  } from './standardSchemaValidator'
19
20
  import { defaultFieldMeta, metaHelper } from './metaHelper'
21
+ import type { ValidationLogicFn } from './ValidationLogic'
20
22
  import type {
21
23
  StandardSchemaV1,
22
24
  StandardSchemaV1Issue,
@@ -84,6 +86,8 @@ export type FormValidateFn<TFormData> = (props: {
84
86
  any,
85
87
  any,
86
88
  any,
89
+ any,
90
+ any,
87
91
  any
88
92
  >
89
93
  }) => unknown
@@ -120,6 +124,8 @@ export type FormValidateAsyncFn<TFormData> = (props: {
120
124
  any,
121
125
  any,
122
126
  any,
127
+ any,
128
+ any,
123
129
  any
124
130
  >
125
131
  signal: AbortSignal
@@ -164,6 +170,8 @@ export interface FormValidators<
164
170
  TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
165
171
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
166
172
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
173
+ TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
174
+ TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
167
175
  > {
168
176
  /**
169
177
  * Optional function that fires as soon as the component mounts.
@@ -195,6 +203,9 @@ export interface FormValidators<
195
203
  onBlurAsyncDebounceMs?: number
196
204
  onSubmit?: TOnSubmit
197
205
  onSubmitAsync?: TOnSubmitAsync
206
+ onDynamic?: TOnDynamic
207
+ onDynamicAsync?: TOnDynamicAsync
208
+ onDynamicAsyncDebounceMs?: number
198
209
  }
199
210
 
200
211
  /**
@@ -209,6 +220,8 @@ export interface FormTransform<
209
220
  TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
210
221
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
211
222
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
223
+ TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
224
+ TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
212
225
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
213
226
  TSubmitMeta = never,
214
227
  > {
@@ -222,6 +235,8 @@ export interface FormTransform<
222
235
  TOnBlurAsync,
223
236
  TOnSubmit,
224
237
  TOnSubmitAsync,
238
+ TOnDynamic,
239
+ TOnDynamicAsync,
225
240
  TOnServer,
226
241
  TSubmitMeta
227
242
  >,
@@ -234,6 +249,8 @@ export interface FormTransform<
234
249
  TOnBlurAsync,
235
250
  TOnSubmit,
236
251
  TOnSubmitAsync,
252
+ TOnDynamic,
253
+ TOnDynamicAsync,
237
254
  TOnServer,
238
255
  TSubmitMeta
239
256
  >
@@ -249,6 +266,8 @@ export interface FormListeners<
249
266
  TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
250
267
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
251
268
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
269
+ TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
270
+ TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
252
271
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
253
272
  TSubmitMeta = never,
254
273
  > {
@@ -262,6 +281,8 @@ export interface FormListeners<
262
281
  TOnBlurAsync,
263
282
  TOnSubmit,
264
283
  TOnSubmitAsync,
284
+ TOnDynamic,
285
+ TOnDynamicAsync,
265
286
  TOnServer,
266
287
  TSubmitMeta
267
288
  >
@@ -279,6 +300,8 @@ export interface FormListeners<
279
300
  TOnBlurAsync,
280
301
  TOnSubmit,
281
302
  TOnSubmitAsync,
303
+ TOnDynamic,
304
+ TOnDynamicAsync,
282
305
  TOnServer,
283
306
  TSubmitMeta
284
307
  >
@@ -296,6 +319,8 @@ export interface FormListeners<
296
319
  TOnBlurAsync,
297
320
  TOnSubmit,
298
321
  TOnSubmitAsync,
322
+ TOnDynamic,
323
+ TOnDynamicAsync,
299
324
  TOnServer,
300
325
  TSubmitMeta
301
326
  >
@@ -311,6 +336,8 @@ export interface FormListeners<
311
336
  TOnBlurAsync,
312
337
  TOnSubmit,
313
338
  TOnSubmitAsync,
339
+ TOnDynamic,
340
+ TOnDynamicAsync,
314
341
  TOnServer,
315
342
  TSubmitMeta
316
343
  >
@@ -344,6 +371,8 @@ export interface FormOptions<
344
371
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
345
372
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
346
373
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
374
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
375
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
347
376
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
348
377
  in out TSubmitMeta = never,
349
378
  > extends BaseFormOptions<TFormData, TSubmitMeta> {
@@ -360,6 +389,8 @@ export interface FormOptions<
360
389
  TOnBlurAsync,
361
390
  TOnSubmit,
362
391
  TOnSubmitAsync,
392
+ TOnDynamic,
393
+ TOnDynamicAsync,
363
394
  TOnServer
364
395
  >
365
396
  >
@@ -386,9 +417,13 @@ export interface FormOptions<
386
417
  TOnBlur,
387
418
  TOnBlurAsync,
388
419
  TOnSubmit,
389
- TOnSubmitAsync
420
+ TOnSubmitAsync,
421
+ TOnDynamic,
422
+ TOnDynamicAsync
390
423
  >
391
424
 
425
+ validationLogic?: ValidationLogicFn
426
+
392
427
  /**
393
428
  * form level listeners
394
429
  */
@@ -401,6 +436,8 @@ export interface FormOptions<
401
436
  TOnBlurAsync,
402
437
  TOnSubmit,
403
438
  TOnSubmitAsync,
439
+ TOnDynamic,
440
+ TOnDynamicAsync,
404
441
  TOnServer,
405
442
  TSubmitMeta
406
443
  >
@@ -419,6 +456,8 @@ export interface FormOptions<
419
456
  TOnBlurAsync,
420
457
  TOnSubmit,
421
458
  TOnSubmitAsync,
459
+ TOnDynamic,
460
+ TOnDynamicAsync,
422
461
  TOnServer,
423
462
  TSubmitMeta
424
463
  >
@@ -438,6 +477,8 @@ export interface FormOptions<
438
477
  TOnBlurAsync,
439
478
  TOnSubmit,
440
479
  TOnSubmitAsync,
480
+ TOnDynamic,
481
+ TOnDynamicAsync,
441
482
  TOnServer,
442
483
  TSubmitMeta
443
484
  >
@@ -452,6 +493,8 @@ export interface FormOptions<
452
493
  NoInfer<TOnBlurAsync>,
453
494
  NoInfer<TOnSubmit>,
454
495
  NoInfer<TOnSubmitAsync>,
496
+ NoInfer<TOnDynamic>,
497
+ NoInfer<TOnDynamicAsync>,
455
498
  NoInfer<TOnServer>,
456
499
  NoInfer<TSubmitMeta>
457
500
  >
@@ -493,6 +536,10 @@ export type FieldInfo<TFormData> = {
493
536
  any,
494
537
  any,
495
538
  any,
539
+ any,
540
+ any,
541
+ any,
542
+ any,
496
543
  any
497
544
  > | null
498
545
  /**
@@ -513,6 +560,8 @@ export type BaseFormState<
513
560
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
514
561
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
515
562
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
563
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
564
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
516
565
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
517
566
  > = {
518
567
  /**
@@ -530,6 +579,8 @@ export type BaseFormState<
530
579
  UnwrapFormAsyncValidateOrFn<TOnBlurAsync>,
531
580
  UnwrapFormValidateOrFn<TOnSubmit>,
532
581
  UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>,
582
+ UnwrapFormValidateOrFn<TOnDynamic>,
583
+ UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>,
533
584
  UnwrapFormAsyncValidateOrFn<TOnServer>
534
585
  >
535
586
  /**
@@ -588,6 +639,8 @@ export type DerivedFormState<
588
639
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
589
640
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
590
641
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
642
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
643
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
591
644
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
592
645
  > = {
593
646
  /**
@@ -609,6 +662,8 @@ export type DerivedFormState<
609
662
  | UnwrapFormAsyncValidateOrFn<TOnBlurAsync>
610
663
  | UnwrapFormValidateOrFn<TOnSubmit>
611
664
  | UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>
665
+ | UnwrapFormValidateOrFn<TOnDynamic>
666
+ | UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>
612
667
  | UnwrapFormAsyncValidateOrFn<TOnServer>
613
668
  >
614
669
  /**
@@ -662,6 +717,8 @@ export interface FormState<
662
717
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
663
718
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
664
719
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
720
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
721
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
665
722
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
666
723
  > extends BaseFormState<
667
724
  TFormData,
@@ -672,6 +729,8 @@ export interface FormState<
672
729
  TOnBlurAsync,
673
730
  TOnSubmit,
674
731
  TOnSubmitAsync,
732
+ TOnDynamic,
733
+ TOnDynamicAsync,
675
734
  TOnServer
676
735
  >,
677
736
  DerivedFormState<
@@ -683,6 +742,8 @@ export interface FormState<
683
742
  TOnBlurAsync,
684
743
  TOnSubmit,
685
744
  TOnSubmitAsync,
745
+ TOnDynamic,
746
+ TOnDynamicAsync,
686
747
  TOnServer
687
748
  > {}
688
749
 
@@ -695,6 +756,8 @@ export type AnyFormState = FormState<
695
756
  any,
696
757
  any,
697
758
  any,
759
+ any,
760
+ any,
698
761
  any
699
762
  >
700
763
 
@@ -707,6 +770,8 @@ function getDefaultFormState<
707
770
  TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
708
771
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
709
772
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
773
+ TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
774
+ TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
710
775
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
711
776
  >(
712
777
  defaultState: Partial<
@@ -719,6 +784,8 @@ function getDefaultFormState<
719
784
  TOnBlurAsync,
720
785
  TOnSubmit,
721
786
  TOnSubmitAsync,
787
+ TOnDynamic,
788
+ TOnDynamicAsync,
722
789
  TOnServer
723
790
  >
724
791
  >,
@@ -731,6 +798,8 @@ function getDefaultFormState<
731
798
  TOnBlurAsync,
732
799
  TOnSubmit,
733
800
  TOnSubmitAsync,
801
+ TOnDynamic,
802
+ TOnDynamicAsync,
734
803
  TOnServer
735
804
  > {
736
805
  return {
@@ -748,6 +817,7 @@ function getDefaultFormState<
748
817
  onSubmit: undefined,
749
818
  onMount: undefined,
750
819
  onServer: undefined,
820
+ onDynamic: undefined,
751
821
  },
752
822
  }
753
823
  }
@@ -767,6 +837,8 @@ export type AnyFormApi = FormApi<
767
837
  any,
768
838
  any,
769
839
  any,
840
+ any,
841
+ any,
770
842
  any
771
843
  >
772
844
 
@@ -786,6 +858,8 @@ export class FormApi<
786
858
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
787
859
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
788
860
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
861
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
862
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
789
863
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
790
864
  in out TSubmitMeta = never,
791
865
  > implements FieldManipulator<TFormData, TSubmitMeta>
@@ -802,6 +876,8 @@ export class FormApi<
802
876
  TOnBlurAsync,
803
877
  TOnSubmit,
804
878
  TOnSubmitAsync,
879
+ TOnDynamic,
880
+ TOnDynamicAsync,
805
881
  TOnServer,
806
882
  TSubmitMeta
807
883
  > = {}
@@ -815,6 +891,8 @@ export class FormApi<
815
891
  TOnBlurAsync,
816
892
  TOnSubmit,
817
893
  TOnSubmitAsync,
894
+ TOnDynamic,
895
+ TOnDynamicAsync,
818
896
  TOnServer
819
897
  >
820
898
  >
@@ -829,6 +907,8 @@ export class FormApi<
829
907
  TOnBlurAsync,
830
908
  TOnSubmit,
831
909
  TOnSubmitAsync,
910
+ TOnDynamic,
911
+ TOnDynamicAsync,
832
912
  TOnServer
833
913
  >
834
914
  >
@@ -859,6 +939,8 @@ export class FormApi<
859
939
  TOnBlurAsync,
860
940
  TOnSubmit,
861
941
  TOnSubmitAsync,
942
+ TOnDynamic,
943
+ TOnDynamicAsync,
862
944
  TOnServer,
863
945
  TSubmitMeta
864
946
  >,
@@ -891,6 +973,8 @@ export class FormApi<
891
973
  TOnBlurAsync,
892
974
  TOnSubmit,
893
975
  TOnSubmitAsync,
976
+ TOnDynamic,
977
+ TOnDynamicAsync,
894
978
  TOnServer
895
979
  >['fieldMeta']
896
980
 
@@ -992,6 +1076,8 @@ export class FormApi<
992
1076
  TOnBlurAsync,
993
1077
  TOnSubmit,
994
1078
  TOnSubmitAsync,
1079
+ TOnDynamic,
1080
+ TOnDynamicAsync,
995
1081
  TOnServer
996
1082
  >
997
1083
  | undefined
@@ -1124,6 +1210,8 @@ export class FormApi<
1124
1210
  TOnBlurAsync,
1125
1211
  TOnSubmit,
1126
1212
  TOnSubmitAsync,
1213
+ TOnDynamic,
1214
+ TOnDynamicAsync,
1127
1215
  TOnServer
1128
1216
  >
1129
1217
 
@@ -1205,6 +1293,8 @@ export class FormApi<
1205
1293
  TOnBlurAsync,
1206
1294
  TOnSubmit,
1207
1295
  TOnSubmitAsync,
1296
+ TOnDynamic,
1297
+ TOnDynamicAsync,
1208
1298
  TOnServer,
1209
1299
  TSubmitMeta
1210
1300
  >,
@@ -1396,7 +1486,12 @@ export class FormApi<
1396
1486
  TOnSubmitAsync
1397
1487
  >
1398
1488
  } => {
1399
- const validates = getSyncValidatorArray(cause, this.options)
1489
+ const validates = getSyncValidatorArray(cause, {
1490
+ ...this.options,
1491
+ form: this,
1492
+ validationLogic: this.options.validationLogic || defaultValidationLogic,
1493
+ })
1494
+
1400
1495
  let hasErrored = false as boolean
1401
1496
 
1402
1497
  // This map will only include fields that have errors in the current validation cycle
@@ -1512,6 +1607,26 @@ export class FormApi<
1512
1607
  },
1513
1608
  }))
1514
1609
  }
1610
+
1611
+ /**
1612
+ * when we have an error for onServer in the state, we want
1613
+ * to clear the error as soon as the user enters a valid value in the field
1614
+ */
1615
+ const serverErrKey = getErrorMapKey('server')
1616
+ if (
1617
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1618
+ this.state.errorMap?.[serverErrKey] &&
1619
+ cause !== 'server' &&
1620
+ !hasErrored
1621
+ ) {
1622
+ this.baseStore.setState((prev) => ({
1623
+ ...prev,
1624
+ errorMap: {
1625
+ ...prev.errorMap,
1626
+ [serverErrKey]: undefined,
1627
+ },
1628
+ }))
1629
+ }
1515
1630
  })
1516
1631
 
1517
1632
  return { hasErrored, fieldsErrorMap: currentValidationErrorMap }
@@ -1534,7 +1649,11 @@ export class FormApi<
1534
1649
  TOnSubmitAsync
1535
1650
  >
1536
1651
  > => {
1537
- const validates = getAsyncValidatorArray(cause, this.options)
1652
+ const validates = getAsyncValidatorArray(cause, {
1653
+ ...this.options,
1654
+ form: this,
1655
+ validationLogic: this.options.validationLogic || defaultValidationLogic,
1656
+ })
1538
1657
 
1539
1658
  if (!this.state.isFormValidating) {
1540
1659
  this.baseStore.setState((prev) => ({ ...prev, isFormValidating: true }))
@@ -1878,6 +1997,7 @@ export class FormApi<
1878
1997
  onSubmit: undefined,
1879
1998
  onMount: undefined,
1880
1999
  onServer: undefined,
2000
+ onDynamic: undefined,
1881
2001
  },
1882
2002
  })
1883
2003
  }
@@ -2198,6 +2318,8 @@ export class FormApi<
2198
2318
  UnwrapFormAsyncValidateOrFn<TOnBlurAsync>,
2199
2319
  UnwrapFormValidateOrFn<TOnSubmit>,
2200
2320
  UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>,
2321
+ UnwrapFormValidateOrFn<TOnDynamic>,
2322
+ UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>,
2201
2323
  UnwrapFormAsyncValidateOrFn<TOnServer>
2202
2324
  >,
2203
2325
  ) {
@@ -2260,6 +2382,8 @@ export class FormApi<
2260
2382
  | UnwrapFormAsyncValidateOrFn<TOnBlurAsync>
2261
2383
  | UnwrapFormValidateOrFn<TOnSubmit>
2262
2384
  | UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>
2385
+ | UnwrapFormValidateOrFn<TOnDynamic>
2386
+ | UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>
2263
2387
  | UnwrapFormAsyncValidateOrFn<TOnServer>
2264
2388
  >
2265
2389
  errorMap: ValidationErrorMap<
@@ -2270,6 +2394,8 @@ export class FormApi<
2270
2394
  UnwrapFormAsyncValidateOrFn<TOnBlurAsync>,
2271
2395
  UnwrapFormValidateOrFn<TOnSubmit>,
2272
2396
  UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>,
2397
+ UnwrapFormValidateOrFn<TOnDynamic>,
2398
+ UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>,
2273
2399
  UnwrapFormAsyncValidateOrFn<TOnServer>
2274
2400
  >
2275
2401
  }
@@ -2359,6 +2485,8 @@ function getErrorMapKey(cause: ValidationCause) {
2359
2485
  return 'onMount'
2360
2486
  case 'server':
2361
2487
  return 'onServer'
2488
+ case 'dynamic':
2489
+ return 'onDynamic'
2362
2490
  case 'change':
2363
2491
  default:
2364
2492
  return 'onChange'
@@ -0,0 +1,200 @@
1
+ import type { AnyFormApi, FormValidators } from './FormApi'
2
+
3
+ interface ValidationLogicValidatorsFn {
4
+ // TODO: Type this properly
5
+ fn: FormValidators<
6
+ any,
7
+ any,
8
+ any,
9
+ any,
10
+ any,
11
+ any,
12
+ any,
13
+ any,
14
+ any,
15
+ any
16
+ >[keyof FormValidators<any, any, any, any, any, any, any, any, any, any>]
17
+ cause: 'change' | 'blur' | 'submit' | 'mount' | 'server' | 'dynamic'
18
+ }
19
+
20
+ export interface ValidationLogicProps {
21
+ // TODO: Type this properly
22
+ form: AnyFormApi
23
+ // TODO: Type this properly
24
+ validators:
25
+ | FormValidators<any, any, any, any, any, any, any, any, any, any>
26
+ | undefined
27
+ | null
28
+ event: {
29
+ type: 'blur' | 'change' | 'submit' | 'mount' | 'server'
30
+ fieldName?: string
31
+ async: boolean
32
+ }
33
+ runValidation: (props: {
34
+ validators: Array<ValidationLogicValidatorsFn | undefined>
35
+ form: AnyFormApi
36
+ }) => void
37
+ }
38
+
39
+ interface RevalidateLogicProps {
40
+ /**
41
+ * @default 'submit'
42
+ *
43
+ * This is the mode that will be used before the form has been submitted.
44
+ * It will run the validation logic on `submit` by default, but can be set to `change` or `blur`.
45
+ */
46
+ mode?: 'change' | 'blur' | 'submit'
47
+ /**
48
+ * @default 'change'
49
+ *
50
+ * This is the mode that will be used after the form has been submitted.
51
+ * It will run the validation logic on `change` by default, but can be set to `blur` or `submit`.
52
+ */
53
+ modeAfterSubmission?: 'change' | 'blur' | 'submit'
54
+ }
55
+
56
+ export type ValidationLogicFn = (props: ValidationLogicProps) => void
57
+
58
+ /**
59
+ * This forces a form's validation logic to be ran as if it were a React Hook Form validation logic.
60
+ *
61
+ * This means that it will only run the `onDynamic` validator, and it will not run any other validators and changes the validation
62
+ * type based on the state of the form itself.
63
+ *
64
+ * When the form is not yet submitted, it will not run the validation logic.
65
+ * When the form is submitted, it will run the validation logic on `change`
66
+ */
67
+ export const revalidateLogic =
68
+ ({
69
+ mode = 'submit',
70
+ modeAfterSubmission = 'change',
71
+ }: RevalidateLogicProps = {}): ValidationLogicFn =>
72
+ (props) => {
73
+ const validatorNames = Object.keys(props.validators ?? {})
74
+ if (validatorNames.length === 0) {
75
+ // No validators is a valid case, just return
76
+ return props.runValidation({
77
+ validators: [],
78
+ form: props.form,
79
+ })
80
+ }
81
+
82
+ const dynamicValidator = {
83
+ fn: props.event.async
84
+ ? props.validators!['onDynamicAsync']
85
+ : props.validators!['onDynamic'],
86
+ cause: 'dynamic',
87
+ } as const
88
+
89
+ const validatorsToAdd = [] as ValidationLogicValidatorsFn[]
90
+
91
+ const modeToWatch =
92
+ props.form.state.submissionAttempts === 0 ? mode : modeAfterSubmission
93
+
94
+ if ([modeToWatch, 'submit'].includes(props.event.type)) {
95
+ validatorsToAdd.push(dynamicValidator)
96
+ }
97
+
98
+ let defaultValidators = [] as ValidationLogicValidatorsFn[]
99
+
100
+ defaultValidationLogic({
101
+ ...props,
102
+ runValidation: (vProps) => {
103
+ defaultValidators = vProps.validators as ValidationLogicValidatorsFn[]
104
+ },
105
+ })
106
+
107
+ if (validatorsToAdd.length === 0) {
108
+ return props.runValidation({
109
+ validators: defaultValidators,
110
+ form: props.form,
111
+ })
112
+ }
113
+
114
+ return props.runValidation({
115
+ validators: [...defaultValidators, ...validatorsToAdd],
116
+ form: props.form,
117
+ })
118
+ }
119
+
120
+ export const defaultValidationLogic: ValidationLogicFn = (props) => {
121
+ // Handle case where no validators are provided
122
+ if (!props.validators) {
123
+ return props.runValidation({
124
+ validators: [],
125
+ form: props.form,
126
+ })
127
+ }
128
+
129
+ const isAsync = props.event.async
130
+
131
+ const onMountValidator = isAsync
132
+ ? undefined
133
+ : ({ fn: props.validators.onMount, cause: 'mount' } as const)
134
+
135
+ const onChangeValidator = {
136
+ fn: isAsync ? props.validators.onChangeAsync : props.validators.onChange,
137
+ cause: 'change',
138
+ } as const
139
+
140
+ const onBlurValidator = {
141
+ fn: isAsync ? props.validators.onBlurAsync : props.validators.onBlur,
142
+ cause: 'blur',
143
+ } as const
144
+
145
+ const onSubmitValidator = {
146
+ fn: isAsync ? props.validators.onSubmitAsync : props.validators.onSubmit,
147
+ cause: 'submit',
148
+ } as const
149
+
150
+ // Allows us to clear onServer errors
151
+ const onServerValidator = isAsync
152
+ ? undefined
153
+ : ({ fn: () => undefined, cause: 'server' } as const)
154
+
155
+ switch (props.event.type) {
156
+ case 'mount': {
157
+ // Run mount validation
158
+ return props.runValidation({
159
+ validators: [onMountValidator],
160
+ form: props.form,
161
+ })
162
+ }
163
+ case 'submit': {
164
+ // Run change, blur, submit, server validation
165
+ return props.runValidation({
166
+ validators: [
167
+ onChangeValidator,
168
+ onBlurValidator,
169
+ onSubmitValidator,
170
+ onServerValidator,
171
+ ],
172
+ form: props.form,
173
+ })
174
+ }
175
+ case 'server': {
176
+ // Run server validation
177
+ return props.runValidation({
178
+ validators: [],
179
+ form: props.form,
180
+ })
181
+ }
182
+ case 'blur': {
183
+ // Run blur, server validation
184
+ return props.runValidation({
185
+ validators: [onBlurValidator, onServerValidator],
186
+ form: props.form,
187
+ })
188
+ }
189
+ case 'change': {
190
+ // Run change, server validation
191
+ return props.runValidation({
192
+ validators: [onChangeValidator, onServerValidator],
193
+ form: props.form,
194
+ })
195
+ }
196
+ default: {
197
+ throw new Error(`Unknown validation event type: ${props.event.type}`)
198
+ }
199
+ }
200
+ }
@@ -2,7 +2,7 @@ import type { FormOptions } from './FormApi'
2
2
 
3
3
  export function formOptions<
4
4
  T extends Partial<
5
- FormOptions<any, any, any, any, any, any, any, any, any, any>
5
+ FormOptions<any, any, any, any, any, any, any, any, any, any, any, any>
6
6
  >,
7
7
  >(defaultOpts: T) {
8
8
  return defaultOpts
package/src/index.ts CHANGED
@@ -7,3 +7,4 @@ export * from './mergeForm'
7
7
  export * from './formOptions'
8
8
  export * from './standardSchemaValidator'
9
9
  export * from './FieldGroupApi'
10
+ export * from './ValidationLogic'