ngx-vflow 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/esm2022/lib/vflow/components/connection/connection.component.mjs +3 -3
  2. package/esm2022/lib/vflow/components/edge/edge.component.mjs +30 -5
  3. package/esm2022/lib/vflow/components/node/node.component.mjs +4 -2
  4. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +15 -3
  5. package/esm2022/lib/vflow/directives/connection-controller.directive.mjs +55 -37
  6. package/esm2022/lib/vflow/directives/root-svg-context.directive.mjs +2 -2
  7. package/esm2022/lib/vflow/directives/selectable.directive.mjs +19 -11
  8. package/esm2022/lib/vflow/interfaces/connection.interface.mjs +1 -1
  9. package/esm2022/lib/vflow/interfaces/edge.interface.mjs +1 -1
  10. package/esm2022/lib/vflow/models/edge.model.mjs +25 -23
  11. package/esm2022/lib/vflow/services/edge-rendering.service.mjs +28 -0
  12. package/esm2022/lib/vflow/services/flow-settings.service.mjs +2 -1
  13. package/esm2022/lib/vflow/services/flow-status.service.mjs +13 -1
  14. package/esm2022/lib/vflow/testing-utils/component-mocks/vflow-mock.component.mjs +4 -2
  15. package/esm2022/lib/vflow/testing-utils/directive-mocks/connection-controller-mock.directive.mjs +6 -2
  16. package/fesm2022/ngx-vflow.mjs +294 -190
  17. package/fesm2022/ngx-vflow.mjs.map +1 -1
  18. package/lib/vflow/components/edge/edge.component.d.ts +10 -2
  19. package/lib/vflow/components/vflow/vflow.component.d.ts +6 -1
  20. package/lib/vflow/directives/connection-controller.directive.d.ts +5 -2
  21. package/lib/vflow/directives/selectable.directive.d.ts +4 -1
  22. package/lib/vflow/interfaces/connection.interface.d.ts +5 -0
  23. package/lib/vflow/interfaces/edge.interface.d.ts +1 -0
  24. package/lib/vflow/models/edge.model.d.ts +4 -0
  25. package/lib/vflow/services/edge-rendering.service.d.ts +10 -0
  26. package/lib/vflow/services/flow-settings.service.d.ts +1 -0
  27. package/lib/vflow/services/flow-status.service.d.ts +24 -1
  28. package/lib/vflow/testing-utils/component-mocks/vflow-mock.component.d.ts +2 -1
  29. package/lib/vflow/testing-utils/directive-mocks/connection-controller-mock.directive.d.ts +4 -2
  30. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, DestroyRef, EventEmitter, OutputEmitterRef, input, NgZone, viewChild, Component, ChangeDetectionStrategy, Injector, output, HostListener, runInInjectionContext, contentChild, Input, forwardRef } from '@angular/core';
2
+ import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, DestroyRef, EventEmitter, OutputEmitterRef, input, NgZone, viewChild, Component, ChangeDetectionStrategy, output, HostListener, Injector, runInInjectionContext, contentChild, Input, forwardRef } from '@angular/core';
3
3
  import { select } from 'd3-selection';
4
4
  import { zoomIdentity, zoom } from 'd3-zoom';
5
5
  import { switchMap, merge, fromEvent, tap, Subject, Observable, skip, map, pairwise, filter, distinctUntilChanged, observeOn, asyncScheduler, zip, animationFrameScheduler, share, startWith, of } from 'rxjs';
@@ -151,6 +151,7 @@ class FlowSettingsService {
151
151
  constructor() {
152
152
  this.entitiesSelectable = signal(true);
153
153
  this.elevateNodesOnSelect = signal(true);
154
+ this.elevateEdgesOnSelect = signal(true);
154
155
  /**
155
156
  * @see {VflowComponent.view}
156
157
  */
@@ -677,12 +678,24 @@ class FlowStatusService {
677
678
  setConnectionStartStatus(source, sourceHandle) {
678
679
  this.status.set({ state: 'connection-start', payload: { source, sourceHandle } });
679
680
  }
681
+ setReconnectionStartStatus(source, sourceHandle, oldEdge) {
682
+ this.status.set({ state: 'reconnection-start', payload: { source, sourceHandle, oldEdge } });
683
+ }
680
684
  setConnectionValidationStatus(valid, source, target, sourceHandle, targetHandle) {
681
685
  this.status.set({ state: 'connection-validation', payload: { source, target, sourceHandle, targetHandle, valid } });
682
686
  }
687
+ setReconnectionValidationStatus(valid, source, target, sourceHandle, targetHandle, oldEdge) {
688
+ this.status.set({
689
+ state: 'reconnection-validation',
690
+ payload: { source, target, sourceHandle, targetHandle, valid, oldEdge },
691
+ });
692
+ }
683
693
  setConnectionEndStatus(source, target, sourceHandle, targetHandle) {
684
694
  this.status.set({ state: 'connection-end', payload: { source, target, sourceHandle, targetHandle } });
685
695
  }
696
+ setReconnectionEndStatus(source, target, sourceHandle, targetHandle, oldEdge) {
697
+ this.status.set({ state: 'reconnection-end', payload: { source, target, sourceHandle, targetHandle, oldEdge } });
698
+ }
686
699
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
687
700
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService }); }
688
701
  }
