ngx-vflow 1.13.2 → 1.15.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 (34) hide show
  1. package/esm2022/lib/vflow/components/node/node.component.mjs +5 -4
  2. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +7 -1
  3. package/esm2022/lib/vflow/interfaces/flow-entity.interface.mjs +1 -1
  4. package/esm2022/lib/vflow/interfaces/node.interface.mjs +13 -5
  5. package/esm2022/lib/vflow/interfaces/optimization.interface.mjs +2 -1
  6. package/esm2022/lib/vflow/interfaces/template-context.interface.mjs +1 -1
  7. package/esm2022/lib/vflow/models/edge.model.mjs +4 -9
  8. package/esm2022/lib/vflow/models/node.model.mjs +51 -7
  9. package/esm2022/lib/vflow/services/edge-rendering.service.mjs +1 -1
  10. package/esm2022/lib/vflow/services/flow-rendering.service.mjs +41 -0
  11. package/esm2022/lib/vflow/utils/is-callable.mjs +10 -0
  12. package/esm2022/lib/vflow/utils/is-vflow-component.mjs +9 -0
  13. package/esm2022/public-api.mjs +2 -1
  14. package/esm2022/testing/component-mocks/vflow-mock.component.mjs +3 -1
  15. package/esm2022/testing/provide-custom-node-mocks.mjs +3 -2
  16. package/fesm2022/ngx-vflow-testing.mjs +4 -1
  17. package/fesm2022/ngx-vflow-testing.mjs.map +1 -1
  18. package/fesm2022/ngx-vflow.mjs +267 -167
  19. package/fesm2022/ngx-vflow.mjs.map +1 -1
  20. package/lib/vflow/components/vflow/vflow.component.d.ts +3 -0
  21. package/lib/vflow/interfaces/flow-entity.interface.d.ts +2 -1
  22. package/lib/vflow/interfaces/node.interface.d.ts +2 -2
  23. package/lib/vflow/interfaces/optimization.interface.d.ts +4 -0
  24. package/lib/vflow/interfaces/template-context.interface.d.ts +4 -0
  25. package/lib/vflow/models/edge.model.d.ts +2 -0
  26. package/lib/vflow/models/node.model.d.ts +4 -0
  27. package/lib/vflow/public-components/custom-template-edge/custom-template-edge.component.d.ts +1 -0
  28. package/lib/vflow/services/edge-rendering.service.d.ts +1 -1
  29. package/lib/vflow/services/flow-rendering.service.d.ts +11 -0
  30. package/lib/vflow/utils/is-callable.d.ts +1 -0
  31. package/lib/vflow/utils/is-vflow-component.d.ts +4 -0
  32. package/package.json +1 -1
  33. package/public-api.d.ts +1 -0
  34. package/testing/component-mocks/vflow-mock.component.d.ts +2 -0
@@ -2,11 +2,11 @@ import * as i0 from '@angular/core';
2
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 { Subject, switchMap, merge, fromEvent, tap, Observable, skip, map, pairwise, filter, distinctUntilChanged, observeOn, asyncScheduler, zip, debounceTime, animationFrameScheduler, share, startWith } from 'rxjs';
6
- import { toObservable, takeUntilDestroyed, outputFromObservable, toSignal } from '@angular/core/rxjs-interop';
5
+ import { Subject, switchMap, merge, fromEvent, tap, Observable, observeOn, asyncScheduler, filter, debounceTime, map, catchError, of, shareReplay, skip, pairwise, distinctUntilChanged, zip, animationFrameScheduler, share, startWith } from 'rxjs';
6
+ import { toObservable, takeUntilDestroyed, toSignal, outputFromObservable } from '@angular/core/rxjs-interop';
7
7
  import { drag } from 'd3-drag';
8
8
  import { __decorate } from 'tslib';
9
- import { NgTemplateOutlet, NgComponentOutlet, KeyValuePipe } from '@angular/common';
9
+ import { NgTemplateOutlet, NgComponentOutlet, AsyncPipe, KeyValuePipe } from '@angular/common';
10
10
 
