ngx-vflow 0.4.0 → 0.6.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/node/node.component.mjs +12 -6
  2. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +51 -9
  3. package/esm2022/lib/vflow/directives/changes-controller.directive.mjs +115 -14
  4. package/esm2022/lib/vflow/directives/map-context.directive.mjs +7 -6
  5. package/esm2022/lib/vflow/directives/pointer.directive.mjs +84 -0
  6. package/esm2022/lib/vflow/directives/root-pointer.directive.mjs +42 -0
  7. package/esm2022/lib/vflow/directives/root-svg-context.directive.mjs +5 -2
  8. package/esm2022/lib/vflow/directives/selectable.directive.mjs +1 -1
  9. package/esm2022/lib/vflow/directives/space-point-context.directive.mjs +10 -6
  10. package/esm2022/lib/vflow/interfaces/component-node-event.interface.mjs +2 -0
  11. package/esm2022/lib/vflow/interfaces/node.interface.mjs +1 -1
  12. package/esm2022/lib/vflow/models/node.model.mjs +25 -5
  13. package/esm2022/lib/vflow/public-components/custom-node.component.mjs +51 -0
  14. package/esm2022/lib/vflow/services/component-event-bus.service.mjs +18 -0
  15. package/esm2022/lib/vflow/services/draggable.service.mjs +2 -2
  16. package/esm2022/lib/vflow/services/selection.service.mjs +2 -2
  17. package/esm2022/lib/vflow/vflow.module.mjs +9 -3
  18. package/esm2022/public-api.mjs +3 -1
  19. package/fesm2022/ngx-vflow.mjs +397 -42
  20. package/fesm2022/ngx-vflow.mjs.map +1 -1
  21. package/lib/vflow/components/node/node.component.d.ts +10 -9
  22. package/lib/vflow/components/vflow/vflow.component.d.ts +9 -2
  23. package/lib/vflow/directives/changes-controller.directive.d.ts +35 -9
  24. package/lib/vflow/directives/pointer.directive.d.ts +21 -0
  25. package/lib/vflow/directives/root-pointer.directive.d.ts +40 -0
  26. package/lib/vflow/directives/space-point-context.directive.d.ts +13 -3
  27. package/lib/vflow/interfaces/component-node-event.interface.d.ts +32 -0
  28. package/lib/vflow/interfaces/node.interface.d.ts +9 -1
  29. package/lib/vflow/models/node.model.d.ts +14 -1
  30. package/lib/vflow/public-components/custom-node.component.d.ts +20 -0
  31. package/lib/vflow/services/component-event-bus.service.d.ts +9 -0
  32. package/lib/vflow/services/selection.service.d.ts +4 -1
  33. package/lib/vflow/vflow.module.d.ts +5 -3
  34. package/package.json +1 -1
  35. package/public-api.d.ts +2 -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, Injector, NgZone, Component, ChangeDetectionStrategy, ViewChild, HostListener, ContentChild, NgModule } from '@angular/core';
