11<script setup lang="ts">
2- import { onClickOutside } from ' @vueuse/core'
2+ import { onClickOutside , useEventListener } from ' @vueuse/core'
33
44const selectedPM = useSelectedPackageManager ()
55
@@ -8,6 +8,18 @@ const triggerRef = useTemplateRef('triggerRef')
88const isOpen = shallowRef (false )
99const highlightedIndex = shallowRef (- 1 )
1010
11+ const dropdownPosition = shallowRef <{ top: number ; left: number } | null >(null )
12+
13+ function getDropdownStyle(): Record <string , string > {
14+ if (! dropdownPosition .value ) return {}
15+ return {
16+ top: ` ${dropdownPosition .value .top }px ` ,
17+ left: ` ${dropdownPosition .value .left }px ` ,
18+ }
19+ }
20+
21+ useEventListener (' scroll' , close , true )
22+
1123// Generate unique ID for accessibility
1224const inputId = useId ()
1325const listboxId = ` ${inputId }-listbox `
@@ -20,6 +32,13 @@ function toggle() {
2032 if (isOpen .value ) {
2133 close ()
2234 } else {
35+ if (triggerRef .value ) {
36+ const rect = triggerRef .value .getBoundingClientRect ()
37+ dropdownPosition .value = {
38+ top: rect .bottom + 4 ,
39+ left: rect .left ,
40+ }
41+ }
2342 isOpen .value = true
2443 highlightedIndex .value = packageManagers .findIndex (pm => pm .id === selectedPM .value )
2544 }
@@ -70,37 +89,37 @@ function handleKeydown(event: KeyboardEvent) {
7089 </script >
7190
7291<template >
73- <div class =" relative" >
74- <button
75- ref =" triggerRef"
76- type =" button"
77- class =" flex items-center gap-1.5 px-2 py-2 font-mono text-xs text-fg-muted bg-bg-subtle border border-border-subtle border-solid rounded-md transition-colors duration-150 hover:(text-fg border-border-hover) active:scale-95 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 hover:text-fg"
78- :aria-expanded =" isOpen"
79- aria-haspopup =" listbox"
80- :aria-label =" $t('settings.package_manager')"
81- :aria-controls =" listboxId"
82- @click =" toggle"
83- @keydown =" handleKeydown"
84- >
85- <ClientOnly >
86- <span class =" inline-block h-3 w-3" :class =" pm.icon" aria-hidden =" true" />
87- <span >{{ pm.label }}</span >
88- <template #fallback >
89- <span class =" inline-block h-3 w-3 i-simple-icons:npm" aria-hidden =" true" />
90- <span >npm</span >
91- </template >
92- </ClientOnly >
93- <span
94- class =" i-carbon:chevron-down w-3 h-3"
95- :class =" [
96- { 'rotate-180': isOpen },
97- prefersReducedMotion ? '' : 'transition-transform duration-200',
98- ]"
99- aria-hidden =" true"
100- />
101- </button >
92+ <button
93+ ref =" triggerRef"
94+ type =" button"
95+ class =" flex items-center gap-1.5 px-2 py-2 font-mono text-xs text-fg-muted bg-bg-subtle border border-border-subtle border-solid rounded-md transition-colors duration-150 hover:(text-fg border-border-hover) active:scale-95 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 hover:text-fg"
96+ :aria-expanded =" isOpen"
97+ aria-haspopup =" listbox"
98+ :aria-label =" $t('settings.package_manager')"
99+ :aria-controls =" listboxId"
100+ @click =" toggle"
101+ @keydown =" handleKeydown"
102+ >
103+ <ClientOnly >
104+ <span class =" inline-block h-3 w-3" :class =" pm.icon" aria-hidden =" true" />
105+ <span >{{ pm.label }}</span >
106+ <template #fallback >
107+ <span class =" inline-block h-3 w-3 i-simple-icons:npm" aria-hidden =" true" />
108+ <span >npm</span >
109+ </template >
110+ </ClientOnly >
111+ <span
112+ class =" i-carbon:chevron-down w-3 h-3"
113+ :class =" [
114+ { 'rotate-180': isOpen },
115+ prefersReducedMotion ? '' : 'transition-transform duration-200',
116+ ]"
117+ aria-hidden =" true"
118+ />
119+ </button >
102120
103- <!-- Dropdown menu -->
121+ <!-- Dropdown menu (teleported to body to avoid clipping) -->
122+ <Teleport to =" body" >
104123 <Transition
105124 :enter-active-class =" prefersReducedMotion ? '' : 'transition-opacity duration-150'"
106125 :enter-from-class =" prefersReducedMotion ? '' : 'opacity-0'"
@@ -120,7 +139,8 @@ function handleKeydown(event: KeyboardEvent) {
120139 : undefined
121140 "
122141 :aria-label =" $t('settings.package_manager')"
123- class =" absolute inset-ie-0 top-full mt-1 bg-bg-elevated border border-border rounded-md shadow-lg z-50 py-1"
142+ :style =" getDropdownStyle()"
143+ class =" fixed bg-bg-subtle border border-border rounded-md shadow-lg z-50"
124144 >
125145 <li
126146 v-for =" (pm, index) in packageManagers"
@@ -131,7 +151,7 @@ function handleKeydown(event: KeyboardEvent) {
131151 class =" flex items-center gap-2 px-3 py-1.5 font-mono text-xs cursor-pointer transition-colors duration-150"
132152 :class =" [
133153 selectedPM === pm.id ? 'text-fg' : 'text-fg-subtle',
134- highlightedIndex === index ? 'bg-bg-subtle ' : 'hover:bg-bg-subtle ',
154+ highlightedIndex === index ? 'bg-bg-elevated ' : 'hover:bg-bg-elevated ',
135155 ]"
136156 @click =" select(pm.id)"
137157 @mouseenter =" highlightedIndex = index"
@@ -146,5 +166,5 @@ function handleKeydown(event: KeyboardEvent) {
146166 </li >
147167 </ul >
148168 </Transition >
149- </div >
169+ </Teleport >
150170</template >
0 commit comments