From 9093371d25230bb4d266a98929b96fa515cd929f Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 15 Dec 2025 05:55:19 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20restore=20focus=20on=20ch?= =?UTF-8?q?at=20close?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit restore keyboard focus to the triggering element when the chat panel closes. Signed-off-by: Cyril --- CHANGELOG.md | 3 ++- .../features/rooms/livekit/prefabs/Chat.tsx | 26 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c46ff9..b273a126 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,5 +13,6 @@ and this project adheres to - ♿(frontend) improve accessibility: - ♿️(frontend) hover controls, focus, SR #803 - ♿️(frontend) change ptt keybinding from space to v #813 -- ♿(frontend) indicate external link opens in new window on feedback #816 +- ♿(frontend) indicate external link opens in new window on feedback #816 - ♿(frontend) fix heading level in modal to maintain semantic hierarchy #815 +- ♿️(frontend) Improve focus management when opening and closing chat #807 diff --git a/src/frontend/src/features/rooms/livekit/prefabs/Chat.tsx b/src/frontend/src/features/rooms/livekit/prefabs/Chat.tsx index ce9b2679..ddc5983d 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/Chat.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/Chat.tsx @@ -36,9 +36,31 @@ export function Chat({ ...props }: ChatProps) { const { isChatOpen } = useSidePanel() const chatSnap = useSnapshot(chatStore) + // Keep track of the element that opened the chat so we can restore focus + // when the chat panel is closed. + const prevIsChatOpenRef = React.useRef(false) + const chatTriggerRef = React.useRef(null) + useEffect(() => { - if (!isChatOpen || !inputRef.current) return - inputRef.current.focus() + const wasChatOpen = prevIsChatOpenRef.current + const isChatPanelOpen = isChatOpen + + // Chat just opened + if (!wasChatOpen && isChatPanelOpen) { + chatTriggerRef.current = document.activeElement as HTMLElement | null + inputRef.current?.focus() + } + + // Chat just closed + if (wasChatOpen && !isChatPanelOpen) { + const trigger = chatTriggerRef.current + if (trigger && document.contains(trigger)) { + trigger.focus() + } + chatTriggerRef.current = null + } + + prevIsChatOpenRef.current = isChatPanelOpen }, [isChatOpen]) // Use useParticipants hook to trigger a re-render when the participant list changes.