ngx-vflow 0.9.1 → 0.11.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 (39) hide show
  1. package/esm2022/lib/vflow/components/custom-node-base/custom-node-base.component.mjs +48 -0
  2. package/esm2022/lib/vflow/components/node/node.component.mjs +16 -13
  3. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +29 -6
  4. package/esm2022/lib/vflow/directives/template.directive.mjs +12 -1
  5. package/esm2022/lib/vflow/interfaces/component-node-event.interface.mjs +1 -1
  6. package/esm2022/lib/vflow/interfaces/node.interface.mjs +39 -2
  7. package/esm2022/lib/vflow/interfaces/optimization.interface.mjs +2 -0
  8. package/esm2022/lib/vflow/models/handle.model.mjs +22 -9
  9. package/esm2022/lib/vflow/models/node.model.mjs +103 -17
  10. package/esm2022/lib/vflow/public-components/custom-dynamic-node.component.mjs +19 -0
  11. package/esm2022/lib/vflow/public-components/custom-node.component.mjs +9 -41
  12. package/esm2022/lib/vflow/services/draggable.service.mjs +25 -10
  13. package/esm2022/lib/vflow/services/handle.service.mjs +7 -2
  14. package/esm2022/lib/vflow/services/node-rendering.service.mjs +12 -3
  15. package/esm2022/lib/vflow/utils/reference-keeper.mjs +1 -1
  16. package/esm2022/lib/vflow/vflow.module.mjs +5 -2
  17. package/esm2022/public-api.mjs +3 -1
  18. package/fesm2022/ngx-vflow.mjs +301 -71
  19. package/fesm2022/ngx-vflow.mjs.map +1 -1
  20. package/lib/vflow/components/custom-node-base/custom-node-base.component.d.ts +21 -0
  21. package/lib/vflow/components/node/node.component.d.ts +3 -2
  22. package/lib/vflow/components/vflow/vflow.component.d.ts +14 -9
  23. package/lib/vflow/directives/template.directive.d.ts +5 -0
  24. package/lib/vflow/interfaces/component-node-event.interface.d.ts +2 -1
  25. package/lib/vflow/interfaces/node.interface.d.ts +64 -5
  26. package/lib/vflow/interfaces/optimization.interface.d.ts +3 -0
  27. package/lib/vflow/models/edge.model.d.ts +17 -1
  28. package/lib/vflow/models/handle.model.d.ts +2 -1
  29. package/lib/vflow/models/node.model.d.ts +31 -22
  30. package/lib/vflow/public-components/custom-dynamic-node.component.d.ts +13 -0
  31. package/lib/vflow/public-components/custom-node.component.d.ts +6 -13
  32. package/lib/vflow/services/draggable.service.d.ts +9 -3
  33. package/lib/vflow/services/handle.service.d.ts +1 -1
  34. package/lib/vflow/services/node-changes.service.d.ts +1 -4
  35. package/lib/vflow/services/node-rendering.service.d.ts +1 -0
  36. package/lib/vflow/utils/reference-keeper.d.ts +2 -2
  37. package/lib/vflow/vflow.module.d.ts +1 -1
  38. package/package.json +1 -1
  39. package/public-api.d.ts +2 -0
@@ -4,8 +4,8 @@ import * as i0 from '@angular/core';
4
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';
5
5
  import { select } from 'd3-selection';
6
6
  import { zoomIdentity, zoom } from 'd3-zoom';
7
- import { Subject, tap, merge, BehaviorSubject, observeOn, animationFrameScheduler, switchMap, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, fromEvent, share, Observable, startWith } from 'rxjs';
8
- import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
7
+ import { Subject, tap, merge, observeOn, animationFrameScheduler, switchMap, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, fromEvent, share, Observable, startWith } from 'rxjs';
8
+ import { takeUntilDestroyed, toObservable, toSignal } 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';
@@ -378,18 +378,24 @@ const round = (num) => Math.round(num * 100) / 100;
378
378
 
