From 563683b5c132e15c085992bf40d2434b281dd98e Mon Sep 17 00:00:00 2001 From: Pragadesh-45 <54320162+Pragadesh-45@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:19:23 +0530 Subject: [PATCH] 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 --- .../bruno-app/src/hooks/useFocusTrap/index.js | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/bruno-app/src/hooks/useFocusTrap/index.js b/packages/bruno-app/src/hooks/useFocusTrap/index.js index 18bead8be7..760603d217 100644 --- a/packages/bruno-app/src/hooks/useFocusTrap/index.js +++ b/packages/bruno-app/src/hooks/useFocusTrap/index.js @@ -1,24 +1,28 @@ import { useEffect, useRef } from 'react'; 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(() => { const modalElement = modalRef.current; if (!modalElement) return; - const focusableElements = modalElement.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ); + const focusableElements = Array.from(document.querySelectorAll(focusableSelector)); + 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]; - const lastElement = focusableElements[focusableElements.length - 1]; - - firstFocusableElementRef.current = firstElement; - lastFocusableElementRef.current = lastElement; + // Set focus to the first focusable element in the modal + const firstElement = modalFocusableElements[0]; + const lastElement = modalFocusableElements[modalFocusableElements.length - 1]; const handleKeyDown = (event) => { if (event.key === 'Tab') { @@ -36,6 +40,12 @@ const useFocusTrap = (modalRef) => { return () => { modalElement.removeEventListener('keydown', handleKeyDown); + + // Restore original tabindex values + elementsToHide.forEach(el => { + const originalTabIndex = el.getAttribute('data-tabindex'); + el.setAttribute('tabindex', originalTabIndex === 'inline' ? '' : originalTabIndex); + }); }; }, [modalRef]); };