import { useCallback, useEffect, useRef, useState } from 'react'; import type { FiltersMap } from '@wix/bex-core'; import type { CellInteractionState } from '../../state/EditableTable/CellInteractionState'; import type { Cell } from '../../state/EditableTable'; const MOUSE_LEAVE_PADDING = 30; const isInsidePaddedRect = (e: MouseEvent, rect: DOMRect, padding: number) => e.clientX >= rect.left - padding && e.clientX <= rect.right + padding && e.clientY >= rect.top - padding && e.clientY <= rect.bottom + padding; export function useEditableTableContextMenu( cellInteraction: CellInteractionState, ) { const [position, setPosition] = useState<{ x: number; y: number } | null>( null, ); const [cell, setCell] = useState(null); const menuRef = useRef(null); const cellElRef = useRef(null); const handleContextMenu = useCallback( (e: React.MouseEvent) => { const cellEl = (e.target as HTMLElement).closest( '[data-row-key][data-column-id]', ); if (!cellEl) { return; } e.preventDefault(); const rowKey = cellEl.getAttribute('data-row-key')!; const columnId = cellEl.getAttribute('data-column-id')!; if (!cellInteraction.isCellSelected(rowKey, columnId)) { cellInteraction.focusCell(rowKey, columnId); } cellElRef.current = cellEl; setPosition({ x: e.clientX, y: e.clientY }); setCell({ rowKey, columnId }); }, [cellInteraction], ); const handleClose = useCallback(() => { setPosition(null); setCell(null); cellElRef.current = null; }, []); useEffect(() => { if (!position) { return; } const isInside = (target: EventTarget | null) => menuRef.current?.contains(target as Node); const onMouseDown = (e: MouseEvent) => { if (!isInside(e.target)) { handleClose(); } }; const onScroll = (e: Event) => { if (!isInside(e.target)) { handleClose(); } }; const onMouseMove = (e: MouseEvent) => { if (!menuRef.current) { return; } const menuRect = menuRef.current.getBoundingClientRect(); // Skip until the menu has been laid out if (menuRect.width === 0 || menuRect.height === 0) { return; } // Stay open if cursor is near the menu if (isInsidePaddedRect(e, menuRect, MOUSE_LEAVE_PADDING)) { return; } // Stay open if cursor is on the cell that triggered the menu if (cellElRef.current) { const cellRect = cellElRef.current.getBoundingClientRect(); if (isInsidePaddedRect(e, cellRect, 0)) { return; } } handleClose(); }; document.addEventListener('mousedown', onMouseDown); document.addEventListener('scroll', onScroll, true); document.addEventListener('mousemove', onMouseMove); return () => { document.removeEventListener('mousedown', onMouseDown); document.removeEventListener('scroll', onScroll, true); document.removeEventListener('mousemove', onMouseMove); }; }, [position, handleClose]); return { position, cell, menuRef, handleContextMenu, handleClose }; }