fix(inspector): sync widget value changes and improve callback handling

This commit is contained in:
Mel Massadian
2026-01-18 20:53:21 +01:00
parent ea6d5d4e59
commit 1accc920ce
4 changed files with 91 additions and 18 deletions
+8 -6
View File
@@ -1,6 +1,8 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false
}
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"plugins": ["prettier-plugin-svelte"]
}
+6 -4
View File
@@ -6,7 +6,7 @@
let {
item = { type: 'NUMBER' },
onInput = (e: Event) => console.log(e),
onInput = (e: Event, val?: unknown) => console.log(e, val),
id = undefined,
value = $bindable(''),
} = $props()
@@ -59,14 +59,16 @@
/>
{/if}
{:else if item.type === 'BOOLEAN'}
{@const isChecked = value === true || value === 'true' || value === 1 || value === '1'}
{@const isChecked = value === true || value === 'true' || value === 1 || value === '1' || value === 'on'}
<label class="mtb-toggle">
<input
type="checkbox"
checked={isChecked}
onchange={(e) => {
value = e.currentTarget.checked
onInput(e)
// ComfyUI toggles use booleans - 'off' string is truthy so doesn't work!
const newValue = e.currentTarget.checked
value = newValue
onInput(e, newValue)
}}
/>
<span class="mtb-toggle-slider"></span>
+23 -8
View File
@@ -31,7 +31,18 @@
} = $props()
let actions = $state<Record<string, Action>>({})
let value = $state<unknown>(item.value ?? item.widgets?.[0]?.value ?? '')
// Derive value from widget - updates when widget value changes
const getWidgetValue = () => item.widgets?.[0]?.value ?? item.value ?? ''
let value = $state<unknown>(getWidgetValue())
// Sync value from widget when it changes (Node → Panel)
$effect(() => {
const widgetVal = getWidgetValue()
if (widgetVal !== value) {
value = widgetVal
}
})
onMount(() => {
actions = {
@@ -68,14 +79,18 @@
})
const onInput = (e: Event | null, val?: unknown) => {
if (!inComfy()) {
console.log(`Not in comfy ${(e?.target as HTMLInputElement)?.value}`)
return
}
if (!inComfy()) return
if (item.widgets) {
for (let i = 0; i < item.widgets.length; i++) {
const w = item.widgets[i]
w.value = val ?? (e?.target as HTMLInputElement)?.value
for (const w of item.widgets as Array<{
value: unknown
callback?: (value: unknown) => void
options?: { callback?: (value: unknown) => void }
}>) {
const newVal = val ?? (e?.target as HTMLInputElement)?.value
w.value = newVal
// Trigger widget callback to notify ComfyUI of the change
w.callback?.(newVal)
w.options?.callback?.(newVal)
}
// @ts-expect-error - app is global in ComfyUI
window.app.canvas.setDirty(true)
+54
View File
@@ -11,6 +11,9 @@ import { API_INPUT_TYPES, type APIInputType, type APINodeSettings, type MTBNode
import { notifyAPIChanged } from './panel.svelte'
import CSS from './api_nodes.css?inline'
/** Symbol to store original callbacks on widgets */
const ORIGINAL_CALLBACK = Symbol('mtb_original_callback')
declare const LiteGraph: {
NODE_TITLE_HEIGHT: number
NODE_COLLAPSED_RADIUS: number
@@ -20,6 +23,51 @@ declare const LiteGraph: {
* Manages the creation and lifecycle of API settings widgets on nodes
*/
export class APISettingsWidgetManager {
/**
* Wraps widget callbacks to notify panel of value changes
*/
private wrapWidgetCallbacks(node: MTBNode): void {
if (!node.widgets) return
for (const widget of node.widgets) {
if (widget.name === 'apiSettings') continue
const w = widget as IWidget & {
callback?: (...args: unknown[]) => void
[ORIGINAL_CALLBACK]?: (...args: unknown[]) => void
}
// Skip if already wrapped
if (w[ORIGINAL_CALLBACK]) continue
// Store original and wrap
w[ORIGINAL_CALLBACK] = w.callback
w.callback = (...args: unknown[]) => {
w[ORIGINAL_CALLBACK]?.(...args)
notifyAPIChanged()
}
}
}
/**
* Restores original widget callbacks
*/
private unwrapWidgetCallbacks(node: MTBNode): void {
if (!node.widgets) return
for (const widget of node.widgets) {
const w = widget as IWidget & {
callback?: (...args: unknown[]) => void
[ORIGINAL_CALLBACK]?: (...args: unknown[]) => void
}
if (w[ORIGINAL_CALLBACK]) {
w.callback = w[ORIGINAL_CALLBACK]
delete w[ORIGINAL_CALLBACK]
}
}
}
/**
* Creates the API settings DOM widget for a node
*/
@@ -57,6 +105,9 @@ export class APISettingsWidgetManager {
hideOnZoom: false,
getHeight: () => element.children.length * 80,
})
// Wrap widget callbacks to sync Node → Panel
this.wrapWidgetCallbacks(node)
}
}
@@ -118,6 +169,9 @@ export class APISettingsWidgetManager {
;(apiSettings as IWidget & { onRemoved?: () => void }).onRemoved?.()
;(apiSettings as IWidget & { onRemove?: () => void }).onRemove?.()
node.widgets = node.widgets?.filter((w) => w.name !== 'apiSettings')
// Restore original widget callbacks
this.unwrapWidgetCallbacks(node)
}
}