ngx-vflow 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/esm2022/lib/vflow/components/custom-node-base/custom-node-base.component.mjs +5 -7
  2. package/esm2022/lib/vflow/components/handle/handle.component.mjs +16 -14
  3. package/esm2022/lib/vflow/components/node/node.component.mjs +8 -3
  4. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +8 -5
  5. package/esm2022/lib/vflow/directives/drag-handle.directive.mjs +27 -0
  6. package/esm2022/lib/vflow/directives/map-context.directive.mjs +24 -17
  7. package/esm2022/lib/vflow/models/node.model.mjs +2 -1
  8. package/esm2022/lib/vflow/models/toolbar.model.mjs +36 -0
  9. package/esm2022/lib/vflow/public-components/node-toolbar/node-toolbar.component.mjs +66 -0
  10. package/esm2022/lib/vflow/services/draggable.service.mjs +13 -15
  11. package/esm2022/lib/vflow/services/overlays.service.mjs +34 -0
  12. package/esm2022/lib/vflow/testing-utils/provide-custom-node-mocks.mjs +67 -0
  13. package/esm2022/lib/vflow/vflow.module.mjs +17 -5
  14. package/esm2022/public-api.mjs +5 -1
  15. package/fesm2022/ngx-vflow.mjs +286 -60
  16. package/fesm2022/ngx-vflow.mjs.map +1 -1
  17. package/lib/vflow/components/handle/handle.component.d.ts +3 -3
  18. package/lib/vflow/components/node/node.component.d.ts +2 -0
  19. package/lib/vflow/directives/drag-handle.directive.d.ts +8 -0
  20. package/lib/vflow/directives/map-context.directive.d.ts +3 -2
  21. package/lib/vflow/models/node.model.d.ts +1 -0
  22. package/lib/vflow/models/toolbar.model.d.ts +19 -0
  23. package/lib/vflow/public-components/node-toolbar/node-toolbar.component.d.ts +22 -0
  24. package/lib/vflow/services/draggable.service.d.ts +0 -5
  25. package/lib/vflow/services/overlays.service.d.ts +11 -0
  26. package/lib/vflow/testing-utils/provide-custom-node-mocks.d.ts +2 -0
  27. package/lib/vflow/vflow.module.d.ts +14 -12
  28. package/package.json +1 -1
  29. package/public-api.d.ts +3 -0
@@ -4,7 +4,7 @@ import * as i0 from '@angular/core';
4
4
  import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, EventEmitter, Output, DestroyRef, Input, runInInjectionContext, Component, Injector, ChangeDetectionStrategy, HostListener, ViewChild, NgZone, ContentChild, NgModule } from '@angular/core';
5
5
  import { select } from 'd3-selection';
6
6
  import { zoomIdentity, zoom } from 'd3-zoom';
7
- import { switchMap, merge, fromEvent, tap, Subject, observeOn, animationFrameScheduler, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, share, Observable, startWith } from 'rxjs';
7
+ import { switchMap, merge, fromEvent, tap, Subject, observeOn, animationFrameScheduler, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, share, Observable, startWith, of } from 'rxjs';
8
8
  import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
9
9
  import { drag } from 'd3-drag';
10
10
  import { __decorate } from 'tslib';
@@ -406,30 +406,37 @@ class MapContextDirective {
406
406
  this.viewportService.readableViewport.set(mapTransformToViewportState(transform));
407
407
  this.zoomableSelection.attr('transform', transform.toString());
408
408
  };
409
+ this.handleZoomStart = ({ transform }) => {
410
+ this.viewportForSelection = {
411
+ start: mapTransformToViewportState(transform)
412
+ };
413
+ };
414
+ this.handleZoomEnd = ({ transform, sourceEvent }) => {
415
+ this.viewportForSelection = {
416
+ ...this.viewportForSelection,
417
+ end: mapTransformToViewportState(transform),
418
+ target: evTarget(sourceEvent)
419
+ };
420
+ this.selectionService.setViewport(this.viewportForSelection);
421
+ };
422
+ this.filterCondition = (event) => {
423
+ if (event.type === 'mousedown' || event.type === 'touchstart') {
424
+ return event.target.closest('.vflow-node') === null;
425
+ }
426
+ return true;
427
+ };
409
428
  }
