@strictly/react-form 0.0.15 → 0.0.17
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/field_adapter_builder.d.ts +1 -1
- package/.out/core/mobx/form_model.d.ts +9 -6
- package/.out/core/mobx/form_model.js +77 -42
- package/.out/core/mobx/specs/form_model.tests.js +80 -20
- package/.out/core/mobx/types.d.ts +4 -4
- package/.out/core/props.d.ts +2 -0
- package/.out/index.d.ts +0 -1
- package/.out/index.js +0 -1
- package/.out/mantine/create_field_view.d.ts +20 -0
- package/.out/mantine/create_field_view.js +54 -0
- package/.out/mantine/create_list.js +3 -2
- package/.out/mantine/hooks.d.ts +4 -1
- package/.out/mantine/hooks.js +14 -2
- package/.out/mantine/specs/field_view_hooks.stories.d.ts +12 -0
- package/.out/mantine/specs/field_view_hooks.stories.js +61 -0
- package/.out/mantine/specs/field_view_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/field_view_hooks.tests.js +12 -0
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/core/mobx/field_adapter_builder.ts +2 -2
- package/core/mobx/form_model.ts +89 -47
- package/core/mobx/specs/form_model.tests.ts +131 -11
- package/core/mobx/types.ts +4 -4
- package/core/props.ts +4 -0
- package/dist/index.cjs +165 -89
- package/dist/index.d.cts +45 -40
- package/dist/index.d.ts +45 -40
- package/dist/index.js +162 -81
- package/index.ts +0 -1
- package/mantine/create_field_view.tsx +94 -0
- package/mantine/create_list.tsx +9 -2
- package/mantine/hooks.tsx +19 -2
- package/mantine/specs/__snapshots__/field_view_hooks.tests.tsx.snap +153 -0
- package/mantine/specs/field_view_hooks.stories.tsx +112 -0
- package/mantine/specs/field_view_hooks.tests.tsx +15 -0
- package/package.json +1 -1
- package/.out/mantine/field_view.d.ts +0 -18
- package/.out/mantine/field_view.js +0 -16
- package/mantine/field_view.tsx +0 -39
|
@@ -21,5 +21,5 @@ export declare function adapterFromPrototype<From, To, ValuePath extends string,
|
|
|
21
21
|
export declare function adapterFromPrototype<From, To, E, ValuePath extends string, Context>(converter: TwoWayFieldConverter<From, To, E, ValuePath, Context>, prototype: From): FieldAdapterBuilder<From, To, E, ValuePath, Context>;
|
|
22
22
|
export declare function identityAdapter<V, ValuePath extends string, Context>(prototype: V, required?: boolean): FieldAdapterBuilder<V, V, never, ValuePath, Context>;
|
|
23
23
|
export declare function trimmingStringAdapter<ValuePath extends string, Context>(): FieldAdapterBuilder<string, string, never, ValuePath, Context>;
|
|
24
|
-
export declare function listAdapter<E, ValuePath extends string, Context>(): FieldAdapterBuilder<readonly E[], readonly E[], never, ValuePath, Context>;
|
|
24
|
+
export declare function listAdapter<E, ValuePath extends string = string, Context = unknown>(): FieldAdapterBuilder<readonly E[], readonly E[], never, ValuePath, Context>;
|
|
25
25
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
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';
|
|
3
4
|
import { type ReadonlyDeep, type SimplifyDeep, type StringKeyOf, type UnionToIntersection, type ValueOf } from 'type-fest';
|
|
4
5
|
import { type Field } from 'types/field';
|
|
5
6
|
import { type ContextOfFieldAdapter, type ErrorOfFieldAdapter, type FieldAdapter, type ToOfFieldAdapter } from './field_adapter';
|
|
@@ -21,18 +22,20 @@ export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<
|
|
|
21
22
|
readonly [K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>;
|
|
22
23
|
} : never;
|
|
23
24
|
export type ContextOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>> = UnionToIntersection<{
|
|
24
|
-
readonly [K in keyof TypePathsToAdapters]: TypePathsToAdapters[K] extends undefined ? undefined : ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>>;
|
|
25
|
-
}[keyof TypePathsToAdapters]>;
|
|
25
|
+
readonly [K in keyof TypePathsToAdapters]: TypePathsToAdapters[K] extends undefined ? undefined : unknown extends ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>> ? never : ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>>;
|
|
26
|
+
}[keyof TypePathsToAdapters] | {}>;
|
|
26
27
|
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>> {
|
|
27
28
|
readonly type: T;
|
|
29
|
+
private readonly originalValue;
|
|
28
30
|
protected readonly adapters: TypePathsToAdapters;
|
|
31
|
+
protected readonly mode: FormMode;
|
|
29
32
|
accessor value: MobxValueOfType<T>;
|
|
30
33
|
accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>;
|
|
31
34
|
accessor errors: FlattenedErrors<ValuePathsToAdapters>;
|
|
32
35
|
private readonly flattenedTypeDefs;
|
|
33
|
-
constructor(type: T,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
constructor(type: T, originalValue: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters, mode: FormMode);
|
|
37
|
+
protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>, valuePath: keyof ValuePathsToAdapters): ContextType;
|
|
38
|
+
get forceMutableFields(): boolean;
|
|
36
39
|
get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>;
|
|
37
40
|
private get knownFields();
|
|
38
41
|
private maybeSynthesizeFieldByValuePath;
|
|
@@ -52,6 +55,6 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
|
|
|
52
55
|
clearAll(value: ValueOfType<T>): void;
|
|
53
56
|
isValuePathActive<K extends keyof ValuePathsToAdapters>(valuePath: K): boolean;
|
|
54
57
|
validateField<K extends keyof ValuePathsToAdapters>(valuePath: K, ignoreDefaultValue?: boolean): boolean;
|
|
55
|
-
validateAll(): boolean;
|
|
58
|
+
validateAll(force?: boolean): boolean;
|
|
56
59
|
}
|
|
57
60
|
export {};
|
|
@@ -49,7 +49,7 @@ import { computed, observable, runInAction, } from 'mobx';
|
|
|
49
49
|
import { UnreliableFieldConversionType, } from 'types/field_converters';
|
|
50
50
|
let FormModel = (() => {
|
|
51
51
|
var _a, _FormModel_value_accessor_storage, _FormModel_fieldOverrides_accessor_storage, _FormModel_errors_accessor_storage;
|
|
52
|
-
var _b, _c, _d
|
|
52
|
+
var _b, _c, _d;
|
|
53
53
|
let _instanceExtraInitializers = [];
|
|
54
54
|
let _value_decorators;
|
|
55
55
|
let _value_initializers = [];
|
|
@@ -60,7 +60,6 @@ let FormModel = (() => {
|
|
|
60
60
|
let _errors_decorators;
|
|
61
61
|
let _errors_initializers = [];
|
|
62
62
|
let _errors_extraInitializers = [];
|
|
63
|
-
let _get_context_decorators;
|
|
64
63
|
let _get_fields_decorators;
|
|
65
64
|
let _get_knownFields_decorators;
|
|
66
65
|
let _get_accessors_decorators;
|
|
@@ -71,19 +70,31 @@ let FormModel = (() => {
|
|
|
71
70
|
set fieldOverrides(value) { __classPrivateFieldSet(this, _FormModel_fieldOverrides_accessor_storage, value, "f"); }
|
|
72
71
|
get errors() { return __classPrivateFieldGet(this, _FormModel_errors_accessor_storage, "f"); }
|
|
73
72
|
set errors(value) { __classPrivateFieldSet(this, _FormModel_errors_accessor_storage, value, "f"); }
|
|
74
|
-
constructor(type,
|
|
73
|
+
constructor(type, originalValue, adapters, mode) {
|
|
75
74
|
Object.defineProperty(this, "type", {
|
|
76
75
|
enumerable: true,
|
|
77
76
|
configurable: true,
|
|
78
77
|
writable: true,
|
|
79
78
|
value: (__runInitializers(this, _instanceExtraInitializers), type)
|
|
80
79
|
});
|
|
80
|
+
Object.defineProperty(this, "originalValue", {
|
|
81
|
+
enumerable: true,
|
|
82
|
+
configurable: true,
|
|
83
|
+
writable: true,
|
|
84
|
+
value: originalValue
|
|
85
|
+
});
|
|
81
86
|
Object.defineProperty(this, "adapters", {
|
|
82
87
|
enumerable: true,
|
|
83
88
|
configurable: true,
|
|
84
89
|
writable: true,
|
|
85
90
|
value: adapters
|
|
86
91
|
});
|
|
92
|
+
Object.defineProperty(this, "mode", {
|
|
93
|
+
enumerable: true,
|
|
94
|
+
configurable: true,
|
|
95
|
+
writable: true,
|
|
96
|
+
value: mode
|
|
97
|
+
});
|
|
87
98
|
_FormModel_value_accessor_storage.set(this, __runInitializers(this, _value_initializers, void 0));
|
|
88
99
|
_FormModel_fieldOverrides_accessor_storage.set(this, (__runInitializers(this, _value_extraInitializers), __runInitializers(this, _fieldOverrides_initializers, void 0)));
|
|
89
100
|
_FormModel_errors_accessor_storage.set(this, (__runInitializers(this, _fieldOverrides_extraInitializers), __runInitializers(this, _errors_initializers, {})));
|
|
@@ -93,12 +104,13 @@ let FormModel = (() => {
|
|
|
93
104
|
writable: true,
|
|
94
105
|
value: __runInitializers(this, _errors_extraInitializers)
|
|
95
106
|
});
|
|
96
|
-
this.value = mobxCopy(type,
|
|
107
|
+
this.value = mobxCopy(type, originalValue);
|
|
97
108
|
this.flattenedTypeDefs = flattenTypesOfType(type);
|
|
98
|
-
const contextValue = this.toContext(value);
|
|
99
109
|
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
100
110
|
// then returned to
|
|
101
|
-
const conversions = flattenValueTo(type, this.value, () => { }, (_t,
|
|
111
|
+
const conversions = flattenValueTo(type, this.value, () => { }, (_t, fieldValue, _setter, typePath, valuePath) => {
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
113
|
+
const contextValue = this.toContext(originalValue, valuePath);
|
|
102
114
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
103
115
|
const adapter = this.adapters[typePath];
|
|
104
116
|
if (adapter == null) {
|
|
@@ -110,15 +122,22 @@ let FormModel = (() => {
|
|
|
110
122
|
return;
|
|
111
123
|
}
|
|
112
124
|
// cannot call this.context yet as the "this" pointer has not been fully created
|
|
113
|
-
return convert(
|
|
125
|
+
return convert(fieldValue, valuePath, contextValue);
|
|
114
126
|
});
|
|
115
127
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
116
128
|
this.fieldOverrides = map(conversions, function (_k, v) {
|
|
117
129
|
return v && [v.value];
|
|
118
130
|
});
|
|
119
131
|
}
|
|
120
|
-
get
|
|
121
|
-
|
|
132
|
+
get forceMutableFields() {
|
|
133
|
+
switch (this.mode) {
|
|
134
|
+
case 'create':
|
|
135
|
+
return true;
|
|
136
|
+
case 'edit':
|
|
137
|
+
return false;
|
|
138
|
+
default:
|
|
139
|
+
return this.mode;
|
|
140
|
+
}
|
|
122
141
|
}
|
|
123
142
|
get fields() {
|
|
124
143
|
return new Proxy(this.knownFields, {
|
|
@@ -173,22 +192,23 @@ let FormModel = (() => {
|
|
|
173
192
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
174
193
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
175
194
|
const fieldTypeDef = this.flattenedTypeDefs[typePath];
|
|
195
|
+
const context = this.toContext(this.value, valuePath);
|
|
176
196
|
const { value, required, readonly, } = convert(accessor != null
|
|
177
197
|
? accessor.value
|
|
178
198
|
: fieldTypeDef != null
|
|
179
199
|
? mobxCopy(fieldTypeDef,
|
|
180
200
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
181
|
-
create(valuePath,
|
|
201
|
+
create(valuePath, context))
|
|
182
202
|
// fake values can't be copied
|
|
183
203
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
184
|
-
: create(valuePath,
|
|
204
|
+
: create(valuePath, context),
|
|
185
205
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
186
|
-
valuePath,
|
|
206
|
+
valuePath, context);
|
|
187
207
|
const error = this.errors[valuePath];
|
|
188
208
|
return {
|
|
189
209
|
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
190
210
|
error,
|
|
191
|
-
readonly,
|
|
211
|
+
readonly: readonly && !this.forceMutableFields,
|
|
192
212
|
required,
|
|
193
213
|
};
|
|
194
214
|
}
|
|
@@ -235,7 +255,10 @@ let FormModel = (() => {
|
|
|
235
255
|
? elementValue[0]
|
|
236
256
|
: elementAdapter.create(
|
|
237
257
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
238
|
-
elementTypePath,
|
|
258
|
+
elementTypePath,
|
|
259
|
+
// TODO what can we use for the value path here?
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
261
|
+
this.toContext(this.value, valuePath));
|
|
239
262
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
263
|
const originalList = accessor.value;
|
|
241
264
|
const newList = [
|
|
@@ -346,7 +369,7 @@ let FormModel = (() => {
|
|
|
346
369
|
const { revert } = this.getAdapterForValuePath(valuePath);
|
|
347
370
|
assertExists(revert, 'setting value not supported {}', valuePath);
|
|
348
371
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
349
|
-
const conversion = revert(value, valuePath, this.
|
|
372
|
+
const conversion = revert(value, valuePath, this.toContext(this.value, valuePath));
|
|
350
373
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
351
374
|
return runInAction(() => {
|
|
352
375
|
this.fieldOverrides[valuePath] = [value];
|
|
@@ -384,8 +407,10 @@ let FormModel = (() => {
|
|
|
384
407
|
return;
|
|
385
408
|
}
|
|
386
409
|
const { convert, create, } = adapter;
|
|
387
|
-
|
|
388
|
-
const
|
|
410
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
411
|
+
const context = this.toContext(this.value, valuePath);
|
|
412
|
+
const value = create(valuePath, context);
|
|
413
|
+
const { value: displayValue, } = convert(value, valuePath, context);
|
|
389
414
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
390
415
|
const key = valuePath;
|
|
391
416
|
runInAction(() => {
|
|
@@ -410,25 +435,26 @@ let FormModel = (() => {
|
|
|
410
435
|
const { convert, revert, create, } = this.getAdapterForValuePath(valuePath);
|
|
411
436
|
const fieldOverride = this.fieldOverrides[valuePath];
|
|
412
437
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
438
|
+
const context = this.toContext(this.value, valuePath);
|
|
413
439
|
const { value: storedValue, } = convert(accessor != null
|
|
414
440
|
? accessor.value
|
|
415
441
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
416
|
-
: create(valuePath,
|
|
442
|
+
: create(valuePath, context),
|
|
417
443
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
418
|
-
valuePath,
|
|
444
|
+
valuePath, context);
|
|
419
445
|
const value = fieldOverride != null
|
|
420
446
|
? fieldOverride[0]
|
|
421
447
|
: storedValue;
|
|
422
448
|
const dirty = storedValue !== value;
|
|
423
449
|
assertExists(revert, 'changing field directly not supported {}', valuePath);
|
|
424
450
|
if (ignoreDefaultValue) {
|
|
425
|
-
const { value: defaultDisplayValue, } = convert(create(valuePath,
|
|
451
|
+
const { value: defaultDisplayValue, } = convert(create(valuePath, context), valuePath, context);
|
|
426
452
|
if (defaultDisplayValue === value) {
|
|
427
453
|
return true;
|
|
428
454
|
}
|
|
429
455
|
}
|
|
430
456
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
431
|
-
const conversion = revert(value, valuePath,
|
|
457
|
+
const conversion = revert(value, valuePath, context);
|
|
432
458
|
return runInAction(() => {
|
|
433
459
|
switch (conversion.type) {
|
|
434
460
|
case UnreliableFieldConversionType.Failure:
|
|
@@ -448,11 +474,12 @@ let FormModel = (() => {
|
|
|
448
474
|
}
|
|
449
475
|
});
|
|
450
476
|
}
|
|
451
|
-
validateAll() {
|
|
477
|
+
validateAll(force = this.mode === 'create') {
|
|
452
478
|
// sort keys shortest to longest so parent changes don't overwrite child changes
|
|
453
479
|
const accessors = toArray(this.accessors).toSorted(function ([a], [b]) {
|
|
454
480
|
return a.length - b.length;
|
|
455
481
|
});
|
|
482
|
+
const flattenedOriginalValues = flattenValuesOfType(this.type, this.originalValue);
|
|
456
483
|
return runInAction(() => {
|
|
457
484
|
return accessors.reduce((success, [valuePath, accessor,]) => {
|
|
458
485
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -468,29 +495,39 @@ let FormModel = (() => {
|
|
|
468
495
|
return success;
|
|
469
496
|
}
|
|
470
497
|
const fieldOverride = this.fieldOverrides[adapterPath];
|
|
471
|
-
|
|
498
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
499
|
+
const context = this.toContext(this.value, valuePath);
|
|
500
|
+
const { value: storedValue, } = convert(accessor.value, valuePath, context);
|
|
472
501
|
const value = fieldOverride != null
|
|
473
502
|
? fieldOverride[0]
|
|
474
503
|
: storedValue;
|
|
475
|
-
// TODO
|
|
504
|
+
// TODO customizable comparisons
|
|
476
505
|
const dirty = fieldOverride != null && fieldOverride[0] !== storedValue;
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
+
}
|
|
493
529
|
}
|
|
530
|
+
return success;
|
|
494
531
|
}, true);
|
|
495
532
|
});
|
|
496
533
|
}
|
|
@@ -503,14 +540,12 @@ let FormModel = (() => {
|
|
|
503
540
|
_value_decorators = [(_b = observable).ref.bind(_b)];
|
|
504
541
|
_fieldOverrides_decorators = [(_c = observable).shallow.bind(_c)];
|
|
505
542
|
_errors_decorators = [(_d = observable).shallow.bind(_d)];
|
|
506
|
-
_get_context_decorators = [(_e = computed).struct.bind(_e)];
|
|
507
543
|
_get_fields_decorators = [computed];
|
|
508
544
|
_get_knownFields_decorators = [computed];
|
|
509
545
|
_get_accessors_decorators = [computed];
|
|
510
546
|
__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);
|
|
511
547
|
__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);
|
|
512
548
|
__esDecorate(_a, null, _errors_decorators, { kind: "accessor", name: "errors", static: false, private: false, access: { has: obj => "errors" in obj, get: obj => obj.errors, set: (obj, value) => { obj.errors = value; } }, metadata: _metadata }, _errors_initializers, _errors_extraInitializers);
|
|
513
|
-
__esDecorate(_a, null, _get_context_decorators, { kind: "getter", name: "context", static: false, private: false, access: { has: obj => "context" in obj, get: obj => obj.context }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
514
549
|
__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);
|
|
515
550
|
__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);
|
|
516
551
|
__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);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { expectDefinedAndReturn } from '@strictly/base';
|
|
2
|
-
import { booleanType, list, nullType, numberType, object, record, stringType, union, } from '@strictly/define';
|
|
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
4
|
import { FormModel, } from 'core/mobx/form_model';
|
|
5
|
+
import { mergeAdaptersWithValidators } from 'core/mobx/merge_field_adapters_with_validators';
|
|
5
6
|
import { IntegerToStringConverter } from 'field_converters/integer_to_string_converter';
|
|
6
7
|
import { NullableToBooleanConverter } from 'field_converters/nullable_to_boolean_converter';
|
|
7
8
|
import { SelectDiscriminatedUnionConverter } from 'field_converters/select_value_type_converter';
|
|
@@ -12,9 +13,10 @@ const IS_NAN_ERROR = 1;
|
|
|
12
13
|
const originalIntegerToStringAdapter = adapterFromTwoWayConverter(new IntegerToStringConverter(IS_NAN_ERROR), prototypingFieldValueFactory(0));
|
|
13
14
|
const originalBooleanToBooleanAdapter = identityAdapter(false);
|
|
14
15
|
class TestFormModel extends FormModel {
|
|
15
|
-
toContext() {
|
|
16
|
+
toContext(value, valuePath) {
|
|
16
17
|
return {
|
|
17
|
-
|
|
18
|
+
value,
|
|
19
|
+
valuePath,
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
}
|
|
@@ -71,7 +73,7 @@ describe('all', function () {
|
|
|
71
73
|
let model;
|
|
72
74
|
beforeEach(function () {
|
|
73
75
|
originalValue = 5;
|
|
74
|
-
model = new TestFormModel(typeDef, originalValue, adapters);
|
|
76
|
+
model = new TestFormModel(typeDef, originalValue, adapters, 'create');
|
|
75
77
|
});
|
|
76
78
|
describe('accessors', function () {
|
|
77
79
|
it('gets the expected value', function () {
|
|
@@ -112,7 +114,7 @@ describe('all', function () {
|
|
|
112
114
|
readonly: false,
|
|
113
115
|
});
|
|
114
116
|
originalValue = 5;
|
|
115
|
-
model = new TestFormModel(typeDef, originalValue, adapters);
|
|
117
|
+
model = new TestFormModel(typeDef, originalValue, adapters, 'create');
|
|
116
118
|
});
|
|
117
119
|
it('reports required status', function () {
|
|
118
120
|
expect(model.fields).toEqual(expect.objectContaining({
|
|
@@ -137,7 +139,7 @@ describe('all', function () {
|
|
|
137
139
|
4,
|
|
138
140
|
17,
|
|
139
141
|
];
|
|
140
|
-
model = new TestFormModel(typeDef, value, adapters);
|
|
142
|
+
model = new TestFormModel(typeDef, value, adapters, 'create');
|
|
141
143
|
});
|
|
142
144
|
describe('accessors', function () {
|
|
143
145
|
it.each([
|
|
@@ -199,7 +201,7 @@ describe('all', function () {
|
|
|
199
201
|
a: 1,
|
|
200
202
|
b: 2,
|
|
201
203
|
};
|
|
202
|
-
model = new TestFormModel(typeDef, value, converters);
|
|
204
|
+
model = new TestFormModel(typeDef, value, converters, 'create');
|
|
203
205
|
});
|
|
204
206
|
describe('accessors', function () {
|
|
205
207
|
it.each([
|
|
@@ -250,7 +252,7 @@ describe('all', function () {
|
|
|
250
252
|
a: 1,
|
|
251
253
|
b: true,
|
|
252
254
|
};
|
|
253
|
-
model = new TestFormModel(typeDef, value, converters);
|
|
255
|
+
model = new TestFormModel(typeDef, value, converters, 'create');
|
|
254
256
|
});
|
|
255
257
|
describe('accessors', function () {
|
|
256
258
|
it.each([
|
|
@@ -296,7 +298,7 @@ describe('all', function () {
|
|
|
296
298
|
const originalValue = 2;
|
|
297
299
|
let model;
|
|
298
300
|
beforeEach(function () {
|
|
299
|
-
model = new TestFormModel(typeDef, originalValue, adapters);
|
|
301
|
+
model = new TestFormModel(typeDef, originalValue, adapters, 'create');
|
|
300
302
|
});
|
|
301
303
|
describe('setFieldValueAndValidate', function () {
|
|
302
304
|
describe('success', function () {
|
|
@@ -398,7 +400,7 @@ describe('all', function () {
|
|
|
398
400
|
3,
|
|
399
401
|
7,
|
|
400
402
|
];
|
|
401
|
-
model = new TestFormModel(typeDef, originalValue, converters);
|
|
403
|
+
model = new TestFormModel(typeDef, originalValue, converters, 'create');
|
|
402
404
|
});
|
|
403
405
|
describe('setFieldValueAndValidate', function () {
|
|
404
406
|
describe('success', function () {
|
|
@@ -493,23 +495,32 @@ describe('all', function () {
|
|
|
493
495
|
let contextCopy;
|
|
494
496
|
beforeEach(function () {
|
|
495
497
|
integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
|
|
496
|
-
contextCopy =
|
|
498
|
+
contextCopy = JSON.stringify(context);
|
|
497
499
|
return {
|
|
498
500
|
type: UnreliableFieldConversionType.Success,
|
|
499
501
|
value: 1,
|
|
500
502
|
};
|
|
501
503
|
});
|
|
502
504
|
});
|
|
503
|
-
it('supplies the
|
|
505
|
+
it('supplies the context when converting', function () {
|
|
504
506
|
model.setFieldValueAndValidate('$.2', '4');
|
|
505
507
|
expect(integerToStringAdapter.revert).toHaveBeenCalledOnce();
|
|
506
508
|
expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2', {
|
|
507
|
-
|
|
509
|
+
// the supplied value isn't a copy, so it will be the model value, even
|
|
510
|
+
// if the value has since changed
|
|
511
|
+
value: model.value,
|
|
512
|
+
valuePath: '$.2',
|
|
508
513
|
});
|
|
509
514
|
});
|
|
510
|
-
it('supplies the context', function () {
|
|
511
|
-
|
|
512
|
-
|
|
515
|
+
it('supplies the correct context value at the time it is being checked', function () {
|
|
516
|
+
// the copy will show the supplied value however
|
|
517
|
+
expect(JSON.parse(contextCopy)).toEqual({
|
|
518
|
+
value: [
|
|
519
|
+
1,
|
|
520
|
+
3,
|
|
521
|
+
7,
|
|
522
|
+
],
|
|
523
|
+
valuePath: '$.2',
|
|
513
524
|
});
|
|
514
525
|
});
|
|
515
526
|
});
|
|
@@ -687,7 +698,7 @@ describe('all', function () {
|
|
|
687
698
|
let model;
|
|
688
699
|
beforeEach(function () {
|
|
689
700
|
originalValue = null;
|
|
690
|
-
model = new TestFormModel(type, originalValue, adapters);
|
|
701
|
+
model = new TestFormModel(type, originalValue, adapters, 'create');
|
|
691
702
|
});
|
|
692
703
|
it('has the expected fields', function () {
|
|
693
704
|
expect(model.fields).toEqual({
|
|
@@ -735,7 +746,7 @@ describe('all', function () {
|
|
|
735
746
|
const model = new TestFormModel(type, {
|
|
736
747
|
d: 'x',
|
|
737
748
|
a: 1,
|
|
738
|
-
}, adapters);
|
|
749
|
+
}, adapters, 'create');
|
|
739
750
|
it.each([
|
|
740
751
|
[
|
|
741
752
|
'$',
|
|
@@ -758,7 +769,7 @@ describe('all', function () {
|
|
|
758
769
|
const model = new TestFormModel(type, {
|
|
759
770
|
d: 'y',
|
|
760
771
|
b: false,
|
|
761
|
-
}, adapters);
|
|
772
|
+
}, adapters, 'create');
|
|
762
773
|
it.each([
|
|
763
774
|
[
|
|
764
775
|
'$',
|
|
@@ -790,7 +801,7 @@ describe('all', function () {
|
|
|
790
801
|
let model;
|
|
791
802
|
beforeEach(function () {
|
|
792
803
|
originalValue = 1;
|
|
793
|
-
model = new TestFormModel(typeDef, originalValue, converters);
|
|
804
|
+
model = new TestFormModel(typeDef, originalValue, converters, 'create');
|
|
794
805
|
});
|
|
795
806
|
it('returns the default value for the fake field', function () {
|
|
796
807
|
expect(model.fields['$.fake']).toEqual(expect.objectContaining({
|
|
@@ -811,5 +822,54 @@ describe('all', function () {
|
|
|
811
822
|
});
|
|
812
823
|
});
|
|
813
824
|
});
|
|
825
|
+
describe('interaction with create and edit modes', () => {
|
|
826
|
+
const typeDef = object().readonlyField('n', numberType.enforce(n => n < 10 ? 'err' : null));
|
|
827
|
+
const adapters = mergeAdaptersWithValidators({
|
|
828
|
+
$: identityAdapter({ n: 0 }),
|
|
829
|
+
'$.n': integerToStringAdapter,
|
|
830
|
+
}, flattenValidatorsOfValidatingType(typeDef));
|
|
831
|
+
let originalValue;
|
|
832
|
+
beforeEach(() => {
|
|
833
|
+
originalValue = {
|
|
834
|
+
n: 1,
|
|
835
|
+
};
|
|
836
|
+
});
|
|
837
|
+
describe('create mode', () => {
|
|
838
|
+
let model;
|
|
839
|
+
beforeEach(() => {
|
|
840
|
+
model = new TestFormModel(typeDef, originalValue, adapters, 'create');
|
|
841
|
+
});
|
|
842
|
+
it('makes the field editable', () => {
|
|
843
|
+
expect(model.fields['$.n'].readonly).toBeFalsy();
|
|
844
|
+
});
|
|
845
|
+
it('fails validation', () => {
|
|
846
|
+
expect(model.validateAll()).toBeFalsy();
|
|
847
|
+
});
|
|
848
|
+
it('passes validation with valid data', () => {
|
|
849
|
+
model.setFieldValue('$.n', '10');
|
|
850
|
+
expect(model.validateAll()).toBeTruthy();
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
describe('edit model', () => {
|
|
854
|
+
let model;
|
|
855
|
+
beforeEach(function () {
|
|
856
|
+
model = new TestFormModel(typeDef, originalValue, adapters, 'edit');
|
|
857
|
+
});
|
|
858
|
+
it('respects the field being readonly', () => {
|
|
859
|
+
expect(model.fields['$.n'].readonly).toBeTruthy();
|
|
860
|
+
});
|
|
861
|
+
it('validates successfully with clean, but invalid data', () => {
|
|
862
|
+
expect(model.validateAll()).toBeTruthy();
|
|
863
|
+
});
|
|
864
|
+
it('fails validation with invalid, dirty data', () => {
|
|
865
|
+
model.setFieldValue('$.n', '2');
|
|
866
|
+
expect(model.validateAll()).toBeFalsy();
|
|
867
|
+
});
|
|
868
|
+
it('passes validation with valid, dirty data', () => {
|
|
869
|
+
model.setFieldValue('$.n', '10');
|
|
870
|
+
expect(model.validateAll()).toBeTruthy();
|
|
871
|
+
});
|
|
872
|
+
});
|
|
873
|
+
});
|
|
814
874
|
});
|
|
815
875
|
});
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { type ToOfFieldAdapter } from './field_adapter';
|
|
2
2
|
import { type FlattenedConvertedFieldsOf, type FormModel } from './form_model';
|
|
3
3
|
/**
|
|
4
|
-
* Used to extract the supported value paths from a
|
|
4
|
+
* Used to extract the supported value paths from a model
|
|
5
5
|
*/
|
|
6
6
|
export type ValuePathsOfModel<Model extends FormModel<any, any, any, any, any>> = Model extends FormModel<infer _1, infer _2, infer _3, infer _4, infer ValuePathsToAdapters> ? keyof ValuePathsToAdapters : never;
|
|
7
7
|
/**
|
|
8
8
|
* Used to extract the render type (so the value that is passed to the view) of a given value path
|
|
9
|
-
* from a
|
|
9
|
+
* from a model
|
|
10
10
|
*/
|
|
11
11
|
export type ToValueOfModelValuePath<Model extends FormModel<any, any, any, any, any>, K extends ValuePathsOfModel<Model>> = Model extends FormModel<infer _1, infer _2, infer _3, infer _4, infer ValuePathsToAdapters> ? ToOfFieldAdapter<ValuePathsToAdapters[K]> : never;
|
|
12
12
|
/**
|
|
13
|
-
* Extracts the form fields from
|
|
13
|
+
* Extracts the form fields from a form model. The recommended way is to
|
|
14
14
|
* define the form fields explicitly and use that type to enforce the types
|
|
15
|
-
* of your converters, but generating the FormFields from your
|
|
15
|
+
* of your converters, but generating the FormFields from your model
|
|
16
16
|
* is less typing, albeit at the cost of potentially getting type errors
|
|
17
17
|
* reported a long way away from the source
|
|
18
18
|
*/
|
package/.out/core/props.d.ts
CHANGED
|
@@ -6,7 +6,9 @@ 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';
|
|
9
10
|
export type FormProps<O> = {
|
|
10
11
|
value: O;
|
|
11
12
|
onValueChange: (value: O) => void;
|
|
13
|
+
mode: FormMode;
|
|
12
14
|
};
|
package/.out/index.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export * from './field_converters/trimming_string_converter';
|
|
|
17
17
|
export * from './field_converters/validating_converter';
|
|
18
18
|
export * from './field_value_factories/prototyping_field_value_factory';
|
|
19
19
|
export * from './mantine/error_renderer';
|
|
20
|
-
export * from './mantine/field_view';
|
|
21
20
|
export * from './mantine/hooks';
|
|
22
21
|
export * from './types/error_of_field';
|
|
23
22
|
export * from './types/field';
|
package/.out/index.js
CHANGED
|
@@ -17,7 +17,6 @@ export * from './field_converters/trimming_string_converter';
|
|
|
17
17
|
export * from './field_converters/validating_converter';
|
|
18
18
|
export * from './field_value_factories/prototyping_field_value_factory';
|
|
19
19
|
export * from './mantine/error_renderer';
|
|
20
|
-
export * from './mantine/field_view';
|
|
21
20
|
export * from './mantine/hooks';
|
|
22
21
|
export * from './types/error_of_field';
|
|
23
22
|
export * from './types/field';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ComponentType } from 'react';
|
|
2
|
+
import { type AllFieldsOfFields } from 'types/all_fields_of_fields';
|
|
3
|
+
import { type ErrorOfField } from 'types/error_of_field';
|
|
4
|
+
import { type Fields } from 'types/field';
|
|
5
|
+
import { type ValueTypeOfField } from 'types/value_type_of_field';
|
|
6
|
+
import { type MantineForm } from './types';
|
|
7
|
+
export type FieldViewProps<F extends Fields, K extends keyof F> = {
|
|
8
|
+
children: (props: {
|
|
9
|
+
value: ValueTypeOfField<F[K]>;
|
|
10
|
+
error: ErrorOfField<F[K]> | undefined;
|
|
11
|
+
ErrorSink: ComponentType<{
|
|
12
|
+
error: ErrorOfField<F[K]>;
|
|
13
|
+
}>;
|
|
14
|
+
onFocus: () => void;
|
|
15
|
+
onBlur: () => void;
|
|
16
|
+
onValueChange: (v: ValueTypeOfField<F[K]>) => void;
|
|
17
|
+
onSubmit: () => void;
|
|
18
|
+
}) => JSX.Element;
|
|
19
|
+
};
|
|
20
|
+
export declare function createFieldView<F extends Fields, K extends keyof AllFieldsOfFields<F>>(this: MantineForm<F>, valuePath: K): ComponentType<FieldViewProps<F, K>>;
|