ngx-vflow 0.3.0 → 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 (48) hide show
  1. package/esm2022/lib/vflow/components/edge/edge.component.mjs +18 -6
  2. package/esm2022/lib/vflow/components/handle/handle.component.mjs +7 -7
  3. package/esm2022/lib/vflow/components/node/node.component.mjs +22 -8
  4. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +33 -20
  5. package/esm2022/lib/vflow/decorators/run-in-injection-context.decorator.mjs +6 -14
  6. package/esm2022/lib/vflow/directives/map-context.directive.mjs +31 -4
  7. package/esm2022/lib/vflow/directives/root-svg-context.directive.mjs +1 -1
  8. package/esm2022/lib/vflow/directives/selectable.directive.mjs +39 -0
  9. package/esm2022/lib/vflow/interfaces/flow-entity.interface.mjs +2 -0
  10. package/esm2022/lib/vflow/interfaces/template-context.interface.mjs +1 -1
  11. package/esm2022/lib/vflow/models/edge.model.mjs +3 -1
  12. package/esm2022/lib/vflow/models/node.model.mjs +9 -12
  13. package/esm2022/lib/vflow/services/edge-changes.service.mjs +6 -2
  14. package/esm2022/lib/vflow/services/flow-entities.service.mjs +5 -1
  15. package/esm2022/lib/vflow/services/flow-settings.service.mjs +25 -0
  16. package/esm2022/lib/vflow/services/node-changes.service.mjs +7 -3
  17. package/esm2022/lib/vflow/services/node-rendering.service.mjs +22 -0
  18. package/esm2022/lib/vflow/services/selection.service.mjs +45 -0
  19. package/esm2022/lib/vflow/types/edge-change.type.mjs +1 -1
  20. package/esm2022/lib/vflow/types/node-change.type.mjs +1 -1
  21. package/esm2022/lib/vflow/vflow.module.mjs +9 -4
  22. package/esm2022/public-api.mjs +2 -1
  23. package/fesm2022/ngx-vflow.mjs +307 -144
  24. package/fesm2022/ngx-vflow.mjs.map +1 -1
  25. package/lib/vflow/components/edge/edge.component.d.ts +5 -2
  26. package/lib/vflow/components/handle/handle.component.d.ts +4 -3
  27. package/lib/vflow/components/node/node.component.d.ts +9 -3
  28. package/lib/vflow/components/vflow/vflow.component.d.ts +11 -5
  29. package/lib/vflow/decorators/run-in-injection-context.decorator.d.ts +2 -5
  30. package/lib/vflow/directives/map-context.directive.d.ts +5 -0
  31. package/lib/vflow/directives/selectable.directive.d.ts +11 -0
  32. package/lib/vflow/interfaces/flow-entity.interface.d.ts +4 -0
  33. package/lib/vflow/interfaces/template-context.interface.d.ts +1 -0
  34. package/lib/vflow/models/edge.model.d.ts +7 -3
  35. package/lib/vflow/models/node.model.d.ts +6 -9
  36. package/lib/vflow/services/edge-changes.service.d.ts +5 -0
  37. package/lib/vflow/services/flow-entities.service.d.ts +5 -2
  38. package/lib/vflow/services/flow-settings.service.d.ts +20 -0
  39. package/lib/vflow/services/node-changes.service.d.ts +5 -0
  40. package/lib/vflow/services/node-rendering.service.d.ts +9 -0
  41. package/lib/vflow/services/selection.service.d.ts +19 -0
  42. package/lib/vflow/types/edge-change.type.d.ts +5 -1
  43. package/lib/vflow/types/node-change.type.d.ts +5 -1
  44. package/lib/vflow/vflow.module.d.ts +4 -3
  45. package/package.json +3 -3
  46. package/public-api.d.ts +1 -0
  47. package/esm2022/lib/vflow/models/flow.model.mjs +0 -18
  48. package/lib/vflow/models/flow.model.d.ts +0 -16
@@ -1,12 +1,12 @@
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, runInInjectionContext, Injector, NgZone, Component, ChangeDetectionStrategy, ViewChild, HostListener, 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, Subject, Observable, Subscription, startWith, fromEvent } from 'rxjs';
10
10
  import { path } from 'd3-path';
11
11
  import { __decorate } from 'tslib';
12
12
 
@@ -58,13 +58,123 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
58
58
  }]
59
59
  }] });