4
+ import { signal, Injectable, inject, ElementRef, Directive, computed, effect, untracked, Input, TemplateRef, EventEmitter, Output, DestroyRef, runInInjectionContext, HostListener, Injector, NgZone, 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, Subscription, startWith, fromEvent } 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 {
@@ -812,20 +892,50 @@ class ChangesControllerDirective {
812
892
  /**
813
893
  * Watch nodes change
814
894
  */
815
- this.onNodesChange = new EventEmitter();
895
+ this.onNodesChange = this.nodesChangeService.changes$;
896
+ this.onNodesChangePosition = this.nodeChangesOfType('position');
897
+ this.onNodesChangePositionSignle = this.singleChange(this.nodeChangesOfType('position'));
898
+ this.onNodesChangePositionMany = this.manyChanges(this.nodeChangesOfType('position'));
899
+ this.onNodesChangeAdd = this.nodeChangesOfType('add');
900
+ this.onNodesChangeAddSingle = this.singleChange(this.nodeChangesOfType('add'));
901
+ this.onNodesChangeAddMany = this.manyChanges(this.nodeChangesOfType('add'));
902
+ this.onNodesChangeRemove = this.nodeChangesOfType('remove');
903
+ this.onNodesChangeRemoveSingle = this.singleChange(this.nodeChangesOfType('remove'));
904
+ this.onNodesChangeRemoveMany = this.manyChanges(this.nodeChangesOfType('remove'));
905
+ this.onNodesChangeSelect = this.nodeChangesOfType('select');
906
+ this.onNodesChangeSelectSingle = this.singleChange(this.nodeChangesOfType('select'));
907
+ this.onNodesChangeSelectMany = this.manyChanges(this.nodeChangesOfType('select'));
816
908
  /**
817
- * Watch nodes change
909
+ * Watch edges change
818
910
  */
819
- this.onEdgesChange = new EventEmitter();
820
- this.nodesChangeProxySubscription = this.nodesChangeService.changes$
821
- .pipe(tap((changes) => this.onNodesChange.emit(changes)), takeUntilDestroyed())
822
- .subscribe();
823
- this.edgesChangeProxySubscription = this.edgesChangeService.changes$
824
- .pipe(tap((changes) => this.onEdgesChange.emit(changes)), takeUntilDestroyed())
825
- .subscribe();
911
+ this.onEdgesChange = this.edgesChangeService.changes$;
912
+ this.onNodesChangeDetached = this.edgeChangesOfType('detached');
913
+ this.onNodesChangeDetachedSingle = this.singleChange(this.edgeChangesOfType('detached'));
914
+ this.onNodesChangeDetachedMany = this.manyChanges(this.edgeChangesOfType('detached'));
915
+ this.onEdgesChangeAdd = this.edgeChangesOfType('add');
916
+ this.onEdgeChangeAddSingle = this.singleChange(this.edgeChangesOfType('add'));
917
+ this.onEdgeChangeAddMany = this.manyChanges(this.edgeChangesOfType('add'));
918
+ this.onEdgeChangeRemove = this.edgeChangesOfType('remove');
919
+ this.onEdgeChangeRemoveSingle = this.singleChange(this.edgeChangesOfType('remove'));
920
+ this.onEdgeChangeRemoveMany = this.manyChanges(this.edgeChangesOfType('remove'));
921
+ this.onEdgeChangeSelect = this.edgeChangesOfType('select');
922
+ this.onEdgeChangeSelectSingle = this.singleChange(this.edgeChangesOfType('select'));
923
+ this.onEdgeChangeSelectMany = this.manyChanges(this.edgeChangesOfType('select'));
924
+ }
925
+ nodeChangesOfType(type) {
926
+ return this.nodesChangeService.changes$.pipe(map(changes => changes.filter((c) => c.type === type)), filter(changes => !!changes.length));
927
+ }
928
+ edgeChangesOfType(type) {
929
+ return this.edgesChangeService.changes$.pipe(map(changes => changes.filter((c) => c.type === type)), filter(changes => !!changes.length));
930
+ }
931
+ singleChange(changes$) {
932
+ return changes$.pipe(filter(changes => changes.length === 1), map(([first]) => first));
933
+ }
934
+ manyChanges(changes$) {
935
+ return changes$.pipe(filter(changes => changes.length > 1));
826
936
  }
827
937
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChangesControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
828
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ChangesControllerDirective, isStandalone: true, selector: "[changesController]", outputs: { onNodesChange: "onNodesChange", onEdgesChange: "onEdgesChange" }, ngImport: i0 }); }
938
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ChangesControllerDirective, isStandalone: true, selector: "[changesController]", outputs: { onNodesChange: "onNodesChange", onNodesChangePosition: "onNodesChange.position", onNodesChangePositionSignle: "onNodesChange.position.single", onNodesChangePositionMany: "onNodesChange.position.many", onNodesChangeAdd: "onNodesChange.add", onNodesChangeAddSingle: "onNodesChange.add.single", onNodesChangeAddMany: "onNodesChange.add.many", onNodesChangeRemove: "onNodesChange.remove", onNodesChangeRemoveSingle: "onNodesChange.remove.single", onNodesChangeRemoveMany: "onNodesChange.remove.many", onNodesChangeSelect: "onNodesChange.select", onNodesChangeSelectSingle: "onNodesChange.select.single", onNodesChangeSelectMany: "onNodesChange.select.many", onEdgesChange: "onEdgesChange", onNodesChangeDetached: "onEdgesChange.detached", onNodesChangeDetachedSingle: "onEdgesChange.detached.single", onNodesChangeDetachedMany: "onEdgesChange.detached.many", onEdgesChangeAdd: "onEdgesChange.add", onEdgeChangeAddSingle: "onEdgesChange.add.single", onEdgeChangeAddMany: "onEdgesChange.add.many", onEdgeChangeRemove: "onEdgesChange.remove", onEdgeChangeRemoveSingle: "onEdgesChange.remove.single", onEdgeChangeRemoveMany: "onEdgesChange.remove.many", onEdgeChangeSelect: "onEdgesChange.select", onEdgeChangeSelectSingle: "onEdgesChange.select.single", onEdgeChangeSelectMany: "onEdgesChange.select.many" }, ngImport: i0 }); }
829
939
  }
