mobx-formkit 1.2.0 → 1.3.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 (49) hide show
  1. package/AGENTS.md +33 -17
  2. package/lib/Base.d.ts +37 -0
  3. package/lib/Base.d.ts.map +1 -1
  4. package/lib/Base.js +60 -0
  5. package/lib/Base.js.map +1 -1
  6. package/lib/Field.d.ts +6 -0
  7. package/lib/Field.d.ts.map +1 -1
  8. package/lib/Field.js +17 -0
  9. package/lib/Field.js.map +1 -1
  10. package/lib/Form.d.ts.map +1 -1
  11. package/lib/Form.js +2 -0
  12. package/lib/Form.js.map +1 -1
  13. package/lib/Options.d.ts.map +1 -1
  14. package/lib/Options.js +2 -0
  15. package/lib/Options.js.map +1 -1
  16. package/lib/esm/Base.js +61 -1
  17. package/lib/esm/Base.js.map +1 -1
  18. package/lib/esm/Field.js +18 -1
  19. package/lib/esm/Field.js.map +1 -1
  20. package/lib/esm/Form.js +2 -0
  21. package/lib/esm/Form.js.map +1 -1
  22. package/lib/esm/Options.js +2 -0
  23. package/lib/esm/Options.js.map +1 -1
  24. package/lib/esm/models/OptionsModel.js +2 -0
  25. package/lib/esm/models/OptionsModel.js.map +1 -1
  26. package/lib/esm/utils.js +24 -2
  27. package/lib/esm/utils.js.map +1 -1
  28. package/lib/models/BaseInterface.d.ts +3 -0
  29. package/lib/models/BaseInterface.d.ts.map +1 -1
  30. package/lib/models/OptionsModel.d.ts +4 -0
  31. package/lib/models/OptionsModel.d.ts.map +1 -1
  32. package/lib/models/OptionsModel.js +2 -0
  33. package/lib/models/OptionsModel.js.map +1 -1
  34. package/lib/utils.d.ts +9 -1
  35. package/lib/utils.d.ts.map +1 -1
  36. package/lib/utils.js +23 -0
  37. package/lib/utils.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/Base.ts +70 -0
  40. package/src/Field.ts +19 -0
  41. package/src/Form.ts +3 -0
  42. package/src/Options.ts +2 -0
  43. package/src/models/BaseInterface.ts +3 -0
  44. package/src/models/OptionsModel.ts +4 -0
  45. package/src/utils.ts +21 -1
  46. package/umd/MobxFormkit.umd.js +105 -0
  47. package/umd/MobxFormkit.umd.js.map +1 -1
  48. package/umd/MobxFormkit.umd.min.js +1 -1
  49. package/umd/MobxFormkit.umd.min.js.map +1 -1
package/src/Base.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  isArrayOfObjects,
31
31
  getObservableMapValues,
32
32
  maxKey,
33
+ cloneValues,
33
34
  $try,
34
35
  isEvent,
35
36
  hasIntKeys,
@@ -80,6 +81,12 @@ export default abstract class Base<
80
81
  $hooks: any = {};
81
82
  $handlers: any = {};
82
83
 
84
+ /**
85
+ Values captured by the last `commit()`.
86
+ Not observable: it is a snapshot, not a UI state.
87
+ */
88
+ $committed: any = undefined;
89
+
83
90
  constructor() {
84
91
  makeObservable(this, {
85
92
  $submitted: observable,
@@ -111,6 +118,8 @@ export default abstract class Base<
111
118
  add: action,
112
119
  del: action,
113
120
  move: action,
121
+ commit: action,
122
+ restore: action,
114
123
  });
115
124
 
116
125
  // Any reorder of the fields (also when `fields.move()` is called
@@ -1125,6 +1134,67 @@ export default abstract class Base<
1125
1134
  });
1126
1135
  }
1127
1136
 
