@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
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,12 +7,12 @@ $ tsup
|
|
|
7
7
|
[34mCLI[39m Target: esnext
|
|
8
8
|
[34mCJS[39m Build start
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[
|
|
13
|
-
[
|
|
10
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m50.08 KB[39m
|
|
11
|
+
[32mCJS[39m ⚡️ Build success in 121ms
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m46.21 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 122ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
18
|
-
Done in
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 9863ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m36.28 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m36.28 KB[39m
|
|
18
|
+
Done in 10.98s.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ReadonlyTypeOfType,
|
|
3
|
+
type ValueOfType,
|
|
4
|
+
} from '@strictly/define'
|
|
5
|
+
import { type FieldsViewProps } from 'core/props'
|
|
6
|
+
import {
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import { type FormPresenter } from './form_presenter'
|
|
11
|
+
import {
|
|
12
|
+
type ToValueOfPresenterValuePath,
|
|
13
|
+
type ValuePathsOfPresenter,
|
|
14
|
+
} from './types'
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
type ValueOfPresenter<P extends FormPresenter<any, any, any, any>> = P extends FormPresenter<infer T, any, any, any>
|
|
18
|
+
? ValueOfType<ReadonlyTypeOfType<T>>
|
|
19
|
+
: never
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
type ModelOfPresenter<P extends FormPresenter<any, any, any, any>> = ReturnType<P['createModel']>
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
export function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, any>>(
|
|
26
|
+
presenter: P,
|
|
27
|
+
value: ValueOfPresenter<P>,
|
|
28
|
+
onValidSubmit?: <Path extends ValuePathsOfPresenter<P>>(
|
|
29
|
+
model: ModelOfPresenter<P>,
|
|
30
|
+
valuePath: Path,
|
|
31
|
+
) => void,
|
|
32
|
+
): {
|
|
33
|
+
model: ModelOfPresenter<P>,
|
|
34
|
+
} & Omit<FieldsViewProps<ModelOfPresenter<P>['fields']>, 'fields'> {
|
|
35
|
+
const model = useMemo(function () {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
|
+
return presenter.createModel(value) as ReturnType<P['createModel']>
|
|
38
|
+
}, [
|
|
39
|
+
presenter,
|
|
40
|
+
value,
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
const onFieldValueChange = useCallback(
|
|
44
|
+
function<Path extends ValuePathsOfPresenter<P>> (
|
|
45
|
+
path: Path,
|
|
46
|
+
value: ToValueOfPresenterValuePath<P, Path>,
|
|
47
|
+
) {
|
|
48
|
+
presenter.clearFieldError(model, path)
|
|
49
|
+
presenter.setFieldValue<Path>(model, path, value)
|
|
50
|
+
},
|
|
51
|
+
[
|
|
52
|
+
presenter,
|
|
53
|
+
model,
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const onFieldSubmit = useCallback(
|
|
58
|
+
function<Path extends ValuePathsOfPresenter<P>> (valuePath: Path) {
|
|
59
|
+
if (presenter.validateField(model, valuePath)) {
|
|
60
|
+
onValidSubmit?.(model, valuePath)
|
|
61
|
+
}
|
|
62
|
+
return false
|
|
63
|
+
},
|
|
64
|
+
[
|
|
65
|
+
presenter,
|
|
66
|
+
model,
|
|
67
|
+
onValidSubmit,
|
|
68
|
+
],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const onFieldBlur = useCallback(
|
|
72
|
+
function<Path extends ValuePathsOfPresenter<P>> (path: Path) {
|
|
73
|
+
// work around potential loss of focus prior to state potentially invalidating change triggering
|
|
74
|
+
// (e.g. changing a discriminator)
|
|
75
|
+
// TODO debounce?
|
|
76
|
+
setTimeout(function () {
|
|
77
|
+
if (presenter.isValuePathActive(model, path)) {
|
|
78
|
+
presenter.validateField(model, path)
|
|
79
|
+
}
|
|
80
|
+
}, 100)
|
|
81
|
+
},
|
|
82
|
+
[
|
|
83
|
+
presenter,
|
|
84
|
+
model,
|
|
85
|
+
],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
model,
|
|
90
|
+
onFieldValueChange,
|
|
91
|
+
onFieldSubmit,
|
|
92
|
+
onFieldBlur,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -85,14 +85,14 @@ describe('all', function () {
|
|
|
85
85
|
ValueOfType<typeof typeDef>
|
|
86
86
|
>
|
|
87
87
|
>
|
|
88
|
-
|
|
88
|
+
type C = Partial<{
|
|
89
89
|
readonly $: ConvenientFieldAdapter<Readonly<Record<'a' | 'b', number>>, ValueOfType<typeof typeDef>>,
|
|
90
90
|
readonly ['$.a']: ConvenientFieldAdapter<number, ValueOfType<typeof typeDef>>,
|
|
91
91
|
readonly ['$.b']: ConvenientFieldAdapter<number, ValueOfType<typeof typeDef>>,
|
|
92
92
|
}>
|
|
93
93
|
|
|
94
94
|
it('equals expected type', function () {
|
|
95
|
-
expectTypeOf(
|
|
95
|
+
expectTypeOf<C>().toEqualTypeOf<T>()
|
|
96
96
|
})
|
|
97
97
|
})
|
|
98
98
|
|
|
@@ -104,13 +104,13 @@ describe('all', function () {
|
|
|
104
104
|
FlattenedValuesOfType<typeof typeDef>,
|
|
105
105
|
ValueOfType<typeof typeDef>
|
|
106
106
|
>
|
|
107
|
-
|
|
107
|
+
type C = Partial<{
|
|
108
108
|
readonly $: ConvenientFieldAdapter<{ readonly x: string, readonly y: boolean }, ValueOfType<typeof typeDef>>,
|
|
109
109
|
readonly ['$.x']: ConvenientFieldAdapter<string, ValueOfType<typeof typeDef>>,
|
|
110
110
|
readonly ['$.y']: ConvenientFieldAdapter<boolean, ValueOfType<typeof typeDef>>,
|
|
111
111
|
}>
|
|
112
112
|
it('equals expected type', function () {
|
|
113
|
-
expectTypeOf(
|
|
113
|
+
expectTypeOf<C>().toEqualTypeOf<T>()
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
it('matches representative adapters', function () {
|
|
@@ -147,12 +147,12 @@ describe('all', function () {
|
|
|
147
147
|
A,
|
|
148
148
|
typeof valuePathsToTypePaths
|
|
149
149
|
>
|
|
150
|
-
|
|
150
|
+
type C = {
|
|
151
151
|
readonly '$.a': A['$.x'],
|
|
152
152
|
readonly '$.b': A['$.y'],
|
|
153
153
|
}
|
|
154
154
|
it('equals expected type', function () {
|
|
155
|
-
expectTypeOf(
|
|
155
|
+
expectTypeOf<C>().toEqualTypeOf<T>()
|
|
156
156
|
})
|
|
157
157
|
})
|
|
158
158
|
})
|
|
@@ -33,14 +33,14 @@ describe('MergedOfFieldAdapterWithTwoWayConverter', function () {
|
|
|
33
33
|
}
|
|
34
34
|
type M = MergedOfFieldAdaptersWithTwoWayConverter<T, typeof error4, typeof context>
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
type C = {
|
|
37
37
|
readonly x: FieldAdapter<boolean, string, typeof error1 | typeof error4, 'x', typeof context>,
|
|
38
38
|
readonly y: FieldAdapter<number, boolean, typeof error2 | typeof error4, 'y', typeof context>,
|
|
39
39
|
readonly z: FieldAdapter<string, number, typeof error3 | typeof error4, 'z', typeof context>,
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
it('merges the errors', function () {
|
|
43
|
-
expectTypeOf<M>().toEqualTypeOf(
|
|
43
|
+
expectTypeOf<M>().toEqualTypeOf<C>()
|
|
44
44
|
})
|
|
45
45
|
})
|
|
46
46
|
|
|
@@ -117,23 +117,23 @@ describe('mergeFieldAdaptersWithTwoWayConverter', function () {
|
|
|
117
117
|
beforeEach(function () {
|
|
118
118
|
result = merged.booleanAdapter.revert!(true, 'booleanAdapter', context)
|
|
119
119
|
})
|
|
120
|
-
})
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
it('returns the same value on revert', function () {
|
|
122
|
+
expect(result).toEqual(expect.objectContaining({
|
|
123
|
+
value: true,
|
|
124
|
+
type: UnreliableFieldConversionType.Success,
|
|
125
|
+
}))
|
|
126
|
+
})
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
it('calls the mocked converter', function () {
|
|
129
|
+
expect(converter.revert).toHaveBeenCalledOnce()
|
|
130
|
+
expect(converter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context)
|
|
131
|
+
})
|
|
133
132
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
it('calls the mocked adapter', function () {
|
|
134
|
+
expect(booleanAdapter.revert).toHaveBeenCalledOnce()
|
|
135
|
+
expect(booleanAdapter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context)
|
|
136
|
+
})
|
|
137
137
|
})
|
|
138
138
|
})
|
|
139
139
|
})
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
numberType,
|
|
3
|
+
object,
|
|
4
|
+
stringType,
|
|
5
|
+
} from '@strictly/define'
|
|
1
6
|
import { type FieldAdapter } from 'core/mobx/field_adapter'
|
|
2
7
|
import {
|
|
3
8
|
subFormFieldAdapters,
|
|
4
9
|
} from 'core/mobx/sub_form_field_adapters'
|
|
10
|
+
import { UnreliableFieldConversionType } from 'types/field_converters'
|
|
5
11
|
import { mockDeep } from 'vitest-mock-extended'
|
|
6
12
|
|
|
7
13
|
describe('subFormFieldAdapters', () => {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
14
|
+
const mockedFieldAdapter1 = mockDeep<Required<FieldAdapter<string, boolean>>>()
|
|
15
|
+
const fieldAdapter1: FieldAdapter<string, boolean> = mockedFieldAdapter1
|
|
16
|
+
const mockedFieldAdapter2 = mockDeep<FieldAdapter<number, boolean>>()
|
|
17
|
+
const fieldAdapter2: FieldAdapter<number, boolean> = mockedFieldAdapter2
|
|
10
18
|
|
|
11
19
|
describe('empty value', () => {
|
|
12
|
-
const adapters = subFormFieldAdapters({}, '$.a')
|
|
20
|
+
const adapters = subFormFieldAdapters({}, '$.a', stringType)
|
|
13
21
|
|
|
14
22
|
it('equals expected type', () => {
|
|
15
23
|
expectTypeOf(adapters).toEqualTypeOf<{}>()
|
|
@@ -21,29 +29,68 @@ describe('subFormFieldAdapters', () => {
|
|
|
21
29
|
})
|
|
22
30
|
|
|
23
31
|
describe('single adapter', () => {
|
|
32
|
+
const type = object().field('a', stringType)
|
|
24
33
|
const adapters = subFormFieldAdapters({
|
|
25
34
|
$: fieldAdapter1,
|
|
26
|
-
}, '$.a')
|
|
35
|
+
}, '$.a', type)
|
|
27
36
|
|
|
28
37
|
it('equals expected type', () => {
|
|
29
|
-
|
|
38
|
+
// TODO toEqualTypeOf (seems to be a TS error)
|
|
39
|
+
expectTypeOf(adapters).toMatchTypeOf<{
|
|
30
40
|
'$.a': FieldAdapter<string, boolean>,
|
|
31
41
|
}>()
|
|
32
42
|
})
|
|
33
43
|
|
|
34
44
|
it('equals expected value', () => {
|
|
35
|
-
expect(adapters).toEqual({ '$.a':
|
|
45
|
+
expect(adapters).toEqual({ '$.a': expect.anything() })
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('calls convert with the correct paths and values', () => {
|
|
49
|
+
const mockedReturnedValue = {
|
|
50
|
+
value: false,
|
|
51
|
+
required: false,
|
|
52
|
+
readonly: false,
|
|
53
|
+
}
|
|
54
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue)
|
|
55
|
+
|
|
56
|
+
const returnedValue = adapters['$.a'].convert('x', '$.a', { a: 'y' })
|
|
57
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('x', '$', 'y')
|
|
58
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('calls revert with the correct paths and values', () => {
|
|
62
|
+
const mockedReturnedValue = {
|
|
63
|
+
type: UnreliableFieldConversionType.Success,
|
|
64
|
+
value: 'ok',
|
|
65
|
+
} as const
|
|
66
|
+
mockedFieldAdapter1.revert.mockReturnValue(mockedReturnedValue)
|
|
67
|
+
|
|
68
|
+
const returnedValue = adapters['$.a'].revert?.(true, '$.a', { a: 'y' })
|
|
69
|
+
expect(fieldAdapter1.revert).toHaveBeenCalledWith(true, '$', 'y')
|
|
70
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('calls create with the correct paths and values', () => {
|
|
74
|
+
const mockedReturnedValue = 'x'
|
|
75
|
+
mockedFieldAdapter1.create.mockReturnValue(mockedReturnedValue)
|
|
76
|
+
|
|
77
|
+
const returnedValue = adapters['$.a'].create('$.a', { a: 'y' })
|
|
78
|
+
expect(fieldAdapter1.create).toHaveBeenCalledWith('$', 'y')
|
|
79
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
36
80
|
})
|
|
37
81
|
})
|
|
38
82
|
|
|
39
83
|
describe('multiple adapters', () => {
|
|
84
|
+
const type = object()
|
|
85
|
+
.field('a', object().field('x', stringType).field('y', numberType))
|
|
40
86
|
const adapters = subFormFieldAdapters({
|
|
41
87
|
'$.x': fieldAdapter1,
|
|
42
88
|
'$.y': fieldAdapter2,
|
|
43
|
-
}, '$.a')
|
|
89
|
+
}, '$.a', type)
|
|
44
90
|
|
|
45
91
|
it('equals expected type', () => {
|
|
46
|
-
|
|
92
|
+
// TODO toEqualTypeOf (seems to be a TS error)
|
|
93
|
+
expectTypeOf(adapters).toMatchTypeOf<{
|
|
47
94
|
'$.a.x': FieldAdapter<string, boolean>,
|
|
48
95
|
'$.a.y': FieldAdapter<number, boolean>,
|
|
49
96
|
}>()
|
|
@@ -51,8 +98,44 @@ describe('subFormFieldAdapters', () => {
|
|
|
51
98
|
|
|
52
99
|
it('equals expected value', () => {
|
|
53
100
|
expect(adapters).toEqual({
|
|
54
|
-
'$.a.x':
|
|
55
|
-
'$.a.y':
|
|
101
|
+
'$.a.x': expect.anything(),
|
|
102
|
+
'$.a.y': expect.anything(),
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('calls convert with correct paths and values', () => {
|
|
107
|
+
const subContext = {
|
|
108
|
+
x: 'a',
|
|
109
|
+
y: 1,
|
|
110
|
+
} as const
|
|
111
|
+
const context = {
|
|
112
|
+
a: subContext,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
it('calls $.a.x', () => {
|
|
116
|
+
const mockedReturnedValue = {
|
|
117
|
+
value: true,
|
|
118
|
+
readonly: true,
|
|
119
|
+
required: false,
|
|
120
|
+
}
|
|
121
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue)
|
|
122
|
+
|
|
123
|
+
const returnedValue = adapters['$.a.x'].convert('b', '$.a.x', context)
|
|
124
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x', subContext)
|
|
125
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('calls $.a.y', () => {
|
|
129
|
+
const mockedReturnedValue = {
|
|
130
|
+
value: false,
|
|
131
|
+
readonly: false,
|
|
132
|
+
required: false,
|
|
133
|
+
}
|
|
134
|
+
mockedFieldAdapter2.convert.mockReturnValue(mockedReturnedValue)
|
|
135
|
+
|
|
136
|
+
const returnedValue = adapters['$.a.y'].convert(2, '$.a.y', context)
|
|
137
|
+
expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y', subContext)
|
|
138
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
56
139
|
})
|
|
57
140
|
})
|
|
58
141
|
})
|
|
@@ -1,21 +1,68 @@
|
|
|
1
1
|
import { type StringConcatOf } from '@strictly/base'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
flattenValuesOfType,
|
|
4
|
+
type Type,
|
|
5
|
+
type ValueOfType,
|
|
6
|
+
} from '@strictly/define'
|
|
7
|
+
import {
|
|
8
|
+
type ErrorOfFieldAdapter,
|
|
9
|
+
type FieldAdapter,
|
|
10
|
+
type FromOfFieldAdapter,
|
|
11
|
+
type ToOfFieldAdapter,
|
|
12
|
+
} from './field_adapter'
|
|
3
13
|
|
|
4
|
-
type
|
|
5
|
-
|
|
14
|
+
type SubFormFieldAdapter<F extends FieldAdapter, P extends string, Context> = FieldAdapter<
|
|
15
|
+
FromOfFieldAdapter<F>,
|
|
16
|
+
ToOfFieldAdapter<F>,
|
|
17
|
+
ErrorOfFieldAdapter<F>,
|
|
18
|
+
P,
|
|
19
|
+
Context
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
type SubFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string, Context> = {
|
|
23
|
+
[K in keyof SubAdapters as K extends StringConcatOf<'$', infer S> ? `${P}${S}` : never]: K extends
|
|
24
|
+
StringConcatOf<'$', infer S> ? SubFormFieldAdapter<
|
|
25
|
+
SubAdapters[K],
|
|
26
|
+
`${P}${S}`,
|
|
27
|
+
Context
|
|
28
|
+
>
|
|
29
|
+
: never
|
|
6
30
|
}
|
|
7
31
|
|
|
8
|
-
export function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string
|
|
32
|
+
export function subFormFieldAdapters<SubAdapters extends Record<string, FieldAdapter>, P extends string,
|
|
33
|
+
ContextType extends Type>(
|
|
9
34
|
subAdapters: SubAdapters,
|
|
10
35
|
prefix: P,
|
|
11
|
-
|
|
36
|
+
contextType: ContextType,
|
|
37
|
+
): SubFormFieldAdapters<SubAdapters, P, ValueOfType<ContextType>> {
|
|
38
|
+
function getSubValuePathAndContext(valuePath: string, context: ValueOfType<ContextType>) {
|
|
39
|
+
const subValuePath = valuePath.replace(prefix, '$')
|
|
40
|
+
const subContext = flattenValuesOfType(contextType, context)[prefix]
|
|
41
|
+
return [
|
|
42
|
+
subValuePath,
|
|
43
|
+
subContext,
|
|
44
|
+
] as const
|
|
45
|
+
}
|
|
46
|
+
|
|
12
47
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
13
48
|
return Object.entries(subAdapters).reduce<Record<string, FieldAdapter>>((acc, [
|
|
14
49
|
subKey,
|
|
15
50
|
subValue,
|
|
16
51
|
]) => {
|
|
17
52
|
const key = subKey.replace('$', prefix)
|
|
18
|
-
|
|
53
|
+
// adapt field adapter with new path and context
|
|
54
|
+
const adaptedAdapter: FieldAdapter = {
|
|
55
|
+
convert: (from, valuePath, context) => {
|
|
56
|
+
return subValue.convert(from, ...getSubValuePathAndContext(valuePath, context))
|
|
57
|
+
},
|
|
58
|
+
create: (valuePath, context) => {
|
|
59
|
+
return subValue.create(...getSubValuePathAndContext(valuePath, context))
|
|
60
|
+
},
|
|
61
|
+
revert: subValue.revert && ((from, valuePath, context) => {
|
|
62
|
+
return subValue.revert!(from, ...getSubValuePathAndContext(valuePath, context))
|
|
63
|
+
}),
|
|
64
|
+
}
|
|
65
|
+
acc[key] = adaptedAdapter
|
|
19
66
|
return acc
|
|
20
|
-
}, {}) as SubFormFieldAdapters<SubAdapters, P
|
|
67
|
+
}, {}) as SubFormFieldAdapters<SubAdapters, P, ValueOfType<ContextType>>
|
|
21
68
|
}
|
package/core/props.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Fields } from 'types/field'
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type FieldsViewProps<F extends Fields> = {
|
|
4
4
|
fields: F,
|
|
5
5
|
|
|
6
6
|
onFieldValueChange<K extends keyof F>(this: void, key: K, value: F[K]['value']): void,
|
|
@@ -14,7 +14,7 @@ export type FormProps<F extends Fields> = {
|
|
|
14
14
|
onFieldSubmit?(this: void, key: keyof F): boolean | void,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export type
|
|
17
|
+
export type FormProps<O> = {
|
|
18
18
|
value: O,
|
|
19
19
|
|
|
20
20
|
onValueChange: (value: O) => void,
|