mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-09 09:30:06 +00:00
refactor(litegraph): replace type assertions with proper type guards
Amp-Thread-ID: https://ampcode.com/threads/T-019babbe-2ab8-7426-aa86-bba47c1ff997 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -120,7 +120,6 @@ app.registerExtension({
|
||||
touchZooming = true
|
||||
|
||||
LiteGraph.closeAllContextMenus(window)
|
||||
// @ts-expect-error
|
||||
app.canvas.search_box?.close()
|
||||
const newTouchDist = getMultiTouchPos(e)
|
||||
|
||||
|
||||
@@ -45,8 +45,7 @@ export class CurveEditor {
|
||||
draw(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
size: Rect,
|
||||
// @ts-expect-error - LGraphCanvas parameter type needs fixing
|
||||
graphcanvas?: LGraphCanvas,
|
||||
_graphcanvas?: LGraphCanvas,
|
||||
background_color?: string,
|
||||
line_color?: string,
|
||||
inactive = false
|
||||
|
||||
@@ -39,11 +39,12 @@ describe('LGraph', () => {
|
||||
expect(result1).toEqual(result2)
|
||||
})
|
||||
test('can be instantiated', ({ expect }) => {
|
||||
// @ts-expect-error Intentional - extra holds any / all consumer data that should be serialised
|
||||
const graph = new LGraph({ extra: 'TestGraph' })
|
||||
// extra holds any / all consumer data that should be serialised
|
||||
const graph = new LGraph({
|
||||
extra: 'TestGraph'
|
||||
} as unknown as ConstructorParameters<typeof LGraph>[0])
|
||||
expect(graph).toBeInstanceOf(LGraph)
|
||||
expect(graph.extra).toBe('TestGraph')
|
||||
expect(graph.extra).toBe('TestGraph')
|
||||
})
|
||||
|
||||
test('is exactly the same type', async ({ expect }) => {
|
||||
@@ -211,12 +212,13 @@ describe('Graph Clearing and Callbacks', () => {
|
||||
|
||||
describe('Legacy LGraph Compatibility Layer', () => {
|
||||
test('can be extended via prototype', ({ expect, minimalGraph }) => {
|
||||
// @ts-expect-error Should always be an error.
|
||||
LGraph.prototype.newMethod = function () {
|
||||
return 'New method added via prototype'
|
||||
}
|
||||
// @ts-expect-error Should always be an error.
|
||||
expect(minimalGraph.newMethod()).toBe('New method added via prototype')
|
||||
;(LGraph.prototype as unknown as Record<string, unknown>).newMethod =
|
||||
function () {
|
||||
return 'New method added via prototype'
|
||||
}
|
||||
expect(
|
||||
(minimalGraph as unknown as Record<string, () => string>).newMethod()
|
||||
).toBe('New method added via prototype')
|
||||
})
|
||||
|
||||
test('is correctly assigned to LiteGraph', ({ expect }) => {
|
||||
|
||||
@@ -197,7 +197,7 @@ export class LGraph
|
||||
last_update_time: number = 0
|
||||
starttime: number = 0
|
||||
catch_errors: boolean = true
|
||||
execution_timer_id?: number | null
|
||||
execution_timer_id?: ReturnType<typeof setInterval> | number | null
|
||||
errors_in_execution?: boolean
|
||||
/** @deprecated Unused */
|
||||
execution_time!: number
|
||||
@@ -206,9 +206,12 @@ export class LGraph
|
||||
/** Must contain serialisable values, e.g. primitive types */
|
||||
config: LGraphConfig = {}
|
||||
vars: Dictionary<unknown> = {}
|
||||
nodes_executing: boolean[] = []
|
||||
nodes_actioning: (string | boolean)[] = []
|
||||
nodes_executedAction: string[] = []
|
||||
/** @deprecated Use a Map or dedicated state management instead */
|
||||
nodes_executing: Record<NodeId, boolean> = {}
|
||||
/** @deprecated Use a Map or dedicated state management instead */
|
||||
nodes_actioning: Record<NodeId, string | boolean> = {}
|
||||
/** @deprecated Use a Map or dedicated state management instead */
|
||||
nodes_executedAction: Record<NodeId, string> = {}
|
||||
extra: LGraphExtra = {}
|
||||
|
||||
/** @deprecated Deserialising a workflow sets this unused property. */
|
||||
@@ -287,9 +290,6 @@ export class LGraph
|
||||
node: LGraphNode
|
||||
): void
|
||||
|
||||
// @ts-expect-error - Private property type needs fixing
|
||||
private _input_nodes?: LGraphNode[]
|
||||
|
||||
/**
|
||||
* See {@link LGraph}
|
||||
* @param o data from previous serialization [optional]
|
||||
@@ -374,9 +374,9 @@ export class LGraph
|
||||
|
||||
this.catch_errors = true
|
||||
|
||||
this.nodes_executing = []
|
||||
this.nodes_actioning = []
|
||||
this.nodes_executedAction = []
|
||||
this.nodes_executing = {}
|
||||
this.nodes_actioning = {}
|
||||
this.nodes_executedAction = {}
|
||||
|
||||
// notify canvas to redraw
|
||||
this.change()
|
||||
@@ -465,7 +465,6 @@ export class LGraph
|
||||
on_frame()
|
||||
} else {
|
||||
// execute every 'interval' ms
|
||||
// @ts-expect-error - Timer ID type mismatch needs fixing
|
||||
this.execution_timer_id = setInterval(() => {
|
||||
// execute
|
||||
this.onBeforeStep?.()
|
||||
@@ -565,9 +564,9 @@ export class LGraph
|
||||
this.iteration += 1
|
||||
this.elapsed_time = (now - this.last_update_time) * 0.001
|
||||
this.last_update_time = now
|
||||
this.nodes_executing = []
|
||||
this.nodes_actioning = []
|
||||
this.nodes_executedAction = []
|
||||
this.nodes_executing = {}
|
||||
this.nodes_actioning = {}
|
||||
this.nodes_executedAction = {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -702,12 +701,13 @@ export class LGraph
|
||||
|
||||
// sort now by priority
|
||||
L.sort(function (A, B) {
|
||||
// @ts-expect-error ctor props
|
||||
const Ap = A.constructor.priority || A.priority || 0
|
||||
// @ts-expect-error ctor props
|
||||
const Bp = B.constructor.priority || B.priority || 0
|
||||
const ctorA = A.constructor as { priority?: number }
|
||||
const ctorB = B.constructor as { priority?: number }
|
||||
const nodeA = A as unknown as { priority?: number }
|
||||
const nodeB = B as unknown as { priority?: number }
|
||||
const Ap = ctorA.priority || nodeA.priority || 0
|
||||
const Bp = ctorB.priority || nodeB.priority || 0
|
||||
// if same priority, sort by order
|
||||
|
||||
return Ap == Bp ? A.order - B.order : Ap - Bp
|
||||
})
|
||||
|
||||
@@ -798,18 +798,18 @@ export class LGraph
|
||||
if (!nodes) return
|
||||
|
||||
for (const node of nodes) {
|
||||
// @ts-expect-error deprecated
|
||||
if (!node[eventname] || node.mode != mode) continue
|
||||
const nodeRecord = node as unknown as Record<
|
||||
string,
|
||||
((...args: unknown[]) => void) | undefined
|
||||
>
|
||||
const handler = nodeRecord[eventname]
|
||||
if (!handler || node.mode != mode) continue
|
||||
if (params === undefined) {
|
||||
// @ts-expect-error deprecated
|
||||
node[eventname]()
|
||||
handler.call(node)
|
||||
} else if (params && params.constructor === Array) {
|
||||
// @ts-expect-error deprecated
|
||||
// eslint-disable-next-line prefer-spread
|
||||
node[eventname].apply(node, params)
|
||||
handler.apply(node, params)
|
||||
} else {
|
||||
// @ts-expect-error deprecated
|
||||
node[eventname](params)
|
||||
handler.call(node, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1221,20 +1221,24 @@ export class LGraph
|
||||
}
|
||||
|
||||
/** @todo Clean up - never implemented. */
|
||||
triggerInput(name: string, value: any): void {
|
||||
triggerInput(name: string, value: unknown): void {
|
||||
const nodes = this.findNodesByTitle(name)
|
||||
for (const node of nodes) {
|
||||
// @ts-expect-error - onTrigger method may not exist on all node types
|
||||
node.onTrigger(value)
|
||||
const nodeWithTrigger = node as LGraphNode & {
|
||||
onTrigger?: (value: unknown) => void
|
||||
}
|
||||
nodeWithTrigger.onTrigger?.(value)
|
||||
}
|
||||
}
|
||||
|
||||
/** @todo Clean up - never implemented. */
|
||||
setCallback(name: string, func: any): void {
|
||||
setCallback(name: string, func: unknown): void {
|
||||
const nodes = this.findNodesByTitle(name)
|
||||
for (const node of nodes) {
|
||||
// @ts-expect-error - setTrigger method may not exist on all node types
|
||||
node.setTrigger(func)
|
||||
const nodeWithTrigger = node as LGraphNode & {
|
||||
setTrigger?: (func: unknown) => void
|
||||
}
|
||||
nodeWithTrigger.setTrigger?.(func)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2136,8 +2140,12 @@ export class LGraph
|
||||
|
||||
const nodeList =
|
||||
!LiteGraph.use_uuids && options?.sortNodes
|
||||
? // @ts-expect-error If LiteGraph.use_uuids is false, ids are numbers.
|
||||
[...this._nodes].sort((a, b) => a.id - b.id)
|
||||
? [...this._nodes].sort((a, b) => {
|
||||
if (typeof a.id === 'number' && typeof b.id === 'number') {
|
||||
return a.id - b.id
|
||||
}
|
||||
return 0
|
||||
})
|
||||
: this._nodes
|
||||
|
||||
const nodes = nodeList.map((node) => node.serialize())
|
||||
@@ -2158,7 +2166,8 @@ export class LGraph
|
||||
if (LiteGraph.saveViewportWithGraph) extra.ds = this.#getDragAndScale()
|
||||
if (!extra.ds) delete extra.ds
|
||||
|
||||
const data: ReturnType<typeof this.asSerialisable> = {
|
||||
const data: SerialisableGraph &
|
||||
Required<Pick<SerialisableGraph, 'nodes' | 'groups' | 'extra'>> = {
|
||||
id,
|
||||
revision,
|
||||
version: LGraph.serialisedSchemaVersion,
|
||||
@@ -2289,12 +2298,12 @@ export class LGraph
|
||||
|
||||
const nodesData = data.nodes
|
||||
|
||||
// copy all stored fields
|
||||
for (const i in data) {
|
||||
// copy all stored fields (legacy property assignment)
|
||||
const thisRecord = this as unknown as Record<string, unknown>
|
||||
const dataRecord = data as unknown as Record<string, unknown>
|
||||
for (const i in dataRecord) {
|
||||
if (LGraph.ConfigureProperties.has(i)) continue
|
||||
|
||||
// @ts-expect-error #574 Legacy property assignment
|
||||
this[i] = data[i]
|
||||
thisRecord[i] = dataRecord[i]
|
||||
}
|
||||
|
||||
// Subgraph definitions
|
||||
|
||||
@@ -104,10 +104,10 @@ import { BaseWidget } from './widgets/BaseWidget'
|
||||
import { toConcreteWidget } from './widgets/widgetMap'
|
||||
|
||||
interface IShowSearchOptions {
|
||||
node_to?: LGraphNode | null
|
||||
node_from?: LGraphNode | null
|
||||
slot_from: number | INodeOutputSlot | INodeInputSlot | null | undefined
|
||||
type_filter_in?: ISlotType
|
||||
node_to?: SubgraphOutputNode | LGraphNode | null
|
||||
node_from?: SubgraphInputNode | LGraphNode | null
|
||||
slot_from?: number | INodeOutputSlot | INodeInputSlot | SubgraphIO | null
|
||||
type_filter_in?: ISlotType | false
|
||||
type_filter_out?: ISlotType | false
|
||||
|
||||
// TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out
|
||||
@@ -161,6 +161,15 @@ interface ICloseable {
|
||||
close(): void
|
||||
}
|
||||
|
||||
interface IPanel extends Element, ICloseable {
|
||||
node?: LGraphNode
|
||||
graph?: LGraph
|
||||
}
|
||||
|
||||
function isPanel(el: Element): el is IPanel {
|
||||
return 'close' in el && typeof el.close === 'function'
|
||||
}
|
||||
|
||||
interface IDialogExtensions extends ICloseable {
|
||||
modified(): void
|
||||
is_modified: boolean
|
||||
@@ -688,7 +697,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
bg_tint?: string | CanvasGradient | CanvasPattern
|
||||
// TODO: This looks like another panel thing
|
||||
prompt_box?: PromptDialog | null
|
||||
search_box?: HTMLDivElement
|
||||
search_box?: HTMLDivElement & ICloseable
|
||||
/** @deprecated Panels */
|
||||
SELECTED_NODE?: LGraphNode
|
||||
/** @deprecated Panels */
|
||||
@@ -728,7 +737,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
/** called when rendering a tooltip */
|
||||
onDrawLinkTooltip?: (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
link: LLink | null,
|
||||
link: LinkSegment | null,
|
||||
canvas?: LGraphCanvas
|
||||
) => boolean
|
||||
|
||||
@@ -1470,8 +1479,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
} else if (item.type == 'Boolean') {
|
||||
value = Boolean(value)
|
||||
}
|
||||
// @ts-expect-error Requires refactor.
|
||||
node[property] = value
|
||||
// Dynamic property assignment for user-defined node properties
|
||||
;(node as unknown as Record<string, NodeProperty>)[property] = value
|
||||
dialog.remove()
|
||||
canvas.setDirty(true, true)
|
||||
}
|
||||
@@ -1489,10 +1498,9 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
|
||||
if (typeof values === 'object') {
|
||||
let desc_value = ''
|
||||
for (const k in values) {
|
||||
// @ts-expect-error deprecated #578
|
||||
if (values[k] != value) continue
|
||||
|
||||
const valuesRecord = values as Record<string, unknown>
|
||||
for (const k in valuesRecord) {
|
||||
if (valuesRecord[k] != value) continue
|
||||
desc_value = k
|
||||
break
|
||||
}
|
||||
@@ -2015,8 +2023,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
if (!this.canvas) return window
|
||||
|
||||
const doc = this.canvas.ownerDocument
|
||||
// @ts-expect-error Check if required
|
||||
return doc.defaultView || doc.parentWindow
|
||||
// parentWindow is an IE-specific fallback, no longer relevant
|
||||
return doc.defaultView ?? window
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3654,8 +3662,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
if (!graph) return
|
||||
|
||||
let block_default = false
|
||||
// @ts-expect-error EventTarget.localName is not in standard types
|
||||
if (e.target.localName == 'input') return
|
||||
const targetEl = e.target
|
||||
if (targetEl instanceof HTMLInputElement) return
|
||||
|
||||
if (e.type == 'keydown') {
|
||||
// TODO: Switch
|
||||
@@ -3692,8 +3700,10 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
this.pasteFromClipboard({ connectInputs: e.shiftKey })
|
||||
} else if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
// delete or backspace
|
||||
// @ts-expect-error EventTarget.localName is not in standard types
|
||||
if (e.target.localName != 'input' && e.target.localName != 'textarea') {
|
||||
if (
|
||||
!(targetEl instanceof HTMLInputElement) &&
|
||||
!(targetEl instanceof HTMLTextAreaElement)
|
||||
) {
|
||||
if (this.selectedItems.size === 0) {
|
||||
this.#noItemsSelected()
|
||||
return
|
||||
@@ -4680,9 +4690,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
|
||||
const { ctx, canvas, graph, linkConnector } = this
|
||||
|
||||
// @ts-expect-error start2D method not in standard CanvasRenderingContext2D
|
||||
if (ctx.start2D && !this.viewport) {
|
||||
// @ts-expect-error start2D method not in standard CanvasRenderingContext2D
|
||||
// start2D is a non-standard method (e.g., GL-backed canvas libraries)
|
||||
if (
|
||||
'start2D' in ctx &&
|
||||
typeof ctx.start2D === 'function' &&
|
||||
!this.viewport
|
||||
) {
|
||||
ctx.start2D()
|
||||
ctx.restore()
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
@@ -5355,11 +5368,9 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
}
|
||||
ctx.fill()
|
||||
|
||||
// @ts-expect-error TODO: Better value typing
|
||||
const { data } = link
|
||||
if (data == null) return
|
||||
|
||||
// @ts-expect-error TODO: Better value typing
|
||||
if (this.onDrawLinkTooltip?.(ctx, link, this) == true) return
|
||||
|
||||
let text: string | null = null
|
||||
@@ -6635,17 +6646,13 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
case 'Search':
|
||||
if (isFrom) {
|
||||
opts.showSearchBox(e, {
|
||||
// @ts-expect-error - Subgraph types
|
||||
node_from: opts.nodeFrom,
|
||||
// @ts-expect-error - Subgraph types
|
||||
slot_from: slotX,
|
||||
type_filter_in: fromSlotType
|
||||
})
|
||||
} else {
|
||||
opts.showSearchBox(e, {
|
||||
// @ts-expect-error - Subgraph types
|
||||
node_to: opts.nodeTo,
|
||||
// @ts-expect-error - Subgraph types
|
||||
slot_from: slotX,
|
||||
type_filter_out: fromSlotType
|
||||
})
|
||||
@@ -6829,9 +6836,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
do_type_filter: LiteGraph.search_filter_enabled,
|
||||
|
||||
// these are default: pass to set initially set values
|
||||
// @ts-expect-error Property missing from interface definition
|
||||
type_filter_in: false,
|
||||
|
||||
type_filter_out: false,
|
||||
show_general_if_none_on_typefilter: true,
|
||||
show_general_after_typefiltered: true,
|
||||
@@ -6939,7 +6944,6 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error Panel?
|
||||
that.search_box?.close()
|
||||
that.search_box = dialog
|
||||
|
||||
@@ -7006,7 +7010,6 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
opt.innerHTML = aSlots[iK]
|
||||
selIn.append(opt)
|
||||
if (
|
||||
// @ts-expect-error Property missing from interface definition
|
||||
options.type_filter_in !== false &&
|
||||
String(options.type_filter_in).toLowerCase() ==
|
||||
String(aSlots[iK]).toLowerCase()
|
||||
@@ -7051,14 +7054,16 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
|
||||
// Handles cases where the searchbox is initiated by
|
||||
// non-click events. e.g. Keyboard shortcuts
|
||||
const defaultY = rect.top + rect.height * 0.5
|
||||
const safeEvent =
|
||||
event ??
|
||||
new MouseEvent('click', {
|
||||
clientX: rect.left + rect.width * 0.5,
|
||||
clientY: rect.top + rect.height * 0.5,
|
||||
// @ts-expect-error layerY is a nonstandard property
|
||||
layerY: rect.top + rect.height * 0.5
|
||||
})
|
||||
Object.assign(
|
||||
new MouseEvent('click', {
|
||||
clientX: rect.left + rect.width * 0.5,
|
||||
clientY: defaultY
|
||||
}),
|
||||
{ layerY: defaultY } // layerY is a nonstandard property used below
|
||||
)
|
||||
|
||||
const left = safeEvent.clientX - 80
|
||||
const top = safeEvent.clientY - 20
|
||||
@@ -7089,27 +7094,32 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
}
|
||||
|
||||
// join node after inserting
|
||||
if (options.node_from) {
|
||||
// These code paths only work with LGraphNode instances (not SubgraphIO nodes)
|
||||
if (options.node_from && options.node_from instanceof LGraphNode) {
|
||||
const nodeFrom = options.node_from
|
||||
// FIXME: any
|
||||
let iS: any = false
|
||||
switch (typeof options.slot_from) {
|
||||
case 'string':
|
||||
iS = options.node_from.findOutputSlot(options.slot_from)
|
||||
iS = nodeFrom.findOutputSlot(options.slot_from)
|
||||
break
|
||||
case 'object':
|
||||
case 'object': {
|
||||
if (options.slot_from == null)
|
||||
throw new TypeError(
|
||||
'options.slot_from was null when showing search box'
|
||||
)
|
||||
|
||||
iS = options.slot_from.name
|
||||
? options.node_from.findOutputSlot(options.slot_from.name)
|
||||
? nodeFrom.findOutputSlot(options.slot_from.name)
|
||||
: -1
|
||||
// @ts-expect-error - slot_index property
|
||||
if (iS == -1 && options.slot_from.slot_index !== undefined)
|
||||
// @ts-expect-error - slot_index property
|
||||
if (
|
||||
iS == -1 &&
|
||||
'slot_index' in options.slot_from &&
|
||||
typeof options.slot_from.slot_index === 'number'
|
||||
)
|
||||
iS = options.slot_from.slot_index
|
||||
break
|
||||
}
|
||||
case 'number':
|
||||
iS = options.slot_from
|
||||
break
|
||||
@@ -7117,44 +7127,44 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
// try with first if no name set
|
||||
iS = 0
|
||||
}
|
||||
if (options.node_from.outputs[iS] !== undefined) {
|
||||
if (nodeFrom.outputs?.[iS] !== undefined) {
|
||||
if (iS !== false && iS > -1) {
|
||||
if (node == null)
|
||||
throw new TypeError(
|
||||
'options.slot_from was null when showing search box'
|
||||
)
|
||||
|
||||
options.node_from.connectByType(
|
||||
iS,
|
||||
node,
|
||||
options.node_from.outputs[iS].type
|
||||
)
|
||||
nodeFrom.connectByType(iS, node, nodeFrom.outputs[iS].type)
|
||||
}
|
||||
} else {
|
||||
// console.warn("can't find slot " + options.slot_from);
|
||||
}
|
||||
}
|
||||
if (options.node_to) {
|
||||
if (options.node_to && options.node_to instanceof LGraphNode) {
|
||||
const nodeTo = options.node_to
|
||||
// FIXME: any
|
||||
let iS: any = false
|
||||
switch (typeof options.slot_from) {
|
||||
case 'string':
|
||||
iS = options.node_to.findInputSlot(options.slot_from)
|
||||
iS = nodeTo.findInputSlot(options.slot_from)
|
||||
break
|
||||
case 'object':
|
||||
case 'object': {
|
||||
if (options.slot_from == null)
|
||||
throw new TypeError(
|
||||
'options.slot_from was null when showing search box'
|
||||
)
|
||||
|
||||
iS = options.slot_from.name
|
||||
? options.node_to.findInputSlot(options.slot_from.name)
|
||||
? nodeTo.findInputSlot(options.slot_from.name)
|
||||
: -1
|
||||
// @ts-expect-error - slot_index property
|
||||
if (iS == -1 && options.slot_from.slot_index !== undefined)
|
||||
// @ts-expect-error - slot_index property
|
||||
if (
|
||||
iS == -1 &&
|
||||
'slot_index' in options.slot_from &&
|
||||
typeof options.slot_from.slot_index === 'number'
|
||||
)
|
||||
iS = options.slot_from.slot_index
|
||||
break
|
||||
}
|
||||
case 'number':
|
||||
iS = options.slot_from
|
||||
break
|
||||
@@ -7162,18 +7172,14 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
// try with first if no name set
|
||||
iS = 0
|
||||
}
|
||||
if (options.node_to.inputs[iS] !== undefined) {
|
||||
if (nodeTo.inputs?.[iS] !== undefined) {
|
||||
if (iS !== false && iS > -1) {
|
||||
if (node == null)
|
||||
throw new TypeError(
|
||||
'options.slot_from was null when showing search box'
|
||||
)
|
||||
// try connection
|
||||
options.node_to.connectByTypeOutput(
|
||||
iS,
|
||||
node,
|
||||
options.node_to.inputs[iS].type
|
||||
)
|
||||
nodeTo.connectByTypeOutput(iS, node, nodeTo.inputs[iS].type)
|
||||
}
|
||||
} else {
|
||||
// console.warn("can't find slot_nodeTO " + options.slot_from);
|
||||
@@ -7423,15 +7429,18 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
input = dialog.querySelector('select')
|
||||
input?.addEventListener('change', function (e) {
|
||||
dialog.modified()
|
||||
setValue((e.target as HTMLSelectElement)?.value)
|
||||
if (e.target instanceof HTMLSelectElement) setValue(e.target.value)
|
||||
})
|
||||
} else if (type == 'boolean' || type == 'toggle') {
|
||||
input = dialog.querySelector('input')
|
||||
input?.addEventListener('click', function () {
|
||||
dialog.modified()
|
||||
// @ts-expect-error setValue function signature not strictly typed
|
||||
setValue(!!input.checked)
|
||||
})
|
||||
if (input instanceof HTMLInputElement) {
|
||||
const checkbox = input
|
||||
checkbox.addEventListener('click', function () {
|
||||
dialog.modified()
|
||||
// Convert boolean to string for setValue which expects string
|
||||
setValue(checkbox.checked ? 'true' : 'false')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
input = dialog.querySelector('input')
|
||||
if (input) {
|
||||
@@ -7447,8 +7456,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
v = JSON.stringify(v)
|
||||
}
|
||||
|
||||
// @ts-expect-error HTMLInputElement.value expects string but v can be other types
|
||||
input.value = v
|
||||
// Ensure v is converted to string for HTMLInputElement.value
|
||||
input.value = String(v)
|
||||
input.addEventListener('keydown', function (e) {
|
||||
if (e.key == 'Escape') {
|
||||
// ESC
|
||||
@@ -7480,6 +7489,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
|
||||
function setValue(value: string | number | undefined) {
|
||||
if (
|
||||
value !== undefined &&
|
||||
info?.values &&
|
||||
typeof info.values === 'object' &&
|
||||
info.values[value] != undefined
|
||||
@@ -7491,8 +7501,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
value = Number(value)
|
||||
}
|
||||
if (type == 'array' || type == 'object') {
|
||||
// @ts-expect-error JSON.parse doesn't care.
|
||||
value = JSON.parse(value)
|
||||
value = JSON.parse(String(value))
|
||||
}
|
||||
node.properties[property] = value
|
||||
if (node.graph) {
|
||||
@@ -7787,18 +7796,18 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
value_element.addEventListener('click', function (event) {
|
||||
const values = options.values || []
|
||||
const propname = this.parentElement?.dataset['property']
|
||||
const inner_clicked = (v: string | null) => {
|
||||
// node.setProperty(propname,v);
|
||||
// graphcanvas.dirty_canvas = true;
|
||||
this.textContent = v
|
||||
innerChange(propname, v)
|
||||
return false
|
||||
}
|
||||
const textElement = this
|
||||
new LiteGraph.ContextMenu(values, {
|
||||
event,
|
||||
className: 'dark',
|
||||
// @ts-expect-error fixme ts strict error - callback signature mismatch
|
||||
callback: inner_clicked
|
||||
callback: (v?: string | IContextMenuValue<string>) => {
|
||||
// node.setProperty(propname,v);
|
||||
// graphcanvas.dirty_canvas = true;
|
||||
const value = typeof v === 'string' ? v : (v?.value ?? null)
|
||||
textElement.textContent = value
|
||||
innerChange(propname, value)
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -7850,9 +7859,11 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
const inner_refresh = () => {
|
||||
// clear
|
||||
panel.content.innerHTML = ''
|
||||
const ctor = node.constructor
|
||||
const nodeDesc =
|
||||
'desc' in ctor && typeof ctor.desc === 'string' ? ctor.desc : ''
|
||||
panel.addHTML(
|
||||
// @ts-expect-error - desc property
|
||||
`<span class='node_type'>${node.type}</span><span class='node_desc'>${node.constructor.desc || ''}</span><span class='separator'></span>`
|
||||
`<span class='node_type'>${node.type}</span><span class='node_desc'>${nodeDesc}</span><span class='separator'></span>`
|
||||
)
|
||||
|
||||
panel.addHTML('<h3>Properties</h3>')
|
||||
@@ -8009,9 +8020,8 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
throw new TypeError('checkPanels - this.canvas.parentNode was null')
|
||||
const panels = this.canvas.parentNode.querySelectorAll('.litegraph.dialog')
|
||||
for (const panel of panels) {
|
||||
// @ts-expect-error Panel
|
||||
if (!isPanel(panel)) continue
|
||||
if (!panel.node) continue
|
||||
// @ts-expect-error Panel
|
||||
if (!panel.node.graph || panel.graph != this.graph) panel.close()
|
||||
}
|
||||
}
|
||||
@@ -8280,8 +8290,9 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
|
||||
menu_info.push(...node.getExtraSlotMenuOptions(slot))
|
||||
}
|
||||
}
|
||||
// @ts-expect-error Slot type can be number and has number checks
|
||||
options.title = (slot.input ? slot.input.type : slot.output.type) || '*'
|
||||
// Slot type can be ISlotType which includes number, but we convert to string for title
|
||||
const slotType = slot.input ? slot.input.type : slot.output?.type
|
||||
options.title = String(slotType ?? '*')
|
||||
if (slot.input && slot.input.type == LiteGraph.ACTION)
|
||||
options.title = 'Action'
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ describe('LGraphNode', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
origLiteGraph = Object.assign({}, LiteGraph)
|
||||
// @ts-expect-error Intended: Force remove an otherwise readonly non-optional property
|
||||
delete origLiteGraph.Classes
|
||||
// Intended: Force remove an otherwise readonly non-optional property for test isolation
|
||||
delete (origLiteGraph as { Classes?: unknown }).Classes
|
||||
|
||||
Object.assign(LiteGraph, {
|
||||
NODE_TITLE_HEIGHT: 20,
|
||||
|
||||
@@ -785,7 +785,15 @@ export class LGraphNode
|
||||
if (this.graph) {
|
||||
this.graph._version++
|
||||
}
|
||||
for (const j in info) {
|
||||
|
||||
// Use Record types to enable dynamic property access on both info and this
|
||||
const infoRecord = info as unknown as Record<string, unknown>
|
||||
const nodeRecord = this as unknown as Record<
|
||||
string,
|
||||
unknown & { configure?(data: unknown): void }
|
||||
>
|
||||
|
||||
for (const j in infoRecord) {
|
||||
if (j == 'properties') {
|
||||
// i don't want to clone properties, I want to reuse the old container
|
||||
for (const k in info.properties) {
|
||||
@@ -795,23 +803,27 @@ export class LGraphNode
|
||||
continue
|
||||
}
|
||||
|
||||
// @ts-expect-error #594
|
||||
if (info[j] == null) {
|
||||
const infoValue = infoRecord[j]
|
||||
if (infoValue == null) {
|
||||
continue
|
||||
// @ts-expect-error #594
|
||||
} else if (typeof info[j] == 'object') {
|
||||
// @ts-expect-error #594
|
||||
if (this[j]?.configure) {
|
||||
// @ts-expect-error #594
|
||||
this[j]?.configure(info[j])
|
||||
} else if (typeof infoValue == 'object') {
|
||||
const nodeValue = nodeRecord[j]
|
||||
if (
|
||||
nodeValue &&
|
||||
typeof nodeValue === 'object' &&
|
||||
'configure' in nodeValue &&
|
||||
typeof nodeValue.configure === 'function'
|
||||
) {
|
||||
nodeValue.configure(infoValue)
|
||||
} else {
|
||||
// @ts-expect-error #594
|
||||
this[j] = LiteGraph.cloneObject(info[j], this[j])
|
||||
nodeRecord[j] = LiteGraph.cloneObject(
|
||||
infoValue as object,
|
||||
nodeValue as object
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// value
|
||||
// @ts-expect-error #594
|
||||
this[j] = info[j]
|
||||
nodeRecord[j] = infoValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,7 +916,6 @@ export class LGraphNode
|
||||
if (this.inputs)
|
||||
o.inputs = this.inputs.map((input) => inputAsSerialisable(input))
|
||||
if (this.outputs)
|
||||
// @ts-expect-error - Output serialization type mismatch
|
||||
o.outputs = this.outputs.map((output) => outputAsSerialisable(output))
|
||||
|
||||
if (this.title && this.title != this.constructor.title) o.title = this.title
|
||||
@@ -916,8 +927,10 @@ export class LGraphNode
|
||||
o.widgets_values = []
|
||||
for (const [i, widget] of widgets.entries()) {
|
||||
if (widget.serialize === false) continue
|
||||
// @ts-expect-error #595 No-null
|
||||
o.widgets_values[i] = widget ? widget.value : null
|
||||
// Widget value can be any serializable type; null is valid for missing widgets
|
||||
o.widgets_values[i] = widget
|
||||
? widget.value
|
||||
: (null as unknown as TWidgetValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -959,10 +972,14 @@ export class LGraphNode
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error Exceptional case: id is removed so that the graph can assign a new one on add.
|
||||
data.id = undefined
|
||||
|
||||
if (LiteGraph.use_uuids) data.id = LiteGraph.uuidv4()
|
||||
// Exceptional case: id is removed so that the graph can assign a new one on add.
|
||||
// The id field is overwritten to -1 which signals the graph to assign a new id.
|
||||
// When using UUIDs, a new UUID is generated immediately.
|
||||
if (LiteGraph.use_uuids) {
|
||||
data.id = LiteGraph.uuidv4()
|
||||
} else {
|
||||
data.id = -1
|
||||
}
|
||||
|
||||
node.configure(data)
|
||||
|
||||
@@ -1326,10 +1343,6 @@ export class LGraphNode
|
||||
case LGraphEventMode.ALWAYS:
|
||||
break
|
||||
|
||||
// @ts-expect-error Not impl.
|
||||
case LiteGraph.ON_REQUEST:
|
||||
break
|
||||
|
||||
default:
|
||||
return false
|
||||
break
|
||||
@@ -1348,17 +1361,14 @@ export class LGraphNode
|
||||
options.action_call ||= `${this.id}_exec_${Math.floor(Math.random() * 9999)}`
|
||||
if (!this.graph) throw new NullGraphError()
|
||||
|
||||
// @ts-expect-error Technically it works when id is a string. Array gets props.
|
||||
this.graph.nodes_executing[this.id] = true
|
||||
this.onExecute(param, options)
|
||||
// @ts-expect-error deprecated
|
||||
this.graph.nodes_executing[this.id] = false
|
||||
|
||||
// save execution/action ref
|
||||
this.exec_version = this.graph.iteration
|
||||
if (options?.action_call) {
|
||||
this.action_call = options.action_call
|
||||
// @ts-expect-error deprecated
|
||||
this.graph.nodes_executedAction[this.id] = options.action_call
|
||||
}
|
||||
}
|
||||
@@ -1382,16 +1392,13 @@ export class LGraphNode
|
||||
options.action_call ||= `${this.id}_${action || 'action'}_${Math.floor(Math.random() * 9999)}`
|
||||
if (!this.graph) throw new NullGraphError()
|
||||
|
||||
// @ts-expect-error deprecated
|
||||
this.graph.nodes_actioning[this.id] = action || 'actioning'
|
||||
this.onAction(action, param, options)
|
||||
// @ts-expect-error deprecated
|
||||
this.graph.nodes_actioning[this.id] = false
|
||||
|
||||
// save execution/action ref
|
||||
if (options?.action_call) {
|
||||
this.action_call = options.action_call
|
||||
// @ts-expect-error deprecated
|
||||
this.graph.nodes_executedAction[this.id] = options.action_call
|
||||
}
|
||||
}
|
||||
@@ -1840,11 +1847,13 @@ export class LGraphNode
|
||||
}
|
||||
}
|
||||
}
|
||||
// litescene mode using the constructor
|
||||
// @ts-expect-error deprecated https://github.com/Comfy-Org/litegraph.js/issues/639
|
||||
if (this.constructor[`@${property}`])
|
||||
// @ts-expect-error deprecated https://github.com/Comfy-Org/litegraph.js/issues/639
|
||||
info = this.constructor[`@${property}`]
|
||||
// litescene mode using the constructor (deprecated)
|
||||
const ctor = this.constructor as unknown as Record<
|
||||
string,
|
||||
INodePropertyInfo | undefined
|
||||
>
|
||||
const ctorPropertyInfo = ctor[`@${property}`]
|
||||
if (ctorPropertyInfo) info = ctorPropertyInfo
|
||||
|
||||
if (this.constructor.widgets_info?.[property])
|
||||
info = this.constructor.widgets_info[property]
|
||||
@@ -1898,8 +1907,7 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
const w: IBaseWidget & { type: Type } = {
|
||||
// @ts-expect-error - Type casting for widget type property
|
||||
type: type.toLowerCase(),
|
||||
type: type.toLowerCase() as Type,
|
||||
name: name,
|
||||
value: value,
|
||||
callback: typeof callback !== 'function' ? undefined : callback,
|
||||
@@ -3398,8 +3406,8 @@ export class LGraphNode
|
||||
trace(msg: string): void {
|
||||
this.console ||= []
|
||||
this.console.push(msg)
|
||||
// @ts-expect-error deprecated
|
||||
if (this.console.length > LGraphNode.MAX_CONSOLE) this.console.shift()
|
||||
const maxConsole = LGraphNode.MAX_CONSOLE ?? 100
|
||||
if (this.console.length > maxConsole) this.console.shift()
|
||||
}
|
||||
|
||||
/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
|
||||
|
||||
@@ -412,10 +412,11 @@ export class LiteGraphGlobal {
|
||||
|
||||
base_class.title ||= classname
|
||||
|
||||
// extend class
|
||||
for (const i in LGraphNode.prototype) {
|
||||
// @ts-expect-error #576 This functionality is deprecated and should be removed.
|
||||
base_class.prototype[i] ||= LGraphNode.prototype[i]
|
||||
// extend class (deprecated - should be using proper class inheritance)
|
||||
const nodeProto = LGraphNode.prototype as unknown as Record<string, unknown>
|
||||
const baseProto = base_class.prototype as unknown as Record<string, unknown>
|
||||
for (const i in nodeProto) {
|
||||
baseProto[i] ||= nodeProto[i]
|
||||
}
|
||||
|
||||
const prev = this.registered_node_types[type]
|
||||
@@ -460,20 +461,24 @@ export class LiteGraphGlobal {
|
||||
* @param slot_type name of the slot type (variable type), eg. string, number, array, boolean, ..
|
||||
*/
|
||||
registerNodeAndSlotType(
|
||||
type: LGraphNode,
|
||||
type: LGraphNode | string,
|
||||
slot_type: ISlotType,
|
||||
out?: boolean
|
||||
): void {
|
||||
out ||= false
|
||||
// Handle both string type names and node instances
|
||||
const base_class =
|
||||
typeof type === 'string' &&
|
||||
// @ts-expect-error Confirm this function no longer supports string types - base_class should always be an instance not a constructor.
|
||||
this.registered_node_types[type] !== 'anonymous'
|
||||
typeof type === 'string' && this.registered_node_types[type]
|
||||
? this.registered_node_types[type]
|
||||
: type
|
||||
|
||||
// @ts-expect-error Confirm this function no longer supports string types - base_class should always be an instance not a constructor.
|
||||
const class_type = base_class.constructor.type
|
||||
// Get the type from the constructor for node classes
|
||||
const ctor =
|
||||
typeof base_class !== 'string' ? base_class.constructor : undefined
|
||||
const class_type =
|
||||
ctor && 'type' in ctor && typeof ctor.type === 'string'
|
||||
? ctor.type
|
||||
: undefined
|
||||
|
||||
let allTypes = []
|
||||
if (typeof slot_type === 'string') {
|
||||
@@ -493,7 +498,8 @@ export class LiteGraphGlobal {
|
||||
register[slotType] ??= { nodes: [] }
|
||||
|
||||
const { nodes } = register[slotType]
|
||||
if (!nodes.includes(class_type)) nodes.push(class_type)
|
||||
if (class_type !== undefined && !nodes.includes(class_type))
|
||||
nodes.push(class_type)
|
||||
|
||||
// check if is a new type
|
||||
const types = out ? this.slot_types_out : this.slot_types_in
|
||||
@@ -559,11 +565,11 @@ export class LiteGraphGlobal {
|
||||
node.pos ||= [this.DEFAULT_POSITION[0], this.DEFAULT_POSITION[1]]
|
||||
node.mode ||= LGraphEventMode.ALWAYS
|
||||
|
||||
// extra options
|
||||
// extra options (dynamic property assignment for node configuration)
|
||||
if (options) {
|
||||
const nodeRecord = node as unknown as Record<string, unknown>
|
||||
for (const i in options) {
|
||||
// @ts-expect-error #577 Requires interface
|
||||
node[i] = options[i]
|
||||
nodeRecord[i] = (options as Record<string, unknown>)[i]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -655,20 +661,21 @@ export class LiteGraphGlobal {
|
||||
}
|
||||
|
||||
// separated just to improve if it doesn't work
|
||||
/** @deprecated Prefer {@link structuredClone} */
|
||||
/**
|
||||
* @deprecated Prefer {@link structuredClone}
|
||||
* Note: JSON.parse returns `unknown`, so type assertions are unavoidable here.
|
||||
* This function is deprecated precisely because it cannot be made type-safe.
|
||||
*/
|
||||
cloneObject<T extends object | undefined | null>(
|
||||
obj: T,
|
||||
target?: T
|
||||
): WhenNullish<T, null> {
|
||||
if (obj == null) return null as WhenNullish<T, null>
|
||||
|
||||
const r = JSON.parse(JSON.stringify(obj))
|
||||
if (!target) return r
|
||||
const cloned: unknown = JSON.parse(JSON.stringify(obj))
|
||||
if (!target) return cloned as WhenNullish<T, null>
|
||||
|
||||
for (const i in r) {
|
||||
// @ts-expect-error deprecated
|
||||
target[i] = r[i]
|
||||
}
|
||||
Object.assign(target, cloned)
|
||||
return target
|
||||
}
|
||||
|
||||
@@ -788,33 +795,30 @@ export class LiteGraphGlobal {
|
||||
}
|
||||
}
|
||||
|
||||
switch (sEvent) {
|
||||
// both pointer and move events
|
||||
case 'down':
|
||||
case 'up':
|
||||
case 'move':
|
||||
case 'over':
|
||||
case 'out':
|
||||
// @ts-expect-error - intentional fallthrough
|
||||
case 'enter': {
|
||||
oDOM.addEventListener(sMethod + sEvent, fCall, capture)
|
||||
}
|
||||
// only pointerevents
|
||||
// falls through
|
||||
case 'leave':
|
||||
case 'cancel':
|
||||
case 'gotpointercapture':
|
||||
// @ts-expect-error - intentional fallthrough
|
||||
case 'lostpointercapture': {
|
||||
if (sMethod != 'mouse') {
|
||||
return oDOM.addEventListener(sMethod + sEvent, fCall, capture)
|
||||
}
|
||||
}
|
||||
// not "pointer" || "mouse"
|
||||
// falls through
|
||||
default:
|
||||
return oDOM.addEventListener(sEvent, fCall, capture)
|
||||
// Events that apply to both pointer and mouse methods
|
||||
const pointerAndMouseEvents = ['down', 'up', 'move', 'over', 'out', 'enter']
|
||||
// Events that only apply to pointer method
|
||||
const pointerOnlyEvents = [
|
||||
'leave',
|
||||
'cancel',
|
||||
'gotpointercapture',
|
||||
'lostpointercapture'
|
||||
]
|
||||
|
||||
if (pointerAndMouseEvents.includes(sEvent)) {
|
||||
oDOM.addEventListener(sMethod + sEvent, fCall, capture)
|
||||
}
|
||||
|
||||
if (
|
||||
pointerAndMouseEvents.includes(sEvent) ||
|
||||
pointerOnlyEvents.includes(sEvent)
|
||||
) {
|
||||
if (sMethod != 'mouse') {
|
||||
return oDOM.addEventListener(sMethod + sEvent, fCall, capture)
|
||||
}
|
||||
}
|
||||
|
||||
return oDOM.addEventListener(sEvent, fCall, capture)
|
||||
}
|
||||
|
||||
pointerListenerRemove(
|
||||
@@ -831,46 +835,43 @@ export class LiteGraphGlobal {
|
||||
)
|
||||
return
|
||||
|
||||
switch (sEvent) {
|
||||
// both pointer and move events
|
||||
case 'down':
|
||||
case 'up':
|
||||
case 'move':
|
||||
case 'over':
|
||||
case 'out':
|
||||
// @ts-expect-error - intentional fallthrough
|
||||
case 'enter': {
|
||||
if (
|
||||
this.pointerevents_method == 'pointer' ||
|
||||
this.pointerevents_method == 'mouse'
|
||||
) {
|
||||
oDOM.removeEventListener(
|
||||
this.pointerevents_method + sEvent,
|
||||
fCall,
|
||||
capture
|
||||
)
|
||||
}
|
||||
// Events that apply to both pointer and mouse methods
|
||||
const pointerAndMouseEvents = ['down', 'up', 'move', 'over', 'out', 'enter']
|
||||
// Events that only apply to pointer method
|
||||
const pointerOnlyEvents = [
|
||||
'leave',
|
||||
'cancel',
|
||||
'gotpointercapture',
|
||||
'lostpointercapture'
|
||||
]
|
||||
|
||||
if (pointerAndMouseEvents.includes(sEvent)) {
|
||||
if (
|
||||
this.pointerevents_method == 'pointer' ||
|
||||
this.pointerevents_method == 'mouse'
|
||||
) {
|
||||
oDOM.removeEventListener(
|
||||
this.pointerevents_method + sEvent,
|
||||
fCall,
|
||||
capture
|
||||
)
|
||||
}
|
||||
// only pointerevents
|
||||
// falls through
|
||||
case 'leave':
|
||||
case 'cancel':
|
||||
case 'gotpointercapture':
|
||||
// @ts-expect-error - intentional fallthrough
|
||||
case 'lostpointercapture': {
|
||||
if (this.pointerevents_method == 'pointer') {
|
||||
return oDOM.removeEventListener(
|
||||
this.pointerevents_method + sEvent,
|
||||
fCall,
|
||||
capture
|
||||
)
|
||||
}
|
||||
}
|
||||
// not "pointer" || "mouse"
|
||||
// falls through
|
||||
default:
|
||||
return oDOM.removeEventListener(sEvent, fCall, capture)
|
||||
}
|
||||
|
||||
if (
|
||||
pointerAndMouseEvents.includes(sEvent) ||
|
||||
pointerOnlyEvents.includes(sEvent)
|
||||
) {
|
||||
if (this.pointerevents_method == 'pointer') {
|
||||
return oDOM.removeEventListener(
|
||||
this.pointerevents_method + sEvent,
|
||||
fCall,
|
||||
capture
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return oDOM.removeEventListener(sEvent, fCall, capture)
|
||||
}
|
||||
|
||||
getTime(): number {
|
||||
|
||||
@@ -32,7 +32,6 @@ LGraph {
|
||||
"title": "A group to test with",
|
||||
},
|
||||
],
|
||||
"_input_nodes": undefined,
|
||||
"_last_trigger_time": undefined,
|
||||
"_links": Map {},
|
||||
"_nodes": [
|
||||
@@ -281,9 +280,9 @@ LGraph {
|
||||
"last_update_time": 0,
|
||||
"links": Map {},
|
||||
"list_of_graphcanvas": null,
|
||||
"nodes_actioning": [],
|
||||
"nodes_executedAction": [],
|
||||
"nodes_executing": [],
|
||||
"nodes_actioning": {},
|
||||
"nodes_executedAction": {},
|
||||
"nodes_executing": {},
|
||||
"onTrigger": undefined,
|
||||
"reroutesInternal": Map {},
|
||||
"revision": 0,
|
||||
|
||||
@@ -196,8 +196,7 @@ export class FloatingRenderLink implements RenderLink {
|
||||
}
|
||||
|
||||
connectToRerouteInput(
|
||||
// @ts-expect-error - Reroute type needs fixing
|
||||
reroute: Reroute,
|
||||
_reroute: Reroute,
|
||||
{ node: inputNode, input }: { node: LGraphNode; input: INodeInputSlot },
|
||||
events: CustomEventTarget<LinkConnectorEventMap>
|
||||
) {
|
||||
@@ -213,8 +212,7 @@ export class FloatingRenderLink implements RenderLink {
|
||||
}
|
||||
|
||||
connectToRerouteOutput(
|
||||
// @ts-expect-error - Reroute type needs fixing
|
||||
reroute: Reroute,
|
||||
_reroute: Reroute,
|
||||
outputNode: LGraphNode,
|
||||
output: INodeOutputSlot,
|
||||
events: CustomEventTarget<LinkConnectorEventMap>
|
||||
|
||||
@@ -224,6 +224,9 @@ export interface LinkSegment {
|
||||
readonly origin_id: NodeId | undefined
|
||||
/** Output slot index */
|
||||
readonly origin_slot: number | undefined
|
||||
|
||||
/** Optional data attached to the link for tooltip display */
|
||||
data?: number | string | boolean | { toToolTip?(): string }
|
||||
}
|
||||
|
||||
interface IInputOrOutput {
|
||||
|
||||
@@ -15,14 +15,13 @@ const boundingRect: ReadOnlyRect = [0, 0, 10, 10]
|
||||
describe('NodeSlot', () => {
|
||||
describe('inputAsSerialisable', () => {
|
||||
it('removes _data from serialized slot', () => {
|
||||
const slot: INodeOutputSlot = {
|
||||
const slot: INodeOutputSlot & { _data: string } = {
|
||||
_data: 'test data',
|
||||
name: 'test-id',
|
||||
type: 'STRING',
|
||||
links: [],
|
||||
boundingRect
|
||||
}
|
||||
// @ts-expect-error Argument type mismatch for test
|
||||
const serialized = outputAsSerialisable(slot)
|
||||
expect(serialized).not.toHaveProperty('_data')
|
||||
})
|
||||
|
||||
@@ -74,12 +74,12 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
|
||||
slot: OptionalProps<INodeSlot, 'boundingRect'>,
|
||||
node: LGraphNode
|
||||
) {
|
||||
// @ts-expect-error Workaround: Ensure internal properties are not copied to the slot (_listenerController
|
||||
// Workaround: Ensure internal properties are not copied to the slot
|
||||
// https://github.com/Comfy-Org/litegraph.js/issues/1138
|
||||
const maybeSubgraphSlot: OptionalProps<
|
||||
const maybeSubgraphSlot = slot as OptionalProps<
|
||||
ISubgraphInput,
|
||||
'link' | 'boundingRect'
|
||||
> = slot
|
||||
> & { _listenerController?: unknown }
|
||||
const { boundingRect, name, type, _listenerController, ...rest } =
|
||||
maybeSubgraphSlot
|
||||
const rectangle = boundingRect
|
||||
|
||||
@@ -5,8 +5,7 @@ import type {
|
||||
import type {
|
||||
INodeInputSlot,
|
||||
INodeOutputSlot,
|
||||
INodeSlot,
|
||||
IWidget
|
||||
INodeSlot
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type {
|
||||
ISerialisableNodeInput,
|
||||
@@ -63,7 +62,7 @@ export function inputAsSerialisable(
|
||||
}
|
||||
|
||||
export function outputAsSerialisable(
|
||||
slot: INodeOutputSlot & { widget?: IWidget }
|
||||
slot: INodeOutputSlot & { widget?: { name: string } }
|
||||
): ISerialisableNodeOutput {
|
||||
const { pos, slot_index, links, widget } = slot
|
||||
// Output widgets do not exist in Litegraph; this is a temporary downstream workaround.
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
// @ts-expect-error Polyfill
|
||||
Symbol.dispose ??= Symbol('Symbol.dispose')
|
||||
// @ts-expect-error Polyfill
|
||||
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose')
|
||||
// Polyfill Symbol.dispose and Symbol.asyncDispose for environments that don't support them
|
||||
// These are well-known symbols added in ES2024 for explicit resource management
|
||||
|
||||
// Use a separate reference to Symbol constructor for creating new symbols
|
||||
// This avoids TypeScript narrowing issues inside the conditional blocks
|
||||
const SymbolCtor: (description?: string) => symbol = Symbol
|
||||
|
||||
const SymbolWithPolyfills = Symbol as unknown as {
|
||||
dispose: symbol
|
||||
asyncDispose: symbol
|
||||
}
|
||||
|
||||
if (!('dispose' in Symbol)) {
|
||||
Object.defineProperty(Symbol, 'dispose', {
|
||||
value: SymbolCtor('Symbol.dispose'),
|
||||
writable: false,
|
||||
configurable: false
|
||||
})
|
||||
}
|
||||
if (!('asyncDispose' in Symbol)) {
|
||||
Object.defineProperty(Symbol, 'asyncDispose', {
|
||||
value: SymbolCtor('Symbol.asyncDispose'),
|
||||
writable: false,
|
||||
configurable: false
|
||||
})
|
||||
}
|
||||
|
||||
// Export for use in other modules
|
||||
export const DisposeSymbol = SymbolWithPolyfills.dispose
|
||||
export const AsyncDisposeSymbol = SymbolWithPolyfills.asyncDispose
|
||||
|
||||
// API *************************************************
|
||||
// like rect but rounded corners
|
||||
@@ -11,14 +37,15 @@ export function loadPolyfills() {
|
||||
window.CanvasRenderingContext2D &&
|
||||
!window.CanvasRenderingContext2D.prototype.roundRect
|
||||
) {
|
||||
// @ts-expect-error Slightly broken polyfill - radius_low not impl. anywhere
|
||||
window.CanvasRenderingContext2D.prototype.roundRect = function (
|
||||
// Legacy polyfill for roundRect with additional radius_low parameter (non-standard)
|
||||
const roundRectPolyfill = function (
|
||||
this: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
radius: number | number[],
|
||||
radius_low: number | number[]
|
||||
radius_low?: number | number[]
|
||||
) {
|
||||
let top_left_radius = 0
|
||||
let top_right_radius = 0
|
||||
@@ -78,16 +105,23 @@ export function loadPolyfills() {
|
||||
this.lineTo(x, y + bottom_left_radius)
|
||||
this.quadraticCurveTo(x, y, x + top_left_radius, y)
|
||||
}
|
||||
|
||||
// Assign the polyfill, casting to handle the slightly different signature
|
||||
window.CanvasRenderingContext2D.prototype.roundRect =
|
||||
roundRectPolyfill as CanvasRenderingContext2D['roundRect']
|
||||
}
|
||||
|
||||
if (typeof window != 'undefined' && !window['requestAnimationFrame']) {
|
||||
// Legacy requestAnimationFrame polyfill for older browsers
|
||||
if (typeof window != 'undefined' && !window.requestAnimationFrame) {
|
||||
const win = window as Window & {
|
||||
webkitRequestAnimationFrame?: typeof requestAnimationFrame
|
||||
mozRequestAnimationFrame?: typeof requestAnimationFrame
|
||||
}
|
||||
window.requestAnimationFrame =
|
||||
// @ts-expect-error Legacy code
|
||||
window.webkitRequestAnimationFrame ||
|
||||
// @ts-expect-error Legacy code
|
||||
window.mozRequestAnimationFrame ||
|
||||
win.webkitRequestAnimationFrame ||
|
||||
win.mozRequestAnimationFrame ||
|
||||
function (callback) {
|
||||
window.setTimeout(callback, 1000 / 60)
|
||||
return window.setTimeout(callback, 1000 / 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,7 @@ describe.skip('Subgraph Construction', () => {
|
||||
const subgraphData = createTestSubgraphData()
|
||||
|
||||
expect(() => {
|
||||
// @ts-expect-error Testing invalid null parameter
|
||||
new Subgraph(null, subgraphData)
|
||||
new Subgraph(null as never, subgraphData)
|
||||
}).toThrow('Root graph is required')
|
||||
})
|
||||
|
||||
|
||||
@@ -136,8 +136,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
80 - subgraphNode.pos[1] // 80 - 100 = -20
|
||||
]
|
||||
|
||||
// @ts-expect-error onMouseDown possibly undefined
|
||||
const handled = subgraphNode.onMouseDown(
|
||||
const handled = subgraphNode.onMouseDown?.(
|
||||
event,
|
||||
clickPosRelativeToNode,
|
||||
canvas
|
||||
@@ -173,8 +172,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
150 - subgraphNode.pos[1] // 150 - 100 = 50
|
||||
]
|
||||
|
||||
// @ts-expect-error onMouseDown possibly undefined
|
||||
const handled = subgraphNode.onMouseDown(
|
||||
const handled = subgraphNode.onMouseDown?.(
|
||||
event,
|
||||
clickPosRelativeToNode,
|
||||
canvas
|
||||
@@ -220,8 +218,7 @@ describe.skip('SubgraphNode Title Button', () => {
|
||||
80 - subgraphNode.pos[1] // -20
|
||||
]
|
||||
|
||||
// @ts-expect-error onMouseDown possibly undefined
|
||||
const handled = subgraphNode.onMouseDown(
|
||||
const handled = subgraphNode.onMouseDown?.(
|
||||
event,
|
||||
clickPosRelativeToNode,
|
||||
canvas
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { ExportedSubgraph } from '@/lib/litegraph/src/types/serialisation'
|
||||
|
||||
import {
|
||||
createTestSubgraph,
|
||||
@@ -76,8 +77,6 @@ describe.skip('SubgraphSerialization - Basic Serialization', () => {
|
||||
// Verify core properties
|
||||
expect(restored.id).toBe(original.id)
|
||||
expect(restored.name).toBe(original.name)
|
||||
// @ts-expect-error description property not in type definition
|
||||
expect(restored.description).toBe(original.description)
|
||||
|
||||
// Verify I/O structure
|
||||
expect(restored.inputs.length).toBe(original.inputs.length)
|
||||
@@ -252,8 +251,10 @@ describe.skip('SubgraphSerialization - Version Compatibility', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
// @ts-expect-error Type mismatch in ExportedSubgraph format
|
||||
const subgraph = new Subgraph(new LGraph(), modernFormat)
|
||||
const subgraph = new Subgraph(
|
||||
new LGraph(),
|
||||
modernFormat as unknown as ExportedSubgraph
|
||||
)
|
||||
expect(subgraph.name).toBe('Modern Subgraph')
|
||||
expect(subgraph.inputs.length).toBe(1)
|
||||
expect(subgraph.outputs.length).toBe(1)
|
||||
@@ -282,8 +283,10 @@ describe.skip('SubgraphSerialization - Version Compatibility', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
// @ts-expect-error Type mismatch in ExportedSubgraph format
|
||||
const subgraph = new Subgraph(new LGraph(), incompleteFormat)
|
||||
const subgraph = new Subgraph(
|
||||
new LGraph(),
|
||||
incompleteFormat as unknown as ExportedSubgraph
|
||||
)
|
||||
expect(subgraph.name).toBe('Incomplete Subgraph')
|
||||
// Should have default empty arrays
|
||||
expect(Array.isArray(subgraph.inputs)).toBe(true)
|
||||
@@ -317,8 +320,10 @@ describe.skip('SubgraphSerialization - Version Compatibility', () => {
|
||||
|
||||
// Should handle future format gracefully
|
||||
expect(() => {
|
||||
// @ts-expect-error Type mismatch in ExportedSubgraph format
|
||||
const subgraph = new Subgraph(new LGraph(), futureFormat)
|
||||
const subgraph = new Subgraph(
|
||||
new LGraph(),
|
||||
futureFormat as unknown as ExportedSubgraph
|
||||
)
|
||||
expect(subgraph.name).toBe('Future Subgraph')
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
@@ -7,6 +7,14 @@ import type {
|
||||
TWidgetType
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { BaseWidget, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type {
|
||||
DrawWidgetOptions,
|
||||
WidgetEventOptions
|
||||
} from '@/lib/litegraph/src/widgets/BaseWidget'
|
||||
import type {
|
||||
IBaseWidget,
|
||||
TWidgetValue
|
||||
} from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import {
|
||||
createEventCapture,
|
||||
@@ -14,11 +22,47 @@ import {
|
||||
createTestSubgraphNode
|
||||
} from './__fixtures__/subgraphHelpers'
|
||||
|
||||
/** Concrete test implementation of abstract BaseWidget */
|
||||
class TestWidget extends BaseWidget<IBaseWidget> {
|
||||
constructor(options: {
|
||||
name: string
|
||||
type: TWidgetType
|
||||
value: TWidgetValue
|
||||
y: number
|
||||
options: Record<string, unknown>
|
||||
node: LGraphNode
|
||||
tooltip?: string
|
||||
}) {
|
||||
super(
|
||||
{
|
||||
name: options.name,
|
||||
type: options.type,
|
||||
value: options.value,
|
||||
y: options.y,
|
||||
options: options.options,
|
||||
tooltip: options.tooltip
|
||||
} as IBaseWidget,
|
||||
options.node
|
||||
)
|
||||
}
|
||||
|
||||
drawWidget(
|
||||
_ctx: CanvasRenderingContext2D,
|
||||
_options: DrawWidgetOptions
|
||||
): void {
|
||||
// No-op for test
|
||||
}
|
||||
|
||||
onClick(_options: WidgetEventOptions): void {
|
||||
// No-op for test
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to create a node with a widget
|
||||
function createNodeWithWidget(
|
||||
title: string,
|
||||
widgetType: TWidgetType = 'number',
|
||||
widgetValue: any = 42,
|
||||
widgetValue: TWidgetValue = 42,
|
||||
slotType: ISlotType = 'number',
|
||||
tooltip?: string
|
||||
) {
|
||||
@@ -26,8 +70,7 @@ function createNodeWithWidget(
|
||||
const input = node.addInput('value', slotType)
|
||||
node.addOutput('out', slotType)
|
||||
|
||||
// @ts-expect-error Abstract class instantiation
|
||||
const widget = new BaseWidget({
|
||||
const widget = new TestWidget({
|
||||
name: 'widget',
|
||||
type: widgetType,
|
||||
value: widgetValue,
|
||||
@@ -181,8 +224,7 @@ describe.skip('SubgraphWidgetPromotion', () => {
|
||||
const numInput = multiWidgetNode.addInput('num', 'number')
|
||||
const strInput = multiWidgetNode.addInput('str', 'string')
|
||||
|
||||
// @ts-expect-error Abstract class instantiation
|
||||
const widget1 = new BaseWidget({
|
||||
const widget1 = new TestWidget({
|
||||
name: 'widget1',
|
||||
type: 'number',
|
||||
value: 10,
|
||||
@@ -191,8 +233,7 @@ describe.skip('SubgraphWidgetPromotion', () => {
|
||||
node: multiWidgetNode
|
||||
})
|
||||
|
||||
// @ts-expect-error Abstract class instantiation
|
||||
const widget2 = new BaseWidget({
|
||||
const widget2 = new TestWidget({
|
||||
name: 'widget2',
|
||||
type: 'string',
|
||||
value: 'hello',
|
||||
@@ -331,8 +372,7 @@ describe.skip('SubgraphWidgetPromotion', () => {
|
||||
const numInput = multiWidgetNode.addInput('num', 'number')
|
||||
const strInput = multiWidgetNode.addInput('str', 'string')
|
||||
|
||||
// @ts-expect-error Abstract class instantiation
|
||||
const widget1 = new BaseWidget({
|
||||
const widget1 = new TestWidget({
|
||||
name: 'widget1',
|
||||
type: 'number',
|
||||
value: 10,
|
||||
@@ -342,8 +382,7 @@ describe.skip('SubgraphWidgetPromotion', () => {
|
||||
tooltip: 'Number widget tooltip'
|
||||
})
|
||||
|
||||
// @ts-expect-error Abstract class instantiation
|
||||
const widget2 = new BaseWidget({
|
||||
const widget2 = new TestWidget({
|
||||
name: 'widget2',
|
||||
type: 'string',
|
||||
value: 'hello',
|
||||
|
||||
@@ -120,29 +120,33 @@ export abstract class BaseWidget<
|
||||
|
||||
// `node` has no setter - Object.assign will throw.
|
||||
// TODO: Resolve this workaround. Ref: https://github.com/Comfy-Org/litegraph.js/issues/1022
|
||||
// Destructure known properties that could conflict with class getters/properties.
|
||||
// These are typed as `unknown` to handle custom widgets that may include them.
|
||||
const {
|
||||
node: _,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
outline_color,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
background_color,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
height,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
text_color,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
secondary_text_color,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
disabledTextColor,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
displayName,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
displayValue,
|
||||
// @ts-expect-error Prevent naming conflicts with custom nodes.
|
||||
labelBaseline,
|
||||
outline_color: _outline_color,
|
||||
background_color: _background_color,
|
||||
height: _height,
|
||||
text_color: _text_color,
|
||||
secondary_text_color: _secondary_text_color,
|
||||
disabledTextColor: _disabledTextColor,
|
||||
displayName: _displayName,
|
||||
displayValue: _displayValue,
|
||||
labelBaseline: _labelBaseline,
|
||||
promoted,
|
||||
...safeValues
|
||||
} = widget
|
||||
} = widget as TWidget & {
|
||||
node: LGraphNode
|
||||
outline_color?: unknown
|
||||
background_color?: unknown
|
||||
height?: unknown
|
||||
text_color?: unknown
|
||||
secondary_text_color?: unknown
|
||||
disabledTextColor?: unknown
|
||||
displayName?: unknown
|
||||
displayValue?: unknown
|
||||
labelBaseline?: unknown
|
||||
}
|
||||
|
||||
Object.assign(this, safeValues)
|
||||
}
|
||||
@@ -341,8 +345,11 @@ export abstract class BaseWidget<
|
||||
* Correctly and safely typing this is currently not possible (practical?) in TypeScript 5.8.
|
||||
*/
|
||||
createCopyForNode(node: LGraphNode): this {
|
||||
// @ts-expect-error - Constructor type casting for widget cloning
|
||||
const cloned: this = new (this.constructor as typeof this)(this, node)
|
||||
const WidgetConstructor = this.constructor as new (
|
||||
widget: TWidget,
|
||||
node: LGraphNode
|
||||
) => this
|
||||
const cloned = new WidgetConstructor(this as unknown as TWidget, node)
|
||||
cloned.value = this.value
|
||||
return cloned
|
||||
}
|
||||
|
||||
@@ -112,11 +112,12 @@ export class ComboWidget
|
||||
// avoids double click event
|
||||
options.canvas.last_mouseclick = 0
|
||||
|
||||
// Handle both string and non-string values for indexOf lookup
|
||||
const currentValue = this.value
|
||||
const foundIndex =
|
||||
typeof values === 'object'
|
||||
? indexedValues.indexOf(String(this.value)) + delta
|
||||
: // @ts-expect-error handle non-string values
|
||||
indexedValues.indexOf(this.value) + delta
|
||||
? indexedValues.indexOf(String(currentValue)) + delta
|
||||
: indexedValues.indexOf(currentValue as string) + delta
|
||||
|
||||
const index = clamp(foundIndex, 0, indexedValues.length - 1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user