ngx-vflow 1.11.1 → 1.12.1

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 (49) hide show
  1. package/esm2022/lib/vflow/components/background/background.component.mjs +4 -3
  2. package/esm2022/lib/vflow/components/node/node.component.mjs +3 -1
  3. package/esm2022/lib/vflow/components/preview-flow/draw-node.mjs +100 -0
  4. package/esm2022/lib/vflow/components/preview-flow/preview-flow.component.mjs +62 -0
  5. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +26 -12
  6. package/esm2022/lib/vflow/directives/map-context.directive.mjs +28 -18
  7. package/esm2022/lib/vflow/directives/space-point-context.directive.mjs +3 -3
  8. package/esm2022/lib/vflow/interfaces/node-preview.interface.mjs +2 -0
  9. package/esm2022/lib/vflow/interfaces/node.interface.mjs +1 -1
  10. package/esm2022/lib/vflow/interfaces/optimization.interface.mjs +6 -2
  11. package/esm2022/lib/vflow/math/edge-path/smooth-step-path.mjs +60 -4
  12. package/esm2022/lib/vflow/models/edge.model.mjs +42 -15
  13. package/esm2022/lib/vflow/models/handle.model.mjs +3 -1
  14. package/esm2022/lib/vflow/models/node.model.mjs +6 -1
  15. package/esm2022/lib/vflow/services/edge-rendering.service.mjs +14 -2
  16. package/esm2022/lib/vflow/services/flow-settings.service.mjs +3 -1
  17. package/esm2022/lib/vflow/services/node-rendering.service.mjs +35 -2
  18. package/esm2022/lib/vflow/services/preview-flow-render-strategy.service.mjs +21 -0
  19. package/esm2022/lib/vflow/services/viewport.service.mjs +8 -1
  20. package/esm2022/lib/vflow/utils/assert-injector.mjs +27 -0
  21. package/esm2022/lib/vflow/utils/signals/extended-computed.mjs +15 -0
  22. package/esm2022/lib/vflow/utils/signals/to-lazy-signal.mjs +35 -0
  23. package/esm2022/lib/vflow/utils/viewport.mjs +37 -1
  24. package/esm2022/public-api.mjs +2 -1
  25. package/esm2022/testing/component-mocks/vflow-mock.component.mjs +7 -7
  26. package/fesm2022/ngx-vflow-testing.mjs +6 -6
  27. package/fesm2022/ngx-vflow-testing.mjs.map +1 -1
  28. package/fesm2022/ngx-vflow.mjs +495 -49
  29. package/fesm2022/ngx-vflow.mjs.map +1 -1
  30. package/lib/vflow/components/preview-flow/draw-node.d.ts +2 -0
  31. package/lib/vflow/components/preview-flow/preview-flow.component.d.ts +15 -0
  32. package/lib/vflow/components/vflow/vflow.component.d.ts +5 -2
  33. package/lib/vflow/directives/map-context.directive.d.ts +3 -2
  34. package/lib/vflow/interfaces/node-preview.interface.d.ts +3 -0
  35. package/lib/vflow/interfaces/node.interface.d.ts +3 -0
  36. package/lib/vflow/interfaces/optimization.interface.d.ts +17 -1
  37. package/lib/vflow/models/node.model.d.ts +3 -0
  38. package/lib/vflow/services/edge-rendering.service.d.ts +2 -0
  39. package/lib/vflow/services/flow-settings.service.d.ts +2 -0
  40. package/lib/vflow/services/node-rendering.service.d.ts +4 -0
  41. package/lib/vflow/services/preview-flow-render-strategy.service.d.ts +12 -0
  42. package/lib/vflow/services/viewport.service.d.ts +3 -0
  43. package/lib/vflow/utils/assert-injector.d.ts +44 -0
  44. package/lib/vflow/utils/signals/extended-computed.d.ts +5 -0
  45. package/lib/vflow/utils/signals/to-lazy-signal.d.ts +20 -0
  46. package/lib/vflow/utils/viewport.d.ts +19 -0
  47. package/package.json +1 -1
  48. package/public-api.d.ts +1 -0
  49. package/testing/component-mocks/vflow-mock.component.d.ts +3 -3
@@ -1,8 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, DestroyRef, EventEmitter, OutputEmitterRef, input, NgZone, viewChild, Component, ChangeDetectionStrategy, output, HostListener, Injector, runInInjectionContext, contentChild, Input, forwardRef } from '@angular/core';
2
+ import { signal, computed, Injectable, inject, ElementRef, Directive, NgZone, effect, untracked, TemplateRef, DestroyRef, EventEmitter, OutputEmitterRef, input, assertInInjectionContext, Injector, runInInjectionContext, viewChild, Component, ChangeDetectionStrategy, output, HostListener, Renderer2, contentChild, Input, forwardRef } from '@angular/core';
3
3
  import { select } from 'd3-selection';
4
4
  import { zoomIdentity, zoom } from 'd3-zoom';
5
- import { switchMap, merge, fromEvent, tap, Subject, Observable, skip, map, pairwise, filter, distinctUntilChanged, observeOn, asyncScheduler, zip, animationFrameScheduler, share, startWith } from 'rxjs';
5
+ import { Subject, switchMap, merge, fromEvent, tap, Observable, skip, map, pairwise, filter, distinctUntilChanged, observeOn, asyncScheduler, zip, debounceTime, animationFrameScheduler, share, startWith } from 'rxjs';
6
6
  import { toObservable, takeUntilDestroyed, outputFromObservable, toSignal } from '@angular/core/rxjs-interop';
7
7
  import { drag } from 'd3-drag';
8
8
  import { __decorate } from 'tslib';
@@ -177,6 +177,48 @@ function getViewportForBounds(bounds, width, height, minZoom, maxZoom, padding)
177
177
  function clamp(value, min = 0, max = 1) {
178
178
  return Math.min(Math.max(value, min), max);
179
179
  }
