@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,106 @@
|
|
|
1
|
+
import { composeStories } from '@storybook/react'
|
|
2
|
+
import { toArray } from '@strictly/base'
|
|
3
|
+
import {
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
type RenderResult,
|
|
7
|
+
} from '@testing-library/react'
|
|
8
|
+
import {
|
|
9
|
+
type Mock,
|
|
10
|
+
vi,
|
|
11
|
+
} from 'vitest'
|
|
12
|
+
import { TEXT_INPUT_LABEL } from './text_input_constants'
|
|
13
|
+
import * as stories from './text_input_hooks.stories'
|
|
14
|
+
|
|
15
|
+
const composedStories = composeStories(stories)
|
|
16
|
+
const {
|
|
17
|
+
Populated,
|
|
18
|
+
} = composedStories
|
|
19
|
+
|
|
20
|
+
describe('mantine checkbox hooks', function () {
|
|
21
|
+
it.each(toArray(composedStories))('renders %s', function (_name, Story) {
|
|
22
|
+
const wrapper = render(<Story />)
|
|
23
|
+
expect(wrapper.container).toMatchSnapshot()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('events', function () {
|
|
27
|
+
let onFieldValueChange: Mock<(path: '$', value: string) => void>
|
|
28
|
+
let onFieldFocus: Mock<(path: '$') => void>
|
|
29
|
+
let onFieldBlur: Mock<(path: '$') => void>
|
|
30
|
+
let onFieldSubmit: Mock<(path: '$') => void>
|
|
31
|
+
let wrapper: RenderResult
|
|
32
|
+
let textInput: HTMLElement
|
|
33
|
+
|
|
34
|
+
beforeEach(async function () {
|
|
35
|
+
onFieldValueChange = vi.fn()
|
|
36
|
+
onFieldFocus = vi.fn()
|
|
37
|
+
onFieldBlur = vi.fn()
|
|
38
|
+
onFieldSubmit = vi.fn()
|
|
39
|
+
wrapper = render((
|
|
40
|
+
<Populated
|
|
41
|
+
onFieldBlur={onFieldBlur}
|
|
42
|
+
onFieldFocus={onFieldFocus}
|
|
43
|
+
onFieldSubmit={onFieldSubmit}
|
|
44
|
+
onFieldValueChange={onFieldValueChange}
|
|
45
|
+
/>
|
|
46
|
+
))
|
|
47
|
+
textInput = await wrapper.findByLabelText(TEXT_INPUT_LABEL)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('fires change event', function () {
|
|
51
|
+
const value = 'new value'
|
|
52
|
+
fireEvent.change(textInput, {
|
|
53
|
+
target: {
|
|
54
|
+
value,
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
expect(onFieldValueChange).toHaveBeenCalledOnce()
|
|
58
|
+
expect(onFieldValueChange).toHaveBeenCalledWith('$', value)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('fires submit event on enter', function () {
|
|
62
|
+
fireEvent.keyUp(textInput, {
|
|
63
|
+
key: 'Enter',
|
|
64
|
+
})
|
|
65
|
+
expect(onFieldSubmit).toHaveBeenCalledOnce()
|
|
66
|
+
expect(onFieldSubmit).toHaveBeenLastCalledWith('$')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it.each([
|
|
70
|
+
'Tab',
|
|
71
|
+
'Space',
|
|
72
|
+
'x',
|
|
73
|
+
])('does not fire submit event on %s', function (key) {
|
|
74
|
+
fireEvent.keyUp(textInput, {
|
|
75
|
+
key,
|
|
76
|
+
})
|
|
77
|
+
expect(onFieldSubmit).not.toHaveBeenCalled()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('focus', function () {
|
|
81
|
+
beforeEach(function () {
|
|
82
|
+
fireEvent.focus(textInput)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('fires focus event', function () {
|
|
86
|
+
expect(onFieldFocus).toHaveBeenCalledOnce()
|
|
87
|
+
expect(onFieldFocus).toHaveBeenCalledWith('$')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('blur', function () {
|
|
91
|
+
beforeEach(function () {
|
|
92
|
+
fireEvent.blur(textInput)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('fires blur event', function () {
|
|
96
|
+
expect(onFieldBlur).toHaveBeenCalledOnce()
|
|
97
|
+
expect(onFieldBlur).toHaveBeenCalledWith('$')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('does not refire focus event', function () {
|
|
101
|
+
expect(onFieldFocus).toHaveBeenCalledOnce()
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JsonInput,
|
|
3
|
+
type JsonInputProps,
|
|
4
|
+
NumberInput,
|
|
5
|
+
type NumberInputProps,
|
|
6
|
+
Rating,
|
|
7
|
+
type RatingProps,
|
|
8
|
+
Slider,
|
|
9
|
+
type SliderProps,
|
|
10
|
+
} from '@mantine/core'
|
|
11
|
+
import { action } from '@storybook/addon-actions'
|
|
12
|
+
import {
|
|
13
|
+
type Meta,
|
|
14
|
+
type StoryObj,
|
|
15
|
+
} from '@storybook/react'
|
|
16
|
+
import { type FormProps } from 'core/props'
|
|
17
|
+
import { type SuppliedValueInputProps } from 'mantine/create_value_input'
|
|
18
|
+
import { useMantineForm } from 'mantine/hooks'
|
|
19
|
+
import {
|
|
20
|
+
type ComponentType,
|
|
21
|
+
} from 'react'
|
|
22
|
+
import { type Field } from 'types/field'
|
|
23
|
+
import {
|
|
24
|
+
NUMBER_INPUT_LABEL,
|
|
25
|
+
SLIDER_LABEL,
|
|
26
|
+
} from './value_input_constants'
|
|
27
|
+
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
type StoryValueInputProps<V> = SuppliedValueInputProps<V, any>
|
|
30
|
+
|
|
31
|
+
function Component<
|
|
32
|
+
V,
|
|
33
|
+
P extends StoryValueInputProps<V>,
|
|
34
|
+
>({
|
|
35
|
+
ValueInput,
|
|
36
|
+
inputs,
|
|
37
|
+
...props
|
|
38
|
+
}: FormProps<{
|
|
39
|
+
$: Field<V, string>,
|
|
40
|
+
}> & {
|
|
41
|
+
ValueInput: ComponentType<P>,
|
|
42
|
+
} & {
|
|
43
|
+
inputs: P,
|
|
44
|
+
}) {
|
|
45
|
+
const form = useMantineForm(props)
|
|
46
|
+
const ValueInputComponent = form.valueInput<'$', P>('$', ValueInput)
|
|
47
|
+
return (
|
|
48
|
+
<ValueInputComponent
|
|
49
|
+
{
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions
|
|
51
|
+
...inputs as any
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const meta: Meta<typeof Component> = {
|
|
58
|
+
component: Component,
|
|
59
|
+
args: {
|
|
60
|
+
onFieldBlur: action('onFieldBlur'),
|
|
61
|
+
onFieldFocus: action('onFieldFocus'),
|
|
62
|
+
onFieldSubmit: action('onFieldSubmit'),
|
|
63
|
+
onFieldValueChange: action('onFieldValueChange'),
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default meta
|
|
68
|
+
|
|
69
|
+
type Story<
|
|
70
|
+
V,
|
|
71
|
+
P extends StoryValueInputProps<V>,
|
|
72
|
+
> = StoryObj<typeof Component<V, P>>
|
|
73
|
+
|
|
74
|
+
export const EmptyNumberInput: Story<number | string, NumberInputProps> = {
|
|
75
|
+
args: {
|
|
76
|
+
fields: {
|
|
77
|
+
$: {
|
|
78
|
+
disabled: false,
|
|
79
|
+
required: true,
|
|
80
|
+
value: '',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
ValueInput: NumberInput,
|
|
84
|
+
inputs: {
|
|
85
|
+
label: NUMBER_INPUT_LABEL,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const PopulatedNumberInput: Story<number | string, NumberInputProps> = {
|
|
91
|
+
args: {
|
|
92
|
+
fields: {
|
|
93
|
+
$: {
|
|
94
|
+
disabled: false,
|
|
95
|
+
required: false,
|
|
96
|
+
value: 3,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
ValueInput: NumberInput,
|
|
100
|
+
inputs: {
|
|
101
|
+
label: NUMBER_INPUT_LABEL,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const DisabledNumberInput: Story<number | string, NumberInputProps> = {
|
|
107
|
+
args: {
|
|
108
|
+
fields: {
|
|
109
|
+
$: {
|
|
110
|
+
disabled: true,
|
|
111
|
+
required: false,
|
|
112
|
+
value: 3,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
ValueInput: NumberInput,
|
|
116
|
+
inputs: {
|
|
117
|
+
label: NUMBER_INPUT_LABEL,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const AnSlider: Story<number, SliderProps> = {
|
|
123
|
+
args: {
|
|
124
|
+
fields: {
|
|
125
|
+
$: {
|
|
126
|
+
disabled: false,
|
|
127
|
+
required: false,
|
|
128
|
+
value: 3,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
ValueInput: Slider,
|
|
132
|
+
inputs: {
|
|
133
|
+
label: SLIDER_LABEL,
|
|
134
|
+
min: 1,
|
|
135
|
+
max: 10,
|
|
136
|
+
marks: [
|
|
137
|
+
{
|
|
138
|
+
value: 1,
|
|
139
|
+
label: 'min',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
value: 5,
|
|
143
|
+
label: 'mid',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
value: 10,
|
|
147
|
+
label: 'max',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const AnRating: Story<number, RatingProps> = {
|
|
155
|
+
args: {
|
|
156
|
+
fields: {
|
|
157
|
+
$: {
|
|
158
|
+
disabled: false,
|
|
159
|
+
required: false,
|
|
160
|
+
value: 2,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
ValueInput: Rating,
|
|
164
|
+
inputs: {},
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const AnJsonInput: Story<string, JsonInputProps> = {
|
|
169
|
+
args: {
|
|
170
|
+
fields: {
|
|
171
|
+
$: {
|
|
172
|
+
disabled: false,
|
|
173
|
+
required: false,
|
|
174
|
+
value: '{}',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
ValueInput: JsonInput,
|
|
178
|
+
inputs: {
|
|
179
|
+
rows: 8,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { composeStories } from '@storybook/react'
|
|
2
|
+
import { toArray } from '@strictly/base'
|
|
3
|
+
import {
|
|
4
|
+
render,
|
|
5
|
+
} from '@testing-library/react'
|
|
6
|
+
import * as stories from './value_input_hooks.stories'
|
|
7
|
+
|
|
8
|
+
const composedStories = composeStories(stories)
|
|
9
|
+
describe('mantine value input hooks', function () {
|
|
10
|
+
it.each(toArray(composedStories))('renders %s', function (_name, Story) {
|
|
11
|
+
const wrapper = render(<Story />)
|
|
12
|
+
expect(wrapper.container).toMatchSnapshot()
|
|
13
|
+
})
|
|
14
|
+
})
|
package/mantine/types.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ComponentType } from 'react'
|
|
2
|
+
import { type Fields } from 'types/field'
|
|
3
|
+
import { type UnsafePartialComponent } from 'util/partial'
|
|
4
|
+
|
|
5
|
+
export type MantineForm<F extends Fields> = {
|
|
6
|
+
fields: F,
|
|
7
|
+
onFieldValueChange: (<K extends keyof F>(this: void, key: K, value: F[K]['value']) => void) | undefined,
|
|
8
|
+
onFieldFocus: ((this: void, key: keyof F) => void) | undefined,
|
|
9
|
+
onFieldBlur: ((this: void, key: keyof F) => void) | undefined,
|
|
10
|
+
onFieldSubmit: ((this: void, key: keyof F) => boolean | void) | undefined,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type MantineFieldComponent<T, P = T> = UnsafePartialComponent<ComponentType<P>, T>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"exports": {
|
|
3
|
+
".": {
|
|
4
|
+
"import": {
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
|
|
7
|
+
"default": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"require": {
|
|
10
|
+
"types": "./dist/index.d.cts",
|
|
11
|
+
|
|
12
|
+
"default": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"@strictly/base": "*",
|
|
4
|
+
"@strictly/define": "*",
|
|
5
|
+
"mobx": "^6.13.5",
|
|
6
|
+
"mobx-react": "^9.1.1",
|
|
7
|
+
"react": "^19.0.0"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
11
|
+
"@babel/plugin-transform-class-static-block": "^7.26.0",
|
|
12
|
+
"@mantine/core": "^7.13.4",
|
|
13
|
+
"@storybook/addon-actions": "^8.4.5",
|
|
14
|
+
"@storybook/addon-essentials": "^8.4.5",
|
|
15
|
+
"@storybook/addon-interactions": "^8.4.5",
|
|
16
|
+
"@storybook/addon-links": "^8.4.5",
|
|
17
|
+
"@storybook/blocks": "^8.4.5",
|
|
18
|
+
"@storybook/builder-vite": "^8.4.5",
|
|
19
|
+
"@storybook/react": "^8.4.5",
|
|
20
|
+
"@storybook/react-vite": "^8.4.5",
|
|
21
|
+
"@storybook/test-runner": "^0.19.1",
|
|
22
|
+
"@strictly/support-vite": "*",
|
|
23
|
+
"@testing-library/dom": "^10.4.0",
|
|
24
|
+
"@testing-library/react": "^16.0.1",
|
|
25
|
+
"@types/react": "^18.3.12",
|
|
26
|
+
"@types/react-dom": "^18.3.1",
|
|
27
|
+
"@vitejs/plugin-react": "^4.3.3",
|
|
28
|
+
"jsdom": "^25.0.1",
|
|
29
|
+
"react-dom": "^19.0.0",
|
|
30
|
+
"resize-observer-polyfill": "^1.5.1",
|
|
31
|
+
"storybook": "^8.4.5",
|
|
32
|
+
"type-fest": "^4.26.1",
|
|
33
|
+
"vite": "^6.0.5",
|
|
34
|
+
"vitest-matchmedia-mock": "^1.0.6"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"name": "@strictly/react-form",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"directory": "packages/react-form",
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/madmaw/de.git"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"check-types": "tsc",
|
|
49
|
+
"clean": "del-cli dist",
|
|
50
|
+
"lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --max-warnings=0",
|
|
51
|
+
"lint:fix": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --fix",
|
|
52
|
+
"release:exports": "json -f package.json -f package.exports.json --merge > package.release.json",
|
|
53
|
+
"storybook": "storybook dev",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:update": "vitest run -u",
|
|
56
|
+
"test:watch": "vitest"
|
|
57
|
+
},
|
|
58
|
+
"type": "module",
|
|
59
|
+
"version": "0.0.1",
|
|
60
|
+
"exports": {
|
|
61
|
+
".": {
|
|
62
|
+
"import": {
|
|
63
|
+
"types": "./dist/index.d.ts",
|
|
64
|
+
"default": "./dist/index.js"
|
|
65
|
+
},
|
|
66
|
+
"require": {
|
|
67
|
+
"types": "./dist/index.d.cts",
|
|
68
|
+
"default": "./dist/index.cjs"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"main": "./dist/index.js",
|
|
73
|
+
"types": "./dist/index.d.ts"
|
|
74
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".",
|
|
4
|
+
"outDir": "./.out"
|
|
5
|
+
},
|
|
6
|
+
"extends": "../../tsconfig.json",
|
|
7
|
+
"include": [
|
|
8
|
+
"**/*.ts",
|
|
9
|
+
"**/*.tsx",
|
|
10
|
+
".storybook/**/*.ts",
|
|
11
|
+
".storybook/**/*.tsx",
|
|
12
|
+
".vitest/**/*.ts",
|
|
13
|
+
"babel.config.cjs",
|
|
14
|
+
"tsconfig.json"
|
|
15
|
+
],
|
|
16
|
+
"references": [
|
|
17
|
+
{
|
|
18
|
+
"path": "../../support/vite"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "../define"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "../base"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineConfig,
|
|
3
|
+
type Options,
|
|
4
|
+
} from 'tsup'
|
|
5
|
+
|
|
6
|
+
export default defineConfig((options: Options) => ({
|
|
7
|
+
entry: ['index.ts'],
|
|
8
|
+
tsconfig: './tsconfig.build.json',
|
|
9
|
+
clean: false,
|
|
10
|
+
dts: true,
|
|
11
|
+
format: [
|
|
12
|
+
'cjs',
|
|
13
|
+
'esm',
|
|
14
|
+
],
|
|
15
|
+
...options,
|
|
16
|
+
}))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Fields } from './field'
|
|
2
|
+
import { type ValueTypeOfField } from './value_type_of_field'
|
|
3
|
+
|
|
4
|
+
// this is a ridiculous type, but it is used for consistency and it seems to force
|
|
5
|
+
// the keys to be strings
|
|
6
|
+
export type AllFieldsOfFields<F extends Fields> = {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
[K in keyof F as ValueTypeOfField<F[K]> extends any ? K : never]: F[K]
|
|
9
|
+
}
|
package/types/field.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
|
+
export type Field<V = any, E = any> = {
|
|
3
|
+
readonly value: V,
|
|
4
|
+
readonly error?: E | undefined,
|
|
5
|
+
readonly disabled: boolean,
|
|
6
|
+
readonly required: boolean,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type Fields = Readonly<Record<string, Field>>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Maybe } from '@strictly/base'
|
|
2
|
+
|
|
3
|
+
export enum FieldConversionResult {
|
|
4
|
+
Success = 0,
|
|
5
|
+
Failure = 1,
|
|
6
|
+
}
|
|
7
|
+
export type FieldConversion<V, E> = {
|
|
8
|
+
type: FieldConversionResult.Success,
|
|
9
|
+
value: V,
|
|
10
|
+
} | {
|
|
11
|
+
type: FieldConversionResult.Failure,
|
|
12
|
+
error: E,
|
|
13
|
+
value: Maybe<V>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// convert to the model type from the display type
|
|
17
|
+
// for example a text field that renders an integer would have
|
|
18
|
+
// a `from` type of `string`, and a `to` type of `number`
|
|
19
|
+
|
|
20
|
+
// TODO converter can also have the allowable value path as a parameter
|
|
21
|
+
export type FieldConverter<
|
|
22
|
+
From,
|
|
23
|
+
To,
|
|
24
|
+
E,
|
|
25
|
+
ValuePath extends string,
|
|
26
|
+
Context,
|
|
27
|
+
> = {
|
|
28
|
+
(from: From, valuePath: ValuePath, context: Context): FieldConversion<To, E>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SafeFieldConverter<
|
|
32
|
+
From,
|
|
33
|
+
To,
|
|
34
|
+
ValuePath extends string,
|
|
35
|
+
Context,
|
|
36
|
+
> = {
|
|
37
|
+
(from: From, valuePath: ValuePath, context: Context): To,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type TwoWayFieldConverter<
|
|
41
|
+
From,
|
|
42
|
+
To,
|
|
43
|
+
E,
|
|
44
|
+
ValuePath extends string,
|
|
45
|
+
Context,
|
|
46
|
+
> = {
|
|
47
|
+
convert: SafeFieldConverter<From, To, ValuePath, Context>,
|
|
48
|
+
|
|
49
|
+
revert: FieldConverter<To, From, E, ValuePath, Context>,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type FieldValueFactory<V, ValuePath extends string, Context> = {
|
|
53
|
+
(valuePath: ValuePath, context: Context): V,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type TwoWayFieldConverterWithValueFactory<
|
|
57
|
+
From,
|
|
58
|
+
To,
|
|
59
|
+
E,
|
|
60
|
+
ValuePath extends string,
|
|
61
|
+
Context,
|
|
62
|
+
> = TwoWayFieldConverter<From, To, E, ValuePath, Context> & {
|
|
63
|
+
readonly create: FieldValueFactory<From, ValuePath, Context>,
|
|
64
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type PrintableOf } from '@strictly/base'
|
|
2
|
+
import { type ValueOf } from 'type-fest'
|
|
3
|
+
import { type Field } from './field'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Maps type paths to value paths for
|
|
7
|
+
*/
|
|
8
|
+
export type FlattenedFormFieldsOf<
|
|
9
|
+
JsonPaths extends Record<string, string>,
|
|
10
|
+
TypePathsToFormFields extends Partial<Readonly<Record<ValueOf<JsonPaths>, Field>>>,
|
|
11
|
+
> = keyof TypePathsToFormFields extends ValueOf<JsonPaths> ? {
|
|
12
|
+
readonly [K in keyof JsonPaths as unknown extends TypePathsToFormFields[JsonPaths[K]] ? never : K]:
|
|
13
|
+
TypePathsToFormFields[JsonPaths[K]]
|
|
14
|
+
}
|
|
15
|
+
// TODO is there a better way of enforcing the types are a subset?
|
|
16
|
+
: `fields missing paths: ${PrintableOf<Exclude<keyof TypePathsToFormFields, ValueOf<JsonPaths>>>}`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Fields } from './field'
|
|
2
|
+
import { type ValueTypeOfField } from './value_type_of_field'
|
|
3
|
+
|
|
4
|
+
export type ListFieldsOfFields<F extends Fields> = {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
[K in keyof F as ValueTypeOfField<F[K]> extends readonly any[] ? K : never]: F[K]
|
|
7
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type BooleanFieldsOfFields } from 'types/boolean_fields_of_fields'
|
|
2
|
+
import { type Field } from 'types/field'
|
|
3
|
+
|
|
4
|
+
describe('BooleanFieldsOfFields', function () {
|
|
5
|
+
describe('filtering', function () {
|
|
6
|
+
const e1 = Symbol()
|
|
7
|
+
const e2 = Symbol()
|
|
8
|
+
const e3 = Symbol()
|
|
9
|
+
type E1 = typeof e1
|
|
10
|
+
type E2 = typeof e2
|
|
11
|
+
type E3 = typeof e3
|
|
12
|
+
type F = {
|
|
13
|
+
b: Field<boolean, E1>,
|
|
14
|
+
s: Field<string, E2>,
|
|
15
|
+
n: Field<number, E3>,
|
|
16
|
+
}
|
|
17
|
+
it('equals expected type', function () {
|
|
18
|
+
expectTypeOf<BooleanFieldsOfFields<F>>().toEqualTypeOf<{
|
|
19
|
+
b: Field<boolean, E1>,
|
|
20
|
+
}>()
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ErrorTypeOfField } from 'types/error_type_of_field'
|
|
2
|
+
import { type Field } from 'types/field'
|
|
3
|
+
|
|
4
|
+
describe('ErrorTypeOfField', function () {
|
|
5
|
+
it('equals expected type', function () {
|
|
6
|
+
const e = Symbol()
|
|
7
|
+
type E = typeof e
|
|
8
|
+
expectTypeOf<ErrorTypeOfField<Field<unknown, E>>>().toEqualTypeOf<E>()
|
|
9
|
+
})
|
|
10
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Field } from 'types/field'
|
|
2
|
+
import { type FlattenedFormFieldsOf } from 'types/flattened_form_fields_of'
|
|
3
|
+
|
|
4
|
+
describe('FlattenedFormFieldsOf', function () {
|
|
5
|
+
describe('subset', function () {
|
|
6
|
+
it('equals expected type', function () {
|
|
7
|
+
type T = FlattenedFormFieldsOf<
|
|
8
|
+
{
|
|
9
|
+
readonly a: 'x',
|
|
10
|
+
readonly b: 'y',
|
|
11
|
+
readonly c: 'z',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
readonly x: Field<1, string>,
|
|
15
|
+
readonly z: Field<3, string>,
|
|
16
|
+
}
|
|
17
|
+
>
|
|
18
|
+
|
|
19
|
+
expectTypeOf<T>().toEqualTypeOf<{
|
|
20
|
+
readonly a: Field<1, string>,
|
|
21
|
+
readonly c: Field<3, string>,
|
|
22
|
+
}>()
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('overlap', function () {
|
|
27
|
+
it('errors to callee', function () {
|
|
28
|
+
type T = FlattenedFormFieldsOf<
|
|
29
|
+
{
|
|
30
|
+
readonly a: 'x',
|
|
31
|
+
readonly b: 'y',
|
|
32
|
+
readonly c: 'z',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
readonly w: Field<0, string>,
|
|
36
|
+
readonly x: Field<1, string>,
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
|
|
40
|
+
expectTypeOf<T>().toEqualTypeOf<'fields missing paths: w'>()
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
})
|