379
379
  class DraggableService {
380
380
  /**
381
- * Enable or disable draggable behavior for element.
382
- * model contains draggable flag which declares if draggable should be enabled or not
381
+ * Enable draggable behavior for element.
383
382
  *
384
383
  * @param element target element for toggling draggable
385
384
  * @param model model with data for this element
386
385
  */
387
- toggleDraggable(element, model) {
386
+ enable(element, model) {
388
387
  const d3Element = select(element);
389
- const behavior = model.draggable
390
- ? this.getDragBehavior(model)
391
- : this.getIgnoreDragBehavior();
392
- d3Element.call(behavior);
388
+ d3Element.call(this.getDragBehavior(model));
389
+ }
390
+ /**
391
+ * Disable draggable behavior for element.
392
+ *
393
+ * @param element target element for toggling draggable
394
+ * @param model model with data for this element
395
+ */
396
+ disable(element) {
397
+ const d3Element = select(element);
398
+ d3Element.call(this.getIgnoreDragBehavior());
393
399
  }
394
400
  /**
395
401
  * TODO: not shure if this work, need to check
@@ -414,10 +420,19 @@ class DraggableService {
414
420
  deltaY = model.point().y - event.y;
415
421
  })
416
422
  .on('drag', (event) => {
417
- model.setPoint({
423
+ let point = {
418
424
  x: round(event.x + deltaX),
419
425
  y: round(event.y + deltaY)
420
- });
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);
421
436
  });
422
437
  }
423
438
  /**
@@ -481,6 +496,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
481
496
  type: Directive,
482
497
  args: [{ selector: 'ng-template[nodeHtml]' }]
483
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
+ }] });
484
510
  class HandleTemplateDirective {
485
511
  constructor() {
486
512
  this.templateRef = inject(TemplateRef);
@@ -706,7 +732,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
706
732
  type: Injectable
707
733
  }] });
708
734
 
709
- class CustomNodeComponent {
735
+ class CustomNodeBaseComponent {
710
736
  constructor() {
711
737
  this.eventBus = inject(ComponentEventBusService);
712
738
  this.destroyRef = inject(DestroyRef);
@@ -714,12 +740,13 @@ class CustomNodeComponent {
714
740
  * Signal with selected state of node
715
741
  */
716
742
  this.selected = signal(false);
743
+ this.data = signal(undefined);
717
744
  }
718
745
  set _selected(value) {
719
746
  this.selected.set(value);
720
747
  }
721
748
  ngOnInit() {
722
- this.trackEvents();
749
+ this.trackEvents().pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
723
750
  }
724
751
  trackEvents() {
725
752
  const props = Object.getOwnPropertyNames(this);
@@ -730,68 +757,216 @@ class CustomNodeComponent {
730
757
  emitters.set(field, prop);
731
758
  }
732
759
  }
733
- merge(...Array.from(emitters.keys()).map(emitter => emitter.pipe(tap((event) => {
760
+ return merge(...Array.from(emitters.keys()).map(emitter => emitter.pipe(tap((event) => {
734
761
  this.eventBus.pushEvent({
735
762
  nodeId: this.node.id,
736
763
  eventName: emitters.get(emitter),
737
764
  eventPayload: event
738
765
  });
739
- }))))
740
- .pipe(takeUntilDestroyed(this.destroyRef))
741
- .subscribe();
766
+ }))));
742
767
  }
743
768
  ;
744
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
745
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CustomNodeComponent, inputs: { node: "node", _selected: "_selected" }, ngImport: i0 }); }
769
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomNodeBaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
770
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CustomNodeBaseComponent, inputs: { _selected: "_selected" }, ngImport: i0 }); }
771
+ }
772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomNodeBaseComponent, decorators: [{
773
+ type: Directive
774
+ }], propDecorators: { _selected: [{
775
+ type: Input
776
+ }] } });
777
+
778
+ class CustomNodeComponent extends CustomNodeBaseComponent {
779
+ ngOnInit() {
780
+ if (this.node.data) {
781
+ this.data.set(this.node.data);
782
+ }
783
+ super.ngOnInit();
784
+ }
785
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
786
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CustomNodeComponent, inputs: { node: "node" }, usesInheritance: true, ngImport: i0 }); }
746
787
  }
