"use client" import { useCallback, useEffect, useRef, useState } from "react" import { useHandleKeyPress } from "@/hooks/useHandleKeyPress" import findTabbableDescendants from "@/utils/tabbable" const TAB_KEY = "Tab" const optionsDefault = { focusOnRender: true, returnFocus: true } type OptionsType = { focusOnRender?: boolean returnFocus?: boolean } export function useTrapFocus(opts?: OptionsType) { const options = opts ? { ...optionsDefault, ...opts } : optionsDefault const ref = useRef(null) const previouseFocusedElement = useRef( document.activeElement as HTMLElement ) const [tabbableElements, setTabbableElements] = useState([]) // Handle initial focus of the referenced element, and return focus to previously focused element on cleanup // and find all the tabbable elements in the referenced element useEffect(() => { const { current } = ref if (current) { const focusableChildNodes = findTabbableDescendants(current) if (options.focusOnRender) { current.focus() } setTabbableElements(focusableChildNodes) } return () => { const { current } = previouseFocusedElement if (current instanceof HTMLElement && options.returnFocus) { current.focus() } } }, [options.focusOnRender, options.returnFocus, ref, setTabbableElements]) const handleUserKeyPress = useCallback( (event: KeyboardEvent) => { const { code, shiftKey } = event const first = tabbableElements[0] const last = tabbableElements[tabbableElements.length - 1] const currentActiveElement = document.activeElement // Scope current tabs to current root element if (isWithinCurrentElementScope([...tabbableElements, ref.current])) { if (code === TAB_KEY) { if ( currentActiveElement === first || currentActiveElement === ref.current ) { // move focus to last element if shift+tab while currently focusing the first tabbable element if (shiftKey) { event.preventDefault() last.focus() } } if (currentActiveElement === last) { // move focus back to first if tabbing while currently focusing the last tabbable element if (!shiftKey) { event.preventDefault() first.focus() } } } } }, [ref, tabbableElements] ) useHandleKeyPress(handleUserKeyPress) return ref } function isWithinCurrentElementScope( elementList: (HTMLInputElement | Element | null)[] ) { const currentActiveElement = document.activeElement return elementList.includes(currentActiveElement) }