@tanstack/form-core 0.40.3 → 0.41.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.
@@ -1,24 +1,13 @@
1
- import { Store } from "@tanstack/store";
1
+ import { batch, Store, Derived } from "@tanstack/store";
2
2
  import { getSyncValidatorArray, getAsyncValidatorArray, getBy, functionalUpdate, setBy, deleteBy, isNonEmptyArray } from "./utils.js";
3
3
  import { isStandardSchemaValidator, standardSchemaValidator } from "./standardSchemaValidator.js";
4
4
  function getDefaultFormState(defaultState) {
5
5
  return {
6
6
  values: defaultState.values ?? {},
7
- errors: defaultState.errors ?? [],
8
7
  errorMap: defaultState.errorMap ?? {},
9
- fieldMeta: defaultState.fieldMeta ?? {},
10
- canSubmit: defaultState.canSubmit ?? true,
11
- isFieldsValid: defaultState.isFieldsValid ?? false,
12
- isFieldsValidating: defaultState.isFieldsValidating ?? false,
13
- isFormValid: defaultState.isFormValid ?? false,
14
- isFormValidating: defaultState.isFormValidating ?? false,
8
+ fieldMetaBase: defaultState.fieldMetaBase ?? {},
15
9
  isSubmitted: defaultState.isSubmitted ?? false,
16
10
  isSubmitting: defaultState.isSubmitting ?? false,
17
- isTouched: defaultState.isTouched ?? false,
18
- isBlurred: defaultState.isBlurred ?? false,
19
- isPristine: defaultState.isPristine ?? true,
20
- isDirty: defaultState.isDirty ?? false,
21
- isValid: defaultState.isValid ?? false,
22
11
  isValidating: defaultState.isValidating ?? false,
23
12
  submissionAttempts: defaultState.submissionAttempts ?? 0,
24
13
  validationMetaMap: defaultState.validationMetaMap ?? {
@@ -43,18 +32,25 @@ class FormApi {
43
32
  this.fieldInfo = {};
44
33
  this.prevTransformArray = [];
45
34
  this.mount = () => {
35
+ const cleanupFieldMetaDerived = this.fieldMetaDerived.mount();
36
+ const cleanupStoreDerived = this.store.mount();
37
+ const cleanup = () => {
38
+ cleanupFieldMetaDerived();
39
+ cleanupStoreDerived();
40
+ };
46
41
  const { onMount } = this.options.validators || {};
47
- if (!onMount) return;
42
+ if (!onMount) return cleanup;
48
43
  this.validateSync("mount");
44
+ return cleanup;
49
45
  };
50
46
  this.update = (options) => {
51
47
  if (!options) return;
52
48
  const oldOptions = this.options;
53
49
  this.options = options;
54
- this.store.batch(() => {
50
+ batch(() => {
55
51
  const shouldUpdateValues = options.defaultValues && options.defaultValues !== oldOptions.defaultValues && !this.state.isTouched;
56
52
  const shouldUpdateState = options.defaultState !== oldOptions.defaultState && !this.state.isTouched;
57
- this.store.setState(
53
+ this.baseStore.setState(
58
54
  () => getDefaultFormState(
59
55
  Object.assign(
60
56
  {},
@@ -70,27 +66,27 @@ class FormApi {
70
66
  };
71
67
  this.reset = (values, opts2) => {
72
68
  const { fieldMeta: currentFieldMeta } = this.state;
73
- const fieldMeta = this.resetFieldMeta(currentFieldMeta);
69
+ const fieldMetaBase = this.resetFieldMeta(currentFieldMeta);
74
70
  if (values && !(opts2 == null ? void 0 : opts2.keepDefaultValues)) {
75
71
  this.options = {
76
72
  ...this.options,
77
73
  defaultValues: values
78
74
  };
79
75
  }
80
- this.store.setState(
76
+ this.baseStore.setState(
81
77
  () => {
82
78
  var _a2;
83
79
  return getDefaultFormState({
84
80
  ...this.options.defaultState,
85
81
  values: values ?? this.options.defaultValues ?? ((_a2 = this.options.defaultState) == null ? void 0 : _a2.values),
86
- fieldMeta
82
+ fieldMetaBase
87
83
  });
88
84
  }
89
85
  );
90
86
  };
91
87
  this.validateAllFields = async (cause) => {
92
88
  const fieldValidationPromises = [];
93
- this.store.batch(() => {
89
+ batch(() => {
94
90
  void Object.values(this.fieldInfo).forEach((field) => {
95
91
  if (!field.instance) return;
96
92
  const fieldInstance = field.instance;
@@ -101,9 +97,6 @@ class FormApi {
101
97
  if (!field.instance.state.meta.isTouched) {
102
98
  field.instance.setMeta((prev) => ({ ...prev, isTouched: true }));
103
99
  }
104
- if (!field.instance.state.meta.isBlurred) {
105
- field.instance.setMeta((prev) => ({ ...prev, isBlurred: true }));
106
- }
107
100
  });
108
101
  });
109
102
  const fieldErrorMapMap = await Promise.all(fieldValidationPromises);
@@ -120,7 +113,7 @@ class FormApi {
120
113
  (fieldKey) => fieldKeysToValidate.some((key) => fieldKey.startsWith(key))
121
114
  );
122
115
  const fieldValidationPromises = [];
123
- this.store.batch(() => {
116
+ batch(() => {
124
117
  fieldsToValidate.forEach((nestedField) => {
125
118
  fieldValidationPromises.push(
126
119
  Promise.resolve().then(() => this.validateField(nestedField, cause))
@@ -137,16 +130,13 @@ class FormApi {
137
130
  if (!fieldInstance.state.meta.isTouched) {
138
131
  fieldInstance.setMeta((prev) => ({ ...prev, isTouched: true }));
139
132
  }
140
- if (!fieldInstance.state.meta.isBlurred) {
141
- fieldInstance.setMeta((prev) => ({ ...prev, isBlurred: true }));
142
- }
143
133
  return fieldInstance.validate(cause);
144
134
  };
145
135
  this.validateSync = (cause) => {
146
136
  const validates = getSyncValidatorArray(cause, this.options);
147
137
  let hasErrored = false;
148
138
  const fieldsErrorMap = {};
149
- this.store.batch(() => {
139
+ batch(() => {
150
140
  for (const validateObj of validates) {
151
141
  if (!validateObj.validate) continue;
152
142
  const rawError = this.runValidator({
@@ -181,7 +171,7 @@ class FormApi {
181
171
  }
182
172
  }
183
173
  if (this.state.errorMap[errorMapKey] !== formError) {
184
- this.store.setState((prev) => ({
174
+ this.baseStore.setState((prev) => ({
185
175
  ...prev,
186
176
  errorMap: {
187
177
  ...prev.errorMap,
@@ -196,7 +186,7 @@ class FormApi {
196
186
  });
197
187
  const submitErrKey = getErrorMapKey("submit");
198
188
  if (this.state.errorMap[submitErrKey] && cause !== "submit" && !hasErrored) {
199
- this.store.setState((prev) => ({
189
+ this.baseStore.setState((prev) => ({
200
190
  ...prev,
201
191
  errorMap: {
202
192
  ...prev.errorMap,
@@ -209,7 +199,7 @@ class FormApi {
209
199
  this.validateAsync = async (cause) => {
210
200
  const validates = getAsyncValidatorArray(cause, this.options);
211
201
  if (!this.state.isFormValidating) {
212
- this.store.setState((prev) => ({ ...prev, isFormValidating: true }));
202
+ this.baseStore.setState((prev) => ({ ...prev, isFormValidating: true }));
213
203
  }
214
204
  const promises = [];
215
205
  let fieldErrors;
@@ -269,7 +259,7 @@ class FormApi {
269
259
  }
270
260
  }
271
261
  }
272
- this.store.setState((prev) => ({
262
+ this.baseStore.setState((prev) => ({
273
263
  ...prev,
274
264
  errorMap: {
275
265
  ...prev.errorMap,
@@ -300,7 +290,7 @@ class FormApi {
300
290
  }
301
291
  }
302
292
  }
303
- this.store.setState((prev) => ({
293
+ this.baseStore.setState((prev) => ({
304
294
  ...prev,
305
295
  isFormValidating: false
306
296
  }));
@@ -315,7 +305,7 @@ class FormApi {
315
305
  };
316
306
  this.handleSubmit = async () => {
317
307
  var _a2, _b, _c, _d;
318
- this.store.setState((old) => ({
308
+ this.baseStore.setState((old) => ({
319
309
  ...old,
320
310
  // Submission attempts mark the form as not submitted
321
311
  isSubmitted: false,
@@ -323,9 +313,9 @@ class FormApi {
323
313
  submissionAttempts: old.submissionAttempts + 1
324
314
  }));
325
315
  if (!this.state.canSubmit) return;
326
- this.store.setState((d) => ({ ...d, isSubmitting: true }));
316
+ this.baseStore.setState((d) => ({ ...d, isSubmitting: true }));
327
317
  const done = () => {
328
- this.store.setState((prev) => ({ ...prev, isSubmitting: false }));
318
+ this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false }));
329
319
  };
330
320
  await this.validateAllFields("submit");
331
321
  if (!this.state.isValid) {
@@ -336,7 +326,7 @@ class FormApi {
336
326
  });
337
327
  return;
338
328
  }
339
- this.store.batch(() => {
329
+ batch(() => {
340
330
  void Object.values(this.fieldInfo).forEach((field) => {
341
331
  var _a3, _b2, _c2;
342
332
  (_c2 = (_b2 = (_a3 = field.instance) == null ? void 0 : _a3.options.listeners) == null ? void 0 : _b2.onSubmit) == null ? void 0 : _c2.call(_b2, {
@@ -347,8 +337,8 @@ class FormApi {
347
337
  });
348
338
  try {
349
339
  await ((_d = (_c = this.options).onSubmit) == null ? void 0 : _d.call(_c, { value: this.state.values, formApi: this }));
350
- this.store.batch(() => {
351
- this.store.setState((prev) => ({ ...prev, isSubmitted: true }));
340
+ batch(() => {
341
+ this.baseStore.setState((prev) => ({ ...prev, isSubmitted: true }));
352
342
  done();
353
343
  });
354
344
  } catch (err) {
@@ -374,12 +364,15 @@ class FormApi {
374
364
  });
375
365
  };
376
366
  this.setFieldMeta = (field, updater) => {
377
- this.store.setState((prev) => {
367
+ this.baseStore.setState((prev) => {
378
368
  return {
379
369
  ...prev,
380
- fieldMeta: {
381
- ...prev.fieldMeta,
382
- [field]: functionalUpdate(updater, prev.fieldMeta[field])
370
+ fieldMetaBase: {
371
+ ...prev.fieldMetaBase,
372
+ [field]: functionalUpdate(
373
+ updater,
374
+ prev.fieldMetaBase[field]
375
+ )
383
376
  }
384
377
  };
385
378
  });
@@ -404,12 +397,11 @@ class FormApi {
404
397
  };
405
398
  this.setFieldValue = (field, updater, opts2) => {
406
399
  const dontUpdateMeta = (opts2 == null ? void 0 : opts2.dontUpdateMeta) ?? false;
407
- this.store.batch(() => {
400
+ batch(() => {
408
401
  if (!dontUpdateMeta) {
409
402
  this.setFieldMeta(field, (prev) => ({
410
403
  ...prev,
411
404
  isTouched: true,
412
- isBlurred: true,
413
405
  isDirty: true,
414
406
  errorMap: {
415
407
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -418,7 +410,7 @@ class FormApi {
418
410
  }
419
411
  }));
420
412
  }
421
- this.store.setState((prev) => {
413
+ this.baseStore.setState((prev) => {
422
414
  return {
423
415
  ...prev,
424
416
  values: setBy(prev.values, field, updater)
@@ -427,10 +419,10 @@ class FormApi {
427
419
  });
428
420
  };
429
421
  this.deleteField = (field) => {
430
- this.store.setState((prev) => {
422
+ this.baseStore.setState((prev) => {
431
423
  const newState = { ...prev };
432
424
  newState.values = deleteBy(newState.values, field);
433
- delete newState.fieldMeta[field];
425
+ delete newState.fieldMetaBase[field];
434
426
  return newState;
435
427
  });
436
428
  delete this.fieldInfo[field];
@@ -519,80 +511,131 @@ class FormApi {
519
511
  this.validateField(`${field}[${index1}]`, "change");
520
512
  this.validateField(`${field}[${index2}]`, "change");
521
513
  };
522
- this.store = new Store(
514
+ this.baseStore = new Store(
523
515
  getDefaultFormState({
524
516
  ...opts == null ? void 0 : opts.defaultState,
525
517
  values: (opts == null ? void 0 : opts.defaultValues) ?? ((_a = opts == null ? void 0 : opts.defaultState) == null ? void 0 : _a.values),
526
518
  isFormValid: true
527
- }),
528
- {
529
- onUpdate: () => {
530
- var _a2, _b, _c, _d;
531
- let { state } = this.store;
532
- const fieldMetaValues = Object.values(state.fieldMeta);
533
- const isFieldsValidating = fieldMetaValues.some(
534
- (field) => field == null ? void 0 : field.isValidating
535
- );
536
- const isFieldsValid = !fieldMetaValues.some(
537
- (field) => (field == null ? void 0 : field.errorMap) && isNonEmptyArray(Object.values(field.errorMap).filter(Boolean))
538
- );
539
- const isTouched = fieldMetaValues.some((field) => field == null ? void 0 : field.isTouched);
540
- const isBlurred = fieldMetaValues.some((field) => field == null ? void 0 : field.isBlurred);
541
- if (isTouched && ((_a2 = state == null ? void 0 : state.errorMap) == null ? void 0 : _a2.onMount)) {
542
- state.errorMap.onMount = void 0;
519
+ })
520
+ );
521
+ this.fieldMetaDerived = new Derived({
522
+ deps: [this.baseStore],
523
+ fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => {
524
+ var _a2;
525
+ const prevVal = _prevVal;
526
+ const prevBaseStore = prevDepVals == null ? void 0 : prevDepVals[0];
527
+ const currBaseStore = currDepVals[0];
528
+ const fieldMeta = {};
529
+ for (const fieldName of Object.keys(
530
+ currBaseStore.fieldMetaBase
531
+ )) {
532
+ const currBaseVal = currBaseStore.fieldMetaBase[fieldName];
533
+ const prevBaseVal = prevBaseStore == null ? void 0 : prevBaseStore.fieldMetaBase[fieldName];
534
+ let fieldErrors = (_a2 = prevVal == null ? void 0 : prevVal[fieldName]) == null ? void 0 : _a2.errors;
535
+ if (!prevBaseVal || currBaseVal.errorMap !== prevBaseVal.errorMap) {
536
+ fieldErrors = Object.values(currBaseVal.errorMap ?? {}).filter(
537
+ (val) => val !== void 0
538
+ );
543
539
  }
544
- const isDirty = fieldMetaValues.some((field) => field == null ? void 0 : field.isDirty);
545
- const isPristine = !isDirty;
546
- const hasOnMountError = Boolean(
547
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
548
- ((_b = state.errorMap) == null ? void 0 : _b.onMount) || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
549
- fieldMetaValues.some((f) => {
550
- var _a3;
551
- return (_a3 = f == null ? void 0 : f.errorMap) == null ? void 0 : _a3.onMount;
552
- })
553
- );
554
- const isValidating = isFieldsValidating || state.isFormValidating;
555
- state.errors = Object.values(state.errorMap).reduce((prev, curr) => {
556
- if (curr === void 0) return prev;
557
- if (typeof curr === "string") {
558
- prev.push(curr);
559
- return prev;
560
- } else if (curr && isFormValidationError(curr)) {
561
- prev.push(curr.form);
562
- return prev;
563
- }
564
- return prev;
565
- }, []);
566
- const isFormValid = state.errors.length === 0;
567
- const isValid = isFieldsValid && isFormValid;
568
- const canSubmit = state.submissionAttempts === 0 && !isTouched && !hasOnMountError || !isValidating && !state.isSubmitting && isValid;
569
- state = {
570
- ...state,
571
- isFieldsValidating,
572
- isFieldsValid,
573
- isFormValid,
574
- isValid,
575
- canSubmit,
576
- isTouched,
577
- isBlurred,
578
- isPristine,
579
- isDirty
540
+ const isFieldPristine = !currBaseVal.isDirty;
541
+ fieldMeta[fieldName] = {
542
+ ...currBaseVal,
543
+ errors: fieldErrors,
544
+ isPristine: isFieldPristine
580
545
  };
581
- this.state = state;
582
- this.store.state = this.state;
583
- const transformArray = ((_c = this.options.transform) == null ? void 0 : _c.deps) ?? [];
584
- const shouldTransform = transformArray.length !== this.prevTransformArray.length || transformArray.some((val, i) => val !== this.prevTransformArray[i]);
585
- if (shouldTransform) {
586
- (_d = this.options.transform) == null ? void 0 : _d.fn(this);
587
- this.store.state = this.state;
588
- this.prevTransformArray = transformArray;
589
- }
590
546
  }
547
+ return fieldMeta;
591
548
  }
592
- );
593
- this.state = this.store.state;
549
+ });
550
+ this.store = new Derived({
551
+ deps: [this.baseStore, this.fieldMetaDerived],
552
+ fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => {
553
+ var _a2, _b, _c, _d;
554
+ const prevVal = _prevVal;
555
+ const prevBaseStore = prevDepVals == null ? void 0 : prevDepVals[0];
556
+ const currBaseStore = currDepVals[0];
557
+ const fieldMetaValues = Object.values(currBaseStore.fieldMetaBase);
558
+ const isFieldsValidating = fieldMetaValues.some(
559
+ (field) => field == null ? void 0 : field.isValidating
560
+ );
561
+ const isFieldsValid = !fieldMetaValues.some(
562
+ (field) => (field == null ? void 0 : field.errorMap) && isNonEmptyArray(Object.values(field.errorMap).filter(Boolean))
563
+ );
564
+ const isTouched = fieldMetaValues.some((field) => field == null ? void 0 : field.isTouched);
565
+ const isBlurred = fieldMetaValues.some((field) => field == null ? void 0 : field.isBlurred);
566
+ const shouldInvalidateOnMount = (
567
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
568
+ isTouched && ((_a2 = currBaseStore == null ? void 0 : currBaseStore.errorMap) == null ? void 0 : _a2.onMount)
569
+ );
570
+ const isDirty = fieldMetaValues.some((field) => field == null ? void 0 : field.isDirty);
571
+ const isPristine = !isDirty;
572
+ const hasOnMountError = Boolean(
573
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
574
+ ((_b = currBaseStore.errorMap) == null ? void 0 : _b.onMount) || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
575
+ fieldMetaValues.some((f) => {
576
+ var _a3;
577
+ return (_a3 = f == null ? void 0 : f.errorMap) == null ? void 0 : _a3.onMount;
578
+ })
579
+ );
580
+ const isValidating = !!isFieldsValidating;
581
+ let errors = (prevVal == null ? void 0 : prevVal.errors) ?? [];
582
+ if (!prevBaseStore || currBaseStore.errorMap !== prevBaseStore.errorMap) {
583
+ errors = Object.values(currBaseStore.errorMap).reduce(
584
+ (prev, curr) => {
585
+ if (curr === void 0) return prev;
586
+ if (typeof curr === "string") {
587
+ prev.push(curr);
588
+ return prev;
589
+ } else if (curr && isFormValidationError(curr)) {
590
+ prev.push(curr.form);
591
+ return prev;
592
+ }
593
+ return prev;
594
+ },
595
+ []
596
+ );
597
+ }
598
+ const isFormValid = errors.length === 0;
599
+ const isValid = isFieldsValid && isFormValid;
600
+ const canSubmit = currBaseStore.submissionAttempts === 0 && !isTouched && !hasOnMountError || !isValidating && !currBaseStore.isSubmitting && isValid;
601
+ let errorMap = currBaseStore.errorMap;
602
+ if (shouldInvalidateOnMount) {
603
+ errors = errors.filter(
604
+ (err) => err !== currBaseStore.errorMap.onMount
605
+ );
606
+ errorMap = Object.assign(errorMap, { onMount: void 0 });
607
+ }
608
+ let state = {
609
+ ...currBaseStore,
610
+ errorMap,
611
+ fieldMeta: this.fieldMetaDerived.state,
612
+ errors,
613
+ isFieldsValidating,
614
+ isFieldsValid,
615
+ isFormValid,
616
+ isValid,
617
+ canSubmit,
618
+ isTouched,
619
+ isBlurred,
620
+ isPristine,
621
+ isDirty
622
+ };
623
+ const transformArray = ((_c = this.options.transform) == null ? void 0 : _c.deps) ?? [];
624
+ const shouldTransform = transformArray.length !== this.prevTransformArray.length || transformArray.some((val, i) => val !== this.prevTransformArray[i]);
625
+ if (shouldTransform) {
626
+ const newObj = Object.assign({}, this, { state });
627
+ (_d = this.options.transform) == null ? void 0 : _d.fn(newObj);
628
+ state = newObj.state;
629
+ this.prevTransformArray = transformArray;
630
+ }
631
+ return state;
632
+ }
633
+ });
594
634
  this.update(opts || {});
595
635
  }
636
+ get state() {
637
+ return this.store.state;
638
+ }
596
639
  /**
597
640
  * @private
598
641
  */
@@ -613,7 +656,7 @@ class FormApi {
613
656
  * Updates the form's errorMap
614
657
  */
615
658
  setErrorMap(errorMap) {
616
- this.store.setState((prev) => ({
659
+ this.baseStore.setState((prev) => ({
617
660
  ...prev,
618
661
  errorMap: {
619
662
  ...prev.errorMap,