/** * The graph domain model and its geometry. * * Kept deliberately small and domain-neutral: the library knows about * positioned nodes, edges between them, and a viewport. What a node *means* — * an argument, a task, a service, a scene object — belongs to the application, * exactly as with {@link ITreeNode}. * * This lives in `lib/` rather than `core/services/` so the presentational graph * components can use it; they are barred from importing stores. */ /** A point in graph space. Graph space is unbounded in every direction. */ export interface IPoint { x: number; y: number; } /** A rectangle in graph space, anchored at its top-left corner. */ export interface IRect extends IPoint { width: number; height: number; } /** * The four sides a node exposes for edge attachment. * * Ports are sides rather than discrete handles: an edge picks whichever side * faces its counterpart, which keeps the model small and means a node needs no * per-handle configuration to be connectable. */ export type GraphPort = 'top' | 'right' | 'bottom' | 'left'; /** A node in the graph. */ export interface IGraphNode { /** Stable identifier, unique across the graph. */ id: string; /** Top-left corner in graph space. */ position: IPoint; /** Rendered size. Defaults to {@link DEFAULT_NODE_SIZE} when omitted. */ size?: { width: number; height: number; }; /** * Your own classification, e.g. `"question"`, `"service"`, `"task"`. Never * interpreted by the library — it exists so you can render and validate by * type, and so palette entries and edge rules can target it. */ kind?: string; /** Free-form payload. The library passes it through untouched. */ data?: unknown; } /** A directed edge between two nodes. */ export interface IGraphEdge { /** Stable identifier, unique across the graph. */ id: string; /** Id of the node the edge leaves. */ source: string; /** Id of the node the edge enters. */ target: string; /** Your own classification, e.g. `"supports"`, `"depends-on"`. */ kind?: string; /** Text rendered at the edge's midpoint. */ label?: string; /** Free-form payload. */ data?: unknown; } /** Pan and zoom. `scale` multiplies graph units to screen pixels. */ export interface IViewport { x: number; y: number; scale: number; } export declare const DEFAULT_NODE_SIZE: { readonly width: 180; readonly height: 72; }; export declare const IDENTITY_VIEWPORT: IViewport; /** The rectangle a node occupies, filling in the default size. */ export declare const nodeRect: (node: IGraphNode) => IRect; export declare const rectCenter: (rect: IRect) => IPoint; /** Convert a screen-space point (relative to the canvas element) to graph space. */ export declare const toGraphSpace: (point: IPoint, viewport: IViewport) => IPoint; /** Convert a graph-space point to screen space (relative to the canvas element). */ export declare const toScreenSpace: (point: IPoint, viewport: IViewport) => IPoint; /** * Zoom about a fixed screen point, so the graph position under the cursor stays * under the cursor. Anything else feels wrong to use. */ export declare const zoomAt: (viewport: IViewport, screenPoint: IPoint, nextScale: number) => IViewport; export declare const clampScale: (scale: number, min?: number, max?: number) => number; /** The point on a rectangle's edge for a given side. */ export declare const portPoint: (rect: IRect, port: GraphPort) => IPoint; /** * Choose which sides an edge should leave and enter, based on which direction * dominates. Picking automatically is what lets a node be connectable without * declaring handles. */ export declare const resolvePorts: (from: IRect, to: IRect) => { source: GraphPort; target: GraphPort; }; /** * The bounding box of a set of nodes, or `null` when there are none. * Used to fit the viewport to the graph. */ export declare const graphBounds: (nodes: IGraphNode[]) => IRect | null; /** * The region of graph space currently visible in an element of the given size. * * This is what a minimap draws as its viewport indicator, and what a culling * pass would test against. */ export declare const viewportRect: (viewport: IViewport, size: { width: number; height: number; }) => IRect; /** * A viewport that puts a graph-space point at the centre of the element, * keeping the current zoom. Used for jumping somewhere from a minimap or a * search result. */ export declare const centerOn: (point: IPoint, size: { width: number; height: number; }, scale: number) => IViewport; /** The smallest rectangle containing both inputs. */ export declare const unionRect: (a: IRect, b: IRect) => IRect; /** A viewport that fits `nodes` inside a element of the given size. */ export declare const fitViewport: (nodes: IGraphNode[], size: { width: number; height: number; }, padding?: number) => IViewport; export type Direction = 'up' | 'down' | 'left' | 'right'; /** * The nearest node in a direction, for arrow-key traversal. * * Scores candidates by distance along the travel axis, penalised by how far * they stray off it. Pure distance would jump to a node that is barely in the * right direction but far to the side; requiring perfect alignment would make * most of the graph unreachable. The 2× penalty on lateral offset is the * compromise, and it matches what feels right when navigating a hand-arranged * diagram. * * Returns `null` when nothing lies in that direction. */ export declare const findNeighbour: (nodes: IGraphNode[], fromId: string, direction: Direction) => IGraphNode | null; /** * A free position for a new node placed relative to an existing one, nudged * along `direction` and then away from anything it would overlap. * * Used when creating a node from the keyboard, where there is no cursor to say * where it should go. */ export declare const placeRelativeTo: (nodes: IGraphNode[], fromId: string, direction: Direction, gap?: number) => IPoint; /** Edges touching a node, in either direction. */ export declare const edgesOf: (edges: IGraphEdge[], nodeId: string) => IGraphEdge[]; /** Whether an edge already joins two nodes, in either direction. */ export declare const isConnected: (edges: IGraphEdge[], a: string, b: string) => boolean; /** Remove a node and every edge attached to it. */ export declare const removeNode: (nodes: IGraphNode[], edges: IGraphEdge[], nodeId: string) => { nodes: IGraphNode[]; edges: IGraphEdge[]; }; /** Generate an id that is unique within a collection. */ export declare const nextId: (prefix: string, existing: { id: string; }[]) => string;