@proyecto-viviana/solidaria 0.2.2 → 0.2.3

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 (210) hide show
  1. package/dist/autocomplete/createAutocomplete.d.ts +2 -2
  2. package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
  3. package/dist/index.js +233 -234
  4. package/dist/index.js.map +2 -2
  5. package/dist/index.ssr.js +233 -234
  6. package/dist/index.ssr.js.map +2 -2
  7. package/dist/interactions/PressEvent.d.ts +13 -10
  8. package/dist/interactions/PressEvent.d.ts.map +1 -1
  9. package/dist/interactions/createPress.d.ts.map +1 -1
  10. package/dist/interactions/index.d.ts +1 -1
  11. package/dist/interactions/index.d.ts.map +1 -1
  12. package/dist/select/createHiddenSelect.d.ts.map +1 -1
  13. package/dist/toolbar/createToolbar.d.ts.map +1 -1
  14. package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
  15. package/package.json +9 -7
  16. package/src/autocomplete/createAutocomplete.ts +341 -0
  17. package/src/autocomplete/index.ts +9 -0
  18. package/src/breadcrumbs/createBreadcrumbs.ts +196 -0
  19. package/src/breadcrumbs/index.ts +8 -0
  20. package/src/button/createButton.ts +142 -0
  21. package/src/button/createToggleButton.ts +101 -0
  22. package/src/button/index.ts +4 -0
  23. package/src/button/types.ts +78 -0
  24. package/src/calendar/createCalendar.ts +138 -0
  25. package/src/calendar/createCalendarCell.ts +187 -0
  26. package/src/calendar/createCalendarGrid.ts +140 -0
  27. package/src/calendar/createRangeCalendar.ts +136 -0
  28. package/src/calendar/createRangeCalendarCell.ts +186 -0
  29. package/src/calendar/index.ts +34 -0
  30. package/src/checkbox/createCheckbox.ts +135 -0
  31. package/src/checkbox/createCheckboxGroup.ts +137 -0
  32. package/src/checkbox/createCheckboxGroupItem.ts +117 -0
  33. package/src/checkbox/createCheckboxGroupState.ts +193 -0
  34. package/src/checkbox/index.ts +13 -0
  35. package/src/color/createColorArea.ts +314 -0
  36. package/src/color/createColorField.ts +137 -0
  37. package/src/color/createColorSlider.ts +197 -0
  38. package/src/color/createColorSwatch.ts +40 -0
  39. package/src/color/createColorWheel.ts +208 -0
  40. package/src/color/index.ts +24 -0
  41. package/src/color/types.ts +116 -0
  42. package/src/combobox/createComboBox.ts +647 -0
  43. package/src/combobox/index.ts +6 -0
  44. package/src/combobox/intl/en-US.json +7 -0
  45. package/src/combobox/intl/es-ES.json +7 -0
  46. package/src/combobox/intl/index.ts +23 -0
  47. package/src/datepicker/createDateField.ts +154 -0
  48. package/src/datepicker/createDatePicker.ts +206 -0
  49. package/src/datepicker/createDateSegment.ts +229 -0
  50. package/src/datepicker/createTimeField.ts +154 -0
  51. package/src/datepicker/index.ts +28 -0
  52. package/src/dialog/createDialog.ts +120 -0
  53. package/src/dialog/index.ts +2 -0
  54. package/src/dialog/types.ts +19 -0
  55. package/src/disclosure/createDisclosure.ts +131 -0
  56. package/src/disclosure/createDisclosureGroup.ts +62 -0
  57. package/src/disclosure/index.ts +11 -0
  58. package/src/dnd/createDrag.ts +209 -0
  59. package/src/dnd/createDraggableCollection.ts +63 -0
  60. package/src/dnd/createDraggableItem.ts +243 -0
  61. package/src/dnd/createDrop.ts +321 -0
  62. package/src/dnd/createDroppableCollection.ts +293 -0
  63. package/src/dnd/createDroppableItem.ts +213 -0
  64. package/src/dnd/index.ts +47 -0
  65. package/src/dnd/types.ts +89 -0
  66. package/src/dnd/utils.ts +294 -0
  67. package/src/focus/FocusScope.tsx +408 -0
  68. package/src/focus/createAutoFocus.ts +321 -0
  69. package/src/focus/createFocusRestore.ts +313 -0
  70. package/src/focus/createVirtualFocus.ts +396 -0
  71. package/src/focus/index.ts +35 -0
  72. package/src/form/createFormReset.ts +51 -0
  73. package/src/form/createFormValidation.ts +224 -0
  74. package/src/form/index.ts +11 -0
  75. package/src/grid/GridKeyboardDelegate.ts +429 -0
  76. package/src/grid/createGrid.ts +261 -0
  77. package/src/grid/createGridCell.ts +182 -0
  78. package/src/grid/createGridRow.ts +153 -0
  79. package/src/grid/index.ts +18 -0
  80. package/src/grid/types.ts +133 -0
  81. package/src/gridlist/createGridList.ts +185 -0
  82. package/src/gridlist/createGridListItem.ts +180 -0
  83. package/src/gridlist/createGridListSelectionCheckbox.ts +59 -0
  84. package/src/gridlist/index.ts +16 -0
  85. package/src/gridlist/types.ts +81 -0
  86. package/src/i18n/NumberFormatter.ts +266 -0
  87. package/src/i18n/createCollator.ts +79 -0
  88. package/src/i18n/createDateFormatter.ts +83 -0
  89. package/src/i18n/createFilter.ts +131 -0
  90. package/src/i18n/createNumberFormatter.ts +52 -0
  91. package/src/i18n/createStringFormatter.ts +87 -0
  92. package/src/i18n/index.ts +40 -0
  93. package/src/i18n/locale.tsx +188 -0
  94. package/src/i18n/utils.ts +99 -0
  95. package/src/index.ts +670 -0
  96. package/src/interactions/FocusableProvider.tsx +44 -0
  97. package/src/interactions/PressEvent.ts +126 -0
  98. package/src/interactions/createFocus.ts +163 -0
  99. package/src/interactions/createFocusRing.ts +89 -0
  100. package/src/interactions/createFocusWithin.ts +206 -0
  101. package/src/interactions/createFocusable.ts +168 -0
  102. package/src/interactions/createHover.ts +254 -0
  103. package/src/interactions/createInteractionModality.ts +424 -0
  104. package/src/interactions/createKeyboard.ts +82 -0
  105. package/src/interactions/createLongPress.ts +174 -0
  106. package/src/interactions/createMove.ts +289 -0
  107. package/src/interactions/createPress.ts +834 -0
  108. package/src/interactions/index.ts +78 -0
  109. package/src/label/createField.ts +145 -0
  110. package/src/label/createLabel.ts +117 -0
  111. package/src/label/createLabels.ts +50 -0
  112. package/src/label/index.ts +19 -0
  113. package/src/landmark/createLandmark.ts +377 -0
  114. package/src/landmark/index.ts +8 -0
  115. package/src/link/createLink.ts +182 -0
  116. package/src/link/index.ts +1 -0
  117. package/src/listbox/createListBox.ts +269 -0
  118. package/src/listbox/createOption.ts +151 -0
  119. package/src/listbox/index.ts +12 -0
  120. package/src/live-announcer/announce.ts +322 -0
  121. package/src/live-announcer/index.ts +9 -0
  122. package/src/menu/createMenu.ts +396 -0
  123. package/src/menu/createMenuItem.ts +149 -0
  124. package/src/menu/createMenuTrigger.ts +88 -0
  125. package/src/menu/index.ts +18 -0
  126. package/src/meter/createMeter.ts +75 -0
  127. package/src/meter/index.ts +1 -0
  128. package/src/numberfield/createNumberField.ts +268 -0
  129. package/src/numberfield/index.ts +5 -0
  130. package/src/overlays/ariaHideOutside.ts +219 -0
  131. package/src/overlays/createInteractOutside.ts +149 -0
  132. package/src/overlays/createModal.tsx +202 -0
  133. package/src/overlays/createOverlay.ts +155 -0
  134. package/src/overlays/createOverlayTrigger.ts +85 -0
  135. package/src/overlays/createPreventScroll.ts +266 -0
  136. package/src/overlays/index.ts +44 -0
  137. package/src/popover/calculatePosition.ts +766 -0
  138. package/src/popover/createOverlayPosition.ts +356 -0
  139. package/src/popover/createPopover.ts +170 -0
  140. package/src/popover/index.ts +24 -0
  141. package/src/progress/createProgressBar.ts +128 -0
  142. package/src/progress/index.ts +5 -0
  143. package/src/radio/createRadio.ts +287 -0
  144. package/src/radio/createRadioGroup.ts +189 -0
  145. package/src/radio/createRadioGroupState.ts +201 -0
  146. package/src/radio/index.ts +23 -0
  147. package/src/searchfield/createSearchField.ts +186 -0
  148. package/src/searchfield/index.ts +2 -0
  149. package/src/select/createHiddenSelect.tsx +236 -0
  150. package/src/select/createSelect.ts +395 -0
  151. package/src/select/index.ts +14 -0
  152. package/src/selection/createTypeSelect.ts +201 -0
  153. package/src/selection/index.ts +6 -0
  154. package/src/separator/createSeparator.ts +82 -0
  155. package/src/separator/index.ts +6 -0
  156. package/src/slider/createSlider.ts +349 -0
  157. package/src/slider/index.ts +2 -0
  158. package/src/ssr/index.tsx +370 -0
  159. package/src/switch/createSwitch.ts +70 -0
  160. package/src/switch/index.ts +1 -0
  161. package/src/table/createTable.ts +526 -0
  162. package/src/table/createTableCell.ts +147 -0
  163. package/src/table/createTableColumnHeader.ts +115 -0
  164. package/src/table/createTableHeaderRow.ts +40 -0
  165. package/src/table/createTableRow.ts +155 -0
  166. package/src/table/createTableRowGroup.ts +32 -0
  167. package/src/table/createTableSelectAllCheckbox.ts +73 -0
  168. package/src/table/createTableSelectionCheckbox.ts +59 -0
  169. package/src/table/index.ts +30 -0
  170. package/src/table/types.ts +165 -0
  171. package/src/tabs/createTabs.ts +472 -0
  172. package/src/tabs/index.ts +14 -0
  173. package/src/tag/createTag.ts +194 -0
  174. package/src/tag/createTagGroup.ts +154 -0
  175. package/src/tag/index.ts +12 -0
  176. package/src/textfield/createTextField.ts +198 -0
  177. package/src/textfield/index.ts +5 -0
  178. package/src/toast/createToast.ts +118 -0
  179. package/src/toast/createToastRegion.ts +100 -0
  180. package/src/toast/index.ts +11 -0
  181. package/src/toggle/createToggle.ts +223 -0
  182. package/src/toggle/createToggleState.ts +94 -0
  183. package/src/toggle/index.ts +7 -0
  184. package/src/toolbar/createToolbar.ts +369 -0
  185. package/src/toolbar/index.ts +6 -0
  186. package/src/tooltip/createTooltip.ts +79 -0
  187. package/src/tooltip/createTooltipTrigger.ts +222 -0
  188. package/src/tooltip/index.ts +6 -0
  189. package/src/tree/createTree.ts +246 -0
  190. package/src/tree/createTreeItem.ts +233 -0
  191. package/src/tree/createTreeSelectionCheckbox.ts +68 -0
  192. package/src/tree/index.ts +16 -0
  193. package/src/tree/types.ts +87 -0
  194. package/src/utils/createDescription.ts +137 -0
  195. package/src/utils/dom.ts +327 -0
  196. package/src/utils/env.ts +54 -0
  197. package/src/utils/events.ts +106 -0
  198. package/src/utils/filterDOMProps.ts +116 -0
  199. package/src/utils/focus.ts +151 -0
  200. package/src/utils/geometry.ts +115 -0
  201. package/src/utils/globalListeners.ts +142 -0
  202. package/src/utils/index.ts +80 -0
  203. package/src/utils/mergeProps.ts +52 -0
  204. package/src/utils/platform.ts +52 -0
  205. package/src/utils/reactivity.ts +36 -0
  206. package/src/utils/textSelection.ts +114 -0
  207. package/src/visually-hidden/createVisuallyHidden.ts +124 -0
  208. package/src/visually-hidden/index.ts +6 -0
  209. package/dist/index.jsx +0 -15845
  210. package/dist/index.jsx.map +0 -7
