Skip to content

Commit b713409

Browse files
DrJKLampagent
andcommitted
fix: preserve null widget values across store, live widget, and callback
Default IBaseWidget TValue to the canonical WidgetValue so a widget value may be null. The Vue update handler now assigns the same normalized value to the store, live widget .value, and callback instead of coercing .value to undefined. Coerce null/void to undefined only at the persisted NodeProperty boundary. Amp-Thread-ID: https://ampcode.com/threads/T-019f2a75-8d64-7605-980d-7aca51a5f19c Co-authored-by: Amp <amp@ampcode.com>
1 parent 32b3c28 commit b713409

7 files changed

Lines changed: 21 additions & 16 deletions

File tree

src/extensions/core/load3d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,9 +635,10 @@ useExtensionService().registerExtension({
635635
const extrinsics = result?.[3]
636636
const intrinsics = result?.[4]
637637

638-
modelWidget.value = filePath?.replaceAll('\\', '/')
638+
const modelFilePath = filePath?.replaceAll('\\', '/')
639+
modelWidget.value = modelFilePath
639640

640-
node.properties['Last Time Model File'] = modelWidget.value
641+
node.properties['Last Time Model File'] = modelFilePath
641642

642643
const settings = {
643644
loadFolder: 'output',

src/extensions/core/widgetInputs.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import type {
77
LLink
88
} from '@/lib/litegraph/src/litegraph'
99
import { NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
10-
import type {
11-
IBaseWidget,
12-
TWidgetValue
13-
} from '@/lib/litegraph/src/types/widgets'
10+
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
1411
import { assetService } from '@/platform/assets/services/assetService'
1512
import { createAssetWidget } from '@/platform/assets/utils/createAssetWidget'
1613
import type { ComfyNodeDef, InputSpec } from '@/schemas/nodeDefSchema'
1714
import { app } from '@/scripts/app'
15+
import type { WidgetValue } from '@/types/simplifiedWidget'
1816
import {
1917
ComfyWidgets,
2018
addValueControlWidgets,
@@ -29,7 +27,7 @@ import { applyFirstWidgetValueToGraph } from './widgetValuePropagation'
2927

3028
const replacePropertyName = 'Run widget replace on values'
3129
export class PrimitiveNode extends LGraphNode {
32-
controlValues?: TWidgetValue[]
30+
controlValues?: WidgetValue[]
3331
lastType?: string
3432
static override category: string
3533
constructor(title: string) {

src/extensions/core/widgetValuePropagation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import type { Point } from '@/lib/litegraph/src/interfaces'
22
import type { LLink } from '@/lib/litegraph/src/litegraph'
33
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
44
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
5-
import type { TWidgetValue } from '@/lib/litegraph/src/types/widgets'
65
import { app } from '@/scripts/app'
6+
import type { WidgetValue } from '@/types/simplifiedWidget'
77

88
type SourceNode = Pick<LGraphNode, 'graph' | 'outputs' | 'widgets'>
99

1010
export function applyFirstWidgetValueToGraph(
1111
node: SourceNode,
1212
extraLinks: LLink[] = [],
13-
transformValue?: (value: TWidgetValue) => TWidgetValue
13+
transformValue?: (value: WidgetValue) => WidgetValue
1414
) {
1515
const output = node.outputs[0]
1616
if (!output?.links?.length || !node.graph) return

src/lib/litegraph/src/LGraphNode.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import type {
9898
TWidgetType,
9999
TWidgetValue
100100
} from './types/widgets'
101+
import type { WidgetValue } from '@/types/simplifiedWidget'
101102
import { findFreeSlotOfType } from './utils/collections'
102103
import { warnDeprecated } from './utils/feedback'
103104
import { getWidgetIds } from './utils/widget'
@@ -1066,14 +1067,17 @@ export class LGraphNode
10661067
* @param name
10671068
* @param value
10681069
*/
1069-
setProperty(name: string, value: TWidgetValue): void {
1070+
setProperty(name: string, value: WidgetValue): void {
10701071
this.properties ||= {}
1071-
if (value === this.properties[name]) return
1072+
// Node properties are persisted and cannot hold null/void, so normalize
1073+
// before comparing and storing.
1074+
const nextValue = value ?? undefined
1075+
if (nextValue === this.properties[name]) return
10721076

10731077
const prev_value = this.properties[name]
1074-
this.properties[name] = value
1078+
this.properties[name] = nextValue
10751079
// abort change
1076-
if (this.onPropertyChanged?.(name, value, prev_value) === false)
1080+
if (this.onPropertyChanged?.(name, nextValue, prev_value) === false)
10771081
this.properties[name] = prev_value
10781082

10791083
if (this.widgets) {

src/lib/litegraph/src/types/widgets.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Bounds } from '@/renderer/core/layout/types'
22
import type { CurveData } from '@/components/curve/types'
33
import type { BoundingBox } from '@/types/boundingBoxes'
44
import type { NodeId } from '@/types/nodeId'
5+
import type { WidgetValue } from '@/types/simplifiedWidget'
56
import type { WidgetId } from '@/types/widgetId'
67

78
import type {
@@ -411,7 +412,7 @@ export function isWidgetValue(value: unknown): value is TWidgetValue {
411412
* @see IWidget
412413
*/
413414
export interface IBaseWidget<
414-
TValue = boolean | number | string | object | undefined,
415+
TValue = WidgetValue,
415416
TType extends string = string,
416417
TOptions extends IWidgetOptions = IWidgetOptions
417418
> {

src/renderer/extensions/vueNodes/composables/useProcessedWidgets.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ describe('live widget update handler', () => {
483483
setActivePinia(createTestingPinia({ stubActions: false }))
484484
})
485485

486-
it('forwards null (not undefined) to the live widget callback', () => {
486+
it('forwards null (not undefined) to both the live widget value and callback', () => {
487487
const callback = vi.fn()
488488
const id = widgetId(GRAPH_ID, toNodeId(1), 'test_widget')
489489
const liveWidget = createMockWidget({
@@ -506,6 +506,7 @@ describe('live widget update handler', () => {
506506

507507
processed.updateHandler(null)
508508

509+
expect(liveWidget.value).toBeNull()
509510
expect(callback).toHaveBeenCalledTimes(1)
510511
expect(callback.mock.calls[0][0]).toBeNull()
511512
})

src/renderer/extensions/vueNodes/composables/useProcessedWidgets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ function createWidgetUpdateHandler({
241241
widgetValueStore.setValue(id, newValue)
242242
if (live) {
243243
const normalized = normalizeWidgetValue(newValue)
244-
live.widget.value = normalized ?? undefined
244+
live.widget.value = normalized
245245
live.widget.callback?.(normalized, app.canvas, live.node)
246246
live.node.widgets?.forEach((w) => w.triggerDraw?.())
247247
}

0 commit comments

Comments
 (0)