️(frontend) fix focus restore when switching side panels

restore trigger focus on panel switch while panel remains open
This commit is contained in:
Cyril
2026-07-01 10:15:06 +02:00
committed by lebaudantoine
parent ba6a4e971f
commit fa85343147
4 changed files with 42 additions and 8 deletions
@@ -177,17 +177,21 @@ export const SidePanel = () => {
const asideRef = useRef<HTMLElement>(null)
const panelManagesFocus = activePanelId === PanelId.CHAT
const focusAside = useCallback(() => {
requestAnimationFrame(() => {
asideRef.current?.focus({ preventScroll: true })
})
}, [])
useRestoreFocus(isSidePanelOpen && !panelManagesFocus, {
onOpened: focusAside,
const handlePanelOpened = useCallback(() => {
if (activePanelId === PanelId.CHAT) return
focusAside()
}, [activePanelId, focusAside])
useRestoreFocus(isSidePanelOpen, {
onOpened: handlePanelOpened,
preventScroll: true,
activeKey: activePanelId,
})
return (
@@ -95,8 +95,13 @@ const ToolButton = ({
export const Tools = () => {
const { data } = useConfig()
const { openTranscript, openScreenRecording, activeSubPanelId, isToolsOpen } =
useSidePanel()
const {
openTranscript,
openScreenRecording,
activeSubPanelId,
isToolsOpen,
isSidePanelOpen,
} = useSidePanel()
const { t } = useTranslation('rooms', { keyPrefix: 'moreTools' })
// Restore focus to the element that opened the Tools panel
@@ -113,6 +118,7 @@ export const Tools = () => {
},
restoreFocusRaf: true,
preventScroll: true,
shouldRestoreOnClose: () => !isSidePanelOpen,
})
const isTranscriptEnabled = useIsRecordingModeEnabled(
@@ -37,7 +37,7 @@ export function Chat({ ...props }: ChatProps) {
const room = useRoomContext()
const { send, chatMessages, isSending } = useChat()
const { isChatOpen } = useSidePanel()
const { isChatOpen, isSidePanelOpen } = useSidePanel()
const chatSnap = useSnapshot(chatStore)
// Keep track of the element that opened the chat so we can restore focus
@@ -51,6 +51,7 @@ export function Chat({ ...props }: ChatProps) {
})
},
preventScroll: true,
shouldRestoreOnClose: () => !isSidePanelOpen,
})
// Use useParticipants hook to trigger a re-render when the participant list changes.
+24 -1
View File
@@ -6,6 +6,10 @@ export type RestoreFocusOptions = {
onClosed?: () => void
restoreFocusRaf?: boolean
preventScroll?: boolean
/** When the panel stays open but its content changes, update the restore target. */
activeKey?: string | null
/** Return false to skip restoring focus on close (e.g. when switching to another panel). */
shouldRestoreOnClose?: () => boolean
}
/**
@@ -22,9 +26,12 @@ export function useRestoreFocus(
onClosed,
restoreFocusRaf = false,
preventScroll = true,
activeKey,
shouldRestoreOnClose,
} = options
const prevIsOpenRef = useRef(false)
const prevActiveKeyRef = useRef(activeKey)
const triggerRef = useRef<HTMLElement | null>(null)
useEffect(() => {
@@ -37,25 +44,41 @@ export function useRestoreFocus(
onOpened?.()
}
// Panel switched while staying open
if (wasOpen && isOpen && activeKey !== prevActiveKeyRef.current) {
const activeEl = document.activeElement as HTMLElement | null
triggerRef.current = resolveTrigger ? resolveTrigger(activeEl) : activeEl
onOpened?.()
}
// Just closed
if (wasOpen && !isOpen) {
const trigger = triggerRef.current
if (trigger && document.contains(trigger)) {
const shouldRestore =
(shouldRestoreOnClose?.() ?? true) &&
trigger &&
document.contains(trigger)
if (shouldRestore) {
const focus = () => trigger.focus({ preventScroll })
if (restoreFocusRaf) requestAnimationFrame(focus)
else focus()
}
triggerRef.current = null
onClosed?.()
}
prevIsOpenRef.current = isOpen
prevActiveKeyRef.current = activeKey
}, [
isOpen,
activeKey,
onClosed,
onOpened,
preventScroll,
resolveTrigger,
restoreFocusRaf,
shouldRestoreOnClose,
])
}