@strictly/react-form 0.0.7 → 0.0.9
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 +4 -0
- package/.out/core/mobx/field_adapter_builder.js +31 -0
- package/.out/core/mobx/hooks.d.ts +5 -1
- package/.out/core/mobx/hooks.js +13 -3
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +56 -9
- package/.out/core/mobx/sub_form_field_adapters.d.ts +5 -5
- package/.out/core/mobx/sub_form_field_adapters.js +13 -10
- package/.out/field_converters/chain_field_converter.js +3 -3
- package/.out/field_converters/nullable_to_boolean_converter.d.ts +2 -2
- package/.out/mantine/create_checkbox.js +1 -0
- package/.out/mantine/create_fields_view.d.ts +1 -1
- package/.out/mantine/create_fields_view.js +4 -4
- package/.out/mantine/create_form.d.ts +1 -1
- package/.out/mantine/create_list.d.ts +1 -1
- package/.out/mantine/create_pill.d.ts +1 -1
- package/.out/mantine/create_radio.d.ts +1 -1
- package/.out/mantine/create_radio_group.js +1 -0
- package/.out/mantine/create_text_input.js +7 -2
- package/.out/mantine/create_value_input.js +1 -0
- package/.out/mantine/error_renderer.d.ts +7 -3
- package/.out/mantine/error_renderer.js +1 -1
- package/.out/mantine/hooks.d.ts +9 -9
- package/.out/mantine/specs/checkbox_hooks.stories.d.ts +2 -6
- package/.out/mantine/specs/checkbox_hooks.stories.js +4 -16
- package/.out/mantine/specs/fields_view_hooks.stories.d.ts +1 -1
- package/.out/mantine/specs/fields_view_hooks.stories.js +6 -3
- package/.out/mantine/specs/form_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/form_hooks.stories.js +4 -1
- package/.out/mantine/specs/radio_group_hooks.stories.d.ts +2 -6
- package/.out/mantine/specs/radio_group_hooks.stories.js +5 -17
- package/.out/mantine/specs/select_hooks.stories.d.ts +2 -6
- package/.out/mantine/specs/select_hooks.stories.js +4 -16
- package/.out/mantine/specs/text_input_hooks.stories.d.ts +2 -5
- package/.out/mantine/specs/text_input_hooks.stories.js +5 -5
- package/.out/mantine/specs/value_input_hooks.stories.d.ts +2 -5
- package/.out/mantine/specs/value_input_hooks.stories.js +5 -5
- package/.out/mantine/types.d.ts +4 -2
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/types/specs/{error_type_of_field.tests.js → error_of_field.tests.js} +1 -1
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/core/mobx/field_adapter_builder.ts +71 -0
- package/core/mobx/hooks.ts +24 -6
- package/core/mobx/specs/sub_form_field_adapters.tests.ts +108 -15
- package/core/mobx/sub_form_field_adapters.ts +41 -25
- package/dist/index.cjs +108 -49
- package/dist/index.d.cts +35 -24
- package/dist/index.d.ts +35 -24
- package/dist/index.js +107 -49
- package/field_converters/chain_field_converter.ts +3 -3
- package/field_converters/nullable_to_boolean_converter.ts +2 -3
- package/mantine/create_checkbox.tsx +2 -1
- package/mantine/create_fields_view.tsx +17 -14
- package/mantine/create_form.tsx +2 -2
- package/mantine/create_list.tsx +1 -1
- package/mantine/create_pill.tsx +1 -1
- package/mantine/create_radio.tsx +1 -1
- package/mantine/create_radio_group.tsx +6 -2
- package/mantine/create_text_input.tsx +9 -3
- package/mantine/create_value_input.tsx +2 -1
- package/mantine/error_renderer.ts +13 -4
- package/mantine/hooks.tsx +19 -14
- package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +1 -64
- package/mantine/specs/__snapshots__/fields_view_hooks.tests.tsx.snap +52 -52
- package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +1 -179
- package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +1 -83
- package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +27 -27
- package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +31 -31
- package/mantine/specs/checkbox_hooks.stories.tsx +5 -21
- package/mantine/specs/fields_view_hooks.stories.tsx +16 -4
- package/mantine/specs/form_hooks.stories.tsx +10 -3
- package/mantine/specs/radio_group_hooks.stories.tsx +6 -20
- package/mantine/specs/select_hooks.stories.tsx +5 -21
- package/mantine/specs/text_input_hooks.stories.tsx +5 -8
- package/mantine/specs/value_input_hooks.stories.tsx +5 -8
- package/mantine/types.ts +7 -3
- package/package.json +2 -1
- package/types/specs/{error_type_of_field.tests.ts → error_of_field.tests.ts} +1 -1
- /package/.out/types/specs/{error_type_of_field.tests.d.ts → error_of_field.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
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
11
|
-
[32mCJS[39m ⚡️ Build success in
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
10
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m51.55 KB[39m
|
|
11
|
+
[32mCJS[39m ⚡️ Build success in 120ms
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m47.61 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 121ms
|
|
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 10031ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m37.40 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m37.40 KB[39m
|
|
18
|
+
Done in 11.07s.
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
unreliableIdentityConverter,
|
|
8
8
|
} from 'field_converters/identity_converter'
|
|
9
9
|
import { MaybeIdentityConverter } from 'field_converters/maybe_identity_converter'
|
|
10
|
+
import { TrimmingStringConverter } from 'field_converters/trimming_string_converter'
|
|
10
11
|
import { prototypingFieldValueFactory } from 'field_value_factories/prototyping_field_value_factory'
|
|
11
12
|
import {
|
|
12
13
|
type AnnotatedFieldConverter,
|
|
13
14
|
type FieldValueFactory,
|
|
14
15
|
type TwoWayFieldConverter,
|
|
15
16
|
type TwoWayFieldConverterWithValueFactory,
|
|
17
|
+
UnreliableFieldConversionType,
|
|
16
18
|
type UnreliableFieldConverter,
|
|
17
19
|
} from 'types/field_converters'
|
|
18
20
|
import { type FieldAdapter } from './field_adapter'
|
|
@@ -82,6 +84,65 @@ class FieldAdapterBuilder<
|
|
|
82
84
|
)
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
nullable(): FieldAdapterBuilder<
|
|
88
|
+
From | null,
|
|
89
|
+
To | null,
|
|
90
|
+
E,
|
|
91
|
+
ValuePath,
|
|
92
|
+
Context
|
|
93
|
+
> {
|
|
94
|
+
return this.or(null)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
optional(): FieldAdapterBuilder<
|
|
98
|
+
From | undefined,
|
|
99
|
+
To | undefined,
|
|
100
|
+
E,
|
|
101
|
+
ValuePath,
|
|
102
|
+
Context
|
|
103
|
+
> {
|
|
104
|
+
return this.or(undefined)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private or<V>(proto: V): FieldAdapterBuilder<
|
|
108
|
+
From | V,
|
|
109
|
+
To | V,
|
|
110
|
+
E,
|
|
111
|
+
ValuePath,
|
|
112
|
+
Context
|
|
113
|
+
> {
|
|
114
|
+
function isFrom(v: From | V): v is From {
|
|
115
|
+
return v !== proto
|
|
116
|
+
}
|
|
117
|
+
function isTo(v: To | V): v is To {
|
|
118
|
+
return v !== proto
|
|
119
|
+
}
|
|
120
|
+
return new FieldAdapterBuilder<
|
|
121
|
+
From | V,
|
|
122
|
+
To | V,
|
|
123
|
+
E,
|
|
124
|
+
ValuePath,
|
|
125
|
+
Context
|
|
126
|
+
>(
|
|
127
|
+
(v, valuePath, context) =>
|
|
128
|
+
isFrom(v)
|
|
129
|
+
? this.convert(v, valuePath, context)
|
|
130
|
+
: {
|
|
131
|
+
value: v,
|
|
132
|
+
readonly: false,
|
|
133
|
+
required: false,
|
|
134
|
+
},
|
|
135
|
+
this.create,
|
|
136
|
+
(v, valuePath, context) =>
|
|
137
|
+
isTo(v) && this.revert
|
|
138
|
+
? this.revert(v, valuePath, context)
|
|
139
|
+
: {
|
|
140
|
+
type: UnreliableFieldConversionType.Success,
|
|
141
|
+
value: proto,
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
85
146
|
withIdentity(isFrom: (from: To | From) => from is From): FieldAdapterBuilder<
|
|
86
147
|
From,
|
|
87
148
|
To | From,
|
|
@@ -263,6 +324,16 @@ export function identityAdapter<
|
|
|
263
324
|
)
|
|
264
325
|
}
|
|
265
326
|
|
|
327
|
+
export function trimmingStringAdapter<
|
|
328
|
+
ValuePath extends string,
|
|
329
|
+
Context,
|
|
330
|
+
>() {
|
|
331
|
+
return adapterFromTwoWayConverter<string, string, never, ValuePath, Context>(
|
|
332
|
+
new TrimmingStringConverter<ValuePath, Context>(),
|
|
333
|
+
prototypingFieldValueFactory<string, ValuePath, Context>(''),
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
266
337
|
export function listAdapter<
|
|
267
338
|
E,
|
|
268
339
|
ValuePath extends string,
|
package/core/mobx/hooks.ts
CHANGED
|
@@ -25,12 +25,16 @@ type ModelOfPresenter<P extends FormPresenter<any, any, any, any>> = ReturnType<
|
|
|
25
25
|
export function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, any>>(
|
|
26
26
|
presenter: P,
|
|
27
27
|
value: ValueOfPresenter<P>,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
{
|
|
29
|
+
onValidFieldSubmit,
|
|
30
|
+
onValidFormSubmit,
|
|
31
|
+
}: {
|
|
32
|
+
onValidFieldSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void,
|
|
33
|
+
onValidFormSubmit?: (model: ModelOfPresenter<P>, value: ValueOfPresenter<P>) => void,
|
|
34
|
+
} = {},
|
|
32
35
|
): {
|
|
33
36
|
model: ModelOfPresenter<P>,
|
|
37
|
+
onFormSubmit?: () => void,
|
|
34
38
|
} & Omit<FieldsViewProps<ModelOfPresenter<P>['fields']>, 'fields'> {
|
|
35
39
|
const model = useMemo(function () {
|
|
36
40
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -57,14 +61,14 @@ export function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, a
|
|
|
57
61
|
const onFieldSubmit = useCallback(
|
|
58
62
|
function<Path extends ValuePathsOfPresenter<P>> (valuePath: Path) {
|
|
59
63
|
if (presenter.validateField(model, valuePath)) {
|
|
60
|
-
|
|
64
|
+
onValidFieldSubmit?.(model, valuePath)
|
|
61
65
|
}
|
|
62
66
|
return false
|
|
63
67
|
},
|
|
64
68
|
[
|
|
65
69
|
presenter,
|
|
66
70
|
model,
|
|
67
|
-
|
|
71
|
+
onValidFieldSubmit,
|
|
68
72
|
],
|
|
69
73
|
)
|
|
70
74
|
|
|
@@ -85,10 +89,24 @@ export function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, a
|
|
|
85
89
|
],
|
|
86
90
|
)
|
|
87
91
|
|
|
92
|
+
const onFormSubmit = useCallback(
|
|
93
|
+
function () {
|
|
94
|
+
if (presenter.validateAll(model)) {
|
|
95
|
+
onValidFormSubmit?.(model, model.value)
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
[
|
|
99
|
+
presenter,
|
|
100
|
+
model,
|
|
101
|
+
onValidFormSubmit,
|
|
102
|
+
],
|
|
103
|
+
)
|
|
104
|
+
|
|
88
105
|
return {
|
|
89
106
|
model,
|
|
90
107
|
onFieldValueChange,
|
|
91
108
|
onFieldSubmit,
|
|
92
109
|
onFieldBlur,
|
|
110
|
+
onFormSubmit,
|
|
93
111
|
}
|
|
94
112
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
list,
|
|
2
3
|
numberType,
|
|
3
4
|
object,
|
|
4
5
|
stringType,
|
|
@@ -8,16 +9,18 @@ import {
|
|
|
8
9
|
subFormFieldAdapters,
|
|
9
10
|
} from 'core/mobx/sub_form_field_adapters'
|
|
10
11
|
import { UnreliableFieldConversionType } from 'types/field_converters'
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
mockDeep,
|
|
14
|
+
mockReset,
|
|
15
|
+
} from 'vitest-mock-extended'
|
|
12
16
|
|
|
13
17
|
describe('subFormFieldAdapters', () => {
|
|
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
|
|
18
|
-
|
|
19
18
|
describe('empty value', () => {
|
|
20
|
-
const adapters = subFormFieldAdapters(
|
|
19
|
+
const adapters = subFormFieldAdapters(
|
|
20
|
+
{},
|
|
21
|
+
'$.a',
|
|
22
|
+
stringType,
|
|
23
|
+
)
|
|
21
24
|
|
|
22
25
|
it('equals expected type', () => {
|
|
23
26
|
expectTypeOf(adapters).toEqualTypeOf<{}>()
|
|
@@ -29,15 +32,35 @@ describe('subFormFieldAdapters', () => {
|
|
|
29
32
|
})
|
|
30
33
|
|
|
31
34
|
describe('single adapter', () => {
|
|
35
|
+
const mockedFieldAdapter1 = mockDeep<Required<FieldAdapter<string, boolean, number, '$', string>>>()
|
|
36
|
+
const fieldAdapter1: FieldAdapter<string, boolean, number, '$', string> = mockedFieldAdapter1
|
|
37
|
+
|
|
32
38
|
const type = object().field('a', stringType)
|
|
33
|
-
const
|
|
39
|
+
const subAdapters = {
|
|
34
40
|
$: fieldAdapter1,
|
|
35
|
-
}
|
|
41
|
+
} as const
|
|
42
|
+
const adapters = subFormFieldAdapters<
|
|
43
|
+
typeof subAdapters,
|
|
44
|
+
'$.a',
|
|
45
|
+
{ '$.a': '$.a' },
|
|
46
|
+
typeof type
|
|
47
|
+
>(
|
|
48
|
+
subAdapters,
|
|
49
|
+
'$.a',
|
|
50
|
+
type,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
mockReset(mockedFieldAdapter1)
|
|
55
|
+
})
|
|
36
56
|
|
|
37
57
|
it('equals expected type', () => {
|
|
38
|
-
// TODO toEqualTypeOf (seems to be a TS
|
|
58
|
+
// TODO toEqualTypeOf (cannot reason about revert optionality, seems to be a TS issue as they
|
|
59
|
+
// are both optional AFAICT)
|
|
39
60
|
expectTypeOf(adapters).toMatchTypeOf<{
|
|
40
|
-
'$.a': FieldAdapter<string, boolean
|
|
61
|
+
'$.a': FieldAdapter<string, boolean, number, '$.a', {
|
|
62
|
+
a: string,
|
|
63
|
+
}>,
|
|
41
64
|
}>()
|
|
42
65
|
})
|
|
43
66
|
|
|
@@ -81,12 +104,26 @@ describe('subFormFieldAdapters', () => {
|
|
|
81
104
|
})
|
|
82
105
|
|
|
83
106
|
describe('multiple adapters', () => {
|
|
107
|
+
const mockedFieldAdapter1 = mockDeep<Required<FieldAdapter<string, boolean>>>()
|
|
108
|
+
const fieldAdapter1: FieldAdapter<string, boolean, number> = mockedFieldAdapter1
|
|
109
|
+
const mockedFieldAdapter2 = mockDeep<FieldAdapter<number, boolean>>()
|
|
110
|
+
const fieldAdapter2: FieldAdapter<number, boolean> = mockedFieldAdapter2
|
|
111
|
+
|
|
84
112
|
const type = object()
|
|
85
113
|
.field('a', object().field('x', stringType).field('y', numberType))
|
|
86
|
-
const adapters = subFormFieldAdapters(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
114
|
+
const adapters = subFormFieldAdapters(
|
|
115
|
+
{
|
|
116
|
+
'$.x': fieldAdapter1,
|
|
117
|
+
'$.y': fieldAdapter2,
|
|
118
|
+
},
|
|
119
|
+
'$.a',
|
|
120
|
+
type,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
mockReset(mockedFieldAdapter1)
|
|
125
|
+
mockReset(mockedFieldAdapter2)
|
|
126
|
+
})
|
|
90
127
|
|
|
91
128
|
it('equals expected type', () => {
|
|
92
129
|
// TODO toEqualTypeOf (seems to be a TS error)
|
|
@@ -139,4 +176,60 @@ describe('subFormFieldAdapters', () => {
|
|
|
139
176
|
})
|
|
140
177
|
})
|
|
141
178
|
})
|
|
179
|
+
|
|
180
|
+
describe('list adapter', () => {
|
|
181
|
+
const mockedFieldAdapter1 = mockDeep<Required<FieldAdapter<string, boolean, number, '$', string>>>()
|
|
182
|
+
const fieldAdapter1: FieldAdapter<string, boolean, number, '$', string> = mockedFieldAdapter1
|
|
183
|
+
const type = list(stringType)
|
|
184
|
+
const subAdapters = {
|
|
185
|
+
$: fieldAdapter1,
|
|
186
|
+
}
|
|
187
|
+
const adapters = subFormFieldAdapters<
|
|
188
|
+
typeof subAdapters,
|
|
189
|
+
'$.*',
|
|
190
|
+
{
|
|
191
|
+
'$.*': `$.${number}`,
|
|
192
|
+
},
|
|
193
|
+
typeof type
|
|
194
|
+
>(
|
|
195
|
+
subAdapters,
|
|
196
|
+
'$.*',
|
|
197
|
+
type,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
mockReset(mockedFieldAdapter1)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('equals expected type', () => {
|
|
205
|
+
// TODO toEqualTypeOf (seems to be a TS error)
|
|
206
|
+
expectTypeOf(adapters).toMatchTypeOf<{
|
|
207
|
+
'$.*': FieldAdapter<string, boolean, number, `$.${number}`, string[]>,
|
|
208
|
+
}>()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('equals expected value', () => {
|
|
212
|
+
expect(adapters).toEqual({
|
|
213
|
+
'$.*': expect.anything(),
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('calls convert with correct paths and values', () => {
|
|
218
|
+
const subContext = 'a'
|
|
219
|
+
const context = [subContext]
|
|
220
|
+
|
|
221
|
+
it('calls $.*', () => {
|
|
222
|
+
const mockedReturnedValue = {
|
|
223
|
+
value: false,
|
|
224
|
+
readonly: false,
|
|
225
|
+
required: false,
|
|
226
|
+
}
|
|
227
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue)
|
|
228
|
+
|
|
229
|
+
const returnedValue = adapters['$.*'].convert('b', '$.0', context)
|
|
230
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$', subContext)
|
|
231
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
})
|
|
142
235
|
})
|
|
@@ -9,35 +9,51 @@ import {
|
|
|
9
9
|
type FieldAdapter,
|
|
10
10
|
type FromOfFieldAdapter,
|
|
11
11
|
type ToOfFieldAdapter,
|
|
12
|
+
type ValuePathOfFieldAdapter,
|
|
12
13
|
} from './field_adapter'
|
|
13
14
|
|
|
14
|
-
type SubFormFieldAdapter<F extends FieldAdapter,
|
|
15
|
+
type SubFormFieldAdapter<F extends FieldAdapter, ValuePath extends string, Context> = FieldAdapter<
|
|
15
16
|
FromOfFieldAdapter<F>,
|
|
16
17
|
ToOfFieldAdapter<F>,
|
|
17
18
|
ErrorOfFieldAdapter<F>,
|
|
18
|
-
|
|
19
|
+
ValuePathOfFieldAdapter<F> extends StringConcatOf<'$', infer ValuePathSuffix> ? `${ValuePath}${ValuePathSuffix}`
|
|
20
|
+
// assume string (they don't care about the value path as a type) if there the path doesn't have a $ prefix
|
|
21
|
+
: string,
|
|
19
22
|
Context
|
|
20
23
|
>
|
|
21
24
|
|
|
22
|
-
type SubFormFieldAdapters<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
type SubFormFieldAdapters<
|
|
26
|
+
SubAdapters extends Record<string, FieldAdapter>,
|
|
27
|
+
TypePath extends string,
|
|
28
|
+
ValuePath extends string,
|
|
29
|
+
Context,
|
|
30
|
+
> = {
|
|
31
|
+
[
|
|
32
|
+
K in keyof SubAdapters as K extends StringConcatOf<'$', infer TypePathSuffix> ? `${TypePath}${TypePathSuffix}`
|
|
33
|
+
: never
|
|
34
|
+
]: SubFormFieldAdapter<
|
|
35
|
+
SubAdapters[K],
|
|
36
|
+
ValuePath,
|
|
37
|
+
Context
|
|
38
|
+
>
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
export function subFormFieldAdapters<
|
|
33
|
-
|
|
41
|
+
export function subFormFieldAdapters<
|
|
42
|
+
SubAdapters extends Record<string, FieldAdapter>,
|
|
43
|
+
TypePath extends string,
|
|
44
|
+
TypePathsToValuePaths extends Record<TypePath, string>,
|
|
45
|
+
ContextType extends Type,
|
|
46
|
+
>(
|
|
34
47
|
subAdapters: SubAdapters,
|
|
35
|
-
|
|
48
|
+
parentTypePath: TypePath,
|
|
36
49
|
contextType: ContextType,
|
|
37
|
-
): SubFormFieldAdapters<SubAdapters,
|
|
50
|
+
): SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath], ValueOfType<ContextType>> {
|
|
51
|
+
// assume the number of '.' in the type path will correspond to the number of '.' in the value path
|
|
52
|
+
const dotCount = parentTypePath.split('.').length
|
|
38
53
|
function getSubValuePathAndContext(valuePath: string, context: ValueOfType<ContextType>) {
|
|
39
|
-
const
|
|
40
|
-
const
|
|
54
|
+
const parentValuePath = valuePath.split('.').slice(0, dotCount).join('.')
|
|
55
|
+
const subValuePath = valuePath.replace(parentValuePath, '$')
|
|
56
|
+
const subContext = flattenValuesOfType(contextType, context)[parentValuePath]
|
|
41
57
|
return [
|
|
42
58
|
subValuePath,
|
|
43
59
|
subContext,
|
|
@@ -46,23 +62,23 @@ export function subFormFieldAdapters<SubAdapters extends Record<string, FieldAda
|
|
|
46
62
|
|
|
47
63
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
48
64
|
return Object.entries(subAdapters).reduce<Record<string, FieldAdapter>>((acc, [
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
subTypePath,
|
|
66
|
+
subAdapter,
|
|
51
67
|
]) => {
|
|
52
|
-
const
|
|
68
|
+
const typePath = subTypePath.replace('$', parentTypePath)
|
|
53
69
|
// adapt field adapter with new path and context
|
|
54
70
|
const adaptedAdapter: FieldAdapter = {
|
|
55
71
|
convert: (from, valuePath, context) => {
|
|
56
|
-
return
|
|
72
|
+
return subAdapter.convert(from, ...getSubValuePathAndContext(valuePath, context))
|
|
57
73
|
},
|
|
58
74
|
create: (valuePath, context) => {
|
|
59
|
-
return
|
|
75
|
+
return subAdapter.create(...getSubValuePathAndContext(valuePath, context))
|
|
60
76
|
},
|
|
61
|
-
revert:
|
|
62
|
-
return
|
|
77
|
+
revert: subAdapter.revert && ((from, valuePath, context) => {
|
|
78
|
+
return subAdapter.revert!(from, ...getSubValuePathAndContext(valuePath, context))
|
|
63
79
|
}),
|
|
64
80
|
}
|
|
65
|
-
acc[
|
|
81
|
+
acc[typePath] = adaptedAdapter
|
|
66
82
|
return acc
|
|
67
|
-
}, {}) as SubFormFieldAdapters<SubAdapters,
|
|
83
|
+
}, {}) as SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath], ValueOfType<ContextType>>
|
|
68
84
|
}
|