@strictly/react-form 0.0.1 → 0.0.2
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.d.ts +7 -6
- package/.out/core/mobx/field_adapter_builder.d.ts +12 -13
- package/.out/core/mobx/field_adapter_builder.js +8 -12
- package/.out/core/mobx/field_adapters_of_values.d.ts +4 -0
- package/.out/core/mobx/flattened_adapters_of_fields.d.ts +2 -2
- package/.out/core/mobx/flattened_list_types_of_type.d.ts +8 -0
- package/.out/core/mobx/form_fields_of_field_adapters.d.ts +8 -0
- package/.out/core/mobx/form_presenter.d.ts +21 -24
- package/.out/core/mobx/form_presenter.js +64 -69
- package/.out/core/mobx/merge_field_adapters_with_two_way_converter.d.ts +13 -0
- package/.out/core/mobx/merge_field_adapters_with_two_way_converter.js +11 -0
- package/.out/core/mobx/merge_field_adapters_with_validators.d.ts +11 -0
- package/.out/core/mobx/merge_field_adapters_with_validators.js +45 -0
- package/.out/core/mobx/specs/fixtures.d.ts +7 -0
- package/.out/core/mobx/specs/fixtures.js +20 -0
- package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.js +5 -2
- package/.out/core/mobx/specs/{flattened_list_type_defs_of.tests.js → flattened_list_types_of_types.tests.js} +7 -7
- package/.out/core/mobx/specs/form_presenter.tests.js +162 -60
- package/.out/core/mobx/specs/merge_field_adapters_with_two_way_converter.js +89 -0
- package/.out/core/mobx/specs/merge_field_adapters_with_validators.tests.js +172 -0
- package/.out/core/mobx/types.d.ts +2 -2
- package/.out/field_converters/chain_field_converter.d.ts +3 -3
- package/.out/field_converters/chain_field_converter.js +17 -12
- package/.out/field_converters/identity_converter.d.ts +3 -3
- package/.out/field_converters/identity_converter.js +10 -6
- package/.out/field_converters/integer_to_string_converter.d.ts +5 -4
- package/.out/field_converters/integer_to_string_converter.js +13 -6
- package/.out/field_converters/list_converter.d.ts +2 -2
- package/.out/field_converters/list_converter.js +6 -1
- package/.out/field_converters/maybe_identity_converter.d.ts +3 -3
- package/.out/field_converters/maybe_identity_converter.js +3 -1
- package/.out/field_converters/nullable_to_boolean_converter.d.ts +9 -8
- package/.out/field_converters/nullable_to_boolean_converter.js +13 -7
- package/.out/field_converters/select_value_type_converter.d.ts +20 -15
- package/.out/field_converters/select_value_type_converter.js +29 -14
- package/.out/field_converters/specs/chain_field_converter.tests.d.ts +1 -0
- package/.out/field_converters/specs/chain_field_converter.tests.js +251 -0
- package/.out/field_converters/trimming_string_converter.d.ts +3 -3
- package/.out/field_converters/trimming_string_converter.js +7 -3
- package/.out/field_converters/validating_converter.d.ts +3 -3
- package/.out/field_converters/validating_converter.js +7 -5
- package/.out/index.d.ts +9 -2
- package/.out/index.js +9 -2
- package/.out/mantine/create_checkbox.d.ts +2 -3
- package/.out/mantine/create_checkbox.js +6 -5
- package/.out/mantine/create_pill.js +2 -2
- package/.out/mantine/create_radio.js +1 -1
- package/.out/mantine/create_radio_group.d.ts +2 -3
- package/.out/mantine/create_radio_group.js +4 -3
- package/.out/mantine/create_text_input.d.ts +2 -3
- package/.out/mantine/create_text_input.js +6 -5
- package/.out/mantine/create_value_input.d.ts +2 -3
- package/.out/mantine/create_value_input.js +6 -5
- package/.out/mantine/error_renderer.d.ts +6 -0
- package/.out/mantine/error_renderer.js +5 -0
- package/.out/mantine/hooks.d.ts +9 -13
- package/.out/mantine/hooks.js +10 -15
- package/.out/mantine/specs/checkbox_hooks.stories.d.ts +7 -2
- package/.out/mantine/specs/checkbox_hooks.stories.js +33 -6
- package/.out/mantine/specs/list_hooks.stories.js +2 -2
- package/.out/mantine/specs/radio_group_hooks.stories.d.ts +7 -2
- package/.out/mantine/specs/radio_group_hooks.stories.js +33 -6
- package/.out/mantine/specs/select_hooks.stories.d.ts +8 -2
- package/.out/mantine/specs/select_hooks.stories.js +45 -8
- package/.out/mantine/specs/text_input_hooks.stories.d.ts +5 -1
- package/.out/mantine/specs/text_input_hooks.stories.js +23 -8
- package/.out/mantine/specs/value_input_hooks.stories.d.ts +7 -2
- package/.out/mantine/specs/value_input_hooks.stories.js +49 -15
- package/.out/mantine/types.d.ts +4 -1
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.out/types/error_of_field.d.ts +2 -0
- package/.out/types/error_of_field.js +1 -0
- package/.out/types/field.d.ts +1 -1
- package/.out/types/field_converters.d.ts +17 -10
- package/.out/types/field_converters.js +5 -5
- package/.out/types/flattened_validators_of_fields.d.ts +8 -0
- package/.out/types/flattened_validators_of_fields.js +1 -0
- package/.out/types/merge_validators.d.ts +7 -0
- package/.out/types/merge_validators.js +38 -0
- package/.out/types/specs/flattened_validators_of_fields.tests.d.ts +1 -0
- package/.out/types/specs/flattened_validators_of_fields.tests.js +16 -0
- package/.out/types/specs/merge_validators.tests.d.ts +1 -0
- package/.out/types/specs/merge_validators.tests.js +192 -0
- package/.out/util/partial.d.ts +11 -5
- package/.out/util/partial.js +55 -15
- package/.turbo/turbo-build.log +9 -9
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-release$colon$exports.log +1 -1
- package/README.md +5 -1
- package/core/mobx/field_adapter.ts +15 -7
- package/core/mobx/field_adapter_builder.ts +39 -75
- package/core/mobx/field_adapters_of_values.ts +17 -0
- package/core/mobx/flattened_adapters_of_fields.ts +3 -3
- package/core/mobx/flattened_list_types_of_type.ts +17 -0
- package/core/mobx/form_fields_of_field_adapters.ts +16 -0
- package/core/mobx/form_presenter.ts +117 -104
- package/core/mobx/merge_field_adapters_with_two_way_converter.ts +68 -0
- package/core/mobx/merge_field_adapters_with_validators.ts +99 -0
- package/core/mobx/specs/fixtures.ts +73 -0
- package/core/mobx/specs/flattened_adapters_of_fields.tests.ts +23 -2
- package/core/mobx/specs/flattened_list_types_of_types.tests.ts +35 -0
- package/core/mobx/specs/form_presenter.tests.ts +248 -124
- package/core/mobx/specs/merge_field_adapters_with_two_way_converter.ts +140 -0
- package/core/mobx/specs/merge_field_adapters_with_validators.tests.ts +259 -0
- package/core/mobx/types.ts +3 -3
- package/dist/index.cjs +459 -211
- package/dist/index.d.cts +153 -111
- package/dist/index.d.ts +153 -111
- package/dist/index.js +453 -200
- package/field_converters/chain_field_converter.ts +37 -23
- package/field_converters/identity_converter.ts +14 -10
- package/field_converters/integer_to_string_converter.ts +15 -9
- package/field_converters/list_converter.ts +8 -3
- package/field_converters/maybe_identity_converter.ts +7 -4
- package/field_converters/nullable_to_boolean_converter.ts +23 -16
- package/field_converters/select_value_type_converter.ts +86 -26
- package/field_converters/specs/chain_field_converter.tests.ts +302 -0
- package/field_converters/trimming_string_converter.ts +11 -6
- package/field_converters/validating_converter.ts +21 -11
- package/index.ts +9 -2
- package/mantine/create_checkbox.tsx +15 -8
- package/mantine/create_list.tsx +1 -4
- package/mantine/create_pill.tsx +2 -2
- package/mantine/create_radio.tsx +1 -1
- package/mantine/create_radio_group.tsx +8 -6
- package/mantine/create_text_input.tsx +20 -8
- package/mantine/create_value_input.tsx +17 -8
- package/mantine/error_renderer.ts +15 -0
- package/mantine/hooks.tsx +25 -51
- package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +126 -0
- package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +356 -0
- package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +208 -12
- package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +45 -0
- package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +194 -8
- package/mantine/specs/checkbox_hooks.stories.tsx +47 -7
- package/mantine/specs/list_hooks.stories.tsx +2 -2
- package/mantine/specs/radio_group_hooks.stories.tsx +47 -7
- package/mantine/specs/select_hooks.stories.tsx +55 -8
- package/mantine/specs/text_input_hooks.stories.tsx +32 -7
- package/mantine/specs/value_input_hooks.stories.tsx +57 -16
- package/mantine/types.ts +5 -1
- package/package.json +16 -4
- package/tsconfig.json +1 -0
- package/types/error_of_field.ts +3 -0
- package/types/field.ts +1 -1
- package/types/field_converters.ts +21 -10
- package/types/flattened_validators_of_fields.ts +34 -0
- package/types/merge_validators.ts +80 -0
- package/types/specs/error_type_of_field.tests.ts +2 -2
- package/types/specs/flattened_validators_of_fields.tests.ts +93 -0
- package/types/specs/merge_validators.tests.ts +267 -0
- package/util/partial.tsx +200 -16
- package/.out/core/mobx/flattened_list_type_defs_of.d.ts +0 -8
- package/.out/field_validators/minimum_string_length_field_validator.d.ts +0 -2
- package/.out/field_validators/minimum_string_length_field_validator.js +0 -8
- package/.out/types/error_type_of_field.d.ts +0 -2
- package/.out/types/field_validator.d.ts +0 -3
- package/.out/types/flattened_form_fields_of.d.ts +0 -9
- package/.out/types/specs/flattened_form_fields_of.tests.js +0 -13
- package/core/mobx/flattened_list_type_defs_of.ts +0 -17
- package/core/mobx/specs/flattened_list_type_defs_of.tests.ts +0 -35
- package/field_validators/minimum_string_length_field_validator.ts +0 -13
- package/mantine/specs/__snapshots__/check_box_hooks.tests.tsx.snap +0 -227
- package/types/error_type_of_field.ts +0 -3
- package/types/field_validator.ts +0 -7
- package/types/flattened_form_fields_of.ts +0 -16
- package/types/specs/flattened_form_fields_of.tests.ts +0 -43
- /package/.out/core/mobx/{flattened_list_type_defs_of.js → field_adapters_of_values.js} +0 -0
- /package/.out/core/mobx/{specs/flattened_list_type_defs_of.tests.d.ts → flattened_list_types_of_type.js} +0 -0
- /package/.out/{types/error_type_of_field.js → core/mobx/form_fields_of_field_adapters.js} +0 -0
- /package/.out/{types/field_validator.js → core/mobx/specs/flattened_list_types_of_types.tests.d.ts} +0 -0
- /package/.out/{types/flattened_form_fields_of.js → core/mobx/specs/merge_field_adapters_with_two_way_converter.d.ts} +0 -0
- /package/.out/{types/specs/flattened_form_fields_of.tests.d.ts → core/mobx/specs/merge_field_adapters_with_validators.tests.d.ts} +0 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
assertState,
|
|
5
5
|
checkValidNumber,
|
|
6
6
|
type ElementOfArray,
|
|
7
|
+
map,
|
|
7
8
|
type Maybe,
|
|
8
9
|
toArray,
|
|
9
10
|
UnreachableError,
|
|
@@ -11,18 +12,19 @@ import {
|
|
|
11
12
|
import {
|
|
12
13
|
type Accessor,
|
|
13
14
|
type AnyValueType,
|
|
14
|
-
|
|
15
|
-
type
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
flattenAccessorsOfType,
|
|
16
|
+
type FlattenedValuesOfType,
|
|
17
|
+
flattenTypesOfType,
|
|
18
|
+
flattenValuesOfType,
|
|
19
|
+
flattenValueTo,
|
|
18
20
|
jsonPathPop,
|
|
19
21
|
mobxCopy,
|
|
20
|
-
type
|
|
21
|
-
type
|
|
22
|
+
type MobxValueOfType,
|
|
23
|
+
type ReadonlyTypeOfType,
|
|
22
24
|
type StrictTypeDef,
|
|
23
25
|
type Type,
|
|
26
|
+
type ValueOfType,
|
|
24
27
|
valuePathToTypePath,
|
|
25
|
-
type ValueTypeOf,
|
|
26
28
|
} from '@strictly/define'
|
|
27
29
|
import {
|
|
28
30
|
computed,
|
|
@@ -39,23 +41,24 @@ import {
|
|
|
39
41
|
type Field,
|
|
40
42
|
} from 'types/field'
|
|
41
43
|
import {
|
|
42
|
-
|
|
44
|
+
type AnnotatedFieldConversion,
|
|
45
|
+
UnreliableFieldConversionType,
|
|
43
46
|
} from 'types/field_converters'
|
|
44
47
|
import {
|
|
45
|
-
type
|
|
48
|
+
type ErrorOfFieldAdapter,
|
|
46
49
|
type FieldAdapter,
|
|
47
|
-
type
|
|
50
|
+
type ToOfFieldAdapter,
|
|
48
51
|
} from './field_adapter'
|
|
49
52
|
import {
|
|
50
|
-
type
|
|
51
|
-
} from './
|
|
53
|
+
type FlattenedListTypesOfType,
|
|
54
|
+
} from './flattened_list_types_of_type'
|
|
52
55
|
|
|
53
56
|
export type FlattenedConvertedFieldsOf<
|
|
54
57
|
ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
55
58
|
> = {
|
|
56
59
|
readonly [K in keyof ValuePathsToAdapters]: Field<
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
ToOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
61
|
+
ErrorOfFieldAdapter<ValuePathsToAdapters[K]>
|
|
59
62
|
>
|
|
60
63
|
}
|
|
61
64
|
|
|
@@ -73,22 +76,20 @@ export type FlattenedTypePathsToAdaptersOf<
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
|
-
type FieldOverride<V = any> =
|
|
77
|
-
value: V,
|
|
78
|
-
}
|
|
79
|
+
type FieldOverride<V = any> = Maybe<V>
|
|
79
80
|
|
|
80
81
|
type FlattenedFieldOverrides<
|
|
81
82
|
ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
82
83
|
> = {
|
|
83
84
|
-readonly [K in keyof ValuePathsToAdapters]?: FieldOverride<
|
|
84
|
-
|
|
85
|
+
ToOfFieldAdapter<ValuePathsToAdapters[K]>
|
|
85
86
|
>
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
type FlattenedErrors<
|
|
89
90
|
ValuePathsToAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
90
91
|
> = {
|
|
91
|
-
-readonly [K in keyof ValuePathsToAdapters]?:
|
|
92
|
+
-readonly [K in keyof ValuePathsToAdapters]?: ErrorOfFieldAdapter<ValuePathsToAdapters[K]>
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export type ValuePathsToAdaptersOf<
|
|
@@ -105,8 +106,8 @@ export class FormPresenter<
|
|
|
105
106
|
T extends Type,
|
|
106
107
|
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
107
108
|
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
FlattenedValuesOfType<T, '*'>,
|
|
110
|
+
ValueOfType<ReadonlyTypeOfType<T>>
|
|
110
111
|
>,
|
|
111
112
|
ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<
|
|
112
113
|
TypePathsToAdapters,
|
|
@@ -114,14 +115,14 @@ export class FormPresenter<
|
|
|
114
115
|
>,
|
|
115
116
|
> {
|
|
116
117
|
constructor(
|
|
117
|
-
readonly
|
|
118
|
+
readonly type: T,
|
|
118
119
|
private readonly adapters: TypePathsToAdapters,
|
|
119
120
|
) {
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
private maybeGetAdapterForValuePath(valuePath: keyof ValuePathsToAdapters) {
|
|
123
124
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
124
|
-
const typePath = valuePathToTypePath(this.
|
|
125
|
+
const typePath = valuePathToTypePath(this.type, valuePath as string, true)
|
|
125
126
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
126
127
|
return this.adapters[typePath as keyof TypePathsToAdapters]
|
|
127
128
|
}
|
|
@@ -135,13 +136,13 @@ export class FormPresenter<
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
typePath<K extends keyof ValueToTypePaths>(valuePath: K): ValueToTypePaths[K] {
|
|
138
|
-
return valuePathToTypePath<ValueToTypePaths, K>(this.
|
|
139
|
+
return valuePathToTypePath<ValueToTypePaths, K>(this.type, valuePath, true)
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
setFieldValueAndValidate<K extends keyof ValuePathsToAdapters>(
|
|
142
143
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
143
144
|
valuePath: K,
|
|
144
|
-
value:
|
|
145
|
+
value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
145
146
|
): boolean {
|
|
146
147
|
return this.internalSetFieldValue(model, valuePath, value, true)
|
|
147
148
|
}
|
|
@@ -149,15 +150,16 @@ export class FormPresenter<
|
|
|
149
150
|
setFieldValue<K extends keyof ValuePathsToAdapters>(
|
|
150
151
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
151
152
|
valuePath: K,
|
|
152
|
-
value:
|
|
153
|
+
value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
153
154
|
): boolean {
|
|
154
155
|
return this.internalSetFieldValue(model, valuePath, value, false)
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
addListItem<K extends keyof
|
|
158
|
+
addListItem<K extends keyof FlattenedListTypesOfType<T>>(
|
|
158
159
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
159
160
|
valuePath: K,
|
|
160
|
-
|
|
161
|
+
// TODO can this type be simplified?
|
|
162
|
+
elementValue: Maybe<ElementOfArray<FlattenedValuesOfType<T>[K]>> = null,
|
|
161
163
|
index?: number,
|
|
162
164
|
) {
|
|
163
165
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -236,7 +238,7 @@ export class FormPresenter<
|
|
|
236
238
|
})
|
|
237
239
|
}
|
|
238
240
|
|
|
239
|
-
removeListItem<K extends keyof
|
|
241
|
+
removeListItem<K extends keyof FlattenedListTypesOfType<T>>(
|
|
240
242
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
241
243
|
elementValuePath: `${K}.${number}`,
|
|
242
244
|
) {
|
|
@@ -316,7 +318,7 @@ export class FormPresenter<
|
|
|
316
318
|
private internalSetFieldValue<K extends keyof ValuePathsToAdapters>(
|
|
317
319
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
318
320
|
valuePath: K,
|
|
319
|
-
value:
|
|
321
|
+
value: ToOfFieldAdapter<ValuePathsToAdapters[K]>,
|
|
320
322
|
displayValidation: boolean,
|
|
321
323
|
): boolean {
|
|
322
324
|
const { revert } = this.getAdapterForValuePath(valuePath)
|
|
@@ -327,11 +329,9 @@ export class FormPresenter<
|
|
|
327
329
|
const conversion = revert(value, valuePath as any, model.value)
|
|
328
330
|
const accessor = model.getAccessorForValuePath(valuePath)
|
|
329
331
|
return runInAction(() => {
|
|
330
|
-
model.fieldOverrides[valuePath] =
|
|
331
|
-
value,
|
|
332
|
-
}
|
|
332
|
+
model.fieldOverrides[valuePath] = [value]
|
|
333
333
|
switch (conversion.type) {
|
|
334
|
-
case
|
|
334
|
+
case UnreliableFieldConversionType.Failure:
|
|
335
335
|
if (displayValidation) {
|
|
336
336
|
model.errors[valuePath] = conversion.error
|
|
337
337
|
}
|
|
@@ -339,7 +339,7 @@ export class FormPresenter<
|
|
|
339
339
|
accessor.set(conversion.value[0])
|
|
340
340
|
}
|
|
341
341
|
return false
|
|
342
|
-
case
|
|
342
|
+
case UnreliableFieldConversionType.Success:
|
|
343
343
|
delete model.errors[valuePath]
|
|
344
344
|
accessor?.set(conversion.value)
|
|
345
345
|
return true
|
|
@@ -377,27 +377,38 @@ export class FormPresenter<
|
|
|
377
377
|
} = adapter
|
|
378
378
|
const accessor = model.accessors[valuePath]
|
|
379
379
|
const value = accessor == null ? create(valuePath, model.value) : accessor.value
|
|
380
|
-
const
|
|
380
|
+
const {
|
|
381
|
+
value: displayValue,
|
|
382
|
+
} = convert(value, valuePath, model.value)
|
|
383
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
384
|
+
const key = valuePath as unknown as keyof ValuePathsToAdapters
|
|
381
385
|
runInAction(function () {
|
|
382
|
-
|
|
383
|
-
model.fieldOverrides[valuePath as unknown as keyof ValuePathsToAdapters] = {
|
|
384
|
-
value: displayValue,
|
|
385
|
-
}
|
|
386
|
+
model.fieldOverrides[key] = [displayValue]
|
|
386
387
|
})
|
|
387
388
|
}
|
|
388
389
|
|
|
389
390
|
clearAll(
|
|
390
391
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
391
|
-
value:
|
|
392
|
+
value: ValueOfType<T>,
|
|
392
393
|
): void {
|
|
393
394
|
runInAction(() => {
|
|
394
395
|
model.errors = {}
|
|
395
396
|
// TODO this isn't correct, should reload from value
|
|
396
397
|
model.fieldOverrides = {}
|
|
397
|
-
model.value = mobxCopy(this.
|
|
398
|
+
model.value = mobxCopy(this.type, value)
|
|
398
399
|
})
|
|
399
400
|
}
|
|
400
401
|
|
|
402
|
+
isValuePathActive<K extends keyof ValuePathsToAdapters>(
|
|
403
|
+
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
404
|
+
valuePath: K,
|
|
405
|
+
): boolean {
|
|
406
|
+
const values = flattenValuesOfType(this.type, model.value)
|
|
407
|
+
const keys = new Set(Object.keys(values))
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
409
|
+
return keys.has(valuePath as string)
|
|
410
|
+
}
|
|
411
|
+
|
|
401
412
|
validateField<K extends keyof ValuePathsToAdapters>(
|
|
402
413
|
model: FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>,
|
|
403
414
|
valuePath: K,
|
|
@@ -409,7 +420,9 @@ export class FormPresenter<
|
|
|
409
420
|
} = this.getAdapterForValuePath(valuePath)
|
|
410
421
|
const fieldOverride = model.fieldOverrides[valuePath]
|
|
411
422
|
const accessor = model.getAccessorForValuePath(valuePath)
|
|
412
|
-
const
|
|
423
|
+
const {
|
|
424
|
+
value: storedValue,
|
|
425
|
+
} = convert(
|
|
413
426
|
accessor != null
|
|
414
427
|
? accessor.value
|
|
415
428
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -419,7 +432,7 @@ export class FormPresenter<
|
|
|
419
432
|
model.value,
|
|
420
433
|
)
|
|
421
434
|
const value = fieldOverride != null
|
|
422
|
-
? fieldOverride
|
|
435
|
+
? fieldOverride[0]
|
|
423
436
|
: storedValue
|
|
424
437
|
const dirty = storedValue !== value
|
|
425
438
|
assertExists(revert, 'changing field directly not supported {}', valuePath)
|
|
@@ -428,13 +441,13 @@ export class FormPresenter<
|
|
|
428
441
|
const conversion = revert(value, valuePath as string, model.value)
|
|
429
442
|
return runInAction(function () {
|
|
430
443
|
switch (conversion.type) {
|
|
431
|
-
case
|
|
444
|
+
case UnreliableFieldConversionType.Failure:
|
|
432
445
|
model.errors[valuePath] = conversion.error
|
|
433
446
|
if (conversion.value != null && accessor != null && dirty) {
|
|
434
447
|
accessor.set(conversion.value[0])
|
|
435
448
|
}
|
|
436
449
|
return false
|
|
437
|
-
case
|
|
450
|
+
case UnreliableFieldConversionType.Success:
|
|
438
451
|
delete model.errors[valuePath]
|
|
439
452
|
if (accessor != null && dirty) {
|
|
440
453
|
accessor.set(conversion.value)
|
|
@@ -476,22 +489,24 @@ export class FormPresenter<
|
|
|
476
489
|
return success
|
|
477
490
|
}
|
|
478
491
|
const fieldOverride = model.fieldOverrides[adapterPath]
|
|
479
|
-
const
|
|
492
|
+
const {
|
|
493
|
+
value: storedValue,
|
|
494
|
+
} = convert(accessor.value, valuePath, model.value)
|
|
480
495
|
const value = fieldOverride != null
|
|
481
|
-
? fieldOverride
|
|
496
|
+
? fieldOverride[0]
|
|
482
497
|
: storedValue
|
|
483
498
|
// TODO more nuanced comparison
|
|
484
|
-
const dirty = fieldOverride != null && fieldOverride
|
|
499
|
+
const dirty = fieldOverride != null && fieldOverride[0] !== storedValue
|
|
485
500
|
|
|
486
501
|
const conversion = revert(value, valuePath, model.value)
|
|
487
502
|
switch (conversion.type) {
|
|
488
|
-
case
|
|
503
|
+
case UnreliableFieldConversionType.Failure:
|
|
489
504
|
model.errors[adapterPath] = conversion.error
|
|
490
505
|
if (conversion.value != null && dirty) {
|
|
491
506
|
accessor.set(conversion.value[0])
|
|
492
507
|
}
|
|
493
508
|
return false
|
|
494
|
-
case
|
|
509
|
+
case UnreliableFieldConversionType.Success:
|
|
495
510
|
if (dirty) {
|
|
496
511
|
accessor.set(conversion.value)
|
|
497
512
|
}
|
|
@@ -506,14 +521,14 @@ export class FormPresenter<
|
|
|
506
521
|
})
|
|
507
522
|
}
|
|
508
523
|
|
|
509
|
-
createModel(value:
|
|
524
|
+
createModel(value: ValueOfType<ReadonlyTypeOfType<T>>): FormModel<
|
|
510
525
|
T,
|
|
511
526
|
ValueToTypePaths,
|
|
512
527
|
TypePathsToAdapters,
|
|
513
528
|
ValuePathsToAdapters
|
|
514
529
|
> {
|
|
515
530
|
return new FormModel<T, ValueToTypePaths, TypePathsToAdapters, ValuePathsToAdapters>(
|
|
516
|
-
this.
|
|
531
|
+
this.type,
|
|
517
532
|
value,
|
|
518
533
|
this.adapters,
|
|
519
534
|
)
|
|
@@ -524,8 +539,8 @@ export class FormModel<
|
|
|
524
539
|
T extends Type,
|
|
525
540
|
ValueToTypePaths extends Readonly<Record<string, string>>,
|
|
526
541
|
TypePathsToAdapters extends FlattenedTypePathsToAdaptersOf<
|
|
527
|
-
|
|
528
|
-
|
|
542
|
+
FlattenedValuesOfType<T, '*'>,
|
|
543
|
+
ValueOfType<ReadonlyTypeOfType<T>>
|
|
529
544
|
>,
|
|
530
545
|
ValuePathsToAdapters extends ValuePathsToAdaptersOf<TypePathsToAdapters, ValueToTypePaths> = ValuePathsToAdaptersOf<
|
|
531
546
|
TypePathsToAdapters,
|
|
@@ -533,7 +548,7 @@ export class FormModel<
|
|
|
533
548
|
>,
|
|
534
549
|
> {
|
|
535
550
|
@observable.ref
|
|
536
|
-
accessor value:
|
|
551
|
+
accessor value: MobxValueOfType<T>
|
|
537
552
|
@observable.shallow
|
|
538
553
|
accessor fieldOverrides: FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
539
554
|
@observable.shallow
|
|
@@ -542,19 +557,25 @@ export class FormModel<
|
|
|
542
557
|
private readonly flattenedTypeDefs: Readonly<Record<string, Type>>
|
|
543
558
|
|
|
544
559
|
constructor(
|
|
545
|
-
private readonly
|
|
546
|
-
value:
|
|
560
|
+
private readonly type: T,
|
|
561
|
+
value: ValueOfType<ReadonlyTypeOfType<T>>,
|
|
547
562
|
private readonly adapters: TypePathsToAdapters,
|
|
548
563
|
) {
|
|
549
|
-
this.value = mobxCopy(
|
|
550
|
-
this.flattenedTypeDefs =
|
|
564
|
+
this.value = mobxCopy(type, value)
|
|
565
|
+
this.flattenedTypeDefs = flattenTypesOfType(type)
|
|
551
566
|
// pre-populate field overrides for consistent behavior when default information is overwritten
|
|
552
567
|
// then returned to
|
|
553
|
-
|
|
554
|
-
|
|
568
|
+
const conversions = flattenValueTo(
|
|
569
|
+
type,
|
|
555
570
|
this.value,
|
|
556
571
|
() => {},
|
|
557
|
-
(
|
|
572
|
+
(
|
|
573
|
+
_t: StrictTypeDef,
|
|
574
|
+
value: AnyValueType,
|
|
575
|
+
_setter,
|
|
576
|
+
typePath,
|
|
577
|
+
valuePath,
|
|
578
|
+
): AnnotatedFieldConversion<FieldOverride> | undefined => {
|
|
558
579
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
559
580
|
const adapter = this.adapters[typePath as keyof TypePathsToAdapters]
|
|
560
581
|
if (adapter == null) {
|
|
@@ -568,12 +589,13 @@ export class FormModel<
|
|
|
568
589
|
// no need to store a temporary value if the value cannot be written back
|
|
569
590
|
return
|
|
570
591
|
}
|
|
571
|
-
|
|
572
|
-
return {
|
|
573
|
-
value: displayValue,
|
|
574
|
-
}
|
|
592
|
+
return convert(value, valuePath, this.value)
|
|
575
593
|
},
|
|
576
594
|
)
|
|
595
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
596
|
+
this.fieldOverrides = map(conversions, function (_k, v) {
|
|
597
|
+
return v && [v.value]
|
|
598
|
+
}) as FlattenedFieldOverrides<ValuePathsToAdapters>
|
|
577
599
|
}
|
|
578
600
|
|
|
579
601
|
@computed
|
|
@@ -598,8 +620,8 @@ export class FormModel<
|
|
|
598
620
|
|
|
599
621
|
@computed
|
|
600
622
|
private get knownFields(): SimplifyDeep<FlattenedConvertedFieldsOf<ValuePathsToAdapters>> {
|
|
601
|
-
return
|
|
602
|
-
this.
|
|
623
|
+
return flattenValueTo(
|
|
624
|
+
this.type,
|
|
603
625
|
this.value,
|
|
604
626
|
() => {},
|
|
605
627
|
// TODO swap these to valuePath, typePath in flatten
|
|
@@ -619,7 +641,7 @@ export class FormModel<
|
|
|
619
641
|
try {
|
|
620
642
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
621
643
|
typePath = valuePathToTypePath<ValueToTypePaths, keyof ValueToTypePaths>(
|
|
622
|
-
this.
|
|
644
|
+
this.type,
|
|
623
645
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
624
646
|
valuePath as keyof ValueToTypePaths,
|
|
625
647
|
true,
|
|
@@ -648,31 +670,32 @@ export class FormModel<
|
|
|
648
670
|
const accessor = this.getAccessorForValuePath(valuePath)
|
|
649
671
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
650
672
|
const fieldTypeDef = this.flattenedTypeDefs[typePath as string]
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
)
|
|
662
|
-
// fake values can't be copied
|
|
673
|
+
const {
|
|
674
|
+
value,
|
|
675
|
+
required,
|
|
676
|
+
readonly,
|
|
677
|
+
} = convert(
|
|
678
|
+
accessor != null
|
|
679
|
+
? accessor.value
|
|
680
|
+
: fieldTypeDef != null
|
|
681
|
+
? mobxCopy(
|
|
682
|
+
fieldTypeDef,
|
|
663
683
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
664
|
-
|
|
684
|
+
create(valuePath as string, this.value),
|
|
685
|
+
)
|
|
686
|
+
// fake values can't be copied
|
|
665
687
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
666
|
-
valuePath as string,
|
|
667
|
-
|
|
668
|
-
|
|
688
|
+
: create(valuePath as string, this.value),
|
|
689
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
690
|
+
valuePath as string,
|
|
691
|
+
this.value,
|
|
692
|
+
)
|
|
669
693
|
const error = this.errors[valuePath]
|
|
670
694
|
return {
|
|
671
|
-
value,
|
|
695
|
+
value: fieldOverride != null ? fieldOverride[0] : value,
|
|
672
696
|
error,
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
required: this.isRequired(valuePath),
|
|
697
|
+
readonly,
|
|
698
|
+
required,
|
|
676
699
|
}
|
|
677
700
|
}
|
|
678
701
|
|
|
@@ -684,22 +707,12 @@ export class FormModel<
|
|
|
684
707
|
@computed
|
|
685
708
|
// should only be referenced internally, so loosely typed
|
|
686
709
|
get accessors(): Readonly<Record<string, Accessor>> {
|
|
687
|
-
return
|
|
688
|
-
this.
|
|
710
|
+
return flattenAccessorsOfType<T, Readonly<Record<string, Accessor>>>(
|
|
711
|
+
this.type,
|
|
689
712
|
this.value,
|
|
690
|
-
(value:
|
|
691
|
-
this.value = mobxCopy(this.
|
|
713
|
+
(value: ValueOfType<T>): void => {
|
|
714
|
+
this.value = mobxCopy(this.type, value)
|
|
692
715
|
},
|
|
693
716
|
)
|
|
694
717
|
}
|
|
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
718
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { map } from '@strictly/base'
|
|
2
|
+
import {
|
|
3
|
+
chainAnnotatedFieldConverter,
|
|
4
|
+
chainUnreliableFieldConverter,
|
|
5
|
+
} from 'field_converters/chain_field_converter'
|
|
6
|
+
import { type TwoWayFieldConverter } from 'types/field_converters'
|
|
7
|
+
import {
|
|
8
|
+
type ErrorOfFieldAdapter,
|
|
9
|
+
type FieldAdapter,
|
|
10
|
+
type FromOfFieldAdapter,
|
|
11
|
+
type ToOfFieldAdapter,
|
|
12
|
+
type ValuePathOfFieldAdapter,
|
|
13
|
+
} from './field_adapter'
|
|
14
|
+
|
|
15
|
+
export type MergedOfFieldAdaptersWithTwoWayConverter<
|
|
16
|
+
FieldAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
17
|
+
E,
|
|
18
|
+
Context,
|
|
19
|
+
> = {
|
|
20
|
+
[K in keyof FieldAdapters]: FieldAdapter<
|
|
21
|
+
FromOfFieldAdapter<FieldAdapters[K]>,
|
|
22
|
+
ToOfFieldAdapter<FieldAdapters[K]>,
|
|
23
|
+
ErrorOfFieldAdapter<FieldAdapters[K]> | E,
|
|
24
|
+
ValuePathOfFieldAdapter<FieldAdapters[K]>,
|
|
25
|
+
Context
|
|
26
|
+
>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ValuePathsOfFieldAdapters<FieldAdapters extends Readonly<Record<string, FieldAdapter>>> = {
|
|
30
|
+
[K in keyof FieldAdapters]: ValuePathOfFieldAdapter<FieldAdapters[K]>
|
|
31
|
+
}[keyof FieldAdapters]
|
|
32
|
+
|
|
33
|
+
type TosOfFieldAdapters<FieldAdapters extends Readonly<Record<string, FieldAdapter>>> = {
|
|
34
|
+
[K in keyof FieldAdapters]: ToOfFieldAdapter<FieldAdapters[K]>
|
|
35
|
+
}[keyof FieldAdapters]
|
|
36
|
+
|
|
37
|
+
export function mergeFieldAdaptersWithTwoWayConverter<
|
|
38
|
+
// must have a field adapter for every validator
|
|
39
|
+
FieldAdapters extends Readonly<Record<string, FieldAdapter>>,
|
|
40
|
+
E,
|
|
41
|
+
Context,
|
|
42
|
+
>(
|
|
43
|
+
fieldAdapters: FieldAdapters,
|
|
44
|
+
converter: TwoWayFieldConverter<
|
|
45
|
+
TosOfFieldAdapters<FieldAdapters>,
|
|
46
|
+
TosOfFieldAdapters<FieldAdapters>,
|
|
47
|
+
E,
|
|
48
|
+
ValuePathsOfFieldAdapters<FieldAdapters>,
|
|
49
|
+
Context
|
|
50
|
+
>,
|
|
51
|
+
): MergedOfFieldAdaptersWithTwoWayConverter<FieldAdapters, E, Context> {
|
|
52
|
+
return map<keyof FieldAdapters, FieldAdapter>(
|
|
53
|
+
fieldAdapters,
|
|
54
|
+
function (_key, adapter) {
|
|
55
|
+
return {
|
|
56
|
+
convert: chainAnnotatedFieldConverter(
|
|
57
|
+
adapter.convert.bind(adapter),
|
|
58
|
+
converter.convert.bind(converter),
|
|
59
|
+
),
|
|
60
|
+
revert: adapter.revert && chainUnreliableFieldConverter(
|
|
61
|
+
converter.revert.bind(converter),
|
|
62
|
+
adapter.revert.bind(adapter),
|
|
63
|
+
),
|
|
64
|
+
create: adapter.create.bind(adapter),
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { reduce } from '@strictly/base'
|
|
2
|
+
import {
|
|
3
|
+
annotations,
|
|
4
|
+
validate,
|
|
5
|
+
type Validator,
|
|
6
|
+
} from '@strictly/define'
|
|
7
|
+
import { type Simplify } from 'type-fest'
|
|
8
|
+
import {
|
|
9
|
+
type AnnotatedFieldConversion,
|
|
10
|
+
type UnreliableFieldConversion,
|
|
11
|
+
UnreliableFieldConversionType,
|
|
12
|
+
} from 'types/field_converters'
|
|
13
|
+
import { type FieldAdapter } from './field_adapter'
|
|
14
|
+
|
|
15
|
+
export type MergedOfFieldAdaptersWithValidators<
|
|
16
|
+
// must have a field adapter for every validator
|
|
17
|
+
FieldAdapters extends Readonly<Record<Key, FieldAdapter>>,
|
|
18
|
+
Validators extends Partial<Readonly<Record<string, Validator>>>,
|
|
19
|
+
Key extends keyof Validators = keyof Validators,
|
|
20
|
+
> = Simplify<{
|
|
21
|
+
readonly [K in Key]: MergedOfFieldAdapterWithValidator<FieldAdapters[K], Validators[K]>
|
|
22
|
+
} & {
|
|
23
|
+
readonly [K in Exclude<keyof FieldAdapters, Key>]: FieldAdapters[K]
|
|
24
|
+
}>
|
|
25
|
+
|
|
26
|
+
type MergedOfFieldAdapterWithValidator<
|
|
27
|
+
A extends FieldAdapter,
|
|
28
|
+
V extends Validator | undefined,
|
|
29
|
+
> = undefined extends V ? A
|
|
30
|
+
: A extends FieldAdapter<infer From, infer To, infer E1, infer P1, infer C1>
|
|
31
|
+
? V extends Validator<From, infer E2, infer P2, infer C2> ? FieldAdapter<From, To, E1 | E2, P1 | P2, C1 | C2>
|
|
32
|
+
: never
|
|
33
|
+
: never
|
|
34
|
+
|
|
35
|
+
export function mergeAdaptersWithValidators<
|
|
36
|
+
// must have a field adapter for every validator
|
|
37
|
+
FieldAdapters extends Readonly<Record<Key, FieldAdapter>>,
|
|
38
|
+
Validators extends Readonly<Record<string, Validator>>,
|
|
39
|
+
Key extends keyof Validators = keyof Validators,
|
|
40
|
+
>(
|
|
41
|
+
adapters: FieldAdapters,
|
|
42
|
+
validators: Validators,
|
|
43
|
+
): MergedOfFieldAdaptersWithValidators<FieldAdapters, Validators, Key> {
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
45
|
+
return reduce<
|
|
46
|
+
Key,
|
|
47
|
+
FieldAdapter,
|
|
48
|
+
Partial<Record<Key, FieldAdapter>>
|
|
49
|
+
>(
|
|
50
|
+
adapters,
|
|
51
|
+
function (acc, key, adapter) {
|
|
52
|
+
const validator = validators[key]
|
|
53
|
+
if (validator == null) {
|
|
54
|
+
acc[key] = adapter
|
|
55
|
+
return acc
|
|
56
|
+
}
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
function revert(to: any, ...params: [any, any]): UnreliableFieldConversion {
|
|
59
|
+
const result = adapter.revert!(to, ...params)
|
|
60
|
+
if (result.type === UnreliableFieldConversionType.Failure) {
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
const validationError = validate(validator, result.value, ...params)
|
|
64
|
+
if (validationError == null) {
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
type: UnreliableFieldConversionType.Failure,
|
|
69
|
+
value: [result.value] as const,
|
|
70
|
+
error: validationError,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
function convert(from: any, ...params: [any, any]): AnnotatedFieldConversion {
|
|
75
|
+
const {
|
|
76
|
+
required: required1,
|
|
77
|
+
readonly: readonly1,
|
|
78
|
+
value,
|
|
79
|
+
} = adapter.convert(from, ...params)
|
|
80
|
+
const {
|
|
81
|
+
required: required2,
|
|
82
|
+
readonly: readonly2,
|
|
83
|
+
} = annotations(validator, ...params)
|
|
84
|
+
return {
|
|
85
|
+
value,
|
|
86
|
+
required: required1 || required2,
|
|
87
|
+
readonly: readonly1 || readonly2,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
acc[key] = {
|
|
91
|
+
...adapter,
|
|
92
|
+
convert,
|
|
93
|
+
revert: adapter.revert && revert,
|
|
94
|
+
}
|
|
95
|
+
return acc
|
|
96
|
+
},
|
|
97
|
+
{},
|
|
98
|
+
) as MergedOfFieldAdaptersWithValidators<FieldAdapters, Validators, Key>
|
|
99
|
+
}
|