From f5ac48c5beadbd91a43affed43bdd8a0cb064081 Mon Sep 17 00:00:00 2001 From: Johnpaul Date: Wed, 31 Dec 2025 00:48:35 +0100 Subject: [PATCH] fix: spacebar panning in vueNodes mode Forward keydown events to litegraph's processKey when Vue nodes have focus. Litegraph only binds keydown to the canvas element, so spacebar panning didn't work when Vue nodes were focused. Also sync pointer state with litegraph canvas during link dragging to enable spacebar panning while dragging connections. Fixes #7806 --- .../useNodePointerInteractions.test.ts | 59 ++++++++++++++++++- .../composables/useNodePointerInteractions.ts | 30 +++++++++- .../composables/useSlotLinkInteraction.ts | 13 ++++ 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts b/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts index c3410051a..06de292e3 100644 --- a/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts +++ b/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.test.ts @@ -2,16 +2,33 @@ import { setActivePinia } from 'pinia' import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick, ref } from 'vue' -import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions' -import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers' import { createTestingPinia } from '@pinia/testing' + import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import type { NodeLayout } from '@/renderer/core/layout/types' +import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers' +import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions' import { useNodeDrag } from '@/renderer/extensions/vueNodes/layout/useNodeDrag' const forwardEventToCanvasMock = vi.fn() const selectedItemsState: { items: Array<{ id?: string }> } = { items: [] } +const mockCanvas = vi.hoisted(() => { + const canvasElement = document.createElement('canvas') + return { + canvas: canvasElement, + processKey: vi.fn() + } +}) + +vi.mock('@/scripts/app', () => ({ + app: { + get canvas() { + return mockCanvas + } + } +})) + // Mock the dependencies vi.mock('@/renderer/core/canvas/useCanvasInteractions', () => ({ useCanvasInteractions: () => ({ @@ -324,4 +341,42 @@ describe('useNodePointerInteractions', () => { true ) }) + + describe('keydown forwarding for spacebar panning', () => { + it('forwards keydown events to canvas.processKey when target is not the canvas', () => { + // Initialize the composable to set up keydown forwarding + useNodePointerInteractions('test-node-123') + + // Create a div to simulate a Vue node element + const vueNodeElement = document.createElement('div') + document.body.appendChild(vueNodeElement) + + // Dispatch keydown event on the Vue node element (not the canvas) + const keydownEvent = new KeyboardEvent('keydown', { + key: ' ', + bubbles: true + }) + vueNodeElement.dispatchEvent(keydownEvent) + + // Should forward to canvas.processKey + expect(mockCanvas.processKey).toHaveBeenCalledWith(keydownEvent) + + document.body.removeChild(vueNodeElement) + }) + + it('does not forward keydown events when target is the canvas itself', () => { + useNodePointerInteractions('test-node-123') + mockCanvas.processKey.mockClear() + + // Dispatch keydown event directly on the canvas element + const keydownEvent = new KeyboardEvent('keydown', { + key: ' ', + bubbles: true + }) + mockCanvas.canvas.dispatchEvent(keydownEvent) + + // Should NOT forward (canvas handles it directly) + expect(mockCanvas.processKey).not.toHaveBeenCalled() + }) + }) }) diff --git a/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts b/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts index 0dfdae51b..4bc74adda 100644 --- a/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts +++ b/src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts @@ -6,12 +6,34 @@ import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle' import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers' -import { isMultiSelectKey } from '@/renderer/extensions/vueNodes/utils/selectionUtils' import { useNodeDrag } from '@/renderer/extensions/vueNodes/layout/useNodeDrag' +import { isMultiSelectKey } from '@/renderer/extensions/vueNodes/utils/selectionUtils' +import { app } from '@/scripts/app' + +// Forward keydown events to litegraph's processKey when Vue nodes have focus +let keydownForwardingInitialized = false + +function initKeydownForwarding() { + if (keydownForwardingInitialized) return + keydownForwardingInitialized = true + + document.addEventListener( + 'keydown', + (e) => { + const canvas = app.canvas + if (!canvas) return + if (e.target === canvas.canvas) return + canvas.processKey(e) + }, + true + ) +} export function useNodePointerInteractions( nodeIdRef: MaybeRefOrGetter ) { + initKeydownForwarding() + const { startDrag, endDrag, handleDrag } = useNodeDrag() // Use canvas interactions for proper wheel event handling and pointer event capture control const { forwardEventToCanvas, shouldHandleNodePointerEvents } = @@ -65,6 +87,12 @@ export function useNodePointerInteractions( function onPointermove(event: PointerEvent) { if (forwardMiddlePointerIfNeeded(event)) return + // Don't handle pointer events when canvas is in panning mode - forward to canvas instead + if (!shouldHandleNodePointerEvents.value) { + forwardEventToCanvas(event) + return + } + // Don't activate drag while resizing if (layoutStore.isResizingVueNodes.value) return diff --git a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts index 6e11902d8..d827c3890 100644 --- a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts +++ b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts @@ -293,6 +293,9 @@ export function useSlotLinkInteraction({ raf.cancel() dragContext.dispose() clearCompatible() + if (app.canvas?.pointer) { + app.canvas.pointer.isDown = false + } } const updatePointerState = (event: PointerEvent) => { @@ -409,6 +412,12 @@ export function useSlotLinkInteraction({ const handlePointerMove = (event: PointerEvent) => { if (!pointerSession.matches(event)) return + + const canvas = app.canvas + if (canvas?.read_only && canvas.dragging_canvas) { + canvas.processMouseMove(event) + } + event.stopPropagation() dragContext.pendingPointerMove = { @@ -703,6 +712,10 @@ export function useSlotLinkInteraction({ ) pointerSession.begin(event.pointerId) + if (canvas.pointer) { + canvas.pointer.isDown = true + } + canvas.last_mouse = [event.clientX, event.clientY] toCanvasPointerEvent(event) updatePointerState(event)