diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts index 5dade163b5..9ecfe51f2d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts @@ -13,7 +13,11 @@ interface UseCanvasContextMenuProps { /** * Hook for managing workflow canvas context menus. - * Handles right-click events, menu state, click-outside detection, and block info extraction. + * + * Handles right-click events on nodes, pane, and selections with proper multi-select behavior. + * + * @param props - Hook configuration + * @returns Context menu state and handlers */ export function useCanvasContextMenu({ blocks, getNodes, setNodes }: UseCanvasContextMenuProps) { const [activeMenu, setActiveMenu] = useState(null) @@ -46,19 +50,29 @@ export function useCanvasContextMenu({ blocks, getNodes, setNodes }: UseCanvasCo event.stopPropagation() const isMultiSelect = event.shiftKey || event.metaKey || event.ctrlKey - setNodes((nodes) => - nodes.map((n) => ({ - ...n, - selected: isMultiSelect ? (n.id === node.id ? true : n.selected) : n.id === node.id, - })) - ) - - const selectedNodes = getNodes().filter((n) => n.selected) - const nodesToUse = isMultiSelect - ? selectedNodes.some((n) => n.id === node.id) - ? selectedNodes - : [...selectedNodes, node] - : [node] + const currentSelectedNodes = getNodes().filter((n) => n.selected) + const isClickedNodeSelected = currentSelectedNodes.some((n) => n.id === node.id) + + let nodesToUse: Node[] + if (isClickedNodeSelected) { + nodesToUse = currentSelectedNodes + } else if (isMultiSelect) { + nodesToUse = [...currentSelectedNodes, node] + setNodes((nodes) => + nodes.map((n) => ({ + ...n, + selected: n.id === node.id ? true : n.selected, + })) + ) + } else { + nodesToUse = [node] + setNodes((nodes) => + nodes.map((n) => ({ + ...n, + selected: n.id === node.id, + })) + ) + } setPosition({ x: event.clientX, y: event.clientY }) setSelectedBlocks(nodesToBlockInfos(nodesToUse)) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-context-menu.ts b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-context-menu.ts index 35b8546b2e..d87a3258d4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-context-menu.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-context-menu.ts @@ -27,18 +27,13 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) { const [isOpen, setIsOpen] = useState(false) const [position, setPosition] = useState({ x: 0, y: 0 }) const menuRef = useRef(null) - // Used to prevent click-outside dismissal when trigger is clicked const dismissPreventedRef = useRef(false) - /** - * Handle right-click event - */ const handleContextMenu = useCallback( (e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() - // Calculate position relative to viewport const x = e.clientX const y = e.clientY @@ -50,17 +45,10 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) { [onContextMenu] ) - /** - * Close the context menu - */ const closeMenu = useCallback(() => { setIsOpen(false) }, []) - /** - * Prevent the next click-outside from dismissing the menu. - * Call this on pointerdown of a toggle trigger to allow proper toggle behavior. - */ const preventDismiss = useCallback(() => { dismissPreventedRef.current = true }, []) @@ -72,7 +60,6 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) { if (!isOpen) return const handleClickOutside = (e: MouseEvent) => { - // Check if dismissal was prevented (e.g., by toggle trigger's pointerdown) if (dismissPreventedRef.current) { dismissPreventedRef.current = false return @@ -82,7 +69,6 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) { } } - // Small delay to prevent immediate close from the same click that opened the menu const timeoutId = setTimeout(() => { document.addEventListener('click', handleClickOutside) }, 0)