diff --git a/src/CanvasPointer.ts b/src/CanvasPointer.ts index c433e4f3..d1aa0c97 100644 --- a/src/CanvasPointer.ts +++ b/src/CanvasPointer.ts @@ -68,8 +68,11 @@ export class CanvasPointer { /** The last pointerup event for the primary button */ eUp: CanvasPointerEvent | null = null - /** If set, as soon as the mouse moves outside the click drift threshold, this action is run once. */ - onDragStart?(): unknown + /** + * If set, as soon as the mouse moves outside the click drift threshold, this action is run once. + * @param pointer This pointer instance. Permits actions such as late binding of the finally() callback. + */ + onDragStart?(pointer: this): unknown /** * Called on pointermove whilst dragging. @@ -232,7 +235,7 @@ export class CanvasPointer { #setDragStarted(): void { this.dragStarted = true - this.onDragStart?.() + this.onDragStart?.(this) delete this.onDragStart } diff --git a/src/LGraph.ts b/src/LGraph.ts index c3429d93..5dc9ccae 100644 --- a/src/LGraph.ts +++ b/src/LGraph.ts @@ -1,4 +1,4 @@ -import type { Dictionary, IContextMenuValue, LinkNetwork, ISlotType, MethodNames, Point, LinkSegment } from "./interfaces" +import type { Dictionary, IContextMenuValue, LinkNetwork, ISlotType, MethodNames, Point, LinkSegment, Positionable } from "./interfaces" import type { ISerialisedGraph, Serialisable, SerialisableGraph, SerialisableReroute } from "./types/serialisation" import { Reroute, RerouteId } from "./Reroute" import { LGraphEventMode } from "./types/globalEnums" @@ -9,6 +9,7 @@ import { type NodeId, LGraphNode } from "./LGraphNode" import { type LinkId, LLink } from "./LLink" import { MapProxyHandler } from "./MapProxyHandler" import { isSortaInsideOctagon } from "./measure" +import { getAllNestedItems } from "./utils/collections" interface IGraphInput { name: string @@ -25,6 +26,26 @@ export interface LGraphState { type ParamsArray, K extends MethodNames> = Parameters[1] extends undefined ? Parameters | Parameters[0] : Parameters +/** Configuration used by {@link LGraph} `config`. */ +export interface LGraphConfig { + /** @deprecated Legacy config - unused */ + align_to_grid?: any + /** + * When set to a positive number, when nodes are moved their positions will + * be rounded to the nearest multiple of this value. Half up. + * Default: `undefined` + * @todo Not implemented - see {@link LiteGraph.CANVAS_GRID_SIZE} + */ + snapToGrid?: number + /** + * If `true`, items always snap to the grid - modifier keys are ignored. + * When {@link snapToGrid} is falsy, a value of `1` is used. + * Default: `false` + */ + alwaysSnapToGrid?: boolean + links_ontop?: any +} + /** * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. * supported callbacks: @@ -81,7 +102,7 @@ export class LGraph implements LinkNetwork, Serialisable { filter?: string _subgraph_node?: LGraphNode /** Must contain serialisable values, e.g. primitive types */ - config: { align_to_grid?: any; links_ontop?: any } + config: LGraphConfig vars: Dictionary nodes_executing: boolean[] nodes_actioning: (string | boolean)[] @@ -710,6 +731,12 @@ export class LGraph implements LinkNetwork, Serialisable { if (!node) return const { state } = this + // Ensure created items are snapped + if (this.config.alwaysSnapToGrid) { + const snapTo = this.getSnapToGridSize() + if (snapTo) node.snapToGrid(snapTo) + } + // LEGACY: This was changed from constructor === LGraphGroup //groups if (node instanceof LGraphGroup) { @@ -961,6 +988,36 @@ export class LGraph implements LinkNetwork, Serialisable { } } + /** + * Snaps the provided items to a grid. + * + * Item positions are reounded to the nearest multiple of {@link LiteGraph.CANVAS_GRID_SIZE}. + * + * When {@link config}.{@link LGraphConfig.alwaysSnapToGrid alwaysSnapToGrid} is enabled + * and the grid size is falsy, a default of 1 is used. + * @param items The items to snap to the grid + * @todo Currently only snaps nodes. + */ + snapToGrid(items: Set): void { + const snapTo = this.getSnapToGridSize() + if (!snapTo) return + + getAllNestedItems(items).forEach(item => { + if (!item.pinned) item.snapToGrid(snapTo) + }) + } + + /** + * Finds the size of the grid that items should be snapped to when moved. + * @returns The size of the grid that items should be snapped to + */ + getSnapToGridSize(): number { + // Default to 1 when always snapping + return this.config.alwaysSnapToGrid + ? LiteGraph.CANVAS_GRID_SIZE || 1 + : LiteGraph.CANVAS_GRID_SIZE + } + /** * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution * this replaces the ones using the old version with the new version diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 54263fbf..d0e3fe99 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -8,14 +8,14 @@ import type { LGraph } from "./LGraph" import type { ContextMenu } from "./ContextMenu" import { CanvasItem, EaseFunction, LGraphEventMode, LinkDirection, LinkMarkerShape, LinkRenderType, RenderShape, TitleMode } from "./types/globalEnums" import { LGraphGroup } from "./LGraphGroup" -import { distance, overlapBounding, isPointInRect, findPointOnCurve, containsRect, isInRectangle, createBounds, isInRect } from "./measure" +import { distance, overlapBounding, isPointInRect, findPointOnCurve, containsRect, isInRectangle, createBounds, isInRect, snapPoint } from "./measure" import { drawSlot, LabelPosition } from "./draw" import { DragAndScale } from "./DragAndScale" import { LinkReleaseContextExtended, LiteGraph, clamp } from "./litegraph" import { stringOrEmpty, stringOrNull } from "./strings" import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange" import { Reroute, type RerouteId } from "./Reroute" -import { getAllNestedItems } from "./utils/collections" +import { getAllNestedItems, findFirstNode } from "./utils/collections" import { CanvasPointer } from "./CanvasPointer" interface IShowSearchOptions { @@ -382,6 +382,12 @@ export class LGraphCanvas { SELECTED_NODE: LGraphNode /** @deprecated Panels */ NODEPANEL_IS_OPEN: boolean + + /** Once per frame check of snap to grid value. @todo Update on change. */ + #snapToGrid?: number + /** Set on keydown, keyup. @todo */ + #shiftDown: boolean = false + getMenuOptions?(): IContextMenuValue[] getExtraMenuOptions?(canvas: LGraphCanvas, options: IContextMenuValue[]): IContextMenuValue[] static active_node: LGraphNode @@ -1834,12 +1840,19 @@ export class LGraphCanvas { cloned.pos[0] += 5 cloned.pos[1] += 5 - graph.add(cloned, false) if (this.allow_dragnodes) { + pointer.onDragStart = (pointer) => { + graph.add(cloned, false) + this.#startDraggingItems(cloned, pointer) + } + pointer.onDragEnd = (e) => this.#processDraggedItems(e) + } else { + // TODO: Check if before/after change are necessary here. graph.beforeChange() - this.isDragging = true + graph.add(cloned, false) + graph.afterChange() } - this.processSelect(cloned, e) + return } } @@ -1874,11 +1887,8 @@ export class LGraphCanvas { pointer.onClick = () => this.processSelect(reroute, e) if (!pointer.onDragStart) { - pointer.onDragStart = () => { - this.processSelect(reroute, e, true) - this.isDragging = true - } - pointer.finally = () => this.isDragging = false + pointer.onDragStart = (pointer) => this.#startDraggingItems(reroute, pointer, true) + pointer.onDragEnd = (e) => this.#processDraggedItems(e) } return } @@ -1912,17 +1922,9 @@ export class LGraphCanvas { return } else if (this.reroutesEnabled && e.altKey && !e.shiftKey) { - pointer.finally = () => { - this.emitAfterChange() - this.isDragging = false - } - - this.emitBeforeChange() const newReroute = graph.createReroute([x, y], linkSegment) - pointer.onDragStart = () => { - this.processSelect(newReroute, e) - this.isDragging = true - } + pointer.onDragStart = (pointer) => this.#startDraggingItems(newReroute, pointer) + pointer.onDragEnd = (e) => this.#processDraggedItems(e) return } } else if (isInRectangle(x, y, centre[0] - 4, centre[1] - 4, 8, 8)) { @@ -1951,12 +1953,11 @@ export class LGraphCanvas { const headerHeight = f * 1.4 if (isInRectangle(x, y, group.pos[0], group.pos[1], group.size[0], headerHeight)) { // In title bar - pointer.onDragStart = () => { + pointer.onDragStart = (pointer) => { group.recomputeInsideNodes() - this.processSelect(group, e, true) - this.isDragging = true + this.#startDraggingItems(group, pointer, true) } - pointer.finally = () => this.isDragging = false + pointer.onDragEnd = (e) => this.#processDraggedItems(e) } } @@ -2183,12 +2184,8 @@ export class LGraphCanvas { return // Drag node - pointer.onDragStart = () => { - graph.beforeChange() - this.processSelect(node, e, true) - this.isDragging = true - } - pointer.finally = () => this.isDragging = false + pointer.onDragStart = (pointer) => this.#startDraggingItems(node, pointer, true) + pointer.onDragEnd = (e) => this.#processDraggedItems(e) } this.dirty_canvas = true @@ -2687,6 +2684,45 @@ export class LGraphCanvas { e.preventDefault() return } + + /** + * Start dragging an item, optionally including all other selected items. + * + * ** This function sets the {@link CanvasPointer.finally}() callback. ** + * @param item The item that the drag event started on + * @param pointer The pointer event that initiated the drag, e.g. pointerdown + * @param sticky If `true`, the item is added to the selection - see {@link processSelect} + */ + #startDraggingItems(item: Positionable, pointer: CanvasPointer, sticky = false): void { + this.emitBeforeChange() + this.graph.beforeChange() + // Ensure that dragging is properly cleaned up, on success or failure. + pointer.finally = () => { + this.isDragging = false + this.graph.afterChange() + this.emitAfterChange() + } + + this.processSelect(item, pointer.eDown, sticky) + this.isDragging = true + } + + /** + * Handles shared clean up and placement after items have been dragged. + * @param e The event that completed the drag, e.g. pointerup, pointermove + */ + #processDraggedItems(e: CanvasPointerEvent): void { + const { graph } = this + if (e.shiftKey || graph.config.alwaysSnapToGrid) + graph.snapToGrid(this.selectedItems) + + this.dirty_canvas = true + this.dirty_bgcanvas = true + + // TODO: Replace legacy behaviour: callbacks were never extended for multiple items + this.onNodeMoved?.(findFirstNode(this.selectedItems)) + } + /** * Called when a mouse up event has to be processed **/ @@ -2730,25 +2766,6 @@ export class LGraphCanvas { //left button this.selected_group = null - // Deprecated - old API for backwards compat - if (this.isDragging && this.selectedItems.size === 1) { - const val = this.selectedItems.values().next().value - if (val instanceof LGraphNode) { - this.onNodeMoved?.(val) - graph.afterChange(val) - } - } - if (this.isDragging && LiteGraph.always_round_positions) { - const selected = this.selectedItems - const allItems = getAllNestedItems(selected) - - allItems.forEach(x => { - x.pos[0] = Math.round(x.pos[0]) - x.pos[1] = Math.round(x.pos[1]) - }) - this.dirty_canvas = true - this.dirty_bgcanvas = true - } this.isDragging = false const x = e.canvasX @@ -2988,6 +3005,7 @@ export class LGraphCanvas { * process a key event **/ processKey(e: KeyboardEvent): boolean | null { + this.#shiftDown = e.shiftKey if (!this.graph) return let block_default = false @@ -3812,6 +3830,11 @@ export class LGraphCanvas { ctx.clip() } + // TODO: Set snapping value when changed instead of once per frame + this.#snapToGrid = this.#shiftDown || this.graph.config.alwaysSnapToGrid + ? this.graph.getSnapToGridSize() + : undefined + //clear //canvas.width = canvas.width; if (this.clear_background) { @@ -3844,18 +3867,23 @@ export class LGraphCanvas { //draw nodes const visible_nodes = this.visible_nodes + const drawSnapGuides = this.#snapToGrid && this.isDragging for (let i = 0; i < visible_nodes.length; ++i) { const node = visible_nodes[i] - //transform coords system ctx.save() + + // Draw snap shadow + if (drawSnapGuides && this.selectedItems.has(node)) + this.drawSnapGuide(ctx, node) + + // Localise co-ordinates to node position ctx.translate(node.pos[0], node.pos[1]) - //Draw + // Draw this.drawNode(node, ctx) - //Restore ctx.restore() } @@ -4673,14 +4701,13 @@ export class LGraphCanvas { bgcolor: CanvasColour, selected: boolean ): void { - //bg rect + // Rendering options ctx.strokeStyle = fgcolor ctx.fillStyle = bgcolor const title_height = LiteGraph.NODE_TITLE_HEIGHT const low_quality = this.ds.scale < 0.5 - //render node area depending on shape const shape = node._shape || node.constructor.shape || RenderShape.ROUND const title_mode = node.constructor.title_mode @@ -4696,8 +4723,7 @@ export class LGraphCanvas { const old_alpha = ctx.globalAlpha - //full node shape - //if(node.flags.collapsed) + // Draw node background (shape) { ctx.beginPath() if (shape == RenderShape.BOX || low_quality) { @@ -5004,9 +5030,59 @@ export class LGraphCanvas { ctx.globalAlpha = 1 } + /** + * Draws a snap guide for a {@link Positionable} item. + * + * Initial design was a simple white rectangle representing the location the + * item would land if dropped. + * @param ctx The 2D canvas context to draw on + * @param item The item to draw a snap guide for + * @param snapTo The grid size to snap to + * @todo Update to align snapping with boundingRect + * @todo Shapes + */ + drawSnapGuide(ctx: CanvasRenderingContext2D, item: Positionable, shape = RenderShape.ROUND) { + const snapGuide = LGraphCanvas.#temp + snapGuide.set(item.boundingRect) + + // Not all items have pos equal to top-left of bounds + const { pos } = item + const offsetX = pos[0] - snapGuide[0] + const offsetY = pos[1] - snapGuide[1] + + // Normalise boundingRect to pos to snap + snapGuide[0] += offsetX + snapGuide[1] += offsetY + snapPoint(snapGuide, this.#snapToGrid) + snapGuide[0] -= offsetX + snapGuide[1] -= offsetY + + const { globalAlpha } = ctx + ctx.globalAlpha = 1 + ctx.beginPath() + const [x, y, w, h] = snapGuide + if (shape === RenderShape.CIRCLE) { + const midX = x + (w * 0.5) + const midY = y + (h * 0.5) + const radius = Math.min(w * 0.5, h * 0.5) + ctx.arc(midX, midY, radius, 0, Math.PI * 2) + } else { + ctx.rect(x, y, w, h) + } + + ctx.lineWidth = 0.5 + ctx.strokeStyle = "#FFFFFF66" + ctx.fillStyle = "#FFFFFF22" + ctx.fill() + ctx.stroke() + ctx.globalAlpha = globalAlpha + } + drawConnections(ctx: CanvasRenderingContext2D): void { const rendered = this.renderedPaths rendered.clear() + const visibleReroutes: Reroute[] = [] + const now = LiteGraph.getTime() const visible_area = this.visible_area LGraphCanvas.#margin_area[0] = visible_area[0] - 20 @@ -5079,8 +5155,13 @@ export class LGraphCanvas { for (let j = 0; j < l; j++) { const reroute = reroutes[j] + // Only render once if (!rendered.has(reroute)) { rendered.add(reroute) + visibleReroutes.push(reroute) + reroute._colour = link.color || + LGraphCanvas.link_type_colors[link.type] || + this.default_link_color const prevReroute = this.graph.reroutes.get(reroute.parentId) const startPos = prevReroute?.pos ?? start_node_slotpos @@ -5123,12 +5204,6 @@ export class LGraphCanvas { end_dir, { startControl }, ) - - // Render the reroute circles - const defaultColor = LGraphCanvas.link_type_colors[link.type] || this.default_link_color - for (const reroute of reroutes) { - reroute.draw(ctx, link.color || defaultColor) - } } else { this.renderLink( ctx, @@ -5164,6 +5239,13 @@ export class LGraphCanvas { } } } + + // Render the reroute circles + for (const reroute of visibleReroutes) { + if (this.#snapToGrid && this.isDragging && this.selectedItems.has(reroute)) + this.drawSnapGuide(ctx, reroute, RenderShape.CIRCLE) + reroute.draw(ctx) + } ctx.globalAlpha = 1 } @@ -5834,6 +5916,7 @@ export class LGraphCanvas { ctx.save() ctx.globalAlpha = 0.5 * this.editor_alpha + const drawSnapGuides = this.#snapToGrid && this.isDragging for (let i = 0; i < groups.length; ++i) { const group = groups[i] @@ -5842,6 +5925,10 @@ export class LGraphCanvas { continue } //out of the visible area + // Draw snap shadow + if (drawSnapGuides && this.selectedItems.has(group)) + this.drawSnapGuide(ctx, group) + group.draw(this, ctx) } diff --git a/src/LGraphGroup.ts b/src/LGraphGroup.ts index ae598fb0..3cdadefa 100644 --- a/src/LGraphGroup.ts +++ b/src/LGraphGroup.ts @@ -3,7 +3,7 @@ import type { LGraph } from "./LGraph" import type { ISerialisedGroup } from "./types/serialisation" import { LiteGraph } from "./litegraph" import { LGraphCanvas } from "./LGraphCanvas" -import { containsCentre, containsRect, isInRectangle, isPointInRect, createBounds } from "./measure" +import { containsCentre, containsRect, isInRectangle, isPointInRect, createBounds, snapPoint } from "./measure" import { LGraphNode } from "./LGraphNode" import { RenderShape, TitleMode } from "./types/globalEnums" @@ -192,6 +192,11 @@ export class LGraphGroup implements Positionable, IPinnable { } } + /** @inheritdoc */ + snapToGrid(snapTo: number): boolean { + return this.pinned ? false : snapPoint(this.pos, snapTo) + } + recomputeInsideNodes(): void { const { nodes, reroutes, groups } = this.graph const children = this._children @@ -233,8 +238,8 @@ export class LGraphGroup implements Positionable, IPinnable { * @param padding Value in graph units to add to all sides of the group. Default: 10 */ resizeTo(objects: Iterable, padding: number = 10): void { - const boundingBox = createBounds(objects, padding); - if(boundingBox === null) return + const boundingBox = createBounds(objects, padding) + if (boundingBox === null) return this.pos[0] = boundingBox[0] this.pos[1] = boundingBox[1] - this.titleHeight diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 50e13b18..7e9521c9 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -9,7 +9,7 @@ import type { Reroute, RerouteId } from "./Reroute" import { LGraphEventMode, NodeSlotType, TitleMode, RenderShape } from "./types/globalEnums" import { BadgePosition, LGraphBadge } from "./LGraphBadge" import { type LGraphNodeConstructor, LiteGraph } from "./litegraph" -import { isInRectangle, isInRect } from "./measure" +import { isInRectangle, isInRect, snapPoint } from "./measure" import { LLink } from "./LLink" export type NodeId = number | string @@ -2295,10 +2295,14 @@ export class LGraphNode implements Positionable, IPinnable { return out } - /* Force align to grid */ + /** @inheritdoc */ + snapToGrid(snapTo: number): boolean { + return this.pinned ? false : snapPoint(this.pos, snapTo) + } + + /** @see {@link snapToGrid} */ alignToGrid(): void { - this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE) - this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE) + this.snapToGrid(LiteGraph.CANVAS_GRID_SIZE) } /* Console output */ diff --git a/src/LiteGraphGlobal.ts b/src/LiteGraphGlobal.ts index 2d341830..ac523aef 100644 --- a/src/LiteGraphGlobal.ts +++ b/src/LiteGraphGlobal.ts @@ -132,8 +132,6 @@ export class LiteGraphGlobal { ctrl_alt_click_do_break_link = true // [true!] who accidentally ctrl-alt-clicks on an in/output? nobody! that's who! snaps_for_comfy = true // [true!] snaps links when dragging connections over valid targets snap_highlights_node = true // [true!] renders a partial border to highlight when a dragged link is snapped to a node - /** After moving items on the canvas, their positions will be rounded. Effectively "snap to grid" with a grid size of 1. */ - always_round_positions = false search_hide_on_mouse_leave = true // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false search_filter_enabled = false // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] diff --git a/src/Reroute.ts b/src/Reroute.ts index 10cb5f5d..72bc2004 100644 --- a/src/Reroute.ts +++ b/src/Reroute.ts @@ -73,6 +73,9 @@ export class Reroute implements Positionable, LinkSegment, Serialisable this.#lastRenderTime)) return @@ -241,9 +254,9 @@ export class Reroute implements Positionable, LinkSegment, Serialisable { */ move(deltaX: number, deltaY: number, skipChildren?: boolean): void + /** + * Snaps this item to a grid. + * + * Position values are rounded to the nearest multiple of {@link snapTo}. + * @param snapTo The size of the grid to align to + * @returns `true` if it moved, or `false` if the snap was rejected (e.g. `pinned`) + */ + snapToGrid(snapTo: number): boolean + /** * Cached position & size as `x, y, width, height`. * @readonly See {@link move} diff --git a/src/measure.ts b/src/measure.ts index 94fbad1f..66c2f943 100644 --- a/src/measure.ts +++ b/src/measure.ts @@ -310,4 +310,19 @@ export function createBounds(objects: Iterable, padding: number = bounds[2] - bounds[0] + (2 * padding), bounds[3] - bounds[1] + (2 * padding) ] -} \ No newline at end of file +} + +/** + * Snaps the provided {@link Point} or {@link Rect} ({@link pos}) to a grid of size {@link snapTo}. + * @param pos The point that will be snapped + * @param snapTo The value to round up/down by (multiples thereof) + * @returns `true` if snapTo is truthy, otherwise `false` + * @remarks `NaN` propagates through this function and does not affect return value. + */ +export function snapPoint(pos: Point | Rect, snapTo: number): boolean { + if (!snapTo) return false + + pos[0] = snapTo * Math.round(pos[0] / snapTo) + pos[1] = snapTo * Math.round(pos[1] / snapTo) + return true +} diff --git a/src/types/globalEnums.ts b/src/types/globalEnums.ts index c75fa68a..f38d19fe 100644 --- a/src/types/globalEnums.ts +++ b/src/types/globalEnums.ts @@ -6,13 +6,19 @@ export enum NodeSlotType { /** Shape that an object will render as - used by nodes and slots */ export enum RenderShape { + /** Rectangle with square corners */ BOX = 1, + /** Rounded rectangle */ ROUND = 2, + /** Circle is circle */ CIRCLE = 3, + /** Two rounded corners: top left & bottom right */ CARD = 4, + /** Slot shape: Arrow */ ARROW = 5, - /** intended for slot arrays */ + /** Slot shape: Grid */ GRID = 6, + /** Slot shape: Hollow circle */ HollowCircle = 7, } diff --git a/src/utils/collections.ts b/src/utils/collections.ts index b2e44206..87d0aa02 100644 --- a/src/utils/collections.ts +++ b/src/utils/collections.ts @@ -1,4 +1,5 @@ -import type { Parent } from "../interfaces" +import type { Parent, Positionable } from "../interfaces" +import { LGraphNode } from "@/LGraphNode" /** * Creates a flat set of all items by recursively iterating through all child items. @@ -16,3 +17,14 @@ export function getAllNestedItems>(items: Readon item.children?.forEach(x => addRecursively(x, flatSet)) } } + +/** + * Iterates through a collection of {@link Positionable} items, returning the first {@link LGraphNode}. + * @param items The items to search through + * @returns The first node found in {@link items}, otherwise `undefined` + */ +export function findFirstNode(items: Iterable): LGraphNode | undefined { + for (const item of items) { + if (item instanceof LGraphNode) return item + } +} diff --git a/test/__snapshots__/litegraph.test.ts.snap b/test/__snapshots__/litegraph.test.ts.snap index 052e4a28..e9f35124 100644 --- a/test/__snapshots__/litegraph.test.ts.snap +++ b/test/__snapshots__/litegraph.test.ts.snap @@ -134,7 +134,6 @@ LiteGraphGlobal { "allow_multi_output_for_events": true, "allow_scripts": false, "alt_drag_do_clone_nodes": false, - "always_round_positions": false, "auto_load_slot_types": false, "auto_sort_node_types": false, "catch_exceptions": true, diff --git a/vite.config.mts b/vite.config.mts index afb0e1b1..062df1e6 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -31,7 +31,7 @@ export default defineConfig({ alias: { '@': '/src' }, }, test: { - alias: { '@': '../src' }, + alias: { '@/': path.resolve(__dirname, './src/') }, environment: 'jsdom', }, })