ngx-vflow 0.2.2 → 0.4.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.
Files changed (60) hide show
  1. package/esm2022/lib/vflow/components/edge/edge.component.mjs +18 -6
  2. package/esm2022/lib/vflow/components/edge-label/edge-label.component.mjs +13 -10
  3. package/esm2022/lib/vflow/components/handle/handle.component.mjs +21 -24
  4. package/esm2022/lib/vflow/components/node/node.component.mjs +69 -20
  5. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +35 -22
  6. package/esm2022/lib/vflow/decorators/microtask.decorator.mjs +11 -0
  7. package/esm2022/lib/vflow/decorators/run-in-injection-context.decorator.mjs +18 -0
  8. package/esm2022/lib/vflow/directives/handle-size-controller.directive.mjs +38 -0
  9. package/esm2022/lib/vflow/directives/map-context.directive.mjs +31 -4
  10. package/esm2022/lib/vflow/directives/root-svg-context.directive.mjs +1 -1
  11. package/esm2022/lib/vflow/directives/selectable.directive.mjs +39 -0
  12. package/esm2022/lib/vflow/interfaces/flow-entity.interface.mjs +2 -0
  13. package/esm2022/lib/vflow/interfaces/template-context.interface.mjs +1 -1
  14. package/esm2022/lib/vflow/models/edge.model.mjs +17 -11
  15. package/esm2022/lib/vflow/models/handle.model.mjs +32 -3
  16. package/esm2022/lib/vflow/models/node.model.mjs +11 -33
  17. package/esm2022/lib/vflow/services/edge-changes.service.mjs +7 -3
  18. package/esm2022/lib/vflow/services/flow-entities.service.mjs +6 -2
  19. package/esm2022/lib/vflow/services/flow-settings.service.mjs +25 -0
  20. package/esm2022/lib/vflow/services/handle.service.mjs +3 -4
  21. package/esm2022/lib/vflow/services/node-changes.service.mjs +7 -3
  22. package/esm2022/lib/vflow/services/node-rendering.service.mjs +22 -0
  23. package/esm2022/lib/vflow/services/selection.service.mjs +45 -0
  24. package/esm2022/lib/vflow/types/edge-change.type.mjs +1 -1
  25. package/esm2022/lib/vflow/types/node-change.type.mjs +1 -1
  26. package/esm2022/lib/vflow/utils/add-nodes-to-edges.mjs +3 -3
  27. package/esm2022/lib/vflow/utils/resizable.mjs +11 -0
  28. package/esm2022/lib/vflow/vflow.module.mjs +11 -3
  29. package/esm2022/public-api.mjs +2 -1
  30. package/fesm2022/ngx-vflow.mjs +536 -253
  31. package/fesm2022/ngx-vflow.mjs.map +1 -1
  32. package/lib/vflow/components/edge/edge.component.d.ts +5 -2
  33. package/lib/vflow/components/handle/handle.component.d.ts +6 -4
  34. package/lib/vflow/components/node/node.component.d.ts +13 -6
  35. package/lib/vflow/components/vflow/vflow.component.d.ts +13 -7
  36. package/lib/vflow/decorators/microtask.decorator.d.ts +1 -0
  37. package/lib/vflow/decorators/run-in-injection-context.decorator.d.ts +5 -0
  38. package/lib/vflow/directives/handle-size-controller.directive.d.ts +10 -0
  39. package/lib/vflow/directives/map-context.directive.d.ts +5 -0
  40. package/lib/vflow/directives/selectable.directive.d.ts +11 -0
  41. package/lib/vflow/interfaces/flow-entity.interface.d.ts +4 -0
  42. package/lib/vflow/interfaces/template-context.interface.d.ts +1 -0
  43. package/lib/vflow/models/edge.model.d.ts +7 -3
  44. package/lib/vflow/models/handle.model.d.ts +30 -2
  45. package/lib/vflow/models/node.model.d.ts +8 -11
  46. package/lib/vflow/services/edge-changes.service.d.ts +5 -0
  47. package/lib/vflow/services/flow-entities.service.d.ts +5 -2
  48. package/lib/vflow/services/flow-settings.service.d.ts +20 -0
  49. package/lib/vflow/services/handle.service.d.ts +3 -6
  50. package/lib/vflow/services/node-changes.service.d.ts +5 -0
  51. package/lib/vflow/services/node-rendering.service.d.ts +9 -0
  52. package/lib/vflow/services/selection.service.d.ts +19 -0
  53. package/lib/vflow/types/edge-change.type.d.ts +5 -1
  54. package/lib/vflow/types/node-change.type.d.ts +5 -1
  55. package/lib/vflow/utils/resizable.d.ts +3 -0
  56. package/lib/vflow/vflow.module.d.ts +5 -3
  57. package/package.json +3 -3
  58. package/public-api.d.ts +1 -0
  59. package/esm2022/lib/vflow/models/flow.model.mjs +0 -18
  60. package/lib/vflow/models/flow.model.d.ts +0 -16
@@ -1,13 +1,14 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { signal, Injectable, inject, ElementRef, Directive, effect, Input, TemplateRef, computed, EventEmitter, Output, untracked, Injector, Component, ChangeDetectionStrategy, ViewChild, HostListener, runInInjectionContext, ContentChild, NgModule } from '@angular/core';
4
+ import { signal, Injectable, inject, ElementRef, Directive, computed, effect, Input, TemplateRef, EventEmitter, Output, untracked, runInInjectionContext, Injector, NgZone, Component, ChangeDetectionStrategy, ViewChild, HostListener, ContentChild, NgModule } from '@angular/core';
5
5
  import { select } from 'd3-selection';
6
6
  import { zoomIdentity, zoom } from 'd3-zoom';
7
+ import { Subject, tap, switchMap, merge, skip, map, pairwise, filter, distinctUntilChanged, observeOn, asyncScheduler, zip, Observable, Subscription, startWith, fromEvent } from 'rxjs';
8
+ import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
7
9
  import { drag } from 'd3-drag';
8
- import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
9
- import { switchMap, merge, skip, map, pairwise, filter, observeOn, asyncScheduler, zip, distinctUntilChanged, tap, fromEvent } from 'rxjs';
10
10
  import { path } from 'd3-path';
11
+ import { __decorate } from 'tslib';
11
12
 
12
13
  class ViewportService {
13
14
  constructor() {
@@ -57,13 +58,123 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
57
58
  }]
58
59
  }] });
59
60
 
