feat: add reset-to-default for widget parameters in right side panel (#8861)

## Summary

Add per-widget and reset-all-parameters functionality to the right side
panel, allowing users to quickly revert widget values to their defaults.

## Changes

- **What**: Per-widget "Reset to default" option in the WidgetActions
overflow menu, plus a "Reset all parameters" button in each
SectionWidgets header. Defaults are derived from the InputSpec (explicit
default, then type-specific fallbacks: 0 for INT/FLOAT, false for
BOOLEAN, empty string for STRING, first option for COMBO).
- **Dependencies**: Builds on #8594 (WidgetValueStore) for reactive UI
updates after reset.

## Review Focus

- `getWidgetDefaultValue` fallback logic in `src/utils/widgetUtil.ts` —
are the type-specific defaults appropriate?
- Deep equality check (`isEqual`) for disabling the reset button when
the value already matches the default.
- Event flow: WidgetActions emits `resetToDefault` → WidgetItem forwards
→ SectionWidgets handles via `writeWidgetValue` (sets value, triggers
callback, marks canvas dirty).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8861-feat-add-reset-to-default-for-widget-parameters-in-right-side-panel-3076d73d365081d1aa08d5b965a16cf4)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
This commit is contained in:
Christian Byrne
2026-02-15 00:55:04 -08:00
committed by GitHub
parent 066a1f1f11
commit d8d0dcbf71
7 changed files with 369 additions and 9 deletions

View File

@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { getWidgetDefaultValue } from '@/utils/widgetUtil'
describe('getWidgetDefaultValue', () => {
it('returns undefined for undefined spec', () => {
expect(getWidgetDefaultValue(undefined)).toBeUndefined()
})
it('returns explicit default when provided', () => {
const spec = { type: 'INT', default: 42 } as InputSpec
expect(getWidgetDefaultValue(spec)).toBe(42)
})
it('returns 0 for INT type without default', () => {
const spec = { type: 'INT' } as InputSpec
expect(getWidgetDefaultValue(spec)).toBe(0)
})
it('returns 0 for FLOAT type without default', () => {
const spec = { type: 'FLOAT' } as InputSpec
expect(getWidgetDefaultValue(spec)).toBe(0)
})
it('returns false for BOOLEAN type without default', () => {
const spec = { type: 'BOOLEAN' } as InputSpec
expect(getWidgetDefaultValue(spec)).toBe(false)
})
it('returns empty string for STRING type without default', () => {
const spec = { type: 'STRING' } as InputSpec
expect(getWidgetDefaultValue(spec)).toBe('')
})
it('returns first option for array options without default', () => {
const spec = { type: 'COMBO', options: ['a', 'b', 'c'] } as InputSpec
expect(getWidgetDefaultValue(spec)).toBe('a')
})
it('returns undefined for unknown type without options', () => {
const spec = { type: 'CUSTOM' } as InputSpec
expect(getWidgetDefaultValue(spec)).toBeUndefined()
})
})

View File

@@ -2,6 +2,32 @@ import { isProxyWidget } from '@/core/graph/subgraph/proxyWidget'
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
export type WidgetValue = boolean | number | string | object | undefined
export function getWidgetDefaultValue(
spec: InputSpec | undefined
): WidgetValue {
if (!spec) return undefined
if (spec.default !== undefined) return spec.default as WidgetValue
switch (spec.type) {
case 'INT':
case 'FLOAT':
return 0
case 'BOOLEAN':
return false
case 'STRING':
return ''
default:
if (Array.isArray(spec.options) && spec.options.length > 0) {
return spec.options[0] as WidgetValue
}
return undefined
}
}
/**
* Renames a widget and its corresponding input.