@proyecto-viviana/solidaria 0.2.4 → 0.2.8

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/dist/actiongroup/createActionGroup.d.ts +29 -0
  3. package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
  4. package/dist/actiongroup/index.d.ts +2 -0
  5. package/dist/actiongroup/index.d.ts.map +1 -0
  6. package/dist/autocomplete/createAutocomplete.d.ts +6 -2
  7. package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
  8. package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
  9. package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
  10. package/dist/button/createToggleButtonGroup.d.ts +32 -0
  11. package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
  12. package/dist/button/index.d.ts +2 -0
  13. package/dist/button/index.d.ts.map +1 -1
  14. package/dist/calendar/createCalendarCell.d.ts +2 -0
  15. package/dist/calendar/createCalendarCell.d.ts.map +1 -1
  16. package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
  17. package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
  18. package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
  19. package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
  20. package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
  21. package/dist/collections/index.d.ts +56 -0
  22. package/dist/collections/index.d.ts.map +1 -0
  23. package/dist/color/createColorArea.d.ts.map +1 -1
  24. package/dist/color/createColorSlider.d.ts.map +1 -1
  25. package/dist/color/createColorWheel.d.ts.map +1 -1
  26. package/dist/combobox/createComboBox.d.ts +6 -0
  27. package/dist/combobox/createComboBox.d.ts.map +1 -1
  28. package/dist/datepicker/createDatePicker.d.ts +6 -0
  29. package/dist/datepicker/createDatePicker.d.ts.map +1 -1
  30. package/dist/datepicker/createDateRangePicker.d.ts +40 -0
  31. package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
  32. package/dist/datepicker/createDateSegment.d.ts +1 -1
  33. package/dist/datepicker/createDateSegment.d.ts.map +1 -1
  34. package/dist/datepicker/createTimeSegment.d.ts +29 -0
  35. package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
  36. package/dist/datepicker/index.d.ts +2 -0
  37. package/dist/datepicker/index.d.ts.map +1 -1
  38. package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
  39. package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
  40. package/dist/dnd/createDrag.d.ts.map +1 -1
  41. package/dist/dnd/createDraggableCollection.d.ts +4 -0
  42. package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
  43. package/dist/dnd/createDraggableItem.d.ts.map +1 -1
  44. package/dist/dnd/createDrop.d.ts.map +1 -1
  45. package/dist/dnd/createDroppableCollection.d.ts +32 -1
  46. package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
  47. package/dist/dnd/createDroppableItem.d.ts.map +1 -1
  48. package/dist/dnd/index.d.ts +1 -1
  49. package/dist/dnd/index.d.ts.map +1 -1
  50. package/dist/grid/createGrid.d.ts.map +1 -1
  51. package/dist/gridlist/createGridList.d.ts.map +1 -1
  52. package/dist/index.d.ts +6 -4
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +4659 -3452
  55. package/dist/index.js.map +1 -7
  56. package/dist/index.ssr.js +4659 -3452
  57. package/dist/index.ssr.js.map +1 -7
  58. package/dist/interactions/createFocus.d.ts.map +1 -1
  59. package/dist/interactions/createFocusWithin.d.ts.map +1 -1
  60. package/dist/link/createLink.d.ts +10 -0
  61. package/dist/link/createLink.d.ts.map +1 -1
  62. package/dist/listbox/createListBox.d.ts +1 -0
  63. package/dist/listbox/createListBox.d.ts.map +1 -1
  64. package/dist/listbox/createOption.d.ts.map +1 -1
  65. package/dist/menu/createMenu.d.ts +1 -0
  66. package/dist/menu/createMenu.d.ts.map +1 -1
  67. package/dist/meter/createMeter.d.ts.map +1 -1
  68. package/dist/numberfield/createNumberField.d.ts +18 -0
  69. package/dist/numberfield/createNumberField.d.ts.map +1 -1
  70. package/dist/overlays/createModal.d.ts +16 -0
  71. package/dist/overlays/createModal.d.ts.map +1 -1
  72. package/dist/overlays/createOverlay.d.ts.map +1 -1
  73. package/dist/overlays/index.d.ts +1 -1
  74. package/dist/overlays/index.d.ts.map +1 -1
  75. package/dist/popover/createOverlayPosition.d.ts.map +1 -1
  76. package/dist/popover/createPopover.d.ts.map +1 -1
  77. package/dist/progress/createProgressBar.d.ts.map +1 -1
  78. package/dist/radio/createRadioGroup.d.ts +2 -2
  79. package/dist/radio/createRadioGroup.d.ts.map +1 -1
  80. package/dist/searchfield/createSearchField.d.ts.map +1 -1
  81. package/dist/select/createHiddenSelect.d.ts.map +1 -1
  82. package/dist/select/createSelect.d.ts.map +1 -1
  83. package/dist/slider/createSlider.d.ts.map +1 -1
  84. package/dist/table/createTable.d.ts.map +1 -1
  85. package/dist/tabs/createTabs.d.ts +1 -1
  86. package/dist/tabs/createTabs.d.ts.map +1 -1
  87. package/dist/tag/createTag.d.ts.map +1 -1
  88. package/dist/tag/createTagGroup.d.ts.map +1 -1
  89. package/dist/toast/createToast.d.ts +4 -0
  90. package/dist/toast/createToast.d.ts.map +1 -1
  91. package/dist/toast/createToastRegion.d.ts.map +1 -1
  92. package/dist/toolbar/createToolbar.d.ts.map +1 -1
  93. package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
  94. package/dist/tree/createTree.d.ts.map +1 -1
  95. package/dist/tree/createTreeItem.d.ts.map +1 -1
  96. package/dist/tree/types.d.ts +4 -0
  97. package/dist/tree/types.d.ts.map +1 -1
  98. package/dist/utils/env.d.ts +1 -1
  99. package/dist/utils/env.d.ts.map +1 -1
  100. package/dist/utils/platform.d.ts.map +1 -1
  101. package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
  102. package/package.json +8 -6
  103. package/src/actiongroup/createActionGroup.ts +324 -0
  104. package/src/actiongroup/index.ts +8 -0
  105. package/src/autocomplete/createAutocomplete.ts +32 -9
  106. package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
  107. package/src/button/createButton.ts +1 -1
  108. package/src/button/createToggleButtonGroup.ts +128 -0
  109. package/src/button/index.ts +9 -0
  110. package/src/calendar/createCalendarCell.ts +6 -4
  111. package/src/calendar/createCalendarGrid.ts +27 -18
  112. package/src/calendar/createRangeCalendarCell.ts +26 -9
  113. package/src/checkbox/createCheckboxGroup.ts +21 -4
  114. package/src/collections/index.ts +242 -0
  115. package/src/color/createColorArea.ts +380 -314
  116. package/src/color/createColorField.ts +137 -137
  117. package/src/color/createColorSlider.ts +286 -197
  118. package/src/color/createColorSwatch.ts +40 -40
  119. package/src/color/createColorWheel.ts +218 -208
  120. package/src/color/index.ts +24 -24
  121. package/src/color/types.ts +116 -116
  122. package/src/combobox/createComboBox.ts +670 -647
  123. package/src/combobox/index.ts +6 -6
  124. package/src/datepicker/createDatePicker.ts +54 -16
  125. package/src/datepicker/createDateRangePicker.ts +246 -0
  126. package/src/datepicker/createDateSegment.ts +185 -31
  127. package/src/datepicker/createTimeSegment.ts +370 -0
  128. package/src/datepicker/index.ts +14 -0
  129. package/src/dialog/createDialog.ts +120 -120
  130. package/src/dialog/index.ts +2 -2
  131. package/src/dialog/types.ts +19 -19
  132. package/src/disclosure/createDisclosureGroup.ts +5 -2
  133. package/src/dnd/createDrag.ts +224 -209
  134. package/src/dnd/createDraggableCollection.ts +96 -63
  135. package/src/dnd/createDraggableItem.ts +259 -243
  136. package/src/dnd/createDrop.ts +322 -321
  137. package/src/dnd/createDroppableCollection.ts +682 -293
  138. package/src/dnd/createDroppableItem.ts +215 -213
  139. package/src/dnd/index.ts +55 -47
  140. package/src/dnd/types.ts +89 -89
  141. package/src/dnd/utils.ts +294 -294
  142. package/src/focus/createAutoFocus.ts +321 -321
  143. package/src/focus/createFocusRestore.ts +313 -313
  144. package/src/focus/createVirtualFocus.ts +396 -396
  145. package/src/form/createFormValidation.ts +224 -224
  146. package/src/form/index.ts +11 -11
  147. package/src/grid/createGrid.ts +3 -1
  148. package/src/gridlist/createGridList.ts +16 -0
  149. package/src/gridlist/createGridListItem.ts +1 -1
  150. package/src/i18n/NumberFormatter.ts +266 -266
  151. package/src/i18n/createCollator.ts +79 -79
  152. package/src/i18n/createDateFormatter.ts +83 -83
  153. package/src/i18n/createFilter.ts +131 -131
  154. package/src/i18n/createNumberFormatter.ts +52 -52
  155. package/src/i18n/index.ts +40 -40
  156. package/src/i18n/locale.tsx +188 -188
  157. package/src/i18n/utils.ts +99 -99
  158. package/src/index.ts +51 -0
  159. package/src/interactions/createFocus.ts +6 -5
  160. package/src/interactions/createFocusWithin.ts +6 -5
  161. package/src/interactions/createLongPress.ts +174 -174
  162. package/src/interactions/createMove.ts +289 -289
  163. package/src/interactions/createPress.ts +5 -5
  164. package/src/landmark/createLandmark.ts +377 -377
  165. package/src/landmark/index.ts +8 -8
  166. package/src/link/createLink.ts +23 -8
  167. package/src/listbox/createListBox.ts +308 -269
  168. package/src/listbox/createOption.ts +162 -151
  169. package/src/listbox/index.ts +12 -12
  170. package/src/live-announcer/announce.ts +322 -322
  171. package/src/live-announcer/index.ts +9 -9
  172. package/src/menu/createMenu.ts +405 -396
  173. package/src/menu/createMenuItem.ts +149 -149
  174. package/src/menu/createMenuTrigger.ts +88 -88
  175. package/src/menu/index.ts +18 -18
  176. package/src/meter/createMeter.ts +1 -6
  177. package/src/numberfield/createNumberField.ts +311 -268
  178. package/src/numberfield/index.ts +5 -5
  179. package/src/overlays/ariaHideOutside.ts +219 -219
  180. package/src/overlays/createInteractOutside.ts +149 -149
  181. package/src/overlays/createModal.tsx +238 -202
  182. package/src/overlays/createOverlay.ts +165 -155
  183. package/src/overlays/createOverlayTrigger.ts +85 -85
  184. package/src/overlays/createPreventScroll.ts +266 -266
  185. package/src/overlays/index.ts +48 -44
  186. package/src/popover/calculatePosition.ts +6 -6
  187. package/src/popover/createOverlayPosition.ts +7 -4
  188. package/src/popover/createPopover.ts +21 -7
  189. package/src/progress/createProgressBar.ts +6 -1
  190. package/src/radio/createRadioGroup.ts +88 -14
  191. package/src/searchfield/createSearchField.ts +241 -186
  192. package/src/searchfield/index.ts +2 -2
  193. package/src/select/createHiddenSelect.tsx +263 -236
  194. package/src/select/createSelect.ts +373 -395
  195. package/src/select/index.ts +14 -14
  196. package/src/slider/createSlider.ts +364 -349
  197. package/src/slider/index.ts +2 -2
  198. package/src/ssr/index.tsx +370 -370
  199. package/src/table/createTable.ts +3 -1
  200. package/src/table/createTableColumnHeader.ts +1 -1
  201. package/src/table/createTableRow.ts +1 -1
  202. package/src/tabs/createTabs.ts +80 -51
  203. package/src/tag/createTag.ts +135 -6
  204. package/src/tag/createTagGroup.ts +7 -2
  205. package/src/toast/createToast.ts +8 -2
  206. package/src/toast/createToastRegion.ts +0 -1
  207. package/src/toolbar/createToolbar.ts +75 -1
  208. package/src/tooltip/createTooltip.ts +79 -79
  209. package/src/tooltip/createTooltipTrigger.ts +226 -222
  210. package/src/tooltip/index.ts +6 -6
  211. package/src/tree/createTree.ts +261 -246
  212. package/src/tree/createTreeItem.ts +282 -233
  213. package/src/tree/createTreeSelectionCheckbox.ts +68 -68
  214. package/src/tree/index.ts +16 -16
  215. package/src/tree/types.ts +91 -87
  216. package/src/utils/env.ts +55 -54
  217. package/src/utils/platform.ts +16 -6
  218. package/src/visually-hidden/createVisuallyHidden.ts +139 -124
  219. package/src/visually-hidden/index.ts +6 -6