61
+ class ConnectionModel {
62
+ constructor(connection) {
63
+ this.connection = connection;
64
+ this.curve = connection.curve ?? 'bezier';
65
+ this.type = connection.type ?? 'default';
66
+ this.validator = connection.validator ?? (() => true);
67
+ }
68
+ }
69
+
70
+ function hashCode(str) {
71
+ return str.split('').reduce((a, b) => {
72
+ a = ((a << 5) - a) + b.charCodeAt(0);
73
+ return a & a;
74
+ }, 0);
75
+ }
76
+
77
+ class FlowEntitiesService {
78
+ constructor() {
79
+ this.nodes = signal([], {
80
+ // empty arrays considered equal, other arrays may not be equal
81
+ equal: (a, b) => !a.length && !b.length ? true : a === b
82
+ });
83
+ this.edges = signal([], {
84
+ // empty arrays considered equal, other arrays may not be equal
85
+ equal: (a, b) => !a.length && !b.length ? true : a === b
86
+ });
87
+ this.connection = signal(new ConnectionModel({}));
88
+ this.markers = computed(() => {
89
+ const markersMap = new Map();
90
+ this.validEdges().forEach(e => {
91
+ if (e.edge.markers?.start) {
92
+ const hash = hashCode(JSON.stringify(e.edge.markers.start));
93
+ markersMap.set(hash, e.edge.markers.start);
94
+ }
95
+ if (e.edge.markers?.end) {
96
+ const hash = hashCode(JSON.stringify(e.edge.markers.end));
97
+ markersMap.set(hash, e.edge.markers.end);
98
+ }
99
+ });
100
+ const connectionMarker = this.connection().connection.marker;
101
+ if (connectionMarker) {
102
+ const hash = hashCode(JSON.stringify(connectionMarker));
103
+ markersMap.set(hash, connectionMarker);
104
+ }
105
+ return markersMap;
106
+ });
107
+ this.validEdges = computed(() => {
108
+ const nodes = this.nodes();
109
+ return this.edges().filter(e => nodes.includes(e.source()) && nodes.includes(e.target()));
110
+ });
111
+ this.entities = computed(() => [
112
+ ...this.nodes(),
113
+ ...this.edges()
114
+ ]);
115
+ }
116
+ getNode(id) {
117
+ return this.nodes().find(({ node }) => node.id === id);
118
+ }
119
+ getDetachedEdges() {
120
+ return this.edges().filter(e => e.detached());
121
+ }
122
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
123
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService }); }
124
+ }
125
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService, decorators: [{
126
+ type: Injectable
127
+ }] });
128
+
129
+ class SelectionService {
130
+ constructor() {
131
+ this.flowEntitiesService = inject(FlowEntitiesService);
132
+ this.viewport$ = new Subject();
133
+ this.resetSelection = this.viewport$.pipe(tap(({ start, end, target }) => {
134
+ if (start && end) {
135
+ const delta = SelectionService.delta;
136
+ const diffX = Math.abs(end.x - start.x);
137
+ const diffY = Math.abs(end.y - start.y);
138
+ // click (not drag)
139
+ const isClick = diffX < delta && diffY < delta;
140
+ // do not reset if event chain contains selectable elems
141
+ const isNotSelectable = !target.closest('.selectable');
142
+ if (isClick && isNotSelectable) {
143
+ this.select(null);
144
+ }
145
+ }
146
+ }), takeUntilDestroyed()).subscribe();
147
+ }
148
+ static { this.delta = 6; }
149
+ setViewport(viewport) {
150
+ this.viewport$.next(viewport);
151
+ }
152
+ select(entity) {
153
+ // undo select for previously selected nodes
154
+ this.flowEntitiesService.entities()
155
+ .filter(n => n.selected)
156
+ .forEach(n => n.selected.set(false));
157
+ if (entity) {
158
+ // select passed entity
159
+ entity.selected.set(true);
160
+ }
161
+ }
162
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
163
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService }); }
164
+ }
165
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectionService, decorators: [{
166
+ type: Injectable
167
+ }] });
168
+
60
169
  class MapContextDirective {
61
170
  constructor() {
62
171
  this.rootSvg = inject(RootSvgReferenceDirective).element;
63
172
  this.host = inject(ElementRef).nativeElement;
173
+ this.selectionService = inject(SelectionService);
64
174
  this.viewportService = inject(ViewportService);
65
175
  this.rootSvgSelection = select(this.rootSvg);
66
176
  this.zoomableSelection = select(this.host);
177
+ this.viewportForSelection = {};
67
178
  // under the hood this effect triggers handleZoom, so error throws without this flag
68
179
  // TODO: hack with timer fixes wrong node scaling (handle positions not matched with content size)
69
180
  this.manualViewportChangeEffect = effect(() => setTimeout(() => {
@@ -90,15 +201,32 @@ class MapContextDirective {
90
201
  }), { allowSignalWrites: true });
91
202
  this.handleZoom = ({ transform }) => {
92
203
  // update public signal for user to read
93
- this.viewportService.readableViewport.set({ zoom: transform.k, x: transform.x, y: transform.y });
204
+ this.viewportService.readableViewport.set(mapTransformToViewportState(transform));
94
205
  this.zoomableSelection.attr('transform', transform.toString());
95
206
  };
96
207
  }
97
208
  ngOnInit() {
98
209
  this.zoomBehavior = zoom()
99
210
  .scaleExtent([this.minZoom, this.maxZoom])
100
- .on('zoom', this.handleZoom);
101
- this.rootSvgSelection.call(this.zoomBehavior);
211
+ .on('start', (event) => this.onD3zoomStart(event))
212
+ .on('zoom', (event) => this.handleZoom(event))
213
+ .on('end', (event) => this.onD3zoomEnd(event));
214
+ this.rootSvgSelection
215
+ .call(this.zoomBehavior)
216
+ .on('dblclick.zoom', null);
217
+ }
218
+ onD3zoomStart({ transform }) {
219
+ this.viewportForSelection = {
220
+ start: mapTransformToViewportState(transform)
221
+ };
222
+ }
223
+ onD3zoomEnd({ transform, sourceEvent }) {
224
+ this.viewportForSelection = {
225
+ ...this.viewportForSelection,
226
+ end: mapTransformToViewportState(transform),
227
+ target: evTarget(sourceEvent)
228
+ };
229
+ this.selectionService.setViewport(this.viewportForSelection);
102
230
  }
103
231
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MapContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
104
232
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: MapContextDirective, selector: "g[mapContext]", inputs: { minZoom: "minZoom", maxZoom: "maxZoom" }, ngImport: i0 }); }
@@ -111,6 +239,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
111
239
  }], maxZoom: [{
112
240
  type: Input
113
241
  }] } });
242
+ const mapTransformToViewportState = (transform) => ({ zoom: transform.k, x: transform.x, y: transform.y });
243
+ const evTarget = (anyEvent) => {
244
+ if (anyEvent instanceof Event && anyEvent.target instanceof Element) {
245
+ return anyEvent.target;
246
+ }
247
+ return undefined;
248
+ };
114
249
 
115
250
  const round = (num) => Math.round(num * 100) / 100;
116
251
 
@@ -237,28 +372,11 @@ function addNodesToEdges(nodes, edges) {
237
372
  return acc;
238
373
  }, {});
239
374
  edges.forEach(e => {
240
- e.source = nodesById[e.edge.source];
241
- e.target = nodesById[e.edge.target];
375
+ e.source.set(nodesById[e.edge.source]);
376
+ e.target.set(nodesById[e.edge.target]);
242
377
  });
243
378
  }
244
379
 
