diff --git a/src/composables/graph/useMoreOptionsMenu.ts b/src/composables/graph/useMoreOptionsMenu.ts index 349a1d7f94..77f4442ecc 100644 --- a/src/composables/graph/useMoreOptionsMenu.ts +++ b/src/composables/graph/useMoreOptionsMenu.ts @@ -80,8 +80,12 @@ export function showNodeOptions( } /** - * Hide the node options popover + * Check if the node options menu is currently open */ +export function isNodeOptionsOpen(): boolean { + return nodeOptionsInstance?.isOpen.value ?? false +} + interface NodeOptionsInstance { toggle: (event: Event) => void show: (event: MouseEvent) => void diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts index 1f017afc31..7ce0846fa6 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.test.ts @@ -7,6 +7,7 @@ import WidgetTextarea from './WidgetTextarea.vue' import { createMockWidget } from './widgetTestUtils' const mockCopyToClipboard = vi.hoisted(() => vi.fn()) +const mockIsNodeOptionsOpen = vi.hoisted(() => vi.fn(() => false)) vi.mock('@/composables/useCopyToClipboard', () => ({ useCopyToClipboard: vi.fn().mockReturnValue({ @@ -14,6 +15,10 @@ vi.mock('@/composables/useCopyToClipboard', () => ({ }) })) +vi.mock('@/composables/graph/useMoreOptionsMenu', () => ({ + isNodeOptionsOpen: mockIsNodeOptionsOpen +})) + function createTextareaWidget( value: string = 'default text', options: SimplifiedWidget['options'] = {}, @@ -277,3 +282,43 @@ describe('WidgetTextarea Value Binding', () => { }) }) }) + +describe('WidgetTextarea contextmenu', () => { + it('prevents browser menu on first right-click (menu closed)', () => { + mockIsNodeOptionsOpen.mockReturnValue(false) + const widget = createTextareaWidget('test') + const wrapper = mountComponent(widget, 'test') + const textarea = wrapper.find('textarea') + + const event = new MouseEvent('contextmenu', { + bubbles: true, + cancelable: true + }) + const preventDefaultSpy = vi.spyOn(event, 'preventDefault') + const stopPropagationSpy = vi.spyOn(event, 'stopPropagation') + + textarea.element.dispatchEvent(event) + + expect(preventDefaultSpy).toHaveBeenCalled() + expect(stopPropagationSpy).not.toHaveBeenCalled() + }) + + it('allows browser menu on second right-click (menu open)', () => { + mockIsNodeOptionsOpen.mockReturnValue(true) + const widget = createTextareaWidget('test') + const wrapper = mountComponent(widget, 'test') + const textarea = wrapper.find('textarea') + + const event = new MouseEvent('contextmenu', { + bubbles: true, + cancelable: true + }) + const preventDefaultSpy = vi.spyOn(event, 'preventDefault') + const stopPropagationSpy = vi.spyOn(event, 'stopPropagation') + + textarea.element.dispatchEvent(event) + + expect(preventDefaultSpy).not.toHaveBeenCalled() + expect(stopPropagationSpy).toHaveBeenCalled() + }) +}) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue index 4023deaa33..2f810124f8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue @@ -31,7 +31,7 @@ @pointerdown.capture.stop @pointermove.capture.stop @pointerup.capture.stop - @contextmenu.capture.stop + @contextmenu.capture="handleContextMenu" />