@strictly/react-form 0.0.25 → 0.0.26

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.
@@ -14,6 +14,9 @@ type FieldOverride<V = any> = Maybe<V>;
14
14
  type FlattenedFieldOverrides<ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>> = {
15
15
  -readonly [K in keyof ValuePathsToAdapters]?: FieldOverride<ToOfFieldAdapter<ValuePathsToAdapters[K]>>;
16
16
  };
17
+ type FlattenedErrorOverrides<ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>> = {
18
+ -readonly [K in keyof ValuePathsToAdapters]?: ErrorOfFieldAdapter<ValuePathsToAdapters[K]>;
19
+ };
17
20
  export declare enum Validation {
18
21
  None = 0,
19
22
  Changed = 1,
@@ -35,6 +38,7 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
35
38
  protected readonly mode: FormMode;
36
39
  accessor value: MobxValueOfType<T>;
37
40
  accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>;
41
+ accessor errorOverrides: FlattenedErrorOverrides<ValuePathsToAdapters>;
38
42
  accessor validation: FlattenedValidation<ValuePathsToAdapters>;
39
43
  private readonly flattenedTypeDefs;
40
44
  private readonly originalValues;
@@ -50,18 +54,26 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
50
54
  get accessors(): Readonly<Record<string, Accessor>>;
51
55
  private maybeGetAdapterForValuePath;
52
56
  private getAdapterForValuePath;
57
+ get dirty(): boolean;
53
58
  typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K];
54
59
  setFieldValue<K extends keyof ValuePathsToAdapters>(valuePath: K, value: ToOfFieldAdapter<ValuePathsToAdapters[K]>, validation?: Validation): boolean;
55
60
  addListItem<K extends keyof FlattenedListTypesOfType<T>>(valuePath: K, elementValue?: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>>, index?: number): void;
56
61
  removeListItem<K extends keyof FlattenedListTypesOfType<T>>(...elementValuePaths: readonly `${K}.${number}`[]): void;
57
62
  private internalSetFieldValue;
63
+ /**
64
+ * Forces an error onto a field. Error will be removed if the field value changes
65
+ * @param valuePath the field to display an error for
66
+ * @param error the error to display
67
+ */
68
+ overrideFieldError<K extends keyof ValuePathsToAdapters>(valuePath: K, error?: ErrorOfFieldAdapter<ValuePathsToAdapters[K]>): void;
58
69
  clearFieldError<K extends keyof ValuePathsToAdapters>(valuePath: K): void;
59
70
  clearFieldValue<K extends StringKeyOf<ValuePathsToAdapters>>(valuePath: K): void;
60
71
  clearAll(value: ValueOfType<T>): void;
61
72
  isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
62
73
  getValidation<K extends keyof ValuePathsToAdapters>(valuePath: K): Validation;
63
- isDirty<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
74
+ isFieldDirty<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
64
75
  validateField<K extends keyof ValuePathsToAdapters>(valuePath: K, validation?: Validation): boolean;
65
76
  validateAll(validation?: Validation): boolean;
77
+ validateSubmit(): boolean;
66
78
  }
67
79
  export {};
@@ -44,7 +44,7 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
44
44
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
45
45
  };
46
46
  import { assertExists, assertExistsAndReturn, assertState, checkValidNumber, map, toArray, UnreachableError, } from '@strictly/base';
47
- import { flattenAccessorsOfType, flattenTypesOfType, flattenValuesOfType, flattenValueTo, jsonPathPop, mobxCopy, valuePathToTypePath, } from '@strictly/define';
47
+ import { equals, flattenAccessorsOfType, flattenTypesOfType, flattenValuesOfType, flattenValueTo, jsonPathPop, mobxCopy, valuePathToTypePath, } from '@strictly/define';
48
48
  import { computed, observable, runInAction, } from 'mobx';
49
49
  import { UnreliableFieldConversionType, } from 'types/field_converters';
50
50
  export var Validation;
