Skip to content

Commit 6cf0357

Browse files
fix(vueNodes): sync node size changes from extensions to Vue components (#7993)
## Summary When extensions like KJNodes call node.setSize(), the Vue component now properly updates its CSS variables to reflect the new size. ## Changes: - LGraphNode pos/size setters now always sync to layoutStore with Canvas source - LGraphNode.vue listens to layoutStore changes and updates CSS variables - Fixed height calculation to account for NODE_TITLE_HEIGHT difference - Removed _syncToLayoutStore flag (simplified - layoutStore ignores non-existent nodes) - Use setPos() helper method instead of direct pos[0]/pos[1] assignment ## Screenshots (if applicable) before https://github.com/user-attachments/assets/236a173a-e41d-485b-8c63-5c28ef1c69bf after https://github.com/user-attachments/assets/5fc3f7e4-35c7-40e1-81ac-38a35ee0ac1b ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7993-fix-vueNodes-sync-node-size-changes-from-extensions-to-Vue-components-2e76d73d3650815799c5f2d9d8c7dcbf) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
1 parent c0c81db commit 6cf0357

File tree

6 files changed

+103
-73
lines changed

6 files changed

+103
-73
lines changed

src/lib/litegraph/src/LGraph.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -776,8 +776,10 @@ export class LGraph
776776
let max_size = 100
777777
let y = margin + LiteGraph.NODE_TITLE_HEIGHT
778778
for (const node of column) {
779-
node.pos[0] = layout == LiteGraph.VERTICAL_LAYOUT ? y : x
780-
node.pos[1] = layout == LiteGraph.VERTICAL_LAYOUT ? x : y
779+
node.setPos(
780+
layout == LiteGraph.VERTICAL_LAYOUT ? y : x,
781+
layout == LiteGraph.VERTICAL_LAYOUT ? x : y
782+
)
781783
const max_size_index = layout == LiteGraph.VERTICAL_LAYOUT ? 1 : 0
782784
if (node.size[max_size_index] > max_size) {
783785
max_size = node.size[max_size_index]
@@ -1759,7 +1761,10 @@ export class LGraph
17591761
)
17601762

17611763
//Correct for title height. It's included in bounding box, but not _posSize
1762-
subgraphNode.pos[1] += LiteGraph.NODE_TITLE_HEIGHT / 2
1764+
subgraphNode.setPos(
1765+
subgraphNode.pos[0],
1766+
subgraphNode.pos[1] + LiteGraph.NODE_TITLE_HEIGHT / 2
1767+
)
17631768

17641769
// Add the subgraph node to the graph
17651770
this.add(subgraphNode)
@@ -1926,8 +1931,7 @@ export class LGraph
19261931

19271932
this.add(node, true)
19281933
node.configure(n_info)
1929-
node.pos[0] += offsetX
1930-
node.pos[1] += offsetY
1934+
node.setPos(node.pos[0] + offsetX, node.pos[1] + offsetY)
19311935
for (const input of node.inputs) {
19321936
input.link = null
19331937
}

src/lib/litegraph/src/LGraphCanvas.slotHitDetection.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ vi.mock('@/renderer/core/layout/store/layoutStore', () => ({
1313
querySlotAtPoint: vi.fn(),
1414
queryRerouteAtPoint: vi.fn(),
1515
getNodeLayoutRef: vi.fn(() => ({ value: null })),
16-
getSlotLayout: vi.fn()
16+
getSlotLayout: vi.fn(),
17+
setSource: vi.fn(),
18+
batchUpdateNodeBounds: vi.fn()
1719
}
1820
}))
1921

src/lib/litegraph/src/LGraphCanvas.ts

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants'
55
import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
66
import type { LinkRenderContext } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
77
import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculations'
8-
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
98
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
109
import { LayoutSource } from '@/renderer/core/layout/types'
11-
import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'
1210
import { forEachNode } from '@/utils/graphTraversalUtil'
1311

1412
import { CanvasPointer } from './CanvasPointer'
@@ -2396,8 +2394,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
23962394
const cloned = items?.created[0] as LGraphNode | undefined
23972395
if (!cloned) return
23982396

2399-
cloned.pos[0] += 5
2400-
cloned.pos[1] += 5
2397+
cloned.setPos(cloned.pos[0] + 5, cloned.pos[1] + 5)
24012398

24022399
if (this.allow_dragnodes) {
24032400
pointer.onDragStart = (pointer) => {
@@ -3581,19 +3578,14 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
35813578
if (dragEvent) {
35823579
this.adjustMouseEvent(dragEvent)
35833580
const e = dragEvent as CanvasPointerEvent
3584-
node.pos[0] = e.canvasX - node.size[0] / 2
3585-
node.pos[1] = e.canvasY + 10
3581+
node.setPos(e.canvasX - node.size[0] / 2, e.canvasY + 10)
35863582
// Update last_mouse to prevent jump on first drag move
35873583
this.last_mouse = [e.clientX, e.clientY]
35883584
} else {
3589-
node.pos[0] = this.graph_mouse[0] - node.size[0] / 2
3590-
node.pos[1] = this.graph_mouse[1] + 10
3591-
}
3592-
3593-
// Sync position to layout store for Vue node rendering
3594-
if (LiteGraph.vueNodesMode) {
3595-
const mutations = this.initLayoutMutations()
3596-
mutations.moveNode(node.id, { x: node.pos[0], y: node.pos[1] })
3585+
node.setPos(
3586+
this.graph_mouse[0] - node.size[0] / 2,
3587+
this.graph_mouse[1] + 10
3588+
)
35973589
}
35983590

35993591
this.state.ghostNodeId = node.id
@@ -4162,31 +4154,30 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
41624154
}
41634155
}
41644156

4165-
// Adjust positions
4157+
// Adjust positions - use move/setPos to ensure layout store is updated
4158+
const dx = position[0] - offsetX
4159+
const dy = position[1] - offsetY
41664160
for (const item of created) {
4167-
item.pos[0] += position[0] - offsetX
4168-
item.pos[1] += position[1] - offsetY
4161+
if (item instanceof LGraphNode) {
4162+
item.setPos(item.pos[0] + dx, item.pos[1] + dy)
4163+
} else if (item instanceof Reroute) {
4164+
item.move(dx, dy)
4165+
}
41694166
}
41704167

41714168
// TODO: Report failures, i.e. `failedNodes`
41724169

41734170
const newPositions = created
41744171
.filter((item): item is LGraphNode => item instanceof LGraphNode)
4175-
.map((node) => {
4176-
const fullHeight = node.size?.[1] ?? 200
4177-
const layoutHeight = LiteGraph.vueNodesMode
4178-
? removeNodeTitleHeight(fullHeight)
4179-
: fullHeight
4180-
return {
4181-
nodeId: String(node.id),
4182-
bounds: {
4183-
x: node.pos[0],
4184-
y: node.pos[1],
4185-
width: node.size?.[0] ?? 100,
4186-
height: layoutHeight
4187-
}
4172+
.map((node) => ({
4173+
nodeId: String(node.id),
4174+
bounds: {
4175+
x: node.pos[0],
4176+
y: node.pos[1],
4177+
width: node.size?.[0] ?? 100,
4178+
height: node.size?.[1] ?? 200
41884179
}
4189-
})
4180+
}))
41904181

41914182
if (newPositions.length) layoutStore.setSource(LayoutSource.Canvas)
41924183
layoutStore.batchUpdateNodeBounds(newPositions)
@@ -6407,7 +6398,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
64076398
options
64086399
)
64096400
) {
6410-
node.pos[0] -= node.size[0] * 0.5
6401+
node.setPos(node.pos[0] - node.size[0] * 0.5, node.pos[1])
64116402
}
64126403
})
64136404
break
@@ -8695,27 +8686,14 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
86958686
* Apply batched node position updates
86968687
*/
86978688
private applyNodePositionUpdates(
8698-
nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }>,
8699-
mutations: ReturnType<typeof useLayoutMutations>
8689+
nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }>
87008690
): void {
87018691
for (const { node, newPos } of nodesToMove) {
8702-
// Update LiteGraph position first so next drag uses correct base position
8703-
node.pos[0] = newPos.x
8704-
node.pos[1] = newPos.y
8705-
// Then update layout store which will update Vue nodes
8706-
mutations.moveNode(node.id, newPos)
8692+
// setPos automatically syncs to layout store
8693+
node.setPos(newPos.x, newPos.y)
87078694
}
87088695
}
87098696

