fix: firefox can give invalid drag coordinates causing incorrect drop position (https://bugzilla.mozilla.org/show_bug.cgi?id=1773886) - change to track during drag events

This commit is contained in:
pythongosssss
2026-05-22 04:06:02 -07:00
parent 7b4fef5eca
commit 8d41e3e080
2 changed files with 100 additions and 1 deletions

View File

@@ -456,4 +456,90 @@ describe('useNodeDragToCanvas', () => {
expect(dispatchPointerDown(600, 250)).not.toHaveBeenCalled()
})
})
describe('native drag position tracking', () => {
beforeEach(() => {
mockCanvas.canvas.getBoundingClientRect.mockReturnValue({
left: 0,
right: 500,
top: 0,
bottom: 500
})
mockConvertEventToCanvasOffset.mockReturnValue([300, 300])
})
// happy-dom has no DragEvent constructor; MouseEvent works since the
// handler only reads clientX/clientY.
function fireDrag(x: number, y: number) {
document.dispatchEvent(
new MouseEvent('dragover', { clientX: x, clientY: y, bubbles: true })
)
}
it('should prefer tracked drag position over dragend coordinates', () => {
const { startDrag, setupGlobalListeners, handleNativeDrop } =
useNodeDragToCanvas()
setupGlobalListeners()
startDrag(mockNodeDef, 'native')
fireDrag(250, 250)
// dragend supplies a bad position (the Firefox bug); the tracked one
// from the last drag event should win.
handleNativeDrop(1505, 102)
expect(mockConvertEventToCanvasOffset).toHaveBeenCalledWith({
clientX: 250,
clientY: 250
})
})
it('should ignore drag events with (0, 0)', () => {
const { startDrag, setupGlobalListeners, handleNativeDrop } =
useNodeDragToCanvas()
setupGlobalListeners()
startDrag(mockNodeDef, 'native')
fireDrag(250, 250)
fireDrag(0, 0)
handleNativeDrop(1505, 102)
expect(mockConvertEventToCanvasOffset).toHaveBeenCalledWith({
clientX: 250,
clientY: 250
})
})
it('should fall back to dragend coordinates when no drag fired', () => {
const { startDrag, setupGlobalListeners, handleNativeDrop } =
useNodeDragToCanvas()
setupGlobalListeners()
startDrag(mockNodeDef, 'native')
handleNativeDrop(250, 250)
expect(mockConvertEventToCanvasOffset).toHaveBeenCalledWith({
clientX: 250,
clientY: 250
})
})
it('should clear tracked position between drags', () => {
const { startDrag, setupGlobalListeners, handleNativeDrop } =
useNodeDragToCanvas()
setupGlobalListeners()
startDrag(mockNodeDef, 'native')
fireDrag(250, 250)
handleNativeDrop(1505, 102)
// Second drag - no drag events, so we should fall back to args.
startDrag(mockNodeDef, 'native')
handleNativeDrop(300, 300)
expect(mockConvertEventToCanvasOffset).toHaveBeenLastCalledWith({
clientX: 300,
clientY: 300
})
})
})
})

View File

@@ -10,16 +10,26 @@ const isDragging = ref(false)
const draggedNode = shallowRef<ComfyNodeDefImpl | null>(null)
const cursorPosition = ref({ x: 0, y: 0 })
const dragMode = ref<DragMode>('click')
const lastNativeDragPosition = shallowRef<{ x: number; y: number } | null>(null)
let listenersSetup = false
function updatePosition(e: PointerEvent) {
cursorPosition.value = { x: e.clientX, y: e.clientY }
}
// Firefox dragend can report stale clientX/Y and `drag` can fire with
// (0, 0). dragover on the target reliably reports real client coords.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1773886
function trackNativeDragPosition(e: DragEvent) {
if (e.clientX === 0 && e.clientY === 0) return
lastNativeDragPosition.value = { x: e.clientX, y: e.clientY }
}
function cancelDrag() {
isDragging.value = false
draggedNode.value = null
dragMode.value = 'click'
lastNativeDragPosition.value = null
}
function isOverCanvas(clientX: number, clientY: number): boolean {
@@ -81,6 +91,7 @@ function setupGlobalListeners() {
document.addEventListener('pointerdown', blockCommitPointerDown, true)
document.addEventListener('pointerup', endDrag, true)
document.addEventListener('keydown', handleKeydown)
document.addEventListener('dragover', trackNativeDragPosition)
}
function cleanupGlobalListeners() {
@@ -91,6 +102,7 @@ function cleanupGlobalListeners() {
document.removeEventListener('pointerdown', blockCommitPointerDown, true)
document.removeEventListener('pointerup', endDrag, true)
document.removeEventListener('keydown', handleKeydown)
document.removeEventListener('dragover', trackNativeDragPosition)
if (isDragging.value && dragMode.value === 'click') {
cancelDrag()
@@ -106,8 +118,9 @@ export function useNodeDragToCanvas() {
function handleNativeDrop(clientX: number, clientY: number) {
if (dragMode.value !== 'native') return
const tracked = lastNativeDragPosition.value
try {
addNodeAtPosition(clientX, clientY)
addNodeAtPosition(tracked?.x ?? clientX, tracked?.y ?? clientY)
} finally {
cancelDrag()
}