@tanstack/form-core 1.16.0 → 1.18.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 (59) 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 +82 -69
  7. package/dist/cjs/FormApi.cjs.map +1 -1
  8. package/dist/cjs/FormApi.d.cts +45 -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/types.d.cts +6 -3
  22. package/dist/cjs/utils.cjs +51 -63
  23. package/dist/cjs/utils.cjs.map +1 -1
  24. package/dist/cjs/utils.d.cts +11 -5
  25. package/dist/esm/FieldApi.d.ts +33 -30
  26. package/dist/esm/FieldApi.js +48 -40
  27. package/dist/esm/FieldApi.js.map +1 -1
  28. package/dist/esm/FieldGroupApi.d.ts +6 -6
  29. package/dist/esm/FieldGroupApi.js.map +1 -1
  30. package/dist/esm/FormApi.d.ts +45 -35
  31. package/dist/esm/FormApi.js +82 -69
  32. package/dist/esm/FormApi.js.map +1 -1
  33. package/dist/esm/ValidationLogic.d.ts +47 -0
  34. package/dist/esm/ValidationLogic.js +106 -0
  35. package/dist/esm/ValidationLogic.js.map +1 -0
  36. package/dist/esm/formOptions.d.ts +1 -1
  37. package/dist/esm/formOptions.js.map +1 -1
  38. package/dist/esm/index.d.ts +1 -0
  39. package/dist/esm/index.js +3 -0
  40. package/dist/esm/index.js.map +1 -1
  41. package/dist/esm/mergeForm.d.ts +1 -1
  42. package/dist/esm/mergeForm.js.map +1 -1
  43. package/dist/esm/metaHelper.d.ts +1 -1
  44. package/dist/esm/metaHelper.js.map +1 -1
  45. package/dist/esm/types.d.ts +6 -3
  46. package/dist/esm/utils.d.ts +11 -5
  47. package/dist/esm/utils.js +51 -63
  48. package/dist/esm/utils.js.map +1 -1
  49. package/package.json +16 -3
  50. package/src/FieldApi.ts +185 -14
  51. package/src/FieldGroupApi.ts +14 -0
  52. package/src/FormApi.ts +139 -3
  53. package/src/ValidationLogic.ts +200 -0
  54. package/src/formOptions.ts +1 -1
  55. package/src/index.ts +1 -0
  56. package/src/mergeForm.ts +16 -1
  57. package/src/metaHelper.ts +4 -0
  58. package/src/types.ts +17 -1
  59. 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,9 +371,15 @@ 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> {
379
+ /**
380
+ * The form name, used for devtools and identification
381
+ */
382
+ formId?: string
350
383
  /**
351
384
  * The default state for the form.
352
385
  */
@@ -360,6 +393,8 @@ export interface FormOptions<
360
393
  TOnBlurAsync,
361
394
  TOnSubmit,
362
395
  TOnSubmitAsync,
396
+ TOnDynamic,
397
+ TOnDynamicAsync,
363
398
  TOnServer
364
399
  >
365
400
  >
@@ -386,9 +421,13 @@ export interface FormOptions<
386
421
  TOnBlur,
387
422
  TOnBlurAsync,
388
423
  TOnSubmit,
389
- TOnSubmitAsync
424
+ TOnSubmitAsync,
425
+ TOnDynamic,
426
+ TOnDynamicAsync
390
427
  >
391
428
 
429
+ validationLogic?: ValidationLogicFn
430
+
392
431
  /**
393
432
  * form level listeners
394
433
  */
@@ -401,6 +440,8 @@ export interface FormOptions<
401
440
  TOnBlurAsync,
402
441
  TOnSubmit,
403
442
  TOnSubmitAsync,
443
+ TOnDynamic,
444
+ TOnDynamicAsync,
404
445
  TOnServer,
405
446
  TSubmitMeta
406
447
  >
@@ -419,6 +460,8 @@ export interface FormOptions<
419
460
  TOnBlurAsync,
420
461
  TOnSubmit,
421
462
  TOnSubmitAsync,
463
+ TOnDynamic,
464
+ TOnDynamicAsync,
422
465
  TOnServer,
423
466
  TSubmitMeta
424
467
  >
@@ -438,6 +481,8 @@ export interface FormOptions<
438
481
  TOnBlurAsync,
439
482
  TOnSubmit,
440
483
  TOnSubmitAsync,
484
+ TOnDynamic,
485
+ TOnDynamicAsync,
441
486
  TOnServer,
442
487
  TSubmitMeta
443
488
  >
@@ -452,6 +497,8 @@ export interface FormOptions<
452
497
  NoInfer<TOnBlurAsync>,
453
498
  NoInfer<TOnSubmit>,
454
499
  NoInfer<TOnSubmitAsync>,
500
+ NoInfer<TOnDynamic>,
501
+ NoInfer<TOnDynamicAsync>,
455
502
  NoInfer<TOnServer>,
456
503
  NoInfer<TSubmitMeta>
457
504
  >
@@ -493,6 +540,10 @@ export type FieldInfo<TFormData> = {
493
540
  any,
494
541
  any,
495
542
  any,
543
+ any,
544
+ any,
545
+ any,
546
+ any,
496
547
  any
497
548
  > | null
498
549
  /**
@@ -513,6 +564,8 @@ export type BaseFormState<
513
564
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
514
565
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
515
566
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
567
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
568
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
516
569
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
517
570
  > = {
518
571
  /**
@@ -530,6 +583,8 @@ export type BaseFormState<
530
583
  UnwrapFormAsyncValidateOrFn<TOnBlurAsync>,
531
584
  UnwrapFormValidateOrFn<TOnSubmit>,
532
585
  UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>,
586
+ UnwrapFormValidateOrFn<TOnDynamic>,
587
+ UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>,
533
588
  UnwrapFormAsyncValidateOrFn<TOnServer>
534
589
  >
535
590
  /**
@@ -588,6 +643,8 @@ export type DerivedFormState<
588
643
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
589
644
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
590
645
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
646
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
647
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
591
648
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
592
649
  > = {
593
650
  /**
@@ -609,6 +666,8 @@ export type DerivedFormState<
609
666
  | UnwrapFormAsyncValidateOrFn<TOnBlurAsync>
610
667
  | UnwrapFormValidateOrFn<TOnSubmit>
611
668
  | UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>
669
+ | UnwrapFormValidateOrFn<TOnDynamic>
670
+ | UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>
612
671
  | UnwrapFormAsyncValidateOrFn<TOnServer>
613
672
  >
614
673
  /**
@@ -662,6 +721,8 @@ export interface FormState<
662
721
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
663
722
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
664
723
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
724
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
725
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
665
726
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
666
727
  > extends BaseFormState<
667
728
  TFormData,
@@ -672,6 +733,8 @@ export interface FormState<
672
733
  TOnBlurAsync,
673
734
  TOnSubmit,
674
735
  TOnSubmitAsync,
736
+ TOnDynamic,
737
+ TOnDynamicAsync,
675
738
  TOnServer
676
739
  >,
677
740
  DerivedFormState<
@@ -683,6 +746,8 @@ export interface FormState<
683
746
  TOnBlurAsync,
684
747
  TOnSubmit,
685
748
  TOnSubmitAsync,
749
+ TOnDynamic,
750
+ TOnDynamicAsync,
686
751
  TOnServer
687
752
  > {}
688
753
 
@@ -695,6 +760,8 @@ export type AnyFormState = FormState<
695
760
  any,
696
761
  any,
697
762
  any,
763
+ any,
764
+ any,
698
765
  any
699
766
  >
700
767
 
@@ -707,6 +774,8 @@ function getDefaultFormState<
707
774
  TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
708
775
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
709
776
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
777
+ TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
778
+ TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
710
779
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
711
780
  >(
712
781
  defaultState: Partial<
@@ -719,6 +788,8 @@ function getDefaultFormState<
719
788
  TOnBlurAsync,
720
789
  TOnSubmit,
721
790
  TOnSubmitAsync,
791
+ TOnDynamic,
792
+ TOnDynamicAsync,
722
793
  TOnServer
723
794
  >
724
795
  >,
@@ -731,6 +802,8 @@ function getDefaultFormState<
731
802
  TOnBlurAsync,
732
803
  TOnSubmit,
733
804
  TOnSubmitAsync,
805
+ TOnDynamic,
806
+ TOnDynamicAsync,
734
807
  TOnServer
735
808
  > {
736
809
  return {
@@ -748,6 +821,7 @@ function getDefaultFormState<
748
821
  onSubmit: undefined,
749
822
  onMount: undefined,
750
823
  onServer: undefined,
824
+ onDynamic: undefined,
751
825
  },
752
826
  }
753
827
  }
@@ -767,6 +841,8 @@ export type AnyFormApi = FormApi<
767
841
  any,
768
842
  any,
769
843
  any,
844
+ any,
845
+ any,
770
846
  any
771
847
  >
772
848
 
@@ -786,6 +862,8 @@ export class FormApi<
786
862
  in out TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
787
863
  in out TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
788
864
  in out TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
865
+ in out TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
866
+ in out TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
789
867
  in out TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
790
868
  in out TSubmitMeta = never,
791
869
  > implements FieldManipulator<TFormData, TSubmitMeta>
@@ -802,6 +880,8 @@ export class FormApi<
802
880
  TOnBlurAsync,
803
881
  TOnSubmit,
804
882
  TOnSubmitAsync,
883
+ TOnDynamic,
884
+ TOnDynamicAsync,
805
885
  TOnServer,
806
886
  TSubmitMeta
807
887
  > = {}
@@ -815,6 +895,8 @@ export class FormApi<
815
895
  TOnBlurAsync,
816
896
  TOnSubmit,
817
897
  TOnSubmitAsync,
898
+ TOnDynamic,
899
+ TOnDynamicAsync,
818
900
  TOnServer
819
901
  >
820
902
  >
@@ -829,6 +911,8 @@ export class FormApi<
829
911
  TOnBlurAsync,
830
912
  TOnSubmit,
831
913
  TOnSubmitAsync,
914
+ TOnDynamic,
915
+ TOnDynamicAsync,
832
916
  TOnServer
833
917
  >
834
918
  >
@@ -859,6 +943,8 @@ export class FormApi<
859
943
  TOnBlurAsync,
860
944
  TOnSubmit,
861
945
  TOnSubmitAsync,
946
+ TOnDynamic,
947
+ TOnDynamicAsync,
862
948
  TOnServer,
863
949
  TSubmitMeta
864
950
  >,
@@ -891,6 +977,8 @@ export class FormApi<
891
977
  TOnBlurAsync,
892
978
  TOnSubmit,
893
979
  TOnSubmitAsync,
980
+ TOnDynamic,
981
+ TOnDynamicAsync,
894
982
  TOnServer
895
983
  >['fieldMeta']
896
984
 
@@ -992,6 +1080,8 @@ export class FormApi<
992
1080
  TOnBlurAsync,
993
1081
  TOnSubmit,
994
1082
  TOnSubmitAsync,
1083
+ TOnDynamic,
1084
+ TOnDynamicAsync,
995
1085
  TOnServer
996
1086
  >
997
1087
  | undefined
@@ -1124,6 +1214,8 @@ export class FormApi<
1124
1214
  TOnBlurAsync,
1125
1215
  TOnSubmit,
1126
1216
  TOnSubmitAsync,
1217
+ TOnDynamic,
1218
+ TOnDynamicAsync,
1127
1219
  TOnServer
1128
1220
  >
1129
1221
 
@@ -1150,6 +1242,10 @@ export class FormApi<
1150
1242
  this.update(opts || {})
1151
1243
  }
1152
1244
 
1245
+ get formId(): string | undefined {
1246
+ return this.options.formId
1247
+ }
1248
+
1153
1249
  /**
1154
1250
  * @private
1155
1251
  */
@@ -1205,6 +1301,8 @@ export class FormApi<
1205
1301
  TOnBlurAsync,
1206
1302
  TOnSubmit,
1207
1303
  TOnSubmitAsync,
1304
+ TOnDynamic,
1305
+ TOnDynamicAsync,
1208
1306
  TOnServer,
1209
1307
  TSubmitMeta
1210
1308
  >,
@@ -1396,7 +1494,12 @@ export class FormApi<
1396
1494
  TOnSubmitAsync
1397
1495
  >
1398
1496
  } => {
1399
- const validates = getSyncValidatorArray(cause, this.options)
1497
+ const validates = getSyncValidatorArray(cause, {
1498
+ ...this.options,
1499
+ form: this,
1500
+ validationLogic: this.options.validationLogic || defaultValidationLogic,
1501
+ })
1502
+
1400
1503
  let hasErrored = false as boolean
1401
1504
 
1402
1505
  // This map will only include fields that have errors in the current validation cycle
@@ -1512,6 +1615,26 @@ export class FormApi<
1512
1615
  },
1513
1616
  }))