@@ -1254,6 +1267,7 @@ class EdgeModel {
1254
1267
  this.target = signal(undefined);
1255
1268
  this.selected = signal(false);
1256
1269
  this.selected$ = toObservable(this.selected);
1270
+ this.renderOrder = signal(0);
1257
1271
  this.detached = computed(() => {
1258
1272
  const source = this.source();
1259
1273
  const target = this.target();
@@ -1278,28 +1292,8 @@ class EdgeModel {
1278
1292
  });
1279
1293
  this.detached$ = toObservable(this.detached);
1280
1294
  this.path = computed(() => {
1281
- let source;
1282
- if (this.edge.sourceHandle) {
1283
- source = this.source()
1284
- ?.handles()
1285
- .find((handle) => handle.rawHandle.id === this.edge.sourceHandle);
1286
- }
1287
- else {
1288
- source = this.source()
1289
- ?.handles()
1290
- .find((handle) => handle.rawHandle.type === 'source');
1291
- }
1292
- let target;
1293
- if (this.edge.targetHandle) {
1294
- target = this.target()
1295
- ?.handles()
1296
- .find((handle) => handle.rawHandle.id === this.edge.targetHandle);
1297
- }
1298
- else {
1299
- target = this.target()
1300
- ?.handles()
1301
- .find((handle) => handle.rawHandle.type === 'target');
1302
- }
1295
+ const source = this.sourceHandle();
1296
+ const target = this.targetHandle();
1303
1297
  // TODO: don't like this
1304
1298
  if (!source || !target) {
1305
1299
  return {
@@ -1322,9 +1316,30 @@ class EdgeModel {
1322
1316
  return smoothStepPath(source.pointAbsolute(), target.pointAbsolute(), source.rawHandle.position, target.rawHandle.position, 0);
1323
1317
  }
1324
1318
  });
1319
+ this.sourceHandle = computed(() => {
1320
+ if (this.edge.sourceHandle) {
1321
+ return (this.source()
1322
+ ?.handles()
1323
+ .find((handle) => handle.rawHandle.id === this.edge.sourceHandle) ?? null);
1324
+ }
1325
+ return (this.source()
1326
+ ?.handles()
1327
+ .find((handle) => handle.rawHandle.type === 'source') ?? null);
1328
+ });
1329
+ this.targetHandle = computed(() => {
1330
+ if (this.edge.targetHandle) {
1331
+ return (this.target()
1332
+ ?.handles()
1333
+ .find((handle) => handle.rawHandle.id === this.edge.targetHandle) ?? null);
1334
+ }
1335
+ return (this.target()
1336
+ ?.handles()
1337
+ .find((handle) => handle.rawHandle.type === 'target') ?? null);
1338
+ });
1325
1339
  this.edgeLabels = {};
1326
1340
  this.type = edge.type ?? 'default';
1327
1341
  this.curve = edge.curve ?? 'bezier';
1342
+ this.reconnectable = edge.reconnectable ?? false;
1328
1343
  if (edge.edgeLabels?.start)
1329
1344
  this.edgeLabels.start = new EdgeLabelModel(edge.edgeLabels.start);
1330
1345
  if (edge.edgeLabels?.center)
@@ -1845,76 +1860,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1845
1860
  args: [{ standalone: true, selector: 'g[edgeLabel]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], template: "@if (model(); as model) {\n @if (model.edgeLabel.type === 'html-template' && htmlTemplate()) {\n @if (htmlTemplate(); as htmlTemplate) {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\">\n <ng-container *ngTemplateOutlet=\"htmlTemplate; context: getLabelContext()\" />\n </div>\n </svg:foreignObject>\n }\n }\n\n @if (model.edgeLabel.type === 'default') {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\" [style]=\"edgeLabelStyle()\">\n {{ model.edgeLabel.text }}\n </div>\n </svg:foreignObject>\n }\n}\n", styles: [".edge-label-wrapper{width:max-content;margin-top:1px;margin-left:1px}\n"] }]
1846
1861
  }] });
1847
1862
 
1848
- class EdgeComponent {
1849
- constructor() {
1850
- this.injector = inject(Injector);
1851
- this.selectionService = inject(SelectionService);
1852
- this.flowSettingsService = inject(FlowSettingsService);
1853
- this.model = input.required();
1854
- this.edgeTemplate = input();
1855
- this.edgeLabelHtmlTemplate = input();
1856
- this.markerStartUrl = computed(() => {
1857
- const marker = this.model().edge.markers?.start;
1858
- return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
1859
- });
1860
- this.markerEndUrl = computed(() => {
1861
- const marker = this.model().edge.markers?.end;
1862
- return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
1863
- });
1864
- }
1865
- ngOnInit() {
1866
- this.edgeContext = {
1867
- $implicit: {
1868
- // TODO: check if edge could change
1869
- edge: this.model().edge,
1870
- path: computed(() => this.model().path().path),
1871
- markerStart: this.markerStartUrl,
1872
- markerEnd: this.markerEndUrl,
1873
- selected: this.model().selected.asReadonly(),
1874
- },
1875
- };
1876
- }
1877
- onEdgeMouseDown() {
1878
- if (this.flowSettingsService.entitiesSelectable()) {
1879
- this.selectionService.select(this.model());
1880
- }
1881
- }
1882
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1883
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EdgeComponent, isStandalone: true, selector: "g[edge]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, edgeTemplate: { classPropertyName: "edgeTemplate", publicName: "edgeTemplate", isSignal: true, isRequired: false, transformFunction: null }, edgeLabelHtmlTemplate: { classPropertyName: "edgeLabelHtmlTemplate", publicName: "edgeLabelHtmlTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "selectable" }, ngImport: i0, template: "@if (model().type === 'default') {\n <svg:path\n class=\"edge\"\n [attr.d]=\"model().path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n [class.edge_selected]=\"model().selected()\"\n (mousedown)=\"onEdgeMouseDown()\" />\n}\n\n@if (model().type === 'template' && edgeTemplate()) {\n @if (edgeTemplate(); as edgeTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"edgeTemplate\"\n [ngTemplateOutletContext]=\"edgeContext\"\n [ngTemplateOutletInjector]=\"injector\" />\n }\n}\n\n@if (model().edgeLabels.start; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.start\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.center; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.center\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.end; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.end\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n", styles: [".edge{fill:none;stroke-width:2;stroke:#b1b1b7}.edge_selected{stroke-width:2.5;stroke:#0f4c75}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: EdgeLabelComponent, selector: "g[edgeLabel]", inputs: ["model", "edgeModel", "point", "htmlTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1884
- }
1885
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeComponent, decorators: [{
1886
- type: Component,
1887
- args: [{ standalone: true, selector: 'g[edge]', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1888
- class: 'selectable',
1889
- }, imports: [NgTemplateOutlet, EdgeLabelComponent], template: "@if (model().type === 'default') {\n <svg:path\n class=\"edge\"\n [attr.d]=\"model().path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n [class.edge_selected]=\"model().selected()\"\n (mousedown)=\"onEdgeMouseDown()\" />\n}\n\n@if (model().type === 'template' && edgeTemplate()) {\n @if (edgeTemplate(); as edgeTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"edgeTemplate\"\n [ngTemplateOutletContext]=\"edgeContext\"\n [ngTemplateOutletInjector]=\"injector\" />\n }\n}\n\n@if (model().edgeLabels.start; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.start\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.center; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.center\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.end; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.end\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n", styles: [".edge{fill:none;stroke-width:2;stroke:#b1b1b7}.edge_selected{stroke-width:2.5;stroke:#0f4c75}\n"] }]
1890
- }] });
1891
-
1892
- class HandleService {
1893
- constructor() {
1894
- this.node = signal(null);
1895
- }
1896
- createHandle(newHandle) {
1897
- const node = this.node();
1898
- if (node) {
1899
- node.handles.update((handles) => [...handles, newHandle]);
1900
- }
1901
- }
1902
- destroyHandle(handleToDestoy) {
1903
- const node = this.node();
1904
- if (node) {
1905
- node.handles.update((handles) => handles.filter((handle) => handle !== handleToDestoy));
1906
- }
1907
- }
1908
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1909
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleService }); }
1910
- }
1911
- __decorate([
1912
- Microtask // TODO fixes rendering of handle for group node
1913
- ], HandleService.prototype, "createHandle", null);
1914
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleService, decorators: [{
1915
- type: Injectable
1916
- }], propDecorators: { createHandle: [] } });
1917
-
1918
1863
  /**
1919
1864
  * This function contains a hack-y behavior.
1920
1865
  * If the handles are of the same type (source-source or target-target),
@@ -1960,42 +1905,24 @@ class ConnectionControllerDirective {
1960
1905
  * @todo add connect event and deprecate onConnect
1961
1906
  */
