// Type declarations for html-overlay-node // ─── Port / Edge / Node primitives ──────────────────────────────────────────── export type PortType = "exec" | "data"; export type DataType = "any" | "number" | "string" | "boolean" | "object" | "array"; export type EdgeStyle = "bezier" | "orthogonal" | "line"; export type ExecMode = "run" | "step"; export type DockSide = "bottom" | "top" | "left" | "right"; export interface PortDef { id?: string; name: string; portType: PortType; datatype?: DataType; dir?: "in" | "out"; defaultValue?: unknown; } export interface NodeState { [key: string]: unknown; } export interface NodeSize { w: number; h: number; } export interface NodePos { x: number; y: number; } export interface Node { id: string; type: string; title: string; pos: NodePos; size: NodeSize; inputs: PortDef[]; outputs: PortDef[]; state: NodeState; color?: string; icon?: string; [key: string]: unknown; } export interface Edge { id: string; fromNode: string; fromPort: string; toNode: string; toPort: string; } // ─── Graph serialization ─────────────────────────────────────────────────────── export interface GraphMeta { name?: string; description?: string; author?: string; } export interface GraphJSON { version: number; meta?: GraphMeta; nodes: Partial[]; edges: Partial[]; } // ─── Graph ──────────────────────────────────────────────────────────────────── export interface Graph { nodes: Map; edges: Map; hooks: Hooks; runner?: Runner; controller?: Controller; addNode(type: string, options?: Partial): Node; removeNode(nodeId: string): void; getNodeById(nodeId: string): Node | undefined; addEdge(fromNode: string, fromPort: string, toNode: string, toPort: string): Edge; removeEdge(edgeId: string): void; clear(): void; toJSON(): GraphJSON; fromJSON(data: GraphJSON): void; } // ─── Registry ───────────────────────────────────────────────────────────────── export interface NodeExecuteContext { /** Set the value of an output port by port name. */ setOutput(portName: string, value: unknown): void; /** Get the current value of an input port by port name. */ getInput(portName: string): unknown; /** Reference to the parent graph. */ graph: Graph; } export interface NodeHtmlParts { body: HTMLElement; header?: HTMLElement; [key: string]: HTMLElement | undefined; } // ─── Property panel widget system ───────────────────────────────────────────── export type WidgetType = "text" | "number" | "slider" | "toggle" | "select" | "radio" | "checkbox-group" | "color" | "textarea"; export interface SelectOption { label: string; value: unknown; } export interface OnChangeContext { controller: Controller; graph: Graph; /** true during slider drag — no undo entry needed; false on committed change */ immediate: boolean; } export interface PropertyWidget { /** Key in node.state to bind. */ key: string; /** Display label. Defaults to key. */ label?: string; /** Widget type. Default: "text". */ widget?: WidgetType; /** Min value (number, slider). */ min?: number; /** Max value (number, slider). */ max?: number; /** Step (number, slider). */ step?: number; /** Options for select widget. */ options?: (string | SelectOption)[]; /** Placeholder text (text, textarea). */ placeholder?: string; /** Make the widget read-only (display only). */ readonly?: boolean; /** * Called when the value changes. * If omitted, the panel automatically calls controller.updateNodeState (undo-safe). * When provided, you control what happens — call controller.updateNodeState * yourself if you want undo support, or do anything else. */ onChange?(node: Node, value: unknown, ctx: OnChangeContext): void; } export interface NodeHtmlDef { /** Called once when the HTML element is first created. */ init(node: Node, el: HTMLElement, parts: NodeHtmlParts & { graph: Graph }): void; /** Called on every render to sync node state → DOM. */ update?(node: Node, el: HTMLElement, parts: NodeHtmlParts): void; /** Called when the element is removed. */ destroy?(node: Node, el: HTMLElement): void; } export interface NodeDefinition { title?: string; color?: string; icon?: string; size?: { w?: number; h?: number }; inputs?: PortDef[]; outputs?: PortDef[]; /** * Declare property panel widgets for this node type. * Each entry binds a widget to a node.state key. * When present, the property panel renders these instead of the auto-generated state fields. */ properties?: PropertyWidget[]; /** HTML overlay descriptor. If provided, the node renders an HTML body. */ html?: NodeHtmlDef; /** Called once when a node instance is first created. */ onCreate?(node: Node): void; /** Called every runner tick (run mode) or on each step. */ onExecute?(node: Node, ctx: NodeExecuteContext): void; } export interface Registry { /** Register a new node type. Throws if the type is already registered. */ register(type: string, definition: NodeDefinition): void; /** Register or silently replace a node type. Use this to override built-in nodes. */ registerOrReplace(type: string, definition: NodeDefinition): void; /** Remove a single registered type. */ unregister(type: string): void; /** Remove all registered types — use for clean-slate custom-only setups. */ removeAll(): void; get(type: string): NodeDefinition | undefined; has(type: string): boolean; /** The underlying Map of type → definition. */ types: Map; } // ─── Hooks ──────────────────────────────────────────────────────────────────── export type HookName = | "node:create" | "node:move" | "node:click" | "node:dblclick" | "node:resize" | "node:updated" | "edge:create" | "edge:delete" | "graph:serialize" | "graph:deserialize" | "group:change" | "runner:tick" | "runner:start" | "runner:stop" | "error" | string; export interface Hooks { on(event: HookName, handler: (payload?: unknown) => void): void; off(event: HookName, handler: (payload?: unknown) => void): void; emit(event: HookName, payload?: unknown): void; } // ─── Runner ─────────────────────────────────────────────────────────────────── export interface RunOnceResult { connectedNodes: Set; connectedEdges: Set; execEdgeOrder: string[]; } export interface Runner { start(): void; stop(): void; setExecutionMode(mode: ExecMode): void; /** Execute a single node synchronously and return traversal results. */ runOnce(startNodeId: string, dt?: number): RunOnceResult; } // ─── Controller (high-level API) ────────────────────────────────────────────── export interface AddNodeOptions { title?: string; x?: number; y?: number; width?: number; height?: number; state?: NodeState; } export interface Controller { /** The graph being edited. */ graph: Graph; /** When true, graph mutations are blocked; only pan/zoom work. */ readOnly: boolean; /** Whether snap-to-grid is active (toggle with G key). */ snapToGrid: boolean; /** Grid cell size in pixels. Default: 20. */ gridSize: number; /** Add a node (recorded in undo history). */ addNode(type: string, options?: AddNodeOptions): Node; /** Remove a node by ID (recorded in undo history). */ removeNode(nodeId: string): void; /** Add an edge (recorded in undo history). */ addEdge(fromNode: string, fromPort: string, toNode: string, toPort: string): void; /** Update node state (recorded in undo history). */ updateNodeState(nodeId: string, newState: NodeState): void; /** Update a single node property — title, x, y, width, height (recorded in undo history). */ updateNodeProperty(nodeId: string, prop: string, value: unknown): void; /** Pan + zoom so all nodes fit the viewport. */ fitToView(): void; /** Arrange all nodes in a grid layout. */ autoLayout(): void; /** Undo the last action. */ undo(): void; /** Redo the last undone action. */ redo(): void; /** Force a full redraw of all canvas layers and HTML overlay. */ render(): void; } // ─── SubGraphPanel ──────────────────────────────────────────────────────────── export interface SubGraphPanel { /** True when the panel is currently open. */ readonly isOpen: boolean; /** Open the panel for a SubGraph node. */ open(node: Node, subGraphData: GraphJSON, breadcrumb?: string[]): void; /** Close the panel and save edited sub-graph data back to the node. */ close(): void; /** True if the panel is open for the given node ID. */ isOpenFor(nodeId: string): boolean; /** Change which side the panel is docked to. */ setDock(side: DockSide): void; } // ─── Plugin API ─────────────────────────────────────────────────────────────── export interface PluginContext { graph: Graph; registry: Registry; hooks: Hooks; runner: Runner; controller: Controller; contextMenu: ContextMenu; } export interface Plugin { name?: string; options?: Record; install(context: PluginContext, options: Record): void; } // ─── ContextMenu ────────────────────────────────────────────────────────────── export interface ContextMenuItemDef { label: string; action(): void; disabled?: boolean; separator?: boolean; } export interface ContextMenu { addItem(item: ContextMenuItemDef): void; destroy(): void; } // ─── createGraphEditor options ──────────────────────────────────────────────── export interface GraphEditorOptions { /** Color theme. Default: system preference or "dark". */ theme?: "dark" | "light"; /** Custom hooks instance. Created internally if omitted. */ hooks?: Hooks; /** Start the runner automatically after creation. Default: true. */ autorun?: boolean; /** Show the interactive minimap. Default: true. */ showMinimap?: boolean; /** Enable the node property panel (opens on double-click). Default: true. */ enablePropertyPanel?: boolean; /** Container for the property panel. Defaults to the editor container. */ propertyPanelContainer?: HTMLElement | null; /** Show the keyboard shortcut help overlay (?). Default: true. */ enableHelp?: boolean; /** Override shortcut descriptions shown in the help overlay. */ helpShortcuts?: Record | null; /** Wire up the built-in right-click context menu. Default: true. */ setupDefaultContextMenu?: boolean; /** Provide a custom context menu setup function called after creation. */ setupContextMenu?: ((menu: ContextMenu, ctx: { controller: Controller; graph: Graph; hooks: Hooks }) => void) | null; /** Plugins to install. Each must implement `install(ctx, options)`. */ plugins?: Plugin[]; } // ─── GraphEditor (return value of createGraphEditor) ───────────────────────── export interface GraphEditor { /** The data model. */ graph: Graph; /** Main canvas renderer. Use to change edge style or read transform. */ renderer: CanvasRenderer; /** Edge canvas renderer (shares transform with main renderer). */ edgeRenderer: CanvasRenderer; /** High-level controller — use this for all programmatic mutations. */ controller: Controller; /** The execution engine. */ runner: Runner; /** HTML overlay manager. */ htmlOverlay: HtmlOverlay; /** Node type registry. */ registry: Registry; /** Event bus. */ hooks: Hooks; /** Context menu instance. */ contextMenu: ContextMenu; /** Split-pane sub-graph editor panel (primary name). */ subGraphPanel: SubGraphPanel; /** Alias for subGraphPanel. */ subNodePanel: SubGraphPanel; /** Minimap instance (null if showMinimap: false). */ minimap: Minimap | null; /** Property panel instance (null if enablePropertyPanel: false). */ propertyPanel: PropertyPanel | null; /** Force a full redraw. */ render(): void; /** Start the runner. */ start(): void; /** Stop the runner. */ stop(): void; /** Change the edge rendering style. */ setEdgeStyle(style: EdgeStyle): void; /** Switch between "run" (continuous) and "step" (manual) execution mode. */ setExecutionMode(mode: ExecMode): void; /** Add a group programmatically. */ addGroup(args?: Record): void; /** Tear down all event listeners, resize observers, and sub-components. */ destroy(): void; } // ─── Supporting renderer / UI types (opaque) ───────────────────────────────── export interface CanvasRenderer { canvas: HTMLCanvasElement; scale: number; offsetX: number; offsetY: number; setEdgeStyle(style: EdgeStyle): void; resize(width: number, height: number): void; zoomAt(factor: number, cx: number, cy: number): void; } export interface HtmlOverlay { clear(): void; destroy(): void; } export interface Minimap { render(): void; destroy(): void; } export interface PropertyPanel { open(node: Node): void; destroy(): void; } // ─── Main entry point ───────────────────────────────────────────────────────── /** * Create and mount a graph editor into the given container element or CSS selector. * * @example * ```ts * import { createGraphEditor } from "html-overlay-node"; * import { registerAllNodes } from "html-overlay-node/nodes"; * import "html-overlay-node/index.css"; * import "html-overlay-node/src/ui/PropertyPanel.css"; * * const editor = createGraphEditor("#editor", { theme: "dark", showMinimap: true }); * registerAllNodes(editor.registry, editor.hooks); * ``` */ export function createGraphEditor( target: string | HTMLElement | HTMLCanvasElement, options?: GraphEditorOptions ): GraphEditor; // ─── Misc exports ───────────────────────────────────────────────────────────── export interface IconManager { register(name: string, svgPath: string): void; } export { IconManager }; // ─── html-overlay-node/nodes ────────────────────────────────────────────────── // These are re-exported from "html-overlay-node/nodes" sub-path. export declare function registerMathNodes(registry: Registry, hooks?: Hooks): void; export declare function registerLogicNodes(registry: Registry, hooks?: Hooks): void; export declare function registerValueNodes(registry: Registry, hooks?: Hooks): void; export declare function registerUtilNodes(registry: Registry, hooks?: Hooks): void; export declare function registerCoreNodes(registry: Registry, hooks?: Hooks): void; export declare function registerSubGraphNodes(registry: Registry): void; /** Register all built-in node types at once. */ export declare function registerAllNodes(registry: Registry, hooks?: Hooks): void; // ─── html-overlay-node/defaults ─────────────────────────────────────────────── export declare function setupDefaultContextMenu( menu: ContextMenu, ctx: { controller: Controller; graph: Graph; hooks: Hooks } ): void;