@tanstack/form-core 0.0.2 → 0.0.4

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.
@@ -8,31 +8,31 @@
8
8
  "children": [
9
9
  {
10
10
  "name": "node_modules/.pnpm/@tanstack+store@0.0.1-beta.84/node_modules/@tanstack/store/build/esm/index.js",
11
- "uid": "7ee7-13"
11
+ "uid": "8485-13"
12
12
  },
13
13
  {
14
14
  "name": "packages/form-core/src",
15
15
  "children": [
16
16
  {
17
- "uid": "7ee7-15",
17
+ "uid": "8485-15",
18
18
  "name": "utils.ts"
19
19
  },
20
20
  {
21
- "uid": "7ee7-17",
21
+ "uid": "8485-17",
22
22
  "name": "FormApi.ts"
23
23
  },
24
24
  {
25
- "uid": "7ee7-21",
25
+ "uid": "8485-21",
26
26
  "name": "FieldApi.ts"
27
27
  },
28
28
  {
29
- "uid": "7ee7-23",
29
+ "uid": "8485-23",
30
30
  "name": "index.ts"
31
31
  }
32
32
  ]
33
33
  },
34
34
  {
35
- "uid": "7ee7-19",
35
+ "uid": "8485-19",
36
36
  "name": "\u0000rollupPluginBabelHelpers.js"
37
37
  }
38
38
  ]
@@ -41,138 +41,138 @@
41
41
  "isRoot": true
42
42
  },
