@i-novus/ui-core
Version:
Базовые UI компоненты
84 lines (83 loc) • 3.16 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { useEffect, forwardRef, useRef, } from 'react';
import { getActiveElement } from '../../../utils/get-active-dom-node';
import { useForwardedRef } from '../../hooks';
const ALL_TABBABLE_SELECTORS = [
'a[href]',
'button',
'input',
'textarea',
'select',
'details',
'[tabindex]:not([tabindex="-1"])',
].toString();
export const FocusTrap = forwardRef(({ children, initialFocusableSelector, }, ref) => {
const firstItemRef = useRef();
const lastItemRef = useRef();
const trapRef = useForwardedRef(ref);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
const element = trapRef.current;
if (!element) {
return undefined;
}
const findFocusableElements = () => {
const allFocusableElements = [...element.querySelectorAll(ALL_TABBABLE_SELECTORS)].filter(el => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
lastItemRef.current = allFocusableElements[allFocusableElements.length - 1];
// eslint-disable-next-line prefer-destructuring
firstItemRef.current = allFocusableElements[0];
};
const observer = new MutationObserver(findFocusableElements);
observer.observe(element, {
childList: true,
subtree: true,
});
findFocusableElements();
const firstEnter = (event) => {
const focusableElement = initialFocusableSelector
? element.querySelector(initialFocusableSelector)
: firstItemRef.current;
if (event.shiftKey) {
lastItemRef.current?.focus();
event.preventDefault();
}
else {
focusableElement?.focus();
event.preventDefault();
}
};
const loop = (event) => {
const activeElement = getActiveElement();
if (event.shiftKey) {
if (activeElement === firstItemRef.current) {
lastItemRef.current?.focus();
event.preventDefault();
}
}
else if (activeElement === lastItemRef.current) {
firstItemRef.current?.focus();
event.preventDefault();
}
};
const handleKeyDown = (event) => {
const isTabPressed = (event.code === 'Tab');
if (!isTabPressed) {
return;
}
const activeElement = getActiveElement();
const isContainerActiveElement = element.contains(activeElement);
if (isContainerActiveElement) {
loop(event);
}
else {
firstEnter(event);
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
observer.disconnect();
};
}, [initialFocusableSelector, trapRef]);
return (_jsx("div", { ref: trapRef, children: children }));
});