@@ -1,268 +1,311 @@
1
- /**
2
- * Provides the behavior and accessibility implementation for a number field.
3
- * Based on @react-aria/numberfield useNumberField.
4
- */
5
-
6
- import { type JSX, createMemo } from 'solid-js';
7
- import { createLabel } from '../label/createLabel';
8
- import { filterDOMProps } from '../utils/filterDOMProps';
9
- import { mergeProps } from '../utils/mergeProps';
10
- import { createId } from '../ssr';
11
- import { access, type MaybeAccessor } from '../utils/reactivity';
12
- import type { NumberFieldState } from '@proyecto-viviana/solid-stately';
13
-
14
- export interface AriaNumberFieldProps {
15
- /** A label for the number field. */
16
- label?: JSX.Element;
17
- /** An accessible label for the number field. */
18
- 'aria-label'?: string;
19
- /** The element ID that labels the number field. */
20
- 'aria-labelledby'?: string;
21
- /** The element ID that describes the number field. */
22
- 'aria-describedby'?: string;
23
- /** Whether the number field is disabled. */
24
- isDisabled?: boolean;
25
- /** Whether the number field is read-only. */
26
- isReadOnly?: boolean;
27
- /** Whether the number field is required. */
28
- isRequired?: boolean;
29
- /** Whether the number field is invalid. */
30
- isInvalid?: boolean;
31
- /** A description for the number field. */
32
- description?: JSX.Element;
33
- /** An error message for the number field. */
34
- errorMessage?: JSX.Element;
35
- /** The ID of the number field. */
36
- id?: string;
37
- /** Whether to auto-focus the input. */
38
- autoFocus?: boolean;
39
- /** The name for the form input. */
40
- name?: string;
41
- }
42
-
43
- export interface NumberFieldAria {
44
- /** Props for the label element. */
45
- labelProps: JSX.HTMLAttributes<HTMLElement>;
46
- /** Props for the input element group. */
47
- groupProps: JSX.HTMLAttributes<HTMLElement>;
48
- /** Props for the input element. */
49
- inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
50
- /** Props for the increment button. */
51
- incrementButtonProps: JSX.ButtonHTMLAttributes<HTMLButtonElement>;
52
- /** Props for the decrement button. */
53
- decrementButtonProps: JSX.ButtonHTMLAttributes<HTMLButtonElement>;
54
- /** Props for the description element. */
55
- descriptionProps: JSX.HTMLAttributes<HTMLElement>;
56
- /** Props for the error message element. */
57
- errorMessageProps: JSX.HTMLAttributes<HTMLElement>;
58
- }
59
-
60
- /**
61
- * Provides the behavior and accessibility implementation for a number field.
62
- */
63
- export function createNumberField(
64
- props: MaybeAccessor<AriaNumberFieldProps>,
65
- state: NumberFieldState,
66
- inputRef?: () => HTMLInputElement | null
67
- ): NumberFieldAria {
68
- const getProps = () => access(props);
69
- const id = createId(getProps().id);
70
-
71
- // Generate IDs for associated elements
72
- const inputId = `${id}-input`;
73
- const incrementId = `${id}-increment`;
74
- const decrementId = `${id}-decrement`;
75
- const descriptionId = `${id}-description`;
76
- const errorMessageId = `${id}-error`;
77
-
78
- // Label handling
79
- const { labelProps, fieldProps } = createLabel({
80
- get id() {
81
- return inputId;
82
- },
83
- get label() {
84
- return getProps().label;
85
- },
86
- get 'aria-label'() {
87
- return getProps()['aria-label'];
88
- },
89
- get 'aria-labelledby'() {
90
- return getProps()['aria-labelledby'];
91
- },
92
- labelElementType: 'span',
93
- });
94
-
95
- // Get the label text for button aria-labels
96
- const getLabelText = (): string => {
97
- const p = getProps();
98
- if (p['aria-label']) return p['aria-label'];
99
- if (typeof p.label === 'string') return p.label;
100
- return 'value';
101
- };
102
-
103
- // Filter DOM props
104
- const domProps = createMemo(() =>
105
- filterDOMProps(getProps() as unknown as Record<string, unknown>, { labelable: true })
106
- );
107
-
108
- // Handle input change
109
- const onInputChange: JSX.EventHandler<HTMLInputElement, Event> = (e) => {
110
- const value = e.currentTarget.value;
111
- if (state.validate(value)) {
112
- state.setInputValue(value);
113
- }
114
- };
115
-
116
- // Handle input blur - commit value
117
- const onInputBlur: JSX.EventHandler<HTMLInputElement, FocusEvent> = () => {
118
- state.commit();
119
- };
120
-
121
- // Handle keyboard events
122
- const onKeyDown: JSX.EventHandler<HTMLInputElement, KeyboardEvent> = (e) => {
123
- const p = getProps();
124
- if (p.isDisabled || p.isReadOnly) return;
125
-
126
- switch (e.key) {
127
- case 'ArrowUp':
128
- e.preventDefault();
129
- state.increment();
130
- break;
131
- case 'ArrowDown':
132
- e.preventDefault();
133
- state.decrement();
134
- break;
135
- case 'PageUp':
136
- e.preventDefault();
137
- state.incrementToMax();
138
- break;
139
- case 'PageDown':
140
- e.preventDefault();
141
- state.decrementToMin();
142
- break;
143
- case 'Home':
144
- e.preventDefault();
145
- state.decrementToMin();
146
- break;
147
- case 'End':
148
- e.preventDefault();
149
- state.incrementToMax();
150
- break;
151
- case 'Enter':
152
- state.commit();
153
- break;
154
- }
155
- };
156
-
157
- // Handle increment button
158
- const onIncrementPress = () => {
159
- state.increment();
160
- // Return focus to input
161
- inputRef?.()?.focus();
162
- };
163
-
164
- // Handle decrement button
165
- const onDecrementPress = () => {
166
- state.decrement();
167
- // Return focus to input
168
- inputRef?.()?.focus();
169
- };
170
-
171
- // Build aria-describedby
172
- const getAriaDescribedBy = () => {
173
- const p = getProps();
174
- const parts: string[] = [];
175
- if (p['aria-describedby']) parts.push(p['aria-describedby']);
176
- if (p.description) parts.push(descriptionId);
177
- if (p.isInvalid && p.errorMessage) parts.push(errorMessageId);
178
- return parts.length > 0 ? parts.join(' ') : undefined;
179
- };
180
-
181
- return {
182
- get labelProps() {
183
- return labelProps as JSX.HTMLAttributes<HTMLElement>;
184
- },
185
- get groupProps() {
186
- return {
187
- role: 'group',
188
- 'aria-disabled': getProps().isDisabled || undefined,
189
- } as JSX.HTMLAttributes<HTMLElement>;
190
- },
191
- get inputProps() {
192
- const p = getProps();
193
- const isDisabled = p.isDisabled ?? state.isDisabled();
194
- const isReadOnly = p.isReadOnly ?? state.isReadOnly();
195
-
196
- return mergeProps(
197
- domProps(),
198
- fieldProps as Record<string, unknown>,
199
- {
200
- id: inputId,
201
- type: 'text',
202
- inputMode: 'decimal' as const,
203
- autoComplete: 'off',
204
- autoCorrect: 'off',
205
- spellcheck: false,
206
- role: 'spinbutton',
207
- 'aria-valuenow': isNaN(state.numberValue()) ? undefined : state.numberValue(),
208
- 'aria-valuemin': state.minValue(),
209
- 'aria-valuemax': state.maxValue(),
210
- 'aria-valuetext': isNaN(state.numberValue()) ? undefined : state.inputValue(),
211
- 'aria-invalid': p.isInvalid || undefined,
212
- 'aria-required': p.isRequired || undefined,
213
- 'aria-describedby': getAriaDescribedBy(),
214
- disabled: isDisabled || undefined,
215
- readOnly: isReadOnly || undefined,
216
- value: state.inputValue(),
217
- onChange: onInputChange,
218
- onBlur: onInputBlur,
219
- onKeyDown,
220
- name: p.name,
221
- autoFocus: p.autoFocus,
222
- } as Record<string, unknown>
223
- ) as JSX.InputHTMLAttributes<HTMLInputElement>;
224
- },
225
- get incrementButtonProps() {
226
- const p = getProps();
227
- const isDisabled = p.isDisabled ?? state.isDisabled();
228
- const canIncrement = state.canIncrement();
229
-
230
- return {
231
- id: incrementId,
232
- type: 'button' as const,
233
- tabIndex: -1, // Exclude from tab order
234
- 'aria-label': `Increase ${getLabelText()}`,
235
- 'aria-controls': inputId,
236
- disabled: isDisabled || !canIncrement,
237
- onClick: onIncrementPress,
238
- onMouseDown: (e: MouseEvent) => e.preventDefault(), // Prevent focus
239
- } as JSX.ButtonHTMLAttributes<HTMLButtonElement>;
240
- },
241
- get decrementButtonProps() {
242
- const p = getProps();
243
- const isDisabled = p.isDisabled ?? state.isDisabled();
244
- const canDecrement = state.canDecrement();
245
-
246
- return {
247
- id: decrementId,
248
- type: 'button' as const,
249
- tabIndex: -1, // Exclude from tab order
250
- 'aria-label': `Decrease ${getLabelText()}`,
251
- 'aria-controls': inputId,
252
- disabled: isDisabled || !canDecrement,
253
- onClick: onDecrementPress,
254
- onMouseDown: (e: MouseEvent) => e.preventDefault(), // Prevent focus
255
- } as JSX.ButtonHTMLAttributes<HTMLButtonElement>;
256
- },
257
- get descriptionProps() {
258
- return {
259
- id: descriptionId,
260
- } as JSX.HTMLAttributes<HTMLElement>;
261
- },
262
- get errorMessageProps() {
263
- return {
264
- id: errorMessageId,
265
- } as JSX.HTMLAttributes<HTMLElement>;
266
- },
267
- };
268
- }
1
+ /**
2
+ * Provides the behavior and accessibility implementation for a number field.
3
+ * Based on @react-aria/numberfield useNumberField.
4
+ */
5
+
6
+ import { type JSX, createMemo } from 'solid-js';
7
+ import { createLabel } from '../label/createLabel';
8
+ import { filterDOMProps } from '../utils/filterDOMProps';
9
+ import { mergeProps } from '../utils/mergeProps';
10
+ import { createId } from '../ssr';
11
+ import { access, type MaybeAccessor } from '../utils/reactivity';
12
+ import type { NumberFieldState } from '@proyecto-viviana/solid-stately';
13
+
14
+ export interface AriaNumberFieldProps {
15
+ /** A label for the number field. */
16
+ label?: JSX.Element;
17
+ /** An accessible label for the number field. */
18
+ 'aria-label'?: string;
19
+ /** The element ID that labels the number field. */
20
+ 'aria-labelledby'?: string;
21
+ /** The element ID that describes the number field. */
22
+ 'aria-describedby'?: string;
23
+ /** Whether the number field is disabled. */
24
+ isDisabled?: boolean;
25
+ /** Whether the number field is read-only. */
26
+ isReadOnly?: boolean;
27
+ /** Whether the number field is required. */
28
+ isRequired?: boolean;
29
+ /** Whether the number field is invalid. */
30
+ isInvalid?: boolean;
31
+ /** A description for the number field. */
32
+ description?: JSX.Element;
33
+ /** An error message for the number field. */
34
+ errorMessage?: JSX.Element;
35
+ /** The ID of the number field. */
36
+ id?: string;
37
+ /** Whether to auto-focus the input. */
38
+ autoFocus?: boolean;
39
+ /** The name for the form input. */
40
+ name?: string;
41
+ /** The form element this input belongs to. */
42
+ form?: string;
43
+ /** Handler for focus events. */
44
+ onFocus?: JSX.EventHandler<HTMLInputElement, FocusEvent>;
45
+ /** Handler for blur events. */
46
+ onBlur?: JSX.EventHandler<HTMLInputElement, FocusEvent>;
47
+ /** Handler called when focus state changes. */
48
+ onFocusChange?: (isFocused: boolean) => void;
49
+ /** Handler for key down events. */
50
+ onKeyDown?: JSX.EventHandler<HTMLInputElement, KeyboardEvent>;
51
+ /** Handler for key up events. */
52
+ onKeyUp?: JSX.EventHandler<HTMLInputElement, KeyboardEvent>;
53
+ /** Handler for paste events. */
54
+ onPaste?: JSX.EventHandler<HTMLInputElement, ClipboardEvent>;
55
+ /** Handler for copy events. */
56
+ onCopy?: JSX.EventHandler<HTMLInputElement, ClipboardEvent>;
57
+ /** Handler for cut events. */
58
+ onCut?: JSX.EventHandler<HTMLInputElement, ClipboardEvent>;
59
+ }
60
+
61
+ export interface NumberFieldAria {
62
+ /** Props for the label element. */
63
+ labelProps: JSX.HTMLAttributes<HTMLElement>;
64
+ /** Props for the input element group. */
65
+ groupProps: JSX.HTMLAttributes<HTMLElement>;
66
+ /** Props for the input element. */
67
+ inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
68
+ /** Props for the increment button. */
69
+ incrementButtonProps: JSX.ButtonHTMLAttributes<HTMLButtonElement>;
70
+ /** Props for the decrement button. */
71
+ decrementButtonProps: JSX.ButtonHTMLAttributes<HTMLButtonElement>;
72
+ /** Props for the description element. */
73
+ descriptionProps: JSX.HTMLAttributes<HTMLElement>;
74
+ /** Props for the error message element. */
75
+ errorMessageProps: JSX.HTMLAttributes<HTMLElement>;
76
+ }
77
+
78
+ /**
79
+ * Provides the behavior and accessibility implementation for a number field.
80
+ */
81
+ export function createNumberField(
82
+ props: MaybeAccessor<AriaNumberFieldProps>,
83
+ state: NumberFieldState,
84
+ inputRef?: () => HTMLInputElement | null
85
+ ): NumberFieldAria {
86
+ const getProps = () => access(props);
87
+ const id = createId(getProps().id);
88
+
89
+ // Generate IDs for associated elements
90
+ const inputId = `${id}-input`;
91
+ const incrementId = `${id}-increment`;
92
+ const decrementId = `${id}-decrement`;
93
+ const descriptionId = `${id}-description`;
94
+ const errorMessageId = `${id}-error`;
95
+
96
+ // Label handling
97
+ const { labelProps, fieldProps } = createLabel({
98
+ get id() {
99
+ return inputId;
100
+ },
101
+ get label() {
102
+ return getProps().label;
103
+ },
104
+ get 'aria-label'() {
105
+ return getProps()['aria-label'];
106
+ },
107
+ get 'aria-labelledby'() {
108
+ return getProps()['aria-labelledby'];
109
+ },
110
+ labelElementType: 'span',
111
+ });
112
+
113
+ // Get the label text for button aria-labels
114
+ const getLabelText = (): string => {
115
+ const p = getProps();
116
+ if (p['aria-label']) return p['aria-label'];
117
+ if (typeof p.label === 'string') return p.label;
118
+ return 'value';
119
+ };
120
+
121
+ // Filter DOM props
122
+ const domProps = createMemo(() =>
123
+ filterDOMProps(getProps() as unknown as Record<string, unknown>, { labelable: true })
124
+ );
125
+
126
+ // Handle input change
127
+ const onInputChange: JSX.EventHandler<HTMLInputElement, Event> = (e) => {
128
+ const value = e.currentTarget.value;
129
+ if (state.validate(value)) {
130
+ state.setInputValue(value);
131
+ }
132
+ };
133
+
134
+ // Handle input blur - commit value
135
+ const onInputBlur: JSX.EventHandler<HTMLInputElement, FocusEvent> = (e) => {
136
+ state.commit();
137
+ const p = getProps();
138
+ p.onBlur?.(e);
139
+ p.onFocusChange?.(false);
140
+ };
141
+
142
+ const onInputFocus: JSX.EventHandler<HTMLInputElement, FocusEvent> = (e) => {
143
+ const p = getProps();
144
+ p.onFocus?.(e);
145
+ p.onFocusChange?.(true);
146
+ };
147
+
148
+ // Handle keyboard events
149
+ const onKeyDown: JSX.EventHandler<HTMLInputElement, KeyboardEvent> = (e) => {
150
+ const p = getProps();
151
+ if (p.isDisabled || p.isReadOnly) {
152
+ p.onKeyDown?.(e);
153
+ return;
154
+ }
155
+
156
+ switch (e.key) {
157
+ case 'ArrowUp':
158
+ e.preventDefault();
159
+ state.increment();
160
+ break;
161
+ case 'ArrowDown':
162
+ e.preventDefault();
163
+ state.decrement();
164
+ break;
165
+ case 'PageUp':
166
+ e.preventDefault();
167
+ state.incrementToMax();
168
+ break;
169
+ case 'PageDown':
170
+ e.preventDefault();
171
+ state.decrementToMin();
172
+ break;
173
+ case 'Home':
174
+ e.preventDefault();
175
+ state.decrementToMin();
176
+ break;
177
+ case 'End':
178
+ e.preventDefault();
179
+ state.incrementToMax();
180
+ break;
181
+ case 'Enter':
182
+ state.commit();
183
+ break;
184
+ }
185
+
186
+ p.onKeyDown?.(e);
187
+ };
188
+
189
+ const onKeyUp: JSX.EventHandler<HTMLInputElement, KeyboardEvent> = (e) => {
190
+ getProps().onKeyUp?.(e);
191
+ };
192
+
193
+ // Handle increment button
194
+ const onIncrementPress = () => {
195
+ state.increment();
196
+ // Return focus to input
197
+ inputRef?.()?.focus();
198
+ };
199
+
200
+ // Handle decrement button
201
+ const onDecrementPress = () => {
202
+ state.decrement();
203
+ // Return focus to input
204
+ inputRef?.()?.focus();
205
+ };
206
+
207
+ // Build aria-describedby
208
+ const getAriaDescribedBy = () => {
209
+ const p = getProps();
210
+ const parts: string[] = [];
211
+ if (p['aria-describedby']) parts.push(p['aria-describedby']);
212
+ if (p.description) parts.push(descriptionId);
213
+ if (p.isInvalid && p.errorMessage) parts.push(errorMessageId);
214
+ return parts.length > 0 ? parts.join(' ') : undefined;
215
+ };
216
+
217
+ return {
218
+ get labelProps() {
219
+ return labelProps as JSX.HTMLAttributes<HTMLElement>;
220
+ },
221
+ get groupProps() {
222
+ return {
223
+ role: 'group',
224
+ 'aria-disabled': getProps().isDisabled || undefined,
225
+ 'aria-invalid': getProps().isInvalid || undefined,
226
+ } as JSX.HTMLAttributes<HTMLElement>;
227
+ },
228
+ get inputProps() {
229
+ const p = getProps();
230
+ const isDisabled = p.isDisabled ?? state.isDisabled();
231
+ const isReadOnly = p.isReadOnly ?? state.isReadOnly();
232
+
233
+ return mergeProps(
234
+ domProps(),
235
+ fieldProps as Record<string, unknown>,
236
+ {
237
+ id: inputId,
238
+ type: 'text',
239
+ inputMode: 'decimal' as const,
240
+ autoComplete: 'off',
241
+ autoCorrect: 'off',
242
+ spellcheck: false,
243
+ role: 'spinbutton',
244
+ 'aria-valuenow': isNaN(state.numberValue()) ? undefined : state.numberValue(),
245
+ 'aria-valuemin': state.minValue(),
246
+ 'aria-valuemax': state.maxValue(),
247
+ 'aria-valuetext': isNaN(state.numberValue()) ? undefined : state.inputValue(),
248
+ 'aria-invalid': p.isInvalid || undefined,
249
+ 'aria-required': p.isRequired || undefined,
250
+ 'aria-describedby': getAriaDescribedBy(),
251
+ disabled: isDisabled || undefined,
252
+ readOnly: isReadOnly || undefined,
253
+ value: state.inputValue(),
254
+ onChange: onInputChange,
255
+ onFocus: onInputFocus,
256
+ onBlur: onInputBlur,
257
+ onKeyDown,
258
+ onKeyUp,
259
+ onPaste: p.onPaste,
260
+ onCopy: p.onCopy,
261
+ onCut: p.onCut,
262
+ name: p.name,
263
+ form: p.form,
264
+ autoFocus: p.autoFocus,
265
+ } as Record<string, unknown>
266
+ ) as JSX.InputHTMLAttributes<HTMLInputElement>;
267
+ },
268
+ get incrementButtonProps() {
269
+ const p = getProps();
270
+ const isDisabled = p.isDisabled ?? state.isDisabled();
271
+ const canIncrement = state.canIncrement();
272
+
273
+ return {
274
+ id: incrementId,
275
+ type: 'button' as const,
276
+ tabIndex: -1, // Exclude from tab order
277
+ 'aria-label': `Increase ${getLabelText()}`,
278
+ 'aria-controls': inputId,
279
+ disabled: isDisabled || !canIncrement,
280
+ onClick: onIncrementPress,
281
+ onMouseDown: (e: MouseEvent) => e.preventDefault(), // Prevent focus
282
+ } as JSX.ButtonHTMLAttributes<HTMLButtonElement>;
283
+ },
284
+ get decrementButtonProps() {
285
+ const p = getProps();
286
+ const isDisabled = p.isDisabled ?? state.isDisabled();
287
+ const canDecrement = state.canDecrement();
288
+
289
+ return {
290
+ id: decrementId,
291
+ type: 'button' as const,
292
+ tabIndex: -1, // Exclude from tab order
293
+ 'aria-label': `Decrease ${getLabelText()}`,
294
+ 'aria-controls': inputId,
295
+ disabled: isDisabled || !canDecrement,
296
+ onClick: onDecrementPress,
297
+ onMouseDown: (e: MouseEvent) => e.preventDefault(), // Prevent focus
298
+ } as JSX.ButtonHTMLAttributes<HTMLButtonElement>;
299
+ },
300
+ get descriptionProps() {
301
+ return {
302
+ id: descriptionId,
303
+ } as JSX.HTMLAttributes<HTMLElement>;
304
+ },
305
+ get errorMessageProps() {
306
+ return {
307
+ id: errorMessageId,
308
+ } as JSX.HTMLAttributes<HTMLElement>;
309
+ },
310
+ };
311
+ }
@@ -1,5 +1,5 @@
1
- export {
2
- createNumberField,
3
- type AriaNumberFieldProps,
4
- type NumberFieldAria,
5
- } from './createNumberField';
1
+ export {
2
+ createNumberField,
3
+ type AriaNumberFieldProps,
4
+ type NumberFieldAria,
5
+ } from './createNumberField';