feat: add Edit Expression context menu and / key toggle for number widgets

Add getContextMenuOptions to NumberWidget for right-click "Edit Expression"
that opens the prompt in text mode, allowing math expressions like 2+3.
Add / key handler in prompt dialog to switch from number to text mode.
Wire up widget context menu items in LGraphCanvas processContextMenu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dante01yoon
2026-03-03 17:07:29 +09:00
parent 1b879a5ac2
commit c749a077fc
4 changed files with 82 additions and 0 deletions

View File

@@ -6973,6 +6973,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
if (e.key == 'Escape') {
// ESC
dialog.close()
} else if (e.key === '/' && this.type === 'number') {
// Switch to text mode for expression editing
this.type = 'text'
this.removeAttribute('min')
this.removeAttribute('max')
this.removeAttribute('step')
} else if (
e.key == 'Enter' &&
(e.target as Element).localName != 'textarea'
@@ -8511,6 +8517,26 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
} else {
// on node
menu_info = this.getNodeMenuOptions(node)
const widget = node.getWidgetOnPos(event.canvasX, event.canvasY)
if (
widget &&
'getContextMenuOptions' in widget &&
typeof widget.getContextMenuOptions === 'function'
) {
const widgetMenuItems = (
widget as {
getContextMenuOptions: (opts: {
e: CanvasPointerEvent
node: LGraphNode
canvas: LGraphCanvas
}) => IContextMenuValue[]
}
).getContextMenuOptions({ e: event, node, canvas: this })
if (widgetMenuItems.length) {
menu_info.unshift(...widgetMenuItems, null)
}
}
}
} else {
menu_info = this.getCanvasMenuOptions()

View File

@@ -149,4 +149,32 @@ describe(NumberWidget, () => {
{ inputType: 'number', min: -1000, max: 1000, step: 1 }
)
})
it('getContextMenuOptions returns Edit Expression with text inputType', () => {
const canvas = createMockCanvas()
const e = createMockEvent(100)
const widget = createNumberWidget(node)
const options = widget.getContextMenuOptions({ e, node, canvas })
expect(options).toHaveLength(1)
expect(options[0].content).toBe('Edit Expression')
})
it('getContextMenuOptions callback opens prompt in text mode', () => {
const canvas = createMockCanvas()
const e = createMockEvent(100)
const widget = createNumberWidget(node)
const options = widget.getContextMenuOptions({ e, node, canvas })
void options[0].callback!.call({} as never)
expect(canvas.prompt).toHaveBeenCalledWith(
'Value',
50,
expect.any(Function),
e,
{ inputType: 'text' }
)
})
})

View File

@@ -1,3 +1,5 @@
import { t } from '@/i18n'
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
import type { INumericWidget } from '@/lib/litegraph/src/types/widgets'
import { evaluateInput, getWidgetStep } from '@/lib/litegraph/src/utils/widget'
@@ -95,6 +97,31 @@ export class NumberWidget
)
}
getContextMenuOptions({
e,
node,
canvas
}: WidgetEventOptions): IContextMenuValue[] {
return [
{
content: t('widgets.editExpression'),
callback: () => {
canvas.prompt(
'Value',
this.value,
(v: string) => {
const parsed = evaluateInput(v)
if (parsed !== undefined)
this.setValue(parsed, { e, node, canvas })
},
e,
{ inputType: 'text' }
)
}
}
]
}
override onDrag({ e, node, canvas }: WidgetEventOptions) {
const width = this.width || node.width
const x = e.canvasX - node.pos[0]

View File

@@ -2535,6 +2535,7 @@
"true": "true",
"false": "false"
},
"editExpression": "Edit Expression",
"node2only": "Node 2.0 only",
"selectModel": "Select model",
"uploadSelect": {