refactor: move isWidgetPromoted to promotionStore for shared use

Move the dual-key promotion check (with/without disambiguatingSourceNodeId)
from promotionUtils.ts into promotionStore as isWidgetPromoted(). This
avoids circular dependency issues when BaseWidget and domWidget need the
same nested subgraph fallback logic. All three consumers now use the
shared store method.
This commit is contained in:
dante01yoon
2026-04-08 20:21:30 +09:00
parent 894768a4b6
commit 2330807b06
6 changed files with 47 additions and 45 deletions

View File

@@ -27,30 +27,6 @@ export function getWidgetName(w: IBaseWidget): string {
return isPromotedWidgetView(w) ? w.sourceWidgetName : w.name
}
/**
* Checks whether a widget is promoted by any subgraph node in the given graph.
* Handles nested subgraph promotions where the stored key may omit the
* disambiguatingSourceNodeId — checks both key shapes (with and without).
*/
export function isWidgetPromoted(
graphId: string,
sourceNodeId: string,
sourceWidgetName: string,
disambiguatingSourceNodeId?: string
): boolean {
const store = usePromotionStore()
if (
disambiguatingSourceNodeId &&
store.isPromotedByAny(graphId, {
sourceNodeId,
sourceWidgetName,
disambiguatingSourceNodeId
})
)
return true
return store.isPromotedByAny(graphId, { sourceNodeId, sourceWidgetName })
}
/**
* Returns true if the given promotion entry corresponds to a linked promotion
* on the subgraph node. Linked promotions are driven by subgraph input

View File

@@ -211,10 +211,11 @@ export abstract class BaseWidget<TWidget extends IBaseWidget = IBaseWidget>
if (
graphId &&
!suppressPromotedOutline &&
usePromotionStore().isPromotedByAny(graphId, {
sourceNodeId: String(this.node.id),
sourceWidgetName: this.name
})
usePromotionStore().isWidgetPromoted(
graphId,
String(this.node.id),
this.name
)
)
return LiteGraph.WIDGET_PROMOTED_OUTLINE_COLOR
return this.advanced

View File

@@ -116,7 +116,7 @@ import {
stripGraphPrefix,
useWidgetValueStore
} from '@/stores/widgetValueStore'
import { isWidgetPromoted } from '@/core/graph/subgraph/promotionUtils'
import { usePromotionStore } from '@/stores/promotionStore'
import { useMissingModelStore } from '@/platform/missingModel/missingModelStore'
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
import type {
@@ -144,6 +144,7 @@ const { shouldHandleNodePointerEvents, forwardEventToCanvas } =
const { isSelectInputsMode } = useAppMode()
const canvasStore = useCanvasStore()
const { bringNodeToFront } = useNodeZIndex()
const promotionStore = usePromotionStore()
const executionErrorStore = useExecutionErrorStore()
const missingModelStore = useMissingModelStore()
@@ -392,7 +393,7 @@ const processedWidgets = computed((): ProcessedWidget[] => {
const sourceWidgetName = widget.storeName ?? widget.name
const isPromoted =
graphId &&
isWidgetPromoted(
promotionStore.isWidgetPromoted(
graphId,
hostNodeId,
sourceWidgetName,

View File

@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { ComponentWidgetImpl, DOMWidgetImpl } from '@/scripts/domWidget'
const isPromotedByAnyMock = vi.hoisted(() => vi.fn())
const isWidgetPromotedMock = vi.hoisted(() => vi.fn())
// Mock dependencies
vi.mock('@/stores/domWidgetStore', () => ({
@@ -14,7 +14,7 @@ vi.mock('@/stores/domWidgetStore', () => ({
vi.mock('@/stores/promotionStore', () => ({
usePromotionStore: () => ({
isPromotedByAny: isPromotedByAnyMock
isWidgetPromoted: isWidgetPromotedMock
})
}))
@@ -120,7 +120,7 @@ describe('DOMWidget draw promotion behavior', () => {
})
test('draws promoted outline for visible promoted widgets', () => {
isPromotedByAnyMock.mockReturnValue(true)
isWidgetPromotedMock.mockReturnValue(true)
const node = new LGraphNode('test-node')
const rootGraph = { id: 'root-graph-id' }
@@ -138,16 +138,17 @@ describe('DOMWidget draw promotion behavior', () => {
widget.draw(ctx as CanvasRenderingContext2D, node, 200, 30, 40)
expect(isPromotedByAnyMock).toHaveBeenCalledWith('root-graph-id', {
sourceNodeId: '-1',
sourceWidgetName: 'seed'
})
expect(isWidgetPromotedMock).toHaveBeenCalledWith(
'root-graph-id',
'-1',
'seed'
)
expect(ctx.strokeRect).toHaveBeenCalledOnce()
expect(onDraw).toHaveBeenCalledWith(widget)
})
test('does not draw promoted outline when widget is not promoted', () => {
isPromotedByAnyMock.mockReturnValue(false)
isWidgetPromotedMock.mockReturnValue(false)
const node = new LGraphNode('test-node')
const rootGraph = { id: 'root-graph-id' }
@@ -187,7 +188,7 @@ describe('DOMWidget draw promotion behavior', () => {
widget.draw(ctx as CanvasRenderingContext2D, node, 200, 30, 40)
expect(isPromotedByAnyMock).not.toHaveBeenCalled()
expect(isWidgetPromotedMock).not.toHaveBeenCalled()
expect(ctx.strokeRect).not.toHaveBeenCalled()
expect(onDraw).toHaveBeenCalledWith(widget)
})

View File

@@ -125,8 +125,6 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
declare readonly name: string
declare readonly options: DOMWidgetOptions<V>
declare callback?: (value: V) => void
readonly promotionStore = usePromotionStore()
readonly id: string
constructor(obj: {
@@ -190,10 +188,11 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
const graphId = this.node.graph?.rootGraph.id
const isPromoted =
graphId &&
this.promotionStore.isPromotedByAny(graphId, {
sourceNodeId: String(this.node.id),
sourceWidgetName: this.name
})
usePromotionStore().isWidgetPromoted(
graphId,
String(this.node.id),
this.name
)
if (!isPromoted) {
this.options.onDraw?.(this)
return

View File

@@ -190,11 +190,35 @@ export const usePromotionStore = defineStore('promotion', () => {
graphRefCounts.value.delete(graphId)
}
/**
* Checks whether a widget is promoted by any subgraph node in the given
* graph. Handles nested subgraph promotions where the stored key may omit
* the disambiguatingSourceNodeId — checks both key shapes (#10612).
*/
function isWidgetPromoted(
graphId: UUID,
sourceNodeId: string,
sourceWidgetName: string,
disambiguatingSourceNodeId?: string
): boolean {
if (
disambiguatingSourceNodeId &&
isPromotedByAny(graphId, {
sourceNodeId,
sourceWidgetName,
disambiguatingSourceNodeId
})
)
return true
return isPromotedByAny(graphId, { sourceNodeId, sourceWidgetName })
}
return {
getPromotionsRef,
getPromotions,
isPromoted,
isPromotedByAny,
isWidgetPromoted,
setPromotions,
promote,
demote,