@strictly/react-form 0.0.24 → 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.
package/dist/index.d.ts CHANGED
@@ -112,6 +112,9 @@ type FieldOverride<V = any> = Maybe<V>;
112
112
  type FlattenedFieldOverrides<ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>> = {
113
113
  -readonly [K in keyof ValuePathsToAdapters]?: FieldOverride<ToOfFieldAdapter<ValuePathsToAdapters[K]>>;
114
114
  };
115
+ type FlattenedErrorOverrides<ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>> = {
116
+ -readonly [K in keyof ValuePathsToAdapters]?: ErrorOfFieldAdapter<ValuePathsToAdapters[K]>;
117
+ };
115
118
  declare enum Validation {
116
119
  None = 0,
117
120
  Changed = 1,
@@ -133,6 +136,7 @@ declare abstract class FormModel<T extends Type, ValueToTypePaths extends Readon
133
136
  protected readonly mode: FormMode;
134
137
  accessor value: MobxValueOfType<T>;
135
138
  accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>;
139
+ accessor errorOverrides: FlattenedErrorOverrides<ValuePathsToAdapters>;
136
140
  accessor validation: FlattenedValidation<ValuePathsToAdapters>;
137
141
  private readonly flattenedTypeDefs;
138
142
  private readonly originalValues;
@@ -142,23 +146,33 @@ declare abstract class FormModel<T extends Type, ValueToTypePaths extends Readon
142
146
  get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>;
143
147
  private get knownFields();
144
148
  private maybeSynthesizeFieldByValuePath;
149
+ private getField;
145
150
  private synthesizeFieldByPaths;
146
151
  getAccessorForValuePath(valuePath: keyof ValuePathsToAdapters): Accessor | undefined;
147
152
  get accessors(): Readonly<Record<string, Accessor>>;
148
153
  private maybeGetAdapterForValuePath;
149
154
  private getAdapterForValuePath;
155
+ get dirty(): boolean;
150
156
  typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K];
151
157
  setFieldValue<K extends keyof ValuePathsToAdapters>(valuePath: K, value: ToOfFieldAdapter<ValuePathsToAdapters[K]>, validation?: Validation): boolean;
152
158
  addListItem<K extends keyof FlattenedListTypesOfType<T>>(valuePath: K, elementValue?: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>>, index?: number): void;
153
159
  removeListItem<K extends keyof FlattenedListTypesOfType<T>>(...elementValuePaths: readonly `${K}.${number}`[]): void;
154
160
  private internalSetFieldValue;
161
+ /**
162
+ * Forces an error onto a field. Error will be removed if the field value changes
163
+ * @param valuePath the field to display an error for
164
+ * @param error the error to display
165
+ */
166
+ overrideFieldError<K extends keyof ValuePathsToAdapters>(valuePath: K, error?: ErrorOfFieldAdapter<ValuePathsToAdapters[K]>): void;
155
167
  clearFieldError<K extends keyof ValuePathsToAdapters>(valuePath: K): void;
156
168
  clearFieldValue<K extends StringKeyOf<ValuePathsToAdapters>>(valuePath: K): void;
157
169
  clearAll(value: ValueOfType<T>): void;
158
170
  isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
159
171
  getValidation<K extends keyof ValuePathsToAdapters>(valuePath: K): Validation;
172
+ isFieldDirty<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
160
173
  validateField<K extends keyof ValuePathsToAdapters>(valuePath: K, validation?: Validation): boolean;
161
174
  validateAll(validation?: Validation): boolean;
175
+ validateSubmit(): boolean;
162
176
  }
163
177
 
164
178
  type ValueOfModel<M extends FormModel<any, any, any, any, any>> = M extends FormModel<infer T, any, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
package/dist/index.js CHANGED
@@ -313,6 +313,7 @@ import {
313
313
  UnreachableError as UnreachableError2
314
314
  } from "@strictly/base";
