@strictly/react-form 0.0.23 → 0.0.25

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.
@@ -15,6 +15,7 @@ type FlattenedFieldOverrides<ValuePathsToAdapters extends Readonly<Record<string
15
15
  -readonly [K in keyof ValuePathsToAdapters]?: FieldOverride<ToOfFieldAdapter<ValuePathsToAdapters[K]>>;
16
16
  };
17
17
  export declare enum Validation {
18
+ None = 0,
18
19
  Changed = 1,
19
20
  Always = 2
20
21
  }
@@ -43,13 +44,14 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
43
44
  get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>;
44
45
  private get knownFields();
45
46
  private maybeSynthesizeFieldByValuePath;
47
+ private getField;
46
48
  private synthesizeFieldByPaths;
47
49
  getAccessorForValuePath(valuePath: keyof ValuePathsToAdapters): Accessor | undefined;
48
50
  get accessors(): Readonly<Record<string, Accessor>>;
49
51
  private maybeGetAdapterForValuePath;
50
52
  private getAdapterForValuePath;
51
53
  typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K];
52
- setFieldValue<K extends keyof ValuePathsToAdapters>(valuePath: K, value: ToOfFieldAdapter<ValuePathsToAdapters[K]>, validation?: Validation | undefined | null): boolean;
54
+ setFieldValue<K extends keyof ValuePathsToAdapters>(valuePath: K, value: ToOfFieldAdapter<ValuePathsToAdapters[K]>, validation?: Validation): boolean;
53
55
  addListItem<K extends keyof FlattenedListTypesOfType<T>>(valuePath: K, elementValue?: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>>, index?: number): void;
54
56
  removeListItem<K extends keyof FlattenedListTypesOfType<T>>(...elementValuePaths: readonly `${K}.${number}`[]): void;
55
57
  private internalSetFieldValue;
@@ -57,6 +59,8 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
57
59
  clearFieldValue<K extends StringKeyOf<ValuePathsToAdapters>>(valuePath: K): void;
58
60
  clearAll(value: ValueOfType<T>): void;
59
61
  isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
62
+ getValidation<K extends keyof ValuePathsToAdapters>(valuePath: K): Validation;
63
+ isDirty<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
60
64
  validateField<K extends keyof ValuePathsToAdapters>(valuePath: K, validation?: Validation): boolean;
61
65
  validateAll(validation?: Validation): boolean;
62
66
  }
@@ -49,6 +49,7 @@ import { computed, observable, runInAction, } from 'mobx';
49
49
  import { UnreliableFieldConversionType, } from 'types/field_converters';
50
50
  export var Validation;
51
51
  (function (Validation) {
52
+ Validation[Validation["None"] = 0] = "None";
52
53
  Validation[Validation["Changed"] = 1] = "Changed";
53
54
  Validation[Validation["Always"] = 2] = "Always";
54
55
  })(Validation || (Validation = {}));
@@ -144,7 +145,7 @@ let FormModel = (() => {
144
145
  case 'edit':
145
146
  return false;
146
147
  default:
147
- return this.mode;
148
+ throw new UnreachableError(this.mode);
148
149
  }
149
150
  }
