ngx-vflow 0.10.0 → 0.12.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 (43) hide show
  1. package/esm2022/lib/vflow/components/node/node.component.mjs +19 -14
  2. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +32 -6
  3. package/esm2022/lib/vflow/directives/changes-controller.directive.mjs +14 -2
  4. package/esm2022/lib/vflow/directives/flow-size-controller.directive.mjs +3 -3
  5. package/esm2022/lib/vflow/directives/root-pointer.directive.mjs +21 -4
  6. package/esm2022/lib/vflow/directives/template.directive.mjs +12 -1
  7. package/esm2022/lib/vflow/interfaces/node.interface.mjs +13 -1
  8. package/esm2022/lib/vflow/interfaces/optimization.interface.mjs +2 -0
  9. package/esm2022/lib/vflow/models/handle.model.mjs +22 -9
  10. package/esm2022/lib/vflow/models/node.model.mjs +91 -15
  11. package/esm2022/lib/vflow/public-components/resizable/resizable.component.mjs +265 -0
  12. package/esm2022/lib/vflow/services/draggable.service.mjs +12 -3
  13. package/esm2022/lib/vflow/services/handle.service.mjs +7 -2
  14. package/esm2022/lib/vflow/services/node-accessor.service.mjs +16 -0
  15. package/esm2022/lib/vflow/services/node-changes.service.mjs +6 -2
  16. package/esm2022/lib/vflow/services/node-rendering.service.mjs +12 -3
  17. package/esm2022/lib/vflow/types/node-change.type.mjs +1 -1
  18. package/esm2022/lib/vflow/utils/resizable.mjs +3 -3
  19. package/esm2022/lib/vflow/vflow.module.mjs +13 -5
  20. package/esm2022/public-api.mjs +3 -1
  21. package/fesm2022/ngx-vflow.mjs +565 -96
  22. package/fesm2022/ngx-vflow.mjs.map +1 -1
  23. package/lib/vflow/components/node/node.component.d.ts +5 -2
  24. package/lib/vflow/components/vflow/vflow.component.d.ts +10 -5
  25. package/lib/vflow/directives/changes-controller.directive.d.ts +5 -2
  26. package/lib/vflow/directives/root-pointer.directive.d.ts +24 -6
  27. package/lib/vflow/directives/space-point-context.directive.d.ts +5 -0
  28. package/lib/vflow/directives/template.directive.d.ts +5 -0
  29. package/lib/vflow/interfaces/node.interface.d.ts +42 -2
  30. package/lib/vflow/interfaces/optimization.interface.d.ts +3 -0
  31. package/lib/vflow/models/edge.model.d.ts +1 -17
  32. package/lib/vflow/models/handle.model.d.ts +2 -1
  33. package/lib/vflow/models/node.model.d.ts +22 -2
  34. package/lib/vflow/public-components/resizable/resizable.component.d.ts +39 -0
  35. package/lib/vflow/services/handle.service.d.ts +1 -1
  36. package/lib/vflow/services/node-accessor.service.d.ts +10 -0
  37. package/lib/vflow/services/node-changes.service.d.ts +8 -0
  38. package/lib/vflow/services/node-rendering.service.d.ts +1 -0
  39. package/lib/vflow/types/node-change.type.d.ts +8 -1
  40. package/lib/vflow/utils/resizable.d.ts +2 -1
  41. package/lib/vflow/vflow.module.d.ts +13 -12
  42. package/package.json +1 -1
  43. package/public-api.d.ts +2 -0
@@ -1,7 +1,7 @@
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, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, EventEmitter, Output, DestroyRef, Input, runInInjectionContext, Injector, Component, ChangeDetectionStrategy, HostListener, ViewChild, HostBinding, ContentChild, NgModule } from '@angular/core';
4
+ import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, EventEmitter, Output, DestroyRef, Input, runInInjectionContext, Injector, Component, ChangeDetectionStrategy, HostListener, ViewChild, NgZone, HostBinding, ContentChild, NgModule } from '@angular/core';
5
5
  import { select } from 'd3-selection';
6
6
  import { zoomIdentity, zoom } from 'd3-zoom';
7
7
  import { Subject, tap, merge, observeOn, animationFrameScheduler, switchMap, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, fromEvent, share, Observable, startWith } from 'rxjs';
@@ -420,10 +420,19 @@ class DraggableService {
420
420
  deltaY = model.point().y - event.y;
421
421
  })
422
422
  .on('drag', (event) => {
423
- model.setPoint({
423
+ let point = {
424
424
  x: round(event.x + deltaX),
425
425
  y: round(event.y + deltaY)
426
- });
426
+ };
427
+ const parent = model.parent();
428
+ // keep node in bounds of parent
429
+ if (parent) {
430
+ point.x = Math.min(parent.size().width - model.size().width, point.x);
431
+ point.x = Math.max(0, point.x);
432
+ point.y = Math.min(parent.size().height - model.size().height, point.y);
433
+ point.y = Math.max(0, point.y);
434
+ }
435
+ model.setPoint(point, true);
427
436
  });
428
437
  }
429
438
  /**
@@ -487,6 +496,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
487
496
  type: Directive,
488
497
  args: [{ selector: 'ng-template[nodeHtml]' }]
489
498
  }] });
499
+ class GroupNodeTemplateDirective {
500
+ constructor() {
501
+ this.templateRef = inject(TemplateRef);
502
+ }
503
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GroupNodeTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
504
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: GroupNodeTemplateDirective, selector: "ng-template[groupNode]", ngImport: i0 }); }
505
+ }
506
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: GroupNodeTemplateDirective, decorators: [{
507
+ type: Directive,
508
+ args: [{ selector: 'ng-template[groupNode]' }]
509
+ }] });
490
510
  class HandleTemplateDirective {
491
511
  constructor() {
492
512
  this.templateRef = inject(TemplateRef);
@@ -811,24 +831,54 @@ function isDefaultStaticNode(node) {
811
831
  function isDefaultDynamicNode(node) {
812
832
  return node.type === 'default';
813
833
  }
834
+ function isDefaultStaticGroupNode(node) {
835
+ return node.type === 'default-group';
836
+ }
837
+ function isDefaultDynamicGroupNode(node) {
838
+ return node.type === 'default-group';
839
+ }
840
+ function isTemplateStaticGroupNode(node) {
841
+ return node.type === 'template-group';
842
+ }
843
+ function isTemplateDynamicGroupNode(node) {
844
+ return node.type === 'template-group';
845
+ }
814
846
 
847
+ // TODO bad naming around points
815
848
  class NodeModel {
816
849
  static { this.defaultWidth = 100; }
817
850
  static { this.defaultHeight = 50; }
851
+ static { this.defaultColor = '#1b262c'; }
818
852
  constructor(node) {
819
853
  this.node = node;
820
854
  this.flowSettingsService = inject(FlowSettingsService);
855
+ this.entitiesService = inject(FlowEntitiesService);
821
856
  this.internalPoint = this.createInternalPointSignal();
822
857
  this.throttledPoint$ = toObservable(this.internalPoint).pipe(observeOn(animationFrameScheduler));
823
- this.point = toSignal(this.throttledPoint$, {
858
+ this.notThrottledPoint$ = new Subject();
859
+ this.point = toSignal(merge(this.throttledPoint$, this.notThrottledPoint$), {
824
860
  initialValue: this.internalPoint()
825
861
  });
826
862
  this.point$ = this.throttledPoint$;
827
863
  this.size = signal({ width: 0, height: 0 });
864
+ this.size$ = toObservable(this.size);
865
+ this.width = computed(() => this.size().width);
866
+ this.height = computed(() => this.size().height);
828
867
  this.renderOrder = signal(0);
829
868
  this.selected = signal(false);
830
869
  this.selected$ = toObservable(this.selected);
831
- this.pointTransform = computed(() => `translate(${this.point().x}, ${this.point().y})`);
870
+ this.globalPoint = computed(() => {
871
+ let parent = this.parent();
872
+ let x = this.point().x;
873
+ let y = this.point().y;
874
+ while (parent !== null) {
875
+ x += parent.point().x;
876
+ y += parent.point().y;
877
+ parent = parent.parent();
878
+ }
879
+ return { x, y };
880
+ });
881
+ this.pointTransform = computed(() => `translate(${this.globalPoint().x}, ${this.globalPoint().y})`);
832
882
  // Now source and handle positions derived from parent flow
833
883
  this.sourcePosition = computed(() => this.flowSettingsService.handlePositions().source);
834
884
  this.targetPosition = computed(() => this.flowSettingsService.handlePositions().target);
@@ -849,6 +899,13 @@ class NodeModel {
849
899
  _selected: this.selected()
850
900
  };
851
901
  });
902
+ this.parent = computed(() => this.entitiesService.nodes().find(n => n.node.id === this.parentId()) ?? null);
903
+ this.children = computed(() => this.entitiesService.nodes().filter(n => n.parentId() === this.node.id));
904
+ this.color = signal(NodeModel.defaultColor);
905
+ this.resizable = signal(false);
906
+ this.resizing = signal(false);
907
+ this.resizerTemplate = signal(null);
908
+ this.parentId = signal(null);
852
909
  if (isDefined(node.draggable)) {
853
910
  if (isDynamicNode(node)) {
854
911
  this.draggable = node.draggable;
@@ -857,29 +914,79 @@ class NodeModel {
857
914
  this.draggable.set(node.draggable);
858
915
  }
859
916
  }
917
+ if (isDefined(node.parentId)) {
918
+ if (isDynamicNode(node)) {
919
+ this.parentId = node.parentId;
920
+ }
921
+ else {
922
+ this.parentId.set(node.parentId);
923
+ }
924
+ }
925
+ if (node.type === 'default-group' && node.color) {
926
+ if (isDynamicNode(node)) {
927
+ this.color = node.color;
928
+ }
929
+ else {
930
+ this.color.set(node.color);
931
+ }
932
+ }
933
+ if (node.type === 'default-group' && node.resizable) {
934
+ if (isDynamicNode(node)) {
935
+ this.resizable = node.resizable;
936
+ }
937
+ else {
938
+ this.resizable.set(node.resizable);
939
+ }
940
+ }
860
941
  }
861
- setPoint(point) {
862
- this.internalPoint.set(point);
942
+ setPoint(point, throttle) {
943
+ if (throttle) {
944
+ this.internalPoint.set(point);
945
+ }
946
+ else {
947
+ this.notThrottledPoint$.next(point);
948
+ }
863
949
  }
864
950
  /**
865
951
  * TODO find the way to implement this better
866
952
  */
