@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.
Files changed (239) hide show
  1. package/.eslintrc.cjs +26 -0
  2. package/.out/.storybook/main.d.ts +3 -0
  3. package/.out/.storybook/main.js +32 -0
  4. package/.out/.storybook/preview.d.ts +4 -0
  5. package/.out/.storybook/preview.js +20 -0
  6. package/.out/.vitest/install_deterministic_random.d.ts +2 -0
  7. package/.out/.vitest/install_deterministic_random.js +15 -0
  8. package/.out/.vitest/install_storybook_preview.d.ts +1 -0
  9. package/.out/.vitest/install_storybook_preview.js +7 -0
  10. package/.out/.vitest/match_media.d.ts +1 -0
  11. package/.out/.vitest/match_media.js +5 -0
  12. package/.out/.vitest/resize_observer.d.ts +1 -0
  13. package/.out/.vitest/resize_observer.js +4 -0
  14. package/.out/core/mobx/field_adapter.d.ts +9 -0
  15. package/.out/core/mobx/field_adapter.js +1 -0
  16. package/.out/core/mobx/field_adapter_builder.d.ts +22 -0
  17. package/.out/core/mobx/field_adapter_builder.js +56 -0
  18. package/.out/core/mobx/flattened_adapters_of_fields.d.ts +9 -0
  19. package/.out/core/mobx/flattened_adapters_of_fields.js +1 -0
  20. package/.out/core/mobx/flattened_list_type_defs_of.d.ts +8 -0
  21. package/.out/core/mobx/flattened_list_type_defs_of.js +1 -0
  22. package/.out/core/mobx/form_presenter.d.ts +61 -0
  23. package/.out/core/mobx/form_presenter.js +425 -0
  24. package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.d.ts +1 -0
  25. package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.js +13 -0
  26. package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.d.ts +1 -0
  27. package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.js +16 -0
  28. package/.out/core/mobx/specs/form_presenter.tests.d.ts +1 -0
  29. package/.out/core/mobx/specs/form_presenter.tests.js +697 -0
  30. package/.out/core/mobx/types.d.ts +19 -0
  31. package/.out/core/mobx/types.js +1 -0
  32. package/.out/core/props.d.ts +12 -0
  33. package/.out/core/props.js +1 -0
  34. package/.out/field_converters/chain_field_converter.d.ts +3 -0
  35. package/.out/field_converters/chain_field_converter.js +46 -0
  36. package/.out/field_converters/identity_converter.d.ts +3 -0
  37. package/.out/field_converters/identity_converter.js +14 -0
  38. package/.out/field_converters/integer_to_string_converter.d.ts +7 -0
  39. package/.out/field_converters/integer_to_string_converter.js +26 -0
  40. package/.out/field_converters/list_converter.d.ts +2 -0
  41. package/.out/field_converters/list_converter.js +8 -0
  42. package/.out/field_converters/maybe_identity_converter.d.ts +8 -0
  43. package/.out/field_converters/maybe_identity_converter.js +15 -0
  44. package/.out/field_converters/nullable_to_boolean_converter.d.ts +11 -0
  45. package/.out/field_converters/nullable_to_boolean_converter.js +31 -0
  46. package/.out/field_converters/select_value_type_converter.d.ts +23 -0
  47. package/.out/field_converters/select_value_type_converter.js +60 -0
  48. package/.out/field_converters/trimming_string_converter.d.ts +6 -0
  49. package/.out/field_converters/trimming_string_converter.js +14 -0
  50. package/.out/field_converters/validating_converter.d.ts +3 -0
  51. package/.out/field_converters/validating_converter.js +21 -0
  52. package/.out/field_validators/minimum_string_length_field_validator.d.ts +2 -0
  53. package/.out/field_validators/minimum_string_length_field_validator.js +8 -0
  54. package/.out/field_value_factories/prototyping_field_value_factory.d.ts +2 -0
  55. package/.out/field_value_factories/prototyping_field_value_factory.js +5 -0
  56. package/.out/index.d.ts +16 -0
  57. package/.out/index.js +16 -0
  58. package/.out/mantine/create_checkbox.d.ts +9 -0
  59. package/.out/mantine/create_checkbox.js +37 -0
  60. package/.out/mantine/create_list.d.ts +15 -0
  61. package/.out/mantine/create_list.js +16 -0
  62. package/.out/mantine/create_pill.d.ts +7 -0
  63. package/.out/mantine/create_pill.js +15 -0
  64. package/.out/mantine/create_radio.d.ts +8 -0
  65. package/.out/mantine/create_radio.js +10 -0
  66. package/.out/mantine/create_radio_group.d.ts +9 -0
  67. package/.out/mantine/create_radio_group.js +34 -0
  68. package/.out/mantine/create_text_input.d.ts +19 -0
  69. package/.out/mantine/create_text_input.js +38 -0
  70. package/.out/mantine/create_value_input.d.ts +17 -0
  71. package/.out/mantine/create_value_input.js +38 -0
  72. package/.out/mantine/hooks.d.ts +56 -0
  73. package/.out/mantine/hooks.js +135 -0
  74. package/.out/mantine/specs/checkbox_constants.d.ts +1 -0
  75. package/.out/mantine/specs/checkbox_constants.js +1 -0
  76. package/.out/mantine/specs/checkbox_hooks.stories.d.ts +13 -0
  77. package/.out/mantine/specs/checkbox_hooks.stories.js +63 -0
  78. package/.out/mantine/specs/checkbox_hooks.tests.d.ts +1 -0
  79. package/.out/mantine/specs/checkbox_hooks.tests.js +74 -0
  80. package/.out/mantine/specs/list_hooks.stories.d.ts +11 -0
  81. package/.out/mantine/specs/list_hooks.stories.js +48 -0
  82. package/.out/mantine/specs/list_hooks.tests.d.ts +1 -0
  83. package/.out/mantine/specs/list_hooks.tests.js +12 -0
  84. package/.out/mantine/specs/radio_group_constants.d.ts +4 -0
  85. package/.out/mantine/specs/radio_group_constants.js +11 -0
  86. package/.out/mantine/specs/radio_group_hooks.stories.d.ts +14 -0
  87. package/.out/mantine/specs/radio_group_hooks.stories.js +68 -0
  88. package/.out/mantine/specs/radio_group_hooks.tests.d.ts +1 -0
  89. package/.out/mantine/specs/radio_group_hooks.tests.js +62 -0
  90. package/.out/mantine/specs/select_hooks.stories.d.ts +12 -0
  91. package/.out/mantine/specs/select_hooks.stories.js +57 -0
  92. package/.out/mantine/specs/select_hooks.tests.d.ts +1 -0
  93. package/.out/mantine/specs/select_hooks.tests.js +12 -0
  94. package/.out/mantine/specs/select_hooks_constant.d.ts +1 -0
  95. package/.out/mantine/specs/select_hooks_constant.js +1 -0
  96. package/.out/mantine/specs/text_input_constants.d.ts +1 -0
  97. package/.out/mantine/specs/text_input_constants.js +1 -0
  98. package/.out/mantine/specs/text_input_hooks.stories.d.ts +21 -0
  99. package/.out/mantine/specs/text_input_hooks.stories.js +88 -0
  100. package/.out/mantine/specs/text_input_hooks.tests.d.ts +1 -0
  101. package/.out/mantine/specs/text_input_hooks.tests.js +79 -0
  102. package/.out/mantine/specs/value_input_constants.d.ts +2 -0
  103. package/.out/mantine/specs/value_input_constants.js +2 -0
  104. package/.out/mantine/specs/value_input_hooks.stories.d.ts +23 -0
  105. package/.out/mantine/specs/value_input_hooks.stories.js +124 -0
  106. package/.out/mantine/specs/value_input_hooks.tests.d.ts +1 -0
  107. package/.out/mantine/specs/value_input_hooks.tests.js +12 -0
  108. package/.out/mantine/types.d.ts +11 -0
  109. package/.out/mantine/types.js +1 -0
  110. package/.out/tsconfig.json +27 -0
  111. package/.out/tsconfig.tsbuildinfo +1 -0
  112. package/.out/tsup.config.d.ts +3 -0
  113. package/.out/tsup.config.js +12 -0
  114. package/.out/types/all_fields_of_fields.d.ts +5 -0
  115. package/.out/types/all_fields_of_fields.js +1 -0
  116. package/.out/types/boolean_fields_of_fields.d.ts +5 -0
  117. package/.out/types/boolean_fields_of_fields.js +1 -0
  118. package/.out/types/error_type_of_field.d.ts +2 -0
  119. package/.out/types/error_type_of_field.js +1 -0
  120. package/.out/types/field.d.ts +7 -0
  121. package/.out/types/field.js +1 -0
  122. package/.out/types/field_converters.d.ts +29 -0
  123. package/.out/types/field_converters.js +5 -0
  124. package/.out/types/field_validator.d.ts +3 -0
  125. package/.out/types/field_validator.js +1 -0
  126. package/.out/types/flattened_form_fields_of.d.ts +9 -0
  127. package/.out/types/flattened_form_fields_of.js +1 -0
  128. package/.out/types/list_fields_of_fields.d.ts +5 -0
  129. package/.out/types/list_fields_of_fields.js +1 -0
  130. package/.out/types/specs/boolean_fields_of_fields.tests.d.ts +1 -0
  131. package/.out/types/specs/boolean_fields_of_fields.tests.js +11 -0
  132. package/.out/types/specs/error_type_of_field.tests.d.ts +1 -0
  133. package/.out/types/specs/error_type_of_field.tests.js +7 -0
  134. package/.out/types/specs/flattened_form_fields_of.tests.d.ts +1 -0
  135. package/.out/types/specs/flattened_form_fields_of.tests.js +13 -0
  136. package/.out/types/specs/string_fields_of_fields.tests.d.ts +1 -0
  137. package/.out/types/specs/string_fields_of_fields.tests.js +19 -0
  138. package/.out/types/specs/value_type_of_field.tests.d.ts +1 -0
  139. package/.out/types/specs/value_type_of_field.tests.js +7 -0
  140. package/.out/types/string_fields_of_fields.d.ts +5 -0
  141. package/.out/types/string_fields_of_fields.js +1 -0
  142. package/.out/types/value_type_of_field.d.ts +2 -0
  143. package/.out/types/value_type_of_field.js +1 -0
  144. package/.out/util/partial.d.ts +11 -0
  145. package/.out/util/partial.js +74 -0
  146. package/.out/vitest.workspace.d.ts +2 -0
  147. package/.out/vitest.workspace.js +22 -0
  148. package/.storybook/main.ts +40 -0
  149. package/.storybook/preview.tsx +28 -0
  150. package/.storybook/vite.config.mts +38 -0
  151. package/.turbo/turbo-build.log +18 -0
  152. package/.turbo/turbo-check-types.log +3 -0
  153. package/.turbo/turbo-release$colon$exports.log +3 -0
  154. package/.vitest/install_deterministic_random.ts +17 -0
  155. package/.vitest/install_storybook_preview.ts +9 -0
  156. package/.vitest/match_media.ts +7 -0
  157. package/.vitest/resize_observer.ts +5 -0
  158. package/README.md +2 -0
  159. package/core/mobx/field_adapter.ts +32 -0
  160. package/core/mobx/field_adapter_builder.ts +313 -0
  161. package/core/mobx/flattened_adapters_of_fields.ts +35 -0
  162. package/core/mobx/flattened_list_type_defs_of.ts +17 -0
  163. package/core/mobx/form_presenter.ts +705 -0
  164. package/core/mobx/specs/flattened_adapters_of_fields.tests.ts +72 -0
  165. package/core/mobx/specs/flattened_list_type_defs_of.tests.ts +35 -0
  166. package/core/mobx/specs/form_presenter.tests.ts +989 -0
  167. package/core/mobx/types.ts +54 -0
  168. package/core/props.ts +21 -0
  169. package/dist/index.cjs +11479 -0
  170. package/dist/index.d.cts +345 -0
  171. package/dist/index.d.ts +345 -0
  172. package/dist/index.js +11486 -0
  173. package/field_converters/chain_field_converter.ts +74 -0
  174. package/field_converters/identity_converter.ts +39 -0
  175. package/field_converters/integer_to_string_converter.ts +32 -0
  176. package/field_converters/list_converter.ts +15 -0
  177. package/field_converters/maybe_identity_converter.ts +23 -0
  178. package/field_converters/nullable_to_boolean_converter.ts +56 -0
  179. package/field_converters/select_value_type_converter.ts +141 -0
  180. package/field_converters/trimming_string_converter.ts +23 -0
  181. package/field_converters/validating_converter.ts +35 -0
  182. package/field_validators/minimum_string_length_field_validator.ts +13 -0
  183. package/field_value_factories/prototyping_field_value_factory.ts +11 -0
  184. package/index.ts +16 -0
  185. package/mantine/create_checkbox.tsx +79 -0
  186. package/mantine/create_list.tsx +58 -0
  187. package/mantine/create_pill.tsx +43 -0
  188. package/mantine/create_radio.tsx +36 -0
  189. package/mantine/create_radio_group.tsx +71 -0
  190. package/mantine/create_text_input.tsx +80 -0
  191. package/mantine/create_value_input.tsx +81 -0
  192. package/mantine/hooks.tsx +394 -0
  193. package/mantine/specs/__snapshots__/check_box_hooks.tests.tsx.snap +227 -0
  194. package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +227 -0
  195. package/mantine/specs/__snapshots__/list_hooks.tests.tsx.snap +68 -0
  196. package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +695 -0
  197. package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +225 -0
  198. package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +202 -0
  199. package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +613 -0
  200. package/mantine/specs/checkbox_constants.ts +1 -0
  201. package/mantine/specs/checkbox_hooks.stories.tsx +79 -0
  202. package/mantine/specs/checkbox_hooks.tests.tsx +100 -0
  203. package/mantine/specs/list_hooks.stories.tsx +83 -0
  204. package/mantine/specs/list_hooks.tests.tsx +15 -0
  205. package/mantine/specs/radio_group_constants.ts +12 -0
  206. package/mantine/specs/radio_group_hooks.stories.tsx +103 -0
  207. package/mantine/specs/radio_group_hooks.tests.tsx +92 -0
  208. package/mantine/specs/select_hooks.stories.tsx +77 -0
  209. package/mantine/specs/select_hooks.tests.tsx +14 -0
  210. package/mantine/specs/select_hooks_constant.ts +1 -0
  211. package/mantine/specs/text_input_constants.ts +1 -0
  212. package/mantine/specs/text_input_hooks.stories.tsx +124 -0
  213. package/mantine/specs/text_input_hooks.tests.tsx +106 -0
  214. package/mantine/specs/value_input_constants.ts +2 -0
  215. package/mantine/specs/value_input_hooks.stories.tsx +182 -0
  216. package/mantine/specs/value_input_hooks.tests.tsx +14 -0
  217. package/mantine/types.ts +13 -0
  218. package/package.exports.json +18 -0
  219. package/package.json +74 -0
  220. package/tsconfig.build.json +13 -0
  221. package/tsconfig.json +27 -0
  222. package/tsup.config.ts +16 -0
  223. package/types/all_fields_of_fields.ts +9 -0
  224. package/types/boolean_fields_of_fields.ts +8 -0
  225. package/types/error_type_of_field.ts +3 -0
  226. package/types/field.ts +9 -0
  227. package/types/field_converters.ts +64 -0
  228. package/types/field_validator.ts +7 -0
  229. package/types/flattened_form_fields_of.ts +16 -0
  230. package/types/list_fields_of_fields.ts +7 -0
  231. package/types/specs/boolean_fields_of_fields.tests.ts +23 -0
  232. package/types/specs/error_type_of_field.tests.ts +10 -0
  233. package/types/specs/flattened_form_fields_of.tests.ts +43 -0
  234. package/types/specs/string_fields_of_fields.tests.ts +40 -0
  235. package/types/specs/value_type_of_field.tests.ts +10 -0
  236. package/types/string_fields_of_fields.ts +6 -0
  237. package/types/value_type_of_field.ts +3 -0
  238. package/util/partial.tsx +200 -0
  239. 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,2 @@
