ngx-vflow 0.3.0 → 0.5.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 (56) 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 +23 -8
  4. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +63 -22
  5. package/esm2022/lib/vflow/decorators/run-in-injection-context.decorator.mjs +6 -14
  6. package/esm2022/lib/vflow/directives/changes-controller.directive.mjs +115 -14
  7. package/esm2022/lib/vflow/directives/map-context.directive.mjs +31 -4
  8. package/esm2022/lib/vflow/directives/pointer.directive.mjs +84 -0
  9. package/esm2022/lib/vflow/directives/root-pointer.directive.mjs +42 -0
  10. package/esm2022/lib/vflow/directives/root-svg-context.directive.mjs +5 -2
  11. package/esm2022/lib/vflow/directives/selectable.directive.mjs +39 -0
  12. package/esm2022/lib/vflow/directives/space-point-context.directive.mjs +10 -6
  13. package/esm2022/lib/vflow/interfaces/flow-entity.interface.mjs +2 -0
  14. package/esm2022/lib/vflow/interfaces/template-context.interface.mjs +1 -1
  15. package/esm2022/lib/vflow/models/edge.model.mjs +3 -1
  16. package/esm2022/lib/vflow/models/node.model.mjs +9 -12
  17. package/esm2022/lib/vflow/services/edge-changes.service.mjs +6 -2
  18. package/esm2022/lib/vflow/services/flow-entities.service.mjs +5 -1
  19. package/esm2022/lib/vflow/services/flow-settings.service.mjs +25 -0
  20. package/esm2022/lib/vflow/services/node-changes.service.mjs +7 -3
  21. package/esm2022/lib/vflow/services/node-rendering.service.mjs +22 -0
  22. package/esm2022/lib/vflow/services/selection.service.mjs +45 -0
  23. package/esm2022/lib/vflow/types/edge-change.type.mjs +1 -1
  24. package/esm2022/lib/vflow/types/node-change.type.mjs +1 -1
  25. package/esm2022/lib/vflow/vflow.module.mjs +15 -4
  26. package/esm2022/public-api.mjs +2 -1
  27. package/fesm2022/ngx-vflow.mjs +582 -161
  28. package/fesm2022/ngx-vflow.mjs.map +1 -1
  29. package/lib/vflow/components/edge/edge.component.d.ts +5 -2
  30. package/lib/vflow/components/handle/handle.component.d.ts +4 -3
  31. package/lib/vflow/components/node/node.component.d.ts +10 -4
  32. package/lib/vflow/components/vflow/vflow.component.d.ts +12 -6
  33. package/lib/vflow/decorators/run-in-injection-context.decorator.d.ts +2 -5
  34. package/lib/vflow/directives/changes-controller.directive.d.ts +35 -9
  35. package/lib/vflow/directives/map-context.directive.d.ts +5 -0
  36. package/lib/vflow/directives/pointer.directive.d.ts +21 -0
  37. package/lib/vflow/directives/root-pointer.directive.d.ts +40 -0
  38. package/lib/vflow/directives/selectable.directive.d.ts +11 -0
  39. package/lib/vflow/directives/space-point-context.directive.d.ts +13 -3
  40. package/lib/vflow/interfaces/flow-entity.interface.d.ts +4 -0
  41. package/lib/vflow/interfaces/template-context.interface.d.ts +1 -0
  42. package/lib/vflow/models/edge.model.d.ts +8 -20
  43. package/lib/vflow/models/node.model.d.ts +6 -9
  44. package/lib/vflow/services/edge-changes.service.d.ts +5 -0
  45. package/lib/vflow/services/flow-entities.service.d.ts +5 -2
  46. package/lib/vflow/services/flow-settings.service.d.ts +20 -0
  47. package/lib/vflow/services/node-changes.service.d.ts +5 -0
  48. package/lib/vflow/services/node-rendering.service.d.ts +9 -0
  49. package/lib/vflow/services/selection.service.d.ts +19 -0
  50. package/lib/vflow/types/edge-change.type.d.ts +5 -1
  51. package/lib/vflow/types/node-change.type.d.ts +5 -1
  52. package/lib/vflow/vflow.module.d.ts +6 -3
  53. package/package.json +3 -3
  54. package/public-api.d.ts +1 -0
  55. package/esm2022/lib/vflow/models/flow.model.mjs +0 -18
  56. 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, HostListener, Injector, NgZone, Component, ChangeDetectionStrategy, ViewChild, 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, fromEvent, animationFrameScheduler, share, Subscription, startWith } 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
