From fd686fc9164b389606fd62d585d20c37cd1475e1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 24 Jan 2026 21:54:11 -0800 Subject: [PATCH 1/2] fix(context-menu): preserve selection when right-clicking selected block --- .../hooks/use-canvas-context-menu.ts | 40 +++++++++++-------- .../sidebar/hooks/use-context-menu.ts | 14 ------- 2 files changed, 23 insertions(+), 31 deletions(-) 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..09da07ceef 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 @@ -11,10 +11,6 @@ interface UseCanvasContextMenuProps { setNodes: (updater: (nodes: Node[]) => Node[]) => void } -/** - * Hook for managing workflow canvas context menus. - * Handles right-click events, menu state, click-outside detection, and block info extraction. - */ export function useCanvasContextMenu({ blocks, getNodes, setNodes }: UseCanvasContextMenuProps) { const [activeMenu, setActiveMenu] = useState(null) const [position, setPosition] = useState({ x: 0, y: 0 }) @@ -46,19 +42,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) From 24521d19f3672441873a4657cb7d3f8802a98c44 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 24 Jan 2026 21:58:48 -0800 Subject: [PATCH 2/2] added tsdoc --- .../w/[workflowId]/hooks/use-canvas-context-menu.ts | 8 ++++++++ 1 file changed, 8 insertions(+) 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 09da07ceef..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 @@ -11,6 +11,14 @@ interface UseCanvasContextMenuProps { setNodes: (updater: (nodes: Node[]) => Node[]) => void } +/** + * Hook for managing workflow canvas context menus. + * + * 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) const [position, setPosition] = useState({ x: 0, y: 0 })