60
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
+
61
169
  class MapContextDirective {
62
170
  constructor() {
63
171
  this.rootSvg = inject(RootSvgReferenceDirective).element;
64
172
  this.host = inject(ElementRef).nativeElement;
173
+ this.selectionService = inject(SelectionService);
65
174
  this.viewportService = inject(ViewportService);
66
175
  this.rootSvgSelection = select(this.rootSvg);
67
176
  this.zoomableSelection = select(this.host);
177
+ this.viewportForSelection = {};
68
178
  // under the hood this effect triggers handleZoom, so error throws without this flag
69
179
  // TODO: hack with timer fixes wrong node scaling (handle positions not matched with content size)
70
180
  this.manualViewportChangeEffect = effect(() => setTimeout(() => {
@@ -91,15 +201,32 @@ class MapContextDirective {
91
201
  }), { allowSignalWrites: true });
92
202
  this.handleZoom = ({ transform }) => {
93
203
  // update public signal for user to read
94
- this.viewportService.readableViewport.set({ zoom: transform.k, x: transform.x, y: transform.y });
204
+ this.viewportService.readableViewport.set(mapTransformToViewportState(transform));
95
205
  this.zoomableSelection.attr('transform', transform.toString());
96
206
  };
97
207
  }
98
208
  ngOnInit() {
99
209
  this.zoomBehavior = zoom()
100
210
  .scaleExtent([this.minZoom, this.maxZoom])
101
- .on('zoom', this.handleZoom);
102
- 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);
103
230
  }
104
231
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MapContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
105
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 }); }
@@ -112,6 +239,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
112
239
  }], maxZoom: [{
113
240
  type: Input
114
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
+ };
115
249
 
116
250
  const round = (num) => Math.round(num * 100) / 100;
117
251
 
@@ -243,23 +377,6 @@ function addNodesToEdges(nodes, edges) {
243
377
  });
244
378
  }
245
379
 