@@ -730,20 +812,50 @@ class ChangesControllerDirective {
730
812
  /**
731
813
  * Watch nodes change
732
814
  */
733
- this.onNodesChange = new EventEmitter();
815
+ this.onNodesChange = this.nodesChangeService.changes$;
816
+ this.onNodesChangePosition = this.nodeChangesOfType('position');
817
+ this.onNodesChangePositionSignle = this.singleChange(this.nodeChangesOfType('position'));
818
+ this.onNodesChangePositionMany = this.manyChanges(this.nodeChangesOfType('position'));
819
+ this.onNodesChangeAdd = this.nodeChangesOfType('add');
820
+ this.onNodesChangeAddSingle = this.singleChange(this.nodeChangesOfType('add'));
821
+ this.onNodesChangeAddMany = this.manyChanges(this.nodeChangesOfType('add'));
822
+ this.onNodesChangeRemove = this.nodeChangesOfType('remove');
823
+ this.onNodesChangeRemoveSingle = this.singleChange(this.nodeChangesOfType('remove'));
824
+ this.onNodesChangeRemoveMany = this.manyChanges(this.nodeChangesOfType('remove'));
825
+ this.onNodesChangeSelect = this.nodeChangesOfType('select');
826
+ this.onNodesChangeSelectSingle = this.singleChange(this.nodeChangesOfType('select'));
827
+ this.onNodesChangeSelectMany = this.manyChanges(this.nodeChangesOfType('select'));
734
828
  /**
735
- * Watch nodes change
829
+ * Watch edges change
736
830
  */
737
- this.onEdgesChange = new EventEmitter();
738
- this.nodesChangeProxySubscription = this.nodesChangeService.changes$
739
- .pipe(tap((changes) => this.onNodesChange.emit(changes)), takeUntilDestroyed())
740
- .subscribe();
741
- this.edgesChangeProxySubscription = this.edgesChangeService.changes$
742
- .pipe(tap((changes) => this.onEdgesChange.emit(changes)), takeUntilDestroyed())
743
- .subscribe();
831
+ this.onEdgesChange = this.edgesChangeService.changes$;
832
+ this.onNodesChangeDetached = this.edgeChangesOfType('detached');
833
+ this.onNodesChangeDetachedSingle = this.singleChange(this.edgeChangesOfType('detached'));
834
+ this.onNodesChangeDetachedMany = this.manyChanges(this.edgeChangesOfType('detached'));
835
+ this.onEdgesChangeAdd = this.edgeChangesOfType('add');
836
+ this.onEdgeChangeAddSingle = this.singleChange(this.edgeChangesOfType('add'));
837
+ this.onEdgeChangeAddMany = this.manyChanges(this.edgeChangesOfType('add'));
838
+ this.onEdgeChangeRemove = this.edgeChangesOfType('remove');
839
+ this.onEdgeChangeRemoveSingle = this.singleChange(this.edgeChangesOfType('remove'));
840
+ this.onEdgeChangeRemoveMany = this.manyChanges(this.edgeChangesOfType('remove'));
841
+ this.onEdgeChangeSelect = this.edgeChangesOfType('select');
842
+ this.onEdgeChangeSelectSingle = this.singleChange(this.edgeChangesOfType('select'));
843
+ this.onEdgeChangeSelectMany = this.manyChanges(this.edgeChangesOfType('select'));
844
+ }
845
+ nodeChangesOfType(type) {
846
+ return this.nodesChangeService.changes$.pipe(map(changes => changes.filter((c) => c.type === type)), filter(changes => !!changes.length));
847
+ }
848
+ edgeChangesOfType(type) {
849
+ return this.edgesChangeService.changes$.pipe(map(changes => changes.filter((c) => c.type === type)), filter(changes => !!changes.length));
850
+ }
851
+ singleChange(changes$) {
852
+ return changes$.pipe(filter(changes => changes.length === 1), map(([first]) => first));
853
+ }
854
+ manyChanges(changes$) {
855
+ return changes$.pipe(filter(changes => changes.length > 1));
744
856
  }
745
857
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChangesControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
746
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ChangesControllerDirective, isStandalone: true, selector: "[changesController]", outputs: { onNodesChange: "onNodesChange", onEdgesChange: "onEdgesChange" }, ngImport: i0 }); }
858
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ChangesControllerDirective, isStandalone: true, selector: "[changesController]", outputs: { onNodesChange: "onNodesChange", onNodesChangePosition: "onNodesChange.position", onNodesChangePositionSignle: "onNodesChange.position.single", onNodesChangePositionMany: "onNodesChange.position.many", onNodesChangeAdd: "onNodesChange.add", onNodesChangeAddSingle: "onNodesChange.add.single", onNodesChangeAddMany: "onNodesChange.add.many", onNodesChangeRemove: "onNodesChange.remove", onNodesChangeRemoveSingle: "onNodesChange.remove.single", onNodesChangeRemoveMany: "onNodesChange.remove.many", onNodesChangeSelect: "onNodesChange.select", onNodesChangeSelectSingle: "onNodesChange.select.single", onNodesChangeSelectMany: "onNodesChange.select.many", onEdgesChange: "onEdgesChange", onNodesChangeDetached: "onEdgesChange.detached", onNodesChangeDetachedSingle: "onEdgesChange.detached.single", onNodesChangeDetachedMany: "onEdgesChange.detached.many", onEdgesChangeAdd: "onEdgesChange.add", onEdgeChangeAddSingle: "onEdgesChange.add.single", onEdgeChangeAddMany: "onEdgesChange.add.many", onEdgeChangeRemove: "onEdgesChange.remove", onEdgeChangeRemoveSingle: "onEdgesChange.remove.single", onEdgeChangeRemoveMany: "onEdgesChange.remove.many", onEdgeChangeSelect: "onEdgesChange.select", onEdgeChangeSelectSingle: "onEdgesChange.select.single", onEdgeChangeSelectMany: "onEdgesChange.select.many" }, ngImport: i0 }); }
747
859
  }
