ngx-vflow 1.0.6 → 1.0.7

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.
@@ -6,6 +6,7 @@ import { MiniMapComponent } from './public-components/minimap/minimap.component'
6
6
  import { NodeToolbarComponent } from './public-components/node-toolbar/node-toolbar.component';
7
7
  import { ResizableComponent } from './public-components/resizable/resizable.component';
8
8
  import { HandleComponent } from './public-components/handle/handle.component';
9
+ import { ConnectionControllerDirective } from './directives/connection-controller.directive';
9
10
  export const Vflow = [
10
11
  VflowComponent,
11
12
  HandleComponent,
@@ -14,6 +15,7 @@ export const Vflow = [
14
15
  MiniMapComponent,
15
16
  NodeToolbarComponent,
16
17
  DragHandleDirective,
18
+ ConnectionControllerDirective,
17
19
  NodeHtmlTemplateDirective,
18
20
  GroupNodeTemplateDirective,
19
21
  EdgeLabelHtmlTemplateDirective,
@@ -21,4 +23,4 @@ export const Vflow = [
21
23
  ConnectionTemplateDirective,
22
24
  HandleTemplateDirective,
23
25
  ];
24
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmZsb3cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtdmZsb3ctbGliL3NyYy9saWIvdmZsb3cvdmZsb3cudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ3BFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ3pFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQ3hFLE9BQU8sRUFDTCwyQkFBMkIsRUFDM0IsOEJBQThCLEVBQzlCLHFCQUFxQixFQUNyQiwwQkFBMEIsRUFDMUIsdUJBQXVCLEVBQ3ZCLHlCQUF5QixHQUMxQixNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLCtDQUErQyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLHlEQUF5RCxDQUFDO0FBQy9GLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLG1EQUFtRCxDQUFDO0FBQ3ZGLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2Q0FBNkMsQ0FBQztBQUU5RSxNQUFNLENBQUMsTUFBTSxLQUFLLEdBQUc7SUFDbkIsY0FBYztJQUNkLGVBQWU7SUFDZixrQkFBa0I7SUFDbEIsbUJBQW1CO0lBQ25CLGdCQUFnQjtJQUNoQixvQkFBb0I7SUFDcEIsbUJBQW1CO0lBRW5CLHlCQUF5QjtJQUN6QiwwQkFBMEI7SUFDMUIsOEJBQThCO0lBQzlCLHFCQUFxQjtJQUNyQiwyQkFBMkI7SUFDM0IsdUJBQXVCO0NBQ2YsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IFZmbG93Q29tcG9uZW50IH0gZnJvbSAnLi9jb21wb25lbnRzL3ZmbG93L3ZmbG93LmNvbXBvbmVudCc7XG5pbXBvcnQgeyBEcmFnSGFuZGxlRGlyZWN0aXZlIH0gZnJvbSAnLi9kaXJlY3RpdmVzL2RyYWctaGFuZGxlLmRpcmVjdGl2ZSc7XG5pbXBvcnQgeyBTZWxlY3RhYmxlRGlyZWN0aXZlIH0gZnJvbSAnLi9kaXJlY3RpdmVzL3NlbGVjdGFibGUuZGlyZWN0aXZlJztcbmltcG9ydCB7XG4gIENvbm5lY3Rpb25UZW1wbGF0ZURpcmVjdGl2ZSxcbiAgRWRnZUxhYmVsSHRtbFRlbXBsYXRlRGlyZWN0aXZlLFxuICBFZGdlVGVtcGxhdGVEaXJlY3RpdmUsXG4gIEdyb3VwTm9kZVRlbXBsYXRlRGlyZWN0aXZlLFxuICBIYW5kbGVUZW1wbGF0ZURpcmVjdGl2ZSxcbiAgTm9kZUh0bWxUZW1wbGF0ZURpcmVjdGl2ZSxcbn0gZnJvbSAnLi9kaXJlY3RpdmVzL3RlbXBsYXRlLmRpcmVjdGl2ZSc7XG5pbXBvcnQgeyBNaW5pTWFwQ29tcG9uZW50IH0gZnJvbSAnLi9wdWJsaWMtY29tcG9uZW50cy9taW5pbWFwL21pbmltYXAuY29tcG9uZW50JztcbmltcG9ydCB7IE5vZGVUb29sYmFyQ29tcG9uZW50IH0gZnJvbSAnLi9wdWJsaWMtY29tcG9uZW50cy9ub2RlLXRvb2xiYXIvbm9kZS10b29sYmFyLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBSZXNpemFibGVDb21wb25lbnQgfSBmcm9tICcuL3B1YmxpYy1jb21wb25lbnRzL3Jlc2l6YWJsZS9yZXNpemFibGUuY29tcG9uZW50JztcbmltcG9ydCB7IEhhbmRsZUNvbXBvbmVudCB9IGZyb20gJy4vcHVibGljLWNvbXBvbmVudHMvaGFuZGxlL2hhbmRsZS5jb21wb25lbnQnO1xuXG5leHBvcnQgY29uc3QgVmZsb3cgPSBbXG4gIFZmbG93Q29tcG9uZW50LFxuICBIYW5kbGVDb21wb25lbnQsXG4gIFJlc2l6YWJsZUNvbXBvbmVudCxcbiAgU2VsZWN0YWJsZURpcmVjdGl2ZSxcbiAgTWluaU1hcENvbXBvbmVudCxcbiAgTm9kZVRvb2xiYXJDb21wb25lbnQsXG4gIERyYWdIYW5kbGVEaXJlY3RpdmUsXG5cbiAgTm9kZUh0bWxUZW1wbGF0ZURpcmVjdGl2ZSxcbiAgR3JvdXBOb2RlVGVtcGxhdGVEaXJlY3RpdmUsXG4gIEVkZ2VMYWJlbEh0bWxUZW1wbGF0ZURpcmVjdGl2ZSxcbiAgRWRnZVRlbXBsYXRlRGlyZWN0aXZlLFxuICBDb25uZWN0aW9uVGVtcGxhdGVEaXJlY3RpdmUsXG4gIEhhbmRsZVRlbXBsYXRlRGlyZWN0aXZlLFxuXSBhcyBjb25zdDtcbiJdfQ==
26
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmZsb3cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtdmZsb3ctbGliL3NyYy9saWIvdmZsb3cvdmZsb3cudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ3BFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ3pFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQ3hFLE9BQU8sRUFDTCwyQkFBMkIsRUFDM0IsOEJBQThCLEVBQzlCLHFCQUFxQixFQUNyQiwwQkFBMEIsRUFDMUIsdUJBQXVCLEVBQ3ZCLHlCQUF5QixHQUMxQixNQUFNLGlDQUFpQyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLCtDQUErQyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLHlEQUF5RCxDQUFDO0FBQy9GLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLG1EQUFtRCxDQUFDO0FBQ3ZGLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2Q0FBNkMsQ0FBQztBQUM5RSxPQUFPLEVBQUUsNkJBQTZCLEVBQUUsTUFBTSw4Q0FBOEMsQ0FBQztBQUU3RixNQUFNLENBQUMsTUFBTSxLQUFLLEdBQUc7SUFDbkIsY0FBYztJQUNkLGVBQWU7SUFDZixrQkFBa0I7SUFDbEIsbUJBQW1CO0lBQ25CLGdCQUFnQjtJQUNoQixvQkFBb0I7SUFDcEIsbUJBQW1CO0lBQ25CLDZCQUE2QjtJQUU3Qix5QkFBeUI7SUFDekIsMEJBQTBCO0lBQzFCLDhCQUE4QjtJQUM5QixxQkFBcUI7SUFDckIsMkJBQTJCO0lBQzNCLHVCQUF1QjtDQUNmLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBWZmxvd0NvbXBvbmVudCB9IGZyb20gJy4vY29tcG9uZW50cy92Zmxvdy92Zmxvdy5jb21wb25lbnQnO1xuaW1wb3J0IHsgRHJhZ0hhbmRsZURpcmVjdGl2ZSB9IGZyb20gJy4vZGlyZWN0aXZlcy9kcmFnLWhhbmRsZS5kaXJlY3RpdmUnO1xuaW1wb3J0IHsgU2VsZWN0YWJsZURpcmVjdGl2ZSB9IGZyb20gJy4vZGlyZWN0aXZlcy9zZWxlY3RhYmxlLmRpcmVjdGl2ZSc7XG5pbXBvcnQge1xuICBDb25uZWN0aW9uVGVtcGxhdGVEaXJlY3RpdmUsXG4gIEVkZ2VMYWJlbEh0bWxUZW1wbGF0ZURpcmVjdGl2ZSxcbiAgRWRnZVRlbXBsYXRlRGlyZWN0aXZlLFxuICBHcm91cE5vZGVUZW1wbGF0ZURpcmVjdGl2ZSxcbiAgSGFuZGxlVGVtcGxhdGVEaXJlY3RpdmUsXG4gIE5vZGVIdG1sVGVtcGxhdGVEaXJlY3RpdmUsXG59IGZyb20gJy4vZGlyZWN0aXZlcy90ZW1wbGF0ZS5kaXJlY3RpdmUnO1xuaW1wb3J0IHsgTWluaU1hcENvbXBvbmVudCB9IGZyb20gJy4vcHVibGljLWNvbXBvbmVudHMvbWluaW1hcC9taW5pbWFwLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBOb2RlVG9vbGJhckNvbXBvbmVudCB9IGZyb20gJy4vcHVibGljLWNvbXBvbmVudHMvbm9kZS10b29sYmFyL25vZGUtdG9vbGJhci5jb21wb25lbnQnO1xuaW1wb3J0IHsgUmVzaXphYmxlQ29tcG9uZW50IH0gZnJvbSAnLi9wdWJsaWMtY29tcG9uZW50cy9yZXNpemFibGUvcmVzaXphYmxlLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBIYW5kbGVDb21wb25lbnQgfSBmcm9tICcuL3B1YmxpYy1jb21wb25lbnRzL2hhbmRsZS9oYW5kbGUuY29tcG9uZW50JztcbmltcG9ydCB7IENvbm5lY3Rpb25Db250cm9sbGVyRGlyZWN0aXZlIH0gZnJvbSAnLi9kaXJlY3RpdmVzL2Nvbm5lY3Rpb24tY29udHJvbGxlci5kaXJlY3RpdmUnO1xuXG5leHBvcnQgY29uc3QgVmZsb3cgPSBbXG4gIFZmbG93Q29tcG9uZW50LFxuICBIYW5kbGVDb21wb25lbnQsXG4gIFJlc2l6YWJsZUNvbXBvbmVudCxcbiAgU2VsZWN0YWJsZURpcmVjdGl2ZSxcbiAgTWluaU1hcENvbXBvbmVudCxcbiAgTm9kZVRvb2xiYXJDb21wb25lbnQsXG4gIERyYWdIYW5kbGVEaXJlY3RpdmUsXG4gIENvbm5lY3Rpb25Db250cm9sbGVyRGlyZWN0aXZlLFxuXG4gIE5vZGVIdG1sVGVtcGxhdGVEaXJlY3RpdmUsXG4gIEdyb3VwTm9kZVRlbXBsYXRlRGlyZWN0aXZlLFxuICBFZGdlTGFiZWxIdG1sVGVtcGxhdGVEaXJlY3RpdmUsXG4gIEVkZ2VUZW1wbGF0ZURpcmVjdGl2ZSxcbiAgQ29ubmVjdGlvblRlbXBsYXRlRGlyZWN0aXZlLFxuICBIYW5kbGVUZW1wbGF0ZURpcmVjdGl2ZSxcbl0gYXMgY29uc3Q7XG4iXX0=
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, output, DestroyRef, EventEmitter, OutputEmitterRef, input, viewChild, Component, ChangeDetectionStrategy, Injector, runInInjectionContext, HostListener, NgZone, contentChild, Input, forwardRef } from '@angular/core';
2
+ import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, DestroyRef, EventEmitter, OutputEmitterRef, input, viewChild, Component, ChangeDetectionStrategy, Injector, runInInjectionContext, output, HostListener, NgZone, 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, observeOn, animationFrameScheduler, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, share, startWith, of } from 'rxjs';
@@ -666,173 +666,6 @@ class FlowStatusService {
666
666
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowStatusService, decorators: [{
667
667
  type: Injectable
668
668
  }] });