1137
+ /**
1138
+ Commit & Restore
1139
+ */
1140
+
1141
+ /**
1142
+ Captures the current values as the new restore reference.
1143
+
1144
+ It does not validate, it does not send anything and it does not change
1145
+ the current values.
1146
+ */
1147
+ commit(): void {
1148
+ this.$committed = cloneValues(this.values());
1149
+ }
1150
+
1151
+ /**
1152
+ Restores the values captured by the last `commit()`.
1153
+
1154
+ Before the first commit the reference is the reset target of the fields
1155
+ (the `default` when it differs from the `initial`, the `initial`
1156
+ otherwise), so `restore()` is useful even without a commit.
1157
+ */
1158
+ restore(): void | Promise<any> {
1159
+ if (this.$committed === void 0) return;
1160
+ this.update(cloneValues(this.$committed));
1161
+ if (this.state.options.get(OptionsEnum.validateOnRestore, this)) {
1162
+ return this.validate({
1163
+ showErrors: this.state.options.get(
1164
+ OptionsEnum.showErrorsOnRestore,
1165
+ this,
1166
+ ),
1167
+ });
1168
+ }
1169
+ }
1170
+
1171
+ /**
1172
+ The values captured by the last `commit()` (read only)
1173
+ */
1174
+ committed(): any {
1175
+ return this.$committed === void 0 ? void 0 : cloneValues(this.$committed);
1176
+ }
1177
+
1178
+ /**
1179
+ Initializes the restore reference with the reset target of the fields
1180
+ (the `default` when it differs from the `initial`, the `initial`
1181
+ otherwise)
1182
+ */
1183
+ initCommittedSnapshot(): void {
1184
+ this.$committed = cloneValues(this.resetTargetValues());
1185
+ }
1186
+
1187
+ /**
1188
+ The values the fields would have after a `reset()`
1189
+ */
1190
+ resetTargetValues(): any {
1191
+ const $values: any = {};
1192
+ getObservableMapValues(this.fields).forEach((field: any) => {
1193
+ $values[field.key] = field.resetTargetValue();
1194
+ });
1195
+ return $values;
1196
+ }
1197
+
1128
1198
  /**
1129
1199
  Del Field
1130
1200
  */
package/src/Field.ts CHANGED
@@ -301,6 +301,9 @@ export default class Field<T = any> extends Base<Record<string, any>> implements
301
301
  // this.checkValidationPlugins();
302
302
  this.initNestedFields(data, update);
303
303
 
304
+ // the restore reference starts from the reset target of the fields
305
+ this.initCommittedSnapshot();
306
+
304
307
  this.incremental = this.hasIncrementalKeys;
305
308
 
306
309
  this.debouncedValidation = debounce(
@@ -508,6 +511,22 @@ export default class Field<T = any> extends Base<Record<string, any>> implements
508
511
  return !isEqual(this.initial, value) || this.deleted;
509
512
  }
510
513
 