748
860
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChangesControllerDirective, decorators: [{
749
861
  type: Directive,
@@ -753,10 +865,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
753
865
  }]
754
866
  }], propDecorators: { onNodesChange: [{
755
867
  type: Output
868
+ }], onNodesChangePosition: [{
869
+ type: Output,
870
+ args: ['onNodesChange.position']
871
+ }], onNodesChangePositionSignle: [{
872
+ type: Output,
873
+ args: ['onNodesChange.position.single']
874
+ }], onNodesChangePositionMany: [{
875
+ type: Output,
876
+ args: ['onNodesChange.position.many']
877
+ }], onNodesChangeAdd: [{
878
+ type: Output,
879
+ args: ['onNodesChange.add']
880
+ }], onNodesChangeAddSingle: [{
881
+ type: Output,
882
+ args: ['onNodesChange.add.single']
883
+ }], onNodesChangeAddMany: [{
884
+ type: Output,
885
+ args: ['onNodesChange.add.many']
886
+ }], onNodesChangeRemove: [{
887
+ type: Output,
888
+ args: ['onNodesChange.remove']
889
+ }], onNodesChangeRemoveSingle: [{
890
+ type: Output,
891
+ args: ['onNodesChange.remove.single']
892
+ }], onNodesChangeRemoveMany: [{
893
+ type: Output,
894
+ args: ['onNodesChange.remove.many']
895
+ }], onNodesChangeSelect: [{
896
+ type: Output,
897
+ args: ['onNodesChange.select']
898
+ }], onNodesChangeSelectSingle: [{
899
+ type: Output,
900
+ args: ['onNodesChange.select.single']
901
+ }], onNodesChangeSelectMany: [{
902
+ type: Output,
903
+ args: ['onNodesChange.select.many']
756
904
  }], onEdgesChange: [{
757
905
  type: Output
906
+ }], onNodesChangeDetached: [{
907
+ type: Output,
908
+ args: ['onEdgesChange.detached']
909
+ }], onNodesChangeDetachedSingle: [{
910
+ type: Output,
911
+ args: ['onEdgesChange.detached.single']
912
+ }], onNodesChangeDetachedMany: [{
913
+ type: Output,
914
+ args: ['onEdgesChange.detached.many']
915
+ }], onEdgesChangeAdd: [{
916
+ type: Output,
917
+ args: ['onEdgesChange.add']
918
+ }], onEdgeChangeAddSingle: [{
919
+ type: Output,
920
+ args: ['onEdgesChange.add.single']
921
+ }], onEdgeChangeAddMany: [{
922
+ type: Output,
923
+ args: ['onEdgesChange.add.many']
924
+ }], onEdgeChangeRemove: [{
925
+ type: Output,
926
+ args: ['onEdgesChange.remove']
927
+ }], onEdgeChangeRemoveSingle: [{
928
+ type: Output,
929
+ args: ['onEdgesChange.remove.single']
930
+ }], onEdgeChangeRemoveMany: [{
931
+ type: Output,
932
+ args: ['onEdgesChange.remove.many']
933
+ }], onEdgeChangeSelect: [{
934
+ type: Output,
935
+ args: ['onEdgesChange.select']
936
+ }], onEdgeChangeSelectSingle: [{
937
+ type: Output,
938
+ args: ['onEdgesChange.select.single']
939
+ }], onEdgeChangeSelectMany: [{
940
+ type: Output,
941
+ args: ['onEdgesChange.select.many']
758
942
  }] } });
759
943
 
