Files
ComfyUI_frontend/src/composables/useUpstreamValue.ts
Terry Jia 16f4f3f3ed fix: address PR review feedback for upstream value composable (#9908)
## Summary
follow up https://github.com/Comfy-Org/ComfyUI_frontend/pull/9851
fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/9877 and
https://github.com/Comfy-Org/ComfyUI_frontend/issues/9878

- Make useUpstreamValue generic to eliminate as Bounds/CurvePoint[]
casts
- Change isBoundsObject to type predicate (value is Bounds)
- Reuse WidgetState from widgetValueStore instead of duplicate interface
- Add length >= 2 guard in isCurvePointArray for empty arrays
- Add disabled guard in effectiveBounds setter
- Add unit tests for singleValueExtractor and boundsExtractor

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9908-fix-address-PR-review-feedback-for-upstream-value-composable-3236d73d365081f7a01dcb416732544a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-13 19:58:30 -07:00

81 lines
2.6 KiB
TypeScript

import { computed } from 'vue'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useWidgetValueStore } from '@/stores/widgetValueStore'
import type { WidgetState } from '@/stores/widgetValueStore'
import type { Bounds } from '@/renderer/core/layout/types'
import type { LinkedUpstreamInfo } from '@/types/simplifiedWidget'
type ValueExtractor<T = unknown> = (
widgets: WidgetState[],
outputName: string | undefined
) => T | undefined
export function useUpstreamValue<T>(
getLinkedUpstream: () => LinkedUpstreamInfo | undefined,
extractValue: ValueExtractor<T>
) {
const canvasStore = useCanvasStore()
const widgetValueStore = useWidgetValueStore()
return computed(() => {
const upstream = getLinkedUpstream()
if (!upstream) return undefined
const graphId = canvasStore.canvas?.graph?.rootGraph.id
if (!graphId) return undefined
const widgets = widgetValueStore.getNodeWidgets(graphId, upstream.nodeId)
return extractValue(widgets, upstream.outputName)
})
}
export function singleValueExtractor<T>(
isValid: (value: unknown) => value is T
): ValueExtractor<T> {
return (widgets, outputName) => {
if (outputName) {
const matched = widgets.find((w) => w.name === outputName)
if (matched && isValid(matched.value)) return matched.value
}
const validValues = widgets.map((w) => w.value).filter(isValid)
return validValues.length === 1 ? validValues[0] : undefined
}
}
function isBoundsObject(value: unknown): value is Bounds {
if (typeof value !== 'object' || value === null) return false
const v = value as Record<string, unknown>
return (
typeof v.x === 'number' &&
typeof v.y === 'number' &&
typeof v.width === 'number' &&
typeof v.height === 'number'
)
}
export function boundsExtractor(): ValueExtractor<Bounds> {
const single = singleValueExtractor(isBoundsObject)
return (widgets, outputName) => {
const singleResult = single(widgets, outputName)
if (singleResult) return singleResult
// Fallback: assemble from individual widgets matching BoundingBoxInputSpec field names
const getNum = (name: string): number | undefined => {
const w = widgets.find((w) => w.name === name)
return typeof w?.value === 'number' ? w.value : undefined
}
const x = getNum('x')
const y = getNum('y')
const width = getNum('width')
const height = getNum('height')
if (
x !== undefined &&
y !== undefined &&
width !== undefined &&
height !== undefined
) {
return { x, y, width, height }
}
return undefined
}
}