747
788
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomNodeComponent, decorators: [{
748
789
  type: Directive
749
790
  }], propDecorators: { node: [{
750
791
  type: Input
751
- }], _selected: [{
792
+ }] } });
793
+
794
+ class CustomDynamicNodeComponent extends CustomNodeBaseComponent {
795
+ ngOnInit() {
796
+ if (this.node.data) {
797
+ this.data = this.node.data;
798
+ }
799
+ super.ngOnInit();
800
+ }
801
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomDynamicNodeComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
802
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: CustomDynamicNodeComponent, inputs: { node: "node" }, usesInheritance: true, ngImport: i0 }); }
803
+ }
804
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CustomDynamicNodeComponent, decorators: [{
805
+ type: Directive
806
+ }], propDecorators: { node: [{
752
807
  type: Input
753
808
  }] } });
754
809
 
810
+ function isStaticNode(node) {
811
+ return typeof node.point !== 'function';
812
+ }
813
+ function isDynamicNode(node) {
814
+ return typeof node.point === 'function';
815
+ }
816
+ function isComponentStaticNode(node) {
817
+ return CustomNodeComponent.isPrototypeOf(node.type);
818
+ }
819
+ function isComponentDynamicNode(node) {
820
+ return CustomDynamicNodeComponent.isPrototypeOf(node.type);
821
+ }
822
+ function isTemplateStaticNode(node) {
823
+ return node.type === 'html-template';
824
+ }
825
+ function isTemplateDynamicNode(node) {
826
+ return node.type === 'html-template';
827
+ }
828
+ function isDefaultStaticNode(node) {
829
+ return node.type === 'default';
830
+ }
831
+ function isDefaultDynamicNode(node) {
832
+ return node.type === 'default';
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
+ }
846
+
755
847
  class NodeModel {
756
- static { this.defaultTypeSize = {
757
- width: 100,
758
- height: 50
759
- }; }
848
+ static { this.defaultWidth = 100; }
849
+ static { this.defaultHeight = 50; }
850
+ static { this.defaultColor = '#1b262c'; }
760
851
  constructor(node) {
761
852
  this.node = node;
762
853
  this.flowSettingsService = inject(FlowSettingsService);
763
- this.internalPoint$ = new BehaviorSubject({ x: 0, y: 0 });
764
- this.throttledPoint$ = this.internalPoint$.pipe(observeOn(animationFrameScheduler));
854
+ this.entitiesService = inject(FlowEntitiesService);
855
+ this.internalPoint = this.createInternalPointSignal();
856
+ this.throttledPoint$ = toObservable(this.internalPoint).pipe(observeOn(animationFrameScheduler));
765
857
  this.point = toSignal(this.throttledPoint$, {
766
- initialValue: this.internalPoint$.getValue()
858
+ initialValue: this.internalPoint()
767
859
  });
768
860
  this.point$ = this.throttledPoint$;
769
861
  this.size = signal({ width: 0, height: 0 });
770
862
  this.renderOrder = signal(0);
771
863
  this.selected = signal(false);
772
864
  this.selected$ = toObservable(this.selected);
773
- this.pointTransform = computed(() => `translate(${this.point().x}, ${this.point().y})`);
865
+ this.globalPoint = computed(() => {
866
+ let parent = this.parent();
867
+ let x = this.point().x;
868
+ let y = this.point().y;
869
+ while (parent !== null) {
870
+ x += parent.point().x;
871
+ y += parent.point().y;
872
+ parent = parent.parent();
873
+ }
874
+ return { x, y };
875
+ });
876
+ this.pointTransform = computed(() => `translate(${this.globalPoint().x}, ${this.globalPoint().y})`);
774
877
  // Now source and handle positions derived from parent flow
775
878
  this.sourcePosition = computed(() => this.flowSettingsService.handlePositions().source);
776
879
  this.targetPosition = computed(() => this.flowSettingsService.handlePositions().target);
777
880
  this.handles = signal([]);
778
881
  this.handles$ = toObservable(this.handles);
779
- this.draggable = true;
882
+ this.draggable = signal(true);
780
883
  // disabled for configuration for now
781
884
  this.magnetRadius = 20;
782
- this.isComponentType = CustomNodeComponent.isPrototypeOf(this.node.type);
885
+ // TODO: not sure if we need to statically store it
886
+ this.isComponentType = CustomNodeComponent.isPrototypeOf(this.node.type) ||
887
+ CustomDynamicNodeComponent.isPrototypeOf(this.node.type);
888
+ // Default node specific thing
889
+ this.text = this.createTextSignal();
890
+ // Component node specific thing
783
891
  this.componentTypeInputs = computed(() => {
784
892
  return {
785
893
  node: this.node,
786
894
  _selected: this.selected()
787
895
  };
788
896
  });
789
- this.setPoint(node.point);
790
- if (isDefined(node.draggable))
791
- this.draggable = node.draggable;
897
+ this.parent = computed(() => this.entitiesService.nodes().find(n => n.node.id === this.parentId()) ?? null);
898
+ this.color = signal(NodeModel.defaultColor);
899
+ this.parentId = signal(null);
900
+ if (isDefined(node.draggable)) {
901
+ if (isDynamicNode(node)) {
902
+ this.draggable = node.draggable;
903
+ }
904
+ else {
905
+ this.draggable.set(node.draggable);
906
+ }
907
+ }
908
+ if (isDefined(node.parentId)) {
909
+ if (isDynamicNode(node)) {
910
+ this.parentId = node.parentId;
911
+ }
912
+ else {
913
+ this.parentId.set(node.parentId);
914
+ }
915
+ }
916
+ if (node.type === 'default-group' && node.color) {
917
+ if (isDynamicNode(node)) {
918
+ this.color = node.color;
919
+ }
920
+ else {
921
+ this.color.set(node.color);
922
+ }
923
+ }
792
924
  }
793
925
  setPoint(point) {
794
- this.internalPoint$.next(point);
926
+ this.internalPoint.set(point);
927
+ }
928
+ /**
929
+ * TODO find the way to implement this better
930
+ */
931
+ linkDefaultNodeSizeWithModelSize() {
932
+ const node = this.node;
933
+ switch (node.type) {
934
+ case 'default':
935
+ case 'default-group':
936
+ case 'template-group': {
937
+ if (isDynamicNode(node)) {
938
+ effect(() => {
939
+ this.size.set({
940
+ width: node.width?.() ?? NodeModel.defaultWidth,
941
+ height: node.height?.() ?? NodeModel.defaultHeight,
942
+ });
943
+ }, { allowSignalWrites: true });
944
+ }
945
+ else {
946
+ this.size.set({
947
+ width: node.width ?? NodeModel.defaultWidth,
948
+ height: node.height ?? NodeModel.defaultHeight
949
+ });
950
+ }
951
+ }
952
+ }
953
+ }
954
+ createTextSignal() {
955
+ const node = this.node;
956
+ if (node.type === 'default') {
957
+ if (isDynamicNode(node)) {
958
+ return node.text ?? signal('');
959
+ }
960
+ else {
961
+ return signal(node.text ?? '');
962
+ }
963
+ }
964
+ return signal('');
965
+ }
966
+ createInternalPointSignal() {
967
+ return isDynamicNode(this.node)
968
+ ? this.node.point
969
+ : signal({ x: this.node.point.x, y: this.node.point.y });
795
970
  }
796
971
  }