669
- /**
670
- * Batch status changes together to call them one after another
671
- *
672
- * @param changes list of set[FlowStatus.state]Status() calls
673
- */
674
- function batchStatusChanges(...changes) {
675
- if (changes.length) {
676
- const [firstChange, ...restChanges] = changes;
677
- // first change is sync
678
- firstChange();
679
- // without timer, subscribed effects/comuted signals only get latest value
680
- restChanges.forEach((change) => setTimeout(() => change()));
681
- }
682
- }
683
-
684
- /**
685
- * This function contains a hack-y behavior.
686
- * If the handles are of the same type (source-source or target-target),
687
- * it returns nodes where source === target and
688
- * handles where sourceHandle === targetHandle
689
- *
690
- * This leads to that notSelfValidator returns false for these cases,
691
- * exactly what we need for strict connection type
692
- */
693
- function adjustDirection(connection) {
694
- const result = {};
695
- if (connection.sourceHandle.rawHandle.type === 'source') {
696
- result.source = connection.source;
697
- result.sourceHandle = connection.sourceHandle;
698
- }
699
- else {
700
- result.source = connection.target;
701
- result.sourceHandle = connection.targetHandle;
702
- }
703
- if (connection.targetHandle.rawHandle.type === 'target') {
704
- result.target = connection.target;
705
- result.targetHandle = connection.targetHandle;
706
- }
707
- else {
708
- result.target = connection.source;
709
- result.targetHandle = connection.sourceHandle;
710
- }
711
- return result;
712
- }
713
-
714
- class ConnectionControllerDirective {
715
- constructor() {
716
- /**
717
- * This event fires when user tries to create new Edge.
718
- *
719
- * `Connection` is an entity that contains data about source and target nodes.
720
- *
721
- * Also it's important to note, that this event only fires when connection is valid by validator function in `ConnectionSettings`,
722
- * by default without passing the validator every connection concidered valid.
723
- *
724
- * @todo add connect event and deprecate onConnect
725
- */
726
- // eslint-disable-next-line @angular-eslint/no-output-on-prefix
727
- this.onConnect = output();
728
- this.statusService = inject(FlowStatusService);
729
- this.flowEntitiesService = inject(FlowEntitiesService);
730
- this.connectEffect = effect(() => {
731
- const status = this.statusService.status();
732
- if (status.state === 'connection-end') {
733
- let source = status.payload.source;
734
- let target = status.payload.target;
735
- let sourceHandle = status.payload.sourceHandle;
736
- let targetHandle = status.payload.targetHandle;
737
- if (this.isStrictMode()) {
738
- const adjusted = adjustDirection({
739
- source: status.payload.source,
740
- sourceHandle: status.payload.sourceHandle,
741
- target: status.payload.target,
742
- targetHandle: status.payload.targetHandle,
743
- });
744
- source = adjusted.source;
745
- target = adjusted.target;
746
- sourceHandle = adjusted.sourceHandle;
747
- targetHandle = adjusted.targetHandle;
748
- }
749
- const sourceId = source.node.id;
750
- const targetId = target.node.id;
751
- const sourceHandleId = sourceHandle.rawHandle.id;
752
- const targetHandleId = targetHandle.rawHandle.id;
753
- const connectionModel = this.flowEntitiesService.connection();
754
- const connection = {
755
- source: sourceId,
756
- target: targetId,
757
- sourceHandle: sourceHandleId,
758
- targetHandle: targetHandleId,
759
- };
760
- if (connectionModel.validator(connection)) {
761
- this.onConnect.emit(connection);
762
- }
763
- }
764
- }, { allowSignalWrites: true });
765
- this.isStrictMode = computed(() => this.flowEntitiesService.connection().mode === 'strict');
766
- }
767
- startConnection(handle) {
768
- this.statusService.setConnectionStartStatus(handle.parentNode, handle);
769
- }
770
- validateConnection(handle) {
771
- const status = this.statusService.status();
772
- if (status.state === 'connection-start') {
773
- let source = status.payload.source;
774
- let target = handle.parentNode;
775
- let sourceHandle = status.payload.sourceHandle;
776
- let targetHandle = handle;
777
- if (this.isStrictMode()) {
778
- // swap direction (if needed) according to actual source and target of strict mode
779
- const adjusted = adjustDirection({
780
- source: status.payload.source,
781
- sourceHandle: status.payload.sourceHandle,
782
- target: handle.parentNode,
783
- targetHandle: handle,
784
- });
785
- source = adjusted.source;
786
- target = adjusted.target;
787
- sourceHandle = adjusted.sourceHandle;
788
- targetHandle = adjusted.targetHandle;
789
- }
790
- const valid = this.flowEntitiesService.connection().validator({
791
- source: source.node.id,
792
- target: target.node.id,
793
- sourceHandle: sourceHandle.rawHandle.id,
794
- targetHandle: targetHandle.rawHandle.id,
795
- });
796
- // TODO: check how react flow handles highlight of handle
797
- // if direction changes
798
- handle.state.set(valid ? 'valid' : 'invalid');
799
- // status is about how we draw connection, so we don't need
800
- // swapped diretion here
801
- this.statusService.setConnectionValidationStatus(valid, status.payload.source, handle.parentNode, status.payload.sourceHandle, handle);
802
- }
803
- }
804
- resetValidateConnection(targetHandle) {
805
- targetHandle.state.set('idle');
806
- // drop back to start status
807
- const status = this.statusService.status();
808
- if (status.state === 'connection-validation') {
809
- this.statusService.setConnectionStartStatus(status.payload.source, status.payload.sourceHandle);
810
- }
811
- }
812
- endConnection() {
813
- const status = this.statusService.status();
814
- if (status.state === 'connection-validation') {
815
- const source = status.payload.source;
816
- const sourceHandle = status.payload.sourceHandle;
817
- const target = status.payload.target;
818
- const targetHandle = status.payload.targetHandle;
819
- batchStatusChanges(
820
- // call to create connection
821
- () => this.statusService.setConnectionEndStatus(source, target, sourceHandle, targetHandle),
822
- // when connection created, we need go back to idle status
823
- () => this.statusService.setIdleStatus());
824
- }
825
- }
826
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
827
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: ConnectionControllerDirective, isStandalone: true, selector: "[connectionController]", outputs: { onConnect: "onConnect" }, ngImport: i0 }); }
828
- }
829
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerDirective, decorators: [{
830
- type: Directive,
831
- args: [{
832
- selector: '[connectionController]',
833
- standalone: true,
834
- }]
835
- }] });
836
669
 
