/** * This is a copy-paste of https://github.com/lukasbach/chakra-ui-contextmenu with a small change. * * The reactflow background element somehow prevents the chakra `useOutsideClick()` hook from working. * With a menu open, clicking on the reactflow background element doesn't close the menu. * * Reactflow does provide an `onPaneClick` to handle clicks on the background element, but it is not * straightforward to programatically close the menu. * * As a (hopefully temporary) workaround, we will use a dirty hack: * - create `globalContextMenuCloseTrigger: number` in `ui` slice * - increment it in `onPaneClick` (and wherever else we want to close the menu) * - `useEffect()` to close the menu when `globalContextMenuCloseTrigger` changes */ import type { MenuButtonProps, MenuProps, PortalProps } from '@chakra-ui/react'; import { Portal, useEventListener } from '@chakra-ui/react'; import { InvMenu, InvMenuButton } from 'common/components/InvMenu/wrapper'; import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger'; import { useCallback, useEffect, useRef, useState } from 'react'; export interface InvContextMenuProps { renderMenu: () => JSX.Element | null; children: (ref: React.MutableRefObject) => JSX.Element | null; menuProps?: Omit & { children?: React.ReactNode }; portalProps?: Omit & { children?: React.ReactNode }; menuButtonProps?: MenuButtonProps; } export const InvContextMenu = ( props: InvContextMenuProps ) => { const [isOpen, setIsOpen] = useState(false); const [isRendered, setIsRendered] = useState(false); const [isDeferredOpen, setIsDeferredOpen] = useState(false); const [position, setPosition] = useState<[number, number]>([0, 0]); const targetRef = useRef(null); useEffect(() => { if (isOpen) { setTimeout(() => { setIsRendered(true); setTimeout(() => { setIsDeferredOpen(true); }); }); } else { setIsDeferredOpen(false); const timeout = setTimeout(() => { setIsRendered(isOpen); }, 1000); return () => clearTimeout(timeout); } }, [isOpen]); const onClose = useCallback(() => { setIsOpen(false); setIsDeferredOpen(false); setIsRendered(false); }, []); // This is the change from the original chakra-ui-contextmenu // Close all menus when the globalContextMenuCloseTrigger changes useGlobalMenuCloseTrigger(onClose); useEventListener('contextmenu', (e) => { if ( targetRef.current?.contains(e.target as HTMLElement) || e.target === targetRef.current ) { e.preventDefault(); setIsOpen(true); setPosition([e.pageX, e.pageY]); } else { setIsOpen(false); } }); const onCloseHandler = useCallback(() => { props.menuProps?.onClose?.(); setIsOpen(false); }, [props.menuProps]); return ( <> {props.children(targetRef)} {isRendered && ( {props.renderMenu()} )} ); };