mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 22:09:55 +00:00
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
This commit is contained in:
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<string>
|
||||
) {
|
||||
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user