feat: implement automatic widget registration in setNodeId()

- setNodeId() now calls registerWidget() with all widget metadata

- Updated BaseWidget tests with 3 new tests for automatic registration

- Fixed IWidgetOptions type to use unknown instead of unknown[]

Amp-Thread-ID: https://ampcode.com/threads/T-019c2631-966c-73a0-a399-7fe85bd6d495
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-02-03 17:16:41 -08:00
parent 36387faaa8
commit f9db91dac5
3 changed files with 78 additions and 23 deletions

View File

@@ -71,16 +71,15 @@ describe('BaseWidget store integration', () => {
describe('metadata properties after registration', () => {
it('reads from store when registered', () => {
const widget = createTestWidget(node, { name: 'storeWidget' })
widget.setNodeId(1)
store.registerWidget(1, 'storeWidget', 'number', 42, {
const widget = createTestWidget(node, {
name: 'storeWidget',
label: 'Store Label',
hidden: true,
disabled: true,
advanced: true,
promoted: true
})
widget.setNodeId(1)
expect(widget.label).toBe('Store Label')
expect(widget.hidden).toBe(true)
@@ -93,8 +92,6 @@ describe('BaseWidget store integration', () => {
const widget = createTestWidget(node, { name: 'writeWidget' })
widget.setNodeId(1)
store.registerWidget(1, 'writeWidget', 'number', 42)
widget.label = 'Updated Label'
widget.hidden = true
widget.disabled = true
@@ -110,11 +107,9 @@ describe('BaseWidget store integration', () => {
})
it('syncs value with store', () => {
const widget = createTestWidget(node, { name: 'valueWidget' })
const widget = createTestWidget(node, { name: 'valueWidget', value: 42 })
widget.setNodeId(1)
store.registerWidget(1, 'valueWidget', 'number', 0)
widget.value = 99
expect(store.get(1, 'valueWidget')).toBe(99)
@@ -123,14 +118,61 @@ describe('BaseWidget store integration', () => {
})
})
describe('automatic registration via setNodeId', () => {
it('registers widget with all metadata', () => {
const widget = createTestWidget(node, {
name: 'autoRegWidget',
value: 100,
label: 'Auto Label',
hidden: true,
disabled: true,
advanced: true,
promoted: true
})
widget.setNodeId(1)
const state = store.getWidget(1, 'autoRegWidget')
expect(state).toBeDefined()
expect(state?.nodeId).toBe(1)
expect(state?.name).toBe('autoRegWidget')
expect(state?.type).toBe('number')
expect(state?.value).toBe(100)
expect(state?.label).toBe('Auto Label')
expect(state?.hidden).toBe(true)
expect(state?.disabled).toBe(true)
expect(state?.advanced).toBe(true)
expect(state?.promoted).toBe(true)
expect(state?.options).toEqual({ min: 0, max: 100 })
})
it('registers widget with default metadata values', () => {
const widget = createTestWidget(node, { name: 'defaultsWidget' })
widget.setNodeId(1)
const state = store.getWidget(1, 'defaultsWidget')
expect(state).toBeDefined()
expect(state?.hidden).toBe(false)
expect(state?.disabled).toBe(false)
expect(state?.advanced).toBe(false)
expect(state?.promoted).toBe(false)
expect(state?.label).toBeUndefined()
})
it('registers widget value in values map', () => {
const widget = createTestWidget(node, { name: 'valuesWidget', value: 77 })
widget.setNodeId(1)
expect(store.get(1, 'valuesWidget')).toBe(77)
})
})
describe('fallback behavior', () => {
it('falls back to internal value when widget not in store', () => {
it('uses internal value before registration', () => {
const widget = createTestWidget(node, {
name: 'fallbackWidget',
label: 'Internal'
})
widget.setNodeId(1)
// Widget not yet registered - should use internal value
expect(widget.label).toBe('Internal')
})
@@ -138,8 +180,6 @@ describe('BaseWidget store integration', () => {
const widget = createTestWidget(node)
widget.setNodeId(1)
store.registerWidget(1, 'testWidget', 'number', 42)
widget.hidden = undefined
widget.disabled = undefined

View File

@@ -11,7 +11,10 @@ import type {
} from '@/lib/litegraph/src/litegraph'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import type {
IBaseWidget,
TWidgetType
} from '@/lib/litegraph/src/types/widgets'
import { useWidgetValueStore } from '@/stores/widgetValueStore'
export interface DrawWidgetOptions {
@@ -198,15 +201,27 @@ export abstract class BaseWidget<
}
/**
* Associates this widget with a node ID and seeds the store with the current value.
* Once set, value reads/writes will be delegated to the WidgetValueStore.
* Associates this widget with a node ID and registers it in the WidgetValueStore.
* Once set, value reads/writes will be delegated to the store.
*/
setNodeId(nodeId: NodeId): void {
this._nodeId = nodeId
if (this._internalValue !== undefined) {
const store = useWidgetValueStore()
store.set(nodeId, this.name, this._internalValue)
}
const store = useWidgetValueStore()
store.registerWidget(
nodeId,
this.name,
this.type as TWidgetType,
this._internalValue,
{
label: this._label,
hidden: this._hidden,
disabled: this._disabled,
advanced: this._advanced,
promoted: this._promoted,
serialize: this.serialize,
widgetOptions: this.options
}
)
}
constructor(widget: TWidget & { node: LGraphNode })

View File

@@ -19,7 +19,7 @@ export interface WidgetState {
disabled: boolean
advanced: boolean
promoted: boolean
options: IWidgetOptions
options: IWidgetOptions<unknown>
serialize: boolean
}
@@ -67,7 +67,7 @@ export const useWidgetValueStore = defineStore('widgetValue', () => {
WidgetState,
'label' | 'hidden' | 'disabled' | 'advanced' | 'promoted' | 'serialize'
>
> & { widgetOptions?: IWidgetOptions } = {}
> & { widgetOptions?: IWidgetOptions<unknown> } = {}
): WidgetState {
const key = makeKey(nodeId, name)
const state: WidgetState = {