fix(context-menu): preserve selection when right-clicking selected block (#2991)

* fix(context-menu): preserve selection when right-clicking selected block

* added tsdoc
This commit is contained in:
Waleed
2026-01-24 22:05:27 -08:00
committed by GitHub
parent 404d8c006e
commit e14cebeec5
2 changed files with 27 additions and 27 deletions

View File

@@ -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<MenuType>(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 currentSelectedNodes = getNodes().filter((n) => n.selected)
const isClickedNodeSelected = currentSelectedNodes.some((n) => 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]
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))

View File

@@ -27,18 +27,13 @@ export function useContextMenu({ onContextMenu }: UseContextMenuProps = {}) {
const [isOpen, setIsOpen] = useState(false)
const [position, setPosition] = useState<ContextMenuPosition>({ x: 0, y: 0 })
const menuRef = useRef<HTMLDivElement>(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)