@strictly/react-form 0.0.1
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/.eslintrc.cjs +26 -0
- package/.out/.storybook/main.d.ts +3 -0
- package/.out/.storybook/main.js +32 -0
- package/.out/.storybook/preview.d.ts +4 -0
- package/.out/.storybook/preview.js +20 -0
- package/.out/.vitest/install_deterministic_random.d.ts +2 -0
- package/.out/.vitest/install_deterministic_random.js +15 -0
- package/.out/.vitest/install_storybook_preview.d.ts +1 -0
- package/.out/.vitest/install_storybook_preview.js +7 -0
- package/.out/.vitest/match_media.d.ts +1 -0
- package/.out/.vitest/match_media.js +5 -0
- package/.out/.vitest/resize_observer.d.ts +1 -0
- package/.out/.vitest/resize_observer.js +4 -0
- package/.out/core/mobx/field_adapter.d.ts +9 -0
- package/.out/core/mobx/field_adapter.js +1 -0
- package/.out/core/mobx/field_adapter_builder.d.ts +22 -0
- package/.out/core/mobx/field_adapter_builder.js +56 -0
- package/.out/core/mobx/flattened_adapters_of_fields.d.ts +9 -0
- package/.out/core/mobx/flattened_adapters_of_fields.js +1 -0
- package/.out/core/mobx/flattened_list_type_defs_of.d.ts +8 -0
- package/.out/core/mobx/flattened_list_type_defs_of.js +1 -0
- package/.out/core/mobx/form_presenter.d.ts +61 -0
- package/.out/core/mobx/form_presenter.js +425 -0
- package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.d.ts +1 -0
- package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.js +13 -0
- package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.d.ts +1 -0
- package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.js +16 -0
- package/.out/core/mobx/specs/form_presenter.tests.d.ts +1 -0
- package/.out/core/mobx/specs/form_presenter.tests.js +697 -0
- package/.out/core/mobx/types.d.ts +19 -0
- package/.out/core/mobx/types.js +1 -0
- package/.out/core/props.d.ts +12 -0
- package/.out/core/props.js +1 -0
- package/.out/field_converters/chain_field_converter.d.ts +3 -0
- package/.out/field_converters/chain_field_converter.js +46 -0
- package/.out/field_converters/identity_converter.d.ts +3 -0
- package/.out/field_converters/identity_converter.js +14 -0
- package/.out/field_converters/integer_to_string_converter.d.ts +7 -0
- package/.out/field_converters/integer_to_string_converter.js +26 -0
- package/.out/field_converters/list_converter.d.ts +2 -0
- package/.out/field_converters/list_converter.js +8 -0
- package/.out/field_converters/maybe_identity_converter.d.ts +8 -0
- package/.out/field_converters/maybe_identity_converter.js +15 -0
- package/.out/field_converters/nullable_to_boolean_converter.d.ts +11 -0
- package/.out/field_converters/nullable_to_boolean_converter.js +31 -0
- package/.out/field_converters/select_value_type_converter.d.ts +23 -0
- package/.out/field_converters/select_value_type_converter.js +60 -0
- package/.out/field_converters/trimming_string_converter.d.ts +6 -0
- package/.out/field_converters/trimming_string_converter.js +14 -0
- package/.out/field_converters/validating_converter.d.ts +3 -0
- package/.out/field_converters/validating_converter.js +21 -0
- package/.out/field_validators/minimum_string_length_field_validator.d.ts +2 -0
- package/.out/field_validators/minimum_string_length_field_validator.js +8 -0
- package/.out/field_value_factories/prototyping_field_value_factory.d.ts +2 -0
- package/.out/field_value_factories/prototyping_field_value_factory.js +5 -0
- package/.out/index.d.ts +16 -0
- package/.out/index.js +16 -0
- package/.out/mantine/create_checkbox.d.ts +9 -0
- package/.out/mantine/create_checkbox.js +37 -0
- package/.out/mantine/create_list.d.ts +15 -0
- package/.out/mantine/create_list.js +16 -0
- package/.out/mantine/create_pill.d.ts +7 -0
- package/.out/mantine/create_pill.js +15 -0
- package/.out/mantine/create_radio.d.ts +8 -0
- package/.out/mantine/create_radio.js +10 -0
- package/.out/mantine/create_radio_group.d.ts +9 -0
- package/.out/mantine/create_radio_group.js +34 -0
- package/.out/mantine/create_text_input.d.ts +19 -0
- package/.out/mantine/create_text_input.js +38 -0
- package/.out/mantine/create_value_input.d.ts +17 -0
- package/.out/mantine/create_value_input.js +38 -0
- package/.out/mantine/hooks.d.ts +56 -0
- package/.out/mantine/hooks.js +135 -0
- package/.out/mantine/specs/checkbox_constants.d.ts +1 -0
- package/.out/mantine/specs/checkbox_constants.js +1 -0
- package/.out/mantine/specs/checkbox_hooks.stories.d.ts +13 -0
- package/.out/mantine/specs/checkbox_hooks.stories.js +63 -0
- package/.out/mantine/specs/checkbox_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/checkbox_hooks.tests.js +74 -0
- package/.out/mantine/specs/list_hooks.stories.d.ts +11 -0
- package/.out/mantine/specs/list_hooks.stories.js +48 -0
- package/.out/mantine/specs/list_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/list_hooks.tests.js +12 -0
- package/.out/mantine/specs/radio_group_constants.d.ts +4 -0
- package/.out/mantine/specs/radio_group_constants.js +11 -0
- package/.out/mantine/specs/radio_group_hooks.stories.d.ts +14 -0
- package/.out/mantine/specs/radio_group_hooks.stories.js +68 -0
- package/.out/mantine/specs/radio_group_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/radio_group_hooks.tests.js +62 -0
- package/.out/mantine/specs/select_hooks.stories.d.ts +12 -0
- package/.out/mantine/specs/select_hooks.stories.js +57 -0
- package/.out/mantine/specs/select_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/select_hooks.tests.js +12 -0
- package/.out/mantine/specs/select_hooks_constant.d.ts +1 -0
- package/.out/mantine/specs/select_hooks_constant.js +1 -0
- package/.out/mantine/specs/text_input_constants.d.ts +1 -0
- package/.out/mantine/specs/text_input_constants.js +1 -0
- package/.out/mantine/specs/text_input_hooks.stories.d.ts +21 -0
- package/.out/mantine/specs/text_input_hooks.stories.js +88 -0
- package/.out/mantine/specs/text_input_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/text_input_hooks.tests.js +79 -0
- package/.out/mantine/specs/value_input_constants.d.ts +2 -0
- package/.out/mantine/specs/value_input_constants.js +2 -0
- package/.out/mantine/specs/value_input_hooks.stories.d.ts +23 -0
- package/.out/mantine/specs/value_input_hooks.stories.js +124 -0
- package/.out/mantine/specs/value_input_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/value_input_hooks.tests.js +12 -0
- package/.out/mantine/types.d.ts +11 -0
- package/.out/mantine/types.js +1 -0
- package/.out/tsconfig.json +27 -0
- package/.out/tsconfig.tsbuildinfo +1 -0
- package/.out/tsup.config.d.ts +3 -0
- package/.out/tsup.config.js +12 -0
- package/.out/types/all_fields_of_fields.d.ts +5 -0
- package/.out/types/all_fields_of_fields.js +1 -0
- package/.out/types/boolean_fields_of_fields.d.ts +5 -0
- package/.out/types/boolean_fields_of_fields.js +1 -0
- package/.out/types/error_type_of_field.d.ts +2 -0
- package/.out/types/error_type_of_field.js +1 -0
- package/.out/types/field.d.ts +7 -0
- package/.out/types/field.js +1 -0
- package/.out/types/field_converters.d.ts +29 -0
- package/.out/types/field_converters.js +5 -0
- package/.out/types/field_validator.d.ts +3 -0
- package/.out/types/field_validator.js +1 -0
- package/.out/types/flattened_form_fields_of.d.ts +9 -0
- package/.out/types/flattened_form_fields_of.js +1 -0
- package/.out/types/list_fields_of_fields.d.ts +5 -0
- package/.out/types/list_fields_of_fields.js +1 -0
- package/.out/types/specs/boolean_fields_of_fields.tests.d.ts +1 -0
- package/.out/types/specs/boolean_fields_of_fields.tests.js +11 -0
- package/.out/types/specs/error_type_of_field.tests.d.ts +1 -0
- package/.out/types/specs/error_type_of_field.tests.js +7 -0
- package/.out/types/specs/flattened_form_fields_of.tests.d.ts +1 -0
- package/.out/types/specs/flattened_form_fields_of.tests.js +13 -0
- package/.out/types/specs/string_fields_of_fields.tests.d.ts +1 -0
- package/.out/types/specs/string_fields_of_fields.tests.js +19 -0
- package/.out/types/specs/value_type_of_field.tests.d.ts +1 -0
- package/.out/types/specs/value_type_of_field.tests.js +7 -0
- package/.out/types/string_fields_of_fields.d.ts +5 -0
- package/.out/types/string_fields_of_fields.js +1 -0
- package/.out/types/value_type_of_field.d.ts +2 -0
- package/.out/types/value_type_of_field.js +1 -0
- package/.out/util/partial.d.ts +11 -0
- package/.out/util/partial.js +74 -0
- package/.out/vitest.workspace.d.ts +2 -0
- package/.out/vitest.workspace.js +22 -0
- package/.storybook/main.ts +40 -0
- package/.storybook/preview.tsx +28 -0
- package/.storybook/vite.config.mts +38 -0
- package/.turbo/turbo-build.log +18 -0
- package/.turbo/turbo-check-types.log +3 -0
- package/.turbo/turbo-release$colon$exports.log +3 -0
- package/.vitest/install_deterministic_random.ts +17 -0
- package/.vitest/install_storybook_preview.ts +9 -0
- package/.vitest/match_media.ts +7 -0
- package/.vitest/resize_observer.ts +5 -0
- package/README.md +2 -0
- package/core/mobx/field_adapter.ts +32 -0
- package/core/mobx/field_adapter_builder.ts +313 -0
- package/core/mobx/flattened_adapters_of_fields.ts +35 -0
- package/core/mobx/flattened_list_type_defs_of.ts +17 -0
- package/core/mobx/form_presenter.ts +705 -0
- package/core/mobx/specs/flattened_adapters_of_fields.tests.ts +72 -0
- package/core/mobx/specs/flattened_list_type_defs_of.tests.ts +35 -0
- package/core/mobx/specs/form_presenter.tests.ts +989 -0
- package/core/mobx/types.ts +54 -0
- package/core/props.ts +21 -0
- package/dist/index.cjs +11479 -0
- package/dist/index.d.cts +345 -0
- package/dist/index.d.ts +345 -0
- package/dist/index.js +11486 -0
- package/field_converters/chain_field_converter.ts +74 -0
- package/field_converters/identity_converter.ts +39 -0
- package/field_converters/integer_to_string_converter.ts +32 -0
- package/field_converters/list_converter.ts +15 -0
- package/field_converters/maybe_identity_converter.ts +23 -0
- package/field_converters/nullable_to_boolean_converter.ts +56 -0
- package/field_converters/select_value_type_converter.ts +141 -0
- package/field_converters/trimming_string_converter.ts +23 -0
- package/field_converters/validating_converter.ts +35 -0
- package/field_validators/minimum_string_length_field_validator.ts +13 -0
- package/field_value_factories/prototyping_field_value_factory.ts +11 -0
- package/index.ts +16 -0
- package/mantine/create_checkbox.tsx +79 -0
- package/mantine/create_list.tsx +58 -0
- package/mantine/create_pill.tsx +43 -0
- package/mantine/create_radio.tsx +36 -0
- package/mantine/create_radio_group.tsx +71 -0
- package/mantine/create_text_input.tsx +80 -0
- package/mantine/create_value_input.tsx +81 -0
- package/mantine/hooks.tsx +394 -0
- package/mantine/specs/__snapshots__/check_box_hooks.tests.tsx.snap +227 -0
- package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +227 -0
- package/mantine/specs/__snapshots__/list_hooks.tests.tsx.snap +68 -0
- package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +695 -0
- package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +225 -0
- package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +202 -0
- package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +613 -0
- package/mantine/specs/checkbox_constants.ts +1 -0
- package/mantine/specs/checkbox_hooks.stories.tsx +79 -0
- package/mantine/specs/checkbox_hooks.tests.tsx +100 -0
- package/mantine/specs/list_hooks.stories.tsx +83 -0
- package/mantine/specs/list_hooks.tests.tsx +15 -0
- package/mantine/specs/radio_group_constants.ts +12 -0
- package/mantine/specs/radio_group_hooks.stories.tsx +103 -0
- package/mantine/specs/radio_group_hooks.tests.tsx +92 -0
- package/mantine/specs/select_hooks.stories.tsx +77 -0
- package/mantine/specs/select_hooks.tests.tsx +14 -0
- package/mantine/specs/select_hooks_constant.ts +1 -0
- package/mantine/specs/text_input_constants.ts +1 -0
- package/mantine/specs/text_input_hooks.stories.tsx +124 -0
- package/mantine/specs/text_input_hooks.tests.tsx +106 -0
- package/mantine/specs/value_input_constants.ts +2 -0
- package/mantine/specs/value_input_hooks.stories.tsx +182 -0
- package/mantine/specs/value_input_hooks.tests.tsx +14 -0
- package/mantine/types.ts +13 -0
- package/package.exports.json +18 -0
- package/package.json +74 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +27 -0
- package/tsup.config.ts +16 -0
- package/types/all_fields_of_fields.ts +9 -0
- package/types/boolean_fields_of_fields.ts +8 -0
- package/types/error_type_of_field.ts +3 -0
- package/types/field.ts +9 -0
- package/types/field_converters.ts +64 -0
- package/types/field_validator.ts +7 -0
- package/types/flattened_form_fields_of.ts +16 -0
- package/types/list_fields_of_fields.ts +7 -0
- package/types/specs/boolean_fields_of_fields.tests.ts +23 -0
- package/types/specs/error_type_of_field.tests.ts +10 -0
- package/types/specs/flattened_form_fields_of.tests.ts +43 -0
- package/types/specs/string_fields_of_fields.tests.ts +40 -0
- package/types/specs/value_type_of_field.tests.ts +10 -0
- package/types/string_fields_of_fields.ts +6 -0
- package/types/value_type_of_field.ts +3 -0
- package/util/partial.tsx +200 -0
- package/vitest.workspace.ts +26 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertExists,
|
|
3
|
+
assertExistsAndReturn,
|
|
4
|
+
assertState,
|
|
5
|
+
checkValidNumber,
|
|
6
|
+
type ElementOfArray,
|
|
7
|
+
type Maybe,
|
|
8
|
+
toArray,
|
|
9
|
+
UnreachableError,
|
|
10
|
+
} from '@strictly/base'
|
|
11
|
+
import {
|
|
12
|
+
type Accessor,
|
|
13
|
+
type AnyValueType,
|
|
14
|
+
flattenAccessorsOf,
|
|
15
|
+
type FlattenedValueTypesOf,
|
|
16
|
+
flattenTypeDefsOf,
|
|
17
|
+
flattenValueTypeTo,
|
|
18
|
+
jsonPathPop,
|
|
19
|
+
mobxCopy,
|
|
20
|
+
type MobxValueTypeOf,
|
|
21
|
+
type ReadonlyTypeDefOf,
|
|
22
|
+
type StrictTypeDef,
|
|
23
|
+
type Type,
|
|
24
|
+
valuePathToTypePath,
|
|
25
|
+
type ValueTypeOf,
|
|
26
|
+
} from '@strictly/define'
|
|
27
|
+
import {
|
|
28
|
+
computed,
|
|
29
|
+
observable,
|
|
30
|
+
runInAction,
|
|
31
|
+
} from 'mobx'
|
|
32
|
+
import {
|
|
33
|
+
type ReadonlyDeep,
|
|
34
|
+
type SimplifyDeep,
|
|
35
|
+
type StringKeyOf,
|
|
36
|
+
type ValueOf,
|
|
37
|
+
} from 'type-fest'
|
|
38
|
+
import {
|
|
39
|
+
type Field,
|
|
40
|
+
} from 'types/field'
|
|
41
|
+
import {
|
|
42
|
+
FieldConversionResult,
|
|
43
|
+
} from 'types/field_converters'
|
|
44
|
+
import {
|
|
45
|
+
type ErrorTypeOfFieldAdapter,
|
|
46
|
+
type FieldAdapter,
|
|
47
|
+
type ToTypeOfFieldAdapter,
|
|
48
|
+
} from './field_adapter'
|
|
49
|
+
import {
|
|
50
|
+
type FlattenedListTypeDefsOf,
|
|
51
|
+
} from './flattened_list_type_defs_of'
|
|
52
|
+
|
|
53
|
+
export type FlattenedConvertedFieldsOf<
|
|
54
|
+
ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
55
|
+
> = {
|
|
56
|
+
readonly [K in keyof ValuePathsToAdapters]: Field<
|
|
57
|
+
ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
58
|
+
ErrorTypeOfFieldAdapter<ValuePathsToAdapters[K]>
|
|
59
|
+
>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type FlattenedTypePathsToAdaptersOf<
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
FlattenedValues extends Readonly<Record<string, any>>,
|
|
65
|
+
Context,
|
|
66
|
+
> = {
|
|
67
|
+
readonly [
|
|
68
|
+
K in keyof FlattenedValues
|
|
69
|
+
// TODO would be better to use the equivalent readonly typedef, but it causes typescript to
|
|
70
|
+
// infinitely recurse
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
]?: FieldAdapter<ReadonlyDeep<FlattenedValues[K]>, any, any, any, Context>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
|
+
type FieldOverride<V = any> = {
|
|
77
|
+
value: V,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type FlattenedFieldOverrides<
|
|
81
|
+
ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
82
|
+
> = {
|
|
83
|
+
-readonly [K in keyof ValuePathsToAdapters]?: FieldOverride<
|
|
84
|
+
ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>
|
|
85
|
+
>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type FlattenedErrors<
|
|
89
|
+
ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
90
|
+
> = {
|
|
91
|
+
-readonly [K in keyof ValuePathsToAdapters]?: ErrorTypeOfFieldAdapter<ValuePathsToAdapters[K]>
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type ValuePathsToAdaptersOf<
|
|
95
|
+
TypePathsToAdapters extends Partial<Readonly<Record<string, FieldAdapter>>>,
|
|
96
|
+
ValuePathsToTypePaths extends Readonly<Record<string, string>>,
|
|
97
|
+
> = keyof TypePathsToAdapters extends ValueOf<ValuePathsToTypePaths> ? {
|
|
98
|
+
readonly [
|
|
99
|
+
K in keyof ValuePathsToTypePaths as unknown extends TypePathsToAdapters[ValuePathsToTypePaths[K]] ? never : K
|
|
100
|
+
]: NonNullable<TypePathsToAdapters[ValuePathsToTypePaths[K]]>
|
|
101
|
+
}
|
|
102
|
+
: never
|
|
103
|
+
|
|
104
|
+
export class FormPresenter<
|
|
105
|
+
T extends Type,
|
|
106
|
+
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
107
|
+
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
108
|
+
FlattenedValueTypesOf<T, '*'>,
|
|
109
|
+
ValueTypeOf<ReadonlyTypeDefOf<T>>
|
|
110
|
+
>,
|
|
111
|
+
ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<
|
|
112
|
+
TypePathsToAdapters,
|
|
113
|
+
ValueToTypePaths
|
|
114
|
+
>,
|
|
115
|
+
> {
|
|
116
|
+
constructor(
|
|
117
|
+
readonly typeDef: T,
|
|
118
|
+
private readonly adapters: TypePathsToAdapters,
|
|
119
|
+
) {
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private maybeGetAdapterForValuePath(valuePath: keyof ValuePathsToAdapters) {
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
124
|
+
const typePath = valuePathToTypePath(this.typeDef, valuePath as string, true)
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
126
|
+
return this.adapters[typePath as keyof TypePathsToAdapters]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private getAdapterForValuePath(valuePath: keyof ValuePathsToAdapters) {
|
|
130
|
+
return assertExistsAndReturn(
|
|
131
|
+
this.maybeGetAdapterForValuePath(valuePath),
|
|
132
|
+
'expected adapter to be defined {}',
|
|
133
|
+
valuePath,
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K] {
|
|
138
|
+
return valuePathToTypePath<ValueToTypePaths, K>(this.typeDef, valuePath, true)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setFieldValueAndValidate<K extends keyof ValuePathsToAdapters>(
|
|
142
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
143
|
+
valuePath: K,
|
|
144
|
+
value: ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
145
|
+
): boolean {
|
|
146
|
+
return this.internalSetFieldValue(model, valuePath, value, true)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setFieldValue<K extends keyof ValuePathsToAdapters>(
|
|
150
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
151
|
+
valuePath: K,
|
|
152
|
+
value: ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
153
|
+
): boolean {
|
|
154
|
+
return this.internalSetFieldValue(model, valuePath, value, false)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
addListItem<K extends keyof FlattenedListTypeDefsOf<T>>(
|
|
158
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
159
|
+
valuePath: K,
|
|
160
|
+
elementValue: Maybe<ElementOfArray<FlattenedValueTypesOf<T>[K]>>,
|
|
161
|
+
index?: number,
|
|
162
|
+
) {
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
164
|
+
const listValuePath = valuePath as string
|
|
165
|
+
const accessor = model.accessors[valuePath]
|
|
166
|
+
const listTypePath = this.typePath(valuePath)
|
|
167
|
+
const definedIndex = index ?? accessor.value.length
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
169
|
+
const elementTypePath = `${listTypePath}.*` as keyof TypePathsToAdapters
|
|
170
|
+
const elementAdapter = assertExistsAndReturn(
|
|
171
|
+
this.adapters[elementTypePath],
|
|
172
|
+
'no adapter specified for list {} ({})',
|
|
173
|
+
elementTypePath,
|
|
174
|
+
valuePath,
|
|
175
|
+
)
|
|
176
|
+
// TODO validation on new elements
|
|
177
|
+
const element = elementValue != null
|
|
178
|
+
? elementValue[0]
|
|
179
|
+
: elementAdapter.create(
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
181
|
+
elementTypePath as string,
|
|
182
|
+
model.value,
|
|
183
|
+
)
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
+
const originalList: any[] = accessor.value
|
|
186
|
+
const newList = [
|
|
187
|
+
...originalList.slice(0, definedIndex),
|
|
188
|
+
element,
|
|
189
|
+
...originalList.slice(definedIndex),
|
|
190
|
+
]
|
|
191
|
+
// shuffle the overrides around to account for new indices
|
|
192
|
+
// to so this we need to sort the array indices in descending order
|
|
193
|
+
const targetPaths = Object.keys(model.fieldOverrides).filter(function (v) {
|
|
194
|
+
return v.startsWith(`${listValuePath}.`)
|
|
195
|
+
}).map(function (v) {
|
|
196
|
+
const parts = v.substring(listValuePath.length + 1).split('.')
|
|
197
|
+
const index = parseInt(parts[0])
|
|
198
|
+
return [
|
|
199
|
+
index,
|
|
200
|
+
parts.slice(1),
|
|
201
|
+
] as const
|
|
202
|
+
}).filter(function ([index]) {
|
|
203
|
+
return index >= definedIndex
|
|
204
|
+
}).sort(function ([a], [b]) {
|
|
205
|
+
// descending
|
|
206
|
+
return b - a
|
|
207
|
+
})
|
|
208
|
+
runInAction(function () {
|
|
209
|
+
targetPaths.forEach(function ([
|
|
210
|
+
index,
|
|
211
|
+
postfix,
|
|
212
|
+
]) {
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
214
|
+
const fromJsonPath = [
|
|
215
|
+
listValuePath,
|
|
216
|
+
`${index}`,
|
|
217
|
+
...postfix,
|
|
218
|
+
].join('.') as keyof ValuePathsToAdapters
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
220
|
+
const toJsonPath = [
|
|
221
|
+
listValuePath,
|
|
222
|
+
`${index + 1}`,
|
|
223
|
+
...postfix,
|
|
224
|
+
].join('.') as keyof ValuePathsToAdapters
|
|
225
|
+
const fieldOverride = model.fieldOverrides[fromJsonPath]
|
|
226
|
+
delete model.fieldOverrides[fromJsonPath]
|
|
227
|
+
model.fieldOverrides[toJsonPath] = fieldOverride
|
|
228
|
+
const error = model.errors[fromJsonPath]
|
|
229
|
+
delete model.errors[fromJsonPath]
|
|
230
|
+
model.errors[toJsonPath] = error
|
|
231
|
+
})
|
|
232
|
+
accessor.set(newList)
|
|
233
|
+
// delete any value overrides so the new list isn't shadowed
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
235
|
+
delete model.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
removeListItem<K extends keyof FlattenedListTypeDefsOf<T>>(
|
|
240
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
241
|
+
elementValuePath: `${K}.${number}`,
|
|
242
|
+
) {
|
|
243
|
+
const [
|
|
244
|
+
listValuePath,
|
|
245
|
+
elementIndexString,
|
|
246
|
+
] = assertExistsAndReturn(
|
|
247
|
+
jsonPathPop(elementValuePath),
|
|
248
|
+
'expected a path with two or more segments {}',
|
|
249
|
+
elementValuePath,
|
|
250
|
+
)
|
|
251
|
+
const accessor = model.accessors[listValuePath]
|
|
252
|
+
const elementIndex = checkValidNumber(
|
|
253
|
+
parseInt(elementIndexString),
|
|
254
|
+
'unexpected index {} ({})',
|
|
255
|
+
elementIndexString,
|
|
256
|
+
elementValuePath,
|
|
257
|
+
)
|
|
258
|
+
const newList = [...accessor.value]
|
|
259
|
+
assertState(
|
|
260
|
+
elementIndex >= 0 && elementIndex < newList.length,
|
|
261
|
+
'invalid index from path {} ({})',
|
|
262
|
+
elementIndex,
|
|
263
|
+
elementValuePath,
|
|
264
|
+
)
|
|
265
|
+
newList.splice(elementIndex, 1)
|
|
266
|
+
|
|
267
|
+
// shuffle the overrides around to account for new indices
|
|
268
|
+
// to so this we need to sort the array indices in descending order
|
|
269
|
+
const targetPaths = Object.keys(model.fieldOverrides).filter(function (v) {
|
|
270
|
+
return v.startsWith(`${listValuePath}.`)
|
|
271
|
+
}).map(function (v) {
|
|
272
|
+
const parts = v.substring(listValuePath.length + 1).split('.')
|
|
273
|
+
const index = parseInt(parts[0])
|
|
274
|
+
return [
|
|
275
|
+
index,
|
|
276
|
+
parts.slice(1),
|
|
277
|
+
] as const
|
|
278
|
+
}).filter(function ([index]) {
|
|
279
|
+
return index > elementIndex
|
|
280
|
+
}).sort(function ([a], [b]) {
|
|
281
|
+
// ascending
|
|
282
|
+
return a - b
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
runInAction(function () {
|
|
286
|
+
targetPaths.forEach(function ([
|
|
287
|
+
index,
|
|
288
|
+
postfix,
|
|
289
|
+
]) {
|
|
290
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
291
|
+
const fromJsonPath = [
|
|
292
|
+
listValuePath,
|
|
293
|
+
`${index}`,
|
|
294
|
+
...postfix,
|
|
295
|
+
].join('.') as keyof ValuePathsToAdapters
|
|
296
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
297
|
+
const toJsonPath = [
|
|
298
|
+
listValuePath,
|
|
299
|
+
`${index - 1}`,
|
|
300
|
+
...postfix,
|
|
301
|
+
].join('.') as keyof ValuePathsToAdapters
|
|
302
|
+
const fieldOverride = model.fieldOverrides[fromJsonPath]
|
|
303
|
+
delete model.fieldOverrides[fromJsonPath]
|
|
304
|
+
model.fieldOverrides[toJsonPath] = fieldOverride
|
|
305
|
+
const error = model.errors[fromJsonPath]
|
|
306
|
+
delete model.errors[fromJsonPath]
|
|
307
|
+
model.errors[toJsonPath] = error
|
|
308
|
+
})
|
|
309
|
+
accessor.set(newList)
|
|
310
|
+
// delete any value overrides so the new list isn't shadowed
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
312
|
+
delete model.fieldOverrides[listValuePath as keyof ValuePathsToAdapters]
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private internalSetFieldValue<K extends keyof ValuePathsToAdapters>(
|
|
317
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
318
|
+
valuePath: K,
|
|
319
|
+
value: ToTypeOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
320
|
+
displayValidation: boolean,
|
|
321
|
+
): boolean {
|
|
322
|
+
const { revert } = this.getAdapterForValuePath(valuePath)
|
|
323
|
+
|
|
324
|
+
assertExists(revert, 'setting value not supported {}', valuePath)
|
|
325
|
+
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
327
|
+
const conversion = revert(value, valuePath as any, model.value)
|
|
328
|
+
const accessor = model.getAccessorForValuePath(valuePath)
|
|
329
|
+
return runInAction(() => {
|
|
330
|
+
model.fieldOverrides[valuePath] = {
|
|
331
|
+
value,
|
|
332
|
+
}
|
|
333
|
+
switch (conversion.type) {
|
|
334
|
+
case FieldConversionResult.Failure:
|
|
335
|
+
if (displayValidation) {
|
|
336
|
+
model.errors[valuePath] = conversion.error
|
|
337
|
+
}
|
|
338
|
+
if (conversion.value != null && accessor != null) {
|
|
339
|
+
accessor.set(conversion.value[0])
|
|
340
|
+
}
|
|
341
|
+
return false
|
|
342
|
+
case FieldConversionResult.Success:
|
|
343
|
+
delete model.errors[valuePath]
|
|
344
|
+
accessor?.set(conversion.value)
|
|
345
|
+
return true
|
|
346
|
+
default:
|
|
347
|
+
throw new UnreachableError(conversion)
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
clearFieldError<K extends keyof ValuePathsToAdapters>(
|
|
353
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
354
|
+
valuePath: K,
|
|
355
|
+
) {
|
|
356
|
+
const fieldOverride = model.fieldOverrides[valuePath]
|
|
357
|
+
if (fieldOverride != null) {
|
|
358
|
+
runInAction(function () {
|
|
359
|
+
delete model.errors[valuePath]
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
clearFieldValue<K extends StringKeyOf<ValuePathsToAdapters>>(
|
|
365
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
366
|
+
valuePath: K,
|
|
367
|
+
) {
|
|
368
|
+
const typePath = this.typePath(valuePath)
|
|
369
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
370
|
+
const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
|
|
371
|
+
if (adapter == null) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
const {
|
|
375
|
+
convert,
|
|
376
|
+
create,
|
|
377
|
+
} = adapter
|
|
378
|
+
const accessor = model.accessors[valuePath]
|
|
379
|
+
const value = accessor == null ? create(valuePath, model.value) : accessor.value
|
|
380
|
+
const displayValue = convert(value, valuePath, model.value)
|
|
381
|
+
runInAction(function () {
|
|
382
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
383
|
+
model.fieldOverrides[valuePath as unknown as keyof ValuePathsToAdapters] = {
|
|
384
|
+
value: displayValue,
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
clearAll(
|
|
390
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
391
|
+
value: ValueTypeOf<T>,
|
|
392
|
+
): void {
|
|
393
|
+
runInAction(() => {
|
|
394
|
+
model.errors = {}
|
|
395
|
+
// TODO this isn't correct, should reload from value
|
|
396
|
+
model.fieldOverrides = {}
|
|
397
|
+
model.value = mobxCopy(this.typeDef, value)
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
validateField<K extends keyof ValuePathsToAdapters>(
|
|
402
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
403
|
+
valuePath: K,
|
|
404
|
+
): boolean {
|
|
405
|
+
const {
|
|
406
|
+
convert,
|
|
407
|
+
revert,
|
|
408
|
+
create,
|
|
409
|
+
} = this.getAdapterForValuePath(valuePath)
|
|
410
|
+
const fieldOverride = model.fieldOverrides[valuePath]
|
|
411
|
+
const accessor = model.getAccessorForValuePath(valuePath)
|
|
412
|
+
const storedValue = convert(
|
|
413
|
+
accessor != null
|
|
414
|
+
? accessor.value
|
|
415
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
416
|
+
: create(valuePath as string, model.value),
|
|
417
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
418
|
+
valuePath as string,
|
|
419
|
+
model.value,
|
|
420
|
+
)
|
|
421
|
+
const value = fieldOverride != null
|
|
422
|
+
? fieldOverride.value
|
|
423
|
+
: storedValue
|
|
424
|
+
const dirty = storedValue !== value
|
|
425
|
+
assertExists(revert, 'changing field directly not supported {}', valuePath)
|
|
426
|
+
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
428
|
+
const conversion = revert(value, valuePath as string, model.value)
|
|
429
|
+
return runInAction(function () {
|
|
430
|
+
switch (conversion.type) {
|
|
431
|
+
case FieldConversionResult.Failure:
|
|
432
|
+
model.errors[valuePath] = conversion.error
|
|
433
|
+
if (conversion.value != null && accessor != null && dirty) {
|
|
434
|
+
accessor.set(conversion.value[0])
|
|
435
|
+
}
|
|
436
|
+
return false
|
|
437
|
+
case FieldConversionResult.Success:
|
|
438
|
+
delete model.errors[valuePath]
|
|
439
|
+
if (accessor != null && dirty) {
|
|
440
|
+
accessor.set(conversion.value)
|
|
441
|
+
}
|
|
442
|
+
return true
|
|
443
|
+
default:
|
|
444
|
+
throw new UnreachableError(conversion)
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
validateAll(model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>): boolean {
|
|
450
|
+
// sort keys shortest to longest so parent changes don't overwrite child changes
|
|
451
|
+
const accessors = toArray(model.accessors).toSorted(function ([a], [b]) {
|
|
452
|
+
return a.length - b.length
|
|
453
|
+
})
|
|
454
|
+
return runInAction(() => {
|
|
455
|
+
return accessors.reduce(
|
|
456
|
+
(
|
|
457
|
+
success,
|
|
458
|
+
[
|
|
459
|
+
valuePath,
|
|
460
|
+
accessor,
|
|
461
|
+
],
|
|
462
|
+
): boolean => {
|
|
463
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
464
|
+
const adapterPath = valuePath as keyof ValuePathsToAdapters
|
|
465
|
+
const adapter = this.maybeGetAdapterForValuePath(adapterPath)
|
|
466
|
+
if (adapter == null) {
|
|
467
|
+
// no adapter == there should be nothing specified for this field
|
|
468
|
+
return success
|
|
469
|
+
}
|
|
470
|
+
const {
|
|
471
|
+
convert,
|
|
472
|
+
revert,
|
|
473
|
+
} = adapter
|
|
474
|
+
if (revert == null) {
|
|
475
|
+
// no convert method means this field is immutable
|
|
476
|
+
return success
|
|
477
|
+
}
|
|
478
|
+
const fieldOverride = model.fieldOverrides[adapterPath]
|
|
479
|
+
const storedValue = convert(accessor.value, valuePath, model.value)
|
|
480
|
+
const value = fieldOverride != null
|
|
481
|
+
? fieldOverride.value
|
|
482
|
+
: storedValue
|
|
483
|
+
// TODO more nuanced comparison
|
|
484
|
+
const dirty = fieldOverride != null && fieldOverride.value !== storedValue
|
|
485
|
+
|
|
486
|
+
const conversion = revert(value, valuePath, model.value)
|
|
487
|
+
switch (conversion.type) {
|
|
488
|
+
case FieldConversionResult.Failure:
|
|
489
|
+
model.errors[adapterPath] = conversion.error
|
|
490
|
+
if (conversion.value != null && dirty) {
|
|
491
|
+
accessor.set(conversion.value[0])
|
|
492
|
+
}
|
|
493
|
+
return false
|
|
494
|
+
case FieldConversionResult.Success:
|
|
495
|
+
if (dirty) {
|
|
496
|
+
accessor.set(conversion.value)
|
|
497
|
+
}
|
|
498
|
+
delete model.errors[adapterPath]
|
|
499
|
+
return success
|
|
500
|
+
default:
|
|
501
|
+
throw new UnreachableError(conversion)
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
true,
|
|
505
|
+
)
|
|
506
|
+
})
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
createModel(value: ValueTypeOf<ReadonlyTypeDefOf<T>>): FormModel<
|
|
510
|
+
T,
|
|
511
|
+
ValueToTypePaths,
|
|
512
|
+
TypePathsToAdapters,
|
|
513
|
+
ValuePathsToAdapters
|
|
514
|
+
> {
|
|
515
|
+
return new FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>(
|
|
516
|
+
this.typeDef,
|
|
517
|
+
value,
|
|
518
|
+
this.adapters,
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export class FormModel<
|
|
524
|
+
T extends Type,
|
|
525
|
+
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
526
|
+
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
527
|
+
FlattenedValueTypesOf<T, '*'>,
|
|
528
|
+
ValueTypeOf<ReadonlyTypeDefOf<T>>
|
|
529
|
+
>,
|
|
530
|
+
ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<
|
|
531
|
+
TypePathsToAdapters,
|
|
532
|
+
ValueToTypePaths
|
|
533
|
+
>,
|
|
534
|
+
> {
|
|
535
|
+
@observable.ref
|
|
536
|
+
accessor value: MobxValueTypeOf<T>
|
|
537
|
+
@observable.shallow
|
|
538
|
+
accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
539
|
+
@observable.shallow
|
|
540
|
+
accessor errors: FlattenedErrors<ValuePathsToAdapters> = {}
|
|
541
|
+
|
|
542
|
+
private readonly flattenedTypeDefs: Readonly<Record<string, Type>>
|
|
543
|
+
|
|
544
|
+
constructor(
|
|
545
|
+
private readonly typeDef: T,
|
|
546
|
+
value: ValueTypeOf<ReadonlyTypeDefOf<T>>,
|
|
547
|
+
private readonly adapters: TypePathsToAdapters,
|
|
548
|
+
) {
|
|
549
|
+
this.value = mobxCopy(typeDef, value)
|
|
550
|
+
this.flattenedTypeDefs = flattenTypeDefsOf(typeDef)
|
|
551
|
+
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
552
|
+
// then returned to
|
|
553
|
+
this.fieldOverrides = flattenValueTypeTo(
|
|
554
|
+
typeDef,
|
|
555
|
+
this.value,
|
|
556
|
+
() => {},
|
|
557
|
+
(_t: StrictTypeDef, value: AnyValueType, _setter, typePath, valuePath): FieldOverride | undefined => {
|
|
558
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
559
|
+
const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
|
|
560
|
+
if (adapter == null) {
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
const {
|
|
564
|
+
convert,
|
|
565
|
+
revert,
|
|
566
|
+
} = adapter
|
|
567
|
+
if (revert == null) {
|
|
568
|
+
// no need to store a temporary value if the value cannot be written back
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
const displayValue = convert(value, valuePath, this.value)
|
|
572
|
+
return {
|
|
573
|
+
value: displayValue,
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
@computed
|
|
580
|
+
get fields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
581
|
+
return new Proxy<SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>>>(
|
|
582
|
+
this.knownFields,
|
|
583
|
+
{
|
|
584
|
+
get: (target, prop) => {
|
|
585
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
586
|
+
const field = (target as any)[prop]
|
|
587
|
+
if (field != null) {
|
|
588
|
+
return field
|
|
589
|
+
}
|
|
590
|
+
if (typeof prop === 'string') {
|
|
591
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
592
|
+
return this.maybeSynthesizeFieldByValuePath(prop as keyof ValuePathsToAdapters)
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
@computed
|
|
600
|
+
private get knownFields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
601
|
+
return flattenValueTypeTo(
|
|
602
|
+
this.typeDef,
|
|
603
|
+
this.value,
|
|
604
|
+
() => {},
|
|
605
|
+
// TODO swap these to valuePath, typePath in flatten
|
|
606
|
+
(_t: StrictTypeDef, _v: AnyValueType, _setter, typePath, valuePath): Field | undefined => {
|
|
607
|
+
return this.synthesizeFieldByPaths(
|
|
608
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
609
|
+
valuePath as keyof ValuePathsToAdapters,
|
|
610
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
611
|
+
typePath as keyof TypePathsToAdapters,
|
|
612
|
+
)
|
|
613
|
+
},
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private maybeSynthesizeFieldByValuePath(valuePath: keyof ValuePathsToAdapters): Field | undefined {
|
|
618
|
+
let typePath: keyof TypePathsToAdapters
|
|
619
|
+
try {
|
|
620
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
621
|
+
typePath = valuePathToTypePath<ValueToTypePaths, keyof ValueToTypePaths>(
|
|
622
|
+
this.typeDef,
|
|
623
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
624
|
+
valuePath as keyof ValueToTypePaths,
|
|
625
|
+
true,
|
|
626
|
+
) as keyof TypePathsToAdapters
|
|
627
|
+
} catch (e) {
|
|
628
|
+
// TODO make jsonValuePathToTypePath return null in the event of an invalid
|
|
629
|
+
// value path instead of throwing an exception
|
|
630
|
+
// assume that the path was invalid
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
return this.synthesizeFieldByPaths(valuePath, typePath)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private synthesizeFieldByPaths(valuePath: keyof ValuePathsToAdapters, typePath: keyof TypePathsToAdapters) {
|
|
637
|
+
const adapter = this.adapters[typePath]
|
|
638
|
+
if (adapter == null) {
|
|
639
|
+
// invalid path, which can happen
|
|
640
|
+
return
|
|
641
|
+
}
|
|
642
|
+
const {
|
|
643
|
+
convert,
|
|
644
|
+
create,
|
|
645
|
+
} = adapter
|
|
646
|
+
|
|
647
|
+
const fieldOverride = this.fieldOverrides[valuePath]
|
|
648
|
+
const accessor = this.getAccessorForValuePath(valuePath)
|
|
649
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
650
|
+
const fieldTypeDef = this.flattenedTypeDefs[typePath as string]
|
|
651
|
+
const value = fieldOverride
|
|
652
|
+
? fieldOverride.value
|
|
653
|
+
: convert(
|
|
654
|
+
accessor != null
|
|
655
|
+
? accessor.value
|
|
656
|
+
: fieldTypeDef != null
|
|
657
|
+
? mobxCopy(
|
|
658
|
+
fieldTypeDef,
|
|
659
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
660
|
+
create(valuePath as string, this.value),
|
|
661
|
+
)
|
|
662
|
+
// fake values can't be copied
|
|
663
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
664
|
+
: create(valuePath as string, this.value),
|
|
665
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
666
|
+
valuePath as string,
|
|
667
|
+
this.value,
|
|
668
|
+
)
|
|
669
|
+
const error = this.errors[valuePath]
|
|
670
|
+
return {
|
|
671
|
+
value,
|
|
672
|
+
error,
|
|
673
|
+
// if we can't write it back, then we have to disable it
|
|
674
|
+
disabled: this.isDisabled(valuePath),
|
|
675
|
+
required: this.isRequired(valuePath),
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
getAccessorForValuePath(valuePath: keyof ValuePathsToAdapters): Accessor | undefined {
|
|
680
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
681
|
+
return this.accessors[valuePath as string]
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
@computed
|
|
685
|
+
// should only be referenced internally, so loosely typed
|
|
686
|
+
get accessors(): Readonly<Record<string, Accessor>> {
|
|
687
|
+
return flattenAccessorsOf<T, Readonly<Record<string, Accessor>>>(
|
|
688
|
+
this.typeDef,
|
|
689
|
+
this.value,
|
|
690
|
+
(value: ValueTypeOf<T>): void => {
|
|
691
|
+
this.value = mobxCopy(this.typeDef, value)
|
|
692
|
+
},
|
|
693
|
+
)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
protected isDisabled(_valuePath: keyof ValuePathsToAdapters): boolean {
|
|
697
|
+
// TODO infer from types
|
|
698
|
+
return false
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
protected isRequired(_valuePath: keyof ValuePathsToAdapters): boolean {
|
|
702
|
+
// TODO infer from types
|
|
703
|
+
return false
|
|
704
|
+
}
|
|
705
|
+
}
|