246
- class FlowModel {
247
- constructor() {
248
- /**
249
- * Global setting with handle positions. Nodes derive this value
250
- *
251
- * @deprecated
252
- */
253
- this.handlePositions = signal({ source: 'right', target: 'left' });
254
- /**
255
- * @see {VflowComponent.view}
256
- */
257
- this.view = signal([400, 400]);
258
- this.flowWidth = computed(() => this.view() === 'auto' ? '100%' : this.view()[0]);
259
- this.flowHeight = computed(() => this.view() === 'auto' ? '100%' : this.view()[1]);
260
- }
261
- }
262
-
263
380
  class FlowStatusService {
264
381
  constructor() {
265
382
  this.status = signal({ state: 'idle', payload: null });
@@ -297,70 +414,6 @@ function batchStatusChanges(...changes) {
297
414
  }
298
415
  }
299
416
 
300
- class ConnectionModel {
301
- constructor(connection) {
302
- this.connection = connection;
303
- this.curve = connection.curve ?? 'bezier';
304
- this.type = connection.type ?? 'default';
305
- this.validator = connection.validator ?? (() => true);
306
- }
307
- }
308
-
309
- function hashCode(str) {
310
- return str.split('').reduce((a, b) => {
311
- a = ((a << 5) - a) + b.charCodeAt(0);
312
- return a & a;
313
- }, 0);
314
- }
315
-
316
- class FlowEntitiesService {
317
- constructor() {
318
- this.nodes = signal([], {
319
- // empty arrays considered equal, other arrays may not be equal
320
- equal: (a, b) => !a.length && !b.length ? true : a === b
321
- });
322
- this.edges = signal([], {
323
- // empty arrays considered equal, other arrays may not be equal
324
- equal: (a, b) => !a.length && !b.length ? true : a === b
325
- });
326
- this.connection = signal(new ConnectionModel({}));
327
- this.markers = computed(() => {
328
- const markersMap = new Map();
329
- this.validEdges().forEach(e => {
330
- if (e.edge.markers?.start) {
331
- const hash = hashCode(JSON.stringify(e.edge.markers.start));
332
- markersMap.set(hash, e.edge.markers.start);
333
- }
334
- if (e.edge.markers?.end) {
335
- const hash = hashCode(JSON.stringify(e.edge.markers.end));
336
- markersMap.set(hash, e.edge.markers.end);
337
- }
338
- });
339
- const connectionMarker = this.connection().connection.marker;
340
- if (connectionMarker) {
341
- const hash = hashCode(JSON.stringify(connectionMarker));
342
- markersMap.set(hash, connectionMarker);
343
- }
344
- return markersMap;
345
- });
346
- this.validEdges = computed(() => {
347
- const nodes = this.nodes();
348
- return this.edges().filter(e => nodes.includes(e.source()) && nodes.includes(e.target()));
349
- });
350
- }
351
- getNode(id) {
352
- return this.nodes().find(({ node }) => node.id === id);
353
- }
354
- getDetachedEdges() {
355
- return this.edges().filter(e => e.detached());
356
- }
357
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
358
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService }); }
359
- }
360
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowEntitiesService, decorators: [{
361
- type: Injectable
362
- }] });
363
-
364
417
  class ConnectionControllerDirective {
365
418
  constructor() {
366
419
  /**
@@ -403,16 +456,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
403
456
  type: Output
404
457
  }] } });
405
458
 
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]);
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 }); }
477
+ }
478
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowSettingsService, decorators: [{
479
+ type: Injectable
480
+ }] });
481
+
406
482
  class NodeModel {
407
483
  constructor(node) {
408
484
  this.node = node;
485
+ this.flowSettingsService = inject(FlowSettingsService);
409
486
  this.point = signal({ x: 0, y: 0 });
410
487
  this.point$ = toObservable(this.point);
411
488
  this.size = signal({ width: 0, height: 0 });
489
+ this.renderOrder = signal(0);
490
+ this.selected = signal(false);
491
+ this.selected$ = toObservable(this.selected);
412
492
  this.pointTransform = computed(() => `translate(${this.point().x}, ${this.point().y})`);
413
493
  // Now source and handle positions derived from parent flow
414
- this.sourcePosition = computed(() => this.flow.handlePositions().source);
415
- this.targetPosition = computed(() => this.flow.handlePositions().target);
494
+ this.sourcePosition = computed(() => this.flowSettingsService.handlePositions().source);
495
+ this.targetPosition = computed(() => this.flowSettingsService.handlePositions().target);
416
496
  this.handles = signal([]);
417
497
  this.handles$ = toObservable(this.handles);
418
498
  this.draggable = true;
@@ -422,14 +502,6 @@ class NodeModel {
422
502
  if (isDefined(node.draggable))
423
503
  this.draggable = node.draggable;
424
504
  }
425
- /**
426
- * Bind parent flow model to node
427
- *
428
- * @param flow parent flow
429
- */
430
- bindFlow(flow) {
431
- this.flow = flow;
432
- }
433
505
  }
434
506
 
435
507
  class EdgeLabelModel {
@@ -547,6 +619,8 @@ class EdgeModel {
547
619
  this.edge = edge;
548
620
  this.source = signal(undefined);
549
621
  this.target = signal(undefined);
622
+ this.selected = signal(false);
623
+ this.selected$ = toObservable(this.selected);
550
624
  this.detached = computed(() => {
551
625
  const source = this.source();
552
626
  const target = this.target();
@@ -671,7 +745,11 @@ class NodesChangeService {
671
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 }))));
672
746
  this.nodeRemoveChange$ = toObservable(this.entitiesService.nodes)
673
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 }))));
674
- 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(
675
753
  // this fixes a bug when on fire node event change,
676
754
  // you can't get valid list of detached edges
677
755
  observeOn(asyncScheduler));
@@ -708,7 +786,11 @@ class EdgeChangesService {
708
786
  .pipe(pairwise(), map(([oldList, newList]) => {
709
787
  return oldList.filter(edge => !newList.includes(edge));
710
788
  }), filter(edges => !!edges.length), map((edges) => edges.map(({ edge }) => ({ type: 'remove', id: edge.id }))));
711
- 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$)
712
794
  .pipe(
713
795
  // this fixes the case when user gets 'deteched' changes
714
796
  // and tries to delete these edges inside stream
@@ -757,6 +839,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
757
839
  type: Output
758
840
  }] } });
