|  | /** | 
        
          |  | * Handle the open/close state of a context menu and the element that triggers it. | 
        
          |  | *   The following cases are taken cared of: | 
        
          |  | *     - Clicking the trigger element will toggle the menu open or close | 
        
          |  | *     - If the menu is open, clicking outside the menu will close it | 
        
          |  | *     - If the menu is open, pressing the esc key will close it | 
        
          |  | *     - The menu may be opened/closed externally since this hook returns the setState function | 
        
          |  | * | 
        
          |  | *   Example Usage: | 
        
          |  | *     import { useHandleMenuOpenState } from 'utils/userInputHandlers'; | 
        
          |  | *     . . . | 
        
          |  | *     const dropdownRef = React.useRef<HTMLDivElement>(null); | 
        
          |  | *     const menuRef = React.useRef<HTMLDivElement>(null); | 
        
          |  | *     . . . | 
        
          |  | *     const [isOpened, setIsOpened] = useHandleMenuOpenState(dropdownRef, menuRef); | 
        
          |  | *     . . . | 
        
          |  | *     <div ... ref={dropdownRef} ... /> | 
        
          |  | *     <div ... ref={menuRef} ... /> | 
        
          |  | */ | 
        
          |  |  | 
        
          |  | import * as React from 'react'; | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * menuTriggerRef  - The element that will trigger the opening of the menu | 
        
          |  | * menuRef - The element that represents the opened menu | 
        
          |  | * @returns [boolean, Function] - Two element array where the first element is the isOpen state and | 
        
          |  | * the second element is the setState function, similarly to the React.useState hook. | 
        
          |  | */ | 
        
          |  | export type HandleMenuOpenStateHook = ( | 
        
          |  | menuTriggerRef: React.RefObject<HTMLElement>, | 
        
          |  | menuRef: React.RefObject<HTMLElement> | 
        
          |  | ) => [boolean, Function]; | 
        
          |  |  | 
        
          |  | export const useHandleMenuOpenState: HandleMenuOpenStateHook = ( | 
        
          |  | menuTriggerRef, | 
        
          |  | menuRef | 
        
          |  | ) => { | 
        
          |  | const [isOpened, setisOpened] = React.useState(false); | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Mousedown handler to toggle menu open state when menutrigger element is clicked | 
        
          |  | */ | 
        
          |  | const toggleMenuHandler = React.useCallback(() => { | 
        
          |  | setisOpened((prevVal) => !prevVal); | 
        
          |  | }, []); | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Mousedown handler for detecting clicks outside the context / dropdown menu | 
        
          |  | * | 
        
          |  | * @param e The mouse event when the mouse button is clicked down | 
        
          |  | */ | 
        
          |  | const outsideMenuClickHandler = React.useCallback((e: MouseEvent) => { | 
        
          |  | if (menuTriggerRef.current && menuRef.current) { | 
        
          |  | // Clicking outside the context menu should close it | 
        
          |  | // The menu trigger is ignored since it acts as a toggle, @see toggleMenuHandler() | 
        
          |  | if ( | 
        
          |  | !e.composedPath().includes(menuRef.current) && | 
        
          |  | !e.composedPath().includes(menuTriggerRef.current) | 
        
          |  | ) { | 
        
          |  | setisOpened(false); | 
        
          |  | } | 
        
          |  | } | 
        
          |  | }, []); | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Keydown handler that closes the menu when the escape key is pressed | 
        
          |  | * | 
        
          |  | * @param e The keyboard event when the key is pressed | 
        
          |  | */ | 
        
          |  | const escKeyHandler = React.useCallback((e: KeyboardEvent) => { | 
        
          |  | if (e.key === 'Escape') { | 
        
          |  | setisOpened(false); | 
        
          |  | } | 
        
          |  | }, []); | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Sets up toggle menu handler | 
        
          |  | */ | 
        
          |  | React.useEffect(() => { | 
        
          |  | if (menuTriggerRef.current) { | 
        
          |  | menuTriggerRef.current.addEventListener('mousedown', toggleMenuHandler); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | return () => { | 
        
          |  | if (menuTriggerRef.current) { | 
        
          |  | menuTriggerRef.current.removeEventListener( | 
        
          |  | 'mousedown', | 
        
          |  | toggleMenuHandler | 
        
          |  | ); | 
        
          |  | } | 
        
          |  | }; | 
        
          |  | }, [menuTriggerRef]); | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Registers the appropriate listeners to close the opened menu, | 
        
          |  | * otherwise the listeners are removed. | 
        
          |  | */ | 
        
          |  | React.useEffect(() => { | 
        
          |  | if (menuTriggerRef.current) { | 
        
          |  | if (isOpened) { | 
        
          |  | document.addEventListener('mousedown', outsideMenuClickHandler); | 
        
          |  | document.addEventListener('keydown', escKeyHandler); | 
        
          |  | } else { | 
        
          |  | document.removeEventListener('mousedown', outsideMenuClickHandler); | 
        
          |  | document.removeEventListener('keydown', escKeyHandler); | 
        
          |  | } | 
        
          |  | } | 
        
          |  | }, [isOpened]); | 
        
          |  |  | 
        
          |  | // Functional Component that uses this hook can get read/write access to the open state | 
        
          |  | return [isOpened, setisOpened]; | 
        
          |  | }; |