ngx-vflow 0.5.0 → 0.7.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 (35) hide show
  1. package/esm2022/lib/vflow/components/background/background.component.mjs +66 -0
  2. package/esm2022/lib/vflow/components/handle/handle.component.mjs +1 -1
  3. package/esm2022/lib/vflow/components/node/node.component.mjs +14 -10
  4. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +29 -14
  5. package/esm2022/lib/vflow/directives/map-context.directive.mjs +7 -6
  6. package/esm2022/lib/vflow/interfaces/component-node-event.interface.mjs +2 -0
  7. package/esm2022/lib/vflow/interfaces/node.interface.mjs +1 -1
  8. package/esm2022/lib/vflow/models/node.model.mjs +25 -5
  9. package/esm2022/lib/vflow/public-components/custom-node.component.mjs +51 -0
  10. package/esm2022/lib/vflow/services/component-event-bus.service.mjs +18 -0
  11. package/esm2022/lib/vflow/services/draggable.service.mjs +2 -2
  12. package/esm2022/lib/vflow/services/selection.service.mjs +2 -2
  13. package/esm2022/lib/vflow/types/background.type.mjs +2 -0
  14. package/esm2022/lib/vflow/utils/id.mjs +5 -0
  15. package/esm2022/lib/vflow/utils/resizable.mjs +3 -3
  16. package/esm2022/lib/vflow/vflow.module.mjs +6 -3
  17. package/esm2022/public-api.mjs +4 -1
  18. package/fesm2022/ngx-vflow.mjs +195 -32
  19. package/fesm2022/ngx-vflow.mjs.map +1 -1
  20. package/lib/vflow/components/background/background.component.d.ts +20 -0
  21. package/lib/vflow/components/node/node.component.d.ts +8 -8
  22. package/lib/vflow/components/vflow/vflow.component.d.ts +11 -3
  23. package/lib/vflow/directives/space-point-context.directive.d.ts +3 -3
  24. package/lib/vflow/interfaces/component-node-event.interface.d.ts +32 -0
  25. package/lib/vflow/interfaces/node.interface.d.ts +9 -1
  26. package/lib/vflow/models/node.model.d.ts +14 -1
  27. package/lib/vflow/public-components/custom-node.component.d.ts +20 -0
  28. package/lib/vflow/services/component-event-bus.service.d.ts +9 -0
  29. package/lib/vflow/services/selection.service.d.ts +4 -1
  30. package/lib/vflow/types/background.type.d.ts +24 -0
  31. package/lib/vflow/utils/id.d.ts +1 -0
  32. package/lib/vflow/utils/resizable.d.ts +1 -2
  33. package/lib/vflow/vflow.module.d.ts +12 -11
  34. package/package.json +1 -1
  35. package/public-api.d.ts +3 -0
@@ -1,11 +1,11 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { signal, Injectable, inject, ElementRef, Directive, computed, effect, Input, TemplateRef, EventEmitter, Output, untracked, runInInjectionContext, HostListener, Injector, NgZone, Component, ChangeDetectionStrategy, ViewChild, ContentChild, NgModule } from '@angular/core';
4
+ import { signal, Injectable, inject, ElementRef, Directive, computed, effect, untracked, Input, TemplateRef, EventEmitter, Output, DestroyRef, runInInjectionContext, HostListener, Injector, Component, ChangeDetectionStrategy, ViewChild, ContentChild, NgModule } from '@angular/core';
5
5
  import { select } from 'd3-selection';
6
6
  import { zoomIdentity, zoom } from 'd3-zoom';
7
- import { Subject, tap, switchMap, merge, skip, map, pairwise, filter, distinctUntilChanged, observeOn, asyncScheduler, zip, Observable, fromEvent, animationFrameScheduler, share, Subscription, startWith } from 'rxjs';
8
- import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
7
+ import { Subject, tap, merge, BehaviorSubject, observeOn, animationFrameScheduler, switchMap, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, Observable, fromEvent, share, Subscription, startWith } from 'rxjs';
8
+ import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
9
9
  import { drag } from 'd3-drag';
10
10
  import { path } from 'd3-path';
