import { useCallback, useRef, useState } from "react"; import { TRANSITIONS_IN_MILLISECONDS } from "utils/constants"; import { isSafari } from "utils/functions"; export type MenuItem = { action?: () => void; checked?: boolean; disabled?: boolean; icon?: string; label?: string; menu?: MenuItem[]; primary?: boolean; seperator?: boolean; toggle?: boolean; }; export type MenuState = { items?: MenuItem[]; x?: number; y?: number; }; export type ContextMenuCapture = { onContextMenuCapture: ( event?: React.MouseEvent | React.TouchEvent, domRect?: DOMRect ) => void; onTouchEnd?: React.TouchEventHandler; onTouchMove?: React.TouchEventHandler; onTouchStart?: React.TouchEventHandler; }; type MenuContextState = { contextMenu: (getItems: () => MenuItem[]) => ContextMenuCapture; menu: MenuState; setMenu: React.Dispatch>; }; const useMenuContextState = (): MenuContextState => { const [menu, setMenu] = useState(Object.create(null) as MenuState); const touchTimer = useRef(0); const touchEvent = useRef(); const contextMenu = useCallback( (getItems: () => MenuItem[]): ContextMenuCapture => { const onContextMenuCapture = ( event?: React.MouseEvent | React.TouchEvent, domRect?: DOMRect ): void => { let x = 0; let y = 0; if (event) { if (event.cancelable) event.preventDefault(); ({ pageX: x, pageY: y } = "touches" in event ? event.touches.item?.(0) || event : event); } else if (domRect) { const { height, x: inputX, y: inputY } = domRect; x = inputX; y = inputY + height; } const items = getItems(); setMenu({ items: items.length > 0 ? items : undefined, x, y }); }; return { onContextMenuCapture, ...(isSafari() && { onTouchEnd: () => { if (touchEvent.current) { onContextMenuCapture(touchEvent.current); touchEvent.current = undefined; } window.clearTimeout(touchTimer.current); }, onTouchMove: () => { touchEvent.current = undefined; window.clearTimeout(touchTimer.current); }, onTouchStart: (event: React.TouchEvent) => { window.clearTimeout(touchTimer.current); touchTimer.current = window.setTimeout(() => { touchEvent.current = event; }, TRANSITIONS_IN_MILLISECONDS.LONG_PRESS); }, }), }; }, [] ); return { contextMenu, menu, setMenu }; }; export default useMenuContextState;