### Install litegraph.js via npm Source: https://github.com/comfy-org/litegraph.js/blob/master/README.md Use this command to install the litegraph.js library in your project. ```bash npm install @comfyorg/litegraph ``` -------------------------------- ### Install Comfy Litegraph Source: https://context7.com/comfy-org/litegraph.js/llms.txt Install the Comfy Litegraph package using npm. Import the necessary components and the CSS file. ```bash npm install @comfyorg/litegraph ``` ```typescript import { LiteGraph, LGraph, LGraphCanvas, LGraphNode } from "@comfyorg/litegraph" import "@comfyorg/litegraph/style.css" ``` -------------------------------- ### Test Setup for Subgraphs in TypeScript Source: https://github.com/comfy-org/litegraph.js/blob/master/CLAUDE.md Utilize the provided test helpers for consistent and reliable setup when testing subgraphs. This ensures that test environments are correctly initialized with necessary components like subgraph and subgraphNode. ```typescript import { createTestSubgraph, createTestSubgraphNode } from "./fixtures/subgraphHelpers" function createTestSetup() { const subgraph = createTestSubgraph() const subgraphNode = createTestSubgraphNode(subgraph) return { subgraph, subgraphNode } } ``` -------------------------------- ### Create and Query Node Instance with LiteGraph Source: https://context7.com/comfy-org/litegraph.js/llms.txt Create a node instance using LiteGraph's factory method and query the node registry. This demonstrates creating a node, logging its title and type, retrieving a node constructor by type, getting all categories, and fetching nodes within a specific category. ```typescript import { LiteGraph, LGraph, LGraphNode } from "@comfyorg/litegraph" // ── Create a node instance (not yet attached to any graph) ─────────────────── const node = LiteGraph.createNode("math/add", "My Adder") console.log(node?.title) // "My Adder" console.log(node?.type) // "math/add" // ── Query the registry ──────────────────────────────────────────────────────── const ctor = LiteGraph.getNodeType("math/add") // returns AddNode class const cats = LiteGraph.getNodeTypesCategories() // ["", "math"] const inMath = LiteGraph.getNodeTypesInCategory("math") // [AddNode] // ── Remove a type ──────────────────────────────────────────────────────────── LiteGraph.unregisterNodeType("math/add") ``` -------------------------------- ### Configure LiteGraph and Register Custom Node Source: https://context7.com/comfy-org/litegraph.js/llms.txt Configure global options for LiteGraph and define/register a custom node type. This example shows setting debug mode, script allowance, UUID usage, snapping, and grid size, then defining an 'AddNode' with inputs, outputs, and execution logic. ```typescript import { LiteGraph, LGraph, LGraphNode } from "@comfyorg/litegraph" // ── Configure global options ────────────────────────────────────────────────── LiteGraph.debug = false LiteGraph.allow_scripts = false LiteGraph.use_uuids = true // use string UUIDs instead of integers for node IDs LiteGraph.alwaysSnapToGrid = true LiteGraph.CANVAS_GRID_SIZE = 10 // ── Define and register a custom node type ─────────────────────────────────── class AddNode extends LGraphNode { static title = "Add" constructor() { super("Add") this.addInput("A", "number") this.addInput("B", "number") this.addOutput("A+B", "number") this.properties = { precision: 1 } } onExecute() { const a = this.getInputData(0) ?? 0 const b = this.getInputData(1) ?? 0 this.setOutputData(0, Number((a + b).toFixed(this.properties.precision))) } } LiteGraph.registerNodeType("math/add", AddNode) ``` -------------------------------- ### Server-Side Headless Execution with LiteGraph.js Source: https://context7.com/comfy-org/litegraph.js/llms.txt Illustrates running LGraph without a canvas in a Node.js environment for headless computation. This setup is useful for pure data-flow workflows. ```typescript // Node.js – no browser globals required for basic execution import { LiteGraph, LGraph, LGraphNode } from "@comfyorg/litegraph" class SourceNode extends LGraphNode { static title = "Source" constructor() { super("Source") this.addOutput("value", "number") } onExecute() { this.setOutputData(0, Date.now()) } } class SinkNode extends LGraphNode { static title = "Sink" public lastValue: number = 0 constructor() { super("Sink") this.addInput("value", "number") } onExecute() { this.lastValue = this.getInputData(0) as number } } LiteGraph.registerNodeType("io/source", SourceNode) LiteGraph.registerNodeType("io/sink", SinkNode) const graph = new LGraph() const source = LiteGraph.createNode("io/source")! const sink = LiteGraph.createNode("io/sink")! graph.add(source) graph.add(sink) source.connect(0, sink, 0) graph.updateExecutionOrder() graph.runStep(1, true) console.log((sink as SinkNode).lastValue) // prints current timestamp ``` -------------------------------- ### Create and Manage LGraphGroup for Node Grouping Source: https://context7.com/comfy-org/litegraph.js/llms.txt Visually group nodes on the canvas using LGraphGroup. This example demonstrates creating a group, setting its title, color, position, and size, and then adding it to the graph. It also shows how to resize, recolor, and remove groups. ```typescript import { LiteGraph, LGraph } from "@comfyorg/litegraph" const graph = new LGraph() // ── Create a group ──────────────────────────────────────────────────────────── const group = new LiteGraph.LGraphGroup() group.title = "Pre-processing" group.color = "#223344" group.pos = [50, 50] group.size = [400, 300] graph.add(group) // ── Resize and colour ──────────────────────────────────────────────────────── group.size = [500, 350] group.color = "#334455" graph.setDirtyCanvas(true) // ── Auto-arrange nodes in execution order ──────────────────────────────────── graph.arrange(100, "horizontal") // margin=100, layout="horizontal"|"vertical" // ── Remove ──────────────────────────────────────────────────────────────────── graph.remove(group) ``` -------------------------------- ### Configure Widget Pointer Events Source: https://github.com/comfy-org/litegraph.js/blob/master/API.md Set `widget.onPointerDown` to define callbacks for click, double-click, drag start, drag, drag end, and finally events. Returning true cancels default Litegraph handling. ```typescript // Callbacks for each pointer action can be configured ahead of time widget.onPointerDown = function (pointer, node, canvas) { const e = pointer.eDown const offsetFromNode = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]] // Click events - no overlap with drag events pointer.onClick = upEvent => { // Provides access to the whole lifecycle of events in every callback console.log(pointer.eDown) console.log(pointer.eMove ?? "Pointer didn't move") console.log(pointer.eUp) } pointer.onDoubleClick = upEvent => this.customFunction(upEvent) // Runs once before the first onDrag event pointer.onDragStart = () => {} // Receives every movement event pointer.onDrag = moveEvent => {} // The pointerup event of a drag pointer.onDragEnd = upEvent => {} // Semantics of a "finally" block (try/catch). Once set, the block always executes. pointer.finally = () => {} // Return true to cancel regular Litegraph handling of this click / drag return true } ``` -------------------------------- ### Add Text and Icon Badges to LiteGraph Nodes Source: https://context7.com/comfy-org/litegraph.js/llms.txt Enhance nodes with visual information using LGraphBadge. This example shows how to add text-based badges with custom styling and icon-based badges using Unicode characters and font families. It also covers setting the badge's position on the node. ```typescript import { LiteGraph, LGraph, LGraphNode, LGraphBadge, BadgePosition, } from "@comfyorg/litegraph" class BadgedNode extends LGraphNode { static title = "Badged Node" constructor() { super("Badged Node") this.addOutput("out", "string") // ── Text badge at top-left ──────────────────────────────────────────── this.badges.push(new LGraphBadge({ text: "v2", fgColor: "#fff", bgColor: "#1a6b3c", fontSize: 11, padding: 5, height: 18, cornerRadius: 4, })) // ── Icon badge (e.g. font-icon) ─────────────────────────────────────── this.badges.push(new LGraphBadge({ text: "", iconOptions: { unicode: "\uf058", // FontAwesome check-circle fontFamily: "FontAwesome", fontSize: 14, color: "#4caf50", }, })) // ── Badge position ──────────────────────────────────────────────────── this.badgePosition = BadgePosition.TopRight } } LiteGraph.registerNodeType("ui/badged", BadgedNode) ``` -------------------------------- ### Create a custom 'Sum' node in litegraph.js Source: https://github.com/comfy-org/litegraph.js/blob/master/README.md Example of creating a custom node type that sums two numerical inputs. This node is registered with the 'basic/sum' identifier. Ensure LiteGraph and LGraphNode are imported correctly. ```typescript import { LiteGraph, LGraphNode } from "./litegraph" class MyAddNode extends LGraphNode { // Name to show title = "Sum" constructor() { this.addInput("A", "number") this.addInput("B", "number") this.addOutput("A+B", "number") this.properties.precision = 1 } // Function to call when the node is executed onExecute() { var A = this.getInputData(0) if (A === undefined) A = 0 var B = this.getInputData(1) if (B === undefined) B = 0 this.setOutputData(0, A + B) } } // Register the node type LiteGraph.registerNodeType("basic/sum", MyAddNode) ``` -------------------------------- ### Implement Interactive Node Widgets with CanvasPointer Source: https://context7.com/comfy-org/litegraph.js/llms.txt Define custom widget behavior within a LiteGraph node using CanvasPointer. This example shows how to handle click, double-click, and drag events on a button widget, overriding default Litegraph behavior by returning true from onPointerDown. ```typescript import { LGraphNode, type LGraphCanvas } from "@comfyorg/litegraph" class InteractiveNode extends LGraphNode { constructor() { super("Interactive") this.addWidget("button", "click me", null, () => {}) const btn = this.widgets![0] btn.onPointerDown = function(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas) { // ── Click ──────────────────────────────────────────────────────────── pointer.onClick = (upEvent) => { console.log("clicked at", pointer.eDown?.canvasX, pointer.eDown?.canvasY) } pointer.onDoubleClick = (upEvent) => { console.log("double-clicked!") } // ── Drag lifecycle ─────────────────────────────────────────────────── let startPos: [number, number] = [0, 0] pointer.onDragStart = (e) => { startPos = [node.pos[0], node.pos[1]] console.log("drag started from", startPos) } pointer.onDrag = (moveEvent) => { // moveEvent.canvasX / canvasY contain the current canvas position } pointer.onDragEnd = (upEvent) => { console.log("drag ended") } // ── Always-run cleanup ─────────────────────────────────────────────── pointer.finally = () => { console.log("interaction complete (click or drag)") } // Returning true prevents Litegraph's own handling of this event return true } } } ``` -------------------------------- ### ProcessorNode Custom Node Source: https://context7.com/comfy-org/litegraph.js/llms.txt Example of extending LGraphNode to create a custom node named 'Processor'. It defines inputs, outputs, properties, widgets, and implements lifecycle callbacks like onExecute, onAction, and onConnectionsChange. ```APIDOC ## ProcessorNode ### Description Extends `LGraphNode` to create a custom node for processing data. This node includes a 'trigger' action input, a 'value' number input, a 'result' number output, and a 'done' event output. It features a configurable 'multiplier' property with a corresponding widget. ### Class Definition ```typescript import { LiteGraph, LGraph, LGraphNode, LGraphEventMode, type INodeInputSlot, type INodeOutputSlot, } from "@comfyorg/litegraph" import type { LLink } from "@comfyorg/litegraph" class ProcessorNode extends LGraphNode { static title = "Processor" static category = "custom" constructor() { super("Processor") // Inputs and outputs (name, type) this.addInput("trigger", LiteGraph.ACTION) this.addInput("value", "number") this.addOutput("result", "number") this.addOutput("done", LiteGraph.EVENT) // Persistent properties (survives serialisation) this.addProperty("multiplier", 2, "number") // Widget linked to the property this.addWidget("number", "multiplier", 2, (v) => { this.properties.multiplier = v }, { min: 1, max: 100, step: 1 }) this.mode = LGraphEventMode.ON_TRIGGER this.color = "#243" } // Called when the node should run onExecute() { const val = this.getInputData(1) as number ?? 0 const result = val * (this.properties.multiplier as number) this.setOutputData(0, result) } // Called when an action event is received on slot 0 onAction(action: string, param: unknown) { console.log("Triggered:", action, param) this.triggerSlot(1, { action, result: this.getOutputData(0) }) } // Callback when a connection changes onConnectionsChange( type: number, index: number, isConnected: boolean, link: LLink | null | undefined, slot: INodeInputSlot | INodeOutputSlot, ) { console.log(`Slot ${index} (${type === 1 ? "input" : "output"}) ${isConnected ? "connected" : "disconnected"}`) } // Add extra right-click menu options getExtraMenuOptions(canvas: any, options: any[]) { options.push({ content: "Reset multiplier", callback: () => this.setProperty("multiplier", 2), }) return options } // Custom serialisation hook onSerialize(o: any) { o.customData = { version: 1 } } onConfigure(info: any) { console.log("Restored customData:", info.customData) } } LiteGraph.registerNodeType("custom/processor", ProcessorNode) ``` ### Programmatic Slot Manipulation Demonstrates dynamic addition and removal of node slots, as well as direct reading and writing of node data. ```typescript const graph = new LGraph() const node = LiteGraph.createNode("custom/processor")! graph.add(node) // Dynamic slots node.addInput("extra", "string") node.addOutput("debug", "string") node.removeInput(node.inputs.length - 1) // Read / write data directly (useful in server-side headless mode) node.setOutputData(0, 99) console.log(node.getOutputData(0)) // 99 ``` ``` -------------------------------- ### LGraphCanvas Initialization and Configuration Source: https://context7.com/comfy-org/litegraph.js/llms.txt Shows how to initialize an LGraphCanvas, attach it to an HTML canvas element, configure visual settings, and manage zoom, pan, and node selection. ```APIDOC ## LGraphCanvas ### Description `LGraphCanvas` is responsible for rendering the graph and handling user interactions like pointer events, keyboard input, and zooming. It is attached to an HTML5 `` element. ### Initialization and Attachment ```typescript import { LiteGraph, LGraph, LGraphCanvas } from "@comfyorg/litegraph" const canvasEl = document.getElementById("myCanvas") as HTMLCanvasElement const graph = new LGraph() // Attach canvas to graph const canvas = new LGraphCanvas(canvasEl, graph) ``` ### Visual Settings Configure the appearance and rendering behavior of the canvas. ```typescript // Visual settings canvas.background_image = "" // optional background image URL canvas.render_shadows = true canvas.render_connection_arrows = false canvas.links_render_mode = 2 // 0=Straight, 1=Linear, 2=Spline ``` ### Zoom and Pan Control Adjust the scale and offset of the graph view. ```typescript // Zoom / pan canvas.ds.changeScale(1.5, [400, 300]) // zoom to 1.5× centred on canvas point canvas.ds.offset = [0, 0] // reset pan ``` ### Node Selection Methods for selecting and deselecting nodes on the canvas. ```typescript // Selection canvas.selectAll() canvas.deselectAllNodes() canvas.selectNode(graph.nodes[0], false) // second arg: add to selection ``` ### Redraw Force the canvas to redraw its contents. ```typescript // Force redraw canvas.setDirty(true, true) // (fg dirty, bg dirty) canvas.draw(true, true) ``` ### Custom Cursor Handling Control cursor appearance based on interaction. ```typescript import { CanvasItem } from "@comfyorg/litegraph" canvas.state.shouldSetCursor = false // take over cursor handling canvas.events.addEventListener("pointer-move", () => { if (canvas.state.hoveringOver & CanvasItem.ResizeSe) canvasEl.style.cursor = "se-resize" }) ``` ### Detach Canvas Remove the canvas from the graph when no longer needed. ```typescript // Detach when done graph.detachCanvas(canvas) ``` ``` -------------------------------- ### Create and Configure a SubgraphNode Instance Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Demonstrates the process of creating a subgraph definition and then instantiating it as a SubgraphNode within a graph. Asserts that the SubgraphNode has the correct number of inputs and outputs, and references the original subgraph definition. ```typescript it("should create and configure a SubgraphNode", () => { // First create the subgraph definition const subgraph = createTestSubgraph({ inputs: [{ name: "value", type: "number" }], outputs: [{ name: "result", type: "number" }] }) // Then create an instance of it const subgraphNode = createTestSubgraphNode(subgraph, { pos: [100, 200], size: [180, 100] }) // The SubgraphNode will have matching slots expect(subgraphNode.inputs).toHaveLength(1) expect(subgraphNode.outputs).toHaveLength(1) expect(subgraphNode.subgraph).toBe(subgraph) }) ``` -------------------------------- ### Server-side Graph Initialization Source: https://github.com/comfy-org/litegraph.js/blob/master/README.md Demonstrates how to initialize and connect nodes in a LiteGraph.js graph on the server-side using NodeJS. Ensure necessary imports are available. ```typescript import { LiteGraph, LGraph } from "./litegraph.js" const graph = new LGraph() const firstNode = LiteGraph.createNode("basic/sum") graph.add(firstNode) const secondNode = LiteGraph.createNode("basic/sum") graph.add(secondNode) firstNode.connect(0, secondNode, 1) graph.start() ``` -------------------------------- ### Serialize and Restore Graphs in LiteGraph.js Source: https://context7.com/comfy-org/litegraph.js/llms.txt Shows how to serialize a graph into a plain object, convert it to JSON, and store it, as well as how to restore a graph from a JSON object or a URL. Includes listening to configuration lifecycle events. ```typescript import { LiteGraph, LGraph } from "@comfyorg/litegraph" const graph = new LGraph() // …add nodes, links, groups… // ── Serialise (new schema v1) ───────────────────────────────────────────────── const plain = graph.asSerialisable({ sortNodes: true }) const json = JSON.stringify(plain, null, 2) localStorage.setItem("workflow", json) // ── Restore ──────────────────────────────────────────────────────────────────── const graph2 = new LGraph() const hasError = graph2.configure(JSON.parse(localStorage.getItem("workflow")!)) if (hasError) console.warn("Some node types were missing; placeholder nodes used.") // ── Load from a URL or File object ──────────────────────────────────────────── graph2.load("/api/workflows/my-workflow.json", () => { console.log("Loaded, nodes:", graph2.nodes.length) }) // ── Listen to configure lifecycle events ───────────────────────────────────── graph2.events.addEventListener("configuring", (e) => { console.log("About to configure, will clear:", e.detail?.clearGraph) }) graph2.events.addEventListener("configured", () => { console.log("Graph fully configured") }) // ── Serialise using the legacy v0.4 schema ──────────────────────────────────── const legacyData = graph2.serialize() console.log(legacyData.version) // 0.4 ``` -------------------------------- ### LiteGraph.js Global Configuration Options Source: https://context7.com/comfy-org/litegraph.js/llms.txt Lists various properties on the `LiteGraph` singleton that control library-wide behavior, including visual grid, node rendering, link rendering, user experience, and deprecation warnings. ```typescript import { LiteGraph } from "@comfyorg/litegraph" // Visual grid & layout LiteGraph.CANVAS_GRID_SIZE = 10 // snap grid cell size in pixels LiteGraph.alwaysSnapToGrid = false // snap on every move, not just modifier key // Node rendering defaults LiteGraph.NODE_TITLE_HEIGHT = 30 LiteGraph.NODE_WIDTH = 140 LiteGraph.NODE_DEFAULT_COLOR = "#333" LiteGraph.NODE_DEFAULT_BGCOLOR = "#353535" LiteGraph.NODE_ERROR_COLOUR = "#E00" LiteGraph.node_box_coloured_when_on = true // colour box indicator on execute LiteGraph.node_box_coloured_by_mode = true // colour box by event mode // Link rendering LiteGraph.LINK_COLOR = "#9A9" LiteGraph.snaps_for_comfy = true // snap links to nearby valid slots LiteGraph.snap_highlights_node = true // UX LiteGraph.use_uuids = false // true = string UUIDs instead of integers LiteGraph.alt_drag_do_clone_nodes = true LiteGraph.ctrl_alt_click_do_break_link = true LiteGraph.macTrackpadGestures = false // treat scroll as trackpad pinch/zoom on Mac LiteGraph.canvasNavigationMode = "standard" // "standard" | "legacy" LiteGraph.saveViewportWithGraph = true // include ds (viewport) in serialised data // Widget text truncation LiteGraph.truncateWidgetTextEvenly = false LiteGraph.truncateWidgetValuesFirst = false // Deprecation warnings LiteGraph.alwaysRepeatWarnings = false LiteGraph.onDeprecationWarning = [console.warn] // Search widget LiteGraph.search_show_all_on_open = true LiteGraph.search_filter_enabled = false ``` -------------------------------- ### Build and Connect Graph Nodes Source: https://context7.com/comfy-org/litegraph.js/llms.txt Creates an LGraph instance, adds custom nodes, connects them, and performs basic graph queries. Ensure nodes are added before connecting them. ```typescript // ── Build a graph ───────────────────────────────────────────────────────────── const graph = new LGraph() const constNode = LiteGraph.createNode("util/const")! const logNode = LiteGraph.createNode("util/log")! graph.add(constNode) graph.add(logNode) // Connect output slot 0 of constNode → input slot 0 of logNode constNode.connect(0, logNode, 0) // ── Query the graph ─────────────────────────────────────────────────────────── console.log(graph.nodes.length) // 2 console.log(graph.links.size) // 1 const found = graph.findNodesByType("util/const") const byId = graph.getNodeById(constNode.id) ``` -------------------------------- ### Register Click, Double Click, and Drag Events Source: https://github.com/comfy-org/litegraph.js/blob/master/API.md Configure `pointer.onClick`, `pointer.onDoubleClick`, `pointer.onDragStart`, `pointer.onDrag`, `pointer.onDragEnd`, and `pointer.finally` to handle various pointer interactions. `finally` is guaranteed to run. ```typescript const { pointer } = this // Click / double click - executed on pointerup pointer.onClick = e => node.executeClick(e) pointer.onDoubleClick = node.gotDoubleClick // Drag events - executed on pointermove pointer.onDragStart = e => { node.isBeingDragged = true canvas.startedDragging(e) } pointer.onDrag = () => {} // finally() is preferred where possible, as it is guaranteed to run pointer.onDragEnd = () => {} // Always run, regardless of outcome pointer.finally = () => (node.isBeingDragged = false) ``` -------------------------------- ### Create and Convert to Subgraph in LiteGraph.js Source: https://context7.com/comfy-org/litegraph.js/llms.txt Demonstrates how to register custom node types, build a root graph, convert selected nodes into a subgraph, and access its definition. This is useful for creating reusable graph components. ```typescript import { LiteGraph, LGraph, LGraphNode } from "@comfyorg/litegraph" // ── Register node types used inside the subgraph ────────────────────────────── class MulNode extends LGraphNode { static title = "Mul" constructor() { super("Mul") this.addInput("a", "number") this.addInput("b", "number") this.addOutput("out", "number") } onExecute() { this.setOutputData(0, (this.getInputData(0) ?? 1) * (this.getInputData(1) ?? 1)) } } LiteGraph.registerNodeType("math/mul", MulNode) // ── Build a root graph ──────────────────────────────────────────────────────── const graph = new LGraph() const n1 = LiteGraph.createNode("math/mul")! const n2 = LiteGraph.createNode("math/mul")! n1.pos = [100, 100] n2.pos = [300, 100] graph.add(n1) graph.add(n2) n1.connect(0, n2, 0) // ── Convert selected nodes to a subgraph ───────────────────────────────────── const items: Set = new Set([n1, n2]) const { subgraph, node: sgNode } = graph.convertToSubgraph(items) console.log(subgraph.name) // "New Subgraph" console.log(sgNode.type) // the subgraph UUID // ── Access the subgraph definition ──────────────────────────────────────────── const definition = graph.subgraphs.get(subgraph.id)! console.log(definition.inputs.length) // number of exposed inputs console.log(definition.outputs.length) // number of exposed outputs // ── Serialise and restore a root graph that includes subgraph definitions ────── const data = graph.asSerialisable() const restored = new LGraph() restored.configure(data) // All subgraph definitions and node instances are restored ``` -------------------------------- ### Utilize Event Capture Fixture Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md The `eventCapture` fixture provides a subgraph with event monitoring. Use it to test event sequences like adding inputs. ```typescript import { subgraphTest } from "./fixtures/subgraphFixtures" subgraphTest("should handle events", ({ eventCapture }) => { const { subgraph, capture } = eventCapture subgraph.addInput("test", "number") expect(capture.events).toHaveLength(2) // adding-input, input-added }) ``` -------------------------------- ### Graph Lifecycle Callbacks Source: https://context7.com/comfy-org/litegraph.js/llms.txt Sets up callback functions for node addition, node removal, and after graph execution. These callbacks allow for custom logic during graph events. ```typescript // ── Lifecycle callbacks ─────────────────────────────────────────────────────── graph.onNodeAdded = (node) => console.log("added:", node.title) graph.onNodeRemoved = (node) => console.log("removed:", node.title) graph.onAfterExecute = () => console.log("step done, time:", graph.globaltime) ``` -------------------------------- ### Use Pre-configured Subgraph Test Fixtures Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Leverage Vitest fixtures like `subgraphTest` for pre-configured subgraph test scenarios, such as `simpleSubgraph` or `eventCapture`. ```typescript // Option 2: Use pre-configured fixtures subgraphTest("should handle events", ({ simpleSubgraph, eventCapture }) => { // simpleSubgraph comes pre-configured with 1 input, 1 output, and 2 nodes expect(simpleSubgraph.inputs).toHaveLength(1) // Your test logic here }) ``` -------------------------------- ### Create and Test a Subgraph Manually Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Use `createTestSubgraph` to manually construct a subgraph for testing. Assert its basic properties like input count. ```typescript import { createTestSubgraph, assertSubgraphStructure } from "./fixtures/subgraphHelpers" import { subgraphTest } from "./fixtures/subgraphFixtures" // Option 1: Create a subgraph manually it("should do something", () => { const subgraph = createTestSubgraph({ name: "My Test Subgraph", inputCount: 2, outputCount: 1 }) // Test your functionality expect(subgraph.inputs).toHaveLength(2) }) ``` -------------------------------- ### Implement Simple Node Click Event Source: https://github.com/comfy-org/litegraph.js/blob/master/API.md Assign a callback to `pointer.onClick` to handle click events on a node. This is executed on `pointerup`. ```typescript if (node.isClickedInSpot(e.canvasX, e.canvasY)) this.pointer.onClick = () => node.gotClickInSpot() ``` -------------------------------- ### Test Event Order with Event Capture Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Use `createEventCapture` to monitor specific events on a subgraph and `verifyEventSequence` to assert the order in which they occur. Remember to clean up listeners with `capture.cleanup()`. ```typescript import { createEventCapture, verifyEventSequence } from "./fixtures/subgraphHelpers" it("should fire events in correct order", () => { const subgraph = createTestSubgraph() const capture = createEventCapture(subgraph.events, ["adding-input", "input-added"]) subgraph.addInput("test", "number") verifyEventSequence(capture.events, ["adding-input", "input-added"]) capture.cleanup() // Important: clean up listeners }) ``` -------------------------------- ### Configure LGraphCanvas for Rendering and Interaction Source: https://context7.com/comfy-org/litegraph.js/llms.txt Initialize LGraphCanvas with a canvas element and a graph. Customize visual settings, zoom/pan behavior, and selection modes. Detach the canvas when no longer needed. ```typescript import { LiteGraph, LGraph, LGraphCanvas } from "@comfyorg/litegraph" const canvasEl = document.getElementById("myCanvas") as HTMLCanvasElement const graph = new LGraph() // ── Attach canvas to graph ──────────────────────────────────────────────────── const canvas = new LGraphCanvas(canvasEl, graph) // ── Visual settings ─────────────────────────────────────────────────────────── canvas.background_image = "" // optional background image URL canvas.render_shadows = true canvas.render_connection_arrows = false canvas.links_render_mode = 2 // 0=Straight, 1=Linear, 2=Spline // ── Zoom / pan ──────────────────────────────────────────────────────────────── canvas.ds.changeScale(1.5, [400, 300]) // zoom to 1.5× centred on canvas point canvas.ds.offset = [0, 0] // reset pan // ── Selection ───────────────────────────────────────────────────────────────── canvas.selectAll() canvas.deselectAllNodes() canvas.selectNode(graph.nodes[0], false) // second arg: add to selection // ── Force redraw ───────────────────────────────────────────────────────────── canvas.setDirty(true, true) // (fg dirty, bg dirty) canvas.draw(true, true) // ── Custom cursor control ───────────────────────────────────────────────────── import { CanvasItem } from "@comfyorg/litegraph" canvas.state.shouldSetCursor = false // take over cursor handling canvas.events.addEventListener("pointer-move", () => { if (canvas.state.hoveringOver & CanvasItem.ResizeSe) canvasEl.style.cursor = "se-resize" }) // ── Detach when done ───────────────────────────────────────────────────────── graph.detachCanvas(canvas) ``` -------------------------------- ### Test Subgraph Input/Output Indexing Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Illustrates the correct method for accessing the index of inputs and outputs within a subgraph. Emphasizes using `indexOf()` on the respective arrays, as items do not have an `index` property. ```typescript // ❌ Wrong expect(input.index).toBe(0) // ✅ Correct expect(subgraph.inputs.indexOf(input)).toBe(0) ``` -------------------------------- ### Load Graph from URL Source: https://context7.com/comfy-org/litegraph.js/llms.txt Loads a graph definition from a specified URL. A callback function can be provided to execute after the loading is complete. ```typescript // ── Load from URL or File ───────────────────────────────────────────────────── graph.load("/path/to/workflow.json", () => console.log("loaded")) ``` -------------------------------- ### Import Widget Type for JSDoc Source: https://github.com/comfy-org/litegraph.js/blob/master/API.md Use JSDoc to import the `IWidget` type for in-IDE typing assistance in JavaScript projects using the @comfyorg/litegraph npm package. ```typescript /** @import { IWidget } from './path/to/@comfyorg/litegraph/litegraph.d.ts' */ /** @type IWidget */ const widget = node.widgets[0] widget.onPointerDown = function (pointer, node, canvas) {} ``` -------------------------------- ### Create and Assert Subgraph Structure Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Use `createTestSubgraph` to generate a subgraph with specified properties and `assertSubgraphStructure` to validate its configuration. ```typescript import { describe, expect, it } from "vitest" import { createTestSubgraph, assertSubgraphStructure } from "./fixtures/subgraphHelpers" describe("My Subgraph Feature", () => { it("should work correctly", () => { const subgraph = createTestSubgraph({ name: "My Test", inputCount: 2, outputCount: 1, nodeCount: 3 }) assertSubgraphStructure(subgraph, { inputCount: 2, outputCount: 1, nodeCount: 3, name: "My Test" }) // Your specific test logic... }) }) ``` -------------------------------- ### Manually Run Graph Step Source: https://context7.com/comfy-org/litegraph.js/llms.txt Updates the execution order of nodes and then manually runs a single step of the graph execution. The 'true' parameter enables step-by-step execution. ```typescript // ── Run one step manually ───────────────────────────────────────────────────── graph.updateExecutionOrder() graph.runStep(1, true) // prints "value: 42" ``` -------------------------------- ### CanvasPointer API Source: https://context7.com/comfy-org/litegraph.js/llms.txt The CanvasPointer API unifies pointer interactions within nodes and widgets, supporting click, double-click, and drag events. ```APIDOC ## CanvasPointer — Click, Double-click, and Drag API `CanvasPointer` unifies all pointer interaction inside nodes and widgets. It is available inside `onPointerDown` widget callbacks and custom canvas handlers. ### Global timing tuning `CanvasPointer.bufferTime` (number): Maximum time in milliseconds to ignore tiny movements. `CanvasPointer.maxClickDrift` (number): Maximum pixel travel before promoting a click to a drag. `CanvasPointer.doubleClickTime` (number): Maximum gap in milliseconds between two presses for a double-click. ### Widget onPointerDown usage When used within a widget's `onPointerDown` callback, `CanvasPointer` provides the following event handlers: - `pointer.onClick(upEvent)`: Callback for a click event. - `pointer.onDoubleClick(upEvent)`: Callback for a double-click event. - `pointer.onDragStart(e)`: Callback when a drag operation begins. - `pointer.onDrag(moveEvent)`: Callback during a drag operation. - `pointer.onDragEnd(upEvent)`: Callback when a drag operation ends. - `pointer.finally()`: Callback that runs after the interaction is complete (click or drag). Returning `true` from `onPointerDown` prevents Litegraph's default handling of the event. ``` -------------------------------- ### Test Subgraph Link Map Size Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Demonstrates the correct way to check the number of links within a subgraph. Links are stored in a Map, so `.size` should be used instead of `.length`. ```typescript // ❌ Wrong expect(subgraph.links.length).toBe(1) // ✅ Correct expect(subgraph.links.size).toBe(1) ``` -------------------------------- ### Create a Custom Processor Node in LiteGraph.js Source: https://context7.com/comfy-org/litegraph.js/llms.txt Extend LGraphNode to define custom node behavior, including inputs, outputs, properties, widgets, and lifecycle callbacks. Register the node type with LiteGraph. ```typescript import { LiteGraph, LGraph, LGraphNode, LGraphEventMode, type INodeInputSlot, type INodeOutputSlot, } from "@comfyorg/litegraph" import type { LLink } from "@comfyorg/litegraph" class ProcessorNode extends LGraphNode { static title = "Processor" static category = "custom" constructor() { super("Processor") // Inputs and outputs (name, type) this.addInput("trigger", LiteGraph.ACTION) this.addInput("value", "number") this.addOutput("result", "number") this.addOutput("done", LiteGraph.EVENT) // Persistent properties (survives serialisation) this.addProperty("multiplier", 2, "number") // Widget linked to the property this.addWidget("number", "multiplier", 2, (v) => { this.properties.multiplier = v }, { min: 1, max: 100, step: 1 }) this.mode = LGraphEventMode.ON_TRIGGER this.color = "#243" } // Called when the node should run onExecute() { const val = this.getInputData(1) as number ?? 0 const result = val * (this.properties.multiplier as number) this.setOutputData(0, result) } // Called when an action event is received on slot 0 onAction(action: string, param: unknown) { console.log("Triggered:", action, param) this.triggerSlot(1, { action, result: this.getOutputData(0) }) } // Callback when a connection changes onConnectionsChange( type: number, index: number, isConnected: boolean, link: LLink | null | undefined, slot: INodeInputSlot | INodeOutputSlot, ) { console.log(`Slot ${index} (${type === 1 ? "input" : "output"}) ${isConnected ? "connected" : "disconnected"}`) } // Add extra right-click menu options getExtraMenuOptions(canvas: any, options: any[]) { options.push({ content: "Reset multiplier", callback: () => this.setProperty("multiplier", 2), }) return options } // Custom serialisation hook onSerialize(o: any) { o.customData = { version: 1 } } onConfigure(info: any) { console.log("Restored customData:", info.customData) } } LiteGraph.registerNodeType("custom/processor", ProcessorNode) // ── Programmatic slot manipulation ──────────────────────────────────────────── const graph = new LGraph() const node = LiteGraph.createNode("custom/processor")! graph.add(node) // Dynamic slots node.addInput("extra", "string") node.addOutput("debug", "string") node.removeInput(node.inputs.length - 1) // Read / write data directly (useful in server-side headless mode) node.setOutputData(0, 99) console.log(node.getOutputData(0)) // 99 ``` -------------------------------- ### Add Various Widget Types to LiteGraph.js Nodes Source: https://context7.com/comfy-org/litegraph.js/llms.txt Demonstrates how to add different types of widgets (number, toggle, combo, text, slider, button) to an LGraphNode. Ensure the widget types and their configurations match your node's requirements. ```typescript import { LiteGraph, LGraph, LGraphNode, NumberWidget, BooleanWidget, ComboWidget, TextWidget, SliderWidget, ButtonWidget, type IWidget, } from "@comfyorg/litegraph" class WidgetShowcase extends LGraphNode { static title = "Widget Showcase" constructor() { super("Widget Showcase") this.addOutput("value", "number") // Number (scroll / drag to change) this.addWidget("number", "speed", 1.0, (v: number) => { this.properties.speed = v }, { min: 0, max: 10, step: 0.1 }) // Toggle this.addWidget("toggle", "enabled", true, (v: boolean) => { this.properties.enabled = v }) // Combo (dropdown) this.addWidget("combo", "mode", "auto", (v: string) => { this.properties.mode = v }, { values: ["auto", "manual", "off"] }) // Text input this.addWidget("text", "label", "hello", (v: string) => { this.properties.label = v }) // Slider (0–100) this.addWidget("slider", "brightness", 50, (v: number) => { this.properties.brightness = v }, { min: 0, max: 100 }) // Button this.addWidget("button", "Reset", null, () => { this.properties.speed = 1.0 if (this.widgets) { for (const w of this.widgets) { if (w.name === "speed") w.value = 1.0 } } }) this.serialize_widgets = true } onExecute() { if (this.properties.enabled) { this.setOutputData(0, this.properties.speed as number) } } } LiteGraph.registerNodeType("demo/widgets", WidgetShowcase) ``` -------------------------------- ### Configure CanvasPointer Timing and Drag Sensitivity Source: https://context7.com/comfy-org/litegraph.js/llms.txt Adjust global timing and sensitivity settings for CanvasPointer to fine-tune click, double-click, and drag behaviors. These properties control how quickly pointer events are processed and how much movement is allowed before an interaction is classified as a drag. ```typescript import { CanvasPointer } from "@comfyorg/litegraph" // ── Global timing tuning ────────────────────────────────────────────────────── CanvasPointer.bufferTime = 150 // ms – maximum time to ignore tiny movements CanvasPointer.maxClickDrift = 6 // px – max travel before promoting to drag CanvasPointer.doubleClickTime = 300 // ms – max gap between two presses for dblclick ``` -------------------------------- ### Configure CanvasPointer Defaults Source: https://github.com/comfy-org/litegraph.js/blob/master/API.md Set static properties on CanvasPointer to adjust default behavior for click drift and double-click timing. ```typescript CanvasPointer.bufferTime = 150 CanvasPointer.maxClickDrift = 6 CanvasPointer.doubleClickTime = 300 ``` -------------------------------- ### Define and Register Custom Nodes Source: https://context7.com/comfy-org/litegraph.js/llms.txt Defines custom node classes (ConstNode, LogNode) inheriting from LGraphNode and registers them with LiteGraph. This is necessary before creating instances of these nodes. ```typescript import { LiteGraph, LGraph, LGraphNode } from "@comfyorg/litegraph" class ConstNode extends LGraphNode { static title = "Const" constructor() { super("Const") this.addOutput("value", "number") this.properties = { value: 42 } } onExecute() { this.setOutputData(0, this.properties.value) } } class LogNode extends LGraphNode { static title = "Log" constructor() { super("Log") this.addInput("value", "number") } onExecute() { console.log("value:", this.getInputData(0)) } } LiteGraph.registerNodeType("util/const", ConstNode) LiteGraph.registerNodeType("util/log", LogNode) ``` -------------------------------- ### Test Deeply Nested Subgraphs Source: https://github.com/comfy-org/litegraph.js/blob/master/test/subgraph/fixtures/README.md Verifies the creation and structure of deeply nested subgraphs. Ensure that the expected number of subgraphs and nodes at the leaf level are correctly generated. ```typescript import { createNestedSubgraphs } from "./fixtures/subgraphHelpers" it("should handle deep nesting", () => { const nested = createNestedSubgraphs({ depth: 5, nodesPerLevel: 2 }) expect(nested.subgraphs).toHaveLength(5) expect(nested.leafSubgraph.nodes).toHaveLength(2) }) ``` -------------------------------- ### LGraphBadge API Source: https://context7.com/comfy-org/litegraph.js/llms.txt The LGraphBadge API enables the addition of small, labelled chips (badges) to nodes for displaying status or information. ```APIDOC ## LGraphBadge — Node Badges Badges are small labelled chips rendered in a corner of a node to display status, type, or icon information. ### Creating Badges Badges are created using the `LGraphBadge` constructor with an options object: `new LGraphBadge(options)` ### Badge Options - `text` (string): The text to display on the badge. - `fgColor` (string): The foreground (text) color. - `bgColor` (string): The background color of the badge. - `fontSize` (number): The font size of the text. - `padding` (number): Padding around the text. - `height` (number): The height of the badge. - `cornerRadius` (number): The radius of the badge's corners. - `iconOptions` (object): Options for displaying an icon: - `unicode` (string): The unicode character for the icon. - `fontFamily` (string): The font family of the icon. - `fontSize` (number): The font size of the icon. - `color` (string): The color of the icon. ### Node Integration Badges are added to a node's `badges` array: `node.badges.push(new LGraphBadge({...}))` ### Badge Positioning - `node.badgePosition` (BadgePosition): Sets the position of the badges on the node. Possible values include `BadgePosition.TopRight`. ```