11
11
  const getOverlappingArea = (rectA, rectB) => {
12
12
  const xOverlap = Math.max(0, Math.min(rectA.x + rectA.width, rectB.x + rectB.width) - Math.max(rectA.x, rectB.x));
@@ -218,6 +218,7 @@ const DEFAULT_OPTIMIZATION = {
218
218
  detachedGroupsLayer: false,
219
219
  virtualization: false,
220
220
  virtualizationZoomThreshold: 0.5,
221
+ lazyLoadTrigger: 'immediate',
221
222
  };
222
223
 
223
224
  class FlowSettingsService {
@@ -826,6 +827,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
826
827
  type: Injectable
827
828
  }] });
828
829
 
830
+ function isCallable(fn) {
831
+ try {
832
+ new Proxy(fn, { apply: () => undefined })();
833
+ return true;
834
+ }
835
+ catch (err) {
836
+ return false;
837
+ }
838
+ }
839
+
829
840
  class ComponentEventBusService {
830
841
  constructor() {
831
842
  this._event$ = new Subject();
@@ -906,7 +917,7 @@ function outputRefToObservable(ref) {
906
917
  });
907
918
  }
908
919
 
909
- class CustomNodeComponent extends CustomNodeBaseComponent {
920
+ class CustomDynamicNodeComponent extends CustomNodeBaseComponent {
910
921
  constructor() {
911
922
  super(...arguments);
912
923
  /**
@@ -915,19 +926,20 @@ class CustomNodeComponent extends CustomNodeBaseComponent {
915
926
  this.node = input.required();
916
927
  }
917
928
  ngOnInit() {
918
- if (this.node().data) {
919
- this.data.set(this.node().data);
929
+ const data = this.node().data;
930
+ if (data) {
931
+ this.data = data;
920
932
  }
921
933
  super.ngOnInit();
922
934
  }
923
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
924
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.12", type: CustomNodeComponent, inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); }
935
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomDynamicNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
936
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.12", type: CustomDynamicNodeComponent, inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); }
925
937
  }
926
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomNodeComponent, decorators: [{
938
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomDynamicNodeComponent, decorators: [{
927
939
  type: Directive
928
940
  }] });
929
941
 
930
- class CustomDynamicNodeComponent extends CustomNodeBaseComponent {
942
+ class CustomNodeComponent extends CustomNodeBaseComponent {
931
943
  constructor() {
932
944
  super(...arguments);
933
945
  /**
@@ -936,19 +948,25 @@ class CustomDynamicNodeComponent extends CustomNodeBaseComponent {
936
948
  this.node = input.required();
937
949
  }
938
950
  ngOnInit() {
939
- const data = this.node().data;
940
- if (data) {
941
- this.data = data;
951
+ if (this.node().data) {
952
+ this.data.set(this.node().data);
942
953
  }
943
954
  super.ngOnInit();
944
955
  }
945
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomDynamicNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
946
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.12", type: CustomDynamicNodeComponent, inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); }
956
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
957
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.12", type: CustomNodeComponent, inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null } }, usesInheritance: true, ngImport: i0 }); }
947
958
  }
948
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomDynamicNodeComponent, decorators: [{
959
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomNodeComponent, decorators: [{
949
960
  type: Directive
950
961
  }] });
951
962
 
963
+ function isCustomNodeComponent(type) {
964
+ return Object.prototype.isPrototypeOf.call(CustomNodeComponent, type);
965
+ }
966
+ function isCustomDynamicNodeComponent(type) {
967
+ return Object.prototype.isPrototypeOf.call(CustomDynamicNodeComponent, type);
968
+ }
969
+
952
970
  function isStaticNode(node) {
953
971
  return typeof node.point !== 'function';
954
972
  }
@@ -956,10 +974,18 @@ function isDynamicNode(node) {
956
974
  return typeof node.point === 'function';
957
975
  }
958
976
  function isComponentStaticNode(node) {
959
- return Object.prototype.isPrototypeOf.call(CustomNodeComponent, node.type);
977
+ if (isCustomNodeComponent(node.type)) {
978
+ return true;
979
+ }
980
+ // Check if the type is a function with dynamic import
981
+ return isCallable(node.type) && !isCallable(node.point);
960
982
  }
961
983
  function isComponentDynamicNode(node) {
962
- return Object.prototype.isPrototypeOf.call(CustomDynamicNodeComponent, node.type);
984
+ if (isCustomDynamicNodeComponent(node.type)) {
985
+ return true;
986
+ }
987
+ // Check if the type is a function with dynamic import
988
+ return isCallable(node.type) && isCallable(node.point);
963
989
  }
964
990
  function isTemplateStaticNode(node) {
965
991
  return node.type === 'html-template';
@@ -1018,6 +1044,138 @@ function toSignalProperties(obj) {
1018
1044
  return newObj;
1019
1045
  }
1020
1046
 
1047
+ function isGroupNode(node) {
1048
+ return node.rawNode.type === 'default-group' || node.rawNode.type === 'template-group';
1049
+ }
1050
+
1051
+ // MIT License
1052
+ // Copyright (c) 2023 Chau Tran
1053
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1054
+ // of this software and associated documentation files (the "Software"), to deal
1055
+ // in the Software without restriction, including without limitation the rights
1056
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1057
+ // copies of the Software, and to permit persons to whom the Software is
1058
+ // furnished to do so, subject to the following conditions:
1059
+ // The above copyright notice and this permission notice shall be included in all
1060
+ // copies or substantial portions of the Software.
1061
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1062
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1063
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1064
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1065
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1066
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1067
+ // SOFTWARE.
1068
+ /* eslint-disable @typescript-eslint/ban-types */
1069
+ function assertInjector(fn, injector, runner) {
1070
+ !injector && assertInInjectionContext(fn);
1071
+ const assertedInjector = injector ?? inject(Injector);
1072
+ if (!runner)
1073
+ return assertedInjector;
1074
+ return runInInjectionContext(assertedInjector, runner);
1075
+ }
1076
+
1077
+ // MIT License
1078
+ // Copyright (c) 2023 Chau Tran
1079
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1080
+ // of this software and associated documentation files (the "Software"), to deal
1081
+ // in the Software without restriction, including without limitation the rights
1082
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1083
+ // copies of the Software, and to permit persons to whom the Software is
1084
+ // furnished to do so, subject to the following conditions:
1085
+ // The above copyright notice and this permission notice shall be included in all
1086
+ // copies or substantial portions of the Software.
1087
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1088
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1089
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1090
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1091
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1092
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1093
+ // SOFTWARE.
1094
+ /**
1095
+ * Function `toLazySignal()` is a proxy function that will call the original
1096
+ * `toSignal()` function when the returned signal is read for the first time.
1097
+ */
1098
+ function toLazySignal(source, options) {
1099
+ const injector = assertInjector(toLazySignal, options?.injector);
1100
+ let s;
1101
+ return computed(() => {
1102
+ if (!s) {
1103
+ s = untracked(() => toSignal(source, { ...options, injector }));
1104
+ }
1105
+ return s();
1106
+ });
1107
+ }
1108
+
1109
+ class NodeRenderingService {
1110
+ constructor() {
1111
+ this.flowEntitiesService = inject(FlowEntitiesService);
1112
+ this.flowSettingsService = inject(FlowSettingsService);
1113
+ this.viewportService = inject(ViewportService);
1114
+ this.nodes = computed(() => {
1115
+ if (!this.flowSettingsService.optimization().virtualization) {
1116
+ return [...this.flowEntitiesService.nodes()].sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1117
+ }
1118
+ return this.viewportNodesAfterInteraction().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1119
+ });
1120
+ this.groups = computed(() => {
1121
+ return this.nodes().filter((n) => isGroupNode(n));
1122
+ });
1123
+ this.nonGroups = computed(() => {
1124
+ return this.nodes().filter((n) => !isGroupNode(n));
1125
+ });
1126
+ this.viewportNodes = computed(() => {
1127
+ const nodes = this.flowEntitiesService.nodes();
1128
+ const viewport = this.viewportService.readableViewport();
1129
+ const flowWidth = this.flowSettingsService.computedFlowWidth();
1130
+ const flowHeight = this.flowSettingsService.computedFlowHeight();
1131
+ return nodes.filter((n) => {
1132
+ const { x, y } = n.globalPoint();
1133
+ const width = n.width();
1134
+ const height = n.height();
1135
+ return isRectInViewport({ x, y, width, height }, viewport, flowWidth, flowHeight);
1136
+ });
1137
+ });
1138
+ this.viewportNodesAfterInteraction = toLazySignal(merge(
1139
+ // TODO: maybe there is a better way wait when viewport is ready?
1140
+ // (to correctly calculate viewport nodes on first render)
1141
+ toObservable(this.flowEntitiesService.nodes).pipe(observeOn(asyncScheduler), filter((nodes) => !!nodes.length)), this.viewportService.viewportChangeEnd$.pipe(debounceTime(300))).pipe(map(() => {
1142
+ const viewport = this.viewportService.readableViewport();
1143
+ const zoomThreshold = this.flowSettingsService.optimization().virtualizationZoomThreshold;
1144
+ return viewport.zoom < zoomThreshold ? [] : this.viewportNodes();
1145
+ })), {
1146
+ initialValue: [],
1147
+ });
1148
+ this.maxOrder = computed(() => {
1149
+ return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
1150
+ });
1151
+ }
1152
+ pullNode(node) {
1153
+ // pull node
1154
+ node.renderOrder.set(this.maxOrder() + 1);
1155
+ // pull children
1156
+ node.children().forEach((n) => this.pullNode(n));
1157
+ }
1158
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1159
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService }); }
1160
+ }
1161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService, decorators: [{
1162
+ type: Injectable
1163
+ }] });
1164
+
1165
+ // MIT License
1166
+ /**
1167
+ * @todo Use `linkedSignal` after Angular update
1168
+ */
1169
+ function extendedComputed(computedCallback, options) {
1170
+ if (!options) {
1171
+ options = { equal: Object.is };
1172
+ }
1173
+ let currentValue = undefined;
1174
+ return computed(() => {
1175
+ return (currentValue = computedCallback(currentValue));
1176
+ }, options);
1177
+ }
1178
+
1021
1179
  class NodeModel {
1022
1180
  static { this.defaultWidth = 100; }
1023
1181
  static { this.defaultHeight = 50; }
@@ -1025,6 +1183,8 @@ class NodeModel {
1025
1183
  constructor(rawNode) {
1026
1184
  this.rawNode = rawNode;
1027
1185
  this.entitiesService = inject(FlowEntitiesService);
1186
+ this.settingsService = inject(FlowSettingsService);
1187
+ this.nodeRenderingService = inject(NodeRenderingService);
1028
1188
  this.isVisible = signal(false);
1029
1189
  this.point = signal({ x: 0, y: 0 });
1030
1190
  this.width = signal(NodeModel.defaultWidth);
@@ -1063,6 +1223,39 @@ class NodeModel {
1063
1223
  this.magnetRadius = 20;
1064
1224
  // TODO: not sure if we need to statically store it
1065
1225
  this.isComponentType = isComponentStaticNode(this.rawNode) || isComponentDynamicNode(this.rawNode);
1226
+ this.shouldLoad = extendedComputed((previousShouldLoad) => {
1227
+ if (previousShouldLoad) {
1228
+ return true;
1229
+ }
1230
+ if (this.settingsService.optimization().lazyLoadTrigger === 'immediate') {
1231
+ return true;
1232
+ }
1233
+ else if (this.settingsService.optimization().lazyLoadTrigger === 'viewport') {
1234
+ // Immediately load component if it's a plain class
1235
+ if (isCustomNodeComponent(this.rawNode.type)) {
1236
+ return true;
1237
+ }
1238
+ // Immediately load component if it's a plain class
1239
+ if (isCustomDynamicNodeComponent(this.rawNode.type)) {
1240
+ return true;
1241
+ }
1242
+ // For cases
1243
+ // - if it's a factory with dynamic import
1244
+ // - if it's a template (html, svg, group)
1245
+ // check if it's in the viewport
1246
+ if (isCallable(this.rawNode.type) ||
1247
+ this.rawNode.type === 'html-template' ||
1248
+ this.rawNode.type === 'svg-template' ||
1249
+ this.rawNode.type === 'template-group') {
1250
+ return this.nodeRenderingService.viewportNodes().includes(this);
1251
+ }
1252
+ }
1253
+ // For each other case, we want to load the component immediately
1254
+ return true;
1255
+ });
1256
+ this.componentInstance$ = toObservable(this.shouldLoad).pipe(filter(Boolean),
1257
+ // @ts-expect-error we assume it's a function with dynamic import
1258
+ switchMap(() => this.rawNode.type()), catchError(() => of(this.rawNode.type)), shareReplay(1));
1066
1259
  // Default node specific thing
1067
1260
  this.text = signal('');
1068
1261
  // Component node specific thing
@@ -1112,7 +1305,8 @@ class NodeModel {
1112
1305
  this.context = {
1113
1306
  $implicit: {
1114
1307
  node: rawNode,
1115
- selected: this.selected,
1308
+ selected: this.selected.asReadonly(),
1309
+ shouldLoad: this.shouldLoad,
1116
1310
  },
1117
1311
  };
1118
1312
  }
@@ -1120,9 +1314,10 @@ class NodeModel {
1120
1314
  this.context = {
1121
1315
  $implicit: {
1122
1316
  node: rawNode,
1123
- selected: this.selected,
1124
- width: this.width,
1125
- height: this.height,
1317
+ selected: this.selected.asReadonly(),
1318
+ width: this.width.asReadonly(),
1319
+ height: this.height.asReadonly(),
1320
+ shouldLoad: this.shouldLoad,
1126
1321
  },
1127
1322
  };
1128
1323
  }
@@ -1131,8 +1326,9 @@ class NodeModel {
1131
1326
  $implicit: {
1132
1327
  node: rawNode,
1133
1328
  selected: this.selected.asReadonly(),
1134
- width: this.width,
1135
- height: this.height,
1329
+ width: this.width.asReadonly(),
1330
+ height: this.height.asReadonly(),
1331
+ shouldLoad: this.shouldLoad,
1136
1332
  },
1137
1333
  };
1138
1334
  }
@@ -1470,20 +1666,6 @@ function smoothStepPath({ sourcePoint, targetPoint, sourcePosition, targetPositi
1470
1666
  };
1471
1667
  }
1472
1668
 
1473
- // MIT License
1474
- /**
1475
- * @todo Use `linkedSignal` after Angular update
1476
- */
1477
- function extendedComputed(computedCallback, options) {
1478
- if (!options) {
1479
- options = { equal: Object.is };
1480
- }
1481
- let currentValue = undefined;
1482
- return computed(() => {
1483
- return (currentValue = computedCallback(currentValue));
1484
- }, options);
1485
- }
1486
-
1487
1669
  class EdgeModel {
1488
1670
  constructor(edge) {
1489
1671
  this.edge = edge;
@@ -1492,6 +1674,7 @@ class EdgeModel {
1492
1674
  this.target = signal(undefined);
1493
1675
  this.selected = signal(false);
1494
1676
  this.selected$ = toObservable(this.selected);
1677
+ this.shouldLoad = computed(() => (this.source()?.shouldLoad() ?? false) && (this.target()?.shouldLoad() ?? false));
1495
1678
  this.renderOrder = signal(0);
1496
1679
  this.detached = computed(() => {
1497
1680
  const source = this.source();
@@ -1521,14 +1704,7 @@ class EdgeModel {
1521
1704
  const target = this.targetHandle();
1522
1705
  // TODO: don't like this
1523
1706
  if (!source || !target) {
1524
- return {
1525
- path: '',
1526
- labelPoints: {
1527
- start: { x: 0, y: 0 },
1528
- center: { x: 0, y: 0 },
1529
- end: { x: 0, y: 0 },
1530
- },
1531
- };
1707
+ return { path: '' };
1532
1708
  }
1533
1709
  const params = this.getPathFactoryParams(source, target);
1534
1710
  switch (this.curve) {
@@ -1660,6 +1836,7 @@ class EdgeModel {
1660
1836
  markerStart: this.markerStartUrl,
1661
1837
  markerEnd: this.markerEndUrl,
1662
1838
  selected: this.selected.asReadonly(),
1839
+ shouldLoad: this.shouldLoad,
1663
1840
  },
1664
1841
  };
1665
1842
  this.edgeLabels = {};
@@ -1947,124 +2124,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1947
2124
  }]
1948
2125
  }] });
1949
2126
 
1950
- function isGroupNode(node) {
1951
- return node.rawNode.type === 'default-group' || node.rawNode.type === 'template-group';
1952
- }
1953
-
1954
- // MIT License
1955
- // Copyright (c) 2023 Chau Tran
1956
- // Permission is hereby granted, free of charge, to any person obtaining a copy
1957
- // of this software and associated documentation files (the "Software"), to deal
1958
- // in the Software without restriction, including without limitation the rights
1959
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1960
- // copies of the Software, and to permit persons to whom the Software is
1961
- // furnished to do so, subject to the following conditions:
1962
- // The above copyright notice and this permission notice shall be included in all
1963
- // copies or substantial portions of the Software.
1964
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1965
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1966
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1967
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1968
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1969
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1970
- // SOFTWARE.
1971
- /* eslint-disable @typescript-eslint/ban-types */
1972
- function assertInjector(fn, injector, runner) {
1973
- !injector && assertInInjectionContext(fn);
1974
- const assertedInjector = injector ?? inject(Injector);
1975
- if (!runner)
1976
- return assertedInjector;
1977
- return runInInjectionContext(assertedInjector, runner);
1978
- }
1979
-
1980
- // MIT License
1981
- // Copyright (c) 2023 Chau Tran
1982
- // Permission is hereby granted, free of charge, to any person obtaining a copy
1983
- // of this software and associated documentation files (the "Software"), to deal
1984
- // in the Software without restriction, including without limitation the rights
1985
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1986
- // copies of the Software, and to permit persons to whom the Software is
1987
- // furnished to do so, subject to the following conditions:
1988
- // The above copyright notice and this permission notice shall be included in all
1989
- // copies or substantial portions of the Software.
1990
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1991
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1992
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1993
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1994
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1995
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1996
- // SOFTWARE.
1997
- /**
1998
- * Function `toLazySignal()` is a proxy function that will call the original
1999
- * `toSignal()` function when the returned signal is read for the first time.
2000
- */
2001
- function toLazySignal(source, options) {
2002
- const injector = assertInjector(toLazySignal, options?.injector);
2003
- let s;
2004
- return computed(() => {
2005
- if (!s) {
2006
- s = untracked(() => toSignal(source, { ...options, injector }));
2007
- }
2008
- return s();
2009
- });
2010
- }
2011
-
2012
- class NodeRenderingService {
2013
- constructor() {
2014
- this.flowEntitiesService = inject(FlowEntitiesService);
2015
- this.flowSettingsService = inject(FlowSettingsService);
2016
- this.viewportService = inject(ViewportService);
2017
- this.nodes = computed(() => {
2018
- if (!this.flowSettingsService.optimization().virtualization) {
2019
- return [...this.flowEntitiesService.nodes()].sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
2020
- }
2021
- return this.viewportNodesAfterInteraction().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
2022
- });
2023
- this.groups = computed(() => {
2024
- return this.nodes().filter((n) => isGroupNode(n));
2025
- });
2026
- this.nonGroups = computed(() => {
2027
- return this.nodes().filter((n) => !isGroupNode(n));
2028
- });
2029
- this.viewportNodes = computed(() => {
2030
- const nodes = this.flowEntitiesService.nodes();
2031
- const viewport = this.viewportService.readableViewport();
2032
- const flowWidth = this.flowSettingsService.computedFlowWidth();
2033
- const flowHeight = this.flowSettingsService.computedFlowHeight();
2034
- return nodes.filter((n) => {
2035
- const { x, y } = n.globalPoint();
2036
- const width = n.width();
2037
- const height = n.height();
2038
- return isRectInViewport({ x, y, width, height }, viewport, flowWidth, flowHeight);
2039
- });
2040
- });
2041
- this.viewportNodesAfterInteraction = toLazySignal(merge(
2042
- // TODO: maybe there is a better way wait when viewport is ready?
2043
- // (to correctly calculate viewport nodes on first render)
2044
- toObservable(this.flowEntitiesService.nodes).pipe(observeOn(asyncScheduler), filter((nodes) => !!nodes.length)), this.viewportService.viewportChangeEnd$.pipe(debounceTime(300))).pipe(map(() => {
2045
- const viewport = this.viewportService.readableViewport();
2046
- const zoomThreshold = this.flowSettingsService.optimization().virtualizationZoomThreshold;
2047
- return viewport.zoom < zoomThreshold ? [] : this.viewportNodes();
2048
- })), {
2049
- initialValue: [],
2050
- });
2051
- this.maxOrder = computed(() => {
2052
- return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
2053
- });
2054
- }
2055
- pullNode(node) {
2056
- // pull node
2057
- node.renderOrder.set(this.maxOrder() + 1);
2058
- // pull children
2059
- node.children().forEach((n) => this.pullNode(n));
2060
- }
2061
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2062
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService }); }
2063
- }
2064
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService, decorators: [{
2065
- type: Injectable
2066
- }] });
2067
-
2068
2127
  class RootPointerDirective {
2069
2128
  constructor() {
2070
2129
  this.host = inject(ElementRef).nativeElement;
@@ -3206,7 +3265,7 @@ class NodeComponent {
3206
3265
  }
3207
3266
  }
3208
3267
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3209
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: NodeComponent, isStandalone: true, selector: "g[node]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null }, nodeSvgTemplate: { classPropertyName: "nodeSvgTemplate", publicName: "nodeSvgTemplate", isSignal: true, isRequired: false, transformFunction: null }, groupNodeTemplate: { classPropertyName: "groupNodeTemplate", publicName: "groupNodeTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "vflow-node" }, providers: [HandleService, NodeAccessorService], ngImport: i0, template: "<!-- Default node -->\n@if (model().rawNode.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- HTML Template node -->\n@if (model().rawNode.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- SVG Template node -->\n@if (model().rawNode.type === 'svg-template' && nodeSvgTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeSvgTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(model().rawNode.type)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Default group node -->\n@if (model().rawNode.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (click)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().rawNode.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (handle.template === undefined) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template === null) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@for (toolbar of toolbars(); track toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }, { kind: "component", type: DefaultNodeComponent, selector: "default-node", inputs: ["selected"] }, { kind: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template", "offsetX", "offsetY"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ResizableComponent, selector: "[resizable]", inputs: ["resizable", "resizerColor", "gap"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }, { kind: "directive", type: NodeHandlesControllerDirective, selector: "[nodeHandlesController]" }, { kind: "directive", type: NodeResizeControllerDirective, selector: "[nodeResizeController]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3268
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: NodeComponent, isStandalone: true, selector: "g[node]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null }, nodeSvgTemplate: { classPropertyName: "nodeSvgTemplate", publicName: "nodeSvgTemplate", isSignal: true, isRequired: false, transformFunction: null }, groupNodeTemplate: { classPropertyName: "groupNodeTemplate", publicName: "groupNodeTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "vflow-node" }, providers: [HandleService, NodeAccessorService], ngImport: i0, template: "<!-- Default node -->\n@if (model().rawNode.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- HTML Template node -->\n@if (model().rawNode.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- SVG Template node -->\n@if (model().rawNode.type === 'svg-template' && nodeSvgTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeSvgTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n @if (model().componentInstance$ | async; as component) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(component)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n }\n}\n\n<!-- Default group node -->\n@if (model().rawNode.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (click)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().rawNode.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (handle.template === undefined) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template === null) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@for (toolbar of toolbars(); track toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }, { kind: "component", type: DefaultNodeComponent, selector: "default-node", inputs: ["selected"] }, { kind: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template", "offsetX", "offsetY"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ResizableComponent, selector: "[resizable]", inputs: ["resizable", "resizerColor", "gap"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }, { kind: "directive", type: NodeHandlesControllerDirective, selector: "[nodeHandlesController]" }, { kind: "directive", type: NodeResizeControllerDirective, selector: "[nodeResizeController]" }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3210
3269
  }