11
11
  import { __decorate } from 'tslib';
@@ -131,7 +131,7 @@ class SelectionService {
131
131
  this.flowEntitiesService = inject(FlowEntitiesService);
132
132
  this.viewport$ = new Subject();
133
133
  this.resetSelection = this.viewport$.pipe(tap(({ start, end, target }) => {
134
- if (start && end) {
134
+ if (start && end && target) {
135
135
  const delta = SelectionService.delta;
136
136
  const diffX = Math.abs(end.x - start.x);
137
137
  const diffY = Math.abs(end.y - start.y);
@@ -176,8 +176,7 @@ class MapContextDirective {
176
176
  this.zoomableSelection = select(this.host);
177
177
  this.viewportForSelection = {};
178
178
  // under the hood this effect triggers handleZoom, so error throws without this flag
179
- // TODO: hack with timer fixes wrong node scaling (handle positions not matched with content size)
180
- this.manualViewportChangeEffect = effect(() => setTimeout(() => {
179
+ this.manualViewportChangeEffect = effect(() => {
181
180
  const viewport = this.viewportService.writableViewport();
182
181
  const state = viewport.state;
183
182
  if (viewport.changeType === 'initial') {
@@ -190,7 +189,9 @@ class MapContextDirective {
190
189
  }
191
190
  // If only pan provided
192
191
  if ((isDefined(state.x) && isDefined(state.y)) && !isDefined(state.zoom)) {
193
- this.rootSvgSelection.call(this.zoomBehavior.translateTo, state.x, state.y);
192
+ // remain same zoom value
193
+ const zoom = untracked(this.viewportService.readableViewport).zoom;
194
+ this.rootSvgSelection.call(this.zoomBehavior.transform, zoomIdentity.translate(state.x, state.y).scale(zoom));
194
195
  return;
195
196
  }
196
197
  // If whole viewort state provided
@@ -198,7 +199,7 @@ class MapContextDirective {
198
199
  this.rootSvgSelection.call(this.zoomBehavior.transform, zoomIdentity.translate(state.x, state.y).scale(state.zoom));
199
200
  return;
200
201
  }
201
- }), { allowSignalWrites: true });
202
+ }, { allowSignalWrites: true });
202
203
  this.handleZoom = ({ transform }) => {
203
204
  // update public signal for user to read
204
205
  this.viewportService.readableViewport.set(mapTransformToViewportState(transform));
@@ -287,7 +288,7 @@ class DraggableService {
287
288
  deltaY = model.point().y - event.y;
288
289
  })
289
290
  .on('drag', (event) => {
290
- model.point.set({
291
+ model.setPoint({
291
292
  x: round(event.x + deltaX),
292
293
  y: round(event.y + deltaY)
293
294
  });
@@ -479,12 +480,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
479
480
  type: Injectable
480
481
  }] });
481
482
 
483
+ class ComponentEventBusService {
484
+ constructor() {
485
+ this._event$ = new Subject();
486
+ this.event$ = this._event$.asObservable();
487
+ }
488
+ pushEvent(event) {
489
+ this._event$.next(event);
490
+ }
491
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ComponentEventBusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
492
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ComponentEventBusService }); }
493
+ }
494
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ComponentEventBusService, decorators: [{
495
+ type: Injectable
496
+ }] });
497
+
498
+ class CustomNodeComponent {
499
+ constructor() {
500
+ this.eventBus = inject(ComponentEventBusService);
501
+ this.destroyRef = inject(DestroyRef);
502
+ /**
503
+ * Signal with selected state of node
504
+ */
505
+ this.selected = signal(false);
506
+ }
507
+ set _selected(value) {
508
+ this.selected.set(value);
509
+ }
510
+ ngOnInit() {
511
+ this.trackEvents();
512
+ }
513
+ trackEvents() {
514
+ const props = Object.getOwnPropertyNames(this);
515
+ const emitters = new Map();
516
+ for (const prop of props) {
517
+ const field = this[prop];
518
+ if (field instanceof EventEmitter) {
519
+ emitters.set(field, prop);
520
+ }
521
+ }
522
+ merge(...Array.from(emitters.keys()).map(emitter => emitter.pipe(tap((event) => {
523
+ this.eventBus.pushEvent({
524
+ nodeId: this.node.id,
525
+ eventName: emitters.get(emitter),
526
+ eventPayload: event
527
+ });
528
+ }))))
529
+ .pipe(takeUntilDestroyed(this.destroyRef))
530
+ .subscribe();
531
+ }
532
+ ;
533
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
534
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CustomNodeComponent, inputs: { node: "node", _selected: "_selected" }, ngImport: i0 }); }
535
+ }
536
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomNodeComponent, decorators: [{
537
+ type: Directive
538
+ }], propDecorators: { node: [{
539
+ type: Input
540
+ }], _selected: [{
541
+ type: Input
542
+ }] } });
543
+
482
544
  class NodeModel {
545
+ static { this.defaultTypeSize = {
546
+ width: 100,
547
+ height: 50
548
+ }; }
483
549
  constructor(node) {
484
550
  this.node = node;
485
551
  this.flowSettingsService = inject(FlowSettingsService);
486
- this.point = signal({ x: 0, y: 0 });
487
- this.point$ = toObservable(this.point);
552
+ this.internalPoint$ = new BehaviorSubject({ x: 0, y: 0 });
553
+ this.throttledPoint$ = this.internalPoint$.pipe(observeOn(animationFrameScheduler));
554
+ this.point = toSignal(this.throttledPoint$, {
555
+ initialValue: this.internalPoint$.getValue()
556
+ });
557
+ this.point$ = this.throttledPoint$;
488
558
  this.size = signal({ width: 0, height: 0 });
489
559
  this.renderOrder = signal(0);
490
560
  this.selected = signal(false);
@@ -498,10 +568,20 @@ class NodeModel {
498
568
  this.draggable = true;
499
569
  // disabled for configuration for now
500
570
  this.magnetRadius = 20;
501
- this.point.set(node.point);
571
+ this.isComponentType = CustomNodeComponent.isPrototypeOf(this.node.type);
572
+ this.componentTypeInputs = computed(() => {
573
+ return {
574
+ node: this.node,
575
+ _selected: this.selected()
576
+ };
577
+ });
578
+ this.setPoint(node.point);
502
579
  if (isDefined(node.draggable))
503
580
  this.draggable = node.draggable;
504
581
  }
582
+ setPoint(point) {
583
+ this.internalPoint$.next(point);
584
+ }
505
585
  }
506
586
 
507
587
  class EdgeLabelModel {
@@ -1058,10 +1138,10 @@ class HandleModel {
1058
1138
  }
1059
1139
  }
1060
1140
 
1061
- function resizable(elems, zone) {
1141
+ function resizable(elems) {
1062
1142
  return new Observable((subscriber) => {
1063
1143
  let ro = new ResizeObserver((entries) => {
1064
- zone.run(() => subscriber.next(entries));
1144
+ subscriber.next(entries);
1065
1145
  });
1066
1146
  elems.forEach(e => ro.observe(e));
1067
1147
  return () => ro.disconnect();
@@ -1254,7 +1334,6 @@ class NodeComponent {
1254
1334
  constructor() {
1255
1335
  this.injector = inject(Injector);
1256
1336
  this.handleService = inject(HandleService);
1257
- this.zone = inject(NgZone);
1258
1337
  this.draggableService = inject(DraggableService);
1259
1338
  this.flowStatusService = inject(FlowStatusService);
1260
1339
  this.flowEntitiesService = inject(FlowEntitiesService);
@@ -1264,13 +1343,15 @@ class NodeComponent {
1264
1343
  this.hostRef = inject(ElementRef);
1265
1344
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
1266
1345
  this.flowStatusService.status().state === 'connection-validation');
1346
+ this.styleWidth = computed(() => `${this.nodeModel.size().width}px`);
1347
+ this.styleHeight = computed(() => `${this.nodeModel.size().height}px`);
1267
1348
  this.subscription = new Subscription();
1268
1349
  }
1269
1350
  ngOnInit() {
1270
1351
  this.handleService.node.set(this.nodeModel);
1271
1352
  this.draggableService.toggleDraggable(this.hostRef.nativeElement, this.nodeModel);
1272
1353
  const sub = this.nodeModel.handles$
1273
- .pipe(switchMap((handles) => resizable(handles.map(h => h.parentReference), this.zone)
1354
+ .pipe(switchMap((handles) => resizable(handles.map(h => h.parentReference))
1274
1355
  .pipe(map(() => handles))), tap((handles) => handles.forEach(h => h.updateParent())))
1275
1356
  .subscribe();
1276
1357
  this.subscription.add(sub);
@@ -1278,11 +1359,13 @@ class NodeComponent {
1278
1359
  ngAfterViewInit() {
1279
1360
  this.setInitialHandles();
1280
1361
  if (this.nodeModel.node.type === 'default') {
1281
- const { width, height } = this.nodeContentRef.nativeElement.getBBox();
1282
- this.nodeModel.size.set({ width, height });
1362
+ this.nodeModel.size.set({
1363
+ width: this.nodeModel.node.width ?? NodeModel.defaultTypeSize.width,
1364
+ height: this.nodeModel.node.height ?? NodeModel.defaultTypeSize.height
1365
+ });
1283
1366
  }
1284
- if (this.nodeModel.node.type === 'html-template') {
1285
- const sub = resizable([this.htmlWrapperRef.nativeElement], this.zone)
1367
+ if (this.nodeModel.node.type === 'html-template' || this.nodeModel.isComponentType) {
1368
+ const sub = resizable([this.htmlWrapperRef.nativeElement])
1286
1369
  .pipe(startWith(null), tap(() => {
1287
1370
  const width = this.htmlWrapperRef.nativeElement.clientWidth;
1288
1371
  const height = this.htmlWrapperRef.nativeElement.clientHeight;
@@ -1369,7 +1452,7 @@ class NodeComponent {
1369
1452
  }
1370
1453
  }
1371
1454
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1372
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeHtmlTemplate: "nodeHtmlTemplate" }, providers: [HandleService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n width=\"100\"\n height=\"50\"\n (mousedown)=\"pullNode(); selectNode()\"\n>\n <div\n #htmlWrapper\n class=\"default-node\"\n [class.default-node_selected]=\"nodeModel.selected()\"\n [innerHTML]=\"nodeModel.node.text ?? ''\"\n ></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\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)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (pointerOver)=\"validateTargetHandle(handle)\"\n (pointerOut)=\"resetValidateTargetHandle(handle)\"\n />\n</ng-container>\n\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{max-width:100px;max-height:100px;width:100px;height:50px;border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}.default-node_selected{border-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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 }); }
1455
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeHtmlTemplate: "nodeHtmlTemplate" }, providers: [HandleService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<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 (mousedown)=\"pullNode(); selectNode()\"\n>\n <div\n #htmlWrapper\n class=\"default-node\"\n [class.default-node_selected]=\"nodeModel.selected()\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n [style.max-width]=\"styleWidth()\"\n [style.max-height]=\"styleHeight()\"\n [innerHTML]=\"nodeModel.node.text ?? ''\"\n ></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\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)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (pointerOver)=\"validateTargetHandle(handle)\"\n (pointerOut)=\"resetValidateTargetHandle(handle)\"\n />\n</ng-container>\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}.default-node_selected{border-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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 }); }
1373
1456
  }
1374
1457
  __decorate([
1375
1458
  Microtask
@@ -1379,7 +1462,7 @@ __decorate([
1379
1462
  ], NodeComponent.prototype, "setInitialHandles", null);
1380
1463
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
1381
1464
  type: Component,
1382
- args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService], template: "<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n width=\"100\"\n height=\"50\"\n (mousedown)=\"pullNode(); selectNode()\"\n>\n <div\n #htmlWrapper\n class=\"default-node\"\n [class.default-node_selected]=\"nodeModel.selected()\"\n [innerHTML]=\"nodeModel.node.text ?? ''\"\n ></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\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)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (pointerOver)=\"validateTargetHandle(handle)\"\n (pointerOut)=\"resetValidateTargetHandle(handle)\"\n />\n</ng-container>\n\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{max-width:100px;max-height:100px;width:100px;height:50px;border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}.default-node_selected{border-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
1465
+ args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService], template: "<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 (mousedown)=\"pullNode(); selectNode()\"\n>\n <div\n #htmlWrapper\n class=\"default-node\"\n [class.default-node_selected]=\"nodeModel.selected()\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n [style.max-width]=\"styleWidth()\"\n [style.max-height]=\"styleHeight()\"\n [innerHTML]=\"nodeModel.node.text ?? ''\"\n ></div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeHtmlTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngTemplateOutlet]=\"nodeHtmlTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div #htmlWrapper class=\"wrapper\">\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\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)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (pointerEnd)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"handle.rawHandle.type === 'target' && showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (pointerOver)=\"validateTargetHandle(handle)\"\n (pointerOut)=\"resetValidateTargetHandle(handle)\"\n />\n</ng-container>\n", styles: [".wrapper{width:max-content}.magnet{opacity:0}.default-node{border:1.5px solid #1b262c;border-radius:5px;display:flex;align-items:center;justify-content:center;color:#000;background-color:#fff}.default-node_selected{border-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
1383
1466
  }], propDecorators: { nodeModel: [{
1384
1467
  type: Input
1385
1468
  }], nodeHtmlTemplate: [{
@@ -1651,6 +1734,71 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1651
1734
  args: [{ required: true }]
1652
1735
  }] } });
1653
1736
 
1737
+ function id() {
1738
+ const randomLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));
1739
+ return randomLetter + Date.now();
1740
+ }
1741
+
1742
+ const defaultBg = '#fff';
1743
+ const defaultGap = 20;
1744
+ const defaultDotSize = 2;
1745
+ const defaultDotColor = 'rgb(177, 177, 183)';
1746
+ class BackgroundComponent {
1747
+ set background(value) {
1748
+ this.backgroundSignal.set(value);
1749
+ }
1750
+ constructor() {
1751
+ this.viewportService = inject(ViewportService);
1752
+ this.rootSvg = inject(RootSvgReferenceDirective).element;
1753
+ this.backgroundSignal = signal({ type: 'solid', color: defaultBg });
1754
+ this.scaledGap = computed(() => {
1755
+ const background = this.backgroundSignal();
1756
+ if (background.type === 'dots') {
1757
+ const zoom = this.viewportService.readableViewport().zoom;
1758
+ return zoom * (background.gap ?? defaultGap);
1759
+ }
1760
+ return 0;
1761
+ });
1762
+ this.x = computed(() => this.viewportService.readableViewport().x % this.scaledGap());
1763
+ this.y = computed(() => this.viewportService.readableViewport().y % this.scaledGap());
1764
+ this.patternColor = computed(() => this.backgroundSignal().color ?? defaultDotColor);
1765
+ this.patternSize = computed(() => {
1766
+ const background = this.backgroundSignal();
1767
+ if (background.type === 'dots') {
1768
+ return (this.viewportService.readableViewport().zoom * (background.size ?? defaultDotSize)) / 2;
1769
+ }
1770
+ return 0;
1771
+ });
1772
+ // Without ID there will be pattern collision for several flows on the page
1773
+ // Later pattern ID may be exposed to API
1774
+ this.patternId = id();
1775
+ this.patternUrl = `url(#${this.patternId})`;
1776
+ effect(() => {
1777
+ const background = this.backgroundSignal();
1778
+ if (background.type === 'dots') {
1779
+ this.rootSvg.style.backgroundColor = background.backgroundColor ?? defaultBg;
1780
+ }
1781
+ if (background.type === 'solid') {
1782
+ this.rootSvg.style.backgroundColor = background.color;
1783
+ }
1784
+ });
1785
+ }
1786
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: BackgroundComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1787
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "16.2.12", type: BackgroundComponent, selector: "g[background]", inputs: { background: ["background", "background", transform] }, ngImport: i0, template: "<ng-container *ngIf=\"backgroundSignal().type === 'dots'\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"x()\"\n [attr.y]=\"y()\"\n [attr.width]=\"scaledGap()\"\n [attr.height]=\"scaledGap()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:circle\n [attr.cx]=\"patternSize()\"\n [attr.cy]=\"patternSize()\"\n [attr.r]=\"patternSize()\"\n [attr.fill]=\"patternColor()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n</ng-container>\n", dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
1788
+ }
1789
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: BackgroundComponent, decorators: [{
1790
+ type: Component,
1791
+ args: [{ selector: 'g[background]', template: "<ng-container *ngIf=\"backgroundSignal().type === 'dots'\">\n <svg:pattern\n [attr.id]=\"patternId\"\n [attr.x]=\"x()\"\n [attr.y]=\"y()\"\n [attr.width]=\"scaledGap()\"\n [attr.height]=\"scaledGap()\"\n patternUnits=\"userSpaceOnUse\"\n >\n <svg:circle\n [attr.cx]=\"patternSize()\"\n [attr.cy]=\"patternSize()\"\n [attr.r]=\"patternSize()\"\n [attr.fill]=\"patternColor()\"\n />\n </svg:pattern>\n\n <svg:rect\n x=\"0\"\n y=\"0\"\n width=\"100%\"\n height=\"100%\"\n [attr.fill]=\"patternUrl\"\n />\n</ng-container>\n" }]
1792
+ }], ctorParameters: function () { return []; }, propDecorators: { background: [{
1793
+ type: Input,
1794
+ args: [{ required: true, transform }]
1795
+ }] } });
1796
+ function transform(background) {
1797
+ return typeof background === 'string'
1798
+ ? { type: 'solid', color: background }
1799
+ : background;
1800
+ }
1801
+
1654
1802
  // TODO: too general purpose nane
1655
1803
  class RootSvgContextDirective {
1656
1804
  constructor() {
@@ -1721,6 +1869,7 @@ class VflowComponent {
1721
1869
  this.edgesChangeService = inject(EdgeChangesService);
1722
1870
  this.nodeRenderingService = inject(NodeRenderingService);
1723
1871
  this.flowSettingsService = inject(FlowSettingsService);
1872
+ this.componentEventBusService = inject(ComponentEventBusService);
1724
1873
  this.injector = inject(Injector);
1725
1874
  /**
1726
1875
  * Minimum zoom value
@@ -1731,12 +1880,20 @@ class VflowComponent {
1731
1880
  */
1732
1881
  this.maxZoom = 3;
1733
1882
  /**
1734
- * Background color for flow
1883
+ * Background for flow
1735
1884
  */
1736
- this.background = '#FFFFFF';
1885
+ this.background = '#fff';
1737
1886
  this.nodeModels = computed(() => this.nodeRenderingService.nodes());
1738
1887
  this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
1739
1888
  // #endregion
1889
+ // #region OUTPUTS
1890
+ /**
1891
+ * Event that accumulates all custom node events
1892
+ *
1893
+ * @experimental
1894
+ */
1895
+ this.onComponentNodeEvent = this.componentEventBusService.event$; // TODO: research how to remove as any
1896
+ // #endregion
1740
1897
  // #region SIGNAL_API
1741
1898
  /**
1742
1899
  * Signal for reading viewport change
@@ -1873,7 +2030,7 @@ class VflowComponent {
1873
2030
  return edge;
1874
2031
  }
1875
2032
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1876
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "16.2.12", type: VflowComponent, selector: "vflow", inputs: { view: "view", minZoom: "minZoom", maxZoom: "maxZoom", handlePositions: "handlePositions", background: "background", entitiesSelectable: "entitiesSelectable", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], nodes: "nodes", edges: "edges" }, providers: [
2033
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "16.2.12", type: VflowComponent, selector: "vflow", inputs: { view: "view", minZoom: "minZoom", maxZoom: "maxZoom", handlePositions: "handlePositions", background: "background", entitiesSelectable: "entitiesSelectable", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], nodes: "nodes", edges: "edges" }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
1877
2034
  DraggableService,
1878
2035
  ViewportService,
1879
2036
  FlowStatusService,
@@ -1882,8 +2039,9 @@ class VflowComponent {
1882
2039
  EdgeChangesService,
1883
2040
  NodeRenderingService,
1884
2041
  SelectionService,
1885
- FlowSettingsService
1886
- ], queries: [{ propertyName: "nodeHtmlDirective", first: true, predicate: NodeHtmlTemplateDirective, 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 }], 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.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 class=\"root-svg\"\n #flow\n [style.backgroundColor]=\"background\"\n [attr.width]=\"flowWidth()\"\n [attr.height]=\"flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\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 [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\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: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeHtmlTemplate"] }, { 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: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]", inputs: ["minZoom", "maxZoom"] }, { kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2042
+ FlowSettingsService,
2043
+ ComponentEventBusService
2044
+ ], queries: [{ propertyName: "nodeHtmlDirective", first: true, predicate: NodeHtmlTemplateDirective, 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 }], 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.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 class=\"root-svg\"\n #flow\n [attr.width]=\"flowWidth()\"\n [attr.height]=\"flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g [background]=\"background\"/>\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\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 [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\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: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: NodeComponent, selector: "g[node]", inputs: ["nodeModel", "nodeHtmlTemplate"] }, { 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]", inputs: ["background"] }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]", inputs: ["minZoom", "maxZoom"] }, { kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1887
2045
  }
1888
2046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
1889
2047
  type: Component,
@@ -1896,11 +2054,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1896
2054
  EdgeChangesService,
1897
2055
  NodeRenderingService,
1898
2056
  SelectionService,
1899
- FlowSettingsService
2057
+ FlowSettingsService,
2058
+ ComponentEventBusService
1900
2059
  ], hostDirectives: [
1901
2060
  connectionControllerHostDirective,
1902
2061
  changesControllerHostDirective
1903
- ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n class=\"root-svg\"\n #flow\n [style.backgroundColor]=\"background\"\n [attr.width]=\"flowWidth()\"\n [attr.height]=\"flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\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 [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\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"] }]
2062
+ ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n class=\"root-svg\"\n #flow\n [attr.width]=\"flowWidth()\"\n [attr.height]=\"flowHeight()\"\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g [background]=\"background\"/>\n\n <svg:g\n mapContext\n spacePointContext\n [minZoom]=\"minZoom\"\n [maxZoom]=\"maxZoom\"\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 [nodeHtmlTemplate]=\"nodeHtmlDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\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"] }]
1904
2063
  }], propDecorators: { view: [{
1905
2064
  type: Input
1906
2065
  }], minZoom: [{
@@ -1921,6 +2080,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1921
2080
  args: [{ required: true }]
1922
2081
  }], edges: [{
1923
2082
  type: Input
2083
+ }], onComponentNodeEvent: [{
2084
+ type: Output
1924
2085
  }], nodeHtmlDirective: [{
1925
2086
  type: ContentChild,
1926
2087
  args: [NodeHtmlTemplateDirective]
@@ -2019,7 +2180,8 @@ const components = [
2019
2180
  EdgeLabelComponent,
2020
2181
  ConnectionComponent,
2021
2182
  HandleComponent,
2022
- DefsComponent
2183
+ DefsComponent,
2184
+ BackgroundComponent
2023
2185
  ];
2024
2186
  const directives = [
2025
2187
  SpacePointContextDirective,
@@ -2046,7 +2208,8 @@ class VflowModule {
2046
2208
  EdgeLabelComponent,
2047
2209
  ConnectionComponent,
2048
2210
  HandleComponent,
2049
- DefsComponent, SpacePointContextDirective,
2211
+ DefsComponent,
2212
+ BackgroundComponent, SpacePointContextDirective,
2050
2213
  MapContextDirective,
2051
2214
  RootSvgReferenceDirective,
2052
2215
  RootSvgContextDirective,
@@ -2086,5 +2249,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2086
2249
  * Generated bundle index. Do not edit.
2087
2250
  */
2088
2251
 
2089
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
2252
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomNodeComponent, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
2090
2253
  //# sourceMappingURL=ngx-vflow.mjs.map