fix(vueNodes): sync node size changes from extensions to Vue components

This commit is contained in:
Terry Jia
2026-02-07 14:06:37 -05:00
parent 442eff1094
commit 26c682f44f
6 changed files with 104 additions and 74 deletions

View File

@@ -765,8 +765,10 @@ export class LGraph
let max_size = 100
let y = margin + LiteGraph.NODE_TITLE_HEIGHT
for (const node of column) {
node.pos[0] = layout == LiteGraph.VERTICAL_LAYOUT ? y : x
node.pos[1] = layout == LiteGraph.VERTICAL_LAYOUT ? x : y
node.setPos(
layout == LiteGraph.VERTICAL_LAYOUT ? y : x,
layout == LiteGraph.VERTICAL_LAYOUT ? x : y
)
const max_size_index = layout == LiteGraph.VERTICAL_LAYOUT ? 1 : 0
if (node.size[max_size_index] > max_size) {
max_size = node.size[max_size_index]
@@ -1733,7 +1735,10 @@ export class LGraph
)
//Correct for title height. It's included in bounding box, but not _posSize
subgraphNode.pos[1] += LiteGraph.NODE_TITLE_HEIGHT / 2
subgraphNode.setPos(
subgraphNode.pos[0],
subgraphNode.pos[1] + LiteGraph.NODE_TITLE_HEIGHT / 2
)
// Add the subgraph node to the graph
this.add(subgraphNode)
@@ -1900,8 +1905,7 @@ export class LGraph
this.add(node, true)
node.configure(n_info)
node.pos[0] += offsetX
node.pos[1] += offsetY
node.setPos(node.pos[0] + offsetX, node.pos[1] + offsetY)
for (const input of node.inputs) {
input.link = null
}

View File

@@ -13,7 +13,9 @@ vi.mock('@/renderer/core/layout/store/layoutStore', () => ({
querySlotAtPoint: vi.fn(),
queryRerouteAtPoint: vi.fn(),
getNodeLayoutRef: vi.fn(() => ({ value: null })),
getSlotLayout: vi.fn()
getSlotLayout: vi.fn(),
setSource: vi.fn(),
batchUpdateNodeBounds: vi.fn()
}
}))

View File

