@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,349 +1,364 @@
1
- /**
2
- * Provides the behavior and accessibility implementation for a slider component.
3
- * A slider allows users to select a value from a range.
4
- * Based on @react-aria/slider useSlider.
5
- */
6
-
7
- import { type JSX, onCleanup, onMount } from 'solid-js';
8
- import { createLabel } from '../label/createLabel';
9
- import { createFocusRing } from '../interactions/createFocusRing';
10
- import { filterDOMProps } from '../utils/filterDOMProps';
11
- import { mergeProps } from '../utils/mergeProps';
12
- import { createId } from '../ssr';
13
- import { access, type MaybeAccessor } from '../utils/reactivity';
14
- import type { SliderState, SliderOrientation } from '@proyecto-viviana/solid-stately';
15
-
16
- export interface AriaSliderProps {
17
- /** An ID for the slider. */
18
- id?: string;
19
- /** Whether the slider is disabled. */
20
- isDisabled?: boolean;
21
- /** The label for the slider. */
22
- label?: JSX.Element;
23
- /** An accessible label for the slider when no visible label is provided. */
24
- 'aria-label'?: string;
25
- /** The ID of an element that labels the slider. */
26
- 'aria-labelledby'?: string;
27
- /** The ID of an element that describes the slider. */
28
- 'aria-describedby'?: string;
29
- /** The orientation of the slider. */
30
- orientation?: SliderOrientation;
31
- }
32
-
33
- export interface SliderAria {
34
- /** Props for the label element. */
35
- labelProps: JSX.HTMLAttributes<HTMLElement>;
36
- /** Props for the root group element. */
37
- groupProps: JSX.HTMLAttributes<HTMLElement>;
38
- /** Props for the track element. */
39
- trackProps: JSX.HTMLAttributes<HTMLElement>;
40
- /** Props for the thumb element. */
41
- thumbProps: JSX.HTMLAttributes<HTMLElement>;
42
- /** Props for the hidden input element. */
43
- inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
44
- /** Props for the output element (showing current value). */
45
- outputProps: JSX.HTMLAttributes<HTMLElement>;
46
- }
47
-
48
- /**
49
- * Provides the behavior and accessibility implementation for a slider.
50
- */
51
- export function createSlider(
52
- props: MaybeAccessor<AriaSliderProps>,
53
- state: SliderState,
54
- trackRef?: () => HTMLElement | null
55
- ): SliderAria {
56
- const getProps = () => access(props);
57
- const id = createId(getProps().id);
58
-
59
- // Generate IDs for associated elements
60
- const inputId = `${id}-input`;
61
- const outputId = `${id}-output`;
62
-
63
- // Filter DOM props
64
- const domProps = () =>
65
- filterDOMProps(getProps() as unknown as Record<string, unknown>, { labelable: true });
66
-
67
- // Label handling
68
- const { labelProps, fieldProps } = createLabel({
69
- get id() {
70
- return inputId;
71
- },
72
- get label() {
73
- return getProps().label;
74
- },
75
- get 'aria-label'() {
76
- return getProps()['aria-label'];
77
- },
78
- get 'aria-labelledby'() {
79
- return getProps()['aria-labelledby'];
80
- },
81
- labelElementType: 'span',
82
- });
83
-
84
- // Focus ring for keyboard focus styling
85
- const { isFocusVisible, focusProps } = createFocusRing({
86
- within: true,
87
- });
88
-
89
- // Track pointer state for drag handling
90
- let currentPointerId: number | null = null;
91
-
92
- // Calculate position from pointer event
93
- const getPositionFromPointer = (clientX: number, clientY: number): number => {
94
- const track = trackRef?.();
95
- if (!track) return 0;
96
-
97
- const rect = track.getBoundingClientRect();
98
- const isVertical = state.orientation === 'vertical';
99
-
100
- let position: number;
101
- if (isVertical) {
102
- position = (rect.bottom - clientY) / rect.height;
103
- } else {
104
- position = (clientX - rect.left) / rect.width;
105
- }
106
-
107
- return Math.max(0, Math.min(1, position));
108
- };
109
-
110
- // Handle pointer down on track
111
- const onTrackPointerDown = (e: PointerEvent) => {
112
- if (state.isDisabled || e.button !== 0) return;
113
-
114
- e.preventDefault();
115
- currentPointerId = e.pointerId;
116
-
117
- const track = trackRef?.();
118
- if (track) {
119
- track.setPointerCapture(e.pointerId);
120
- }
121
-
122
- const percent = getPositionFromPointer(e.clientX, e.clientY);
123
- state.setValuePercent(percent);
124
- state.setDragging(true);
125
- };
126
-
127
- // Handle pointer move for dragging
128
- const onTrackPointerMove = (e: PointerEvent) => {
129
- if (!state.isDragging() || e.pointerId !== currentPointerId) return;
130
-
131
- const percent = getPositionFromPointer(e.clientX, e.clientY);
132
- state.setValuePercent(percent);
133
- };
134
-
135
- // Handle pointer up to end drag
136
- const onTrackPointerUp = (e: PointerEvent) => {
137
- if (e.pointerId !== currentPointerId) return;
138
-
139
- const track = trackRef?.();
140
- if (track) {
141
- track.releasePointerCapture(e.pointerId);
142
- }
143
-
144
- currentPointerId = null;
145
- state.setDragging(false);
146
- };
147
-
148
- // Keyboard navigation for thumb
149
- const onThumbKeyDown = (e: KeyboardEvent) => {
150
- if (state.isDisabled) return;
151
-
152
- const isVertical = state.orientation === 'vertical';
153
-
154
- switch (e.key) {
155
- case 'ArrowRight':
156
- case 'ArrowUp':
157
- e.preventDefault();
158
- if ((e.key === 'ArrowRight' && !isVertical) || (e.key === 'ArrowUp' && isVertical)) {
159
- state.increment();
160
- } else {
161
- state.decrement();
162
- }
163
- break;
164
- case 'ArrowLeft':
165
- case 'ArrowDown':
166
- e.preventDefault();
167
- if ((e.key === 'ArrowLeft' && !isVertical) || (e.key === 'ArrowDown' && isVertical)) {
168
- state.decrement();
169
- } else {
170
- state.increment();
171
- }
172
- break;
173
- case 'PageUp':
174
- e.preventDefault();
175
- state.increment(state.pageStep / state.step);
176
- break;
177
- case 'PageDown':
178
- e.preventDefault();
179
- state.decrement(state.pageStep / state.step);
180
- break;
181
- case 'Home':
182
- e.preventDefault();
183
- state.setValue(state.minValue);
184
- break;
185
- case 'End':
186
- e.preventDefault();
187
- state.setValue(state.maxValue);
188
- break;
189
- }
190
- };
191
-
192
- // Handle focus events
193
- const onFocus = () => {
194
- state.setFocused(true);
195
- };
196
-
197
- const onBlur = () => {
198
- state.setFocused(false);
199
- };
200
-
201
- // Handle pointer down on thumb (for direct thumb dragging)
202
- const onThumbPointerDown = (e: PointerEvent) => {
203
- if (state.isDisabled || e.button !== 0) return;
204
-
205
- e.preventDefault();
206
- e.stopPropagation(); // Prevent track from also handling
207
- currentPointerId = e.pointerId;
208
-
209
- // Capture pointer on document for smooth dragging
210
- document.body.setPointerCapture(e.pointerId);
211
- state.setDragging(true);
212
- };
213
-
214
- // Global pointer move handler for thumb dragging
215
- const onDocumentPointerMove = (e: PointerEvent) => {
216
- if (!state.isDragging() || e.pointerId !== currentPointerId) return;
217
-
218
- const percent = getPositionFromPointer(e.clientX, e.clientY);
219
- state.setValuePercent(percent);
220
- };
221
-
222
- // Global pointer up handler
223
- const onDocumentPointerUp = (e: PointerEvent) => {
224
- if (e.pointerId !== currentPointerId) return;
225
-
226
- try {
227
- document.body.releasePointerCapture(e.pointerId);
228
- } catch {
229
- // Ignore if capture was already released
230
- }
231
-
232
- currentPointerId = null;
233
- state.setDragging(false);
234
- };
235
-
236
- // Set up global listeners on mount (client-side only)
237
- onMount(() => {
238
- if (typeof document === 'undefined') return;
239
-
240
- document.addEventListener('pointermove', onDocumentPointerMove);
241
- document.addEventListener('pointerup', onDocumentPointerUp);
242
- document.addEventListener('pointercancel', onDocumentPointerUp);
243
-
244
- // Cleanup when component unmounts
245
- onCleanup(() => {
246
- document.removeEventListener('pointermove', onDocumentPointerMove);
247
- document.removeEventListener('pointerup', onDocumentPointerUp);
248
- document.removeEventListener('pointercancel', onDocumentPointerUp);
249
- });
250
- });
251
-
252
- return {
253
- get labelProps() {
254
- return labelProps as JSX.HTMLAttributes<HTMLElement>;
255
- },
256
- get groupProps() {
257
- return mergeProps(
258
- domProps(),
259
- fieldProps as Record<string, unknown>,
260
- {
261
- role: 'group',
262
- 'data-disabled': state.isDisabled || undefined,
263
- 'data-orientation': state.orientation,
264
- } as Record<string, unknown>
265
- ) as JSX.HTMLAttributes<HTMLElement>;
266
- },
267
- get trackProps() {
268
- return {
269
- onPointerDown: onTrackPointerDown,
270
- onPointerMove: onTrackPointerMove,
271
- onPointerUp: onTrackPointerUp,
272
- onPointerCancel: onTrackPointerUp,
273
- style: {
274
- position: 'relative',
275
- 'touch-action': 'none',
276
- },
277
- 'data-disabled': state.isDisabled || undefined,
278
- 'data-orientation': state.orientation,
279
- 'data-dragging': state.isDragging() || undefined,
280
- } as JSX.HTMLAttributes<HTMLElement>;
281
- },
282
- get thumbProps() {
283
- const percent = state.getValuePercent();
284
- const isVertical = state.orientation === 'vertical';
285
-
286
- return mergeProps(
287
- focusProps as Record<string, unknown>,
288
- {
289
- role: 'slider',
290
- tabIndex: state.isDisabled ? undefined : 0,
291
- 'aria-valuemin': state.minValue,
292
- 'aria-valuemax': state.maxValue,
293
- 'aria-valuenow': state.value(),
294
- 'aria-valuetext': state.getFormattedValue(),
295
- 'aria-orientation': state.orientation,
296
- 'aria-disabled': state.isDisabled || undefined,
297
- 'aria-labelledby': (labelProps as { id?: string }).id,
298
- onPointerDown: onThumbPointerDown,
299
- onKeyDown: onThumbKeyDown,
300
- onFocus,
301
- onBlur,
302
- style: {
303
- position: 'absolute',
304
- [isVertical ? 'bottom' : 'left']: `${percent * 100}%`,
305
- transform: isVertical ? 'translateY(50%)' : 'translateX(-50%)',
306
- },
307
- 'data-disabled': state.isDisabled || undefined,
308
- 'data-dragging': state.isDragging() || undefined,
309
- 'data-focus-visible': isFocusVisible() || undefined,
310
- } as Record<string, unknown>
311
- ) as JSX.HTMLAttributes<HTMLElement>;
312
- },
313
- get inputProps() {
314
- return {
315
- type: 'range',
316
- id: inputId,
317
- min: state.minValue,
318
- max: state.maxValue,
319
- step: state.step,
320
- value: state.value(),
321
- disabled: state.isDisabled,
322
- 'aria-hidden': true,
323
- tabIndex: -1,
324
- style: {
325
- position: 'absolute',
326
- width: '1px',
327
- height: '1px',
328
- padding: '0',
329
- margin: '-1px',
330
- overflow: 'hidden',
331
- clip: 'rect(0, 0, 0, 0)',
332
- 'white-space': 'nowrap',
333
- border: '0',
334
- },
335
- onChange: (e: Event) => {
336
- const target = e.target as HTMLInputElement;
337
- state.setValue(parseFloat(target.value));
338
- },
339
- } as JSX.InputHTMLAttributes<HTMLInputElement>;
340
- },
341
- get outputProps() {
342
- return {
343
- id: outputId,
344
- 'for': inputId,
345
- 'aria-live': 'off',
346
- } as JSX.HTMLAttributes<HTMLElement>;
347
- },
348
- };
349
- }
1
+ /**
2
+ * Provides the behavior and accessibility implementation for a slider component.
3
+ * A slider allows users to select a value from a range.
4
+ * Based on @react-aria/slider useSlider.
5
+ */
6
+
7
+ import { type JSX, onCleanup, onMount } from 'solid-js';
8
+ import { createLabel } from '../label/createLabel';
9
+ import { createFocusRing } from '../interactions/createFocusRing';
10
+ import { filterDOMProps } from '../utils/filterDOMProps';
11
+ import { mergeProps } from '../utils/mergeProps';
12
+ import { createId } from '../ssr';
13
+ import { access, type MaybeAccessor } from '../utils/reactivity';
14
+ import type { SliderState, SliderOrientation } from '@proyecto-viviana/solid-stately';
15
+ import { useLocale } from '../i18n';
16
+
17
+ export interface AriaSliderProps {
18
+ /** An ID for the slider. */
19
+ id?: string;
20
+ /** Whether the slider is disabled. */
21
+ isDisabled?: boolean;
22
+ /** The label for the slider. */
23
+ label?: JSX.Element;
24
+ /** An accessible label for the slider when no visible label is provided. */
25
+ 'aria-label'?: string;
26
+ /** The ID of an element that labels the slider. */
27
+ 'aria-labelledby'?: string;
28
+ /** The ID of an element that describes the slider. */
29
+ 'aria-describedby'?: string;
30
+ /** The orientation of the slider. */
31
+ orientation?: SliderOrientation;
32
+ }
33
+
34
+ export interface SliderAria {
35
+ /** Props for the label element. */
36
+ labelProps: JSX.HTMLAttributes<HTMLElement>;
37
+ /** Props for the root group element. */
38
+ groupProps: JSX.HTMLAttributes<HTMLElement>;
39
+ /** Props for the track element. */
40
+ trackProps: JSX.HTMLAttributes<HTMLElement>;
41
+ /** Props for the thumb element. */
42
+ thumbProps: JSX.HTMLAttributes<HTMLElement>;
43
+ /** Props for the hidden input element. */
44
+ inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
45
+ /** Props for the output element (showing current value). */
46
+ outputProps: JSX.HTMLAttributes<HTMLElement>;
47
+ }
48
+
49
+ /**
50
+ * Provides the behavior and accessibility implementation for a slider.
51
+ */
52
+ export function createSlider(
53
+ props: MaybeAccessor<AriaSliderProps>,
54
+ state: SliderState,
55
+ trackRef?: () => HTMLElement | null
56
+ ): SliderAria {
57
+ const getProps = () => access(props);
58
+ const id = createId(getProps().id);
59
+ const locale = useLocale();
60
+
61
+ // Generate IDs for associated elements
62
+ const inputId = `${id}-input`;
63
+ const outputId = `${id}-output`;
64
+
65
+ // Filter DOM props
66
+ const domProps = () =>
67
+ filterDOMProps(getProps() as unknown as Record<string, unknown>, { labelable: true });
68
+
69
+ // Label handling
70
+ const { labelProps, fieldProps } = createLabel({
71
+ get id() {
72
+ return inputId;
73
+ },
74
+ get label() {
75
+ return getProps().label;
76
+ },
77
+ get 'aria-label'() {
78
+ return getProps()['aria-label'];
79
+ },
80
+ get 'aria-labelledby'() {
81
+ return getProps()['aria-labelledby'];
82
+ },
83
+ labelElementType: 'span',
84
+ });
85
+
86
+ // Focus ring for keyboard focus styling
87
+ const { isFocusVisible, focusProps } = createFocusRing({
88
+ within: true,
89
+ });
90
+
91
+ // Track pointer state for drag handling
92
+ let currentPointerId: number | null = null;
93
+
94
+ // Calculate position from pointer event
95
+ const getPositionFromPointer = (clientX: number, clientY: number): number => {
96
+ const track = trackRef?.();
97
+ if (!track) return 0;
98
+
99
+ const rect = track.getBoundingClientRect();
100
+ const isVertical = state.orientation === 'vertical';
101
+
102
+ let position: number;
103
+ if (isVertical) {
104
+ position = (rect.bottom - clientY) / rect.height;
105
+ } else {
106
+ position = (clientX - rect.left) / rect.width;
107
+ }
108
+
109
+ return Math.max(0, Math.min(1, position));
110
+ };
111
+
112
+ // Handle pointer down on track
113
+ const onTrackPointerDown = (e: PointerEvent) => {
114
+ if (state.isDisabled || e.button !== 0) return;
115
+
116
+ e.preventDefault();
117
+ currentPointerId = e.pointerId;
118
+
119
+ const track = trackRef?.();
120
+ if (track) {
121
+ track.setPointerCapture(e.pointerId);
122
+ }
123
+
124
+ const percent = getPositionFromPointer(e.clientX, e.clientY);
125
+ state.setValuePercent(percent);
126
+ state.setDragging(true);
127
+ };
128
+
129
+ // Handle pointer move for dragging
130
+ const onTrackPointerMove = (e: PointerEvent) => {
131
+ if (!state.isDragging() || e.pointerId !== currentPointerId) return;
132
+
133
+ const percent = getPositionFromPointer(e.clientX, e.clientY);
134
+ state.setValuePercent(percent);
135
+ };
136
+
137
+ // Handle pointer up to end drag
138
+ const onTrackPointerUp = (e: PointerEvent) => {
139
+ if (e.pointerId !== currentPointerId) return;
140
+
141
+ const track = trackRef?.();
142
+ if (track) {
143
+ track.releasePointerCapture(e.pointerId);
144
+ }
145
+
146
+ currentPointerId = null;
147
+ state.setDragging(false);
148
+ };
149
+
150
+ // Keyboard navigation for thumb
151
+ const onThumbKeyDown = (e: KeyboardEvent) => {
152
+ if (state.isDisabled) return;
153
+
154
+ const isVertical = state.orientation === 'vertical';
155
+ const isRTL = locale().direction === 'rtl';
156
+ const shouldIncrementOnArrowRight = !isVertical && !isRTL;
157
+ const shouldIncrementOnArrowLeft = !isVertical && isRTL;
158
+
159
+ switch (e.key) {
160
+ case 'ArrowRight':
161
+ case 'ArrowUp':
162
+ e.preventDefault();
163
+ if (
164
+ (e.key === 'ArrowRight' && shouldIncrementOnArrowRight) ||
165
+ (e.key === 'ArrowUp' && isVertical)
166
+ ) {
167
+ state.increment();
168
+ } else {
169
+ state.decrement();
170
+ }
171
+ break;
172
+ case 'ArrowLeft':
173
+ case 'ArrowDown':
174
+ e.preventDefault();
175
+ if (
176
+ (e.key === 'ArrowLeft' && shouldIncrementOnArrowLeft) ||
177
+ (e.key === 'ArrowDown' && isVertical)
178
+ ) {
179
+ state.increment();
180
+ } else {
181
+ state.decrement();
182
+ }
183
+ break;
184
+ case 'PageUp':
185
+ e.preventDefault();
186
+ state.increment(state.pageStep / state.step);
187
+ break;
188
+ case 'PageDown':
189
+ e.preventDefault();
190
+ state.decrement(state.pageStep / state.step);
191
+ break;
192
+ case 'Home':
193
+ e.preventDefault();
194
+ state.setValue(state.minValue);
195
+ break;
196
+ case 'End':
197
+ e.preventDefault();
198
+ state.setValue(state.maxValue);
199
+ break;
200
+ }
201
+ };
202
+
203
+ // Handle focus events
204
+ const onFocus = () => {
205
+ state.setFocused(true);
206
+ };
207
+
208
+ const onBlur = () => {
209
+ state.setFocused(false);
210
+ };
211
+
212
+ // Handle pointer down on thumb (for direct thumb dragging)
213
+ const onThumbPointerDown = (e: PointerEvent) => {
214
+ if (state.isDisabled || e.button !== 0) return;
215
+
216
+ e.preventDefault();
217
+ e.stopPropagation(); // Prevent track from also handling
218
+ currentPointerId = e.pointerId;
219
+
220
+ // Capture pointer on document for smooth dragging
221
+ document.body.setPointerCapture(e.pointerId);
222
+ state.setDragging(true);
223
+ };
224
+
225
+ // Global pointer move handler for thumb dragging
226
+ const onDocumentPointerMove = (e: PointerEvent) => {
227
+ if (!state.isDragging() || e.pointerId !== currentPointerId) return;
228
+
229
+ const percent = getPositionFromPointer(e.clientX, e.clientY);
230
+ state.setValuePercent(percent);
231
+ };
232
+
233
+ // Global pointer up handler
234
+ const onDocumentPointerUp = (e: PointerEvent) => {
235
+ if (e.pointerId !== currentPointerId) return;
236
+
237
+ try {
238
+ document.body.releasePointerCapture(e.pointerId);
239
+ } catch {
240
+ // Ignore if capture was already released
241
+ }
242
+
243
+ currentPointerId = null;
244
+ state.setDragging(false);
245
+ };
246
+
247
+ // Set up global listeners on mount (client-side only)
248
+ onMount(() => {
249
+ if (typeof document === 'undefined') return;
250
+
251
+ document.addEventListener('pointermove', onDocumentPointerMove);
252
+ document.addEventListener('pointerup', onDocumentPointerUp);
253
+ document.addEventListener('pointercancel', onDocumentPointerUp);
254
+
255
+ // Cleanup when component unmounts
256
+ onCleanup(() => {
257
+ document.removeEventListener('pointermove', onDocumentPointerMove);
258
+ document.removeEventListener('pointerup', onDocumentPointerUp);
259
+ document.removeEventListener('pointercancel', onDocumentPointerUp);
260
+ });
261
+ });
262
+
263
+ const labelledBy = () => (fieldProps as { 'aria-labelledby'?: string })['aria-labelledby'];
264
+ const ariaLabel = () => (fieldProps as { 'aria-label'?: string })['aria-label'];
265
+
266
+ return {
267
+ get labelProps() {
268
+ return labelProps as JSX.HTMLAttributes<HTMLElement>;
269
+ },
270
+ get groupProps() {
271
+ return mergeProps(
272
+ domProps(),
273
+ fieldProps as Record<string, unknown>,
274
+ {
275
+ role: 'group',
276
+ 'data-disabled': state.isDisabled || undefined,
277
+ 'data-orientation': state.orientation,
278
+ } as Record<string, unknown>
279
+ ) as JSX.HTMLAttributes<HTMLElement>;
280
+ },
281
+ get trackProps() {
282
+ return {
283
+ onPointerDown: onTrackPointerDown,
284
+ onPointerMove: onTrackPointerMove,
285
+ onPointerUp: onTrackPointerUp,
286
+ onPointerCancel: onTrackPointerUp,
287
+ style: {
288
+ position: 'relative',
289
+ 'touch-action': 'none',
290
+ },
291
+ 'data-disabled': state.isDisabled || undefined,
292
+ 'data-orientation': state.orientation,
293
+ 'data-dragging': state.isDragging() || undefined,
294
+ } as JSX.HTMLAttributes<HTMLElement>;
295
+ },
296
+ get thumbProps() {
297
+ const percent = state.getValuePercent();
298
+ const isVertical = state.orientation === 'vertical';
299
+
300
+ return mergeProps(
301
+ focusProps as Record<string, unknown>,
302
+ {
303
+ role: 'slider',
304
+ tabIndex: state.isDisabled ? undefined : 0,
305
+ 'aria-valuemin': state.minValue,
306
+ 'aria-valuemax': state.maxValue,
307
+ 'aria-valuenow': state.value(),
308
+ 'aria-valuetext': state.getFormattedValue(),
309
+ 'aria-orientation': state.orientation,
310
+ 'aria-disabled': state.isDisabled || undefined,
311
+ 'aria-labelledby': labelledBy(),
312
+ 'aria-label': labelledBy() ? undefined : ariaLabel(),
313
+ onPointerDown: onThumbPointerDown,
314
+ onKeyDown: onThumbKeyDown,
315
+ onFocus,
316
+ onBlur,
317
+ style: {
318
+ position: 'absolute',
319
+ [isVertical ? 'bottom' : 'left']: `${percent * 100}%`,
320
+ transform: isVertical ? 'translateY(50%)' : 'translateX(-50%)',
321
+ },
322
+ 'data-disabled': state.isDisabled || undefined,
323
+ 'data-dragging': state.isDragging() || undefined,
324
+ 'data-focus-visible': isFocusVisible() || undefined,
325
+ } as Record<string, unknown>
326
+ ) as JSX.HTMLAttributes<HTMLElement>;
327
+ },
328
+ get inputProps() {
329
+ return {
330
+ type: 'range',
331
+ id: inputId,
332
+ min: state.minValue,
333
+ max: state.maxValue,
334
+ step: state.step,
335
+ value: state.value(),
336
+ disabled: state.isDisabled,
337
+ 'aria-hidden': true,
338
+ tabIndex: -1,
339
+ style: {
340
+ position: 'absolute',
341
+ width: '1px',
342
+ height: '1px',
343
+ padding: '0',
344
+ margin: '-1px',
345
+ overflow: 'hidden',
346
+ clip: 'rect(0, 0, 0, 0)',
347
+ 'white-space': 'nowrap',
348
+ border: '0',
349
+ },
350
+ onChange: (e: Event) => {
351
+ const target = e.target as HTMLInputElement;
352
+ state.setValue(parseFloat(target.value));
353
+ },
354
+ } as JSX.InputHTMLAttributes<HTMLInputElement>;
355
+ },
356
+ get outputProps() {
357
+ return {
358
+ id: outputId,
359
+ 'for': inputId,
360
+ 'aria-live': 'off',
361
+ } as JSX.HTMLAttributes<HTMLElement>;
362
+ },
363
+ };
364
+ }