867
953
  linkDefaultNodeSizeWithModelSize() {
868
954
  const node = this.node;
869
- if (node.type === 'default') {
870
- if (isDynamicNode(node)) {
871
- effect(() => {
955
+ switch (node.type) {
956
+ case 'default':
957
+ case 'default-group':
958
+ case 'template-group': {
959
+ if (isDynamicNode(node)) {
960
+ effect(() => {
961
+ this.size.set({
962
+ width: node.width?.() ?? NodeModel.defaultWidth,
963
+ height: node.height?.() ?? NodeModel.defaultHeight,
964
+ });
965
+ }, { allowSignalWrites: true });
966
+ }
967
+ else {
872
968
  this.size.set({
873
- width: node.width?.() ?? NodeModel.defaultWidth,
874
- height: node.height?.() ?? NodeModel.defaultHeight,
969
+ width: node.width ?? NodeModel.defaultWidth,
970
+ height: node.height ?? NodeModel.defaultHeight
875
971
  });
972
+ }
973
+ }
974
+ }
975
+ if (node.type === 'html-template' || this.isComponentType) {
976
+ if (isDynamicNode(node)) {
977
+ effect(() => {
978
+ if (node.width && node.height) {
979
+ this.size.set({
980
+ width: node.width(),
981
+ height: node.height(),
982
+ });
983
+ }
876
984
  }, { allowSignalWrites: true });
877
985
  }
878
986
  else {
879
- this.size.set({
880
- width: node.width ?? NodeModel.defaultWidth,
881
- height: node.height ?? NodeModel.defaultHeight
882
- });
987
+ if (node.width && node.height) {
988
+ this.size.set({ width: node.width, height: node.height });
989
+ }
883
990
  }
884
991
  }
885
992
  }
@@ -1142,6 +1249,10 @@ class NodesChangeService {
1142
1249
  map(changedNode => [
1143
1250
  { type: 'position', id: changedNode.node.id, point: changedNode.point() }
1144
1251
  ]));
1252
+ this.nodeSizeChange$ = toObservable(this.entitiesService.nodes)
1253
+ .pipe(switchMap((nodes) => merge(...nodes.map(node => node.size$.pipe(skip(1), map(() => node))))), map(changedNode => [
1254
+ { type: 'size', id: changedNode.node.id, size: changedNode.size() }
1255
+ ]));
1145
1256
  this.nodeAddChange$ = toObservable(this.entitiesService.nodes)
1146
1257
  .pipe(pairwise(), map(([oldList, newList]) => newList.filter(node => !oldList.includes(node))), filter((nodes) => !!nodes.length), map((nodes) => nodes.map(node => ({ type: 'add', id: node.node.id }))));
1147
1258
  this.nodeRemoveChange$ = toObservable(this.entitiesService.nodes)
@@ -1150,7 +1261,7 @@ class NodesChangeService {
1150
1261
  .pipe(switchMap((nodes) => merge(...nodes.map(node => node.selected$.pipe(distinctUntilChanged(), skip(1), map(() => node))))), map((changedNode) => [
1151
1262
  { type: 'select', id: changedNode.node.id, selected: changedNode.selected() }
1152
1263
  ]));
1153
- this.changes$ = merge(this.nodesPositionChange$, this.nodeAddChange$, this.nodeRemoveChange$, this.nodeSelectedChange$).pipe(
1264
+ this.changes$ = merge(this.nodesPositionChange$, this.nodeSizeChange$, this.nodeAddChange$, this.nodeRemoveChange$, this.nodeSelectedChange$).pipe(
1154
1265
  // this fixes a bug when on fire node event change,
1155
1266
  // you can't get valid list of detached edges
1156
1267
  observeOn(asyncScheduler, DELAY_FOR_SCHEDULER));
@@ -1217,6 +1328,9 @@ class ChangesControllerDirective {
1217
1328
  this.onNodesChangePosition = this.nodeChangesOfType('position');
1218
1329
  this.onNodesChangePositionSignle = this.singleChange(this.nodeChangesOfType('position'));
1219
1330
  this.onNodesChangePositionMany = this.manyChanges(this.nodeChangesOfType('position'));
1331
+ this.onNodesChangeSize = this.nodeChangesOfType('size');
1332
+ this.onNodesChangeSizeSingle = this.singleChange(this.nodeChangesOfType('size'));
1333
+ this.onNodesChangeSizeMany = this.manyChanges(this.nodeChangesOfType('size'));
1220
1334
  this.onNodesChangeAdd = this.nodeChangesOfType('add');
1221
1335
  this.onNodesChangeAddSingle = this.singleChange(this.nodeChangesOfType('add'));
1222
1336
  this.onNodesChangeAddMany = this.manyChanges(this.nodeChangesOfType('add'));
@@ -1256,7 +1370,7 @@ class ChangesControllerDirective {
1256
1370
  return changes$.pipe(filter(changes => changes.length > 1));
1257
1371
  }
1258
1372
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChangesControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1259
- 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 }); }
1373
+ 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", onNodesChangeSize: "onNodesChange.size", onNodesChangeSizeSingle: "onNodesChange.size.single", onNodesChangeSizeMany: "onNodesChange.size.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 }); }
1260
1374
  }
