@utisha/graph-editor 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
- import { EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core';
1
+ import { EventEmitter, OnChanges, OnInit, SimpleChanges, Type } from '@angular/core';
2
2
  import { Graph, GraphEdge, GraphNode, Position } from './graph.model';
3
3
  import { ContextMenuEvent, GraphEditorConfig, NodeTypeDefinition, SelectionState, ValidationResult } from './graph-editor.config';
4
4
  import { SvgIconDefinition } from './icons/workflow-icons';
5
+ import { NodeHtmlTemplateDirective, NodeSvgTemplateDirective, EdgeTemplateDirective, NodeTemplateContext, EdgeTemplateContext } from './template.directives';
6
+ import { ResolvedTheme } from './theme.resolver';
5
7
  import * as i0 from "@angular/core";
6
8
  /**
7
9
  * Main graph editor component.
@@ -55,7 +57,7 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
55
57
  port: 'top' | 'bottom' | 'left' | 'right';
56
58
  } | null;
57
59
  showAttachmentPoints: import("@angular/core").WritableSignal<string | null>;
58
- activeTool: import("@angular/core").WritableSignal<"hand" | "line">;
60
+ activeTool: import("@angular/core").WritableSignal<"line" | "hand">;
59
61
  private pendingEdge;
60
62
  previewLine: import("@angular/core").WritableSignal<{
61
63
  source: Position;
@@ -80,12 +82,17 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
80
82
  width: number;
81
83
  height: number;
82
84
  }>;
85
+ resolvedTheme: ResolvedTheme;
83
86
  shadowsEnabled: import("@angular/core").Signal<boolean>;
87
+ protected nodeHtmlTemplate: import("@angular/core").Signal<NodeHtmlTemplateDirective | undefined>;
88
+ protected nodeSvgTemplate: import("@angular/core").Signal<NodeSvgTemplateDirective | undefined>;
89
+ protected edgeTemplate: import("@angular/core").Signal<EdgeTemplateDirective | undefined>;
84
90
  selectedEdgeMidpoint: import("@angular/core").Signal<{
85
91
  edge: GraphEdge;
86
92
  x: number;
87
93
  y: number;
88
94
  } | null>;
95
+ private readonly hostEl;
89
96
  constructor();
90
97
  ngOnChanges(changes: SimpleChanges): void;
91
98
  ngOnInit(): void;
@@ -110,6 +117,8 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
110
117
  applyLayout(direction?: 'TB' | 'LR'): Promise<void>;
111
118
  fitToScreen(padding?: number): void;
112
119
  zoomTo(level: number): void;
120
+ zoomIn(): void;
121
+ zoomOut(): void;
113
122
  getSelection(): SelectionState;
114
123
  onCanvasMouseDown(event: MouseEvent): void;
115
124
  onCanvasMouseMove(event: MouseEvent): void;
@@ -139,6 +148,8 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
139
148
  height: number;
140
149
  };
141
150
  getEdgePath(edge: GraphEdge): string;
151
+ /** Get the control point offset direction for a port (used by bezier path). */
152
+ private getPortControlOffset;
142
153
  getEdgeColor(edge: GraphEdge): string;
143
154
  getEdgeMarkerEnd(edge: GraphEdge): string | null;
144
155
  getEdgeMarkerStart(edge: GraphEdge): string | null;
@@ -168,6 +179,11 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
168
179
  * Used to render multiple path elements from a single path string.
169
180
  */
170
181
  splitIconPaths(pathData: string): string[];
182
+ /**
183
+ * Split node types into columns for the palette.
184
+ * When there are too many node types to fit vertically, creates additional columns.
185
+ */
186
+ getPaletteColumns(): NodeTypeDefinition[][];
171
187
  /**
172
188
  * Get the position for the node image (top-left corner of image).
173
189
  * Uses same positioning logic as icon but accounts for image dimensions.
@@ -223,6 +239,14 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
223
239
  private findClosestPort;
224
240
  private getPortWorldPosition;
225
241
  private findClosestPortForEdge;
242
+ /** Get the component type for a node (from NodeTypeDefinition.component, if set). */
243
+ getNodeComponent(node: GraphNode): Type<any> | null;
244
+ /** Build inputs map for ngComponentOutlet when rendering a node's custom component. */
245
+ getNodeComponentInputs(node: GraphNode): Record<string, any>;
246
+ /** Build the template context for custom node templates. */
247
+ getNodeTemplateContext(node: GraphNode): NodeTemplateContext;
248
+ /** Build the template context for custom edge templates. */
249
+ getEdgeTemplateContext(edge: GraphEdge): EdgeTemplateContext;
226
250
  static ɵfac: i0.ɵɵFactoryDeclaration<GraphEditorComponent, never>;
227
- static ɵcmp: i0.ɵɵComponentDeclaration<GraphEditorComponent, "graph-editor", never, { "config": { "alias": "config"; "required": true; }; "graph": { "alias": "graph"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "visualizationMode": { "alias": "visualizationMode"; "required": false; }; "overlayData": { "alias": "overlayData"; "required": false; }; }, { "graphChange": "graphChange"; "nodeAdded": "nodeAdded"; "nodeUpdated": "nodeUpdated"; "nodeRemoved": "nodeRemoved"; "edgeAdded": "edgeAdded"; "edgeUpdated": "edgeUpdated"; "edgeRemoved": "edgeRemoved"; "selectionChange": "selectionChange"; "validationChange": "validationChange"; "nodeClick": "nodeClick"; "nodeDoubleClick": "nodeDoubleClick"; "edgeClick": "edgeClick"; "edgeDoubleClick": "edgeDoubleClick"; "canvasClick": "canvasClick"; "contextMenu": "contextMenu"; }, never, never, true, never>;
251
+ static ɵcmp: i0.ɵɵComponentDeclaration<GraphEditorComponent, "graph-editor", never, { "config": { "alias": "config"; "required": true; }; "graph": { "alias": "graph"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "visualizationMode": { "alias": "visualizationMode"; "required": false; }; "overlayData": { "alias": "overlayData"; "required": false; }; }, { "graphChange": "graphChange"; "nodeAdded": "nodeAdded"; "nodeUpdated": "nodeUpdated"; "nodeRemoved": "nodeRemoved"; "edgeAdded": "edgeAdded"; "edgeUpdated": "edgeUpdated"; "edgeRemoved": "edgeRemoved"; "selectionChange": "selectionChange"; "validationChange": "validationChange"; "nodeClick": "nodeClick"; "nodeDoubleClick": "nodeDoubleClick"; "edgeClick": "edgeClick"; "edgeDoubleClick": "edgeDoubleClick"; "canvasClick": "canvasClick"; "contextMenu": "contextMenu"; }, ["nodeHtmlTemplate", "nodeSvgTemplate", "edgeTemplate"], never, true, never>;
228
252
  }
@@ -207,10 +207,160 @@ export interface ContextMenuContext {
207
207
  * Theme configuration.
208
208
  */
209
209
  export interface ThemeConfig {
210
- /** CSS custom property values */
210
+ /** CSS custom property values (applied to host element) */
211
211
  variables?: Record<string, string>;
212
212
  /** Enable drop shadows on nodes and edges (default: true) */
213
213
  shadows?: boolean;
214
+ /** Canvas theming */
215
+ canvas?: CanvasTheme;
216
+ /** Node theming */
217
+ node?: NodeTheme;
218
+ /** Edge theming */
219
+ edge?: EdgeTheme;
220
+ /** Port/attachment point theming */
221
+ port?: PortTheme;
222
+ /** Selection theming */
223
+ selection?: SelectionTheme;
224
+ /** Font configuration */
225
+ font?: FontTheme;
226
+ /** Toolbar & palette chrome theming */
227
+ toolbar?: ToolbarTheme;
228
+ }
229
+ /**
230
+ * Canvas visual theme.
231
+ */
232
+ export interface CanvasTheme {
233
+ /** Canvas background color (default: '#f8f9fa') */
234
+ background?: string;
235
+ /** Grid pattern type (default: 'line') */
236
+ gridType?: 'line' | 'dot';
237
+ /** Grid line/dot color (default: '#e0e0e0') */
238
+ gridColor?: string;
239
+ }
240
+ /**
241
+ * Node visual theme.
242
+ */
243
+ export interface NodeTheme {
244
+ /** Node background fill (default: 'white') */
245
+ background?: string;
246
+ /** Node border color (default: '#e2e8f0') */
247
+ borderColor?: string;
248
+ /** Node border width in px (default: 1.5) */
249
+ borderWidth?: number;
250
+ /** Node corner radius in px (default: 12) */
251
+ borderRadius?: number;
252
+ /** Border color when selected (default: selection.color) */
253
+ selectedBorderColor?: string;
254
+ /** Border width when selected in px (default: 2.5) */
255
+ selectedBorderWidth?: number;
256
+ /** Shadow color (default: 'rgba(0,0,0,0.08)') */
257
+ shadowColor?: string;
258
+ /** Label text color (default: '#1e293b') */
259
+ labelColor?: string;
260
+ /** Label font family (default: 'system-ui, -apple-system, sans-serif') */
261
+ labelFont?: string;
262
+ /**
263
+ * Per-type visual overrides. Keys are node type identifiers.
264
+ * @example { 'llm-call': { accentColor: '#1D6A96' } }
265
+ */
266
+ typeStyles?: Record<string, NodeTypeStyle>;
267
+ }
268
+ /**
269
+ * Per-node-type visual overrides.
270
+ */
271
+ export interface NodeTypeStyle {
272
+ /** Node background for this type */
273
+ background?: string;
274
+ /** Node border color for this type */
275
+ borderColor?: string;
276
+ /** Accent/header background color */
277
+ accentColor?: string;
278
+ /** Accent/header text color */
279
+ accentTextColor?: string;
280
+ }
281
+ /**
282
+ * Edge visual theme.
283
+ */
284
+ export interface EdgeTheme {
285
+ /** Edge stroke color (default: '#94a3b8') */
286
+ stroke?: string;
287
+ /** Edge stroke width in px (default: 2) */
288
+ strokeWidth?: number;
289
+ /** Edge stroke color when selected (default: selection.color) */
290
+ selectedStroke?: string;
291
+ /** Edge stroke width when selected in px (default: 2.5) */
292
+ selectedStrokeWidth?: number;
293
+ /** Arrow marker fill color (default: '#94a3b8') */
294
+ markerColor?: string;
295
+ /** Arrow marker fill color when selected (default: selection.color) */
296
+ selectedMarkerColor?: string;
297
+ /** Edge path routing algorithm (default: 'straight') */
298
+ pathType?: 'straight' | 'bezier' | 'step';
299
+ }
300
+ /**
301
+ * Port/attachment point visual theme.
302
+ */
303
+ export interface PortTheme {
304
+ /** Port fill color (default: '#94a3b8') */
305
+ fill?: string;
306
+ /** Port border color (default: 'white') */
307
+ stroke?: string;
308
+ /** Port border width in px (default: 2) */
309
+ strokeWidth?: number;
310
+ /** Port radius in px (default: 6) */
311
+ radius?: number;
312
+ /** Port fill color on hover (default: '#2563eb') */
313
+ hoverFill?: string;
314
+ /** Port radius on hover in px (default: 8) */
315
+ hoverRadius?: number;
316
+ }
317
+ /**
318
+ * Selection visual theme.
319
+ */
320
+ export interface SelectionTheme {
321
+ /** Primary selection color — also used as default for node/edge selected states (default: '#3b82f6') */
322
+ color?: string;
323
+ /** Box selection fill (default: 'rgba(59, 130, 246, 0.1)') */
324
+ boxFill?: string;
325
+ /** Box selection stroke (default: selection.color) */
326
+ boxStroke?: string;
327
+ }
328
+ /**
329
+ * Font theme.
330
+ */
331
+ export interface FontTheme {
332
+ /** Primary font family (default: 'system-ui, -apple-system, sans-serif') */
333
+ family?: string;
334
+ /** Monospace font family (default: 'monospace') */
335
+ monoFamily?: string;
336
+ }
337
+ /**
338
+ * Toolbar & palette chrome theme.
339
+ * Controls the top toolbar, left palette, and edge direction selector.
340
+ */
341
+ export interface ToolbarTheme {
342
+ /** Panel background (default: 'rgba(255, 255, 255, 0.95)') */
343
+ background?: string;
344
+ /** Panel border radius in px (default: 8) */
345
+ borderRadius?: number;
346
+ /** Panel box shadow (default: '0 2px 8px rgba(0, 0, 0, 0.1)') */
347
+ shadow?: string;
348
+ /** Button background (default: '#ffffff') */
349
+ buttonBackground?: string;
350
+ /** Button border color (default: '#e5e7eb') */
351
+ buttonBorderColor?: string;
352
+ /** Button text/icon color (default: '#4b5563') */
353
+ buttonTextColor?: string;
354
+ /** Button hover background (default: '#f9fafb') */
355
+ buttonHoverBackground?: string;
356
+ /** Button hover border & text color — matches selection.color by default */
357
+ buttonHoverAccent?: string;
358
+ /** Active (pressed) tool button background (default: selection.color) */
359
+ buttonActiveBackground?: string;
360
+ /** Active tool button text color (default: '#ffffff') */
361
+ buttonActiveTextColor?: string;
362
+ /** Divider line color between button groups (default: '#e5e7eb') */
363
+ dividerColor?: string;
214
364
  }
215
365
  /**
216
366
  * Palette configuration.
@@ -0,0 +1,116 @@
1
+ import { TemplateRef } from '@angular/core';
2
+ import { GraphNode, GraphEdge } from './graph.model';
3
+ import { NodeTypeDefinition } from './graph-editor.config';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Context provided to node templates (both HTML and SVG).
7
+ *
8
+ * Usage:
9
+ * ```html
10
+ * <ng-template geNodeHtml let-ctx>
11
+ * <div>{{ ctx.node.data.name }}</div>
12
+ * </ng-template>
13
+ * ```
14
+ */
15
+ export interface NodeTemplateContext {
16
+ $implicit: {
17
+ /** The raw node data */
18
+ node: GraphNode;
19
+ /** The node type definition from config */
20
+ type: NodeTypeDefinition;
21
+ /** Whether this node is currently selected */
22
+ selected: boolean;
23
+ /** Current node width (respects resize) */
24
+ width: number;
25
+ /** Current node height (respects resize) */
26
+ height: number;
27
+ };
28
+ }
29
+ /**
30
+ * Context provided to edge templates.
31
+ *
32
+ * Usage:
33
+ * ```html
34
+ * <ng-template geEdge let-ctx>
35
+ * <svg:path [attr.d]="ctx.path" stroke="red" />
36
+ * </ng-template>
37
+ * ```
38
+ */
39
+ export interface EdgeTemplateContext {
40
+ $implicit: {
41
+ /** The raw edge data */
42
+ edge: GraphEdge;
43
+ /** Computed SVG path string */
44
+ path: string;
45
+ /** Whether this edge is currently selected */
46
+ selected: boolean;
47
+ };
48
+ }
49
+ /**
50
+ * Marks an `<ng-template>` as a custom HTML node renderer.
51
+ * Content is rendered inside an `<svg:foreignObject>` — write standard HTML/CSS.
52
+ *
53
+ * @example
54
+ * ```html
55
+ * <graph-editor [config]="config" [graph]="graph">
56
+ * <ng-template geNodeHtml let-ctx>
57
+ * <div class="my-node" [class.selected]="ctx.selected">
58
+ * <div class="header">{{ ctx.type.label }}</div>
59
+ * <div class="body">{{ ctx.node.data.name }}</div>
60
+ * </div>
61
+ * </ng-template>
62
+ * </graph-editor>
63
+ * ```
64
+ */
65
+ export declare class NodeHtmlTemplateDirective {
66
+ templateRef: TemplateRef<NodeTemplateContext>;
67
+ static ngTemplateContextGuard(_dir: NodeHtmlTemplateDirective, _ctx: unknown): _ctx is NodeTemplateContext;
68
+ static ɵfac: i0.ɵɵFactoryDeclaration<NodeHtmlTemplateDirective, never>;
69
+ static ɵdir: i0.ɵɵDirectiveDeclaration<NodeHtmlTemplateDirective, "ng-template[geNodeHtml]", never, {}, {}, never, never, true, never>;
70
+ }
71
+ /**
72
+ * Marks an `<ng-template>` as a custom SVG node renderer.
73
+ * Content is rendered inside an `<svg:g>` — use `svg:` prefixed elements.
74
+ *
75
+ * @example
76
+ * ```html
77
+ * <graph-editor [config]="config" [graph]="graph">
78
+ * <ng-template geNodeSvg let-ctx>
79
+ * <svg:rect [attr.width]="ctx.width" [attr.height]="ctx.height"
80
+ * rx="8" fill="white" stroke="#ccc" />
81
+ * <svg:text x="10" y="24">{{ ctx.node.data.name }}</svg:text>
82
+ * </ng-template>
83
+ * </graph-editor>
84
+ * ```
85
+ *
86
+ * **Important:** All SVG elements inside the template MUST use the `svg:` prefix
87
+ * (e.g. `<svg:rect>`, `<svg:text>`, `<svg:g>`).
88
+ */
89
+ export declare class NodeSvgTemplateDirective {
90
+ templateRef: TemplateRef<NodeTemplateContext>;
91
+ static ngTemplateContextGuard(_dir: NodeSvgTemplateDirective, _ctx: unknown): _ctx is NodeTemplateContext;
92
+ static ɵfac: i0.ɵɵFactoryDeclaration<NodeSvgTemplateDirective, never>;
93
+ static ɵdir: i0.ɵɵDirectiveDeclaration<NodeSvgTemplateDirective, "ng-template[geNodeSvg]", never, {}, {}, never, never, true, never>;
94
+ }
95
+ /**
96
+ * Marks an `<ng-template>` as a custom edge renderer.
97
+ * Content is rendered inside an `<svg:g>` — use `svg:` prefixed elements.
98
+ * The library still handles the invisible hit-area and endpoint circles.
99
+ *
100
+ * @example
101
+ * ```html
102
+ * <graph-editor [config]="config" [graph]="graph">
103
+ * <ng-template geEdge let-ctx>
104
+ * <svg:path [attr.d]="ctx.path"
105
+ * [attr.stroke]="ctx.selected ? 'blue' : 'gray'"
106
+ * stroke-width="2" fill="none" />
107
+ * </ng-template>
108
+ * </graph-editor>
109
+ * ```
110
+ */
111
+ export declare class EdgeTemplateDirective {
112
+ templateRef: TemplateRef<EdgeTemplateContext>;
113
+ static ngTemplateContextGuard(_dir: EdgeTemplateDirective, _ctx: unknown): _ctx is EdgeTemplateContext;
114
+ static ɵfac: i0.ɵɵFactoryDeclaration<EdgeTemplateDirective, never>;
115
+ static ɵdir: i0.ɵɵDirectiveDeclaration<EdgeTemplateDirective, "ng-template[geEdge]", never, {}, {}, never, never, true, never>;
116
+ }
@@ -0,0 +1,78 @@
1
+ import { ThemeConfig } from './graph-editor.config';
2
+ /**
3
+ * Fully-resolved theme with no optional fields.
4
+ * Every value has a sensible default so templates can reference without null-checking.
5
+ */
6
+ export interface ResolvedTheme {
7
+ shadows: boolean;
8
+ canvas: {
9
+ background: string;
10
+ gridType: 'line' | 'dot';
11
+ gridColor: string;
12
+ };
13
+ node: {
14
+ background: string;
15
+ borderColor: string;
16
+ borderWidth: number;
17
+ borderRadius: number;
18
+ selectedBorderColor: string;
19
+ selectedBorderWidth: number;
20
+ shadowColor: string;
21
+ labelColor: string;
22
+ labelFont: string;
23
+ typeStyles: Record<string, {
24
+ background?: string;
25
+ borderColor?: string;
26
+ accentColor?: string;
27
+ accentTextColor?: string;
28
+ }>;
29
+ };
30
+ edge: {
31
+ stroke: string;
32
+ strokeWidth: number;
33
+ selectedStroke: string;
34
+ selectedStrokeWidth: number;
35
+ markerColor: string;
36
+ selectedMarkerColor: string;
37
+ pathType: 'straight' | 'bezier' | 'step';
38
+ };
39
+ port: {
40
+ fill: string;
41
+ stroke: string;
42
+ strokeWidth: number;
43
+ radius: number;
44
+ hoverFill: string;
45
+ hoverRadius: number;
46
+ };
47
+ selection: {
48
+ color: string;
49
+ boxFill: string;
50
+ boxStroke: string;
51
+ };
52
+ font: {
53
+ family: string;
54
+ monoFamily: string;
55
+ };
56
+ toolbar: {
57
+ background: string;
58
+ borderRadius: number;
59
+ shadow: string;
60
+ buttonBackground: string;
61
+ buttonBorderColor: string;
62
+ buttonTextColor: string;
63
+ buttonHoverBackground: string;
64
+ buttonHoverAccent: string;
65
+ buttonActiveBackground: string;
66
+ buttonActiveTextColor: string;
67
+ dividerColor: string;
68
+ };
69
+ }
70
+ /**
71
+ * Resolves a partial ThemeConfig into a complete ResolvedTheme with all defaults filled.
72
+ */
73
+ export declare function resolveTheme(theme?: ThemeConfig): ResolvedTheme;
74
+ /**
75
+ * Applies resolved theme values as CSS custom properties on a host element.
76
+ * This enables consumer templates to use `var(--ge-*)` in their CSS.
77
+ */
78
+ export declare function applyThemeCssProperties(host: HTMLElement, t: ResolvedTheme, userVars?: Record<string, string>): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utisha/graph-editor",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Configuration-driven visual graph editor for Angular 19+",
5
5
  "author": "Utisha",
6
6
  "license": "MIT",
package/public-api.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  export { GraphEditorComponent } from './lib/graph-editor.component';
2
2
  export type { Graph, GraphNode, GraphEdge, Position, NodeMetadata, EdgeMetadata, EdgeStyle, GraphMetadata } from './lib/graph.model';
3
- export type { GraphEditorConfig, NodesConfig, EdgesConfig, CanvasConfig, ValidationConfig, LayoutConfig, InteractionConfig, ThemeConfig, PaletteConfig, NodeTypeDefinition, PortConfig, PortDefinition, NodeConstraints, GridConfig, ZoomConfig, PanConfig, ValidationRule, ValidationError, LayoutOptions, ContextMenuConfig, ContextMenuItem, ContextMenuContext, SelectionState, ValidationResult, ContextMenuEvent } from './lib/graph-editor.config';
3
+ export type { GraphEditorConfig, NodesConfig, EdgesConfig, CanvasConfig, ValidationConfig, LayoutConfig, InteractionConfig, ThemeConfig, PaletteConfig, NodeTypeDefinition, PortConfig, PortDefinition, NodeConstraints, GridConfig, ZoomConfig, PanConfig, ValidationRule, ValidationError, LayoutOptions, ContextMenuConfig, ContextMenuItem, ContextMenuContext, SelectionState, ValidationResult, ContextMenuEvent, CanvasTheme, NodeTheme, NodeTypeStyle, EdgeTheme, PortTheme, SelectionTheme, FontTheme, ToolbarTheme } from './lib/graph-editor.config';
4
4
  export type { SvgIconDefinition } from './lib/icons/workflow-icons';
5
5
  export { renderIconSvg, iconToDataUrl } from './lib/icons/workflow-icons';
6
+ export { NodeHtmlTemplateDirective, NodeSvgTemplateDirective, EdgeTemplateDirective } from './lib/template.directives';
7
+ export type { NodeTemplateContext, EdgeTemplateContext } from './lib/template.directives';
8
+ export type { ResolvedTheme } from './lib/theme.resolver';