From c5629f6b374025a48210e927c65feefbabecb403 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Sun, 30 Nov 2025 13:22:06 -0800 Subject: [PATCH 1/5] Support on slot click listeners in vue --- .../vueNodes/components/InputSlot.vue | 19 ++++++++++++-- .../composables/useSlotLinkInteraction.ts | 25 +++++++++++++++++++ .../extensions/vueNodes/utils/eventUtils.ts | 13 ++++++++++ .../widgets/components/WidgetLegacy.vue | 18 +++++-------- 4 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 src/renderer/extensions/vueNodes/utils/eventUtils.ts diff --git a/src/renderer/extensions/vueNodes/components/InputSlot.vue b/src/renderer/extensions/vueNodes/components/InputSlot.vue index 9edeacbf8b9..b74593285f9 100644 --- a/src/renderer/extensions/vueNodes/components/InputSlot.vue +++ b/src/renderer/extensions/vueNodes/components/InputSlot.vue @@ -6,7 +6,11 @@ ref="connectionDotRef" :color="slotColor" :class="cn('-translate-x-1/2 w-3', errorClassesDot)" - @pointerdown="onPointerDown" + @click="onClick" + @dblclick="onDoubleClick" + @pointerdown.stop.prevent="pointerDown" + @pointerup.stop.prevent="pointerUp" + @pointerleave.stop.prevent="(e: PointerEvent) => pointerLeave(e)" /> @@ -142,9 +146,20 @@ useSlotElementTracking({ element: slotElRef }) -const { onPointerDown } = useSlotLinkInteraction({ +const { onClick, onDoubleClick, onPointerDown } = useSlotLinkInteraction({ nodeId: props.nodeId ?? '', index: props.index, type: 'input' }) + +let pointerLeave: (leavEvent: PointerEvent) => void = () => {} +function pointerDown(e: PointerEvent) { + pointerLeave = () => { + onPointerDown(e) + pointerLeave = () => {} + } +} +function pointerUp() { + pointerLeave = () => {} +} diff --git a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts index 9c9750458dc..3e95b53710e 100644 --- a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts +++ b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts @@ -2,6 +2,7 @@ import { tryOnScopeDispose, useEventListener } from '@vueuse/core' import type { Fn } from '@vueuse/core' import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import type { LGraph } from '@/lib/litegraph/src/LGraph' import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode' import { LLink } from '@/lib/litegraph/src/LLink' @@ -29,6 +30,7 @@ import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import type { Point } from '@/renderer/core/layout/types' import { toPoint } from '@/renderer/core/layout/utils/geometry' import { createSlotLinkDragContext } from '@/renderer/extensions/vueNodes/composables/slotLinkDragContext' +import { translateEvent } from '@/renderer/extensions/vueNodes/utils/eventUtils' import { app } from '@/scripts/app' import { createRafBatch } from '@/utils/rafBatch' @@ -39,6 +41,8 @@ interface SlotInteractionOptions { } interface SlotInteractionHandlers { + onClick: (event: PointerEvent) => void + onDoubleClick: (event: PointerEvent) => void onPointerDown: (event: PointerEvent) => void } @@ -716,7 +720,28 @@ export function useSlotLinkInteraction({ } }) + function onDoubleClick(e: PointerEvent) { + const canvas = useCanvasStore().getCanvas() + const { graph } = canvas + if (!graph) return + const node = graph.getNodeById(nodeId) + if (!node) return + translateEvent(app.canvas, node, e) + node.onInputDblClick?.(index, e) + } + function onClick(e: PointerEvent) { + const canvas = useCanvasStore().getCanvas() + const { graph } = canvas + if (!graph) return + const node = graph.getNodeById(nodeId) + if (!node) return + translateEvent(app.canvas, node, e) + node.onInputClick?.(index, e) + } + return { + onClick, + onDoubleClick, onPointerDown } } diff --git a/src/renderer/extensions/vueNodes/utils/eventUtils.ts b/src/renderer/extensions/vueNodes/utils/eventUtils.ts new file mode 100644 index 00000000000..66124b2fb32 --- /dev/null +++ b/src/renderer/extensions/vueNodes/utils/eventUtils.ts @@ -0,0 +1,13 @@ +import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' +import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' + +export function translateEvent( + canvas: LGraphCanvas, + node: LGraphNode, + e: PointerEvent +): asserts e is CanvasPointerEvent { + canvas.adjustMouseEvent(e) + canvas.graph_mouse[0] = e.offsetX + node.pos[0] + canvas.graph_mouse[1] = e.offsetY + node.pos[1] +} diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue index 3d3fb9237c3..82325ee5ae5 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue @@ -6,9 +6,9 @@ import { useChainCallback } from '@/composables/functional/useChainCallback' import { CanvasPointer } from '@/lib/litegraph/src/CanvasPointer' import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' -import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' +import { translateEvent } from '@/renderer/extensions/vueNodes/utils/eventUtils' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import type { SimplifiedWidget } from '@/types/simplifiedWidget' @@ -64,16 +64,10 @@ function draw() { ctx.scale(scaleFactor, scaleFactor) widgetInstance.draw?.(ctx, node, width, 1, height) } -function translateEvent(e: PointerEvent): asserts e is CanvasPointerEvent { - if (!node) return - canvas.adjustMouseEvent(e) - canvas.graph_mouse[0] = e.offsetX + node.pos[0] - canvas.graph_mouse[1] = e.offsetY + node.pos[1] -} //See LGraphCanvas.processWidgetClick function handleDown(e: PointerEvent) { if (!node || !widgetInstance || !pointer) return - translateEvent(e) + translateEvent(canvas, node, e) pointer.down(e) if (widgetInstance.mouse) pointer.onDrag = (e) => @@ -82,14 +76,14 @@ function handleDown(e: PointerEvent) { canvas.processWidgetClick(e, node, widgetInstance, pointer) } function handleUp(e: PointerEvent) { - if (!pointer) return - translateEvent(e) + if (!pointer || !node) return + translateEvent(canvas, node, e) e.click_time = e.timeStamp - (pointer?.eDown?.timeStamp ?? 0) pointer.up(e) } function handleMove(e: PointerEvent) { - if (!pointer) return - translateEvent(e) + if (!pointer || !node) return + translateEvent(canvas, node, e) pointer.move(e) } From ba2a334b7975739ac40887d1e796105e69a16b53 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Sun, 30 Nov 2025 13:32:49 -0800 Subject: [PATCH 2/5] Cleaner link drop handling --- .../extensions/vueNodes/components/InputSlot.vue | 15 +-------------- .../composables/useSlotLinkInteraction.ts | 2 +- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/renderer/extensions/vueNodes/components/InputSlot.vue b/src/renderer/extensions/vueNodes/components/InputSlot.vue index b74593285f9..a86a789e684 100644 --- a/src/renderer/extensions/vueNodes/components/InputSlot.vue +++ b/src/renderer/extensions/vueNodes/components/InputSlot.vue @@ -8,9 +8,7 @@ :class="cn('-translate-x-1/2 w-3', errorClassesDot)" @click="onClick" @dblclick="onDoubleClick" - @pointerdown.stop.prevent="pointerDown" - @pointerup.stop.prevent="pointerUp" - @pointerleave.stop.prevent="(e: PointerEvent) => pointerLeave(e)" + @pointerdown="onPointerDown" /> @@ -151,15 +149,4 @@ const { onClick, onDoubleClick, onPointerDown } = useSlotLinkInteraction({ index: props.index, type: 'input' }) - -let pointerLeave: (leavEvent: PointerEvent) => void = () => {} -function pointerDown(e: PointerEvent) { - pointerLeave = () => { - onPointerDown(e) - pointerLeave = () => {} - } -} -function pointerUp() { - pointerLeave = () => {} -} diff --git a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts index 3e95b53710e..759135ac937 100644 --- a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts +++ b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts @@ -504,7 +504,7 @@ export function useSlotLinkInteraction({ const hasConnected = connectByPriority(canvasEvent.target, snappedCandidate) - if (!hasConnected) { + if (!hasConnected && event.target === app.canvas?.canvas) { activeAdapter?.dropOnCanvas(canvasEvent) } From cdb22e1b3756eb7534313ce8839923067760bb80 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Mon, 1 Dec 2025 11:07:52 -0800 Subject: [PATCH 3/5] Clearer naming for now extracted function --- .../vueNodes/composables/useSlotLinkInteraction.ts | 6 +++--- src/renderer/extensions/vueNodes/utils/eventUtils.ts | 6 +++--- .../vueNodes/widgets/components/WidgetLegacy.vue | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts index 759135ac937..2a1954394b7 100644 --- a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts +++ b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts @@ -30,7 +30,7 @@ import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import type { Point } from '@/renderer/core/layout/types' import { toPoint } from '@/renderer/core/layout/utils/geometry' import { createSlotLinkDragContext } from '@/renderer/extensions/vueNodes/composables/slotLinkDragContext' -import { translateEvent } from '@/renderer/extensions/vueNodes/utils/eventUtils' +import { augmentToCanvasPointerEvent } from '@/renderer/extensions/vueNodes/utils/eventUtils' import { app } from '@/scripts/app' import { createRafBatch } from '@/utils/rafBatch' @@ -726,7 +726,7 @@ export function useSlotLinkInteraction({ if (!graph) return const node = graph.getNodeById(nodeId) if (!node) return - translateEvent(app.canvas, node, e) + augmentToCanvasPointerEvent(e, node, app.canvas) node.onInputDblClick?.(index, e) } function onClick(e: PointerEvent) { @@ -735,7 +735,7 @@ export function useSlotLinkInteraction({ if (!graph) return const node = graph.getNodeById(nodeId) if (!node) return - translateEvent(app.canvas, node, e) + augmentToCanvasPointerEvent(e, node, app.canvas) node.onInputClick?.(index, e) } diff --git a/src/renderer/extensions/vueNodes/utils/eventUtils.ts b/src/renderer/extensions/vueNodes/utils/eventUtils.ts index 66124b2fb32..94cf43880b5 100644 --- a/src/renderer/extensions/vueNodes/utils/eventUtils.ts +++ b/src/renderer/extensions/vueNodes/utils/eventUtils.ts @@ -2,10 +2,10 @@ import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' -export function translateEvent( - canvas: LGraphCanvas, +export function augmentToCanvasPointerEvent( + e: PointerEvent, node: LGraphNode, - e: PointerEvent + canvas: LGraphCanvas ): asserts e is CanvasPointerEvent { canvas.adjustMouseEvent(e) canvas.graph_mouse[0] = e.offsetX + node.pos[0] diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue index 82325ee5ae5..bd2aac77253 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue @@ -8,7 +8,7 @@ import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' -import { translateEvent } from '@/renderer/extensions/vueNodes/utils/eventUtils' +import { augmentToCanvasPointerEvent } from '@/renderer/extensions/vueNodes/utils/eventUtils' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import type { SimplifiedWidget } from '@/types/simplifiedWidget' @@ -67,7 +67,7 @@ function draw() { //See LGraphCanvas.processWidgetClick function handleDown(e: PointerEvent) { if (!node || !widgetInstance || !pointer) return - translateEvent(canvas, node, e) + augmentToCanvasPointerEvent(e, node, canvas) pointer.down(e) if (widgetInstance.mouse) pointer.onDrag = (e) => @@ -77,13 +77,13 @@ function handleDown(e: PointerEvent) { } function handleUp(e: PointerEvent) { if (!pointer || !node) return - translateEvent(canvas, node, e) + augmentToCanvasPointerEvent(e, node, canvas) e.click_time = e.timeStamp - (pointer?.eDown?.timeStamp ?? 0) pointer.up(e) } function handleMove(e: PointerEvent) { if (!pointer || !node) return - translateEvent(canvas, node, e) + augmentToCanvasPointerEvent(e, node, canvas) pointer.move(e) } From b0ef7bc3db31a5cc02fa2c9b2f29f5beadc3c558 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Mon, 1 Dec 2025 11:09:25 -0800 Subject: [PATCH 4/5] Don't drop link on run button --- .../tests/vueNodes/interactions/links/linkInteraction.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts index 2da4b112275..508c307edb8 100644 --- a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts +++ b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts @@ -897,7 +897,7 @@ test.describe('Vue Node Link Interaction', () => { 0, false ) - const dropPos = { x: outputCenter.x + 200, y: outputCenter.y - 120 } + const dropPos = { x: outputCenter.x + 200, y: outputCenter.y - 100 } await comfyMouse.move(outputCenter) await comfyPage.page.keyboard.down('Shift') From a5a847d359b47afbffa48fadc6f2ce396c2c895a Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Mon, 1 Dec 2025 13:42:41 -0800 Subject: [PATCH 5/5] Revert useCanvasStore().getCanvas() usage --- .../vueNodes/composables/useSlotLinkInteraction.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts index 2a1954394b7..a7e5f1a67c6 100644 --- a/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts +++ b/src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts @@ -2,7 +2,6 @@ import { tryOnScopeDispose, useEventListener } from '@vueuse/core' import type { Fn } from '@vueuse/core' import { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion' -import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import type { LGraph } from '@/lib/litegraph/src/LGraph' import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode' import { LLink } from '@/lib/litegraph/src/LLink' @@ -721,8 +720,7 @@ export function useSlotLinkInteraction({ }) function onDoubleClick(e: PointerEvent) { - const canvas = useCanvasStore().getCanvas() - const { graph } = canvas + const { graph } = app.canvas if (!graph) return const node = graph.getNodeById(nodeId) if (!node) return @@ -730,8 +728,7 @@ export function useSlotLinkInteraction({ node.onInputDblClick?.(index, e) } function onClick(e: PointerEvent) { - const canvas = useCanvasStore().getCanvas() - const { graph } = canvas + const { graph } = app.canvas if (!graph) return const node = graph.getNodeById(nodeId) if (!node) return