1261
1375
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ChangesControllerDirective, decorators: [{
1262
1376
  type: Directive,
@@ -1275,6 +1389,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1275
1389
  }], onNodesChangePositionMany: [{
1276
1390
  type: Output,
1277
1391
  args: ['onNodesChange.position.many']
1392
+ }], onNodesChangeSize: [{
1393
+ type: Output,
1394
+ args: ['onNodesChange.size']
1395
+ }], onNodesChangeSizeSingle: [{
1396
+ type: Output,
1397
+ args: ['onNodesChange.size.single']
1398
+ }], onNodesChangeSizeMany: [{
1399
+ type: Output,
1400
+ args: ['onNodesChange.size.many']
1278
1401
  }], onNodesChangeAdd: [{
1279
1402
  type: Output,
1280
1403
  args: ['onNodesChange.add']
@@ -1349,10 +1472,19 @@ class NodeRenderingService {
1349
1472
  return this.flowEntitiesService.nodes()
1350
1473
  .sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1351
1474
  });
1475
+ this.maxOrder = computed(() => {
1476
+ return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
1477
+ });
1352
1478
  }
1353
1479
  pullNode(node) {
1354
- const maxOrder = Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
1355
- node.renderOrder.set(maxOrder + 1);
1480
+ // TODO do not pull when the node is already on top
1481
+ // pull node
1482
+ node.renderOrder.set(this.maxOrder() + 1);
1483
+ // pull children
1484
+ this.flowEntitiesService
1485
+ .nodes()
1486
+ .filter(n => n.parent() === node)
1487
+ .forEach(n => this.pullNode(n));
1356
1488
  }
1357
1489
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1358
1490
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService }); }
@@ -1365,25 +1497,42 @@ class RootPointerDirective {
1365
1497
  constructor() {
1366
1498
  this.host = inject(ElementRef).nativeElement;
1367
1499
  this.initialTouch$ = new Subject();
1500
+ this.prevTouchEvent = null;
1368
1501
  // TODO: do not emit if mouse not down
1369
1502
  this.mouseMovement$ = fromEvent(this.host, 'mousemove').pipe(map(event => ({
1370
1503
  x: event.clientX,
1371
1504
  y: event.clientY,
1505
+ movementX: event.movementX,
1506
+ movementY: event.movementY,
1507
+ target: event.target,
1372
1508
  originalEvent: event
1373
1509
  })), observeOn(animationFrameScheduler), share());
1374
1510
  this.touchMovement$ = merge(this.initialTouch$, fromEvent(this.host, 'touchmove')).pipe(tap((event) => event.preventDefault()), map((originalEvent) => {
1375
1511
  const x = originalEvent.touches[0]?.clientX ?? 0;
1376
1512
  const y = originalEvent.touches[0]?.clientY ?? 0;
1513
+ const movementX = this.prevTouchEvent
1514
+ ? originalEvent.touches[0].pageX - this.prevTouchEvent.touches[0].pageX
1515
+ : 0;
1516
+ const movementY = this.prevTouchEvent
1517
+ ? originalEvent.touches[0].pageY - this.prevTouchEvent.touches[0].pageY
1518
+ : 0;
1377
1519
  const target = document.elementFromPoint(x, y);
1378
- return { x, y, target, originalEvent };
1379
- }), observeOn(animationFrameScheduler), share());
1520
+ return { x, y, movementX, movementY, target, originalEvent };
1521
+ }), tap((event) => this.prevTouchEvent = event.originalEvent), observeOn(animationFrameScheduler), share());
1522
+ this.pointerMovement$ = merge(this.mouseMovement$, this.touchMovement$);
1380
1523
  this.touchEnd$ = fromEvent(this.host, 'touchend').pipe(map((originalEvent) => {
1381
1524
  const x = originalEvent.changedTouches[0]?.clientX ?? 0;
1382
1525
  const y = originalEvent.changedTouches[0]?.clientY ?? 0;
1383
1526
  const target = document.elementFromPoint(x, y);
1384
1527
  return { x, y, target, originalEvent };
1528
+ }), tap(() => this.prevTouchEvent = null), share());
1529
+ this.mouseUp$ = fromEvent(this.host, 'mouseup').pipe(map((originalEvent) => {
1530
+ const x = originalEvent.clientX;
1531
+ const y = originalEvent.clientY;
1532
+ const target = originalEvent.target;
1533
+ return { x, y, target, originalEvent };
1385
1534
  }), share());
1386
- this.pointerMovement$ = merge(this.mouseMovement$, this.touchMovement$);
1535
+ this.documentPointerEnd$ = merge(fromEvent(document, 'mouseup'), fromEvent(document, 'touchend')).pipe(share());
1387
1536
  }
1388
1537
  /**
1389
1538
  * We should know when user started a touch in order to not
@@ -1434,6 +1583,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1434
1583
  args: [{ selector: 'g[spacePointContext]' }]
1435
1584
  }] });
1436
1585
 
1586
+ function Microtask(target, key, descriptor) {
1587
+ const originalMethod = descriptor.value;
1588
+ descriptor.value = function (...args) {
1589
+ queueMicrotask(() => {
1590
+ originalMethod?.apply(this, args);
1591
+ });
1592
+ };
1593
+ // Return the modified descriptor
1594
+ return descriptor;
1595
+ }
1596
+
1437
1597
  class HandleService {
1438
1598
  constructor() {
1439
1599
  this.node = signal(null);
@@ -1453,14 +1613,17 @@ class HandleService {
1453
1613
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1454
1614
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleService }); }
1455
1615
  }
1616
+ __decorate([
1617
+ Microtask // TODO fixes rendering of handle for group node
1618
+ ], HandleService.prototype, "createHandle", null);
1456
1619
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleService, decorators: [{
1457
1620
  type: Injectable
1458
- }] });
1621
+ }], propDecorators: { createHandle: [] } });
1459
1622
 
1460
- function resizable(elems) {
1623
+ function resizable(elems, zone) {
1461
1624
  return new Observable((subscriber) => {
1462
1625
  let ro = new ResizeObserver((entries) => {
1463
- subscriber.next(entries);
1626
+ zone.run(() => subscriber.next(entries));
1464
1627
  });
1465
1628
  elems.forEach(e => ro.observe(e));
1466
1629
  return () => ro.disconnect();
@@ -1484,16 +1647,19 @@ const implementsWithInjector = (instance) => {
1484
1647
  return 'injector' in instance && 'get' in instance.injector;
1485
1648
  };
1486
1649
 
1487
- function Microtask(target, key, descriptor) {
1488
- const originalMethod = descriptor.value;
1489
- descriptor.value = function (...args) {
1490
- queueMicrotask(() => {
1491
- originalMethod?.apply(this, args);
1492
- });
1493
- };
1494
- // Return the modified descriptor
1495
- return descriptor;
1650
+ /**
1651
+ * Service to fix cyclic dependency between node and resizable component
1652
+ */
1653
+ class NodeAccessorService {
1654
+ constructor() {
1655
+ this.model = signal(null);
1656
+ }
1657
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeAccessorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1658
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeAccessorService }); }
1496
1659
  }
1660
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeAccessorService, decorators: [{
1661
+ type: Injectable
1662
+ }] });
1497
1663
 
1498
1664
  class HandleModel {
1499
1665
  constructor(rawHandle, parentNode) {
@@ -1538,21 +1704,22 @@ class HandleModel {
1538
1704
  });
1539
1705
  this.pointAbsolute = computed(() => {
1540
1706
  return {
1541
- x: this.parentNode.point().x + this.offset().x + this.sizeOffset().x,
1542
- y: this.parentNode.point().y + this.offset().y + this.sizeOffset().y,
1707
+ x: this.parentNode.globalPoint().x + this.offset().x + this.sizeOffset().x,
1708
+ y: this.parentNode.globalPoint().y + this.offset().y + this.sizeOffset().y,
1543
1709
  };
1544
1710
  });
1545
1711
  this.state = signal('idle');
1546
1712
  this.updateParentSizeAndPosition$ = new Subject();
1547
- this.parentSize = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => ({
1548
- width: this.parentReference.offsetWidth,
1549
- height: this.parentReference.offsetHeight
1550
- }))), {
1713
+ this.parentSize = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => this.getParentSize())), {
1551
1714
  initialValue: { width: 0, height: 0 }
1552
1715
  });
