ngx-workflow 0.0.1 â 0.1.0
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.
- package/README.md +434 -203
- package/fesm2022/ngx-workflow.mjs +3434 -473
- package/fesm2022/ngx-workflow.mjs.map +1 -1
- package/package.json +2 -1
- package/types/ngx-workflow.d.ts +642 -34
- package/types/ngx-workflow.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -1,291 +1,522 @@
|
|
|
1
1
|
# ngx-workflow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ngx-workflow)
|
|
4
|
+
[](https://github.com/abdulkyume/ngx-workflow/blob/main/LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
A powerful, highly customizable Angular library for building interactive node-based editors, flow charts, and diagrams. Built with Angular Signals for high performance and reactivity.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- đ **Connect Edges** - Connect nodes via handles with bezier/straight/step paths
|
|
9
|
-
- đ **Pan & Zoom** - Navigate large diagrams with mouse wheel zoom and canvas panning
|
|
10
|
-
- đ¨ **Customizable** - Style nodes and edges via CSS variables or custom components
|
|
11
|
-
- âŠī¸ **Undo/Redo** - Full undo/redo support for all diagram changes
|
|
12
|
-
- â¨ī¸ **Keyboard Shortcuts** - Delete, undo (Ctrl+Z), redo (Ctrl+Shift+Z)
|
|
13
|
-
- đĻ **Lasso Selection** - Select multiple nodes (Shift + drag)
|
|
8
|
+

|
|
14
9
|
|
|
15
|
-
##
|
|
10
|
+
## đ Features
|
|
11
|
+
|
|
12
|
+
- **Native Angular**: Built from the ground up for Angular, using Signals and OnPush change detection.
|
|
13
|
+
- **Interactive**: Drag & drop nodes, zoom & pan canvas, connect edges.
|
|
14
|
+
- **Customizable**: Fully custom node and edge templates.
|
|
15
|
+
- **Rich UI**: Built-in minimap, background patterns, controls, and alignment tools.
|
|
16
|
+
- **Layouts**: Automatic layout support via Dagre and ELK.
|
|
17
|
+
- **History**: Robust Undo/Redo history stack.
|
|
18
|
+
- **Export**: Export to JSON, PNG, or SVG.
|
|
19
|
+
- **Theming**: Extensive CSS variables for easy styling.
|
|
20
|
+
|
|
21
|
+
## ⨠New Features (Latest Release)
|
|
22
|
+
|
|
23
|
+
We've added **8 powerful new features** to enhance your workflow experience:
|
|
24
|
+
|
|
25
|
+
1. **Before Delete Hook** - Control deletion with cancellable events
|
|
26
|
+
2. **Z-Index Layer Management** - Keyboard shortcuts + context menu for node stacking
|
|
27
|
+
3. **Connection Limits** - Restrict connections per handle (global or per-handle)
|
|
28
|
+
4. **Edge Label Components** - Use custom Angular components for rich edge labels
|
|
29
|
+
5. **Batch Operations** - `selectAll()`, `alignNodes()`, `distributeNodes()` methods
|
|
30
|
+
6. **Mini-Map Enhancements** - Node colors, selection highlighting, pulse animations
|
|
31
|
+
7. **Node Collision Detection** - Visual feedback when nodes overlap during drag
|
|
32
|
+
8. **Bug Fixes** - Edge interaction & injection context improvements
|
|
33
|
+
|
|
34
|
+
đ **[View complete feature documentation â](./FEATURES.md)**
|
|
35
|
+
|
|
36
|
+
## đĻ Installation
|
|
16
37
|
|
|
17
38
|
```bash
|
|
18
39
|
npm install ngx-workflow
|
|
19
40
|
```
|
|
20
41
|
|
|
21
|
-
## Quick Start
|
|
22
|
-
|
|
23
|
-
### Declarative Approach (Recommended)
|
|
42
|
+
## đ Quick Start
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
### Standalone Component (Recommended)
|
|
26
45
|
|
|
27
46
|
```typescript
|
|
28
47
|
import { Component } from '@angular/core';
|
|
29
|
-
import { Node, Edge } from 'ngx-workflow';
|
|
48
|
+
import { NgxWorkflowModule, Node, Edge } from 'ngx-workflow';
|
|
30
49
|
|
|
31
50
|
@Component({
|
|
32
|
-
selector: 'app-
|
|
51
|
+
selector: 'app-root',
|
|
52
|
+
standalone: true,
|
|
53
|
+
imports: [NgxWorkflowModule],
|
|
33
54
|
template: `
|
|
34
|
-
<div style="width: 100%;
|
|
35
|
-
<ngx-diagram
|
|
55
|
+
<div style="height: 100vh; width: 100%;">
|
|
56
|
+
<ngx-workflow-diagram
|
|
36
57
|
[initialNodes]="nodes"
|
|
37
58
|
[initialEdges]="edges"
|
|
59
|
+
[zIndexMode]="'layered'"
|
|
60
|
+
[preventNodeOverlap]="true"
|
|
38
61
|
(nodeClick)="onNodeClick($event)"
|
|
39
|
-
(
|
|
40
|
-
|
|
62
|
+
(beforeDelete)="onBeforeDelete($event)">
|
|
63
|
+
|
|
64
|
+
<ngx-workflow-minimap [showNodeColors]="true">
|
|
65
|
+
</ngx-workflow-minimap>
|
|
66
|
+
</ngx-workflow-diagram>
|
|
41
67
|
</div>
|
|
42
68
|
`
|
|
43
69
|
})
|
|
44
|
-
export class
|
|
70
|
+
export class AppComponent {
|
|
45
71
|
nodes: Node[] = [
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
position: { x: 50, y: 50 },
|
|
49
|
-
data: { label: 'Start' },
|
|
50
|
-
draggable: true
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: '2',
|
|
54
|
-
position: { x: 300, y: 50 },
|
|
55
|
-
data: { label: 'Process' },
|
|
56
|
-
draggable: true
|
|
57
|
-
}
|
|
72
|
+
{ id: '1', position: { x: 100, y: 100 }, label: 'Start' },
|
|
73
|
+
{ id: '2', position: { x: 300, y: 100 }, label: 'End' }
|
|
58
74
|
];
|
|
59
75
|
|
|
60
76
|
edges: Edge[] = [
|
|
61
|
-
{
|
|
62
|
-
id: 'e1',
|
|
63
|
-
source: '1',
|
|
64
|
-
sourceHandle: 'right',
|
|
65
|
-
target: '2',
|
|
66
|
-
targetHandle: 'left',
|
|
67
|
-
type: 'bezier'
|
|
68
|
-
}
|
|
77
|
+
{ id: 'e1-2', source: '1', target: '2' }
|
|
69
78
|
];
|
|
70
79
|
|
|
71
80
|
onNodeClick(node: Node) {
|
|
72
|
-
console.log('
|
|
81
|
+
console.log('Clicked:', node);
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
id: Date.now().toString(),
|
|
80
|
-
...connection,
|
|
81
|
-
type: 'bezier'
|
|
82
|
-
}];
|
|
84
|
+
onBeforeDelete(event: any) {
|
|
85
|
+
if (!confirm('Delete selected items?')) {
|
|
86
|
+
event.cancel();
|
|
87
|
+
}
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
```
|
|
86
91
|
|
|
87
|
-
|
|
92
|
+
---
|
|
88
93
|
|
|
89
|
-
|
|
94
|
+
## đ Complete API Reference
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
import { NgxFlowModule } from 'ngx-workflow';
|
|
96
|
+
### `<ngx-workflow-diagram>` Inputs
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
imports: [NgxFlowModule],
|
|
96
|
-
})
|
|
97
|
-
export class AppModule {}
|
|
98
|
-
```
|
|
98
|
+
#### Core Configuration
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
| Input | Type | Default | Description |
|
|
101
|
+
|-------|------|---------|-------------|
|
|
102
|
+
| `initialNodes` | `Node[]` | `[]` | Initial nodes array |
|
|
103
|
+
| `initialEdges` | `Edge[]` | `[]` | Initial edges array |
|
|
104
|
+
| `initialViewport` | `Viewport` | `undefined` | Initial viewport `{ x, y, zoom }` |
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
import { Component } from '@angular/core';
|
|
104
|
-
import { DiagramStateService } from 'ngx-workflow';
|
|
106
|
+
#### Display Options
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
export class WorkflowComponent {
|
|
116
|
-
constructor(private diagramState: DiagramStateService) {
|
|
117
|
-
// Add initial nodes
|
|
118
|
-
this.diagramState.addNode({
|
|
119
|
-
id: '1',
|
|
120
|
-
position: { x: 50, y: 50 },
|
|
121
|
-
data: { label: 'Start' },
|
|
122
|
-
draggable: true,
|
|
123
|
-
width: 170,
|
|
124
|
-
height: 60
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
this.diagramState.addNode({
|
|
128
|
-
id: '2',
|
|
129
|
-
position: { x: 300, y: 50 },
|
|
130
|
-
data: { label: 'Process' },
|
|
131
|
-
draggable: true,
|
|
132
|
-
width: 170,
|
|
133
|
-
height: 60
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Add an edge
|
|
137
|
-
this.diagramState.addEdge({
|
|
138
|
-
id: 'e1',
|
|
139
|
-
source: '1',
|
|
140
|
-
sourceHandle: 'right',
|
|
141
|
-
target: '2',
|
|
142
|
-
targetHandle: 'left',
|
|
143
|
-
type: 'bezier'
|
|
144
|
-
});
|
|
145
|
-
}
|
|
108
|
+
| Input | Type | Default | Description |
|
|
109
|
+
|-------|------|---------|-------------|
|
|
110
|
+
| `showZoomControls` | `boolean` | `true` | Show zoom controls (bottom-left) |
|
|
111
|
+
| `showMinimap` | `boolean` | `true` | Show minimap (bottom-right) |
|
|
112
|
+
| `showBackground` | `boolean` | `true` | Show background pattern |
|
|
113
|
+
| `showGrid` | `boolean` | `false` | Show grid overlay |
|
|
114
|
+
| `showExportControls` | `boolean` | `false` | Show export controls |
|
|
115
|
+
| `showLayoutControls` | `boolean` | `false` | Show layout controls |
|
|
146
116
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
117
|
+
#### Background Configuration
|
|
118
|
+
|
|
119
|
+
| Input | Type | Default | Description |
|
|
120
|
+
|-------|------|---------|-------------|
|
|
121
|
+
| `backgroundVariant` | `'dots' \| 'lines' \| 'cross'` | `'dots'` | Background pattern style |
|
|
122
|
+
| `backgroundGap` | `number` | `20` | Gap between pattern elements |
|
|
123
|
+
| `backgroundSize` | `number` | `1` | Size of pattern elements |
|
|
124
|
+
| `backgroundColor` | `string` | `'#81818a'` | Pattern color |
|
|
125
|
+
| `backgroundBgColor` | `string` | `'#f0f0f0'` | Canvas background color |
|
|
126
|
+
|
|
127
|
+
#### Grid Configuration
|
|
128
|
+
|
|
129
|
+
| Input | Type | Default | Description |
|
|
130
|
+
|-------|------|---------|-------------|
|
|
131
|
+
| `gridSize` | `number` | `20` | Grid cell size in pixels |
|
|
132
|
+
| `snapToGrid` | `boolean` | `false` | Snap nodes to grid |
|
|
133
|
+
|
|
134
|
+
#### Node & Edge Behavior
|
|
157
135
|
|
|
158
|
-
|
|
136
|
+
| Input | Type | Default | Description |
|
|
137
|
+
|-------|------|---------|-------------|
|
|
138
|
+
| `nodesResizable` | `boolean` | `true` | Global toggle for node resizing |
|
|
139
|
+
| `nodeDraggable` | `boolean` | `true` | Can nodes be dragged |
|
|
140
|
+
| `edgeReconnectable` | `boolean` | `false` | Can edges be reconnected |
|
|
141
|
+
| `validateConnection` | `Function` | `undefined` | Custom connection validation |
|
|
142
|
+
| `maxConnectionsPerHandle` | `number` | `undefined` | Global connection limit per handle |
|
|
159
143
|
|
|
160
|
-
|
|
144
|
+
#### Z-Index & Layer Management
|
|
161
145
|
|
|
162
|
-
|
|
146
|
+
| Input | Type | Default | Description |
|
|
147
|
+
|-------|------|---------|-------------|
|
|
148
|
+
| `zIndexMode` | `'default' \| 'layered'` | `'default'` | Enable z-index layer management |
|
|
163
149
|
|
|
164
|
-
|
|
165
|
-
- `[initialEdges]` - Initial array of edges to display
|
|
166
|
-
- `[initialViewport]` - Initial viewport state `{ x: number, y: number, zoom: number }`
|
|
150
|
+
#### Collision Detection
|
|
167
151
|
|
|
168
|
-
|
|
152
|
+
| Input | Type | Default | Description |
|
|
153
|
+
|-------|------|---------|-------------|
|
|
154
|
+
| `preventNodeOverlap` | `boolean` | `false` | Enable collision detection |
|
|
155
|
+
| `nodeSpacing` | `number` | `10` | Minimum spacing between nodes (px) |
|
|
169
156
|
|
|
170
|
-
|
|
171
|
-
- `(edgeClick)` - Emitted when an edge is clicked. Payload: `Edge`
|
|
172
|
-
- `(connect)` - Emitted when a new edge is created. Payload: `{ source: string, sourceHandle?: string, target: string, targetHandle?: string }`
|
|
173
|
-
- `(nodesChange)` - Emitted when nodes change. Payload: `Node[]`
|
|
174
|
-
- `(edgesChange)` - Emitted when edges change. Payload: `Edge[]`
|
|
157
|
+
#### Auto-Panning
|
|
175
158
|
|
|
176
|
-
|
|
159
|
+
| Input | Type | Default | Description |
|
|
160
|
+
|-------|------|---------|-------------|
|
|
161
|
+
| `autoPanOnNodeDrag` | `boolean` | `true` | Auto-pan when dragging near edge |
|
|
162
|
+
| `autoPanOnConnect` | `boolean` | `true` | Auto-pan when connecting near edge |
|
|
163
|
+
| `autoPanSpeed` | `number` | `15` | Pan speed (pixels per frame) |
|
|
164
|
+
| `autoPanEdgeThreshold` | `number` | `50` | Distance from edge to trigger (px) |
|
|
177
165
|
|
|
178
|
-
|
|
166
|
+
#### Auto-Save
|
|
179
167
|
|
|
180
|
-
|
|
168
|
+
| Input | Type | Default | Description |
|
|
169
|
+
|-------|------|---------|-------------|
|
|
170
|
+
| `autoSave` | `boolean` | `false` | Enable auto-save |
|
|
171
|
+
| `autoSaveInterval` | `number` | `1000` | Auto-save interval (milliseconds) |
|
|
172
|
+
| `maxVersions` | `number` | `10` | Maximum saved versions |
|
|
181
173
|
|
|
182
|
-
|
|
183
|
-
- `removeNode(nodeId: string): void` - Remove a node
|
|
184
|
-
- `moveNode(nodeId: string, position: XYPosition): void` - Move a node
|
|
185
|
-
- `addEdge(edge: Edge): void` - Add a new edge
|
|
186
|
-
- `removeEdge(edgeId: string): void` - Remove an edge
|
|
187
|
-
- `selectNodes(nodeIds: string[], append?: boolean): void` - Select nodes
|
|
188
|
-
- `undo(): void` - Undo last change
|
|
189
|
-
- `redo(): void` - Redo last undone change
|
|
174
|
+
---
|
|
190
175
|
|
|
191
|
-
|
|
176
|
+
### `<ngx-workflow-diagram>` Outputs
|
|
192
177
|
|
|
193
|
-
|
|
194
|
-
- `edges()` - Current edges array
|
|
195
|
-
- `viewport()` - Current viewport state (x, y, zoom)
|
|
178
|
+
#### Basic Events
|
|
196
179
|
|
|
197
|
-
|
|
180
|
+
| Output | Type | Description |
|
|
181
|
+
|--------|------|-------------|
|
|
182
|
+
| `nodeClick` | `EventEmitter<Node>` | Node clicked |
|
|
183
|
+
| `nodeDoubleClick` | `EventEmitter<Node>` | Node double-clicked |
|
|
184
|
+
| `edgeClick` | `EventEmitter<Edge>` | Edge clicked |
|
|
185
|
+
| `connect` | `EventEmitter<Connection>` | New connection created |
|
|
186
|
+
| `nodesChange` | `EventEmitter<Node[]>` | Nodes array changed |
|
|
187
|
+
| `edgesChange` | `EventEmitter<Edge[]>` | Edges array changed |
|
|
198
188
|
|
|
199
|
-
|
|
200
|
-
- `edgeClick` - Emitted when an edge is clicked
|
|
201
|
-
- `connect` - Emitted when a new edge is created
|
|
202
|
-
- `nodesChange` - Emitted when nodes change
|
|
203
|
-
- `edgesChange` - Emitted when edges change
|
|
189
|
+
#### Mouse Interaction Events
|
|
204
190
|
|
|
205
|
-
|
|
191
|
+
| Output | Type | Description |
|
|
192
|
+
|--------|------|-------------|
|
|
193
|
+
| `nodeMouseEnter` | `EventEmitter<Node>` | Mouse entered node |
|
|
194
|
+
| `nodeMouseLeave` | `EventEmitter<Node>` | Mouse left node |
|
|
195
|
+
| `nodeMouseMove` | `EventEmitter<{node, event}>` | Mouse moved over node |
|
|
196
|
+
| `edgeMouseEnter` | `EventEmitter<Edge>` | Mouse entered edge |
|
|
197
|
+
| `edgeMouseLeave` | `EventEmitter<Edge>` | Mouse left edge |
|
|
198
|
+
|
|
199
|
+
#### Canvas Events
|
|
200
|
+
|
|
201
|
+
| Output | Type | Description |
|
|
202
|
+
|--------|------|-------------|
|
|
203
|
+
| `paneClick` | `EventEmitter<{event, position}>` | Empty canvas clicked |
|
|
204
|
+
| `paneScroll` | `EventEmitter<WheelEvent>` | Canvas scrolled/zoomed |
|
|
205
|
+
| `contextMenu` | `EventEmitter<{type, item, event}>` | Right-click context menu |
|
|
206
|
+
|
|
207
|
+
#### Connection Events
|
|
208
|
+
|
|
209
|
+
| Output | Type | Description |
|
|
210
|
+
|--------|------|-------------|
|
|
211
|
+
| `connectStart` | `EventEmitter<{nodeId, handleId}>` | Connection drag started |
|
|
212
|
+
| `connectEnd` | `EventEmitter<{nodeId, handleId}>` | Connection drag ended |
|
|
213
|
+
| `connectionDrop` | `EventEmitter<{position, event, sourceNodeId, sourceHandleId}>` | Connection dropped |
|
|
214
|
+
|
|
215
|
+
#### Control Events
|
|
216
|
+
|
|
217
|
+
| Output | Type | Description |
|
|
218
|
+
|--------|------|-------------|
|
|
219
|
+
| `beforeDelete` | `EventEmitter<{nodes, edges, cancel}>` | Before deletion (cancellable) |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## đ¯ Interfaces
|
|
224
|
+
|
|
225
|
+
### `Node`
|
|
206
226
|
|
|
207
227
|
```typescript
|
|
208
228
|
interface Node {
|
|
209
|
-
id: string;
|
|
210
|
-
position: { x: number
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
229
|
+
id: string; // Unique identifier
|
|
230
|
+
position: XYPosition; // { x: number, y: number }
|
|
231
|
+
label?: string; // Display label
|
|
232
|
+
type?: string; // 'default', 'group', or custom
|
|
233
|
+
data?: any; // Custom data for your components
|
|
234
|
+
width?: number; // Width in pixels (default: 150)
|
|
235
|
+
height?: number; // Height in pixels (default: 40)
|
|
236
|
+
zIndex?: number; // Stacking order (when zIndexMode='layered')
|
|
237
|
+
draggable?: boolean; // Can be dragged (default: true)
|
|
238
|
+
selectable?: boolean; // Can be selected (default: true)
|
|
239
|
+
connectable?: boolean; // Can connect edges (default: true)
|
|
240
|
+
resizable?: boolean; // Can be resized (default: true)
|
|
241
|
+
selected?: boolean; // Currently selected
|
|
242
|
+
className?: string; // Custom CSS class
|
|
243
|
+
style?: CSSStyleDeclaration; // Inline styles
|
|
244
|
+
parentId?: string; // Parent node ID (for grouping)
|
|
218
245
|
}
|
|
219
246
|
```
|
|
220
247
|
|
|
221
|
-
### Edge
|
|
248
|
+
### `Edge`
|
|
222
249
|
|
|
223
250
|
```typescript
|
|
224
251
|
interface Edge {
|
|
225
|
-
id: string;
|
|
226
|
-
source: string;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
targetHandle?: string;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
id: string; // Unique identifier
|
|
253
|
+
source: string; // Source node ID
|
|
254
|
+
target: string; // Target node ID
|
|
255
|
+
sourceHandle?: string; // Source handle ID
|
|
256
|
+
targetHandle?: string; // Target handle ID
|
|
257
|
+
label?: string; // Label text
|
|
258
|
+
type?: 'bezier' | 'straight' | 'step'; // Path type
|
|
259
|
+
animated?: boolean; // Animated dashed line
|
|
260
|
+
markerEnd?: 'arrow' | 'arrowclosed'; // Arrow type
|
|
261
|
+
selected?: boolean; // Currently selected
|
|
262
|
+
style?: CSSStyleDeclaration; // SVG styles
|
|
263
|
+
data?: any; // Custom data
|
|
233
264
|
}
|
|
234
265
|
```
|
|
235
266
|
|
|
236
|
-
|
|
267
|
+
### `Viewport`
|
|
237
268
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
269
|
+
```typescript
|
|
270
|
+
interface Viewport {
|
|
271
|
+
x: number; // Pan X offset
|
|
272
|
+
y: number; // Pan Y offset
|
|
273
|
+
zoom: number; // Zoom level (1 = 100%)
|
|
274
|
+
}
|
|
275
|
+
```
|
|
241
276
|
|
|
242
|
-
###
|
|
243
|
-
1. Click on a handle (small blue circle at node edges)
|
|
244
|
-
2. Drag to another node's handle
|
|
245
|
-
3. Release to create the connection
|
|
277
|
+
### `Connection`
|
|
246
278
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
279
|
+
```typescript
|
|
280
|
+
interface Connection {
|
|
281
|
+
source: string; // Source node ID
|
|
282
|
+
sourceHandle?: string; // Source handle ID
|
|
283
|
+
target: string; // Target node ID
|
|
284
|
+
targetHandle?: string; // Target handle ID
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## â¨ī¸ Keyboard Shortcuts
|
|
291
|
+
|
|
292
|
+
### Selection & Navigation
|
|
293
|
+
|
|
294
|
+
| Shortcut | Action |
|
|
295
|
+
|----------|--------|
|
|
296
|
+
| `Ctrl/Cmd + A` | Select all nodes |
|
|
297
|
+
| `Ctrl/Cmd + Click` | Multi-select nodes |
|
|
298
|
+
| `Shift + Drag` | Lasso selection |
|
|
299
|
+
| `Escape` | Clear selection |
|
|
300
|
+
|
|
301
|
+
### Editing
|
|
302
|
+
|
|
303
|
+
| Shortcut | Action |
|
|
304
|
+
|----------|--------|
|
|
305
|
+
| `Delete / Backspace` | Delete selected |
|
|
306
|
+
| `Ctrl/Cmd + C` | Copy |
|
|
307
|
+
| `Ctrl/Cmd + V` | Paste |
|
|
308
|
+
| `Ctrl/Cmd + X` | Cut |
|
|
309
|
+
| `Ctrl/Cmd + D` | Duplicate |
|
|
250
310
|
|
|
251
|
-
###
|
|
252
|
-
- Use mouse wheel to zoom in/out
|
|
253
|
-
- Zoom is centered on mouse position
|
|
311
|
+
### History
|
|
254
312
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
- Press Delete to remove selected nodes/edges
|
|
313
|
+
| Shortcut | Action |
|
|
314
|
+
|----------|--------|
|
|
315
|
+
| `Ctrl/Cmd + Z` | Undo |
|
|
316
|
+
| `Ctrl/Cmd + Shift + Z` | Redo |
|
|
260
317
|
|
|
261
|
-
###
|
|
262
|
-
- Ctrl/Cmd + Z to undo
|
|
263
|
-
- Ctrl/Cmd + Shift + Z to redo
|
|
318
|
+
### Z-Index (Layer Management)
|
|
264
319
|
|
|
265
|
-
|
|
320
|
+
| Shortcut | Action |
|
|
321
|
+
|----------|--------|
|
|
322
|
+
| `Ctrl/Cmd + ]` | Bring to front |
|
|
323
|
+
| `Ctrl/Cmd + [` | Send to back |
|
|
324
|
+
| `Ctrl/Cmd + Shift + ]` | Raise layer |
|
|
325
|
+
| `Ctrl/Cmd + Shift + [` | Lower layer |
|
|
266
326
|
|
|
267
|
-
###
|
|
327
|
+
### Grouping
|
|
328
|
+
|
|
329
|
+
| Shortcut | Action |
|
|
330
|
+
|----------|--------|
|
|
331
|
+
| `Ctrl/Cmd + G` | Group selected nodes |
|
|
332
|
+
| `Ctrl/Cmd + Shift + G` | Ungroup |
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## đ¨ Customization
|
|
337
|
+
|
|
338
|
+
### Custom Node Components
|
|
339
|
+
|
|
340
|
+
Create custom node types:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
@Component({
|
|
344
|
+
selector: 'app-custom-node',
|
|
345
|
+
template: `
|
|
346
|
+
<div class="custom-node">
|
|
347
|
+
<h3>{{ node.data.title }}</h3>
|
|
348
|
+
<p>{{ node.data.description }}</p>
|
|
349
|
+
<ngx-workflow-handle type="source" position="right">
|
|
350
|
+
</ngx-workflow-handle>
|
|
351
|
+
<ngx-workflow-handle type="target" position="left">
|
|
352
|
+
</ngx-workflow-handle>
|
|
353
|
+
</div>
|
|
354
|
+
`,
|
|
355
|
+
styles: [`
|
|
356
|
+
.custom-node {
|
|
357
|
+
background: white;
|
|
358
|
+
border: 2px solid #3b82f6;
|
|
359
|
+
border-radius: 8px;
|
|
360
|
+
padding: 12px;
|
|
361
|
+
min-width: 200px;
|
|
362
|
+
}
|
|
363
|
+
`]
|
|
364
|
+
})
|
|
365
|
+
export class CustomNodeComponent {
|
|
366
|
+
@Input() node!: Node;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Register it:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { NGX_WORKFLOW_NODE_TYPES } from 'ngx-workflow';
|
|
374
|
+
|
|
375
|
+
providers: [
|
|
376
|
+
{
|
|
377
|
+
provide: NGX_WORKFLOW_NODE_TYPES,
|
|
378
|
+
useValue: {
|
|
379
|
+
'custom': CustomNodeComponent
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Use it:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
{
|
|
389
|
+
id: '1',
|
|
390
|
+
type: 'custom',
|
|
391
|
+
position: { x: 0, y: 0 },
|
|
392
|
+
data: { title: 'My Node', description: 'Details...' }
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Custom Edge Labels
|
|
397
|
+
|
|
398
|
+
Use Angular components for edge labels:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
<ngx-workflow-diagram [nodes]="nodes" [edges]="edges">
|
|
402
|
+
<ng-template #edgeLabelTemplate let-edge>
|
|
403
|
+
<div class="custom-label">
|
|
404
|
+
<button (click)="editEdge(edge)">âī¸</button>
|
|
405
|
+
<span>{{ edge.label }}</span>
|
|
406
|
+
<span class="badge">{{ edge.data?.priority }}</span>
|
|
407
|
+
</div>
|
|
408
|
+
</ng-template>
|
|
409
|
+
</ngx-workflow-diagram>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Theming
|
|
413
|
+
|
|
414
|
+
Override CSS variables:
|
|
268
415
|
|
|
269
416
|
```css
|
|
270
|
-
:
|
|
417
|
+
:root {
|
|
271
418
|
--ngx-workflow-primary: #3b82f6;
|
|
272
|
-
--ngx-workflow-primary-hover: #2563eb;
|
|
273
419
|
--ngx-workflow-bg: #f8fafc;
|
|
274
|
-
--ngx-workflow-
|
|
275
|
-
--ngx-workflow-
|
|
276
|
-
--ngx-workflow-
|
|
277
|
-
--ngx-workflow-
|
|
278
|
-
--ngx-workflow-
|
|
279
|
-
--ngx-workflow-
|
|
280
|
-
--ngx-workflow-glass-bg: rgba(255, 255, 255, 0.8);
|
|
281
|
-
--ngx-workflow-glass-blur: blur(12px);
|
|
420
|
+
--ngx-workflow-grid-color: #e2e8f0;
|
|
421
|
+
--ngx-workflow-node-bg: #ffffff;
|
|
422
|
+
--ngx-workflow-node-border: #cbd5e1;
|
|
423
|
+
--ngx-workflow-handle-color: #3b82f6;
|
|
424
|
+
--ngx-workflow-edge-stroke: #64748b;
|
|
425
|
+
--ngx-workflow-selection-stroke: #3b82f6;
|
|
282
426
|
}
|
|
283
427
|
```
|
|
284
428
|
|
|
285
|
-
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## đ§ Programmatic API
|
|
432
|
+
|
|
433
|
+
Access via `DiagramStateService`:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
constructor(private diagramState: DiagramStateService) {}
|
|
437
|
+
|
|
438
|
+
// Selection
|
|
439
|
+
this.diagramState.selectAll();
|
|
440
|
+
this.diagramState.deselectAll();
|
|
441
|
+
this.diagramState.selectNodes(['node-1', 'node-2']);
|
|
442
|
+
|
|
443
|
+
// Alignment
|
|
444
|
+
this.diagramState.alignNodes('left');
|
|
445
|
+
// Options: 'left', 'right', 'center', 'top', 'bottom', 'middle'
|
|
446
|
+
|
|
447
|
+
// Distribution
|
|
448
|
+
this.diagramState.distributeNodes('horizontal');
|
|
449
|
+
// Options: 'horizontal', 'vertical'
|
|
450
|
+
|
|
451
|
+
// Deletion
|
|
452
|
+
this.diagramState.deleteAll();
|
|
453
|
+
|
|
454
|
+
// Z-Index
|
|
455
|
+
this.diagramState.bringToFront('node-1');
|
|
456
|
+
this.diagramState.sendToBack('node-1');
|
|
457
|
+
this.diagramState.raiseLayer('node-1');
|
|
458
|
+
this.diagramState.lowerLayer('node-1');
|
|
459
|
+
|
|
460
|
+
// Viewport
|
|
461
|
+
this.diagramState.setViewport({ x: 0, y: 0, zoom: 1 });
|
|
462
|
+
this.diagramState.fitView();
|
|
463
|
+
|
|
464
|
+
// Nodes & Edges
|
|
465
|
+
this.diagramState.addNode(node);
|
|
466
|
+
this.diagramState.updateNode('node-1', { label: 'Updated' });
|
|
467
|
+
this.diagramState.deleteNode('node-1');
|
|
468
|
+
this.diagramState.addEdge(edge);
|
|
469
|
+
this.diagramState.deleteEdge('edge-1');
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## đ Examples
|
|
475
|
+
|
|
476
|
+
### With Connection Limits
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
<ngx-workflow-diagram
|
|
480
|
+
[maxConnectionsPerHandle]="1">
|
|
481
|
+
</ngx-workflow-diagram>
|
|
482
|
+
|
|
483
|
+
// Or per-handle:
|
|
484
|
+
node.data = {
|
|
485
|
+
handleConfig: {
|
|
486
|
+
'output': { maxConnections: 1 }
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### With Collision Detection
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
<ngx-workflow-diagram
|
|
495
|
+
[preventNodeOverlap]="true"
|
|
496
|
+
[nodeSpacing]="10">
|
|
497
|
+
</ngx-workflow-diagram>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### With Before Delete Hook
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
<ngx-workflow-diagram
|
|
504
|
+
(beforeDelete)="onBeforeDelete($event)">
|
|
505
|
+
</ngx-workflow-diagram>
|
|
506
|
+
|
|
507
|
+
onBeforeDelete(event: any) {
|
|
508
|
+
if (!confirm('Delete?')) {
|
|
509
|
+
event.cancel();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## đ¤ Contributing
|
|
286
517
|
|
|
287
|
-
|
|
518
|
+
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
288
519
|
|
|
289
|
-
##
|
|
520
|
+
## đ License
|
|
290
521
|
|
|
291
|
-
|
|
522
|
+
MIT License - see [LICENSE](LICENSE).
|