ngx-vflow 1.14.0 → 1.16.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 (44) hide show
  1. package/esm2022/lib/vflow/components/alignment-helper/alignment-helper.component.mjs +103 -0
  2. package/esm2022/lib/vflow/components/node/node.component.mjs +5 -4
  3. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +8 -5
  4. package/esm2022/lib/vflow/interfaces/alignment-helper-settings.interface.mjs +2 -0
  5. package/esm2022/lib/vflow/interfaces/flow-entity.interface.mjs +1 -1
  6. package/esm2022/lib/vflow/interfaces/node.interface.mjs +13 -5
  7. package/esm2022/lib/vflow/interfaces/optimization.interface.mjs +2 -1
  8. package/esm2022/lib/vflow/interfaces/rect.mjs +10 -2
  9. package/esm2022/lib/vflow/interfaces/template-context.interface.mjs +1 -1
  10. package/esm2022/lib/vflow/models/edge.model.mjs +4 -9
  11. package/esm2022/lib/vflow/models/node.model.mjs +51 -7
  12. package/esm2022/lib/vflow/services/draggable.service.mjs +7 -1
  13. package/esm2022/lib/vflow/services/edge-rendering.service.mjs +1 -1
  14. package/esm2022/lib/vflow/services/flow-rendering.service.mjs +9 -1
  15. package/esm2022/lib/vflow/services/flow-status.service.mjs +13 -1
  16. package/esm2022/lib/vflow/utils/is-callable.mjs +10 -0
  17. package/esm2022/lib/vflow/utils/is-vflow-component.mjs +9 -0
  18. package/esm2022/public-api.mjs +3 -1
  19. package/esm2022/testing/component-mocks/vflow-mock.component.mjs +4 -3
  20. package/esm2022/testing/provide-custom-node-mocks.mjs +3 -2
  21. package/fesm2022/ngx-vflow-testing.mjs +5 -3
  22. package/fesm2022/ngx-vflow-testing.mjs.map +1 -1
  23. package/fesm2022/ngx-vflow.mjs +387 -201
  24. package/fesm2022/ngx-vflow.mjs.map +1 -1
  25. package/lib/vflow/components/alignment-helper/alignment-helper.component.d.ts +21 -0
  26. package/lib/vflow/components/vflow/vflow.component.d.ts +3 -1
  27. package/lib/vflow/interfaces/alignment-helper-settings.interface.d.ts +4 -0
  28. package/lib/vflow/interfaces/flow-entity.interface.d.ts +2 -1
  29. package/lib/vflow/interfaces/node.interface.d.ts +2 -2
  30. package/lib/vflow/interfaces/optimization.interface.d.ts +4 -0
  31. package/lib/vflow/interfaces/rect.d.ts +7 -0
  32. package/lib/vflow/interfaces/template-context.interface.d.ts +4 -0
  33. package/lib/vflow/models/edge.model.d.ts +2 -0
  34. package/lib/vflow/models/node.model.d.ts +4 -0
  35. package/lib/vflow/public-components/custom-template-edge/custom-template-edge.component.d.ts +1 -0
  36. package/lib/vflow/services/draggable.service.d.ts +1 -0
  37. package/lib/vflow/services/edge-rendering.service.d.ts +1 -1
  38. package/lib/vflow/services/flow-rendering.service.d.ts +4 -0
  39. package/lib/vflow/services/flow-status.service.d.ts +17 -1
  40. package/lib/vflow/utils/is-callable.d.ts +1 -0
  41. package/lib/vflow/utils/is-vflow-component.d.ts +4 -0
  42. package/package.json +1 -1
  43. package/public-api.d.ts +2 -0
  44. package/testing/component-mocks/vflow-mock.component.d.ts +3 -2