1553
1716
  this.parentPosition = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => ({
1554
- x: this.parentReference.offsetLeft,
1555
- y: this.parentReference.offsetTop
1717
+ x: this.parentReference instanceof HTMLElement
1718
+ ? this.parentReference.offsetLeft
1719
+ : 0,
1720
+ y: this.parentReference instanceof HTMLElement
1721
+ ? this.parentReference.offsetTop
1722
+ : 0 // for now just 0 for group nodes
1556
1723
  }))), {
1557
1724
  initialValue: { x: 0, y: 0 }
1558
1725
  });
@@ -1568,6 +1735,18 @@ class HandleModel {
1568
1735
  updateParent() {
1569
1736
  this.updateParentSizeAndPosition$.next();
1570
1737
  }
1738
+ getParentSize() {
1739
+ if (this.parentReference instanceof HTMLElement) {
1740
+ return {
1741
+ width: this.parentReference.offsetWidth,
1742
+ height: this.parentReference.offsetHeight
1743
+ };
1744
+ }
1745
+ else if (this.parentReference instanceof SVGGraphicsElement) {
1746
+ return this.parentReference.getBBox();
1747
+ }
1748
+ return { width: 0, height: 0 };
1749
+ }
1571
1750
  }
1572
1751
 
1573
1752
  class HandleComponent {
@@ -1611,42 +1790,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1611
1790
  type: Input
1612
1791
  }], ngOnInit: [] } });
1613
1792
 
1614
- class HandleSizeControllerDirective {
1615
- constructor() {
1616
- this.handleWrapper = inject(ElementRef);
1617
- }
1618
- ngAfterViewInit() {
1619
- const element = this.handleWrapper.nativeElement;
1620
- const rect = element.getBBox();
1621
- const stroke = getChildStrokeWidth(element);
1622
- this.handleModel.size.set({
1623
- width: rect.width + stroke,
1624
- height: rect.height + stroke
1625
- });
1626
- }
1627
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleSizeControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1628
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: { handleModel: ["handleSizeController", "handleModel"] }, ngImport: i0 }); }
1629
- }
1630
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleSizeControllerDirective, decorators: [{
1631
- type: Directive,
1632
- args: [{ selector: '[handleSizeController]' }]
1633
- }], propDecorators: { handleModel: [{
1634
- type: Input,
1635
- args: [{ required: true, alias: 'handleSizeController' }]
1636
- }] } });
1637
- function getChildStrokeWidth(element) {
1638
- const child = element.firstElementChild;
1639
- if (child) {
1640
- const stroke = getComputedStyle(child).strokeWidth;
1641
- const strokeAsNumber = Number(stroke.replace('px', ''));
1642
- if (isNaN(strokeAsNumber)) {
1643
- return 0;
1644
- }
1645
- return strokeAsNumber;
1646
- }
1647
- return 0;
1648
- }
1649
-
1650
1793
  class PointerDirective {
1651
1794
  constructor() {
1652
1795
  this.hostElement = inject(ElementRef).nativeElement;
@@ -1726,6 +1869,295 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1726
1869
  args: ['mouseout', ['$event']]
1727
1870
  }] } });
1728
1871
 
