65 lines
1.7 KiB
JavaScript
65 lines
1.7 KiB
JavaScript
|
|
import { useEffect, useRef } from 'react';
|
||
|
|
|
||
|
|
const FOCUSABLE_SELECTOR =
|
||
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
||
|
|
|
||
|
|
function getFocusableElements(container) {
|
||
|
|
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
||
|
|
(el) => !el.disabled && el.getAttribute('aria-hidden') !== 'true'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Trap focus inside a modal while `active` and restore focus on close.
|
||
|
|
* Returns a ref to attach to the dialog panel (not the backdrop).
|
||
|
|
*/
|
||
|
|
export function useFocusTrap(active) {
|
||
|
|
const containerRef = useRef(null);
|
||
|
|
const previouslyFocusedRef = useRef(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!active) return;
|
||
|
|
|
||
|
|
previouslyFocusedRef.current = document.activeElement;
|
||
|
|
const container = containerRef.current;
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
const focusFirst = () => {
|
||
|
|
const nodes = getFocusableElements(container);
|
||
|
|
nodes[0]?.focus();
|
||
|
|
};
|
||
|
|
|
||
|
|
focusFirst();
|
||
|
|
|
||
|
|
const handleKeyDown = (event) => {
|
||
|
|
if (event.key !== 'Tab') return;
|
||
|
|
|
||
|
|
const nodes = getFocusableElements(container);
|
||
|
|
if (nodes.length === 0) return;
|
||
|
|
|
||
|
|
const first = nodes[0];
|
||
|
|
const last = nodes[nodes.length - 1];
|
||
|
|
|
||
|
|
if (event.shiftKey && document.activeElement === first) {
|
||
|
|
event.preventDefault();
|
||
|
|
last.focus();
|
||
|
|
} else if (!event.shiftKey && document.activeElement === last) {
|
||
|
|
event.preventDefault();
|
||
|
|
first.focus();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
document.addEventListener('keydown', handleKeyDown);
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
document.removeEventListener('keydown', handleKeyDown);
|
||
|
|
const previous = previouslyFocusedRef.current;
|
||
|
|
if (previous && typeof previous.focus === 'function') {
|
||
|
|
previous.focus();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}, [active]);
|
||
|
|
|
||
|
|
return containerRef;
|
||
|
|
}
|