1962
1907
  // eslint-disable-next-line @angular-eslint/no-output-on-prefix
1963
- this.onConnect = outputFromObservable(toObservable(this.statusService.status).pipe(filter((status) => status.state === 'connection-end'), map((status) => {
1964
- let source = status.payload.source;
1965
- let target = status.payload.target;
1966
- let sourceHandle = status.payload.sourceHandle;
1967
- let targetHandle = status.payload.targetHandle;
1968
- if (this.isStrictMode()) {
1969
- const adjusted = adjustDirection({
1970
- source: status.payload.source,
1971
- sourceHandle: status.payload.sourceHandle,
1972
- target: status.payload.target,
1973
- targetHandle: status.payload.targetHandle,
1974
- });
1975
- source = adjusted.source;
1976
- target = adjusted.target;
1977
- sourceHandle = adjusted.sourceHandle;
1978
- targetHandle = adjusted.targetHandle;
1979
- }
1980
- const sourceId = source.node.id;
1981
- const targetId = target.node.id;
1982
- const sourceHandleId = sourceHandle.rawHandle.id;
1983
- const targetHandleId = targetHandle.rawHandle.id;
1984
- return {
1985
- source: sourceId,
1986
- target: targetId,
1987
- sourceHandle: sourceHandleId,
1988
- targetHandle: targetHandleId,
1989
- };
1990
- }), tap(() => this.statusService.setIdleStatus()), filter((connection) => this.flowEntitiesService.connection().validator(connection))));
1908
+ this.onConnect = outputFromObservable(toObservable(this.statusService.status).pipe(filter((status) => status.state === 'connection-end'), map((status) => statusToConnection(status, this.isStrictMode())), tap(() => this.statusService.setIdleStatus()), filter((connection) => this.flowEntitiesService.connection().validator(connection))));
1909
+ this.onReconnect = outputFromObservable(toObservable(this.statusService.status).pipe(filter((status) => status.state === 'reconnection-end'), map((status) => {
1910
+ const connection = statusToConnection(status, this.isStrictMode());
1911
+ const oldEdge = status.payload.oldEdge.edge;
1912
+ return { connection, oldEdge };
1913
+ }), tap(() => this.statusService.setIdleStatus()), filter(({ connection }) => this.flowEntitiesService.connection().validator(connection))));
1991
1914
  this.isStrictMode = computed(() => this.flowEntitiesService.connection().mode === 'strict');
1992
1915
  }
1993
1916
  startConnection(handle) {
1994
1917
  this.statusService.setConnectionStartStatus(handle.parentNode, handle);
1995
1918
  }
1919
+ startReconnection(handle, oldEdge) {
1920
+ this.statusService.setReconnectionStartStatus(handle.parentNode, handle, oldEdge);
1921
+ }
1996
1922
  validateConnection(handle) {
1997
1923
  const status = this.statusService.status();
1998
- if (status.state === 'connection-start') {
1924
+ if (status.state === 'connection-start' || status.state === 'reconnection-start') {
1925
+ const isReconnection = status.state === 'reconnection-start';
1999
1926
  let source = status.payload.source;
2000
1927
  let target = handle.parentNode;
2001
1928
  let sourceHandle = status.payload.sourceHandle;
@@ -2024,89 +1951,97 @@ class ConnectionControllerDirective {
2024
1951
  handle.state.set(valid ? 'valid' : 'invalid');
2025
1952
  // status is about how we draw connection, so we don't need
2026
1953
  // swapped diretion here
2027
- this.statusService.setConnectionValidationStatus(valid, status.payload.source, handle.parentNode, status.payload.sourceHandle, handle);
1954
+ isReconnection
1955
+ ? this.statusService.setReconnectionValidationStatus(valid, status.payload.source, handle.parentNode, status.payload.sourceHandle, handle, status.payload.oldEdge)
1956
+ : this.statusService.setConnectionValidationStatus(valid, status.payload.source, handle.parentNode, status.payload.sourceHandle, handle);
2028
1957
  }
2029
1958
  }
2030
1959
  resetValidateConnection(targetHandle) {
2031
1960
  targetHandle.state.set('idle');
2032
1961
  // drop back to start status
2033
1962
  const status = this.statusService.status();
2034
- if (status.state === 'connection-validation') {
2035
- this.statusService.setConnectionStartStatus(status.payload.source, status.payload.sourceHandle);
1963
+ if (status.state === 'connection-validation' || status.state === 'reconnection-validation') {
1964
+ const isReconnection = status.state === 'reconnection-validation';
1965
+ isReconnection
1966
+ ? this.statusService.setReconnectionStartStatus(status.payload.source, status.payload.sourceHandle, status.payload.oldEdge)
1967
+ : this.statusService.setConnectionStartStatus(status.payload.source, status.payload.sourceHandle);
2036
1968
  }
2037
1969
  }
2038
1970
  endConnection() {
2039
1971
  const status = this.statusService.status();
2040
- if (status.state === 'connection-validation') {
1972
+ if (status.state === 'connection-validation' || status.state === 'reconnection-validation') {
1973
+ const isReconnection = status.state === 'reconnection-validation';
2041
1974
  const source = status.payload.source;
2042
1975
  const sourceHandle = status.payload.sourceHandle;
2043
1976
  const target = status.payload.target;
2044
1977
  const targetHandle = status.payload.targetHandle;
2045
- this.statusService.setConnectionEndStatus(source, target, sourceHandle, targetHandle);
1978
+ isReconnection
1979
+ ? this.statusService.setReconnectionEndStatus(source, target, sourceHandle, targetHandle, status.payload.oldEdge)
1980
+ : this.statusService.setConnectionEndStatus(source, target, sourceHandle, targetHandle);
2046
1981
  }
2047
1982
  }
2048
1983
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2049
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: ConnectionControllerDirective, isStandalone: true, selector: "[onConnect]", outputs: { onConnect: "onConnect" }, ngImport: i0 }); }
1984
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: ConnectionControllerDirective, isStandalone: true, selector: "[onConnect], [onReconnect]", outputs: { onConnect: "onConnect", onReconnect: "onReconnect" }, ngImport: i0 }); }
2050
1985
  }