@@ -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 {
@@ -553,10 +554,58 @@ function align(num, constant) {
553
554
  return Math.ceil(num / constant) * constant;
554
555
  }
555
556
 
557
+ class FlowStatusService {
558
+ constructor() {
559
+ this.status = signal({ state: 'idle', payload: null });
560
+ }
561
+ setIdleStatus() {
562
+ this.status.set({ state: 'idle', payload: null });
563
+ }
564
+ setConnectionStartStatus(source, sourceHandle) {
565
+ this.status.set({ state: 'connection-start', payload: { source, sourceHandle } });
566
+ }
567
+ setReconnectionStartStatus(source, sourceHandle, oldEdge) {
568
+ this.status.set({ state: 'reconnection-start', payload: { source, sourceHandle, oldEdge } });
569
+ }
570
+ setConnectionValidationStatus(valid, source, target, sourceHandle, targetHandle) {
571
+ this.status.set({ state: 'connection-validation', payload: { source, target, sourceHandle, targetHandle, valid } });
572
+ }
573
+ setReconnectionValidationStatus(valid, source, target, sourceHandle, targetHandle, oldEdge) {
574
+ this.status.set({
575
+ state: 'reconnection-validation',
576
+ payload: { source, target, sourceHandle, targetHandle, valid, oldEdge },
577
+ });
578
+ }
579
+ setConnectionEndStatus(source, target, sourceHandle, targetHandle) {
580
+ this.status.set({ state: 'connection-end', payload: { source, target, sourceHandle, targetHandle } });
581
+ }
582
+ setReconnectionEndStatus(source, target, sourceHandle, targetHandle, oldEdge) {
583
+ this.status.set({ state: 'reconnection-end', payload: { source, target, sourceHandle, targetHandle, oldEdge } });
584
+ }
585
+ setNodeDragStartStatus(node) {
586
+ this.status.set({ state: 'node-drag-start', payload: { node } });
587
+ }
588
+ setNodeDragEndStatus(node) {
589
+ this.status.set({ state: 'node-drag-end', payload: { node } });
590
+ }
591
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
592
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService }); }
593
+ }
594
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService, decorators: [{
595
+ type: Injectable
596
+ }] });
597
+ function isNodeDragStartStatus(params) {
598
+ return params.state === 'node-drag-start';
599
+ }
600
+ function isNodeDragEndStatus(params) {
601
+ return params.state === 'node-drag-end';
602
+ }
603
+
556
604
  class DraggableService {
557
605
  constructor() {
558
606
  this.entitiesService = inject(FlowEntitiesService);
559
607
  this.settingsService = inject(FlowSettingsService);
608
+ this.flowStatusService = inject(FlowStatusService);
560
609
  }
561
610
  /**
562
611
  * Enable draggable behavior for element.
@@ -604,6 +653,7 @@ class DraggableService {
604
653
  .filter(filterCondition)
605
654
  .on('start', (event) => {
606
655
  dragNodes = this.getDragNodes(model);
656
+ this.flowStatusService.setNodeDragStartStatus(model);
607
657
  initialPositions = dragNodes.map((node) => ({
608
658
  x: node.point().x - event.x,
609
659
  y: node.point().y - event.y,
@@ -617,6 +667,9 @@ class DraggableService {
617
667
  };
618
668
  this.moveNode(model, point);
619
669
  });
670
+ })
671
+ .on('end', () => {
672
+ this.flowStatusService.setNodeDragEndStatus(model);
620
673
  });
621
674
  }
622
675
  getDragNodes(model) {
@@ -791,40 +844,15 @@ function addNodesToEdges(nodes, edges) {
791
844
  });
792
845
  }
793
846
 
794
- class FlowStatusService {
795
- constructor() {
796
- this.status = signal({ state: 'idle', payload: null });
797
- }
798
- setIdleStatus() {
799
- this.status.set({ state: 'idle', payload: null });
800
- }
801
- setConnectionStartStatus(source, sourceHandle) {
802
- this.status.set({ state: 'connection-start', payload: { source, sourceHandle } });
803
- }
804
- setReconnectionStartStatus(source, sourceHandle, oldEdge) {
805
- this.status.set({ state: 'reconnection-start', payload: { source, sourceHandle, oldEdge } });
806
- }
807
- setConnectionValidationStatus(valid, source, target, sourceHandle, targetHandle) {
808
- this.status.set({ state: 'connection-validation', payload: { source, target, sourceHandle, targetHandle, valid } });
809
- }
810
- setReconnectionValidationStatus(valid, source, target, sourceHandle, targetHandle, oldEdge) {
811
- this.status.set({
812
- state: 'reconnection-validation',
813
- payload: { source, target, sourceHandle, targetHandle, valid, oldEdge },
814
- });
815
- }
816
- setConnectionEndStatus(source, target, sourceHandle, targetHandle) {
817
- this.status.set({ state: 'connection-end', payload: { source, target, sourceHandle, targetHandle } });
847
+ function isCallable(fn) {
848
+ try {
849
+ new Proxy(fn, { apply: () => undefined })();
850
+ return true;
818
851
  }
819
- setReconnectionEndStatus(source, target, sourceHandle, targetHandle, oldEdge) {
820
- this.status.set({ state: 'reconnection-end', payload: { source, target, sourceHandle, targetHandle, oldEdge } });
852
+ catch (err) {
853
+ return false;
821
854
  }
822
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
823
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService }); }
824
855
  }
825
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService, decorators: [{
826
- type: Injectable
827
- }] });
828
856
 
829
857
  class ComponentEventBusService {
830
858
  constructor() {
@@ -906,7 +934,7 @@ function outputRefToObservable(ref) {
906
934
  });
907
935
  }
908
936
 
909
- class CustomNodeComponent extends CustomNodeBaseComponent {
937
+ class CustomDynamicNodeComponent extends CustomNodeBaseComponent {
910
938
  constructor() {
911
939
  super(...arguments);
912
940
  /**
@@ -915,19 +943,20 @@ class CustomNodeComponent extends CustomNodeBaseComponent {
915
943
  this.node = input.required();
916
944
  }
917
945
  ngOnInit() {
918
- if (this.node().data) {
919
- this.data.set(this.node().data);
946
+ const data = this.node().data;
947
+ if (data) {
948
+ this.data = data;
920
949
  }
921
950
  super.ngOnInit();
922
951
  }
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 }); }
952
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomDynamicNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
953
+ 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
954
  }
926
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomNodeComponent, decorators: [{
955
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomDynamicNodeComponent, decorators: [{
927
956
  type: Directive
928
957
  }] });
929
958
 
930
- class CustomDynamicNodeComponent extends CustomNodeBaseComponent {
959
+ class CustomNodeComponent extends CustomNodeBaseComponent {
931
960
  constructor() {
932
961
  super(...arguments);
933
962
  /**
@@ -936,19 +965,25 @@ class CustomDynamicNodeComponent extends CustomNodeBaseComponent {
936
965
  this.node = input.required();
937
966
  }
938
967
  ngOnInit() {
939
- const data = this.node().data;
940
- if (data) {
941
- this.data = data;
968
+ if (this.node().data) {
969
+ this.data.set(this.node().data);
942
970
  }
943
971
  super.ngOnInit();
944
972
  }
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 }); }
973
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
974
+ 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
975
  }
948
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomDynamicNodeComponent, decorators: [{
976
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CustomNodeComponent, decorators: [{
949
977
  type: Directive
950
978
  }] });
951
979
 
980
+ function isCustomNodeComponent(type) {
981
+ return Object.prototype.isPrototypeOf.call(CustomNodeComponent, type);
982
+ }
983
+ function isCustomDynamicNodeComponent(type) {
984
+ return Object.prototype.isPrototypeOf.call(CustomDynamicNodeComponent, type);
985
+ }
986
+
952
987
  function isStaticNode(node) {
953
988
  return typeof node.point !== 'function';
954
989
  }
@@ -956,10 +991,18 @@ function isDynamicNode(node) {
956
991
  return typeof node.point === 'function';
957
992
  }
958
993
  function isComponentStaticNode(node) {
959
- return Object.prototype.isPrototypeOf.call(CustomNodeComponent, node.type);
994
+ if (isCustomNodeComponent(node.type)) {
995
+ return true;
996
+ }
997
+ // Check if the type is a function with dynamic import
998
+ return isCallable(node.type) && !isCallable(node.point);
960
999
  }
961
1000
  function isComponentDynamicNode(node) {
962
- return Object.prototype.isPrototypeOf.call(CustomDynamicNodeComponent, node.type);
1001
+ if (isCustomDynamicNodeComponent(node.type)) {
1002
+ return true;
1003
+ }
1004
+ // Check if the type is a function with dynamic import
1005
+ return isCallable(node.type) && isCallable(node.point);
963
1006
  }
964
1007
  function isTemplateStaticNode(node) {
965
1008
  return node.type === 'html-template';
@@ -1018,6 +1061,138 @@ function toSignalProperties(obj) {
1018
1061
  return newObj;
1019
1062
  }
1020
1063
 
1064
+ function isGroupNode(node) {
1065
+ return node.rawNode.type === 'default-group' || node.rawNode.type === 'template-group';
1066
+ }
1067
+
1068
+ // MIT License
1069
+ // Copyright (c) 2023 Chau Tran
1070
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1071
+ // of this software and associated documentation files (the "Software"), to deal
1072
+ // in the Software without restriction, including without limitation the rights
1073
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1074
+ // copies of the Software, and to permit persons to whom the Software is
1075
+ // furnished to do so, subject to the following conditions:
1076
+ // The above copyright notice and this permission notice shall be included in all
1077
+ // copies or substantial portions of the Software.
1078
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1079
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1080
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1081
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1082
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1083
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1084
+ // SOFTWARE.
1085
+ /* eslint-disable @typescript-eslint/ban-types */
1086
+ function assertInjector(fn, injector, runner) {
1087
+ !injector && assertInInjectionContext(fn);
1088
+ const assertedInjector = injector ?? inject(Injector);
1089
+ if (!runner)
1090
+ return assertedInjector;
1091
+ return runInInjectionContext(assertedInjector, runner);
1092
+ }
1093
+
1094
+ // MIT License
1095
+ // Copyright (c) 2023 Chau Tran
1096
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
1097
+ // of this software and associated documentation files (the "Software"), to deal
1098
+ // in the Software without restriction, including without limitation the rights
1099
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1100
+ // copies of the Software, and to permit persons to whom the Software is
1101
+ // furnished to do so, subject to the following conditions:
1102
+ // The above copyright notice and this permission notice shall be included in all
1103
+ // copies or substantial portions of the Software.
1104
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1105
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1106
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1107
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1108
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1109
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1110
+ // SOFTWARE.
1111
+ /**
1112
+ * Function `toLazySignal()` is a proxy function that will call the original
1113
+ * `toSignal()` function when the returned signal is read for the first time.
1114
+ */
1115
+ function toLazySignal(source, options) {
1116
+ const injector = assertInjector(toLazySignal, options?.injector);
1117
+ let s;
1118
+ return computed(() => {
1119
+ if (!s) {
1120
+ s = untracked(() => toSignal(source, { ...options, injector }));
1121
+ }
1122
+ return s();
1123
+ });
1124
+ }
1125
+
1126
+ class NodeRenderingService {
1127
+ constructor() {
1128
+ this.flowEntitiesService = inject(FlowEntitiesService);
1129
+ this.flowSettingsService = inject(FlowSettingsService);
1130
+ this.viewportService = inject(ViewportService);
1131
+ this.nodes = computed(() => {
1132
+ if (!this.flowSettingsService.optimization().virtualization) {
1133
+ return [...this.flowEntitiesService.nodes()].sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1134
+ }
1135
+ return this.viewportNodesAfterInteraction().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1136
+ });
1137
+ this.groups = computed(() => {
1138
+ return this.nodes().filter((n) => isGroupNode(n));
1139
+ });
1140
+ this.nonGroups = computed(() => {
1141
+ return this.nodes().filter((n) => !isGroupNode(n));
1142
+ });
1143
+ this.viewportNodes = computed(() => {
1144
+ const nodes = this.flowEntitiesService.nodes();
1145
+ const viewport = this.viewportService.readableViewport();
1146
+ const flowWidth = this.flowSettingsService.computedFlowWidth();
1147
+ const flowHeight = this.flowSettingsService.computedFlowHeight();
1148
+ return nodes.filter((n) => {
1149
+ const { x, y } = n.globalPoint();
1150
+ const width = n.width();
1151
+ const height = n.height();
1152
+ return isRectInViewport({ x, y, width, height }, viewport, flowWidth, flowHeight);
1153
+ });
1154
+ });
1155
+ this.viewportNodesAfterInteraction = toLazySignal(merge(
1156
+ // TODO: maybe there is a better way wait when viewport is ready?
1157
+ // (to correctly calculate viewport nodes on first render)
1158
+ toObservable(this.flowEntitiesService.nodes).pipe(observeOn(asyncScheduler), filter((nodes) => !!nodes.length)), this.viewportService.viewportChangeEnd$.pipe(debounceTime(300))).pipe(map(() => {
1159
+ const viewport = this.viewportService.readableViewport();
1160
+ const zoomThreshold = this.flowSettingsService.optimization().virtualizationZoomThreshold;
1161
+ return viewport.zoom < zoomThreshold ? [] : this.viewportNodes();
1162
+ })), {
1163
+ initialValue: [],
1164
+ });
1165
+ this.maxOrder = computed(() => {
1166
+ return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
1167
+ });
1168
+ }
1169
+ pullNode(node) {
1170
+ // pull node
1171
+ node.renderOrder.set(this.maxOrder() + 1);
1172
+ // pull children
1173
+ node.children().forEach((n) => this.pullNode(n));
1174
+ }
1175
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1176
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService }); }
1177
+ }
1178
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService, decorators: [{
1179
+ type: Injectable
1180
+ }] });
1181
+
1182
+ // MIT License
1183
+ /**
1184
+ * @todo Use `linkedSignal` after Angular update
1185
+ */
1186
+ function extendedComputed(computedCallback, options) {
1187
+ if (!options) {
1188
+ options = { equal: Object.is };
1189
+ }
1190
+ let currentValue = undefined;
1191
+ return computed(() => {
1192
+ return (currentValue = computedCallback(currentValue));
1193
+ }, options);
1194
+ }
1195
+
1021
1196
  class NodeModel {
1022
1197
  static { this.defaultWidth = 100; }
1023
1198
  static { this.defaultHeight = 50; }
@@ -1025,6 +1200,8 @@ class NodeModel {
1025
1200
  constructor(rawNode) {
1026
1201
  this.rawNode = rawNode;
1027
1202
  this.entitiesService = inject(FlowEntitiesService);
1203
+ this.settingsService = inject(FlowSettingsService);
1204
+ this.nodeRenderingService = inject(NodeRenderingService);
1028
1205
  this.isVisible = signal(false);
1029
1206
  this.point = signal({ x: 0, y: 0 });
1030
1207
  this.width = signal(NodeModel.defaultWidth);
@@ -1063,6 +1240,39 @@ class NodeModel {
1063
1240
  this.magnetRadius = 20;
1064
1241
  // TODO: not sure if we need to statically store it
1065
1242
  this.isComponentType = isComponentStaticNode(this.rawNode) || isComponentDynamicNode(this.rawNode);
1243
+ this.shouldLoad = extendedComputed((previousShouldLoad) => {
1244
+ if (previousShouldLoad) {
1245
+ return true;
1246
+ }
1247
+ if (this.settingsService.optimization().lazyLoadTrigger === 'immediate') {
1248
+ return true;
1249
+ }
1250
+ else if (this.settingsService.optimization().lazyLoadTrigger === 'viewport') {
1251
+ // Immediately load component if it's a plain class
1252
+ if (isCustomNodeComponent(this.rawNode.type)) {
1253
+ return true;
1254
+ }
1255
+ // Immediately load component if it's a plain class
1256
+ if (isCustomDynamicNodeComponent(this.rawNode.type)) {
1257
+ return true;
1258
+ }
1259
+ // For cases
1260
+ // - if it's a factory with dynamic import
1261
+ // - if it's a template (html, svg, group)
1262
+ // check if it's in the viewport
1263
+ if (isCallable(this.rawNode.type) ||
1264
+ this.rawNode.type === 'html-template' ||
1265
+ this.rawNode.type === 'svg-template' ||
1266
+ this.rawNode.type === 'template-group') {
1267
+ return this.nodeRenderingService.viewportNodes().includes(this);
1268
+ }
1269
+ }
1270
+ // For each other case, we want to load the component immediately
1271
+ return true;
1272
+ });
1273
+ this.componentInstance$ = toObservable(this.shouldLoad).pipe(filter(Boolean),
1274
+ // @ts-expect-error we assume it's a function with dynamic import
1275
+ switchMap(() => this.rawNode.type()), catchError(() => of(this.rawNode.type)), shareReplay(1));
1066
1276
  // Default node specific thing
1067
1277
  this.text = signal('');
1068
1278
  // Component node specific thing
@@ -1112,7 +1322,8 @@ class NodeModel {
1112
1322
  this.context = {
1113
1323
  $implicit: {
1114
1324
  node: rawNode,
1115
- selected: this.selected,
1325
+ selected: this.selected.asReadonly(),
1326
+ shouldLoad: this.shouldLoad,
1116
1327
  },
1117
1328
  };
1118
1329
  }
@@ -1120,9 +1331,10 @@ class NodeModel {
1120
1331
  this.context = {
1121
1332
  $implicit: {
1122
1333
  node: rawNode,
1123
- selected: this.selected,
1124
- width: this.width,
1125
- height: this.height,
1334
+ selected: this.selected.asReadonly(),
1335
+ width: this.width.asReadonly(),
1336
+ height: this.height.asReadonly(),
1337
+ shouldLoad: this.shouldLoad,
1126
1338
  },
1127
1339
  };
1128
1340
  }
@@ -1131,8 +1343,9 @@ class NodeModel {
1131
1343
  $implicit: {
1132
1344
  node: rawNode,
1133
1345
  selected: this.selected.asReadonly(),
1134
- width: this.width,
1135
- height: this.height,
1346
+ width: this.width.asReadonly(),
1347
+ height: this.height.asReadonly(),
1348
+ shouldLoad: this.shouldLoad,
1136
1349
  },
1137
1350
  };
1138
1351
  }
@@ -1470,20 +1683,6 @@ function smoothStepPath({ sourcePoint, targetPoint, sourcePosition, targetPositi
1470
1683
  };
1471
1684
  }
1472
1685
 
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
1686
  class EdgeModel {
1488
1687
  constructor(edge) {
1489
1688
  this.edge = edge;
@@ -1492,6 +1691,7 @@ class EdgeModel {
1492
1691
  this.target = signal(undefined);
1493
1692
  this.selected = signal(false);
1494
1693
  this.selected$ = toObservable(this.selected);
1694
+ this.shouldLoad = computed(() => (this.source()?.shouldLoad() ?? false) && (this.target()?.shouldLoad() ?? false));
1495
1695
  this.renderOrder = signal(0);
1496
1696
  this.detached = computed(() => {
1497
1697
  const source = this.source();
@@ -1521,14 +1721,7 @@ class EdgeModel {
1521
1721
  const target = this.targetHandle();
1522
1722
  // TODO: don't like this
1523
1723
  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
- };
1724
+ return { path: '' };
1532
1725
  }
1533
1726
  const params = this.getPathFactoryParams(source, target);
1534
1727
  switch (this.curve) {
@@ -1660,6 +1853,7 @@ class EdgeModel {
1660
1853
  markerStart: this.markerStartUrl,
1661
1854
  markerEnd: this.markerEndUrl,
1662
1855
  selected: this.selected.asReadonly(),
1856
+ shouldLoad: this.shouldLoad,
1663
1857
  },
1664
1858
  };
1665
1859
  this.edgeLabels = {};
@@ -1947,124 +2141,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1947
2141
  }]
1948
2142
  }] });
1949
2143
 
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
2144
  class RootPointerDirective {
2069
2145
  constructor() {
2070
2146
  this.host = inject(ElementRef).nativeElement;
@@ -3206,7 +3282,7 @@ class NodeComponent {
3206
3282
  }
3207
3283
  }
3208
3284
  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 }); }
