diff --git a/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx b/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx index 247184349d..08f0f4ed3e 100644 --- a/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx +++ b/web/app/components/workflow/__tests__/selection-contextmenu.spec.tsx @@ -81,19 +81,31 @@ describe('SelectionContextmenu', () => { expect(screen.queryByText('operator.vertical')).not.toBeInTheDocument() }) - it('should keep the menu inside the workflow container bounds', () => { + it('should still render the menu when the requested position exceeds workflow container bounds', () => { const nodes = [ createNode({ id: 'n1', selected: true, width: 80, height: 40 }), createNode({ id: 'n2', selected: true, position: { x: 140, y: 0 }, width: 80, height: 40 }), ] const { store } = renderSelectionMenu({ nodes }) + const container = document.querySelector('#workflow-container') as HTMLDivElement + + vi.spyOn(container, 'getBoundingClientRect').mockReturnValue({ + x: 16, + y: 24, + left: 16, + top: 24, + right: 816, + bottom: 624, + width: 800, + height: 600, + toJSON: () => ({}), + }) act(() => { store.setState({ selectionMenu: { left: 780, top: 590 } }) }) - const menu = screen.getByTestId('selection-contextmenu') - expect(menu).toHaveStyle({ left: '540px', top: '210px' }) + expect(screen.getByTestId('selection-contextmenu-item-left')).toBeInTheDocument() }) it('should close itself when only one node is selected', async () => { diff --git a/web/app/components/workflow/selection-contextmenu.tsx b/web/app/components/workflow/selection-contextmenu.tsx index 434f4bc4a5..7f3d242eef 100644 --- a/web/app/components/workflow/selection-contextmenu.tsx +++ b/web/app/components/workflow/selection-contextmenu.tsx @@ -25,7 +25,6 @@ import { ContextMenuGroupLabel, ContextMenuItem, ContextMenuSeparator, - ContextMenuTrigger, } from '@/app/components/base/ui/context-menu' import CreateSnippetDialog from './create-snippet-dialog' import { useNodesReadOnly, useNodesSyncDraft } from './hooks' @@ -295,6 +294,25 @@ const SelectionContextmenu = () => { return getMenuPosition(selectionMenu, container?.getBoundingClientRect()) }, [selectionMenu]) + const anchor = useMemo(() => { + if (!selectionMenu) + return null + + const container = document.querySelector('#workflow-container') + const containerRect = container?.getBoundingClientRect() + if (!containerRect) + return null + + return { + getBoundingClientRect: () => DOMRect.fromRect({ + width: 0, + height: 0, + x: containerRect.left + menuPosition.left, + y: containerRect.top + menuPosition.top, + }), + } + }, [menuPosition.left, menuPosition.top, selectionMenu]) + useEffect(() => { if (selectionMenu && selectedNodes.length <= 1) handleSelectionContextmenuCancel() @@ -382,18 +400,11 @@ const SelectionContextmenu = () => { } }, [store, workflowStore, selectedNodes, getNodesReadOnly, handleSyncWorkflowDraft, saveStateToHistory, handleSelectionContextmenuCancel]) - if (!selectionMenu && !isCreateSnippetDialogOpen) + if ((!selectionMenu || !anchor) && !isCreateSnippetDialogOpen) return null return ( -