245
- class FlowModel {
246
- constructor() {
247
- /**
248
- * Global setting with handle positions. Nodes derive this value
249
- *
250
- * @deprecated
251
- */
252
- this.handlePositions = signal({ source: 'right', target: 'left' });
253
- /**
254
- * @see {VflowComponent.view}
255
- */
256
- this.view = signal([400, 400]);
257
- this.flowWidth = computed(() => this.view() === 'auto' ? '100%' : this.view()[0]);
258
- this.flowHeight = computed(() => this.view() === 'auto' ? '100%' : this.view()[1]);
259
- }
260
- }
261
-
262
380
  class FlowStatusService {
263
381
  constructor() {
264
382
  this.status = signal({ state: 'idle', payload: null });
@@ -296,70 +414,6 @@ function batchStatusChanges(...changes) {
296
414
  }
297
415
  }
298
416
 
299
- class ConnectionModel {
300
- constructor(connection) {
301
- this.connection = connection;
302
- this.curve = connection.curve ?? 'bezier';
303
- this.type = connection.type ?? 'default';
304
- this.validator = connection.validator ?? (() => true);
305
- }
306
- }
307
-
308
- function hashCode(str) {
309
- return str.split('').reduce((a, b) => {
310
- a = ((a << 5) - a) + b.charCodeAt(0);
311
- return a & a;
312
- }, 0);
313
- }
314
-
315
- class FlowEntitiesService {
316
- constructor() {
317
- this.nodes = signal([], {
318
- // empty arrays considered equal, other arrays may not be equal
319
- equal: (a, b) => !a.length && !b.length ? true : a === b
320
- });
321
- this.edges = signal([], {
322
- // empty arrays considered equal, other arrays may not be equal
323
- equal: (a, b) => !a.length && !b.length ? true : a === b
324
- });
325
- this.connection = signal(new ConnectionModel({}));
326
- this.markers = computed(() => {
327
- const markersMap = new Map();
328
- this.validEdges().forEach(e => {
329
- if (e.edge.markers?.start) {
330
- const hash = hashCode(JSON.stringify(e.edge.markers.start));
331
- markersMap.set(hash, e.edge.markers.start);
332
- }
333
- if (e.edge.markers?.end) {
334
- const hash = hashCode(JSON.stringify(e.edge.markers.end));
335
- markersMap.set(hash, e.edge.markers.end);
336
- }
337
- });
338
- const connectionMarker = this.connection().connection.marker;
339
- if (connectionMarker) {
340
- const hash = hashCode(JSON.stringify(connectionMarker));
341
- markersMap.set(hash, connectionMarker);
342
- }
343
- return markersMap;
344
- });
345
- this.validEdges = computed(() => {
346
- const nodes = this.nodes();
347
- return this.edges().filter(e => nodes.includes(e.source) && nodes.includes(e.target));
348
- });
349
- }
350
- getNode(id) {
351
- return this.nodes().find(({ node }) => node.id === id);
352
- }
353
- getDetachedEdges() {
354
- return this.edges().filter(e => e.detached());
355
- }
356
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
357
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService }); }
358
- }
359
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService, decorators: [{
360
- type: Injectable
361
- }] });
362
-
363
417
  class ConnectionControllerDirective {
364
418
  constructor() {
365
419
  /**
@@ -402,84 +456,45 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
402
456
  type: Output
403
457
  }] } });
404
458
 
405
- class HandleModel {
406
- constructor(rawHandle, parentNode) {
407
- this.rawHandle = rawHandle;
408
- this.parentNode = parentNode;
409
- this.strokeWidth = 2;
410
- this.size = signal({
411
- width: 10 + (2 * this.strokeWidth),
412
- height: 10 + (2 * this.strokeWidth)
413
- });
414
- this.offset = computed(() => {
415
- switch (this.rawHandle.position) {
416
- case 'left': return {
417
- x: 0,
418
- y: this.parentPosition().y + (this.parentSize().height / 2)
419
- };
420
- case 'right': return {
421
- x: this.parentNode.size().width,
422
- y: this.parentPosition().y + (this.parentSize().height / 2)
423
- };
424
- case 'top': return {
425
- x: this.parentPosition().x + (this.parentSize().width / 2),
426
- y: 0
427
- };
428
- case 'bottom': return {
429
- x: this.parentPosition().x + this.parentSize().width / 2,
430
- y: this.parentNode.size().height
431
- };
432
- }
433
- });
434
- this.sizeOffset = computed(() => {
435
- switch (this.rawHandle.position) {
436
- case 'left': return { x: -(this.size().width / 2), y: 0 };
437
- case 'right': return { x: this.size().width / 2, y: 0 };
438
- case 'top': return { x: 0, y: -(this.size().height / 2) };
439
- case 'bottom': return { x: 0, y: this.size().height / 2 };
440
- }
441
- });
442
- this.pointAbsolute = computed(() => {
443
- return {
444
- x: this.parentNode.point().x + this.offset().x + this.sizeOffset().x,
445
- y: this.parentNode.point().y + this.offset().y + this.sizeOffset().y,
446
- };
447
- });
448
- this.parentSize = signal(this.rawHandle.parentSize);
449
- this.parentPosition = signal(this.rawHandle.parentPosition);
459
+ class FlowSettingsService {
460
+ constructor() {
461
+ this.entitiesSelectable = signal(true);
462
+ /**
463
+ * Global setting with handle positions. Nodes derive this value
464
+ *
465
+ * @deprecated
466
+ */
467
+ this.handlePositions = signal({ source: 'right', target: 'left' });
468
+ /**
469
+ * @see {VflowComponent.view}
470
+ */
471
+ this.view = signal([400, 400]);
472
+ this.flowWidth = computed(() => this.view() === 'auto' ? '100%' : this.view()[0]);
473
+ this.flowHeight = computed(() => this.view() === 'auto' ? '100%' : this.view()[1]);
450
474
  }
475
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
476
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowSettingsService }); }
451
477
  }
478
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowSettingsService, decorators: [{
479
+ type: Injectable
480
+ }] });
452
481
 
