@strictly/react-form 0.0.15 → 0.0.16
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 +3 -4
- package/.out/core/mobx/form_model.js +27 -23
- package/.out/core/mobx/specs/form_model.tests.js +18 -8
- package/.out/core/mobx/types.d.ts +4 -4
- 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 +13 -1
- 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 +43 -27
- package/core/mobx/specs/form_model.tests.ts +28 -11
- package/core/mobx/types.ts +4 -4
- package/dist/index.cjs +124 -70
- package/dist/index.d.cts +26 -26
- package/dist/index.d.ts +26 -26
- package/dist/index.js +121 -62
- package/index.ts +0 -1
- package/mantine/create_field_view.tsx +94 -0
- package/mantine/create_list.tsx +9 -2
- package/mantine/hooks.tsx +18 -1
- 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 {};
|
|
@@ -21,8 +21,8 @@ export type ValuePathsToAdaptersOf<TypePathsToAdapters extends Partial<Readonly<
|
|
|
21
21
|
readonly [K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>;
|
|
22
22
|
} : never;
|
|
23
23
|
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]>;
|
|
24
|
+
readonly [K in keyof TypePathsToAdapters]: TypePathsToAdapters[K] extends undefined ? undefined : unknown extends ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>> ? never : ContextOfFieldAdapter<NonNullable<TypePathsToAdapters[K]>>;
|
|
25
|
+
}[keyof TypePathsToAdapters] | {}>;
|
|
26
26
|
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
27
|
readonly type: T;
|
|
28
28
|
protected readonly adapters: TypePathsToAdapters;
|
|
@@ -31,8 +31,7 @@ export declare abstract class FormModel<T extends Type, ValueToTypePaths extends
|
|
|
31
31
|
accessor errors: FlattenedErrors<ValuePathsToAdapters>;
|
|
32
32
|
private readonly flattenedTypeDefs;
|
|
33
33
|
constructor(type: T, value: ValueOfType<ReadonlyTypeOfType<T>>, adapters: TypePathsToAdapters);
|
|
34
|
-
|
|
35
|
-
protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>): ContextType;
|
|
34
|
+
protected abstract toContext(value: ValueOfType<ReadonlyTypeOfType<T>>, valuePath: keyof ValuePathsToAdapters): ContextType;
|
|
36
35
|
get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>;
|
|
37
36
|
private get knownFields();
|
|
38
37
|
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;
|
|
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;
|
|
@@ -95,10 +94,11 @@ let FormModel = (() => {
|
|
|
95
94
|
});
|
|
96
95
|
this.value = mobxCopy(type, value);
|
|
97
96
|
this.flattenedTypeDefs = flattenTypesOfType(type);
|
|
98
|
-
const contextValue = this.toContext(value);
|
|
99
97
|
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
100
98
|
// then returned to
|
|
101
|
-
const conversions = flattenValueTo(type, this.value, () => { }, (_t,
|
|
99
|
+
const conversions = flattenValueTo(type, this.value, () => { }, (_t, fieldValue, _setter, typePath, valuePath) => {
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
101
|
+
const contextValue = this.toContext(value, valuePath);
|
|
102
102
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
103
103
|
const adapter = this.adapters[typePath];
|
|
104
104
|
if (adapter == null) {
|
|
@@ -110,16 +110,13 @@ let FormModel = (() => {
|
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
// cannot call this.context yet as the "this" pointer has not been fully created
|
|
113
|
-
return convert(
|
|
113
|
+
return convert(fieldValue, valuePath, contextValue);
|
|
114
114
|
});
|
|
115
115
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
116
116
|
this.fieldOverrides = map(conversions, function (_k, v) {
|
|
117
117
|
return v && [v.value];
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
|
-
get context() {
|
|
121
|
-
return this.toContext(this.value);
|
|
122
|
-
}
|
|
123
120
|
get fields() {
|
|
124
121
|
return new Proxy(this.knownFields, {
|
|
125
122
|
get: (target, prop) => {
|
|
@@ -173,17 +170,18 @@ let FormModel = (() => {
|
|
|
173
170
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
174
171
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
175
172
|
const fieldTypeDef = this.flattenedTypeDefs[typePath];
|
|
173
|
+
const context = this.toContext(this.value, valuePath);
|
|
176
174
|
const { value, required, readonly, } = convert(accessor != null
|
|
177
175
|
? accessor.value
|
|
178
176
|
: fieldTypeDef != null
|
|
179
177
|
? mobxCopy(fieldTypeDef,
|
|
180
178
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
181
|
-
create(valuePath,
|
|
179
|
+
create(valuePath, context))
|
|
182
180
|
// fake values can't be copied
|
|
183
181
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
184
|
-
: create(valuePath,
|
|
182
|
+
: create(valuePath, context),
|
|
185
183
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
186
|
-
valuePath,
|
|
184
|
+
valuePath, context);
|
|
187
185
|
const error = this.errors[valuePath];
|
|
188
186
|
return {
|
|
189
187
|
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
@@ -235,7 +233,10 @@ let FormModel = (() => {
|
|
|
235
233
|
? elementValue[0]
|
|
236
234
|
: elementAdapter.create(
|
|
237
235
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
238
|
-
elementTypePath,
|
|
236
|
+
elementTypePath,
|
|
237
|
+
// TODO what can we use for the value path here?
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
239
|
+
this.toContext(this.value, valuePath));
|
|
239
240
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
241
|
const originalList = accessor.value;
|
|
241
242
|
const newList = [
|
|
@@ -346,7 +347,7 @@ let FormModel = (() => {
|
|
|
346
347
|
const { revert } = this.getAdapterForValuePath(valuePath);
|
|
347
348
|
assertExists(revert, 'setting value not supported {}', valuePath);
|
|
348
349
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
349
|
-
const conversion = revert(value, valuePath, this.
|
|
350
|
+
const conversion = revert(value, valuePath, this.toContext(this.value, valuePath));
|
|
350
351
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
351
352
|
return runInAction(() => {
|
|
352
353
|
this.fieldOverrides[valuePath] = [value];
|
|
@@ -384,8 +385,10 @@ let FormModel = (() => {
|
|
|
384
385
|
return;
|
|
385
386
|
}
|
|
386
387
|
const { convert, create, } = adapter;
|
|
387
|
-
|
|
388
|
-
const
|
|
388
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
389
|
+
const context = this.toContext(this.value, valuePath);
|
|
390
|
+
const value = create(valuePath, context);
|
|
391
|
+
const { value: displayValue, } = convert(value, valuePath, context);
|
|
389
392
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
390
393
|
const key = valuePath;
|
|
391
394
|
runInAction(() => {
|
|
@@ -410,25 +413,26 @@ let FormModel = (() => {
|
|
|
410
413
|
const { convert, revert, create, } = this.getAdapterForValuePath(valuePath);
|
|
411
414
|
const fieldOverride = this.fieldOverrides[valuePath];
|
|
412
415
|
const accessor = this.getAccessorForValuePath(valuePath);
|
|
416
|
+
const context = this.toContext(this.value, valuePath);
|
|
413
417
|
const { value: storedValue, } = convert(accessor != null
|
|
414
418
|
? accessor.value
|
|
415
419
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
416
|
-
: create(valuePath,
|
|
420
|
+
: create(valuePath, context),
|
|
417
421
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
418
|
-
valuePath,
|
|
422
|
+
valuePath, context);
|
|
419
423
|
const value = fieldOverride != null
|
|
420
424
|
? fieldOverride[0]
|
|
421
425
|
: storedValue;
|
|
422
426
|
const dirty = storedValue !== value;
|
|
423
427
|
assertExists(revert, 'changing field directly not supported {}', valuePath);
|
|
424
428
|
if (ignoreDefaultValue) {
|
|
425
|
-
const { value: defaultDisplayValue, } = convert(create(valuePath,
|
|
429
|
+
const { value: defaultDisplayValue, } = convert(create(valuePath, context), valuePath, context);
|
|
426
430
|
if (defaultDisplayValue === value) {
|
|
427
431
|
return true;
|
|
428
432
|
}
|
|
429
433
|
}
|
|
430
434
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
431
|
-
const conversion = revert(value, valuePath,
|
|
435
|
+
const conversion = revert(value, valuePath, context);
|
|
432
436
|
return runInAction(() => {
|
|
433
437
|
switch (conversion.type) {
|
|
434
438
|
case UnreliableFieldConversionType.Failure:
|
|
@@ -468,13 +472,15 @@ let FormModel = (() => {
|
|
|
468
472
|
return success;
|
|
469
473
|
}
|
|
470
474
|
const fieldOverride = this.fieldOverrides[adapterPath];
|
|
471
|
-
|
|
475
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
476
|
+
const context = this.toContext(this.value, valuePath);
|
|
477
|
+
const { value: storedValue, } = convert(accessor.value, valuePath, context);
|
|
472
478
|
const value = fieldOverride != null
|
|
473
479
|
? fieldOverride[0]
|
|
474
480
|
: storedValue;
|
|
475
481
|
// TODO more nuanced comparison
|
|
476
482
|
const dirty = fieldOverride != null && fieldOverride[0] !== storedValue;
|
|
477
|
-
const conversion = revert(value, valuePath,
|
|
483
|
+
const conversion = revert(value, valuePath, context);
|
|
478
484
|
switch (conversion.type) {
|
|
479
485
|
case UnreliableFieldConversionType.Failure:
|
|
480
486
|
this.errors[adapterPath] = conversion.error;
|
|
@@ -503,14 +509,12 @@ let FormModel = (() => {
|
|
|
503
509
|
_value_decorators = [(_b = observable).ref.bind(_b)];
|
|
504
510
|
_fieldOverrides_decorators = [(_c = observable).shallow.bind(_c)];
|
|
505
511
|
_errors_decorators = [(_d = observable).shallow.bind(_d)];
|
|
506
|
-
_get_context_decorators = [(_e = computed).struct.bind(_e)];
|
|
507
512
|
_get_fields_decorators = [computed];
|
|
508
513
|
_get_knownFields_decorators = [computed];
|
|
509
514
|
_get_accessors_decorators = [computed];
|
|
510
515
|
__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
516
|
__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
517
|
__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
518
|
__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
519
|
__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
520
|
__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);
|
|
@@ -12,9 +12,10 @@ const IS_NAN_ERROR = 1;
|
|
|
12
12
|
const originalIntegerToStringAdapter = adapterFromTwoWayConverter(new IntegerToStringConverter(IS_NAN_ERROR), prototypingFieldValueFactory(0));
|
|
13
13
|
const originalBooleanToBooleanAdapter = identityAdapter(false);
|
|
14
14
|
class TestFormModel extends FormModel {
|
|
15
|
-
toContext() {
|
|
15
|
+
toContext(value, valuePath) {
|
|
16
16
|
return {
|
|
17
|
-
|
|
17
|
+
value,
|
|
18
|
+
valuePath,
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -493,23 +494,32 @@ describe('all', function () {
|
|
|
493
494
|
let contextCopy;
|
|
494
495
|
beforeEach(function () {
|
|
495
496
|
integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
|
|
496
|
-
contextCopy =
|
|
497
|
+
contextCopy = JSON.stringify(context);
|
|
497
498
|
return {
|
|
498
499
|
type: UnreliableFieldConversionType.Success,
|
|
499
500
|
value: 1,
|
|
500
501
|
};
|
|
501
502
|
});
|
|
502
503
|
});
|
|
503
|
-
it('supplies the
|
|
504
|
+
it('supplies the context when converting', function () {
|
|
504
505
|
model.setFieldValueAndValidate('$.2', '4');
|
|
505
506
|
expect(integerToStringAdapter.revert).toHaveBeenCalledOnce();
|
|
506
507
|
expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2', {
|
|
507
|
-
|
|
508
|
+
// the supplied value isn't a copy, so it will be the model value, even
|
|
509
|
+
// if the value has since changed
|
|
510
|
+
value: model.value,
|
|
511
|
+
valuePath: '$.2',
|
|
508
512
|
});
|
|
509
513
|
});
|
|
510
|
-
it('supplies the context', function () {
|
|
511
|
-
|
|
512
|
-
|
|
514
|
+
it('supplies the correct context value at the time it is being checked', function () {
|
|
515
|
+
// the copy will show the supplied value however
|
|
516
|
+
expect(JSON.parse(contextCopy)).toEqual({
|
|
517
|
+
value: [
|
|
518
|
+
1,
|
|
519
|
+
3,
|
|
520
|
+
7,
|
|
521
|
+
],
|
|
522
|
+
valuePath: '$.2',
|
|
513
523
|
});
|
|
514
524
|
});
|
|
515
525
|
});
|
|
@@ -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/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>>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Observer } from 'mobx-react';
|
|
3
|
+
import { useCallback, } from 'react';
|
|
4
|
+
import { Empty } from 'util/empty';
|
|
5
|
+
/**
|
|
6
|
+
* Displays current value and error of a field
|
|
7
|
+
*/
|
|
8
|
+
function FieldView({ valuePath, form, children, }) {
|
|
9
|
+
const onFocus = useCallback(() => {
|
|
10
|
+
var _a;
|
|
11
|
+
(_a = form.onFieldFocus) === null || _a === void 0 ? void 0 : _a.call(form, valuePath);
|
|
12
|
+
}, [
|
|
13
|
+
form,
|
|
14
|
+
valuePath,
|
|
15
|
+
]);
|
|
16
|
+
const onBlur = useCallback(() => {
|
|
17
|
+
var _a;
|
|
18
|
+
(_a = form.onFieldBlur) === null || _a === void 0 ? void 0 : _a.call(form, valuePath);
|
|
19
|
+
}, [
|
|
20
|
+
form,
|
|
21
|
+
valuePath,
|
|
22
|
+
]);
|
|
23
|
+
const onValueChange = useCallback((value) => {
|
|
24
|
+
var _a;
|
|
25
|
+
(_a = form.onFieldValueChange) === null || _a === void 0 ? void 0 : _a.call(form, valuePath, value);
|
|
26
|
+
}, [
|
|
27
|
+
form,
|
|
28
|
+
valuePath,
|
|
29
|
+
]);
|
|
30
|
+
const onSubmit = useCallback(() => {
|
|
31
|
+
var _a;
|
|
32
|
+
(_a = form.onFieldSubmit) === null || _a === void 0 ? void 0 : _a.call(form, valuePath);
|
|
33
|
+
}, [
|
|
34
|
+
form,
|
|
35
|
+
valuePath,
|
|
36
|
+
]);
|
|
37
|
+
return (_jsx(Observer, { children: () => {
|
|
38
|
+
const { value, error, } = form.fields[valuePath];
|
|
39
|
+
return children({
|
|
40
|
+
value,
|
|
41
|
+
error,
|
|
42
|
+
ErrorSink: Empty,
|
|
43
|
+
onFocus,
|
|
44
|
+
onBlur,
|
|
45
|
+
onValueChange,
|
|
46
|
+
onSubmit,
|
|
47
|
+
});
|
|
48
|
+
} }));
|
|
49
|
+
}
|
|
50
|
+
export function createFieldView(valuePath) {
|
|
51
|
+
return (props) => {
|
|
52
|
+
return (_jsx(FieldView, Object.assign({ form: this, valuePath: valuePath }, props)));
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, } from 'react';
|
|
2
3
|
import { createUnsafePartialObserverComponent } from 'util/partial';
|
|
3
4
|
export function createList(valuePath, List) {
|
|
4
5
|
const propSource = () => {
|
|
@@ -13,6 +14,6 @@ export function createList(valuePath, List) {
|
|
|
13
14
|
export function DefaultList({ values, listPath, children, }) {
|
|
14
15
|
return (_jsx(_Fragment, { children: values.map(function (value, index) {
|
|
15
16
|
const valuePath = `${listPath}.${index}`;
|
|
16
|
-
return children(valuePath, value, index);
|
|
17
|
+
return (_jsx(Fragment, { children: children(valuePath, value, index) }, valuePath));
|
|
17
18
|
}) }));
|
|
18
19
|
}
|
package/.out/mantine/hooks.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { type StringFieldsOfFields } from 'types/string_fields_of_fields';
|
|
|
11
11
|
import { type SubFormFields } from 'types/sub_form_fields';
|
|
12
12
|
import { type ValueTypeOfField } from 'types/value_type_of_field';
|
|
13
13
|
import { type SuppliedCheckboxProps } from './create_checkbox';
|
|
14
|
+
import { type FieldViewProps } from './create_field_view';
|
|
14
15
|
import { type FieldsView } from './create_fields_view';
|
|
15
16
|
import { DefaultList, type SuppliedListProps } from './create_list';
|
|
16
17
|
import { type SuppliedPillProps } from './create_pill';
|
|
@@ -22,7 +23,7 @@ import { type MantineFieldComponent, type MantineForm } from './types';
|
|
|
22
23
|
declare function SimpleSelect(props: SelectProps & {
|
|
23
24
|
onChange?: (value: string | null) => void;
|
|
24
25
|
}): import("react/jsx-runtime").JSX.Element;
|
|
25
|
-
export declare function useMantineFormFields<F extends Fields>({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }: FieldsViewProps<F>): MantineFormImpl<F>;
|
|
26
|
+
export declare function useMantineFormFields<F extends Fields>({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }: FieldsViewProps<F>): Omit<MantineFormImpl<F>, 'fields'>;
|
|
26
27
|
declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
|
|
27
28
|
private readonly textInputCache;
|
|
28
29
|
private readonly valueInputCache;
|
|
@@ -31,6 +32,7 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
|
|
|
31
32
|
private readonly radioCache;
|
|
32
33
|
private readonly pillCache;
|
|
33
34
|
private readonly listCache;
|
|
35
|
+
private readonly fieldViewCache;
|
|
34
36
|
private readonly fieldsViewCache;
|
|
35
37
|
private readonly formCache;
|
|
36
38
|
accessor fields: F;
|
|
@@ -52,6 +54,7 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
|
|
|
52
54
|
pill<K extends keyof AllFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedPillProps, PillProps, ErrorOfField<F[K]>>;
|
|
53
55
|
pill<K extends keyof AllFieldsOfFields<F>, P extends SuppliedPillProps>(valuePath: K, Pill: ComponentType<P>): MantineFieldComponent<SuppliedPillProps, P, ErrorOfField<F[K]>>;
|
|
54
56
|
list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<`${K}.${number}`>, ComponentProps<typeof DefaultList<ElementOfArray<F[K]['value']>, K>>, never>;
|
|
57
|
+
fieldView<K extends keyof AllFieldsOfFields<F>>(valuePath: K): ComponentType<FieldViewProps<F, K>>;
|
|
55
58
|
fieldsView<K extends keyof AllFieldsOfFields<F>, P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>>(valuePath: K, FieldsView: ComponentType<P>): FieldsView<K, MantineFieldComponent<FieldsViewProps<P['fields']>, P, never>>;
|
|
56
59
|
form<K extends keyof AllFieldsOfFields<F>, P extends FormProps<ValueTypeOfField<F[K]>> = FormProps<ValueTypeOfField<F[K]>>>(valuePath: K, Form: ComponentType<P>): MantineFieldComponent<FormProps<ValueTypeOfField<F[K]>>, P, never>;
|
|
57
60
|
}
|
package/.out/mantine/hooks.js
CHANGED
|
@@ -49,6 +49,7 @@ import { Cache, } from '@strictly/base';
|
|
|
49
49
|
import { observable, runInAction, } from 'mobx';
|
|
50
50
|
import { useEffect, useMemo, } from 'react';
|
|
51
51
|
import { createCheckbox, } from './create_checkbox';
|
|
52
|
+
import { createFieldView, } from './create_field_view';
|
|
52
53
|
import { createFieldsView, } from './create_fields_view';
|
|
53
54
|
import { createForm } from './create_form';
|
|
54
55
|
import { createList, DefaultList, } from './create_list';
|
|
@@ -60,7 +61,9 @@ import { createValueInput, } from './create_value_input';
|
|
|
60
61
|
function SimpleSelect(props) {
|
|
61
62
|
return _jsx(Select, Object.assign({}, props));
|
|
62
63
|
}
|
|
63
|
-
export function useMantineFormFields({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields,
|
|
64
|
+
export function useMantineFormFields({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields,
|
|
65
|
+
// should use FieldView rather than observing fields directly from here
|
|
66
|
+
}) {
|
|
64
67
|
const form = useMemo(function () {
|
|
65
68
|
return new MantineFormImpl(fields);
|
|
66
69
|
},
|
|
@@ -153,6 +156,12 @@ let MantineFormImpl = (() => {
|
|
|
153
156
|
writable: true,
|
|
154
157
|
value: new Cache(createList.bind(this))
|
|
155
158
|
});
|
|
159
|
+
Object.defineProperty(this, "fieldViewCache", {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
configurable: true,
|
|
162
|
+
writable: true,
|
|
163
|
+
value: new Cache(createFieldView.bind(this))
|
|
164
|
+
});
|
|
156
165
|
Object.defineProperty(this, "fieldsViewCache", {
|
|
157
166
|
enumerable: true,
|
|
158
167
|
configurable: true,
|
|
@@ -248,6 +257,9 @@ let MantineFormImpl = (() => {
|
|
|
248
257
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
249
258
|
return this.listCache.retrieveOrCreate(valuePath, DefaultList);
|
|
250
259
|
}
|
|
260
|
+
fieldView(valuePath) {
|
|
261
|
+
return this.fieldViewCache.retrieveOrCreate(valuePath);
|
|
262
|
+
}
|
|
251
263
|
fieldsView(valuePath, FieldsView) {
|
|
252
264
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
253
265
|
return this.fieldsViewCache.retrieveOrCreate(valuePath,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
2
|
+
import { type FieldsViewProps } from 'core/props';
|
|
3
|
+
import { type Field } from 'types/field';
|
|
4
|
+
declare function Component(props: FieldsViewProps<{
|
|
5
|
+
$: Field<string, string>;
|
|
6
|
+
}>): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
declare const meta: Meta<typeof Component>;
|
|
8
|
+
export default meta;
|
|
9
|
+
type Story = StoryObj<typeof Component>;
|
|
10
|
+
export declare const Empty: Story;
|
|
11
|
+
export declare const Populated: Story;
|
|
12
|
+
export declare const Error: Story;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Group, TextInput, } from '@mantine/core';
|
|
3
|
+
import { action } from '@storybook/addon-actions';
|
|
4
|
+
import { useMantineFormFields } from 'mantine/hooks';
|
|
5
|
+
import { useCallback, } from 'react';
|
|
6
|
+
function Component(props) {
|
|
7
|
+
const form = useMantineFormFields(props);
|
|
8
|
+
const FieldView = form.fieldView('$');
|
|
9
|
+
return (_jsx(FieldView, { children: ({ error, value, onBlur, onFocus, onSubmit, onValueChange, }) => {
|
|
10
|
+
// this *is* a component eslint
|
|
11
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
12
|
+
const onChange = useCallback(({ target: { value } }) => {
|
|
13
|
+
onValueChange(value);
|
|
14
|
+
}, [onValueChange]);
|
|
15
|
+
return (_jsxs(Group, { align: 'start', flex: 1, children: [_jsx(TextInput, { error: error, flex: 1, onBlur: onBlur, onChange: onChange, onFocus: onFocus, value: value }), _jsx(Button, { onClick: onSubmit, children: "Submit Field" })] }));
|
|
16
|
+
} }));
|
|
17
|
+
}
|
|
18
|
+
const meta = {
|
|
19
|
+
component: Component,
|
|
20
|
+
args: {
|
|
21
|
+
onFieldBlur: action('onFieldBlur'),
|
|
22
|
+
onFieldFocus: action('onFieldFocus'),
|
|
23
|
+
onFieldSubmit: action('onFieldSubmit'),
|
|
24
|
+
onFieldValueChange: action('onFieldValueChange'),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export default meta;
|
|
28
|
+
export const Empty = {
|
|
29
|
+
args: {
|
|
30
|
+
fields: {
|
|
31
|
+
$: {
|
|
32
|
+
readonly: false,
|
|
33
|
+
required: false,
|
|
34
|
+
value: '',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export const Populated = {
|
|
40
|
+
args: {
|
|
41
|
+
fields: {
|
|
42
|
+
$: {
|
|
43
|
+
readonly: false,
|
|
44
|
+
required: false,
|
|
45
|
+
value: 'hello',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export const Error = {
|
|
51
|
+
args: {
|
|
52
|
+
fields: {
|
|
53
|
+
$: {
|
|
54
|
+
readonly: false,
|
|
55
|
+
required: false,
|
|
56
|
+
value: 'hello',
|
|
57
|
+
error: 'no hellos allowed',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { composeStories } from '@storybook/react';
|
|
3
|
+
import { toArray } from '@strictly/base';
|
|
4
|
+
import { render, } from '@testing-library/react';
|
|
5
|
+
import * as stories from './field_view_hooks.stories';
|
|
6
|
+
const composedStories = composeStories(stories);
|
|
7
|
+
describe('field view hooks', function () {
|
|
8
|
+
it.each(toArray(composedStories))('renders %s', function (_name, Story) {
|
|
9
|
+
const wrapper = render(_jsx(Story, {}));
|
|
10
|
+
expect(wrapper.container).toMatchSnapshot();
|
|
11
|
+
});
|
|
12
|
+
});
|