1514
1617
  }
1618
+
1619
+ /**
1620
+ * when we have an error for onServer in the state, we want
1621
+ * to clear the error as soon as the user enters a valid value in the field
1622
+ */
1623
+ const serverErrKey = getErrorMapKey('server')
1624
+ if (
1625
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1626
+ this.state.errorMap?.[serverErrKey] &&
1627
+ cause !== 'server' &&
1628
+ !hasErrored
1629
+ ) {
1630
+ this.baseStore.setState((prev) => ({
1631
+ ...prev,
1632
+ errorMap: {
1633
+ ...prev.errorMap,
1634
+ [serverErrKey]: undefined,
1635
+ },
1636
+ }))
1637
+ }
1515
1638
  })
1516
1639
 
1517
1640
  return { hasErrored, fieldsErrorMap: currentValidationErrorMap }
@@ -1534,7 +1657,11 @@ export class FormApi<
1534
1657
  TOnSubmitAsync
1535
1658
  >
1536
1659
  > => {
1537
- const validates = getAsyncValidatorArray(cause, this.options)
1660
+ const validates = getAsyncValidatorArray(cause, {
1661
+ ...this.options,
1662
+ form: this,
1663
+ validationLogic: this.options.validationLogic || defaultValidationLogic,
1664
+ })
1538
1665
 
1539
1666
  if (!this.state.isFormValidating) {
1540
1667
  this.baseStore.setState((prev) => ({ ...prev, isFormValidating: true }))
@@ -1878,6 +2005,7 @@ export class FormApi<
1878
2005
  onSubmit: undefined,
1879
2006
  onMount: undefined,
1880
2007
  onServer: undefined,
2008
+ onDynamic: undefined,
1881
2009
  },
1882
2010
  })
