From d159ad58af391c8df4473b02a6257b188b7bdfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Tue, 10 Nov 2026 15:33:30 +0900 Subject: [PATCH] fix(sw): restore dropdown focus after position calculation --- .../sw-header/components/SWHeaderDropdown.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/sw/src/components/sw-header/components/SWHeaderDropdown.tsx b/packages/sw/src/components/sw-header/components/SWHeaderDropdown.tsx index 4ea2b05..e765e08 100644 --- a/packages/sw/src/components/sw-header/components/SWHeaderDropdown.tsx +++ b/packages/sw/src/components/sw-header/components/SWHeaderDropdown.tsx @@ -109,11 +109,16 @@ export function useSWHeaderDropdown({ open: openProp, onOpenChange, disabled = f /** Focusable entries of the panel in DOM order (rows, inputs, footer). */ const entries = () => Array.from(panelRef.current?.querySelectorAll("button:not(:disabled), input:not(:disabled)") ?? []) + // ArrowDown on the closed trigger asks for the entry focus. A fixed overlay (property window, SW popup) stays + // visibility: hidden until its position is measured, and focus() on it is a no-op: the request stays pending and is + // retried on the render that shows the list (the position update), then cleared once focus moved; closing drops it. useEffect(() => { - if (open && focusEntryRef.current) { - focusEntryRef.current = false + if (!open) { focusEntryRef.current = false; return } + if (focusEntryRef.current) { const current = panelRef.current?.querySelector('[aria-current="true"]:not(:disabled), [aria-pressed="true"]:not(:disabled)') - ;(current ?? entries()[0])?.focus() + const target = current ?? entries()[0] + target?.focus() + if (!target || document.activeElement === target) focusEntryRef.current = false } })