3211
3270
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeComponent, decorators: [{
3212
3271
  type: Component,
@@ -3222,7 +3281,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3222
3281
  HandleSizeControllerDirective,
3223
3282
  NodeHandlesControllerDirective,
3224
3283
  NodeResizeControllerDirective,
3225
- ], template: "<!-- Default node -->\n@if (model().rawNode.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- HTML Template node -->\n@if (model().rawNode.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- SVG Template node -->\n@if (model().rawNode.type === 'svg-template' && nodeSvgTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeSvgTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(model().rawNode.type)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Default group node -->\n@if (model().rawNode.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (click)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().rawNode.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (handle.template === undefined) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template === null) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@for (toolbar of toolbars(); track toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
3284
+ AsyncPipe,
3285
+ ], template: "<!-- Default node -->\n@if (model().rawNode.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- HTML Template node -->\n@if (model().rawNode.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- SVG Template node -->\n@if (model().rawNode.type === 'svg-template' && nodeSvgTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeSvgTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n @if (model().componentInstance$ | async; as component) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (click)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(component)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n }\n}\n\n<!-- Default group node -->\n@if (model().rawNode.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (click)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().rawNode.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (click)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"model().context\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (handle.template === undefined) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template === null) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@for (toolbar of toolbars(); track toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
3226
3286
  }] });