1883
2011
  }
@@ -2198,6 +2326,8 @@ export class FormApi<
2198
2326
  UnwrapFormAsyncValidateOrFn<TOnBlurAsync>,
2199
2327
  UnwrapFormValidateOrFn<TOnSubmit>,
2200
2328
  UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>,
2329
+ UnwrapFormValidateOrFn<TOnDynamic>,
2330
+ UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>,
2201
2331
  UnwrapFormAsyncValidateOrFn<TOnServer>
2202
2332
  >,
2203
2333
  ) {
@@ -2260,6 +2390,8 @@ export class FormApi<
2260
2390
  | UnwrapFormAsyncValidateOrFn<TOnBlurAsync>
2261
2391
  | UnwrapFormValidateOrFn<TOnSubmit>
2262
2392
  | UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>
2393
+ | UnwrapFormValidateOrFn<TOnDynamic>
2394
+ | UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>
2263
2395
  | UnwrapFormAsyncValidateOrFn<TOnServer>
2264
2396
  >
2265
2397
  errorMap: ValidationErrorMap<
@@ -2270,6 +2402,8 @@ export class FormApi<
2270
2402
  UnwrapFormAsyncValidateOrFn<TOnBlurAsync>,
2271
2403
  UnwrapFormValidateOrFn<TOnSubmit>,
2272
2404
  UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>,
2405
+ UnwrapFormValidateOrFn<TOnDynamic>,
2406
+ UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>,
2273
2407
  UnwrapFormAsyncValidateOrFn<TOnServer>
2274
2408
  >
2275
2409
  }
@@ -2359,6 +2493,8 @@ function getErrorMapKey(cause: ValidationCause) {
2359
2493
  return 'onMount'
2360
2494
  case 'server':
2361
2495
  return 'onServer'
2496
+ case 'dynamic':
2497
+ return 'onDynamic'
2362
2498
  case 'change':
2363
2499
  default:
2364
2500
  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'