830
940
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChangesControllerDirective, decorators: [{
831
941
  type: Directive,
@@ -835,8 +945,80 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
835
945
  }]
836
946
  }], propDecorators: { onNodesChange: [{
837
947
  type: Output
948
+ }], onNodesChangePosition: [{
949
+ type: Output,
950
+ args: ['onNodesChange.position']
951
+ }], onNodesChangePositionSignle: [{
952
+ type: Output,
953
+ args: ['onNodesChange.position.single']
954
+ }], onNodesChangePositionMany: [{
955
+ type: Output,
956
+ args: ['onNodesChange.position.many']
957
+ }], onNodesChangeAdd: [{
958
+ type: Output,
959
+ args: ['onNodesChange.add']
960
+ }], onNodesChangeAddSingle: [{
961
+ type: Output,
962
+ args: ['onNodesChange.add.single']
963
+ }], onNodesChangeAddMany: [{
964
+ type: Output,
965
+ args: ['onNodesChange.add.many']
966
+ }], onNodesChangeRemove: [{
967
+ type: Output,
968
+ args: ['onNodesChange.remove']
969
+ }], onNodesChangeRemoveSingle: [{
970
+ type: Output,
971
+ args: ['onNodesChange.remove.single']
972
+ }], onNodesChangeRemoveMany: [{
973
+ type: Output,
974
+ args: ['onNodesChange.remove.many']
975
+ }], onNodesChangeSelect: [{
976
+ type: Output,
977
+ args: ['onNodesChange.select']
978
+ }], onNodesChangeSelectSingle: [{
979
+ type: Output,
980
+ args: ['onNodesChange.select.single']
981
+ }], onNodesChangeSelectMany: [{
982
+ type: Output,
983
+ args: ['onNodesChange.select.many']
838
984
  }], onEdgesChange: [{
839
985
  type: Output
986
+ }], onNodesChangeDetached: [{
987
+ type: Output,
988
+ args: ['onEdgesChange.detached']
989
+ }], onNodesChangeDetachedSingle: [{
990
+ type: Output,
991
+ args: ['onEdgesChange.detached.single']
992
+ }], onNodesChangeDetachedMany: [{
993
+ type: Output,
994
+ args: ['onEdgesChange.detached.many']
995
+ }], onEdgesChangeAdd: [{
996
+ type: Output,
997
+ args: ['onEdgesChange.add']
998
+ }], onEdgeChangeAddSingle: [{
999
+ type: Output,
1000
+ args: ['onEdgesChange.add.single']
1001
+ }], onEdgeChangeAddMany: [{
1002
+ type: Output,
1003
+ args: ['onEdgesChange.add.many']
1004
+ }], onEdgeChangeRemove: [{
1005
+ type: Output,
1006
+ args: ['onEdgesChange.remove']
1007
+ }], onEdgeChangeRemoveSingle: [{
1008
+ type: Output,
1009
+ args: ['onEdgesChange.remove.single']
1010
+ }], onEdgeChangeRemoveMany: [{
1011
+ type: Output,
1012
+ args: ['onEdgesChange.remove.many']
1013
+ }], onEdgeChangeSelect: [{
1014
+ type: Output,
1015
+ args: ['onEdgesChange.select']
1016
+ }], onEdgeChangeSelectSingle: [{
1017
+ type: Output,
1018
+ args: ['onEdgesChange.select.single']
1019
+ }], onEdgeChangeSelectMany: [{
1020
+ type: Output,
1021
+ args: ['onEdgesChange.select.many']
840
1022
  }] } });
841
1023
 
