@tanstack/form-core 0.3.7 → 0.4.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/build/legacy/FieldApi.cjs +46 -26
  2. package/build/legacy/FieldApi.cjs.map +1 -1
  3. package/build/legacy/FieldApi.d.cts +1 -0
  4. package/build/legacy/FieldApi.d.ts +1 -0
  5. package/build/legacy/FieldApi.js +46 -18
  6. package/build/legacy/FieldApi.js.map +1 -1
  7. package/build/legacy/FormApi.cjs +10 -12
  8. package/build/legacy/FormApi.cjs.map +1 -1
  9. package/build/legacy/FormApi.d.cts +2 -1
  10. package/build/legacy/FormApi.d.ts +2 -1
  11. package/build/legacy/FormApi.js +10 -14
  12. package/build/legacy/FormApi.js.map +1 -1
  13. package/build/legacy/index.cjs +3 -1
  14. package/build/legacy/index.cjs.map +1 -1
  15. package/build/legacy/index.d.cts +48 -52
  16. package/build/legacy/index.d.ts +48 -52
  17. package/build/legacy/index.js +1 -0
  18. package/build/legacy/index.js.map +1 -1
  19. package/build/legacy/types.cjs +19 -0
  20. package/build/legacy/types.cjs.map +1 -0
  21. package/build/legacy/types.d.cts +7 -0
  22. package/build/legacy/types.d.ts +7 -0
  23. package/build/legacy/types.js +1 -0
  24. package/build/legacy/utils.js +0 -2
  25. package/build/legacy/utils.js.map +1 -1
  26. package/build/modern/FieldApi.cjs +44 -9
  27. package/build/modern/FieldApi.cjs.map +1 -1
  28. package/build/modern/FieldApi.d.cts +1 -0
  29. package/build/modern/FieldApi.d.ts +1 -0
  30. package/build/modern/FieldApi.js +44 -9
  31. package/build/modern/FieldApi.js.map +1 -1
  32. package/build/modern/FormApi.cjs +10 -12
  33. package/build/modern/FormApi.cjs.map +1 -1
  34. package/build/modern/FormApi.d.cts +2 -1
  35. package/build/modern/FormApi.d.ts +2 -1
  36. package/build/modern/FormApi.js +10 -12
  37. package/build/modern/FormApi.js.map +1 -1
  38. package/build/modern/index.cjs +3 -1
  39. package/build/modern/index.cjs.map +1 -1
  40. package/build/modern/index.d.cts +48 -52
  41. package/build/modern/index.d.ts +48 -52
  42. package/build/modern/index.js +1 -0
  43. package/build/modern/index.js.map +1 -1
  44. package/build/modern/types.cjs +19 -0
  45. package/build/modern/types.cjs.map +1 -0
  46. package/build/modern/types.d.cts +7 -0
  47. package/build/modern/types.d.ts +7 -0
  48. package/build/modern/types.js +1 -0
  49. package/build/modern/types.js.map +1 -0
  50. package/package.json +1 -1
  51. package/src/FieldApi.ts +171 -44
  52. package/src/FormApi.ts +61 -46
  53. package/src/index.ts +1 -0
  54. package/src/tests/FieldApi.spec.ts +1 -1
  55. package/src/tests/FieldApi.test-d.ts +26 -11
  56. package/src/tests/FormApi.spec.ts +1 -1
  57. package/src/types.ts +7 -0
  58. package/build/legacy/chunk-4QZDOMDG.js +0 -19
  59. /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, ValidationError, ValidationErrorMap } from './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<TParentData, TName extends DeepKeys<TParentData>, TData> = (
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
- TData,
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
- * If TData is unknown, we can use the TName generic to determine the type
30
- */
31
- TData = DeepValue<TParentData, TName>,
63
+ ValidatorType,
64
+ FormValidator,
65
+ TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
32
66
  > {
33
- name: DeepKeys<TParentData>
67
+ name: TName
34
68
  index?: TData extends any[] ? number : never
35
69
  defaultValue?: TData
36
70
  asyncDebounceMs?: number
37
71
  asyncAlways?: boolean
38
- onMount?: (formApi: FieldApi<TParentData, TName>) => void
39
- onChange?: ValidateFn<TParentData, TName, TData>
40
- onChangeAsync?: ValidateAsyncFn<TParentData, TName, TData>
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?: ValidateFn<TParentData, TName, TData>
43
- onBlurAsync?: ValidateAsyncFn<TParentData, TName, TData>
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?: ValidateAsyncFn<TParentData, TName, TData>
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
- TData = DeepValue<TParentData, TName>,
53
- > extends FieldOptions<TParentData, TName, TData> {
54
- form: FormApi<TParentData>
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
- TData = DeepValue<TParentData, TName>,
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<TParentData, TName, TData> & {
91
- form: FormApi<TParentData>
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 any
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 = (opts: FieldApiOptions<TParentData, TName, TData>) => {
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
- const error = normalizeError(validate(value as never, this as never))
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
- #leaseValidateAsync = () => {
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.#leaseValidateAsync()
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.#leaseValidateAsync()
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 validate(value as never, this as never)
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
- const errorMapKey = getErrorMapKey(cause)
385
- // If there is an error mapped to the errorMapKey (eg. onChange, onBlur, onSubmit), return the errors array, do not attempt async validation
386
- if (this.getMeta().errorMap[errorMapKey]) {
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
- export type FormOptions<TData> = {
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
- onMount?: (values: TData, formApi: FormApi<TData>) => ValidationError
12
- onMountAsync?: (
13
- values: TData,
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?: (values: TData, formApi: FormApi<TData>) => ValidationError
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?: (values: TData, formApi: FormApi<TData>) => ValidationError
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?: (values: TData, formApi: FormApi<TData>) => any | Promise<any>
30
- onSubmitInvalid?: (values: TData, formApi: FormApi<TData>) => void
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, 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>> = {} as any
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 (Object.values(this.fieldInfo) as FieldInfo<any>[]).forEach(
206
- (field) => {
207
- Object.values(field.instances).forEach((instance) => {
208
- // If any fields are not touched
209
- if (!instance.state.meta.isTouched) {
210
- // Mark them as touched
211
- instance.setMeta((prev) => ({ ...prev, isTouched: true }))
212
- // Validate the field
213
- fieldValidationPromises.push(
214
- Promise.resolve().then(() => instance.validate(cause)),
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>[number],
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>[number],
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
@@ -1,3 +1,4 @@
1
1
  export * from './FormApi'
2
2
  export * from './FieldApi'
3
3
  export * from './utils'
4
+ export * from './types'
@@ -551,7 +551,7 @@ describe('field api', () => {
551
551
  interface Form {
552
552
  name: string
553
553
  }
554
- const form = new FormApi<Form>()
554
+ const form = new FormApi<Form, unknown>()
555
555
 
556
556
  const field = new FieldApi({
557
557
  form,
@@ -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 a subfield properly', () => {
5
+ it('should type value properly', () => {
6
6
  const form = new FormApi({
7
7
  defaultValues: {
8
- names: {
9
- first: 'one',
10
- second: 'two',
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: 'names',
14
+ name: 'name',
18
15
  })
19
16
 
20
- const subfield = field.getSubField('first')
21
-
22
- assertType<'one'>(subfield.getValue())
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
+ })
@@ -119,7 +119,7 @@ describe('form api', () => {
119
119
  },
120
120
  })
121
121
 
122
- form.pushFieldValue('name', 'other')
122
+ form.setFieldValue('name', 'other')
123
123
  form.state.submissionAttempts = 300
124
124
 
125
125
  form.reset()
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