[feat] Add containerNode property for DOM widget positioning in subgraphs (#1128)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Byrne
2025-07-20 18:36:33 -07:00
committed by GitHub
parent 2fd94319bc
commit 0a8f16fe19
7 changed files with 380 additions and 5 deletions

View File

@@ -1,6 +1,8 @@
import type { LGraphEventMap } from "./LGraphEventMap"
import type { SubgraphInput } from "@/subgraph/SubgraphInput"
import type { SubgraphNode } from "@/subgraph/SubgraphNode"
import type { SubgraphOutput } from "@/subgraph/SubgraphOutput"
import type { IBaseWidget } from "@/types/widgets"
export interface SubgraphEventMap extends LGraphEventMap {
"adding-input": {
@@ -40,4 +42,13 @@ export interface SubgraphEventMap extends LGraphEventMap {
oldName: string
newName: string
}
"widget-promoted": {
widget: IBaseWidget
subgraphNode: SubgraphNode
}
"widget-unpromoted": {
widget: IBaseWidget
subgraphNode: SubgraphNode
}
}

View File

@@ -187,8 +187,13 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
}
#setWidget(subgraphInput: Readonly<SubgraphInput>, input: INodeInputSlot, widget: Readonly<IBaseWidget>) {
// Use the first matching widget
const promotedWidget = toConcreteWidget(widget, this).createCopyForNode(this)
const concreteWidget = toConcreteWidget(widget, this)
const promotedWidget = concreteWidget.createCopyForNode(this)
// Set parentSubgraphNode for all promoted widgets to track their origin
promotedWidget.parentSubgraphNode = this
Object.assign(promotedWidget, {
get name() {
return subgraphInput.name
@@ -212,6 +217,9 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
this.widgets.push(promotedWidget)
// Dispatch widget-promoted event
this.subgraph.events.dispatch("widget-promoted", { widget: promotedWidget, subgraphNode: this })
input.widget = { name: subgraphInput.name }
input._widget = promotedWidget
}
@@ -307,7 +315,28 @@ export class SubgraphNode extends LGraphNode implements BaseLGraph {
return nodes
}
override removeWidgetByName(name: string): void {
const widget = this.widgets.find(w => w.name === name)
if (widget) {
this.subgraph.events.dispatch("widget-unpromoted", { widget, subgraphNode: this })
}
super.removeWidgetByName(name)
}
override ensureWidgetRemoved(widget: IBaseWidget): void {
if (this.widgets.includes(widget)) {
this.subgraph.events.dispatch("widget-unpromoted", { widget, subgraphNode: this })
}
super.ensureWidgetRemoved(widget)
}
override onRemoved(): void {
// Clean up all promoted widgets
for (const widget of this.widgets) {
widget.parentSubgraphNode = undefined
this.subgraph.events.dispatch("widget-unpromoted", { widget, subgraphNode: this })
}
for (const input of this.inputs) {
input._listenerController?.abort()
}

View File

@@ -201,6 +201,12 @@ export interface IBaseWidget<
tooltip?: string
/**
* Reference to the subgraph container node when this widget is promoted from a subgraph.
* This allows the widget to know which SubgraphNode it belongs to in the parent graph.
*/
parentSubgraphNode?: LGraphNode
// TODO: Confirm this format
callback?(
value: any,

View File

@@ -56,6 +56,12 @@ export abstract class BaseWidget<TWidget extends IBaseWidget = IBaseWidget> impl
return this.#node
}
/**
* Reference to the subgraph container node when this widget is promoted from a subgraph.
* This allows the widget to know which SubgraphNode it belongs to in the parent graph.
*/
parentSubgraphNode?: LGraphNode
linkedWidgets?: IBaseWidget[]
name: string
options: TWidget["options"]
@@ -297,4 +303,12 @@ export abstract class BaseWidget<TWidget extends IBaseWidget = IBaseWidget> impl
cloned.value = this.value
return cloned
}
/**
* Type guard to check if this widget has a DOM element.
* @returns True if the widget has a DOM element attached
*/
isDOMWidget(): this is this & { element: HTMLElement } {
return this.element instanceof HTMLElement
}
}