180
+ /**
181
+ * Calculates the visible area bounds in world coordinates based on the current viewport state
182
+ *
183
+ * @param viewport Current viewport state (x, y, zoom)
184
+ * @param flowWidth Width of the flow container
185
+ * @param flowHeight Height of the flow container
186
+ * @returns Rect representing the visible area in world coordinates
187
+ */
188
+ function getViewportBounds(viewport, flowWidth, flowHeight) {
189
+ const zoom = viewport.zoom;
190
+ return {
191
+ x: -viewport.x / zoom,
192
+ y: -viewport.y / zoom,
193
+ width: flowWidth / zoom,
194
+ height: flowHeight / zoom,
195
+ };
196
+ }
197
+ /**
198
+ * Checks if a rectangle intersects with the viewport's visible area
199
+ *
200
+ * @param rect Rectangle to check (in world coordinates)
201
+ * @param viewport Current viewport state
202
+ * @param flowWidth Width of the flow container
203
+ * @param flowHeight Height of the flow container
204
+ * @returns true if the rectangle intersects with the viewport, false otherwise
205
+ */
206
+ function isRectInViewport(rect, viewport, flowWidth, flowHeight) {
207
+ const viewportBounds = getViewportBounds(viewport, flowWidth, flowHeight);
208
+ // Check if rectangles intersect using standard rectangle intersection test
209
+ // No intersection if: rect is completely to the left, right, above, or below the viewport
210
+ const isNotIntersecting = rect.x + rect.width < viewportBounds.x || // Rect is completely to the left
211
+ rect.x > viewportBounds.x + viewportBounds.width || // Rect is completely to the right
212
+ rect.y + rect.height < viewportBounds.y || // Rect is completely above
213
+ rect.y > viewportBounds.y + viewportBounds.height; // Rect is completely below
214
+ return !isNotIntersecting;
215
+ }
216
+
217
+ const DEFAULT_OPTIMIZATION = {
218
+ detachedGroupsLayer: false,
219
+ virtualization: false,
220
+ virtualizationZoomThreshold: 0.5,
221
+ };
180
222
 
181
223
  class FlowSettingsService {
182
224
  constructor() {
@@ -199,6 +241,7 @@ class FlowSettingsService {
199
241
  this.maxZoom = signal(3);
200
242
  this.background = signal({ type: 'solid', color: '#fff' });
201
243
  this.snapGrid = signal([1, 1]);
244
+ this.optimization = signal(DEFAULT_OPTIMIZATION);
202
245
  }
203
246
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
204
247
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService }); }
@@ -226,6 +269,7 @@ class ViewportService {
226
269
  * - writableViewport signal
227
270
  */
228
271
  this.readableViewport = signal(ViewportService.getDefaultViewport());
272
+ this.viewportChangeEnd$ = new Subject();
229
273
  }