453
482
  class NodeModel {
454
483
  constructor(node) {
455
484
  this.node = node;
485
+ this.flowSettingsService = inject(FlowSettingsService);
456
486
  this.point = signal({ x: 0, y: 0 });
457
487
  this.point$ = toObservable(this.point);
458
488
  this.size = signal({ width: 0, height: 0 });
489
+ this.renderOrder = signal(0);
490
+ this.selected = signal(false);
491
+ this.selected$ = toObservable(this.selected);
459
492
  this.pointTransform = computed(() => `translate(${this.point().x}, ${this.point().y})`);
460
493
  // Now source and handle positions derived from parent flow
461
- this.sourcePosition = computed(() => this.flow.handlePositions().source);
462
- this.targetPosition = computed(() => this.flow.handlePositions().target);
463
- this.handles = computed(() => {
464
- if (this.node.type === 'html-template') {
465
- return this.rawHandles();
466
- }
467
- return [
468
- new HandleModel({
469
- position: this.sourcePosition(),
470
- type: 'source',
471
- parentPosition: { x: 0, y: 0 },
472
- parentSize: this.size()
473
- }, this),
474
- new HandleModel({
475
- position: this.targetPosition(),
476
- type: 'target',
477
- parentPosition: { x: 0, y: 0 },
478
- parentSize: this.size()
479
- }, this),
480
- ];
481
- });
482
- this.rawHandles = signal([]);
494
+ this.sourcePosition = computed(() => this.flowSettingsService.handlePositions().source);
495
+ this.targetPosition = computed(() => this.flowSettingsService.handlePositions().target);
496
+ this.handles = signal([]);
497
+ this.handles$ = toObservable(this.handles);
483
498
  this.draggable = true;
484
499
  // disabled for configuration for now
485
500
  this.magnetRadius = 20;
@@ -487,14 +502,6 @@ class NodeModel {
487
502
  if (isDefined(node.draggable))
488
503
  this.draggable = node.draggable;
489
504
  }
490
- /**
491
- * Bind parent flow model to node
492
- *
493
- * @param flow parent flow
494
- */
495
- bindFlow(flow) {
496
- this.flow = flow;
497
- }
498
505
  }
499
506
 
500
507
  class EdgeLabelModel {
@@ -610,26 +617,32 @@ function getPointOnBezier(sourcePoint, targetPoint, controlPoint1, controlPoint2
610
617
  class EdgeModel {
611
618
  constructor(edge) {
612
619
  this.edge = edge;
620
+ this.source = signal(undefined);
621
+ this.target = signal(undefined);
622
+ this.selected = signal(false);
623
+ this.selected$ = toObservable(this.selected);
613
624
  this.detached = computed(() => {
614
- if (!this.source || !this.target) {
625
+ const source = this.source();
626
+ const target = this.target();
627
+ if (!source || !target) {
615
628
  return true;
616
629
  }
617
630
  let existsSourceHandle = false;
618
631
  let existsTargetHandle = false;
619
632
  if (this.edge.sourceHandle) {
620
- existsSourceHandle = !!this.source.handles()
633
+ existsSourceHandle = !!source.handles()
621
634
  .find(handle => handle.rawHandle.id === this.edge.sourceHandle);
622
635
  }
623
636
  else {
624
- existsSourceHandle = !!this.source.handles()
637
+ existsSourceHandle = !!source.handles()
625
638
  .find(handle => handle.rawHandle.type === 'source');
626
639
  }
627
640
  if (this.edge.targetHandle) {
628
- existsTargetHandle = !!this.target.handles()
641
+ existsTargetHandle = !!target.handles()
629
642
  .find(handle => handle.rawHandle.id === this.edge.targetHandle);
630
643
  }
631
644
  else {
632
- existsTargetHandle = !!this.target.handles()
645
+ existsTargetHandle = !!target.handles()
633
646
  .find(handle => handle.rawHandle.type === 'target');
634
647
  }
635
648
  return !existsSourceHandle || !existsTargetHandle;
@@ -638,20 +651,20 @@ class EdgeModel {
638
651
  this.path = computed(() => {
639
652
  let source;
640
653
  if (this.edge.sourceHandle) {
641
- source = this.source.handles()
654
+ source = this.source()?.handles()
642
655
  .find(handle => handle.rawHandle.id === this.edge.sourceHandle);
643
656
  }
644
657
  else {
645
- source = this.source.handles()
658
+ source = this.source()?.handles()
646
659
  .find(handle => handle.rawHandle.type === 'source');
647
660
  }
648
661
  let target;
649
662
  if (this.edge.targetHandle) {
650
- target = this.target.handles()
663
+ target = this.target()?.handles()
651
664
  .find(handle => handle.rawHandle.id === this.edge.targetHandle);
652
665
  }
653
666
  else {
654
- target = this.target.handles()
667
+ target = this.target()?.handles()
655
668
  .find(handle => handle.rawHandle.type === 'target');
656
669
  }
657
670
  // TODO: don't like this
@@ -732,7 +745,11 @@ class NodesChangeService {
732
745
  .pipe(pairwise(), map(([oldList, newList]) => newList.filter(node => !oldList.includes(node))), filter((nodes) => !!nodes.length), map((nodes) => nodes.map(node => ({ type: 'add', id: node.node.id }))));
733
746
  this.nodeRemoveChange$ = toObservable(this.entitiesService.nodes)
734
747
  .pipe(pairwise(), map(([oldList, newList]) => oldList.filter(node => !newList.includes(node))), filter((nodes) => !!nodes.length), map((nodes) => nodes.map(node => ({ type: 'remove', id: node.node.id }))));
735
- this.changes$ = merge(this.nodesPositionChange$, this.nodeAddChange$, this.nodeRemoveChange$).pipe(
748
+ this.nodeSelectedChange$ = toObservable(this.entitiesService.nodes)
749
+ .pipe(switchMap((nodes) => merge(...nodes.map(node => node.selected$.pipe(distinctUntilChanged(), skip(1), map(() => node))))), map((changedNode) => [
750
+ { type: 'select', id: changedNode.node.id, selected: changedNode.selected() }
751
+ ]));
752
+ this.changes$ = merge(this.nodesPositionChange$, this.nodeAddChange$, this.nodeRemoveChange$, this.nodeSelectedChange$).pipe(
736
753
  // this fixes a bug when on fire node event change,
737
754
  // you can't get valid list of detached edges
738
755
  observeOn(asyncScheduler));
@@ -752,7 +769,7 @@ class EdgeChangesService {
752
769
  this.edgeDetachedChange$ = merge(toObservable(computed(() => {
753
770
  const nodes = this.entitiesService.nodes();
754
771
  const edges = untracked(this.entitiesService.edges);
755
- return edges.filter(({ source, target }) => !nodes.includes(source) || !nodes.includes(target));
772
+ return edges.filter(({ source, target }) => !nodes.includes(source()) || !nodes.includes(target()));
756
773
  })), toObservable(this.entitiesService.edges).pipe(switchMap((edges) => {
757
774
  return zip(...edges.map(e => e.detached$.pipe(map(() => e))));
758
775
  }), map((edges) => edges.filter(e => e.detached())),
@@ -769,7 +786,11 @@ class EdgeChangesService {
769
786
  .pipe(pairwise(), map(([oldList, newList]) => {
770
787
  return oldList.filter(edge => !newList.includes(edge));
771
788
  }), filter(edges => !!edges.length), map((edges) => edges.map(({ edge }) => ({ type: 'remove', id: edge.id }))));
772
- this.changes$ = merge(this.edgeDetachedChange$, this.edgeAddChange$, this.edgeRemoveChange$)
789
+ this.edgeSelectChange$ = toObservable(this.entitiesService.edges)
790
+ .pipe(switchMap((edges) => merge(...edges.map(edge => edge.selected$.pipe(distinctUntilChanged(), skip(1), map(() => edge))))), map((changedEdge) => [
791
+ { type: 'select', id: changedEdge.edge.id, selected: changedEdge.selected() }
792
+ ]));
793
+ this.changes$ = merge(this.edgeDetachedChange$, this.edgeAddChange$, this.edgeRemoveChange$, this.edgeSelectChange$)
773
794
  .pipe(
774
795
  // this fixes the case when user gets 'deteched' changes
775
796
  // and tries to delete these edges inside stream
@@ -818,6 +839,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
818
839
  type: Output
819
840
  }] } });
820
841
 
842
+ class NodeRenderingService {
843
+ constructor() {
844
+ this.flowEntitiesService = inject(FlowEntitiesService);
845
+ this.nodes = computed(() => {
846
+ return this.flowEntitiesService.nodes()
847
+ .sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
848
+ });
849
+ }
850
+ pullNode(node) {
851
+ const maxOrder = Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
852
+ node.renderOrder.set(maxOrder + 1);
853
+ }
854
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
855
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService }); }
856
+ }
857
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService, decorators: [{
858
+ type: Injectable
859
+ }] });
860
+
821
861
  class HandleService {
822
862
  constructor() {
823
863
  this.node = signal(null);
@@ -825,14 +865,13 @@ class HandleService {
825
865
  createHandle(newHandle) {
826
866
  const node = this.node();
827
867
  if (node) {
828
- node.rawHandles.update(handles => [...handles, newHandle]);
868
+ node.handles.update(handles => [...handles, newHandle]);
829
869
  }
830
870
  }
831
871
  destroyHandle(handleToDestoy) {
832
872
  const node = this.node();
833
- // TODO: microtask
834
873
  if (node) {
835
- queueMicrotask(() => node.rawHandles.update(handles => handles.filter(handle => handle !== handleToDestoy)));
874
+ node.handles.update(handles => handles.filter(handle => handle !== handleToDestoy));
836
875
  }
837
876
  }
838
877
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -842,39 +881,199 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
842
881
  type: Injectable
843
882
  }] });
844
883
 
884
+ class HandleModel {
885
+ constructor(rawHandle, parentNode) {
886
+ this.rawHandle = rawHandle;
887
+ this.parentNode = parentNode;
888
+ this.strokeWidth = 2;
889
+ /**
890
+ * Pre-computed size for default handle, changed dynamically
891
+ * for custom handles
892
+ */
893
+ this.size = signal({
894
+ width: 10 + (2 * this.strokeWidth),
895
+ height: 10 + (2 * this.strokeWidth)
896
+ });
897
+ this.offset = computed(() => {
898
+ switch (this.rawHandle.position) {
899
+ case 'left': return {
900
+ x: 0,
901
+ y: this.parentPosition().y + (this.parentSize().height / 2)
902
+ };
903
+ case 'right': return {
904
+ x: this.parentNode.size().width,
905
+ y: this.parentPosition().y + (this.parentSize().height / 2)
906
+ };
907
+ case 'top': return {
908
+ x: this.parentPosition().x + (this.parentSize().width / 2),
909
+ y: 0
910
+ };
911
+ case 'bottom': return {
912
+ x: this.parentPosition().x + this.parentSize().width / 2,
913
+ y: this.parentNode.size().height
914
+ };
915
+ }
916
+ });
917
+ this.sizeOffset = computed(() => {
918
+ switch (this.rawHandle.position) {
919
+ case 'left': return { x: -(this.size().width / 2), y: 0 };
920
+ case 'right': return { x: this.size().width / 2, y: 0 };
921
+ case 'top': return { x: 0, y: -(this.size().height / 2) };
922
+ case 'bottom': return { x: 0, y: this.size().height / 2 };
923
+ }
924
+ });
925
+ this.pointAbsolute = computed(() => {
926
+ return {
927
+ x: this.parentNode.point().x + this.offset().x + this.sizeOffset().x,
928
+ y: this.parentNode.point().y + this.offset().y + this.sizeOffset().y,
929
+ };
930
+ });
931
+ this.state = signal('idle');
932
+ this.updateParentSizeAndPosition$ = new Subject();
933
+ this.parentSize = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => ({
934
+ width: this.parentReference.offsetWidth,
935
+ height: this.parentReference.offsetHeight
936
+ }))), {
937
+ initialValue: { width: 0, height: 0 }
938
+ });
939
+ this.parentPosition = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => ({
940
+ x: this.parentReference.offsetLeft,
941
+ y: this.parentReference.offsetTop
942
+ }))), {
943
+ initialValue: { x: 0, y: 0 }
944
+ });
945
+ this.parentReference = this.rawHandle.parentReference;
946
+ this.template = this.rawHandle.template;
947
+ this.templateContext = {
948
+ $implicit: {
949
+ point: this.offset,
950
+ state: this.state
951
+ }
952
+ };
953
+ }
954
+ updateParent() {
955
+ this.updateParentSizeAndPosition$.next();
956
+ }
957
+ }
958
+
959
+ function resizable(elems, zone) {
960
+ return new Observable((subscriber) => {
961
+ let ro = new ResizeObserver((entries) => {
962
+ zone.run(() => subscriber.next(entries));
963
+ });
964
+ elems.forEach(e => ro.observe(e));
965
+ return () => ro.disconnect();
966
+ });
967
+ }
968
+
969
+ function InjectionContext(target, key, descriptor) {
970
+ const originalMethod = descriptor.value;
971
+ descriptor.value = function (...args) {
972
+ if (implementsWithInjector(this)) {
973
+ return runInInjectionContext(this.injector, () => originalMethod.apply(this, args));
974
+ }
975
+ else {
976
+ throw new Error('Class that contains decorated method must extends WithInjectorDirective class');
977
+ }
978
+ };
979
+ // Return the modified descriptor
980
+ return descriptor;
981
+ }
982
+ const implementsWithInjector = (instance) => {
983
+ return 'injector' in instance && 'get' in instance.injector;
984
+ };
985
+
986
+ function Microtask(target, key, descriptor) {
987
+ const originalMethod = descriptor.value;
988
+ descriptor.value = function (...args) {
989
+ queueMicrotask(() => {
990
+ originalMethod?.apply(this, args);
991
+ });
992
+ };
993
+ // Return the modified descriptor
994
+ return descriptor;
995
+ }
996
+
997
+ class HandleSizeControllerDirective {
998
+ constructor() {
999
+ this.handleWrapper = inject(ElementRef);
1000
+ }
1001
+ ngAfterViewInit() {
1002
+ const element = this.handleWrapper.nativeElement;
1003
+ const rect = element.getBBox();
1004
+ const stroke = getChildStrokeWidth(element);
1005
+ this.handleModel.size.set({
1006
+ width: rect.width + stroke,
1007
+ height: rect.height + stroke
1008
+ });
1009
+ }
1010
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleSizeControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1011
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: { handleModel: ["handleSizeController", "handleModel"] }, ngImport: i0 }); }
1012
+ }
1013
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleSizeControllerDirective, decorators: [{
1014
+ type: Directive,
1015
+ args: [{ selector: '[handleSizeController]' }]
1016
+ }], propDecorators: { handleModel: [{
1017
+ type: Input,
1018
+ args: [{ required: true, alias: 'handleSizeController' }]
1019
+ }] } });
1020
+ function getChildStrokeWidth(element) {
1021
+ const child = element.firstElementChild;
1022
+ if (child) {
1023
+ const stroke = getComputedStyle(child).strokeWidth;
1024
+ const strokeAsNumber = Number(stroke.replace('px', ''));
1025
+ if (isNaN(strokeAsNumber)) {
1026
+ return 0;
1027
+ }
1028
+ return strokeAsNumber;
1029
+ }
1030
+ return 0;
1031
+ }
1032
+
845
1033
  class NodeComponent {
846
1034
  constructor() {
847
- this.handleService = inject(HandleService);
848
1035
  this.injector = inject(Injector);
1036
+ this.handleService = inject(HandleService);
1037
+ this.zone = inject(NgZone);
849
1038
  this.draggableService = inject(DraggableService);
850
1039
  this.flowStatusService = inject(FlowStatusService);
851
1040
  this.flowEntitiesService = inject(FlowEntitiesService);
1041
+ this.nodeRenderingService = inject(NodeRenderingService);
1042
+ this.flowSettingsService = inject(FlowSettingsService);
1043
+ this.selectionService = inject(SelectionService);
852
1044
  this.hostRef = inject(ElementRef);
853
1045
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
854
1046
  this.flowStatusService.status().state === 'connection-validation');
855
- this.sourceHanldeState = signal('idle');
856
- this.targetHandleState = signal('idle');
1047
+ this.subscription = new Subscription();
857
1048
  }
