Compare commits

...

1 Commits

Author SHA1 Message Date
Kelly Yang
d1f38585c6 fix: clear isDraggingVueNodes when pointerup fires while canvas is read-only
When a user drags a VueNode then presses Space (which sets canvas.read_only
= true), releasing the mouse triggers onPointerup with
shouldHandleNodePointerEvents = false. The early-return path was skipping
safeDragEnd, leaving isDraggingVueNodes stuck true and causing the node to
follow the cursor after the drag was released.

Call safeDragEnd before forwarding to canvas whenever a drag was in progress
and canvas becomes read-only between pointerdown and pointerup.
2026-05-02 23:45:44 -07:00
2 changed files with 37 additions and 1 deletions

View File

@@ -12,11 +12,15 @@ import { useNodeDrag } from '@/renderer/extensions/vueNodes/layout/useNodeDrag'
const forwardEventToCanvasMock = vi.fn()
const selectedItemsState: { items: Array<{ id?: string }> } = { items: [] }
const { shouldHandleNodePointerEventsMock } = vi.hoisted(() => ({
shouldHandleNodePointerEventsMock: { value: true } as { value: boolean }
}))
// Mock the dependencies
vi.mock('@/renderer/core/canvas/useCanvasInteractions', () => ({
useCanvasInteractions: () => ({
forwardEventToCanvas: forwardEventToCanvasMock,
shouldHandleNodePointerEvents: ref(true)
shouldHandleNodePointerEvents: shouldHandleNodePointerEventsMock
})
}))
@@ -136,6 +140,7 @@ describe('useNodePointerInteractions', () => {
vi.resetAllMocks()
selectedItemsState.items = []
setActivePinia(createTestingPinia())
shouldHandleNodePointerEventsMock.value = true
})
it('should only start drag on left-click', async () => {
@@ -270,6 +275,32 @@ describe('useNodePointerInteractions', () => {
expect(handleNodeSelect).toHaveBeenCalledTimes(1)
})
it('clears drag state when pointerup fires while canvas is read-only (e.g. Space held)', async () => {
const { pointerHandlers } = useNodePointerInteractions('test-node-123')
// Start a drag normally
pointerHandlers.onPointerdown(
createPointerEvent('pointerdown', { clientX: 100, clientY: 100 })
)
pointerHandlers.onPointermove(
createPointerEvent('pointermove', {
clientX: 115,
clientY: 115,
buttons: 1
})
)
expect(layoutStore.isDraggingVueNodes.value).toBe(true)
// Canvas goes read-only (Space pressed) before mouse is released
shouldHandleNodePointerEventsMock.value = false
pointerHandlers.onPointerup(
createPointerEvent('pointerup', { clientX: 115, clientY: 115 })
)
expect(layoutStore.isDraggingVueNodes.value).toBe(false)
})
it('on ctrl+click: calls toggleNodeSelectionAfterPointerUp on pointer up (not pointer down)', async () => {
const { pointerHandlers } = useNodePointerInteractions('test-node-123')
const { toggleNodeSelectionAfterPointerUp } = useNodeEventHandlers()

View File

@@ -124,6 +124,11 @@ export function useNodePointerInteractions(
// Don't handle pointer events when canvas is in panning mode - forward to canvas instead
const canHandlePointer = shouldHandleNodePointerEvents.value
if (!canHandlePointer) {
// Space (or other read_only triggers) can go active between pointerdown and
// pointerup; call safeDragEnd so isDraggingVueNodes doesn't get stuck true.
if (hasDraggingStarted || layoutStore.isDraggingVueNodes.value) {
safeDragEnd(event)
}
forwardEventToCanvas(event)
return
}