230
274
  /**
231
275
  * The default value used by d3, just copy it here
@@ -242,6 +286,11 @@ class ViewportService {
242
286
  const duration = options.duration ?? 0;
243
287
  this.writableViewport.set({ changeType: 'absolute', state, duration });
244
288
  }
289
+ triggerViewportChangeEvent(type) {
290
+ if (type === 'end') {
291
+ this.viewportChangeEnd$.next();
292
+ }
293
+ }
245
294
  getBoundsNodes(nodeIds) {
246
295
  return !nodeIds?.length
247
296
  ? // If nodes option not passed or the list is empty, then get fit the whole view
@@ -398,8 +447,9 @@ class MapContextDirective {
398
447
  this.selectionService = inject(SelectionService);
399
448
  this.viewportService = inject(ViewportService);
400
449
  this.flowSettingsService = inject(FlowSettingsService);
450
+ this.zone = inject(NgZone);
401
451
  this.rootSvgSelection = select(this.rootSvg);
402
- this.zoomableSelection = select(this.host);
452
+ this.transform = signal('');
403
453
  this.viewportForSelection = {};
404
454
  // under the hood this effect triggers handleZoom, so error throws without this flag
405
455
  this.manualViewportChangeEffect = effect(() => {
@@ -435,7 +485,7 @@ class MapContextDirective {
435
485
  this.handleZoom = ({ transform }) => {
436
486
  // update public signal for user to read
437
487
  this.viewportService.readableViewport.set(mapTransformToViewportState(transform));
438
- this.zoomableSelection.attr('transform', transform.toString());
488
+ this.transform.set(transform.toString());
439
489
  };
440
490
  this.handleZoomStart = ({ transform }) => {
441
491
  this.viewportForSelection = {
@@ -443,12 +493,16 @@ class MapContextDirective {
443
493
  };
444
494
  };
445
495
  this.handleZoomEnd = ({ transform, sourceEvent }) => {
446
- this.viewportForSelection = {
447
- ...this.viewportForSelection,
448
- end: mapTransformToViewportState(transform),
449
- target: evTarget(sourceEvent),
450
- };
451
- this.selectionService.setViewport(this.viewportForSelection);
496
+ this.zone.run(() => {
497
+ this.viewportForSelection = {
498
+ ...this.viewportForSelection,
499
+ end: mapTransformToViewportState(transform),
500
+ target: evTarget(sourceEvent),
501
+ };
502
+ this.viewportService.triggerViewportChangeEvent('end');
503
+ // TODO: maybe use triggerViewportChangeEvent instead of this method?
504
+ this.selectionService.setViewport(this.viewportForSelection);
505
+ });
452
506
  };
453
507
  this.filterCondition = (event) => {
454
508
  if (event.type === 'mousedown' || event.type === 'touchstart') {
@@ -458,22 +512,27 @@ class MapContextDirective {
458
512
  };
459
513
  }
460
514
  ngOnInit() {
461
- this.zoomBehavior = zoom()
462
- .scaleExtent([this.flowSettingsService.minZoom(), this.flowSettingsService.maxZoom()])
463
- .filter(this.filterCondition)
464
- .on('start', this.handleZoomStart)
465
- .on('zoom', this.handleZoom)
466
- .on('end', this.handleZoomEnd);
467
- this.rootSvgSelection.call(this.zoomBehavior).on('dblclick.zoom', null);
515
+ this.zone.runOutsideAngular(() => {
516
+ this.zoomBehavior = zoom()
517
+ .scaleExtent([this.flowSettingsService.minZoom(), this.flowSettingsService.maxZoom()])
518
+ .filter(this.filterCondition)
519
+ .on('start', this.handleZoomStart)
520
+ .on('zoom', this.handleZoom)
521
+ .on('end', this.handleZoomEnd);
522
+ this.rootSvgSelection.call(this.zoomBehavior).on('dblclick.zoom', null);
523
+ });
468
524
  }
469
525
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MapContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
470
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: MapContextDirective, isStandalone: true, selector: "g[mapContext]", ngImport: i0 }); }
526
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: MapContextDirective, isStandalone: true, selector: "g[mapContext]", host: { properties: { "attr.transform": "transform()" } }, ngImport: i0 }); }
471
527
  }
472
528
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MapContextDirective, decorators: [{
473
529
  type: Directive,
474
530
  args: [{
475
531
  standalone: true,
476
532
  selector: 'g[mapContext]',
533
+ host: {
534
+ '[attr.transform]': 'transform()',
535
+ },
477
536
  }]
478
537
  }] });
479
538
  const mapTransformToViewportState = (transform) => ({
@@ -966,6 +1025,7 @@ class NodeModel {
966
1025
  constructor(rawNode) {
967
1026
  this.rawNode = rawNode;
968
1027
  this.entitiesService = inject(FlowEntitiesService);
1028
+ this.isVisible = signal(false);
969
1029
  this.point = signal({ x: 0, y: 0 });
970
1030
  this.width = signal(NodeModel.defaultWidth);
971
1031
  this.height = signal(NodeModel.defaultHeight);
@@ -979,6 +1039,7 @@ class NodeModel {
979
1039
  this.foHeight = computed(() => this.height() + MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME);
980
1040
  this.renderOrder = signal(0);
981
1041
  this.selected = signal(false);
1042
+ this.preview = signal({ style: {} });
982
1043
  this.globalPoint = computed(() => {
983
1044
  let parent = this.parent();
984
1045
  let x = this.point().x;
@@ -1030,6 +1091,9 @@ class NodeModel {
1030
1091
  if (internalNode.parentId) {
1031
1092
  this.parentId = internalNode.parentId;
1032
1093
  }
1094
+ if (internalNode.preview) {
1095
+ this.preview = internalNode.preview;
1096
+ }
1033
1097
  if (internalNode.type === 'default-group' && internalNode.color) {
1034
1098
  this.color = internalNode.color;
1035
1099
  }
@@ -1334,17 +1398,87 @@ function smoothStepPath({ sourcePoint, targetPoint, sourcePosition, targetPositi
1334
1398
  res += segment;
1335
1399
  return res;
1336
1400
  }, '');
1401
+ // Performance optimization: Pre-calculate cumulative distances and use binary search
1402
+ const n = points.length;
1403
+ if (n < 2) {
1404
+ return {
1405
+ path,
1406
+ labelPoints: {
1407
+ start: { x: labelX, y: labelY },
1408
+ center: { x: labelX, y: labelY },
1409
+ end: { x: labelX, y: labelY },
1410
+ },
1411
+ };
1412
+ }
1413
+ // Pre-calculate segment lengths and cumulative distances in a single loop
1414
+ const segmentLengths = new Array(n - 1);
1415
+ const cumulativeDistances = new Array(n);
1416
+ cumulativeDistances[0] = 0;
1417
+ let totalLength = 0;
1418
+ for (let i = 0; i < n - 1; i++) {
1419
+ const dx = points[i + 1].x - points[i].x;
1420
+ const dy = points[i + 1].y - points[i].y;
1421
+ const len = Math.sqrt(dx * dx + dy * dy);
1422
+ segmentLengths[i] = len;
1423
+ totalLength += len;
1424
+ cumulativeDistances[i + 1] = totalLength;
1425
+ }
1426
+ // Optimized helper function using binary search
1427
+ const getPointAtRatio = (ratio) => {
1428
+ const targetDistance = totalLength * ratio;
1429
+ // Edge cases
1430
+ if (targetDistance <= 0)
1431
+ return points[0];
1432
+ if (targetDistance >= totalLength)
1433
+ return points[n - 1];
1434
+ // Binary search for the correct segment
1435
+ let low = 0;
1436
+ let high = n - 1;
1437
+ while (low < high - 1) {
1438
+ const mid = (low + high) >>> 1; // Bitwise right shift is faster than Math.floor
1439
+ if (cumulativeDistances[mid] < targetDistance) {
1440
+ low = mid;
1441
+ }
1442
+ else {
1443
+ high = mid;
1444
+ }
1445
+ }
1446
+ // Calculate position within the segment
1447
+ const segmentStartDistance = cumulativeDistances[low];
1448
+ const localDistance = targetDistance - segmentStartDistance;
1449
+ const t = localDistance / segmentLengths[low];
1450
+ // Linear interpolation
1451
+ const start = points[low];
1452
+ const end = points[low + 1];
1453
+ return {
1454
+ x: start.x + (end.x - start.x) * t,
1455
+ y: start.y + (end.y - start.y) * t,
1456
+ };
1457
+ };
1337
1458
  return {
1338
1459
  path,
1339
1460
  labelPoints: {
1340
- // TODO start and end points temporary unavailable for this path
1341
- start: { x: labelX, y: labelY },
1461
+ start: getPointAtRatio(0.15),
1342
1462
  center: { x: labelX, y: labelY },
1343
- end: { x: labelX, y: labelY },
1463
+ end: getPointAtRatio(0.85),
1344
1464
  },
1345
1465
  };
1346
1466
  }
1347
1467
 
1468
+ // MIT License
1469
+ /**
1470
+ * @todo Use `linkedSignal` after Angular update
1471
+ */
1472
+ function extendedComputed(computedCallback, options) {
1473
+ if (!options) {
1474
+ options = { equal: Object.is };
1475
+ }
1476
+ let currentValue = undefined;
1477
+ return computed(() => {
1478
+ return (currentValue = computedCallback(currentValue));
1479
+ }, options);
1480
+ }
1481
+
1348
1482
  class EdgeModel {
1349
1483
  constructor(edge) {
1350
1484
  this.edge = edge;
@@ -1405,25 +1539,51 @@ class EdgeModel {
1405
1539
  return this.curve(params);
1406
1540
  }
1407
1541
  });
