mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
[fix] do not drag on right-click + fix refs (#5784)
## Summary Fixes drag handling logic. ## Changes Only check for drag on left-click. Adds handler logic for following pointer events: 1. drag termination 2. context menu 3. pointer cancel Adds tests. Consolidates cleanup tasks. ## Screenshots Fixed State: Ignore first failed drag, browser window didn't have context. https://github.com/user-attachments/assets/00ec685a-1ef7-4102-b19b-4cdb9b201d22 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5784-fix-do-not-drag-on-right-click-fix-refs-27a6d73d3650812ea797fccf14022568) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -38,9 +38,7 @@
|
|||||||
},
|
},
|
||||||
dragStyle
|
dragStyle
|
||||||
]"
|
]"
|
||||||
@pointerdown="handlePointerDown"
|
v-bind="pointerHandlers"
|
||||||
@pointermove="handlePointerMove"
|
|
||||||
@pointerup="handlePointerUp"
|
|
||||||
@wheel="handleWheel"
|
@wheel="handleWheel"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -232,13 +230,10 @@ onErrorCaptured((error) => {
|
|||||||
|
|
||||||
// Use layout system for node position and dragging
|
// Use layout system for node position and dragging
|
||||||
const { position, size, zIndex, resize } = useNodeLayout(() => nodeData.id)
|
const { position, size, zIndex, resize } = useNodeLayout(() => nodeData.id)
|
||||||
const {
|
const { pointerHandlers, isDragging, dragStyle } = useNodePointerInteractions(
|
||||||
handlePointerDown,
|
() => nodeData,
|
||||||
handlePointerUp,
|
handleNodeSelect
|
||||||
handlePointerMove,
|
)
|
||||||
isDragging,
|
|
||||||
dragStyle
|
|
||||||
} = useNodePointerInteractions(() => nodeData, handleNodeSelect)
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (size.value && transformState?.camera) {
|
if (size.value && transformState?.camera) {
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { nextTick, ref } from 'vue'
|
||||||
|
|
||||||
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||||
|
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
|
||||||
|
|
||||||
|
// Mock the dependencies
|
||||||
|
vi.mock('@/renderer/core/canvas/useCanvasInteractions', () => ({
|
||||||
|
useCanvasInteractions: () => ({
|
||||||
|
forwardEventToCanvas: vi.fn(),
|
||||||
|
shouldHandleNodePointerEvents: ref(true)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/renderer/extensions/vueNodes/layout/useNodeLayout', () => ({
|
||||||
|
useNodeLayout: () => ({
|
||||||
|
startDrag: vi.fn(),
|
||||||
|
endDrag: vi.fn().mockResolvedValue(undefined),
|
||||||
|
handleDrag: vi.fn().mockResolvedValue(undefined)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/renderer/core/layout/store/layoutStore', () => ({
|
||||||
|
layoutStore: {
|
||||||
|
isDraggingVueNodes: ref(false)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
const createMockVueNodeData = (
|
||||||
|
overrides: Partial<VueNodeData> = {}
|
||||||
|
): VueNodeData => ({
|
||||||
|
id: 'test-node-123',
|
||||||
|
title: 'Test Node',
|
||||||
|
type: 'TestNodeType',
|
||||||
|
mode: 0,
|
||||||
|
selected: false,
|
||||||
|
executing: false,
|
||||||
|
inputs: [],
|
||||||
|
outputs: [],
|
||||||
|
widgets: [],
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
|
||||||
|
const createPointerEvent = (
|
||||||
|
eventType: string,
|
||||||
|
overrides: Partial<PointerEventInit> = {}
|
||||||
|
): PointerEvent => {
|
||||||
|
return new PointerEvent(eventType, {
|
||||||
|
pointerId: 1,
|
||||||
|
button: 0,
|
||||||
|
clientX: 100,
|
||||||
|
clientY: 100,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createMouseEvent = (
|
||||||
|
eventType: string,
|
||||||
|
overrides: Partial<MouseEventInit> = {}
|
||||||
|
): MouseEvent => {
|
||||||
|
return new MouseEvent(eventType, {
|
||||||
|
button: 2, // Right click
|
||||||
|
clientX: 100,
|
||||||
|
clientY: 100,
|
||||||
|
...overrides
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('useNodePointerInteractions', () => {
|
||||||
|
let mockNodeData: VueNodeData
|
||||||
|
let mockOnPointerUp: ReturnType<typeof vi.fn>
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockNodeData = createMockVueNodeData()
|
||||||
|
mockOnPointerUp = vi.fn()
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should only start drag on left-click', async () => {
|
||||||
|
const { pointerHandlers } = useNodePointerInteractions(
|
||||||
|
ref(mockNodeData),
|
||||||
|
mockOnPointerUp
|
||||||
|
)
|
||||||
|
|
||||||
|
// Right-click should not start drag
|
||||||
|
const rightClickEvent = createPointerEvent('pointerdown', { button: 2 })
|
||||||
|
pointerHandlers.onPointerdown(rightClickEvent)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
// Left-click should start drag and emit callback
|
||||||
|
const leftClickEvent = createPointerEvent('pointerdown', { button: 0 })
|
||||||
|
pointerHandlers.onPointerdown(leftClickEvent)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const pointerUpEvent = createPointerEvent('pointerup')
|
||||||
|
pointerHandlers.onPointerup(pointerUpEvent)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(mockOnPointerUp).toHaveBeenCalledWith(
|
||||||
|
pointerUpEvent,
|
||||||
|
mockNodeData,
|
||||||
|
false // wasDragging = false (same position)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should distinguish drag from click based on distance threshold', async () => {
|
||||||
|
const { pointerHandlers } = useNodePointerInteractions(
|
||||||
|
ref(mockNodeData),
|
||||||
|
mockOnPointerUp
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test drag (distance > 4px)
|
||||||
|
pointerHandlers.onPointerdown(
|
||||||
|
createPointerEvent('pointerdown', { clientX: 100, clientY: 100 })
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const dragUpEvent = createPointerEvent('pointerup', {
|
||||||
|
clientX: 200,
|
||||||
|
clientY: 200
|
||||||
|
})
|
||||||
|
pointerHandlers.onPointerup(dragUpEvent)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(mockOnPointerUp).toHaveBeenCalledWith(
|
||||||
|
dragUpEvent,
|
||||||
|
mockNodeData,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
mockOnPointerUp.mockClear()
|
||||||
|
|
||||||
|
// Test click (same position)
|
||||||
|
const samePos = { clientX: 100, clientY: 100 }
|
||||||
|
pointerHandlers.onPointerdown(createPointerEvent('pointerdown', samePos))
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const clickUpEvent = createPointerEvent('pointerup', samePos)
|
||||||
|
pointerHandlers.onPointerup(clickUpEvent)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(mockOnPointerUp).toHaveBeenCalledWith(
|
||||||
|
clickUpEvent,
|
||||||
|
mockNodeData,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle drag termination via cancel and context menu', async () => {
|
||||||
|
const { pointerHandlers } = useNodePointerInteractions(
|
||||||
|
ref(mockNodeData),
|
||||||
|
mockOnPointerUp
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test pointer cancel
|
||||||
|
pointerHandlers.onPointerdown(createPointerEvent('pointerdown'))
|
||||||
|
await nextTick()
|
||||||
|
pointerHandlers.onPointercancel(createPointerEvent('pointercancel'))
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// Should not emit callback on cancel
|
||||||
|
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
// Test context menu during drag prevents default
|
||||||
|
pointerHandlers.onPointerdown(createPointerEvent('pointerdown'))
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const contextMenuEvent = createMouseEvent('contextmenu')
|
||||||
|
const preventDefaultSpy = vi.spyOn(contextMenuEvent, 'preventDefault')
|
||||||
|
|
||||||
|
pointerHandlers.onContextmenu(contextMenuEvent)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(preventDefaultSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not emit callback when nodeData becomes null', async () => {
|
||||||
|
const nodeDataRef = ref<VueNodeData | null>(mockNodeData)
|
||||||
|
const { pointerHandlers } = useNodePointerInteractions(
|
||||||
|
nodeDataRef,
|
||||||
|
mockOnPointerUp
|
||||||
|
)
|
||||||
|
|
||||||
|
pointerHandlers.onPointerdown(createPointerEvent('pointerdown'))
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// Clear nodeData before pointerup
|
||||||
|
nodeDataRef.value = null
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
pointerHandlers.onPointerup(createPointerEvent('pointerup'))
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(mockOnPointerUp).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should integrate with layout store dragging state', async () => {
|
||||||
|
const { layoutStore } = await import(
|
||||||
|
'@/renderer/core/layout/store/layoutStore'
|
||||||
|
)
|
||||||
|
const { pointerHandlers } = useNodePointerInteractions(
|
||||||
|
ref(mockNodeData),
|
||||||
|
mockOnPointerUp
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start drag
|
||||||
|
pointerHandlers.onPointerdown(createPointerEvent('pointerdown'))
|
||||||
|
await nextTick()
|
||||||
|
expect(layoutStore.isDraggingVueNodes.value).toBe(true)
|
||||||
|
|
||||||
|
// End drag
|
||||||
|
pointerHandlers.onPointercancel(createPointerEvent('pointercancel'))
|
||||||
|
await nextTick()
|
||||||
|
expect(layoutStore.isDraggingVueNodes.value).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type MaybeRefOrGetter, computed, ref, toValue } from 'vue'
|
import { type MaybeRefOrGetter, computed, onUnmounted, ref, toValue } from 'vue'
|
||||||
|
|
||||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||||
@@ -9,16 +9,27 @@ import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayo
|
|||||||
const DRAG_THRESHOLD_PX = 4
|
const DRAG_THRESHOLD_PX = 4
|
||||||
|
|
||||||
export function useNodePointerInteractions(
|
export function useNodePointerInteractions(
|
||||||
nodeDataMaybe: MaybeRefOrGetter<VueNodeData>,
|
nodeDataMaybe: MaybeRefOrGetter<VueNodeData | null>,
|
||||||
onPointerUp: (
|
onPointerUp: (
|
||||||
event: PointerEvent,
|
event: PointerEvent,
|
||||||
nodeData: VueNodeData,
|
nodeData: VueNodeData,
|
||||||
wasDragging: boolean
|
wasDragging: boolean
|
||||||
) => void
|
) => void
|
||||||
) {
|
) {
|
||||||
const nodeData = toValue(nodeDataMaybe)
|
const nodeData = computed(() => {
|
||||||
|
const value = toValue(nodeDataMaybe)
|
||||||
|
if (!value) {
|
||||||
|
console.warn(
|
||||||
|
'useNodePointerInteractions: nodeDataMaybe resolved to null/undefined'
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
})
|
||||||
|
|
||||||
const { startDrag, endDrag, handleDrag } = useNodeLayout(nodeData.id)
|
// Avoid potential null access during component initialization
|
||||||
|
const nodeIdComputed = computed(() => nodeData.value?.id ?? '')
|
||||||
|
const { startDrag, endDrag, handleDrag } = useNodeLayout(nodeIdComputed)
|
||||||
// Use canvas interactions for proper wheel event handling and pointer event capture control
|
// Use canvas interactions for proper wheel event handling and pointer event capture control
|
||||||
const { forwardEventToCanvas, shouldHandleNodePointerEvents } =
|
const { forwardEventToCanvas, shouldHandleNodePointerEvents } =
|
||||||
useCanvasInteractions()
|
useCanvasInteractions()
|
||||||
@@ -28,17 +39,21 @@ export function useNodePointerInteractions(
|
|||||||
const dragStyle = computed(() => ({
|
const dragStyle = computed(() => ({
|
||||||
cursor: isDragging.value ? 'grabbing' : 'grab'
|
cursor: isDragging.value ? 'grabbing' : 'grab'
|
||||||
}))
|
}))
|
||||||
const lastX = ref(0)
|
const startPosition = ref({ x: 0, y: 0 })
|
||||||
const lastY = ref(0)
|
|
||||||
|
|
||||||
const handlePointerDown = (event: PointerEvent) => {
|
const handlePointerDown = (event: PointerEvent) => {
|
||||||
if (!nodeData) {
|
if (!nodeData.value) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'LGraphNode: nodeData is null/undefined in handlePointerDown'
|
'LGraphNode: nodeData is null/undefined in handlePointerDown'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only start drag on left-click (button 0)
|
||||||
|
if (event.button !== 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Don't handle pointer events when canvas is in panning mode - forward to canvas instead
|
// Don't handle pointer events when canvas is in panning mode - forward to canvas instead
|
||||||
if (!shouldHandleNodePointerEvents.value) {
|
if (!shouldHandleNodePointerEvents.value) {
|
||||||
forwardEventToCanvas(event)
|
forwardEventToCanvas(event)
|
||||||
@@ -52,8 +67,7 @@ export function useNodePointerInteractions(
|
|||||||
layoutStore.isDraggingVueNodes.value = true
|
layoutStore.isDraggingVueNodes.value = true
|
||||||
|
|
||||||
startDrag(event)
|
startDrag(event)
|
||||||
lastY.value = event.clientY
|
startPosition.value = { x: event.clientX, y: event.clientY }
|
||||||
lastX.value = event.clientX
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePointerMove = (event: PointerEvent) => {
|
const handlePointerMove = (event: PointerEvent) => {
|
||||||
@@ -62,13 +76,42 @@ export function useNodePointerInteractions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centralized cleanup function for drag state
|
||||||
|
* Ensures consistent cleanup across all drag termination scenarios
|
||||||
|
*/
|
||||||
|
const cleanupDragState = () => {
|
||||||
|
isDragging.value = false
|
||||||
|
layoutStore.isDraggingVueNodes.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely ends drag operation with proper error handling
|
||||||
|
* @param event - PointerEvent to end the drag with
|
||||||
|
*/
|
||||||
|
const safeDragEnd = async (event: PointerEvent): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await endDrag(event)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during endDrag:', error)
|
||||||
|
} finally {
|
||||||
|
cleanupDragState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common drag termination handler with fallback cleanup
|
||||||
|
*/
|
||||||
|
const handleDragTermination = (event: PointerEvent, errorContext: string) => {
|
||||||
|
safeDragEnd(event).catch((error) => {
|
||||||
|
console.error(`Failed to complete ${errorContext}:`, error)
|
||||||
|
cleanupDragState() // Fallback cleanup
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handlePointerUp = (event: PointerEvent) => {
|
const handlePointerUp = (event: PointerEvent) => {
|
||||||
if (isDragging.value) {
|
if (isDragging.value) {
|
||||||
isDragging.value = false
|
handleDragTermination(event, 'drag end')
|
||||||
void endDrag(event)
|
|
||||||
|
|
||||||
// Clear Vue node dragging state for selection toolbox
|
|
||||||
layoutStore.isDraggingVueNodes.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't emit node-click when canvas is in panning mode - forward to canvas instead
|
// Don't emit node-click when canvas is in panning mode - forward to canvas instead
|
||||||
@@ -78,16 +121,52 @@ export function useNodePointerInteractions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Emit node-click for selection handling in GraphCanvas
|
// Emit node-click for selection handling in GraphCanvas
|
||||||
const dx = event.clientX - lastX.value
|
const dx = event.clientX - startPosition.value.x
|
||||||
const dy = event.clientY - lastY.value
|
const dy = event.clientY - startPosition.value.y
|
||||||
const wasDragging = Math.hypot(dx, dy) > DRAG_THRESHOLD_PX
|
const wasDragging = Math.hypot(dx, dy) > DRAG_THRESHOLD_PX
|
||||||
onPointerUp(event, nodeData, wasDragging)
|
|
||||||
|
if (!nodeData?.value) return
|
||||||
|
onPointerUp(event, nodeData.value, wasDragging)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles pointer cancellation events (e.g., touch cancelled by browser)
|
||||||
|
* Ensures drag state is properly cleaned up when pointer interaction is interrupted
|
||||||
|
*/
|
||||||
|
const handlePointerCancel = (event: PointerEvent) => {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
handleDragTermination(event, 'drag cancellation')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles right-click during drag operations
|
||||||
|
* Cancels the current drag to prevent context menu from appearing while dragging
|
||||||
|
*/
|
||||||
|
const handleContextMenu = (event: MouseEvent) => {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
// Simply cleanup state without calling endDrag to avoid synthetic event creation
|
||||||
|
cleanupDragState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup on unmount to prevent resource leaks
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (!isDragging.value) return
|
||||||
|
cleanupDragState()
|
||||||
|
})
|
||||||
|
|
||||||
|
const pointerHandlers = {
|
||||||
|
onPointerdown: handlePointerDown,
|
||||||
|
onPointermove: handlePointerMove,
|
||||||
|
onPointerup: handlePointerUp,
|
||||||
|
onPointercancel: handlePointerCancel,
|
||||||
|
onContextmenu: handleContextMenu
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isDragging,
|
isDragging,
|
||||||
dragStyle,
|
dragStyle,
|
||||||
handlePointerMove,
|
pointerHandlers
|
||||||
handlePointerDown,
|
|
||||||
handlePointerUp
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
type MaybeRefOrGetter,
|
type MaybeRefOrGetter,
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
|
ref,
|
||||||
toValue
|
toValue
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
|
|||||||
const zIndex = computed(() => layoutRef.value?.zIndex ?? 0)
|
const zIndex = computed(() => layoutRef.value?.zIndex ?? 0)
|
||||||
|
|
||||||
// Drag state
|
// Drag state
|
||||||
let isDragging = false
|
const isDragging = ref(false)
|
||||||
let dragStartPos: Point | null = null
|
let dragStartPos: Point | null = null
|
||||||
let dragStartMouse: Point | null = null
|
let dragStartMouse: Point | null = null
|
||||||
let otherSelectedNodesStartPositions: Map<string, Point> | null = null
|
let otherSelectedNodesStartPositions: Map<string, Point> | null = null
|
||||||
@@ -59,9 +60,9 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
|
|||||||
* Start dragging the node
|
* Start dragging the node
|
||||||
*/
|
*/
|
||||||
function startDrag(event: PointerEvent) {
|
function startDrag(event: PointerEvent) {
|
||||||
if (!layoutRef.value) return
|
if (!layoutRef.value || !transformState) return
|
||||||
|
|
||||||
isDragging = true
|
isDragging.value = true
|
||||||
dragStartPos = { ...position.value }
|
dragStartPos = { ...position.value }
|
||||||
dragStartMouse = { x: event.clientX, y: event.clientY }
|
dragStartMouse = { x: event.clientX, y: event.clientY }
|
||||||
|
|
||||||
@@ -87,15 +88,20 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
|
|||||||
mutations.setSource(LayoutSource.Vue)
|
mutations.setSource(LayoutSource.Vue)
|
||||||
|
|
||||||
// Capture pointer
|
// Capture pointer
|
||||||
const target = event.target as HTMLElement
|
if (!(event.target instanceof HTMLElement)) return
|
||||||
target.setPointerCapture(event.pointerId)
|
event.target.setPointerCapture(event.pointerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle drag movement
|
* Handle drag movement
|
||||||
*/
|
*/
|
||||||
const handleDrag = (event: PointerEvent) => {
|
const handleDrag = (event: PointerEvent) => {
|
||||||
if (!isDragging || !dragStartPos || !dragStartMouse || !transformState) {
|
if (
|
||||||
|
!isDragging.value ||
|
||||||
|
!dragStartPos ||
|
||||||
|
!dragStartMouse ||
|
||||||
|
!transformState
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,16 +147,16 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
|
|||||||
* End dragging
|
* End dragging
|
||||||
*/
|
*/
|
||||||
function endDrag(event: PointerEvent) {
|
function endDrag(event: PointerEvent) {
|
||||||
if (!isDragging) return
|
if (!isDragging.value) return
|
||||||
|
|
||||||
isDragging = false
|
isDragging.value = false
|
||||||
dragStartPos = null
|
dragStartPos = null
|
||||||
dragStartMouse = null
|
dragStartMouse = null
|
||||||
otherSelectedNodesStartPositions = null
|
otherSelectedNodesStartPositions = null
|
||||||
|
|
||||||
// Release pointer
|
// Release pointer
|
||||||
const target = event.target as HTMLElement
|
if (!(event.target instanceof HTMLElement)) return
|
||||||
target.releasePointerCapture(event.pointerId)
|
event.target.releasePointerCapture(event.pointerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,6 +183,7 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
|
|||||||
bounds,
|
bounds,
|
||||||
isVisible,
|
isVisible,
|
||||||
zIndex,
|
zIndex,
|
||||||
|
isDragging,
|
||||||
|
|
||||||
// Mutations
|
// Mutations
|
||||||
moveTo,
|
moveTo,
|
||||||
@@ -196,7 +203,7 @@ export function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {
|
|||||||
width: `${size.value.width}px`,
|
width: `${size.value.width}px`,
|
||||||
height: `${size.value.height}px`,
|
height: `${size.value.height}px`,
|
||||||
zIndex: zIndex.value,
|
zIndex: zIndex.value,
|
||||||
cursor: isDragging ? 'grabbing' : 'grab'
|
cursor: isDragging.value ? 'grabbing' : 'grab'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user