759
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
+
760
861
  class HandleService {
761
862
  constructor() {
762
863
  this.node = signal(null);
@@ -868,7 +969,7 @@ function resizable(elems, zone) {
868
969
  function InjectionContext(target, key, descriptor) {
869
970
  const originalMethod = descriptor.value;
870
971
  descriptor.value = function (...args) {
871
- if (this instanceof WithInjectorDirective) {
972
+ if (implementsWithInjector(this)) {
872
973
  return runInInjectionContext(this.injector, () => originalMethod.apply(this, args));
873
974
  }
874
975
  else {
@@ -878,16 +979,9 @@ function InjectionContext(target, key, descriptor) {
878
979
  // Return the modified descriptor
879
980
  return descriptor;
880
981
  }
881
- class WithInjectorDirective {
882
- constructor() {
883
- this.injector = inject(Injector);
884
- }
885
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: WithInjectorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
886
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: WithInjectorDirective, ngImport: i0 }); }
887
- }
888
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: WithInjectorDirective, decorators: [{
889
- type: Directive
890
- }] });
982
+ const implementsWithInjector = (instance) => {
983
+ return 'injector' in instance && 'get' in instance.injector;
984
+ };
891
985
 
892
986
  function Microtask(target, key, descriptor) {
893
987
  const originalMethod = descriptor.value;
@@ -936,14 +1030,17 @@ function getChildStrokeWidth(element) {
936
1030
  return 0;
937
1031
  }
938
1032
 
939
- class NodeComponent extends WithInjectorDirective {
1033
+ class NodeComponent {
940
1034
  constructor() {
941
- super(...arguments);
1035
+ this.injector = inject(Injector);
942
1036
  this.handleService = inject(HandleService);
943
1037
  this.zone = inject(NgZone);
944
1038
  this.draggableService = inject(DraggableService);
945
1039
  this.flowStatusService = inject(FlowStatusService);
946
1040
  this.flowEntitiesService = inject(FlowEntitiesService);
1041
+ this.nodeRenderingService = inject(NodeRenderingService);
1042
+ this.flowSettingsService = inject(FlowSettingsService);
1043
+ this.selectionService = inject(SelectionService);
947
1044
  this.hostRef = inject(ElementRef);
948
1045
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
949
1046
  this.flowStatusService.status().state === 'connection-validation');
@@ -1029,6 +1126,14 @@ class NodeComponent extends WithInjectorDirective {
1029
1126
  this.flowStatusService.setConnectionStartStatus(status.payload.sourceNode, status.payload.sourceHandle);
1030
1127
  }
1031
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
+ }
1032
1137
  setInitialHandles() {
1033
1138
  if (this.nodeModel.node.type === 'default') {
1034
1139
  this.handleService.createHandle(new HandleModel({
@@ -1043,8 +1148,8 @@ class NodeComponent extends WithInjectorDirective {
1043
1148
  }, this.nodeModel));
1044
1149
  }
1045
1150
  }
1046
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1047
- 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 }], usesInheritance: true, ngImport: i0, template: "<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n #nodeContent\n width=\"100\"\n height=\"50\"\n>\n <div #htmlWrapper 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 *ngIf=\"!handle.template\"\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: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: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"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1151
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
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 }); }
1048
1153
  }
