ngx-vflow 0.12.0 → 0.13.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 (41) hide show
  1. package/esm2022/lib/vflow/components/custom-node-base/custom-node-base.component.mjs +7 -3
  2. package/esm2022/lib/vflow/components/node/node.component.mjs +3 -3
  3. package/esm2022/lib/vflow/components/vflow/vflow.component.mjs +13 -4
  4. package/esm2022/lib/vflow/directives/flow-size-controller.directive.mjs +17 -16
  5. package/esm2022/lib/vflow/directives/root-pointer.directive.mjs +1 -1
  6. package/esm2022/lib/vflow/directives/selectable.directive.mjs +5 -2
  7. package/esm2022/lib/vflow/interfaces/component-node-event.interface.mjs +1 -1
  8. package/esm2022/lib/vflow/interfaces/node.interface.mjs +3 -3
  9. package/esm2022/lib/vflow/models/node.model.mjs +3 -3
  10. package/esm2022/lib/vflow/public-components/custom-dynamic-node/custom-dynamic-node.component.mjs +19 -0
  11. package/esm2022/lib/vflow/public-components/custom-node/custom-node.component.mjs +19 -0
  12. package/esm2022/lib/vflow/public-components/resizable/resizable.component.mjs +124 -180
  13. package/esm2022/lib/vflow/services/component-event-bus.service.mjs +1 -1
  14. package/esm2022/lib/vflow/services/draggable.service.mjs +40 -19
  15. package/esm2022/lib/vflow/services/keyboard.service.mjs +45 -0
  16. package/esm2022/lib/vflow/services/node-changes.service.mjs +6 -7
  17. package/esm2022/lib/vflow/services/selection.service.mjs +12 -5
  18. package/esm2022/lib/vflow/types/keyboard-action.type.mjs +2 -0
  19. package/esm2022/lib/vflow/utils/get-os.mjs +24 -0
  20. package/esm2022/public-api.mjs +4 -3
  21. package/fesm2022/ngx-vflow.mjs +279 -231
  22. package/fesm2022/ngx-vflow.mjs.map +1 -1
  23. package/lib/vflow/components/vflow/vflow.component.d.ts +4 -1
  24. package/lib/vflow/directives/flow-size-controller.directive.d.ts +3 -2
  25. package/lib/vflow/directives/root-pointer.directive.d.ts +10 -16
  26. package/lib/vflow/directives/space-point-context.directive.d.ts +1 -15
  27. package/lib/vflow/interfaces/component-node-event.interface.d.ts +2 -2
  28. package/lib/vflow/interfaces/node.interface.d.ts +2 -2
  29. package/lib/vflow/models/edge.model.d.ts +17 -1
  30. package/lib/vflow/public-components/{custom-dynamic-node.component.d.ts → custom-dynamic-node/custom-dynamic-node.component.d.ts} +2 -2
  31. package/lib/vflow/public-components/{custom-node.component.d.ts → custom-node/custom-node.component.d.ts} +2 -2
  32. package/lib/vflow/public-components/resizable/resizable.component.d.ts +6 -11
  33. package/lib/vflow/services/draggable.service.d.ts +2 -0
  34. package/lib/vflow/services/keyboard.service.d.ts +11 -0
  35. package/lib/vflow/services/selection.service.d.ts +1 -0
  36. package/lib/vflow/types/keyboard-action.type.d.ts +2 -0
  37. package/lib/vflow/utils/get-os.d.ts +1 -0
  38. package/package.json +1 -1
  39. package/public-api.d.ts +3 -2
  40. package/esm2022/lib/vflow/public-components/custom-dynamic-node.component.mjs +0 -19
  41. package/esm2022/lib/vflow/public-components/custom-node.component.mjs +0 -19