3227
3287
 
3228
3288
  class ConnectionComponent {
@@ -3768,6 +3828,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3768
3828
  }]
3769
3829
  }], ctorParameters: () => [] });
3770
3830
 
3831
+ class FlowRenderingService {
3832
+ constructor() {
3833
+ this.nodeRenderingService = inject(NodeRenderingService);
3834
+ this.edgeRenderingService = inject(EdgeRenderingService);
3835
+ this.flowEntitiesService = inject(FlowEntitiesService);
3836
+ this.settingsService = inject(FlowSettingsService);
3837
+ this.flowInitialized = signal(false);
3838
+ inject(NgZone).runOutsideAngular(async () => {
3839
+ await skipFrames(2);
3840
+ this.flowInitialized.set(true);
3841
+ });
3842
+ }
3843
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3844
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowRenderingService }); }
3845
+ }
3846
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowRenderingService, decorators: [{
3847
+ type: Injectable
3848
+ }], ctorParameters: () => [] });
3849
+ // TODO may break on edge cases
3850
+ function skipFrames(count) {
3851
+ return new Promise((resolve) => {
3852
+ let frames = 0;
3853
+ function checkFrame() {
3854
+ frames++;
3855
+ if (frames < count) {
3856
+ requestAnimationFrame(checkFrame);
3857
+ }
3858
+ else {
3859
+ resolve();
3860
+ }
3861
+ }
3862
+ requestAnimationFrame(checkFrame);
3863
+ });
3864
+ }
3865
+
3771
3866
  const changesControllerHostDirective = {
3772
3867
  directive: ChangesControllerDirective,
3773
3868
  outputs: [
@@ -3815,6 +3910,7 @@ class VflowComponent {
3815
3910
  this.componentEventBusService = inject(ComponentEventBusService);
3816
3911
  this.keyboardService = inject(KeyboardService);
3817
3912
  this.injector = inject(Injector);
3913
+ this.flowRenderingService = inject(FlowRenderingService);
3818
3914
  this.nodeModels = this.nodeRenderingService.nodes;
3819
3915
  this.groups = this.nodeRenderingService.groups;
3820
3916
  this.nonGroups = this.nodeRenderingService.nonGroups;
@@ -3857,6 +3953,7 @@ class VflowComponent {
3857
3953
  this.edgesChange = toLazySignal(this.edgesChangeService.changes$, {
3858
3954
  initialValue: [],
3859
3955
  });
3956
+ this.initialized = this.flowRenderingService.flowInitialized.asReadonly();
3860
3957
  // #endregion
3861
3958
  // #region RX_API
3862
3959
  /**
@@ -3871,6 +3968,7 @@ class VflowComponent {
3871
3968
  * Observable with edges change
3872
3969
  */
3873
3970
  this.edgesChange$ = this.edgesChangeService.changes$;
3971
+ this.initialized$ = toObservable(this.flowRenderingService.flowInitialized);
3874
3972
  // #endregion
3875
3973
  this.markers = this.flowEntitiesService.markers;
3876
3974
  this.minimap = this.flowEntitiesService.minimap;
@@ -4089,6 +4187,7 @@ class VflowComponent {
4089
4187
  KeyboardService,
4090
4188
  OverlaysService,
4091
4189
  { provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
4190
+ FlowRenderingService,
4092
4191
  ], 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 }); }
4093
4192
  }
4094
4193
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, decorators: [{
@@ -4108,6 +4207,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4108
4207
  KeyboardService,
4109
4208
  OverlaysService,
4110
4209
  { provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
4210
+ FlowRenderingService,
4111
4211
  ], hostDirectives: [changesControllerHostDirective], imports: [
4112
4212
  RootSvgReferenceDirective,
4113
4213
  RootSvgContextDirective,
@@ -4473,5 +4573,5 @@ const Vflow = [
4473
4573
  * Generated bundle index. Do not edit.
4474
4574
  */
4475
4575
 
4476
- 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 };
4576
+ 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, NodeRenderingService as ɵNodeRenderingService, RootPointerDirective as ɵRootPointerDirective, SelectionService as ɵSelectionService, SpacePointContextDirective as ɵSpacePointContextDirective, ViewportService as ɵViewportService };
4477
4577
  //# sourceMappingURL=ngx-vflow.mjs.map