315
315
  import {
316
+ equals,
316
317
  flattenAccessorsOfType,
317
318
  flattenTypesOfType,
318
319
  flattenValuesOfType,
@@ -332,8 +333,8 @@ var Validation = /* @__PURE__ */ ((Validation2) => {
332
333
  Validation2[Validation2["Always"] = 2] = "Always";
333
334
  return Validation2;
334
335
  })(Validation || {});
335
- var _accessors_dec, _knownFields_dec, _fields_dec, _validation_dec, _fieldOverrides_dec, _value_dec, _init, _value, _fieldOverrides, _validation;
336
- _value_dec = [observable.ref], _fieldOverrides_dec = [observable.shallow], _validation_dec = [observable.shallow], _fields_dec = [computed], _knownFields_dec = [computed], _accessors_dec = [computed];
336
+ var _dirty_dec, _accessors_dec, _knownFields_dec, _fields_dec, _validation_dec, _errorOverrides_dec, _fieldOverrides_dec, _value_dec, _init, _value, _fieldOverrides, _errorOverrides, _validation;
337
+ _value_dec = [observable.ref], _fieldOverrides_dec = [observable.shallow], _errorOverrides_dec = [observable.shallow], _validation_dec = [observable.shallow], _fields_dec = [computed], _knownFields_dec = [computed], _accessors_dec = [computed], _dirty_dec = [computed];
337
338
  var FormModel = class {
338
339
  constructor(type, originalValue, adapters, mode) {
339
340
  this.type = type;
@@ -342,7 +343,8 @@ var FormModel = class {
342
343
  __runInitializers(_init, 5, this);
343
344
  __privateAdd(this, _value, __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
344
345
  __privateAdd(this, _fieldOverrides, __runInitializers(_init, 12, this)), __runInitializers(_init, 15, this);
345
- __privateAdd(this, _validation, __runInitializers(_init, 16, this, {})), __runInitializers(_init, 19, this);
346
+ __privateAdd(this, _errorOverrides, __runInitializers(_init, 16, this, {})), __runInitializers(_init, 19, this);
347
+ __privateAdd(this, _validation, __runInitializers(_init, 20, this, {})), __runInitializers(_init, 23, this);
346
348
  __publicField(this, "flattenedTypeDefs");
347
349
  // cannot be type safe
348
350
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -432,8 +434,7 @@ var FormModel = class {
432
434
  }
433
435
  return this.synthesizeFieldByPaths(valuePath, typePath);
434
436
  }
435
- synthesizeFieldByPaths(valuePath, typePath) {
436
- var _a;
437
+ getField(valuePath, typePath) {
437
438
  const adapter2 = this.adapters[typePath];
438
439
  if (adapter2 == null) {
439
440
  return;
@@ -461,39 +462,68 @@ var FormModel = class {
461
462
  valuePath,
462
463
  context
463
464
  );
464
- let error = void 0;
465
465
  const displayedValue = fieldOverride != null ? fieldOverride[0] : value;
466
+ return {
467
+ context,
468
+ convert,
469
+ create,
470
+ revert,
471
+ displayedValue,
472
+ // value,
473
+ required,
474
+ readonly,
475
+ defaultValue
476
+ };
477
+ }
478
+ synthesizeFieldByPaths(valuePath, typePath) {
479
+ var _a;
480
+ const field = this.getField(valuePath, typePath);
481
+ if (field == null) {
482
+ return;
483
+ }
484
+ const {
485
+ context,
486
+ convert,
487
+ revert,
488
+ displayedValue,
489
+ required,
490
+ readonly,
491
+ defaultValue
492
+ } = field;
466
493
  const validation = (_a = this.validation[valuePath]) != null ? _a : 0 /* None */;
467
- switch (validation) {
468
- case 0 /* None */:
469
- break;
470
- case 1 /* Changed */:
471
- if (revert != null) {
472
- const originalValue = valuePath in this.originalValues ? this.originalValues[valuePath] : defaultValue;
473
- const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
474
- if (displayedValue !== originalDisplayedValue) {
475
- const revertResult = revert(displayedValue, valuePath, context);
494
+ let error = this.errorOverrides[valuePath];
495
+ if (error == null) {
496
+ switch (validation) {
497
+ case 0 /* None */:
498
+ break;
499
+ case 1 /* Changed */:
500
+ if (revert != null) {
501
+ const originalValue = valuePath in this.originalValues ? this.originalValues[valuePath] : defaultValue;
502
+ const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
503
+ if (displayedValue !== originalDisplayedValue) {
504
+ const revertResult = revert(displayedValue, valuePath, context);
505
+ if ((revertResult == null ? void 0 : revertResult.type) === 1 /* Failure */) {
506
+ ;
507
+ ({ error } = revertResult);
508
+ }
509
+ }
510
+ }
511
+ break;
512
+ case 2 /* Always */:
513
+ {
514
+ const revertResult = revert == null ? void 0 : revert(displayedValue, valuePath, context);
476
515
  if ((revertResult == null ? void 0 : revertResult.type) === 1 /* Failure */) {
477
516
  ;
478
517
  ({ error } = revertResult);
479
518
  }
480
519
  }
481
- }
482
- break;
483
- case 2 /* Always */:
484
- {
485
- const revertResult = revert == null ? void 0 : revert(displayedValue, valuePath, context);
486
- if ((revertResult == null ? void 0 : revertResult.type) === 1 /* Failure */) {
487
- ;
488
- ({ error } = revertResult);
489
- }
490
- }
491
- break;
492
- default:
493
- throw new UnreachableError2(validation);
520
+ break;
521
+ default:
522
+ throw new UnreachableError2(validation);
523
+ }
494
524
  }
495
525
  return {
496
- value: fieldOverride != null ? fieldOverride[0] : value,
526
+ value: displayedValue,
497
527
  error,
498
528
  readonly: readonly && !this.forceMutableFields,
499
529
  required
@@ -523,6 +553,11 @@ var FormModel = class {
523
553
  valuePath
524
554
  );
525
555
  }
556
+ get dirty() {
557
+ return Object.keys(this.accessors).some((valuePath) => {
558
+ return this.isFieldDirty(valuePath);
559
+ });
560
+ }
526
561
  typePath(valuePath) {
527
562
  return valuePathToTypePath(this.type, valuePath, true);
528
563
  }
@@ -668,6 +703,7 @@ var FormModel = class {
668
703
  const accessor = this.getAccessorForValuePath(valuePath);
669
704
  return runInAction(() => {
670
705
  this.fieldOverrides[valuePath] = [value];
706
+ delete this.errorOverrides[valuePath];
671
707
  if (validation != null) {
672
708
  this.validation[valuePath] = validation;
673
709
  }
@@ -685,11 +721,26 @@ var FormModel = class {
685
721
  }
686
722
  });
687
723
  }
724
+ /**
725
+ * Forces an error onto a field. Error will be removed if the field value changes
726
+ * @param valuePath the field to display an error for
727
+ * @param error the error to display
728
+ */
729
+ overrideFieldError(valuePath, error) {
730
+ runInAction(() => {
731
+ if (error) {
732
+ this.errorOverrides[valuePath] = error;
733
+ } else {
734
+ delete this.errorOverrides[valuePath];
735
+ }
736
+ });
737
+ }
688
738
  clearFieldError(valuePath) {
689
739
  const fieldOverride = this.fieldOverrides[valuePath];
690
740
  if (fieldOverride != null) {
691
741
  runInAction(() => {
692
742
  delete this.validation[valuePath];
743
+ delete this.errorOverrides[valuePath];
693
744
  });
694
745
  }
695
746
  }
@@ -712,12 +763,14 @@ var FormModel = class {
712
763
  runInAction(() => {
713
764
  this.fieldOverrides[key] = [displayValue];
714
765
  delete this.validation[key];
766
+ delete this.errorOverrides[key];
715
767
  });
716
768
  }
717
769
  clearAll(value) {
718
770
  runInAction(() => {
719
771
  this.validation = {};
720
772
  this.fieldOverrides = {};
773
+ this.errorOverrides = {};
721
774
  this.value = mobxCopy(this.type, value);
722
775
  });
723
776
  }
@@ -730,9 +783,44 @@ var FormModel = class {
730
783
  var _a;
731
784
  return (_a = this.validation[valuePath]) != null ? _a : 0 /* None */;
732
785
  }
786
+ isFieldDirty(valuePath) {
787
+ const typePath = valuePathToTypePath(
788
+ this.type,
789
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
790
+ valuePath,
791
+ true
792
+ );
793
+ const field = this.getField(valuePath, typePath);
794
+ if (field == null) {
795
+ return false;
796
+ }
797
+ const {
798
+ displayedValue,
799
+ convert,
800
+ revert,
801
+ context,
802
+ defaultValue
803
+ } = field;
804
+ const originalValue = valuePath in this.originalValues ? this.originalValues[valuePath] : defaultValue;
805
+ if (revert != null) {
806
+ const typeDef = this.flattenedTypeDefs[typePath];
807
+ const {
808
+ value,
809
+ type
810
+ } = revert(displayedValue, valuePath, context);
811
+ if (type === 0 /* Success */) {
812
+ if (equals(typeDef, originalValue, value)) {
813
+ return false;
814
+ }
815
+ }
816
+ }
817
+ const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
818
+ return displayedValue !== originalDisplayedValue;
819
+ }
733
820
  validateField(valuePath, validation = 2 /* Always */) {
734
821
  runInAction(() => {
735
822
  this.validation[valuePath] = validation;
823
+ delete this.errorOverrides[valuePath];
736
824
  });
737
825
  return this.fields[valuePath].error == null;
738
826
  }
@@ -750,17 +838,23 @@ var FormModel = class {
750
838
  }
751
839
  );
752
840
  }
841
+ validateSubmit() {
842
+ return this.validateAll();
843
+ }
753
844
  };
754
845
  _init = __decoratorStart(null);
755
846
  _value = new WeakMap();
756
847
  _fieldOverrides = new WeakMap();
848
+ _errorOverrides = new WeakMap();
757
849
  _validation = new WeakMap();
758
850
  __decorateElement(_init, 4, "value", _value_dec, FormModel, _value);
759
851
  __decorateElement(_init, 4, "fieldOverrides", _fieldOverrides_dec, FormModel, _fieldOverrides);
852
+ __decorateElement(_init, 4, "errorOverrides", _errorOverrides_dec, FormModel, _errorOverrides);
760
853
  __decorateElement(_init, 4, "validation", _validation_dec, FormModel, _validation);
761
854
  __decorateElement(_init, 2, "fields", _fields_dec, FormModel);
762
855
  __decorateElement(_init, 2, "knownFields", _knownFields_dec, FormModel);
763
856
  __decorateElement(_init, 2, "accessors", _accessors_dec, FormModel);
857
+ __decorateElement(_init, 2, "dirty", _dirty_dec, FormModel);
764
858
  __decoratorMetadata(_init, FormModel);
765
859
 
766
860
  // core/mobx/hooks.tsx
@@ -773,7 +867,8 @@ function useDefaultMobxFormHooks(model, {
773
867
  } = {}) {
774
868
  const onFieldValueChange = useCallback(
775
869
  function(path, value) {
776
- model.setFieldValue(path, value);
870
+ const validation = Math.min(model.getValidation(path), 1 /* Changed */);
871
+ model.setFieldValue(path, value, validation);
777
872
  },
778
873
  [model]
779
874
  );
@@ -792,7 +887,7 @@ function useDefaultMobxFormHooks(model, {
792
887
  const onFieldBlur = useCallback(
793
888
  function(path) {
794
889
  setTimeout(function() {
795
- if (model.isValuePathActive(path)) {
890
+ if (model.isValuePathActive(path) && model.isFieldDirty(path)) {
796
891
  model.validateField(path, Math.max(1 /* Changed */, model.getValidation(path)));
797
892
  }
798
893
  }, 100);
@@ -801,7 +896,7 @@ function useDefaultMobxFormHooks(model, {
801
896
  );
802
897
  const onFormSubmit = useCallback(
803
898
  function() {
804
- if (model.validateAll()) {
899
+ if (model.validateSubmit()) {
805
900
  onValidFormSubmit == null ? void 0 : onValidFormSubmit(model.value);
806
901
  }
807
902
  },
package/package.json CHANGED
@@ -72,7 +72,7 @@
72
72
  "test:watch": "vitest"
73
73
  },
74
74
  "type": "module",
75
- "version": "0.0.24",
75
+ "version": "0.0.26",
76
76
  "exports": {
77
77
  ".": {
78
78
  "import": {