@kubit-ui-web/react-components
Version:
Kubit React Components is a customizable, accessible library of React web components, designed to enhance your application's user experience
65 lines • 3.38 kB
JavaScript
import React from 'react';
import { getFocusableDescendantsV2 } from '../../utils/focusHandlers/focusHandlers';
export const useTrapFocus = ({ ref,
// Default false in order to only active the window listener when needed
trapFocus = false, }) => {
React.useEffect(() => {
const handleKeyDown = (e) => {
// Do nothing is ref is not defined
// Do nothing if tab is not pressed
if (!ref.current || e.key !== 'Tab') {
return;
}
const focusableElements = getFocusableDescendantsV2({ element: ref.current });
// When the ref does not contain focusable elements
if (!focusableElements.length) {
// Do not change the current focus if the current focus is inside the ref
// This happens in some element like in actionBottomSheet dropdown
// When the focus is trap, but the options are not focusabled
if (ref.current.contains(document.activeElement)) {
e.preventDefault();
}
return;
}
const firstFocusableElement = focusableElements[0];
const lastFocusableElement = focusableElements[focusableElements.length - 1];
// If we are at the last focusable element and we press tab (without shift), change the focus to the first focusable element
if (!e.shiftKey && document.activeElement === lastFocusableElement) {
firstFocusableElement?.focus();
e.preventDefault();
return;
}
// If we are at the first focusable element and we press shift + tab, change the focus to the last focusable element
if (e.shiftKey && document.activeElement === firstFocusableElement) {
lastFocusableElement?.focus();
e.preventDefault();
return;
}
// If we have preceded or passed the focusable elements, then:
// When tab -> focus first element
// When shift + tab -> focus last element
// preceded or passed how ? -> Imagine no focusable elements inside the element container with tabIndex="-1", but they are focused manually with focus()
if (firstFocusableElement?.compareDocumentPosition(e.target) &
Node.DOCUMENT_POSITION_PRECEDING ||
lastFocusableElement?.compareDocumentPosition(e.target) &
Node.DOCUMENT_POSITION_FOLLOWING) {
if (e.shiftKey) {
lastFocusableElement?.focus();
e.preventDefault();
return;
}
firstFocusableElement?.focus();
e.preventDefault();
}
};
// It is being used window event listener instead of ref.eventListener because the focus is redirected to the body when a focused element disappears or is marked as disabled
// In these cases, the handleKeyDown would not be triggered if the event listener is attached to the ref
if (trapFocus) {
window.addEventListener('keydown', handleKeyDown);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [trapFocus]);
};
//# sourceMappingURL=useTrapFocus.js.map