1872
+ class ResizableComponent {
1873
+ constructor() {
1874
+ this.nodeAccessor = inject(NodeAccessorService);
1875
+ this.rootPointer = inject(RootPointerDirective);
1876
+ this.viewportService = inject(ViewportService);
1877
+ this.hostRef = inject(ElementRef);
1878
+ this.resizerColor = '#2e414c';
1879
+ this.gap = 1.5;
1880
+ this.lineGap = 3;
1881
+ this.handleSize = 6;
1882
+ this.resizeSide = null;
1883
+ this.zoom = computed(() => this.viewportService.readableViewport().zoom ?? 0);
1884
+ this.minWidth = 0;
1885
+ this.minHeight = 0;
1886
+ // TODO: allow reszie beside the flow
1887
+ this.resizeOnGlobalMouseMove = this.rootPointer.pointerMovement$
1888
+ .pipe(filter(() => this.resizeSide !== null), tap((event) => this.resize(event)), takeUntilDestroyed())
1889
+ .subscribe();
1890
+ this.endResizeOnGlobalMouseUp = this.rootPointer.documentPointerEnd$
1891
+ .pipe(tap(() => this.endResize()), takeUntilDestroyed())
1892
+ .subscribe();
1893
+ }
1894
+ set resizable(value) {
1895
+ if (typeof value === 'boolean') {
1896
+ this.model.resizable.set(value);
1897
+ }
1898
+ else {
1899
+ this.model.resizable.set(true);
1900
+ }
1901
+ }
1902
+ get model() {
1903
+ return this.nodeAccessor.model();
1904
+ }
1905
+ ngOnInit() {
1906
+ this.model.resizerTemplate.set(this.resizer);
1907
+ }
1908
+ ngAfterViewInit() {
1909
+ this.minWidth = +getComputedStyle(this.hostRef.nativeElement).minWidth.replace('px', '') || 0;
1910
+ this.minHeight = +getComputedStyle(this.hostRef.nativeElement).minHeight.replace('px', '') || 0;
1911
+ }
1912
+ startResize(side, event) {
1913
+ event.stopPropagation();
1914
+ this.resizeSide = side;
1915
+ this.model.resizing.set(true);
1916
+ }
1917
+ resize({ movementX, movementY }) {
1918
+ const offsetX = round(movementX / this.zoom());
1919
+ const offsetY = round(movementY / this.zoom());
1920
+ switch (this.resizeSide) {
1921
+ case 'left':
1922
+ let x = this.model.point().x + offsetX;
1923
+ x = Math.max(x, this.getMinX());
1924
+ x = Math.min(x, this.getMaxX());
1925
+ // TODO this fixes increasing width when current node hits the parent
1926
+ if (x === this.getMinX() || x === this.getMaxX()) {
1927
+ return;
1928
+ }
1929
+ this.model.setPoint({ x, y: this.model.point().y }, false);
1930
+ this.model.size.update(({ height, width }) => {
1931
+ width -= offsetX;
1932
+ width = Math.max(width, this.minWidth);
1933
+ width = Math.min(width, this.getMaxWidth());
1934
+ return { height, width: width };
1935
+ });
1936
+ return;
1937
+ case 'right':
1938
+ this.model.size.update(({ height, width }) => {
1939
+ width += offsetX;
1940
+ width = Math.max(width, this.minWidth);
1941
+ width = Math.min(width, this.getMaxWidth());
1942
+ const bounds = getNodesBounds(this.model.children());
1943
+ width = Math.max(width, bounds.x + bounds.width);
1944
+ return { height, width };
1945
+ });
1946
+ return;
1947
+ case 'top':
1948
+ let y = this.model.point().y + offsetY;
1949
+ y = Math.max(y, this.getMinY());
1950
+ y = Math.min(y, this.getMaxY());
1951
+ if (y === this.getMinY() || y === this.getMaxY()) {
1952
+ return;
1953
+ }
1954
+ this.model.setPoint({ x: this.model.point().x, y }, false);
1955
+ this.model.size.update(({ height, width }) => {
1956
+ height -= offsetY;
1957
+ height = Math.max(height, this.minHeight);
1958
+ height = Math.min(height, this.getMaxHeight());
1959
+ return { width, height };
1960
+ });
1961
+ return;
1962
+ case 'bottom':
1963
+ this.model.size.update(({ height, width }) => {
1964
+ height += offsetY;
1965
+ height = Math.max(height, this.minHeight);
1966
+ height = Math.min(height, this.getMaxHeight());
1967
+ const bounds = getNodesBounds(this.model.children());
1968
+ height = Math.max(height, bounds.y + bounds.height);
1969
+ return { width, height };
1970
+ });
1971
+ return;
1972
+ case 'top-left': {
1973
+ let x = this.model.point().x + offsetX;
1974
+ x = Math.max(x, this.getMinX());
1975
+ x = Math.min(x, this.getMaxX());
1976
+ let y = this.model.point().y + offsetY;
1977
+ y = Math.max(y, this.getMinY());
1978
+ y = Math.min(y, this.getMaxY());
1979
+ if (x === this.getMinX() || y === this.getMinY() ||
1980
+ x === this.getMaxX() || y === this.getMaxY()) {
1981
+ return;
1982
+ }
1983
+ this.model.setPoint({ x, y }, false);
1984
+ this.model.size.update(({ height, width }) => {
1985
+ width -= offsetX;
1986
+ width = Math.max(width, this.minWidth);
1987
+ width = Math.min(width, this.getMaxWidth());
1988
+ height -= offsetY;
1989
+ height = Math.max(height, this.minHeight);
1990
+ height = Math.min(height, this.getMaxHeight());
1991
+ return { height, width };
1992
+ });
1993
+ return;
1994
+ }
1995
+ case 'top-right': {
1996
+ let y = this.model.point().y + offsetY;
1997
+ y = Math.max(y, this.getMinY());
1998
+ y = Math.min(y, this.getMaxY());
1999
+ if (y === this.getMinX() || y === this.getMaxY()) {
2000
+ return;
2001
+ }
2002
+ this.model.setPoint({ x: this.model.point().x, y }, false);
2003
+ this.model.size.update(({ height, width }) => {
2004
+ const bounds = getNodesBounds(this.model.children());
2005
+ width += offsetX;
2006
+ width = Math.max(width, this.minWidth);
2007
+ width = Math.min(width, this.getMaxWidth());
2008
+ width = Math.max(width, bounds.x + bounds.width);
2009
+ height -= offsetY;
2010
+ height = Math.max(height, this.minHeight);
2011
+ height = Math.min(height, this.getMaxHeight());
2012
+ return { height, width };
2013
+ });
2014
+ return;
2015
+ }
2016
+ case 'bottom-left': {
2017
+ let x = this.model.point().x + offsetX;
2018
+ x = Math.max(x, this.getMinX());
2019
+ x = Math.min(x, this.getMaxX());
2020
+ if (x === this.getMinX() || x === this.getMaxX()) {
2021
+ return;
2022
+ }
2023
+ this.model.setPoint({ x, y: this.model.point().y }, false);
2024
+ this.model.size.update(({ height, width }) => {
2025
+ width -= offsetX;
2026
+ width = Math.max(width, this.minWidth);
2027
+ width = Math.min(width, this.getMaxWidth());
2028
+ height += offsetY;
2029
+ height = Math.max(height, this.minHeight);
2030
+ height = Math.min(height, this.getMaxHeight());
2031
+ const bounds = getNodesBounds(this.model.children());
2032
+ height = Math.max(height, bounds.y + bounds.height);
2033
+ return { height, width };
2034
+ });
2035
+ return;
2036
+ }
2037
+ case 'bottom-right': {
2038
+ this.model.size.update(({ height, width }) => {
2039
+ const bounds = getNodesBounds(this.model.children());
2040
+ width += offsetX;
2041
+ width = Math.max(width, this.minWidth);
2042
+ width = Math.min(width, this.getMaxWidth());
2043
+ width = Math.max(width, bounds.x + bounds.width);
2044
+ height += offsetY;
2045
+ height = Math.max(height, this.minHeight);
2046
+ height = Math.min(height, this.getMaxHeight());
2047
+ height = Math.max(height, bounds.y + bounds.height);
2048
+ return { height, width };
2049
+ });
2050
+ }
2051
+ }
2052
+ }
2053
+ endResize() {
2054
+ this.resizeSide = null;
2055
+ this.model.resizing.set(false);
2056
+ }
2057
+ getMaxWidth() {
2058
+ const parent = this.model.parent();
2059
+ if (parent) {
2060
+ return parent.size().width - this.model.point().x;
2061
+ }
2062
+ return Infinity;
2063
+ }
2064
+ getMaxHeight() {
2065
+ const parent = this.model.parent();
2066
+ if (parent) {
2067
+ return parent.size().height - this.model.point().y;
2068
+ }
2069
+ return Infinity;
2070
+ }
2071
+ getMinX() {
2072
+ const parent = this.model.parent();
2073
+ if (parent) {
2074
+ return 0;
2075
+ }
2076
+ return -Infinity;
2077
+ }
2078
+ getMinY() {
2079
+ const parent = this.model.parent();
2080
+ if (parent) {
2081
+ return 0;
2082
+ }
2083
+ return -Infinity;
2084
+ }
2085
+ getMaxX() {
2086
+ const x = this.model.point().x;
2087
+ const width = this.model.size().width;
2088
+ const children = this.model.children();
2089
+ if (children) {
2090
+ const bounds = getNodesBounds(children);
2091
+ return x + (bounds.x + bounds.width) >= x + width ? x : (width - this.minWidth) + x;
2092
+ }
2093
+ return (width - this.minWidth) + x;
2094
+ }
2095
+ getMaxY() {
2096
+ const y = this.model.point().y;
2097
+ const height = this.model.size().height;
2098
+ const children = this.model.children();
2099
+ if (children) {
2100
+ const bounds = getNodesBounds(children);
2101
+ return y + (bounds.y + bounds.height) >= y + height ? y : (height - this.minHeight) + y;
2102
+ }
2103
+ return (height - this.minHeight) + y;
2104
+ }
2105
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ResizableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2106
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ResizableComponent, selector: "[resizable]", inputs: { resizable: "resizable", resizerColor: "resizerColor", gap: "gap" }, viewQueries: [{ propertyName: "resizer", first: true, predicate: ["resizer"], descendants: true, static: true }], ngImport: i0, template: "<ng-template #resizer>\n <svg:g>\n <!-- top line -->\n <svg:line\n class=\"top\"\n [attr.x1]=\"lineGap\"\n [attr.y1]=\"-gap\"\n [attr.x2]=\"model.size().width - lineGap\"\n [attr.y2]=\"-gap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('top', $event)\"\n />\n <!-- Left line -->\n <svg:line\n class=\"left\"\n [attr.x1]=\"-gap\"\n [attr.y1]=\"lineGap\"\n [attr.x2]=\"-gap\"\n [attr.y2]=\"model.size().height - lineGap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('left', $event)\"\n />\n <!-- Bottom line -->\n <svg:line\n class=\"bottom\"\n [attr.x1]=\"lineGap\"\n [attr.y1]=\"model.size().height + gap\"\n [attr.x2]=\"model.size().width - lineGap\"\n [attr.y2]=\"model.size().height + gap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('bottom', $event)\"\n />\n <!-- Right line -->\n <svg:line\n class=\"right\"\n [attr.x1]=\"model.size().width + gap\"\n [attr.y1]=\"lineGap\"\n [attr.x2]=\"model.size().width + gap\"\n [attr.y2]=\"model.size().height - lineGap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('right', $event)\"\n />\n\n <!-- Top Left -->\n <svg:rect\n class=\"top-left\"\n [attr.x]=\"-(handleSize / 2) - gap\"\n [attr.y]=\"-(handleSize / 2) - gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('top-left', $event)\"\n />\n\n <!-- Top right -->\n <svg:rect\n class=\"top-right\"\n [attr.x]=\"model.size().width - (handleSize / 2) + gap\"\n [attr.y]=\"-(handleSize / 2) - gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('top-right', $event)\"\n />\n\n <!-- Bottom left -->\n <svg:rect\n class=\"bottom-left\"\n [attr.x]=\"-(handleSize / 2) - gap\"\n [attr.y]=\"model.size().height - (handleSize / 2) + gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('bottom-left', $event)\"\n />\n\n <!-- Bottom right -->\n <svg:rect\n class=\"bottom-right\"\n [attr.x]=\"model.size().width - (handleSize / 2) + gap\"\n [attr.y]=\"model.size().height - (handleSize / 2) + gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('bottom-right', $event)\"\n />\n </svg:g>\n</ng-template>\n\n<ng-content />\n", styles: [".top{cursor:n-resize}.left{cursor:w-resize}.right{cursor:e-resize}.bottom{cursor:s-resize}.top-left{cursor:nw-resize}.top-right{cursor:ne-resize}.bottom-left{cursor:sw-resize}.bottom-right{cursor:se-resize}\n"], dependencies: [{ kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }] }); }
2107
+ }
2108
+ __decorate([
2109
+ Microtask
2110
+ ], ResizableComponent.prototype, "ngAfterViewInit", null);
2111
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ResizableComponent, decorators: [{
2112
+ type: Component,
2113
+ args: [{ selector: '[resizable]', template: "<ng-template #resizer>\n <svg:g>\n <!-- top line -->\n <svg:line\n class=\"top\"\n [attr.x1]=\"lineGap\"\n [attr.y1]=\"-gap\"\n [attr.x2]=\"model.size().width - lineGap\"\n [attr.y2]=\"-gap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('top', $event)\"\n />\n <!-- Left line -->\n <svg:line\n class=\"left\"\n [attr.x1]=\"-gap\"\n [attr.y1]=\"lineGap\"\n [attr.x2]=\"-gap\"\n [attr.y2]=\"model.size().height - lineGap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('left', $event)\"\n />\n <!-- Bottom line -->\n <svg:line\n class=\"bottom\"\n [attr.x1]=\"lineGap\"\n [attr.y1]=\"model.size().height + gap\"\n [attr.x2]=\"model.size().width - lineGap\"\n [attr.y2]=\"model.size().height + gap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('bottom', $event)\"\n />\n <!-- Right line -->\n <svg:line\n class=\"right\"\n [attr.x1]=\"model.size().width + gap\"\n [attr.y1]=\"lineGap\"\n [attr.x2]=\"model.size().width + gap\"\n [attr.y2]=\"model.size().height - lineGap\"\n [attr.stroke]=\"resizerColor\"\n stroke-width=\"2\"\n (pointerStart)=\"startResize('right', $event)\"\n />\n\n <!-- Top Left -->\n <svg:rect\n class=\"top-left\"\n [attr.x]=\"-(handleSize / 2) - gap\"\n [attr.y]=\"-(handleSize / 2) - gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('top-left', $event)\"\n />\n\n <!-- Top right -->\n <svg:rect\n class=\"top-right\"\n [attr.x]=\"model.size().width - (handleSize / 2) + gap\"\n [attr.y]=\"-(handleSize / 2) - gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('top-right', $event)\"\n />\n\n <!-- Bottom left -->\n <svg:rect\n class=\"bottom-left\"\n [attr.x]=\"-(handleSize / 2) - gap\"\n [attr.y]=\"model.size().height - (handleSize / 2) + gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('bottom-left', $event)\"\n />\n\n <!-- Bottom right -->\n <svg:rect\n class=\"bottom-right\"\n [attr.x]=\"model.size().width - (handleSize / 2) + gap\"\n [attr.y]=\"model.size().height - (handleSize / 2) + gap\"\n [attr.width]=\"handleSize\"\n [attr.height]=\"handleSize\"\n [attr.fill]=\"resizerColor\"\n (pointerStart)=\"startResize('bottom-right', $event)\"\n />\n </svg:g>\n</ng-template>\n\n<ng-content />\n", styles: [".top{cursor:n-resize}.left{cursor:w-resize}.right{cursor:e-resize}.bottom{cursor:s-resize}.top-left{cursor:nw-resize}.top-right{cursor:ne-resize}.bottom-left{cursor:sw-resize}.bottom-right{cursor:se-resize}\n"] }]
2114
+ }], propDecorators: { resizable: [{
2115
+ type: Input
2116
+ }], resizerColor: [{
2117
+ type: Input
2118
+ }], gap: [{
2119
+ type: Input
2120
+ }], resizer: [{
2121
+ type: ViewChild,
2122
+ args: ['resizer', { static: true }]
2123
+ }], ngAfterViewInit: [] } });
2124
+
2125
+ class HandleSizeControllerDirective {
2126
+ constructor() {
2127
+ this.handleWrapper = inject(ElementRef);
2128
+ }
2129
+ ngAfterViewInit() {
2130
+ const element = this.handleWrapper.nativeElement;
2131
+ const rect = element.getBBox();
2132
+ const stroke = getChildStrokeWidth(element);
2133
+ this.handleModel.size.set({
2134
+ width: rect.width + stroke,
2135
+ height: rect.height + stroke
2136
+ });
2137
+ }
2138
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleSizeControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2139
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: { handleModel: ["handleSizeController", "handleModel"] }, ngImport: i0 }); }
2140
+ }
2141
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleSizeControllerDirective, decorators: [{
2142
+ type: Directive,
2143
+ args: [{ selector: '[handleSizeController]' }]
2144
+ }], propDecorators: { handleModel: [{
2145
+ type: Input,
2146
+ args: [{ required: true, alias: 'handleSizeController' }]
2147
+ }] } });
2148
+ function getChildStrokeWidth(element) {
2149
+ const child = element.firstElementChild;
2150
+ if (child) {
2151
+ const stroke = getComputedStyle(child).strokeWidth;
2152
+ const strokeAsNumber = Number(stroke.replace('px', ''));
2153
+ if (isNaN(strokeAsNumber)) {
2154
+ return 0;
2155
+ }
2156
+ return strokeAsNumber;
2157
+ }
2158
+ return 0;
2159
+ }
2160
+
1729
2161
  class NodeComponent {
1730
2162
  constructor() {
1731
2163
  this.injector = inject(Injector);
@@ -1737,12 +2169,15 @@ class NodeComponent {
1737
2169
  this.selectionService = inject(SelectionService);
1738
2170
  this.hostRef = inject(ElementRef);
1739
2171
  this.connectionController = inject(ConnectionControllerDirective);
2172
+ this.nodeAccessor = inject(NodeAccessorService);
2173
+ this.zone = inject(NgZone);
1740
2174
  this.showMagnet = computed(() => this.flowStatusService.status().state === 'connection-start' ||
1741
2175
  this.flowStatusService.status().state === 'connection-validation');
1742
2176
  this.styleWidth = computed(() => `${this.nodeModel.size().width}px`);
1743
2177
  this.styleHeight = computed(() => `${this.nodeModel.size().height}px`);
1744
2178
  }
1745
2179
  ngOnInit() {
2180
+ this.nodeAccessor.model.set(this.nodeModel);
1746
2181
  this.handleService.node.set(this.nodeModel);
1747
2182
  effect(() => {
1748
2183
  if (this.nodeModel.draggable()) {
@@ -1753,7 +2188,8 @@ class NodeComponent {
1753
2188
  }
1754
2189
  });
1755
2190
  this.nodeModel.handles$
1756
- .pipe(switchMap((handles) => resizable(handles.map(h => h.parentReference)).pipe(map(() => handles))), tap((handles) => {
2191
+ .pipe(switchMap((handles) => resizable(handles.map(h => h.parentReference), this.zone)
2192
+ .pipe(map(() => handles))), tap((handles) => {
1757
2193
  // TODO (performance) inspect how to avoid calls of this when flow initially rendered
1758
2194
  handles.forEach(h => h.updateParent());
1759
2195
  }), takeUntilDestroyed())
@@ -1762,8 +2198,8 @@ class NodeComponent {
1762
2198
  ngAfterViewInit() {
1763
2199
  this.nodeModel.linkDefaultNodeSizeWithModelSize();
1764
2200
  if (this.nodeModel.node.type === 'html-template' || this.nodeModel.isComponentType) {
1765
- resizable([this.htmlWrapperRef.nativeElement])
1766
- .pipe(startWith(null), tap(() => {
2201
+ resizable([this.htmlWrapperRef.nativeElement], this.zone)
2202
+ .pipe(startWith(null), tap(() => this.nodeModel.handles().forEach(h => h.updateParent())), filter(() => !this.nodeModel.resizing()), tap(() => {
1767
2203
  const width = this.htmlWrapperRef.nativeElement.clientWidth;
1768
2204
  const height = this.htmlWrapperRef.nativeElement.clientHeight;
1769
2205
  this.nodeModel.size.set({ width, height });
@@ -1796,22 +2232,22 @@ class NodeComponent {
1796
2232
  }
1797
2233
  }
1798
2234
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1799
- 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 >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\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)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".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: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template"] }, { 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 }); }
2235
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeTemplate: "nodeTemplate", groupNodeTemplate: "groupNodeTemplate" }, providers: [HandleService, NodeAccessorService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (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 >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\n </div>\n</svg:foreignObject>\n\n<!-- Template node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (mousedown)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (mousedown)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-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-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"], dependencies: [{ kind: "directive", type: i1.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: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template"] }, { kind: "component", type: ResizableComponent, selector: "[resizable]", inputs: ["resizable", "resizerColor", "gap"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }, { kind: "directive", type: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1800
2236
  }
1801
2237
  __decorate([
1802
2238
  InjectionContext
1803
2239
  ], NodeComponent.prototype, "ngOnInit", null);
1804
2240
  __decorate([
1805
- Microtask // TODO (performance) check if we need microtask here
1806
- ,
1807
2241
  InjectionContext
1808
2242
  ], NodeComponent.prototype, "ngAfterViewInit", null);
1809
2243
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
1810
2244
  type: Component,
1811
- 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 >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\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)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".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"] }]
2245
+ args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService, NodeAccessorService], template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (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 >\n <div [outerHTML]=\"nodeModel.text()\"></div>\n\n <handle type=\"source\" [position]=\"nodeModel.sourcePosition()\" />\n <handle type=\"target\" [position]=\"nodeModel.targetPosition()\" />\n </div>\n</svg:foreignObject>\n\n<!-- Template node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'html-template' && nodeTemplate\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (mousedown)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (mousedown)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (mousedown)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-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-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
1812
2246
  }], propDecorators: { nodeModel: [{
1813
2247
  type: Input
1814
- }], nodeHtmlTemplate: [{
2248
+ }], nodeTemplate: [{
2249
+ type: Input
2250
+ }], groupNodeTemplate: [{
1815
2251
  type: Input
1816
2252
  }], nodeContentRef: [{
1817
2253
  type: ViewChild,
@@ -2154,7 +2590,7 @@ class FlowSizeControllerDirective {
2154
2590
  this.flowWidth = view === 'auto' ? '100%' : view[0];
2155
2591
  this.flowHeight = view === 'auto' ? '100%' : view[1];
2156
2592
  });
2157
- resizable([this.host.nativeElement]).pipe(tap(([entry]) => {
2593
+ resizable([this.host.nativeElement], inject(NgZone)).pipe(tap(([entry]) => {
2158
2594
  this.flowSettingsService.computedFlowWidth.set(entry.contentRect.width);
2159
2595
  this.flowSettingsService.computedFlowHeight.set(entry.contentRect.height);
2160
2596
  }), takeUntilDestroyed()).subscribe();
@@ -2184,6 +2620,9 @@ const changesControllerHostDirective = {
2184
2620
  'onNodesChange.position',
2185
2621
  'onNodesChange.position.single',
2186
2622
  'onNodesChange.position.many',
2623
+ 'onNodesChange.size',
2624
+ 'onNodesChange.size.single',
2625
+ 'onNodesChange.size.many',
2187
2626
  'onNodesChange.add',
2188
2627
  'onNodesChange.add.single',
2189
2628
  'onNodesChange.add.many',
@@ -2223,6 +2662,9 @@ class VflowComponent {
2223
2662
  * Background for flow
2224
2663
  */
2225
2664
  this.background = '#fff';
2665
+ this.optimization = {
2666
+ computeLayersOnInit: true
2667
+ };
2226
2668
  this.nodeModels = computed(() => this.nodeRenderingService.nodes());
2227
2669
  this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
2228
2670
  // #endregion
@@ -2333,6 +2775,9 @@ class VflowComponent {
2333
2775
  addNodesToEdges(this.nodeModels(), newModels);
2334
2776
  this.flowEntitiesService.edges.set(newModels);
2335
2777
  }
2778
+ ngOnInit() {
2779
+ this.setInitialNodesOrder();
2780
+ }
2336
2781
  // #region METHODS_API
2337
2782
  /**
2338
2783
  * Change viewport to specified state
@@ -2388,8 +2833,20 @@ class VflowComponent {
2388
2833
  trackEdges(idx, { edge }) {
2389
2834
  return edge;
2390
2835
  }
2836
+ setInitialNodesOrder() {
2837
+ if (this.optimization.computeLayersOnInit) {
2838
+ this.nodeModels().forEach(model => {
2839
+ switch (model.node.type) {
2840
+ case 'default-group':
2841
+ case 'template-group': {
2842
+ this.nodeRenderingService.pullNode(model);
2843
+ }
2844
+ }
2845
+ });
2846
+ }
2847
+ }
2391
2848
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2392
- 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: [
2849
+ 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", optimization: "optimization", entitiesSelectable: "entitiesSelectable", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], nodes: "nodes", edges: "edges" }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
2393
2850
  DraggableService,
2394
2851
  ViewportService,
2395
2852
  FlowStatusService,
@@ -2400,7 +2857,7 @@ class VflowComponent {
2400
2857
  SelectionService,
2401
2858
  FlowSettingsService,
2402
2859
  ComponentEventBusService
2403
- ], 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 }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true }], hostDirectives: [{ directive: ConnectionControllerDirective, outputs: ["onConnect", "onConnect"] }, { directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.add", "onNodesChange.add", "onNodesChange.add.single", "onNodesChange.add.single", "onNodesChange.add.many", "onNodesChange.add.many", "onNodesChange.remove", "onNodesChange.remove", "onNodesChange.remove.single", "onNodesChange.remove.single", "onNodesChange.remove.many", "onNodesChange.remove.many", "onNodesChange.select", "onNodesChange.select", "onNodesChange.select.single", "onNodesChange.select.single", "onNodesChange.select.many", "onNodesChange.select.many", "onEdgesChange", "onEdgesChange", "onEdgesChange.detached", "onEdgesChange.detached", "onEdgesChange.detached.single", "onEdgesChange.detached.single", "onEdgesChange.detached.many", "onEdgesChange.detached.many", "onEdgesChange.add", "onEdgesChange.add", "onEdgesChange.add.single", "onEdgesChange.add.single", "onEdgesChange.add.many", "onEdgesChange.add.many", "onEdgesChange.remove", "onEdgesChange.remove", "onEdgesChange.remove.single", "onEdgesChange.remove.single", "onEdgesChange.remove.many", "onEdgesChange.remove.many", "onEdgesChange.select", "onEdgesChange.select", "onEdgesChange.select.single", "onEdgesChange.select.single", "onEdgesChange.select.many", "onEdgesChange.select.many"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g [background]=\"background\"/>\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [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]" }, { kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }, { kind: "directive", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2860
+ ], queries: [{ propertyName: "nodeTemplateDirective", first: true, predicate: NodeHtmlTemplateDirective, descendants: true }, { propertyName: "groupNodeTemplateDirective", first: true, predicate: GroupNodeTemplateDirective, descendants: true }, { propertyName: "edgeTemplateDirective", first: true, predicate: EdgeTemplateDirective, descendants: true }, { propertyName: "edgeLabelHtmlDirective", first: true, predicate: EdgeLabelHtmlTemplateDirective, descendants: true }, { propertyName: "connectionTemplateDirective", first: true, predicate: ConnectionTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "mapContext", first: true, predicate: MapContextDirective, descendants: true }, { propertyName: "spacePointContext", first: true, predicate: SpacePointContextDirective, descendants: true }], hostDirectives: [{ directive: ConnectionControllerDirective, outputs: ["onConnect", "onConnect"] }, { directive: ChangesControllerDirective, outputs: ["onNodesChange", "onNodesChange", "onNodesChange.position", "onNodesChange.position", "onNodesChange.position.single", "onNodesChange.position.single", "onNodesChange.position.many", "onNodesChange.position.many", "onNodesChange.size", "onNodesChange.size", "onNodesChange.size.single", "onNodesChange.size.single", "onNodesChange.size.many", "onNodesChange.size.many", "onNodesChange.add", "onNodesChange.add", "onNodesChange.add.single", "onNodesChange.add.single", "onNodesChange.add.many", "onNodesChange.add.many", "onNodesChange.remove", "onNodesChange.remove", "onNodesChange.remove.single", "onNodesChange.remove.single", "onNodesChange.remove.many", "onNodesChange.remove.many", "onNodesChange.select", "onNodesChange.select", "onNodesChange.select.single", "onNodesChange.select.single", "onNodesChange.select.many", "onNodesChange.select.many", "onEdgesChange", "onEdgesChange", "onEdgesChange.detached", "onEdgesChange.detached", "onEdgesChange.detached.single", "onEdgesChange.detached.single", "onEdgesChange.detached.many", "onEdgesChange.detached.many", "onEdgesChange.add", "onEdgesChange.add", "onEdgesChange.add.single", "onEdgesChange.add.single", "onEdgesChange.add.many", "onEdgesChange.add.many", "onEdgesChange.remove", "onEdgesChange.remove", "onEdgesChange.remove.single", "onEdgesChange.remove.single", "onEdgesChange.remove.many", "onEdgesChange.remove.many", "onEdgesChange.select", "onEdgesChange.select", "onEdgesChange.select.single", "onEdgesChange.select.single", "onEdgesChange.select.many", "onEdgesChange.select.many"] }], ngImport: i0, template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g [background]=\"background\"/>\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n</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", "nodeTemplate", "groupNodeTemplate"] }, { kind: "component", type: EdgeComponent, selector: "g[edge]", inputs: ["model", "edgeTemplate", "edgeLabelHtmlTemplate"] }, { kind: "component", type: ConnectionComponent, selector: "g[connection]", inputs: ["model", "template"] }, { kind: "component", type: DefsComponent, selector: "defs[flowDefs]", inputs: ["markers"] }, { kind: "component", type: BackgroundComponent, selector: "g[background]", inputs: ["background"] }, { kind: "directive", type: SpacePointContextDirective, selector: "g[spacePointContext]" }, { kind: "directive", type: MapContextDirective, selector: "g[mapContext]" }, { kind: "directive", type: RootSvgReferenceDirective, selector: "svg[rootSvgRef]" }, { kind: "directive", type: RootSvgContextDirective, selector: "svg[rootSvgContext]" }, { kind: "directive", type: RootPointerDirective, selector: "svg[rootPointer]" }, { kind: "directive", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2404
2861
  }
2405
2862
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
2406
2863
  type: Component,
@@ -2418,7 +2875,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2418
2875
  ], hostDirectives: [
2419
2876
  connectionControllerHostDirective,
2420
2877
  changesControllerHostDirective
2421
- ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g [background]=\"background\"/>\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [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"] }]
2878
+ ], template: "<svg:svg\n rootSvgRef\n rootSvgContext\n rootPointer\n flowSizeController\n class=\"root-svg\"\n #flow\n>\n <defs [markers]=\"markers()\" flowDefs />\n\n <g [background]=\"background\"/>\n\n <svg:g\n mapContext\n spacePointContext\n >\n <!-- Connection -->\n <svg:g\n connection\n [model]=\"connection\"\n [template]=\"connectionTemplateDirective?.templateRef\"\n />\n\n <!-- Edges -->\n <svg:g\n *ngFor=\"let model of edgeModels(); trackBy: trackEdges\"\n edge\n [model]=\"model\"\n [edgeTemplate]=\"edgeTemplateDirective?.templateRef\"\n [edgeLabelHtmlTemplate]=\"edgeLabelHtmlDirective?.templateRef\"\n />\n\n <!-- Nodes -->\n <svg:g\n *ngFor=\"let model of nodeModels(); trackBy: trackNodes\"\n node\n [nodeModel]=\"model\"\n [nodeTemplate]=\"nodeTemplateDirective?.templateRef\"\n [groupNodeTemplate]=\"groupNodeTemplateDirective?.templateRef\"\n [attr.transform]=\"model.pointTransform()\"\n />\n </svg:g>\n\n</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"] }]
2422
2879
  }], propDecorators: { view: [{
2423
2880
  type: Input
2424
2881
  }], minZoom: [{
@@ -2429,6 +2886,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2429
2886
  type: Input
2430
2887
  }], background: [{
2431
2888
  type: Input
2889
+ }], optimization: [{
2890
+ type: Input
2432
2891
  }], entitiesSelectable: [{
2433
2892
  type: Input
2434
2893
  }], connection: [{
@@ -2441,9 +2900,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2441
2900
  type: Input
2442
2901
  }], onComponentNodeEvent: [{
2443
2902
  type: Output
2444
- }], nodeHtmlDirective: [{
2903
+ }], nodeTemplateDirective: [{
2445
2904
  type: ContentChild,
2446
2905
  args: [NodeHtmlTemplateDirective]
2906
+ }], groupNodeTemplateDirective: [{
2907
+ type: ContentChild,
2908
+ args: [GroupNodeTemplateDirective]
2447
2909
  }], edgeTemplateDirective: [{
2448
2910
  type: ContentChild,
2449
2911
  args: [EdgeTemplateDirective]
@@ -2502,7 +2964,8 @@ const components = [
2502
2964
  ConnectionComponent,
2503
2965
  HandleComponent,
2504
2966
  DefsComponent,
2505
- BackgroundComponent
2967
+ BackgroundComponent,
2968
+ ResizableComponent,
2506
2969
  ];
2507
2970
  const directives = [
2508
2971
  SpacePointContextDirective,
@@ -2513,10 +2976,11 @@ const directives = [
2513
2976
  SelectableDirective,
2514
2977
  PointerDirective,
2515
2978
  RootPointerDirective,
2516
- FlowSizeControllerDirective
2979
+ FlowSizeControllerDirective,
2517
2980
  ];
2518
2981
  const templateDirectives = [
2519
2982
  NodeHtmlTemplateDirective,
2983
+ GroupNodeTemplateDirective,
2520
2984
  EdgeLabelHtmlTemplateDirective,
2521
2985
  EdgeTemplateDirective,
2522
2986
  ConnectionTemplateDirective,
@@ -2531,7 +2995,8 @@ class VflowModule {
2531
2995
  ConnectionComponent,
2532
2996
  HandleComponent,
2533
2997
  DefsComponent,
2534
- BackgroundComponent, SpacePointContextDirective,
2998
+ BackgroundComponent,
2999
+ ResizableComponent, SpacePointContextDirective,
2535
3000
  MapContextDirective,
2536
3001
  RootSvgReferenceDirective,
2537
3002
  RootSvgContextDirective,
@@ -2540,12 +3005,15 @@ class VflowModule {
2540
3005
  PointerDirective,
2541
3006
  RootPointerDirective,
2542
3007
  FlowSizeControllerDirective, NodeHtmlTemplateDirective,
3008
+ GroupNodeTemplateDirective,
2543
3009
  EdgeLabelHtmlTemplateDirective,
2544
3010
  EdgeTemplateDirective,
2545
3011
  ConnectionTemplateDirective,
2546
3012
  HandleTemplateDirective], imports: [CommonModule], exports: [VflowComponent,
2547
3013
  HandleComponent,
3014
+ ResizableComponent,
2548
3015
  SelectableDirective, NodeHtmlTemplateDirective,
3016
+ GroupNodeTemplateDirective,
2549
3017
  EdgeLabelHtmlTemplateDirective,
2550
3018
  EdgeTemplateDirective,
2551
3019
  ConnectionTemplateDirective,
@@ -2559,6 +3027,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2559
3027
  exports: [
2560
3028
  VflowComponent,
2561
3029
  HandleComponent,
3030
+ ResizableComponent,
2562
3031
  SelectableDirective,
2563
3032
  ...templateDirectives
2564
3033
  ],
@@ -2572,5 +3041,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2572
3041
  * Generated bundle index. Do not edit.
2573
3042
  */
2574
3043
 
2575
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isTemplateDynamicNode, isTemplateStaticNode };
3044
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, ResizableComponent, SelectableDirective, VflowComponent, VflowModule, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode };
2576
3045
  //# sourceMappingURL=ngx-vflow.mjs.map