@@ -54,8 +54,8 @@ export var Validation;
54
54
  Validation[Validation["Always"] = 2] = "Always";
55
55
  })(Validation || (Validation = {}));
56
56
  let FormModel = (() => {
57
- var _a, _FormModel_value_accessor_storage, _FormModel_fieldOverrides_accessor_storage, _FormModel_validation_accessor_storage;
58
- var _b, _c, _d;
57
+ var _a, _FormModel_value_accessor_storage, _FormModel_fieldOverrides_accessor_storage, _FormModel_errorOverrides_accessor_storage, _FormModel_validation_accessor_storage;
58
+ var _b, _c, _d, _e;
59
59
  let _instanceExtraInitializers = [];
60
60
  let _value_decorators;
61
61
  let _value_initializers = [];
@@ -63,17 +63,23 @@ let FormModel = (() => {
63
63
  let _fieldOverrides_decorators;
64
64
  let _fieldOverrides_initializers = [];
65
65
  let _fieldOverrides_extraInitializers = [];
66
+ let _errorOverrides_decorators;
67
+ let _errorOverrides_initializers = [];
68
+ let _errorOverrides_extraInitializers = [];
66
69
  let _validation_decorators;
67
70
  let _validation_initializers = [];
68
71
  let _validation_extraInitializers = [];
69
72
  let _get_fields_decorators;
70
73
  let _get_knownFields_decorators;
71
74
  let _get_accessors_decorators;
75
+ let _get_dirty_decorators;
72
76
  return _a = class FormModel {
73
77
  get value() { return __classPrivateFieldGet(this, _FormModel_value_accessor_storage, "f"); }
74
78
  set value(value) { __classPrivateFieldSet(this, _FormModel_value_accessor_storage, value, "f"); }
75
79
  get fieldOverrides() { return __classPrivateFieldGet(this, _FormModel_fieldOverrides_accessor_storage, "f"); }
76
80
  set fieldOverrides(value) { __classPrivateFieldSet(this, _FormModel_fieldOverrides_accessor_storage, value, "f"); }
81
+ get errorOverrides() { return __classPrivateFieldGet(this, _FormModel_errorOverrides_accessor_storage, "f"); }
82
+ set errorOverrides(value) { __classPrivateFieldSet(this, _FormModel_errorOverrides_accessor_storage, value, "f"); }
77
83
  get validation() { return __classPrivateFieldGet(this, _FormModel_validation_accessor_storage, "f"); }
78
84
  set validation(value) { __classPrivateFieldSet(this, _FormModel_validation_accessor_storage, value, "f"); }
79
85
  constructor(type, originalValue, adapters, mode) {
@@ -97,7 +103,8 @@ let FormModel = (() => {
97
103
  });
98
104
  _FormModel_value_accessor_storage.set(this, __runInitializers(this, _value_initializers, void 0));
99
105
  _FormModel_fieldOverrides_accessor_storage.set(this, (__runInitializers(this, _value_extraInitializers), __runInitializers(this, _fieldOverrides_initializers, void 0)));
100
- _FormModel_validation_accessor_storage.set(this, (__runInitializers(this, _fieldOverrides_extraInitializers), __runInitializers(this, _validation_initializers, {})));
106
+ _FormModel_errorOverrides_accessor_storage.set(this, (__runInitializers(this, _fieldOverrides_extraInitializers), __runInitializers(this, _errorOverrides_initializers, {})));
107
+ _FormModel_validation_accessor_storage.set(this, (__runInitializers(this, _errorOverrides_extraInitializers), __runInitializers(this, _validation_initializers, {})));
101
108
  Object.defineProperty(this, "flattenedTypeDefs", {
102
109
  enumerable: true,
103
110
  configurable: true,
@@ -233,40 +240,42 @@ let FormModel = (() => {
233
240
  }
234
241
  const { context, convert, revert, displayedValue, required, readonly, defaultValue, } = field;
235
242
  const validation = (_b = this.validation[valuePath]) !== null && _b !== void 0 ? _b : Validation.None;
236
- let error;
237
- switch (validation) {
238
- case Validation.None:
239
- // skip validation
240
- break;
241
- case Validation.Changed:
242
- if (revert != null) {
243
- const originalValue = valuePath in this.originalValues
243
+ let error = this.errorOverrides[valuePath];
244
+ if (error == null) {
245
+ switch (validation) {
246
+ case Validation.None:
247
+ // skip validation
248
+ break;
249
+ case Validation.Changed:
250
+ if (revert != null) {
251
+ const originalValue = valuePath in this.originalValues
252
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
253
+ ? this.originalValues[valuePath]
254
+ : defaultValue;
244
255
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
245
- ? this.originalValues[valuePath]
246
- : defaultValue;
247
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
248
- const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
249
- // TODO better comparisons, displayed values can still be complex
250
- if (displayedValue !== originalDisplayedValue) {
251
- const revertResult = revert(displayedValue, valuePath, context);
256
+ const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
257
+ // TODO better comparisons, displayed values can still be complex
258
+ if (displayedValue !== originalDisplayedValue) {
259
+ const revertResult = revert(displayedValue, valuePath, context);
260
+ if ((revertResult === null || revertResult === void 0 ? void 0 : revertResult.type) === UnreliableFieldConversionType.Failure) {
261
+ ;
262
+ ({ error } = revertResult);
263
+ }
264
+ }
265
+ }
266
+ break;
267
+ case Validation.Always:
268
+ {
269
+ const revertResult = revert === null || revert === void 0 ? void 0 : revert(displayedValue, valuePath, context);
252
270
  if ((revertResult === null || revertResult === void 0 ? void 0 : revertResult.type) === UnreliableFieldConversionType.Failure) {
253
271
  ;
254
272
  ({ error } = revertResult);
255
273
  }
256
274
  }
257
- }
258
- break;
259
- case Validation.Always:
260
- {
261
- const revertResult = revert === null || revert === void 0 ? void 0 : revert(displayedValue, valuePath, context);
262
- if ((revertResult === null || revertResult === void 0 ? void 0 : revertResult.type) === UnreliableFieldConversionType.Failure) {
263
- ;
264
- ({ error } = revertResult);
265
- }
266
- }
267
- break;
268
- default:
269
- throw new UnreachableError(validation);
275
+ break;
276
+ default:
277
+ throw new UnreachableError(validation);
278
+ }
270
279
  }
271
280
  return {
272
281
  value: displayedValue,
@@ -293,6 +302,12 @@ let FormModel = (() => {
293
302
  getAdapterForValuePath(valuePath) {
294
303
  return assertExistsAndReturn(this.maybeGetAdapterForValuePath(valuePath), 'expected adapter to be defined {}', valuePath);
295
304
  }
305
+ get dirty() {
306
+ return Object.keys(this.accessors).some((valuePath) => {
307
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
308
+ return this.isFieldDirty(valuePath);
309
+ });
310
+ }
296
311
  typePath(valuePath) {
297
312
  return valuePathToTypePath(this.type, valuePath, true);
298
313
  }
@@ -433,6 +448,7 @@ let FormModel = (() => {
433
448
  const accessor = this.getAccessorForValuePath(valuePath);
434
449
  return runInAction(() => {
435
450
  this.fieldOverrides[valuePath] = [value];
451
+ delete this.errorOverrides[valuePath];
436
452
  if (validation != null) {
437
453
  this.validation[valuePath] = validation;
438
454
  }
@@ -450,11 +466,27 @@ let FormModel = (() => {
450
466
  }
451
467
  });
452
468
  }
469
+ /**
470
+ * Forces an error onto a field. Error will be removed if the field value changes
471
+ * @param valuePath the field to display an error for
472
+ * @param error the error to display
473
+ */
474
+ overrideFieldError(valuePath, error) {
475
+ runInAction(() => {
476
+ if (error) {
477
+ this.errorOverrides[valuePath] = error;
478
+ }
479
+ else {
480
+ delete this.errorOverrides[valuePath];
481
+ }
482
+ });
483
+ }
453
484
  clearFieldError(valuePath) {
454
485
  const fieldOverride = this.fieldOverrides[valuePath];
455
486
  if (fieldOverride != null) {
456
487
  runInAction(() => {
457
488
  delete this.validation[valuePath];
489
+ delete this.errorOverrides[valuePath];
458
490
  });
459
491
  }
460
492
  }
@@ -475,6 +507,7 @@ let FormModel = (() => {
475
507
  runInAction(() => {
476
508
  this.fieldOverrides[key] = [displayValue];
477
509
  delete this.validation[key];
510
+ delete this.errorOverrides[key];
478
511
  });
479
512
  }
480
513
  clearAll(value) {
@@ -482,6 +515,7 @@ let FormModel = (() => {
482
515
  this.validation = {};
483
516
  // TODO this isn't correct, should reload from value
484
517
  this.fieldOverrides = {};
518
+ this.errorOverrides = {};
485
519
  this.value = mobxCopy(this.type, value);
486
520
  });
487
521
  }
@@ -495,7 +529,7 @@ let FormModel = (() => {
495
529
  var _b;
496
530
  return (_b = this.validation[valuePath]) !== null && _b !== void 0 ? _b : Validation.None;
497
531
  }
498
- isDirty(valuePath) {
532
+ isFieldDirty(valuePath) {
499
533
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
500
534
  const typePath = valuePathToTypePath(this.type,
501
535
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -504,19 +538,31 @@ let FormModel = (() => {
504
538
  if (field == null) {
505
539
  return false;
506
540
  }
507
- const { displayedValue, convert, context, defaultValue, } = field;
541
+ const { displayedValue, convert, revert, context, defaultValue, } = field;
542
+ // if either the display value, or the stored value, match the original, then assume it's not dirty
508
543
  const originalValue = valuePath in this.originalValues
509
544
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
510
545
  ? this.originalValues[valuePath]
511
546
  : defaultValue;
547
+ if (revert != null) {
548
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
549
+ const typeDef = this.flattenedTypeDefs[typePath];
550
+ const { value, type, } = revert(displayedValue, valuePath, context);
551
+ if (type === UnreliableFieldConversionType.Success) {
552
+ if (equals(typeDef, originalValue, value)) {
553
+ return false;
554
+ }
555
+ }
556
+ }
512
557
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
513
558
  const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
514
- // TODO better comparisons, displayed values can still be complex
515
- return (displayedValue !== originalDisplayedValue);
559
+ // try to compare the displayed values directly if we can't revert the displayed value
560
+ return displayedValue !== originalDisplayedValue;
516
561
  }
517
562
  validateField(valuePath, validation = Validation.Always) {
518
563
  runInAction(() => {
519
564
  this.validation[valuePath] = validation;
565
+ delete this.errorOverrides[valuePath];
520
566
  });
521
567
  return this.fields[valuePath].error == null;
522
568
  }
@@ -534,24 +580,32 @@ let FormModel = (() => {
534
580
  return (field === null || field === void 0 ? void 0 : field.error) == null;
535
581
  });
536
582
  }
583
+ validateSubmit() {
584
+ return this.validateAll();
585
+ }
537
586
  },
538
587
  _FormModel_value_accessor_storage = new WeakMap(),
539
588
  _FormModel_fieldOverrides_accessor_storage = new WeakMap(),
589
+ _FormModel_errorOverrides_accessor_storage = new WeakMap(),
540
590
  _FormModel_validation_accessor_storage = new WeakMap(),
541
591
  (() => {
542
592
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
543
593
  _value_decorators = [(_b = observable).ref.bind(_b)];
544
594
  _fieldOverrides_decorators = [(_c = observable).shallow.bind(_c)];
545
- _validation_decorators = [(_d = observable).shallow.bind(_d)];
595
+ _errorOverrides_decorators = [(_d = observable).shallow.bind(_d)];
596
+ _validation_decorators = [(_e = observable).shallow.bind(_e)];
546
597
  _get_fields_decorators = [computed];
547
598
  _get_knownFields_decorators = [computed];
548
599
  _get_accessors_decorators = [computed];
600
+ _get_dirty_decorators = [computed];
549
601
  __esDecorate(_a, null, _value_decorators, { kind: "accessor", name: "value", static: false, private: false, access: { has: obj => "value" in obj, get: obj => obj.value, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, _value_initializers, _value_extraInitializers);
550
602
  __esDecorate(_a, null, _fieldOverrides_decorators, { kind: "accessor", name: "fieldOverrides", static: false, private: false, access: { has: obj => "fieldOverrides" in obj, get: obj => obj.fieldOverrides, set: (obj, value) => { obj.fieldOverrides = value; } }, metadata: _metadata }, _fieldOverrides_initializers, _fieldOverrides_extraInitializers);
603
+ __esDecorate(_a, null, _errorOverrides_decorators, { kind: "accessor", name: "errorOverrides", static: false, private: false, access: { has: obj => "errorOverrides" in obj, get: obj => obj.errorOverrides, set: (obj, value) => { obj.errorOverrides = value; } }, metadata: _metadata }, _errorOverrides_initializers, _errorOverrides_extraInitializers);
551
604
  __esDecorate(_a, null, _validation_decorators, { kind: "accessor", name: "validation", static: false, private: false, access: { has: obj => "validation" in obj, get: obj => obj.validation, set: (obj, value) => { obj.validation = value; } }, metadata: _metadata }, _validation_initializers, _validation_extraInitializers);
552
605
  __esDecorate(_a, null, _get_fields_decorators, { kind: "getter", name: "fields", static: false, private: false, access: { has: obj => "fields" in obj, get: obj => obj.fields }, metadata: _metadata }, null, _instanceExtraInitializers);
553
606
  __esDecorate(_a, null, _get_knownFields_decorators, { kind: "getter", name: "knownFields", static: false, private: false, access: { has: obj => "knownFields" in obj, get: obj => obj.knownFields }, metadata: _metadata }, null, _instanceExtraInitializers);
554
607
  __esDecorate(_a, null, _get_accessors_decorators, { kind: "getter", name: "accessors", static: false, private: false, access: { has: obj => "accessors" in obj, get: obj => obj.accessors }, metadata: _metadata }, null, _instanceExtraInitializers);
608
+ __esDecorate(_a, null, _get_dirty_decorators, { kind: "getter", name: "dirty", static: false, private: false, access: { has: obj => "dirty" in obj, get: obj => obj.dirty }, metadata: _metadata }, null, _instanceExtraInitializers);
555
609
  if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
556
610
  })(),
557
611
  _a;
@@ -20,14 +20,14 @@ export function useDefaultMobxFormHooks(model, { onValidFieldSubmit, onValidForm
20
20
  // TODO debounce?
21
21
  setTimeout(function () {
22
22
  // only start validation if the user has changed the field
23
- if (model.isValuePathActive(path) && model.isDirty(path)) {
23
+ if (model.isValuePathActive(path) && model.isFieldDirty(path)) {
24
24
  // further workaround to make sure we don't downgrade the existing validation
25
25
  model.validateField(path, Math.max(Validation.Changed, model.getValidation(path)));
26
26
  }
27
27
  }, 100);
28
28
  }, [model]);
29
29
  const onFormSubmit = useCallback(function () {
30
- if (model.validateAll()) {
30
+ if (model.validateSubmit()) {
31
31
  onValidFormSubmit === null || onValidFormSubmit === void 0 ? void 0 : onValidFormSubmit(model.value);
32
32
  }
33
33
  }, [