43
43
  "nodeParts": {
44
- "7ee7-13": {
44
+ "8485-13": {
45
45
  "renderedLength": 1288,
46
46
  "gzipLength": 497,
47
47
  "brotliLength": 0,
48
- "mainUid": "7ee7-12"
48
+ "mainUid": "8485-12"
49
49
  },
50
- "7ee7-15": {
50
+ "8485-15": {
51
51
  "renderedLength": 1944,
52
52
  "gzipLength": 684,
53
53
  "brotliLength": 0,
54
- "mainUid": "7ee7-14"
54
+ "mainUid": "8485-14"
55
55
  },
56
- "7ee7-17": {
56
+ "8485-17": {
57
57
  "renderedLength": 10266,
58
58
  "gzipLength": 2308,
59
59
  "brotliLength": 0,
60
- "mainUid": "7ee7-16"
60
+ "mainUid": "8485-16"
61
61
  },
62
- "7ee7-19": {
62
+ "8485-19": {
63
63
  "renderedLength": 353,
64
64
  "gzipLength": 227,
65
65
  "brotliLength": 0,
66
- "mainUid": "7ee7-18"
66
+ "mainUid": "8485-18"
67
67
  },
68
- "7ee7-21": {
69
- "renderedLength": 7553,
70
- "gzipLength": 1868,
68
+ "8485-21": {
69
+ "renderedLength": 10602,
70
+ "gzipLength": 2507,
71
71
  "brotliLength": 0,
72
- "mainUid": "7ee7-20"
72
+ "mainUid": "8485-20"
73
73
  },
74
- "7ee7-23": {
74
+ "8485-23": {
75
75
  "renderedLength": 0,
76
76
  "gzipLength": 0,
77
77
  "brotliLength": 0,
78
- "mainUid": "7ee7-22"
78
+ "mainUid": "8485-22"
79
79
  }
80
80
  },
81
81
  "nodeMetas": {
82
- "7ee7-12": {
82
+ "8485-12": {
83
83
  "id": "/node_modules/.pnpm/@tanstack+store@0.0.1-beta.84/node_modules/@tanstack/store/build/esm/index.js",
84
84
  "moduleParts": {
85
- "index.production.js": "7ee7-13"
85
+ "index.production.js": "8485-13"
86
86
  },
87
87
  "imported": [],
88
88
  "importedBy": [
89
89
  {
90
- "uid": "7ee7-16"
90
+ "uid": "8485-16"
91
91
  },
92
92
  {
93
- "uid": "7ee7-20"
93
+ "uid": "8485-20"
94
94
  }
95
95
  ]
96
96
  },
97
- "7ee7-14": {
97
+ "8485-14": {
98
98
  "id": "/packages/form-core/src/utils.ts",
99
99
  "moduleParts": {
100
- "index.production.js": "7ee7-15"
100
+ "index.production.js": "8485-15"
101
101
  },
102
102
  "imported": [],
103
103
  "importedBy": [
104
104
  {
105
- "uid": "7ee7-22"
105
+ "uid": "8485-22"
106
106
  },
107
107
  {
108
- "uid": "7ee7-16"
108
+ "uid": "8485-16"
109
109
  }
110
110
  ]
111
111
  },
112
- "7ee7-16": {
112
+ "8485-16": {
113
113
  "id": "/packages/form-core/src/FormApi.ts",
114
114
  "moduleParts": {
115
- "index.production.js": "7ee7-17"
115
+ "index.production.js": "8485-17"
116
116
  },
117
117
  "imported": [
118
118
  {
119
- "uid": "7ee7-12"
119
+ "uid": "8485-12"
120
120
  },
121
121
  {
122
- "uid": "7ee7-14"
122
+ "uid": "8485-14"
123
123
  }
124
124
  ],
125
125
  "importedBy": [
126
126
  {
127
- "uid": "7ee7-22"
127
+ "uid": "8485-22"
128
128
  }
129
129
  ]
130
130
  },
131
- "7ee7-18": {
131
+ "8485-18": {
132
132
  "id": "\u0000rollupPluginBabelHelpers.js",
133
133
  "moduleParts": {
134
- "index.production.js": "7ee7-19"
134
+ "index.production.js": "8485-19"
135
135
  },
136
136
  "imported": [],
137
137
  "importedBy": [
138
138
  {
139
- "uid": "7ee7-20"
139
+ "uid": "8485-20"
140
140
  }
141
141
  ]
142
142
  },
143
- "7ee7-20": {
143
+ "8485-20": {
144
144
  "id": "/packages/form-core/src/FieldApi.ts",
145
145
  "moduleParts": {
146
- "index.production.js": "7ee7-21"
146
+ "index.production.js": "8485-21"
147
147
  },
148
148
  "imported": [
149
149
  {
150
- "uid": "7ee7-18"
150
+ "uid": "8485-18"
151
151
  },
152
152
  {
153
- "uid": "7ee7-12"
153
+ "uid": "8485-12"
154
154
  }
155
155
  ],
156
156
  "importedBy": [
157
157
  {
158
- "uid": "7ee7-22"
158
+ "uid": "8485-22"
159
159
  }
160
160
  ]
161
161
  },
162
- "7ee7-22": {
162
+ "8485-22": {
163
163
  "id": "/packages/form-core/src/index.ts",
164
164
  "moduleParts": {
165
- "index.production.js": "7ee7-23"
165
+ "index.production.js": "8485-23"
166
166
  },
167
167
  "imported": [
168
168
  {
169
- "uid": "7ee7-16"
169
+ "uid": "8485-16"
170
170
  },
171
171
  {
172
- "uid": "7ee7-20"
172
+ "uid": "8485-20"
173
173
  },
174
174
  {
175
- "uid": "7ee7-14"
175
+ "uid": "8485-14"
176
176
  }
177
177
  ],
178
178
  "importedBy": [],
@@ -1,7 +1,7 @@
1
1
  import type { DeepKeys, DeepValue, RequiredByKey, Updater } from './utils';
2
2
  import type { FormApi, ValidationError } from './FormApi';
3
3
  import { Store } from '@tanstack/store';
4
- declare type ValidateOn = 'change' | 'blur' | 'submit';
4
+ export declare type ValidationCause = 'change' | 'blur' | 'submit';
5
5
  export declare type FieldOptions<TData, TFormData> = {
6
6
  name: unknown extends TFormData ? string : DeepKeys<TFormData>;
7
7
  defaultValue?: TData;
@@ -9,14 +9,10 @@ export declare type FieldOptions<TData, TFormData> = {
9
9
  validate?: (value: TData, fieldApi: FieldApi<TData, TFormData>) => ValidationError;
10
10
  validateAsync?: (value: TData, fieldApi: FieldApi<TData, TFormData>) => ValidationError | Promise<ValidationError>;
11
11
  validatePristine?: boolean;
12
- validateOn?: ValidateOn;
13
- validateAsyncOn?: ValidateOn;
12
+ validateOn?: ValidationCause;
13
+ validateAsyncOn?: ValidationCause;
14
14
  validateAsyncDebounceMs?: number;
15
- filterValue?: (value: TData) => TData;
16
15
  defaultMeta?: Partial<FieldMeta>;
17
- change?: boolean;
18
- blur?: boolean;
19
- submit?: boolean;
20
16
  };
21
17
  export declare type FieldMeta = {
22
18
  isTouched: boolean;
@@ -44,7 +40,7 @@ export declare class FieldApi<TData, TFormData> {
44
40
  name: DeepKeys<TFormData>;
45
41
  store: Store<FieldState<TData>>;
46
42
  state: FieldState<TData>;
47
- options: RequiredByKey<FieldOptions<TData, TFormData>, 'validateOn' | 'validateAsyncOn'>;
43
+ options: RequiredByKey<FieldOptions<TData, TFormData>, 'validatePristine' | 'validateOn' | 'validateAsyncOn' | 'validateAsyncDebounceMs'>;
48
44
  constructor(opts: FieldApiOptions<TData, TFormData>);
49
45
  mount: () => () => void;
50
46
  update: (opts: FieldApiOptions<TData, TFormData>) => void;
@@ -61,9 +57,11 @@ export declare class FieldApi<TData, TFormData> {
61
57
  removeValue: (index: number) => void;
62
58
  swapValues: (aIndex: number, bIndex: number) => void;
63
59
  getSubField: <TName extends DeepKeys<TData>>(name: TName) => FieldApi<DeepValue<TData, TName>, TFormData>;
64
- validate: () => Promise<ValidationError>;
65
- validateAsync: () => Promise<ValidationError>;
60
+ validateSync: (value?: TData) => Promise<void>;
61
+ cancelValidateAsync: () => void;
62
+ validateAsync: (value?: TData) => Promise<ValidationError>;
63
+ shouldValidate: (isAsync: boolean, cause?: ValidationCause) => boolean;
64
+ validate: (cause?: ValidationCause, value?: TData) => Promise<ValidationError>;
66
65
  getChangeProps: <T extends ChangeProps<any>>(props?: T) => ChangeProps<TData> & Omit<T, keyof ChangeProps<TData>>;
67
66
  getInputProps: <T extends InputProps>(props?: T) => InputProps & Omit<T, keyof InputProps>;
68
67
  }
69
- export {};
@@ -1,21 +1,25 @@
1
1
  import type { FormEvent } from 'react';
2
2
  import { Store } from '@tanstack/store';
3
3
  import type { DeepKeys, DeepValue, Updater } from './utils';
4
- import type { FieldApi, FieldMeta } from './FieldApi';
4
+ import type { FieldApi, FieldMeta, ValidationCause } from './FieldApi';
5
5
  export declare type FormOptions<TData> = {
6
6
  defaultValues?: TData;
7
7
  defaultState?: Partial<FormState<TData>>;
8
- onSubmit?: (values: TData, formApi: FormApi<TData>) => Promise<any>;
8
+ onSubmit?: (values: TData, formApi: FormApi<TData>) => void;
9
9
  onInvalidSubmit?: (values: TData, formApi: FormApi<TData>) => void;
10
10
  validate?: (values: TData, formApi: FormApi<TData>) => Promise<any>;
11
11
  debugForm?: boolean;
12
- validatePristine?: boolean;
12
+ defaultValidatePristine?: boolean;
13
+ defaultValidateOn?: ValidationCause;
14
+ defaultValidateAsyncOn?: ValidationCause;
15
+ defaultValidateAsyncDebounceMs?: number;
13
16
  };
14
17
  export declare type FieldInfo<TFormData> = {
15
18
  instances: Record<string, FieldApi<any, TFormData>>;
16
19
  } & ValidationMeta;
17
20
  export declare type ValidationMeta = {
18
21
  validationCount?: number;
22
+ validationAsyncCount?: number;
19
23
  validationPromise?: Promise<ValidationError>;
20
24
  validationResolve?: (error: ValidationError) => void;
21
25
  validationReject?: (error: unknown) => void;
@@ -447,7 +447,7 @@
447
447
 
448
448
  var _updateStore = /*#__PURE__*/_classPrivateFieldLooseKey("updateStore");
449
449
 
450
- var _validate = /*#__PURE__*/_classPrivateFieldLooseKey("validate");
450
+ var _leaseValidateAsync = /*#__PURE__*/_classPrivateFieldLooseKey("leaseValidateAsync");
451
451
 
452
452
  class FieldApi {
453
453
  constructor(_opts) {
@@ -494,10 +494,13 @@
494
494
  });
495
495
 
496
496
  this.update = opts => {
497
+ var _this$form$options$de, _this$form$options$de2, _this$form$options$de3, _this$form$options$de4;
498
+
497
499
  this.options = {
498
- validateOn: 'change',
499
- validateAsyncOn: 'blur',
500
- validateAsyncDebounceMs: 0,
500
+ validatePristine: (_this$form$options$de = this.form.options.defaultValidatePristine) != null ? _this$form$options$de : false,
501
+ validateOn: (_this$form$options$de2 = this.form.options.defaultValidateOn) != null ? _this$form$options$de2 : 'change',
502
+ validateAsyncOn: (_this$form$options$de3 = this.form.options.defaultValidateAsyncOn) != null ? _this$form$options$de3 : 'blur',
503
+ validateAsyncDebounceMs: (_this$form$options$de4 = this.form.options.defaultValidateAsyncDebounceMs) != null ? _this$form$options$de4 : 0,
501
504
  ...opts
502
505
  }; // Default Value
503
506
 
@@ -535,48 +538,91 @@
535
538
  form: this.form
536
539
  });
537
540
 
538
- Object.defineProperty(this, _validate, {
539
- writable: true,
540
- value: async isAsync => {
541
- if (!this.options.validate) {
542
- return;
543
- }
541
+ this.validateSync = async (value = this.state.value) => {
542
+ const {
543
+ validate
544
+ } = this.options;
545
+
546
+ if (!validate) {
547
+ return;
548
+ } // Use the validationCount for all field instances to
549
+ // track freshness of the validation
544
550
 
551
+
552
+ const validationCount = (this.getInfo().validationCount || 0) + 1;
553
+ this.getInfo().validationCount = validationCount;
554
+ const error = normalizeError(validate(value, this));
555
+
556
+ if (this.state.meta.error !== error) {
545
557
  this.setMeta(prev => ({ ...prev,
546
- isValidating: true
547
- })); // Use the validationCount for all field instances to
548
- // track freshness of the validation
558
+ error
559
+ }));
560
+ } // If a sync error is encountered, cancel any async validation
549
561
 
550
- const validationCount = (this.getInfo().validationCount || 0) + 1;
551
- this.getInfo().validationCount = validationCount;
552
562
 
553
- const checkLatest = () => validationCount === this.getInfo().validationCount;
563
+ if (this.state.meta.error) {
564
+ this.cancelValidateAsync();
565
+ }
566
+ };
554
567
 
555
- if (!this.getInfo().validationPromise) {
556
- this.getInfo().validationPromise = new Promise((resolve, reject) => {
557
- this.getInfo().validationResolve = resolve;
558
- this.getInfo().validationReject = reject;
559
- });
560
- }
568
+ Object.defineProperty(this, _leaseValidateAsync, {
569
+ writable: true,
570
+ value: () => {
571
+ const count = (this.getInfo().validationAsyncCount || 0) + 1;
572
+ this.getInfo().validationAsyncCount = count;
573
+ return count;
574
+ }
575
+ });
561
576
 
562
- try {
563
- const rawError = await this.options.validate(this.state.value, this);
577
+ this.cancelValidateAsync = () => {
578
+ // Lease a new validation count to ignore any pending validations
579
+ _classPrivateFieldLooseBase(this, _leaseValidateAsync)[_leaseValidateAsync](); // Cancel any pending validation state
564
580
 
565
- if (checkLatest()) {
566
- var _this$getInfo$validat, _this$getInfo;
567
581
 
568
- const error = (() => {
569
- if (rawError) {
570
- if (typeof rawError !== 'string') {
571
- return 'Invalid Form Values';
572
- }
582
+ this.setMeta(prev => ({ ...prev,
583
+ isValidating: false
584
+ }));
585
+ };
573
586
 
574
- return rawError;
575
- }
587
+ this.validateAsync = async (value = this.state.value) => {
588
+ const {
589
+ validateAsync,
590
+ validateAsyncDebounceMs
591
+ } = this.options;
576
592
 
577
- return undefined;
578
- })();
593
+ if (!validateAsync) {
594
+ return;
595
+ }
596
+
597
+ if (this.state.meta.isValidating !== true) this.setMeta(prev => ({ ...prev,
598
+ isValidating: true
599
+ })); // Use the validationCount for all field instances to
600
+ // track freshness of the validation
601
+
602
+ const validationAsyncCount = _classPrivateFieldLooseBase(this, _leaseValidateAsync)[_leaseValidateAsync]();
603
+
604
+ const checkLatest = () => validationAsyncCount === this.getInfo().validationAsyncCount;
605
+
606
+ if (!this.getInfo().validationPromise) {
607
+ this.getInfo().validationPromise = new Promise((resolve, reject) => {
608
+ this.getInfo().validationResolve = resolve;
609
+ this.getInfo().validationReject = reject;
610
+ });
611
+ }
579
612
 
613
+ if (validateAsyncDebounceMs > 0) {
614
+ await new Promise(r => setTimeout(r, validateAsyncDebounceMs));
615
+ } // Only kick off validation if this validation is the latest attempt
616
+
617
+
618
+ if (checkLatest()) {
619
+ try {
620
+ const rawError = await validateAsync(value, this);
621
+
622
+ if (checkLatest()) {
623
+ var _this$getInfo$validat, _this$getInfo;
624
+
625
+ const error = normalizeError(rawError);
580
626
  this.setMeta(prev => ({ ...prev,
581
627
  isValidating: false,
582
628
  error
@@ -598,14 +644,45 @@
598
644
  delete this.getInfo().validationPromise;
599
645
  }
600
646
  }
647
+ } // Always return the latest validation promise to the caller
648
+
649
+
650
+ return this.getInfo().validationPromise;
651
+ };
652
+
653
+ this.shouldValidate = (isAsync, cause) => {
654
+ const {
655
+ validateOn,
656
+ validateAsyncOn
657
+ } = this.options;
658
+ const level = getValidationCauseLevel(cause); // Must meet *at least* the validation level to validate,
659
+ // e.g. if validateOn is 'change' and validateCause is 'blur',
660
+ // the field will still validate
661
+
662
+ return Object.keys(validateCauseLevels).some(d => isAsync ? validateAsyncOn : validateOn === d && level >= validateCauseLevels[d]);
663
+ };
664
+
665
+ this.validate = async (cause, value) => {
666
+ // If the field is pristine and validatePristine is false, do not validate
667
+ if (!this.options.validatePristine && !this.state.meta.isTouched) return; // Attempt to sync validate first
668
+
669
+ if (this.shouldValidate(false, cause)) {
670
+ this.validateSync(value);
671
+ } // If there is an error, return it, do not attempt async validation
601
672
 
602
- return this.getInfo().validationPromise;
603
- }
604
- });
605
673
 
606
- this.validate = () => _classPrivateFieldLooseBase(this, _validate)[_validate](false);
674
+ if (this.state.meta.error) {
675
+ return this.state.meta.error;
676
+ } // No error? Attempt async validation
607
677
 
608
- this.validateAsync = () => _classPrivateFieldLooseBase(this, _validate)[_validate](true);
678
+
679
+ if (this.shouldValidate(true, cause)) {
680
+ return this.validateAsync(value);
681
+ } // If there is no sync error or async validation attempt, there is no error
682
+
683
+
684
+ return undefined;
685
+ };
609
686
 
610
687
  this.getChangeProps = (props = {}) => {
611
688
  return { ...props,
@@ -618,14 +695,7 @@
618
695
  this.setMeta(prev => ({ ...prev,
619
696
  isTouched: true
620
697
  }));
621
- const {
622
- validateOn
623
- } = this.options;
624
-
625
- if (validateOn === 'blur' || validateOn.split('-')[0] === 'blur') {
626
- this.validate();
627
- }
628
-
698
+ this.validate('blur');
629
699
  props.onBlur == null ? void 0 : props.onBlur(e);
630
700
  }
631
701
  };
@@ -664,17 +734,12 @@
664
734
  onUpdate: next => {
665
735
  next.meta.touchedError = next.meta.isTouched ? next.meta.error : undefined; // Do not validate pristine fields
666
736
 
667
- if (!this.options.validatePristine && !next.meta.isTouched) return; // If validateOn is set to a variation of change, run the validation
737
+ const prevState = this.state;
738
+ this.state = next;
668
739
 
669
- if (this.options.validateOn === 'change' || this.options.validateOn.split('-')[0] === 'change') {
670
- try {
671
- this.validate();
672
- } catch (err) {
673
- console.error('An error occurred during validation', err);
674
- }
740
+ if (next.value !== prevState.value) {
741
+ this.validate('change', next.value);
675
742
  }
676
-
677
- this.state = next;
678
743
  }
679
744
  });
680
745
  this.state = this.store.state;
@@ -682,6 +747,27 @@
682
747
  }
683
748
 
684
749
  }
750
+ const validateCauseLevels = {
751
+ change: 0,
752
+ blur: 1,
753
+ submit: 2
754
+ };
755
+
756
+ function getValidationCauseLevel(cause) {
757
+ return !cause ? 3 : validateCauseLevels[cause];
758
+ }
759
+
760
+ function normalizeError(rawError) {
761
+ if (rawError) {
762
+ if (typeof rawError !== 'string') {
763
+ return 'Invalid Form Values';
764
+ }
765
+
766
+ return rawError;
767
+ }
768
+
769
+ return undefined;
770
+ }
685
771
 
686
772
  exports.FieldApi = FieldApi;
687
773
  exports.FormApi = FormApi;