150
151
  get fields() {
@@ -189,7 +190,7 @@ let FormModel = (() => {
189
190
  }
190
191
  return this.synthesizeFieldByPaths(valuePath, typePath);
191
192
  }
192
- synthesizeFieldByPaths(valuePath, typePath) {
193
+ getField(valuePath, typePath) {
193
194
  const adapter = this.adapters[typePath];
194
195
  if (adapter == null) {
195
196
  // invalid path, which can happen
@@ -211,12 +212,30 @@ let FormModel = (() => {
211
212
  : defaultValue,
212
213
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
213
214
  valuePath, context);
214
- // const error = this.errors[valuePath]
215
- let error = undefined;
216
215
  const displayedValue = fieldOverride != null ? fieldOverride[0] : value;
217
- const validation = this.validation[valuePath];
216
+ return {
217
+ context,
218
+ convert,
219
+ create,
220
+ revert,
221
+ displayedValue,
222
+ // value,
223
+ required,
224
+ readonly,
225
+ defaultValue,
226
+ };
227
+ }
228
+ synthesizeFieldByPaths(valuePath, typePath) {
229
+ var _b;
230
+ const field = this.getField(valuePath, typePath);
231
+ if (field == null) {
232
+ return;
233
+ }
234
+ const { context, convert, revert, displayedValue, required, readonly, defaultValue, } = field;
235
+ const validation = (_b = this.validation[valuePath]) !== null && _b !== void 0 ? _b : Validation.None;
236
+ let error;
218
237
  switch (validation) {
219
- case undefined:
238
+ case Validation.None:
220
239
  // skip validation
221
240
  break;
222
241
  case Validation.Changed:
@@ -250,7 +269,7 @@ let FormModel = (() => {
250
269
  throw new UnreachableError(validation);
251
270
  }
252
271
  return {
253
- value: fieldOverride != null ? fieldOverride[0] : value,
272
+ value: displayedValue,
254
273
  error,
255
274
  readonly: readonly && !this.forceMutableFields,
256
275
  required,
@@ -277,7 +296,7 @@ let FormModel = (() => {
277
296
  typePath(valuePath) {
278
297
  return valuePathToTypePath(this.type, valuePath, true);
279
298
  }
280
- setFieldValue(valuePath, value, validation = this.validation[valuePath]) {
299
+ setFieldValue(valuePath, value, validation) {
281
300
  return this.internalSetFieldValue(valuePath, value, validation);
282
301
  }
283
302
  addListItem(valuePath,
@@ -417,9 +436,6 @@ let FormModel = (() => {
417
436
  if (validation != null) {
418
437
  this.validation[valuePath] = validation;
419
438
  }
420
- else {
421
- delete this.validation[valuePath];
422
- }
423
439
  switch (conversion.type) {
424
440
  case UnreliableFieldConversionType.Failure:
425
441
  if (conversion.value != null && accessor != null) {
@@ -475,15 +491,36 @@ let FormModel = (() => {
475
491
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
476
492
  return keys.has(valuePath);
477
493
  }
478
- validateField(valuePath, validation) {
494
+ getValidation(valuePath) {
479
495
  var _b;
480
- if (validation === void 0) { validation = Math.max(this.mode === 'create' ? Validation.Always : Validation.Changed, (_b = this.validation[valuePath]) !== null && _b !== void 0 ? _b : Validation.Changed); }
496
+ return (_b = this.validation[valuePath]) !== null && _b !== void 0 ? _b : Validation.None;
497
+ }
498
+ isDirty(valuePath) {
499
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
500
+ const typePath = valuePathToTypePath(this.type,
501
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
502
+ valuePath, true);
503
+ const field = this.getField(valuePath, typePath);
504
+ if (field == null) {
505
+ return false;
506
+ }
507
+ const { displayedValue, convert, context, defaultValue, } = field;
508
+ const originalValue = valuePath in this.originalValues
509
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
510
+ ? this.originalValues[valuePath]
511
+ : defaultValue;
512
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
513
+ const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
514
+ // TODO better comparisons, displayed values can still be complex
515
+ return (displayedValue !== originalDisplayedValue);
516
+ }
517
+ validateField(valuePath, validation = Validation.Always) {
481
518
  runInAction(() => {
482
519
  this.validation[valuePath] = validation;
483
520
  });
484
521
  return this.fields[valuePath].error == null;
485
522
  }
486
- validateAll(validation = this.mode === 'create' ? Validation.Always : Validation.Changed) {
523
+ validateAll(validation = Validation.Always) {
487
524
  const accessors = toArray(this.accessors);
488
525
  runInAction(() => {
489
526
  accessors.forEach(([valuePath]) => {
@@ -1,8 +1,9 @@
1
1
  import { useCallback, } from 'react';
2
+ import { Validation, } from './form_model';
2
3
  export function useDefaultMobxFormHooks(model, { onValidFieldSubmit, onValidFormSubmit, } = {}) {
3
4
  const onFieldValueChange = useCallback(function (path, value) {
4
- // clear any validation
5
- model.setFieldValue(path, value, null);
5
+ const validation = Math.min(model.getValidation(path), Validation.Changed);
6
+ model.setFieldValue(path, value, validation);
6
7
  }, [model]);
7
8
  const onFieldSubmit = useCallback(function (valuePath) {
8
9
  if (model.validateField(valuePath)) {
@@ -18,8 +19,10 @@ export function useDefaultMobxFormHooks(model, { onValidFieldSubmit, onValidForm
18
19
  // (e.g. changing a discriminator)
19
20
  // TODO debounce?
20
21
  setTimeout(function () {
21
- if (model.isValuePathActive(path)) {
22
- model.validateField(path);
22
+ // only start validation if the user has changed the field
23
+ if (model.isValuePathActive(path) && model.isDirty(path)) {
24
+ // further workaround to make sure we don't downgrade the existing validation
25
+ model.validateField(path, Math.max(Validation.Changed, model.getValidation(path)));
23
26
  }
24
27
  }, 100);
25
28
  }, [model]);
@@ -863,8 +863,8 @@ describe('all', function () {
863
863
  it('respects the field being readonly', () => {
864
864
  expect(model.fields['$.n'].readonly).toBeTruthy();
865
865
  });
866
- it('validates successfully with clean, but invalid data', () => {
867
- expect(model.validateAll()).toBeTruthy();
866
+ it('fails validation with invalid, clean data', () => {
867
+ expect(model.validateAll()).toBeFalsy();
868
868
  });
869
869
  it('fails validation with invalid, dirty data', () => {
870
870
  model.setFieldValue('$.n', '2');