@utisha/graph-editor 1.0.5 → 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;
@@ -141,6 +148,8 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
141
148
  height: number;
142
149
  };
143
150
  getEdgePath(edge: GraphEdge): string;
151
+ /** Get the control point offset direction for a port (used by bezier path). */
152
+ private getPortControlOffset;
144
153
  getEdgeColor(edge: GraphEdge): string;
145
154
  getEdgeMarkerEnd(edge: GraphEdge): string | null;
146
155
  getEdgeMarkerStart(edge: GraphEdge): string | null;
@@ -230,6 +239,14 @@ export declare class GraphEditorComponent implements OnInit, OnChanges {
230
239
  private findClosestPort;
231
240
  private getPortWorldPosition;
232
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;
233
250
  static ɵfac: i0.ɵɵFactoryDeclaration<GraphEditorComponent, never>;
234
- 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>;
235
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.5",
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';