8710-
/**
8711-
* Initialize layout mutations with Canvas source
8712-
*/
8713-
private initLayoutMutations(): ReturnType<typeof useLayoutMutations> {
8714-
const mutations = useLayoutMutations()
8715-
mutations.setSource(LayoutSource.Canvas)
8716-
return mutations
8717-
}
8718-
87198697
/**
87208698
* Collect all nodes that are children of groups in the selection
87218699
*/
@@ -8763,7 +8741,6 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
87638741
deltaX: number,
87648742
deltaY: number
87658743
) {
8766-
const mutations = this.initLayoutMutations()
87678744
const nodesInMovingGroups = this.collectNodesInGroups(allItems)
87688745
const nodesToMove: NewNodePosition[] = []
87698746

@@ -8789,12 +8766,11 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
87898766
}
87908767

87918768
// Now apply all the node moves at once
8792-
this.applyNodePositionUpdates(nodesToMove, mutations)
8769+
this.applyNodePositionUpdates(nodesToMove)
87938770
}
87948771

87958772
repositionNodesVueMode(nodesToReposition: NewNodePosition[]) {
8796-
const mutations = this.initLayoutMutations()
8797-
this.applyNodePositionUpdates(nodesToReposition, mutations)
8773+
this.applyNodePositionUpdates(nodesToReposition)
87988774
}
87998775