837
670
  class ComponentEventBusService {
838
671
  constructor() {
@@ -2032,6 +1865,147 @@ const implementsWithInjector = (instance) => {
2032
1865
  return 'injector' in instance && 'get' in instance.injector;
2033
1866
  };
2034
1867
 
1868
+ /**
1869
+ * This function contains a hack-y behavior.
1870
+ * If the handles are of the same type (source-source or target-target),
1871
+ * it returns nodes where source === target and
1872
+ * handles where sourceHandle === targetHandle
1873
+ *
1874
+ * This leads to that notSelfValidator returns false for these cases,
1875
+ * exactly what we need for strict connection type
1876
+ */
1877
+ function adjustDirection(connection) {
1878
+ const result = {};
1879
+ if (connection.sourceHandle.rawHandle.type === 'source') {
1880
+ result.source = connection.source;
1881
+ result.sourceHandle = connection.sourceHandle;
1882
+ }
1883
+ else {
1884
+ result.source = connection.target;
1885
+ result.sourceHandle = connection.targetHandle;
1886
+ }
1887
+ if (connection.targetHandle.rawHandle.type === 'target') {
1888
+ result.target = connection.target;
1889
+ result.targetHandle = connection.targetHandle;
1890
+ }
1891
+ else {
1892
+ result.target = connection.source;
1893
+ result.targetHandle = connection.sourceHandle;
1894
+ }
1895
+ return result;
1896
+ }
1897
+
1898
+ class ConnectionControllerDirective {
1899
+ constructor() {
1900
+ this.statusService = inject(FlowStatusService);
1901
+ this.flowEntitiesService = inject(FlowEntitiesService);
1902
+ /**
1903
+ * This event fires when user tries to create new Edge.
1904
+ *
1905
+ * `Connection` is an entity that contains data about source and target nodes.
1906
+ *
1907
+ * Also it's important to note, that this event only fires when connection is valid by validator function in `ConnectionSettings`,
1908
+ * by default without passing the validator every connection concidered valid.
1909
+ *
1910
+ * @todo add connect event and deprecate onConnect
1911
+ */
1912
+ // eslint-disable-next-line @angular-eslint/no-output-on-prefix
1913
+ this.onConnect = outputFromObservable(toObservable(this.statusService.status).pipe(filter((status) => status.state === 'connection-end'), map((status) => {
1914
+ let source = status.payload.source;
1915
+ let target = status.payload.target;
1916
+ let sourceHandle = status.payload.sourceHandle;
1917
+ let targetHandle = status.payload.targetHandle;
1918
+ if (this.isStrictMode()) {
1919
+ const adjusted = adjustDirection({
1920
+ source: status.payload.source,
1921
+ sourceHandle: status.payload.sourceHandle,
1922
+ target: status.payload.target,
1923
+ targetHandle: status.payload.targetHandle,
1924
+ });
1925
+ source = adjusted.source;
1926
+ target = adjusted.target;
1927
+ sourceHandle = adjusted.sourceHandle;
1928
+ targetHandle = adjusted.targetHandle;
1929
+ }
1930
+ const sourceId = source.node.id;
1931
+ const targetId = target.node.id;
1932
+ const sourceHandleId = sourceHandle.rawHandle.id;
1933
+ const targetHandleId = targetHandle.rawHandle.id;
1934
+ return {
1935
+ source: sourceId,
1936
+ target: targetId,
1937
+ sourceHandle: sourceHandleId,
1938
+ targetHandle: targetHandleId,
1939
+ };
1940
+ }), tap(() => this.statusService.setIdleStatus()), filter((connection) => this.flowEntitiesService.connection().validator(connection))));
1941
+ this.isStrictMode = computed(() => this.flowEntitiesService.connection().mode === 'strict');
1942
+ }
1943
+ startConnection(handle) {
1944
+ this.statusService.setConnectionStartStatus(handle.parentNode, handle);
1945
+ }
1946
+ validateConnection(handle) {
1947
+ const status = this.statusService.status();
1948
+ if (status.state === 'connection-start') {
1949
+ let source = status.payload.source;
1950
+ let target = handle.parentNode;
1951
+ let sourceHandle = status.payload.sourceHandle;
1952
+ let targetHandle = handle;
1953
+ if (this.isStrictMode()) {
1954
+ // swap direction (if needed) according to actual source and target of strict mode
1955
+ const adjusted = adjustDirection({
1956
+ source: status.payload.source,
1957
+ sourceHandle: status.payload.sourceHandle,
1958
+ target: handle.parentNode,
1959
+ targetHandle: handle,
1960
+ });
1961
+ source = adjusted.source;
1962
+ target = adjusted.target;
1963
+ sourceHandle = adjusted.sourceHandle;
1964
+ targetHandle = adjusted.targetHandle;
1965
+ }
1966
+ const valid = this.flowEntitiesService.connection().validator({
1967
+ source: source.node.id,
1968
+ target: target.node.id,
1969
+ sourceHandle: sourceHandle.rawHandle.id,
1970
+ targetHandle: targetHandle.rawHandle.id,
1971
+ });
1972
+ // TODO: check how react flow handles highlight of handle
1973
+ // if direction changes
1974
+ handle.state.set(valid ? 'valid' : 'invalid');
1975
+ // status is about how we draw connection, so we don't need
1976
+ // swapped diretion here
1977
+ this.statusService.setConnectionValidationStatus(valid, status.payload.source, handle.parentNode, status.payload.sourceHandle, handle);
1978
+ }
1979
+ }
1980
+ resetValidateConnection(targetHandle) {
1981
+ targetHandle.state.set('idle');
1982
+ // drop back to start status
1983
+ const status = this.statusService.status();
1984
+ if (status.state === 'connection-validation') {
1985
+ this.statusService.setConnectionStartStatus(status.payload.source, status.payload.sourceHandle);
1986
+ }
1987
+ }
1988
+ endConnection() {
1989
+ const status = this.statusService.status();
1990
+ if (status.state === 'connection-validation') {
1991
+ const source = status.payload.source;
1992
+ const sourceHandle = status.payload.sourceHandle;
1993
+ const target = status.payload.target;
1994
+ const targetHandle = status.payload.targetHandle;
1995
+ this.statusService.setConnectionEndStatus(source, target, sourceHandle, targetHandle);
1996
+ }
1997
+ }
1998
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1999
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.3.12", type: ConnectionControllerDirective, isStandalone: true, selector: "[onConnect]", outputs: { onConnect: "onConnect" }, ngImport: i0 }); }
2000
+ }
2001
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConnectionControllerDirective, decorators: [{
2002
+ type: Directive,
2003
+ args: [{
2004
+ selector: '[onConnect]',
2005
+ standalone: true,
2006
+ }]
2007
+ }] });
2008
+
2035
2009
  class HandleSizeControllerDirective {
2036
2010
  constructor() {
2037
2011
  this.handleModel = input.required({
@@ -2517,10 +2491,11 @@ class NodeComponent {
2517
2491
  this.flowSettingsService = inject(FlowSettingsService);
2518
2492
  this.selectionService = inject(SelectionService);
2519
2493
  this.hostRef = inject(ElementRef);
2520
- this.connectionController = inject(ConnectionControllerDirective);
2521
2494
  this.nodeAccessor = inject(NodeAccessorService);
2522
2495
  this.overlaysService = inject(OverlaysService);
2523
2496
  this.zone = inject(NgZone);
2497
+ // TODO remove dependency from this directive
2498
+ this.connectionController = inject(ConnectionControllerDirective, { optional: true });
2524
2499
  this.nodeModel = input.required();
2525
2500
  this.nodeTemplate = input();
2526
2501
  this.groupNodeTemplate = input();
@@ -2569,16 +2544,16 @@ class NodeComponent {
2569
2544
  startConnection(event, handle) {
2570
2545
  // ignore drag by stopping propagation
2571
2546
  event.stopPropagation();
2572
- this.connectionController.startConnection(handle);
2547
+ this.connectionController?.startConnection(handle);
2573
2548
  }
2574
2549
  validateConnection(handle) {
2575
- this.connectionController.validateConnection(handle);
2550
+ this.connectionController?.validateConnection(handle);
2576
2551
  }
2577
2552
  resetValidateConnection(targetHandle) {
2578
- this.connectionController.resetValidateConnection(targetHandle);
2553
+ this.connectionController?.resetValidateConnection(targetHandle);
2579
2554
  }
2580
2555
  endConnection() {
2581
- this.connectionController.endConnection();
2556
+ this.connectionController?.endConnection();
2582
2557
  }
2583
2558
  pullNode() {
2584
2559
  this.nodeRenderingService.pullNode(this.nodeModel());
@@ -2932,10 +2907,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2932
2907
  args: ['document:touchend']
2933
2908
  }] } });
2934
2909
 
2935
- const connectionControllerHostDirective = {
2936
- directive: ConnectionControllerDirective,
2937
- outputs: ['onConnect'],
2938
- };
2939
2910
  const changesControllerHostDirective = {
2940
2911
  directive: ChangesControllerDirective,
2941
2912
  outputs: [
@@ -3208,7 +3179,7 @@ class VflowComponent {
3208
3179
  ComponentEventBusService,
3209
3180
  KeyboardService,
3210
3181
  OverlaysService,
3211
- ], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, 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: ConnectionControllerDirective, outputs: ["onConnect", "onConnect"] }, { directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.size", "onNodesChange.size", "onNodesChange.size.single", "onNodesChange.size.single", "onNodesChange.size.many", "onNodesChange.size.many", "onNodesChange.add", "onNodesChange.add", "onNodesChange.add.single", "onNodesChange.add.single", "onNodesChange.add.many", "onNodesChange.add.many", "onNodesChange.remove", "onNodesChange.remove", "onNodesChange.remove.single", "onNodesChange.remove.single", "onNodesChange.remove.many", "onNodesChange.remove.many", "onNodesChange.select", "onNodesChange.select", "onNodesChange.select.single", "onNodesChange.select.single", "onNodesChange.select.many", "onNodesChange.select.many", "onEdgesChange", "onEdgesChange", "onEdgesChange.detached", "onEdgesChange.detached", "onEdgesChange.detached.single", "onEdgesChange.detached.single", "onEdgesChange.detached.many", "onEdgesChange.detached.many", "onEdgesChange.add", "onEdgesChange.add", "onEdgesChange.add.single", "onEdgesChange.add.single", "onEdgesChange.add.many", "onEdgesChange.add.many", "onEdgesChange.remove", "onEdgesChange.remove", "onEdgesChange.remove.single", "onEdgesChange.remove.single", "onEdgesChange.remove.many", "onEdgesChange.remove.many", "onEdgesChange.select", "onEdgesChange.select", "onEdgesChange.select.single", "onEdgesChange.select.single", "onEdgesChange.select.many", "onEdgesChange.select.many"] }], ngImport: i0, template: "<svg:svg #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (optimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [nodeModel]=\"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 [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!optimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n }\n</svg:svg>\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }, { kind: "directive", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]" }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3182
+ ], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true, isSignal: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true, isSignal: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true, isSignal: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.size", "onNodesChange.size", "onNodesChange.size.single", "onNodesChange.size.single", "onNodesChange.size.many", "onNodesChange.size.many", "onNodesChange.add", "onNodesChange.add", "onNodesChange.add.single", "onNodesChange.add.single", "onNodesChange.add.many", "onNodesChange.add.many", "onNodesChange.remove", "onNodesChange.remove", "onNodesChange.remove.single", "onNodesChange.remove.single", "onNodesChange.remove.many", "onNodesChange.remove.many", "onNodesChange.select", "onNodesChange.select", "onNodesChange.select.single", "onNodesChange.select.single", "onNodesChange.select.many", "onNodesChange.select.many", "onEdgesChange", "onEdgesChange", "onEdgesChange.detached", "onEdgesChange.detached", "onEdgesChange.detached.single", "onEdgesChange.detached.single", "onEdgesChange.detached.many", "onEdgesChange.detached.many", "onEdgesChange.add", "onEdgesChange.add", "onEdgesChange.add.single", "onEdgesChange.add.single", "onEdgesChange.add.many", "onEdgesChange.add.many", "onEdgesChange.remove", "onEdgesChange.remove", "onEdgesChange.remove.single", "onEdgesChange.remove.single", "onEdgesChange.remove.many", "onEdgesChange.remove.many", "onEdgesChange.select", "onEdgesChange.select", "onEdgesChange.select.single", "onEdgesChange.select.single", "onEdgesChange.select.many", "onEdgesChange.select.many"] }], ngImport: i0, template: "<svg:svg #flow rootSvgRef rootSvgContext rootPointer flowSizeController class=\"root-svg\">\n <defs flowDefs [markers]=\"markers()\" />\n\n <g background />\n\n <svg:g mapContext spacePointContext>\n <!-- Connection -->\n <svg:g connection [model]=\"connection\" [template]=\"connectionTemplateDirective()?.templateRef\" />\n\n @if (optimization().detachedGroupsLayer) {\n <!-- Groups -->\n @for (model of groups(); track trackNodes($index, model)) {\n <svg:g\n node\n [nodeModel]=\"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 [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n\n @if (!optimization().detachedGroupsLayer) {\n <!-- Edges -->\n @for (model of edgeModels(); track trackEdges($index, model)) {\n <svg:g\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective()?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective()?.templateRef\" />\n }\n <!-- Nodes -->\n @for (model of nodeModels(); track trackNodes($index, model)) {\n <svg:g\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective()?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective()?.templateRef\"\n [attr.transform]=\"model.pointTransform()\" />\n }\n }\n </svg:g>\n\n <!-- Minimap -->\n @if (minimap(); as minimap) {\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n }\n</svg:svg>\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }, { kind: "directive", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]" }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3212
3183
  }
3213
3184
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, decorators: [{
3214
3185
  type: Component,
@@ -3225,7 +3196,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3225
3196
  ComponentEventBusService,
3226
3197
  KeyboardService,
3227
3198
  OverlaysService,
3228
- ], hostDirectives: [connectionControllerHostDirective, changesControllerHostDirective], imports: [
3199
+ ], hostDirectives: [changesControllerHostDirective], imports: [
3229
3200
  RootSvgReferenceDirective,
3230
3201
  RootSvgContextDirective,
3231
3202
  RootPointerDirective,
@@ -3532,6 +3503,7 @@ const Vflow = [
3532
3503
  MiniMapComponent,
3533
3504
  NodeToolbarComponent,
3534
3505
  DragHandleDirective,
3506
+ ConnectionControllerDirective,
3535
3507
  NodeHtmlTemplateDirective,
3536
3508
  GroupNodeTemplateDirective,
3537
3509
  EdgeLabelHtmlTemplateDirective,