944
+ class NodeRenderingService {
945
+ constructor() {
946
+ this.flowEntitiesService = inject(FlowEntitiesService);
947
+ this.nodes = computed(() => {
948
+ return this.flowEntitiesService.nodes()
949
+ .sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
950
+ });
951
+ }
952
+ pullNode(node) {
953
+ const maxOrder = Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
954
+ node.renderOrder.set(maxOrder + 1);
955
+ }
956
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
957
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService }); }
958
+ }
959
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService, decorators: [{
960
+ type: Injectable
961
+ }] });
962
+
760
963
  class HandleService {
761
964
  constructor() {
762
965
  this.node = signal(null);
@@ -868,7 +1071,7 @@ function resizable(elems, zone) {
868
1071
  function InjectionContext(target, key, descriptor) {
869
1072
  const originalMethod = descriptor.value;
870
1073
  descriptor.value = function (...args) {
871
- if (this instanceof WithInjectorDirective) {
1074
+ if (implementsWithInjector(this)) {
872
1075
  return runInInjectionContext(this.injector, () => originalMethod.apply(this, args));
873
1076
  }
874
1077
  else {
@@ -878,16 +1081,9 @@ function InjectionContext(target, key, descriptor) {
878
1081
  // Return the modified descriptor
879
1082
  return descriptor;
880
1083
  }
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
- }] });
1084
+ const implementsWithInjector = (instance) => {
1085
+ return 'injector' in instance && 'get' in instance.injector;
1086
+ };
891
1087
 
892
1088
  function Microtask(target, key, descriptor) {
893
1089
  const originalMethod = descriptor.value;
@@ -936,14 +1132,135 @@ function getChildStrokeWidth(element) {
936
1132
  return 0;
937
1133
  }
938
1134
 
939
- class NodeComponent extends WithInjectorDirective {
1135
+ class RootPointerDirective {
940
1136
  constructor() {
941
- super(...arguments);
1137
+ this.host = inject(ElementRef).nativeElement;
1138
+ this.initialTouch$ = new Subject();
1139
+ // TODO: do not emit if mouse not down
1140
+ this.mouseMovement$ = fromEvent(this.host, 'mousemove').pipe(map(event => ({
1141
+ x: event.clientX,
1142
+ y: event.clientY,
1143
+ originalEvent: event
1144
+ })), observeOn(animationFrameScheduler), share());
1145
+ this.touchMovement$ = merge(this.initialTouch$, fromEvent(this.host, 'touchmove')).pipe(tap((event) => event.preventDefault()), map((originalEvent) => {
1146
+ const x = originalEvent.touches[0]?.clientX ?? 0;
1147
+ const y = originalEvent.touches[0]?.clientY ?? 0;
1148
+ const target = document.elementFromPoint(x, y);
1149
+ return { x, y, target, originalEvent };
1150
+ }), observeOn(animationFrameScheduler), share());
1151
+ this.touchEnd$ = fromEvent(this.host, 'touchend').pipe(map((originalEvent) => {
1152
+ const x = originalEvent.changedTouches[0]?.clientX ?? 0;
1153
+ const y = originalEvent.changedTouches[0]?.clientY ?? 0;
1154
+ const target = document.elementFromPoint(x, y);
1155
+ return { x, y, target, originalEvent };
1156
+ }), share());
1157
+ this.pointerMovement$ = merge(this.mouseMovement$, this.touchMovement$);
1158
+ }
1159
+ /**
1160
+ * We should know when user started a touch in order to not
1161
+ * show old touch position when connection creation is started
1162
+ */
1163
+ setInitialTouch(event) {
1164
+ this.initialTouch$.next(event);
1165
+ }
1166
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootPointerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1167
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RootPointerDirective, selector: "svg[rootPointer]", ngImport: i0 }); }
1168
+ }
1169
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootPointerDirective, decorators: [{
1170
+ type: Directive,
1171
+ args: [{ selector: 'svg[rootPointer]' }]
1172
+ }] });
1173
+
1174
+ class PointerDirective {
1175
+ constructor() {
1176
+ this.hostElement = inject(ElementRef).nativeElement;
1177
+ this.pointerMovementDirective = inject(RootPointerDirective);
1178
+ this.pointerOver = new EventEmitter();
1179
+ this.pointerOut = new EventEmitter();
1180
+ this.pointerStart = new EventEmitter();
1181
+ this.pointerEnd = new EventEmitter();
1182
+ this.wasPointerOver = false;
1183
+ // TODO check if i could avoid global touch end
1184
+ this.touchEnd = this.pointerMovementDirective.touchEnd$
1185
+ .pipe(filter(({ target }) => target === this.hostElement), tap(({ originalEvent }) => this.pointerEnd.emit(originalEvent)), takeUntilDestroyed())
1186
+ .subscribe();
1187
+ this.touchOverOut = this.pointerMovementDirective.touchMovement$
1188
+ .pipe(tap(({ target, originalEvent }) => {
1189
+ this.handleTouchOverAndOut(target, originalEvent);
1190
+ }), takeUntilDestroyed())
1191
+ .subscribe();
1192
+ }
1193
+ onPointerStart(event) {
1194
+ this.pointerStart.emit(event);
1195
+ if (event instanceof TouchEvent) {
1196
+ this.pointerMovementDirective.setInitialTouch(event);
1197
+ }
1198
+ }
1199
+ onPointerEnd(event) {
1200
+ this.pointerEnd.emit(event);
1201
+ }
1202
+ onMouseOver(event) {
1203
+ this.pointerOver.emit(event);
1204
+ }
1205
+ onMouseOut(event) {
1206
+ this.pointerOut.emit(event);
1207
+ }
1208
+ // TODO: dirty imperative implementation
1209
+ handleTouchOverAndOut(target, event) {
1210
+ if (target === this.hostElement) {
1211
+ this.pointerOver.emit(event);
1212
+ this.wasPointerOver = true;
1213
+ }
1214
+ else {
1215
+ // should not emit before pointerOver
1216
+ if (this.wasPointerOver) {
1217
+ this.pointerOut.emit(event);
1218
+ }
1219
+ this.wasPointerOver = false;
1220
+ }
1221
+ }
1222
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PointerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1223
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: { pointerOver: "pointerOver", pointerOut: "pointerOut", pointerStart: "pointerStart", pointerEnd: "pointerEnd" }, host: { listeners: { "mousedown": "onPointerStart($event)", "touchstart": "onPointerStart($event)", "mouseup": "onPointerEnd($event)", "mouseover": "onMouseOver($event)", "mouseout": "onMouseOut($event)" } }, ngImport: i0 }); }
1224
+ }
1225
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PointerDirective, decorators: [{
1226
+ type: Directive,
1227
+ args: [{ selector: '[pointerStart], [pointerEnd], [pointerOver], [pointerOut]' }]
1228
+ }], propDecorators: { pointerOver: [{
1229
+ type: Output
1230
+ }], pointerOut: [{
1231
+ type: Output
1232
+ }], pointerStart: [{
1233
+ type: Output
1234
+ }], pointerEnd: [{
1235
+ type: Output
1236
+ }], onPointerStart: [{
1237
+ type: HostListener,
1238
+ args: ['mousedown', ['$event']]
1239
+ }, {
1240
+ type: HostListener,
1241
+ args: ['touchstart', ['$event']]
1242
+ }], onPointerEnd: [{
1243
+ type: HostListener,
1244
+ args: ['mouseup', ['$event']]
1245
+ }], onMouseOver: [{
1246
+ type: HostListener,
1247
+ args: ['mouseover', ['$event']]
1248
+ }], onMouseOut: [{
1249
+ type: HostListener,
1250
+ args: ['mouseout', ['$event']]
1251
+ }] } });
1252
+
1253
+ class NodeComponent {
1254
+ constructor() {
1255
+ this.injector = inject(Injector);
942
1256
  this.handleService = inject(HandleService);
943
1257
  this.zone = inject(NgZone);
944
1258
  this.draggableService = inject(DraggableService);
945
1259
  this.flowStatusService = inject(FlowStatusService);
946
1260
  this.flowEntitiesService = inject(FlowEntitiesService);
1261
+ this.nodeRenderingService = inject(NodeRenderingService);
1262
+ this.flowSettingsService = inject(FlowSettingsService);
1263
+ this.selectionService = inject(SelectionService);
947
1264
  this.hostRef = inject(ElementRef);
948
1265
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
949
1266
  this.flowStatusService.status().state === 'connection-validation');
@@ -1029,6 +1346,14 @@ class NodeComponent extends WithInjectorDirective {
1029
1346
  this.flowStatusService.setConnectionStartStatus(status.payload.sourceNode, status.payload.sourceHandle);
1030
1347
  }
1031
1348
  }
