@proyecto-viviana/solidaria 0.2.2 → 0.2.4

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,424 @@
1
+ /**
2
+ * createInteractionModality + focus-visible tracking for solidaria
3
+ *
4
+ * Port of @react-aria/interactions useFocusVisible/useInteractionModality.
5
+ * Tracks the current interaction modality (keyboard, pointer, or virtual) and
6
+ * provides focus-visible state and listeners.
7
+ */
8
+
9
+ import { type Accessor, createSignal, createEffect, onCleanup } from 'solid-js';
10
+ import { isServer } from 'solid-js/web';
11
+ import { getOwnerDocument, getOwnerWindow, isMac, isVirtualClick, openLink } from '../utils';
12
+
13
+ // ============================================
14
+ // TYPES
15
+ // ============================================
16
+
17
+ export type Modality = 'keyboard' | 'pointer' | 'virtual';
18
+ export type PointerType = 'mouse' | 'pen' | 'touch' | 'keyboard' | 'virtual';
19
+ type HandlerEvent = PointerEvent | MouseEvent | KeyboardEvent | FocusEvent | null;
20
+ type Handler = (modality: Modality, e: HandlerEvent) => void;
21
+
22
+ export type FocusVisibleHandler = (isFocusVisible: boolean) => void;
23
+
24
+ export interface FocusVisibleProps {
25
+ /** Whether the element is a text input. */
26
+ isTextInput?: boolean;
27
+ /** Whether the element will be auto focused. */
28
+ autoFocus?: boolean;
29
+ }
30
+
31
+ export interface FocusVisibleResult {
32
+ /** Whether keyboard focus is visible globally. */
33
+ isFocusVisible: Accessor<boolean>;
34
+ }
35
+
36
+ export interface InteractionModalityResult {
37
+ /** The current interaction modality. */
38
+ modality: Accessor<Modality | null>;
39
+ }
40
+
41
+ // ============================================
42
+ // GLOBAL STATE
43
+ // ============================================
44
+
45
+ let currentModality: Modality | null = null;
46
+ let currentPointerType: PointerType = 'keyboard';
47
+ const changeHandlers = new Set<Handler>();
48
+
49
+ export let hasSetupGlobalListeners: Map<
50
+ Window,
51
+ { focus: typeof window.HTMLElement.prototype.focus; canOverride: boolean }
52
+ > = new Map();
53
+ let hasEventBeforeFocus = false;
54
+ let hasBlurredWindowRecently = false;
55
+ let ignoreFocusEvent = false;
56
+
57
+ const FOCUS_VISIBLE_INPUT_KEYS: Record<string, boolean> = {
58
+ Tab: true,
59
+ Escape: true,
60
+ };
61
+
62
+ function triggerChangeHandlers(modality: Modality, e: HandlerEvent) {
63
+ for (const handler of changeHandlers) {
64
+ handler(modality, e);
65
+ }
66
+ }
67
+
68
+ function isValidKey(e: KeyboardEvent) {
69
+ return !(
70
+ e.metaKey ||
71
+ (!isMac() && e.altKey) ||
72
+ e.ctrlKey ||
73
+ e.key === 'Control' ||
74
+ e.key === 'Shift' ||
75
+ e.key === 'Meta'
76
+ );
77
+ }
78
+
79
+ function handleKeyboardEvent(e: KeyboardEvent) {
80
+ hasEventBeforeFocus = true;
81
+ const isOpening = (openLink as { isOpening?: boolean }).isOpening;
82
+ if (!isOpening && isValidKey(e)) {
83
+ currentModality = 'keyboard';
84
+ currentPointerType = 'keyboard';
85
+ triggerChangeHandlers('keyboard', e);
86
+ }
87
+ }
88
+
89
+ function handlePointerEvent(e: PointerEvent | MouseEvent) {
90
+ currentModality = 'pointer';
91
+ currentPointerType = 'pointerType' in e ? (e.pointerType as PointerType) : 'mouse';
92
+ if (e.type === 'mousedown' || e.type === 'pointerdown') {
93
+ hasEventBeforeFocus = true;
94
+ triggerChangeHandlers('pointer', e);
95
+ }
96
+ }
97
+
98
+ function handleClickEvent(e: MouseEvent) {
99
+ const isOpening = (openLink as { isOpening?: boolean }).isOpening;
100
+ if (!isOpening && isVirtualClick(e)) {
101
+ hasEventBeforeFocus = true;
102
+ currentModality = 'virtual';
103
+ currentPointerType = 'virtual';
104
+ }
105
+ }
106
+
107
+ function handleFocusEvent(e: FocusEvent) {
108
+ if (!e.isTrusted || ignoreFocusEvent) {
109
+ return;
110
+ }
111
+
112
+ const target = e.target as EventTarget | null;
113
+ const targetElement = target && (target as Element).nodeType ? (target as Element) : null;
114
+ const ownerWindow = targetElement ? getOwnerWindow(targetElement) : window;
115
+ const ownerDocument = targetElement ? getOwnerDocument(targetElement) : document;
116
+
117
+ if (target === ownerWindow || target === ownerDocument) {
118
+ return;
119
+ }
120
+
121
+ if (!hasEventBeforeFocus && !hasBlurredWindowRecently) {
122
+ currentModality = 'virtual';
123
+ currentPointerType = 'virtual';
124
+ triggerChangeHandlers('virtual', e);
125
+ }
126
+
127
+ hasEventBeforeFocus = false;
128
+ hasBlurredWindowRecently = false;
129
+ }
130
+
131
+ function handleWindowBlur() {
132
+ if (ignoreFocusEvent) {
133
+ return;
134
+ }
135
+
136
+ hasEventBeforeFocus = false;
137
+ hasBlurredWindowRecently = true;
138
+ }
139
+
140
+ function setupGlobalFocusEvents(element?: HTMLElement | null) {
141
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
142
+ return;
143
+ }
144
+
145
+ const windowObject = getOwnerWindow(element);
146
+ if (hasSetupGlobalListeners.get(windowObject)) {
147
+ return;
148
+ }
149
+
150
+ const documentObject = getOwnerDocument(element);
151
+
152
+ const originalFocus = windowObject.HTMLElement.prototype.focus;
153
+ let canOverride = true;
154
+ try {
155
+ windowObject.HTMLElement.prototype.focus = function () {
156
+ hasEventBeforeFocus = true;
157
+ originalFocus.apply(this, arguments as unknown as [options?: FocusOptions | undefined]);
158
+ };
159
+ } catch {
160
+ canOverride = false;
161
+ }
162
+
163
+ documentObject.addEventListener('keydown', handleKeyboardEvent, true);
164
+ documentObject.addEventListener('keyup', handleKeyboardEvent, true);
165
+ documentObject.addEventListener('click', handleClickEvent, true);
166
+
167
+ windowObject.addEventListener('focus', handleFocusEvent, true);
168
+ windowObject.addEventListener('blur', handleWindowBlur, false);
169
+
170
+ if (typeof windowObject.PointerEvent !== 'undefined') {
171
+ documentObject.addEventListener('pointerdown', handlePointerEvent, true);
172
+ documentObject.addEventListener('pointermove', handlePointerEvent, true);
173
+ documentObject.addEventListener('pointerup', handlePointerEvent, true);
174
+ } else {
175
+ documentObject.addEventListener('mousedown', handlePointerEvent, true);
176
+ documentObject.addEventListener('mousemove', handlePointerEvent, true);
177
+ documentObject.addEventListener('mouseup', handlePointerEvent, true);
178
+ }
179
+
180
+ windowObject.addEventListener(
181
+ 'beforeunload',
182
+ () => {
183
+ tearDownWindowFocusTracking(element);
184
+ },
185
+ { once: true }
186
+ );
187
+
188
+ hasSetupGlobalListeners.set(windowObject, { focus: originalFocus, canOverride });
189
+ }
190
+
191
+ function tearDownWindowFocusTracking(element?: HTMLElement | null, loadListener?: () => void) {
192
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
193
+ return;
194
+ }
195
+
196
+ const windowObject = getOwnerWindow(element);
197
+ const documentObject = getOwnerDocument(element);
198
+
199
+ if (loadListener) {
200
+ documentObject.removeEventListener('DOMContentLoaded', loadListener);
201
+ }
202
+
203
+ if (!hasSetupGlobalListeners.has(windowObject)) {
204
+ return;
205
+ }
206
+
207
+ const entry = hasSetupGlobalListeners.get(windowObject)!;
208
+ if (entry.canOverride) {
209
+ windowObject.HTMLElement.prototype.focus = entry.focus;
210
+ }
211
+
212
+ documentObject.removeEventListener('keydown', handleKeyboardEvent, true);
213
+ documentObject.removeEventListener('keyup', handleKeyboardEvent, true);
214
+ documentObject.removeEventListener('click', handleClickEvent, true);
215
+
216
+ windowObject.removeEventListener('focus', handleFocusEvent, true);
217
+ windowObject.removeEventListener('blur', handleWindowBlur, false);
218
+
219
+ if (typeof windowObject.PointerEvent !== 'undefined') {
220
+ documentObject.removeEventListener('pointerdown', handlePointerEvent, true);
221
+ documentObject.removeEventListener('pointermove', handlePointerEvent, true);
222
+ documentObject.removeEventListener('pointerup', handlePointerEvent, true);
223
+ } else {
224
+ documentObject.removeEventListener('mousedown', handlePointerEvent, true);
225
+ documentObject.removeEventListener('mousemove', handlePointerEvent, true);
226
+ documentObject.removeEventListener('mouseup', handlePointerEvent, true);
227
+ }
228
+
229
+ hasSetupGlobalListeners.delete(windowObject);
230
+ }
231
+
232
+ /**
233
+ * Adds a window (i.e. iframe) to the list of windows that are being tracked for focus visible.
234
+ */
235
+ export function addWindowFocusTracking(element?: HTMLElement | null): () => void {
236
+ const documentObject = getOwnerDocument(element);
237
+ let loadListener: (() => void) | undefined;
238
+
239
+ if (documentObject.readyState !== 'loading') {
240
+ setupGlobalFocusEvents(element);
241
+ } else {
242
+ loadListener = () => {
243
+ setupGlobalFocusEvents(element);
244
+ };
245
+ documentObject.addEventListener('DOMContentLoaded', loadListener);
246
+ }
247
+
248
+ return () => tearDownWindowFocusTracking(element, loadListener);
249
+ }
250
+
251
+ export function setupGlobalFocusListeners(): void {
252
+ addWindowFocusTracking();
253
+ }
254
+
255
+ if (typeof document !== 'undefined') {
256
+ addWindowFocusTracking();
257
+ }
258
+
259
+ /**
260
+ * If true, keyboard focus is visible.
261
+ */
262
+ export function isFocusVisible(): boolean {
263
+ return currentModality !== 'pointer';
264
+ }
265
+
266
+ /**
267
+ * Gets the current interaction modality.
268
+ */
269
+ export function getInteractionModality(): Modality | null {
270
+ return currentModality;
271
+ }
272
+
273
+ /**
274
+ * Sets the current interaction modality.
275
+ */
276
+ export function setInteractionModality(modality: Modality): void {
277
+ currentModality = modality;
278
+ currentPointerType = modality === 'pointer' ? 'mouse' : modality;
279
+ triggerChangeHandlers(modality, null);
280
+ }
281
+
282
+ /**
283
+ * Gets the current pointer type.
284
+ */
285
+ export function getPointerType(): PointerType {
286
+ return currentPointerType;
287
+ }
288
+
289
+ function isKeyboardFocusEvent(
290
+ isTextInput: boolean,
291
+ modality: Modality,
292
+ e: HandlerEvent
293
+ ): boolean {
294
+ if (!e) {
295
+ return true;
296
+ }
297
+
298
+ const target = 'target' in e ? (e.target as Element | null) : null;
299
+ const ownerDocument = target ? getOwnerDocument(target) : document;
300
+ const ownerWindow = target ? getOwnerWindow(target) : window;
301
+
302
+ const IHTMLInputElement = ownerWindow.HTMLInputElement;
303
+ const IHTMLTextAreaElement = ownerWindow.HTMLTextAreaElement;
304
+ const IHTMLElement = ownerWindow.HTMLElement;
305
+ const IKeyboardEvent = ownerWindow.KeyboardEvent;
306
+
307
+ const nonTextInputTypes = new Set([
308
+ 'checkbox',
309
+ 'radio',
310
+ 'range',
311
+ 'color',
312
+ 'file',
313
+ 'image',
314
+ 'button',
315
+ 'submit',
316
+ 'reset',
317
+ ]);
318
+
319
+ isTextInput =
320
+ isTextInput ||
321
+ (ownerDocument.activeElement instanceof IHTMLInputElement &&
322
+ !nonTextInputTypes.has(ownerDocument.activeElement.type)) ||
323
+ ownerDocument.activeElement instanceof IHTMLTextAreaElement ||
324
+ (ownerDocument.activeElement instanceof IHTMLElement && ownerDocument.activeElement.isContentEditable);
325
+
326
+ return !(
327
+ isTextInput &&
328
+ modality === 'keyboard' &&
329
+ e instanceof IKeyboardEvent &&
330
+ !FOCUS_VISIBLE_INPUT_KEYS[e.key]
331
+ );
332
+ }
333
+
334
+ /**
335
+ * Listens for trigger change and reports if focus is visible.
336
+ */
337
+ export function createFocusVisibleListener(
338
+ handler: FocusVisibleHandler,
339
+ opts?: { isTextInput?: boolean }
340
+ ): () => void {
341
+ setupGlobalFocusEvents();
342
+ const listener: Handler = (modality: Modality, e: HandlerEvent) => {
343
+ if (!isKeyboardFocusEvent(!!opts?.isTextInput, modality, e)) {
344
+ return;
345
+ }
346
+ handler(isFocusVisible());
347
+ };
348
+ changeHandlers.add(listener);
349
+ return () => {
350
+ changeHandlers.delete(listener);
351
+ };
352
+ }
353
+
354
+ /**
355
+ * Manages focus visible state for the page.
356
+ */
357
+ export function createFocusVisible(props: FocusVisibleProps = {}): FocusVisibleResult {
358
+ if (isServer) {
359
+ return { isFocusVisible: () => false };
360
+ }
361
+
362
+ const { isTextInput, autoFocus } = props;
363
+ const [isVisible, setIsVisible] = createSignal<boolean>(autoFocus || isFocusVisible());
364
+
365
+ createEffect(() => {
366
+ const cleanup = createFocusVisibleListener(setIsVisible, { isTextInput });
367
+ onCleanup(cleanup);
368
+ });
369
+
370
+ return { isFocusVisible: isVisible };
371
+ }
372
+
373
+ /**
374
+ * Tracks the current interaction modality.
375
+ */
376
+ export function createInteractionModality(): InteractionModalityResult {
377
+ if (isServer) {
378
+ return {
379
+ modality: () => null,
380
+ };
381
+ }
382
+
383
+ const [modality, setModality] = createSignal<Modality | null>(currentModality);
384
+
385
+ createEffect(() => {
386
+ setupGlobalFocusEvents();
387
+ const handler: Handler = (newModality: Modality) => {
388
+ setModality(newModality);
389
+ };
390
+ changeHandlers.add(handler);
391
+ onCleanup(() => {
392
+ changeHandlers.delete(handler);
393
+ });
394
+ });
395
+
396
+ return {
397
+ modality,
398
+ };
399
+ }
400
+
401
+ /**
402
+ * Adds a listener for modality changes.
403
+ */
404
+ export function addModalityListener(handler: (modality: Modality) => void): () => void {
405
+ const wrapped: Handler = (modality) => {
406
+ handler(modality);
407
+ };
408
+ changeHandlers.add(wrapped);
409
+ return () => {
410
+ changeHandlers.delete(wrapped);
411
+ };
412
+ }
413
+
414
+ /**
415
+ * Hook to track whether the user is currently interacting with the keyboard.
416
+ */
417
+ export function useIsKeyboardFocused(): Accessor<boolean> {
418
+ if (isServer) {
419
+ return () => false;
420
+ }
421
+
422
+ const { modality } = createInteractionModality();
423
+ return () => modality() === 'keyboard';
424
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * createKeyboard - Handles keyboard interactions for a focusable element.
3
+ *
4
+ * This is a 1-1 port of React-Aria's useKeyboard hook adapted for SolidJS.
5
+ */
6
+
7
+ import { JSX } from 'solid-js';
8
+
9
+ /**
10
+ * Keyboard event with continuePropagation support.
11
+ * By default, keyboard events stop propagation.
12
+ */
13
+ export interface KeyboardEvent extends globalThis.KeyboardEvent {
14
+ /** Call this to allow the event to propagate to parent elements. */
15
+ continuePropagation(): void;
16
+ }
17
+
18
+ export interface KeyboardEvents {
19
+ /** Handler that is called when a key is pressed. */
20
+ onKeyDown?: (e: KeyboardEvent) => void;
21
+ /** Handler that is called when a key is released. */
22
+ onKeyUp?: (e: KeyboardEvent) => void;
23
+ }
24
+
25
+ export interface CreateKeyboardProps extends KeyboardEvents {
26
+ /** Whether the keyboard events should be disabled. */
27
+ isDisabled?: boolean;
28
+ }
29
+
30
+ export interface KeyboardResult {
31
+ /** Props to spread onto the target element. */
32
+ keyboardProps: JSX.HTMLAttributes<HTMLElement>;
33
+ }
34
+
35
+ /**
36
+ * Wraps a keyboard event handler to make stopPropagation the default,
37
+ * and support continuePropagation instead.
38
+ */
39
+ function createEventHandler<T extends globalThis.KeyboardEvent>(
40
+ handler?: (e: KeyboardEvent) => void
41
+ ): ((e: T) => void) | undefined {
42
+ if (!handler) {
43
+ return undefined;
44
+ }
45
+
46
+ return (e: T) => {
47
+ let shouldStopPropagation = true;
48
+
49
+ // Create a wrapped event with continuePropagation
50
+ const event = Object.assign(e, {
51
+ continuePropagation() {
52
+ shouldStopPropagation = false;
53
+ },
54
+ }) as KeyboardEvent;
55
+
56
+ handler(event);
57
+
58
+ if (shouldStopPropagation) {
59
+ e.stopPropagation();
60
+ }
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Handles keyboard interactions for a focusable element.
66
+ *
67
+ * Based on react-aria's useKeyboard but adapted for SolidJS.
68
+ */
69
+ export function createKeyboard(props: CreateKeyboardProps = {}): KeyboardResult {
70
+ if (props.isDisabled) {
71
+ return {
72
+ keyboardProps: {},
73
+ };
74
+ }
75
+
76
+ return {
77
+ keyboardProps: {
78
+ onKeyDown: createEventHandler(props.onKeyDown),
79
+ onKeyUp: createEventHandler(props.onKeyUp),
80
+ },
81
+ };
82
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * createLongPress - Handles long press interactions across mouse and touch.
3
+ *
4
+ * Port of @react-aria/interactions useLongPress, adapted for SolidJS.
5
+ */
6
+
7
+ import { JSX, onCleanup } from 'solid-js';
8
+ import { createPress, type PressEvent } from './createPress';
9
+ import { mergeProps, focusWithoutScrolling, createGlobalListeners } from '../utils';
10
+ import { createDescription } from '../utils/createDescription';
11
+ import { type MaybeAccessor } from '../utils/reactivity';
12
+
13
+ export interface LongPressEvent {
14
+ /** The type of long press event being fired. */
15
+ type: 'longpressstart' | 'longpressend' | 'longpress';
16
+ /** The pointer type that triggered the long press. */
17
+ pointerType: PressEvent['pointerType'];
18
+ /** The target element of the long press event. */
19
+ target: Element;
20
+ /** Whether the shift keyboard modifier was held during the long press event. */
21
+ shiftKey: boolean;
22
+ /** Whether the ctrl keyboard modifier was held during the long press event. */
23
+ ctrlKey: boolean;
24
+ /** Whether the meta keyboard modifier was held during the long press event. */
25
+ metaKey: boolean;
26
+ /** Whether the alt keyboard modifier was held during the long press event. */
27
+ altKey: boolean;
28
+ /** X position relative to the target. */
29
+ x: number;
30
+ /** Y position relative to the target. */
31
+ y: number;
32
+ }
33
+
34
+ export interface LongPressProps {
35
+ /** Whether long press events should be disabled. */
36
+ isDisabled?: MaybeAccessor<boolean>;
37
+ /** Handler that is called when a long press interaction starts. */
38
+ onLongPressStart?: (e: LongPressEvent) => void;
39
+ /**
40
+ * Handler that is called when a long press interaction ends, either
41
+ * over the target or when the pointer leaves the target.
42
+ */
43
+ onLongPressEnd?: (e: LongPressEvent) => void;
44
+ /**
45
+ * Handler that is called when the threshold time is met while
46
+ * the press is over the target.
47
+ */
48
+ onLongPress?: (e: LongPressEvent) => void;
49
+ /**
50
+ * The amount of time in milliseconds to wait before triggering a long press.
51
+ * @default 500ms
52
+ */
53
+ threshold?: number;
54
+ /**
55
+ * A description for assistive technology users indicating that a long press
56
+ * action is available, e.g. "Long press to open menu".
57
+ */
58
+ accessibilityDescription?: string;
59
+ }
60
+
61
+ export interface LongPressResult {
62
+ /** Props to spread on the target element. */
63
+ longPressProps: JSX.HTMLAttributes<HTMLElement>;
64
+ }
65
+
66
+ const DEFAULT_THRESHOLD = 500;
67
+
68
+ function isDisabledValue(isDisabled: MaybeAccessor<boolean> | undefined): boolean {
69
+ return typeof isDisabled === 'function' ? isDisabled() : !!isDisabled;
70
+ }
71
+
72
+ function createLongPressEvent(type: LongPressEvent['type'], e: PressEvent): LongPressEvent {
73
+ return {
74
+ type,
75
+ pointerType: e.pointerType,
76
+ target: e.target,
77
+ shiftKey: e.shiftKey,
78
+ ctrlKey: e.ctrlKey,
79
+ metaKey: e.metaKey,
80
+ altKey: e.altKey,
81
+ x: e.x,
82
+ y: e.y,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Handles long press interactions across mouse and touch devices.
88
+ */
89
+ export function createLongPress(props: LongPressProps = {}): LongPressResult {
90
+ const {
91
+ isDisabled,
92
+ onLongPressStart,
93
+ onLongPressEnd,
94
+ onLongPress,
95
+ threshold = DEFAULT_THRESHOLD,
96
+ accessibilityDescription,
97
+ } = props;
98
+
99
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
100
+ const { addGlobalListener } = createGlobalListeners();
101
+
102
+ const { pressProps } = createPress({
103
+ isDisabled,
104
+ onPressStart(e) {
105
+ e.continuePropagation();
106
+ if (e.pointerType === 'mouse' || e.pointerType === 'touch') {
107
+ onLongPressStart?.(createLongPressEvent('longpressstart', e));
108
+
109
+ timeoutId = setTimeout(() => {
110
+ // Prevent other press handlers from also handling this event.
111
+ e.target.dispatchEvent(new PointerEvent('pointercancel', { bubbles: true }));
112
+
113
+ // Ensure target is focused. On touch devices, browsers typically focus on pointer up.
114
+ if (document.activeElement !== e.target) {
115
+ focusWithoutScrolling(e.target as HTMLElement);
116
+ }
117
+
118
+ onLongPress?.(createLongPressEvent('longpress', e));
119
+ timeoutId = undefined;
120
+ }, threshold);
121
+
122
+ if (e.pointerType === 'touch') {
123
+ const onContextMenu = (event: Event) => {
124
+ event.preventDefault();
125
+ };
126
+ const target = e.target as HTMLElement;
127
+ target.addEventListener('contextmenu', onContextMenu, { once: true });
128
+
129
+ addGlobalListener(
130
+ 'pointerup',
131
+ () => {
132
+ setTimeout(() => {
133
+ target.removeEventListener('contextmenu', onContextMenu);
134
+ }, 30);
135
+ },
136
+ { isWindow: true, once: true }
137
+ );
138
+ }
139
+ }
140
+ },
141
+ onPressEnd(e) {
142
+ if (timeoutId) {
143
+ clearTimeout(timeoutId);
144
+ timeoutId = undefined;
145
+ }
146
+
147
+ if (onLongPressEnd && (e.pointerType === 'mouse' || e.pointerType === 'touch')) {
148
+ onLongPressEnd(createLongPressEvent('longpressend', e));
149
+ }
150
+ },
151
+ });
152
+
153
+ const descriptionProps = createDescription(() =>
154
+ onLongPress && !isDisabledValue(isDisabled) ? accessibilityDescription : undefined
155
+ );
156
+
157
+ onCleanup(() => {
158
+ if (timeoutId) {
159
+ clearTimeout(timeoutId);
160
+ timeoutId = undefined;
161
+ }
162
+ });
163
+
164
+ const longPressProps = mergeProps(pressProps) as JSX.HTMLAttributes<HTMLElement>;
165
+ Object.defineProperty(longPressProps, 'aria-describedby', {
166
+ get: () => descriptionProps['aria-describedby'],
167
+ enumerable: true,
168
+ configurable: true,
169
+ });
170
+
171
+ return {
172
+ longPressProps,
173
+ };
174
+ }