Skip to content

Commit e14cebe

Browse files
authored
fix(context-menu): preserve selection when right-clicking selected block (#2991)
* fix(context-menu): preserve selection when right-clicking selected block * added tsdoc
1 parent 404d8c0 commit e14cebe

File tree

2 files changed

+28
-28
lines changed

2 files changed

+28
-28
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-canvas-context-menu.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ interface UseCanvasContextMenuProps {
1313

1414
/**
1515
* Hook for managing workflow canvas context menus.
16-
* Handles right-click events, menu state, click-outside detection, and block info extraction.
16+
*
17+
* Handles right-click events on nodes, pane, and selections with proper multi-select behavior.
18+
*
19+
* @param props - Hook configuration
20+
* @returns Context menu state and handlers
1721
*/
1822
export function useCanvasContextMenu({ blocks, getNodes, setNodes }: UseCanvasContextMenuProps) {
1923
const [activeMenu, setActiveMenu] = useState<MenuType>(null)
@@ -46,19 +50,29 @@ export function useCanvasContextMenu({ blocks, getNodes, setNodes }: UseCanvasCo
4650
event.stopPropagation()
4751

4852
const isMultiSelect = event.shiftKey || event.metaKey || event.ctrlKey
49-
setNodes((nodes) =>
50-
nodes.map((n) => ({
51-
...n,
52-
selected: isMultiSelect ? (n.id === node.id ? true : n.selected) : n.id === node.id,
53-
}))
54-
)
55-
56-
const selectedNodes = getNodes().filter((n) => n.selected)
57-
const nodesToUse = isMultiSelect
58-
? selectedNodes.some((n) => n.id === node.id)
59-
? selectedNodes
60-
: [...selectedNodes, node]
61-
: [node]
53+
const currentSelectedNodes = getNodes().filter((n) => n.selected)
54+
const isClickedNodeSelected = currentSelectedNodes.some((n) => n.id === node.id)
55+
56+
let nodesToUse: Node[]
57+
if (isClickedNodeSelected) {
58+
nodesToUse = currentSelectedNodes
59+
} else if (isMultiSelect) {
60+
nodesToUse = [...currentSelectedNodes, node]
61+
setNodes((nodes) =>
62+
nodes.map((n) => ({
63+
...n,
64+
selected: n.id === node.id ? true : n.selected,
65+
}))
66+
)
67+
} else {
68+
nodesToUse = [node]
69+
setNodes((nodes) =>
70+
nodes.map((n) => ({
71+
...n,
72+
selected: n.id === node.id,
73+
}))
74+
)
75+
}
6276

6377
setPosition({ x: event.clientX, y: event.clientY })
6478
setSelectedBlocks(nodesToBlockInfos(nodesToUse))

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-context-menu.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,13 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) {
2727
const [isOpen, setIsOpen] = useState(false)
2828
const [position, setPosition] = useState<ContextMenuPosition>({ x: 0, y: 0 })
2929
const menuRef = useRef<HTMLDivElement>(null)
30-
// Used to prevent click-outside dismissal when trigger is clicked
3130
const dismissPreventedRef = useRef(false)
3231

33-
/**
34-
* Handle right-click event
35-
*/
3632
const handleContextMenu = useCallback(
3733
(e: React.MouseEvent) => {
3834
e.preventDefault()
3935
e.stopPropagation()
4036

41-
// Calculate position relative to viewport
4237
const x = e.clientX
4338
const y = e.clientY
4439

@@ -50,17 +45,10 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) {
5045
[onContextMenu]
5146
)
5247

53-
/**
54-
* Close the context menu
55-
*/
5648
const closeMenu = useCallback(() => {
5749
setIsOpen(false)
5850
}, [])
5951

60-
/**
61-
* Prevent the next click-outside from dismissing the menu.
62-
* Call this on pointerdown of a toggle trigger to allow proper toggle behavior.
63-
*/
6452
const preventDismiss = useCallback(() => {
6553
dismissPreventedRef.current = true
6654
}, [])
@@ -72,7 +60,6 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) {
7260
if (!isOpen) return
7361

7462
const handleClickOutside = (e: MouseEvent) => {
75-
// Check if dismissal was prevented (e.g., by toggle trigger's pointerdown)
7663
if (dismissPreventedRef.current) {
7764
dismissPreventedRef.current = false
7865
return
@@ -82,7 +69,6 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) {
8269
}
8370
}
8471

85-
// Small delay to prevent immediate close from the same click that opened the menu
8672
const timeoutId = setTimeout(() => {
8773
document.addEventListener('click', handleClickOutside)
8874
}, 0)

0 commit comments

Comments
 (0)