1349
+ pullNode() {
1350
+ this.nodeRenderingService.pullNode(this.nodeModel);
1351
+ }
1352
+ selectNode() {
1353
+ if (this.flowSettingsService.entitiesSelectable()) {
1354
+ this.selectionService.select(this.nodeModel);
1355
+ }
1356
+ }
1032
1357
  setInitialHandles() {
1033
1358
  if (this.nodeModel.node.type === 'default') {
1034
1359
  this.handleService.createHandle(new HandleModel({
@@ -1043,8 +1368,8 @@ class NodeComponent extends WithInjectorDirective {
1043
1368
  }, this.nodeModel));
1044
1369
  }
1045
1370
  }
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 }); }
1371
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1372
+ 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 (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"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 (pointerEnd)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (pointerOver)=\"validateTargetHandle(handle)\"\n (pointerOut)=\"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"] }, { kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1048
1373
  }
1049
1374
  __decorate([
1050
1375
  Microtask
@@ -1054,7 +1379,7 @@ __decorate([
1054
1379
  ], NodeComponent.prototype, "setInitialHandles", null);
1055
1380
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
1056
1381
  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"] }]
1382
+ 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 (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"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 (pointerEnd)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (pointerOver)=\"validateTargetHandle(handle)\"\n (pointerOut)=\"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
1383
  }], propDecorators: { nodeModel: [{
1059
1384
  type: Input
1060
1385
  }], nodeHtmlTemplate: [{
@@ -1125,6 +1450,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1125
1450
 
1126
1451
  class EdgeComponent {
1127
1452
  constructor() {
1453
+ this.injector = inject(Injector);
1454
+ this.selectionService = inject(SelectionService);
1455
+ this.flowSettingsService = inject(FlowSettingsService);
1128
1456
  this.markerStartUrl = computed(() => {
1129
1457
  const marker = this.model.edge.markers?.start;
1130
1458
  return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
@@ -1133,7 +1461,6 @@ class EdgeComponent {
1133
1461
  const marker = this.model.edge.markers?.end;
1134
1462
  return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
1135
1463
  });
1136
- this.defaultColor = 'rgb(177, 177, 183)';
1137
1464
  }
1138
1465
  ngOnInit() {
1139
1466
  this.edgeContext = {
@@ -1142,16 +1469,24 @@ class EdgeComponent {
1142
1469
  edge: this.model.edge,
1143
1470
  path: computed(() => this.model.path().path),
1144
1471
  markerStart: this.markerStartUrl,
1145
- markerEnd: this.markerEndUrl
1472
+ markerEnd: this.markerEndUrl,
1473
+ selected: this.model.selected.asReadonly()
1146
1474
  }
1147
1475
  };
1148
1476
  }
1477
+ onEdgeMouseDown() {
1478
+ if (this.flowSettingsService.entitiesSelectable()) {
1479
+ this.selectionService.select(this.model);
1480
+ }
1481
+ }
1149
1482
  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 }); }
1483
+ 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
1484
  }
1152
1485
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: EdgeComponent, decorators: [{
1153
1486
  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" }]
1487
+ args: [{ selector: 'g[edge]', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1488
+ 'class': 'selectable'
1489
+ }, 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
1490
  }], propDecorators: { model: [{
1156
1491
  type: Input
1157
1492
  }], edgeTemplate: [{
@@ -1162,19 +1497,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1162
1497
 
1163
1498
  class SpacePointContextDirective {
1164
1499
  constructor() {
1500
+ this.pointerMovementDirective = inject(RootPointerDirective);
1501
+ this.rootSvg = inject(RootSvgReferenceDirective).element;
1502
+ this.host = inject(ElementRef).nativeElement;
1165
1503
  /**
1166
1504
  * Signal with current mouse position in svg space
1167
1505
  */
1168
1506
  this.svgCurrentSpacePoint = computed(() => {
1169
- const movement = this.mouseMovement();
1507
+ const movement = this.pointerMovement();
1508
+ if (!movement) {
1509
+ return { x: 0, y: 0 };
1510
+ }
1170
1511
  const point = this.rootSvg.createSVGPoint();
1171
1512
  point.x = movement.x;
1172
1513
  point.y = movement.y;
1173
1514
  return point.matrixTransform(this.host.getScreenCTM().inverse());
1174
1515
  });
1175
- this.rootSvg = inject(RootSvgReferenceDirective).element;
1176
- this.host = inject(ElementRef).nativeElement;
1177
- this.mouseMovement = toSignal(fromEvent(this.rootSvg, 'mousemove').pipe(map(event => ({ x: event.clientX, y: event.clientY }))), { initialValue: { x: 0, y: 0 } });
1516
+ this.pointerMovement = toSignal(this.pointerMovementDirective.pointerMovement$);
1178
1517
  }
1179
1518
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpacePointContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1180
1519
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: SpacePointContextDirective, selector: "g[spacePointContext]", ngImport: i0 }); }
@@ -1325,7 +1664,7 @@ class RootSvgContextDirective {
1325
1664
  }
1326
1665
  }
1327
1666
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootSvgContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1328
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RootSvgContextDirective, selector: "svg[rootSvgContext]", host: { listeners: { "document:mouseup": "resetConnection()" } }, ngImport: i0 }); }
1667
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RootSvgContextDirective, selector: "svg[rootSvgContext]", host: { listeners: { "document:mouseup": "resetConnection()", "document:touchend": "resetConnection()" } }, ngImport: i0 }); }
1329
1668
  }
