feature/useFocusTrap: Support focusable tab cycles in Modal (Update of PR #3075) (#3133)

* enhance useFocusTrap: implemented focus trapping, hide non-focusable elements

* add reference link
This commit is contained in:
Pragadesh-45 2024-09-20 14:19:23 +05:30 committed by GitHub
parent e019a96cd5
commit 563683b5c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,24 +1,28 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
const useFocusTrap = (modalRef) => { const useFocusTrap = (modalRef) => {
const firstFocusableElementRef = useRef(null);
const lastFocusableElementRef = useRef(null);
// refer to this implementation for modal focus: https://stackoverflow.com/a/38865836
const focusableSelector = 'a[href], area[href], input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex]:not([tabindex="-1"]), *[contenteditable]';
useEffect(() => { useEffect(() => {
const modalElement = modalRef.current; const modalElement = modalRef.current;
if (!modalElement) return; if (!modalElement) return;
const focusableElements = modalElement.querySelectorAll( const focusableElements = Array.from(document.querySelectorAll(focusableSelector));
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' const modalFocusableElements = Array.from(modalElement.querySelectorAll(focusableSelector));
); const elementsToHide = focusableElements.filter(el => !modalFocusableElements.includes(el));
if (focusableElements.length === 0) return; // Hide elements outside the modal
elementsToHide.forEach(el => {
const originalTabIndex = el.getAttribute('tabindex');
el.setAttribute('data-tabindex', originalTabIndex || 'inline');
el.setAttribute('tabindex', -1);
});
const firstElement = focusableElements[0]; // Set focus to the first focusable element in the modal
const lastElement = focusableElements[focusableElements.length - 1]; const firstElement = modalFocusableElements[0];
const lastElement = modalFocusableElements[modalFocusableElements.length - 1];
firstFocusableElementRef.current = firstElement;
lastFocusableElementRef.current = lastElement;
const handleKeyDown = (event) => { const handleKeyDown = (event) => {
if (event.key === 'Tab') { if (event.key === 'Tab') {
@ -36,6 +40,12 @@ const useFocusTrap = (modalRef) => {
return () => { return () => {
modalElement.removeEventListener('keydown', handleKeyDown); modalElement.removeEventListener('keydown', handleKeyDown);
// Restore original tabindex values
elementsToHide.forEach(el => {
const originalTabIndex = el.getAttribute('data-tabindex');
el.setAttribute('tabindex', originalTabIndex === 'inline' ? '' : originalTabIndex);
});
}; };
}, [modalRef]); }, [modalRef]);
}; };