858
1049
  ngOnInit() {
859
1050
  this.handleService.node.set(this.nodeModel);
860
1051
  this.draggableService.toggleDraggable(this.hostRef.nativeElement, this.nodeModel);
1052
+ const sub = this.nodeModel.handles$
1053
+ .pipe(switchMap((handles) => resizable(handles.map(h => h.parentReference), this.zone)
1054
+ .pipe(map(() => handles))), tap((handles) => handles.forEach(h => h.updateParent())))
1055
+ .subscribe();
1056
+ this.subscription.add(sub);
861
1057
  }
862
1058
  ngAfterViewInit() {
863
- // TODO remove microtask
864
- queueMicrotask(() => {
865
- if (this.nodeModel.node.type === 'default') {
866
- const { width, height } = this.nodeContentRef.nativeElement.getBBox();
867
- this.nodeModel.size.set({ width, height });
868
- }
869
- if (this.nodeModel.node.type === 'html-template') {
1059
+ this.setInitialHandles();
1060
+ if (this.nodeModel.node.type === 'default') {
1061
+ const { width, height } = this.nodeContentRef.nativeElement.getBBox();
1062
+ this.nodeModel.size.set({ width, height });
1063
+ }
1064
+ if (this.nodeModel.node.type === 'html-template') {
1065
+ const sub = resizable([this.htmlWrapperRef.nativeElement], this.zone)
1066
+ .pipe(startWith(null), tap(() => {
870
1067
  const width = this.htmlWrapperRef.nativeElement.clientWidth;
871
1068
  const height = this.htmlWrapperRef.nativeElement.clientHeight;
872
1069
  this.nodeModel.size.set({ width, height });
873
- }
874
- });
1070
+ })).subscribe();
1071
+ this.subscription.add(sub);
1072
+ }
875
1073
  }
876
1074
  ngOnDestroy() {
877
1075
  this.draggableService.destroy(this.hostRef.nativeElement);
1076
+ this.subscription.unsubscribe();
878
1077
  }
879
1078
  startConnection(event, handle) {
880
1079
  // ignore drag by stopping propagation
@@ -912,27 +1111,55 @@ class NodeComponent {
912
1111
  sourceHandle: sourceHandle.rawHandle.id,
913
1112
  targetHandle: targetHandle.rawHandle.id
914
1113
  });
915
- this.targetHandleState.set(valid ? 'valid' : 'invalid');
1114
+ targetHandle.state.set(valid ? 'valid' : 'invalid');
916
1115
  this.flowStatusService.setConnectionValidationStatus(valid, sourceNode, targetNode, sourceHandle, targetHandle);
917
1116
  }
918
1117
  }
919
1118
  /**
920
1119
  * TODO srp
921
1120
  */
922
- resetValidateTargetHandle() {
923
- this.targetHandleState.set('idle');
1121
+ resetValidateTargetHandle(targetHandle) {
1122
+ targetHandle.state.set('idle');
924
1123
  // drop back to start status
925
1124
  const status = this.flowStatusService.status();
926
1125
  if (status.state === 'connection-validation') {
927
1126
  this.flowStatusService.setConnectionStartStatus(status.payload.sourceNode, status.payload.sourceHandle);
928
1127
  }
929
1128
  }
1129
+ pullNode() {
1130
+ this.nodeRenderingService.pullNode(this.nodeModel);
1131
+ }
1132
+ selectNode() {
1133
+ if (this.flowSettingsService.entitiesSelectable()) {
1134
+ this.selectionService.select(this.nodeModel);
1135
+ }
1136
+ }
1137
+ setInitialHandles() {
1138
+ if (this.nodeModel.node.type === 'default') {
1139
+ this.handleService.createHandle(new HandleModel({
1140
+ position: this.nodeModel.sourcePosition(),
1141
+ type: 'source',
1142
+ parentReference: this.htmlWrapperRef.nativeElement
1143
+ }, this.nodeModel));
1144
+ this.handleService.createHandle(new HandleModel({
1145
+ position: this.nodeModel.targetPosition(),
1146
+ type: 'target',
1147
+ parentReference: this.htmlWrapperRef.nativeElement
1148
+ }, this.nodeModel));
1149
+ }
1150
+ }
930
1151
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
931
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeHtmlTemplate: "nodeHtmlTemplate" }, providers: [HandleService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n #nodeContent\n width=\"100\"\n height=\"50\"\n>\n <div class=\"default-node\" [innerHTML]=\"nodeModel.node.text ?? ''\"></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n #sourceHandle\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n stroke=\"white\"\n fill=\"black\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (mouseup)=\"endConnection(); resetValidateTargetHandle()\"\n (mouseover)=\"validateTargetHandle(handle)\"\n (mouseout)=\"resetValidateTargetHandle()\"\n />\n</ng-container>\n\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{max-width:100px;max-height:100px;width:100px;height:50px;border:2px solid black;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1152
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeHtmlTemplate: "nodeHtmlTemplate" }, providers: [HandleService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n width=\"100\"\n height=\"50\"\n (mousedown)=\"pullNode(); selectNode()\"\n>\n <div\n #htmlWrapper\n class=\"default-node\"\n [class.default-node_selected]=\"nodeModel.selected()\"\n [innerHTML]=\"nodeModel.node.text ?? ''\"\n ></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (mouseup)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (mouseover)=\"validateTargetHandle(handle)\"\n (mouseout)=\"resetValidateTargetHandle(handle)\"\n />\n</ng-container>\n\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{max-width:100px;max-height:100px;width:100px;height:50px;border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}.default-node_selected{border-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
932
1153
  }
