UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

69 lines (68 loc) 2.43 kB
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, ); }