Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 33 additions & 10 deletions src/lib/litegraph/src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,10 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
this._key_callback = this.processKey.bind(this)

canvas.addEventListener('keydown', this._key_callback, true)
// In Vue nodes mode, also listen on document for keydown since Vue elements may have focus
if (LiteGraph.vueNodesMode) {
document.addEventListener('keydown', this._key_callback, true)
}
// keyup event must be bound on the document
document.addEventListener('keyup', this._key_callback, true)

Expand All @@ -2019,14 +2023,24 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
const { canvas } = this

// Assertions: removing nullish is fine.
canvas.removeEventListener('pointercancel', this._mousecancel_callback!)
// Note: capture flag must match addEventListener for removal to work
canvas.removeEventListener(
'pointercancel',
this._mousecancel_callback!,
true
)
canvas.removeEventListener('pointerout', this._mouseout_callback!)
canvas.removeEventListener('pointermove', this._mousemove_callback!)
canvas.removeEventListener('pointerup', this._mouseup_callback!)
canvas.removeEventListener('pointerdown', this._mousedown_callback!)
canvas.removeEventListener('pointerup', this._mouseup_callback!, true)
canvas.removeEventListener('pointerdown', this._mousedown_callback!, true)
canvas.removeEventListener('wheel', this._mousewheel_callback!)
canvas.removeEventListener('keydown', this._key_callback!)
document.removeEventListener('keyup', this._key_callback!)
canvas.removeEventListener('keydown', this._key_callback!, true)
// Always remove document keydown listener - it may have been added if vueNodesMode
// was true during bindEvents, even if vueNodesMode has since changed
if (this._key_callback) {
document.removeEventListener('keydown', this._key_callback, true)
}
document.removeEventListener('keyup', this._key_callback!, true)
canvas.removeEventListener('contextmenu', this._doNothing)
canvas.removeEventListener('dragenter', this._doReturnTrue)

Expand Down Expand Up @@ -3707,8 +3721,14 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
if (!graph) return

let block_default = false
// @ts-expect-error EventTarget.localName is not in standard types
if (e.target.localName == 'input') return
// Skip all text-editable surfaces to avoid blocking typing/selection/copy
const target = e.target as HTMLElement | null
if (
target?.localName === 'input' ||
target?.localName === 'textarea' ||
target?.isContentEditable
)
return
Comment on lines +3724 to +3731
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Text-editable guard behavior is correct; prefer instanceof HTMLElement over type assertion

The new early-return and Delete/Backspace guard now correctly skip all text-editable surfaces, which fixes the prior regression where space / Ctrl+A / Ctrl+C / Delete could be intercepted while typing. That behavior looks solid.

To better align with the “avoid type assertions in litegraph TS” guideline and the earlier suggestion for this block, consider tightening the typing with an instanceof check instead of asserting:

