@itwin/itwinui-react
Version:
A react component library for iTwinUI
69 lines (68 loc) • 2.43 kB
JavaScript
import * as React from 'react';
import { useSyncExternalStore, useLatestRef } from '../hooks/index.js';
let tabbableElementsSelector =
'a[href], button, input, textarea, select, details, audio[controls], video[controls], [contenteditable]:not([contenteditable="false"]), [tabindex]:not([tabindex="-1"])';
export const getTabbableElements = (container) => {
if (!container) return [];
let elements = container.querySelectorAll(tabbableElementsSelector);
return Array.from(elements).filter(
(el) =>
!el.hasAttribute('disabled') &&
!el.classList.contains('iui-disabled') &&
'true' !== el.getAttribute('aria-disabled'),
);
};
export const getFocusableElements = (container) => {
if (!container) return [];
let elements = container.querySelectorAll(
`${tabbableElementsSelector}, [tabindex="-1"]`,
);
return Array.from(elements).filter(
(el) =>
!el.hasAttribute('disabled') &&
!el.classList.contains('iui-disabled') &&
'true' !== el.getAttribute('aria-disabled'),
);
};
export function useFocusableElements(root, extraOptions) {
let focusableElementsRef = React.useRef([]);
let [focusableElements, setFocusableElements] = React.useState(
focusableElementsRef.current,
);
let setFocusableElementsRefAndState = (newFocusableElements) => {
focusableElementsRef.current = newFocusableElements;
setFocusableElements(newFocusableElements);
};
let { filter: filterProp } = extraOptions ?? {};
let filter = useLatestRef(filterProp);
let returnValue = React.useMemo(
() => ({
focusableElementsRef,
focusableElements,
}),
[focusableElementsRef, focusableElements],
);
return useSyncExternalStore(
React.useCallback(() => {
if (!root) {
setFocusableElementsRefAndState([]);
return () => {};
}
updateFocusableElements();
let observer = new MutationObserver(() => updateFocusableElements());
observer.observe(root, {
childList: true,
subtree: true,
});
return () => observer.disconnect();
function updateFocusableElements() {
let newFocusableElements = getFocusableElements(root);
if (filter.current)
newFocusableElements = filter.current?.(newFocusableElements);
setFocusableElementsRefAndState(newFocusableElements);
}
}, [root, filter]),
() => returnValue,
() => returnValue,
);
}