mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
fix mmb navigation when click starts on Vue nodes (#5921)
## Summary Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/5860 by updating Vue node pointer interactions to forward middle mouse button events to canvas instead of handling them locally. ## Review Focus Middle mouse button event detection logic using both `button` property and `buttons` bitmask for cross-browser compatibility. Test coverage for pointer event forwarding behavior. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5921-fix-mmb-navigation-when-click-starts-on-Vue-nodes-2826d73d3650819688eec4600666755d) by [Unito](https://www.unito.io) --------- Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
This commit is contained in:
@@ -23,7 +23,7 @@ const config: KnipConfig = {
|
||||
project: ['src/**/*.{js,ts}']
|
||||
}
|
||||
},
|
||||
ignoreBinaries: ['python3'],
|
||||
ignoreBinaries: ['python3', 'stylelint'],
|
||||
ignoreDependencies: [
|
||||
// Weird importmap things
|
||||
'@iconify/json',
|
||||
@@ -32,7 +32,8 @@ const config: KnipConfig = {
|
||||
'@primeuix/utils',
|
||||
'@primevue/icons',
|
||||
// Dev
|
||||
'@trivago/prettier-plugin-sort-imports'
|
||||
'@trivago/prettier-plugin-sort-imports',
|
||||
'stylelint'
|
||||
],
|
||||
ignore: [
|
||||
// Auto generated manager types
|
||||
|
||||
22
src/base/pointerUtils.ts
Normal file
22
src/base/pointerUtils.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Utilities for pointer event handling
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a pointer or mouse event is a middle button input
|
||||
* @param event - The pointer or mouse event to check
|
||||
* @returns true if the event is from the middle button/wheel
|
||||
*/
|
||||
export function isMiddlePointerInput(
|
||||
event: PointerEvent | MouseEvent
|
||||
): boolean {
|
||||
if ('button' in event && event.button === 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
if ('buttons' in event && typeof event.buttons === 'number') {
|
||||
return event.buttons === 4
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { isMiddlePointerInput } from '@/base/pointerUtils'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { app } from '@/scripts/app'
|
||||
@@ -59,6 +60,11 @@ export function useCanvasInteractions() {
|
||||
* be forwarded to canvas (e.g., space+drag for panning)
|
||||
*/
|
||||
const handlePointer = (event: PointerEvent) => {
|
||||
if (isMiddlePointerInput(event)) {
|
||||
forwardEventToCanvas(event)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if canvas exists using established pattern
|
||||
const canvas = getCanvas()
|
||||
if (!canvas) return
|
||||
|
||||
@@ -4,10 +4,12 @@ import { nextTick, ref } from 'vue'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
|
||||
|
||||
const forwardEventToCanvasMock = vi.fn()
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock('@/renderer/core/canvas/useCanvasInteractions', () => ({
|
||||
useCanvasInteractions: () => ({
|
||||
forwardEventToCanvas: vi.fn(),
|
||||
forwardEventToCanvas: forwardEventToCanvasMock,
|
||||
shouldHandleNodePointerEvents: ref(true)
|
||||
})
|
||||
}))
|
||||
@@ -69,6 +71,7 @@ const createMouseEvent = (
|
||||
describe('useNodePointerInteractions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
forwardEventToCanvasMock.mockClear()
|
||||
})
|
||||
|
||||
it('should only start drag on left-click', async () => {
|
||||
@@ -100,6 +103,34 @@ describe('useNodePointerInteractions', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('forwards middle mouse interactions to the canvas', () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
|
||||
const { pointerHandlers } = useNodePointerInteractions(
|
||||
ref(mockNodeData),
|
||||
mockOnPointerUp
|
||||
)
|
||||
|
||||
const middlePointerDown = createPointerEvent('pointerdown', { button: 1 })
|
||||
pointerHandlers.onPointerdown(middlePointerDown)
|
||||
expect(forwardEventToCanvasMock).toHaveBeenCalledWith(middlePointerDown)
|
||||
|
||||
forwardEventToCanvasMock.mockClear()
|
||||
|
||||
const middlePointerMove = createPointerEvent('pointermove', { buttons: 4 })
|
||||
pointerHandlers.onPointermove(middlePointerMove)
|
||||
expect(forwardEventToCanvasMock).toHaveBeenCalledWith(middlePointerMove)
|
||||
|
||||
forwardEventToCanvasMock.mockClear()
|
||||
|
||||
const middlePointerUp = createPointerEvent('pointerup', { button: 1 })
|
||||
pointerHandlers.onPointerup(middlePointerUp)
|
||||
expect(forwardEventToCanvasMock).toHaveBeenCalledWith(middlePointerUp)
|
||||
|
||||
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should distinguish drag from click based on distance threshold', async () => {
|
||||
const mockNodeData = createMockVueNodeData()
|
||||
const mockOnPointerUp = vi.fn()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type MaybeRefOrGetter, computed, onUnmounted, ref, toValue } from 'vue'
|
||||
|
||||
import { isMiddlePointerInput } from '@/base/pointerUtils'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
@@ -34,6 +35,12 @@ export function useNodePointerInteractions(
|
||||
const { forwardEventToCanvas, shouldHandleNodePointerEvents } =
|
||||
useCanvasInteractions()
|
||||
|
||||
const forwardMiddlePointerIfNeeded = (event: PointerEvent) => {
|
||||
if (!isMiddlePointerInput(event)) return false
|
||||
forwardEventToCanvas(event)
|
||||
return true
|
||||
}
|
||||
|
||||
// Drag state for styling
|
||||
const isDragging = ref(false)
|
||||
const dragStyle = computed(() => {
|
||||
@@ -52,6 +59,8 @@ export function useNodePointerInteractions(
|
||||
return
|
||||
}
|
||||
|
||||
if (forwardMiddlePointerIfNeeded(event)) return
|
||||
|
||||
const stopNodeDragTarget =
|
||||
event.target instanceof HTMLElement
|
||||
? event.target.closest('[data-capture-node="true"]')
|
||||
@@ -90,6 +99,8 @@ export function useNodePointerInteractions(
|
||||
}
|
||||
|
||||
const handlePointerMove = (event: PointerEvent) => {
|
||||
if (forwardMiddlePointerIfNeeded(event)) return
|
||||
|
||||
if (isDragging.value) {
|
||||
void handleDrag(event)
|
||||
}
|
||||
@@ -129,6 +140,8 @@ export function useNodePointerInteractions(
|
||||
}
|
||||
|
||||
const handlePointerUp = (event: PointerEvent) => {
|
||||
if (forwardMiddlePointerIfNeeded(event)) return
|
||||
|
||||
if (isDragging.value) {
|
||||
handleDragTermination(event, 'drag end')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user