import { type DrawLayer } from "./render/canvas2d.js"; import { type Panel } from "./render/panelLayer.js"; import { type NodeHtmlOverlay } from "./render/overlayLayer.js"; import { type Edge, type Graph, type Side } from "./core/graph.js"; import { type RenderGraph } from "./core/lod.js"; import { type DenseLayoutOptions, type LayoutOptions } from "./core/layout.js"; import { type RgRule } from "./core/rule.js"; import { type RgTheme, type RgThemeInput } from "./core/theme.js"; import type { SummarizeFn } from "./core/summary.js"; import { type ViewTransform } from "./core/grid.js"; export interface RguiOptions { /** the node graph to render (mutated in place by dragging) */ graph?: Graph; /** customize every readability threshold for your use case */ rule?: Partial; /** * chrome colors (default "dark"): "dark" | "light" | a partial palette * over a base ({ base: "light", accent: "#e91e63" }). Both built-ins keep * the mascot field-arrow pair — purple ⊙ toward the viewer, gold ⊗ away. * Swap live with viewer.setTheme(). */ theme?: RgThemeInput; /** element to render live debug info into (grid px / scale / pos / size) */ debug?: HTMLElement | null; /** extra draw layers rendered between the grid and the graph */ layers?: DrawLayer[]; /** initial view; default centers world origin in the viewport */ view?: ViewTransform; /** called after each rendered frame */ onFrame?: (view: ViewTransform, rg: RenderGraph | null) => void; /** fires on every grid-snapped position change during a drag (unthrottled) */ onNodeMove?: (nodeId: string, pos: { x: number; y: number; }) => void; /** fires once per node when a drag ends; pseudo drags fire per member */ onNodeMoveEnd?: (nodeId: string, pos: { x: number; y: number; }) => void; /** gate for interactive edge creation (port-to-port drag) */ isValidConnection?: (from: PortRef, to: PortRef) => boolean; /** fires when a valid port-to-port drag completes; host owns graph mutation */ onConnect?: (from: PortRef, to: PortRef) => void; /** * SNAP-CONNECT (default on): snapping two nodes flush so that an output * edge faces a compatible input edge wires them together; pulling them * apart cuts the wire. The derived edges carry `temp: true` and are * recomputed from geometry on every move — a cheap, reversible way to * build a pipeline by pushing blocks together. Set false to disable. */ snapConnect?: boolean; /** * fires when the snap-connected (temp) edge set changes — the argument is * the full current set, so a host can mirror it into its own state */ onSnapConnectChange?: (edges: Edge[]) => void; /** plain click on a node (no drag movement) */ onNodeClick?: (nodeId: string, screen: { x: number; y: number; }) => void; /** right-click (mouse) or long-press (touch) on a node. When the node is * part of a multi-selection, `nodeIds` carries the WHOLE selection so the * host can show one shared menu for all of it; otherwise it is just the * pressed node. */ onNodeContextMenu?: (nodeId: string, screen: { x: number; y: number; }, nodeIds?: string[]) => void; /** selection changed (click select, shift+drag box select, setSelection) */ onSelectionChange?: (nodeIds: string[]) => void; /** a node's pin was toggled via the header glyph */ onPinChange?: (nodeId: string, pinned: boolean) => void; /** * fires during a corner-grip resize (grid-snapped, overlap-clamped). * `scale` is the node's content scale — it only moves in the shift-drag * RESCALE mode, where the node magnifies instead of reflowing. */ onNodeResize?: (nodeId: string, size: { w: number; h: number; scale: number; x?: number; y?: number; }) => void; /** fires once when a corner-grip resize ends */ onNodeResizeEnd?: (nodeId: string, size: { w: number; h: number; scale: number; x?: number; y?: number; }) => void; /** * screen-anchored palettes/panels drawn as canvas chrome — items support * click-to-add (Panel.onItemClick) and drag-onto-canvas (Panel.onItemDrop) */ panels?: Panel[]; /** * a panel was moved by a header drag (fires on release): its anchor is * now an explicit screen position — persist it (e.g. localStorage) and * pass it back via Panel.anchor on the next run. While dragging, panels * snap to the viewport margins and flush against other panels; flush * boundaries dissolve like snapped nodes. */ onPanelMove?: (panel: Panel, anchor: { x: number; y: number; }) => void; /** * a panel header was clicked, toggling its collapsed state — persist it * and pass it back via Panel.collapsed on the next run */ onPanelToggle?: (panel: Panel, collapsed: boolean) => void; /** * summarize rule: when a node is too small for its fields ("small") or * nodes merge into a pseudo-node ("pseudo"), rgui asks for compact * host-defined content and renders it screen-constant. Return null to * fall back to defaults. */ summarize?: SummarizeFn; /** right-click (no drag) on empty canvas */ onCanvasContextMenu?: (screen: { x: number; y: number; }, world: { x: number; y: number; }) => void; /** * rendering backend (default "auto"): "webgpu" renders the background + * grid field on a GPU underlay canvas (graph content stays 2D on top); * falls back to "canvas2d" when WebGPU is unavailable */ renderer?: "auto" | "canvas2d" | "webgpu"; /** * cap the canvas backing-store scale (default: device pixel ratio). * Raster cost grows with dpr² — 1.5 is a good perf/sharpness trade on * retina displays with busy pages. */ maxDpr?: number; /** * canvas background fill; false = transparent (page background shows * through — lets hosts layer DOM behind the graph) */ background?: string | false; /** * input preset (default "figma"): * - figma: 2-finger scroll = pan · pinch / ctrl+wheel / mouse wheel = zoom · * plain or right drag on empty = box select · space+drag / middle drag = pan * - classic: wheel = zoom · plain drag on empty = pan · shift+drag = box select */ input?: "figma" | "classic"; /** * keyboard navigation (default true), modelled on CapsLockX's cursor accel: * WASD pans, R/F zoom in/out (time-based acceleration — hold to speed up), * N/P (or Tab / Shift+Tab) cycle focus between nodes, and ? toggles a * shortcuts panel. Keys act only while the pointer is over the canvas (so a * host app's own hotkeys keep working elsewhere) and never while typing in * an input/textarea. Set false to disable entirely. */ keyboard?: boolean; /** * keyboard pan / zoom acceleration rates (units per first-second of hold), * fed to the CapsLockX AccModel. Defaults mirror CapsLockX: pan 1600, zoom * 1600. Larger = faster. */ keyboardSpeed?: { pan?: number; zoom?: number; }; /** plain click on a wire */ onEdgeClick?: (edge: Edge, screen: { x: number; y: number; }) => void; /** right-click / context-menu on a wire */ onEdgeContextMenu?: (edge: Edge, screen: { x: number; y: number; }) => void; /** * wire drag released on empty canvas (no valid target port) — open a * "create node here" palette and wire it up yourself */ onConnectEnd?: (from: PortRef, at: { screen: { x: number; y: number; }; world: { x: number; y: number; }; }) => void; /** right-button drag from a node body: host resolves smart port matches */ onSmartLinkEnd?: (fromNodeId: string, at: { screen: { x: number; y: number; }; world: { x: number; y: number; }; targetNodeId?: string; }) => void; } /** reference to one port of one node */ export interface PortRef { node: string; port: string; side: "in" | "out"; } export interface Rgui { canvas: HTMLCanvasElement; readonly view: ViewTransform; readonly rule: RgRule; /** active backend ("webgpu" once the GPU pipeline is live) */ readonly rendererKind: "canvas2d" | "webgpu"; /** viewport roll in radians (about the viewport center) */ readonly rotation: number; /** full 3-D orientation of the graph plane */ readonly rotation3: { yaw: number; pitch: number; roll: number; }; /** rotate the whole viewport in-plane (roll only; see setRotation3) */ setRotation(rad: number, opts?: { animate?: boolean; }): void; /** * orient the graph plane in 3-D (orthographic): yaw/pitch tilt it, * foreshortened nodes visually converge and the LOD merges them — * a pure rendering trick, base positions never change */ setRotation3(target: { yaw?: number; pitch?: number; roll?: number; }, opts?: { animate?: boolean; }): void; graph: Graph; setGraph(g: Graph): void; /** selected node ids (click to select, shift+drag to box-select) */ readonly selection: string[]; setSelection(nodeIds: string[]): void; /** programmatic viewport control (syncs d3-zoom state). `animate` glides * to the target (easeInOut pan + log-space zoom) instead of hard-switching — * use it for focus/goto jumps so the user keeps spatial context. Any user * zoom/pan gesture mid-flight cancels the glide. */ setView(view: ViewTransform, opts?: { animate?: boolean; durationMs?: number; }): void; /** fit all nodes into the viewport with the given screen-px padding */ fitView(paddingPx?: number): void; /** fit one node into the viewport with the given screen-px padding */ fitNode(nodeId: string, paddingPx?: number): void; /** * screen position of a port as currently laid out (flush-snap aware) — * null if the node/port is missing or collapsed into a pseudo-node. * `hidden` = dissolved into a flush stack (not drawn, not hittable). */ portScreenPos(nodeId: string, portId: string, side: "in" | "out"): { x: number; y: number; edge: Side; hidden: boolean; } | null; /** replace the panel set (host mutates panels + calls this or invalidate) */ setPanels(panels: Panel[]): void; /** lightweight runtime debug snapshot for host/e2e inspection */ debugPanels(): { count: number; ids: string[]; rects: { id: string; x: number; y: number; w: number; h: number; items: number; }[]; }; /** * attach/replace/remove a node-anchored HTML overlay at runtime * (declarative alternative: set GraphNode.overlay before rendering) */ setNodeOverlay(nodeId: string, overlay: HTMLElement | NodeHtmlOverlay | null): void; /** * programmatic resize (for nodes that want to size themselves) — snapped * to minimums and clamped against neighbors (一格一物), then re-rendered */ resizeNode(nodeId: string, size: { w?: number; h?: number; }): void; /** * programmatic RESCALE: magnify the node about its top-left corner, * w/h ratio preserved and every interior metric scaled with it — the * shift+grip drag's endpoint, reachable from code. * * RELATIVE, not an absolute setter: the factor applied is * `scale / contentScale(node)`. Calling rescaleNode(id, 2) on a node * whose `scale` you already set to 2 by hand is a NO-OP (factor 1) and * leaves the box unscaled. Rescale from a node still at its base scale. */ rescaleNode(nodeId: string, scale: number): void; /** * Auto-layout by connection optimization. The default layered mode keeps * pinned nodes fixed. Dense mode contracts direct chains, snaps positions * AND sizes, and relayouts the whole workflow. Animates ~300ms, then fires * persistence callbacks for every changed position/size. */ autoLayout(opts?: AutoLayoutOptions): void; /** * snap every node — POSITION and SIZE — to the MAIN visible grid at the * current scale; one call makes a generated/imported graph obey the snap * rule. Fires onNodeMoveEnd/onNodeResizeEnd per changed node (host * broadcast) unless silent. */ snapGraph(opts?: { silent?: boolean; }): void; /** * screen midpoint of a wire's bezier as currently drawn — null if the * wire is dissolved (inside a flush stack) or an endpoint is collapsed. */ edgeMidScreen(edge: { from: { node: string; port: string; }; to: { node: string; port: string; }; }): { x: number; y: number; } | null; /** the resolved active theme (live object — do not mutate; use setTheme) */ readonly theme: RgTheme; /** swap the chrome palette live: "dark" | "light" | partial over a base */ setTheme(theme: RgThemeInput): void; /** * change rg-rule fields live (radix, readability thresholds…). Existing * node sizes are left alone — they were snapped under the OLD radix, and * re-snapping them would skew any rescaled node's ratio; call snapGraph() * afterwards to re-seat the graph on the new lattice. */ setRule(rule: Partial): void; /** request a re-render on the next animation frame */ invalidate(): void; destroy(): void; } export type AutoLayoutOptions = { animate?: boolean; } & (({ mode?: "layered"; } & LayoutOptions) | ({ mode: "dense"; } & DenseLayoutOptions)); export declare function createRgui(canvas: HTMLCanvasElement, options?: RguiOptions): Rgui; //# sourceMappingURL=rgui.d.ts.map