@tanstack/form-core 0.3.6 → 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 +49 -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 +49 -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 +47 -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 +47 -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 +175 -44
  52. package/src/FormApi.ts +61 -46
  53. package/src/index.ts +1 -0
  54. package/src/tests/FieldApi.spec.ts +17 -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,11 @@ 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
178
+
179
+ if (opts.defaultValue !== undefined) {
180
+ this.form.setFieldValue(this.name, opts.defaultValue as never)
181
+ }
103
182
 
104
183
  this.store = new Store<FieldState<TData>>(
105
184
  {
@@ -163,12 +242,14 @@ export class FieldApi<
163
242
  unsubscribe()
164
243
  delete info.instances[this.uid]
165
244
  if (!Object.keys(info.instances).length) {
166
- delete this.form.fieldInfo[this.name]
245
+ delete this.form.fieldInfo[this.name as never]
167
246
  }
168
247
  }
169
248
  }
170
249
 
171
- update = (opts: FieldApiOptions<TParentData, TName, TData>) => {
250
+ update = (
251
+ opts: FieldApiOptions<TParentData, TName, ValidatorType, TData>,
252
+ ) => {
172
253
  // Default Value
173
254
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
174
255
  if (this.state.value === undefined) {
@@ -234,10 +315,10 @@ export class FieldApi<
234
315
 
235
316
  getSubField = <
236
317
  TSubName extends DeepKeys<TData>,
237
- TSubData = DeepValue<TData, TSubName>,
318
+ TSubData extends DeepValue<TData, TSubName> = DeepValue<TData, TSubName>,
238
319
  >(
239
320
  name: TSubName,
240
- ): FieldApi<TData, TSubName, TSubData> =>
321
+ ): FieldApi<TData, TSubName, ValidatorType, TSubData> =>
241
322
  new FieldApi({
242
323
  name: `${this.name}.${name}` as never,
243
324
  form: this.form,
@@ -247,13 +328,36 @@ export class FieldApi<
247
328
  const { onChange, onBlur } = this.options
248
329
  const validate =
249
330
  cause === 'submit' ? undefined : cause === 'change' ? onChange : onBlur
331
+
250
332
  if (!validate) return
251
333
 
252
334
  // Use the validationCount for all field instances to
253
335
  // track freshness of the validation
254
336
  const validationCount = (this.getInfo().validationCount || 0) + 1
255
337
  this.getInfo().validationCount = validationCount
256
- 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())
257
361
  const errorMapKey = getErrorMapKey(cause)
258
362
  if (this.state.meta.errorMap[errorMapKey] !== error) {
259
363
  this.setMeta((prev) => ({
@@ -271,7 +375,7 @@ export class FieldApi<
271
375
  }
272
376
  }
273
377
 
274
- #leaseValidateAsync = () => {
378
+ __leaseValidateAsync = () => {
275
379
  const count = (this.getInfo().validationAsyncCount || 0) + 1
276
380
  this.getInfo().validationAsyncCount = count
277
381
  return count
@@ -279,7 +383,7 @@ export class FieldApi<
279
383
 
280
384
  cancelValidateAsync = () => {
281
385
  // Lease a new validation count to ignore any pending validations
282
- this.#leaseValidateAsync()
386
+ this.__leaseValidateAsync()
283
387
  // Cancel any pending validation state
284
388
  this.setMeta((prev) => ({
285
389
  ...prev,
@@ -313,12 +417,13 @@ export class FieldApi<
313
417
  asyncDebounceMs ??
314
418
  0
315
419
 
316
- if (this.state.meta.isValidating !== true)
420
+ if (this.state.meta.isValidating !== true) {
317
421
  this.setMeta((prev) => ({ ...prev, isValidating: true }))
422
+ }
318
423
 
319
424
  // Use the validationCount for all field instances to
320
425
  // track freshness of the validation
321
- const validationAsyncCount = this.#leaseValidateAsync()
426
+ const validationAsyncCount = this.__leaseValidateAsync()
322
427
 
323
428
  const checkLatest = () =>
324
429
  validationAsyncCount === this.getInfo().validationAsyncCount
@@ -334,11 +439,31 @@ export class FieldApi<
334
439
  await new Promise((r) => setTimeout(r, debounceMs))
335
440
  }
336
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
+
337
462
  // Only kick off validation if this validation is the latest attempt
338
463
  if (checkLatest()) {
339
464
  const prevErrors = this.getMeta().errors
340
465
  try {
341
- const rawError = await validate(value as never, this as never)
466
+ const rawError = await doValidate()
342
467
  if (checkLatest()) {
343
468
  const error = normalizeError(rawError)
344
469
  this.setMeta((prev) => ({
@@ -374,12 +499,18 @@ export class FieldApi<
374
499
  ): ValidationError[] | Promise<ValidationError[]> => {
375
500
  // If the field is pristine and validatePristine is false, do not validate
376
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
+
377
507
  // Attempt to sync validate first
378
508
  this.validateSync(value, cause)
379
509
 
380
- const errorMapKey = getErrorMapKey(cause)
381
- // If there is an error mapped to the errorMapKey (eg. onChange, onBlur, onSubmit), return the errors array, do not attempt async validation
382
- 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) {
383
514
  if (!this.options.asyncAlways) {
384
515
  return this.state.meta.errors
385
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'
@@ -20,6 +20,22 @@ describe('field api', () => {
20
20
  expect(field.getValue()).toBe('test')
21
21
  })
22
22
 
23
+ it('should use field default value first', () => {
24
+ const form = new FormApi({
25
+ defaultValues: {
26
+ name: 'test',
27
+ },
28
+ })
29
+
30
+ const field = new FieldApi({
31
+ form,
32
+ defaultValue: 'other',
33
+ name: 'name',
34
+ })
35
+
36
+ expect(field.getValue()).toBe('other')
37
+ })
38
+
23
39
  it('should get default meta', () => {
24
40
  const form = new FormApi()
25
41
  const field = new FieldApi({
@@ -535,7 +551,7 @@ describe('field api', () => {
535
551
  interface Form {
536
552
  name: string
537
553
  }
538
- const form = new FormApi<Form>()
554
+ const form = new FormApi<Form, unknown>()
539
555
 
540
556
  const field = new FieldApi({
541
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