@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,425 @@
|
|
|
1
|
+
import { assertExists, assertExistsAndReturn, assertState, checkValidNumber, toArray, UnreachableError, } from '@strictly/base';
|
|
2
|
+
import { flattenAccessorsOf, flattenTypeDefsOf, flattenValueTypeTo, jsonPathPop, mobxCopy, valuePathToTypePath, } from '@strictly/define';
|
|
3
|
+
import { computed, observable, runInAction, } from 'mobx';
|
|
4
|
+
import { FieldConversionResult, } from 'types/field_converters';
|
|
5
|
+
export class FormPresenter {
|
|
6
|
+
typeDef;
|
|
7
|
+
adapters;
|
|
8
|
+
constructor(typeDef, adapters) {
|
|
9
|
+
this.typeDef = typeDef;
|
|
10
|
+
this.adapters = adapters;
|
|
11
|
+
}
|
|
12
|
+
maybeGetAdapterForValuePath(valuePath) {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
14
|
+
const typePath = valuePathToTypePath(this.typeDef, valuePath, true);
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
16
|
+
return this.adapters[typePath];
|
|
17
|
+
}
|
|
18
|
+
getAdapterForValuePath(valuePath) {
|
|
19
|
+
return assertExistsAndReturn(this.maybeGetAdapterForValuePath(valuePath), 'expected adapter to be defined {}', valuePath);
|
|
20
|
+
}
|
|
21
|
+
typePath(valuePath) {
|
|
22
|
+
return valuePathToTypePath(this.typeDef, valuePath, true);
|
|
23
|
+
}
|
|
24
|
+
setFieldValueAndValidate(model, valuePath, value) {
|
|
25
|
+
return this.internalSetFieldValue(model, valuePath, value, true);
|
|
26
|
+
}
|
|
27
|
+
setFieldValue(model, valuePath, value) {
|
|
28
|
+
return this.internalSetFieldValue(model, valuePath, value, false);
|
|
29
|
+
}
|
|
30
|
+
addListItem(model, valuePath, elementValue, index) {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
32
|
+
const listValuePath = valuePath;
|
|
33
|
+
const accessor = model.accessors[valuePath];
|
|
34
|
+
const listTypePath = this.typePath(valuePath);
|
|
35
|
+
const definedIndex = index ?? accessor.value.length;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
|
+
const elementTypePath = `${listTypePath}.*`;
|
|
38
|
+
const elementAdapter = assertExistsAndReturn(this.adapters[elementTypePath], 'no adapter specified for list {} ({})', elementTypePath, valuePath);
|
|
39
|
+
// TODO validation on new elements
|
|
40
|
+
const element = elementValue != null
|
|
41
|
+
? elementValue[0]
|
|
42
|
+
: elementAdapter.create(
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
44
|
+
elementTypePath, model.value);
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
const originalList = accessor.value;
|
|
47
|
+
const newList = [
|
|
48
|
+
...originalList.slice(0, definedIndex),
|
|
49
|
+
element,
|
|
50
|
+
...originalList.slice(definedIndex),
|
|
51
|
+
];
|
|
52
|
+
// shuffle the overrides around to account for new indices
|
|
53
|
+
// to so this we need to sort the array indices in descending order
|
|
54
|
+
const targetPaths = Object.keys(model.fieldOverrides).filter(function (v) {
|
|
55
|
+
return v.startsWith(`${listValuePath}.`);
|
|
56
|
+
}).map(function (v) {
|
|
57
|
+
const parts = v.substring(listValuePath.length + 1).split('.');
|
|
58
|
+
const index = parseInt(parts[0]);
|
|
59
|
+
return [
|
|
60
|
+
index,
|
|
61
|
+
parts.slice(1),
|
|
62
|
+
];
|
|
63
|
+
}).filter(function ([index]) {
|
|
64
|
+
return index >= definedIndex;
|
|
65
|
+
}).sort(function ([a], [b]) {
|
|
66
|
+
// descending
|
|
67
|
+
return b - a;
|
|
68
|
+
});
|
|
69
|
+
runInAction(function () {
|
|
70
|
+
targetPaths.forEach(function ([index, postfix,]) {
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
72
|
+
const fromJsonPath = [
|
|
73
|
+
listValuePath,
|
|
74
|
+
`${index}`,
|
|
75
|
+
...postfix,
|
|
76
|
+
].join('.');
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
78
|
+
const toJsonPath = [
|
|
79
|
+
listValuePath,
|
|
80
|
+
`${index + 1}`,
|
|
81
|
+
...postfix,
|
|
82
|
+
].join('.');
|
|
83
|
+
const fieldOverride = model.fieldOverrides[fromJsonPath];
|
|
84
|
+
delete model.fieldOverrides[fromJsonPath];
|
|
85
|
+
model.fieldOverrides[toJsonPath] = fieldOverride;
|
|
86
|
+
const error = model.errors[fromJsonPath];
|
|
87
|
+
delete model.errors[fromJsonPath];
|
|
88
|
+
model.errors[toJsonPath] = error;
|
|
89
|
+
});
|
|
90
|
+
accessor.set(newList);
|
|
91
|
+
// delete any value overrides so the new list isn't shadowed
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
93
|
+
delete model.fieldOverrides[listValuePath];
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
removeListItem(model, elementValuePath) {
|
|
97
|
+
const [listValuePath, elementIndexString,] = assertExistsAndReturn(jsonPathPop(elementValuePath), 'expected a path with two or more segments {}', elementValuePath);
|
|
98
|
+
const accessor = model.accessors[listValuePath];
|
|
99
|
+
const elementIndex = checkValidNumber(parseInt(elementIndexString), 'unexpected index {} ({})', elementIndexString, elementValuePath);
|
|
100
|
+
const newList = [...accessor.value];
|
|
101
|
+
assertState(elementIndex >= 0 && elementIndex < newList.length, 'invalid index from path {} ({})', elementIndex, elementValuePath);
|
|
102
|
+
newList.splice(elementIndex, 1);
|
|
103
|
+
// shuffle the overrides around to account for new indices
|
|
104
|
+
// to so this we need to sort the array indices in descending order
|
|
105
|
+
const targetPaths = Object.keys(model.fieldOverrides).filter(function (v) {
|
|
106
|
+
return v.startsWith(`${listValuePath}.`);
|
|
107
|
+
}).map(function (v) {
|
|
108
|
+
const parts = v.substring(listValuePath.length + 1).split('.');
|
|
109
|
+
const index = parseInt(parts[0]);
|
|
110
|
+
return [
|
|
111
|
+
index,
|
|
112
|
+
parts.slice(1),
|
|
113
|
+
];
|
|
114
|
+
}).filter(function ([index]) {
|
|
115
|
+
return index > elementIndex;
|
|
116
|
+
}).sort(function ([a], [b]) {
|
|
117
|
+
// ascending
|
|
118
|
+
return a - b;
|
|
119
|
+
});
|
|
120
|
+
runInAction(function () {
|
|
121
|
+
targetPaths.forEach(function ([index, postfix,]) {
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
123
|
+
const fromJsonPath = [
|
|
124
|
+
listValuePath,
|
|
125
|
+
`${index}`,
|
|
126
|
+
...postfix,
|
|
127
|
+
].join('.');
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
129
|
+
const toJsonPath = [
|
|
130
|
+
listValuePath,
|
|
131
|
+
`${index - 1}`,
|
|
132
|
+
...postfix,
|
|
133
|
+
].join('.');
|
|
134
|
+
const fieldOverride = model.fieldOverrides[fromJsonPath];
|
|
135
|
+
delete model.fieldOverrides[fromJsonPath];
|
|
136
|
+
model.fieldOverrides[toJsonPath] = fieldOverride;
|
|
137
|
+
const error = model.errors[fromJsonPath];
|
|
138
|
+
delete model.errors[fromJsonPath];
|
|
139
|
+
model.errors[toJsonPath] = error;
|
|
140
|
+
});
|
|
141
|
+
accessor.set(newList);
|
|
142
|
+
// delete any value overrides so the new list isn't shadowed
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
144
|
+
delete model.fieldOverrides[listValuePath];
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
internalSetFieldValue(model, valuePath, value, displayValidation) {
|
|
148
|
+
const { revert } = this.getAdapterForValuePath(valuePath);
|
|
149
|
+
assertExists(revert, 'setting value not supported {}', valuePath);
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
151
|
+
const conversion = revert(value, valuePath, model.value);
|
|
152
|
+
const accessor = model.getAccessorForValuePath(valuePath);
|
|
153
|
+
return runInAction(() => {
|
|
154
|
+
model.fieldOverrides[valuePath] = {
|
|
155
|
+
value,
|
|
156
|
+
};
|
|
157
|
+
switch (conversion.type) {
|
|
158
|
+
case FieldConversionResult.Failure:
|
|
159
|
+
if (displayValidation) {
|
|
160
|
+
model.errors[valuePath] = conversion.error;
|
|
161
|
+
}
|
|
162
|
+
if (conversion.value != null && accessor != null) {
|
|
163
|
+
accessor.set(conversion.value[0]);
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
case FieldConversionResult.Success:
|
|
167
|
+
delete model.errors[valuePath];
|
|
168
|
+
accessor?.set(conversion.value);
|
|
169
|
+
return true;
|
|
170
|
+
default:
|
|
171
|
+
throw new UnreachableError(conversion);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
clearFieldError(model, valuePath) {
|
|
176
|
+
const fieldOverride = model.fieldOverrides[valuePath];
|
|
177
|
+
if (fieldOverride != null) {
|
|
178
|
+
runInAction(function () {
|
|
179
|
+
delete model.errors[valuePath];
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
clearFieldValue(model, valuePath) {
|
|
184
|
+
const typePath = this.typePath(valuePath);
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
186
|
+
const adapter = this.adapters[typePath];
|
|
187
|
+
if (adapter == null) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const { convert, create, } = adapter;
|
|
191
|
+
const accessor = model.accessors[valuePath];
|
|
192
|
+
const value = accessor == null ? create(valuePath, model.value) : accessor.value;
|
|
193
|
+
const displayValue = convert(value, valuePath, model.value);
|
|
194
|
+
runInAction(function () {
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
196
|
+
model.fieldOverrides[valuePath] = {
|
|
197
|
+
value: displayValue,
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
clearAll(model, value) {
|
|
202
|
+
runInAction(() => {
|
|
203
|
+
model.errors = {};
|
|
204
|
+
// TODO this isn't correct, should reload from value
|
|
205
|
+
model.fieldOverrides = {};
|
|
206
|
+
model.value = mobxCopy(this.typeDef, value);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
validateField(model, valuePath) {
|
|
210
|
+
const { convert, revert, create, } = this.getAdapterForValuePath(valuePath);
|
|
211
|
+
const fieldOverride = model.fieldOverrides[valuePath];
|
|
212
|
+
const accessor = model.getAccessorForValuePath(valuePath);
|
|
213
|
+
const storedValue = convert(accessor != null
|
|
214
|
+
? accessor.value
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
216
|
+
: create(valuePath, model.value),
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
218
|
+
valuePath, model.value);
|
|
219
|
+
const value = fieldOverride != null
|
|
220
|
+
? fieldOverride.value
|
|
221
|
+
: storedValue;
|
|
222
|
+
const dirty = storedValue !== value;
|
|
223
|
+
assertExists(revert, 'changing field directly not supported {}', valuePath);
|
|
224
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
225
|
+
const conversion = revert(value, valuePath, model.value);
|
|
226
|
+
return runInAction(function () {
|
|
227
|
+
switch (conversion.type) {
|
|
228
|
+
case FieldConversionResult.Failure:
|
|
229
|
+
model.errors[valuePath] = conversion.error;
|
|
230
|
+
if (conversion.value != null && accessor != null && dirty) {
|
|
231
|
+
accessor.set(conversion.value[0]);
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
case FieldConversionResult.Success:
|
|
235
|
+
delete model.errors[valuePath];
|
|
236
|
+
if (accessor != null && dirty) {
|
|
237
|
+
accessor.set(conversion.value);
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
default:
|
|
241
|
+
throw new UnreachableError(conversion);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
validateAll(model) {
|
|
246
|
+
// sort keys shortest to longest so parent changes don't overwrite child changes
|
|
247
|
+
const accessors = toArray(model.accessors).toSorted(function ([a], [b]) {
|
|
248
|
+
return a.length - b.length;
|
|
249
|
+
});
|
|
250
|
+
return runInAction(() => {
|
|
251
|
+
return accessors.reduce((success, [valuePath, accessor,]) => {
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
253
|
+
const adapterPath = valuePath;
|
|
254
|
+
const adapter = this.maybeGetAdapterForValuePath(adapterPath);
|
|
255
|
+
if (adapter == null) {
|
|
256
|
+
// no adapter == there should be nothing specified for this field
|
|
257
|
+
return success;
|
|
258
|
+
}
|
|
259
|
+
const { convert, revert, } = adapter;
|
|
260
|
+
if (revert == null) {
|
|
261
|
+
// no convert method means this field is immutable
|
|
262
|
+
return success;
|
|
263
|
+
}
|
|
264
|
+
const fieldOverride = model.fieldOverrides[adapterPath];
|
|
265
|
+
const storedValue = convert(accessor.value, valuePath, model.value);
|
|
266
|
+
const value = fieldOverride != null
|
|
267
|
+
? fieldOverride.value
|
|
268
|
+
: storedValue;
|
|
269
|
+
// TODO more nuanced comparison
|
|
270
|
+
const dirty = fieldOverride != null && fieldOverride.value !== storedValue;
|
|
271
|
+
const conversion = revert(value, valuePath, model.value);
|
|
272
|
+
switch (conversion.type) {
|
|
273
|
+
case FieldConversionResult.Failure:
|
|
274
|
+
model.errors[adapterPath] = conversion.error;
|
|
275
|
+
if (conversion.value != null && dirty) {
|
|
276
|
+
accessor.set(conversion.value[0]);
|
|
277
|
+
}
|
|
278
|
+
return false;
|
|
279
|
+
case FieldConversionResult.Success:
|
|
280
|
+
if (dirty) {
|
|
281
|
+
accessor.set(conversion.value);
|
|
282
|
+
}
|
|
283
|
+
delete model.errors[adapterPath];
|
|
284
|
+
return success;
|
|
285
|
+
default:
|
|
286
|
+
throw new UnreachableError(conversion);
|
|
287
|
+
}
|
|
288
|
+
}, true);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
createModel(value) {
|
|
292
|
+
return new FormModel(this.typeDef, value, this.adapters);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
export class FormModel {
|
|
296
|
+
typeDef;
|
|
297
|
+
adapters;
|
|
298
|
+
@observable.ref
|
|
299
|
+
accessor value;
|
|
300
|
+
@observable.shallow
|
|
301
|
+
accessor fieldOverrides;
|
|
302
|
+
@observable.shallow
|
|
303
|
+
accessor errors = {};
|
|
304
|
+
flattenedTypeDefs;
|
|
305
|
+
constructor(typeDef, value, adapters) {
|
|
306
|
+
this.typeDef = typeDef;
|
|
307
|
+
this.adapters = adapters;
|
|
308
|
+
this.value = mobxCopy(typeDef, value);
|
|
309
|
+
this.flattenedTypeDefs = flattenTypeDefsOf(typeDef);
|
|
310
|
+
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
311
|
+
// then returned to
|
|
312
|
+
this.fieldOverrides = flattenValueTypeTo(typeDef, this.value, () => { }, (_t, value, _setter, typePath, valuePath) => {
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
314
|
+
const adapter = this.adapters[typePath];
|
|
315
|
+
if (adapter == null) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const { convert, revert, } = adapter;
|
|
319
|
+
if (revert == null) {
|
|
320
|
+
// no need to store a temporary value if the value cannot be written back
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const displayValue = convert(value, valuePath, this.value);
|
|
324
|
+
return {
|
|
325
|
+
value: displayValue,
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
@computed
|
|
330
|
+
get fields() {
|
|
331
|
+
return new Proxy(this.knownFields, {
|
|
332
|
+
get: (target, prop) => {
|
|
333
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
|
334
|
+
const field = target[prop];
|
|
335
|
+
if (field != null) {
|
|
336
|
+
return field;
|
|
337
|
+
}
|
|
338
|
+
if (typeof prop === 'string') {
|
|
339
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
340
|
+
return this.maybeSynthesizeFieldByValuePath(prop);
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
@computed
|
|
346
|
+
get knownFields() {
|
|
347
|
+
return flattenValueTypeTo(this.typeDef, this.value, () => { },
|
|
348
|
+
// TODO swap these to valuePath, typePath in flatten
|
|
349
|
+
(_t, _v, _setter, typePath, valuePath) => {
|
|
350
|
+
return this.synthesizeFieldByPaths(
|
|
351
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
352
|
+
valuePath,
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
354
|
+
typePath);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
maybeSynthesizeFieldByValuePath(valuePath) {
|
|
358
|
+
let typePath;
|
|
359
|
+
try {
|
|
360
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
361
|
+
typePath = valuePathToTypePath(this.typeDef,
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
363
|
+
valuePath, true);
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
// TODO make jsonValuePathToTypePath return null in the event of an invalid
|
|
367
|
+
// value path instead of throwing an exception
|
|
368
|
+
// assume that the path was invalid
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
return this.synthesizeFieldByPaths(valuePath, typePath);
|
|
372
|
+
}
|
|
373
|
+
synthesizeFieldByPaths(valuePath, typePath) {
|
|
374
|
+
const adapter = this.adapters[typePath];
|
|
375
|
+
if (adapter == null) {
|
|
376
|
+
// invalid path, which can happen
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const { convert, create, } = adapter;
|
|
380
|
+
const fieldOverride = this.fieldOverrides[valuePath];
|
|
381
|
+
const accessor = this.getAccessorForValuePath(valuePath);
|
|
382
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
383
|
+
const fieldTypeDef = this.flattenedTypeDefs[typePath];
|
|
384
|
+
const value = fieldOverride
|
|
385
|
+
? fieldOverride.value
|
|
386
|
+
: convert(accessor != null
|
|
387
|
+
? accessor.value
|
|
388
|
+
: fieldTypeDef != null
|
|
389
|
+
? mobxCopy(fieldTypeDef,
|
|
390
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
391
|
+
create(valuePath, this.value))
|
|
392
|
+
// fake values can't be copied
|
|
393
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
394
|
+
: create(valuePath, this.value),
|
|
395
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
396
|
+
valuePath, this.value);
|
|
397
|
+
const error = this.errors[valuePath];
|
|
398
|
+
return {
|
|
399
|
+
value,
|
|
400
|
+
error,
|
|
401
|
+
// if we can't write it back, then we have to disable it
|
|
402
|
+
disabled: this.isDisabled(valuePath),
|
|
403
|
+
required: this.isRequired(valuePath),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
getAccessorForValuePath(valuePath) {
|
|
407
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
408
|
+
return this.accessors[valuePath];
|
|
409
|
+
}
|
|
410
|
+
@computed
|
|
411
|
+
// should only be referenced internally, so loosely typed
|
|
412
|
+
get accessors() {
|
|
413
|
+
return flattenAccessorsOf(this.typeDef, this.value, (value) => {
|
|
414
|
+
this.value = mobxCopy(this.typeDef, value);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
isDisabled(_valuePath) {
|
|
418
|
+
// TODO infer from types
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
isRequired(_valuePath) {
|
|
422
|
+
// TODO infer from types
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const error = Symbol();
|
|
2
|
+
describe('FlattenedAdaptersOfFIelds', function () {
|
|
3
|
+
it('maps the converter types', function () {
|
|
4
|
+
expectTypeOf().toEqualTypeOf();
|
|
5
|
+
});
|
|
6
|
+
it('ignores extraneous types not listed in the fields', function () {
|
|
7
|
+
expectTypeOf().toEqualTypeOf();
|
|
8
|
+
});
|
|
9
|
+
it('handles multiple fields fields', function () {
|
|
10
|
+
expectTypeOf().toEqualTypeOf();
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { list, nullType, numberType, object, record, stringType, union, } from '@strictly/define';
|
|
2
|
+
describe('FlattenedListTypeDefsOf', function () {
|
|
3
|
+
it('filters lists types', function () {
|
|
4
|
+
const listTypeDef = list(numberType);
|
|
5
|
+
const recordTypeDef = record(stringType);
|
|
6
|
+
const objectTypeDef = object();
|
|
7
|
+
const unionTypeDef = union().add('0', numberType).add('1', nullType);
|
|
8
|
+
const typeDef = object()
|
|
9
|
+
.set('literal', numberType)
|
|
10
|
+
.set('list', listTypeDef)
|
|
11
|
+
.set('record', recordTypeDef)
|
|
12
|
+
.set('object', objectTypeDef)
|
|
13
|
+
.set('union', unionTypeDef);
|
|
14
|
+
expectTypeOf().toEqualTypeOf();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|