@@ -5,10 +5,8 @@ import { PREFIX, SEPARATOR } from '@/constants/groupNodeConstants'
import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
import type { LinkRenderContext } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculations'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { LayoutSource } from '@/renderer/core/layout/types'
import { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'
import { forEachNode } from '@/utils/graphTraversalUtil'
import { CanvasPointer } from './CanvasPointer'
@@ -2396,8 +2394,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
const cloned = items?.created[0] as LGraphNode | undefined
if (!cloned) return
cloned.pos[0] += 5
cloned.pos[1] += 5
cloned.setPos(cloned.pos[0] + 5, cloned.pos[1] + 5)
if (this.allow_dragnodes) {
pointer.onDragStart = (pointer) => {
@@ -3581,19 +3578,14 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
if (dragEvent) {
this.adjustMouseEvent(dragEvent)
const e = dragEvent as CanvasPointerEvent
node.pos[0] = e.canvasX - node.size[0] / 2
node.pos[1] = e.canvasY + 10
node.setPos(e.canvasX - node.size[0] / 2, e.canvasY + 10)
// Update last_mouse to prevent jump on first drag move
this.last_mouse = [e.clientX, e.clientY]
} else {
node.pos[0] = this.graph_mouse[0] - node.size[0] / 2
node.pos[1] = this.graph_mouse[1] + 10
}
// Sync position to layout store for Vue node rendering
if (LiteGraph.vueNodesMode) {
const mutations = this.initLayoutMutations()
mutations.moveNode(node.id, { x: node.pos[0], y: node.pos[1] })
node.setPos(
this.graph_mouse[0] - node.size[0] / 2,
this.graph_mouse[1] + 10
)
}
this.state.ghostNodeId = node.id
@@ -4162,31 +4154,30 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
}
}
// Adjust positions
// Adjust positions - use move/setPos to ensure layout store is updated
const dx = position[0] - offsetX
const dy = position[1] - offsetY
for (const item of created) {
item.pos[0] += position[0] - offsetX
item.pos[1] += position[1] - offsetY
if (item instanceof LGraphNode) {
item.setPos(item.pos[0] + dx, item.pos[1] + dy)
} else if (item instanceof Reroute) {
item.move(dx, dy)
}
}
// TODO: Report failures, i.e. `failedNodes`
const newPositions = created
.filter((item): item is LGraphNode => item instanceof LGraphNode)
.map((node) => {
const fullHeight = node.size?.[1] ?? 200
const layoutHeight = LiteGraph.vueNodesMode
? removeNodeTitleHeight(fullHeight)
: fullHeight
return {
nodeId: String(node.id),
bounds: {
x: node.pos[0],
y: node.pos[1],
width: node.size?.[0] ?? 100,
height: layoutHeight
}
.map((node) => ({
nodeId: String(node.id),
bounds: {
x: node.pos[0],
y: node.pos[1],
width: node.size?.[0] ?? 100,
height: node.size?.[1] ?? 200
}
})
}))
if (newPositions.length) layoutStore.setSource(LayoutSource.Canvas)
layoutStore.batchUpdateNodeBounds(newPositions)
@@ -6407,7 +6398,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
options
)
) {
node.pos[0] -= node.size[0] * 0.5
node.setPos(node.pos[0] - node.size[0] * 0.5, node.pos[1])
}
})
break
@@ -8695,27 +8686,14 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
* Apply batched node position updates
*/
private applyNodePositionUpdates(
nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }>,
mutations: ReturnType<typeof useLayoutMutations>
nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }>
): void {
for (const { node, newPos } of nodesToMove) {
// Update LiteGraph position first so next drag uses correct base position
node.pos[0] = newPos.x
node.pos[1] = newPos.y
// Then update layout store which will update Vue nodes
mutations.moveNode(node.id, newPos)
// setPos automatically syncs to layout store
node.setPos(newPos.x, newPos.y)
}
}
/**
* Initialize layout mutations with Canvas source
*/
private initLayoutMutations(): ReturnType<typeof useLayoutMutations> {
const mutations = useLayoutMutations()
mutations.setSource(LayoutSource.Canvas)
return mutations
}
/**
* Collect all nodes that are children of groups in the selection
*/
@@ -8763,7 +8741,6 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
deltaX: number,
deltaY: number
) {
const mutations = this.initLayoutMutations()
const nodesInMovingGroups = this.collectNodesInGroups(allItems)
const nodesToMove: NewNodePosition[] = []
@@ -8789,12 +8766,11 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
}
// Now apply all the node moves at once
this.applyNodePositionUpdates(nodesToMove, mutations)
this.applyNodePositionUpdates(nodesToMove)
}
repositionNodesVueMode(nodesToReposition: NewNodePosition[]) {
const mutations = this.initLayoutMutations()
this.applyNodePositionUpdates(nodesToReposition, mutations)
this.applyNodePositionUpdates(nodesToReposition)
}
/**

View File

@@ -486,6 +486,17 @@ export class LGraphNode
this._pos[0] = value[0]
this._pos[1] = value[1]
const mutations = useLayoutMutations()
mutations.setSource(LayoutSource.Canvas)
mutations.moveNode(String(this.id), { x: value[0], y: value[1] })
}
/**
* Set the node position to an absolute location.
*/
setPos(x: number, y: number): void {
this.pos = [x, y]
}
public get size() {
@@ -497,6 +508,13 @@ export class LGraphNode
this._size[0] = value[0]
this._size[1] = value[1]
const mutations = useLayoutMutations()
mutations.setSource(LayoutSource.Canvas)
mutations.resizeNode(String(this.id), {
width: value[0],
height: value[1]
})
}
/**
@@ -2020,8 +2038,7 @@ export class LGraphNode
return
}
this.pos[0] += deltaX
this.pos[1] += deltaY
this.pos = [this._pos[0] + deltaX, this._pos[1] + deltaY]
}
/**

View File

@@ -138,8 +138,7 @@ export function alignNodes(
})
for (const { node, newPos } of nodePositions) {
node.pos[0] = newPos.x
node.pos[1] = newPos.y
node.setPos(newPos.x, newPos.y)
}
return nodePositions
}

View File

@@ -167,6 +167,7 @@ import {
nextTick,
onErrorCaptured,
onMounted,
onUnmounted,
ref,
watch
} from 'vue'
@@ -189,6 +190,7 @@ import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { LayoutSource } from '@/renderer/core/layout/types'
import SlotConnectionDot from '@/renderer/extensions/vueNodes/components/SlotConnectionDot.vue'
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
@@ -327,15 +329,8 @@ const handleContextMenu = (event: MouseEvent) => {
showNodeOptions(event)
}
onMounted(() => {
initSizeStyles()
})
/**
* Set initial DOM size from layout store, but respect intrinsic content minimum.
* Important: nodes can mount in a collapsed state, and the collapse watcher won't
* run initially. Match the collapsed runtime behavior by writing to the correct
* CSS variables on mount.
* Set initial DOM size from layout store.
*/
function initSizeStyles() {
const el = nodeContainerRef.value
@@ -343,14 +338,51 @@ function initSizeStyles() {
if (!el) return
const suffix = isCollapsed.value ? '-x' : ''
const fullHeight = height + LiteGraph.NODE_TITLE_HEIGHT
el.style.setProperty(`--node-width${suffix}`, `${width}px`)
el.style.setProperty(
`--node-height${suffix}`,
`${height + LiteGraph.NODE_TITLE_HEIGHT}px`
)
el.style.setProperty(`--node-height${suffix}`, `${fullHeight}px`)
}
/**
* Handle external size changes (e.g., from extensions calling node.setSize()).
* Updates CSS variables when layoutStore changes from Canvas/External source.
*/
function handleLayoutChange(change: {
source: LayoutSource
nodeIds: string[]
}) {
// Only handle Canvas or External source (extensions calling setSize)
if (
change.source !== LayoutSource.Canvas &&
change.source !== LayoutSource.External
)
return
if (!change.nodeIds.includes(nodeData.id)) return
if (layoutStore.isResizingVueNodes.value) return
if (isCollapsed.value) return
const el = nodeContainerRef.value
if (!el) return
const newSize = size.value
const fullHeight = newSize.height + LiteGraph.NODE_TITLE_HEIGHT
el.style.setProperty('--node-width', `${newSize.width}px`)
el.style.setProperty('--node-height', `${fullHeight}px`)
}
let unsubscribeLayoutChange: (() => void) | null = null
onMounted(() => {
initSizeStyles()
unsubscribeLayoutChange = layoutStore.onChange(handleLayoutChange)
})
onUnmounted(() => {
unsubscribeLayoutChange?.()
})
const baseResizeHandleClasses =
'absolute h-5 w-5 opacity-0 pointer-events-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-white/40'