@strictly/react-form 0.0.17 → 0.0.19
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/.out/core/mobx/form_model.d.ts +13 -10
- package/.out/core/mobx/form_model.js +103 -137
- package/.out/core/mobx/hooks.js +3 -4
- package/.out/core/mobx/merge_field_adapters_with_two_way_converter.d.ts +1 -1
- package/.out/core/mobx/merge_field_adapters_with_validators.js +5 -1
- package/.out/core/mobx/specs/form_model.tests.js +28 -23
- package/.out/core/props.d.ts +0 -2
- package/.out/mantine/create_fields_view.d.ts +2 -1
- package/.out/mantine/hooks.d.ts +6 -5
- package/.out/mantine/specs/checkbox_hooks.stories.d.ts +5 -2
- package/.out/mantine/specs/checkbox_hooks.stories.js +3 -2
- package/.out/mantine/specs/text_input_hooks.stories.d.ts +3 -2
- package/.out/mantine/specs/text_input_hooks.stories.js +3 -2
- package/.out/mantine/types.d.ts +3 -3
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/util/partial.d.ts +5 -2
- package/.out/util/specs/partial.tests.d.ts +1 -0
- package/.out/util/specs/partial.tests.js +8 -0
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/core/mobx/form_model.ts +97 -158
- package/core/mobx/hooks.tsx +6 -5
- package/core/mobx/merge_field_adapters_with_two_way_converter.ts +2 -1
- package/core/mobx/merge_field_adapters_with_validators.ts +1 -1
- package/core/mobx/specs/form_model.tests.ts +39 -27
- package/core/props.ts +0 -4
- package/dist/index.cjs +93 -139
- package/dist/index.d.cts +41 -35
- package/dist/index.d.ts +41 -35
- package/dist/index.js +92 -139
- package/mantine/create_fields_view.tsx +8 -4
- package/mantine/hooks.tsx +23 -15
- package/mantine/specs/checkbox_hooks.stories.tsx +7 -1
- package/mantine/specs/text_input_hooks.stories.tsx +8 -1
- package/mantine/types.ts +12 -4
- package/package.json +1 -1
- package/util/partial.tsx +8 -1
- package/util/specs/partial.tests.tsx +21 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ElementOfArray, type Maybe } from '@strictly/base';
|
|
2
2
|
import { type Accessor, type FlattenedValuesOfType, type MobxValueOfType, type ReadonlyTypeOfType, type Type, type ValueOfType } from '@strictly/define';
|
|
3
|
-
import { type FormMode } from 'core/props';
|
|
4
3
|
import { type ReadonlyDeep, type SimplifyDeep, type StringKeyOf, type UnionToIntersection, type ValueOf } from 'type-fest';
|
|
5
4
|
import { type Field } from 'types/field';
|
|
6
5
|
import { type ContextOfFieldAdapter, type ErrorOfFieldAdapter, type FieldAdapter, type ToOfFieldAdapter } from './field_adapter';
|
|
@@ -15,8 +14,12 @@ type FieldOverride<V = any> = Maybe<V>;
|
|
|
15
14
|
type FlattenedFieldOverrides<ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>> = {
|
|
16
15
|
-readonly [K in keyof ValuePathsToAdapters]?: FieldOverride<ToOfFieldAdapter<ValuePathsToAdapters[K]>>;
|
|
17
16
|
};
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export declare enum Validation {
|
|
18
|
+
Changed = 1,
|
|
19
|
+
Always = 2
|
|
20
|
+
}
|
|
21
|
+
type FlattenedValidation<ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>> = {
|
|
22
|
+
-readonly [K in keyof ValuePathsToAdapters]?: Validation;
|
|
20
23
|
};
|
|
21
24
|
export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>, ValuePathsToTypePaths extends Readonly<Record<string, string>>> = keyof TypePathsToAdapters extends ValueOf<ValuePathsToTypePaths> ? {
|
|
22
25
|
readonly [K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>;
|
|
@@ -24,15 +27,16 @@ export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<
|
|
|
24
27
|
export type ContextOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>> = UnionToIntersection<{
|
|
25
28
|
readonly [K in keyof TypePathsToAdapters]: TypePathsToAdapters[K] extends undefined ? undefined : unknown extends ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>> ? never : ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>>;
|
|
26
29
|
}[keyof TypePathsToAdapters] | {}>;
|
|
30
|
+
export type FormMode = 'edit' | 'create';
|
|
27
31
|
export declare abstract class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ContextType>, ContextType = ContextOf<TypePathsToAdapters>, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
|
|
28
32
|
readonly type: T;
|
|
29
|
-
private readonly originalValue;
|
|
30
33
|
protected readonly adapters: TypePathsToAdapters;
|
|
31
34
|
protected readonly mode: FormMode;
|
|
32
35
|
accessor value: MobxValueOfType<T>;
|
|
33
36
|
accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>;
|
|
34
|
-
accessor
|
|
37
|
+
accessor validation: FlattenedValidation<ValuePathsToAdapters>;
|
|
35
38
|
private readonly flattenedTypeDefs;
|
|
39
|
+
private readonly originalValues;
|
|
36
40
|
constructor(type: T, originalValue: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters, mode: FormMode);
|
|
37
41
|
protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>, valuePath: keyof ValuePathsToAdapters): ContextType;
|
|
38
42
|
get forceMutableFields(): boolean;
|
|
@@ -45,16 +49,15 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
|
|
|
45
49
|
private maybeGetAdapterForValuePath;
|
|
46
50
|
private getAdapterForValuePath;
|
|
47
51
|
typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K];
|
|
48
|
-
|
|
49
|
-
setFieldValue<K extends keyof ValuePathsToAdapters>(valuePath: K, value: ToOfFieldAdapter<ValuePathsToAdapters[K]>): boolean;
|
|
52
|
+
setFieldValue<K extends keyof ValuePathsToAdapters>(valuePath: K, value: ToOfFieldAdapter<ValuePathsToAdapters[K]>, validation?: Validation | undefined | null): boolean;
|
|
50
53
|
addListItem<K extends keyof FlattenedListTypesOfType<T>>(valuePath: K, elementValue?: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>>, index?: number): void;
|
|
51
54
|
removeListItem<K extends keyof FlattenedListTypesOfType<T>>(...elementValuePaths: readonly `${K}.${number}`[]): void;
|
|
52
55
|
private internalSetFieldValue;
|
|
53
56
|
clearFieldError<K extends keyof ValuePathsToAdapters>(valuePath: K): void;
|
|
54
|
-
clearFieldValue<K extends StringKeyOf<
|
|
57
|
+
clearFieldValue<K extends StringKeyOf<ValuePathsToAdapters>>(valuePath: K): void;
|
|
55
58
|
clearAll(value: ValueOfType<T>): void;
|
|
56
59
|
isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
|
|
57
|
-
validateField<K extends keyof ValuePathsToAdapters>(valuePath: K,
|
|
58
|
-
validateAll(
|
|
60
|
+
validateField<K extends keyof ValuePathsToAdapters>(valuePath: K, validation?: Validation): boolean;
|
|
61
|
+
validateAll(validation?: Validation): boolean;
|
|
59
62
|
}
|
|
60
63
|
export {};
|
|
@@ -47,8 +47,13 @@ import { assertExists, assertExistsAndReturn, assertState, checkValidNumber, map
|
|
|
47
47
|
import { 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
|
+
export var Validation;
|
|
51
|
+
(function (Validation) {
|
|
52
|
+
Validation[Validation["Changed"] = 1] = "Changed";
|
|
53
|
+
Validation[Validation["Always"] = 2] = "Always";
|
|
54
|
+
})(Validation || (Validation = {}));
|
|
50
55
|
let FormModel = (() => {
|
|
51
|
-
var _a, _FormModel_value_accessor_storage, _FormModel_fieldOverrides_accessor_storage,
|
|
56
|
+
var _a, _FormModel_value_accessor_storage, _FormModel_fieldOverrides_accessor_storage, _FormModel_validation_accessor_storage;
|
|
52
57
|
var _b, _c, _d;
|
|
53
58
|
let _instanceExtraInitializers = [];
|
|
54
59
|
let _value_decorators;
|
|
@@ -57,9 +62,9 @@ let FormModel = (() => {
|
|
|
57
62
|
let _fieldOverrides_decorators;
|
|
58
63
|
let _fieldOverrides_initializers = [];
|
|
59
64
|
let _fieldOverrides_extraInitializers = [];
|
|
60
|
-
let
|
|
61
|
-
let
|
|
62
|
-
let
|
|
65
|
+
let _validation_decorators;
|
|
66
|
+
let _validation_initializers = [];
|
|
67
|
+
let _validation_extraInitializers = [];
|
|
63
68
|
let _get_fields_decorators;
|
|
64
69
|
let _get_knownFields_decorators;
|
|
65
70
|
let _get_accessors_decorators;
|
|
@@ -68,8 +73,8 @@ let FormModel = (() => {
|
|
|
68
73
|
set value(value) { __classPrivateFieldSet(this, _FormModel_value_accessor_storage, value, "f"); }
|
|
69
74
|
get fieldOverrides() { return __classPrivateFieldGet(this, _FormModel_fieldOverrides_accessor_storage, "f"); }
|
|
70
75
|
set fieldOverrides(value) { __classPrivateFieldSet(this, _FormModel_fieldOverrides_accessor_storage, value, "f"); }
|
|
71
|
-
get
|
|
72
|
-
set
|
|
76
|
+
get validation() { return __classPrivateFieldGet(this, _FormModel_validation_accessor_storage, "f"); }
|
|
77
|
+
set validation(value) { __classPrivateFieldSet(this, _FormModel_validation_accessor_storage, value, "f"); }
|
|
73
78
|
constructor(type, originalValue, adapters, mode) {
|
|
74
79
|
Object.defineProperty(this, "type", {
|
|
75
80
|
enumerable: true,
|
|
@@ -77,12 +82,6 @@ let FormModel = (() => {
|
|
|
77
82
|
writable: true,
|
|
78
83
|
value: (__runInitializers(this, _instanceExtraInitializers), type)
|
|
79
84
|
});
|
|
80
|
-
Object.defineProperty(this, "originalValue", {
|
|
81
|
-
enumerable: true,
|
|
82
|
-
configurable: true,
|
|
83
|
-
writable: true,
|
|
84
|
-
value: originalValue
|
|
85
|
-
});
|
|
86
85
|
Object.defineProperty(this, "adapters", {
|
|
87
86
|
enumerable: true,
|
|
88
87
|
configurable: true,
|
|
@@ -97,13 +96,22 @@ let FormModel = (() => {
|
|
|
97
96
|
});
|
|
98
97
|
_FormModel_value_accessor_storage.set(this, __runInitializers(this, _value_initializers, void 0));
|
|
99
98
|
_FormModel_fieldOverrides_accessor_storage.set(this, (__runInitializers(this, _value_extraInitializers), __runInitializers(this, _fieldOverrides_initializers, void 0)));
|
|
100
|
-
|
|
99
|
+
_FormModel_validation_accessor_storage.set(this, (__runInitializers(this, _fieldOverrides_extraInitializers), __runInitializers(this, _validation_initializers, {})));
|
|
101
100
|
Object.defineProperty(this, "flattenedTypeDefs", {
|
|
102
101
|
enumerable: true,
|
|
103
102
|
configurable: true,
|
|
104
103
|
writable: true,
|
|
105
|
-
value: __runInitializers(this,
|
|
104
|
+
value: __runInitializers(this, _validation_extraInitializers)
|
|
106
105
|
});
|
|
106
|
+
// cannot be type safe
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
Object.defineProperty(this, "originalValues", {
|
|
109
|
+
enumerable: true,
|
|
110
|
+
configurable: true,
|
|
111
|
+
writable: true,
|
|
112
|
+
value: void 0
|
|
113
|
+
});
|
|
114
|
+
this.originalValues = flattenValuesOfType(type, originalValue);
|
|
107
115
|
this.value = mobxCopy(type, originalValue);
|
|
108
116
|
this.flattenedTypeDefs = flattenTypesOfType(type);
|
|
109
117
|
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
@@ -187,24 +195,60 @@ let FormModel = (() => {
|
|
|
187
195
|
// invalid path, which can happen
|
|
188
196
|
return;
|
|
189
197
|
}
|
|
190
|
-
const { convert, create, } = adapter;
|
|
198
|
+
const { convert, create, revert, } = adapter;
|
|
191
199
|
const fieldOverride = this.fieldOverrides[valuePath];
|
|
192
200
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
193
201
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
194
202
|
const fieldTypeDef = this.flattenedTypeDefs[typePath];
|
|
195
203
|
const context = this.toContext(this.value, valuePath);
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
205
|
+
const defaultValue = create(valuePath, context);
|
|
196
206
|
const { value, required, readonly, } = convert(accessor != null
|
|
197
207
|
? accessor.value
|
|
198
208
|
: fieldTypeDef != null
|
|
199
|
-
? mobxCopy(fieldTypeDef,
|
|
200
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
201
|
-
create(valuePath, context))
|
|
209
|
+
? mobxCopy(fieldTypeDef, defaultValue)
|
|
202
210
|
// fake values can't be copied
|
|
203
|
-
|
|
204
|
-
: create(valuePath, context),
|
|
211
|
+
: defaultValue,
|
|
205
212
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
206
213
|
valuePath, context);
|
|
207
|
-
const error = this.errors[valuePath]
|
|
214
|
+
// const error = this.errors[valuePath]
|
|
215
|
+
let error = undefined;
|
|
216
|
+
const displayedValue = fieldOverride != null ? fieldOverride[0] : value;
|
|
217
|
+
const validation = this.validation[valuePath];
|
|
218
|
+
switch (validation) {
|
|
219
|
+
case undefined:
|
|
220
|
+
// skip validation
|
|
221
|
+
break;
|
|
222
|
+
case Validation.Changed:
|
|
223
|
+
if (revert != null) {
|
|
224
|
+
const originalValue = valuePath in this.originalValues
|
|
225
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
226
|
+
? this.originalValues[valuePath]
|
|
227
|
+
: defaultValue;
|
|
228
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
229
|
+
const { value: originalDisplayedValue } = convert(originalValue, valuePath, context);
|
|
230
|
+
// TODO better comparisons, displayed values can still be complex
|
|
231
|
+
if (displayedValue !== originalDisplayedValue) {
|
|
232
|
+
const revertResult = revert(displayedValue, valuePath, context);
|
|
233
|
+
if ((revertResult === null || revertResult === void 0 ? void 0 : revertResult.type) === UnreliableFieldConversionType.Failure) {
|
|
234
|
+
;
|
|
235
|
+
({ error } = revertResult);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case Validation.Always:
|
|
241
|
+
{
|
|
242
|
+
const revertResult = revert === null || revert === void 0 ? void 0 : revert(displayedValue, valuePath, context);
|
|
243
|
+
if ((revertResult === null || revertResult === void 0 ? void 0 : revertResult.type) === UnreliableFieldConversionType.Failure) {
|
|
244
|
+
;
|
|
245
|
+
({ error } = revertResult);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
throw new UnreachableError(validation);
|
|
251
|
+
}
|
|
208
252
|
return {
|
|
209
253
|
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
210
254
|
error,
|
|
@@ -233,11 +277,8 @@ let FormModel = (() => {
|
|
|
233
277
|
typePath(valuePath) {
|
|
234
278
|
return valuePathToTypePath(this.type, valuePath, true);
|
|
235
279
|
}
|
|
236
|
-
|
|
237
|
-
return this.internalSetFieldValue(valuePath, value,
|
|
238
|
-
}
|
|
239
|
-
setFieldValue(valuePath, value) {
|
|
240
|
-
return this.internalSetFieldValue(valuePath, value, false);
|
|
280
|
+
setFieldValue(valuePath, value, validation = this.validation[valuePath]) {
|
|
281
|
+
return this.internalSetFieldValue(valuePath, value, validation);
|
|
241
282
|
}
|
|
242
283
|
addListItem(valuePath,
|
|
243
284
|
// TODO can this type be simplified?
|
|
@@ -300,9 +341,9 @@ let FormModel = (() => {
|
|
|
300
341
|
const fieldOverride = this.fieldOverrides[fromJsonPath];
|
|
301
342
|
delete this.fieldOverrides[fromJsonPath];
|
|
302
343
|
this.fieldOverrides[toJsonPath] = fieldOverride;
|
|
303
|
-
const
|
|
304
|
-
delete this.
|
|
305
|
-
this.
|
|
344
|
+
const validation = this.validation[fromJsonPath];
|
|
345
|
+
delete this.validation[fromJsonPath];
|
|
346
|
+
this.validation[toJsonPath] = validation;
|
|
306
347
|
});
|
|
307
348
|
accessor.set(newList);
|
|
308
349
|
// delete any value overrides so the new list isn't shadowed
|
|
@@ -354,9 +395,9 @@ let FormModel = (() => {
|
|
|
354
395
|
const fieldOverride = this.fieldOverrides[fromJsonPath];
|
|
355
396
|
delete this.fieldOverrides[fromJsonPath];
|
|
356
397
|
this.fieldOverrides[toJsonPath] = fieldOverride;
|
|
357
|
-
const
|
|
358
|
-
delete this.
|
|
359
|
-
this.
|
|
398
|
+
const validation = this.validation[fromJsonPath];
|
|
399
|
+
delete this.validation[fromJsonPath];
|
|
400
|
+
this.validation[toJsonPath] = validation;
|
|
360
401
|
});
|
|
361
402
|
accessor.set(newList);
|
|
362
403
|
// delete any value overrides so the new list isn't shadowed
|
|
@@ -365,7 +406,7 @@ let FormModel = (() => {
|
|
|
365
406
|
});
|
|
366
407
|
});
|
|
367
408
|
}
|
|
368
|
-
internalSetFieldValue(valuePath, value,
|
|
409
|
+
internalSetFieldValue(valuePath, value, validation) {
|
|
369
410
|
const { revert } = this.getAdapterForValuePath(valuePath);
|
|
370
411
|
assertExists(revert, 'setting value not supported {}', valuePath);
|
|
371
412
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
@@ -373,17 +414,19 @@ let FormModel = (() => {
|
|
|
373
414
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
374
415
|
return runInAction(() => {
|
|
375
416
|
this.fieldOverrides[valuePath] = [value];
|
|
417
|
+
if (validation != null) {
|
|
418
|
+
this.validation[valuePath] = validation;
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
delete this.validation[valuePath];
|
|
422
|
+
}
|
|
376
423
|
switch (conversion.type) {
|
|
377
424
|
case UnreliableFieldConversionType.Failure:
|
|
378
|
-
if (displayValidation) {
|
|
379
|
-
this.errors[valuePath] = conversion.error;
|
|
380
|
-
}
|
|
381
425
|
if (conversion.value != null && accessor != null) {
|
|
382
426
|
accessor.set(conversion.value[0]);
|
|
383
427
|
}
|
|
384
428
|
return false;
|
|
385
429
|
case UnreliableFieldConversionType.Success:
|
|
386
|
-
delete this.errors[valuePath];
|
|
387
430
|
accessor === null || accessor === void 0 ? void 0 : accessor.set(conversion.value);
|
|
388
431
|
return true;
|
|
389
432
|
default:
|
|
@@ -395,7 +438,7 @@ let FormModel = (() => {
|
|
|
395
438
|
const fieldOverride = this.fieldOverrides[valuePath];
|
|
396
439
|
if (fieldOverride != null) {
|
|
397
440
|
runInAction(() => {
|
|
398
|
-
delete this.
|
|
441
|
+
delete this.validation[valuePath];
|
|
399
442
|
});
|
|
400
443
|
}
|
|
401
444
|
}
|
|
@@ -415,11 +458,12 @@ let FormModel = (() => {
|
|
|
415
458
|
const key = valuePath;
|
|
416
459
|
runInAction(() => {
|
|
417
460
|
this.fieldOverrides[key] = [displayValue];
|
|
461
|
+
delete this.validation[key];
|
|
418
462
|
});
|
|
419
463
|
}
|
|
420
464
|
clearAll(value) {
|
|
421
465
|
runInAction(() => {
|
|
422
|
-
this.
|
|
466
|
+
this.validation = {};
|
|
423
467
|
// TODO this isn't correct, should reload from value
|
|
424
468
|
this.fieldOverrides = {};
|
|
425
469
|
this.value = mobxCopy(this.type, value);
|
|
@@ -431,121 +475,43 @@ let FormModel = (() => {
|
|
|
431
475
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
432
476
|
return keys.has(valuePath);
|
|
433
477
|
}
|
|
434
|
-
validateField(valuePath,
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const { value: storedValue, } = convert(accessor != null
|
|
440
|
-
? accessor.value
|
|
441
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
442
|
-
: create(valuePath, context),
|
|
443
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
444
|
-
valuePath, context);
|
|
445
|
-
const value = fieldOverride != null
|
|
446
|
-
? fieldOverride[0]
|
|
447
|
-
: storedValue;
|
|
448
|
-
const dirty = storedValue !== value;
|
|
449
|
-
assertExists(revert, 'changing field directly not supported {}', valuePath);
|
|
450
|
-
if (ignoreDefaultValue) {
|
|
451
|
-
const { value: defaultDisplayValue, } = convert(create(valuePath, context), valuePath, context);
|
|
452
|
-
if (defaultDisplayValue === value) {
|
|
453
|
-
return true;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
457
|
-
const conversion = revert(value, valuePath, context);
|
|
458
|
-
return runInAction(() => {
|
|
459
|
-
switch (conversion.type) {
|
|
460
|
-
case UnreliableFieldConversionType.Failure:
|
|
461
|
-
this.errors[valuePath] = conversion.error;
|
|
462
|
-
if (conversion.value != null && accessor != null && dirty) {
|
|
463
|
-
accessor.set(conversion.value[0]);
|
|
464
|
-
}
|
|
465
|
-
return false;
|
|
466
|
-
case UnreliableFieldConversionType.Success:
|
|
467
|
-
delete this.errors[valuePath];
|
|
468
|
-
if (accessor != null && dirty) {
|
|
469
|
-
accessor.set(conversion.value);
|
|
470
|
-
}
|
|
471
|
-
return true;
|
|
472
|
-
default:
|
|
473
|
-
throw new UnreachableError(conversion);
|
|
474
|
-
}
|
|
478
|
+
validateField(valuePath, validation) {
|
|
479
|
+
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); }
|
|
481
|
+
runInAction(() => {
|
|
482
|
+
this.validation[valuePath] = validation;
|
|
475
483
|
});
|
|
484
|
+
return this.fields[valuePath].error == null;
|
|
476
485
|
}
|
|
477
|
-
validateAll(
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
});
|
|
482
|
-
const flattenedOriginalValues = flattenValuesOfType(this.type, this.originalValue);
|
|
483
|
-
return runInAction(() => {
|
|
484
|
-
return accessors.reduce((success, [valuePath, accessor,]) => {
|
|
485
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
486
|
-
const adapterPath = valuePath;
|
|
487
|
-
const adapter = this.maybeGetAdapterForValuePath(adapterPath);
|
|
488
|
-
if (adapter == null) {
|
|
489
|
-
// no adapter == there should be nothing specified for this field
|
|
490
|
-
return success;
|
|
491
|
-
}
|
|
492
|
-
const { convert, revert, } = adapter;
|
|
493
|
-
if (revert == null) {
|
|
494
|
-
// no convert method means this field is immutable
|
|
495
|
-
return success;
|
|
496
|
-
}
|
|
497
|
-
const fieldOverride = this.fieldOverrides[adapterPath];
|
|
486
|
+
validateAll(validation = this.mode === 'create' ? Validation.Always : Validation.Changed) {
|
|
487
|
+
const accessors = toArray(this.accessors);
|
|
488
|
+
runInAction(() => {
|
|
489
|
+
accessors.forEach(([valuePath]) => {
|
|
498
490
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const needsValidation = force
|
|
507
|
-
|| !(valuePath in flattenedOriginalValues)
|
|
508
|
-
|| storedValue !== convert(flattenedOriginalValues[valuePath], valuePath,
|
|
509
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
510
|
-
this.toContext(this.originalValue, valuePath)).value;
|
|
511
|
-
if (needsValidation) {
|
|
512
|
-
const conversion = revert(value, valuePath, context);
|
|
513
|
-
switch (conversion.type) {
|
|
514
|
-
case UnreliableFieldConversionType.Failure:
|
|
515
|
-
this.errors[adapterPath] = conversion.error;
|
|
516
|
-
if (conversion.value != null && dirty) {
|
|
517
|
-
accessor.set(conversion.value[0]);
|
|
518
|
-
}
|
|
519
|
-
return false;
|
|
520
|
-
case UnreliableFieldConversionType.Success:
|
|
521
|
-
if (dirty) {
|
|
522
|
-
accessor.set(conversion.value);
|
|
523
|
-
}
|
|
524
|
-
delete this.errors[adapterPath];
|
|
525
|
-
return success;
|
|
526
|
-
default:
|
|
527
|
-
throw new UnreachableError(conversion);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
return success;
|
|
531
|
-
}, true);
|
|
491
|
+
this.validation[valuePath] = validation;
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
return accessors.every(([valuePath]) => {
|
|
495
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
496
|
+
const field = this.fields[valuePath];
|
|
497
|
+
return (field === null || field === void 0 ? void 0 : field.error) == null;
|
|
532
498
|
});
|
|
533
499
|
}
|
|
534
500
|
},
|
|
535
501
|
_FormModel_value_accessor_storage = new WeakMap(),
|
|
536
502
|
_FormModel_fieldOverrides_accessor_storage = new WeakMap(),
|
|
537
|
-
|
|
503
|
+
_FormModel_validation_accessor_storage = new WeakMap(),
|
|
538
504
|
(() => {
|
|
539
505
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
540
506
|
_value_decorators = [(_b = observable).ref.bind(_b)];
|
|
541
507
|
_fieldOverrides_decorators = [(_c = observable).shallow.bind(_c)];
|
|
542
|
-
|
|
508
|
+
_validation_decorators = [(_d = observable).shallow.bind(_d)];
|
|
543
509
|
_get_fields_decorators = [computed];
|
|
544
510
|
_get_knownFields_decorators = [computed];
|
|
545
511
|
_get_accessors_decorators = [computed];
|
|
546
512
|
__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);
|
|
547
513
|
__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);
|
|
548
|
-
__esDecorate(_a, null,
|
|
514
|
+
__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);
|
|
549
515
|
__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);
|
|
550
516
|
__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);
|
|
551
517
|
__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);
|
package/.out/core/mobx/hooks.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useCallback, } from 'react';
|
|
2
2
|
export function useDefaultMobxFormHooks(model, { onValidFieldSubmit, onValidFormSubmit, } = {}) {
|
|
3
3
|
const onFieldValueChange = useCallback(function (path, value) {
|
|
4
|
-
//
|
|
5
|
-
model.
|
|
6
|
-
model.setFieldValue(path, value);
|
|
4
|
+
// clear any validation
|
|
5
|
+
model.setFieldValue(path, value, null);
|
|
7
6
|
}, [model]);
|
|
8
7
|
const onFieldSubmit = useCallback(function (valuePath) {
|
|
9
8
|
if (model.validateField(valuePath)) {
|
|
@@ -20,7 +19,7 @@ export function useDefaultMobxFormHooks(model, { onValidFieldSubmit, onValidForm
|
|
|
20
19
|
// TODO debounce?
|
|
21
20
|
setTimeout(function () {
|
|
22
21
|
if (model.isValuePathActive(path)) {
|
|
23
|
-
model.validateField(path
|
|
22
|
+
model.validateField(path);
|
|
24
23
|
}
|
|
25
24
|
}, 100);
|
|
26
25
|
}, [model]);
|
|
@@ -9,5 +9,5 @@ type ValuePathsOfFieldAdapters<FieldAdapters extends Readonly<Record<string, Fie
|
|
|
9
9
|
type TosOfFieldAdapters<FieldAdapters extends Readonly<Record<string, FieldAdapter>>> = {
|
|
10
10
|
[K in keyof FieldAdapters]: ToOfFieldAdapter<FieldAdapters[K]>;
|
|
11
11
|
}[keyof FieldAdapters];
|
|
12
|
-
export declare function mergeFieldAdaptersWithTwoWayConverter<FieldAdapters extends Readonly<Record<string, FieldAdapter>>, E, Context
|
|
12
|
+
export declare function mergeFieldAdaptersWithTwoWayConverter<FieldAdapters extends Readonly<Record<string, FieldAdapter>>, E, Context, P extends ValuePathsOfFieldAdapters<FieldAdapters>>(fieldAdapters: FieldAdapters, converter: TwoWayFieldConverter<TosOfFieldAdapters<FieldAdapters>, TosOfFieldAdapters<FieldAdapters>, E, P, Context>): MergedOfFieldAdaptersWithTwoWayConverter<FieldAdapters, E, Context>;
|
|
13
13
|
export {};
|
|
@@ -35,7 +35,11 @@ export function mergeAdaptersWithValidators(adapters, validators) {
|
|
|
35
35
|
readonly: readonly1 || readonly2,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
acc[key] =
|
|
38
|
+
acc[key] = {
|
|
39
|
+
create: adapter.create.bind(adapter),
|
|
40
|
+
convert,
|
|
41
|
+
revert: adapter.revert && revert,
|
|
42
|
+
};
|
|
39
43
|
return acc;
|
|
40
44
|
}, {});
|
|
41
45
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { expectDefinedAndReturn } from '@strictly/base';
|
|
2
2
|
import { booleanType, flattenValidatorsOfValidatingType, list, nullType, numberType, object, record, stringType, union, } from '@strictly/define';
|
|
3
3
|
import { adapterFromTwoWayConverter, identityAdapter, } from 'core/mobx/field_adapter_builder';
|
|
4
|
-
import { FormModel, } from 'core/mobx/form_model';
|
|
4
|
+
import { FormModel, Validation, } from 'core/mobx/form_model';
|
|
5
5
|
import { mergeAdaptersWithValidators } from 'core/mobx/merge_field_adapters_with_validators';
|
|
6
6
|
import { IntegerToStringConverter } from 'field_converters/integer_to_string_converter';
|
|
7
7
|
import { NullableToBooleanConverter } from 'field_converters/nullable_to_boolean_converter';
|
|
@@ -19,6 +19,9 @@ class TestFormModel extends FormModel {
|
|
|
19
19
|
valuePath,
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
setFieldValueAndValidate(valuePath, value) {
|
|
23
|
+
this.setFieldValue(valuePath, value, Validation.Always);
|
|
24
|
+
}
|
|
22
25
|
}
|
|
23
26
|
describe('all', function () {
|
|
24
27
|
const integerToStringAdapter = createMockedAdapter(originalIntegerToStringAdapter);
|
|
@@ -336,10 +339,10 @@ describe('all', function () {
|
|
|
336
339
|
});
|
|
337
340
|
describe('conversion succeeds, but validation fails', function () {
|
|
338
341
|
const newValue = -1;
|
|
339
|
-
const errorCode =
|
|
342
|
+
const errorCode = IS_NAN_ERROR;
|
|
340
343
|
beforeEach(function () {
|
|
341
344
|
var _a;
|
|
342
|
-
(_a = integerToStringAdapter.revert) === null || _a === void 0 ? void 0 : _a.
|
|
345
|
+
(_a = integerToStringAdapter.revert) === null || _a === void 0 ? void 0 : _a.mockReturnValue({
|
|
343
346
|
type: UnreliableFieldConversionType.Failure,
|
|
344
347
|
error: errorCode,
|
|
345
348
|
value: [newValue],
|
|
@@ -527,9 +530,10 @@ describe('all', function () {
|
|
|
527
530
|
describe('addListItem', function () {
|
|
528
531
|
describe('adds default to start of the list', function () {
|
|
529
532
|
beforeEach(function () {
|
|
530
|
-
model.
|
|
531
|
-
model.
|
|
532
|
-
model.
|
|
533
|
+
model.setFieldValue('$.0', 'x');
|
|
534
|
+
model.setFieldValue('$.1', '3');
|
|
535
|
+
model.setFieldValue('$.2', 'z');
|
|
536
|
+
model.validateAll();
|
|
533
537
|
model.addListItem('$', null, 0);
|
|
534
538
|
});
|
|
535
539
|
it('adds the list item to the underlying value', function () {
|
|
@@ -547,7 +551,7 @@ describe('all', function () {
|
|
|
547
551
|
],
|
|
548
552
|
[
|
|
549
553
|
'$.1',
|
|
550
|
-
'
|
|
554
|
+
'x',
|
|
551
555
|
],
|
|
552
556
|
[
|
|
553
557
|
'$.2',
|
|
@@ -555,7 +559,7 @@ describe('all', function () {
|
|
|
555
559
|
],
|
|
556
560
|
[
|
|
557
561
|
'$.3',
|
|
558
|
-
'
|
|
562
|
+
'z',
|
|
559
563
|
],
|
|
560
564
|
])('it reports the value of field %s as %s', function (path, fieldValue) {
|
|
561
565
|
var _a;
|
|
@@ -568,15 +572,15 @@ describe('all', function () {
|
|
|
568
572
|
],
|
|
569
573
|
[
|
|
570
574
|
'$.1',
|
|
571
|
-
|
|
575
|
+
IS_NAN_ERROR,
|
|
572
576
|
],
|
|
573
577
|
[
|
|
574
578
|
'$.2',
|
|
575
|
-
|
|
579
|
+
undefined,
|
|
576
580
|
],
|
|
577
581
|
[
|
|
578
582
|
'$.3',
|
|
579
|
-
|
|
583
|
+
IS_NAN_ERROR,
|
|
580
584
|
],
|
|
581
585
|
])('it reports the error of field %s', function (path, error) {
|
|
582
586
|
var _a;
|
|
@@ -615,9 +619,10 @@ describe('all', function () {
|
|
|
615
619
|
});
|
|
616
620
|
describe('removeListItem', function () {
|
|
617
621
|
beforeEach(function () {
|
|
618
|
-
model.
|
|
619
|
-
model.
|
|
620
|
-
model.
|
|
622
|
+
model.setFieldValue('$.0', 'x');
|
|
623
|
+
model.setFieldValue('$.1', '3');
|
|
624
|
+
model.setFieldValue('$.2', 'z');
|
|
625
|
+
model.validateAll();
|
|
621
626
|
});
|
|
622
627
|
describe('remove first item', function () {
|
|
623
628
|
beforeEach(function () {
|
|
@@ -633,11 +638,11 @@ describe('all', function () {
|
|
|
633
638
|
expect(model.fields).toEqual({
|
|
634
639
|
'$.0': expect.objectContaining({
|
|
635
640
|
value: '3',
|
|
636
|
-
error:
|
|
641
|
+
error: undefined,
|
|
637
642
|
}),
|
|
638
643
|
'$.1': expect.objectContaining({
|
|
639
|
-
value: '
|
|
640
|
-
error:
|
|
644
|
+
value: 'z',
|
|
645
|
+
error: IS_NAN_ERROR,
|
|
641
646
|
}),
|
|
642
647
|
});
|
|
643
648
|
});
|
|
@@ -655,12 +660,12 @@ describe('all', function () {
|
|
|
655
660
|
it('updates the field values and errors', function () {
|
|
656
661
|
expect(model.fields).toEqual({
|
|
657
662
|
'$.0': expect.objectContaining({
|
|
658
|
-
value: '
|
|
659
|
-
error:
|
|
663
|
+
value: 'x',
|
|
664
|
+
error: IS_NAN_ERROR,
|
|
660
665
|
}),
|
|
661
666
|
'$.1': expect.objectContaining({
|
|
662
|
-
value: '
|
|
663
|
-
error:
|
|
667
|
+
value: 'z',
|
|
668
|
+
error: IS_NAN_ERROR,
|
|
664
669
|
}),
|
|
665
670
|
});
|
|
666
671
|
});
|
|
@@ -675,8 +680,8 @@ describe('all', function () {
|
|
|
675
680
|
it('updates the field values and errors', function () {
|
|
676
681
|
expect(model.fields).toEqual({
|
|
677
682
|
'$.0': expect.objectContaining({
|
|
678
|
-
value: '
|
|
679
|
-
error:
|
|
683
|
+
value: 'z',
|
|
684
|
+
error: IS_NAN_ERROR,
|
|
680
685
|
}),
|
|
681
686
|
});
|
|
682
687
|
});
|
package/.out/core/props.d.ts
CHANGED
|
@@ -6,9 +6,7 @@ export type FieldsViewProps<F extends Fields> = {
|
|
|
6
6
|
onFieldBlur?(this: void, key: keyof F): void;
|
|
7
7
|
onFieldSubmit?(this: void, key: keyof F): boolean | void;
|
|
8
8
|
};
|
|
9
|
-
export type FormMode = 'edit' | 'create';
|
|
10
9
|
export type FormProps<O> = {
|
|
11
10
|
value: O;
|
|
12
11
|
onValueChange: (value: O) => void;
|
|
13
|
-
mode: FormMode;
|
|
14
12
|
};
|