@@ -0,0 +1,369 @@
1
+ /**
2
+ * createToolbar - Accessibility hook for toolbar elements
3
+ *
4
+ * Provides keyboard navigation between toolbar items using arrow keys.
5
+ * Based on @react-aria/toolbar useToolbar.
6
+ */
7
+
8
+ import {
9
+ createSignal,
10
+ createEffect,
11
+ onMount,
12
+ onCleanup,
13
+ type Accessor,
14
+ } from 'solid-js'
15
+ import { type MaybeAccessor, access } from '../utils'
16
+ import { useLocale } from '../i18n'
17
+ import { getOwnerDocument, isFocusable } from '../utils'
18
+ import { focusSafely } from '../utils/focus'
19
+
20
+ // ============================================
21
+ // TYPES
22
+ // ============================================
23
+
24
+ export type Orientation = 'horizontal' | 'vertical'
25
+
26
+ export interface AriaToolbarProps {
27
+ /** The orientation of the toolbar. @default 'horizontal' */
28
+ orientation?: MaybeAccessor<Orientation>
29
+ /** An accessibility label for the toolbar. */
30
+ 'aria-label'?: MaybeAccessor<string>
31
+ /** Identifies the element (or elements) that labels the toolbar. */
32
+ 'aria-labelledby'?: MaybeAccessor<string>
33
+ }
34
+
35
+ export interface ToolbarAria {
36
+ /** Props for the toolbar container element. */
37
+ toolbarProps: {
38
+ role: 'toolbar' | 'group'
39
+ 'aria-orientation': Orientation
40
+ 'aria-label'?: string
41
+ 'aria-labelledby'?: string
42
+ tabIndex?: number
43
+ ref: (el: HTMLElement) => void
44
+ }
45
+ /** The orientation of the toolbar. */
46
+ orientation: Accessor<Orientation>
47
+ }
48
+
49
+ // ============================================
50
+ // FOCUS MANAGER FOR TOOLBAR
51
+ // ============================================
52
+
53
+ interface FocusManagerOptions {
54
+ from?: Element
55
+ tabbable?: boolean
56
+ wrap?: boolean
57
+ accept?: (node: Element) => boolean
58
+ }
59
+
60
+ interface FocusManager {
61
+ focusNext(opts?: FocusManagerOptions): HTMLElement | null
62
+ focusPrevious(opts?: FocusManagerOptions): HTMLElement | null
63
+ focusFirst(opts?: FocusManagerOptions): HTMLElement | null
64
+ focusLast(opts?: FocusManagerOptions): HTMLElement | null
65
+ }
66
+
67
+ function isTabbable(element: Element): boolean {
68
+ if (!isFocusable(element)) {
69
+ return false
70
+ }
71
+ const tabIndex = element.getAttribute('tabindex')
72
+ if (tabIndex != null) {
73
+ return parseInt(tabIndex, 10) >= 0
74
+ }
75
+ return true
76
+ }
77
+
78
+ function getFocusableElements(root: Element, tabbable = false): HTMLElement[] {
79
+ const elements: HTMLElement[] = []
80
+ const filter = tabbable ? isTabbable : isFocusable
81
+
82
+ // Check the root element itself
83
+ if (filter(root)) {
84
+ elements.push(root as HTMLElement)
85
+ }
86
+
87
+ // Check all descendants
88
+ const descendants = root.querySelectorAll('*')
89
+ for (let i = 0; i < descendants.length; i++) {
90
+ const el = descendants[i]
91
+ if (filter(el)) {
92
+ elements.push(el as HTMLElement)
93
+ }
94
+ }
95
+
96
+ return elements
97
+ }
98
+
99
+ function getActiveElement(doc: Document): Element | null {
100
+ let activeElement = doc.activeElement
101
+ while (activeElement?.shadowRoot?.activeElement) {
102
+ activeElement = activeElement.shadowRoot.activeElement
103
+ }
104
+ return activeElement
105
+ }
106
+
107
+ function createFocusManager(ref: Accessor<HTMLElement | undefined>): FocusManager {
108
+ return {
109
+ focusNext(opts: FocusManagerOptions = {}) {
110
+ const root = ref()
111
+ if (!root) return null
112
+
113
+ const { from, tabbable = true, wrap = false, accept } = opts
114
+ const doc = getOwnerDocument(root)
115
+ const current = from || getActiveElement(doc)
116
+
117
+ let elements = getFocusableElements(root, tabbable)
118
+ if (accept) {
119
+ elements = elements.filter(accept)
120
+ }
121
+
122
+ if (!current || elements.length === 0) return null
123
+
124
+ const currentIndex = elements.indexOf(current as HTMLElement)
125
+ let nextIndex = currentIndex + 1
126
+
127
+ if (nextIndex >= elements.length) {
128
+ if (wrap) {
129
+ nextIndex = 0
130
+ } else {
131
+ return null
132
+ }
133
+ }
134
+
135
+ const nextElement = elements[nextIndex]
136
+ if (nextElement) {
137
+ focusSafely(nextElement)
138
+ return nextElement
139
+ }
140
+
141
+ return null
142
+ },
143
+
144
+ focusPrevious(opts: FocusManagerOptions = {}) {
145
+ const root = ref()
146
+ if (!root) return null
147
+
148
+ const { from, tabbable = true, wrap = false, accept } = opts
149
+ const doc = getOwnerDocument(root)
150
+ const current = from || getActiveElement(doc)
151
+
152
+ let elements = getFocusableElements(root, tabbable)
153
+ if (accept) {
154
+ elements = elements.filter(accept)
155
+ }
156
+
157
+ if (!current || elements.length === 0) return null
158
+
159
+ const currentIndex = elements.indexOf(current as HTMLElement)
160
+ let prevIndex = currentIndex - 1
161
+
162
+ if (prevIndex < 0) {
163
+ if (wrap) {
164
+ prevIndex = elements.length - 1
165
+ } else {
166
+ return null
167
+ }
168
+ }
169
+
170
+ const prevElement = elements[prevIndex]
171
+ if (prevElement) {
172
+ focusSafely(prevElement)
173
+ return prevElement
174
+ }
175
+
176
+ return null
177
+ },
178
+
179
+ focusFirst(opts: FocusManagerOptions = {}) {
180
+ const root = ref()
181
+ if (!root) return null
182
+
183
+ const { tabbable = true, accept } = opts
184
+ let elements = getFocusableElements(root, tabbable)
185
+ if (accept) {
186
+ elements = elements.filter(accept)
187
+ }
188
+
189
+ if (elements.length > 0) {
190
+ focusSafely(elements[0])
191
+ return elements[0]
192
+ }
193
+
194
+ return null
195
+ },
196
+
197
+ focusLast(opts: FocusManagerOptions = {}) {
198
+ const root = ref()
199
+ if (!root) return null
200
+
201
+ const { tabbable = true, accept } = opts
202
+ let elements = getFocusableElements(root, tabbable)
203
+ if (accept) {
204
+ elements = elements.filter(accept)
205
+ }
206
+
207
+ if (elements.length > 0) {
208
+ const lastElement = elements[elements.length - 1]
209
+ focusSafely(lastElement)
210
+ return lastElement
211
+ }
212
+
213
+ return null
214
+ },
215
+ }
216
+ }
217
+
218
+ // ============================================
219
+ // CREATE TOOLBAR HOOK
220
+ // ============================================
221
+
222
+ /**
223
+ * Provides the behavior and accessibility implementation for a toolbar.
224
+ * A toolbar is a container for a set of interactive controls with arrow key navigation.
225
+ *
226
+ * @example
227
+ * ```tsx
228
+ * let ref;
229
+ * const { toolbarProps } = createToolbar({ orientation: 'horizontal' });
230
+ * return (
231
+ * <div {...toolbarProps}>
232
+ * <Button>Cut</Button>
233
+ * <Button>Copy</Button>
234
+ * <Button>Paste</Button>
235
+ * </div>
236
+ * );
237
+ * ```
238
+ */
239
+ export function createToolbar(props: AriaToolbarProps = {}): ToolbarAria {
240
+ let toolbarRef: HTMLElement | undefined
241
+ const [isInToolbar, setIsInToolbar] = createSignal(false)
242
+ let lastFocusedElement: Element | null = null
243
+
244
+ const locale = useLocale()
245
+ const orientation = () => access(props.orientation) ?? 'horizontal'
246
+ const ariaLabel = () => access(props['aria-label'])
247
+ const ariaLabelledby = () => access(props['aria-labelledby'])
248
+
249
+ const focusManager = createFocusManager(() => toolbarRef)
250
+
251
+ // Check if this toolbar is nested inside another toolbar
252
+ onMount(() => {
253
+ if (toolbarRef) {
254
+ const parentToolbar = toolbarRef.parentElement?.closest('[role="toolbar"]')
255
+ setIsInToolbar(!!parentToolbar)
256
+ }
257
+ })
258
+
259
+ // Keyboard event handler
260
+ const onKeyDown = (e: KeyboardEvent) => {
261
+ // Don't handle if nested toolbar (parent handles navigation)
262
+ if (isInToolbar()) return
263
+
264
+ const dir = locale().direction
265
+ const isRTL = dir === 'rtl'
266
+ const isHorizontal = orientation() === 'horizontal'
267
+
268
+ let handled = false
269
+
270
+ switch (e.key) {
271
+ case 'ArrowRight':
272
+ if (isHorizontal) {
273
+ if (isRTL) {
274
+ focusManager.focusPrevious({ tabbable: true })
275
+ } else {
276
+ focusManager.focusNext({ tabbable: true })
277
+ }
278
+ handled = true
279
+ }
280
+ break
281
+ case 'ArrowLeft':
282
+ if (isHorizontal) {
283
+ if (isRTL) {
284
+ focusManager.focusNext({ tabbable: true })
285
+ } else {
286
+ focusManager.focusPrevious({ tabbable: true })
287
+ }
288
+ handled = true
289
+ }
290
+ break
291
+ case 'ArrowDown':
292
+ if (!isHorizontal) {
293
+ focusManager.focusNext({ tabbable: true })
294
+ handled = true
295
+ }
296
+ break
297
+ case 'ArrowUp':
298
+ if (!isHorizontal) {
299
+ focusManager.focusPrevious({ tabbable: true })
300
+ handled = true
301
+ }
302
+ break
303
+ case 'Tab':
304
+ // Store the last focused element for re-entry
305
+ lastFocusedElement = e.target as Element
306
+ break
307
+ }
308
+
309
+ if (handled) {
310
+ e.preventDefault()
311
+ e.stopPropagation()
312
+ }
313
+ }
314
+
315
+ // Focus handler - restore last focused element when re-entering
316
+ const onFocus = (e: FocusEvent) => {
317
+ if (isInToolbar()) return
318
+
319
+ // Only restore if focus is coming from outside the toolbar
320
+ const root = toolbarRef
321
+ if (!root) return
322
+
323
+ const relatedTarget = e.relatedTarget as Element | null
324
+
325
+ // If focus came from outside and we have a last focused element
326
+ if (
327
+ lastFocusedElement &&
328
+ root.contains(lastFocusedElement) &&
329
+ (!relatedTarget || !root.contains(relatedTarget))
330
+ ) {
331
+ // Restore focus to the last focused element
332
+ focusSafely(lastFocusedElement as HTMLElement)
333
+ }
334
+ }
335
+
336
+ // Set up capture event listeners
337
+ const setRef = (el: HTMLElement) => {
338
+ toolbarRef = el
339
+
340
+ // Use capture phase for keyboard events
341
+ el.addEventListener('keydown', onKeyDown, true)
342
+ el.addEventListener('focus', onFocus, true)
343
+
344
+ onCleanup(() => {
345
+ el.removeEventListener('keydown', onKeyDown, true)
346
+ el.removeEventListener('focus', onFocus, true)
347
+ })
348
+ }
349
+
350
+ return {
351
+ toolbarProps: {
352
+ get role() {
353
+ return isInToolbar() ? 'group' : 'toolbar'
354
+ },
355
+ get 'aria-orientation'() {
356
+ return orientation()
357
+ },
358
+ get 'aria-label'() {
359
+ return ariaLabel()
360
+ },
361
+ get 'aria-labelledby'() {
362
+ // Only use aria-labelledby if no aria-label is provided
363
+ return ariaLabel() ? undefined : ariaLabelledby()
364
+ },
365
+ ref: setRef,
366
+ },
367
+ orientation,
368
+ }
369
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ createToolbar,
3
+ type AriaToolbarProps,
4
+ type ToolbarAria,
5
+ type Orientation,
6
+ } from './createToolbar'
@@ -0,0 +1,79 @@
1
+ /**
2
+ * createTooltip hook for Solidaria
3
+ *
4
+ * Provides the accessibility implementation for a Tooltip component.
5
+ *
6
+ * Port of @react-aria/tooltip useTooltip.
7
+ */
8
+
9
+ import { type JSX } from 'solid-js';
10
+ import { type TooltipTriggerState } from '@proyecto-viviana/solid-stately';
11
+ import { createHover } from '../interactions/createHover';
12
+ import { filterDOMProps, mergeProps } from '../utils';
13
+
14
+ // ============================================
15
+ // TYPES
16
+ // ============================================
17
+
18
+ export interface TooltipProps {
19
+ /** Whether the tooltip is disabled. */
20
+ isDisabled?: boolean;
21
+ /** Custom aria-label for the tooltip. */
22
+ 'aria-label'?: string;
23
+ /** ID of an element that labels the tooltip. */
24
+ 'aria-labelledby'?: string;
25
+ /** ID of an element that describes the tooltip. */
26
+ 'aria-describedby'?: string;
27
+ }
28
+
29
+ export interface TooltipAria {
30
+ /** Props to spread on the tooltip element. */
31
+ tooltipProps: JSX.HTMLAttributes<HTMLElement>;
32
+ }
33
+
34
+ // ============================================
35
+ // IMPLEMENTATION
36
+ // ============================================
37
+
38
+ /**
39
+ * Provides the accessibility implementation for a Tooltip component.
40
+ *
41
+ * When hovering over the tooltip itself, it stays open. When the mouse leaves
42
+ * the tooltip, it closes.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * import { createTooltip } from 'solidaria';
47
+ * import { createTooltipTriggerState } from 'solid-stately';
48
+ *
49
+ * function Tooltip(props) {
50
+ * const state = props.state;
51
+ * const { tooltipProps } = createTooltip(props, state);
52
+ *
53
+ * return (
54
+ * <div {...tooltipProps} role="tooltip">
55
+ * {props.children}
56
+ * </div>
57
+ * );
58
+ * }
59
+ * ```
60
+ */
61
+ export function createTooltip(
62
+ props: TooltipProps = {},
63
+ state?: TooltipTriggerState
64
+ ): TooltipAria {
65
+ const domProps = filterDOMProps(props, { labelable: true });
66
+
67
+ const { hoverProps } = createHover({
68
+ onHoverStart: () => state?.open(true),
69
+ onHoverEnd: () => state?.close(),
70
+ });
71
+
72
+ return {
73
+ tooltipProps: mergeProps<JSX.HTMLAttributes<HTMLElement>>(
74
+ domProps,
75
+ hoverProps,
76
+ { role: 'tooltip' }
77
+ ),
78
+ };
79
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * createTooltipTrigger hook for Solidaria
3
+ *
4
+ * Provides the behavior and accessibility implementation for a tooltip trigger,
5
+ * e.g. a button that shows a description when focused or hovered.
6
+ *
7
+ * Port of @react-aria/tooltip useTooltipTrigger.
8
+ */
9
+
10
+ import { type JSX, createEffect, onCleanup } from 'solid-js';
11
+ import { type TooltipTriggerState } from '@proyecto-viviana/solid-stately';
12
+ import { createHover } from '../interactions/createHover';
13
+ import { createFocusable } from '../interactions/createFocusable';
14
+ import { mergeProps } from '../utils';
15
+ import { createId } from '../ssr';
16
+
17
+ // ============================================
18
+ // TYPES
19
+ // ============================================
20
+
21
+ export interface TooltipTriggerProps {
22
+ /** Whether the tooltip should be disabled. */
23
+ isDisabled?: boolean;
24
+ /**
25
+ * The trigger mechanism for the tooltip.
26
+ * @default 'focus'
27
+ */
28
+ trigger?: 'focus';
29
+ /**
30
+ * Whether the tooltip should close when the trigger is pressed.
31
+ * @default true
32
+ */
33
+ shouldCloseOnPress?: boolean;
34
+ }
35
+
36
+ export interface TooltipTriggerAria {
37
+ /** Props to spread on the trigger element. */
38
+ triggerProps: JSX.HTMLAttributes<HTMLElement>;
39
+ /** Props to spread on the tooltip element (id for accessibility). */
40
+ tooltipProps: { id: string };
41
+ }
42
+
43
+ // ============================================
44
+ // GLOBAL STATE
45
+ // ============================================
46
+
47
+ type Modality = 'keyboard' | 'pointer' | 'virtual';
48
+ let currentModality: Modality | null = null;
49
+
50
+ // Track interaction modality (pointer vs keyboard)
51
+ if (typeof document !== 'undefined') {
52
+ document.addEventListener('keydown', () => {
53
+ currentModality = 'keyboard';
54
+ }, true);
55
+ document.addEventListener('pointerdown', () => {
56
+ currentModality = 'pointer';
57
+ }, true);
58
+ document.addEventListener('pointermove', () => {
59
+ currentModality = 'pointer';
60
+ }, true);
61
+ }
62
+
63
+ function isFocusVisible(): boolean {
64
+ return currentModality === 'keyboard';
65
+ }
66
+
67
+ // ============================================
68
+ // IMPLEMENTATION
69
+ // ============================================
70
+
71
+ /**
72
+ * Provides the behavior and accessibility implementation for a tooltip trigger.
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * import { createTooltipTrigger } from 'solidaria';
77
+ * import { createTooltipTriggerState } from 'solid-stately';
78
+ *
79
+ * function TooltipButton(props) {
80
+ * let ref;
81
+ * const state = createTooltipTriggerState({ delay: 500 });
82
+ * const { triggerProps, tooltipProps } = createTooltipTrigger(
83
+ * { isDisabled: props.isDisabled },
84
+ * state,
85
+ * () => ref
86
+ * );
87
+ *
88
+ * return (
89
+ * <>
90
+ * <button ref={ref} {...triggerProps}>
91
+ * Hover me
92
+ * </button>
93
+ * <Show when={state.isOpen()}>
94
+ * <div {...tooltipProps}>Tooltip content</div>
95
+ * </Show>
96
+ * </>
97
+ * );
98
+ * }
99
+ * ```
100
+ */
101
+ export function createTooltipTrigger(
102
+ props: TooltipTriggerProps,
103
+ state: TooltipTriggerState,
104
+ ref: () => HTMLElement | null | undefined
105
+ ): TooltipTriggerAria {
106
+ const {
107
+ isDisabled = false,
108
+ trigger,
109
+ shouldCloseOnPress = true,
110
+ } = props;
111
+
112
+ const tooltipId = createId();
113
+
114
+ // Track hover and focus state
115
+ let isHovered = false;
116
+ let isFocused = false;
117
+
118
+ const handleShow = () => {
119
+ if (isHovered || isFocused) {
120
+ state.open(isFocused);
121
+ }
122
+ };
123
+
124
+ const handleHide = (immediate?: boolean) => {
125
+ if (!isHovered && !isFocused) {
126
+ state.close(immediate);
127
+ }
128
+ };
129
+
130
+ // Handle Escape key to dismiss tooltip
131
+ createEffect(() => {
132
+ if (!state.isOpen()) return;
133
+
134
+ const onKeyDown = (e: KeyboardEvent) => {
135
+ const element = ref();
136
+ if (element) {
137
+ if (e.key === 'Escape') {
138
+ e.stopPropagation();
139
+ state.close(true);
140
+ }
141
+ }
142
+ };
143
+
144
+ document.addEventListener('keydown', onKeyDown, true);
145
+ onCleanup(() => {
146
+ document.removeEventListener('keydown', onKeyDown, true);
147
+ });
148
+ });
149
+
150
+ const onHoverStart = () => {
151
+ if (trigger === 'focus') {
152
+ return;
153
+ }
154
+ // Hover events (onPointerEnter) only fire from pointer interactions,
155
+ // so we can always set isHovered to true here
156
+ isHovered = true;
157
+ handleShow();
158
+ };
159
+
160
+ const onHoverEnd = () => {
161
+ if (trigger === 'focus') {
162
+ return;
163
+ }
164
+ isFocused = false;
165
+ isHovered = false;
166
+ handleHide();
167
+ };
168
+
169
+ const onPressStart = () => {
170
+ if (!shouldCloseOnPress) {
171
+ return;
172
+ }
173
+ isFocused = false;
174
+ isHovered = false;
175
+ handleHide(true);
176
+ };
177
+
178
+ const onFocus = () => {
179
+ const visible = isFocusVisible();
180
+ if (visible) {
181
+ isFocused = true;
182
+ handleShow();
183
+ }
184
+ };
185
+
186
+ const onBlur = () => {
187
+ isFocused = false;
188
+ isHovered = false;
189
+ handleHide(true);
190
+ };
191
+
192
+ const { hoverProps } = createHover({
193
+ isDisabled,
194
+ onHoverStart,
195
+ onHoverEnd,
196
+ });
197
+
198
+ const { focusableProps } = createFocusable({
199
+ isDisabled,
200
+ onFocus,
201
+ onBlur,
202
+ });
203
+
204
+ const triggerProps = {
205
+ ...focusableProps,
206
+ ...hoverProps,
207
+ get 'aria-describedby'() {
208
+ return state.isOpen() ? tooltipId : undefined;
209
+ },
210
+ onPointerDown: onPressStart,
211
+ onKeyDown: onPressStart,
212
+ // Remove tabIndex set by focusableProps to avoid overriding
213
+ tabIndex: undefined,
214
+ };
215
+
216
+ return {
217
+ triggerProps: triggerProps as JSX.HTMLAttributes<HTMLElement>,
218
+ tooltipProps: {
219
+ id: tooltipId,
220
+ },
221
+ };
222
+ }
@@ -0,0 +1,6 @@
1
+ export { createTooltip, type TooltipProps, type TooltipAria } from './createTooltip';
2
+ export {
3
+ createTooltipTrigger,
4
+ type TooltipTriggerProps,
5
+ type TooltipTriggerAria,
6
+ } from './createTooltipTrigger';