514
+ /**
515
+ The value the field would have after a `reset()`: the `default` when it
516
+ differs from the `initial`, the `initial` otherwise. For a field with
517
+ nested fields it is the tree of the nested values.
518
+ */
519
+ resetTargetValue(): any {
520
+ if (this.hasNestedFields) {
521
+ const $nested: any = {};
522
+ getObservableMapValues(this.fields).forEach((field: any) => {
523
+ $nested[field.key] = field.resetTargetValue();
524
+ });
525
+ return this.hasIncrementalKeys ? Object.values($nested) : $nested;
526
+ }
527
+ return this.$default !== this.$initial ? this.$default : this.$initial;
528
+ }
529
+
511
530
  get isPristine(): boolean {
512
531
  const value = this.changed ? this.value : this.initial;
513
532
  return isEqual(this.initial, value) && !this.deleted;
package/src/Form.ts CHANGED
@@ -108,6 +108,9 @@ export default class Form<F extends Record<string, any> = Record<string, any>> e
108
108
 
109
109
  this.initFields(initial.setup);
110
110
 
111
+ // the restore reference starts from the reset target of the fields
112
+ this.initCommittedSnapshot();
113
+
111
114
  this.debouncedValidation = debounce(
112
115
  this.validate,
113
116
  this.state.options.get(OptionsEnum.validationDebounceWait),
package/src/Options.ts CHANGED
@@ -26,6 +26,7 @@ export default class Options implements OptionsInterface {
26
26
  showErrorsOnChange: true,
27
27
  showErrorsOnClear: false,
28
28
  showErrorsOnReset: true,
29
+ showErrorsOnRestore: false,
29
30
  validateOnInit: true,
30
31
  validateOnSubmit: true,
31
32
  validateOnBlur: true,
@@ -34,6 +35,7 @@ export default class Options implements OptionsInterface {
34
35
  validateOnChangeAfterSubmit: false,
35
36
  validateOnClear: false,
36
37
  validateOnReset: false,
38
+ validateOnRestore: false,
37
39
  validateDisabledFields: false,
38
40
  validateDeletedFields: false,
39
41
  validatePristineFields: true,
@@ -40,6 +40,9 @@ export interface BaseInterface<F extends Record<string, any> = Record<string, an
40
40
  onAdd(args: any): any;
41
41
  onDel(args: any): any;
42
42
  firstError(): string | null;
43
+ commit(): void;
44
+ restore(): void | Promise<any>;
45
+ committed(): any;
43
46
  }
44
47
 
45
48
  export default BaseInterface;
@@ -10,6 +10,7 @@ export enum OptionsEnum {
10
10
  showErrorsOnChange = 'showErrorsOnChange',
11
11
  showErrorsOnClear = 'showErrorsOnClear',
12
12
  showErrorsOnReset = 'showErrorsOnReset',
13
+ showErrorsOnRestore = 'showErrorsOnRestore',
13
14
  validateOnInit = 'validateOnInit',
14
15
  validateOnSubmit = 'validateOnSubmit',
15
16
  validateOnBlur = 'validateOnBlur',
@@ -22,6 +23,7 @@ export enum OptionsEnum {
22
23
  validateTrimmedValue = 'validateTrimmedValue',
23
24
  validateOnClear = 'validateOnClear',
24
25
  validateOnReset = 'validateOnReset',
26
+ validateOnRestore = 'validateOnRestore',
25
27
  strictSet = 'strictSet',
26
28
  strictUpdate = 'strictUpdate',
27
29
  strictDelete = 'strictDelete',
@@ -58,6 +60,7 @@ export interface OptionsModel {
58
60
  [OptionsEnum.showErrorsOnChange]?: boolean;
59
61
  [OptionsEnum.showErrorsOnClear]?: boolean;
60
62
  [OptionsEnum.showErrorsOnReset]?: boolean;
63
+ [OptionsEnum.showErrorsOnRestore]?: boolean;
61
64
  [OptionsEnum.validateOnInit]?: boolean;
62
65
  [OptionsEnum.validateOnSubmit]?: boolean;
63
66
  [OptionsEnum.validateOnBlur]?: boolean;
@@ -66,6 +69,7 @@ export interface OptionsModel {
66
69
  [OptionsEnum.validateOnChangeAfterSubmit]?: boolean;
67
70
  [OptionsEnum.validateOnClear]?: boolean;
68
71
  [OptionsEnum.validateOnReset]?: boolean;
72
+ [OptionsEnum.validateOnRestore]?: boolean;
69
73
  [OptionsEnum.validateDisabledFields]?: boolean;
70
74
  [OptionsEnum.validateDeletedFields]?: boolean;
71
75
  [OptionsEnum.validatePristineFields]?: boolean;
package/src/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { has, isEmpty, isPlainObject } from "lodash";
1
+ import { each, has, isEmpty, isPlainObject } from "lodash";
2
2
  import { values as mobxValues, keys as mobxKeys } from "mobx";
3
3
  import { FieldInterface } from "./models/FieldInterface";
4
4
  import { AllowedFieldPropsTypes, FieldPropsEnum, FieldPropsOccurrence } from "./models/FieldProps";
@@ -165,6 +165,25 @@ const maxKey = (fields: any): number => {
165
165
  return maxVal === void 0 ? 0 : maxVal + 1;
166
166
  };
167
167
 
168
+ /**
169
+ Clones a value tree keeping it independent from the original one.
170
+
171
+ Plain objects, arrays and dates are cloned, any other object (File, Blob,
172
+ class instances, ...) is kept by reference because it cannot be cloned
173
+ safely.
174
+ */
175
+ const cloneValues = (value: any): any => {
176
+ if (value === null || typeof value !== "object") return value;
177
+ if (Array.isArray(value)) return value.map((item: any) => cloneValues(item));
178
+ if (value instanceof Date) return new Date(value.getTime());
179
+ if (!isPlainObject(value)) return value;
180
+ const result: any = {};
181
+ each(Object.keys(value), (key: string) => {
182
+ result[key] = cloneValues(value[key]);
183
+ });
184
+ return result;
185
+ };
186
+
168
187
  let _idCounter = 0;
169
188
  const _localUniqueId = (prefix: string): string => `${prefix}${++_idCounter}`;
170
189
 
@@ -207,6 +226,7 @@ export {
207
226
  parseIntKeys,
208
227
  hasIntKeys,
209
228
  maxKey,
229
+ cloneValues,
210
230
  uniqueId,
211
231
  isEvent,
212
232
  hasFiles,
@@ -619,6 +619,28 @@
619
619
  const maxVal = keys.length ? Math.max(...keys) : undefined;
620
620
  return maxVal === void 0 ? 0 : maxVal + 1;
621
621
  };
622
+ /**
623
+ Clones a value tree keeping it independent from the original one.
624
+
625
+ Plain objects, arrays and dates are cloned, any other object (File, Blob,
626
+ class instances, ...) is kept by reference because it cannot be cloned
627
+ safely.
628
+ */
629
+ const cloneValues = (value) => {
630
+ if (value === null || typeof value !== "object")
631
+ return value;
632
+ if (Array.isArray(value))
633
+ return value.map((item) => cloneValues(item));
634
+ if (value instanceof Date)
635
+ return new Date(value.getTime());
636
+ if (!lodash.isPlainObject(value))
637
+ return value;
638
+ const result = {};
639
+ lodash.each(Object.keys(value), (key) => {
640
+ result[key] = cloneValues(value[key]);
641
+ });
642
+ return result;
643
+ };
622
644
  let _idCounter = 0;
623
645
  const _localUniqueId = (prefix) => `${prefix}${++_idCounter}`;
624
646
  const uniqueId = (field) => _localUniqueId([field.path.replace(/\./g, "-"), "--"].join(""));
@@ -879,6 +901,7 @@
879
901
  OptionsEnum["showErrorsOnChange"] = "showErrorsOnChange";
880
902
  OptionsEnum["showErrorsOnClear"] = "showErrorsOnClear";
881
903
  OptionsEnum["showErrorsOnReset"] = "showErrorsOnReset";
904
+ OptionsEnum["showErrorsOnRestore"] = "showErrorsOnRestore";
882
905
  OptionsEnum["validateOnInit"] = "validateOnInit";
883
906
  OptionsEnum["validateOnSubmit"] = "validateOnSubmit";
884
907
  OptionsEnum["validateOnBlur"] = "validateOnBlur";
@@ -891,6 +914,7 @@
891
914
  OptionsEnum["validateTrimmedValue"] = "validateTrimmedValue";
892
915
  OptionsEnum["validateOnClear"] = "validateOnClear";
893
916
  OptionsEnum["validateOnReset"] = "validateOnReset";
917
+ OptionsEnum["validateOnRestore"] = "validateOnRestore";
894
918
  OptionsEnum["strictSet"] = "strictSet";
895
919
  OptionsEnum["strictUpdate"] = "strictUpdate";
896
920
  OptionsEnum["strictDelete"] = "strictDelete";
@@ -936,6 +960,11 @@
936
960
  $changed = 0;
937
961
  $hooks = {};
938
962
  $handlers = {};
963
+ /**
964
+ Values captured by the last `commit()`.
965
+ Not observable: it is a snapshot, not a UI state.
966
+ */
967
+ $committed = undefined;
939
968
  constructor() {
940
969
  makeObservable(this, {
941
970
  $submitted: mobx.observable,
@@ -967,6 +996,8 @@
967
996
  add: mobx.action,
968
997
  del: mobx.action,
969
998
  move: mobx.action,
999
+ commit: mobx.action,
1000
+ restore: mobx.action,
970
1001
  });
971
1002
  // Any reorder of the fields (also when `fields.move()` is called
972
1003
  // directly) flags the field and the form as changed. (#657)
@@ -1667,6 +1698,59 @@
1667
1698
  }
1668
1699
  });
1669
1700
  }
1701
+ /**
1702
+ Commit & Restore
1703
+ */
1704
+ /**
1705
+ Captures the current values as the new restore reference.
1706
+
1707
+ It does not validate, it does not send anything and it does not change
1708
+ the current values.
1709
+ */
1710
+ commit() {
1711
+ this.$committed = cloneValues(this.values());
1712
+ }
1713
+ /**
1714
+ Restores the values captured by the last `commit()`.
1715
+
1716
+ Before the first commit the reference is the reset target of the fields
1717
+ (the `default` when it differs from the `initial`, the `initial`
1718
+ otherwise), so `restore()` is useful even without a commit.
1719
+ */
1720
+ restore() {
1721
+ if (this.$committed === void 0)
1722
+ return;
1723
+ this.update(cloneValues(this.$committed));
1724
+ if (this.state.options.get(OptionsEnum.validateOnRestore, this)) {
1725
+ return this.validate({
1726
+ showErrors: this.state.options.get(OptionsEnum.showErrorsOnRestore, this),
1727
+ });
1728
+ }
1729
+ }
1730
+ /**
1731
+ The values captured by the last `commit()` (read only)
1732
+ */
1733
+ committed() {
1734
+ return this.$committed === void 0 ? void 0 : cloneValues(this.$committed);
1735
+ }
1736
+ /**
1737
+ Initializes the restore reference with the reset target of the fields
1738
+ (the `default` when it differs from the `initial`, the `initial`
1739
+ otherwise)
1740
+ */
1741
+ initCommittedSnapshot() {
1742
+ this.$committed = cloneValues(this.resetTargetValues());
1743
+ }
1744
+ /**
1745
+ The values the fields would have after a `reset()`
1746
+ */
1747
+ resetTargetValues() {
1748
+ const $values = {};
1749
+ getObservableMapValues(this.fields).forEach((field) => {
1750
+ $values[field.key] = field.resetTargetValue();
1751
+ });
1752
+ return $values;
1753
+ }
1670
1754
  /**
1671
1755
  Del Field
1672
1756
  */
@@ -2068,6 +2152,7 @@
2068
2152
  showErrorsOnChange: true,
2069
2153
  showErrorsOnClear: false,
2070
2154
  showErrorsOnReset: true,
2155
+ showErrorsOnRestore: false,
2071
2156
  validateOnInit: true,
2072
2157
  validateOnSubmit: true,
2073
2158
  validateOnBlur: true,
@@ -2076,6 +2161,7 @@
2076
2161
  validateOnChangeAfterSubmit: false,
2077
2162
  validateOnClear: false,
2078
2163
  validateOnReset: false,
2164
+ validateOnRestore: false,
2079
2165
  validateDisabledFields: false,
2080
2166
  validateDeletedFields: false,
2081
2167
  validatePristineFields: true,
@@ -2529,6 +2615,8 @@
2529
2615
  this.setupField(key, path, struct, data, props, update);
2530
2616
  // this.checkValidationPlugins();
2531
2617
  this.initNestedFields(data, update);
2618
+ // the restore reference starts from the reset target of the fields
2619
+ this.initCommittedSnapshot();
2532
2620
  this.incremental = this.hasIncrementalKeys;
2533
2621
  this.debouncedValidation = lodash.debounce(this.validate, this.state.options.get(OptionsEnum.validationDebounceWait, this), this.state.options.get(OptionsEnum.validationDebounceOptions, this));
2534
2622
  this.observeValidationOnBlur();
@@ -2677,6 +2765,21 @@
2677
2765
  // a soft deleted field differs from its initial state
2678
2766
  return !lodash.isEqual(this.initial, value) || this.deleted;
2679
2767
  }
2768
+ /**
2769
+ The value the field would have after a `reset()`: the `default` when it
2770
+ differs from the `initial`, the `initial` otherwise. For a field with
2771
+ nested fields it is the tree of the nested values.
2772
+ */
2773
+ resetTargetValue() {
2774
+ if (this.hasNestedFields) {
2775
+ const $nested = {};
2776
+ getObservableMapValues(this.fields).forEach((field) => {
2777
+ $nested[field.key] = field.resetTargetValue();
2778
+ });
2779
+ return this.hasIncrementalKeys ? Object.values($nested) : $nested;
2780
+ }
2781
+ return this.$default !== this.$initial ? this.$default : this.$initial;
2782
+ }
2680
2783
  get isPristine() {
2681
2784
  const value = this.changed ? this.value : this.initial;
2682
2785
  return lodash.isEqual(this.initial, value) && !this.deleted;
@@ -3139,6 +3242,8 @@
3139
3242
  plugins: initial.plugins,
3140
3243
  });
3141
3244
  this.initFields(initial.setup);
3245
+ // the restore reference starts from the reset target of the fields
3246
+ this.initCommittedSnapshot();
3142
3247
  this.debouncedValidation = lodash.debounce(this.validate, this.state.options.get(OptionsEnum.validationDebounceWait), this.state.options.get(OptionsEnum.validationDebounceOptions));
3143
3248
  // execute validation on form initialization
3144
3249
  this.state.options.get(OptionsEnum.validateOnInit) &&