1154
+ __decorate([
1155
+ Microtask
1156
+ ], NodeComponent.prototype, "ngAfterViewInit", null);
1157
+ __decorate([
1158
+ InjectionContext
1159
+ ], NodeComponent.prototype, "setInitialHandles", null);
933
1160
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
934
1161
  type: Component,
935
- args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService], template: "<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n #nodeContent\n width=\"100\"\n height=\"50\"\n>\n <div class=\"default-node\" [innerHTML]=\"nodeModel.node.text ?? ''\"></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n #sourceHandle\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n stroke=\"white\"\n fill=\"black\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (mouseup)=\"endConnection(); resetValidateTargetHandle()\"\n (mouseover)=\"validateTargetHandle(handle)\"\n (mouseout)=\"resetValidateTargetHandle()\"\n />\n</ng-container>\n\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{max-width:100px;max-height:100px;width:100px;height:50px;border:2px solid black;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}\n"] }]
1162
+ args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService], template: "<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n width=\"100\"\n height=\"50\"\n (mousedown)=\"pullNode(); selectNode()\"\n>\n <div\n #htmlWrapper\n class=\"default-node\"\n [class.default-node_selected]=\"nodeModel.selected()\"\n [innerHTML]=\"nodeModel.node.text ?? ''\"\n ></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (mouseup)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (mouseover)=\"validateTargetHandle(handle)\"\n (mouseout)=\"resetValidateTargetHandle(handle)\"\n />\n</ng-container>\n\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{max-width:100px;max-height:100px;width:100px;height:50px;border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}.default-node_selected{border-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
936
1163
  }], propDecorators: { nodeModel: [{
937
1164
  type: Input
938
1165
  }], nodeHtmlTemplate: [{
@@ -943,7 +1170,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
943
1170
  }], htmlWrapperRef: [{
944
1171
  type: ViewChild,
945
1172
  args: ['htmlWrapper']
946
- }] } });
1173
+ }], ngAfterViewInit: [], setInitialHandles: [] } });
947
1174
 
948
1175
  class EdgeLabelComponent {
949
1176
  constructor() {
@@ -964,14 +1191,12 @@ class EdgeLabelComponent {
964
1191
  }
965
1192
  set point(point) { this.pointSignal.set(point); }
966
1193
  ngAfterViewInit() {
967
- queueMicrotask(() => {
968
- // this is a fix for visual artifact in chrome that for some reason adresses only for edge label.
969
- // the bug reproduces if edgeLabelWrapperRef size fully matched the size of parent foreignObject
970
- const MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME = 2;
971
- const width = this.edgeLabelWrapperRef.nativeElement.clientWidth + MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME;
972
- const height = this.edgeLabelWrapperRef.nativeElement.clientHeight + MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME;
973
- this.model.size.set({ width, height });
974
- });
1194
+ // this is a fix for visual artifact in chrome that for some reason adresses only for edge label.
1195
+ // the bug reproduces if edgeLabelWrapperRef size fully matched the size of parent foreignObject
1196
+ const MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME = 2;
1197
+ const width = this.edgeLabelWrapperRef.nativeElement.clientWidth + MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME;
1198
+ const height = this.edgeLabelWrapperRef.nativeElement.clientHeight + MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME;
1199
+ this.model.size.set({ width, height });
975
1200
  }
976
1201
  getLabelContext() {
977
1202
  return {
@@ -984,6 +1209,9 @@ class EdgeLabelComponent {
984
1209
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: EdgeLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
985
1210
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: EdgeLabelComponent, selector: "g[edgeLabel]", inputs: { model: "model", edgeModel: "edgeModel", point: "point", htmlTemplate: "htmlTemplate" }, viewQueries: [{ propertyName: "edgeLabelWrapperRef", first: true, predicate: ["edgeLabelWrapper"], descendants: true }], ngImport: i0, template: "<svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\"\n *ngIf=\"model.edgeLabel.type === 'html-template' && htmlTemplate\"\n>\n <div #edgeLabelWrapper class=\"edge-label-wrapper\">\n <ng-container\n *ngTemplateOutlet=\"htmlTemplate; context: getLabelContext()\"\n />\n </div>\n</svg:foreignObject>\n", styles: [".edge-label-wrapper{width:max-content;margin-top:1px;margin-left:1px}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
986
1211
  }
1212
+ __decorate([
1213
+ Microtask
1214
+ ], EdgeLabelComponent.prototype, "ngAfterViewInit", null);
987
1215
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: EdgeLabelComponent, decorators: [{
988
1216
  type: Component,
989
1217
  args: [{ selector: 'g[edgeLabel]', changeDetection: ChangeDetectionStrategy.OnPush, template: "<svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\"\n *ngIf=\"model.edgeLabel.type === 'html-template' && htmlTemplate\"\n>\n <div #edgeLabelWrapper class=\"edge-label-wrapper\">\n <ng-container\n *ngTemplateOutlet=\"htmlTemplate; context: getLabelContext()\"\n />\n </div>\n</svg:foreignObject>\n", styles: [".edge-label-wrapper{width:max-content;margin-top:1px;margin-left:1px}\n"] }]
@@ -998,10 +1226,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
998
1226
  }], edgeLabelWrapperRef: [{
999
1227
  type: ViewChild,
1000
1228
  args: ['edgeLabelWrapper']
1001
- }] } });
1229
+ }], ngAfterViewInit: [] } });
1002
1230
 
