From a9ac303d3e712bb02fd75627d3263567d6a72ca9 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 27 Sep 2026 19:58:18 +0200 Subject: [PATCH] fix(side-panel): the key's focus steps into the peeked card in the commit that opens it A frame later never came in a page the browser was not painting (measured: a hidden page runs no requestAnimationFrame), and the focus stayed on the inert tab under the card. --- frontend/src/components/ui/SidePanel.tsx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/ui/SidePanel.tsx b/frontend/src/components/ui/SidePanel.tsx index 54fe4376..726e77b1 100644 --- a/frontend/src/components/ui/SidePanel.tsx +++ b/frontend/src/components/ui/SidePanel.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, type ReactNode } from "react"; +import { useLayoutEffect, useRef, useState, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; import { Check, Pencil, Pin, PinOff, RotateCcw } from "lucide-react"; import { useScrollFade } from "../../lib/useScrollFade"; @@ -65,6 +65,7 @@ export default function SidePanel({ }) { const { t } = useTranslation(); const cardRef = useRef(null); + const tabRef = useRef(null); const fade = useScrollFade(); const peek = useHoverFocusWithin(); const drawerMode = useMediaQuery(DRAWER_QUERY, false); @@ -77,6 +78,16 @@ export default function SidePanel({ // Turning the tablet to a width where the panel is a card again, or pinning it, ends the drawer — // adjusted during render, so it cannot come back open on the next turn. if (drawerOpen && (!drawerMode || pinned)) setDrawerOpen(false); + // A key landing on the tab peeks the card open over it, and the tab then goes inert below — with + // the focus still on it: a hidden control, out of the tree, that Enter still pinned (round 2, F1). + // The focus steps into the card as it opens, onto its pin: the same control. In the commit that + // mounts the card, not a frame later — a frame never comes in a page the browser is not painting. + useLayoutEffect(() => { + const tab = tabRef.current; + if (!expanded || pinned || drawerMode || !tab || document.activeElement !== tab) return; + if (tab.matches(":focus-visible")) + cardRef.current?.querySelector("button")?.focus(); + }, [expanded, pinned, drawerMode]); const titleNode = onTitleClick ? (