88008776
/**

src/lib/litegraph/src/LGraphNode.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,17 @@ export class LGraphNode
490490

491491
this._pos[0] = value[0]
492492
this._pos[1] = value[1]
493+
494+
const mutations = useLayoutMutations()
495+
mutations.setSource(LayoutSource.Canvas)
496+
mutations.moveNode(String(this.id), { x: value[0], y: value[1] })
497+
}
498+
499+
/**
500+
* Set the node position to an absolute location.
501+
*/
502+
setPos(x: number, y: number): void {
503+
this.pos = [x, y]
493504
}
494505

495506
public get size() {
@@ -501,6 +512,13 @@ export class LGraphNode
501512

502513
this._size[0] = value[0]
503514
this._size[1] = value[1]
515+
516+
const mutations = useLayoutMutations()
517+
mutations.setSource(LayoutSource.Canvas)
518+
mutations.resizeNode(String(this.id), {
519+
width: value[0],
520+
height: value[1]
521+
})
504522
}
505523

506524
/**
@@ -2032,8 +2050,7 @@ export class LGraphNode
20322050
return
20332051
}
20342052

2035-
this.pos[0] += deltaX
2036-
this.pos[1] += deltaY
2053+
this.pos = [this._pos[0] + deltaX, this._pos[1] + deltaY]
20372054
}
20382055

20392056
/**

src/lib/litegraph/src/utils/arrange.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ export function alignNodes(
138138
})
139139

140140
for (const { node, newPos } of nodePositions) {
141-
node.pos[0] = newPos.x
142-
node.pos[1] = newPos.y
141+
node.setPos(newPos.x, newPos.y)
143142
}
144143
return nodePositions
145144
}

src/renderer/extensions/vueNodes/components/LGraphNode.vue

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ import {
199199
nextTick,
200200
onErrorCaptured,
201201
onMounted,
202+
onUnmounted,
202203
ref,
203204
watch
204205
} from 'vue'
@@ -223,6 +224,7 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
223224
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
224225
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
225226
import NodeBadges from '@/renderer/extensions/vueNodes/components/NodeBadges.vue'
227+
import { LayoutSource } from '@/renderer/core/layout/types'
226228
import SlotConnectionDot from '@/renderer/extensions/vueNodes/components/SlotConnectionDot.vue'
227229
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
228230
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
@@ -362,30 +364,60 @@ const handleContextMenu = (event: MouseEvent) => {
362364
showNodeOptions(event)
363365
}
364366
365-
onMounted(() => {
366-
initSizeStyles()
367-
})
368-
369367
/**
370-
* Set initial DOM size from layout store, but respect intrinsic content minimum.
371-
* Important: nodes can mount in a collapsed state, and the collapse watcher won't
372-
* run initially. Match the collapsed runtime behavior by writing to the correct
373-
* CSS variables on mount.
368+
* Set initial DOM size from layout store.
374369
*/
375370
function initSizeStyles() {
376371
const el = nodeContainerRef.value
377372
const { width, height } = size.value
378373
if (!el) return
379374
380375
const suffix = isCollapsed.value ? '-x' : ''
376+
const fullHeight = height + LiteGraph.NODE_TITLE_HEIGHT
381377
382378
el.style.setProperty(`--node-width${suffix}`, `${width}px`)
383-
el.style.setProperty(
384-
`--node-height${suffix}`,
385-
`${height + LiteGraph.NODE_TITLE_HEIGHT}px`
379+
el.style.setProperty(`--node-height${suffix}`, `${fullHeight}px`)
380+
}
381+
382+
/**
383+
* Handle external size changes (e.g., from extensions calling node.setSize()).
384+
* Updates CSS variables when layoutStore changes from Canvas/External source.
385+
*/
386+
function handleLayoutChange(change: {
387+
source: LayoutSource
388+
nodeIds: string[]
389+
}) {
390+
// Only handle Canvas or External source (extensions calling setSize)
391+
if (
392+
change.source !== LayoutSource.Canvas &&
393+
change.source !== LayoutSource.External
386394
)
395+
return
396+
397+
if (!change.nodeIds.includes(nodeData.id)) return
398+
if (layoutStore.isResizingVueNodes.value) return
399+
if (isCollapsed.value) return
400+
401+
const el = nodeContainerRef.value
402+
if (!el) return
403+
404+
const newSize = size.value
405+
const fullHeight = newSize.height + LiteGraph.NODE_TITLE_HEIGHT
406+
el.style.setProperty('--node-width', `${newSize.width}px`)
407+
el.style.setProperty('--node-height', `${fullHeight}px`)
387408
}
388409
410+
let unsubscribeLayoutChange: (() => void) | null = null
411+
412+
onMounted(() => {
413+
initSizeStyles()
414+
unsubscribeLayoutChange = layoutStore.onChange(handleLayoutChange)
415+
})
416+
417+
onUnmounted(() => {
418+
unsubscribeLayoutChange?.()
419+
})
420+
389421
const baseResizeHandleClasses =
390422
'absolute h-5 w-5 opacity-0 pointer-events-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-white/40'
391423

0 commit comments

Comments
 (0)