797
972
 
@@ -1242,10 +1417,19 @@ class NodeRenderingService {
1242
1417
  return this.flowEntitiesService.nodes()
1243
1418
  .sort((aNode, bNode) => aNode.renderOrder() - bNode.renderOrder());
1244
1419
  });
1420
+ this.maxOrder = computed(() => {
1421
+ return Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
1422
+ });
1245
1423
  }
1246
1424
  pullNode(node) {
1247
- const maxOrder = Math.max(...this.flowEntitiesService.nodes().map((n) => n.renderOrder()));
1248
- node.renderOrder.set(maxOrder + 1);
1425
+ // TODO do not pull when the node is already on top
1426
+ // pull node
1427
+ node.renderOrder.set(this.maxOrder() + 1);
1428
+ // pull children
1429
+ this.flowEntitiesService
1430
+ .nodes()
1431
+ .filter(n => n.parent() === node)
1432
+ .forEach(n => this.pullNode(n));
1249
1433
  }
1250
1434
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1251
1435
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeRenderingService }); }
@@ -1327,6 +1511,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1327
1511
  args: [{ selector: 'g[spacePointContext]' }]
1328
1512
  }] });
1329
1513
 
1514
+ function Microtask(target, key, descriptor) {
1515
+ const originalMethod = descriptor.value;
1516
+ descriptor.value = function (...args) {
1517
+ queueMicrotask(() => {
1518
+ originalMethod?.apply(this, args);
1519
+ });
1520
+ };
1521
+ // Return the modified descriptor
1522
+ return descriptor;
1523
+ }
1524
+
1330
1525
  class HandleService {
1331
1526
  constructor() {
1332
1527
  this.node = signal(null);
@@ -1346,9 +1541,12 @@ class HandleService {
1346
1541
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1347
1542
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleService }); }
1348
1543
  }
