@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,356 @@
1
+ /**
2
+ * Handles positioning overlays like popovers and menus relative to a trigger
3
+ * element, and updating the position when the window resizes.
4
+ *
5
+ * Ported from @react-aria/overlays useOverlayPosition.
6
+ */
7
+
8
+ import { createEffect, createSignal, onCleanup, type JSX } from 'solid-js';
9
+ import {
10
+ calculatePosition,
11
+ getRect,
12
+ type Placement,
13
+ type PlacementAxis,
14
+ type PositionResult,
15
+ } from './calculatePosition';
16
+
17
+ export interface PositionProps {
18
+ /**
19
+ * The placement of the element with respect to its anchor element.
20
+ * @default 'bottom'
21
+ */
22
+ placement?: Placement;
23
+ /**
24
+ * The placement padding that should be applied between the element and its
25
+ * surrounding container.
26
+ * @default 12
27
+ */
28
+ containerPadding?: number;
29
+ /**
30
+ * The additional offset applied along the main axis between the element and its
31
+ * anchor element.
32
+ * @default 0
33
+ */
34
+ offset?: number;
35
+ /**
36
+ * The additional offset applied along the cross axis between the element and its
37
+ * anchor element.
38
+ * @default 0
39
+ */
40
+ crossOffset?: number;
41
+ /**
42
+ * Whether the element should flip its orientation (e.g. top to bottom or left to right) when
43
+ * there is insufficient room for it to render completely.
44
+ * @default true
45
+ */
46
+ shouldFlip?: boolean;
47
+ /** Whether the overlay is currently open. */
48
+ isOpen?: boolean;
49
+ }
50
+
51
+ export interface AriaPositionProps extends PositionProps {
52
+ /**
53
+ * Cross size of the overlay arrow in pixels.
54
+ * @default 0
55
+ */
56
+ arrowSize?: number;
57
+ /**
58
+ * Element that that serves as the positioning boundary.
59
+ * @default document.body
60
+ */
61
+ boundaryElement?: Element;
62
+ /**
63
+ * The ref for the element which the overlay positions itself with respect to.
64
+ */
65
+ targetRef: () => Element | null;
66
+ /**
67
+ * The ref for the overlay element.
68
+ */
69
+ overlayRef: () => Element | null;
70
+ /**
71
+ * The ref for the arrow element.
72
+ */
73
+ arrowRef?: () => Element | null;
74
+ /**
75
+ * A ref for the scrollable region within the overlay.
76
+ * @default overlayRef
77
+ */
78
+ scrollRef?: () => Element | null;
79
+ /**
80
+ * Whether the overlay should update its position automatically.
81
+ * @default true
82
+ */
83
+ shouldUpdatePosition?: boolean;
84
+ /** Handler that is called when the overlay should close. */
85
+ onClose?: (() => void) | null;
86
+ /**
87
+ * The maxHeight specified for the overlay element.
88
+ * By default, it will take all space up to the current viewport height.
89
+ */
90
+ maxHeight?: number;
91
+ /**
92
+ * The minimum distance the arrow's edge should be from the edge of the overlay element.
93
+ * @default 0
94
+ */
95
+ arrowBoundaryOffset?: number;
96
+ }
97
+
98
+ export interface PositionAria {
99
+ /** Props for the overlay container element. */
100
+ overlayProps: JSX.HTMLAttributes<HTMLElement>;
101
+ /** Props for the overlay tip arrow if any. */
102
+ arrowProps: JSX.HTMLAttributes<HTMLElement>;
103
+ /** Placement of the overlay with respect to the overlay trigger. */
104
+ placement: () => PlacementAxis | null;
105
+ /** The origin of the target in the overlay's coordinate system. Useful for animations. */
106
+ triggerAnchorPoint: () => { x: number; y: number } | null;
107
+ /** Updates the position of the overlay. */
108
+ updatePosition: () => void;
109
+ }
110
+
111
+ const visualViewport = typeof document !== 'undefined' ? window.visualViewport : null;
112
+
113
+ function translateRTL(position: string, direction: string): string {
114
+ if (direction === 'rtl') {
115
+ return position.replace('start', 'right').replace('end', 'left');
116
+ }
117
+ return position.replace('start', 'left').replace('end', 'right');
118
+ }
119
+
120
+ /**
121
+ * Handles positioning overlays like popovers and menus relative to a trigger
122
+ * element, and updating the position when the window resizes.
123
+ */
124
+ export function createOverlayPosition(props: AriaPositionProps): PositionAria {
125
+ const direction = 'ltr'; // TODO: get from locale context
126
+
127
+ const arrowSize = () => props.arrowSize ?? 0;
128
+ const targetRef = () => props.targetRef();
129
+ const overlayRef = () => props.overlayRef();
130
+ const arrowRef = () => props.arrowRef?.() ?? null;
131
+ const scrollRef = () => props.scrollRef?.() ?? overlayRef();
132
+ const placement = () => (props.placement ?? 'bottom') as Placement;
133
+ const containerPadding = () => props.containerPadding ?? 12;
134
+ const shouldFlip = () => props.shouldFlip ?? true;
135
+ const boundaryElement = () =>
136
+ props.boundaryElement ?? (typeof document !== 'undefined' ? document.body : null);
137
+ const offset = () => props.offset ?? 0;
138
+ const crossOffset = () => props.crossOffset ?? 0;
139
+ const shouldUpdatePosition = () => props.shouldUpdatePosition ?? true;
140
+ const isOpen = () => props.isOpen ?? true;
141
+ const onClose = () => props.onClose;
142
+ const maxHeight = () => props.maxHeight;
143
+ const arrowBoundaryOffset = () => props.arrowBoundaryOffset ?? 0;
144
+
145
+ const [position, setPosition] = createSignal<PositionResult | null>(null);
146
+
147
+ // Track the last scale to freeze overlay during pinch zoom
148
+ let lastScale = visualViewport?.scale;
149
+
150
+ createEffect(() => {
151
+ if (isOpen()) {
152
+ lastScale = visualViewport?.scale;
153
+ }
154
+ });
155
+
156
+ const updatePosition = () => {
157
+ const overlayNode = overlayRef();
158
+ const targetNode = targetRef();
159
+ const boundary = boundaryElement();
160
+
161
+ if (!shouldUpdatePosition() || !isOpen() || !overlayNode || !targetNode || !boundary) {
162
+ return;
163
+ }
164
+
165
+ if (visualViewport?.scale !== lastScale) {
166
+ return;
167
+ }
168
+
169
+ const scrollNode = scrollRef();
170
+ const arrowNode = arrowRef();
171
+
172
+ // Reset overlay's previous max height
173
+ const overlay = overlayNode as HTMLElement;
174
+ if (!maxHeight() && overlayNode) {
175
+ overlay.style.top = '0px';
176
+ overlay.style.bottom = '';
177
+ overlay.style.maxHeight = (window.visualViewport?.height ?? window.innerHeight) + 'px';
178
+ }
179
+
180
+ const result = calculatePosition({
181
+ placement: translateRTL(placement(), direction) as Placement,
182
+ overlayNode,
183
+ targetNode,
184
+ scrollNode: scrollNode || overlayNode,
185
+ padding: containerPadding(),
186
+ shouldFlip: shouldFlip(),
187
+ boundaryElement: boundary,
188
+ offset: offset(),
189
+ crossOffset: crossOffset(),
190
+ maxHeight: maxHeight(),
191
+ arrowSize: arrowSize() ?? (arrowNode ? getRect(arrowNode, true).width : 0),
192
+ arrowBoundaryOffset: arrowBoundaryOffset(),
193
+ });
194
+
195
+ if (!result.position) {
196
+ return;
197
+ }
198
+
199
+ // Apply styles directly for immediate positioning
200
+ overlay.style.top = '';
201
+ overlay.style.bottom = '';
202
+ overlay.style.left = '';
203
+ overlay.style.right = '';
204
+
205
+ Object.keys(result.position).forEach((key) => {
206
+ (overlay.style as any)[key] = (result.position as any)[key] + 'px';
207
+ });
208
+ overlay.style.maxHeight = result.maxHeight != null ? result.maxHeight + 'px' : '';
209
+
210
+ setPosition(result);
211
+ };
212
+
213
+ // Update position when dependencies change
214
+ createEffect(() => {
215
+ // Track all dependencies
216
+ shouldUpdatePosition();
217
+ placement();
218
+ overlayRef();
219
+ targetRef();
220
+ arrowRef();
221
+ scrollRef();
222
+ containerPadding();
223
+ shouldFlip();
224
+ boundaryElement();
225
+ offset();
226
+ crossOffset();
227
+ isOpen();
228
+ maxHeight();
229
+ arrowBoundaryOffset();
230
+ arrowSize();
231
+
232
+ updatePosition();
233
+ });
234
+
235
+ // Update position on window resize
236
+ createEffect(() => {
237
+ if (!isOpen()) return;
238
+
239
+ const handleResize = () => updatePosition();
240
+ window.addEventListener('resize', handleResize, false);
241
+
242
+ onCleanup(() => {
243
+ window.removeEventListener('resize', handleResize, false);
244
+ });
245
+ });
246
+
247
+ // Update position when overlay changes size using ResizeObserver
248
+ createEffect(() => {
249
+ const overlayNode = overlayRef();
250
+ if (!overlayNode || !isOpen()) return;
251
+
252
+ const resizeObserver = new ResizeObserver(() => updatePosition());
253
+ resizeObserver.observe(overlayNode);
254
+
255
+ onCleanup(() => {
256
+ resizeObserver.disconnect();
257
+ });
258
+ });
259
+
260
+ // Update position when target changes size
261
+ createEffect(() => {
262
+ const targetNode = targetRef();
263
+ if (!targetNode || !isOpen()) return;
264
+
265
+ const resizeObserver = new ResizeObserver(() => updatePosition());
266
+ resizeObserver.observe(targetNode);
267
+
268
+ onCleanup(() => {
269
+ resizeObserver.disconnect();
270
+ });
271
+ });
272
+
273
+ // Handle visual viewport resize (for iOS virtual keyboard)
274
+ createEffect(() => {
275
+ if (!isOpen()) return;
276
+
277
+ let timeout: ReturnType<typeof setTimeout>;
278
+ let isResizing = false;
279
+
280
+ const onResize = () => {
281
+ isResizing = true;
282
+ clearTimeout(timeout);
283
+ timeout = setTimeout(() => {
284
+ isResizing = false;
285
+ }, 500);
286
+ updatePosition();
287
+ };
288
+
289
+ const onScroll = () => {
290
+ if (isResizing) {
291
+ onResize();
292
+ }
293
+ };
294
+
295
+ visualViewport?.addEventListener('resize', onResize);
296
+ visualViewport?.addEventListener('scroll', onScroll);
297
+
298
+ onCleanup(() => {
299
+ visualViewport?.removeEventListener('resize', onResize);
300
+ visualViewport?.removeEventListener('scroll', onScroll);
301
+ clearTimeout(timeout);
302
+ });
303
+ });
304
+
305
+ // Close on scroll (when scrolling a parent of the trigger)
306
+ createEffect(() => {
307
+ const targetNode = targetRef();
308
+ const closeHandler = onClose();
309
+ if (!targetNode || !isOpen() || !closeHandler) return;
310
+
311
+ const handleScroll = (e: Event) => {
312
+ const target = e.target as Element;
313
+ // Don't close if scrolling within the overlay
314
+ if (overlayRef()?.contains(target)) return;
315
+ // Close if scrolling a parent of the target (but not body/html)
316
+ if (
317
+ target !== document.body &&
318
+ target !== document.documentElement &&
319
+ target.contains(targetNode)
320
+ ) {
321
+ closeHandler();
322
+ }
323
+ };
324
+
325
+ document.addEventListener('scroll', handleScroll, true);
326
+
327
+ onCleanup(() => {
328
+ document.removeEventListener('scroll', handleScroll, true);
329
+ });
330
+ });
331
+
332
+ return {
333
+ overlayProps: {
334
+ style: {
335
+ position: position() ? 'absolute' : 'fixed',
336
+ top: !position() ? 0 : undefined,
337
+ left: !position() ? 0 : undefined,
338
+ 'z-index': 100000,
339
+ 'max-height': position()?.maxHeight ?? '100vh',
340
+ } as JSX.CSSProperties,
341
+ },
342
+ placement: () => position()?.placement ?? null,
343
+ triggerAnchorPoint: () => position()?.triggerAnchorPoint ?? null,
344
+ arrowProps: {
345
+ 'aria-hidden': 'true',
346
+ role: 'presentation',
347
+ style: {
348
+ left: position()?.arrowOffsetLeft != null ? `${position()!.arrowOffsetLeft}px` : undefined,
349
+ top: position()?.arrowOffsetTop != null ? `${position()!.arrowOffsetTop}px` : undefined,
350
+ } as JSX.CSSProperties,
351
+ },
352
+ updatePosition,
353
+ };
354
+ }
355
+
356
+ export { type Placement, type PlacementAxis } from './calculatePosition';
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Provides the behavior and accessibility implementation for a popover component.
3
+ * A popover is an overlay element positioned relative to a trigger.
4
+ *
5
+ * Ported from @react-aria/overlays usePopover.
6
+ */
7
+
8
+ import { createEffect, onCleanup, type JSX } from 'solid-js';
9
+ import { createOverlay } from '../overlays/createOverlay';
10
+ import {
11
+ createOverlayPosition,
12
+ type AriaPositionProps,
13
+ type PlacementAxis,
14
+ } from './createOverlayPosition';
15
+ import { createPreventScroll } from '../overlays/createPreventScroll';
16
+ import { ariaHideOutside, keepVisible } from '../overlays/ariaHideOutside';
17
+ import { mergeProps } from '../utils/mergeProps';
18
+
19
+ export interface OverlayTriggerState {
20
+ /** Whether the overlay is currently open. */
21
+ isOpen: () => boolean;
22
+ /** Opens the overlay. */
23
+ open: () => void;
24
+ /** Closes the overlay. */
25
+ close: () => void;
26
+ /** Toggles the overlay's open state. */
27
+ toggle: () => void;
28
+ }
29
+
30
+ export interface AriaPopoverProps
31
+ extends Omit<AriaPositionProps, 'isOpen' | 'onClose' | 'targetRef' | 'overlayRef'> {
32
+ /**
33
+ * The ref for the element which the popover positions itself with respect to.
34
+ */
35
+ triggerRef: () => Element | null;
36
+ /**
37
+ * The ref for the popover element.
38
+ */
39
+ popoverRef: () => Element | null;
40
+ /** A ref for the popover arrow element. */
41
+ arrowRef?: () => Element | null;
42
+ /**
43
+ * An optional ref for a group of popovers, e.g. submenus.
44
+ * When provided, this element is used to detect outside interactions
45
+ * and hiding elements from assistive technologies instead of the popoverRef.
46
+ */
47
+ groupRef?: () => Element | null;
48
+ /**
49
+ * Whether the popover is non-modal, i.e. elements outside the popover may be
50
+ * interacted with by assistive technologies.
51
+ *
52
+ * Most popovers should not use this option as it may negatively impact the screen
53
+ * reader experience. Only use with components such as combobox, which are designed
54
+ * to handle this situation carefully.
55
+ */
56
+ isNonModal?: boolean;
57
+ /**
58
+ * Whether pressing the escape key to close the popover should be disabled.
59
+ *
60
+ * Most popovers should not use this option. When set to true, an alternative
61
+ * way to close the popover with a keyboard must be provided.
62
+ *
63
+ * @default false
64
+ */
65
+ isKeyboardDismissDisabled?: boolean;
66
+ /**
67
+ * When user interacts with the argument element outside of the popover ref,
68
+ * return true if onClose should be called. This gives you a chance to filter
69
+ * out interaction with elements that should not dismiss the popover.
70
+ * By default, onClose will always be called on interaction outside the popover ref.
71
+ */
72
+ shouldCloseOnInteractOutside?: (element: Element) => boolean;
73
+ /**
74
+ * The type of trigger that opened the popover.
75
+ * Used for submenu detection.
76
+ */
77
+ trigger?: string;
78
+ }
79
+
80
+ export interface PopoverAria {
81
+ /** Props for the popover element. */
82
+ popoverProps: JSX.HTMLAttributes<HTMLElement>;
83
+ /** Props for the popover tip arrow if any. */
84
+ arrowProps: JSX.HTMLAttributes<HTMLElement>;
85
+ /** Props to apply to the underlay element, if any. */
86
+ underlayProps: JSX.HTMLAttributes<HTMLElement>;
87
+ /** Placement of the popover with respect to the trigger. */
88
+ placement: () => PlacementAxis | null;
89
+ /** The origin of the target in the overlay's coordinate system. Useful for animations. */
90
+ triggerAnchorPoint: () => { x: number; y: number } | null;
91
+ }
92
+
93
+ /**
94
+ * Provides the behavior and accessibility implementation for a popover component.
95
+ * A popover is an overlay element positioned relative to a trigger.
96
+ */
97
+ export function createPopover(
98
+ props: AriaPopoverProps,
99
+ state: OverlayTriggerState
100
+ ): PopoverAria {
101
+ const triggerRef = () => props.triggerRef();
102
+ const popoverRef = () => props.popoverRef();
103
+ const groupRef = () => props.groupRef?.() ?? null;
104
+ const isNonModal = () => props.isNonModal ?? false;
105
+ const isKeyboardDismissDisabled = () => props.isKeyboardDismissDisabled ?? false;
106
+ const shouldCloseOnInteractOutside = props.shouldCloseOnInteractOutside;
107
+ const isSubmenu = () => props.trigger === 'SubmenuTrigger';
108
+
109
+ // Overlay behavior (dismiss handling)
110
+ const { overlayProps, underlayProps } = createOverlay(
111
+ {
112
+ isOpen: state.isOpen(),
113
+ onClose: state.close,
114
+ shouldCloseOnBlur: true,
115
+ isDismissable: !isNonModal() || isSubmenu(),
116
+ isKeyboardDismissDisabled: isKeyboardDismissDisabled(),
117
+ shouldCloseOnInteractOutside,
118
+ },
119
+ () => groupRef() ?? popoverRef()
120
+ );
121
+
122
+ // Overlay positioning
123
+ const {
124
+ overlayProps: positionProps,
125
+ arrowProps,
126
+ placement,
127
+ triggerAnchorPoint,
128
+ } = createOverlayPosition({
129
+ ...props,
130
+ targetRef: triggerRef,
131
+ overlayRef: popoverRef,
132
+ isOpen: state.isOpen(),
133
+ onClose: isNonModal() && !isSubmenu() ? state.close : null,
134
+ });
135
+
136
+ // Prevent scroll when modal popover is open
137
+ createPreventScroll({
138
+ isDisabled: isNonModal() || !state.isOpen(),
139
+ });
140
+
141
+ // Aria-hide outside elements
142
+ createEffect(() => {
143
+ if (state.isOpen() && popoverRef()) {
144
+ const element = groupRef() ?? popoverRef();
145
+ if (!element) return;
146
+
147
+ let cleanup: (() => void) | undefined;
148
+
149
+ if (isNonModal()) {
150
+ cleanup = keepVisible(element);
151
+ } else {
152
+ cleanup = ariaHideOutside([element], { shouldUseInert: true });
153
+ }
154
+
155
+ onCleanup(() => {
156
+ cleanup?.();
157
+ });
158
+ }
159
+ });
160
+
161
+ return {
162
+ popoverProps: mergeProps(overlayProps, positionProps),
163
+ arrowProps,
164
+ underlayProps,
165
+ placement,
166
+ triggerAnchorPoint,
167
+ };
168
+ }
169
+
170
+ export { type PlacementAxis } from './createOverlayPosition';
@@ -0,0 +1,24 @@
1
+ // Popover
2
+ export {
3
+ createPopover,
4
+ type AriaPopoverProps,
5
+ type PopoverAria,
6
+ type OverlayTriggerState,
7
+ } from './createPopover';
8
+
9
+ // Overlay Position
10
+ export {
11
+ createOverlayPosition,
12
+ type AriaPositionProps,
13
+ type PositionProps,
14
+ type PositionAria,
15
+ } from './createOverlayPosition';
16
+
17
+ // Position calculation utilities
18
+ export {
19
+ calculatePosition,
20
+ type Placement,
21
+ type PlacementAxis,
22
+ type PositionOpts,
23
+ type PositionResult,
24
+ } from './calculatePosition';
@@ -0,0 +1,128 @@
1
+ /**
2
+ * ProgressBar hook for Solidaria
3
+ *
4
+ * Provides the accessibility implementation for a progress bar component.
5
+ * Progress bars show either determinate or indeterminate progress of an operation
6
+ * over time.
7
+ *
8
+ * This is a 1:1 port of @react-aria/progress's useProgressBar hook.
9
+ */
10
+
11
+ import { createLabel } from '../label/createLabel';
12
+ import { mergeProps } from '../utils/mergeProps';
13
+ import { filterDOMProps } from '../utils/filterDOMProps';
14
+ import { type MaybeAccessor, access } from '../utils/reactivity';
15
+
16
+ // ============================================
17
+ // TYPES
18
+ // ============================================
19
+
20
+ export interface AriaProgressBarProps {
21
+ /** The current value (controlled). */
22
+ value?: number;
23
+ /** The smallest value allowed for the input. @default 0 */
24
+ minValue?: number;
25
+ /** The largest value allowed for the input. @default 100 */
26
+ maxValue?: number;
27
+ /** The content to display as the value's label (e.g. 1 of 4). */
28
+ valueLabel?: string;
29
+ /** Whether presentation is indeterminate when progress isn't known. */
30
+ isIndeterminate?: boolean;
31
+ /** The display format of the value label. */
32
+ formatOptions?: Intl.NumberFormatOptions;
33
+ /** The content to display as the label. */
34
+ label?: string;
35
+ /** An accessibility label for this item. */
36
+ 'aria-label'?: string;
37
+ /** Identifies the element (or elements) that labels the current element. */
38
+ 'aria-labelledby'?: string;
39
+ /** Identifies the element (or elements) that describes the object. */
40
+ 'aria-describedby'?: string;
41
+ /** Identifies the element (or elements) that provide a detailed, extended description for the object. */
42
+ 'aria-details'?: string;
43
+ }
44
+
45
+ export interface ProgressBarAria {
46
+ /** Props for the progress bar container element. */
47
+ progressBarProps: Record<string, unknown>;
48
+ /** Props for the progress bar's visual label element (if any). */
49
+ labelProps: Record<string, unknown>;
50
+ }
51
+
52
+ // ============================================
53
+ // UTILITIES
54
+ // ============================================
55
+
56
+ function clamp(value: number, min: number, max: number): number {
57
+ return Math.min(Math.max(value, min), max);
58
+ }
59
+
60
+ // ============================================
61
+ // IMPLEMENTATION
62
+ // ============================================
63
+
64
+ /**
65
+ * Provides the accessibility implementation for a progress bar component.
66
+ * Progress bars show either determinate or indeterminate progress of an operation
67
+ * over time.
68
+ */
69
+ export function createProgressBar(
70
+ props: MaybeAccessor<AriaProgressBarProps> = {}
71
+ ): ProgressBarAria {
72
+ const getProps = () => access(props);
73
+
74
+ // Create label handling
75
+ const { labelProps, fieldProps } = createLabel({
76
+ get label() { return getProps().label; },
77
+ get 'aria-label'() { return getProps()['aria-label']; },
78
+ get 'aria-labelledby'() { return getProps()['aria-labelledby']; },
79
+ // Progress bar is not an HTML input element so it
80
+ // shouldn't be labeled by a <label> element.
81
+ labelElementType: 'span',
82
+ });
83
+
84
+ // Build progress bar props
85
+ const getProgressBarProps = (): Record<string, unknown> => {
86
+ const p = getProps();
87
+ const value = p.value ?? 0;
88
+ const minValue = p.minValue ?? 0;
89
+ const maxValue = p.maxValue ?? 100;
90
+ const isIndeterminate = p.isIndeterminate ?? false;
91
+ const formatOptions = p.formatOptions ?? { style: 'percent' as const };
92
+
93
+ const clampedValue = clamp(value, minValue, maxValue);
94
+ const percentage = (clampedValue - minValue) / (maxValue - minValue);
95
+
96
+ // Format value label
97
+ let valueLabel = p.valueLabel;
98
+ if (!isIndeterminate && !valueLabel) {
99
+ const valueToFormat = formatOptions.style === 'percent' ? percentage : clampedValue;
100
+ try {
101
+ const formatter = new Intl.NumberFormat(undefined, formatOptions);
102
+ valueLabel = formatter.format(valueToFormat);
103
+ } catch {
104
+ // Fallback if formatting fails
105
+ valueLabel = `${Math.round(percentage * 100)}%`;
106
+ }
107
+ }
108
+
109
+ const domProps = filterDOMProps(p as Record<string, unknown>, { labelable: true });
110
+
111
+ return mergeProps(domProps, fieldProps as Record<string, unknown>, {
112
+ 'aria-valuenow': isIndeterminate ? undefined : clampedValue,
113
+ 'aria-valuemin': minValue,
114
+ 'aria-valuemax': maxValue,
115
+ 'aria-valuetext': isIndeterminate ? undefined : valueLabel,
116
+ role: 'progressbar',
117
+ });
118
+ };
119
+
120
+ return {
121
+ get progressBarProps() {
122
+ return getProgressBarProps();
123
+ },
124
+ get labelProps() {
125
+ return labelProps as Record<string, unknown>;
126
+ },
127
+ };
128
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ createProgressBar,
3
+ type AriaProgressBarProps,
4
+ type ProgressBarAria,
5
+ } from './createProgressBar';