@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,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UnreachableError,
|
|
3
|
+
} from '@strictly/base'
|
|
4
|
+
import {
|
|
5
|
+
type FieldConversion,
|
|
6
|
+
FieldConversionResult,
|
|
7
|
+
type FieldConverter,
|
|
8
|
+
type SafeFieldConverter,
|
|
9
|
+
} from 'types/field_converters'
|
|
10
|
+
|
|
11
|
+
export function chainFieldConverter<
|
|
12
|
+
From,
|
|
13
|
+
Intermediate,
|
|
14
|
+
To,
|
|
15
|
+
E1,
|
|
16
|
+
E2,
|
|
17
|
+
ValuePath extends string,
|
|
18
|
+
Context,
|
|
19
|
+
>(
|
|
20
|
+
from: FieldConverter<From, Intermediate, E1, ValuePath, Context>,
|
|
21
|
+
to: FieldConverter<Intermediate, To, E2, ValuePath, Context>,
|
|
22
|
+
): FieldConverter<From, To, E1 | E2, ValuePath, Context> {
|
|
23
|
+
return function (value: From, valuePath: ValuePath, context: Context): FieldConversion<To, E1 | E2> {
|
|
24
|
+
const fromConversion = from(value, valuePath, context)
|
|
25
|
+
switch (fromConversion.type) {
|
|
26
|
+
case FieldConversionResult.Success:
|
|
27
|
+
return to(fromConversion.value, valuePath, context)
|
|
28
|
+
case FieldConversionResult.Failure:
|
|
29
|
+
if (fromConversion.value != null) {
|
|
30
|
+
const toConversion = to(fromConversion.value[0], valuePath, context)
|
|
31
|
+
switch (toConversion.type) {
|
|
32
|
+
case FieldConversionResult.Success:
|
|
33
|
+
return {
|
|
34
|
+
type: FieldConversionResult.Failure,
|
|
35
|
+
error: fromConversion.error,
|
|
36
|
+
value: [toConversion.value],
|
|
37
|
+
}
|
|
38
|
+
case FieldConversionResult.Failure:
|
|
39
|
+
return {
|
|
40
|
+
type: FieldConversionResult.Failure,
|
|
41
|
+
error: fromConversion.error,
|
|
42
|
+
value: toConversion.value,
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
throw new UnreachableError(toConversion)
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
return {
|
|
49
|
+
type: FieldConversionResult.Failure,
|
|
50
|
+
error: fromConversion.error,
|
|
51
|
+
value: null,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
throw new UnreachableError(fromConversion)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function chainSafeFieldConverter<
|
|
61
|
+
From,
|
|
62
|
+
Intermediate,
|
|
63
|
+
To,
|
|
64
|
+
ValuePath extends string,
|
|
65
|
+
Context,
|
|
66
|
+
>(
|
|
67
|
+
from: SafeFieldConverter<From, Intermediate, ValuePath, Context>,
|
|
68
|
+
to: SafeFieldConverter<Intermediate, To, ValuePath, Context>,
|
|
69
|
+
): SafeFieldConverter<From, To, ValuePath, Context> {
|
|
70
|
+
return function (value: From, valuePath: ValuePath, context: Context): To {
|
|
71
|
+
const intermediate = from(value, valuePath, context)
|
|
72
|
+
return to(intermediate, valuePath, context)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FieldConversionResult,
|
|
3
|
+
type FieldConverter,
|
|
4
|
+
type SafeFieldConverter,
|
|
5
|
+
} from 'types/field_converters'
|
|
6
|
+
|
|
7
|
+
export function safeIdentityConverter<
|
|
8
|
+
V,
|
|
9
|
+
ValuePath extends string,
|
|
10
|
+
Context,
|
|
11
|
+
>(): SafeFieldConverter<
|
|
12
|
+
V,
|
|
13
|
+
V,
|
|
14
|
+
ValuePath,
|
|
15
|
+
Context
|
|
16
|
+
> {
|
|
17
|
+
return function (v: V) {
|
|
18
|
+
return v
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function identityConverter<
|
|
23
|
+
V,
|
|
24
|
+
ValuePath extends string,
|
|
25
|
+
Context,
|
|
26
|
+
>(): FieldConverter<
|
|
27
|
+
V,
|
|
28
|
+
V,
|
|
29
|
+
never,
|
|
30
|
+
ValuePath,
|
|
31
|
+
Context
|
|
32
|
+
> {
|
|
33
|
+
return function (value: V) {
|
|
34
|
+
return {
|
|
35
|
+
type: FieldConversionResult.Success,
|
|
36
|
+
value,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FieldConversion,
|
|
3
|
+
FieldConversionResult,
|
|
4
|
+
type TwoWayFieldConverter,
|
|
5
|
+
} from 'types/field_converters'
|
|
6
|
+
|
|
7
|
+
export class IntegerToStringConverter<E, ValuePath extends string, Context>
|
|
8
|
+
implements TwoWayFieldConverter<number, string, E, ValuePath, Context>
|
|
9
|
+
{
|
|
10
|
+
constructor(private readonly isNanError: E) {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
convert(from: number): string {
|
|
14
|
+
return Math.floor(from).toString()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
revert(from: string): FieldConversion<number, E> {
|
|
18
|
+
const value = parseInt(from, 10)
|
|
19
|
+
if (Number.isNaN(value)) {
|
|
20
|
+
return {
|
|
21
|
+
type: FieldConversionResult.Failure,
|
|
22
|
+
error: this.isNanError,
|
|
23
|
+
value: null,
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
return {
|
|
27
|
+
type: FieldConversionResult.Success,
|
|
28
|
+
value,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type SafeFieldConverter } from 'types/field_converters'
|
|
2
|
+
|
|
3
|
+
export function listConverter<
|
|
4
|
+
E,
|
|
5
|
+
K extends string,
|
|
6
|
+
ValuePath extends string,
|
|
7
|
+
Context,
|
|
8
|
+
>(): SafeFieldConverter<readonly E[], K[], ValuePath, Context> {
|
|
9
|
+
return function (from: readonly E[], valuePath: ValuePath) {
|
|
10
|
+
return from.map(function (_v, i) {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
12
|
+
return `${valuePath as string}.${i}` as K
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FieldConversion,
|
|
3
|
+
type TwoWayFieldConverter,
|
|
4
|
+
} from 'types/field_converters'
|
|
5
|
+
|
|
6
|
+
export class MaybeIdentityConverter<From, To, E, ValuePath extends string, Context>
|
|
7
|
+
implements TwoWayFieldConverter<From, From | To, E, ValuePath, Context>
|
|
8
|
+
{
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly converter: TwoWayFieldConverter<From, To, E, ValuePath, Context>,
|
|
11
|
+
private readonly isFrom: (to: From | To) => to is From,
|
|
12
|
+
) {
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
convert(from: From, valuePath: ValuePath, context: Context): To | From {
|
|
16
|
+
return this.converter.convert(from, valuePath, context)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
revert(from: To | From, valuePath: ValuePath, context: Context): FieldConversion<From, E> {
|
|
20
|
+
const value = this.isFrom(from) ? this.converter.convert(from, valuePath, context) : from
|
|
21
|
+
return this.converter.revert(value, valuePath, context)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copy,
|
|
3
|
+
type ReadonlyTypeDefOf,
|
|
4
|
+
type Type,
|
|
5
|
+
type ValueTypeOf,
|
|
6
|
+
} from '@strictly/define'
|
|
7
|
+
import {
|
|
8
|
+
type FieldConversion,
|
|
9
|
+
FieldConversionResult,
|
|
10
|
+
type TwoWayFieldConverterWithValueFactory,
|
|
11
|
+
} from 'types/field_converters'
|
|
12
|
+
|
|
13
|
+
export class NullableToBooleanConverter<
|
|
14
|
+
T extends Type,
|
|
15
|
+
E,
|
|
16
|
+
ValuePath extends string,
|
|
17
|
+
Context,
|
|
18
|
+
> implements TwoWayFieldConverterWithValueFactory<
|
|
19
|
+
ValueTypeOf<ReadonlyTypeDefOf<T>> | null,
|
|
20
|
+
boolean,
|
|
21
|
+
E,
|
|
22
|
+
ValuePath,
|
|
23
|
+
Context
|
|
24
|
+
> {
|
|
25
|
+
readonly defaultValue: ValueTypeOf<ReadonlyTypeDefOf<T>> | null
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly typeDef: T,
|
|
29
|
+
private readonly prototype: ValueTypeOf<ReadonlyTypeDefOf<T>>,
|
|
30
|
+
defaultToNull = true,
|
|
31
|
+
) {
|
|
32
|
+
this.defaultValue = defaultToNull ? null : prototype
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
convert(from: ValueTypeOf<ReadonlyTypeDefOf<T>> | null): boolean {
|
|
36
|
+
return from != null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
revert(from: boolean): FieldConversion<ValueTypeOf<ReadonlyTypeDefOf<T>> | null, E> {
|
|
40
|
+
if (from) {
|
|
41
|
+
const value: ValueTypeOf<T> = copy(this.typeDef, this.prototype)
|
|
42
|
+
return {
|
|
43
|
+
type: FieldConversionResult.Success,
|
|
44
|
+
value,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
type: FieldConversionResult.Success,
|
|
49
|
+
value: null,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
create(): ValueTypeOf<ReadonlyTypeDefOf<T>> | null {
|
|
54
|
+
return this.defaultValue
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
reverse,
|
|
3
|
+
type StringKeyOf,
|
|
4
|
+
} from '@strictly/base'
|
|
5
|
+
import {
|
|
6
|
+
copy,
|
|
7
|
+
type LiteralTypeDef,
|
|
8
|
+
type Type,
|
|
9
|
+
type UnionTypeDef,
|
|
10
|
+
type ValueTypeOf,
|
|
11
|
+
type ValueTypesOfDiscriminatedUnion,
|
|
12
|
+
} from '@strictly/define'
|
|
13
|
+
import {
|
|
14
|
+
type FieldConversion,
|
|
15
|
+
FieldConversionResult,
|
|
16
|
+
type TwoWayFieldConverterWithValueFactory,
|
|
17
|
+
} from 'types/field_converters'
|
|
18
|
+
|
|
19
|
+
export abstract class AbstractSelectValueTypeConverter<
|
|
20
|
+
T extends Type,
|
|
21
|
+
To extends string | null,
|
|
22
|
+
Values extends Readonly<Record<NonNullable<To>, ValueTypeOf<T>>>,
|
|
23
|
+
NoSuchValueError,
|
|
24
|
+
ValuePath extends string,
|
|
25
|
+
Context,
|
|
26
|
+
> implements TwoWayFieldConverterWithValueFactory<
|
|
27
|
+
ValueTypeOf<T>,
|
|
28
|
+
keyof Values | null,
|
|
29
|
+
NoSuchValueError,
|
|
30
|
+
ValuePath,
|
|
31
|
+
Context
|
|
32
|
+
> {
|
|
33
|
+
constructor(
|
|
34
|
+
protected readonly typeDef: T,
|
|
35
|
+
protected readonly values: Values,
|
|
36
|
+
private readonly defaultValueKey: keyof Values | null,
|
|
37
|
+
private readonly noSuchValueError: NoSuchValueError | null,
|
|
38
|
+
) {
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
revert(from: keyof Values | null): FieldConversion<ValueTypeOf<T>, NoSuchValueError> {
|
|
42
|
+
const prototype: ValueTypeOf<T> | null = from == null ? null : this.values[from]
|
|
43
|
+
if (prototype == null && this.noSuchValueError != null) {
|
|
44
|
+
return {
|
|
45
|
+
type: FieldConversionResult.Failure,
|
|
46
|
+
error: this.noSuchValueError,
|
|
47
|
+
value: null,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const value = prototype == null ? null : copy(this.typeDef, prototype)
|
|
51
|
+
// TODO given we are dealing with strings, maybe we should have a check to make sure value is in the record
|
|
52
|
+
// of values?
|
|
53
|
+
return {
|
|
54
|
+
type: FieldConversionResult.Success,
|
|
55
|
+
value: value!,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
convert(from: ValueTypeOf<T>): To {
|
|
60
|
+
if (from == null) {
|
|
61
|
+
return null!
|
|
62
|
+
}
|
|
63
|
+
return this.doConvert(from)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected abstract doConvert(from: ValueTypeOf<T>): To
|
|
67
|
+
|
|
68
|
+
create(): ValueTypeOf<T> {
|
|
69
|
+
return this.defaultValueKey != null ? this.values[this.defaultValueKey] : null!
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class SelectDiscriminatedUnionConverter<
|
|
74
|
+
U extends UnionTypeDef,
|
|
75
|
+
To extends StringKeyOf<U['unions']> | null,
|
|
76
|
+
ValuePath extends string,
|
|
77
|
+
Context,
|
|
78
|
+
> extends AbstractSelectValueTypeConverter<
|
|
79
|
+
Type<U>,
|
|
80
|
+
To,
|
|
81
|
+
ValueTypesOfDiscriminatedUnion<U>,
|
|
82
|
+
never,
|
|
83
|
+
ValuePath,
|
|
84
|
+
Context
|
|
85
|
+
> {
|
|
86
|
+
constructor(
|
|
87
|
+
typeDef: Type<U>,
|
|
88
|
+
values: ValueTypesOfDiscriminatedUnion<U>,
|
|
89
|
+
defaultValueKey: keyof U['unions'],
|
|
90
|
+
) {
|
|
91
|
+
super(
|
|
92
|
+
typeDef,
|
|
93
|
+
values,
|
|
94
|
+
defaultValueKey,
|
|
95
|
+
null,
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected override doConvert(from: ValueTypeOf<Type<U>>) {
|
|
100
|
+
const {
|
|
101
|
+
definition: {
|
|
102
|
+
discriminator,
|
|
103
|
+
},
|
|
104
|
+
} = this.typeDef
|
|
105
|
+
return from[discriminator!]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class SelectLiteralConverter<
|
|
110
|
+
L extends string | number | null,
|
|
111
|
+
To extends string | null,
|
|
112
|
+
Values extends Record<NonNullable<L>, NonNullable<To>>,
|
|
113
|
+
NoSuchValueError,
|
|
114
|
+
ValuePath extends string,
|
|
115
|
+
Context,
|
|
116
|
+
> extends AbstractSelectValueTypeConverter<
|
|
117
|
+
Type<LiteralTypeDef<L>>,
|
|
118
|
+
To,
|
|
119
|
+
Record<NonNullable<To>, L>,
|
|
120
|
+
NoSuchValueError,
|
|
121
|
+
ValuePath,
|
|
122
|
+
Context
|
|
123
|
+
> {
|
|
124
|
+
constructor(
|
|
125
|
+
typeDef: Type<LiteralTypeDef<L>>,
|
|
126
|
+
private readonly valuesToStrings: Values,
|
|
127
|
+
defaultValue: L | null,
|
|
128
|
+
noSuchValueError: NoSuchValueError | null,
|
|
129
|
+
) {
|
|
130
|
+
super(
|
|
131
|
+
typeDef,
|
|
132
|
+
reverse(valuesToStrings),
|
|
133
|
+
defaultValue && valuesToStrings[defaultValue],
|
|
134
|
+
noSuchValueError,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
protected override doConvert(from: L) {
|
|
139
|
+
return from && this.valuesToStrings[from]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FieldConversion,
|
|
3
|
+
FieldConversionResult,
|
|
4
|
+
type TwoWayFieldConverter,
|
|
5
|
+
} from 'types/field_converters'
|
|
6
|
+
|
|
7
|
+
export class TrimmingStringConverter<ValuePath extends string, Context>
|
|
8
|
+
implements TwoWayFieldConverter<string, string, never, ValuePath, Context>
|
|
9
|
+
{
|
|
10
|
+
constructor() {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
convert(to: string): string {
|
|
14
|
+
return to.trim()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
revert(from: string): FieldConversion<string, never> {
|
|
18
|
+
return {
|
|
19
|
+
type: FieldConversionResult.Success,
|
|
20
|
+
value: from.trim(),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FieldConversion,
|
|
3
|
+
FieldConversionResult,
|
|
4
|
+
type FieldConverter,
|
|
5
|
+
} from 'types/field_converters'
|
|
6
|
+
import { type FieldValidator } from 'types/field_validator'
|
|
7
|
+
|
|
8
|
+
export function validatingConverter<
|
|
9
|
+
V,
|
|
10
|
+
E,
|
|
11
|
+
ValuePath extends string,
|
|
12
|
+
Context,
|
|
13
|
+
>(validators: readonly FieldValidator<V, E, ValuePath, Context>[] = []): FieldConverter<V, V, E, ValuePath, Context> {
|
|
14
|
+
return function (value: V, valuePath: ValuePath, context: Context): FieldConversion<V, E> {
|
|
15
|
+
return validators.reduce<FieldConversion<V, E>>(
|
|
16
|
+
function (acc, validator) {
|
|
17
|
+
if (acc.type === FieldConversionResult.Success) {
|
|
18
|
+
const error = validator(value, valuePath, context)
|
|
19
|
+
if (error != null) {
|
|
20
|
+
return {
|
|
21
|
+
type: FieldConversionResult.Failure,
|
|
22
|
+
error,
|
|
23
|
+
value: [value],
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return acc
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: FieldConversionResult.Success,
|
|
31
|
+
value,
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type FieldValidator } from 'types/field_validator'
|
|
2
|
+
|
|
3
|
+
export function minimumStringLengthFieldValidatorFactory<E, ValuePath extends string, Context>(
|
|
4
|
+
minimumLength: number,
|
|
5
|
+
error: E,
|
|
6
|
+
): FieldValidator<string, E, ValuePath, Context> {
|
|
7
|
+
return function (value: string) {
|
|
8
|
+
if (value.length < minimumLength) {
|
|
9
|
+
return error
|
|
10
|
+
}
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FieldValueFactory } from 'types/field_converters'
|
|
2
|
+
|
|
3
|
+
export function prototypingFieldValueFactory<
|
|
4
|
+
V,
|
|
5
|
+
ValuePath extends string,
|
|
6
|
+
Context,
|
|
7
|
+
>(prototype: V): FieldValueFactory<V, ValuePath, Context> {
|
|
8
|
+
return function (): V {
|
|
9
|
+
return prototype
|
|
10
|
+
}
|
|
11
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './core/mobx/field_adapter_builder'
|
|
2
|
+
export * from './core/mobx/flattened_adapters_of_fields'
|
|
3
|
+
export * from './core/mobx/form_presenter'
|
|
4
|
+
export * from './core/mobx/types'
|
|
5
|
+
export * from './core/props'
|
|
6
|
+
export * from './field_converters/integer_to_string_converter'
|
|
7
|
+
export * from './field_converters/nullable_to_boolean_converter'
|
|
8
|
+
export * from './field_converters/select_value_type_converter'
|
|
9
|
+
export * from './field_converters/trimming_string_converter'
|
|
10
|
+
export * from './field_converters/validating_converter'
|
|
11
|
+
export * from './field_validators/minimum_string_length_field_validator'
|
|
12
|
+
export * from './field_value_factories/prototyping_field_value_factory'
|
|
13
|
+
export * from './mantine/hooks'
|
|
14
|
+
export * from './types/field'
|
|
15
|
+
export * from './types/flattened_form_fields_of'
|
|
16
|
+
export * from './util/partial'
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CheckboxProps,
|
|
3
|
+
} from '@mantine/core'
|
|
4
|
+
import { type ComponentType } from 'react'
|
|
5
|
+
import { type BooleanFieldsOfFields } from 'types/boolean_fields_of_fields'
|
|
6
|
+
import { type ErrorTypeOfField } from 'types/error_type_of_field'
|
|
7
|
+
import { type Fields } from 'types/field'
|
|
8
|
+
import { createUnsafePartialObserverComponent } from 'util/partial'
|
|
9
|
+
import { type ErrorRenderer } from './hooks'
|
|
10
|
+
import {
|
|
11
|
+
type MantineFieldComponent,
|
|
12
|
+
type MantineForm,
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
export type SuppliedCheckboxProps = Pick<
|
|
16
|
+
CheckboxProps,
|
|
17
|
+
| 'name'
|
|
18
|
+
| 'checked'
|
|
19
|
+
| 'disabled'
|
|
20
|
+
| 'required'
|
|
21
|
+
| 'error'
|
|
22
|
+
| 'onChange'
|
|
23
|
+
| 'onFocus'
|
|
24
|
+
| 'onBlur'
|
|
25
|
+
| 'onKeyUp'
|
|
26
|
+
>
|
|
27
|
+
|
|
28
|
+
export function createCheckbox<
|
|
29
|
+
F extends Fields,
|
|
30
|
+
K extends keyof BooleanFieldsOfFields<F>,
|
|
31
|
+
Props extends SuppliedCheckboxProps,
|
|
32
|
+
>(
|
|
33
|
+
this: MantineForm<F>,
|
|
34
|
+
valuePath: K,
|
|
35
|
+
Checkbox: ComponentType<Props>,
|
|
36
|
+
ErrorRenderer: ErrorRenderer<ErrorTypeOfField<F[K]>>,
|
|
37
|
+
): MantineFieldComponent<SuppliedCheckboxProps, Props> {
|
|
38
|
+
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
39
|
+
this.onFieldValueChange?.(valuePath, e.target.checked)
|
|
40
|
+
}
|
|
41
|
+
const onFocus = () => {
|
|
42
|
+
this.onFieldFocus?.(valuePath)
|
|
43
|
+
}
|
|
44
|
+
const onBlur = () => {
|
|
45
|
+
this.onFieldBlur?.(valuePath)
|
|
46
|
+
}
|
|
47
|
+
const onKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
48
|
+
if (e.key === 'Enter') {
|
|
49
|
+
if (this.onFieldSubmit?.(valuePath)) {
|
|
50
|
+
e.preventDefault()
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const propSource = () => {
|
|
56
|
+
const {
|
|
57
|
+
disabled,
|
|
58
|
+
required,
|
|
59
|
+
value,
|
|
60
|
+
error,
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
62
|
+
} = this.fields[valuePath as string]
|
|
63
|
+
return {
|
|
64
|
+
name: valuePath,
|
|
65
|
+
checked: value,
|
|
66
|
+
disabled,
|
|
67
|
+
required,
|
|
68
|
+
error: error && <ErrorRenderer error={error} />,
|
|
69
|
+
onChange,
|
|
70
|
+
onFocus,
|
|
71
|
+
onBlur,
|
|
72
|
+
onKeyUp,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return createUnsafePartialObserverComponent<typeof Checkbox, SuppliedCheckboxProps>(
|
|
76
|
+
Checkbox,
|
|
77
|
+
propSource,
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ElementOfArray } from '@strictly/base'
|
|
2
|
+
import { type ComponentType } from 'react'
|
|
3
|
+
import { type Fields } from 'types/field'
|
|
4
|
+
import { type ListFieldsOfFields } from 'types/list_fields_of_fields'
|
|
5
|
+
import { type ValueTypeOfField } from 'types/value_type_of_field'
|
|
6
|
+
import { createUnsafePartialObserverComponent } from 'util/partial'
|
|
7
|
+
import {
|
|
8
|
+
type MantineFieldComponent,
|
|
9
|
+
type MantineForm,
|
|
10
|
+
} from './types'
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export type SuppliedListProps<Value = any> = {
|
|
14
|
+
values: readonly Value[],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createList<
|
|
18
|
+
F extends Fields,
|
|
19
|
+
K extends keyof ListFieldsOfFields<F>,
|
|
20
|
+
Props extends SuppliedListProps<ElementOfArray<ValueTypeOfField<F[K]>>> & {
|
|
21
|
+
children: (
|
|
22
|
+
value: ElementOfArray<ValueTypeOfField<F[K]>>,
|
|
23
|
+
index: number,
|
|
24
|
+
) => React.ReactNode,
|
|
25
|
+
},
|
|
26
|
+
>(
|
|
27
|
+
this: MantineForm<F>,
|
|
28
|
+
valuePath: K,
|
|
29
|
+
List: ComponentType<Props>,
|
|
30
|
+
): MantineFieldComponent<SuppliedListProps<ElementOfArray<ValueTypeOfField<F[K]>>>, Props> {
|
|
31
|
+
const propSource = () => {
|
|
32
|
+
const values = [...this.fields[valuePath].value]
|
|
33
|
+
return {
|
|
34
|
+
values,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return createUnsafePartialObserverComponent<
|
|
38
|
+
typeof List,
|
|
39
|
+
SuppliedListProps<ElementOfArray<ValueTypeOfField<F[K]>>>
|
|
40
|
+
>(List, propSource)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function DefaultList<
|
|
44
|
+
Value,
|
|
45
|
+
>({
|
|
46
|
+
values,
|
|
47
|
+
children,
|
|
48
|
+
}: SuppliedListProps<Value> & {
|
|
49
|
+
children: (value: Value, index: number) => React.ReactNode,
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
{values.map(function (value, index) {
|
|
54
|
+
return children(value, index)
|
|
55
|
+
})}
|
|
56
|
+
</>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PillProps,
|
|
3
|
+
} from '@mantine/core'
|
|
4
|
+
import { type ComponentType } from 'react'
|
|
5
|
+
import { type AllFieldsOfFields } from 'types/all_fields_of_fields'
|
|
6
|
+
import { type Fields } from 'types/field'
|
|
7
|
+
import { createUnsafePartialObserverComponent } from 'util/partial'
|
|
8
|
+
import {
|
|
9
|
+
type MantineFieldComponent,
|
|
10
|
+
type MantineForm,
|
|
11
|
+
} from './types'
|
|
12
|
+
|
|
13
|
+
// TODO should probably supply everything
|
|
14
|
+
export type SuppliedPillProps = Pick<
|
|
15
|
+
PillProps,
|
|
16
|
+
| 'children'
|
|
17
|
+
| 'disabled'
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
export function createPill<
|
|
21
|
+
F extends Fields,
|
|
22
|
+
K extends keyof AllFieldsOfFields<F>,
|
|
23
|
+
Props extends SuppliedPillProps,
|
|
24
|
+
>(
|
|
25
|
+
this: MantineForm<F>,
|
|
26
|
+
valuePath: K,
|
|
27
|
+
Pill: ComponentType<Props>,
|
|
28
|
+
): MantineFieldComponent<SuppliedPillProps, Props> {
|
|
29
|
+
const propSource = () => {
|
|
30
|
+
const {
|
|
31
|
+
disabled,
|
|
32
|
+
value,
|
|
33
|
+
// note: individual pills cannot display an error!
|
|
34
|
+
// error,
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
36
|
+
} = this.fields[valuePath as string]
|
|
37
|
+
return {
|
|
38
|
+
children: value,
|
|
39
|
+
disabled,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return createUnsafePartialObserverComponent<typeof Pill, SuppliedPillProps>(Pill, propSource)
|
|
43
|
+
}
|