2051
1986
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerDirective, decorators: [{
2052
1987
  type: Directive,
2053
1988
  args: [{
2054
- selector: '[onConnect]',
1989
+ selector: '[onConnect], [onReconnect]',
2055
1990
  standalone: true,
2056
1991
  }]
2057
1992
  }] });
1993
+ function statusToConnection(status, isStrictMode) {
1994
+ let source = status.payload.source;
1995
+ let target = status.payload.target;
1996
+ let sourceHandle = status.payload.sourceHandle;
1997
+ let targetHandle = status.payload.targetHandle;
1998
+ if (isStrictMode) {
1999
+ const adjusted = adjustDirection({
2000
+ source: status.payload.source,
2001
+ sourceHandle: status.payload.sourceHandle,
2002
+ target: status.payload.target,
2003
+ targetHandle: status.payload.targetHandle,
2004
+ });
2005
+ source = adjusted.source;
2006
+ target = adjusted.target;
2007
+ sourceHandle = adjusted.sourceHandle;
2008
+ targetHandle = adjusted.targetHandle;
2009
+ }
2010
+ const sourceId = source.node.id;
2011
+ const targetId = target.node.id;
2012
+ const sourceHandleId = sourceHandle.rawHandle.id;
2013
+ const targetHandleId = targetHandle.rawHandle.id;
2014
+ return {
2015
+ source: sourceId,
2016
+ target: targetId,
2017
+ sourceHandle: sourceHandleId,
2018
+ targetHandle: targetHandleId,
2019
+ };
2020
+ }
2058
2021
 