410
429
  ngOnInit() {
411
430
  this.zoomBehavior = zoom()
412
431
  .scaleExtent([this.flowSettingsService.minZoom(), this.flowSettingsService.maxZoom()])
413
- .on('start', (event) => this.onD3zoomStart(event))
414
- .on('zoom', (event) => this.handleZoom(event))
415
- .on('end', (event) => this.onD3zoomEnd(event));
432
+ .filter(this.filterCondition)
433
+ .on('start', this.handleZoomStart)
434
+ .on('zoom', this.handleZoom)
435
+ .on('end', this.handleZoomEnd);
416
436
  this.rootSvgSelection
417
437
  .call(this.zoomBehavior)
418
438
  .on('dblclick.zoom', null);
419
439
  }
420
- onD3zoomStart({ transform }) {
421
- this.viewportForSelection = {
422
- start: mapTransformToViewportState(transform)
423
- };
424
- }
425
- onD3zoomEnd({ transform, sourceEvent }) {
426
- this.viewportForSelection = {
427
- ...this.viewportForSelection,
428
- end: mapTransformToViewportState(transform),
429
- target: evTarget(sourceEvent)
430
- };
431
- this.selectionService.setViewport(this.viewportForSelection);
432
- }
433
440
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MapContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
434
441
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: MapContextDirective, selector: "g[mapContext]", ngImport: i0 }); }
435
442
  }