842
1024
  class NodeRenderingService {
@@ -1030,6 +1212,124 @@ function getChildStrokeWidth(element) {
1030
1212
  return 0;
1031
1213
  }
1032
1214
 
1215
+ class RootPointerDirective {
1216
+ constructor() {
1217
+ this.host = inject(ElementRef).nativeElement;
1218
+ this.initialTouch$ = new Subject();
1219
+ // TODO: do not emit if mouse not down
1220
+ this.mouseMovement$ = fromEvent(this.host, 'mousemove').pipe(map(event => ({
1221
+ x: event.clientX,
1222
+ y: event.clientY,
1223
+ originalEvent: event
1224
+ })), observeOn(animationFrameScheduler), share());
1225
+ this.touchMovement$ = merge(this.initialTouch$, fromEvent(this.host, 'touchmove')).pipe(tap((event) => event.preventDefault()), map((originalEvent) => {
1226
+ const x = originalEvent.touches[0]?.clientX ?? 0;
1227
+ const y = originalEvent.touches[0]?.clientY ?? 0;
1228
+ const target = document.elementFromPoint(x, y);
1229
+ return { x, y, target, originalEvent };
1230
+ }), observeOn(animationFrameScheduler), share());
1231
+ this.touchEnd$ = fromEvent(this.host, 'touchend').pipe(map((originalEvent) => {
1232
+ const x = originalEvent.changedTouches[0]?.clientX ?? 0;
1233
+ const y = originalEvent.changedTouches[0]?.clientY ?? 0;
1234
+ const target = document.elementFromPoint(x, y);
1235
+ return { x, y, target, originalEvent };
1236
+ }), share());
1237
+ this.pointerMovement$ = merge(this.mouseMovement$, this.touchMovement$);
1238
+ }
1239
+ /**
1240
+ * We should know when user started a touch in order to not
1241
+ * show old touch position when connection creation is started
1242
+ */
1243
+ setInitialTouch(event) {
1244
+ this.initialTouch$.next(event);
1245
+ }
1246
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootPointerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1247
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RootPointerDirective, selector: "svg[rootPointer]", ngImport: i0 }); }
1248
+ }
1249
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootPointerDirective, decorators: [{
1250
+ type: Directive,
1251
+ args: [{ selector: 'svg[rootPointer]' }]
1252
+ }] });
1253
+
1254
+ class PointerDirective {
1255
+ constructor() {
1256
+ this.hostElement = inject(ElementRef).nativeElement;
1257
+ this.pointerMovementDirective = inject(RootPointerDirective);
1258
+ this.pointerOver = new EventEmitter();
1259
+ this.pointerOut = new EventEmitter();
1260
+ this.pointerStart = new EventEmitter();
1261
+ this.pointerEnd = new EventEmitter();
1262
+ this.wasPointerOver = false;
1263
+ // TODO check if i could avoid global touch end
1264
+ this.touchEnd = this.pointerMovementDirective.touchEnd$
1265
+ .pipe(filter(({ target }) => target === this.hostElement), tap(({ originalEvent }) => this.pointerEnd.emit(originalEvent)), takeUntilDestroyed())
1266
+ .subscribe();
1267
+ this.touchOverOut = this.pointerMovementDirective.touchMovement$
1268
+ .pipe(tap(({ target, originalEvent }) => {
1269
+ this.handleTouchOverAndOut(target, originalEvent);
1270
+ }), takeUntilDestroyed())
1271
+ .subscribe();
1272
+ }
1273
+ onPointerStart(event) {
1274
+ this.pointerStart.emit(event);
1275
+ if (event instanceof TouchEvent) {
1276
+ this.pointerMovementDirective.setInitialTouch(event);
1277
+ }
1278
+ }
1279
+ onPointerEnd(event) {
1280
+ this.pointerEnd.emit(event);
1281
+ }
1282
+ onMouseOver(event) {
1283
+ this.pointerOver.emit(event);
1284
+ }
1285
+ onMouseOut(event) {
1286
+ this.pointerOut.emit(event);
1287
+ }
1288
+ // TODO: dirty imperative implementation
1289
+ handleTouchOverAndOut(target, event) {
1290
+ if (target === this.hostElement) {
1291
+ this.pointerOver.emit(event);
1292
+ this.wasPointerOver = true;
1293
+ }
1294
+ else {
1295
+ // should not emit before pointerOver
1296
+ if (this.wasPointerOver) {
1297
+ this.pointerOut.emit(event);
1298
+ }
1299
+ this.wasPointerOver = false;
1300
+ }
1301
+ }
1302
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PointerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1303
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: { pointerOver: "pointerOver", pointerOut: "pointerOut", pointerStart: "pointerStart", pointerEnd: "pointerEnd" }, host: { listeners: { "mousedown": "onPointerStart($event)", "touchstart": "onPointerStart($event)", "mouseup": "onPointerEnd($event)", "mouseover": "onMouseOver($event)", "mouseout": "onMouseOut($event)" } }, ngImport: i0 }); }
1304
+ }
1305
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PointerDirective, decorators: [{
1306
+ type: Directive,
1307
+ args: [{ selector: '[pointerStart], [pointerEnd], [pointerOver], [pointerOut]' }]
1308
+ }], propDecorators: { pointerOver: [{
1309
+ type: Output
1310
+ }], pointerOut: [{
1311
+ type: Output
1312
+ }], pointerStart: [{
1313
+ type: Output
1314
+ }], pointerEnd: [{
1315
+ type: Output
1316
+ }], onPointerStart: [{
1317
+ type: HostListener,
1318
+ args: ['mousedown', ['$event']]
1319
+ }, {
1320
+ type: HostListener,
1321
+ args: ['touchstart', ['$event']]
1322
+ }], onPointerEnd: [{
1323
+ type: HostListener,
1324
+ args: ['mouseup', ['$event']]
1325
+ }], onMouseOver: [{
1326
+ type: HostListener,
1327
+ args: ['mouseover', ['$event']]
1328
+ }], onMouseOut: [{
1329
+ type: HostListener,
1330
+ args: ['mouseout', ['$event']]
1331
+ }] } });
1332
+
1033
1333
  class NodeComponent {
1034
1334
  constructor() {
1035
1335
  this.injector = inject(Injector);
@@ -1044,6 +1344,8 @@ class NodeComponent {
1044
1344
  this.hostRef = inject(ElementRef);
1045
1345
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
1046
1346
  this.flowStatusService.status().state === 'connection-validation');
1347
+ this.styleWidth = computed(() => `${this.nodeModel.size().width}px`);
1348
+ this.styleHeight = computed(() => `${this.nodeModel.size().height}px`);
1047
1349
  this.subscription = new Subscription();
1048
1350
  }
1049
1351
  ngOnInit() {
@@ -1058,10 +1360,12 @@ class NodeComponent {
1058
1360
  ngAfterViewInit() {
1059
1361
  this.setInitialHandles();
1060
1362
  if (this.nodeModel.node.type === 'default') {
1061
- const { width, height } = this.nodeContentRef.nativeElement.getBBox();
1062
- this.nodeModel.size.set({ width, height });
1363
+ this.nodeModel.size.set({
1364
+ width: this.nodeModel.node.width ?? NodeModel.defaultTypeSize.width,
1365
+ height: this.nodeModel.node.height ?? NodeModel.defaultTypeSize.height
1366
+ });
1063
1367
  }
1064
- if (this.nodeModel.node.type === 'html-template') {
1368
+ if (this.nodeModel.node.type === 'html-template' || this.nodeModel.isComponentType) {
1065
1369
  const sub = resizable([this.htmlWrapperRef.nativeElement], this.zone)
1066
1370
  .pipe(startWith(null), tap(() => {
1067
1371
  const width = this.htmlWrapperRef.nativeElement.clientWidth;
@@ -1149,7 +1453,7 @@ class NodeComponent {
1149
1453
  }
1150
1454
  }
1151
1455
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1152
- 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 (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"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 (mouseup)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (mouseover)=\"validateTargetHandle(handle)\"\n (mouseout)=\"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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1456
+ 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 }); }
1153
1457
  }
1154
1458
  __decorate([
1155
1459
  Microtask
@@ -1159,7 +1463,7 @@ __decorate([
1159
1463
  ], NodeComponent.prototype, "setInitialHandles", null);
1160
1464
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
1161
1465
  type: Component,
1162
- 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 (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"handle.rawHandle.type === 'target' ? endConnection() : null\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (mousedown)=\"handle.rawHandle.type === 'source' ? startConnection($event, handle) : null\"\n (mouseup)=\"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 (mouseup)=\"endConnection(); resetValidateTargetHandle(handle)\"\n (mouseover)=\"validateTargetHandle(handle)\"\n (mouseout)=\"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"] }]
1466
+ 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"] }]
1163
1467
  }], propDecorators: { nodeModel: [{
1164
1468
  type: Input
1165
1469
  }], nodeHtmlTemplate: [{
@@ -1277,19 +1581,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1277
1581
 
1278
1582
  class SpacePointContextDirective {
1279
1583
  constructor() {
1584
+ this.pointerMovementDirective = inject(RootPointerDirective);
1585
+ this.rootSvg = inject(RootSvgReferenceDirective).element;
1586
+ this.host = inject(ElementRef).nativeElement;
1280
1587
  /**
1281
1588
  * Signal with current mouse position in svg space
1282
1589
  */
1283
1590
  this.svgCurrentSpacePoint = computed(() => {
1284
- const movement = this.mouseMovement();
1591
+ const movement = this.pointerMovement();
1592
+ if (!movement) {
1593
+ return { x: 0, y: 0 };
1594
+ }
1285
1595
  const point = this.rootSvg.createSVGPoint();
1286
1596
  point.x = movement.x;
1287
1597
  point.y = movement.y;
1288
1598
  return point.matrixTransform(this.host.getScreenCTM().inverse());
1289
1599
  });
1290
- this.rootSvg = inject(RootSvgReferenceDirective).element;
1291
- this.host = inject(ElementRef).nativeElement;
1292
- this.mouseMovement = toSignal(fromEvent(this.rootSvg, 'mousemove').pipe(map(event => ({ x: event.clientX, y: event.clientY }))), { initialValue: { x: 0, y: 0 } });
1600
+ this.pointerMovement = toSignal(this.pointerMovementDirective.pointerMovement$);
1293
1601
  }
1294
1602
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SpacePointContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1295
1603
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: SpacePointContextDirective, selector: "g[spacePointContext]", ngImport: i0 }); }
@@ -1440,7 +1748,7 @@ class RootSvgContextDirective {
1440
1748
  }
1441
1749
  }
1442
1750
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootSvgContextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1443
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RootSvgContextDirective, selector: "svg[rootSvgContext]", host: { listeners: { "document:mouseup": "resetConnection()" } }, ngImport: i0 }); }
1751
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: RootSvgContextDirective, selector: "svg[rootSvgContext]", host: { listeners: { "document:mouseup": "resetConnection()", "document:touchend": "resetConnection()" } }, ngImport: i0 }); }
1444
1752
  }