2059
- class HandleSizeControllerDirective {
2022
+ class EdgeRenderingService {
2060
2023
  constructor() {
2061
- this.handleModel = input.required({
2062
- alias: 'handleSizeController',
2024
+ this.flowEntitiesService = inject(FlowEntitiesService);
2025
+ this.edges = computed(() => {
2026
+ return this.flowEntitiesService.validEdges().sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
2063
2027
  });
2064
- this.handleWrapper = inject(ElementRef);
2065
- }
2066
- ngAfterViewInit() {
2067
- const element = this.handleWrapper.nativeElement;
2068
- const rect = element.getBBox();
2069
- const stroke = getChildStrokeWidth(element);
2070
- this.handleModel().size.set({
2071
- width: rect.width + stroke,
2072
- height: rect.height + stroke,
2028
+ this.maxOrder = computed(() => {
2029
+ return Math.max(...this.flowEntitiesService.validEdges().map((n) => n.renderOrder()));
2073
2030
  });
2074
2031
  }
2075
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleSizeControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2076
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.12", type: HandleSizeControllerDirective, isStandalone: true, selector: "[handleSizeController]", inputs: { handleModel: { classPropertyName: "handleModel", publicName: "handleSizeController", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
2077
- }
2078
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleSizeControllerDirective, decorators: [{
2079
- type: Directive,
2080
- args: [{
2081
- standalone: true,
2082
- selector: '[handleSizeController]',
2083
- }]
2084
- }] });
2085
- function getChildStrokeWidth(element) {
2086
- const child = element.firstElementChild;
2087
- if (child) {
2088
- const stroke = getComputedStyle(child).strokeWidth;
2089
- const strokeAsNumber = Number(stroke.replace('px', ''));
2090
- if (isNaN(strokeAsNumber)) {
2091
- return 0;
2032
+ pull(edge) {
2033
+ const isAlreadyOnTop = edge.renderOrder() !== 0 && this.maxOrder() === edge.renderOrder();
2034
+ if (isAlreadyOnTop) {
2035
+ return;
2092
2036
  }
2093
- return strokeAsNumber;
2037
+ // pull node
2038
+ edge.renderOrder.set(this.maxOrder() + 1);
2094
2039
  }
2095
- return 0;
2040
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2041
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeRenderingService }); }
2096
2042
  }
2097
-
2098
- class DefaultNodeComponent {
2099
- constructor() {
2100
- this.selected = input(false);
2101
- }
2102
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DefaultNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2103
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "17.3.12", type: DefaultNodeComponent, isStandalone: true, selector: "default-node", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.selected": "selected()" } }, ngImport: i0, template: "<ng-content />\n", styles: [":host{border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}:host(.selected){border-width:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2104
- }
2105
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DefaultNodeComponent, decorators: [{
2106
- type: Component,
2107
- args: [{ standalone: true, selector: 'default-node', host: {
2108
- '[class.selected]': 'selected()',
2109
- }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content />\n", styles: [":host{border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}:host(.selected){border-width:2px}\n"] }]
2043
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeRenderingService, decorators: [{
2044
+ type: Injectable
2110
2045
  }] });
2111
2046
 
2112
2047
  function isTouchEvent(event) {
@@ -2191,6 +2126,150 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2191
2126
  args: ['mouseout', ['$event']]
2192
2127
  }] } });
2193
2128
 
2129
+ class EdgeComponent {
2130
+ constructor() {
2131
+ this.injector = inject(Injector);
2132
+ this.selectionService = inject(SelectionService);
2133
+ this.flowSettingsService = inject(FlowSettingsService);
2134
+ this.flowStatusService = inject(FlowStatusService);
2135
+ this.edgeRenderingService = inject(EdgeRenderingService);
2136
+ // TODO remove dependency from this directive
2137
+ this.connectionController = inject(ConnectionControllerDirective, { optional: true });
2138
+ this.model = input.required();
2139
+ this.edgeTemplate = input();
2140
+ this.edgeLabelHtmlTemplate = input();
2141
+ this.interactiveEdgeRef = viewChild.required('interactiveEdge');
2142
+ this.markerStartUrl = computed(() => {
2143
+ const marker = this.model().edge.markers?.start;
2144
+ return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
2145
+ });
2146
+ this.markerEndUrl = computed(() => {
2147
+ const marker = this.model().edge.markers?.end;
2148
+ return marker ? `url(#${hashCode(JSON.stringify(marker))})` : '';
2149
+ });
2150
+ this.isReconnecting = computed(() => {
2151
+ const status = this.flowStatusService.status();
2152
+ const isReconnecting = status.state === 'reconnection-start' || status.state === 'reconnection-validation';
2153
+ return isReconnecting && status.payload.oldEdge === this.model();
2154
+ });
2155
+ }
2156
+ ngOnInit() {
2157
+ this.edgeContext = {
2158
+ $implicit: {
2159
+ // TODO: check if edge could change
2160
+ edge: this.model().edge,
2161
+ path: computed(() => this.model().path().path),
2162
+ markerStart: this.markerStartUrl,
2163
+ markerEnd: this.markerEndUrl,
2164
+ selected: this.model().selected.asReadonly(),
2165
+ },
2166
+ };
2167
+ }
2168
+ select() {
2169
+ if (this.flowSettingsService.entitiesSelectable()) {
2170
+ this.selectionService.select(this.model());
2171
+ }
2172
+ }
2173
+ pull() {
2174
+ if (this.flowSettingsService.elevateEdgesOnSelect()) {
2175
+ this.edgeRenderingService.pull(this.model());
2176
+ }
2177
+ }
2178
+ startReconnection(event, handle) {
2179
+ // ignore drag by stopping propagation
2180
+ event.stopPropagation();
2181
+ this.connectionController?.startReconnection(handle, this.model());
2182
+ }
2183
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2184
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EdgeComponent, isStandalone: true, selector: "g[edge]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, edgeTemplate: { classPropertyName: "edgeTemplate", publicName: "edgeTemplate", isSignal: true, isRequired: false, transformFunction: null }, edgeLabelHtmlTemplate: { classPropertyName: "edgeLabelHtmlTemplate", publicName: "edgeLabelHtmlTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.visibility": "isReconnecting() ? \"hidden\" : \"visible\"" }, classAttribute: "selectable" }, viewQueries: [{ propertyName: "interactiveEdgeRef", first: true, predicate: ["interactiveEdge"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (model().type === 'default') {\n <svg:path\n class=\"edge\"\n [attr.d]=\"model().path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n [class.edge_selected]=\"model().selected()\" />\n\n <svg:path\n #interactiveEdge\n class=\"interactive-edge\"\n [attr.d]=\"model().path().path\"\n (pointerStart)=\"select(); pull()\" />\n}\n\n@if (model().type === 'template' && edgeTemplate()) {\n @if (edgeTemplate(); as edgeTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"edgeTemplate\"\n [ngTemplateOutletContext]=\"edgeContext\"\n [ngTemplateOutletInjector]=\"injector\" />\n\n <svg:path #interactiveEdge class=\"interactive-edge\" [attr.d]=\"model().path().path\" (pointerStart)=\"pull()\" />\n }\n}\n\n@if (model().edgeLabels.start; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.start\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.center; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.center\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.end; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.end\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().sourceHandle() && model().targetHandle()) {\n @if (model().reconnectable === true || model().reconnectable === 'source') {\n <svg:circle\n class=\"reconnect-handle\"\n r=\"10\"\n [attr.cx]=\"model().sourceHandle()!.pointAbsolute().x\"\n [attr.cy]=\"model().sourceHandle()!.pointAbsolute().y\"\n (pointerStart)=\"startReconnection($event, model().targetHandle()!)\" />\n }\n\n @if (model().reconnectable === true || model().reconnectable === 'target') {\n <svg:circle\n class=\"reconnect-handle\"\n r=\"10\"\n [attr.cx]=\"model().targetHandle()!.pointAbsolute().x\"\n [attr.cy]=\"model().targetHandle()!.pointAbsolute().y\"\n (pointerStart)=\"startReconnection($event, model().sourceHandle()!)\" />\n }\n}\n", styles: [".edge{fill:none;stroke-width:2;stroke:#b1b1b7}.edge_selected{stroke-width:2.5;stroke:#0f4c75}.interactive-edge{fill:none;stroke-width:20;stroke:transparent}.reconnect-handle{fill:transparent;cursor:move}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: EdgeLabelComponent, selector: "g[edgeLabel]", inputs: ["model", "edgeModel", "point", "htmlTemplate"] }, { kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2185
+ }
2186
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeComponent, decorators: [{
2187
+ type: Component,
2188
+ args: [{ standalone: true, selector: 'g[edge]', changeDetection: ChangeDetectionStrategy.OnPush, host: {
2189
+ class: 'selectable',
2190
+ '[style.visibility]': 'isReconnecting() ? "hidden" : "visible"',
2191
+ }, imports: [NgTemplateOutlet, EdgeLabelComponent, PointerDirective], template: "@if (model().type === 'default') {\n <svg:path\n class=\"edge\"\n [attr.d]=\"model().path().path\"\n [attr.marker-start]=\"markerStartUrl()\"\n [attr.marker-end]=\"markerEndUrl()\"\n [class.edge_selected]=\"model().selected()\" />\n\n <svg:path\n #interactiveEdge\n class=\"interactive-edge\"\n [attr.d]=\"model().path().path\"\n (pointerStart)=\"select(); pull()\" />\n}\n\n@if (model().type === 'template' && edgeTemplate()) {\n @if (edgeTemplate(); as edgeTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"edgeTemplate\"\n [ngTemplateOutletContext]=\"edgeContext\"\n [ngTemplateOutletInjector]=\"injector\" />\n\n <svg:path #interactiveEdge class=\"interactive-edge\" [attr.d]=\"model().path().path\" (pointerStart)=\"pull()\" />\n }\n}\n\n@if (model().edgeLabels.start; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.start\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.center; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.center\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().edgeLabels.end; as label) {\n <svg:g\n edgeLabel\n [model]=\"label\"\n [point]=\"model().path().points.end\"\n [edgeModel]=\"model()\"\n [htmlTemplate]=\"edgeLabelHtmlTemplate()\" />\n}\n\n@if (model().sourceHandle() && model().targetHandle()) {\n @if (model().reconnectable === true || model().reconnectable === 'source') {\n <svg:circle\n class=\"reconnect-handle\"\n r=\"10\"\n [attr.cx]=\"model().sourceHandle()!.pointAbsolute().x\"\n [attr.cy]=\"model().sourceHandle()!.pointAbsolute().y\"\n (pointerStart)=\"startReconnection($event, model().targetHandle()!)\" />\n }\n\n @if (model().reconnectable === true || model().reconnectable === 'target') {\n <svg:circle\n class=\"reconnect-handle\"\n r=\"10\"\n [attr.cx]=\"model().targetHandle()!.pointAbsolute().x\"\n [attr.cy]=\"model().targetHandle()!.pointAbsolute().y\"\n (pointerStart)=\"startReconnection($event, model().sourceHandle()!)\" />\n }\n}\n", styles: [".edge{fill:none;stroke-width:2;stroke:#b1b1b7}.edge_selected{stroke-width:2.5;stroke:#0f4c75}.interactive-edge{fill:none;stroke-width:20;stroke:transparent}.reconnect-handle{fill:transparent;cursor:move}\n"] }]
2192
+ }] });
2193
+
2194
+ class HandleService {
2195
+ constructor() {
2196
+ this.node = signal(null);
2197
+ }
2198
+ createHandle(newHandle) {
2199
+ const node = this.node();
2200
+ if (node) {
2201
+ node.handles.update((handles) => [...handles, newHandle]);
2202
+ }
2203
+ }
2204
+ destroyHandle(handleToDestoy) {
2205
+ const node = this.node();
2206
+ if (node) {
2207
+ node.handles.update((handles) => handles.filter((handle) => handle !== handleToDestoy));
2208
+ }
2209
+ }
2210
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2211
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleService }); }
2212
+ }
2213
+ __decorate([
2214
+ Microtask // TODO fixes rendering of handle for group node
2215
+ ], HandleService.prototype, "createHandle", null);
2216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleService, decorators: [{
2217
+ type: Injectable
2218
+ }], propDecorators: { createHandle: [] } });
2219
+
2220
+ class HandleSizeControllerDirective {
2221
+ constructor() {
2222
+ this.handleModel = input.required({
2223
+ alias: 'handleSizeController',
2224
+ });
2225
+ this.handleWrapper = inject(ElementRef);
2226
+ }
2227
+ ngAfterViewInit() {
2228
+ const element = this.handleWrapper.nativeElement;
2229
+ const rect = element.getBBox();
2230
+ const stroke = getChildStrokeWidth(element);
2231
+ this.handleModel().size.set({
2232
+ width: rect.width + stroke,
2233
+ height: rect.height + stroke,
2234
+ });
2235
+ }
2236
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleSizeControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2237
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "17.3.12", type: HandleSizeControllerDirective, isStandalone: true, selector: "[handleSizeController]", inputs: { handleModel: { classPropertyName: "handleModel", publicName: "handleSizeController", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
2238
+ }
2239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HandleSizeControllerDirective, decorators: [{
2240
+ type: Directive,
2241
+ args: [{
2242
+ standalone: true,
2243
+ selector: '[handleSizeController]',
2244
+ }]
2245
+ }] });
2246
+ function getChildStrokeWidth(element) {
2247
+ const child = element.firstElementChild;
2248
+ if (child) {
2249
+ const stroke = getComputedStyle(child).strokeWidth;
2250
+ const strokeAsNumber = Number(stroke.replace('px', ''));
2251
+ if (isNaN(strokeAsNumber)) {
2252
+ return 0;
2253
+ }
2254
+ return strokeAsNumber;
2255
+ }
2256
+ return 0;
2257
+ }
2258
+
2259
+ class DefaultNodeComponent {
2260
+ constructor() {
2261
+ this.selected = input(false);
2262
+ }
2263
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DefaultNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2264
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "17.3.12", type: DefaultNodeComponent, isStandalone: true, selector: "default-node", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.selected": "selected()" } }, ngImport: i0, template: "<ng-content />\n", styles: [":host{border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}:host(.selected){border-width:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2265
+ }
2266
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DefaultNodeComponent, decorators: [{
2267
+ type: Component,
2268
+ args: [{ standalone: true, selector: 'default-node', host: {
2269
+ '[class.selected]': 'selected()',
2270
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content />\n", styles: [":host{border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}:host(.selected){border-width:2px}\n"] }]
2271
+ }] });
2272
+
2194
2273
  class ResizableComponent {
2195
2274
  get model() {
2196
2275
  return this.nodeAccessor.model();
@@ -2589,7 +2668,9 @@ class NodeComponent {
2589
2668
  this.nodeTemplate = input();
2590
2669
  this.groupNodeTemplate = input();
2591
2670
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
2592
- this.flowStatusService.status().state === 'connection-validation');
2671
+ this.flowStatusService.status().state === 'connection-validation' ||
2672
+ this.flowStatusService.status().state === 'reconnection-start' ||
2673
+ this.flowStatusService.status().state === 'reconnection-validation');
2593
2674
  this.toolbar = computed(() => this.overlaysService.nodeToolbars().get(this.model()));
2594
2675
  }
2595
2676
  ngOnInit() {
@@ -2659,7 +2740,7 @@ class ConnectionComponent {
2659
2740
  this.spacePointContext = inject(SpacePointContextDirective);
2660
2741
  this.path = computed(() => {
2661
2742
  const status = this.flowStatusService.status();
2662
- if (status.state === 'connection-start') {
2743
+ if (status.state === 'connection-start' || status.state === 'reconnection-start') {
2663
2744
  const sourceHandle = status.payload.sourceHandle;
2664
2745
  const sourcePoint = sourceHandle.pointAbsolute();
2665
2746
  const sourcePosition = sourceHandle.rawHandle.position;
@@ -2676,7 +2757,7 @@ class ConnectionComponent {
2676
2757
  return smoothStepPath(sourcePoint, targetPoint, sourcePosition, targetPosition, 0).path;
2677
2758
  }
2678
2759
  }
2679
- if (status.state === 'connection-validation') {
2760
+ if (status.state === 'connection-validation' || status.state === 'reconnection-validation') {
2680
2761
  const sourceHandle = status.payload.sourceHandle;
2681
2762
  const sourcePoint = sourceHandle.pointAbsolute();
2682
2763
  const sourcePosition = sourceHandle.rawHandle.position;
@@ -2950,7 +3031,7 @@ class RootSvgContextDirective {
2950
3031
  // TODO: check for multiple instances on page
2951
3032
  resetConnection() {
2952
3033
  const status = this.flowStatusService.status();
2953
- if (status.state === 'connection-start') {
3034
+ if (status.state === 'connection-start' || status.state === 'reconnection-start') {
2954
3035
  this.flowStatusService.setIdleStatus();
2955
3036
  }
2956
3037
  }
@@ -3016,6 +3097,7 @@ class VflowComponent {
3016
3097
  this.nodesChangeService = inject(NodesChangeService);
3017
3098
  this.edgesChangeService = inject(EdgeChangesService);
3018
3099
  this.nodeRenderingService = inject(NodeRenderingService);
3100
+ this.edgeRenderingService = inject(EdgeRenderingService);
3019
3101
  this.flowSettingsService = inject(FlowSettingsService);
3020
3102
  this.componentEventBusService = inject(ComponentEventBusService);
3021
3103
  this.keyboardService = inject(KeyboardService);
@@ -3026,7 +3108,7 @@ class VflowComponent {
3026
3108
  this.nodeModels = computed(() => this.nodeRenderingService.nodes());
3027
3109
  this.groups = computed(() => this.nodeRenderingService.groups());
3028
3110
  this.nonGroups = computed(() => this.nodeRenderingService.nonGroups());
3029
- this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
3111
+ this.edgeModels = computed(() => this.edgeRenderingService.edges());
3030
3112
  // #endregion
3031
3113
  // #region OUTPUTS
3032
3114
  /**
@@ -3144,6 +3226,12 @@ class VflowComponent {
3144
3226
  set elevateNodesOnSelect(value) {
3145
3227
  this.flowSettingsService.elevateNodesOnSelect.set(value);
3146
3228
  }
3229
+ /**
3230
+ * Raizing z-index for selected edge
3231
+ */
3232
+ set elevateEdgesOnSelect(value) {
3233
+ this.flowSettingsService.elevateEdgesOnSelect.set(value);
3234
+ }
3147
3235
  // #endregion
3148
3236
  // #region MAIN_INPUTS
3149
3237
  /**
@@ -3245,7 +3333,7 @@ class VflowComponent {
3245
3333
  });
3246
3334
  }
3247
3335
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3248
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: false, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: false, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: false, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, optimization: { classPropertyName: "optimization", publicName: "optimization", isSignal: true, isRequired: false, transformFunction: null }, entitiesSelectable: { classPropertyName: "entitiesSelectable", publicName: "entitiesSelectable", isSignal: false, isRequired: false, transformFunction: null }, keyboardShortcuts: { classPropertyName: "keyboardShortcuts", publicName: "keyboardShortcuts", isSignal: false, isRequired: false, transformFunction: null }, connection: { classPropertyName: "connection", publicName: "connection", isSignal: false, isRequired: false, transformFunction: (settings) => new ConnectionModel(settings) }, snapGrid: { classPropertyName: "snapGrid", publicName: "snapGrid", isSignal: false, isRequired: false, transformFunction: null }, elevateNodesOnSelect: { classPropertyName: "elevateNodesOnSelect", publicName: "elevateNodesOnSelect", isSignal: false, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: false, isRequired: true, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
3336
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: false, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: false, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: false, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, optimization: { classPropertyName: "optimization", publicName: "optimization", isSignal: true, isRequired: false, transformFunction: null }, entitiesSelectable: { classPropertyName: "entitiesSelectable", publicName: "entitiesSelectable", isSignal: false, isRequired: false, transformFunction: null }, keyboardShortcuts: { classPropertyName: "keyboardShortcuts", publicName: "keyboardShortcuts", isSignal: false, isRequired: false, transformFunction: null }, connection: { classPropertyName: "connection", publicName: "connection", isSignal: false, isRequired: false, transformFunction: (settings) => new ConnectionModel(settings) }, snapGrid: { classPropertyName: "snapGrid", publicName: "snapGrid", isSignal: false, isRequired: false, transformFunction: null }, elevateNodesOnSelect: { classPropertyName: "elevateNodesOnSelect", publicName: "elevateNodesOnSelect", isSignal: false, isRequired: false, transformFunction: null }, elevateEdgesOnSelect: { classPropertyName: "elevateEdgesOnSelect", publicName: "elevateEdgesOnSelect", isSignal: false, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: false, isRequired: true, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
3249
3337
  DraggableService,
3250
3338
  ViewportService,
3251
3339
  FlowStatusService,
@@ -3253,6 +3341,7 @@ class VflowComponent {
3253
3341
  NodesChangeService,
3254
3342
  EdgeChangesService,
3255
3343
  NodeRenderingService,
3344
+ EdgeRenderingService,
3256
3345
  SelectionService,
3257
3346
  FlowSettingsService,
3258
3347
  ComponentEventBusService,
@@ -3270,6 +3359,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3270
3359
  NodesChangeService,
3271
3360
  EdgeChangesService,
3272
3361
  NodeRenderingService,
3362
+ EdgeRenderingService,
3273
3363
  SelectionService,
3274
3364
  FlowSettingsService,
3275
3365
  ComponentEventBusService,
@@ -3310,6 +3400,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3310
3400
  type: Input
3311
3401
  }], elevateNodesOnSelect: [{
3312
3402
  type: Input
3403
+ }], elevateEdgesOnSelect: [{
3404
+ type: Input
3313
3405
  }], nodes: [{
3314
3406
  type: Input,
3315
3407
  args: [{ required: true }]
@@ -3348,8 +3440,12 @@ class SelectableDirective {
3348
3440
  this.selectionService = inject(SelectionService);
3349
3441
  this.parentEdge = inject(EdgeComponent, { optional: true });
3350
3442
  this.parentNode = inject(NodeComponent, { optional: true });
3443
+ this.host = inject(ElementRef);
3444
+ this.selectOnEvent = this.getEvent$()
3445
+ .pipe(tap(() => this.select()), takeUntilDestroyed())
3446
+ .subscribe();
3351
3447
  }
3352
- onMousedown() {
3448
+ select() {
3353
3449
  const entity = this.entity();
3354
3450
  if (entity && this.flowSettingsService.entitiesSelectable()) {
3355
3451
  this.selectionService.select(entity);
@@ -3364,8 +3460,16 @@ class SelectableDirective {
3364
3460
  }
3365
3461
  return null;
3366
3462
  }
3463
+ getEvent$() {
3464
+ if (this.parentEdge) {
3465
+ // get not the edge itself, but the interactive edge (which is more UX friendly)
3466
+ const interactiveEdge = this.parentEdge.interactiveEdgeRef().nativeElement;
3467
+ return merge(fromEvent(interactiveEdge, 'mousedown'), fromEvent(interactiveEdge, 'touchstart'));
3468
+ }
3469
+ return merge(fromEvent(this.host.nativeElement, 'mousedown'), fromEvent(this.host.nativeElement, 'touchstart'));
3470
+ }
3367
3471
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SelectableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
3368
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: SelectableDirective, isStandalone: true, selector: "[selectable]", host: { listeners: { "mousedown": "onMousedown()", "touchstart": "onMousedown()" } }, ngImport: i0 }); }
3472
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: SelectableDirective, isStandalone: true, selector: "[selectable]", ngImport: i0 }); }
3369
3473
  }
3370
3474
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SelectableDirective, decorators: [{
3371
3475
  type: Directive,
@@ -3373,13 +3477,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3373
3477
  standalone: true,
3374
3478
  selector: '[selectable]',
3375
3479
  }]
3376
- }], propDecorators: { onMousedown: [{
3377
- type: HostListener,
3378
- args: ['mousedown']
3379
- }, {
3380
- type: HostListener,
3381
- args: ['touchstart']
3382
- }] } });
3480
+ }] });
3383
3481
 
3384
3482
  class MinimapModel {
3385
3483
  constructor() {
@@ -3794,7 +3892,7 @@ class VflowMockComponent {
3794
3892
  return signal(value);
3795
3893
  }
3796
3894
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowMockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3797
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowMockComponent, isStandalone: true, selector: "vflow", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: false, isRequired: true, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: false, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: false, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: false, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, optimization: { classPropertyName: "optimization", publicName: "optimization", isSignal: true, isRequired: false, transformFunction: null }, entitiesSelectable: { classPropertyName: "entitiesSelectable", publicName: "entitiesSelectable", isSignal: false, isRequired: false, transformFunction: null }, keyboardShortcuts: { classPropertyName: "keyboardShortcuts", publicName: "keyboardShortcuts", isSignal: false, isRequired: false, transformFunction: null }, connection: { classPropertyName: "connection", publicName: "connection", isSignal: false, isRequired: false, transformFunction: (settings) => new ConnectionModel(settings) }, snapGrid: { classPropertyName: "snapGrid", publicName: "snapGrid", isSignal: false, isRequired: false, transformFunction: null }, elevateNodesOnSelect: { classPropertyName: "elevateNodesOnSelect", publicName: "elevateNodesOnSelect", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateMockDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
3895
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowMockComponent, isStandalone: true, selector: "vflow", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: false, isRequired: true, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: false, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: false, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: false, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, optimization: { classPropertyName: "optimization", publicName: "optimization", isSignal: true, isRequired: false, transformFunction: null }, entitiesSelectable: { classPropertyName: "entitiesSelectable", publicName: "entitiesSelectable", isSignal: false, isRequired: false, transformFunction: null }, keyboardShortcuts: { classPropertyName: "keyboardShortcuts", publicName: "keyboardShortcuts", isSignal: false, isRequired: false, transformFunction: null }, connection: { classPropertyName: "connection", publicName: "connection", isSignal: false, isRequired: false, transformFunction: (settings) => new ConnectionModel(settings) }, snapGrid: { classPropertyName: "snapGrid", publicName: "snapGrid", isSignal: false, isRequired: false, transformFunction: null }, elevateNodesOnSelect: { classPropertyName: "elevateNodesOnSelect", publicName: "elevateNodesOnSelect", isSignal: false, isRequired: false, transformFunction: null }, elevateEdgesOnSelect: { classPropertyName: "elevateEdgesOnSelect", publicName: "elevateEdgesOnSelect", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateMockDirective, descendants: true, isSignal: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateMockDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
3798
3896
  <ng-content />
3799
3897
 
3800
3898
  @for (node of nodes; track $index) {
@@ -4005,6 +4103,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
4005
4103
  type: Input
4006
4104
  }], elevateNodesOnSelect: [{
4007
4105
  type: Input
4106
+ }], elevateEdgesOnSelect: [{
4107
+ type: Input
4008
4108
  }] } });
4009
4109
 
4010
4110
  class HandleMockComponent {
@@ -4099,17 +4199,21 @@ class ConnectionControllerMockDirective {
4099
4199
  constructor() {
4100
4200
  // eslint-disable-next-line @angular-eslint/no-output-on-prefix
4101
4201
  this.onConnect = output();
4202
+ // eslint-disable-next-line @angular-eslint/no-output-on-prefix
4203
+ this.onReconnect = output();
4102
4204
  }
4103
4205
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4104
4206
  startConnection(handle) { }
4105
4207
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4208
+ startReconnection(handle) { }
4209
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4106
4210
  validateConnection(handle) { }
4107
4211
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4108
4212
  resetValidateConnection(targetHandle) { }
4109
4213
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4110
4214
  endConnection() { }
4111
4215
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerMockDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
4112
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: ConnectionControllerMockDirective, isStandalone: true, selector: "[onConnect]", outputs: { onConnect: "onConnect" }, ngImport: i0 }); }
4216
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: ConnectionControllerMockDirective, isStandalone: true, selector: "[onConnect]", outputs: { onConnect: "onConnect", onReconnect: "onReconnect" }, ngImport: i0 }); }
4113
4217
  }
4114
4218
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerMockDirective, decorators: [{
4115
4219
  type: Directive,