@@ -458,8 +465,8 @@ class DraggableService {
458
465
  * @param model model with data for this element
459
466
  */
460
467
  enable(element, model) {
461
- const d3Element = select(element);
462
- d3Element.call(this.getDragBehavior(model));
468
+ select(element)
469
+ .call(this.getDragBehavior(model));
463
470
  }
464
471
  /**
465
472
  * Disable draggable behavior for element.
@@ -468,8 +475,8 @@ class DraggableService {
468
475
  * @param model model with data for this element
469
476
  */
470
477
  disable(element) {
471
- const d3Element = select(element);
472
- d3Element.call(this.getIgnoreDragBehavior());
478
+ select(element)
479
+ .call(drag().on('drag', null));
473
480
  }
474
481
  /**
475
482
  * TODO: not shure if this work, need to check
@@ -488,7 +495,15 @@ class DraggableService {
488
495
  getDragBehavior(model) {
489
496
  let dragNodes = [];
490
497
  let initialPositions = [];
498
+ const filterCondition = (event) => {
499
+ // if there is at least one drag handle, we should check if we are dragging it
500
+ if (model.dragHandlesCount()) {
501
+ return !!event.target.closest('.vflow-drag-handle');
502
+ }
503
+ return true;
504
+ };
491
505
  return drag()
506
+ .filter(filterCondition)
492
507
  .on('start', (event) => {
493
508
  dragNodes = this.getDragNodes(model);
494
509
  initialPositions = dragNodes.map(node => ({
@@ -506,16 +521,6 @@ class DraggableService {
506
521
  });
507
522
  });
508
523
  }
509
- /**
510
- * Specify ignoring drag behavior. It's responsible for not moving the map when user tries to drag node
511
- * with disabled drag behavior
512
- */
513
- getIgnoreDragBehavior() {
514
- return drag()
515
- .on('drag', (event) => {
516
- event.sourceEvent.stopPropagation();
517
- });
518
- }
519
524
  getDragNodes(model) {
520
525
  return model.selected()
521
526
  ? this.entitiesService
@@ -825,7 +830,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
825
830
 
826
831
  class CustomNodeBaseComponent {
827
832
  constructor() {
828
- this.eventBus = inject(ComponentEventBusService, { optional: true });
833
+ this.eventBus = inject(ComponentEventBusService);
829
834
  this.destroyRef = inject(DestroyRef);
830
835
  /**
831
836
  * Signal with selected state of node
@@ -837,11 +842,9 @@ class CustomNodeBaseComponent {
837
842
  this.selected.set(value);
838
843
  }
839
844
  ngOnInit() {
840
- if (this.eventBus) {
841
- this.trackEvents()
842
- .pipe(takeUntilDestroyed(this.destroyRef))
843
- .subscribe();
844
- }
845
+ this.trackEvents()
846
+ .pipe(takeUntilDestroyed(this.destroyRef))
847
+ .subscribe();
845
848
  }
846
849
  trackEvents() {
847
850
  const props = Object.getOwnPropertyNames(this);
@@ -980,6 +983,7 @@ class NodeModel {
980
983
  this.handles = signal([]);
981
984
  this.handles$ = toObservable(this.handles);
982
985
  this.draggable = signal(true);
986
+ this.dragHandlesCount = signal(0);
983
987
  // disabled for configuration for now
984
988
  this.magnetRadius = 20;
985
989
  // TODO: not sure if we need to statically store it
@@ -1689,6 +1693,36 @@ function Microtask(target, key, descriptor) {
1689
1693
  return descriptor;
1690
1694
  }
1691
1695
 
1696
+ class OverlaysService {
1697
+ constructor() {
1698
+ this.toolbars = signal([]);
1699
+ this.nodeToolbars = computed(() => {
1700
+ const map = new Map();
1701
+ this.toolbars().forEach((toolbar) => {
1702
+ map.set(toolbar.node, toolbar);
1703
+ });
1704
+ return map;
1705
+ });
1706
+ }
1707
+ addToolbar(toolbar) {
1708
+ this.toolbars.update((toolbars) => [...toolbars, toolbar]);
1709
+ }
1710
+ removeToolbar(toolbar) {
1711
+ this.toolbars.update((toolbars) => toolbars.filter(t => t !== toolbar));
1712
+ }
1713
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OverlaysService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1714
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OverlaysService }); }
1715
+ }
1716
+ __decorate([
1717
+ Microtask
1718
+ ], OverlaysService.prototype, "addToolbar", null);
1719
+ __decorate([
1720
+ Microtask
1721
+ ], OverlaysService.prototype, "removeToolbar", null);
1722
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OverlaysService, decorators: [{
1723
+ type: Injectable
1724
+ }], propDecorators: { addToolbar: [], removeToolbar: [] } });
1725
+
1692
1726
  class HandleService {
1693
1727
  constructor() {
1694
1728
  this.node = signal(null);
@@ -1866,20 +1900,22 @@ class HandleComponent {
1866
1900
  this.injector = inject(Injector);
1867
1901
  this.handleService = inject(HandleService);
1868
1902
  this.element = inject(ElementRef).nativeElement;
1903
+ this.destroyRef = inject(DestroyRef);
1869
1904
  }
1870
1905
  ngOnInit() {
1871
- this.model = new HandleModel({
1872
- position: this.position,
1873
- type: this.type,
1874
- id: this.id,
1875
- parentReference: this.element.parentElement,
1876
- template: this.template
1877
- }, this.handleService.node());
1878
- this.handleService.createHandle(this.model);
1879
- requestAnimationFrame(() => this.model.updateParent());
1880
- }
1881
- ngOnDestroy() {
1882
- this.handleService.destroyHandle(this.model);
1906
+ const node = this.handleService.node();
1907
+ if (node) {
1908
+ this.model = new HandleModel({
1909
+ position: this.position,
1910
+ type: this.type,
1911
+ id: this.id,
1912
+ parentReference: this.element.parentElement,
1913
+ template: this.template
1914
+ }, node);
1915
+ this.handleService.createHandle(this.model);
1916
+ requestAnimationFrame(() => this.model.updateParent());
1917
+ this.destroyRef.onDestroy(() => this.handleService.destroyHandle(this.model));
1918
+ }
1883
1919
  }
1884
1920
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1885
1921
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: HandleComponent, selector: "handle", inputs: { position: "position", type: "type", id: "id", template: "template" }, ngImport: i0, template: "", changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -2225,11 +2261,13 @@ class NodeComponent {
2225
2261
  this.hostRef = inject(ElementRef);
2226
2262
  this.connectionController = inject(ConnectionControllerDirective);
2227
2263
  this.nodeAccessor = inject(NodeAccessorService);
2264
+ this.overlaysService = inject(OverlaysService);
2228
2265
  this.zone = inject(NgZone);
2229
2266
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
2230
2267
  this.flowStatusService.status().state === 'connection-validation');
2231
2268
  this.styleWidth = computed(() => `${this.nodeModel.size().width}px`);
2232
2269
  this.styleHeight = computed(() => `${this.nodeModel.size().height}px`);
2270
+ this.toolbar = computed(() => this.overlaysService.nodeToolbars().get(this.nodeModel));
2233
2271
  }
2234
2272
  ngOnInit() {
2235
2273
  this.nodeAccessor.model.set(this.nodeModel);
@@ -2287,7 +2325,7 @@ class NodeComponent {
2287
2325
  }
2288
2326
  }
2289
2327
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2290
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeTemplate: "nodeTemplate", groupNodeTemplate: "groupNodeTemplate" }, providers: [HandleService, NodeAccessorService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode(); selectNode()\"\n>\n <default-node\n #htmlWrapper\n [selected]=\"nodeModel.selected()\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n [style.max-width]=\"styleWidth()\"\n [style.max-height]=\"styleHeight()\"\n >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\n </default-node>\n</svg:foreignObject>\n\n<!-- Template node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (pointerStart)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (pointerStart)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: DefaultNodeComponent, selector: "default-node", inputs: ["selected"] }, { kind: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template"] }, { kind: "component", type: ResizableComponent, selector: "[resizable]", inputs: ["resizable", "resizerColor", "gap"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }, { kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2328
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeTemplate: "nodeTemplate", groupNodeTemplate: "groupNodeTemplate" }, host: { classAttribute: "vflow-node" }, providers: [HandleService, NodeAccessorService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode(); selectNode()\"\n>\n <default-node\n #htmlWrapper\n [selected]=\"nodeModel.selected()\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n [style.max-width]=\"styleWidth()\"\n [style.max-height]=\"styleHeight()\"\n >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\n </default-node>\n</svg:foreignObject>\n\n<!-- Template node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (pointerStart)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (pointerStart)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n\n<!-- Toolbar -->\n<svg:foreignObject\n *ngIf=\"toolbar() as toolbar\"\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\"\n>\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n</svg:foreignObject>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: DefaultNodeComponent, selector: "default-node", inputs: ["selected"] }, { kind: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template"] }, { kind: "component", type: ResizableComponent, selector: "[resizable]", inputs: ["resizable", "resizerColor", "gap"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }, { kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2291
2329
  }
2292
2330
  __decorate([
2293
2331
  InjectionContext
@@ -2297,7 +2335,9 @@ __decorate([
2297
2335
  ], NodeComponent.prototype, "ngAfterViewInit", null);
2298
2336
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
2299
2337
  type: Component,
2300
- args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService, NodeAccessorService], template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode(); selectNode()\"\n>\n <default-node\n #htmlWrapper\n [selected]=\"nodeModel.selected()\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n [style.max-width]=\"styleWidth()\"\n [style.max-height]=\"styleHeight()\"\n >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\n </default-node>\n</svg:foreignObject>\n\n<!-- Template node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (pointerStart)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (pointerStart)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
2338
+ args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService, NodeAccessorService], host: {
2339
+ 'class': 'vflow-node',
2340
+ }, template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode(); selectNode()\"\n>\n <default-node\n #htmlWrapper\n [selected]=\"nodeModel.selected()\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n [style.max-width]=\"styleWidth()\"\n [style.max-height]=\"styleHeight()\"\n >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\n </default-node>\n</svg:foreignObject>\n\n<!-- Template node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (pointerStart)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (pointerStart)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n\n<!-- Toolbar -->\n<svg:foreignObject\n *ngIf=\"toolbar() as toolbar\"\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\"\n>\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n</svg:foreignObject>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
2301
2341
  }], propDecorators: { nodeModel: [{
2302
2342
  type: Input
2303
2343
  }], nodeTemplate: [{
@@ -2910,8 +2950,9 @@ class VflowComponent {
2910
2950
  SelectionService,
2911
2951
  FlowSettingsService,
2912
2952
  ComponentEventBusService,
2913
- KeyboardService
2914
- ], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: 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\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n <!-- Minimap -->\n <ng-container *ngIf=\"minimap() as minimap\">\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n </ng-container>\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: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { 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]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2953
+ KeyboardService,
2954
+ OverlaysService
2955
+ ], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: 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\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n <!-- Minimap -->\n <ng-container *ngIf=\"minimap() as minimap\">\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n </ng-container>\n</svg:svg>\n\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: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]" }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { 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]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2915
2956
  }
2916
2957
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
2917
2958
  type: Component,
@@ -2926,11 +2967,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2926
2967
  SelectionService,
2927
2968
  FlowSettingsService,
2928
2969
  ComponentEventBusService,
2929
- KeyboardService
2970
+ KeyboardService,
2971
+ OverlaysService
2930
2972
  ], hostDirectives: [
2931
2973
  connectionControllerHostDirective,
2932
2974
  changesControllerHostDirective
2933
- ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n <!-- Minimap -->\n <ng-container *ngIf=\"minimap() as minimap\">\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n </ng-container>\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"] }]
2975
+ ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g background />\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n <!-- Minimap -->\n <ng-container *ngIf=\"minimap() as minimap\">\n <ng-container [ngTemplateOutlet]=\"minimap.template()\" />\n </ng-container>\n</svg:svg>\n\n", styles: [":host{display:block;width:100%;height:100%;-webkit-user-select:none;user-select:none}:host ::ng-deep *{box-sizing:border-box}\n"] }]
2934
2976
  }], propDecorators: { view: [{
2935
2977
  type: Input
2936
2978
  }], minZoom: [{
@@ -3131,6 +3173,125 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
3131
3173
  args: ['minimap', { static: true }]
3132
3174
  }] } });
3133
3175
 
3176
+ class ToolbarModel {
3177
+ constructor(node) {
3178
+ this.node = node;
3179
+ this.position = signal('top');
3180
+ this.template = signal(null);
3181
+ this.offset = signal(10);
3182
+ this.point = computed(() => {
3183
+ switch (this.position()) {
3184
+ case 'top':
3185
+ return {
3186
+ x: this.node.size().width / 2 - this.size().width / 2,
3187
+ y: -this.size().height - this.offset()
3188
+ };
3189
+ case 'bottom':
3190
+ return {
3191
+ x: this.node.size().width / 2 - this.size().width / 2,
3192
+ y: this.node.size().height + this.offset()
3193
+ };
3194
+ case 'left':
3195
+ return {
3196
+ x: -this.size().width - this.offset(),
3197
+ y: this.node.size().height / 2 - this.size().height / 2
3198
+ };
3199
+ case 'right':
3200
+ return {
3201
+ x: this.node.size().width + this.offset(),
3202
+ y: this.node.size().height / 2 - this.size().height / 2
3203
+ };
3204
+ }
3205
+ });
3206
+ this.transform = computed(() => `translate(${this.point().x}, ${this.point().y})`);
3207
+ this.size = signal({ width: 0, height: 0 });
3208
+ }
3209
+ }
3210
+
3211
+ class NodeToolbarComponent {
3212
+ constructor() {
3213
+ this.overlaysService = inject(OverlaysService);
3214
+ this.nodeService = inject(NodeAccessorService);
3215
+ this.model = new ToolbarModel(this.nodeService.model());
3216
+ }
3217
+ set position(value) {
3218
+ this.model.position.set(value);
3219
+ }
3220
+ ngOnInit() {
3221
+ this.model.template.set(this.toolbarContentTemplate);
3222
+ this.overlaysService.addToolbar(this.model);
3223
+ }
3224
+ ngOnDestroy() {
3225
+ this.overlaysService.removeToolbar(this.model);
3226
+ }
3227
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3228
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeToolbarComponent, selector: "node-toolbar", inputs: { position: "position" }, viewQueries: [{ propertyName: "toolbarContentTemplate", first: true, predicate: ["toolbar"], descendants: true, static: true }], ngImport: i0, template: `
3229
+ <ng-template #toolbar>
3230
+ <div class="wrapper" nodeToolbarWrapper [model]="model">
3231
+ <ng-content />
3232
+ </div>
3233
+ </ng-template>
3234
+ `, isInline: true, styles: [".wrapper{width:max-content}\n"], dependencies: [{ kind: "directive", type: i0.forwardRef(function () { return NodeToolbarWrapperDirective; }), selector: "[nodeToolbarWrapper]", inputs: ["model"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3235
+ }
3236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeToolbarComponent, decorators: [{
3237
+ type: Component,
3238
+ args: [{ selector: 'node-toolbar', template: `
3239
+ <ng-template #toolbar>
3240
+ <div class="wrapper" nodeToolbarWrapper [model]="model">
3241
+ <ng-content />
3242
+ </div>
3243
+ </ng-template>
3244
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".wrapper{width:max-content}\n"] }]
3245
+ }], propDecorators: { position: [{
3246
+ type: Input
3247
+ }], toolbarContentTemplate: [{
3248
+ type: ViewChild,
3249
+ args: ['toolbar', { static: true }]
3250
+ }] } });
3251
+ class NodeToolbarWrapperDirective {
3252
+ constructor() {
3253
+ this.element = inject(ElementRef);
3254
+ }
3255
+ ngOnInit() {
3256
+ this.model.size.set({
3257
+ width: this.element.nativeElement.clientWidth,
3258
+ height: this.element.nativeElement.clientHeight
3259
+ });
3260
+ }
3261
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeToolbarWrapperDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
3262
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: NodeToolbarWrapperDirective, selector: "[nodeToolbarWrapper]", inputs: { model: "model" }, ngImport: i0 }); }
3263
+ }
3264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeToolbarWrapperDirective, decorators: [{
3265
+ type: Directive,
3266
+ args: [{ selector: '[nodeToolbarWrapper]' }]
3267
+ }], propDecorators: { model: [{
3268
+ type: Input
3269
+ }] } });
3270
+
3271
+ class DragHandleDirective {
3272
+ get model() {
3273
+ return this.nodeAccessor.model();
3274
+ }
3275
+ constructor() {
3276
+ this.nodeAccessor = inject(NodeAccessorService);
3277
+ this.model.dragHandlesCount.update((count) => count + 1);
3278
+ inject(DestroyRef).onDestroy(() => {
3279
+ this.model.dragHandlesCount.update(count => count - 1);
3280
+ });
3281
+ }
3282
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragHandleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
3283
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: DragHandleDirective, selector: "[dragHandle]", host: { classAttribute: "vflow-drag-handle" }, ngImport: i0 }); }
3284
+ }
3285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DragHandleDirective, decorators: [{
3286
+ type: Directive,
3287
+ args: [{
3288
+ selector: '[dragHandle]',
3289
+ host: {
3290
+ 'class': 'vflow-drag-handle'
3291
+ }
3292
+ }]
3293
+ }], ctorParameters: function () { return []; } });
3294
+
3134
3295
  const components = [
3135
3296
  VflowComponent,
3136
3297
  NodeComponent,
@@ -3142,7 +3303,8 @@ const components = [
3142
3303
  DefsComponent,
3143
3304
  BackgroundComponent,
3144
3305
  ResizableComponent,
3145
- MiniMapComponent
3306
+ MiniMapComponent,
3307
+ NodeToolbarComponent
3146
3308
  ];
3147
3309
  const directives = [
3148
3310
  SpacePointContextDirective,
@@ -3151,9 +3313,11 @@ const directives = [
3151
3313
  RootSvgContextDirective,
3152
3314
  HandleSizeControllerDirective,
3153
3315
  SelectableDirective,
3316
+ DragHandleDirective,
3154
3317
  PointerDirective,
3155
3318
  RootPointerDirective,
3156
3319
  FlowSizeControllerDirective,
3320
+ NodeToolbarWrapperDirective
3157
3321
  ];
3158
3322
  const templateDirectives = [
3159
3323
  NodeHtmlTemplateDirective,
@@ -3175,15 +3339,18 @@ class VflowModule {
3175
3339
  DefsComponent,
3176
3340
  BackgroundComponent,
3177
3341
  ResizableComponent,
3178
- MiniMapComponent, SpacePointContextDirective,
3342
+ MiniMapComponent,
3343
+ NodeToolbarComponent, SpacePointContextDirective,
3179
3344
  MapContextDirective,
3180
3345
  RootSvgReferenceDirective,
3181
3346
  RootSvgContextDirective,
3182
3347
  HandleSizeControllerDirective,
3183
3348
  SelectableDirective,
3349
+ DragHandleDirective,
3184
3350
  PointerDirective,
3185
3351
  RootPointerDirective,
3186
- FlowSizeControllerDirective, NodeHtmlTemplateDirective,
3352
+ FlowSizeControllerDirective,
3353
+ NodeToolbarWrapperDirective, NodeHtmlTemplateDirective,
3187
3354
  GroupNodeTemplateDirective,
3188
3355
  EdgeLabelHtmlTemplateDirective,
3189
3356
  EdgeTemplateDirective,
@@ -3192,7 +3359,9 @@ class VflowModule {
3192
3359
  HandleComponent,
3193
3360
  ResizableComponent,
3194
3361
  SelectableDirective,
3195
- MiniMapComponent, NodeHtmlTemplateDirective,
3362
+ MiniMapComponent,
3363
+ NodeToolbarComponent,
3364
+ DragHandleDirective, NodeHtmlTemplateDirective,
3196
3365
  GroupNodeTemplateDirective,
3197
3366
  EdgeLabelHtmlTemplateDirective,
3198
3367
  EdgeTemplateDirective,
@@ -3210,17 +3379,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
3210
3379
  ResizableComponent,
3211
3380
  SelectableDirective,
3212
3381
  MiniMapComponent,
3382
+ NodeToolbarComponent,
3383
+ DragHandleDirective,
3213
3384
  ...templateDirectives
3214
3385
  ],
3215
3386
  declarations: [...components, ...directives, ...templateDirectives],
3216
3387
  }]
3217
3388
  }] });
3218
3389
 
3390
+ const mockModel = () => new NodeModel({ id: 'mock', type: 'default', point: { x: 0, y: 0 } });
3391
+ function provideCustomNodeMocks() {
3392
+ return [
3393
+ {
3394
+ provide: ComponentEventBusService,
3395
+ useValue: {
3396
+ pushEvent: () => { }
3397
+ }
3398
+ },
3399
+ {
3400
+ provide: HandleService,
3401
+ useFactory: () => ({
3402
+ node: signal(mockModel()),
3403
+ createHandle: () => { },
3404
+ destroyHandle: () => { },
3405
+ })
3406
+ },
3407
+ {
3408
+ provide: RootPointerDirective,
3409
+ useValue: {
3410
+ pointerMovement$: of({
3411
+ x: 0,
3412
+ y: 0,
3413
+ movementX: 0,
3414
+ movementY: 0,
3415
+ target: null,
3416
+ originalEvent: null
3417
+ }),
3418
+ documentPointerEnd$: of(null)
3419
+ }
3420
+ },
3421
+ {
3422
+ provide: SpacePointContextDirective,
3423
+ useValue: {
3424
+ documentPointToFlowPoint: (point) => point
3425
+ }
3426
+ },
3427
+ {
3428
+ provide: NodeAccessorService,
3429
+ useFactory: () => ({
3430
+ model: signal(mockModel())
3431
+ })
3432
+ },
3433
+ {
3434
+ provide: SelectionService,
3435
+ useValue: {
3436
+ select: () => { },
3437
+ }
3438
+ },
3439
+ FlowSettingsService,
3440
+ FlowEntitiesService,
3441
+ ViewportService
3442
+ ];
3443
+ }
3444
+
3219
3445
  // Modules
3220
3446
 
3221
3447
  /**
3222
3448
  * Generated bundle index. Do not edit.
3223
3449
  */
3224
3450
 
3225
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, MiniMapComponent, NodeHtmlTemplateDirective, ResizableComponent, SelectableDirective, VflowComponent, VflowModule, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode };
3451
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, DragHandleDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, MiniMapComponent, NodeHtmlTemplateDirective, NodeToolbarComponent, NodeToolbarWrapperDirective, ResizableComponent, SelectableDirective, VflowComponent, VflowModule, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode, provideCustomNodeMocks };
3226
3452
  //# sourceMappingURL=ngx-vflow.mjs.map