Skip to content

Commit b812888

Browse files
fix: space bar panning over Vue nodes in standard nav mode
Bridge LGraphCanvas.read_only to Vue reactivity via onReadOnlyChanged callback so the existing CSS pointer-events-auto/none toggle on LGraphNode.vue and NodeWidgets.vue re-evaluates when space key toggles panning mode. Events then fall through to the LiteGraph canvas naturally — no per-handler forwarding or force flags needed. Fixes #7806 Amp-Thread-ID: https://ampcode.com/threads/T-019c796c-e83c-769d-85f4-20a349994bad
1 parent 38edba7 commit b812888

File tree

5 files changed

+94
-2
lines changed

5 files changed

+94
-2
lines changed

src/lib/litegraph/src/LGraphCanvas.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,11 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
401401
set read_only(value: boolean) {
402402
this.state.readOnly = value
403403
this._updateCursorStyle()
404+
this.onReadOnlyChanged?.(value)
404405
}
405406

407+
onReadOnlyChanged?: (readOnly: boolean) => void
408+
406409
get isDragging(): boolean {
407410
return this.state.draggingItems
408411
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { createTestingPinia } from '@pinia/testing'
2+
import { setActivePinia } from 'pinia'
3+
import { beforeEach, describe, expect, it, vi } from 'vitest'
4+
import { nextTick } from 'vue'
5+
6+
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
7+
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
8+
9+
vi.mock('@/scripts/app', () => ({
10+
app: { canvas: null }
11+
}))
12+
13+
vi.mock('@vueuse/core', async (importOriginal) => {
14+
const actual = await importOriginal()
15+
return {
16+
...(actual as Record<string, unknown>),
17+
useEventListener: vi.fn()
18+
}
19+
})
20+
21+
function createMockCanvas(
22+
readOnly = false
23+
): LGraphCanvas & { onReadOnlyChanged?: (v: boolean) => void } {
24+
return {
25+
read_only: readOnly,
26+
canvas: document.createElement('canvas'),
27+
onReadOnlyChanged: undefined
28+
} as unknown as LGraphCanvas & {
29+
onReadOnlyChanged?: (v: boolean) => void
30+
}
31+
}
32+
33+
describe('useCanvasStore', () => {
34+
beforeEach(() => {
35+
setActivePinia(createTestingPinia({ stubActions: false }))
36+
vi.clearAllMocks()
37+
})
38+
39+
describe('isReadOnly', () => {
40+
it('syncs initial read_only value when canvas is set', async () => {
41+
const store = useCanvasStore()
42+
const mockCanvas = createMockCanvas(true)
43+
44+
store.canvas = mockCanvas as unknown as LGraphCanvas
45+
await nextTick()
46+
47+
expect(store.isReadOnly).toBe(true)
48+
})
49+
50+
it('updates isReadOnly when onReadOnlyChanged callback fires', async () => {
51+
const store = useCanvasStore()
52+
const mockCanvas = createMockCanvas(false)
53+
54+
store.canvas = mockCanvas as unknown as LGraphCanvas
55+
await nextTick()
56+
57+
expect(store.isReadOnly).toBe(false)
58+
59+
// Simulate space key press → LGraphCanvas sets read_only = true
60+
mockCanvas.onReadOnlyChanged?.(true)
61+
62+
expect(store.isReadOnly).toBe(true)
63+
64+
// Simulate space key release
65+
mockCanvas.onReadOnlyChanged?.(false)
66+
67+
expect(store.isReadOnly).toBe(false)
68+
})
69+
70+
it('registers onReadOnlyChanged callback on the canvas', async () => {
71+
const store = useCanvasStore()
72+
const mockCanvas = createMockCanvas(false)
73+
74+
store.canvas = mockCanvas as unknown as LGraphCanvas
75+
await nextTick()
76+
77+
expect(mockCanvas.onReadOnlyChanged).toBeDefined()
78+
expect(typeof mockCanvas.onReadOnlyChanged).toBe('function')
79+
})
80+
})
81+
})

src/renderer/core/canvas/canvasStore.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const useCanvasStore = defineStore('canvas', () => {
4141
const appScalePercentage = ref(100)
4242

4343
const linearMode = ref(false)
44+
const isReadOnly = ref(false)
4445

4546
// Set up scale synchronization when canvas is available
4647
let originalOnChanged: ((scale: number, offset: Point) => void) | undefined =
@@ -115,6 +116,11 @@ export const useCanvasStore = defineStore('canvas', () => {
115116
whenever(
116117
() => canvas.value,
117118
(newCanvas) => {
119+
isReadOnly.value = newCanvas.read_only
120+
newCanvas.onReadOnlyChanged = (value: boolean) => {
121+
isReadOnly.value = value
122+
}
123+
118124
useEventListener(
119125
newCanvas.canvas,
120126
'litegraph:set-graph',
@@ -141,6 +147,7 @@ export const useCanvasStore = defineStore('canvas', () => {
141147
rerouteSelected,
142148
appScalePercentage,
143149
linearMode,
150+
isReadOnly,
144151
updateSelectedItems,
145152
getCanvas,
146153
setAppZoomFromPercentage,

src/renderer/core/canvas/useCanvasInteractions.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ vi.mock('@/renderer/core/canvas/canvasStore', () => {
1212
return {
1313
useCanvasStore: vi.fn(() => ({
1414
getCanvas,
15-
setCursorStyle
15+
setCursorStyle,
16+
isReadOnly: false
1617
}))
1718
}
1819
})

src/renderer/core/canvas/useCanvasInteractions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function useCanvasInteractions() {
2323
* Returns false when canvas is in read-only/panning mode (e.g., space key held for panning).
2424
*/
2525
const shouldHandleNodePointerEvents = computed(
26-
() => !(canvasStore.canvas?.read_only ?? false)
26+
() => !canvasStore.isReadOnly
2727
)
2828

2929
/**

0 commit comments

Comments
 (0)