1330
1669
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootSvgContextDirective, decorators: [{
1331
1670
  type: Directive,
@@ -1333,6 +1672,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1333
1672
  }], propDecorators: { resetConnection: [{
1334
1673
  type: HostListener,
1335
1674
  args: ['document:mouseup']
1675
+ }, {
1676
+ type: HostListener,
1677
+ args: ['document:touchend']
1336
1678
  }] } });
1337
1679
 
1338
1680
  const connectionControllerHostDirective = {
@@ -1341,7 +1683,34 @@ const connectionControllerHostDirective = {
1341
1683
  };
1342
1684
  const changesControllerHostDirective = {
1343
1685
  directive: ChangesControllerDirective,
1344
- outputs: ['onNodesChange', 'onEdgesChange']
1686
+ outputs: [
1687
+ 'onNodesChange',
1688
+ 'onNodesChange.position',
1689
+ 'onNodesChange.position.single',
1690
+ 'onNodesChange.position.many',
1691
+ 'onNodesChange.add',
1692
+ 'onNodesChange.add.single',
1693
+ 'onNodesChange.add.many',
1694
+ 'onNodesChange.remove',
1695
+ 'onNodesChange.remove.single',
1696
+ 'onNodesChange.remove.many',
1697
+ 'onNodesChange.select',
1698
+ 'onNodesChange.select.single',
1699
+ 'onNodesChange.select.many',
1700
+ 'onEdgesChange',
1701
+ 'onEdgesChange.detached',
1702
+ 'onEdgesChange.detached.single',
1703
+ 'onEdgesChange.detached.many',
1704
+ 'onEdgesChange.add',
1705
+ 'onEdgesChange.add.single',
1706
+ 'onEdgesChange.add.many',
1707
+ 'onEdgesChange.remove',
1708
+ 'onEdgesChange.remove.single',
1709
+ 'onEdgesChange.remove.many',
1710
+ 'onEdgesChange.select',
1711
+ 'onEdgesChange.select.single',
1712
+ 'onEdgesChange.select.many',
1713
+ ]
1345
1714
  };
1346
1715
  class VflowComponent {
1347
1716
  constructor() {
@@ -1350,6 +1719,8 @@ class VflowComponent {
1350
1719
  this.flowEntitiesService = inject(FlowEntitiesService);
1351
1720
  this.nodesChangeService = inject(NodesChangeService);
1352
1721
  this.edgesChangeService = inject(EdgeChangesService);
1722
+ this.nodeRenderingService = inject(NodeRenderingService);
1723
+ this.flowSettingsService = inject(FlowSettingsService);
1353
1724
  this.injector = inject(Injector);
1354
1725
  /**
1355
1726
  * Minimum zoom value
@@ -1363,6 +1734,8 @@ class VflowComponent {
1363
1734
  * Background color for flow
1364
1735
  */
1365
1736
  this.background = '#FFFFFF';
1737
+ this.nodeModels = computed(() => this.nodeRenderingService.nodes());
1738
+ this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
1366
1739
  // #endregion
1367
1740
  // #region SIGNAL_API
1368
1741
  /**
@@ -1389,12 +1762,12 @@ class VflowComponent {
1389
1762
  */
1390
1763
  this.nodesChange$ = this.nodesChangeService.changes$;
1391
1764
  /**
1392
- * Observable with nodes change
1765
+ * Observable with edges change
1393
1766
  */
1394
1767
  this.edgesChange$ = this.edgesChangeService.changes$;
1395
1768
  // #endregion
1396
- // TODO: probably better to make it injectable
1397
- this.flowModel = new FlowModel();
1769
+ this.flowWidth = this.flowSettingsService.flowWidth;
1770
+ this.flowHeight = this.flowSettingsService.flowHeight;
1398
1771
  this.markers = this.flowEntitiesService.markers;
1399
1772
  }
1400
1773
  // #endregion
@@ -1407,7 +1780,7 @@ class VflowComponent {
1407
1780
  * - 'auto' to compute size based on parent element size
1408
1781
  */
1409
1782
  set view(view) {
1410
- this.flowModel.view.set(view);
1783
+ this.flowSettingsService.view.set(view);
1411
1784
  }
1412
1785
  /**
1413
1786
  * Object that controls flow direction.
@@ -1418,7 +1791,13 @@ class VflowComponent {
1418
1791
  * @deprecated
1419
1792
  */
1420
1793
  set handlePositions(handlePositions) {
1421
- this.flowModel.handlePositions.set(handlePositions);
1794
+ this.flowSettingsService.handlePositions.set(handlePositions);
1795
+ }
1796
+ /**
1797
+ * Global rule if you can or can't select entities
1798
+ */
1799
+ set entitiesSelectable(value) {
1800
+ this.flowSettingsService.entitiesSelectable.set(value);
1422
1801
  }
1423
1802
  /**
1424
1803
  * Settings for connection (it renders when user tries to create edge between nodes)
@@ -1434,23 +1813,19 @@ class VflowComponent {
1434
1813
  */
1435
1814
  set nodes(newNodes) {
1436
1815
  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
1816
  // quick and dirty binding nodes to edges
1440
1817
  addNodesToEdges(newModels, this.flowEntitiesService.edges());
1441
1818
  this.flowEntitiesService.nodes.set(newModels);
1442
1819
  }
1443
- get nodeModels() { return this.flowEntitiesService.nodes(); }
1444
1820
  /**
1445
1821
  * Edges to render
1446
1822
  */
1447
1823
  set edges(newEdges) {
1448
1824
  const newModels = runInInjectionContext(this.injector, () => ReferenceKeeper.edges(newEdges, this.flowEntitiesService.edges()));
1449
1825
  // quick and dirty binding nodes to edges
1450
- addNodesToEdges(this.nodeModels, newModels);
1826
+ addNodesToEdges(this.nodeModels(), newModels);
1451
1827
  this.flowEntitiesService.edges.set(newModels);
1452
1828
  }
1453
- get edgeModels() { return this.flowEntitiesService.validEdges(); }
1454
1829
  // #region METHODS_API
1455
1830
  /**
1456
1831
  * Change viewport to specified state
@@ -1498,14 +1873,17 @@ class VflowComponent {
1498
1873
  return edge;
1499
1874
  }
1500
1875
  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: [
1876
+ 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
1877
  DraggableService,
1503
1878
  ViewportService,
1504
1879
  FlowStatusService,
1505
1880
  FlowEntitiesService,
1506
1881
  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 }); }
1882
+ EdgeChangesService,
1883
+ NodeRenderingService,
1884
+ SelectionService,
1885
+ FlowSettingsService
1886
+ ], 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", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.add", "onNodesChange.add", "onNodesChange.add.single", "onNodesChange.add.single", "onNodesChange.add.many", "onNodesChange.add.many", "onNodesChange.remove", "onNodesChange.remove", "onNodesChange.remove.single", "onNodesChange.remove.single", "onNodesChange.remove.many", "onNodesChange.remove.many", "onNodesChange.select", "onNodesChange.select", "onNodesChange.select.single", "onNodesChange.select.single", "onNodesChange.select.many", "onNodesChange.select.many", "onEdgesChange", "onEdgesChange", "onEdgesChange.detached", "onEdgesChange.detached", "onEdgesChange.detached.single", "onEdgesChange.detached.single", "onEdgesChange.detached.many", "onEdgesChange.detached.many", "onEdgesChange.add", "onEdgesChange.add", "onEdgesChange.add.single", "onEdgesChange.add.single", "onEdgesChange.add.many", "onEdgesChange.add.many", "onEdgesChange.remove", "onEdgesChange.remove", "onEdgesChange.remove.single", "onEdgesChange.remove.single", "onEdgesChange.remove.many", "onEdgesChange.remove.many", "onEdgesChange.select", "onEdgesChange.select", "onEdgesChange.select.single", "onEdgesChange.select.single", "onEdgesChange.select.many", "onEdgesChange.select.many"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\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]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1509
1887
  }
1510
1888
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
1511
1889
  type: Component,
@@ -1515,11 +1893,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1515
1893
  FlowStatusService,
1516
1894
  FlowEntitiesService,
1517
1895
  NodesChangeService,
1518
- EdgeChangesService
1896
+ EdgeChangesService,
1897
+ NodeRenderingService,
1898
+ SelectionService,
1899
+ FlowSettingsService
1519
1900
  ], hostDirectives: [
1520
1901
  connectionControllerHostDirective,
1521
1902
  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"] }]
1903
+ ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\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
1904
  }], propDecorators: { view: [{
1524
1905
  type: Input
1525
1906
  }], minZoom: [{
@@ -1530,6 +1911,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1530
1911
  type: Input
1531
1912
  }], background: [{
1532
1913
  type: Input
1914
+ }], entitiesSelectable: [{
1915
+ type: Input
1533
1916
  }], connection: [{
1534
1917
  type: Input,
1535
1918
  args: [{ transform: (settings) => new ConnectionModel(settings) }]
@@ -1554,13 +1937,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1554
1937
  type: ViewChild,
1555
1938
  args: [MapContextDirective]
1556
1939
  }] } });
1557
- function bindFlowToNodes(flow, nodes) {
1558
- nodes.forEach(n => n.bindFlow(flow));
1559
- }
1560
1940
 
1561
- class HandleComponent extends WithInjectorDirective {
1941
+ class HandleComponent {
1562
1942
  constructor() {
1563
- super(...arguments);
1943
+ this.injector = inject(Injector);
1564
1944
  this.handleService = inject(HandleService);
1565
1945
  this.element = inject(ElementRef).nativeElement;
1566
1946
  }
@@ -1578,8 +1958,8 @@ class HandleComponent extends WithInjectorDirective {
1578
1958
  ngOnDestroy() {
1579
1959
  this.handleService.destroyHandle(this.model);
1580
1960
  }
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: "" }); }
1961
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1962
+ 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
1963
  }
1584
1964
  __decorate([
1585
1965
  InjectionContext
@@ -1599,6 +1979,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1599
1979
  type: Input
1600
1980
  }], ngOnInit: [] } });
1601
1981
 
1982
+ class SelectableDirective {
1983
+ constructor() {
1984
+ this.flowSettingsService = inject(FlowSettingsService);
1985
+ this.selectionService = inject(SelectionService);
1986
+ this.parentEdge = inject(EdgeComponent, { optional: true });
1987
+ this.parentNode = inject(NodeComponent, { optional: true });
1988
+ }
1989
+ onMousedown() {
1990
+ const entity = this.entity();
1991
+ if (entity && this.flowSettingsService.entitiesSelectable()) {
1992
+ this.selectionService.select(entity);
1993
+ }
1994
+ }
1995
+ entity() {
1996
+ if (this.parentNode) {
1997
+ return this.parentNode.nodeModel;
1998
+ }
1999
+ else if (this.parentEdge) {
2000
+ return this.parentEdge.model;
2001
+ }
2002
+ return null;
2003
+ }
2004
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2005
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: SelectableDirective, selector: "[selectable]", host: { listeners: { "mousedown": "onMousedown()" } }, ngImport: i0 }); }
2006
+ }
2007
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectableDirective, decorators: [{
2008
+ type: Directive,
2009
+ args: [{ selector: '[selectable]' }]
2010
+ }], propDecorators: { onMousedown: [{
2011
+ type: HostListener,
2012
+ args: ['mousedown']
2013
+ }] } });
2014
+
1602
2015
  const components = [
1603
2016
  VflowComponent,
1604
2017
  NodeComponent,
@@ -1613,7 +2026,10 @@ const directives = [
1613
2026
  MapContextDirective,
1614
2027
  RootSvgReferenceDirective,
1615
2028
  RootSvgContextDirective,
1616
- HandleSizeControllerDirective
2029
+ HandleSizeControllerDirective,
2030
+ SelectableDirective,
2031
+ PointerDirective,
2032
+ RootPointerDirective
1617
2033
  ];
1618
2034
  const templateDirectives = [
1619
2035
  NodeHtmlTemplateDirective,
@@ -1634,12 +2050,16 @@ class VflowModule {
1634
2050
  MapContextDirective,
1635
2051
  RootSvgReferenceDirective,
1636
2052
  RootSvgContextDirective,
1637
- HandleSizeControllerDirective, NodeHtmlTemplateDirective,
2053
+ HandleSizeControllerDirective,
2054
+ SelectableDirective,
2055
+ PointerDirective,
2056
+ RootPointerDirective, NodeHtmlTemplateDirective,
1638
2057
  EdgeLabelHtmlTemplateDirective,
1639
2058
  EdgeTemplateDirective,
1640
2059
  ConnectionTemplateDirective,
1641
2060
  HandleTemplateDirective], imports: [CommonModule], exports: [VflowComponent,
1642
- HandleComponent, NodeHtmlTemplateDirective,
2061
+ HandleComponent,
2062
+ SelectableDirective, NodeHtmlTemplateDirective,
1643
2063
  EdgeLabelHtmlTemplateDirective,
1644
2064
  EdgeTemplateDirective,
1645
2065
  ConnectionTemplateDirective,
@@ -1653,6 +2073,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1653
2073
  exports: [
1654
2074
  VflowComponent,
1655
2075
  HandleComponent,
2076
+ SelectableDirective,
1656
2077
  ...templateDirectives
1657
2078
  ],
1658
2079
  declarations: [...components, ...directives, ...templateDirectives],
@@ -1665,5 +2086,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1665
2086
  * Generated bundle index. Do not edit.
1666
2087
  */
1667
2088
 
1668
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, VflowComponent, VflowModule };
2089
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
1669
2090
  //# sourceMappingURL=ngx-vflow.mjs.map