1445
1753
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RootSvgContextDirective, decorators: [{
1446
1754
  type: Directive,
@@ -1448,6 +1756,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1448
1756
  }], propDecorators: { resetConnection: [{
1449
1757
  type: HostListener,
1450
1758
  args: ['document:mouseup']
1759
+ }, {
1760
+ type: HostListener,
1761
+ args: ['document:touchend']
1451
1762
  }] } });
1452
1763
 
1453
1764
  const connectionControllerHostDirective = {
@@ -1456,7 +1767,34 @@ const connectionControllerHostDirective = {
1456
1767
  };
1457
1768
  const changesControllerHostDirective = {
1458
1769
  directive: ChangesControllerDirective,
1459
- outputs: ['onNodesChange', 'onEdgesChange']
1770
+ outputs: [
1771
+ 'onNodesChange',
1772
+ 'onNodesChange.position',
1773
+ 'onNodesChange.position.single',
1774
+ 'onNodesChange.position.many',
1775
+ 'onNodesChange.add',
1776
+ 'onNodesChange.add.single',
1777
+ 'onNodesChange.add.many',
1778
+ 'onNodesChange.remove',
1779
+ 'onNodesChange.remove.single',
1780
+ 'onNodesChange.remove.many',
1781
+ 'onNodesChange.select',
1782
+ 'onNodesChange.select.single',
1783
+ 'onNodesChange.select.many',
1784
+ 'onEdgesChange',
1785
+ 'onEdgesChange.detached',
1786
+ 'onEdgesChange.detached.single',
1787
+ 'onEdgesChange.detached.many',
1788
+ 'onEdgesChange.add',
1789
+ 'onEdgesChange.add.single',
1790
+ 'onEdgesChange.add.many',
1791
+ 'onEdgesChange.remove',
1792
+ 'onEdgesChange.remove.single',
1793
+ 'onEdgesChange.remove.many',
1794
+ 'onEdgesChange.select',
1795
+ 'onEdgesChange.select.single',
1796
+ 'onEdgesChange.select.many',
1797
+ ]
1460
1798
  };
1461
1799
  class VflowComponent {
1462
1800
  constructor() {
@@ -1467,6 +1805,7 @@ class VflowComponent {
1467
1805
  this.edgesChangeService = inject(EdgeChangesService);
1468
1806
  this.nodeRenderingService = inject(NodeRenderingService);
1469
1807
  this.flowSettingsService = inject(FlowSettingsService);
1808
+ this.componentEventBusService = inject(ComponentEventBusService);
1470
1809
  this.injector = inject(Injector);
1471
1810
  /**
1472
1811
  * Minimum zoom value
@@ -1483,6 +1822,14 @@ class VflowComponent {
1483
1822
  this.nodeModels = computed(() => this.nodeRenderingService.nodes());
1484
1823
  this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
1485
1824
  // #endregion
1825
+ // #region OUTPUTS
1826
+ /**
1827
+ * Event that accumulates all custom node events
1828
+ *
1829
+ * @experimental
1830
+ */
1831
+ this.onComponentNodeEvent = this.componentEventBusService.event$; // TODO: research how to remove as any
1832
+ // #endregion
1486
1833
  // #region SIGNAL_API
1487
1834
  /**
1488
1835
  * Signal for reading viewport change
@@ -1508,7 +1855,7 @@ class VflowComponent {
1508
1855
  */
1509
1856
  this.nodesChange$ = this.nodesChangeService.changes$;
1510
1857
  /**
1511
- * Observable with nodes change
1858
+ * Observable with edges change
1512
1859
  */
1513
1860
  this.edgesChange$ = this.edgesChangeService.changes$;
1514
1861
  // #endregion
@@ -1619,7 +1966,7 @@ class VflowComponent {
1619
1966
  return edge;
1620
1967
  }
1621
1968
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1622
- 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: [
1969
+ 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: [
1623
1970
  DraggableService,
1624
1971
  ViewportService,
1625
1972
  FlowStatusService,
@@ -1628,8 +1975,9 @@ class VflowComponent {
1628
1975
  EdgeChangesService,
1629
1976
  NodeRenderingService,
1630
1977
  SelectionService,
1631
- FlowSettingsService
1632
- ], 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", "onEdgesChange", "onEdgesChange"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\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]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1978
+ FlowSettingsService,
1979
+ ComponentEventBusService
1980
+ ], 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 }); }
1633
1981
  }
1634
1982
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
1635
1983
  type: Component,
@@ -1642,11 +1990,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1642
1990
  EdgeChangesService,
1643
1991
  NodeRenderingService,
1644
1992
  SelectionService,
1645
- FlowSettingsService
1993
+ FlowSettingsService,
1994
+ ComponentEventBusService
1646
1995
  ], hostDirectives: [
1647
1996
  connectionControllerHostDirective,
1648
1997
  changesControllerHostDirective
1649
- ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\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"] }]
1998
+ ], 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"] }]
1650
1999
  }], propDecorators: { view: [{
1651
2000
  type: Input
1652
2001
  }], minZoom: [{
@@ -1667,6 +2016,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1667
2016
  args: [{ required: true }]
1668
2017
  }], edges: [{
1669
2018
  type: Input
2019
+ }], onComponentNodeEvent: [{
2020
+ type: Output
1670
2021
  }], nodeHtmlDirective: [{
1671
2022
  type: ContentChild,
1672
2023
  args: [NodeHtmlTemplateDirective]
@@ -1773,7 +2124,9 @@ const directives = [
1773
2124
  RootSvgReferenceDirective,
1774
2125
  RootSvgContextDirective,
1775
2126
  HandleSizeControllerDirective,
1776
- SelectableDirective
2127
+ SelectableDirective,
2128
+ PointerDirective,
2129
+ RootPointerDirective
1777
2130
  ];
1778
2131
  const templateDirectives = [
1779
2132
  NodeHtmlTemplateDirective,
@@ -1795,7 +2148,9 @@ class VflowModule {
1795
2148
  RootSvgReferenceDirective,
1796
2149
  RootSvgContextDirective,
1797
2150
  HandleSizeControllerDirective,
1798
- SelectableDirective, NodeHtmlTemplateDirective,
2151
+ SelectableDirective,
2152
+ PointerDirective,
2153
+ RootPointerDirective, NodeHtmlTemplateDirective,
1799
2154
  EdgeLabelHtmlTemplateDirective,
1800
2155
  EdgeTemplateDirective,
1801
2156
  ConnectionTemplateDirective,
@@ -1828,5 +2183,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1828
2183
  * Generated bundle index. Do not edit.
1829
2184
  */
1830
2185
 
1831
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
2186
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomNodeComponent, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
1832
2187
  //# sourceMappingURL=ngx-vflow.mjs.map