1003
1231
  class EdgeComponent {
1004
1232
  constructor() {
1233
+ this.injector = inject(Injector);
1234
+ this.selectionService = inject(SelectionService);
1235
+ this.flowSettingsService = inject(FlowSettingsService);
1005
1236
  this.markerStartUrl = computed(() => {
1006
1237
  const marker = this.model.edge.markers?.start;
1007
1238
  return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
@@ -1010,7 +1241,6 @@ class EdgeComponent {
1010
1241
  const marker = this.model.edge.markers?.end;
1011
1242
  return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
1012
1243
  });
1013
- this.defaultColor = 'rgb(177, 177, 183)';
1014
1244
  }
1015
1245
  ngOnInit() {
1016
1246
  this.edgeContext = {
@@ -1019,16 +1249,24 @@ class EdgeComponent {
1019
1249
  edge: this.model.edge,
1020
1250
  path: computed(() => this.model.path().path),
1021
1251
  markerStart: this.markerStartUrl,
1022
- markerEnd: this.markerEndUrl
1252
+ markerEnd: this.markerEndUrl,
1253
+ selected: this.model.selected.asReadonly()
1023
1254
  }
1024
1255
  };
1025
1256
  }
1257
+ onEdgeMouseDown() {
1258
+ if (this.flowSettingsService.entitiesSelectable()) {
1259
+ this.selectionService.select(this.model);
1260
+ }
1261
+ }
1026
1262
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: EdgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1027
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: EdgeComponent, selector: "g[edge]", inputs: { model: "model", edgeTemplate: "edgeTemplate", edgeLabelHtmlTemplate: "edgeLabelHtmlTemplate" }, ngImport: i0, template: "<svg:path\n *ngIf=\"model.type === 'default'\"\n [attr.d]=\"model.path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n [attr.stroke]=\"defaultColor\"\n stroke-width=\"2\"\n fill=\"none\"\n/>\n\n<ng-container *ngIf=\"model.type === 'template' && edgeTemplate\">\n <ng-container *ngTemplateOutlet=\"edgeTemplate; context: edgeContext\"></ng-container>\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.start as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.start\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.center as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.center\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.end as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.end\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n", dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: EdgeLabelComponent, selector: "g[edgeLabel]", inputs: ["model", "edgeModel", "point", "htmlTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1263
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: EdgeComponent, selector: "g[edge]", inputs: { model: "model", edgeTemplate: "edgeTemplate", edgeLabelHtmlTemplate: "edgeLabelHtmlTemplate" }, host: { classAttribute: "selectable" }, ngImport: i0, template: "<svg:path\n *ngIf=\"model.type === 'default'\"\n (mousedown)=\"onEdgeMouseDown()\"\n [attr.d]=\"model.path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n class=\"edge\"\n [class.edge_selected]=\"model.selected()\"\n/>\n\n<ng-container *ngIf=\"model.type === 'template' && edgeTemplate\">\n <ng-container\n [ngTemplateOutlet]=\"edgeTemplate\"\n [ngTemplateOutletContext]=\"edgeContext\"\n [ngTemplateOutletInjector]=\"injector\"\n ></ng-container>\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.start as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.start\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.center as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.center\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.end as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.end\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n", styles: [".edge{fill:none;stroke-width:2;stroke:#b1b1b7}.edge_selected{stroke-width:2.5;stroke:#0f4c75}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: EdgeLabelComponent, selector: "g[edgeLabel]", inputs: ["model", "edgeModel", "point", "htmlTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1028
1264
  }
1029
1265
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: EdgeComponent, decorators: [{
1030
1266
  type: Component,
1031
- args: [{ selector: 'g[edge]', changeDetection: ChangeDetectionStrategy.OnPush, template: "<svg:path\n *ngIf=\"model.type === 'default'\"\n [attr.d]=\"model.path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n [attr.stroke]=\"defaultColor\"\n stroke-width=\"2\"\n fill=\"none\"\n/>\n\n<ng-container *ngIf=\"model.type === 'template' && edgeTemplate\">\n <ng-container *ngTemplateOutlet=\"edgeTemplate; context: edgeContext\"></ng-container>\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.start as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.start\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.center as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.center\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.end as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.end\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n" }]
1267
+ args: [{ selector: 'g[edge]', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1268
+ 'class': 'selectable'
1269
+ }, template: "<svg:path\n *ngIf=\"model.type === 'default'\"\n (mousedown)=\"onEdgeMouseDown()\"\n [attr.d]=\"model.path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n class=\"edge\"\n [class.edge_selected]=\"model.selected()\"\n/>\n\n<ng-container *ngIf=\"model.type === 'template' && edgeTemplate\">\n <ng-container\n [ngTemplateOutlet]=\"edgeTemplate\"\n [ngTemplateOutletContext]=\"edgeContext\"\n [ngTemplateOutletInjector]=\"injector\"\n ></ng-container>\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.start as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.start\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.center as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.center\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n\n<ng-container *ngIf=\"model.edgeLabels?.end as label\">\n <svg:g edgeLabel\n [model]=\"label\"\n [point]=\"model.path().points.end\"\n [edgeModel]=\"model\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate\"\n />\n</ng-container>\n", styles: [".edge{fill:none;stroke-width:2;stroke:#b1b1b7}.edge_selected{stroke-width:2.5;stroke:#0f4c75}\n"] }]
1032
1270
  }], propDecorators: { model: [{
1033
1271
  type: Input
1034
1272
  }], edgeTemplate: [{
@@ -1227,6 +1465,8 @@ class VflowComponent {
1227
1465
  this.flowEntitiesService = inject(FlowEntitiesService);
1228
1466
  this.nodesChangeService = inject(NodesChangeService);
1229
1467
  this.edgesChangeService = inject(EdgeChangesService);
1468
+ this.nodeRenderingService = inject(NodeRenderingService);
1469
+ this.flowSettingsService = inject(FlowSettingsService);
1230
1470
  this.injector = inject(Injector);
1231
1471
  /**
1232
1472
  * Minimum zoom value
@@ -1240,6 +1480,8 @@ class VflowComponent {
1240
1480
  * Background color for flow
1241
1481
  */
1242
1482
  this.background = '#FFFFFF';
1483
+ this.nodeModels = computed(() => this.nodeRenderingService.nodes());
1484
+ this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
1243
1485
  // #endregion
1244
1486
  // #region SIGNAL_API
1245
1487
  /**
@@ -1270,8 +1512,8 @@ class VflowComponent {
1270
1512
  */
1271
1513
  this.edgesChange$ = this.edgesChangeService.changes$;
1272
1514
  // #endregion
1273
- // TODO: probably better to make it injectable
1274
- this.flowModel = new FlowModel();
1515
+ this.flowWidth = this.flowSettingsService.flowWidth;
1516
+ this.flowHeight = this.flowSettingsService.flowHeight;
1275
1517
  this.markers = this.flowEntitiesService.markers;
1276
1518
  }
1277
1519
  // #endregion
@@ -1284,7 +1526,7 @@ class VflowComponent {
1284
1526
  * - 'auto' to compute size based on parent element size
1285
1527
  */
1286
1528
  set view(view) {
1287
- this.flowModel.view.set(view);
1529
+ this.flowSettingsService.view.set(view);
1288
1530
  }
1289
1531
  /**
1290
1532
  * Object that controls flow direction.
@@ -1295,7 +1537,13 @@ class VflowComponent {
1295
1537
  * @deprecated
1296
1538
  */
1297
1539
  set handlePositions(handlePositions) {
1298
- this.flowModel.handlePositions.set(handlePositions);
1540
+ this.flowSettingsService.handlePositions.set(handlePositions);
1541
+ }
1542
+ /**
1543
+ * Global rule if you can or can't select entities
1544
+ */
1545
+ set entitiesSelectable(value) {
1546
+ this.flowSettingsService.entitiesSelectable.set(value);
1299
1547
  }
1300
1548
  /**
1301
1549
  * Settings for connection (it renders when user tries to create edge between nodes)
@@ -1311,23 +1559,19 @@ class VflowComponent {
1311
1559
  */
1312
1560
  set nodes(newNodes) {
1313
1561
  const newModels = runInInjectionContext(this.injector, () => ReferenceKeeper.nodes(newNodes, this.flowEntitiesService.nodes()));
1314
- // TODO better to solve this by DI
1315
- bindFlowToNodes(this.flowModel, newModels);
1316
1562
  // quick and dirty binding nodes to edges
1317
1563
  addNodesToEdges(newModels, this.flowEntitiesService.edges());
1318
1564
  this.flowEntitiesService.nodes.set(newModels);
1319
1565
  }
1320
- get nodeModels() { return this.flowEntitiesService.nodes(); }
1321
1566
  /**
1322
1567
  * Edges to render
1323
1568
  */
1324
1569
  set edges(newEdges) {
1325
1570
  const newModels = runInInjectionContext(this.injector, () => ReferenceKeeper.edges(newEdges, this.flowEntitiesService.edges()));
1326
1571
  // quick and dirty binding nodes to edges
1327
- addNodesToEdges(this.nodeModels, newModels);
1572
+ addNodesToEdges(this.nodeModels(), newModels);
1328
1573
  this.flowEntitiesService.edges.set(newModels);
1329
1574
  }
1330
- get edgeModels() { return this.flowEntitiesService.validEdges(); }
1331
1575
  // #region METHODS_API
1332
1576
  /**
1333
1577
  * Change viewport to specified state
@@ -1369,20 +1613,23 @@ class VflowComponent {
1369
1613
  }
1370
1614
  // #endregion
1371
1615
  trackNodes(idx, { node }) {
1372
- return node.id;
1616
+ return node;
1373
1617
  }
1374
1618
  trackEdges(idx, { edge }) {
1375
- return edge.id;
1619
+ return edge;
1376
1620
  }
1377
1621
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1378
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "16.2.12", type: VflowComponent, selector: "vflow", inputs: { view: "view", minZoom: "minZoom", maxZoom: "maxZoom", handlePositions: "handlePositions", background: "background", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], nodes: "nodes", edges: "edges" }, providers: [
1622
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "16.2.12", type: VflowComponent, selector: "vflow", inputs: { view: "view", minZoom: "minZoom", maxZoom: "maxZoom", handlePositions: "handlePositions", background: "background", entitiesSelectable: "entitiesSelectable", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], nodes: "nodes", edges: "edges" }, providers: [
1379
1623
  DraggableService,
1380
1624
  ViewportService,
1381
1625
  FlowStatusService,
1382
1626
  FlowEntitiesService,
1383
1627
  NodesChangeService,
1384
- EdgeChangesService
1385
- ], queries: [{ propertyName: "nodeHtmlDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true }], hostDirectives: [{ directive: ConnectionControllerDirective, outputs: ["onConnect", "onConnect"] }, { directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onEdgesChange", "onEdgesChange"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\n class=\"root-svg\"\n #flow\n [style.backgroundColor]=\"background\"\n [attr.width]=\"flowModel.flowWidth()\"\n [attr.height]=\"flowModel.flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels; trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels; trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n</svg:svg>\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeHtmlTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]", inputs: ["minZoom", "maxZoom"] }, { kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1628
+ EdgeChangesService,
1629
+ NodeRenderingService,
1630
+ SelectionService,
1631
+ FlowSettingsService
1632
+ ], queries: [{ propertyName: "nodeHtmlDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true }], hostDirectives: [{ directive: ConnectionControllerDirective, outputs: ["onConnect", "onConnect"] }, { directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onEdgesChange", "onEdgesChange"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\n class=\"root-svg\"\n #flow\n [style.backgroundColor]=\"background\"\n [attr.width]=\"flowWidth()\"\n [attr.height]=\"flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n</svg:svg>\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeHtmlTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]", inputs: ["minZoom", "maxZoom"] }, { kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1386
1633
  }
1387
1634
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
1388
1635
  type: Component,
@@ -1392,11 +1639,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1392
1639
  FlowStatusService,
1393
1640
  FlowEntitiesService,
1394
1641
  NodesChangeService,
1395
- EdgeChangesService
1642
+ EdgeChangesService,
1643
+ NodeRenderingService,
1644
+ SelectionService,
1645
+ FlowSettingsService
1396
1646
  ], hostDirectives: [
1397
1647
  connectionControllerHostDirective,
1398
1648
  changesControllerHostDirective
1399
- ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n class=\"root-svg\"\n #flow\n [style.backgroundColor]=\"background\"\n [attr.width]=\"flowModel.flowWidth()\"\n [attr.height]=\"flowModel.flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels; trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels; trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n</svg:svg>\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"] }]
1649
+ ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n class=\"root-svg\"\n #flow\n [style.backgroundColor]=\"background\"\n [attr.width]=\"flowWidth()\"\n [attr.height]=\"flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n</svg:svg>\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"] }]
1400
1650
  }], propDecorators: { view: [{
1401
1651
  type: Input
1402
1652
  }], minZoom: [{
@@ -1407,6 +1657,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1407
1657
  type: Input
1408
1658
  }], background: [{
1409
1659
  type: Input
1660
+ }], entitiesSelectable: [{
1661
+ type: Input
1410
1662
  }], connection: [{
1411
1663
  type: Input,
1412
1664
  args: [{ transform: (settings) => new ConnectionModel(settings) }]
@@ -1431,43 +1683,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1431
1683
  type: ViewChild,
1432
1684
  args: [MapContextDirective]
1433
1685
  }] } });
1434
- function bindFlowToNodes(flow, nodes) {
1435
- nodes.forEach(n => n.bindFlow(flow));
1436
- }
1437
1686
 
1438
1687
  class HandleComponent {
1439
1688
  constructor() {
1689
+ this.injector = inject(Injector);
1440
1690
  this.handleService = inject(HandleService);
1441
1691
  this.element = inject(ElementRef).nativeElement;
1442
1692
  }
1443
1693
  ngOnInit() {
1444
- queueMicrotask(() => {
1445
- const { width, height, x, y } = this.parentRect();
1446
- this.model = new HandleModel({
1447
- position: this.position,
1448
- type: this.type,
1449
- id: this.id,
1450
- parentPosition: { x, y },
1451
- parentSize: { width, height }
1452
- }, this.handleService.node());
1453
- this.handleService.createHandle(this.model);
1454
- });
1694
+ this.model = new HandleModel({
1695
+ position: this.position,
1696
+ type: this.type,
1697
+ id: this.id,
1698
+ parentReference: this.element.parentElement,
1699
+ template: this.template
1700
+ }, this.handleService.node());
1701
+ this.handleService.createHandle(this.model);
1702
+ queueMicrotask(() => this.model.updateParent());
1455
1703
  }
1456
1704
  ngOnDestroy() {
1457
1705
  this.handleService.destroyHandle(this.model);
1458
1706
  }
1459
- parentRect() {
1460
- const parent = this.element.parentElement;
1461
- return {
1462
- x: parent.offsetLeft,
1463
- y: parent.offsetTop,
1464
- width: parent.clientWidth,
1465
- height: parent.clientHeight
1466
- };
1467
- }
1468
1707
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1469
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: HandleComponent, selector: "handle", inputs: { position: "position", type: "type", id: "id" }, ngImport: i0, template: "" }); }
1708
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: HandleComponent, selector: "handle", inputs: { position: "position", type: "type", id: "id", template: "template" }, ngImport: i0, template: "" }); }
1470
1709
  }
1710
+ __decorate([
1711
+ InjectionContext
1712
+ ], HandleComponent.prototype, "ngOnInit", null);
1471
1713
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleComponent, decorators: [{
1472
1714
  type: Component,
1473
1715
  args: [{ selector: 'handle', template: "" }]
@@ -1479,6 +1721,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1479
1721
  args: [{ required: true }]
1480
1722
  }], id: [{
1481
1723
  type: Input
1724
+ }], template: [{
1725
+ type: Input
1726
+ }], ngOnInit: [] } });
1727
+
1728
+ class SelectableDirective {
1729
+ constructor() {
1730
+ this.flowSettingsService = inject(FlowSettingsService);
1731
+ this.selectionService = inject(SelectionService);
1732
+ this.parentEdge = inject(EdgeComponent, { optional: true });
1733
+ this.parentNode = inject(NodeComponent, { optional: true });
1734
+ }
1735
+ onMousedown() {
1736
+ const entity = this.entity();
1737
+ if (entity && this.flowSettingsService.entitiesSelectable()) {
1738
+ this.selectionService.select(entity);
1739
+ }
1740
+ }
1741
+ entity() {
1742
+ if (this.parentNode) {
1743
+ return this.parentNode.nodeModel;
1744
+ }
1745
+ else if (this.parentEdge) {
1746
+ return this.parentEdge.model;
1747
+ }
1748
+ return null;
1749
+ }
1750
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1751
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: SelectableDirective, selector: "[selectable]", host: { listeners: { "mousedown": "onMousedown()" } }, ngImport: i0 }); }
1752
+ }
1753
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectableDirective, decorators: [{
1754
+ type: Directive,
1755
+ args: [{ selector: '[selectable]' }]
1756
+ }], propDecorators: { onMousedown: [{
1757
+ type: HostListener,
1758
+ args: ['mousedown']
1482
1759
  }] } });
1483
1760
 
1484
1761
  const components = [
@@ -1495,6 +1772,8 @@ const directives = [
1495
1772
  MapContextDirective,
1496
1773
  RootSvgReferenceDirective,
1497
1774
  RootSvgContextDirective,
1775
+ HandleSizeControllerDirective,
1776
+ SelectableDirective
1498
1777
  ];
1499
1778
  const templateDirectives = [
1500
1779
  NodeHtmlTemplateDirective,
@@ -1514,12 +1793,15 @@ class VflowModule {
1514
1793
  DefsComponent, SpacePointContextDirective,
1515
1794
  MapContextDirective,
1516
1795
  RootSvgReferenceDirective,
1517
- RootSvgContextDirective, NodeHtmlTemplateDirective,
1796
+ RootSvgContextDirective,
1797
+ HandleSizeControllerDirective,
1798
+ SelectableDirective, NodeHtmlTemplateDirective,
1518
1799
  EdgeLabelHtmlTemplateDirective,
1519
1800
  EdgeTemplateDirective,
1520
1801
  ConnectionTemplateDirective,
1521
1802
  HandleTemplateDirective], imports: [CommonModule], exports: [VflowComponent,
1522
- HandleComponent, NodeHtmlTemplateDirective,
1803
+ HandleComponent,
1804
+ SelectableDirective, NodeHtmlTemplateDirective,
1523
1805
  EdgeLabelHtmlTemplateDirective,
1524
1806
  EdgeTemplateDirective,
1525
1807
  ConnectionTemplateDirective,
@@ -1533,6 +1815,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1533
1815
  exports: [
1534
1816
  VflowComponent,
1535
1817
  HandleComponent,
1818
+ SelectableDirective,
1536
1819
  ...templateDirectives
1537
1820
  ],
1538
1821
  declarations: [...components, ...directives, ...templateDirectives],
@@ -1545,5 +1828,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1545
1828
  * Generated bundle index. Do not edit.
1546
1829
  */
1547
1830
 
1548
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, VflowComponent, VflowModule };
1831
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
1549
1832
  //# sourceMappingURL=ngx-vflow.mjs.map