mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 20:08:24 +00:00
♿️(frontend) add visible focus ring on restore focus
Show focus ring when restoring focus, keyboard navigation only
This commit is contained in:
@@ -26,8 +26,21 @@ export function useRestoreFocus(
|
||||
|
||||
const prevIsOpenRef = useRef(false)
|
||||
const triggerRef = useRef<HTMLElement | null>(null)
|
||||
const cleanupRef = useRef<(() => void) | null>(null)
|
||||
const lastInteractionRef = useRef<'keyboard' | 'mouse' | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Track last interaction type (like native :focus-visible behavior)
|
||||
const handleKeyDown = () => {
|
||||
lastInteractionRef.current = 'keyboard'
|
||||
}
|
||||
const handleMouseDown = () => {
|
||||
lastInteractionRef.current = 'mouse'
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
document.addEventListener('mousedown', handleMouseDown)
|
||||
|
||||
const wasOpen = prevIsOpenRef.current
|
||||
|
||||
// Just opened
|
||||
@@ -41,7 +54,28 @@ export function useRestoreFocus(
|
||||
if (wasOpen && !isOpen) {
|
||||
const trigger = triggerRef.current
|
||||
if (trigger && document.contains(trigger)) {
|
||||
const focus = () => trigger.focus({ preventScroll })
|
||||
const focus = () => {
|
||||
trigger.focus({ preventScroll })
|
||||
// Only show focus ring if last interaction was keyboard (like native :focus-visible)
|
||||
if (lastInteractionRef.current === 'keyboard') {
|
||||
trigger.setAttribute('data-focus-visible', '')
|
||||
// Remove focus ring when focus moves to another element
|
||||
const handleFocusChange = (e: FocusEvent) => {
|
||||
if (e.target !== trigger && document.contains(trigger)) {
|
||||
trigger.removeAttribute('data-focus-visible')
|
||||
}
|
||||
}
|
||||
document.addEventListener('focusin', handleFocusChange, {
|
||||
once: true,
|
||||
})
|
||||
// Store cleanup for unmount case
|
||||
cleanupRef.current = () => {
|
||||
if (document.contains(trigger)) {
|
||||
trigger.removeAttribute('data-focus-visible')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (restoreFocusRaf) requestAnimationFrame(focus)
|
||||
else focus()
|
||||
}
|
||||
@@ -50,6 +84,14 @@ export function useRestoreFocus(
|
||||
}
|
||||
|
||||
prevIsOpenRef.current = isOpen
|
||||
|
||||
// Cleanup: remove focus ring if component unmounts before focus changes
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
document.removeEventListener('mousedown', handleMouseDown)
|
||||
cleanupRef.current?.()
|
||||
cleanupRef.current = null
|
||||
}
|
||||
}, [
|
||||
isOpen,
|
||||
onClosed,
|
||||
|
||||
@@ -25,7 +25,9 @@ body,
|
||||
[data-rac][data-focus-visible]:not(label, .react-aria-Select),
|
||||
:is(a, button, input[type='text'], select, textarea):not(
|
||||
[data-rac]
|
||||
):focus-visible {
|
||||
):focus-visible,
|
||||
/* Show focus ring when data-focus-visible is set programmatically (e.g., when restoring focus) */
|
||||
[data-focus-visible]:is(a, button, input[type='text'], select, textarea):focus {
|
||||
outline: 2px solid var(--colors-focus-ring);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user