diff --git a/src/lib/litegraph/src/LGraphCanvas.dragStartDeferral.test.ts b/src/lib/litegraph/src/LGraphCanvas.dragStartDeferral.test.ts index b4732d3886..281a5b748f 100644 --- a/src/lib/litegraph/src/LGraphCanvas.dragStartDeferral.test.ts +++ b/src/lib/litegraph/src/LGraphCanvas.dragStartDeferral.test.ts @@ -120,4 +120,31 @@ describe('_startDraggingItems defers onSelectionChange', () => { expect(canvas.onSelectionChange).toBe(onSelectionChange) }) + + it('does not schedule a deferred notification when starting a drag on an already-selected sticky item', () => { + canvas.select(node) + const onSelectionChange = vi.fn() + canvas.onSelectionChange = onSelectionChange + + canvas['_startDraggingItems'](node, pointer, true) + + vi.advanceTimersByTime(16) + expect(onSelectionChange).not.toHaveBeenCalled() + }) + + it('restores onSelectionChange even when processSelect throws', () => { + const onSelectionChange = vi.fn() + canvas.onSelectionChange = onSelectionChange + const original = canvas.processSelect + canvas.processSelect = () => { + throw new Error('boom') + } + + expect(() => canvas['_startDraggingItems'](node, pointer, true)).toThrow( + 'boom' + ) + + expect(canvas.onSelectionChange).toBe(onSelectionChange) + canvas.processSelect = original + }) }) diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index a7d2641508..5c320b9466 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -3615,11 +3615,20 @@ export class LGraphCanvas implements CustomEventDispatcher // Selection-update side effects (onSelectionChange callback) are deferred // to the next frame so the node visibly starts following the pointer // before downstream reactivity (e.g. Vue store updates) runs. + // The sentinel records whether processSelect actually notified, so we + // skip the RAF on the no-op sticky-resselect path and avoid swallowing + // the listener if processSelect throws. const onSelectionChange = this.onSelectionChange - this.onSelectionChange = undefined - this.processSelect(item, pointer.eDown, sticky) - this.onSelectionChange = onSelectionChange - if (onSelectionChange) { + let selectionNotified = false + this.onSelectionChange = () => { + selectionNotified = true + } + try { + this.processSelect(item, pointer.eDown, sticky) + } finally { + this.onSelectionChange = onSelectionChange + } + if (onSelectionChange && selectionNotified) { requestAnimationFrame(() => onSelectionChange(this.selected_nodes)) }