1049
1154
  __decorate([
1050
1155
  Microtask
@@ -1054,7 +1159,7 @@ __decorate([
1054
1159
  ], NodeComponent.prototype, "setInitialHandles", null);
1055
1160
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
1056
1161
  type: Component,
1057
- 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 #htmlWrapper 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 *ngIf=\"!handle.template\"\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: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: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"] }]
1058
1163
  }], propDecorators: { nodeModel: [{
1059
1164
  type: Input
1060
1165
  }], nodeHtmlTemplate: [{
@@ -1125,6 +1230,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1125
1230
 
1126
1231
  class EdgeComponent {
1127
1232
  constructor() {
1233
+ this.injector = inject(Injector);
1234
+ this.selectionService = inject(SelectionService);
1235
+ this.flowSettingsService = inject(FlowSettingsService);
1128
1236
  this.markerStartUrl = computed(() => {
1129
1237
  const marker = this.model.edge.markers?.start;
1130
1238
  return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
@@ -1133,7 +1241,6 @@ class EdgeComponent {
1133
1241
  const marker = this.model.edge.markers?.end;
1134
1242
  return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
1135
1243
  });
1136
- this.defaultColor = 'rgb(177, 177, 183)';
1137
1244
  }
1138
1245
  ngOnInit() {
1139
1246
  this.edgeContext = {
@@ -1142,16 +1249,24 @@ class EdgeComponent {
1142
1249
  edge: this.model.edge,
1143
1250
  path: computed(() => this.model.path().path),
1144
1251
  markerStart: this.markerStartUrl,
1145
- markerEnd: this.markerEndUrl
1252
+ markerEnd: this.markerEndUrl,
1253
+ selected: this.model.selected.asReadonly()
1146
1254
  }
1147
1255
  };
1148
1256
  }
1257
+ onEdgeMouseDown() {
1258
+ if (this.flowSettingsService.entitiesSelectable()) {
1259
+ this.selectionService.select(this.model);
1260
+ }
1261
+ }
1149
1262
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: EdgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1150
- 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 }); }
1151
1264
  }
1152
1265
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: EdgeComponent, decorators: [{
1153
1266
  type: Component,
1154
- 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"] }]
1155
1270
  }], propDecorators: { model: [{
1156
1271
  type: Input
1157
1272
  }], edgeTemplate: [{
@@ -1350,6 +1465,8 @@ class VflowComponent {
1350
1465
  this.flowEntitiesService = inject(FlowEntitiesService);
1351
1466
  this.nodesChangeService = inject(NodesChangeService);
1352
1467
  this.edgesChangeService = inject(EdgeChangesService);
1468
+ this.nodeRenderingService = inject(NodeRenderingService);
1469
+ this.flowSettingsService = inject(FlowSettingsService);
1353
1470
  this.injector = inject(Injector);
1354
1471
  /**
1355
1472
  * Minimum zoom value
@@ -1363,6 +1480,8 @@ class VflowComponent {
1363
1480
  * Background color for flow
1364
1481
  */
1365
1482
  this.background = '#FFFFFF';
1483
+ this.nodeModels = computed(() => this.nodeRenderingService.nodes());
1484
+ this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
1366
1485
  // #endregion
1367
1486
  // #region SIGNAL_API
1368
1487
  /**
@@ -1393,8 +1512,8 @@ class VflowComponent {
1393
1512
  */
1394
1513
  this.edgesChange$ = this.edgesChangeService.changes$;
1395
1514
  // #endregion
1396
- // TODO: probably better to make it injectable
1397
- this.flowModel = new FlowModel();
1515
+ this.flowWidth = this.flowSettingsService.flowWidth;
1516
+ this.flowHeight = this.flowSettingsService.flowHeight;
1398
1517
  this.markers = this.flowEntitiesService.markers;
1399
1518
  }
1400
1519
  // #endregion
@@ -1407,7 +1526,7 @@ class VflowComponent {
1407
1526
  * - 'auto' to compute size based on parent element size
1408
1527
  */
1409
1528
  set view(view) {
1410
- this.flowModel.view.set(view);
1529
+ this.flowSettingsService.view.set(view);
1411
1530
  }
1412
1531
  /**
1413
1532
  * Object that controls flow direction.
@@ -1418,7 +1537,13 @@ class VflowComponent {
1418
1537
  * @deprecated
1419
1538
  */
1420
1539
  set handlePositions(handlePositions) {
1421
- 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);
1422
1547
  }
1423
1548
  /**
1424
1549
  * Settings for connection (it renders when user tries to create edge between nodes)
@@ -1434,23 +1559,19 @@ class VflowComponent {
1434
1559
  */
1435
1560
  set nodes(newNodes) {
1436
1561
  const newModels = runInInjectionContext(this.injector, () => ReferenceKeeper.nodes(newNodes, this.flowEntitiesService.nodes()));
1437
- // TODO better to solve this by DI
1438
- bindFlowToNodes(this.flowModel, newModels);
1439
1562
  // quick and dirty binding nodes to edges
1440
1563
  addNodesToEdges(newModels, this.flowEntitiesService.edges());
1441
1564
  this.flowEntitiesService.nodes.set(newModels);
1442
1565
  }
1443
- get nodeModels() { return this.flowEntitiesService.nodes(); }
1444
1566
  /**
1445
1567
  * Edges to render
1446
1568
  */
1447
1569
  set edges(newEdges) {
1448
1570
  const newModels = runInInjectionContext(this.injector, () => ReferenceKeeper.edges(newEdges, this.flowEntitiesService.edges()));
1449
1571
  // quick and dirty binding nodes to edges
1450
- addNodesToEdges(this.nodeModels, newModels);
1572
+ addNodesToEdges(this.nodeModels(), newModels);
1451
1573
  this.flowEntitiesService.edges.set(newModels);
1452
1574
  }
1453
- get edgeModels() { return this.flowEntitiesService.validEdges(); }
1454
1575
  // #region METHODS_API
1455
1576
  /**
1456
1577
  * Change viewport to specified state
@@ -1498,14 +1619,17 @@ class VflowComponent {
1498
1619
  return edge;
1499
1620
  }
1500
1621
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1501
- 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: [
1502
1623
  DraggableService,
1503
1624
  ViewportService,
1504
1625
  FlowStatusService,
1505
1626
  FlowEntitiesService,
1506
1627
  NodesChangeService,
1507
- EdgeChangesService
1508
- ], 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 }); }
1509
1633
  }
1510
1634
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
1511
1635
  type: Component,
@@ -1515,11 +1639,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1515
1639
  FlowStatusService,
1516
1640
  FlowEntitiesService,
1517
1641
  NodesChangeService,
1518
- EdgeChangesService
1642
+ EdgeChangesService,
1643
+ NodeRenderingService,
1644
+ SelectionService,
1645
+ FlowSettingsService
1519
1646
  ], hostDirectives: [
1520
1647
  connectionControllerHostDirective,
1521
1648
  changesControllerHostDirective
1522
- ], 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"] }]
1523
1650
  }], propDecorators: { view: [{
1524
1651
  type: Input
1525
1652
  }], minZoom: [{
@@ -1530,6 +1657,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1530
1657
  type: Input
1531
1658
  }], background: [{
1532
1659
  type: Input
1660
+ }], entitiesSelectable: [{
1661
+ type: Input
1533
1662
  }], connection: [{
1534
1663
  type: Input,
1535
1664
  args: [{ transform: (settings) => new ConnectionModel(settings) }]
@@ -1554,13 +1683,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1554
1683
  type: ViewChild,
1555
1684
  args: [MapContextDirective]
1556
1685
  }] } });
1557
- function bindFlowToNodes(flow, nodes) {
1558
- nodes.forEach(n => n.bindFlow(flow));
1559
- }
1560
1686
 
1561
- class HandleComponent extends WithInjectorDirective {
1687
+ class HandleComponent {
1562
1688
  constructor() {
1563
- super(...arguments);
1689
+ this.injector = inject(Injector);
1564
1690
  this.handleService = inject(HandleService);
1565
1691
  this.element = inject(ElementRef).nativeElement;
1566
1692
  }
@@ -1578,8 +1704,8 @@ class HandleComponent extends WithInjectorDirective {
1578
1704
  ngOnDestroy() {
1579
1705
  this.handleService.destroyHandle(this.model);
1580
1706
  }
1581
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1582
- 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" }, usesInheritance: true, ngImport: i0, template: "" }); }
1707
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
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: "" }); }
1583
1709
  }
1584
1710
  __decorate([
1585
1711
  InjectionContext
@@ -1599,6 +1725,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1599
1725
  type: Input
1600
1726
  }], ngOnInit: [] } });
1601
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']
1759
+ }] } });
1760
+
1602
1761
  const components = [
1603
1762
  VflowComponent,
1604
1763
  NodeComponent,
@@ -1613,7 +1772,8 @@ const directives = [
1613
1772
  MapContextDirective,
1614
1773
  RootSvgReferenceDirective,
1615
1774
  RootSvgContextDirective,
1616
- HandleSizeControllerDirective
1775
+ HandleSizeControllerDirective,
1776
+ SelectableDirective
1617
1777
  ];
1618
1778
  const templateDirectives = [
1619
1779
  NodeHtmlTemplateDirective,
@@ -1634,12 +1794,14 @@ class VflowModule {
1634
1794
  MapContextDirective,
1635
1795
  RootSvgReferenceDirective,
1636
1796
  RootSvgContextDirective,
1637
- HandleSizeControllerDirective, NodeHtmlTemplateDirective,
1797
+ HandleSizeControllerDirective,
1798
+ SelectableDirective, NodeHtmlTemplateDirective,
1638
1799
  EdgeLabelHtmlTemplateDirective,
1639
1800
  EdgeTemplateDirective,
1640
1801
  ConnectionTemplateDirective,
1641
1802
  HandleTemplateDirective], imports: [CommonModule], exports: [VflowComponent,
1642
- HandleComponent, NodeHtmlTemplateDirective,
1803
+ HandleComponent,
1804
+ SelectableDirective, NodeHtmlTemplateDirective,
1643
1805
  EdgeLabelHtmlTemplateDirective,
1644
1806
  EdgeTemplateDirective,
1645
1807
  ConnectionTemplateDirective,
@@ -1653,6 +1815,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1653
1815
  exports: [
1654
1816
  VflowComponent,
1655
1817
  HandleComponent,
1818
+ SelectableDirective,
1656
1819
  ...templateDirectives
1657
1820
  ],
1658
1821
  declarations: [...components, ...directives, ...templateDirectives],
@@ -1665,5 +1828,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1665
1828
  * Generated bundle index. Do not edit.
1666
1829
  */
1667
1830
 
1668
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, VflowComponent, VflowModule };
1831
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
1669
1832
  //# sourceMappingURL=ngx-vflow.mjs.map