@strictly/react-form 0.0.13 → 0.0.14
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.d.ts +1 -0
- package/.out/core/mobx/form_model.d.ts +3 -1
- package/.out/core/mobx/form_model.js +23 -15
- package/.out/core/mobx/hooks.d.ts +2 -2
- package/.out/core/mobx/merge_field_adapters_with_two_way_converter.d.ts +2 -2
- package/.out/core/mobx/specs/form_model.tests.js +26 -21
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +14 -21
- package/.out/core/mobx/sub_form_field_adapters.d.ts +5 -6
- package/.out/core/mobx/sub_form_field_adapters.js +6 -11
- package/.out/core/mobx/types.d.ts +3 -3
- package/.out/index.d.ts +2 -0
- package/.out/index.js +2 -0
- package/.out/mantine/field_view.d.ts +18 -0
- package/.out/mantine/field_view.js +16 -0
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/util/empty.d.ts +1 -0
- package/.out/util/empty.js +3 -0
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/core/mobx/field_adapter.ts +9 -0
- package/core/mobx/form_model.ts +26 -16
- package/core/mobx/hooks.tsx +2 -2
- package/core/mobx/merge_field_adapters_with_two_way_converter.ts +2 -1
- package/core/mobx/specs/form_model.tests.ts +35 -20
- package/core/mobx/specs/sub_form_field_adapters.tests.ts +14 -34
- package/core/mobx/sub_form_field_adapters.ts +11 -26
- package/core/mobx/types.ts +10 -7
- package/dist/index.cjs +99 -67
- package/dist/index.d.cts +32 -12
- package/dist/index.d.ts +32 -12
- package/dist/index.js +77 -49
- package/index.ts +2 -0
- package/mantine/field_view.tsx +39 -0
- package/package.json +1 -1
- package/util/empty.tsx +3 -0
|
@@ -8,3 +8,4 @@ export type FromOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<
|
|
|
8
8
|
export type ToOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<infer _F, infer To> ? To : never;
|
|
9
9
|
export type ErrorOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<infer _From, infer _To, infer E> ? NonNullable<E> : never;
|
|
10
10
|
export type ValuePathOfFieldAdapter<C extends FieldAdapter> = C extends FieldAdapter<infer _From, infer _To, infer _E, infer ValuePath> ? ValuePath : never;
|
|
11
|
+
export type ContextOfFieldAdapter<F extends FieldAdapter> = F extends FieldAdapter<infer _From, infer _To, infer _E, infer _P, infer Context> ? Context : never;
|
|
@@ -20,7 +20,7 @@ type FlattenedErrors<ValuePathsToAdapters extends Readonly<Record<string, FieldA
|
|
|
20
20
|
export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>, ValuePathsToTypePaths extends Readonly<Record<string, string>>> = keyof TypePathsToAdapters extends ValueOf<ValuePathsToTypePaths> ? {
|
|
21
21
|
readonly [K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>;
|
|
22
22
|
} : never;
|
|
23
|
-
export declare class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>,
|
|
23
|
+
export declare abstract class FormModel<T extends Type, ValueToTypePaths extends Readonly<Record<string, string>>, TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<FlattenedValuesOfType<T, '*'>, ContextType>, ContextType = {}, ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths>> {
|
|
24
24
|
readonly type: T;
|
|
25
25
|
protected readonly adapters: TypePathsToAdapters;
|
|
26
26
|
accessor value: MobxValueOfType<T>;
|
|
@@ -28,6 +28,8 @@ export declare class FormModel<T extends Type, ValueToTypePaths extends Readonly
|
|
|
28
28
|
accessor errors: FlattenedErrors<ValuePathsToAdapters>;
|
|
29
29
|
private readonly flattenedTypeDefs;
|
|
30
30
|
constructor(type: T, value: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters);
|
|
31
|
+
get context(): ContextType;
|
|
32
|
+
protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>): ContextType;
|
|
31
33
|
get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>;
|
|
32
34
|
private get knownFields();
|
|
33
35
|
private maybeSynthesizeFieldByValuePath;
|
|
@@ -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, _e;
|
|
53
53
|
let _instanceExtraInitializers = [];
|
|
54
54
|
let _value_decorators;
|
|
55
55
|
let _value_initializers = [];
|
|
@@ -60,6 +60,7 @@ let FormModel = (() => {
|
|
|
60
60
|
let _errors_decorators;
|
|
61
61
|
let _errors_initializers = [];
|
|
62
62
|
let _errors_extraInitializers = [];
|
|
63
|
+
let _get_context_decorators;
|
|
63
64
|
let _get_fields_decorators;
|
|
64
65
|
let _get_knownFields_decorators;
|
|
65
66
|
let _get_accessors_decorators;
|
|
@@ -94,6 +95,7 @@ let FormModel = (() => {
|
|
|
94
95
|
});
|
|
95
96
|
this.value = mobxCopy(type, value);
|
|
96
97
|
this.flattenedTypeDefs = flattenTypesOfType(type);
|
|
98
|
+
const contextValue = this.toContext(value);
|
|
97
99
|
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
98
100
|
// then returned to
|
|
99
101
|
const conversions = flattenValueTo(type, this.value, () => { }, (_t, value, _setter, typePath, valuePath) => {
|
|
@@ -107,13 +109,17 @@ let FormModel = (() => {
|
|
|
107
109
|
// no need to store a temporary value if the value cannot be written back
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
110
|
-
|
|
112
|
+
// cannot call this.context yet as the "this" pointer has not been fully created
|
|
113
|
+
return convert(value, valuePath, contextValue);
|
|
111
114
|
});
|
|
112
115
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
113
116
|
this.fieldOverrides = map(conversions, function (_k, v) {
|
|
114
117
|
return v && [v.value];
|
|
115
118
|
});
|
|
116
119
|
}
|
|
120
|
+
get context() {
|
|
121
|
+
return this.toContext(this.value);
|
|
122
|
+
}
|
|
117
123
|
get fields() {
|
|
118
124
|
return new Proxy(this.knownFields, {
|
|
119
125
|
get: (target, prop) => {
|
|
@@ -172,12 +178,12 @@ let FormModel = (() => {
|
|
|
172
178
|
: fieldTypeDef != null
|
|
173
179
|
? mobxCopy(fieldTypeDef,
|
|
174
180
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
175
|
-
create(valuePath, this.
|
|
181
|
+
create(valuePath, this.context))
|
|
176
182
|
// fake values can't be copied
|
|
177
183
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
178
|
-
: create(valuePath, this.
|
|
184
|
+
: create(valuePath, this.context),
|
|
179
185
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
180
|
-
valuePath, this.
|
|
186
|
+
valuePath, this.context);
|
|
181
187
|
const error = this.errors[valuePath];
|
|
182
188
|
return {
|
|
183
189
|
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
@@ -229,7 +235,7 @@ let FormModel = (() => {
|
|
|
229
235
|
? elementValue[0]
|
|
230
236
|
: elementAdapter.create(
|
|
231
237
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
232
|
-
elementTypePath, this.
|
|
238
|
+
elementTypePath, this.context);
|
|
233
239
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
234
240
|
const originalList = accessor.value;
|
|
235
241
|
const newList = [
|
|
@@ -340,7 +346,7 @@ let FormModel = (() => {
|
|
|
340
346
|
const { revert } = this.getAdapterForValuePath(valuePath);
|
|
341
347
|
assertExists(revert, 'setting value not supported {}', valuePath);
|
|
342
348
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
343
|
-
const conversion = revert(value, valuePath, this.
|
|
349
|
+
const conversion = revert(value, valuePath, this.context);
|
|
344
350
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
345
351
|
return runInAction(() => {
|
|
346
352
|
this.fieldOverrides[valuePath] = [value];
|
|
@@ -378,8 +384,8 @@ let FormModel = (() => {
|
|
|
378
384
|
return;
|
|
379
385
|
}
|
|
380
386
|
const { convert, create, } = adapter;
|
|
381
|
-
const value = create(valuePath, this.
|
|
382
|
-
const { value: displayValue, } = convert(value, valuePath, this.
|
|
387
|
+
const value = create(valuePath, this.context);
|
|
388
|
+
const { value: displayValue, } = convert(value, valuePath, this.context);
|
|
383
389
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
384
390
|
const key = valuePath;
|
|
385
391
|
runInAction(() => {
|
|
@@ -407,22 +413,22 @@ let FormModel = (() => {
|
|
|
407
413
|
const { value: storedValue, } = convert(accessor != null
|
|
408
414
|
? accessor.value
|
|
409
415
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
410
|
-
: create(valuePath, this.
|
|
416
|
+
: create(valuePath, this.context),
|
|
411
417
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
412
|
-
valuePath, this.
|
|
418
|
+
valuePath, this.context);
|
|
413
419
|
const value = fieldOverride != null
|
|
414
420
|
? fieldOverride[0]
|
|
415
421
|
: storedValue;
|
|
416
422
|
const dirty = storedValue !== value;
|
|
417
423
|
assertExists(revert, 'changing field directly not supported {}', valuePath);
|
|
418
424
|
if (ignoreDefaultValue) {
|
|
419
|
-
const { value: defaultDisplayValue, } = convert(create(valuePath, this.
|
|
425
|
+
const { value: defaultDisplayValue, } = convert(create(valuePath, this.context), valuePath, this.context);
|
|
420
426
|
if (defaultDisplayValue === value) {
|
|
421
427
|
return true;
|
|
422
428
|
}
|
|
423
429
|
}
|
|
424
430
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
425
|
-
const conversion = revert(value, valuePath, this.
|
|
431
|
+
const conversion = revert(value, valuePath, this.context);
|
|
426
432
|
return runInAction(() => {
|
|
427
433
|
switch (conversion.type) {
|
|
428
434
|
case UnreliableFieldConversionType.Failure:
|
|
@@ -462,13 +468,13 @@ let FormModel = (() => {
|
|
|
462
468
|
return success;
|
|
463
469
|
}
|
|
464
470
|
const fieldOverride = this.fieldOverrides[adapterPath];
|
|
465
|
-
const { value: storedValue, } = convert(accessor.value, valuePath, this.
|
|
471
|
+
const { value: storedValue, } = convert(accessor.value, valuePath, this.context);
|
|
466
472
|
const value = fieldOverride != null
|
|
467
473
|
? fieldOverride[0]
|
|
468
474
|
: storedValue;
|
|
469
475
|
// TODO more nuanced comparison
|
|
470
476
|
const dirty = fieldOverride != null && fieldOverride[0] !== storedValue;
|
|
471
|
-
const conversion = revert(value, valuePath, this.
|
|
477
|
+
const conversion = revert(value, valuePath, this.context);
|
|
472
478
|
switch (conversion.type) {
|
|
473
479
|
case UnreliableFieldConversionType.Failure:
|
|
474
480
|
this.errors[adapterPath] = conversion.error;
|
|
@@ -497,12 +503,14 @@ let FormModel = (() => {
|
|
|
497
503
|
_value_decorators = [(_b = observable).ref.bind(_b)];
|
|
498
504
|
_fieldOverrides_decorators = [(_c = observable).shallow.bind(_c)];
|
|
499
505
|
_errors_decorators = [(_d = observable).shallow.bind(_d)];
|
|
506
|
+
_get_context_decorators = [(_e = computed).struct.bind(_e)];
|
|
500
507
|
_get_fields_decorators = [computed];
|
|
501
508
|
_get_knownFields_decorators = [computed];
|
|
502
509
|
_get_accessors_decorators = [computed];
|
|
503
510
|
__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);
|
|
504
511
|
__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);
|
|
505
512
|
__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);
|
|
506
514
|
__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);
|
|
507
515
|
__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);
|
|
508
516
|
__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,8 +1,8 @@
|
|
|
1
1
|
import { type ReadonlyTypeOfType, type ValueOfType } from '@strictly/define';
|
|
2
2
|
import { type FormModel } from './form_model';
|
|
3
3
|
import { type FormFieldsOfModel, type ValuePathsOfModel } from './types';
|
|
4
|
-
type ValueOfModel<M extends FormModel<any, any, any, any>> = M extends FormModel<infer T, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
|
|
5
|
-
export declare function useDefaultMobxFormHooks<M extends FormModel<any, any, any, any>, F extends FormFieldsOfModel<M> = FormFieldsOfModel<M>>(model: M, { onValidFieldSubmit, onValidFormSubmit, }?: {
|
|
4
|
+
type ValueOfModel<M extends FormModel<any, any, any, any, any>> = M extends FormModel<infer T, any, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
|
|
5
|
+
export declare function useDefaultMobxFormHooks<M extends FormModel<any, any, any, any, any>, F extends FormFieldsOfModel<M> = FormFieldsOfModel<M>>(model: M, { onValidFieldSubmit, onValidFormSubmit, }?: {
|
|
6
6
|
onValidFieldSubmit?: <Path extends ValuePathsOfModel<M>>(valuePath: Path) => void;
|
|
7
7
|
onValidFormSubmit?: (value: ValueOfModel<M>) => void;
|
|
8
8
|
}): {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type TwoWayFieldConverter } from 'types/field_converters';
|
|
2
|
-
import { type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
|
|
2
|
+
import { type ContextOfFieldAdapter, type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
|
|
3
3
|
export type MergedOfFieldAdaptersWithTwoWayConverter<FieldAdapters extends Readonly<Record<string, FieldAdapter>>, E, Context> = {
|
|
4
|
-
[K in keyof FieldAdapters]: FieldAdapter<FromOfFieldAdapter<FieldAdapters[K]>, ToOfFieldAdapter<FieldAdapters[K]>, ErrorOfFieldAdapter<FieldAdapters[K]> | E, ValuePathOfFieldAdapter<FieldAdapters[K]>, Context>;
|
|
4
|
+
[K in keyof FieldAdapters]: FieldAdapter<FromOfFieldAdapter<FieldAdapters[K]>, ToOfFieldAdapter<FieldAdapters[K]>, ErrorOfFieldAdapter<FieldAdapters[K]> | E, ValuePathOfFieldAdapter<FieldAdapters[K]>, ContextOfFieldAdapter<FieldAdapters[K]> & Context>;
|
|
5
5
|
};
|
|
6
6
|
type ValuePathsOfFieldAdapters<FieldAdapters extends Readonly<Record<string, FieldAdapter>>> = {
|
|
7
7
|
[K in keyof FieldAdapters]: ValuePathOfFieldAdapter<FieldAdapters[K]>;
|
|
@@ -11,6 +11,13 @@ import { createMockedAdapter, resetMockAdapter, } from './fixtures';
|
|
|
11
11
|
const IS_NAN_ERROR = 1;
|
|
12
12
|
const originalIntegerToStringAdapter = adapterFromTwoWayConverter(new IntegerToStringConverter(IS_NAN_ERROR), prototypingFieldValueFactory(0));
|
|
13
13
|
const originalBooleanToBooleanAdapter = identityAdapter(false);
|
|
14
|
+
class TestFormModel extends FormModel {
|
|
15
|
+
toContext() {
|
|
16
|
+
return {
|
|
17
|
+
ctx: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
14
21
|
describe('all', function () {
|
|
15
22
|
const integerToStringAdapter = createMockedAdapter(originalIntegerToStringAdapter);
|
|
16
23
|
const booleanToBooleanAdapter = createMockedAdapter(originalBooleanToBooleanAdapter);
|
|
@@ -64,7 +71,7 @@ describe('all', function () {
|
|
|
64
71
|
let model;
|
|
65
72
|
beforeEach(function () {
|
|
66
73
|
originalValue = 5;
|
|
67
|
-
model = new
|
|
74
|
+
model = new TestFormModel(typeDef, originalValue, adapters);
|
|
68
75
|
});
|
|
69
76
|
describe('accessors', function () {
|
|
70
77
|
it('gets the expected value', function () {
|
|
@@ -105,7 +112,7 @@ describe('all', function () {
|
|
|
105
112
|
readonly: false,
|
|
106
113
|
});
|
|
107
114
|
originalValue = 5;
|
|
108
|
-
model = new
|
|
115
|
+
model = new TestFormModel(typeDef, originalValue, adapters);
|
|
109
116
|
});
|
|
110
117
|
it('reports required status', function () {
|
|
111
118
|
expect(model.fields).toEqual(expect.objectContaining({
|
|
@@ -130,7 +137,7 @@ describe('all', function () {
|
|
|
130
137
|
4,
|
|
131
138
|
17,
|
|
132
139
|
];
|
|
133
|
-
model = new
|
|
140
|
+
model = new TestFormModel(typeDef, value, adapters);
|
|
134
141
|
});
|
|
135
142
|
describe('accessors', function () {
|
|
136
143
|
it.each([
|
|
@@ -192,7 +199,7 @@ describe('all', function () {
|
|
|
192
199
|
a: 1,
|
|
193
200
|
b: 2,
|
|
194
201
|
};
|
|
195
|
-
model = new
|
|
202
|
+
model = new TestFormModel(typeDef, value, converters);
|
|
196
203
|
});
|
|
197
204
|
describe('accessors', function () {
|
|
198
205
|
it.each([
|
|
@@ -243,7 +250,7 @@ describe('all', function () {
|
|
|
243
250
|
a: 1,
|
|
244
251
|
b: true,
|
|
245
252
|
};
|
|
246
|
-
model = new
|
|
253
|
+
model = new TestFormModel(typeDef, value, converters);
|
|
247
254
|
});
|
|
248
255
|
describe('accessors', function () {
|
|
249
256
|
it.each([
|
|
@@ -289,7 +296,7 @@ describe('all', function () {
|
|
|
289
296
|
const originalValue = 2;
|
|
290
297
|
let model;
|
|
291
298
|
beforeEach(function () {
|
|
292
|
-
model = new
|
|
299
|
+
model = new TestFormModel(typeDef, originalValue, adapters);
|
|
293
300
|
});
|
|
294
301
|
describe('setFieldValueAndValidate', function () {
|
|
295
302
|
describe('success', function () {
|
|
@@ -391,7 +398,7 @@ describe('all', function () {
|
|
|
391
398
|
3,
|
|
392
399
|
7,
|
|
393
400
|
];
|
|
394
|
-
model = new
|
|
401
|
+
model = new TestFormModel(typeDef, originalValue, converters);
|
|
395
402
|
});
|
|
396
403
|
describe('setFieldValueAndValidate', function () {
|
|
397
404
|
describe('success', function () {
|
|
@@ -486,7 +493,7 @@ describe('all', function () {
|
|
|
486
493
|
let contextCopy;
|
|
487
494
|
beforeEach(function () {
|
|
488
495
|
integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
|
|
489
|
-
contextCopy =
|
|
496
|
+
contextCopy = Object.assign({}, context);
|
|
490
497
|
return {
|
|
491
498
|
type: UnreliableFieldConversionType.Success,
|
|
492
499
|
value: 1,
|
|
@@ -496,16 +503,14 @@ describe('all', function () {
|
|
|
496
503
|
it('supplies the full, previous context when converting', function () {
|
|
497
504
|
model.setFieldValueAndValidate('$.2', '4');
|
|
498
505
|
expect(integerToStringAdapter.revert).toHaveBeenCalledOnce();
|
|
499
|
-
expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2',
|
|
500
|
-
|
|
501
|
-
|
|
506
|
+
expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2', {
|
|
507
|
+
ctx: true,
|
|
508
|
+
});
|
|
502
509
|
});
|
|
503
|
-
it('supplies the context
|
|
504
|
-
expect(contextCopy).toEqual(
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
7,
|
|
508
|
-
]);
|
|
510
|
+
it('supplies the context', function () {
|
|
511
|
+
expect(contextCopy).toEqual({
|
|
512
|
+
ctx: true,
|
|
513
|
+
});
|
|
509
514
|
});
|
|
510
515
|
});
|
|
511
516
|
describe('addListItem', function () {
|
|
@@ -682,7 +687,7 @@ describe('all', function () {
|
|
|
682
687
|
let model;
|
|
683
688
|
beforeEach(function () {
|
|
684
689
|
originalValue = null;
|
|
685
|
-
model = new
|
|
690
|
+
model = new TestFormModel(type, originalValue, adapters);
|
|
686
691
|
});
|
|
687
692
|
it('has the expected fields', function () {
|
|
688
693
|
expect(model.fields).toEqual({
|
|
@@ -727,7 +732,7 @@ describe('all', function () {
|
|
|
727
732
|
};
|
|
728
733
|
describe('isValuePathActive', function () {
|
|
729
734
|
describe('discriminator x', function () {
|
|
730
|
-
const model = new
|
|
735
|
+
const model = new TestFormModel(type, {
|
|
731
736
|
d: 'x',
|
|
732
737
|
a: 1,
|
|
733
738
|
}, adapters);
|
|
@@ -750,7 +755,7 @@ describe('all', function () {
|
|
|
750
755
|
});
|
|
751
756
|
});
|
|
752
757
|
describe('discriminator y', function () {
|
|
753
|
-
const model = new
|
|
758
|
+
const model = new TestFormModel(type, {
|
|
754
759
|
d: 'y',
|
|
755
760
|
b: false,
|
|
756
761
|
}, adapters);
|
|
@@ -785,7 +790,7 @@ describe('all', function () {
|
|
|
785
790
|
let model;
|
|
786
791
|
beforeEach(function () {
|
|
787
792
|
originalValue = 1;
|
|
788
|
-
model = new
|
|
793
|
+
model = new TestFormModel(typeDef, originalValue, converters);
|
|
789
794
|
});
|
|
790
795
|
it('returns the default value for the fake field', function () {
|
|
791
796
|
expect(model.fields['$.fake']).toEqual(expect.objectContaining({
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { list, numberType, object, stringType, } from '@strictly/define';
|
|
2
1
|
import { subFormFieldAdapters, } from 'core/mobx/sub_form_field_adapters';
|
|
3
2
|
import { UnreliableFieldConversionType } from 'types/field_converters';
|
|
4
3
|
import { mockDeep, mockReset, } from 'vitest-mock-extended';
|
|
5
4
|
describe('subFormFieldAdapters', () => {
|
|
6
5
|
describe('empty value', () => {
|
|
7
|
-
const adapters = subFormFieldAdapters({}, '$.a'
|
|
6
|
+
const adapters = subFormFieldAdapters({}, '$.a');
|
|
8
7
|
it('equals expected type', () => {
|
|
9
8
|
expectTypeOf(adapters).toEqualTypeOf();
|
|
10
9
|
});
|
|
@@ -15,11 +14,10 @@ describe('subFormFieldAdapters', () => {
|
|
|
15
14
|
describe('single adapter', () => {
|
|
16
15
|
const mockedFieldAdapter1 = mockDeep();
|
|
17
16
|
const fieldAdapter1 = mockedFieldAdapter1;
|
|
18
|
-
const type = object().field('a', stringType);
|
|
19
17
|
const subAdapters = {
|
|
20
18
|
$: fieldAdapter1,
|
|
21
19
|
};
|
|
22
|
-
const adapters = subFormFieldAdapters(subAdapters, '$.a'
|
|
20
|
+
const adapters = subFormFieldAdapters(subAdapters, '$.a');
|
|
23
21
|
beforeEach(() => {
|
|
24
22
|
mockReset(mockedFieldAdapter1);
|
|
25
23
|
});
|
|
@@ -38,7 +36,7 @@ describe('subFormFieldAdapters', () => {
|
|
|
38
36
|
readonly: false,
|
|
39
37
|
};
|
|
40
38
|
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
|
|
41
|
-
const returnedValue = adapters['$.a'].convert('x', '$.a',
|
|
39
|
+
const returnedValue = adapters['$.a'].convert('x', '$.a', 'y');
|
|
42
40
|
expect(fieldAdapter1.convert).toHaveBeenCalledWith('x', '$', 'y');
|
|
43
41
|
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
44
42
|
});
|
|
@@ -49,14 +47,14 @@ describe('subFormFieldAdapters', () => {
|
|
|
49
47
|
value: 'ok',
|
|
50
48
|
};
|
|
51
49
|
mockedFieldAdapter1.revert.mockReturnValue(mockedReturnedValue);
|
|
52
|
-
const returnedValue = (_b = (_a = adapters['$.a']).revert) === null || _b === void 0 ? void 0 : _b.call(_a, true, '$.a',
|
|
50
|
+
const returnedValue = (_b = (_a = adapters['$.a']).revert) === null || _b === void 0 ? void 0 : _b.call(_a, true, '$.a', 'y');
|
|
53
51
|
expect(fieldAdapter1.revert).toHaveBeenCalledWith(true, '$', 'y');
|
|
54
52
|
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
55
53
|
});
|
|
56
54
|
it('calls create with the correct paths and values', () => {
|
|
57
55
|
const mockedReturnedValue = 'x';
|
|
58
56
|
mockedFieldAdapter1.create.mockReturnValue(mockedReturnedValue);
|
|
59
|
-
const returnedValue = adapters['$.a'].create('$.a',
|
|
57
|
+
const returnedValue = adapters['$.a'].create('$.a', 'y');
|
|
60
58
|
expect(fieldAdapter1.create).toHaveBeenCalledWith('$', 'y');
|
|
61
59
|
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
62
60
|
});
|
|
@@ -66,12 +64,10 @@ describe('subFormFieldAdapters', () => {
|
|
|
66
64
|
const fieldAdapter1 = mockedFieldAdapter1;
|
|
67
65
|
const mockedFieldAdapter2 = mockDeep();
|
|
68
66
|
const fieldAdapter2 = mockedFieldAdapter2;
|
|
69
|
-
const type = object()
|
|
70
|
-
.field('a', object().field('x', stringType).field('y', numberType));
|
|
71
67
|
const adapters = subFormFieldAdapters({
|
|
72
68
|
'$.x': fieldAdapter1,
|
|
73
69
|
'$.y': fieldAdapter2,
|
|
74
|
-
}, '$.a'
|
|
70
|
+
}, '$.a');
|
|
75
71
|
beforeEach(() => {
|
|
76
72
|
mockReset(mockedFieldAdapter1);
|
|
77
73
|
mockReset(mockedFieldAdapter2);
|
|
@@ -87,12 +83,11 @@ describe('subFormFieldAdapters', () => {
|
|
|
87
83
|
});
|
|
88
84
|
});
|
|
89
85
|
describe('calls convert with correct paths and values', () => {
|
|
90
|
-
const subContext = {
|
|
91
|
-
x: 'a',
|
|
92
|
-
y: 1,
|
|
93
|
-
};
|
|
94
86
|
const context = {
|
|
95
|
-
a:
|
|
87
|
+
a: {
|
|
88
|
+
x: 'a',
|
|
89
|
+
y: 1,
|
|
90
|
+
},
|
|
96
91
|
};
|
|
97
92
|
it('calls $.a.x', () => {
|
|
98
93
|
const mockedReturnedValue = {
|
|
@@ -102,7 +97,7 @@ describe('subFormFieldAdapters', () => {
|
|
|
102
97
|
};
|
|
103
98
|
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
|
|
104
99
|
const returnedValue = adapters['$.a.x'].convert('b', '$.a.x', context);
|
|
105
|
-
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x',
|
|
100
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x', context);
|
|
106
101
|
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
107
102
|
});
|
|
108
103
|
it('calls $.a.y', () => {
|
|
@@ -113,7 +108,7 @@ describe('subFormFieldAdapters', () => {
|
|
|
113
108
|
};
|
|
114
109
|
mockedFieldAdapter2.convert.mockReturnValue(mockedReturnedValue);
|
|
115
110
|
const returnedValue = adapters['$.a.y'].convert(2, '$.a.y', context);
|
|
116
|
-
expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y',
|
|
111
|
+
expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y', context);
|
|
117
112
|
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
118
113
|
});
|
|
119
114
|
});
|
|
@@ -121,11 +116,10 @@ describe('subFormFieldAdapters', () => {
|
|
|
121
116
|
describe('list adapter', () => {
|
|
122
117
|
const mockedFieldAdapter1 = mockDeep();
|
|
123
118
|
const fieldAdapter1 = mockedFieldAdapter1;
|
|
124
|
-
const type = list(stringType);
|
|
125
119
|
const subAdapters = {
|
|
126
120
|
$: fieldAdapter1,
|
|
127
121
|
};
|
|
128
|
-
const adapters = subFormFieldAdapters(subAdapters, '$.*'
|
|
122
|
+
const adapters = subFormFieldAdapters(subAdapters, '$.*');
|
|
129
123
|
beforeEach(() => {
|
|
130
124
|
mockReset(mockedFieldAdapter1);
|
|
131
125
|
});
|
|
@@ -140,7 +134,6 @@ describe('subFormFieldAdapters', () => {
|
|
|
140
134
|
});
|
|
141
135
|
describe('calls convert with correct paths and values', () => {
|
|
142
136
|
const subContext = 'a';
|
|
143
|
-
const context = [subContext];
|
|
144
137
|
it('calls $.*', () => {
|
|
145
138
|
const mockedReturnedValue = {
|
|
146
139
|
value: false,
|
|
@@ -148,7 +141,7 @@ describe('subFormFieldAdapters', () => {
|
|
|
148
141
|
required: false,
|
|
149
142
|
};
|
|
150
143
|
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
|
|
151
|
-
const returnedValue = adapters['$.*'].convert('b', '$.0',
|
|
144
|
+
const returnedValue = adapters['$.*'].convert('b', '$.0', subContext);
|
|
152
145
|
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$', subContext);
|
|
153
146
|
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
154
147
|
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { type StringConcatOf } from '@strictly/base';
|
|
2
|
-
import { type
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
[K in keyof SubAdapters as K extends StringConcatOf<'$', infer TypePathSuffix> ? `${TypePath}${TypePathSuffix}` : never]: SubFormFieldAdapter<SubAdapters[K], ValuePath, Context>;
|
|
2
|
+
import { type ContextOfFieldAdapter, type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter, type ValuePathOfFieldAdapter } from './field_adapter';
|
|
3
|
+
type SubFormFieldAdapter<F extends FieldAdapter, ValuePath extends string> = FieldAdapter<FromOfFieldAdapter<F>, ToOfFieldAdapter<F>, ErrorOfFieldAdapter<F>, ValuePathOfFieldAdapter<F> extends StringConcatOf<'$', infer ValuePathSuffix> ? `${ValuePath}${ValuePathSuffix}` : string, ContextOfFieldAdapter<F>>;
|
|
4
|
+
type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, ValuePath extends string> = {
|
|
5
|
+
[K in keyof SubAdapters as K extends StringConcatOf<'$', infer TypePathSuffix> ? `${TypePath}${TypePathSuffix}` : never]: SubFormFieldAdapter<SubAdapters[K], ValuePath>;
|
|
7
6
|
};
|
|
8
|
-
export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, TypePathsToValuePaths extends Record<TypePath, string
|
|
7
|
+
export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, TypePath extends string, TypePathsToValuePaths extends Record<TypePath, string>>(subAdapters: SubAdapters, parentTypePath: TypePath): SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath]>;
|
|
9
8
|
export {};
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
export function subFormFieldAdapters(subAdapters, parentTypePath, contextType) {
|
|
1
|
+
export function subFormFieldAdapters(subAdapters, parentTypePath) {
|
|
3
2
|
// assume the number of '.' in the type path will correspond to the number of '.' in the value path
|
|
4
3
|
const dotCount = parentTypePath.split('.').length;
|
|
5
|
-
function
|
|
4
|
+
function getSubValuePath(valuePath) {
|
|
6
5
|
const parentValuePath = valuePath.split('.').slice(0, dotCount).join('.');
|
|
7
6
|
const subValuePath = valuePath.replace(parentValuePath, '$');
|
|
8
|
-
|
|
9
|
-
return [
|
|
10
|
-
subValuePath,
|
|
11
|
-
subContext,
|
|
12
|
-
];
|
|
7
|
+
return subValuePath;
|
|
13
8
|
}
|
|
14
9
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
15
10
|
return Object.entries(subAdapters).reduce((acc, [subTypePath, subAdapter,]) => {
|
|
@@ -17,13 +12,13 @@ export function subFormFieldAdapters(subAdapters, parentTypePath, contextType) {
|
|
|
17
12
|
// adapt field adapter with new path and context
|
|
18
13
|
const adaptedAdapter = {
|
|
19
14
|
convert: (from, valuePath, context) => {
|
|
20
|
-
return subAdapter.convert(from,
|
|
15
|
+
return subAdapter.convert(from, getSubValuePath(valuePath), context);
|
|
21
16
|
},
|
|
22
17
|
create: (valuePath, context) => {
|
|
23
|
-
return subAdapter.create(
|
|
18
|
+
return subAdapter.create(getSubValuePath(valuePath), context);
|
|
24
19
|
},
|
|
25
20
|
revert: subAdapter.revert && ((from, valuePath, context) => {
|
|
26
|
-
return subAdapter.revert(from,
|
|
21
|
+
return subAdapter.revert(from, getSubValuePath(valuePath), context);
|
|
27
22
|
}),
|
|
28
23
|
};
|
|
29
24
|
acc[typePath] = adaptedAdapter;
|
|
@@ -3,12 +3,12 @@ import { type FlattenedConvertedFieldsOf, type FormModel } from './form_model';
|
|
|
3
3
|
/**
|
|
4
4
|
* Used to extract the supported value paths from a presenter
|
|
5
5
|
*/
|
|
6
|
-
export type ValuePathsOfModel<
|
|
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
9
|
* from a presenter
|
|
10
10
|
*/
|
|
11
|
-
export type ToValueOfModelValuePath<
|
|
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
13
|
* Extracts the form fields from the presenter. The recommended way is to
|
|
14
14
|
* define the form fields explicitly and use that type to enforce the types
|
|
@@ -16,4 +16,4 @@ export type ToValueOfModelValuePath<Presenter extends FormModel<any, any, any, a
|
|
|
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
|
*/
|
|
19
|
-
export type FormFieldsOfModel<
|
|
19
|
+
export type FormFieldsOfModel<Model extends FormModel<any, any, any, any, any>> = Model extends FormModel<infer _1, infer _2, infer _3, infer _4, infer ValuePathsToAdapters> ? FlattenedConvertedFieldsOf<ValuePathsToAdapters> : never;
|
package/.out/index.d.ts
CHANGED
|
@@ -17,9 +17,11 @@ 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';
|
|
20
21
|
export * from './mantine/hooks';
|
|
21
22
|
export * from './types/error_of_field';
|
|
22
23
|
export * from './types/field';
|
|
23
24
|
export * from './types/field_converters';
|
|
24
25
|
export * from './types/merge_validators';
|
|
26
|
+
export * from './util/empty';
|
|
25
27
|
export * from './util/partial';
|
package/.out/index.js
CHANGED
|
@@ -17,9 +17,11 @@ 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';
|
|
20
21
|
export * from './mantine/hooks';
|
|
21
22
|
export * from './types/error_of_field';
|
|
22
23
|
export * from './types/field';
|
|
23
24
|
export * from './types/field_converters';
|
|
24
25
|
export * from './types/merge_validators';
|
|
26
|
+
export * from './util/empty';
|
|
25
27
|
export * from './util/partial';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ComponentType } from 'react';
|
|
2
|
+
import { type ErrorOfField } from 'types/error_of_field';
|
|
3
|
+
import { type Fields } from 'types/field';
|
|
4
|
+
import { type ValueTypeOfField } from 'types/value_type_of_field';
|
|
5
|
+
/**
|
|
6
|
+
* Displays current value and error of a field
|
|
7
|
+
*/
|
|
8
|
+
export declare function FieldView<F extends Fields, K extends keyof F>({ fields, valuePath, children, }: {
|
|
9
|
+
fields: F;
|
|
10
|
+
valuePath: K;
|
|
11
|
+
children: (props: {
|
|
12
|
+
value: ValueTypeOfField<F[K]>;
|
|
13
|
+
error: ErrorOfField<F[K]> | undefined;
|
|
14
|
+
ErrorSink: ComponentType<{
|
|
15
|
+
error: ErrorOfField<F[K]>;
|
|
16
|
+
}>;
|
|
17
|
+
}) => JSX.Element;
|
|
18
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Observer } from 'mobx-react';
|
|
3
|
+
import { Empty } from 'util/empty';
|
|
4
|
+
/**
|
|
5
|
+
* Displays current value and error of a field
|
|
6
|
+
*/
|
|
7
|
+
export function FieldView({ fields, valuePath, children, }) {
|
|
8
|
+
return (_jsx(Observer, { children: () => {
|
|
9
|
+
const { value, error, } = fields[valuePath];
|
|
10
|
+
return children({
|
|
11
|
+
value,
|
|
12
|
+
error,
|
|
13
|
+
ErrorSink: Empty,
|
|
14
|
+
});
|
|
15
|
+
} }));
|
|
16
|
+
}
|