1544
+ __decorate([
1545
+ Microtask // TODO fixes rendering of handle for group node
1546
+ ], HandleService.prototype, "createHandle", null);
1349
1547
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HandleService, decorators: [{
1350
1548
  type: Injectable
1351
- }] });
1549
+ }], propDecorators: { createHandle: [] } });
1352
1550
 
1353
1551
  function resizable(elems) {
1354
1552
  return new Observable((subscriber) => {
@@ -1377,17 +1575,6 @@ const implementsWithInjector = (instance) => {
1377
1575
  return 'injector' in instance && 'get' in instance.injector;
1378
1576
  };
1379
1577
 
1380
- function Microtask(target, key, descriptor) {
1381
- const originalMethod = descriptor.value;
1382
- descriptor.value = function (...args) {
1383
- queueMicrotask(() => {
1384
- originalMethod?.apply(this, args);
1385
- });
1386
- };
1387
- // Return the modified descriptor
1388
- return descriptor;
1389
- }
1390
-
1391
1578
  class HandleModel {
1392
1579
  constructor(rawHandle, parentNode) {
1393
1580
  this.rawHandle = rawHandle;
@@ -1431,21 +1618,22 @@ class HandleModel {
1431
1618
  });
1432
1619
  this.pointAbsolute = computed(() => {
1433
1620
  return {
1434
- x: this.parentNode.point().x + this.offset().x + this.sizeOffset().x,
1435
- y: this.parentNode.point().y + this.offset().y + this.sizeOffset().y,
1621
+ x: this.parentNode.globalPoint().x + this.offset().x + this.sizeOffset().x,
1622
+ y: this.parentNode.globalPoint().y + this.offset().y + this.sizeOffset().y,
1436
1623
  };
1437
1624
  });
1438
1625
  this.state = signal('idle');
1439
1626
  this.updateParentSizeAndPosition$ = new Subject();
1440
- this.parentSize = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => ({
1441
- width: this.parentReference.offsetWidth,
1442
- height: this.parentReference.offsetHeight
1443
- }))), {
1627
+ this.parentSize = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => this.getParentSize())), {
1444
1628
  initialValue: { width: 0, height: 0 }
1445
1629
  });
1446
1630
  this.parentPosition = toSignal(this.updateParentSizeAndPosition$.pipe(map(() => ({
1447
- x: this.parentReference.offsetLeft,
1448
- y: this.parentReference.offsetTop
1631
+ x: this.parentReference instanceof HTMLElement
1632
+ ? this.parentReference.offsetLeft
1633
+ : 0,
1634
+ y: this.parentReference instanceof HTMLElement
1635
+ ? this.parentReference.offsetTop
1636
+ : 0 // for now just 0 for group nodes
1449
1637
  }))), {
1450
1638
  initialValue: { x: 0, y: 0 }
1451
1639
  });
@@ -1461,6 +1649,18 @@ class HandleModel {
1461
1649
  updateParent() {
1462
1650
  this.updateParentSizeAndPosition$.next();
1463
1651
  }
1652
+ getParentSize() {
1653
+ if (this.parentReference instanceof HTMLElement) {
1654
+ return {
1655
+ width: this.parentReference.offsetWidth,
1656
+ height: this.parentReference.offsetHeight
1657
+ };
1658
+ }
1659
+ else if (this.parentReference instanceof SVGGraphicsElement) {
1660
+ return this.parentReference.getBBox();
1661
+ }
1662
+ return { width: 0, height: 0 };
1663
+ }
1464
1664
  }
1465
1665
 