@@ -1,11 +1,11 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { signal, 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';
4
+ import { signal, computed, Injectable, inject, ElementRef, Directive, effect, untracked, TemplateRef, EventEmitter, Output, DestroyRef, Input, runInInjectionContext, Injector, Component, ChangeDetectionStrategy, HostListener, ViewChild, NgZone, ContentChild, NgModule } from '@angular/core';
5
5
  import { select } from 'd3-selection';
6
6
  import { zoomIdentity, zoom } from 'd3-zoom';
7
- import { 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';
7
+ import { switchMap, merge, fromEvent, tap, Subject, observeOn, animationFrameScheduler, skip, map, pairwise, filter, distinctUntilChanged, asyncScheduler, zip, share, Observable, startWith } from 'rxjs';
8
+ import { toObservable, takeUntilDestroyed, 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';
@@ -249,9 +249,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
249
249
  }]
250
250
  }] });
251
251
 
252
+ function getOS() {
253
+ const userAgent = window.navigator.userAgent.toLowerCase();
254
+ const macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos)/i;
255
+ const windowsPlatforms = /(win32|win64|windows|wince)/i;
256
+ const iosPlatforms = /(iphone|ipad|ipod)/i;
257
+ let os = null;
258
+ if (macosPlatforms.test(userAgent)) {
259
+ os = "macos";
260
+ }
261
+ else if (iosPlatforms.test(userAgent)) {
262
+ os = "ios";
263
+ }
264
+ else if (windowsPlatforms.test(userAgent)) {
265
+ os = "windows";
266
+ }
267
+ else if (/android/.test(userAgent)) {
268
+ os = "android";
269
+ }
270
+ else if (!os && /linux/.test(userAgent)) {
271
+ os = "linux";
272
+ }
273
+ return os;
274
+ }
275
+
276
+ class KeyboardService {
277
+ constructor() {
278
+ this.actions = signal({
279
+ multiSelection: [
280
+ getOS() === 'macos' ? 'MetaLeft' : 'ControlLeft',
281
+ getOS() === 'macos' ? 'MetaRight' : 'ControlRight',
282
+ ]
283
+ });
284
+ this.actionsActive = {
285
+ multiSelection: false
286
+ };
287
+ toObservable(this.actions).pipe(switchMap(() => merge(fromEvent(document, 'keydown').pipe(tap(event => {
288
+ for (const action in this.actions()) {
289
+ const keyCodes = this.actions()[action] ?? [];
290
+ if (keyCodes.includes(event.code)) {
291
+ this.actionsActive[action] = true;
292
+ }
293
+ }
294
+ })), fromEvent(document, 'keyup').pipe(tap(event => {
295
+ for (const action in this.actions()) {
296
+ const keyCodes = this.actions()[action] ?? [];
297
+ if (keyCodes.includes(event.code)) {
298
+ this.actionsActive[action] = false;
299
+ }
300
+ }
301
+ })))), takeUntilDestroyed()).subscribe();
302
+ }
303
+ setShortcuts(newActions) {
304
+ this.actions.update((actions) => ({ ...actions, ...newActions }));
305
+ }
306
+ isActiveAction(action) {
307
+ return this.actionsActive[action];
308
+ }
309
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: KeyboardService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
310
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: KeyboardService }); }
311
+ }
312
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: KeyboardService, decorators: [{
313
+ type: Injectable
314
+ }], ctorParameters: function () { return []; } });
315
+
252
316
  class SelectionService {
253
317
  constructor() {
254
318
  this.flowEntitiesService = inject(FlowEntitiesService);
319
+ this.keyboardService = inject(KeyboardService);
255
320
  this.viewport$ = new Subject();
256
321
  this.resetSelection = this.viewport$.pipe(tap(({ start, end, target }) => {
257
322
  if (start && end && target) {
@@ -273,10 +338,15 @@ class SelectionService {
273
338
  this.viewport$.next(viewport);
274
339
  }
275
340
  select(entity) {
276
- // undo select for previously selected nodes
277
- this.flowEntitiesService.entities()
278
- .filter(n => n.selected)
279
- .forEach(n => n.selected.set(false));
341
+ // ? May be not a responsibility of this method
342
+ // if entity already selected - do nothing
343
+ if (entity?.selected()) {
344
+ return;
345
+ }
346
+ if (!this.keyboardService.isActiveAction('multiSelection')) {
347
+ // undo select for previously selected nodes
348
+ this.flowEntitiesService.entities().forEach(n => n.selected.set(false));
349
+ }
280
350
  if (entity) {
281
351
  // select passed entity
282
352
  entity.selected.set(true);
@@ -377,6 +447,9 @@ const evTarget = (anyEvent) => {
377
447
  const round = (num) => Math.round(num * 100) / 100;
378
448
 
379
449
  class DraggableService {
450
+ constructor() {
451
+ this.entitiesService = inject(FlowEntitiesService);
452
+ }
380
453
  /**
381
454
  * Enable draggable behavior for element.
382
455
  *
@@ -412,27 +485,24 @@ class DraggableService {
412
485
  * @returns
413
486
  */
414
487
  getDragBehavior(model) {
415
- let deltaX;
416
- let deltaY;
488
+ let dragNodes = [];
489
+ let initialPositions = [];
417
490
  return drag()
418
491
  .on('start', (event) => {
419
- deltaX = model.point().x - event.x;
420
- deltaY = model.point().y - event.y;
492
+ dragNodes = this.getDragNodes(model);
493
+ initialPositions = dragNodes.map(node => ({
494
+ x: node.point().x - event.x,
495
+ y: node.point().y - event.y
496
+ }));
421
497
  })
422
498
  .on('drag', (event) => {
423
- let point = {
424
- x: round(event.x + deltaX),
425
- y: round(event.y + deltaY)
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);
499
+ dragNodes.forEach((model, index) => {
500
+ let point = {
501
+ x: round(event.x + initialPositions[index].x),
502
+ y: round(event.y + initialPositions[index].y)
503
+ };
504
+ moveNode(model, point);
505
+ });
436
506
  });
437
507
  }
438
508
  /**
@@ -445,12 +515,32 @@ class DraggableService {
445
515
  event.sourceEvent.stopPropagation();
446
516
  });
447
517
  }
518
+ getDragNodes(model) {
519
+ return model.selected()
520
+ ? this.entitiesService
521
+ .nodes()
522
+ // selected draggable nodes (with current node)
523
+ .filter(node => node.selected() && node.draggable())
524
+ // we only can move current node if it's not selected
525
+ : [model];
526
+ }
448
527
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DraggableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
449
528
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DraggableService }); }
450
529
  }
451
530
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DraggableService, decorators: [{
452
531
  type: Injectable
453
532
  }] });
533
+ function moveNode(model, point) {
534
+ const parent = model.parent();
535
+ // keep node in bounds of parent
536
+ if (parent) {
537
+ point.x = Math.min(parent.size().width - model.size().width, point.x);
538
+ point.x = Math.max(0, point.x);
539
+ point.y = Math.min(parent.size().height - model.size().height, point.y);
540
+ point.y = Math.max(0, point.y);
541
+ }
542
+ model.setPoint(point, true);
543
+ }
454
544
 
455
545
  class EdgeTemplateDirective {
456
546
  constructor() {
@@ -734,7 +824,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
734
824
 
735
825
  class CustomNodeBaseComponent {
736
826
  constructor() {
737
- this.eventBus = inject(ComponentEventBusService);
827
+ this.eventBus = inject(ComponentEventBusService, { optional: true });
738
828
  this.destroyRef = inject(DestroyRef);
739
829
  /**
740
830
  * Signal with selected state of node
@@ -746,7 +836,11 @@ class CustomNodeBaseComponent {
746
836
  this.selected.set(value);
747
837
  }
748
838
  ngOnInit() {
749
- this.trackEvents().pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
839
+ if (this.eventBus) {
840
+ this.trackEvents()
841
+ .pipe(takeUntilDestroyed(this.destroyRef))
842
+ .subscribe();
843
+ }
750
844
  }
751
845
  trackEvents() {
752
846
  const props = Object.getOwnPropertyNames(this);
@@ -1243,12 +1337,11 @@ class NodesChangeService {
1243
1337
  // Check for nodes list change and watch for specific node from this list change its position
1244
1338
  switchMap((nodes) => merge(...nodes.map(node => node.point$.pipe(
1245
1339
  // skip initial position from signal
1246
- skip(1), map(() => node))))),
1247
- // For now it's a single node, later this list will also be filled
1248
- // with child node position changes
1249
- map(changedNode => [
1250
- { type: 'position', id: changedNode.node.id, point: changedNode.point() }
1251
- ]));
1340
+ skip(1), map(() => node))))), map((changedNode) => {
1341
+ return this.entitiesService.nodes()
1342
+ .filter(node => node === changedNode || node.selected())
1343
+ .map(node => ({ type: 'position', id: node.node.id, point: node.point() }));
1344
+ }));
1252
1345
  this.nodeSizeChange$ = toObservable(this.entitiesService.nodes)
1253
1346
  .pipe(switchMap((nodes) => merge(...nodes.map(node => node.size$.pipe(skip(1), map(() => node))))), map(changedNode => [
1254
1347
  { type: 'size', id: changedNode.node.id, size: changedNode.size() }
@@ -1874,6 +1967,7 @@ class ResizableComponent {
1874
1967
  this.nodeAccessor = inject(NodeAccessorService);
1875
1968
  this.rootPointer = inject(RootPointerDirective);
1876
1969
  this.viewportService = inject(ViewportService);
1970
+ this.spacePointContext = inject(SpacePointContextDirective);
1877
1971
  this.hostRef = inject(ElementRef);
1878
1972
  this.resizerColor = '#2e414c';
1879
1973
  this.gap = 1.5;
@@ -1885,7 +1979,7 @@ class ResizableComponent {
1885
1979
  this.minHeight = 0;
1886
1980
  // TODO: allow reszie beside the flow
1887
1981
  this.resizeOnGlobalMouseMove = this.rootPointer.pointerMovement$
1888
- .pipe(filter(() => this.resizeSide !== null), tap((event) => this.resize(event)), takeUntilDestroyed())
1982
+ .pipe(filter(() => this.resizeSide !== null), filter((event) => event.movementX !== 0 || event.movementY !== 0), tap((event) => this.resize(event)), takeUntilDestroyed())
1889
1983
  .subscribe();
1890
1984
  this.endResizeOnGlobalMouseUp = this.rootPointer.documentPointerEnd$
1891
1985
  .pipe(tap(() => this.endResize()), takeUntilDestroyed())
@@ -1914,193 +2008,55 @@ class ResizableComponent {
1914
2008
  this.resizeSide = side;
1915
2009
  this.model.resizing.set(true);
1916
2010
  }
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
- }
2011
+ resize(event) {
2012
+ if (!this.resizeSide)
2013
+ return;
2014
+ if (this.isResizeConstrained(event))
2015
+ return;
2016
+ const offset = calcOffset(event.movementX, event.movementY, this.zoom());
2017
+ const { x, y, width, height } = constrainRect(applyResize(this.resizeSide, this.model, offset), this.model, this.resizeSide, this.minWidth, this.minHeight);
2018
+ this.model.setPoint({ x, y }, false);
2019
+ this.model.size.set({ width, height });
2052
2020
  }
2053
2021
  endResize() {
2054
2022
  this.resizeSide = null;
2055
2023
  this.model.resizing.set(false);
2056
2024
  }
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;
2025
+ isResizeConstrained({ x, y, movementX, movementY }) {
2026
+ const flowPoint = this.spacePointContext.documentPointToFlowPoint({ x, y });
2027
+ if (this.resizeSide?.includes('right')) {
2028
+ if (movementX > 0 && flowPoint.x < (this.model.point().x + this.model.size().width)) {
2029
+ return true;
2030
+ }
2031
+ if (movementX < 0 && flowPoint.x > this.model.point().x + this.model.size().width) {
2032
+ return true;
2033
+ }
2075
2034
  }
2076
- return -Infinity;
2077
- }
2078
- getMinY() {
2079
- const parent = this.model.parent();
2080
- if (parent) {
2081
- return 0;
2035
+ if (this.resizeSide?.includes('left')) {
2036
+ if (movementX < 0 && flowPoint.x > this.model.point().x) {
2037
+ return true;
2038
+ }
2039
+ if (movementX > 0 && flowPoint.x < this.model.point().x) {
2040
+ return true;
2041
+ }
2082
2042
  }
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;
2043
+ if (this.resizeSide?.includes('bottom')) {
2044
+ if (movementY > 0 && flowPoint.y < (this.model.point().y + this.model.size().height)) {
2045
+ return true;
2046
+ }
2047
+ if (movementY < 0 && flowPoint.y > this.model.point().y + this.model.size().height) {
2048
+ return true;
2049
+ }
2092
2050
  }
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;
2051
+ if (this.resizeSide?.includes('top')) {
2052
+ if (movementY < 0 && flowPoint.y > this.model.point().y) {
2053
+ return true;
2054
+ }
2055
+ if (movementY > 0 && flowPoint.y < this.model.point().y) {
2056
+ return true;
2057
+ }
2102
2058
  }
2103
- return (height - this.minHeight) + y;
2059
+ return false;
2104
2060
  }
2105
2061
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ResizableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2106
2062
  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"] }] }); }
@@ -2121,6 +2077,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2121
2077
  type: ViewChild,
2122
2078
  args: ['resizer', { static: true }]
2123
2079
  }], ngAfterViewInit: [] } });
2080
+ function calcOffset(movementX, movementY, zoom) {
2081
+ return {
2082
+ offsetX: round(movementX / zoom),
2083
+ offsetY: round(movementY / zoom)
2084
+ };
2085
+ }
2086
+ function applyResize(side, model, offset) {
2087
+ const { offsetX, offsetY } = offset;
2088
+ const { x, y } = model.point();
2089
+ const { width, height } = model.size();
2090
+ // Handle each case of resizing (top, bottom, left, right, corners)
2091
+ switch (side) {
2092
+ case 'left':
2093
+ return { x: x + offsetX, y, width: width - offsetX, height };
2094
+ case 'right':
2095
+ return { x, y, width: width + offsetX, height };
2096
+ case 'top':
2097
+ return { x, y: y + offsetY, width, height: height - offsetY };
2098
+ case 'bottom':
2099
+ return { x, y, width, height: height + offsetY };
2100
+ case 'top-left':
2101
+ return { x: x + offsetX, y: y + offsetY, width: width - offsetX, height: height - offsetY };
2102
+ case 'top-right':
2103
+ return { x, y: y + offsetY, width: width + offsetX, height: height - offsetY };
2104
+ case 'bottom-left':
2105
+ return { x: x + offsetX, y, width: width - offsetX, height: height + offsetY };
2106
+ case 'bottom-right':
2107
+ return { x, y, width: width + offsetX, height: height + offsetY };
2108
+ }
2109
+ }
2110
+ function constrainRect(rect, model, side, minWidth, minHeight) {
2111
+ let { x, y, width, height } = rect;
2112
+ // 1. Prevent negative dimensions
2113
+ width = Math.max(width, 0);
2114
+ height = Math.max(height, 0);
2115
+ // 2. Apply minimum size constraints
2116
+ width = Math.max(minWidth, width);
2117
+ height = Math.max(minHeight, height);
2118
+ // Apply left/top constraints based on minimum size
2119
+ x = Math.min(x, model.point().x + model.size().width - minWidth);
2120
+ y = Math.min(y, model.point().y + model.size().height - minHeight);
2121
+ const parent = model.parent();
2122
+ // 3. Apply maximum size constraints based on parent size (if exists)
2123
+ if (parent) {
2124
+ x = Math.max(x, 0); // Left boundary of the parent
2125
+ y = Math.max(y, 0); // Top boundary of the parent
2126
+ if (x === 0) {
2127
+ width = model.point().x + model.size().width;
2128
+ }
2129
+ if (y === 0) {
2130
+ height = model.point().y + model.size().height;
2131
+ }
2132
+ width = Math.min(width, parent.size().width - model.point().x);
2133
+ height = Math.min(height, parent.size().height - model.point().y);
2134
+ }
2135
+ const bounds = getNodesBounds(model.children());
2136
+ // 4. Apply child node constraints (if children exist)
2137
+ if (bounds) {
2138
+ if (side.includes('left')) {
2139
+ x = Math.min(x, (model.point().x + model.size().width) - (bounds.x + bounds.width));
2140
+ width = Math.max(width, bounds.x + bounds.width);
2141
+ }
2142
+ if (side.includes('right')) {
2143
+ width = Math.max(width, bounds.x + bounds.width);
2144
+ }
2145
+ if (side.includes('bottom')) {
2146
+ height = Math.max(height, bounds.y + bounds.height);
2147
+ }
2148
+ if (side.includes('top')) {
2149
+ y = Math.min(y, (model.point().y + model.size().height) - (bounds.y + bounds.height));
2150
+ height = Math.max(height, bounds.y + bounds.height);
2151
+ }
2152
+ }
2153
+ return {
2154
+ x,
2155
+ y,
2156
+ width,
2157
+ height
2158
+ };
2159
+ }
2124
2160
 
2125
2161
  class HandleSizeControllerDirective {
2126
2162
  constructor() {
@@ -2232,7 +2268,7 @@ class NodeComponent {
2232
2268
  }
2233
2269
  }
2234
2270
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
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 }); }
2271
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NodeComponent, selector: "g[node]", inputs: { nodeModel: "nodeModel", nodeTemplate: "nodeTemplate", groupNodeTemplate: "groupNodeTemplate" }, providers: [HandleService, NodeAccessorService], viewQueries: [{ propertyName: "nodeContentRef", first: true, predicate: ["nodeContent"], descendants: true }, { propertyName: "htmlWrapperRef", first: true, predicate: ["htmlWrapper"], descendants: true }], ngImport: i0, template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode(); selectNode()\"\n>\n <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 (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (pointerStart)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (pointerStart)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-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 }); }
2236
2272
  }
2237
2273
  __decorate([
2238
2274
  InjectionContext
@@ -2242,7 +2278,7 @@ __decorate([
2242
2278
  ], NodeComponent.prototype, "ngAfterViewInit", null);
2243
2279
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NodeComponent, decorators: [{
2244
2280
  type: Component,
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"] }]
2281
+ args: [{ selector: 'g[node]', changeDetection: ChangeDetectionStrategy.OnPush, providers: [HandleService, NodeAccessorService], template: "<!-- Default node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.node.type === 'default'\"\n class=\"selectable\"\n #nodeContent\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode(); selectNode()\"\n>\n <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 (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Component node -->\n<svg:foreignObject\n *ngIf=\"nodeModel.isComponentType\"\n class=\"selectable\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n (pointerStart)=\"pullNode()\"\n>\n <div\n #htmlWrapper\n class=\"wrapper\"\n [style.width]=\"styleWidth()\"\n [style.height]=\"styleHeight()\"\n >\n <ng-container\n [ngComponentOutlet]=\"$any(nodeModel.node.type)\"\n [ngComponentOutletInputs]=\"nodeModel.componentTypeInputs()\"\n [ngComponentOutletInjector]=\"injector\"\n />\n </div>\n</svg:foreignObject>\n\n<!-- Default group node -->\n<svg:rect\n *ngIf=\"nodeModel.node.type === 'default-group'\"\n [resizable]=\"nodeModel.resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"nodeModel.color()\"\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [class.default-group-node_selected]=\"nodeModel.selected()\"\n [attr.width]=\"nodeModel.size().width\"\n [attr.height]=\"nodeModel.size().height\"\n [style.stroke]=\"nodeModel.color()\"\n [style.fill]=\"nodeModel.color()\"\n (pointerStart)=\"pullNode(); selectNode()\"\n/>\n\n<!-- Template group node -->\n<svg:g\n *ngIf=\"nodeModel.node.type === 'template-group' && groupNodeTemplate\"\n class=\"selectable\"\n (pointerStart)=\"pullNode()\"\n>\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: { node: nodeModel.node, selected: nodeModel.selected, width: nodeModel.width, height: nodeModel.height } }\"\n [ngTemplateOutletInjector]=\"injector\"\n />\n</svg:g>\n\n<!-- Resizer -->\n<ng-container *ngIf=\"nodeModel.resizerTemplate() as template\">\n <ng-container *ngIf=\"nodeModel.resizable()\">\n <ng-template [ngTemplateOutlet]=\"template\" />\n </ng-container>\n</ng-container>\n\n<!-- Handles -->\n<ng-container *ngFor=\"let handle of nodeModel.handles()\">\n <svg:circle\n *ngIf=\"!handle.template\"\n class=\"default-handle\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n r=\"5\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n />\n\n <svg:g\n *ngIf=\"handle.template\"\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection(handle)\"\n >\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n\n <svg:circle\n *ngIf=\"showMagnet()\"\n class=\"magnet\"\n [attr.r]=\"nodeModel.magnetRadius\"\n [attr.cx]=\"handle.offset().x\"\n [attr.cy]=\"handle.offset().y\"\n (pointerEnd)=\"endConnection(handle); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\"\n />\n</ng-container>\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-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"] }]
2246
2282
  }], propDecorators: { nodeModel: [{
2247
2283
  type: Input
2248
2284
  }], nodeTemplate: [{
@@ -2583,12 +2619,13 @@ class FlowSizeControllerDirective {
2583
2619
  constructor() {
2584
2620
  this.host = inject(ElementRef);
2585
2621
  this.flowSettingsService = inject(FlowSettingsService);
2586
- this.flowWidth = 0;
2587
- this.flowHeight = 0;
2588
- effect(() => {
2622
+ this.flowWidth = computed(() => {
2589
2623
  const view = this.flowSettingsService.view();
2590
- this.flowWidth = view === 'auto' ? '100%' : view[0];
2591
- this.flowHeight = view === 'auto' ? '100%' : view[1];
2624
+ return view === 'auto' ? '100%' : view[0];
2625
+ });
2626
+ this.flowHeight = computed(() => {
2627
+ const view = this.flowSettingsService.view();
2628
+ return view === 'auto' ? '100%' : view[1];
2592
2629
  });
2593
2630
  resizable([this.host.nativeElement], inject(NgZone)).pipe(tap(([entry]) => {
2594
2631
  this.flowSettingsService.computedFlowWidth.set(entry.contentRect.width);
@@ -2596,18 +2633,18 @@ class FlowSizeControllerDirective {
2596
2633
  }), takeUntilDestroyed()).subscribe();
2597
2634
  }
2598
2635
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowSizeControllerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2599
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]", host: { properties: { "attr.width": "this.flowWidth", "attr.height": "this.flowHeight" } }, ngImport: i0 }); }
2636
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: FlowSizeControllerDirective, selector: "svg[flowSizeController]", host: { properties: { "attr.width": "flowWidth()", "attr.height": "flowHeight()" } }, ngImport: i0 }); }
2600
2637
  }
2601
2638
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FlowSizeControllerDirective, decorators: [{
2602
2639
  type: Directive,
2603
- args: [{ selector: 'svg[flowSizeController]' }]
2604
- }], ctorParameters: function () { return []; }, propDecorators: { flowWidth: [{
2605
- type: HostBinding,
2606
- args: ['attr.width']
2607
- }], flowHeight: [{
2608
- type: HostBinding,
2609
- args: ['attr.height']
2610
- }] } });
2640
+ args: [{
2641
+ selector: 'svg[flowSizeController]',
2642
+ host: {
2643
+ '[attr.width]': 'flowWidth()',
2644
+ '[attr.height]': 'flowHeight()'
2645
+ }
2646
+ }]
2647
+ }], ctorParameters: function () { return []; } });
2611
2648
 
2612
2649
  const connectionControllerHostDirective = {
2613
2650
  directive: ConnectionControllerDirective,
@@ -2657,6 +2694,7 @@ class VflowComponent {
2657
2694
  this.nodeRenderingService = inject(NodeRenderingService);
2658
2695
  this.flowSettingsService = inject(FlowSettingsService);
2659
2696
  this.componentEventBusService = inject(ComponentEventBusService);
2697
+ this.keyboardService = inject(KeyboardService);
2660
2698
  this.injector = inject(Injector);
2661
2699
  /**
2662
2700
  * Background for flow
@@ -2748,6 +2786,9 @@ class VflowComponent {
2748
2786
  set entitiesSelectable(value) {
2749
2787
  this.flowSettingsService.entitiesSelectable.set(value);
2750
2788
  }
2789
+ set keyboardShortcuts(value) {
2790
+ this.keyboardService.setShortcuts(value);
2791
+ }
2751
2792
  /**
2752
2793
  * Settings for connection (it renders when user tries to create edge between nodes)
2753
2794
  *
@@ -2846,7 +2887,7 @@ class VflowComponent {
2846
2887
  }
2847
2888
  }
2848
2889
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
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: [
2890
+ 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", keyboardShortcuts: "keyboardShortcuts", connection: ["connection", "connection", (settings) => new ConnectionModel(settings)], nodes: "nodes", edges: "edges" }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
2850
2891
  DraggableService,
2851
2892
  ViewportService,
2852
2893
  FlowStatusService,
@@ -2856,7 +2897,8 @@ class VflowComponent {
2856
2897
  NodeRenderingService,
2857
2898
  SelectionService,
2858
2899
  FlowSettingsService,
2859
- ComponentEventBusService
2900
+ ComponentEventBusService,
2901
+ KeyboardService
2860
2902
  ], 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 }); }
2861
2903
  }
2862
2904
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: VflowComponent, decorators: [{
@@ -2871,7 +2913,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2871
2913
  NodeRenderingService,
2872
2914
  SelectionService,
2873
2915
  FlowSettingsService,
2874
- ComponentEventBusService
2916
+ ComponentEventBusService,
2917
+ KeyboardService
2875
2918
  ], hostDirectives: [
2876
2919
  connectionControllerHostDirective,
2877
2920
  changesControllerHostDirective
@@ -2890,6 +2933,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2890
2933
  type: Input
2891
2934
  }], entitiesSelectable: [{
2892
2935
  type: Input
2936
+ }], keyboardShortcuts: [{
2937
+ type: Input
2893
2938
  }], connection: [{
2894
2939
  type: Input,
2895
2940
  args: [{ transform: (settings) => new ConnectionModel(settings) }]
@@ -2946,7 +2991,7 @@ class SelectableDirective {
2946
2991
  return null;
2947
2992
  }
2948
2993
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectableDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2949
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: SelectableDirective, selector: "[selectable]", host: { listeners: { "mousedown": "onMousedown()" } }, ngImport: i0 }); }
2994
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: SelectableDirective, selector: "[selectable]", host: { listeners: { "mousedown": "onMousedown()", "touchstart": "onMousedown()" } }, ngImport: i0 }); }
2950
2995
  }
2951
2996
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SelectableDirective, decorators: [{
2952
2997
  type: Directive,
@@ -2954,6 +2999,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
2954
2999
  }], propDecorators: { onMousedown: [{
2955
3000
  type: HostListener,
2956
3001
  args: ['mousedown']
3002
+ }, {
3003
+ type: HostListener,
3004
+ args: ['touchstart']
2957
3005
  }] } });
2958
3006
 
2959
3007
  const components = [