@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/mergeForm.ts CHANGED
@@ -81,10 +81,25 @@ export function mergeForm<TFormData>(
81
81
  any,
82
82
  any,
83
83
  any,
84
+ any,
85
+ any,
84
86
  any
85
87
  >,
86
88
  state: Partial<
87
- FormApi<TFormData, any, any, any, any, any, any, any, any, any>['state']
89
+ FormApi<
90
+ TFormData,
91
+ any,
92
+ any,
93
+ any,
94
+ any,
95
+ any,
96
+ any,
97
+ any,
98
+ any,
99
+ any,
100
+ any,
101
+ any
102
+ >['state']
88
103
  >,
89
104
  ) {
90
105
  mutateMergeDeep(baseForm.state, state)
package/src/metaHelper.ts CHANGED
@@ -30,6 +30,8 @@ export function metaHelper<
30
30
  TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
31
31
  TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
32
32
  TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
33
+ TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
34
+ TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
33
35
  TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
34
36
  TSubmitMeta,
35
37
  >(
@@ -42,6 +44,8 @@ export function metaHelper<
42
44
  TOnBlurAsync,
43
45
  TOnSubmit,
44
46
  TOnSubmitAsync,
47
+ TOnDynamic,
48
+ TOnDynamicAsync,
45
49
  TOnServer,
46
50
  TSubmitMeta
47
51
  >,
package/src/types.ts CHANGED
@@ -10,7 +10,13 @@ export type ValidationSource = 'form' | 'field'
10
10
  * "server" is only intended for SSR/SSG validation and should not execute anything
11
11
  * @private
12
12
  */
13
- export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount' | 'server'
13
+ export type ValidationCause =
14
+ | 'change'
15
+ | 'blur'
16
+ | 'submit'
17
+ | 'mount'
18
+ | 'server'
19
+ | 'dynamic'
14
20
 
15
21
  /**
16
22
  * @private
@@ -33,12 +39,15 @@ export type ValidationErrorMap<
33
39
  TOnBlurAsyncReturn = unknown,
34
40
  TOnSubmitReturn = unknown,
35
41
  TOnSubmitAsyncReturn = unknown,
42
+ TOnDynamicReturn = unknown,
43
+ TOnDynamicAsyncReturn = unknown,
36
44
  TOnServerReturn = unknown,
37
45
  > = {
38
46
  onMount?: TOnMountReturn
39
47
  onChange?: TOnChangeReturn | TOnChangeAsyncReturn
40
48
  onBlur?: TOnBlurReturn | TOnBlurAsyncReturn
41
49
  onSubmit?: TOnSubmitReturn | TOnSubmitAsyncReturn
50
+ onDynamic?: TOnDynamicReturn | TOnDynamicAsyncReturn
42
51
  onServer?: TOnServerReturn
43
52
  }
44
53
 
@@ -51,6 +60,7 @@ export type ValidationErrorMapSource = {
51
60
  onBlur?: ValidationSource
52
61
  onSubmit?: ValidationSource
53
62
  onServer?: ValidationSource
63
+ onDynamic?: ValidationSource
54
64
  }
55
65
 
56
66
  /**
@@ -65,6 +75,8 @@ export type FormValidationErrorMap<
65
75
  TOnBlurAsyncReturn = unknown,
66
76
  TOnSubmitReturn = unknown,
67
77
  TOnSubmitAsyncReturn = unknown,
78
+ TOnDynamicReturn = unknown,
79
+ TOnDynamicAsyncReturn = unknown,
68
80
  TOnServerReturn = unknown,
69
81
  > = {
70
82
  onMount?: TOnMountReturn | GlobalFormValidationError<TFormData>
@@ -80,6 +92,10 @@ export type FormValidationErrorMap<
80
92
  | TOnSubmitReturn
81
93
  | TOnSubmitAsyncReturn
82
94
  | GlobalFormValidationError<TFormData>
95
+ onDynamic?:
96
+ | TOnDynamicReturn
97
+ | TOnDynamicAsyncReturn
98
+ | GlobalFormValidationError<TFormData>
83
99
  onServer?: TOnServerReturn
84
100
  }
85
101
 
package/src/utils.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { defaultValidationLogic } from './ValidationLogic'
2
+ import type { ValidationLogicProps } from './ValidationLogic'
1
3
  import type { FieldValidators } from './FieldApi'
2
4
  import type { FormValidators } from './FormApi'
3
5
  import type {
@@ -225,78 +227,6 @@ export interface AsyncValidator<T> {
225
227
  debounceMs: number
226
228
  }
227
229
 
228
- /**
229
- * @private
230
- */
231
- export function getAsyncValidatorArray<T>(
232
- cause: ValidationCause,
233
- options: AsyncValidatorArrayPartialOptions<T>,
234
- ): T extends FieldValidators<any, any, any, any, any, any, any, any, any, any>
235
- ? Array<
236
- AsyncValidator<T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']>
237
- >
238
- : T extends FormValidators<any, any, any, any, any, any, any, any>
239
- ? Array<
240
- AsyncValidator<
241
- T['onChangeAsync'] | T['onBlurAsync'] | T['onSubmitAsync']
242
- >
243
- >
244
- : never {
245
- const { asyncDebounceMs } = options
246
- const {
247
- onChangeAsync,
248
- onBlurAsync,
249
- onSubmitAsync,
250
- onBlurAsyncDebounceMs,
251
- onChangeAsyncDebounceMs,
252
- } = (options.validators || {}) as
253
- | FieldValidators<any, any, any, any, any, any, any, any, any, any>
254
- | FormValidators<any, any, any, any, any, any, any, any>
255
-
256
- const defaultDebounceMs = asyncDebounceMs ?? 0
257
-
258
- const changeValidator = {
259
- cause: 'change',
260
- validate: onChangeAsync,
261
- debounceMs: onChangeAsyncDebounceMs ?? defaultDebounceMs,
262
- } as const
263
-
264
- const blurValidator = {
265
- cause: 'blur',
266
- validate: onBlurAsync,
267
- debounceMs: onBlurAsyncDebounceMs ?? defaultDebounceMs,
268
- } as const
269
-
270
- const submitValidator = {
271
- cause: 'submit',
272
- validate: onSubmitAsync,
273
- debounceMs: 0,
274
- } as const
275
-
276
- const noopValidator = (
277
- validator:
278
- | typeof changeValidator
279
- | typeof blurValidator
280
- | typeof submitValidator,
281
- ) => ({ ...validator, debounceMs: 0 }) as const
282
-
283
- switch (cause) {
284
- case 'submit':
285
- return [
286
- noopValidator(changeValidator),
287
- noopValidator(blurValidator),
288
- submitValidator,
289
- ] as never
290
- case 'blur':
291
- return [blurValidator] as never
292
- case 'change':
293
- return [changeValidator] as never
294
- case 'server':
295
- default:
296
- return [] as never
297
- }
298
- }
299
-
300
230
  interface SyncValidatorArrayPartialOptions<T> {
301
231
  validators?: T
302
232
  }
@@ -314,51 +244,171 @@ export interface SyncValidator<T> {
314
244
  */
315
245
  export function getSyncValidatorArray<T>(
316
246
  cause: ValidationCause,
317
- options: SyncValidatorArrayPartialOptions<T>,
318
- ): T extends FieldValidators<any, any, any, any, any, any, any, any, any, any>
247
+ options: SyncValidatorArrayPartialOptions<T> & {
248
+ validationLogic?: any
249
+ form?: any
250
+ },
251
+ ): T extends FieldValidators<
252
+ any,
253
+ any,
254
+ any,
255
+ any,
256
+ any,
257
+ any,
258
+ any,
259
+ any,
260
+ any,
261
+ any,
262
+ any,
263
+ any
264
+ >
319
265
  ? Array<
320
- SyncValidator<T['onChange'] | T['onBlur'] | T['onSubmit'] | T['onMount']>
266
+ SyncValidator<
267
+ | T['onChange']
268
+ | T['onBlur']
269
+ | T['onSubmit']
270
+ | T['onMount']
271
+ | T['onDynamic']
272
+ >
321
273
  >
322
- : T extends FormValidators<any, any, any, any, any, any, any, any>
274
+ : T extends FormValidators<any, any, any, any, any, any, any, any, any, any>
323
275
  ? Array<
324
276
  SyncValidator<
325
- T['onChange'] | T['onBlur'] | T['onSubmit'] | T['onMount']
277
+ | T['onChange']
278
+ | T['onBlur']
279
+ | T['onSubmit']
280
+ | T['onMount']
281
+ | T['onDynamic']
326
282
  >
327
283
  >
328
284
  : never {
329
- const { onChange, onBlur, onSubmit, onMount } = (options.validators || {}) as
330
- | FieldValidators<any, any, any, any, any, any, any, any, any, any>
331
- | FormValidators<any, any, any, any, any, any, any, any>
332
-
333
- const changeValidator = { cause: 'change', validate: onChange } as const
334
- const blurValidator = { cause: 'blur', validate: onBlur } as const
335
- const submitValidator = { cause: 'submit', validate: onSubmit } as const
336
- const mountValidator = { cause: 'mount', validate: onMount } as const
337
-
338
- // Allows us to clear onServer errors
339
- const serverValidator = {
340
- cause: 'server',
341
- validate: () => undefined,
342
- } as const
343
-
344
- switch (cause) {
345
- case 'mount':
346
- return [mountValidator] as never
347
- case 'submit':
348
- return [
349
- changeValidator,
350
- blurValidator,
351
- submitValidator,
352
- serverValidator,
353
- ] as never
354
- case 'server':
355
- return [serverValidator] as never
356
- case 'blur':
357
- return [blurValidator, serverValidator] as never
358
- case 'change':
359
- default:
360
- return [changeValidator, serverValidator] as never
285
+ const runValidation = (
286
+ props: Parameters<ValidationLogicProps['runValidation']>[0],
287
+ ) => {
288
+ return props.validators.filter(Boolean).map((validator) => {
289
+ return {
290
+ cause: validator!.cause,
291
+ validate: validator!.fn,
292
+ }
293
+ })
361
294
  }
295
+
296
+ return options.validationLogic({
297
+ form: options.form,
298
+ validators: options.validators,
299
+ event: { type: cause, async: false },
300
+ runValidation,
301
+ })
302
+ }
303
+
304
+ /**
305
+ * @private
306
+ */
307
+ export function getAsyncValidatorArray<T>(
308
+ cause: ValidationCause,
309
+ options: AsyncValidatorArrayPartialOptions<T> & {
310
+ validationLogic?: any
311
+ form?: any
312
+ },
313
+ ): T extends FieldValidators<
314
+ any,
315
+ any,
316
+ any,
317
+ any,
318
+ any,
319
+ any,
320
+ any,
321
+ any,
322
+ any,
323
+ any,
324
+ any,
325
+ any
326
+ >
327
+ ? Array<
328
+ AsyncValidator<
329
+ | T['onChangeAsync']
330
+ | T['onBlurAsync']
331
+ | T['onSubmitAsync']
332
+ | T['onDynamicAsync']
333
+ >
334
+ >
335
+ : T extends FormValidators<any, any, any, any, any, any, any, any, any, any>
336
+ ? Array<
337
+ AsyncValidator<
338
+ | T['onChangeAsync']
339
+ | T['onBlurAsync']
340
+ | T['onSubmitAsync']
341
+ | T['onDynamicAsync']
342
+ >
343
+ >
344
+ : never {
345
+ const { asyncDebounceMs } = options
346
+ const {
347
+ onBlurAsyncDebounceMs,
348
+ onChangeAsyncDebounceMs,
349
+ onDynamicAsyncDebounceMs,
350
+ } = (options.validators || {}) as
351
+ | FieldValidators<
352
+ any,
353
+ any,
354
+ any,
355
+ any,
356
+ any,
357
+ any,
358
+ any,
359
+ any,
360
+ any,
361
+ any,
362
+ any,
363
+ any
364
+ >
365
+ | FormValidators<any, any, any, any, any, any, any, any, any, any>
366
+
367
+ const defaultDebounceMs = asyncDebounceMs ?? 0
368
+
369
+ const runValidation = (
370
+ props: Parameters<ValidationLogicProps['runValidation']>[0],
371
+ ) => {
372
+ return props.validators.filter(Boolean).map((validator) => {
373
+ const validatorCause = validator?.cause || cause
374
+
375
+ let debounceMs = defaultDebounceMs
376
+
377
+ switch (validatorCause) {
378
+ case 'change':
379
+ debounceMs = onChangeAsyncDebounceMs ?? defaultDebounceMs
380
+ break
381
+ case 'blur':
382
+ debounceMs = onBlurAsyncDebounceMs ?? defaultDebounceMs
383
+ break
384
+ case 'dynamic':
385
+ debounceMs = onDynamicAsyncDebounceMs ?? defaultDebounceMs
386
+ break
387
+ case 'submit':
388
+ debounceMs = 0 // submit validators are always run immediately
389
+ break
390
+ default:
391
+ break
392
+ }
393
+
394
+ if (cause === 'submit') {
395
+ debounceMs = 0
396
+ }
397
+
398
+ return {
399
+ cause: validatorCause,
400
+ validate: validator!.fn,
401
+ debounceMs: debounceMs,
402
+ }
403
+ })
404
+ }
405
+
406
+ return options.validationLogic({
407
+ form: options.form,
408
+ validators: options.validators,
409
+ event: { type: cause, async: true },
410
+ runValidation,
411
+ })
362
412
  }
363
413
 
364
414
  export const isGlobalFormValidationError = (