3285
+ 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
3286
  }
3211
3287
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeComponent, decorators: [{
3212
3288
  type: Component,
@@ -3222,7 +3298,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3222
3298
  HandleSizeControllerDirective,
3223
3299
  NodeHandlesControllerDirective,
3224
3300
  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"] }]
3301
+ AsyncPipe,
3302
+ ], 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
3303
  }] });
3227
3304
 
3228
3305
  class ConnectionComponent {
@@ -3770,6 +3847,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3770
3847
 
3771
3848
  class FlowRenderingService {
3772
3849
  constructor() {
3850
+ this.nodeRenderingService = inject(NodeRenderingService);
3851
+ this.edgeRenderingService = inject(EdgeRenderingService);
3852
+ this.flowEntitiesService = inject(FlowEntitiesService);
3853
+ this.settingsService = inject(FlowSettingsService);
3773
3854
  this.flowInitialized = signal(false);
3774
3855
  inject(NgZone).runOutsideAngular(async () => {
3775
3856
  await skipFrames(2);
@@ -3799,6 +3880,109 @@ function skipFrames(count) {
3799
3880
  });
3800
3881
  }
3801
3882
 
3883
+ function rectToRectWithSides(rect) {
3884
+ return {
3885
+ ...rect,
3886
+ left: rect.x,
3887
+ right: rect.x + rect.width,
3888
+ top: rect.y,
3889
+ bottom: rect.y + rect.height,
3890
+ };
3891
+ }
3892
+
3893
+ class AlignmentHelperComponent {
3894
+ constructor() {
3895
+ this.nodeRenderingService = inject(NodeRenderingService);
3896
+ this.flowStatus = inject(FlowStatusService);
3897
+ this.tolerance = input(10);
3898
+ this.lineColor = input('#1b262c');
3899
+ this.isNodeDragging = computed(() => isNodeDragStartStatus(this.flowStatus.status()));
3900
+ this.intersections = extendedComputed((lastValue) => {
3901
+ const status = this.flowStatus.status();
3902
+ if (isNodeDragStartStatus(status)) {
3903
+ const node = status.payload.node;
3904
+ const d = rectToRectWithSides(nodeToRect(node));
3905
+ const otherRects = this.nodeRenderingService
3906
+ .viewportNodes()
3907
+ .filter((n) => n !== node)
3908
+ // do not check children of the dragged node
3909
+ .filter((n) => !node.children().includes(n))
3910
+ .map((n) => rectToRectWithSides(nodeToRect(n)));
3911
+ const lines = [];
3912
+ let snappedX = d.x;
3913
+ let snappedY = d.y;
3914
+ let closestXDiff = Infinity;
3915
+ let closestYDiff = Infinity;
3916
+ otherRects.forEach((o) => {
3917
+ const dCenterX = d.left + d.width / 2;
3918
+ const oCenterX = o.left + o.width / 2;
3919
+ for (const [dX, oX, snapX, isCenter] of [
3920
+ // center check
3921
+ [dCenterX, oCenterX, oCenterX - d.width / 2, true],
3922
+ [d.left, o.left, o.left, false],
3923
+ [d.left, o.right, o.right, false],
3924
+ [d.right, o.left, o.left - d.width, false],
3925
+ [d.right, o.right, o.right - d.width, false],
3926
+ ]) {
3927
+ const diff = Math.abs(dX - oX);
3928
+ if (diff <= this.tolerance()) {
3929
+ const y = Math.min(d.top, o.top);
3930
+ const y2 = Math.max(d.bottom, o.bottom);
3931
+ lines.push({ x: oX, y, x2: oX, y2, isCenter });
3932
+ if (diff < closestXDiff) {
3933
+ closestXDiff = diff;
3934
+ snappedX = snapX;
3935
+ }
3936
+ if (isCenter)
3937
+ break;
3938
+ }
3939
+ }
3940
+ const dCenterY = d.top + d.height / 2;
3941
+ const oCenterY = o.top + o.height / 2;
3942
+ for (const [dY, oY, snapY, isCenter] of [
3943
+ // center check
3944
+ [dCenterY, oCenterY, oCenterY - d.height / 2, true],
3945
+ [d.top, o.top, o.top, false],
3946
+ [d.top, o.bottom, o.bottom, false],
3947
+ [d.bottom, o.top, o.top - d.height, false],
3948
+ [d.bottom, o.bottom, o.bottom - d.height, false],
3949
+ ]) {
3950
+ const diff = Math.abs(dY - oY);
3951
+ if (diff <= this.tolerance()) {
3952
+ const x = Math.min(d.left, o.left);
3953
+ const x2 = Math.max(d.right, o.right);
3954
+ lines.push({ x, y: oY, x2, y2: oY, isCenter });
3955
+ if (diff < closestYDiff) {
3956
+ closestYDiff = diff;
3957
+ snappedY = snapY;
3958
+ }
3959
+ if (isCenter)
3960
+ break;
3961
+ }
3962
+ }
3963
+ });
3964
+ return { lines, snappedX, snappedY };
3965
+ }
3966
+ return lastValue;
3967
+ });
3968
+ toObservable(this.flowStatus.status)
3969
+ .pipe(filter(isNodeDragEndStatus), map((status) => status.payload.node), map((node) => [node, this.intersections()]), tap(([node, intersections]) => {
3970
+ if (intersections) {
3971
+ const snapped = { x: intersections.snappedX, y: intersections.snappedY };
3972
+ const parentIfExists = node.parent() ? [node.parent()] : [];
3973
+ node.setPoint(getSpacePoints(snapped, parentIfExists)[0]);
3974
+ }
3975
+ }), takeUntilDestroyed())
3976
+ .subscribe();
3977
+ }
3978
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AlignmentHelperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3979
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: AlignmentHelperComponent, isStandalone: true, selector: "g[alignmentHelper]", inputs: { tolerance: { classPropertyName: "tolerance", publicName: "tolerance", isSignal: true, isRequired: false, transformFunction: null }, lineColor: { classPropertyName: "lineColor", publicName: "lineColor", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isNodeDragging()) {\n @if (intersections(); as intersections) {\n @for (intersection of intersections.lines; track $index) {\n <svg:line\n [attr.stroke]=\"lineColor()\"\n [attr.stroke-dasharray]=\"intersection.isCenter ? 4 : null\"\n [attr.x1]=\"intersection.x\"\n [attr.y1]=\"intersection.y\"\n [attr.x2]=\"intersection.x2\"\n [attr.y2]=\"intersection.y2\" />\n }\n }\n}\n", changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3980
+ }
3981
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AlignmentHelperComponent, decorators: [{
3982
+ type: Component,
3983
+ args: [{ selector: 'g[alignmentHelper]', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "@if (isNodeDragging()) {\n @if (intersections(); as intersections) {\n @for (intersection of intersections.lines; track $index) {\n <svg:line\n [attr.stroke]=\"lineColor()\"\n [attr.stroke-dasharray]=\"intersection.isCenter ? 4 : null\"\n [attr.x1]=\"intersection.x\"\n [attr.y1]=\"intersection.y\"\n [attr.x2]=\"intersection.x2\"\n [attr.y2]=\"intersection.y2\" />\n }\n }\n}\n" }]
3984
+ }], ctorParameters: () => [] });
3985
+
3802
3986
  const changesControllerHostDirective = {
3803
3987
  directive: ChangesControllerDirective,
3804
3988
  outputs: [
@@ -3847,6 +4031,7 @@ class VflowComponent {
3847
4031
  this.keyboardService = inject(KeyboardService);
3848
4032
  this.injector = inject(Injector);
3849
4033
  this.flowRenderingService = inject(FlowRenderingService);
4034
+ this.alignmentHelper = input(false);
3850
4035
  this.nodeModels = this.nodeRenderingService.nodes;
3851
4036
  this.groups = this.nodeRenderingService.groups;
3852
4037
  this.nonGroups = this.nodeRenderingService.nonGroups;
@@ -4108,7 +4293,7 @@ class VflowComponent {
4108
4293
  return edge;
4109
4294
  }
4110
4295
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4111
- 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: [
4296
+ 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: false, 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 }, alignmentHelper: { classPropertyName: "alignmentHelper", publicName: "alignmentHelper", isSignal: true, isRequired: false, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
4112
4297
  DraggableService,
4113
4298
  ViewportService,
4114
4299
  FlowStatusService,
@@ -4124,7 +4309,7 @@ class VflowComponent {
4124
4309
  OverlaysService,
4125
4310
  { provide: PreviewFlowRenderStrategyService, useClass: ViewportPreviewFlowRenderStrategyService },
4126
4311
  FlowRenderingService,
4127
- ], 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 }); }
4312
+ ], 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 @if (alignmentHelper(); as alignmentHelper) {\n @if (alignmentHelper === true) {\n <svg:g alignmentHelper />\n } @else {\n <svg:g alignmentHelper [tolerance]=\"alignmentHelper.tolerance\" [lineColor]=\"alignmentHelper.lineColor\" />\n }\n }\n\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"] }, { kind: "component", type: AlignmentHelperComponent, selector: "g[alignmentHelper]", inputs: ["tolerance", "lineColor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4128
4313
  }
4129
4314
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, decorators: [{
4130
4315
  type: Component,
@@ -4158,7 +4343,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4158
4343
  EdgeComponent,
4159
4344
  NgTemplateOutlet,
4160
4345
  PreviewFlowComponent,
4161
- ], 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"] }]
4346
+ AlignmentHelperComponent,
4347
+ ], 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 @if (alignmentHelper(); as alignmentHelper) {\n @if (alignmentHelper === true) {\n <svg:g alignmentHelper />\n } @else {\n <svg:g alignmentHelper [tolerance]=\"alignmentHelper.tolerance\" [lineColor]=\"alignmentHelper.lineColor\" />\n }\n }\n\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"] }]
4162
4348
  }], propDecorators: { view: [{
4163
4349
  type: Input
4164
4350
  }], minZoom: [{
@@ -4509,5 +4695,5 @@ const Vflow = [
4509
4695
  * Generated bundle index. Do not edit.
4510
4696
  */
4511
4697
 
4512
- 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 };
4698
+ 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 };
4513
4699
  //# sourceMappingURL=ngx-vflow.mjs.map