fix: Improve legacy widget compatibility in vueNodes mode (#7766)

## Summary
- Fix widget callback signature to pass node as 3rd parameter for
extensions like Impact Pack
- Add triggerDraw call to update all legacy widgets when any widget
value changes
- Support computeLayoutSize for dynamic widget height calculation
- Set node.canvasHeight for extensions that rely on this property
- Use step/10 for number input buttons to match litegraph behavior

Fixes display and interaction issues with Impact Pack's Mask Rect Area
nodes.

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7615 and
https://github.com/Comfy-Org/ComfyUI_frontend/issues/7616

it also requires Impact pack PR
https://github.com/ltdrdata/ComfyUI-Impact-Pack/pull/1167

## Screenshots
Before


https://github.com/user-attachments/assets/eb890f7c-c1a0-4c7b-a8d7-dde304de83e4


After


https://github.com/user-attachments/assets/dad65b52-d71e-4c19-92c0-367b7dcafed0

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7766-fix-Improve-legacy-widget-compatibility-in-vueNodes-mode-2d56d73d365081b18d83f4a41ff0f377)
by [Unito](https://www.unito.io)
This commit is contained in:
Terry Jia
2026-01-01 21:35:08 -05:00
committed by GitHub
parent 5e932bb1e8
commit 4c955f6725
5 changed files with 55 additions and 13 deletions

View File

@@ -34,6 +34,7 @@ import type {
} from '@/lib/litegraph/src/litegraph' } from '@/lib/litegraph/src/litegraph'
import type { TitleMode } from '@/lib/litegraph/src/types/globalEnums' import type { TitleMode } from '@/lib/litegraph/src/types/globalEnums'
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums' import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
import { app } from '@/scripts/app'
export interface WidgetSlotMetadata { export interface WidgetSlotMetadata {
index: number index: number
@@ -47,6 +48,7 @@ export interface SafeWidgetData {
borderStyle?: string borderStyle?: string
callback?: ((value: unknown) => void) | undefined callback?: ((value: unknown) => void) | undefined
controlWidget?: SafeControlWidget controlWidget?: SafeControlWidget
hasLayoutSize?: boolean
isDOMWidget?: boolean isDOMWidget?: boolean
label?: string label?: string
nodeType?: string nodeType?: string
@@ -171,7 +173,12 @@ export function safeWidgetMapper(
const callback = (v: unknown) => { const callback = (v: unknown) => {
const value = normalizeWidgetValue(v) const value = normalizeWidgetValue(v)
widget.value = value ?? undefined widget.value = value ?? undefined
widget.callback?.(value) // Match litegraph callback signature: (value, canvas, node, pos, event)
// Some extensions (e.g., Impact Pack) expect node as the 3rd parameter
widget.callback?.(value, app.canvas, node)
// Trigger redraw for all legacy widgets on this node (e.g., mask preview)
// This ensures widgets that depend on other widget values get updated
node.widgets?.forEach((w) => w.triggerDraw?.())
} }
return { return {
@@ -181,6 +188,7 @@ export function safeWidgetMapper(
borderStyle, borderStyle,
callback, callback,
controlWidget: getControlWidget(widget), controlWidget: getControlWidget(widget),
hasLayoutSize: typeof widget.computeLayoutSize === 'function',
isDOMWidget: isDOMWidget(widget), isDOMWidget: isDOMWidget(widget),
label: widget.label, label: widget.label,
nodeType: getNodeType(node, widget), nodeType: getNodeType(node, widget),

View File

@@ -203,10 +203,13 @@ const processedWidgets = computed((): ProcessedWidget[] => {
}) })
const gridTemplateRows = computed((): string => { const gridTemplateRows = computed((): string => {
const widgets = toValue(processedWidgets) if (!nodeData?.widgets) return ''
return widgets const processedNames = new Set(toValue(processedWidgets).map((w) => w.name))
.filter((w) => !w.simplified.options?.hidden) return nodeData.widgets
.map((w) => (shouldExpand(w.type) ? 'auto' : 'min-content')) .filter((w) => processedNames.has(w.name) && !w.options?.hidden)
.map((w) =>
shouldExpand(w.type) || w.hasLayoutSize ? 'auto' : 'min-content'
)
.join(' ') .join(' ')
}) })
</script> </script>

View File

@@ -31,10 +31,17 @@ const precision = computed(() => {
// Calculate the step value based on precision or widget options // Calculate the step value based on precision or widget options
const stepValue = computed(() => { const stepValue = computed(() => {
// Use step2 (correct input spec value) instead of step (legacy 10x value) // Use step2 (correct input spec value) if available
if (props.widget.options?.step2 !== undefined) { if (props.widget.options?.step2 !== undefined) {
return Number(props.widget.options.step2) return Number(props.widget.options.step2)
} }
// Use step / 10 for custom large step values (> 10) to match litegraph behavior
// This is important for extensions like Impact Pack that use custom step values (e.g., 640)
// We skip default step values (1, 10) to avoid affecting normal widgets
const step = props.widget.options?.step
if (step !== undefined && step > 10) {
return Number(step) / 10
}
// Otherwise, derive from precision // Otherwise, derive from precision
if (precision.value !== undefined) { if (precision.value !== undefined) {
if (precision.value === 0) { if (precision.value === 0) {

View File

@@ -17,6 +17,7 @@ const props = defineProps<{
}>() }>()
const canvasEl = ref() const canvasEl = ref()
const containerHeight = ref(20)
const canvas: LGraphCanvas = useCanvasStore().canvas as LGraphCanvas const canvas: LGraphCanvas = useCanvasStore().canvas as LGraphCanvas
let node: LGraphNode | undefined let node: LGraphNode | undefined
@@ -52,9 +53,19 @@ onBeforeUnmount(() => {
function draw() { function draw() {
if (!widgetInstance || !node) return if (!widgetInstance || !node) return
const width = canvasEl.value.parentElement.clientWidth const width = canvasEl.value.parentElement.clientWidth
const height = widgetInstance.computeSize // Priority: computedHeight (from litegraph) > computeLayoutSize > computeSize
? widgetInstance.computeSize(width)[1] let height = 20
: 20 if (widgetInstance.computedHeight) {
height = widgetInstance.computedHeight
} else if (widgetInstance.computeLayoutSize) {
height = widgetInstance.computeLayoutSize(node).minHeight
} else if (widgetInstance.computeSize) {
height = widgetInstance.computeSize(width)[1]
}
containerHeight.value = height
// Set node.canvasHeight for legacy widgets that use it (e.g., Impact Pack)
// @ts-expect-error canvasHeight is a custom property used by some extensions
node.canvasHeight = height
widgetInstance.y = 0 widgetInstance.y = 0
canvasEl.value.height = (height + 2) * scaleFactor canvasEl.value.height = (height + 2) * scaleFactor
canvasEl.value.width = width * scaleFactor canvasEl.value.width = width * scaleFactor
@@ -87,7 +98,10 @@ function handleMove(e: PointerEvent) {
} }
</script> </script>
<template> <template>
<div class="relative mx-[-12px] min-w-0 basis-0"> <div
class="relative mx-[-12px] min-w-0 basis-0"
:style="{ minHeight: `${containerHeight}px` }"
>
<canvas <canvas
ref="canvasEl" ref="canvasEl"
class="absolute mt-[-13px] w-full cursor-crosshair" class="absolute mt-[-13px] w-full cursor-crosshair"

View File

@@ -2,6 +2,7 @@ import { computed, toValue } from 'vue'
import type { MaybeRefOrGetter } from 'vue' import type { MaybeRefOrGetter } from 'vue'
interface NumberWidgetOptions { interface NumberWidgetOptions {
step?: number
step2?: number step2?: number
precision?: number precision?: number
} }
@@ -17,10 +18,17 @@ export function useNumberStepCalculation(
) { ) {
return computed(() => { return computed(() => {
const precision = toValue(precisionArg) const precision = toValue(precisionArg)
// Use step2 (correct input spec value) instead of step (legacy 10x value) // Use step2 (correct input spec value) if available
if (options?.step2 !== undefined) { if (options?.step2 !== undefined) {
return Number(options.step2) return Number(options.step2)
} }
// Use step / 10 for custom large step values (> 10) to match litegraph behavior
// This is important for extensions like Impact Pack that use custom step values (e.g., 640)
// We skip default step values (1, 10) to avoid affecting normal widgets
const step = options?.step
if (step !== undefined && step > 10) {
return Number(step) / 10
}
if (precision === undefined) { if (precision === undefined) {
return returnUndefinedForDefault ? undefined : 0 return returnUndefinedForDefault ? undefined : 0
@@ -29,7 +37,9 @@ export function useNumberStepCalculation(
if (precision === 0) return 1 if (precision === 0) return 1
// For precision > 0, step = 1 / (10^precision) // For precision > 0, step = 1 / (10^precision)
const step = 1 / Math.pow(10, precision) const calculatedStep = 1 / Math.pow(10, precision)
return returnUndefinedForDefault ? step : Number(step.toFixed(precision)) return returnUndefinedForDefault
? calculatedStep
: Number(calculatedStep.toFixed(precision))
}) })
} }