@proyecto-viviana/solidaria-components 0.2.4 → 0.2.9
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.
- package/LICENSE +21 -0
- package/dist/ActionBar.d.ts +71 -0
- package/dist/ActionBar.d.ts.map +1 -0
- package/dist/ActionGroup.d.ts +74 -0
- package/dist/ActionGroup.d.ts.map +1 -0
- package/dist/Alert.d.ts +70 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Breadcrumbs.d.ts +10 -2
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +4 -0
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +13 -0
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +2 -2
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +125 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +114 -2
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +42 -0
- package/dist/ColorEditor.d.ts.map +1 -0
- package/dist/ComboBox.d.ts +64 -0
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +40 -0
- package/dist/ContextualHelpTrigger.d.ts.map +1 -0
- package/dist/DateField.d.ts +27 -2
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +67 -2
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +2 -0
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +80 -0
- package/dist/DragAndDrop.d.ts.map +1 -0
- package/dist/DragPreview.d.ts +14 -0
- package/dist/DragPreview.d.ts.map +1 -0
- package/dist/DropZone.d.ts +27 -0
- package/dist/DropZone.d.ts.map +1 -0
- package/dist/FieldError.d.ts +23 -0
- package/dist/FieldError.d.ts.map +1 -0
- package/dist/FileTrigger.d.ts +26 -0
- package/dist/FileTrigger.d.ts.map +1 -0
- package/dist/Focusable.d.ts +27 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Form.d.ts +27 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +40 -1
- package/dist/GridList.d.ts.map +1 -1
- package/dist/Icon.d.ts +57 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/Keyboard.d.ts +13 -0
- package/dist/Keyboard.d.ts.map +1 -0
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +43 -1
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +38 -0
- package/dist/ListDropTargetDelegate.d.ts.map +1 -0
- package/dist/Menu.d.ts +20 -2
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +2 -2
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +2 -0
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +2 -0
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +4 -2
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +27 -0
- package/dist/Pressable.d.ts.map +1 -0
- package/dist/ProgressBar.d.ts +2 -2
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +5 -0
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +75 -0
- package/dist/RouterProvider.d.ts.map +1 -0
- package/dist/SearchField.d.ts +2 -3
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +11 -0
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +30 -0
- package/dist/SelectionIndicator.d.ts.map +1 -0
- package/dist/SharedElementTransition.d.ts +39 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +6 -3
- package/dist/Slider.d.ts.map +1 -1
- package/dist/Table.d.ts +39 -0
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +4 -3
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -2
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +10 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/TextField.d.ts +4 -0
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +26 -1
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +30 -0
- package/dist/ToggleButton.d.ts.map +1 -0
- package/dist/ToggleButtonGroup.d.ts +33 -0
- package/dist/ToggleButtonGroup.d.ts.map +1 -0
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +9 -0
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +44 -2
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +61 -0
- package/dist/Virtualizer.d.ts.map +1 -0
- package/dist/VirtualizerLayouts.d.ts +82 -0
- package/dist/VirtualizerLayouts.d.ts.map +1 -0
- package/dist/VisuallyHidden.d.ts +3 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +1 -0
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +57 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13961 -5946
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +9612 -2401
- package/dist/index.ssr.js.map +1 -7
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +7 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +79 -0
- package/dist/virtualizer/Layout.d.ts.map +1 -0
- package/package.json +8 -6
- package/src/ActionBar.tsx +248 -0
- package/src/ActionGroup.tsx +285 -0
- package/src/Alert.tsx +177 -0
- package/src/Autocomplete.tsx +1 -1
- package/src/Breadcrumbs.tsx +103 -17
- package/src/Button.tsx +65 -21
- package/src/Calendar.tsx +179 -53
- package/src/Checkbox.tsx +1 -2
- package/src/Collection.tsx +341 -0
- package/src/Color.tsx +652 -34
- package/src/ColorEditor.tsx +231 -0
- package/src/ComboBox.tsx +315 -81
- package/src/ContextualHelpTrigger.tsx +183 -0
- package/src/DateField.tsx +93 -19
- package/src/DatePicker.tsx +495 -25
- package/src/Dialog.tsx +40 -9
- package/src/Disclosure.tsx +33 -27
- package/src/DragAndDrop.tsx +334 -0
- package/src/DragPreview.tsx +45 -0
- package/src/DropZone.tsx +213 -0
- package/src/FieldError.tsx +67 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +106 -0
- package/src/Form.tsx +85 -0
- package/src/GridList.tsx +379 -41
- package/src/Icon.tsx +154 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Link.tsx +14 -1
- package/src/ListBox.tsx +484 -33
- package/src/ListDropTargetDelegate.ts +282 -0
- package/src/Menu.tsx +388 -35
- package/src/Meter.tsx +7 -3
- package/src/Modal.tsx +32 -4
- package/src/NumberField.tsx +163 -43
- package/src/Popover.tsx +136 -180
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +7 -3
- package/src/RadioGroup.tsx +35 -25
- package/src/RangeCalendar.tsx +100 -68
- package/src/RouterProvider.tsx +240 -0
- package/src/SearchField.tsx +142 -34
- package/src/Select.tsx +221 -73
- package/src/SelectionIndicator.tsx +105 -0
- package/src/SharedElementTransition.tsx +258 -0
- package/src/Slider.tsx +16 -6
- package/src/Table.tsx +417 -57
- package/src/Tabs.tsx +68 -35
- package/src/TagGroup.tsx +121 -36
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +25 -8
- package/src/TimeField.tsx +101 -151
- package/src/Toast.tsx +108 -14
- package/src/ToggleButton.tsx +159 -0
- package/src/ToggleButtonGroup.tsx +136 -0
- package/src/Toolbar.tsx +14 -8
- package/src/Tooltip.tsx +108 -19
- package/src/Tree.tsx +1143 -87
- package/src/Virtualizer.tsx +702 -0
- package/src/VirtualizerLayouts.ts +265 -0
- package/src/VisuallyHidden.tsx +15 -21
- package/src/contexts.ts +1 -0
- package/src/index.ts +1057 -620
- package/src/useDragAndDrop.ts +351 -0
- package/src/utils.tsx +37 -3
- package/src/virtualizer/Layout.ts +200 -0
package/src/Popover.tsx
CHANGED
|
@@ -8,19 +8,21 @@
|
|
|
8
8
|
import {
|
|
9
9
|
type JSX,
|
|
10
10
|
createContext,
|
|
11
|
+
createEffect,
|
|
11
12
|
createMemo,
|
|
12
13
|
createSignal,
|
|
13
14
|
createUniqueId,
|
|
15
|
+
onCleanup,
|
|
14
16
|
splitProps,
|
|
15
17
|
useContext,
|
|
16
18
|
Show,
|
|
17
|
-
createEffect,
|
|
18
|
-
onCleanup,
|
|
19
19
|
} from 'solid-js'
|
|
20
20
|
import { Portal, isServer } from 'solid-js/web'
|
|
21
21
|
import {
|
|
22
22
|
createOverlayTrigger,
|
|
23
|
+
createPopover,
|
|
23
24
|
FocusScope,
|
|
25
|
+
useUNSAFE_PortalContext,
|
|
24
26
|
type Placement,
|
|
25
27
|
type PlacementAxis,
|
|
26
28
|
} from '@proyecto-viviana/solidaria'
|
|
@@ -145,8 +147,13 @@ export interface PopoverTriggerProps {
|
|
|
145
147
|
// Re-export from shared contexts
|
|
146
148
|
export { PopoverTriggerContext, usePopoverTrigger, type PopoverTriggerContextValue } from './contexts'
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
interface PopoverContextValue {
|
|
151
|
+
placement: () => PlacementAxis | null
|
|
152
|
+
arrowProps: () => JSX.HTMLAttributes<HTMLElement>
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Internal context for placement + arrow
|
|
156
|
+
export const PopoverContext = createContext<PopoverContextValue | null>(null)
|
|
150
157
|
|
|
151
158
|
// ============================================
|
|
152
159
|
// POPOVER TRIGGER COMPONENT
|
|
@@ -181,9 +188,6 @@ export function PopoverTrigger(props: PopoverTriggerProps): JSX.Element {
|
|
|
181
188
|
() => triggerRef
|
|
182
189
|
)
|
|
183
190
|
|
|
184
|
-
// Track if triggerRef has been set (to prevent buttons inside Popover from overwriting it)
|
|
185
|
-
let triggerRefSet = false
|
|
186
|
-
|
|
187
191
|
// Context value
|
|
188
192
|
const contextValue = createMemo(() => ({
|
|
189
193
|
state: {
|
|
@@ -192,28 +196,17 @@ export function PopoverTrigger(props: PopoverTriggerProps): JSX.Element {
|
|
|
192
196
|
close: () => state.close(),
|
|
193
197
|
toggle: () => state.toggle(),
|
|
194
198
|
},
|
|
195
|
-
triggerRef: () =>
|
|
196
|
-
return triggerRef
|
|
197
|
-
},
|
|
199
|
+
triggerRef: () => triggerRef,
|
|
198
200
|
setTriggerRef: (el: HTMLElement | null) => {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!triggerRefSet && el) {
|
|
201
|
+
if (!el) return
|
|
202
|
+
if (!triggerRef || !triggerRef.isConnected) {
|
|
202
203
|
triggerRef = el
|
|
203
|
-
triggerRefSet = true
|
|
204
204
|
}
|
|
205
205
|
},
|
|
206
206
|
triggerId,
|
|
207
207
|
trigger: 'PopoverTrigger',
|
|
208
208
|
}))
|
|
209
209
|
|
|
210
|
-
// Just render children inside the provider - the Button component will detect
|
|
211
|
-
// the PopoverTriggerContext and handle toggling. The Popover component will
|
|
212
|
-
// detect the context and use it for open state.
|
|
213
|
-
//
|
|
214
|
-
// IMPORTANT: In SolidJS, JSX children are lazily evaluated when they're part
|
|
215
|
-
// of JSX expression. So {props.children} here means the children (Button, Popover)
|
|
216
|
-
// will be evaluated inside the Provider context.
|
|
217
210
|
return (
|
|
218
211
|
<PopoverTriggerContext.Provider value={contextValue()}>
|
|
219
212
|
{props.children}
|
|
@@ -290,160 +283,70 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
290
283
|
return null
|
|
291
284
|
}
|
|
292
285
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
document.addEventListener('mousedown', handleClickOutside, true)
|
|
351
|
-
}, 0)
|
|
352
|
-
|
|
353
|
-
onCleanup(() => {
|
|
354
|
-
clearTimeout(timeoutId)
|
|
355
|
-
document.removeEventListener('mousedown', handleClickOutside, true)
|
|
356
|
-
})
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
// Calculate position based on trigger element
|
|
360
|
-
// Using position: fixed so we use viewport coordinates directly from getBoundingClientRect
|
|
361
|
-
const updatePosition = () => {
|
|
362
|
-
const trigger = getTriggerRef()
|
|
363
|
-
const popover = popoverRef
|
|
364
|
-
if (!trigger || !popover) return
|
|
365
|
-
|
|
366
|
-
const triggerRect = trigger.getBoundingClientRect()
|
|
367
|
-
// Use offsetWidth/offsetHeight which are more reliable than getBoundingClientRect
|
|
368
|
-
// when the element might be positioned off-screen initially
|
|
369
|
-
const popoverWidth = popover.offsetWidth
|
|
370
|
-
const popoverHeight = popover.offsetHeight
|
|
371
|
-
const offset = local.offset ?? 8
|
|
372
|
-
|
|
373
|
-
let top = 0
|
|
374
|
-
let left = 0
|
|
375
|
-
const placementValue = local.placement ?? 'bottom'
|
|
376
|
-
|
|
377
|
-
// Using viewport coordinates for position: fixed
|
|
378
|
-
switch (placementValue) {
|
|
379
|
-
case 'top':
|
|
380
|
-
case 'top start':
|
|
381
|
-
case 'top end':
|
|
382
|
-
top = triggerRect.top - popoverHeight - offset
|
|
383
|
-
left = triggerRect.left + (triggerRect.width - popoverWidth) / 2
|
|
384
|
-
setPlacement('top')
|
|
385
|
-
break
|
|
386
|
-
case 'bottom':
|
|
387
|
-
case 'bottom start':
|
|
388
|
-
case 'bottom end':
|
|
389
|
-
top = triggerRect.bottom + offset
|
|
390
|
-
left = triggerRect.left + (triggerRect.width - popoverWidth) / 2
|
|
391
|
-
setPlacement('bottom')
|
|
392
|
-
break
|
|
393
|
-
case 'left':
|
|
394
|
-
case 'left top':
|
|
395
|
-
case 'left bottom':
|
|
396
|
-
top = triggerRect.top + (triggerRect.height - popoverHeight) / 2
|
|
397
|
-
left = triggerRect.left - popoverWidth - offset
|
|
398
|
-
setPlacement('left')
|
|
399
|
-
break
|
|
400
|
-
case 'right':
|
|
401
|
-
case 'right top':
|
|
402
|
-
case 'right bottom':
|
|
403
|
-
top = triggerRect.top + (triggerRect.height - popoverHeight) / 2
|
|
404
|
-
left = triggerRect.right + offset
|
|
405
|
-
setPlacement('right')
|
|
406
|
-
break
|
|
407
|
-
default:
|
|
408
|
-
top = triggerRect.bottom + offset
|
|
409
|
-
left = triggerRect.left + (triggerRect.width - popoverWidth) / 2
|
|
410
|
-
setPlacement('bottom')
|
|
286
|
+
const popoverAria = createPopover(
|
|
287
|
+
{
|
|
288
|
+
triggerRef: getTriggerRef,
|
|
289
|
+
popoverRef: () => popoverRef ?? null,
|
|
290
|
+
get placement() {
|
|
291
|
+
return local.placement
|
|
292
|
+
},
|
|
293
|
+
get containerPadding() {
|
|
294
|
+
return local.containerPadding
|
|
295
|
+
},
|
|
296
|
+
get offset() {
|
|
297
|
+
return local.offset ?? 8
|
|
298
|
+
},
|
|
299
|
+
get crossOffset() {
|
|
300
|
+
return local.crossOffset
|
|
301
|
+
},
|
|
302
|
+
get shouldFlip() {
|
|
303
|
+
return local.shouldFlip
|
|
304
|
+
},
|
|
305
|
+
get isNonModal() {
|
|
306
|
+
return local.isNonModal
|
|
307
|
+
},
|
|
308
|
+
get isKeyboardDismissDisabled() {
|
|
309
|
+
return local.isKeyboardDismissDisabled
|
|
310
|
+
},
|
|
311
|
+
get shouldCloseOnInteractOutside() {
|
|
312
|
+
return local.shouldCloseOnInteractOutside
|
|
313
|
+
},
|
|
314
|
+
get trigger() {
|
|
315
|
+
return local.trigger ?? triggerContext?.trigger
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
isOpen,
|
|
320
|
+
open: () => {
|
|
321
|
+
if (local.isOpen !== undefined) {
|
|
322
|
+
local.onOpenChange?.(true)
|
|
323
|
+
} else if (triggerContext) {
|
|
324
|
+
triggerContext.state.open()
|
|
325
|
+
local.onOpenChange?.(true)
|
|
326
|
+
} else {
|
|
327
|
+
setInternalOpen(true)
|
|
328
|
+
local.onOpenChange?.(true)
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
close,
|
|
332
|
+
toggle: () => {
|
|
333
|
+
if (isOpen()) close()
|
|
334
|
+
else if (local.isOpen !== undefined) {
|
|
335
|
+
local.onOpenChange?.(true)
|
|
336
|
+
} else if (triggerContext) {
|
|
337
|
+
triggerContext.state.toggle()
|
|
338
|
+
} else {
|
|
339
|
+
setInternalOpen(true)
|
|
340
|
+
local.onOpenChange?.(true)
|
|
341
|
+
}
|
|
342
|
+
},
|
|
411
343
|
}
|
|
412
|
-
|
|
413
|
-
setPositionStyles({
|
|
414
|
-
top: `${top}px`,
|
|
415
|
-
left: `${left}px`,
|
|
416
|
-
visibility: 'visible',
|
|
417
|
-
})
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Set up positioning effect - runs when open and trigger ref is available
|
|
421
|
-
createEffect(() => {
|
|
422
|
-
if (!isOpen()) return
|
|
423
|
-
|
|
424
|
-
const triggerElement = getTriggerRef()
|
|
425
|
-
if (!triggerElement) return
|
|
426
|
-
|
|
427
|
-
// Initial position calculation - use requestAnimationFrame to ensure
|
|
428
|
-
// the element is rendered and has proper dimensions
|
|
429
|
-
requestAnimationFrame(() => {
|
|
430
|
-
updatePosition()
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
// Update on scroll/resize
|
|
434
|
-
window.addEventListener('scroll', updatePosition, true)
|
|
435
|
-
window.addEventListener('resize', updatePosition)
|
|
436
|
-
|
|
437
|
-
onCleanup(() => {
|
|
438
|
-
window.removeEventListener('scroll', updatePosition, true)
|
|
439
|
-
window.removeEventListener('resize', updatePosition)
|
|
440
|
-
})
|
|
441
|
-
})
|
|
344
|
+
)
|
|
442
345
|
|
|
443
346
|
// Render props values
|
|
444
347
|
const renderValues = createMemo<PopoverRenderProps>(() => ({
|
|
445
348
|
trigger: local.trigger ?? triggerContext?.trigger ?? null,
|
|
446
|
-
placement: placement(),
|
|
349
|
+
placement: popoverAria.placement(),
|
|
447
350
|
isEntering: local.isEntering ?? false,
|
|
448
351
|
isExiting: local.isExiting ?? false,
|
|
449
352
|
}))
|
|
@@ -462,28 +365,65 @@ export function Popover(props: PopoverProps): JSX.Element {
|
|
|
462
365
|
// Filter DOM props
|
|
463
366
|
const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }))
|
|
464
367
|
|
|
368
|
+
// Remove style/ref from spread props to avoid collisions
|
|
369
|
+
const cleanPopoverProps = () => {
|
|
370
|
+
const { style: _style, ref: _ref, ...remaining } = popoverAria.popoverProps as Record<string, unknown>
|
|
371
|
+
return remaining
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const mergedStyle = (): JSX.CSSProperties => {
|
|
375
|
+
const ariaStyle = (popoverAria.popoverProps as Record<string, unknown>).style as JSX.CSSProperties | undefined
|
|
376
|
+
const renderStyle = renderProps.style() || {}
|
|
377
|
+
return {
|
|
378
|
+
...(ariaStyle ?? {}),
|
|
379
|
+
...renderStyle,
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
465
383
|
// Check if we should render with dialog role
|
|
466
384
|
const shouldBeDialog = () => !local.isNonModal
|
|
385
|
+
const portalContext = useUNSAFE_PortalContext()
|
|
386
|
+
const portalContainer = () => portalContext.getContainer?.() ?? undefined
|
|
387
|
+
|
|
388
|
+
// Ensure Escape handling works even when popover content has no focusable children.
|
|
389
|
+
createEffect(() => {
|
|
390
|
+
if (!isOpen() || !shouldBeDialog()) return
|
|
391
|
+
if (!popoverRef) return
|
|
392
|
+
if (document.activeElement !== popoverRef) {
|
|
393
|
+
popoverRef.focus()
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
// Fallback Escape handling for environments where focus is not moved into the popover.
|
|
398
|
+
createEffect(() => {
|
|
399
|
+
if (!isOpen()) return
|
|
400
|
+
if (local.isKeyboardDismissDisabled) return
|
|
401
|
+
|
|
402
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
403
|
+
if (event.key !== 'Escape') return
|
|
404
|
+
if (event.defaultPrevented) return
|
|
405
|
+
close()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
document.addEventListener('keydown', onKeyDown)
|
|
409
|
+
onCleanup(() => document.removeEventListener('keydown', onKeyDown))
|
|
410
|
+
})
|
|
467
411
|
|
|
468
412
|
return (
|
|
469
413
|
<Show when={isOpen() || local.isExiting}>
|
|
470
|
-
<Portal>
|
|
471
|
-
<PopoverContext.Provider value={{ placement: () =>
|
|
414
|
+
<Portal mount={portalContainer()}>
|
|
415
|
+
<PopoverContext.Provider value={{ placement: popoverAria.placement, arrowProps: () => popoverAria.arrowProps }}>
|
|
472
416
|
<FocusScope contain={shouldBeDialog()} restoreFocus autoFocus>
|
|
473
417
|
<div
|
|
474
418
|
{...domProps()}
|
|
419
|
+
{...cleanPopoverProps()}
|
|
475
420
|
ref={popoverRef}
|
|
476
421
|
role={shouldBeDialog() ? 'dialog' : undefined}
|
|
477
422
|
tabIndex={shouldBeDialog() ? -1 : undefined}
|
|
478
423
|
class={renderProps.class()}
|
|
479
|
-
style={
|
|
480
|
-
position: 'fixed',
|
|
481
|
-
'z-index': 100000,
|
|
482
|
-
...positionStyles(),
|
|
483
|
-
...renderProps.style(),
|
|
484
|
-
}}
|
|
424
|
+
style={mergedStyle()}
|
|
485
425
|
data-trigger={local.trigger ?? triggerContext?.trigger}
|
|
486
|
-
data-placement={placement()}
|
|
426
|
+
data-placement={popoverAria.placement()}
|
|
487
427
|
data-entering={dataAttr(local.isEntering)}
|
|
488
428
|
data-exiting={dataAttr(local.isExiting)}
|
|
489
429
|
>
|
|
@@ -516,6 +456,21 @@ export function OverlayArrow(props: OverlayArrowProps): JSX.Element {
|
|
|
516
456
|
const popoverContext = useContext(PopoverContext)
|
|
517
457
|
const placement = () => popoverContext?.placement() ?? null
|
|
518
458
|
|
|
459
|
+
const cleanArrowProps = () => {
|
|
460
|
+
const contextArrowProps = popoverContext?.arrowProps() as Record<string, unknown> | undefined
|
|
461
|
+
if (!contextArrowProps) return {}
|
|
462
|
+
const { style: _style, ref: _ref, ...rest } = contextArrowProps
|
|
463
|
+
return rest
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const mergedStyle = () => {
|
|
467
|
+
const contextStyle = (popoverContext?.arrowProps() as Record<string, unknown> | undefined)?.style as JSX.CSSProperties | undefined
|
|
468
|
+
return {
|
|
469
|
+
...(contextStyle ?? {}),
|
|
470
|
+
...(props.style ?? {}),
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
519
474
|
const resolveChildren = () => {
|
|
520
475
|
const children = props.children
|
|
521
476
|
if (typeof children === 'function') {
|
|
@@ -526,8 +481,9 @@ export function OverlayArrow(props: OverlayArrowProps): JSX.Element {
|
|
|
526
481
|
|
|
527
482
|
return (
|
|
528
483
|
<div
|
|
484
|
+
{...cleanArrowProps()}
|
|
529
485
|
class={props.class}
|
|
530
|
-
style={
|
|
486
|
+
style={mergedStyle()}
|
|
531
487
|
data-placement={placement()}
|
|
532
488
|
aria-hidden="true"
|
|
533
489
|
role="presentation"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pressable component for solidaria-components
|
|
3
|
+
*
|
|
4
|
+
* A render-prop component that wraps createPress + createFocusable
|
|
5
|
+
* to make an element pressable. Port of React Aria's Pressable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type JSX, children as resolveChildren, createEffect, onCleanup, splitProps } from 'solid-js';
|
|
9
|
+
import {
|
|
10
|
+
createPress,
|
|
11
|
+
createFocusable,
|
|
12
|
+
type CreatePressProps,
|
|
13
|
+
type CreateFocusableProps,
|
|
14
|
+
} from '@proyecto-viviana/solidaria';
|
|
15
|
+
|
|
16
|
+
// ============================================
|
|
17
|
+
// TYPES
|
|
18
|
+
// ============================================
|
|
19
|
+
|
|
20
|
+
export interface PressableProps extends CreatePressProps {
|
|
21
|
+
/** A single child element to make pressable. */
|
|
22
|
+
children: JSX.Element;
|
|
23
|
+
/** Ref callback. */
|
|
24
|
+
ref?: HTMLElement | ((el: HTMLElement) => void);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ============================================
|
|
28
|
+
// COMPONENT
|
|
29
|
+
// ============================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Makes its child element pressable and focusable.
|
|
33
|
+
* Combines createPress and createFocusable for full interaction support.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* <Pressable onPress={() => console.log('pressed')}>
|
|
38
|
+
* <div role="button" tabIndex={0}>Click me</div>
|
|
39
|
+
* </Pressable>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function Pressable(props: PressableProps): JSX.Element {
|
|
43
|
+
const [local, pressProps] = splitProps(props, ['children', 'ref']);
|
|
44
|
+
|
|
45
|
+
let ref!: HTMLElement;
|
|
46
|
+
const { pressProps: domPressProps } = createPress(pressProps);
|
|
47
|
+
const { focusableProps: domFocusableProps } = createFocusable(pressProps as CreateFocusableProps, () => ref);
|
|
48
|
+
|
|
49
|
+
const resolved = resolveChildren(() => local.children);
|
|
50
|
+
|
|
51
|
+
createEffect(() => {
|
|
52
|
+
const child = resolved() as HTMLElement;
|
|
53
|
+
if (child instanceof HTMLElement) {
|
|
54
|
+
ref = child;
|
|
55
|
+
// Forward ref
|
|
56
|
+
if (typeof local.ref === 'function') {
|
|
57
|
+
local.ref(child);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Apply press props
|
|
61
|
+
const allProps = { ...domFocusableProps, ...domPressProps };
|
|
62
|
+
const listeners: Array<[string, EventListener]> = [];
|
|
63
|
+
for (const [key, handler] of Object.entries(allProps)) {
|
|
64
|
+
if (key.startsWith('on') && typeof handler === 'function') {
|
|
65
|
+
const eventName = key.slice(2).toLowerCase();
|
|
66
|
+
const listener = handler as EventListener;
|
|
67
|
+
child.addEventListener(eventName, listener);
|
|
68
|
+
listeners.push([eventName, listener]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Apply non-event press/focusable props (e.g. tabIndex/data attrs).
|
|
73
|
+
// Keep explicit child tabIndex to mirror mergeProps(child.props precedence).
|
|
74
|
+
for (const [key, value] of Object.entries(allProps)) {
|
|
75
|
+
if (key === 'ref' || (key.startsWith('on') && typeof value === 'function')) continue;
|
|
76
|
+
|
|
77
|
+
if (key === 'tabIndex') {
|
|
78
|
+
if (child.hasAttribute('tabindex')) continue;
|
|
79
|
+
if (value == null || value === false) {
|
|
80
|
+
child.removeAttribute('tabindex');
|
|
81
|
+
} else {
|
|
82
|
+
child.tabIndex = Number(value);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (key.startsWith('aria-') || key.startsWith('data-') || key === 'id' || key === 'role') {
|
|
88
|
+
if (child.hasAttribute(key)) continue;
|
|
89
|
+
if (value == null || value === false) {
|
|
90
|
+
child.removeAttribute(key);
|
|
91
|
+
} else if (value === true) {
|
|
92
|
+
child.setAttribute(key, '');
|
|
93
|
+
} else {
|
|
94
|
+
child.setAttribute(key, String(value));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onCleanup(() => {
|
|
100
|
+
for (const [eventName, listener] of listeners) {
|
|
101
|
+
child.removeEventListener(eventName, listener);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return <>{resolved()}</>;
|
|
108
|
+
}
|
package/src/ProgressBar.tsx
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
type JSX,
|
|
10
|
-
type ParentProps,
|
|
11
10
|
createContext,
|
|
12
11
|
createMemo,
|
|
13
12
|
splitProps,
|
|
@@ -63,6 +62,11 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
63
62
|
return Math.min(Math.max(value, min), max);
|
|
64
63
|
}
|
|
65
64
|
|
|
65
|
+
function getSafeRange(min: number, max: number): number {
|
|
66
|
+
const range = max - min;
|
|
67
|
+
return Number.isFinite(range) && range > 0 ? range : 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
// ============================================
|
|
67
71
|
// PROGRESSBAR COMPONENT
|
|
68
72
|
// ============================================
|
|
@@ -84,7 +88,7 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
84
88
|
* </ProgressBar>
|
|
85
89
|
* ```
|
|
86
90
|
*/
|
|
87
|
-
export function ProgressBar(props:
|
|
91
|
+
export function ProgressBar(props: ProgressBarProps): JSX.Element {
|
|
88
92
|
const [local, ariaProps] = splitProps(props, [
|
|
89
93
|
'children',
|
|
90
94
|
'class',
|
|
@@ -119,7 +123,7 @@ export function ProgressBar(props: ParentProps<ProgressBarProps>): JSX.Element {
|
|
|
119
123
|
return undefined;
|
|
120
124
|
}
|
|
121
125
|
const clampedValue = clamp(value(), minValue(), maxValue());
|
|
122
|
-
return ((clampedValue - minValue()) / (
|
|
126
|
+
return ((clampedValue - minValue()) / getSafeRange(minValue(), maxValue())) * 100;
|
|
123
127
|
});
|
|
124
128
|
|
|
125
129
|
// Get value text from aria props
|
package/src/RadioGroup.tsx
CHANGED
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
type RadioGroupProps as RadioGroupStateProps,
|
|
29
29
|
} from '@proyecto-viviana/solid-stately';
|
|
30
30
|
import { VisuallyHidden } from './VisuallyHidden';
|
|
31
|
+
import {
|
|
32
|
+
SelectionIndicatorContext,
|
|
33
|
+
type SelectionIndicatorContextValue,
|
|
34
|
+
} from './SelectionIndicator';
|
|
31
35
|
import {
|
|
32
36
|
type RenderChildren,
|
|
33
37
|
type ClassNameOrFunction,
|
|
@@ -267,6 +271,10 @@ function RadioImpl(props: { radioProps: RadioProps; state: RadioGroupState }): J
|
|
|
267
271
|
renderValues
|
|
268
272
|
);
|
|
269
273
|
|
|
274
|
+
const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
|
|
275
|
+
isSelected: radioAria.isSelected,
|
|
276
|
+
}));
|
|
277
|
+
|
|
270
278
|
// Filter DOM props
|
|
271
279
|
const domProps = createMemo(() => {
|
|
272
280
|
const filtered = filterDOMProps(ariaProps, { global: true });
|
|
@@ -294,31 +302,33 @@ function RadioImpl(props: { radioProps: RadioProps; state: RadioGroupState }): J
|
|
|
294
302
|
};
|
|
295
303
|
|
|
296
304
|
return (
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
<
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
305
|
+
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
306
|
+
<label
|
|
307
|
+
{...domProps()}
|
|
308
|
+
{...cleanLabelProps()}
|
|
309
|
+
{...cleanHoverProps()}
|
|
310
|
+
class={renderProps.class()}
|
|
311
|
+
style={renderProps.style()}
|
|
312
|
+
data-selected={radioAria.isSelected() || undefined}
|
|
313
|
+
data-pressed={radioAria.isPressed() || undefined}
|
|
314
|
+
data-hovered={isHovered() || undefined}
|
|
315
|
+
data-focused={isFocused() || undefined}
|
|
316
|
+
data-focus-visible={isFocusVisible() || undefined}
|
|
317
|
+
data-disabled={radioAria.isDisabled || undefined}
|
|
318
|
+
data-readonly={state.isReadOnly || undefined}
|
|
319
|
+
data-invalid={state.isInvalid || undefined}
|
|
320
|
+
data-required={state.isRequired || undefined}
|
|
321
|
+
>
|
|
322
|
+
<VisuallyHidden>
|
|
323
|
+
<input
|
|
324
|
+
ref={(el) => (inputRef = el)}
|
|
325
|
+
{...cleanInputProps()}
|
|
326
|
+
{...cleanFocusProps()}
|
|
327
|
+
/>
|
|
328
|
+
</VisuallyHidden>
|
|
329
|
+
{renderProps.renderChildren()}
|
|
330
|
+
</label>
|
|
331
|
+
</SelectionIndicatorContext.Provider>
|
|
322
332
|
);
|
|
323
333
|
}
|
|
324
334
|
|