@@ -81,12 +81,13 @@ function getAriaLabel(c: GitHubContributor): string {
8181 })
8282}
8383
84- // --- Popover Logic (Global Single Instance with Event Delegation + Direct DOM Manipulation) ---
84+ // --- Popover Logic (Global Single Instance with Event Delegation) ---
85+ // We use a single global popover instance for performance (especially in Firefox with many items).
86+ // Event delegation on the list handles interactions, avoiding listeners on every item.
8587const activeContributor = shallowRef <GitHubContributor >()
8688const popoverPos = reactive ({ top: 0 , left: 0 , align: ' center' as ' left' | ' center' | ' right' })
87- const panelRef = ref <HTMLElement >()
89+ const panelRef = useTemplateRef <HTMLElement >(' panelRef ' )
8890const activeBtnEl = shallowRef <HTMLElement >()
89- let activeBtnDom: HTMLElement | null = null // Direct DOM reference for performance
9091let closeTimer: ReturnType <typeof setTimeout > | undefined
9192let lastOpenTime = 0
9293
@@ -102,8 +103,10 @@ function computePos(btn: HTMLElement) {
102103 const vw = window .innerWidth
103104 const POP_W = 256
104105 const GAP = 8
106+
105107 popoverPos .top = r .bottom + GAP
106108 const center = r .left + r .width / 2
109+
107110 if (center - POP_W / 2 < GAP ) {
108111 popoverPos .align = ' left'
109112 popoverPos .left = r .left
@@ -116,6 +119,7 @@ function computePos(btn: HTMLElement) {
116119 }
117120}
118121
122+ // DON'T MOVE aria-expanded to the template, Firefox performance issues
119123function setActiveBtnExpanded(btn : HTMLElement | null , value : boolean ) {
120124 if (activeBtnDom && activeBtnDom !== btn ) {
121125 activeBtnDom .removeAttribute (' aria-expanded' )
@@ -166,7 +170,7 @@ function onListMouseEnter(e: MouseEvent) {
166170}
167171
168172function onListMouseLeave(e : MouseEvent ) {
169- // Solo cerrar si salimos del < ul> completamente
173+ // only close if we exist > ul>
170174 const related = e .relatedTarget as Element | null
171175 if (related ?.closest (' [data-popover-panel]' )) return
172176 if (! related ?.closest (' button[data-cid]' )) scheduleCloseActive ()
@@ -187,13 +191,13 @@ function onListClick(e: MouseEvent) {
187191 }
188192}
189193
190- // ── Panel mouse events ───────────────────────────────────────────────────────
194+ // Panel mouse events
191195function onPanelMouseLeave(e : MouseEvent ) {
192196 const related = e .relatedTarget as Element | null
193197 if (! related ?.closest (' button[data-cid]' )) scheduleCloseActive ()
194198}
195199
196- // ── Tab management dentro del panel (foco manual) ───────────────────────────
200+ // Tab management inside the panel (manual focus)
197201function onPanelKeydown(e : KeyboardEvent ) {
198202 if (e .key !== ' Tab' || ! panelRef .value ) return
199203 const focusables = [... panelRef .value .querySelectorAll <HTMLElement >(' a[href]' )]
@@ -234,7 +238,7 @@ function onPanelKeydown(e: KeyboardEvent) {
234238 }
235239}
236240
237- // ── Document listeners ───────────────────────────────────────────────────────
241+ // Document listeners
238242function onDocumentPointerDown(e : PointerEvent ) {
239243 if (! activeContributor .value ) return
240244 const t = e .target as Element
@@ -252,6 +256,8 @@ function onDocumentKeydown(e: KeyboardEvent) {
252256 }
253257}
254258
259+ let activeBtnDom: HTMLElement | null = null
260+
255261onMounted (() => {
256262 document .addEventListener (' pointerdown' , onDocumentPointerDown )
257263 document .addEventListener (' keydown' , onDocumentKeydown )
@@ -422,7 +428,6 @@ onBeforeUnmount(() => {
422428 </div >
423429 <ul
424430 v-else-if =" contributors.length"
425- ref =" listRef"
426431 class =" flex flex-wrap justify-center gap-2 list-none p-0 overflow-visible"
427432 @mouseover =" onListMouseEnter"
428433 @mouseout =" onListMouseLeave"
@@ -615,6 +620,10 @@ onBeforeUnmount(() => {
615620
616621[data-cid ][aria-expanded = ' true' ] img {
617622 @apply ring- 2 ring-accent ;
623+ transform : scale (1.1 );
624+ --un-ring-opacity : 1 ;
625+ --un-ring-color : color-mix(in srgb , var (--accent ) var (--un-ring-opacity ), transparent );
626+ box-shadow : 0 0 0 2px var (--un-ring-color );
618627}
619628
620629@media (hover : hover ) {
@@ -627,6 +636,8 @@ onBeforeUnmount(() => {
627636 }
628637}
629638
639+ /* Capture tap/click (focus without keyboard navigation => for chrome tap) */
640+ [data-cid ]:focus:not (:focus-visible ) img ,
630641[data-cid ]:focus-visible img {
631642 transform : scale (1.1 );
632643 --un-ring-opacity : 1 ;
@@ -639,6 +650,11 @@ onBeforeUnmount(() => {
639650 z-index : 20 ;
640651}
641652
653+ /* FF popup outline */
654+ .contributor-popover :focus {
655+ outline : none ;
656+ }
657+
642658.contributor-popover {
643659 position : fixed ;
644660 z-index : 9999 ;
0 commit comments