1408
- this.sourceHandle = computed(() => {
1542
+ this.sourceHandle = extendedComputed((previousHandle) => {
1543
+ let handle = null;
1409
1544
  if (this.edge.sourceHandle) {
1410
- return (this.source()
1411
- ?.handles()
1412
- .find((handle) => handle.rawHandle.id === this.edge.sourceHandle) ?? null);
1545
+ handle =
1546
+ this.source()
1547
+ ?.handles()
1548
+ .find((handle) => handle.rawHandle.id === this.edge.sourceHandle) ?? null;
1549
+ }
1550
+ else {
1551
+ handle =
1552
+ this.source()
1553
+ ?.handles()
1554
+ .find((handle) => handle.rawHandle.type === 'source') ?? null;
1413
1555
  }
1414
- return (this.source()
1415
- ?.handles()
1416
- .find((handle) => handle.rawHandle.type === 'source') ?? null);
1556
+ // In case of virtual scrolling, if the node is scrolled out of view the handle may disappear
1557
+ // which could lead to the edge not being rendered
1558
+ // so we return the previous handle if the current one is null
1559
+ // TODO: check if this breaks anything
1560
+ if (handle === null) {
1561
+ return previousHandle;
1562
+ }
1563
+ return handle;
1417
1564
  });
1418
- this.targetHandle = computed(() => {
1565
+ this.targetHandle = extendedComputed((previousHandle) => {
1566
+ let handle = null;
1419
1567
  if (this.edge.targetHandle) {
1420
- return (this.target()
1421
- ?.handles()
1422
- .find((handle) => handle.rawHandle.id === this.edge.targetHandle) ?? null);
1568
+ handle =
1569
+ this.target()
1570
+ ?.handles()
1571
+ .find((handle) => handle.rawHandle.id === this.edge.targetHandle) ?? null;
1572
+ }
1573
+ else {
1574
+ handle =
1575
+ this.target()
1576
+ ?.handles()
1577
+ .find((handle) => handle.rawHandle.type === 'target') ?? null;
1423
1578
  }
1424
- return (this.target()
1425
- ?.handles()
1426
- .find((handle) => handle.rawHandle.type === 'target') ?? null);
1579
+ // In case of virtual scrolling, if the node is scrolled out of view the handle may disappear
1580
+ // which could lead to the edge not being rendered
1581
+ // so we return the previous handle if the current one is null
1582
+ // TODO: check if this breaks anything
1583
+ if (handle === null) {
1584
+ return previousHandle;
1585
+ }
1586
+ return handle;
1427
1587
  });
1428
1588
  /**
1429
1589
  * TODO: not reactive
@@ -1737,11 +1897,74 @@ function isGroupNode(node) {
1737
1897
  return node.rawNode.type === 'default-group' || node.rawNode.type === 'template-group';
1738
1898
  }
1739
1899
 
1900
+ // MIT License
1901
+ // Copyright (c) 2023 Chau Tran
1902
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1903
+ // of this software and associated documentation files (the "Software"), to deal
1904
+ // in the Software without restriction, including without limitation the rights
1905
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1906
+ // copies of the Software, and to permit persons to whom the Software is
1907
+ // furnished to do so, subject to the following conditions:
1908
+ // The above copyright notice and this permission notice shall be included in all
1909
+ // copies or substantial portions of the Software.
1910
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1911
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1912
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1913
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1914
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1915
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1916
+ // SOFTWARE.
1917
+ /* eslint-disable @typescript-eslint/ban-types */
1918
+ function assertInjector(fn, injector, runner) {
1919
+ !injector && assertInInjectionContext(fn);
1920
+ const assertedInjector = injector ?? inject(Injector);
1921
+ if (!runner)
1922
+ return assertedInjector;
1923
+ return runInInjectionContext(assertedInjector, runner);
1924
+ }
1925
+
1926
+ // MIT License
1927
+ // Copyright (c) 2023 Chau Tran
1928
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1929
+ // of this software and associated documentation files (the "Software"), to deal
1930
+ // in the Software without restriction, including without limitation the rights
1931
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1932
+ // copies of the Software, and to permit persons to whom the Software is
1933
+ // furnished to do so, subject to the following conditions:
1934
+ // The above copyright notice and this permission notice shall be included in all
1935
+ // copies or substantial portions of the Software.
1936
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1937
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1938
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1939
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1940
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1941
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1942
+ // SOFTWARE.
1943
+ /**
1944
+ * Function `toLazySignal()` is a proxy function that will call the original
1945
+ * `toSignal()` function when the returned signal is read for the first time.
1946
+ */
1947
+ function toLazySignal(source, options) {
1948
+ const injector = assertInjector(toLazySignal, options?.injector);
1949
+ let s;
1950
+ return computed(() => {
1951
+ if (!s) {
1952
+ s = untracked(() => toSignal(source, { ...options, injector }));
1953
+ }
1954
+ return s();
1955
+ });
1956
+ }
1957
+
1740
1958
  class NodeRenderingService {
1741
1959
  constructor() {
1742
1960
  this.flowEntitiesService = inject(FlowEntitiesService);
1961
+ this.flowSettingsService = inject(FlowSettingsService);
1962
+ this.viewportService = inject(ViewportService);
1743
1963
  this.nodes = computed(() => {
1744
- return [...this.flowEntitiesService.nodes().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder())];
1964
+ if (!this.flowSettingsService.optimization().virtualization) {
1965
+ return [...this.flowEntitiesService.nodes()].sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1966
+ }
1967
+ return this.viewportNodesAfterInteraction().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1745
1968
  });
1746
1969
  this.groups = computed(() => {
1747
1970
  return this.nodes().filter((n) => isGroupNode(n));
@@ -1749,6 +1972,28 @@ class NodeRenderingService {
1749
1972
  this.nonGroups = computed(() => {
1750
1973
  return this.nodes().filter((n) => !isGroupNode(n));
1751
1974
  });
1975
+ this.viewportNodes = computed(() => {
1976
+ const nodes = this.flowEntitiesService.nodes();
1977
+ const viewport = this.viewportService.readableViewport();
1978
+ const flowWidth = this.flowSettingsService.computedFlowWidth();
1979
+ const flowHeight = this.flowSettingsService.computedFlowHeight();
1980
+ return nodes.filter((n) => {
1981
+ const { x, y } = n.globalPoint();
1982
+ const width = n.width();
1983
+ const height = n.height();
1984
+ return isRectInViewport({ x, y, width, height }, viewport, flowWidth, flowHeight);
1985
+ });
1986
+ });
1987
+ this.viewportNodesAfterInteraction = toLazySignal(merge(
1988
+ // TODO: maybe there is a better way wait when viewport is ready?
1989
+ // (to correctly calculate viewport nodes on first render)
1990
+ toObservable(this.flowEntitiesService.nodes).pipe(observeOn(asyncScheduler), filter((nodes) => !!nodes.length)), this.viewportService.viewportChangeEnd$.pipe(debounceTime(300))).pipe(map(() => {
1991
+ const viewport = this.viewportService.readableViewport();
1992
+ const zoomThreshold = this.flowSettingsService.optimization().virtualizationZoomThreshold;
1993
+ return viewport.zoom < zoomThreshold ? [] : this.viewportNodes();
1994
+ })), {
1995
+ initialValue: [],
1996
+ });
1752
1997
  this.maxOrder = computed(() => {
1753
1998
  return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
1754
1999
  });
@@ -1839,7 +2084,7 @@ class SpacePointContextDirective {
1839
2084
  y: movement.y,
1840
2085
  });
1841
2086
  });
1842
- this.pointerMovement = toSignal(this.pointerMovementDirective.pointerMovement$);
2087
+ this.pointerMovement = toLazySignal(this.pointerMovementDirective.pointerMovement$);
1843
2088
  }
1844
2089
  documentPointToFlowPoint(documentPoint) {
1845
2090
  const point = this.rootSvg.createSVGPoint();
@@ -2144,8 +2389,19 @@ function statusToConnection(status, isStrictMode) {
2144
2389
  class EdgeRenderingService {
2145
2390
  constructor() {
2146
2391
  this.flowEntitiesService = inject(FlowEntitiesService);
2392
+ this.flowSettingsService = inject(FlowSettingsService);
2147
2393
  this.edges = computed(() => {
2148
- return this.flowEntitiesService.validEdges().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
2394
+ if (!this.flowSettingsService.optimization().virtualization) {
2395
+ return [...this.flowEntitiesService.validEdges()].sort((aEdge, bEdge) => aEdge.renderOrder() - bEdge.renderOrder());
2396
+ }
2397
+ return this.viewportEdges().sort((aEdge, bEdge) => aEdge.renderOrder() - bEdge.renderOrder());
2398
+ });
2399
+ this.viewportEdges = computed(() => {
2400
+ return this.flowEntitiesService.validEdges().filter((e) => {
2401
+ const sourceHandle = e.sourceHandle();
2402
+ const targetHandle = e.targetHandle();
2403
+ return sourceHandle && targetHandle;
2404
+ });
2149
2405
  });
2150
2406
  this.maxOrder = computed(() => {
2151
2407
  return Math.max(...this.flowEntitiesService.validEdges().map((n) => n.renderOrder()));
@@ -2645,9 +2901,11 @@ class HandleModel {
2645
2901
  });
2646
2902
  this.state = signal('idle');
2647
2903
  this.updateHostSizeAndPosition$ = new Subject();
2904
+ // TODO: for some reason toLazySignal breaks unit tests, so we use toSignal here
2648
2905
  this.hostSize = toSignal(this.updateHostSizeAndPosition$.pipe(map(() => this.getHostSize())), {
2649
2906
  initialValue: { width: 0, height: 0 },
2650
2907
  });
2908
+ // TODO: for some reason toLazySignal breaks unit tests, so we use toSignal here
2651
2909
  this.hostPosition = toSignal(this.updateHostSizeAndPosition$.pipe(map(() => ({
2652
2910
  x: this.hostReference instanceof HTMLElement ? this.hostReference.offsetLeft : 0, // for now just 0 for group nodes
2653
2911
  y: this.hostReference instanceof HTMLElement ? this.hostReference.offsetTop : 0, // for now just 0 for group nodes
@@ -2846,6 +3104,7 @@ class NodeComponent {
2846
3104
  this.toolbars = computed(() => this.overlaysService.nodeToolbarsMap().get(this.model()));
2847
3105
  }
2848
3106
  ngOnInit() {
3107
+ this.model().isVisible.set(true);
2849
3108
  this.nodeAccessor.model.set(this.model());
2850
3109
  this.handleService.node.set(this.model());
2851
3110
  effect(() => {
@@ -2858,6 +3117,7 @@ class NodeComponent {
2858
3117
  }, { injector: this.injector });
2859
3118
  }
2860
3119
  ngOnDestroy() {
3120
+ this.model().isVisible.set(false);
2861
3121
  this.draggableService.destroy(this.hostRef.nativeElement);
2862
3122
  }
2863
3123
  startConnection(event, handle) {
@@ -3098,7 +3358,7 @@ class BackgroundComponent {
3098
3358
  const background = this.backgroundSignal();
3099
3359
  return background.type === 'image' ? background.src : '';
3100
3360
  });
3101
- this.imageSize = toSignal(toObservable(this.backgroundSignal).pipe(switchMap(() => createImage(this.bgImageSrc())), map((image) => ({ width: image.naturalWidth, height: image.naturalHeight }))), { initialValue: { width: 0, height: 0 } });
3361
+ this.imageSize = toLazySignal(toObservable(this.backgroundSignal).pipe(switchMap(() => createImage(this.bgImageSrc())), map((image) => ({ width: image.naturalWidth, height: image.naturalHeight }))), { initialValue: { width: 0, height: 0 } });
3102
3362
  this.scaledImageWidth = computed(() => {
3103
3363
  const background = this.backgroundSignal();
3104
3364
  if (background.type === 'image') {
@@ -3272,6 +3532,181 @@ function getSpacePoints(point, groups) {
3272
3532
  return result;
3273
3533
  }
3274
3534
 
3535
+ class PreviewFlowRenderStrategyService {
3536
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3537
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService }); }
3538
+ }
3539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowRenderStrategyService, decorators: [{
3540
+ type: Injectable
3541
+ }] });
3542
+ class ViewportPreviewFlowRenderStrategyService extends PreviewFlowRenderStrategyService {
3543
+ shouldRenderNode(node) {
3544
+ // Do not render preview node if the real node is visible
3545
+ return !node.isVisible();
3546
+ }
3547
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
3548
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService }); }
3549
+ }
3550
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ViewportPreviewFlowRenderStrategyService, decorators: [{
3551
+ type: Injectable
3552
+ }] });
3553
+
3554
+ function drawNode(ctx, node) {
3555
+ if (Object.keys(node.preview().style).length) {
3556
+ drawStyledNode(ctx, node);
3557
+ return;
3558
+ }
3559
+ if (node.rawNode.type === 'default') {
3560
+ drawDefaultNode(ctx, node);
3561
+ return;
3562
+ }
3563
+ if (node.rawNode.type === 'default-group') {
3564
+ drawDefaultGroupNode(ctx, node);
3565
+ return;
3566
+ }
3567
+ drawUnknownNode(ctx, node);
3568
+ }
3569
+ function drawDefaultNode(ctx, node) {
3570
+ const point = node.globalPoint();
3571
+ const width = node.width();
3572
+ const height = node.height();
3573
+ borderRadius(ctx, node, 5);
3574
+ // Draw background (background-color: white)
3575
+ ctx.fillStyle = 'white';
3576
+ ctx.fill();
3577
+ // Draw border (border: 1.5px solid #1b262c)
3578
+ ctx.strokeStyle = '#1b262c';
3579
+ ctx.lineWidth = 1.5;
3580
+ ctx.stroke();
3581
+ // Draw centered text (color: black, justify-content: center)
3582
+ ctx.fillStyle = 'black';
3583
+ // TODO: use as in default node
3584
+ ctx.font = '14px Arial';
3585
+ ctx.textAlign = 'center';
3586
+ ctx.textBaseline = 'middle';
3587
+ const centerX = point.x + width / 2;
3588
+ const centerY = point.y + height / 2;
3589
+ ctx.fillText(node.text(), centerX, centerY);
3590
+ }
3591
+ function drawDefaultGroupNode(ctx, node) {
3592
+ const point = node.globalPoint();
3593
+ const width = node.width();
3594
+ const height = node.height();
3595
+ ctx.globalAlpha = 0.05;
3596
+ ctx.fillStyle = node.color();
3597
+ ctx.fillRect(point.x, point.y, width, height);
3598
+ ctx.globalAlpha = 1;
3599
+ ctx.strokeStyle = node.color();
3600
+ ctx.lineWidth = 1.5;
3601
+ ctx.strokeRect(point.x, point.y, width, height);
3602
+ }
3603
+ function drawStyledNode(ctx, node) {
3604
+ const point = node.globalPoint();
3605
+ const width = node.width();
3606
+ const height = node.height();
3607
+ const style = node.preview().style;
3608
+ if (style.borderRadius) {
3609
+ const radius = parseFloat(style.borderRadius);
3610
+ borderRadius(ctx, node, radius);
3611
+ }
3612
+ else {
3613
+ ctx.beginPath();
3614
+ ctx.rect(point.x, point.y, width, height);
3615
+ ctx.closePath();
3616
+ }
3617
+ if (style.backgroundColor) {
3618
+ ctx.fillStyle = style.backgroundColor;
3619
+ }
3620
+ if (style.borderColor) {
3621
+ ctx.strokeStyle = style.borderColor;
3622
+ }
3623
+ if (style.borderWidth) {
3624
+ ctx.lineWidth = parseFloat(style.borderWidth);
3625
+ }
3626
+ ctx.fill();
3627
+ ctx.stroke();
3628
+ }
3629
+ function drawUnknownNode(ctx, node) {
3630
+ const point = node.globalPoint();
3631
+ const width = node.width();
3632
+ const height = node.height();
3633
+ ctx.fillStyle = 'rgb(0 0 0 / 10%)';
3634
+ ctx.fillRect(point.x, point.y, width, height);
3635
+ }
3636
+ function borderRadius(ctx, node, radius) {
3637
+ const point = node.globalPoint();
3638
+ const width = node.width();
3639
+ const height = node.height();
3640
+ // Create rounded rectangle path
3641
+ ctx.beginPath();
3642
+ ctx.moveTo(point.x + radius, point.y);
3643
+ ctx.lineTo(point.x + width - radius, point.y);
3644
+ ctx.quadraticCurveTo(point.x + width, point.y, point.x + width, point.y + radius);
3645
+ ctx.lineTo(point.x + width, point.y + height - radius);
3646
+ ctx.quadraticCurveTo(point.x + width, point.y + height, point.x + width - radius, point.y + height);
3647
+ ctx.lineTo(point.x + radius, point.y + height);
3648
+ ctx.quadraticCurveTo(point.x, point.y + height, point.x, point.y + height - radius);
3649
+ ctx.lineTo(point.x, point.y + radius);
3650
+ ctx.quadraticCurveTo(point.x, point.y, point.x + radius, point.y);
3651
+ ctx.closePath();
3652
+ }
3653
+
3654
+ class PreviewFlowComponent {
3655
+ constructor() {
3656
+ this.viewportService = inject(ViewportService);
3657
+ this.renderStrategy = inject(PreviewFlowRenderStrategyService);
3658
+ this.nodeRenderingService = inject(NodeRenderingService);
3659
+ this.renderer2 = inject(Renderer2);
3660
+ this.element = inject(ElementRef).nativeElement;
3661
+ this.ctx = this.element.getContext('2d');
3662
+ this.width = input(0);
3663
+ this.height = input(0);
3664
+ this.dpr = window.devicePixelRatio;
3665
+ effect(() => {
3666
+ // Set the "actual" size of the canvas
3667
+ this.renderer2.setProperty(this.element, 'width', this.width() * this.dpr);
3668
+ this.renderer2.setProperty(this.element, 'height', this.height() * this.dpr);
3669
+ // Set the "drawn" size of the canvas
3670
+ this.renderer2.setStyle(this.element, 'width', `${this.width()}px`);
3671
+ this.renderer2.setStyle(this.element, 'height', `${this.height()}px`);
3672
+ // Scale the context to match device pixel ratio
3673
+ this.ctx.scale(this.dpr, this.dpr);
3674
+ });
3675
+ effect(() => {
3676
+ const viewport = this.viewportService.readableViewport();
3677
+ this.ctx.clearRect(0, 0, this.width(), this.height());
3678
+ // Save the current context state
3679
+ this.ctx.save();
3680
+ // Apply viewport transformations (zoom and pan)
3681
+ this.ctx.setTransform(viewport.zoom * this.dpr, // horizontal scaling with DPR
3682
+ 0, // horizontal skewing
3683
+ 0, // vertical skewing
3684
+ viewport.zoom * this.dpr, // vertical scaling with DPR
3685
+ viewport.x * this.dpr, // horizontal translation with DPR
3686
+ viewport.y * this.dpr);
3687
+ for (let i = 0; i < this.nodeRenderingService.viewportNodes().length; i++) {
3688
+ const node = this.nodeRenderingService.viewportNodes()[i];
3689
+ if (this.renderStrategy.shouldRenderNode(node)) {
3690
+ drawNode(this.ctx, node);
3691
+ }
3692
+ }
3693
+ // Restore the context state
3694
+ this.ctx.restore();
3695
+ });
3696
+ }
3697
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3698
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "17.3.12", type: PreviewFlowComponent, isStandalone: true, selector: "canvas[previewFlow]", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3699
+ }
3700
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PreviewFlowComponent, decorators: [{
3701
+ type: Component,
3702
+ args: [{
3703
+ standalone: true,
3704
+ changeDetection: ChangeDetectionStrategy.OnPush,
3705
+ selector: 'canvas[previewFlow]',
3706
+ template: '',
3707
+ }]
3708
+ }], ctorParameters: () => [] });
3709
+
3275
3710
  const changesControllerHostDirective = {
3276
3711
  directive: ChangesControllerDirective,
3277
3712
  outputs: [
@@ -3319,9 +3754,6 @@ class VflowComponent {
3319
3754
  this.componentEventBusService = inject(ComponentEventBusService);
3320
3755
  this.keyboardService = inject(KeyboardService);
3321
3756
  this.injector = inject(Injector);
3322
- this.optimization = input({
3323
- detachedGroupsLayer: false,
3324
- });
3325
3757
  this.nodeModels = this.nodeRenderingService.nodes;
3326
3758
  this.groups = this.nodeRenderingService.groups;
3327
3759
  this.nonGroups = this.nodeRenderingService.nonGroups;
@@ -3355,13 +3787,13 @@ class VflowComponent {
3355
3787
  /**
3356
3788
  * Signal for reading nodes change
3357
3789
  */
3358
- this.nodesChange = toSignal(this.nodesChangeService.changes$, {
3790
+ this.nodesChange = toLazySignal(this.nodesChangeService.changes$, {
3359
3791
  initialValue: [],
3360
3792
  });
3361
3793
  /**
3362
3794
  * Signal to reading edges change
3363
3795
  */
3364
- this.edgesChange = toSignal(this.edgesChangeService.changes$, {
3796
+ this.edgesChange = toLazySignal(this.edgesChangeService.changes$, {
3365
3797
  initialValue: [],
3366
3798
  });
3367
3799
  // #endregion
@@ -3381,6 +3813,9 @@ class VflowComponent {
3381
3813
  // #endregion
3382
3814
  this.markers = this.flowEntitiesService.markers;
3383
3815
  this.minimap = this.flowEntitiesService.minimap;
3816
+ this.flowOptimization = this.flowSettingsService.optimization;
3817
+ this.flowWidth = this.flowSettingsService.computedFlowWidth;
3818
+ this.flowHeight = this.flowSettingsService.computedFlowHeight;
3384
3819
  }
3385
3820
  // #endregion
3386
3821
  // #region SETTINGS
@@ -3412,6 +3847,12 @@ class VflowComponent {
3412
3847
  set background(value) {
3413
3848
  this.flowSettingsService.background.set(transformBackground(value));
3414
3849
  }
3850
+ set optimization(newOptimization) {
3851
+ this.flowSettingsService.optimization.update((optimization) => ({
3852
+ ...optimization,
3853
+ ...newOptimization,
3854
+ }));
3855
+ }
3415
3856
  /**
3416
3857
  * Global rule if you can or can't select entities
3417
3858
  */
@@ -3469,7 +3910,7 @@ class VflowComponent {
3469
3910
  set edges(newEdges) {
3470
3911
  const newModels = runInInjectionContext(this.injector, () => ReferenceIdentityChecker.edges(newEdges, this.flowEntitiesService.edges()));
3471
3912
  // quick and dirty binding nodes to edges
3472
- addNodesToEdges(this.nodeModels(), newModels);
3913
+ addNodesToEdges(this.flowEntitiesService.nodes(), newModels);
3473
3914
  this.flowEntitiesService.edges.set(newModels);
3474
3915
  }
3475
3916
  // #region METHODS_API
@@ -3572,7 +4013,7 @@ class VflowComponent {
3572
4013
  return edge;
3573
4014
  }
3574
4015
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3575
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: false, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: false, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: false, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, optimization: { classPropertyName: "optimization", publicName: "optimization", isSignal: true, isRequired: false, transformFunction: null }, entitiesSelectable: { classPropertyName: "entitiesSelectable", publicName: "entitiesSelectable", isSignal: false, isRequired: false, transformFunction: null }, keyboardShortcuts: { classPropertyName: "keyboardShortcuts", publicName: "keyboardShortcuts", isSignal: false, isRequired: false, transformFunction: null }, connection: { classPropertyName: "connection", publicName: "connection", isSignal: false, isRequired: false, transformFunction: (settings) => new ConnectionModel(settings) }, snapGrid: { classPropertyName: "snapGrid", publicName: "snapGrid", isSignal: false, isRequired: false, transformFunction: null }, elevateNodesOnSelect: { classPropertyName: "elevateNodesOnSelect", publicName: "elevateNodesOnSelect", isSignal: false, isRequired: false, transformFunction: null }, elevateEdgesOnSelect: { classPropertyName: "elevateEdgesOnSelect", publicName: "elevateEdgesOnSelect", isSignal: false, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: false, isRequired: true, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
4016
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view: "view", minZoom: "minZoom", maxZoom: "maxZoom", background: "background", optimization: "optimization", entitiesSelectable: "entitiesSelectable", keyboardShortcuts: "keyboardShortcuts", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], snapGrid: "snapGrid", elevateNodesOnSelect: "elevateNodesOnSelect", elevateEdgesOnSelect: "elevateEdgesOnSelect", nodes: "nodes", edges: "edges" }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
3576
4017
  DraggableService,
3577
4018
  ViewportService,
3578
4019
  FlowStatusService,
@@ -3586,7 +4027,8 @@ class VflowComponent {
3586
4027
  ComponentEventBusService,
3587
4028
  KeyboardService,
3588
4029
  OverlaysService,
3589
- ], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "nodeSvgTemplateDirective", first: true, predicate: NodeSvgTemplateDirective, descendants: true, isSignal: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true, isSignal: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.size", "onNodesChange.size", "onNodesChange.size.single", "onNodesChange.size.single", "onNodesChange.size.many", "onNodesChange.size.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 #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (optimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nonGroups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!optimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n\n <!-- Nodes -->\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\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: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }, { kind: "directive", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]" }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["model", "nodeTemplate", "nodeSvgTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4030
+ { provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
4031
+ ], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "nodeSvgTemplateDirective", first: true, predicate: NodeSvgTemplateDirective, descendants: true, isSignal: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true, isSignal: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.size", "onNodesChange.size", "onNodesChange.size.single", "onNodesChange.size.single", "onNodesChange.size.many", "onNodesChange.size.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 #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (flowOptimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nonGroups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!flowOptimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n }\n</svg:svg>\n\n@if (flowOptimization().virtualization) {\n <canvas previewFlow class=\"preview-flow\" [width]=\"flowWidth()\" [height]=\"flowHeight()\"></canvas>\n}\n", styles: [":host{display:grid;grid-template-columns:1fr;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}.root-svg{grid-row-start:1;grid-column-start:1}.preview-flow{pointer-events:none;grid-row-start:1;grid-column-start:1}\n"], dependencies: [{ kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }, { kind: "directive", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]" }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["model", "nodeTemplate", "nodeSvgTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PreviewFlowComponent, selector: "canvas[previewFlow]", inputs: ["width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3590
4032
  }
3591
4033
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, decorators: [{
3592
4034
  type: Component,
@@ -3604,6 +4046,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3604
4046
  ComponentEventBusService,
3605
4047
  KeyboardService,
3606
4048
  OverlaysService,
4049
+ { provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
3607
4050
  ], hostDirectives: [changesControllerHostDirective], imports: [
3608
4051
  RootSvgReferenceDirective,
3609
4052
  RootSvgContextDirective,
@@ -3617,7 +4060,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3617
4060
  NodeComponent,
3618
4061
  EdgeComponent,
3619
4062
  NgTemplateOutlet,
3620
- ], template: "<svg:svg #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (optimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nonGroups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!optimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n\n <!-- Nodes -->\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\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"] }]
4063
+ PreviewFlowComponent,
4064
+ ], template: "<svg:svg #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (flowOptimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nonGroups(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!flowOptimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [model]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [nodeSvgTemplate]=\"nodeSvgTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n }\n</svg:svg>\n\n@if (flowOptimization().virtualization) {\n <canvas previewFlow class=\"preview-flow\" [width]=\"flowWidth()\" [height]=\"flowHeight()\"></canvas>\n}\n", styles: [":host{display:grid;grid-template-columns:1fr;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}.root-svg{grid-row-start:1;grid-column-start:1}.preview-flow{pointer-events:none;grid-row-start:1;grid-column-start:1}\n"] }]
3621
4065
  }], propDecorators: { view: [{
3622
4066
  type: Input
3623
4067
  }], minZoom: [{
@@ -3626,6 +4070,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3626
4070
  type: Input
3627
4071
  }], background: [{
3628
4072
  type: Input
4073
+ }], optimization: [{
4074
+ type: Input
3629
4075
  }], entitiesSelectable: [{
3630
4076
  type: Input
3631
4077
  }], keyboardShortcuts: [{
@@ -3966,5 +4412,5 @@ const Vflow = [
3966
4412
  * Generated bundle index. Do not edit.
3967
4413
  */
3968
4414
 
3969
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, CustomTemplateEdgeComponent, DragHandleDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, MiniMapComponent, NodeHtmlTemplateDirective, NodeSvgTemplateDirective, NodeToolbarComponent, NodeToolbarWrapperDirective, ResizableComponent, SelectableDirective, Vflow, VflowComponent, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isSvgTemplateDynamicNode, isSvgTemplateStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode, ComponentEventBusService as ɵComponentEventBusService, ConnectionModel as ɵConnectionModel, FlowEntitiesService as ɵFlowEntitiesService, FlowSettingsService as ɵFlowSettingsService, HandleModel as ɵHandleModel, HandleService as ɵHandleService, NodeAccessorService as ɵNodeAccessorService, NodeModel as ɵNodeModel, RootPointerDirective as ɵRootPointerDirective, SelectionService as ɵSelectionService, SpacePointContextDirective as ɵSpacePointContextDirective, ViewportService as ɵViewportService };
4415
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, CustomTemplateEdgeComponent, DEFAULT_OPTIMIZATION, DragHandleDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, MiniMapComponent, NodeHtmlTemplateDirective, NodeSvgTemplateDirective, NodeToolbarComponent, NodeToolbarWrapperDirective, ResizableComponent, SelectableDirective, Vflow, VflowComponent, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isSvgTemplateDynamicNode, isSvgTemplateStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode, ComponentEventBusService as ɵComponentEventBusService, ConnectionModel as ɵConnectionModel, FlowEntitiesService as ɵFlowEntitiesService, FlowSettingsService as ɵFlowSettingsService, HandleModel as ɵHandleModel, HandleService as ɵHandleService, NodeAccessorService as ɵNodeAccessorService, NodeModel as ɵNodeModel, RootPointerDirective as ɵRootPointerDirective, SelectionService as ɵSelectionService, SpacePointContextDirective as ɵSpacePointContextDirective, ViewportService as ɵViewportService };
3970
4416
  //# sourceMappingURL=ngx-vflow.mjs.map