1
+ export const NUMBER_INPUT_LABEL = 'Number Input'
2
+ export const SLIDER_LABEL = 'Slider'
@@ -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
+ })
@@ -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
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": "."
4
+ },
5
+ "extends": "../../tsconfig.build.json",
6
+ "include": [
7
+ ".eslintrc.cjs",
8
+ ".storybook/vite.config.mts",
9
+ "tsup.config.ts",
10
+ "vitest.workspace.ts"
11
+
12
+ ]
13
+ }
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
+ }
@@ -0,0 +1,8 @@
1
+ import {
2
+ type Fields,
3
+ } from './field'
4
+ import { type ValueTypeOfField } from './value_type_of_field'
5
+
6
+ export type BooleanFieldsOfFields<F extends Fields> = {
7
+ [K in keyof F as ValueTypeOfField<F[K]> extends boolean ? K : never]: F[K]
8
+ }
@@ -0,0 +1,3 @@
1
+ import { type Field } from './field'
2
+
3
+ export type ErrorTypeOfField<F extends Field> = F extends Field<infer _V, infer E> ? E : never
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,7 @@
1
+ export type FieldValidator<V, E, ValuePath extends string, Context> = {
2
+ (
3
+ value: V,
4
+ valuePath: ValuePath,
5
+ context: Context,
6
+ ): E | null,
7
+ }
@@ -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
+ })