Skip to content
This repository was archived by the owner on Jun 2, 2026. It is now read-only.

Commit b17b980

Browse files
committed
feat: improve styling and accessibility of Select component test page
1 parent 1f7d520 commit b17b980

2 files changed

Lines changed: 83 additions & 106 deletions

File tree

src/lib/Select.svelte

Lines changed: 32 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
102102
// Generate unique ID for popover
103103
const popoverId = `select-popover-${Math.random().toString(36).slice(2, 9)}`
104+
const anchorName = `--anchor-${popoverId}`
104105
105106
// Ensure value is properly typed for single/multiple mode
106107
// Normalize on access rather than maintaining separate state
@@ -258,7 +259,6 @@
258259
})
259260
260261
let searchEL: HTMLInputElement | undefined = $state(undefined)
261-
let triggerEl: HTMLButtonElement | undefined = $state(undefined)
262262
let popoverEl: HTMLElement | undefined = $state(undefined)
263263
let scrollContainerEl: HTMLDivElement | undefined = $state(undefined)
264264
@@ -347,55 +347,8 @@
347347
// Virtual list implementation
348348
let scrollTop = $state(0)
349349
const itemHeight = 40 // Approximate height of each item in pixels
350-
const maxContainerHeight = 320 // max-h-80 = 320px
351-
const minContainerHeight = 120
352-
let listViewportHeight = $state(maxContainerHeight)
350+
const listViewportHeight = 320 // max-h-80 = 320px
353351
let visibleCount = $derived(Math.ceil(listViewportHeight / itemHeight) + 2) // Add buffer items
354-
let dropdownPanelStyle = $state('')
355-
const dropdownGap = 8
356-
357-
function updateDropdownPosition() {
358-
if (!triggerEl || !popoverEl) return
359-
360-
const rect = triggerEl.getBoundingClientRect()
361-
const viewportHeight = window.innerHeight
362-
const viewportWidth = window.innerWidth
363-
const viewportPadding = 8
364-
365-
const spaceBelow = viewportHeight - rect.bottom - dropdownGap - viewportPadding
366-
const spaceAbove = rect.top - dropdownGap - viewportPadding
367-
const openUpward = spaceBelow < minContainerHeight && spaceAbove > spaceBelow
368-
369-
const availableHeight = Math.max(minContainerHeight, openUpward ? spaceAbove : spaceBelow)
370-
listViewportHeight = Math.min(maxContainerHeight, availableHeight)
371-
372-
const maxLeft = Math.max(viewportPadding, viewportWidth - rect.width - viewportPadding)
373-
const left = Math.min(Math.max(viewportPadding, rect.left), maxLeft)
374-
375-
const minWidthStyle = dropdownMinWidth ? `min-width: ${dropdownMinWidth};` : ''
376-
if (openUpward) {
377-
const bottom = Math.max(viewportPadding, viewportHeight - rect.top + dropdownGap)
378-
dropdownPanelStyle = `position: fixed; left: ${left}px; bottom: ${bottom}px; width: ${rect.width}px; max-height: ${availableHeight}px; ${minWidthStyle}`
379-
} else {
380-
const top = Math.max(viewportPadding, rect.bottom + dropdownGap)
381-
dropdownPanelStyle = `position: fixed; left: ${left}px; top: ${top}px; width: ${rect.width}px; max-height: ${availableHeight}px; ${minWidthStyle}`
382-
}
383-
}
384-
385-
$effect(() => {
386-
if (!dropdownOpen) return
387-
388-
updateDropdownPosition()
389-
const reposition = () => updateDropdownPosition()
390-
391-
window.addEventListener('resize', reposition)
392-
window.addEventListener('scroll', reposition, true)
393-
394-
return () => {
395-
window.removeEventListener('resize', reposition)
396-
window.removeEventListener('scroll', reposition, true)
397-
}
398-
})
399352
400353
// Calculate visible items based on scroll position (for flatList)
401354
let visibleItems = $derived.by(() => {
@@ -529,10 +482,7 @@
529482
function handlePopoverToggle(e: ToggleEvent) {
530483
dropdownOpen = e.newState === 'open'
531484
if (dropdownOpen) {
532-
requestAnimationFrame(() => {
533-
updateDropdownPosition()
534-
if (scrollContainerEl) scrollTop = scrollContainerEl.scrollTop
535-
})
485+
if (scrollContainerEl) scrollTop = scrollContainerEl.scrollTop
536486
searchEL?.focus()
537487
} else {
538488
hasScrolledOnOpen = false
@@ -555,7 +505,6 @@
555505
{#if !disabled}
556506
<!-- Trigger button with popover target and anchor positioning -->
557507
<button
558-
bind:this={triggerEl}
559508
type="button"
560509
popovertarget={popoverId}
561510
popovertargetaction="show"
@@ -565,6 +514,7 @@
565514
aria-controls={popoverId}
566515
tabindex="-1"
567516
class="select relative h-max min-h-10 w-full min-w-12 cursor-pointer bg-none! pr-1 text-left"
517+
style="anchor-name: {anchorName}"
568518
title={tooltipText}
569519
onclick={() => {
570520
searchEL?.focus()
@@ -659,7 +609,7 @@
659609
role="listbox"
660610
inert={!dropdownOpen}
661611
class="menu bg-base-100 rounded-box z-50 m-0 flex flex-col flex-nowrap gap-1 overflow-hidden p-2 shadow outline"
662-
style={dropdownPanelStyle}
612+
style="position: fixed; position-anchor: {anchorName}; top: anchor(bottom); left: anchor(left); width: anchor-size(width); max-width: calc(100vw - 1rem); margin-top: 0.5rem; margin-bottom: 0;{dropdownMinWidth ? ` min-width: ${dropdownMinWidth};` : ''} position-try-fallbacks: --select-above, --select-below-right, --select-above-right;"
663613
ontoggle={handlePopoverToggle}>
664614
{#if multiple && filteredItems.length > 1}
665615
<!-- Select All / Clear All options for multi-select -->
@@ -778,6 +728,33 @@
778728
</Label>
779729

780730
<style>
731+
@position-try --select-above {
732+
top: unset;
733+
bottom: anchor(top);
734+
left: anchor(left);
735+
right: unset;
736+
margin-top: 0;
737+
margin-bottom: 0.5rem;
738+
}
739+
740+
@position-try --select-below-right {
741+
top: anchor(bottom);
742+
bottom: unset;
743+
left: unset;
744+
right: anchor(right);
745+
margin-top: 0.5rem;
746+
margin-bottom: 0;
747+
}
748+
749+
@position-try --select-above-right {
750+
top: unset;
751+
bottom: anchor(top);
752+
left: unset;
753+
right: anchor(right);
754+
margin-top: 0;
755+
margin-bottom: 0.5rem;
756+
}
757+
781758
/* The `menu` DaisyUI class applies display:flex as an author style, which has higher
782759
cascade priority than the UA stylesheet's display:none for closed [popover] elements.
783760
This rule restores the correct hide behavior without relying on UA defaults. */

0 commit comments

Comments
 (0)