Proposed cleanup of target typing
-    // Skip all text-editable surfaces to avoid blocking typing/selection/copy
-    const target = e.target as HTMLElement | null
+    // Skip all text-editable surfaces to avoid blocking typing/selection/copy
+    const target =
+      e.target instanceof HTMLElement ? e.target : null
@@
       } else if (e.key === 'Delete' || e.key === 'Backspace') {
-        // delete or backspace (but don't intercept when editing text)
+        // delete or backspace (but don't intercept when editing text)
         if (
           target?.localName !== 'input' &&
           target?.localName !== 'textarea' &&
           !target?.isContentEditable
         ) {

Functionality stays the same, but this removes an unnecessary assertion and keeps the guard in line with the TypeScript/litegraph typing guidelines.

Also applies to: 3767-3772

🤖 Prompt for AI Agents
In @src/lib/litegraph/src/LGraphCanvas.ts around lines 3724 - 3731, Change the
type assertion on the event target to use a runtime instanceof check: replace
the cast "const target = e.target as HTMLElement | null" with code that first
checks "if (!(e.target instanceof HTMLElement)) return" (or assign after that
check) and then keep the existing localName/isContentEditable guards; apply the
same replacement for the duplicate block around the other occurrence (the block
referencing target, e.target, localName, and isContentEditable) so no type
assertion is used and the early-return logic remains identical.


if (e.type == 'keydown') {
// TODO: Switch
Expand Down Expand Up @@ -3744,9 +3764,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
// paste
this.pasteFromClipboard({ connectInputs: e.shiftKey })
} else if (e.key === 'Delete' || e.key === 'Backspace') {
// delete or backspace
// @ts-expect-error EventTarget.localName is not in standard types
if (e.target.localName != 'input' && e.target.localName != 'textarea') {
// delete or backspace (but don't intercept when editing text)
if (
target?.localName !== 'input' &&
target?.localName !== 'textarea' &&
!target?.isContentEditable
) {
if (this.selectedItems.size === 0) {
this.#noItemsSelected()
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue'

import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
import { createTestingPinia } from '@pinia/testing'

import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import type { NodeLayout } from '@/renderer/core/layout/types'
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
import { useNodeDrag } from '@/renderer/extensions/vueNodes/layout/useNodeDrag'

const forwardEventToCanvasMock = vi.fn()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
import { isMultiSelectKey } from '@/renderer/extensions/vueNodes/utils/selectionUtils'
import { useNodeDrag } from '@/renderer/extensions/vueNodes/layout/useNodeDrag'
import { isMultiSelectKey } from '@/renderer/extensions/vueNodes/utils/selectionUtils'

export function useNodePointerInteractions(
nodeIdRef: MaybeRefOrGetter<string>
Expand Down Expand Up @@ -65,6 +65,12 @@ export function useNodePointerInteractions(
function onPointermove(event: PointerEvent) {
if (forwardMiddlePointerIfNeeded(event)) return

// Don't handle pointer events when canvas is in panning mode - forward to canvas instead
if (!shouldHandleNodePointerEvents.value) {
forwardEventToCanvas(event)
return
}

// Don't activate drag while resizing
if (layoutStore.isResizingVueNodes.value) return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ export function useSlotLinkInteraction({
raf.cancel()
dragContext.dispose()
clearCompatible()
// Reset litegraph pointer state
if (app.canvas) {
app.canvas.pointer.isDown = false
}
}

const updatePointerState = (event: PointerEvent) => {
Expand Down Expand Up @@ -409,6 +413,11 @@ export function useSlotLinkInteraction({

const handlePointerMove = (event: PointerEvent) => {
if (!pointerSession.matches(event)) return

// When in panning mode (read_only), let litegraph handle panning - don't stop propagation
if (app.canvas?.read_only) return

// Not in panning mode - Vue handles link drag, stop propagation to prevent litegraph interference
event.stopPropagation()

dragContext.pendingPointerMove = {
Expand Down Expand Up @@ -539,7 +548,10 @@ export function useSlotLinkInteraction({
}

const handlePointerUp = (event: PointerEvent) => {
event.stopPropagation()
// When in panning mode, let litegraph handle - but still cleanup our link drag state
if (!app.canvas?.read_only) {
event.stopPropagation()
}
finishInteraction(event)
}

Expand Down Expand Up @@ -584,6 +596,10 @@ export function useSlotLinkInteraction({
if (event.button !== 0) return
if (!nodeId) return
if (pointerSession.isActive()) return

// Don't start link drag if in panning mode - let litegraph handle panning
if (app.canvas?.read_only) return

event.preventDefault()
event.stopPropagation()

Expand Down Expand Up @@ -703,6 +719,9 @@ export function useSlotLinkInteraction({
)

pointerSession.begin(event.pointerId)
// Sync pointer state with litegraph so spacebar panning works
canvas.last_mouse = [event.clientX, event.clientY]
canvas.pointer.isDown = true

toCanvasPointerEvent(event)
updatePointerState(event)
Expand Down