1466
1666
  class HandleComponent {
@@ -1637,7 +1837,14 @@ class NodeComponent {
1637
1837
  }
1638
1838
  ngOnInit() {
1639
1839
  this.handleService.node.set(this.nodeModel);
1640
- this.draggableService.toggleDraggable(this.hostRef.nativeElement, this.nodeModel);
1840
+ effect(() => {
1841
+ if (this.nodeModel.draggable()) {
1842
+ this.draggableService.enable(this.hostRef.nativeElement, this.nodeModel);
1843
+ }
1844
+ else {
1845
+ this.draggableService.disable(this.hostRef.nativeElement);
1846
+ }
1847
+ });
1641
1848
  this.nodeModel.handles$
1642
1849
  .pipe(switchMap((handles) => resizable(handles.map(h => h.parentReference)).pipe(map(() => handles))), tap((handles) => {
1643
1850
  // TODO (performance) inspect how to avoid calls of this when flow initially rendered
@@ -1646,12 +1853,7 @@ class NodeComponent {
1646
1853
  .subscribe();
1647
1854
  }
1648
1855
  ngAfterViewInit() {
1649
- if (this.nodeModel.node.type === 'default') {
1650
- this.nodeModel.size.set({
1651
- width: this.nodeModel.node.width ?? NodeModel.defaultTypeSize.width,
1652
- height: this.nodeModel.node.height ?? NodeModel.defaultTypeSize.height
1653
- });
1654
- }
1856
+ this.nodeModel.linkDefaultNodeSizeWithModelSize();
1655
1857
  if (this.nodeModel.node.type === 'html-template' || this.nodeModel.isComponentType) {
1656
1858
  resizable([this.htmlWrapperRef.nativeElement])
1657
1859
  .pipe(startWith(null), tap(() => {
@@ -1687,7 +1889,7 @@ class NodeComponent {
1687
1889
  }
1688
1890
  }
1689
1891
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1690
- 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.node.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 }); }
1892
+ 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], 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 #htmlWrapper class=\"wrapper\">\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 #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<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\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<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 } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\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: [".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-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: "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 }); }
1691
1893
  }
1692
1894
  __decorate([
1693
1895
  InjectionContext
@@ -1699,10 +1901,12 @@ __decorate([
1699
1901
  ], NodeComponent.prototype, "ngAfterViewInit", null);
1700
1902
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
1701
1903
  type: Component,
1702
- 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.node.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"] }]
1904
+ args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService], 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 #htmlWrapper class=\"wrapper\">\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 #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<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\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<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 } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\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: [".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-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
1703
1905
  }], propDecorators: { nodeModel: [{
1704
1906
  type: Input
1705
- }], nodeHtmlTemplate: [{
1907
+ }], nodeTemplate: [{
1908
+ type: Input
1909
+ }], groupNodeTemplate: [{
1706
1910
  type: Input
1707
1911
  }], nodeContentRef: [{
1708
1912
  type: ViewChild,
@@ -2114,6 +2318,9 @@ class VflowComponent {
2114
2318
  * Background for flow
2115
2319
  */
2116
2320
  this.background = '#fff';
2321
+ this.optimization = {
2322
+ computeLayersOnInit: true
2323
+ };
2117
2324
  this.nodeModels = computed(() => this.nodeRenderingService.nodes());
2118
2325
  this.edgeModels = computed(() => this.flowEntitiesService.validEdges());
2119
2326
  // #endregion
@@ -2224,6 +2431,9 @@ class VflowComponent {
2224
2431
  addNodesToEdges(this.nodeModels(), newModels);
2225
2432
  this.flowEntitiesService.edges.set(newModels);
2226
2433
  }
2434
+ ngOnInit() {
2435
+ this.setInitialNodesOrder();
2436
+ }
2227
2437
  // #region METHODS_API
2228
2438
  /**
2229
2439
  * Change viewport to specified state
@@ -2279,8 +2489,20 @@ class VflowComponent {
2279
2489
  trackEdges(idx, { edge }) {
2280
2490
  return edge;
2281
2491
  }
2492
+ setInitialNodesOrder() {
2493
+ if (this.optimization.computeLayersOnInit) {
2494
+ this.nodeModels().forEach(model => {
2495
+ switch (model.node.type) {
2496
+ case 'default-group':
2497
+ case 'template-group': {
2498
+ this.nodeRenderingService.pullNode(model);
2499
+ }
2500
+ }
2501
+ });
2502
+ }
2503
+ }
2282
2504
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2283
- 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: [
2505
+ 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: [
2284
2506
  DraggableService,
2285
2507
  ViewportService,
2286
2508
  FlowStatusService,
@@ -2291,7 +2513,7 @@ class VflowComponent {
2291
2513
  SelectionService,
2292
2514
  FlowSettingsService,
2293
2515
  ComponentEventBusService
2294
- ], 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 }); }
2516
+ ], 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.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 }); }
2295
2517
  }
2296
2518
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
2297
2519
  type: Component,
@@ -2309,7 +2531,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2309
2531
  ], hostDirectives: [
2310
2532
  connectionControllerHostDirective,
2311
2533
  changesControllerHostDirective
2312
- ], 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"] }]
2534
+ ], 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"] }]
2313
2535
  }], propDecorators: { view: [{
2314
2536
  type: Input
2315
2537
  }], minZoom: [{
@@ -2320,6 +2542,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2320
2542
  type: Input
2321
2543
  }], background: [{
2322
2544
  type: Input
2545
+ }], optimization: [{
2546
+ type: Input
2323
2547
  }], entitiesSelectable: [{
2324
2548
  type: Input
2325
2549
  }], connection: [{
@@ -2332,9 +2556,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2332
2556
  type: Input
2333
2557
  }], onComponentNodeEvent: [{
2334
2558
  type: Output
2335
- }], nodeHtmlDirective: [{
2559
+ }], nodeTemplateDirective: [{
2336
2560
  type: ContentChild,
2337
2561
  args: [NodeHtmlTemplateDirective]
2562
+ }], groupNodeTemplateDirective: [{
2563
+ type: ContentChild,
2564
+ args: [GroupNodeTemplateDirective]
2338
2565
  }], edgeTemplateDirective: [{
2339
2566
  type: ContentChild,
2340
2567
  args: [EdgeTemplateDirective]
@@ -2408,6 +2635,7 @@ const directives = [
2408
2635
  ];
2409
2636
  const templateDirectives = [
2410
2637
  NodeHtmlTemplateDirective,
2638
+ GroupNodeTemplateDirective,
2411
2639
  EdgeLabelHtmlTemplateDirective,
2412
2640
  EdgeTemplateDirective,
2413
2641
  ConnectionTemplateDirective,
@@ -2431,12 +2659,14 @@ class VflowModule {
2431
2659
  PointerDirective,
2432
2660
  RootPointerDirective,
2433
2661
  FlowSizeControllerDirective, NodeHtmlTemplateDirective,
2662
+ GroupNodeTemplateDirective,
2434
2663
  EdgeLabelHtmlTemplateDirective,
2435
2664
  EdgeTemplateDirective,
2436
2665
  ConnectionTemplateDirective,
2437
2666
  HandleTemplateDirective], imports: [CommonModule], exports: [VflowComponent,
2438
2667
  HandleComponent,
2439
2668
  SelectableDirective, NodeHtmlTemplateDirective,
2669
+ GroupNodeTemplateDirective,
2440
2670
  EdgeLabelHtmlTemplateDirective,
2441
2671
  EdgeTemplateDirective,
2442
2672
  ConnectionTemplateDirective,
@@ -2463,5 +2693,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2463
2693
  * Generated bundle index. Do not edit.
2464
2694
  */
2465
2695
 
2466
- export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomNodeComponent, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule };
2696
+ export { ChangesControllerDirective, ConnectionControllerDirective, ConnectionTemplateDirective, CustomDynamicNodeComponent, CustomNodeComponent, EdgeLabelHtmlTemplateDirective, EdgeTemplateDirective, GroupNodeTemplateDirective, HandleComponent, HandleTemplateDirective, NodeHtmlTemplateDirective, SelectableDirective, VflowComponent, VflowModule, isComponentDynamicNode, isComponentStaticNode, isDefaultDynamicGroupNode, isDefaultDynamicNode, isDefaultStaticGroupNode, isDefaultStaticNode, isDynamicNode, isStaticNode, isTemplateDynamicGroupNode, isTemplateDynamicNode, isTemplateStaticGroupNode, isTemplateStaticNode };
2467
2697
  //# sourceMappingURL=ngx-vflow.mjs.map