@strictly/react-form 0.0.5 → 0.0.7
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/hooks.d.ts +10 -0
- package/.out/core/mobx/hooks.js +47 -0
- package/.out/core/mobx/specs/form_presenter.tests.js +3 -6
- package/.out/core/mobx/specs/{merge_field_adapters_with_two_way_converter.js → merge_field_adapters_with_two_way_converter.tests.js} +15 -16
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +78 -10
- package/.out/core/mobx/sub_form_field_adapters.d.ts +6 -4
- package/.out/core/mobx/sub_form_field_adapters.js +23 -2
- package/.out/core/props.d.ts +2 -2
- package/.out/index.d.ts +1 -0
- package/.out/index.js +1 -0
- package/.out/mantine/create_fields_view.d.ts +7 -0
- package/.out/mantine/{create_sub_form.js → create_fields_view.js} +4 -5
- package/.out/mantine/create_form.d.ts +7 -0
- package/.out/mantine/create_form.js +13 -0
- package/.out/mantine/hooks.d.ts +6 -4
- package/.out/mantine/hooks.js +17 -7
- package/.out/mantine/specs/checkbox_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/checkbox_hooks.stories.js +2 -2
- package/.out/mantine/specs/{sub_form_hooks.stories.d.ts → fields_view_hooks.stories.d.ts} +2 -2
- package/.out/mantine/specs/{sub_form_hooks.stories.js → fields_view_hooks.stories.js} +9 -8
- package/.out/mantine/specs/fields_view_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/fields_view_hooks.tests.js +12 -0
- package/.out/mantine/specs/form_hooks.stories.d.ts +12 -0
- package/.out/mantine/specs/form_hooks.stories.js +60 -0
- package/.out/mantine/specs/form_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/form_hooks.tests.js +12 -0
- package/.out/mantine/specs/list_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/list_hooks.stories.js +2 -2
- package/.out/mantine/specs/radio_group_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/radio_group_hooks.stories.js +2 -2
- package/.out/mantine/specs/select_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/select_hooks.stories.js +2 -2
- package/.out/mantine/specs/text_input_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/text_input_hooks.stories.js +2 -2
- package/.out/mantine/specs/value_input_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/value_input_hooks.stories.js +2 -2
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/core/mobx/hooks.ts +94 -0
- package/core/mobx/specs/form_presenter.tests.ts +6 -6
- package/core/mobx/specs/{merge_field_adapters_with_two_way_converter.ts → merge_field_adapters_with_two_way_converter.tests.ts} +16 -16
- package/core/mobx/specs/sub_form_field_adapters.tests.ts +93 -10
- package/core/mobx/sub_form_field_adapters.ts +54 -7
- package/core/props.ts +2 -2
- package/dist/index.cjs +200 -90
- package/dist/index.d.cts +43 -34
- package/dist/index.d.ts +43 -34
- package/dist/index.js +186 -70
- package/index.ts +1 -0
- package/mantine/{create_sub_form.tsx → create_fields_view.tsx} +27 -16
- package/mantine/create_form.tsx +43 -0
- package/mantine/hooks.tsx +48 -14
- package/mantine/specs/__snapshots__/fields_view_hooks.tests.tsx.snap +460 -0
- package/mantine/specs/__snapshots__/form_hooks.tests.tsx.snap +273 -0
- package/mantine/specs/checkbox_hooks.stories.tsx +4 -4
- package/mantine/specs/{sub_form_hooks.stories.tsx → fields_view_hooks.stories.tsx} +23 -11
- package/mantine/specs/fields_view_hooks.tests.tsx +15 -0
- package/mantine/specs/form_hooks.stories.tsx +107 -0
- package/mantine/specs/form_hooks.tests.tsx +15 -0
- package/mantine/specs/list_hooks.stories.tsx +4 -4
- package/mantine/specs/radio_group_hooks.stories.tsx +4 -4
- package/mantine/specs/select_hooks.stories.tsx +4 -4
- package/mantine/specs/text_input_hooks.stories.tsx +4 -4
- package/mantine/specs/value_input_hooks.stories.tsx +4 -4
- package/package.json +1 -1
- package/.out/mantine/create_sub_form.d.ts +0 -6
- /package/.out/core/mobx/specs/{merge_field_adapters_with_two_way_converter.d.ts → merge_field_adapters_with_two_way_converter.tests.d.ts} +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReadonlyTypeOfType, type ValueOfType } from '@strictly/define';
|
|
2
|
+
import { type FieldsViewProps } from 'core/props';
|
|
3
|
+
import { type FormPresenter } from './form_presenter';
|
|
4
|
+
import { type ValuePathsOfPresenter } from './types';
|
|
5
|
+
type ValueOfPresenter<P extends FormPresenter<any, any, any, any>> = P extends FormPresenter<infer T, any, any, any> ? ValueOfType<ReadonlyTypeOfType<T>> : never;
|
|
6
|
+
type ModelOfPresenter<P extends FormPresenter<any, any, any, any>> = ReturnType<P['createModel']>;
|
|
7
|
+
export declare function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, any>>(presenter: P, value: ValueOfPresenter<P>, onValidSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void): {
|
|
8
|
+
model: ModelOfPresenter<P>;
|
|
9
|
+
} & Omit<FieldsViewProps<ModelOfPresenter<P>['fields']>, 'fields'>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback, useMemo, } from 'react';
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
|
+
export function useDefaultMobxFormHooks(presenter, value, onValidSubmit) {
|
|
4
|
+
const model = useMemo(function () {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
6
|
+
return presenter.createModel(value);
|
|
7
|
+
}, [
|
|
8
|
+
presenter,
|
|
9
|
+
value,
|
|
10
|
+
]);
|
|
11
|
+
const onFieldValueChange = useCallback(function (path, value) {
|
|
12
|
+
presenter.clearFieldError(model, path);
|
|
13
|
+
presenter.setFieldValue(model, path, value);
|
|
14
|
+
}, [
|
|
15
|
+
presenter,
|
|
16
|
+
model,
|
|
17
|
+
]);
|
|
18
|
+
const onFieldSubmit = useCallback(function (valuePath) {
|
|
19
|
+
if (presenter.validateField(model, valuePath)) {
|
|
20
|
+
onValidSubmit?.(model, valuePath);
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}, [
|
|
24
|
+
presenter,
|
|
25
|
+
model,
|
|
26
|
+
onValidSubmit,
|
|
27
|
+
]);
|
|
28
|
+
const onFieldBlur = useCallback(function (path) {
|
|
29
|
+
// work around potential loss of focus prior to state potentially invalidating change triggering
|
|
30
|
+
// (e.g. changing a discriminator)
|
|
31
|
+
// TODO debounce?
|
|
32
|
+
setTimeout(function () {
|
|
33
|
+
if (presenter.isValuePathActive(model, path)) {
|
|
34
|
+
presenter.validateField(model, path);
|
|
35
|
+
}
|
|
36
|
+
}, 100);
|
|
37
|
+
}, [
|
|
38
|
+
presenter,
|
|
39
|
+
model,
|
|
40
|
+
]);
|
|
41
|
+
return {
|
|
42
|
+
model,
|
|
43
|
+
onFieldValueChange,
|
|
44
|
+
onFieldSubmit,
|
|
45
|
+
onFieldBlur,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -21,18 +21,16 @@ describe('all', function () {
|
|
|
21
21
|
describe('FlattenedTypePathsToConvertersOf', function () {
|
|
22
22
|
describe('record', function () {
|
|
23
23
|
const typeDef = record(numberType);
|
|
24
|
-
let t;
|
|
25
24
|
it('equals expected type', function () {
|
|
26
|
-
expectTypeOf(
|
|
25
|
+
expectTypeOf().toEqualTypeOf();
|
|
27
26
|
});
|
|
28
27
|
});
|
|
29
28
|
describe('object', function () {
|
|
30
29
|
const typeDef = object()
|
|
31
30
|
.field('x', stringType)
|
|
32
31
|
.field('y', booleanType);
|
|
33
|
-
let t;
|
|
34
32
|
it('equals expected type', function () {
|
|
35
|
-
expectTypeOf(
|
|
33
|
+
expectTypeOf().toEqualTypeOf();
|
|
36
34
|
});
|
|
37
35
|
it('matches representative adapters', function () {
|
|
38
36
|
expectTypeOf().toMatchTypeOf();
|
|
@@ -50,9 +48,8 @@ describe('all', function () {
|
|
|
50
48
|
'$.b': '$.y',
|
|
51
49
|
'$.c': '$.z',
|
|
52
50
|
};
|
|
53
|
-
let t;
|
|
54
51
|
it('equals expected type', function () {
|
|
55
|
-
expectTypeOf(
|
|
52
|
+
expectTypeOf().toEqualTypeOf();
|
|
56
53
|
});
|
|
57
54
|
});
|
|
58
55
|
});
|
|
@@ -9,9 +9,8 @@ const error3 = Symbol();
|
|
|
9
9
|
const error4 = Symbol();
|
|
10
10
|
const context = Symbol();
|
|
11
11
|
describe('MergedOfFieldAdapterWithTwoWayConverter', function () {
|
|
12
|
-
let m;
|
|
13
12
|
it('merges the errors', function () {
|
|
14
|
-
expectTypeOf().toEqualTypeOf(
|
|
13
|
+
expectTypeOf().toEqualTypeOf();
|
|
15
14
|
});
|
|
16
15
|
});
|
|
17
16
|
const originalIntegerAdapter = identityAdapter(0);
|
|
@@ -69,20 +68,20 @@ describe('mergeFieldAdaptersWithTwoWayConverter', function () {
|
|
|
69
68
|
beforeEach(function () {
|
|
70
69
|
result = merged.booleanAdapter.revert(true, 'booleanAdapter', context);
|
|
71
70
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
71
|
+
it('returns the same value on revert', function () {
|
|
72
|
+
expect(result).toEqual(expect.objectContaining({
|
|
73
|
+
value: true,
|
|
74
|
+
type: UnreliableFieldConversionType.Success,
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
77
|
+
it('calls the mocked converter', function () {
|
|
78
|
+
expect(converter.revert).toHaveBeenCalledOnce();
|
|
79
|
+
expect(converter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context);
|
|
80
|
+
});
|
|
81
|
+
it('calls the mocked adapter', function () {
|
|
82
|
+
expect(booleanAdapter.revert).toHaveBeenCalledOnce();
|
|
83
|
+
expect(booleanAdapter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context);
|
|
84
|
+
});
|
|
86
85
|
});
|
|
87
86
|
});
|
|
88
87
|
});
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import { numberType, object, stringType, } from '@strictly/define';
|
|
1
2
|
import { subFormFieldAdapters, } from 'core/mobx/sub_form_field_adapters';
|
|
3
|
+
import { UnreliableFieldConversionType } from 'types/field_converters';
|
|
2
4
|
import { mockDeep } from 'vitest-mock-extended';
|
|
3
5
|
describe('subFormFieldAdapters', () => {
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
+
const mockedFieldAdapter1 = mockDeep();
|
|
7
|
+
const fieldAdapter1 = mockedFieldAdapter1;
|
|
8
|
+
const mockedFieldAdapter2 = mockDeep();
|
|
9
|
+
const fieldAdapter2 = mockedFieldAdapter2;
|
|
6
10
|
describe('empty value', () => {
|
|
7
|
-
const adapters = subFormFieldAdapters({}, '$.a');
|
|
11
|
+
const adapters = subFormFieldAdapters({}, '$.a', stringType);
|
|
8
12
|
it('equals expected type', () => {
|
|
9
13
|
expectTypeOf(adapters).toEqualTypeOf();
|
|
10
14
|
});
|
|
@@ -13,28 +17,92 @@ describe('subFormFieldAdapters', () => {
|
|
|
13
17
|
});
|
|
14
18
|
});
|
|
15
19
|
describe('single adapter', () => {
|
|
20
|
+
const type = object().field('a', stringType);
|
|
16
21
|
const adapters = subFormFieldAdapters({
|
|
17
22
|
$: fieldAdapter1,
|
|
18
|
-
}, '$.a');
|
|
23
|
+
}, '$.a', type);
|
|
19
24
|
it('equals expected type', () => {
|
|
20
|
-
|
|
25
|
+
// TODO toEqualTypeOf (seems to be a TS error)
|
|
26
|
+
expectTypeOf(adapters).toMatchTypeOf();
|
|
21
27
|
});
|
|
22
28
|
it('equals expected value', () => {
|
|
23
|
-
expect(adapters).toEqual({ '$.a':
|
|
29
|
+
expect(adapters).toEqual({ '$.a': expect.anything() });
|
|
30
|
+
});
|
|
31
|
+
it('calls convert with the correct paths and values', () => {
|
|
32
|
+
const mockedReturnedValue = {
|
|
33
|
+
value: false,
|
|
34
|
+
required: false,
|
|
35
|
+
readonly: false,
|
|
36
|
+
};
|
|
37
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
|
|
38
|
+
const returnedValue = adapters['$.a'].convert('x', '$.a', { a: 'y' });
|
|
39
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('x', '$', 'y');
|
|
40
|
+
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
41
|
+
});
|
|
42
|
+
it('calls revert with the correct paths and values', () => {
|
|
43
|
+
const mockedReturnedValue = {
|
|
44
|
+
type: UnreliableFieldConversionType.Success,
|
|
45
|
+
value: 'ok',
|
|
46
|
+
};
|
|
47
|
+
mockedFieldAdapter1.revert.mockReturnValue(mockedReturnedValue);
|
|
48
|
+
const returnedValue = adapters['$.a'].revert?.(true, '$.a', { a: 'y' });
|
|
49
|
+
expect(fieldAdapter1.revert).toHaveBeenCalledWith(true, '$', 'y');
|
|
50
|
+
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
51
|
+
});
|
|
52
|
+
it('calls create with the correct paths and values', () => {
|
|
53
|
+
const mockedReturnedValue = 'x';
|
|
54
|
+
mockedFieldAdapter1.create.mockReturnValue(mockedReturnedValue);
|
|
55
|
+
const returnedValue = adapters['$.a'].create('$.a', { a: 'y' });
|
|
56
|
+
expect(fieldAdapter1.create).toHaveBeenCalledWith('$', 'y');
|
|
57
|
+
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
24
58
|
});
|
|
25
59
|
});
|
|
26
60
|
describe('multiple adapters', () => {
|
|
61
|
+
const type = object()
|
|
62
|
+
.field('a', object().field('x', stringType).field('y', numberType));
|
|
27
63
|
const adapters = subFormFieldAdapters({
|
|
28
64
|
'$.x': fieldAdapter1,
|
|
29
65
|
'$.y': fieldAdapter2,
|
|
30
|
-
}, '$.a');
|
|
66
|
+
}, '$.a', type);
|
|
31
67
|
it('equals expected type', () => {
|
|
32
|
-
|
|
68
|
+
// TODO toEqualTypeOf (seems to be a TS error)
|
|
69
|
+
expectTypeOf(adapters).toMatchTypeOf();
|
|
33
70
|
});
|
|
34
71
|
it('equals expected value', () => {
|
|
35
72
|
expect(adapters).toEqual({
|
|
36
|
-
'$.a.x':
|
|
37
|
-
'$.a.y':
|
|
73
|
+
'$.a.x': expect.anything(),
|
|
74
|
+
'$.a.y': expect.anything(),
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('calls convert with correct paths and values', () => {
|
|
78
|
+
const subContext = {
|
|
79
|
+
x: 'a',
|
|
80
|
+
y: 1,
|
|
81
|
+
};
|
|
82
|
+
const context = {
|
|
83
|
+
a: subContext,
|
|
84
|
+
};
|
|
85
|
+
it('calls $.a.x', () => {
|
|
86
|
+
const mockedReturnedValue = {
|
|
87
|
+
value: true,
|
|
88
|
+
readonly: true,
|
|
89
|
+
required: false,
|
|
90
|
+
};
|
|
91
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue);
|
|
92
|
+
const returnedValue = adapters['$.a.x'].convert('b', '$.a.x', context);
|
|
93
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x', subContext);
|
|
94
|
+
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
95
|
+
});
|
|
96
|
+
it('calls $.a.y', () => {
|
|
97
|
+
const mockedReturnedValue = {
|
|
98
|
+
value: false,
|
|
99
|
+
readonly: false,
|
|
100
|
+
required: false,
|
|
101
|
+
};
|
|
102
|
+
mockedFieldAdapter2.convert.mockReturnValue(mockedReturnedValue);
|
|
103
|
+
const returnedValue = adapters['$.a.y'].convert(2, '$.a.y', context);
|
|
104
|
+
expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y', subContext);
|
|
105
|
+
expect(returnedValue).toEqual(mockedReturnedValue);
|
|
38
106
|
});
|
|
39
107
|
});
|
|
40
108
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type StringConcatOf } from '@strictly/base';
|
|
2
|
-
import { type
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { type Type, type ValueOfType } from '@strictly/define';
|
|
3
|
+
import { type ErrorOfFieldAdapter, type FieldAdapter, type FromOfFieldAdapter, type ToOfFieldAdapter } from './field_adapter';
|
|
4
|
+
type SubFormFieldAdapter<F extends FieldAdapter, P extends string, Context> = FieldAdapter<FromOfFieldAdapter<F>, ToOfFieldAdapter<F>, ErrorOfFieldAdapter<F>, P, Context>;
|
|
5
|
+
type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string, Context> = {
|
|
6
|
+
[K in keyof SubAdapters as K extends StringConcatOf<'$', infer S> ? `${P}${S}` : never]: K extends StringConcatOf<'$', infer S> ? SubFormFieldAdapter<SubAdapters[K], `${P}${S}`, Context> : never;
|
|
5
7
|
};
|
|
6
|
-
export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string>(subAdapters: SubAdapters, prefix: P): SubFormFieldAdapters<SubAdapters, P
|
|
8
|
+
export declare function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string, ContextType extends Type>(subAdapters: SubAdapters, prefix: P, contextType: ContextType): SubFormFieldAdapters<SubAdapters, P, ValueOfType<ContextType>>;
|
|
7
9
|
export {};
|
|
@@ -1,8 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
import { flattenValuesOfType, } from '@strictly/define';
|
|
2
|
+
export function subFormFieldAdapters(subAdapters, prefix, contextType) {
|
|
3
|
+
function getSubValuePathAndContext(valuePath, context) {
|
|
4
|
+
const subValuePath = valuePath.replace(prefix, '$');
|
|
5
|
+
const subContext = flattenValuesOfType(contextType, context)[prefix];
|
|
6
|
+
return [
|
|
7
|
+
subValuePath,
|
|
8
|
+
subContext,
|
|
9
|
+
];
|
|
10
|
+
}
|
|
2
11
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3
12
|
return Object.entries(subAdapters).reduce((acc, [subKey, subValue,]) => {
|
|
4
13
|
const key = subKey.replace('$', prefix);
|
|
5
|
-
|
|
14
|
+
// adapt field adapter with new path and context
|
|
15
|
+
const adaptedAdapter = {
|
|
16
|
+
convert: (from, valuePath, context) => {
|
|
17
|
+
return subValue.convert(from, ...getSubValuePathAndContext(valuePath, context));
|
|
18
|
+
},
|
|
19
|
+
create: (valuePath, context) => {
|
|
20
|
+
return subValue.create(...getSubValuePathAndContext(valuePath, context));
|
|
21
|
+
},
|
|
22
|
+
revert: subValue.revert && ((from, valuePath, context) => {
|
|
23
|
+
return subValue.revert(from, ...getSubValuePathAndContext(valuePath, context));
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
acc[key] = adaptedAdapter;
|
|
6
27
|
return acc;
|
|
7
28
|
}, {});
|
|
8
29
|
}
|
package/.out/core/props.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type Fields } from 'types/field';
|
|
2
|
-
export type
|
|
2
|
+
export type FieldsViewProps<F extends Fields> = {
|
|
3
3
|
fields: F;
|
|
4
4
|
onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void;
|
|
5
5
|
onFieldFocus?(this: void, key: keyof F): void;
|
|
6
6
|
onFieldBlur?(this: void, key: keyof F): void;
|
|
7
7
|
onFieldSubmit?(this: void, key: keyof F): boolean | void;
|
|
8
8
|
};
|
|
9
|
-
export type
|
|
9
|
+
export type FormProps<O> = {
|
|
10
10
|
value: O;
|
|
11
11
|
onValueChange: (value: O) => void;
|
|
12
12
|
};
|
package/.out/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from './core/mobx/field_adapters_of_values';
|
|
|
4
4
|
export * from './core/mobx/flattened_adapters_of_fields';
|
|
5
5
|
export * from './core/mobx/form_fields_of_field_adapters';
|
|
6
6
|
export * from './core/mobx/form_presenter';
|
|
7
|
+
export * from './core/mobx/hooks';
|
|
7
8
|
export * from './core/mobx/merge_field_adapters_with_two_way_converter';
|
|
8
9
|
export * from './core/mobx/merge_field_adapters_with_validators';
|
|
9
10
|
export * from './core/mobx/sub_form_field_adapters';
|
package/.out/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from './core/mobx/field_adapters_of_values';
|
|
|
4
4
|
export * from './core/mobx/flattened_adapters_of_fields';
|
|
5
5
|
export * from './core/mobx/form_fields_of_field_adapters';
|
|
6
6
|
export * from './core/mobx/form_presenter';
|
|
7
|
+
export * from './core/mobx/hooks';
|
|
7
8
|
export * from './core/mobx/merge_field_adapters_with_two_way_converter';
|
|
8
9
|
export * from './core/mobx/merge_field_adapters_with_validators';
|
|
9
10
|
export * from './core/mobx/sub_form_field_adapters';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FieldsViewProps } from 'core/props';
|
|
2
|
+
import type { ComponentType } from 'react';
|
|
3
|
+
import type { AllFieldsOfFields } from 'types/all_fields_of_fields';
|
|
4
|
+
import type { Fields } from 'types/field';
|
|
5
|
+
import type { SubFormFields } from 'types/sub_form_fields';
|
|
6
|
+
import type { MantineFieldComponent } from './types';
|
|
7
|
+
export declare function createFieldsView<F extends Fields, K extends keyof AllFieldsOfFields<F>, P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>>(valuePath: K, FieldsView: ComponentType<P>, observableProps: FieldsViewProps<F>): MantineFieldComponent<FieldsViewProps<P['fields']>, P>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { observer } from 'mobx-react';
|
|
3
|
-
export function
|
|
3
|
+
export function createFieldsView(valuePath, FieldsView, observableProps) {
|
|
4
4
|
function toKey(subKey) {
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
6
6
|
return subKey.replace('$', valuePath);
|
|
@@ -22,7 +22,8 @@ export function createSubForm(valuePath, SubForm, observableProps) {
|
|
|
22
22
|
function onFieldSubmit(subKey) {
|
|
23
23
|
observableProps.onFieldSubmit?.(toKey(subKey));
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
26
|
+
return observer(function (props) {
|
|
26
27
|
// convert fields to sub-fields
|
|
27
28
|
const subFields = Object.entries(observableProps.fields).reduce((acc, [fieldKey, fieldValue,]) => {
|
|
28
29
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -31,9 +32,7 @@ export function createSubForm(valuePath, SubForm, observableProps) {
|
|
|
31
32
|
}
|
|
32
33
|
return acc;
|
|
33
34
|
}, {});
|
|
34
|
-
return (_jsx(
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
36
|
-
, {
|
|
35
|
+
return (_jsx(FieldsView, { ...props,
|
|
37
36
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
38
37
|
fields: subFields, onFieldBlur: onFieldBlur, onFieldFocus: onFieldFocus, onFieldSubmit: onFieldSubmit, onFieldValueChange: onFieldValueChange }));
|
|
39
38
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type FieldsViewProps, type FormProps } from 'core/props';
|
|
2
|
+
import { type ComponentType } from 'react';
|
|
3
|
+
import { type AllFieldsOfFields } from 'types/all_fields_of_fields';
|
|
4
|
+
import { type Fields } from 'types/field';
|
|
5
|
+
import { type ValueTypeOfField } from 'types/value_type_of_field';
|
|
6
|
+
import { type MantineFieldComponent } from './types';
|
|
7
|
+
export declare function createForm<F extends Fields, K extends keyof AllFieldsOfFields<F>, P extends FormProps<ValueTypeOfField<F[K]>> = FormProps<ValueTypeOfField<F[K]>>>(valuePath: K, Form: ComponentType<P>, observableProps: FieldsViewProps<F>): MantineFieldComponent<FormProps<ValueTypeOfField<F[K]>>, P>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
import { useCallback, } from 'react';
|
|
4
|
+
export function createForm(valuePath, Form, observableProps) {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
6
|
+
return observer((props) => {
|
|
7
|
+
const { value } = observableProps.fields[valuePath];
|
|
8
|
+
const onValueChange = useCallback((value) => {
|
|
9
|
+
observableProps.onFieldValueChange(valuePath, value);
|
|
10
|
+
}, []);
|
|
11
|
+
return (_jsx(Form, { ...props, onValueChange: onValueChange, value: value }));
|
|
12
|
+
});
|
|
13
|
+
}
|
package/.out/mantine/hooks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CheckboxProps, type PillProps, type RadioGroupProps, type RadioProps, type SelectProps, type TextInputProps } from '@mantine/core';
|
|
2
2
|
import { type ElementOfArray } from '@strictly/base';
|
|
3
|
-
import { type FormProps } from 'core/props';
|
|
3
|
+
import { type FieldsViewProps, type FormProps } from 'core/props';
|
|
4
4
|
import { type ComponentProps, type ComponentType } from 'react';
|
|
5
5
|
import { type AllFieldsOfFields } from 'types/all_fields_of_fields';
|
|
6
6
|
import { type BooleanFieldsOfFields } from 'types/boolean_fields_of_fields';
|
|
@@ -21,7 +21,7 @@ import { type MantineFieldComponent, type MantineForm } from './types';
|
|
|
21
21
|
declare function SimpleSelect(props: SelectProps & {
|
|
22
22
|
onChange?: (value: string | null) => void;
|
|
23
23
|
}): import("react/jsx-runtime").JSX.Element;
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function useMantineFormFields<F extends Fields>({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }: FieldsViewProps<F>): MantineFormImpl<F>;
|
|
25
25
|
declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
|
|
26
26
|
private readonly textInputCache;
|
|
27
27
|
private readonly valueInputCache;
|
|
@@ -30,7 +30,8 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
|
|
|
30
30
|
private readonly radioCache;
|
|
31
31
|
private readonly pillCache;
|
|
32
32
|
private readonly listCache;
|
|
33
|
-
private readonly
|
|
33
|
+
private readonly fieldsViewCache;
|
|
34
|
+
private readonly formCache;
|
|
34
35
|
accessor fields: F;
|
|
35
36
|
onFieldValueChange: <K extends keyof F>(this: void, key: K, value: F[K]['value']) => void;
|
|
36
37
|
onFieldFocus: ((this: void, key: keyof F) => void) | undefined;
|
|
@@ -50,6 +51,7 @@ declare class MantineFormImpl<F extends Fields> implements MantineForm<F> {
|
|
|
50
51
|
pill<K extends keyof AllFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedPillProps, PillProps, ErrorOfField<F[K]>>;
|
|
51
52
|
pill<K extends keyof AllFieldsOfFields<F>, P extends SuppliedPillProps>(valuePath: K, Pill: ComponentType<P>): MantineFieldComponent<SuppliedPillProps, P>;
|
|
52
53
|
list<K extends keyof ListFieldsOfFields<F>>(valuePath: K): MantineFieldComponent<SuppliedListProps<`${K}.${number}`>, ComponentProps<typeof DefaultList<ElementOfArray<F[K]['value']>, K>>>;
|
|
53
|
-
|
|
54
|
+
fieldsView<K extends keyof AllFieldsOfFields<F>, P extends FieldsViewProps<Fields> = FieldsViewProps<SubFormFields<F, K>>>(valuePath: K, FieldsView: ComponentType<P>): MantineFieldComponent<FieldsViewProps<P['fields']>, P>;
|
|
55
|
+
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>;
|
|
54
56
|
}
|
|
55
57
|
export {};
|
package/.out/mantine/hooks.js
CHANGED
|
@@ -4,17 +4,18 @@ import { Cache, } from '@strictly/base';
|
|
|
4
4
|
import { observable, runInAction, } from 'mobx';
|
|
5
5
|
import { useEffect, useMemo, } from 'react';
|
|
6
6
|
import { createCheckbox, } from './create_checkbox';
|
|
7
|
+
import { createFieldsView } from './create_fields_view';
|
|
8
|
+
import { createForm } from './create_form';
|
|
7
9
|
import { createList, DefaultList, } from './create_list';
|
|
8
10
|
import { createPill, } from './create_pill';
|
|
9
11
|
import { createRadio, } from './create_radio';
|
|
10
12
|
import { createRadioGroup, } from './create_radio_group';
|
|
11
|
-
import { createSubForm } from './create_sub_form';
|
|
12
13
|
import { createTextInput, } from './create_text_input';
|
|
13
14
|
import { createValueInput, } from './create_value_input';
|
|
14
15
|
function SimpleSelect(props) {
|
|
15
16
|
return _jsx(Select, { ...props });
|
|
16
17
|
}
|
|
17
|
-
export function
|
|
18
|
+
export function useMantineFormFields({ onFieldValueChange, onFieldBlur, onFieldFocus, onFieldSubmit, fields, }) {
|
|
18
19
|
const form = useMemo(function () {
|
|
19
20
|
return new MantineFormImpl(fields);
|
|
20
21
|
},
|
|
@@ -63,7 +64,8 @@ class MantineFormImpl {
|
|
|
63
64
|
radioCache = new Cache(createRadio.bind(this));
|
|
64
65
|
pillCache = new Cache(createPill.bind(this));
|
|
65
66
|
listCache = new Cache(createList.bind(this));
|
|
66
|
-
|
|
67
|
+
fieldsViewCache = new Cache(createFieldsView.bind(this));
|
|
68
|
+
formCache = new Cache(createForm.bind(this));
|
|
67
69
|
@observable.ref
|
|
68
70
|
accessor fields;
|
|
69
71
|
onFieldValueChange;
|
|
@@ -129,11 +131,19 @@ class MantineFormImpl {
|
|
|
129
131
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
130
132
|
return this.listCache.retrieveOrCreate(valuePath, DefaultList);
|
|
131
133
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return this.
|
|
134
|
+
fieldsView(valuePath, FieldsView) {
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
136
|
+
return this.fieldsViewCache.retrieveOrCreate(valuePath,
|
|
137
|
+
// strip props from component since we lose information in the cache
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
139
|
+
FieldsView, this);
|
|
140
|
+
}
|
|
141
|
+
form(valuePath, Form) {
|
|
142
|
+
// strip props from component since we lose information in the cache
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
144
|
+
return this.formCache.retrieveOrCreate(valuePath,
|
|
135
145
|
// strip props from component since we lose information in the cache
|
|
136
146
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
137
|
-
|
|
147
|
+
Form, this);
|
|
138
148
|
}
|
|
139
149
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type FieldsViewProps } from 'core/props';
|
|
3
3
|
import { type ErrorRenderer } from 'mantine/error_renderer';
|
|
4
4
|
import { type Field } from 'types/field';
|
|
5
|
-
declare function Component({ ErrorRenderer, ...props }:
|
|
5
|
+
declare function Component({ ErrorRenderer, ...props }: FieldsViewProps<{
|
|
6
6
|
$: Field<boolean, string>;
|
|
7
7
|
}> & {
|
|
8
8
|
ErrorRenderer?: ErrorRenderer;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { action } from '@storybook/addon-actions';
|
|
3
|
-
import {
|
|
3
|
+
import { useMantineFormFields } from 'mantine/hooks';
|
|
4
4
|
import { CHECKBOX_LABEL } from './checkbox_constants';
|
|
5
5
|
function Component({ ErrorRenderer, ...props }) {
|
|
6
|
-
const inputProps =
|
|
6
|
+
const inputProps = useMantineFormFields(props);
|
|
7
7
|
const CheckboxComponent = inputProps.checkbox('$');
|
|
8
8
|
return (_jsx(CheckboxComponent, { ErrorRenderer: ErrorRenderer, label: CHECKBOX_LABEL }));
|
|
9
9
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type FieldsViewProps } from 'core/props';
|
|
3
3
|
import { type Field } from 'types/field';
|
|
4
|
-
declare function Component(props:
|
|
4
|
+
declare function Component(props: FieldsViewProps<{
|
|
5
5
|
$: Field<string, string>;
|
|
6
6
|
'$.a': Field<string, string>;
|
|
7
7
|
}>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Stack, } from '@mantine/core';
|
|
2
|
+
import { Button, Stack, } from '@mantine/core';
|
|
3
3
|
import { action } from '@storybook/addon-actions';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { useMantineFormFields } from 'mantine/hooks';
|
|
5
|
+
const onClick = action('some button clicked');
|
|
6
|
+
function SubFieldsView(props) {
|
|
7
|
+
const form = useMantineFormFields(props);
|
|
7
8
|
const TextInput = form.textInput('$');
|
|
8
|
-
return _jsx(TextInput, { label: 'sub
|
|
9
|
+
return (_jsxs(Stack, { children: [_jsx(TextInput, { label: 'sub fields view' }), _jsx(Button, { onClick: props.onClick, children: "Bonus Button" })] }));
|
|
9
10
|
}
|
|
10
11
|
function Component(props) {
|
|
11
|
-
const form =
|
|
12
|
-
const
|
|
12
|
+
const form = useMantineFormFields(props);
|
|
13
|
+
const FieldsView = form.fieldsView('$.a', SubFieldsView);
|
|
13
14
|
const TextInput = form.textInput('$');
|
|
14
|
-
return (_jsxs(Stack, { children: [_jsx(TextInput, { label: '
|
|
15
|
+
return (_jsxs(Stack, { children: [_jsx(TextInput, { label: 'fields view' }), _jsx(FieldsView, { onClick: onClick })] }));
|
|
15
16
|
}
|
|
16
17
|
const meta = {
|
|
17
18
|
component: Component,
|
|
@@ -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 './fields_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
|
+
});
|
|
@@ -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>;
|
|
6
|
+
'$.a': Field<number>;
|
|
7
|
+
}>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare const meta: Meta<typeof Component>;
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof Component>;
|
|
11
|
+
export declare const Empty: Story;
|
|
12
|
+
export declare const Populated: Story;
|