[fix] Allow creating connections from empty subgraph slots (#1167)

This commit is contained in:
Christian Byrne
2025-07-29 00:20:37 -07:00
committed by GitHub
parent bdcda7308b
commit fcebbbcba8
4 changed files with 61 additions and 10 deletions

View File

@@ -6026,7 +6026,11 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
}
const { name } = slotX
iSlotConn = nodeX.slots.findIndex(s => s.name === name)
slotX = nodeX.slots[iSlotConn]
// If it's not found in the main slots, it might be the empty slot from a Subgraph node.
// In that case, the original `slotX` object is the correct one, so don't overwrite it.
if (iSlotConn !== -1) {
slotX = nodeX.slots[iSlotConn]
}
if (!slotX) {
console.warn("Cant get slot information", slotX)
return

View File

@@ -283,6 +283,8 @@ export class LinkConnector {
this.renderLinks.push(renderLink)
this.state.connectingTo = "input"
this.#setLegacyLinks(false)
}
dragNewFromSubgraphOutput(network: LinkNetwork, outputNode: SubgraphOutputNode, output: SubgraphOutput, fromReroute?: Reroute): void {
@@ -292,6 +294,8 @@ export class LinkConnector {
this.renderLinks.push(renderLink)
this.state.connectingTo = "output"
this.#setLegacyLinks(true)
}
/**
@@ -425,19 +429,15 @@ export class LinkConnector {
}
/**
* Connects the links being droppe
* Connects the links being dropped
* @param event Contains the drop location, in canvas space
*/
dropLinks(locator: ItemLocator, event: CanvasPointerEvent): void {
if (!this.isConnecting) {
console.warn("Attempted to drop links when not connecting to anything.")
return
const mayContinue = this.events.dispatch("before-drop-links", { renderLinks: this.renderLinks, event })
if (mayContinue === false) return
}
const { renderLinks } = this
const mayContinue = this.events.dispatch("before-drop-links", { renderLinks, event })
if (mayContinue === false) return
try {
const { canvasX, canvasY } = event
@@ -461,11 +461,11 @@ export class LinkConnector {
}
}
} finally {
this.events.dispatch("after-drop-links", { renderLinks, event })
this.events.dispatch("after-drop-links", { renderLinks: this.renderLinks, event })
}
}
dropOnIoNode(ioNode: SubgraphInputNode | SubgraphOutputNode, event: CanvasPointerEvent) {
dropOnIoNode(ioNode: SubgraphInputNode | SubgraphOutputNode, event: CanvasPointerEvent): void {
const { renderLinks, state } = this
const { connectingTo } = state
const { canvasX, canvasY } = event

View File

@@ -103,6 +103,18 @@ export class SubgraphInputNode extends SubgraphIONodeBase<SubgraphInput> impleme
const inputSlot = target_node.findInputByType(target_slotType)
if (!inputSlot) return
if (slot === -1) {
// This indicates a connection is being made from the "Empty" slot.
// We need to create a new, concrete input on the subgraph that matches the target.
const newSubgraphInput = this.subgraph.addInput(inputSlot.slot.name, String(inputSlot.slot.type ?? ""))
const newSlotIndex = this.slots.indexOf(newSubgraphInput)
if (newSlotIndex === -1) {
console.error("Could not find newly created subgraph input slot.")
return
}
slot = newSlotIndex
}
return this.slots[slot].connect(inputSlot.slot, target_node, optsIn?.afterRerouteId)
}

View File

@@ -349,3 +349,38 @@ describe("SubgraphIO - Advanced Scenarios", () => {
expect(instance3.outputs.length).toBe(2)
})
})
describe("SubgraphIO - Empty Slot Connection", () => {
subgraphTest("creates new input and connects when dragging from empty slot inside subgraph", ({ subgraphWithNode }) => {
const { subgraph, subgraphNode } = subgraphWithNode
// Create a node inside the subgraph that will receive the connection
const internalNode = new LGraphNode("Internal Node")
internalNode.addInput("in", "string")
subgraph.add(internalNode)
// Simulate the connection process from the empty slot to an internal node
// The -1 indicates a connection from the "empty" slot
subgraph.inputNode.connectByType(-1, internalNode, "string")
// 1. A new input should have been created on the subgraph
expect(subgraph.inputs.length).toBe(2) // Fixture adds one input already
const newInput = subgraph.inputs[1]
expect(newInput.name).toBe("in")
expect(newInput.type).toBe("string")
// 2. The subgraph node should now have a corresponding real input slot
expect(subgraphNode.inputs.length).toBe(2)
const subgraphInputSlot = subgraphNode.inputs[1]
expect(subgraphInputSlot.name).toBe("in")
// 3. A link should be established inside the subgraph
expect(internalNode.inputs[0].link).not.toBe(null)
const link = subgraph.links.get(internalNode.inputs[0].link!)
expect(link).toBeDefined()
expect(link.target_id).toBe(internalNode.id)
expect(link.target_slot).toBe(0)
expect(link.origin_id).toBe(subgraph.inputNode.id)
expect(link.origin_slot).toBe(1) // Should be the second slot
})
})