ngx-vflow 1.1.1 → 1.2.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.
@@ -3,10 +3,12 @@ import { select } from 'd3-selection';
3
3
  import { drag } from 'd3-drag';
4
4
  import { round } from '../utils/round';
5
5
  import { FlowEntitiesService } from './flow-entities.service';
6
+ import { FlowSettingsService } from './flow-settings.service';
6
7
  import * as i0 from "@angular/core";
7
8
  export class DraggableService {
8
9
  constructor() {
9
10
  this.entitiesService = inject(FlowEntitiesService);
11
+ this.settingsService = inject(FlowSettingsService);
10
12
  }
11
13
  /**
12
14
  * Enable draggable behavior for element.
@@ -65,7 +67,7 @@ export class DraggableService {
65
67
  x: round(event.x + initialPositions[index].x),
66
68
  y: round(event.y + initialPositions[index].y),
67
69
  };
68
- moveNode(model, point);
70
+ this.moveNode(model, point);
69
71
  });
70
72
  });
71
73
  }
@@ -78,21 +80,41 @@ export class DraggableService {
78
80
  : // we only can move current node if it's not selected
79
81
  [model];
80
82
  }
83
+ /**
84
+ * @todo make it unit testable
85
+ */
86
+ moveNode(model, point) {
87
+ point = this.alignToGrid(point);
88
+ const parent = model.parent();
89
+ // keep node in bounds of parent
90
+ if (parent) {
91
+ point.x = Math.min(parent.width() - model.width(), point.x);
92
+ point.x = Math.max(0, point.x);
93
+ point.y = Math.min(parent.height() - model.height(), point.y);
94
+ point.y = Math.max(0, point.y);
95
+ }
96
+ model.setPoint(point);
97
+ }
98
+ /**
99
+ * @todo make it unit testable
100
+ */
101
+ alignToGrid(point) {
102
+ const [snapX, snapY] = this.settingsService.snapGrid();
103
+ if (snapX > 1) {
104
+ point.x = align(point.x, snapX);
105
+ }
106
+ if (snapY > 1) {
107
+ point.y = align(point.y, snapY);
108
+ }
109
+ return point;
110
+ }
81
111
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DraggableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
82
112
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DraggableService }); }
83
113
  }
84
114
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DraggableService, decorators: [{
85
115
  type: Injectable
86
116
  }] });
87
- function moveNode(model, point) {
88
- const parent = model.parent();
89
- // keep node in bounds of parent
90
- if (parent) {
91
- point.x = Math.min(parent.size().width - model.size().width, point.x);
92
- point.x = Math.max(0, point.x);
93
- point.y = Math.min(parent.size().height - model.size().height, point.y);
94
- point.y = Math.max(0, point.y);
95
- }
96
- model.setPoint(point);
117
+ function align(num, constant) {
118
+ return Math.ceil(num / constant) * constant;
97
119
  }
98
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHJhZ2dhYmxlLnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtdmZsb3ctbGliL3NyYy9saWIvdmZsb3cvc2VydmljZXMvZHJhZ2dhYmxlLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN0QyxPQUFPLEVBQWUsSUFBSSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBRTVDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUN2QyxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQzs7QUFNOUQsTUFBTSxPQUFPLGdCQUFnQjtJQUQ3QjtRQUVVLG9CQUFlLEdBQUcsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUM7S0FrRnZEO0lBaEZDOzs7OztPQUtHO0lBQ0ksTUFBTSxDQUFDLE9BQWdCLEVBQUUsS0FBZ0I7UUFDOUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDcEQsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0ksT0FBTyxDQUFDLE9BQWdCO1FBQzdCLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ2hELENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksT0FBTyxDQUFDLE9BQWdCO1FBQzdCLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3BDLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNLLGVBQWUsQ0FB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TSxHQUFHLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hFLEtBQUssQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFFRCxLQUFLLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ3hCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IHNlbGVjdCB9IGZyb20gJ2QzLXNlbGVjdGlvbic7XG5pbXBvcnQgeyBEM0RyYWdFdmVudCwgZHJhZyB9IGZyb20gJ2QzLWRyYWcnO1xuaW1wb3J0IHsgTm9kZU1vZGVsIH0gZnJvbSAnLi4vbW9kZWxzL25vZGUubW9kZWwnO1xuaW1wb3J0IHsgcm91bmQgfSBmcm9tICcuLi91dGlscy9yb3VuZCc7XG5pbXBvcnQgeyBGbG93RW50aXRpZXNTZXJ2aWNlIH0gZnJvbSAnLi9mbG93LWVudGl0aWVzLnNlcnZpY2UnO1xuaW1wb3J0IHsgUG9pbnQgfSBmcm9tICcuLi9pbnRlcmZhY2VzL3BvaW50LmludGVyZmFjZSc7XG5cbnR5cGUgRHJhZ0V2ZW50ID0gRDNEcmFnRXZlbnQ8RWxlbWVudCwgdW5rbm93biwgdW5rbm93bj47XG5cbkBJbmplY3RhYmxlKClcbmV4cG9ydCBjbGFzcyBEcmFnZ2FibGVTZXJ2aWNlIHtcbiAgcHJpdmF0ZSBlbnRpdGllc1NlcnZpY2UgPSBpbmplY3QoRmxvd0VudGl0aWVzU2VydmljZSk7XG5cbiAgLyoqXG4gICAqIEVuYWJsZSBkcmFnZ2FibGUgYmVoYXZpb3IgZm9yIGVsZW1lbnQuXG4gICAqXG4gICAqIEBwYXJhbSBlbGVtZW50IHRhcmdldCBlbGVtZW50IGZvciB0b2dnbGluZyBkcmFnZ2FibGVcbiAgICogQHBhcmFtIG1vZGVsIG1vZGVsIHdpdGggZGF0YSBmb3IgdGhpcyBlbGVtZW50XG4gICAqL1xuICBwdWJsaWMgZW5hYmxlKGVsZW1lbnQ6IEVsZW1lbnQsIG1vZGVsOiBOb2RlTW9kZWwpIHtcbiAgICBzZWxlY3QoZWxlbWVudCkuY2FsbCh0aGlzLmdldERyYWdCZWhhdmlvcihtb2RlbCkpO1xuICB9XG5cbiAgLyoqXG4gICAqIERpc2FibGUgZHJhZ2dhYmxlIGJlaGF2aW9yIGZvciBlbGVtZW50LlxuICAgKlxuICAgKiBAcGFyYW0gZWxlbWVudCB0YXJnZXQgZWxlbWVudCBmb3IgdG9nZ2xpbmcgZHJhZ2dhYmxlXG4gICAqIEBwYXJhbSBtb2RlbCBtb2RlbCB3aXRoIGRhdGEgZm9yIHRoaXMgZWxlbWVudFxuICAgKi9cbiAgcHVibGljIGRpc2FibGUoZWxlbWVudDogRWxlbWVudCkge1xuICAgIHNlbGVjdChlbGVtZW50KS5jYWxsKGRyYWcoKS5vbignZHJhZycsIG51bGwpKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUT0RPOiBub3Qgc2h1cmUgaWYgdGhpcyB3b3JrLCBuZWVkIHRvIGNoZWNrXG4gICAqXG4gICAqIEBwYXJhbSBlbGVtZW50XG4gICAqL1xuICBwdWJsaWMgZGVzdHJveShlbGVtZW50OiBFbGVtZW50KSB7XG4gICAgc2VsZWN0KGVsZW1lbnQpLm9uKCcuZHJhZycsIG51bGwpO1xuICB9XG5cbiAgLyoqXG4gICAqIE5vZGUgZHJhZyBiZWhhdmlvci4gVXBkYXRlZCBub2RlJ3MgY29vcmRpbmF0ZSBhY2NvcmRpbmcgdG8gZHJhZ2dpbmdcbiAgICpcbiAgICogQHBhcmFtIG1vZGVsXG4gICAqIEByZXR1cm5zXG4gICAqL1xuICBwcml2YXRlIGdldERyYWdCZWhhdmlvcihtb2RlbDogTm9kZU1vZGVsKSB7XG4gICAgbGV0IGRyYWdOb2RlczogTm9kZU1vZGVsW10gPSBbXTtcbiAgICBsZXQgaW5pdGlhbFBvc2l0aW9uczogUG9pbnRbXSA9IFtdO1xuXG4gICAgY29uc3QgZmlsdGVyQ29uZGl0aW9uID0gKGV2ZW50OiBFdmVudCkgPT4ge1xuICAgICAgLy8gaWYgdGhlcmUgaXMgYXQgbGVhc3Qgb25lIGRyYWcgaGFuZGxlLCB3ZSBzaG91bGQgY2hlY2sgaWYgd2UgYXJlIGRyYWdnaW5nIGl0XG4gICAgICBpZiAobW9kZWwuZHJhZ0hhbmRsZXNDb3VudCgpKSB7XG4gICAgICAgIHJldHVybiAhIShldmVudC50YXJnZXQgYXMgRWxlbWVudCkuY2xvc2VzdCgnLnZmbG93LWRyYWctaGFuZGxlJyk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH07XG5cbiAgICByZXR1cm4gZHJhZygpXG4gICAgICAuZmlsdGVyKGZpbHRlckNvbmRpdGlvbilcbiAgICAgIC5vbignc3RhcnQnLCAoZXZlbnQ6IERyYWdFdmVudCkgPT4ge1xuICAgICAgICBkcmFnTm9kZXMgPSB0aGlzLmdldERyYWdOb2Rlcyhtb2RlbCk7XG5cbiAgICAgICAgaW5pdGlhbFBvc2l0aW9ucyA9IGRyYWdOb2Rlcy5tYXAoKG5vZGUpID0+ICh7XG4gICAgICAgICAgeDogbm9kZS5wb2ludCgpLnggLSBldmVudC54LFxuICAgICAgICAgIHk6IG5vZGUucG9pbnQoKS55IC0gZXZlbnQueSxcbiAgICAgICAgfSkpO1xuICAgICAgfSlcblxuICAgICAgLm9uKCdkcmFnJywgKGV2ZW50OiBEcmFnRXZlbnQpID0+IHtcbiAgICAgICAgZHJhZ05vZGVzLmZvckVhY2goKG1vZGVsLCBpbmRleCkgPT4ge1xuICAgICAgICAgIGNvbnN0IHBvaW50ID0ge1xuICAgICAgICAgICAgeDogcm91bmQoZXZlbnQueCArIGluaXRpYWxQb3NpdGlvbnNbaW5kZXhdLngpLFxuICAgICAgICAgICAgeTogcm91bmQoZXZlbnQueSArIGluaXRpYWxQb3NpdGlvbnNbaW5kZXhdLnkpLFxuICAgICAgICAgIH07XG5cbiAgICAgICAgICBtb3ZlTm9kZShtb2RlbCwgcG9pbnQpO1xuICAgICAgICB9KTtcbiAgICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXREcmFnTm9kZXMobW9kZWw6IE5vZGVNb2RlbCkge1xuICAgIHJldHVybiBtb2RlbC5zZWxlY3RlZCgpXG4gICAgICA/IHRoaXMuZW50aXRpZXNTZXJ2aWNlXG4gICAgICAgICAgLm5vZGVzKClcbiAgICAgICAgICAvLyBzZWxlY3RlZCBkcmFnZ2FibGUgbm9kZXMgKHdpdGggY3VycmVudCBub2RlKVxuICAgICAgICAgIC5maWx0ZXIoKG5vZGUpID0+IG5vZGUuc2VsZWN0ZWQoKSAmJiBub2RlLmRyYWdnYWJsZSgpKVxuICAgICAgOiAvLyB3ZSBvbmx5IGNhbiBtb3ZlIGN1cnJlbnQgbm9kZSBpZiBpdCdzIG5vdCBzZWxlY3RlZFxuICAgICAgICBbbW9kZWxdO1xuICB9XG59XG5cbmZ1bmN0aW9uIG1vdmVOb2RlKG1vZGVsOiBOb2RlTW9kZWwsIHBvaW50OiBQb2ludCkge1xuICBjb25zdCBwYXJlbnQgPSBtb2RlbC5wYXJlbnQoKTtcbiAgLy8ga2VlcCBub2RlIGluIGJvdW5kcyBvZiBwYXJlbnRcbiAgaWYgKHBhcmVudCkge1xuICAgIHBvaW50LnggPSBNYXRoLm1pbihwYXJlbnQuc2l6ZSgpLndpZHRoIC0gbW9kZWwuc2l6ZSgpLndpZHRoLCBwb2ludC54KTtcbiAgICBwb2ludC54ID0gTWF0aC5tYXgoMCwgcG9pbnQueCk7XG5cbiAgICBwb2ludC55ID0gTWF0aC5taW4ocGFyZW50LnNpemUoKS5oZWlnaHQgLSBtb2RlbC5zaXplKCkuaGVpZ2h0LCBwb2ludC55KTtcbiAgICBwb2ludC55ID0gTWF0aC5tYXgoMCwgcG9pbnQueSk7XG4gIH1cblxuICBtb2RlbC5zZXRQb2ludChwb2ludCk7XG59XG4iXX0=
120
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHJhZ2dhYmxlLnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtdmZsb3ctbGliL3NyYy9saWIvdmZsb3cvc2VydmljZXMvZHJhZ2dhYmxlLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN0QyxPQUFPLEVBQWUsSUFBSSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBRTVDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUN2QyxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUU5RCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQzs7QUFLOUQsTUFBTSxPQUFPLGdCQUFnQjtJQUQ3QjtRQUVVLG9CQUFlLEdBQUcsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDOUMsb0JBQWUsR0FBRyxNQUFNLENBQUMsbUJBQW1CLENBQUMsQ0FBQztLQXNIdkQ7SUFwSEM7Ozs7O09BS0c7SUFDSSxNQUFNLENBQUMsT0FBZ0IsRUFBRSxLQUFnQjtRQUM5QyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUNwRCxDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxPQUFPLENBQUMsT0FBZ0I7UUFDN0IsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDaEQsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxPQUFPLENBQUMsT0FBZ0I7UUFDN0IsTUFBTSxDQUFD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Q0FBQztZQUUvQixLQUFLLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxHQUFHLEtBQUssQ0FBQyxNQUFNLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDOUQsS0FBSyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDakMsQ0FBQztRQUVELEtBQUssQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDeEIsQ0FBQztJQUVEOztPQUVHO0lBQ0ssV0FBVyxDQUFDLEtBQVk7UUFDOUIsTUFBTSxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsZUFBZSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBRXZELElBQUksS0FBSyxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ2QsS0FBSyxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUNsQyxDQUFDO1FBRUQsSUFBSSxLQUFLLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDZCxLQUFLLENBQUMsQ0FBQyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ2xDLENBQUM7UUFFRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7K0dBdkhVLGdCQUFnQjttSEFBaEIsZ0JBQWdCOzs0RkFBaEIsZ0JBQWdCO2tCQUQ1QixVQUFVOztBQTJIWCxTQUFTLEtBQUssQ0FBQyxHQUFXLEVBQUUsUUFBZ0I7SUFDMUMsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsR0FBRyxRQUFRLENBQUMsR0FBRyxRQUFRLENBQUM7QUFDOUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUsIGluamVjdCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgc2VsZWN0IH0gZnJvbSAnZDMtc2VsZWN0aW9uJztcbmltcG9ydCB7IEQzRHJhZ0V2ZW50LCBkcmFnIH0gZnJvbSAnZDMtZHJhZyc7XG5pbXBvcnQgeyBOb2RlTW9kZWwgfSBmcm9tICcuLi9tb2RlbHMvbm9kZS5tb2RlbCc7XG5pbXBvcnQgeyByb3VuZCB9IGZyb20gJy4uL3V0aWxzL3JvdW5kJztcbmltcG9ydCB7IEZsb3dFbnRpdGllc1NlcnZpY2UgfSBmcm9tICcuL2Zsb3ctZW50aXRpZXMuc2VydmljZSc7XG5pbXBvcnQgeyBQb2ludCB9IGZyb20gJy4uL2ludGVyZmFjZXMvcG9pbnQuaW50ZXJmYWNlJztcbmltcG9ydCB7IEZsb3dTZXR0aW5nc1NlcnZpY2UgfSBmcm9tICcuL2Zsb3ctc2V0dGluZ3Muc2VydmljZSc7XG5cbnR5cGUgRHJhZ0V2ZW50ID0gRDNEcmFnRXZlbnQ8RWxlbWVudCwgdW5rbm93biwgdW5rbm93bj47XG5cbkBJbmplY3RhYmxlKClcbmV4cG9ydCBjbGFzcyBEcmFnZ2FibGVTZXJ2aWNlIHtcbiAgcHJpdmF0ZSBlbnRpdGllc1NlcnZpY2UgPSBpbmplY3QoRmxvd0VudGl0aWVzU2VydmljZSk7XG4gIHByaXZhdGUgc2V0dGluZ3NTZXJ2aWNlID0gaW5qZWN0KEZsb3dTZXR0aW5nc1NlcnZpY2UpO1xuXG4gIC8qKlxuICAgKiBFbmFibGUgZHJhZ2dhYmxlIGJlaGF2aW9yIGZvciBlbGVtZW50LlxuICAgKlxuICAgKiBAcGFyYW0gZWxlbWVudCB0YXJnZXQgZWxlbWVudCBmb3IgdG9nZ2xpbmcgZHJhZ2dhYmxlXG4gICAqIEBwYXJhbSBtb2RlbCBtb2RlbCB3aXRoIGRhdGEgZm9yIHRoaXMgZWxlbWVudFxuICAgKi9cbiAgcHVibGljIGVuYWJsZShlbGVtZW50OiBFbGVtZW50LCBtb2RlbDogTm9kZU1vZGVsKSB7XG4gICAgc2VsZWN0KGVsZW1lbnQpLmNhbGwodGhpcy5nZXREcmFnQmVoYXZpb3IobW9kZWwpKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEaXNhYmxlIGRyYWdnYWJsZSBiZWhhdmlvciBmb3IgZWxlbWVudC5cbiAgICpcbiAgICogQHBhcmFtIGVsZW1lbnQgdGFyZ2V0IGVsZW1lbnQgZm9yIHRvZ2dsaW5nIGRyYWdnYWJsZVxuICAgKiBAcGFyYW0gbW9kZWwgbW9kZWwgd2l0aCBkYXRhIGZvciB0aGlzIGVsZW1lbnRcbiAgICovXG4gIHB1YmxpYyBkaXNhYmxlKGVsZW1lbnQ6IEVsZW1lbnQpIHtcbiAgICBzZWxlY3QoZWxlbWVudCkuY2FsbChkcmFnKCkub24oJ2RyYWcnLCBudWxsKSk7XG4gIH1cblxuICAvKipcbiAgICogVE9ETzogbm90IHNodXJlIGlmIHRoaXMgd29yaywgbmVlZCB0byBjaGVja1xuICAgKlxuICAgKiBAcGFyYW0gZWxlbWVudFxuICAgKi9cbiAgcHVibGljIGRlc3Ryb3koZWxlbWVudDogRWxlbWVudCkge1xuICAgIHNlbGVjdChlbGVtZW50KS5vbignLmRyYWcnLCBudWxsKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBOb2RlIGRyYWcgYmVoYXZpb3IuIFVwZGF0ZWQgbm9kZSdzIGNvb3JkaW5hdGUgYWNjb3JkaW5nIHRvIGRyYWdnaW5nXG4gICAqXG4gICAqIEBwYXJhbSBtb2RlbFxuICAgKiBAcmV0dXJuc1xuICAgKi9cbiAgcHJpdmF0ZSBnZXREcmFnQmVoYXZpb3IobW9kZWw6IE5vZGVNb2RlbCkge1xuICAgIGxldCBkcmFnTm9kZXM6IE5vZGVNb2RlbFtdID0gW107XG4gICAgbGV0IGluaXRpYWxQb3NpdGlvbnM6IFBvaW50W10gPSBbXTtcblxuICAgIGNvbnN0IGZpbHRlckNvbmRpdGlvbiA9IChldmVudDogRXZlbnQpID0+IHtcbiAgICAgIC8vIGlmIHRoZXJlIGlzIGF0IGxlYXN0IG9uZSBkcmFnIGhhbmRsZSwgd2Ugc2hvdWxkIGNoZWNrIGlmIHdlIGFyZSBkcmFnZ2luZyBpdFxuICAgICAgaWYgKG1vZGVsLmRyYWdIYW5kbGVzQ291bnQoKSkge1xuICAgICAgICByZXR1cm4gISEoZXZlbnQudGFyZ2V0IGFzIEVsZW1lbnQpLmNsb3Nlc3QoJy52Zmxvdy1kcmFnLWhhbmRsZScpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9O1xuXG4gICAgcmV0dXJuIGRyYWcoKVxuICAgICAgLmZpbHRlcihmaWx0ZXJDb25kaXRpb24pXG4gICAgICAub24oJ3N0YXJ0JywgKGV2ZW50OiBEcmFnRXZlbnQpID0+IHtcbiAgICAgICAgZHJhZ05vZGVzID0gdGhpcy5nZXREcmFnTm9kZXMobW9kZWwpO1xuXG4gICAgICAgIGluaXRpYWxQb3NpdGlvbnMgPSBkcmFnTm9kZXMubWFwKChub2RlKSA9PiAoe1xuICAgICAgICAgIHg6IG5vZGUucG9pbnQoKS54IC0gZXZlbnQueCxcbiAgICAgICAgICB5OiBub2RlLnBvaW50KCkueSAtIGV2ZW50LnksXG4gICAgICAgIH0pKTtcbiAgICAgIH0pXG5cbiAgICAgIC5vbignZHJhZycsIChldmVudDogRHJhZ0V2ZW50KSA9PiB7XG4gICAgICAgIGRyYWdOb2Rlcy5mb3JFYWNoKChtb2RlbCwgaW5kZXgpID0+IHtcbiAgICAgICAgICBjb25zdCBwb2ludCA9IHtcbiAgICAgICAgICAgIHg6IHJvdW5kKGV2ZW50LnggKyBpbml0aWFsUG9zaXRpb25zW2luZGV4XS54KSxcbiAgICAgICAgICAgIHk6IHJvdW5kKGV2ZW50LnkgKyBpbml0aWFsUG9zaXRpb25zW2luZGV4XS55KSxcbiAgICAgICAgICB9O1xuXG4gICAgICAgICAgdGhpcy5tb3ZlTm9kZShtb2RlbCwgcG9pbnQpO1xuICAgICAgICB9KTtcbiAgICAgIH0pO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXREcmFnTm9kZXMobW9kZWw6IE5vZGVNb2RlbCkge1xuICAgIHJldHVybiBtb2RlbC5zZWxlY3RlZCgpXG4gICAgICA/IHRoaXMuZW50aXRpZXNTZXJ2aWNlXG4gICAgICAgICAgLm5vZGVzKClcbiAgICAgICAgICAvLyBzZWxlY3RlZCBkcmFnZ2FibGUgbm9kZXMgKHdpdGggY3VycmVudCBub2RlKVxuICAgICAgICAgIC5maWx0ZXIoKG5vZGUpID0+IG5vZGUuc2VsZWN0ZWQoKSAmJiBub2RlLmRyYWdnYWJsZSgpKVxuICAgICAgOiAvLyB3ZSBvbmx5IGNhbiBtb3ZlIGN1cnJlbnQgbm9kZSBpZiBpdCdzIG5vdCBzZWxlY3RlZFxuICAgICAgICBbbW9kZWxdO1xuICB9XG5cbiAgLyoqXG4gICAqIEB0b2RvIG1ha2UgaXQgdW5pdCB0ZXN0YWJsZVxuICAgKi9cbiAgcHJpdmF0ZSBtb3ZlTm9kZShtb2RlbDogTm9kZU1vZGVsLCBwb2ludDogUG9pbnQpIHtcbiAgICBwb2ludCA9IHRoaXMuYWxpZ25Ub0dyaWQocG9pbnQpO1xuXG4gICAgY29uc3QgcGFyZW50ID0gbW9kZWwucGFyZW50KCk7XG4gICAgLy8ga2VlcCBub2RlIGluIGJvdW5kcyBvZiBwYXJlbnRcbiAgICBpZiAocGFyZW50KSB7XG4gICAgICBwb2ludC54ID0gTWF0aC5taW4ocGFyZW50LndpZHRoKCkgLSBtb2RlbC53aWR0aCgpLCBwb2ludC54KTtcbiAgICAgIHBvaW50LnggPSBNYXRoLm1heCgwLCBwb2ludC54KTtcblxuICAgICAgcG9pbnQueSA9IE1hdGgubWluKHBhcmVudC5oZWlnaHQoKSAtIG1vZGVsLmhlaWdodCgpLCBwb2ludC55KTtcbiAgICAgIHBvaW50LnkgPSBNYXRoLm1heCgwLCBwb2ludC55KTtcbiAgICB9XG5cbiAgICBtb2RlbC5zZXRQb2ludChwb2ludCk7XG4gIH1cblxuICAvKipcbiAgICogQHRvZG8gbWFrZSBpdCB1bml0IHRlc3RhYmxlXG4gICAqL1xuICBwcml2YXRlIGFsaWduVG9HcmlkKHBvaW50OiBQb2ludCkge1xuICAgIGNvbnN0IFtzbmFwWCwgc25hcFldID0gdGhpcy5zZXR0aW5nc1NlcnZpY2Uuc25hcEdyaWQoKTtcblxuICAgIGlmIChzbmFwWCA+IDEpIHtcbiAgICAgIHBvaW50LnggPSBhbGlnbihwb2ludC54LCBzbmFwWCk7XG4gICAgfVxuXG4gICAgaWYgKHNuYXBZID4gMSkge1xuICAgICAgcG9pbnQueSA9IGFsaWduKHBvaW50LnksIHNuYXBZKTtcbiAgICB9XG5cbiAgICByZXR1cm4gcG9pbnQ7XG4gIH1cbn1cblxuZnVuY3Rpb24gYWxpZ24obnVtOiBudW1iZXIsIGNvbnN0YW50OiBudW1iZXIpOiBudW1iZXIge1xuICByZXR1cm4gTWF0aC5jZWlsKG51bSAvIGNvbnN0YW50KSAqIGNvbnN0YW50O1xufVxuIl19
@@ -18,6 +18,7 @@ export class FlowSettingsService {
18
18
  this.minZoom = signal(0.5);
19
19
  this.maxZoom = signal(3);
20
20
  this.background = signal({ type: 'solid', color: '#fff' });
21
+ this.snapGrid = signal([1, 1]);
21
22
  }
22
23
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
23
24
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService }); }
@@ -25,4 +26,4 @@ export class FlowSettingsService {
25
26
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService, decorators: [{
26
27
  type: Injectable
27
28
  }] });
28
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmxvdy1zZXR0aW5ncy5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LXZmbG93LWxpYi9zcmMvbGliL3ZmbG93L3NlcnZpY2VzL2Zsb3ctc2V0dGluZ3Muc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFrQixNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7O0FBSW5FLE1BQU0sT0FBTyxtQkFBbUI7SUFEaEM7UUFFUyx1QkFBa0IsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekM7O1dBRUc7UUFDSSxTQUFJLEdBQThDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBRTVFOztXQUVHO1FBQ0ksc0JBQWlCLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRXJDOztXQUVHO1FBQ0ksdUJBQWtCLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRS9CLFlBQU8sR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFdEIsWUFBTyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVwQixlQUFVLEdBQUcsTUFBTSxDQUFhLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztLQUMxRTsrR0F0QlksbUJBQW1CO21IQUFuQixtQkFBbUI7OzRGQUFuQixtQkFBbUI7a0JBRC9CLFVBQVUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBXcml0YWJsZVNpZ25hbCwgc2lnbmFsIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBCYWNrZ3JvdW5kIH0gZnJvbSAnLi4vdHlwZXMvYmFja2dyb3VuZC50eXBlJztcblxuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIEZsb3dTZXR0aW5nc1NlcnZpY2Uge1xuICBwdWJsaWMgZW50aXRpZXNTZWxlY3RhYmxlID0gc2lnbmFsKHRydWUpO1xuICAvKipcbiAgICogQHNlZSB7VmZsb3dDb21wb25lbnQudmlld31cbiAgICovXG4gIHB1YmxpYyB2aWV3OiBXcml0YWJsZVNpZ25hbDxbbnVtYmVyLCBudW1iZXJdIHwgJ2F1dG8nPiA9IHNpZ25hbChbNDAwLCA0MDBdKTtcblxuICAvKipcbiAgICogU2V0IGJhc2VkIG9uIHZpZXcgcHJvcGVydHkuIE1heSBjaGFuZ2UgaWYgdmlldyBpcyAnYXV0bydcbiAgICovXG4gIHB1YmxpYyBjb21wdXRlZEZsb3dXaWR0aCA9IHNpZ25hbCgwKTtcblxuICAvKipcbiAgICogU2V0IGJhc2VkIG9uIHZpZXcgcHJvcGVydHkuIE1heSBjaGFuZ2UgaWYgdmlldyBpcyAnYXV0bydcbiAgICovXG4gIHB1YmxpYyBjb21wdXRlZEZsb3dIZWlnaHQgPSBzaWduYWwoMCk7XG5cbiAgcHVibGljIG1pblpvb20gPSBzaWduYWwoMC41KTtcblxuICBwdWJsaWMgbWF4Wm9vbSA9IHNpZ25hbCgzKTtcblxuICBwdWJsaWMgYmFja2dyb3VuZCA9IHNpZ25hbDxCYWNrZ3JvdW5kPih7IHR5cGU6ICdzb2xpZCcsIGNvbG9yOiAnI2ZmZicgfSk7XG59XG4iXX0=
29
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmxvdy1zZXR0aW5ncy5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LXZmbG93LWxpYi9zcmMvbGliL3ZmbG93L3NlcnZpY2VzL2Zsb3ctc2V0dGluZ3Muc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFrQixNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7O0FBSW5FLE1BQU0sT0FBTyxtQkFBbUI7SUFEaEM7UUFFUyx1QkFBa0IsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekM7O1dBRUc7UUFDSSxTQUFJLEdBQThDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBRTVFOztXQUVHO1FBQ0ksc0JBQWlCLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRXJDOztXQUVHO1FBQ0ksdUJBQWtCLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRS9CLFlBQU8sR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFdEIsWUFBTyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVwQixlQUFVLEdBQUcsTUFBTSxDQUFhLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUVsRSxhQUFRLEdBQUcsTUFBTSxDQUFtQixDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0tBQ3BEOytHQXhCWSxtQkFBbUI7bUhBQW5CLG1CQUFtQjs7NEZBQW5CLG1CQUFtQjtrQkFEL0IsVUFBVSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUsIFdyaXRhYmxlU2lnbmFsLCBzaWduYWwgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEJhY2tncm91bmQgfSBmcm9tICcuLi90eXBlcy9iYWNrZ3JvdW5kLnR5cGUnO1xuXG5ASW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgRmxvd1NldHRpbmdzU2VydmljZSB7XG4gIHB1YmxpYyBlbnRpdGllc1NlbGVjdGFibGUgPSBzaWduYWwodHJ1ZSk7XG4gIC8qKlxuICAgKiBAc2VlIHtWZmxvd0NvbXBvbmVudC52aWV3fVxuICAgKi9cbiAgcHVibGljIHZpZXc6IFdyaXRhYmxlU2lnbmFsPFtudW1iZXIsIG51bWJlcl0gfCAnYXV0byc+ID0gc2lnbmFsKFs0MDAsIDQwMF0pO1xuXG4gIC8qKlxuICAgKiBTZXQgYmFzZWQgb24gdmlldyBwcm9wZXJ0eS4gTWF5IGNoYW5nZSBpZiB2aWV3IGlzICdhdXRvJ1xuICAgKi9cbiAgcHVibGljIGNvbXB1dGVkRmxvd1dpZHRoID0gc2lnbmFsKDApO1xuXG4gIC8qKlxuICAgKiBTZXQgYmFzZWQgb24gdmlldyBwcm9wZXJ0eS4gTWF5IGNoYW5nZSBpZiB2aWV3IGlzICdhdXRvJ1xuICAgKi9cbiAgcHVibGljIGNvbXB1dGVkRmxvd0hlaWdodCA9IHNpZ25hbCgwKTtcblxuICBwdWJsaWMgbWluWm9vbSA9IHNpZ25hbCgwLjUpO1xuXG4gIHB1YmxpYyBtYXhab29tID0gc2lnbmFsKDMpO1xuXG4gIHB1YmxpYyBiYWNrZ3JvdW5kID0gc2lnbmFsPEJhY2tncm91bmQ+KHsgdHlwZTogJ3NvbGlkJywgY29sb3I6ICcjZmZmJyB9KTtcblxuICBwdWJsaWMgc25hcEdyaWQgPSBzaWduYWw8W251bWJlciwgbnVtYmVyXT4oWzEsIDFdKTtcbn1cbiJdfQ==
@@ -165,6 +165,7 @@ class FlowSettingsService {
165
165
  this.minZoom = signal(0.5);
166
166
  this.maxZoom = signal(3);
167
167
  this.background = signal({ type: 'solid', color: '#fff' });
168
+ this.snapGrid = signal([1, 1]);
168
169
  }
169
170
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
170
171
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FlowSettingsService }); }
@@ -459,6 +460,7 @@ const round = (num) => Math.round(num * 100) / 100;
459
460
  class DraggableService {
460
461
  constructor() {
461
462
  this.entitiesService = inject(FlowEntitiesService);
463
+ this.settingsService = inject(FlowSettingsService);
462
464
  }
463
465
  /**
464
466
  * Enable draggable behavior for element.
@@ -517,7 +519,7 @@ class DraggableService {
517
519
  x: round(event.x + initialPositions[index].x),
518
520
  y: round(event.y + initialPositions[index].y),
519
521
  };
520
- moveNode(model, point);
522
+ this.moveNode(model, point);
521
523
  });
522
524
  });
523
525
  }
@@ -530,22 +532,42 @@ class DraggableService {
530
532
  : // we only can move current node if it's not selected
531
533
  [model];
532
534
  }
535
+ /**
536
+ * @todo make it unit testable
537
+ */
538
+ moveNode(model, point) {
539
+ point = this.alignToGrid(point);
540
+ const parent = model.parent();
541
+ // keep node in bounds of parent
542
+ if (parent) {
543
+ point.x = Math.min(parent.width() - model.width(), point.x);
544
+ point.x = Math.max(0, point.x);
545
+ point.y = Math.min(parent.height() - model.height(), point.y);
546
+ point.y = Math.max(0, point.y);
547
+ }
548
+ model.setPoint(point);
549
+ }
550
+ /**
551
+ * @todo make it unit testable
552
+ */
553
+ alignToGrid(point) {
554
+ const [snapX, snapY] = this.settingsService.snapGrid();
555
+ if (snapX > 1) {
556
+ point.x = align(point.x, snapX);
557
+ }
558
+ if (snapY > 1) {
559
+ point.y = align(point.y, snapY);
560
+ }
561
+ return point;
562
+ }
533
563
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DraggableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
534
564
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DraggableService }); }
535
565
  }
536
566
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DraggableService, decorators: [{
537
567
  type: Injectable
538
568
  }] });
539
- function moveNode(model, point) {
540
- const parent = model.parent();
541
- // keep node in bounds of parent
542
- if (parent) {
543
- point.x = Math.min(parent.size().width - model.size().width, point.x);
544
- point.x = Math.max(0, point.x);
545
- point.y = Math.min(parent.size().height - model.size().height, point.y);
546
- point.y = Math.max(0, point.y);
547
- }
548
- model.setPoint(point);
569
+ function align(num, constant) {
570
+ return Math.ceil(num / constant) * constant;
549
571
  }
550
572
 
551
573
  class EdgeTemplateDirective {
@@ -827,6 +849,10 @@ function isTemplateDynamicGroupNode(node) {
827
849
  return node.type === 'template-group';
828
850
  }
829
851
 
852
+ // this is a number to fix visual artifact in chrome.
853
+ // the bug reproduces if edgeLabelWrapperRef size fully matched the size of parent foreignObject
854
+ const MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME = 2;
855
+
830
856
  class NodeModel {
831
857
  static { this.defaultWidth = 100; }
832
858
  static { this.defaultHeight = 50; }
@@ -850,6 +876,8 @@ class NodeModel {
850
876
  this.size$ = toObservable(this.size);
851
877
  this.styleWidth = computed(() => `${this.width()}px`);
852
878
  this.styleHeight = computed(() => `${this.height()}px`);
879
+ this.foWidth = computed(() => this.width() + MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME);
880
+ this.foHeight = computed(() => this.height() + MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME);
853
881
  this.renderOrder = signal(0);
854
882
  this.selected = signal(false);
855
883
  this.selected$ = toObservable(this.selected);
@@ -1752,6 +1780,7 @@ class EdgeLabelComponent {
1752
1780
  constructor() {
1753
1781
  this.zone = inject(NgZone);
1754
1782
  this.destroyRef = inject(DestroyRef);
1783
+ this.settingsService = inject(FlowSettingsService);
1755
1784
  // TODO: too many inputs
1756
1785
  this.model = input.required();
1757
1786
  this.edgeModel = input.required();
@@ -1771,15 +1800,29 @@ class EdgeLabelComponent {
1771
1800
  y: point.y - height / 2,
1772
1801
  };
1773
1802
  });
1803
+ this.edgeLabelStyle = computed(() => {
1804
+ const label = this.model().edgeLabel;
1805
+ if (label.type === 'default' && label.style) {
1806
+ const flowBackground = this.settingsService.background();
1807
+ let color = 'transparent';
1808
+ if (flowBackground.type === 'dots') {
1809
+ color = flowBackground.backgroundColor ?? '#fff';
1810
+ }
1811
+ if (flowBackground.type === 'solid') {
1812
+ color = flowBackground.color;
1813
+ }
1814
+ label.style.backgroundColor = label.style.backgroundColor ?? color;
1815
+ return label.style;
1816
+ }
1817
+ return null;
1818
+ });
1774
1819
  }
1775
1820
  ngAfterViewInit() {
1776
- // this is a fix for visual artifact in chrome that for some reason adresses only for edge label.
1777
- // the bug reproduces if edgeLabelWrapperRef size fully matched the size of parent foreignObject
1778
- const MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME = 2;
1779
- resizable([this.edgeLabelWrapperRef().nativeElement], this.zone)
1821
+ const labelElement = this.edgeLabelWrapperRef().nativeElement;
1822
+ resizable([labelElement], this.zone)
1780
1823
  .pipe(startWith(null), tap(() => {
1781
- const width = this.edgeLabelWrapperRef().nativeElement.clientWidth + MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME;
1782
- const height = this.edgeLabelWrapperRef().nativeElement.clientHeight + MAGIC_VALUE_TO_FIX_GLITCH_IN_CHROME;
1824
+ const width = labelElement.clientWidth + MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME;
1825
+ const height = labelElement.clientHeight + MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME;
1783
1826
  this.model().size.set({ width, height });
1784
1827
  }), takeUntilDestroyed(this.destroyRef))
1785
1828
  .subscribe();
@@ -1793,11 +1836,11 @@ class EdgeLabelComponent {
1793
1836
  };
1794
1837
  }
1795
1838
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1796
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EdgeLabelComponent, isStandalone: true, selector: "g[edgeLabel]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, edgeModel: { classPropertyName: "edgeModel", publicName: "edgeModel", isSignal: true, isRequired: true, transformFunction: null }, point: { classPropertyName: "point", publicName: "point", isSignal: true, isRequired: false, transformFunction: null }, htmlTemplate: { classPropertyName: "htmlTemplate", publicName: "htmlTemplate", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "edgeLabelWrapperRef", first: true, predicate: ["edgeLabelWrapper"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (model().edgeLabel.type === 'html-template' && htmlTemplate()) {\n @if (htmlTemplate(); as htmlTemplate) {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\">\n <ng-container *ngTemplateOutlet=\"htmlTemplate; context: getLabelContext()\" />\n </div>\n </svg:foreignObject>\n }\n}\n", styles: [".edge-label-wrapper{width:max-content;margin-top:1px;margin-left:1px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1839
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EdgeLabelComponent, isStandalone: true, selector: "g[edgeLabel]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, edgeModel: { classPropertyName: "edgeModel", publicName: "edgeModel", isSignal: true, isRequired: true, transformFunction: null }, point: { classPropertyName: "point", publicName: "point", isSignal: true, isRequired: false, transformFunction: null }, htmlTemplate: { classPropertyName: "htmlTemplate", publicName: "htmlTemplate", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "edgeLabelWrapperRef", first: true, predicate: ["edgeLabelWrapper"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (model(); as model) {\n @if (model.edgeLabel.type === 'html-template' && htmlTemplate()) {\n @if (htmlTemplate(); as htmlTemplate) {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\">\n <ng-container *ngTemplateOutlet=\"htmlTemplate; context: getLabelContext()\" />\n </div>\n </svg:foreignObject>\n }\n }\n\n @if (model.edgeLabel.type === 'default') {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\" [style]=\"edgeLabelStyle()\">\n {{ model.edgeLabel.text }}\n </div>\n </svg:foreignObject>\n }\n}\n", styles: [".edge-label-wrapper{width:max-content;margin-top:1px;margin-left:1px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1797
1840
  }
1798
1841
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeLabelComponent, decorators: [{
1799
1842
  type: Component,
1800
- args: [{ standalone: true, selector: 'g[edgeLabel]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], template: "@if (model().edgeLabel.type === 'html-template' && htmlTemplate()) {\n @if (htmlTemplate(); as htmlTemplate) {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\">\n <ng-container *ngTemplateOutlet=\"htmlTemplate; context: getLabelContext()\" />\n </div>\n </svg:foreignObject>\n }\n}\n", styles: [".edge-label-wrapper{width:max-content;margin-top:1px;margin-left:1px}\n"] }]
1843
+ args: [{ standalone: true, selector: 'g[edgeLabel]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], template: "@if (model(); as model) {\n @if (model.edgeLabel.type === 'html-template' && htmlTemplate()) {\n @if (htmlTemplate(); as htmlTemplate) {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\">\n <ng-container *ngTemplateOutlet=\"htmlTemplate; context: getLabelContext()\" />\n </div>\n </svg:foreignObject>\n }\n }\n\n @if (model.edgeLabel.type === 'default') {\n <svg:foreignObject\n [attr.x]=\"edgeLabelPoint().x\"\n [attr.y]=\"edgeLabelPoint().y\"\n [attr.width]=\"model.size().width\"\n [attr.height]=\"model.size().height\">\n <div #edgeLabelWrapper class=\"edge-label-wrapper\" [style]=\"edgeLabelStyle()\">\n {{ model.edgeLabel.text }}\n </div>\n </svg:foreignObject>\n }\n}\n", styles: [".edge-label-wrapper{width:max-content;margin-top:1px;margin-left:1px}\n"] }]
1801
1844
  }] });
1802
1845
 
1803
1846
  class EdgeComponent {
@@ -2619,7 +2662,7 @@ class NodeComponent {
2619
2662
  }
2620
2663
  }
2621
2664
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2622
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: NodeComponent, isStandalone: true, selector: "g[node]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null }, groupNodeTemplate: { classPropertyName: "groupNodeTemplate", publicName: "groupNodeTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "vflow-node" }, providers: [HandleService, NodeAccessorService], ngImport: i0, template: "<!-- Default node -->\n@if (model().node.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n (pointerStart)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- Template node -->\n@if (model().node.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: { node: model().node, selected: model().selected },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(model().node.type)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Default group node -->\n@if (model().node.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (pointerStart)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().node.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (pointerStart)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: {\n node: model().node,\n selected: model().selected,\n width: model().width,\n height: model().height,\n },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (!handle.template) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@if (toolbar(); as toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.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: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }, { kind: "component", type: DefaultNodeComponent, selector: "default-node", inputs: ["selected"] }, { kind: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ResizableComponent, selector: "[resizable]", inputs: ["resizable", "resizerColor", "gap"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }, { kind: "directive", type: NodeHandlesControllerDirective, selector: "[nodeHandlesController]" }, { kind: "directive", type: NodeResizeControllerDirective, selector: "[nodeResizeController]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2665
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: NodeComponent, isStandalone: true, selector: "g[node]", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null }, groupNodeTemplate: { classPropertyName: "groupNodeTemplate", publicName: "groupNodeTemplate", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "vflow-node" }, providers: [HandleService, NodeAccessorService], ngImport: i0, template: "<!-- Default node -->\n@if (model().node.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (pointerStart)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- Template node -->\n@if (model().node.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: { node: model().node, selected: model().selected },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(model().node.type)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Default group node -->\n@if (model().node.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (pointerStart)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().node.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (pointerStart)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: {\n node: model().node,\n selected: model().selected,\n width: model().width,\n height: model().height,\n },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (!handle.template) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@if (toolbar(); as toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.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: PointerDirective, selector: "[pointerStart], [pointerEnd], [pointerOver], [pointerOut]", outputs: ["pointerOver", "pointerOut", "pointerStart", "pointerEnd"] }, { kind: "component", type: DefaultNodeComponent, selector: "default-node", inputs: ["selected"] }, { kind: "component", type: HandleComponent, selector: "handle", inputs: ["position", "type", "id", "template"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }, { kind: "component", type: ResizableComponent, selector: "[resizable]", inputs: ["resizable", "resizerColor", "gap"] }, { kind: "directive", type: HandleSizeControllerDirective, selector: "[handleSizeController]", inputs: ["handleSizeController"] }, { kind: "directive", type: NodeHandlesControllerDirective, selector: "[nodeHandlesController]" }, { kind: "directive", type: NodeResizeControllerDirective, selector: "[nodeResizeController]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2623
2666
  }
2624
2667
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeComponent, decorators: [{
2625
2668
  type: Component,
@@ -2635,7 +2678,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
2635
2678
  HandleSizeControllerDirective,
2636
2679
  NodeHandlesControllerDirective,
2637
2680
  NodeResizeControllerDirective,
2638
- ], template: "<!-- Default node -->\n@if (model().node.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n (pointerStart)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- Template node -->\n@if (model().node.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: { node: model().node, selected: model().selected },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(model().node.type)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Default group node -->\n@if (model().node.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (pointerStart)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().node.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (pointerStart)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: {\n node: model().node,\n selected: model().selected,\n width: model().width,\n height: model().height,\n },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (!handle.template) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@if (toolbar(); as toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
2681
+ ], template: "<!-- Default node -->\n@if (model().node.type === 'default') {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (pointerStart)=\"pullNode(); selectNode()\">\n <default-node\n nodeHandlesController\n [selected]=\"model().selected()\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\"\n [style.max-width]=\"model().styleWidth()\"\n [style.max-height]=\"model().styleHeight()\">\n <div [outerHTML]=\"model().text()\"></div>\n\n <handle type=\"source\" position=\"right\" />\n <handle type=\"target\" position=\"left\" />\n </default-node>\n </svg:foreignObject>\n}\n\n<!-- Template node -->\n@if (model().node.type === 'html-template' && nodeTemplate()) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: { node: model().node, selected: model().selected },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Component node -->\n@if (model().isComponentType) {\n <svg:foreignObject\n class=\"selectable\"\n [attr.width]=\"model().foWidth()\"\n [attr.height]=\"model().foHeight()\"\n (pointerStart)=\"pullNode()\">\n <div\n nodeHandlesController\n nodeResizeController\n class=\"wrapper\"\n [style.width]=\"model().styleWidth()\"\n [style.height]=\"model().styleHeight()\">\n <ng-container\n [ngComponentOutlet]=\"$any(model().node.type)\"\n [ngComponentOutletInputs]=\"model().componentTypeInputs\"\n [ngComponentOutletInjector]=\"injector\" />\n </div>\n </svg:foreignObject>\n}\n\n<!-- Default group node -->\n@if (model().node.type === 'default-group') {\n <svg:rect\n class=\"default-group-node\"\n rx=\"5\"\n ry=\"5\"\n [resizable]=\"model().resizable()\"\n [gap]=\"3\"\n [resizerColor]=\"model().color()\"\n [class.default-group-node_selected]=\"model().selected()\"\n [attr.width]=\"model().size().width\"\n [attr.height]=\"model().size().height\"\n [style.stroke]=\"model().color()\"\n [style.fill]=\"model().color()\"\n (pointerStart)=\"pullNode(); selectNode()\" />\n}\n\n<!-- Template group node -->\n@if (model().node.type === 'template-group' && groupNodeTemplate()) {\n <svg:g class=\"selectable\" nodeHandlesController (pointerStart)=\"pullNode()\">\n <ng-container\n [ngTemplateOutlet]=\"groupNodeTemplate() ?? null\"\n [ngTemplateOutletContext]=\"{\n $implicit: {\n node: model().node,\n selected: model().selected,\n width: model().width,\n height: model().height,\n },\n }\"\n [ngTemplateOutletInjector]=\"injector\" />\n </svg:g>\n}\n\n<!-- Resizer -->\n@if (model().resizerTemplate(); as template) {\n @if (model().resizable()) {\n <ng-template [ngTemplateOutlet]=\"template\" />\n }\n}\n\n<!-- Handles -->\n@for (handle of model().handles(); track handle) {\n @if (!handle.template) {\n <svg:circle\n class=\"default-handle\"\n r=\"5\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n [attr.stroke-width]=\"handle.strokeWidth\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\" />\n }\n\n @if (handle.template) {\n <svg:g\n [handleSizeController]=\"handle\"\n (pointerStart)=\"startConnection($event, handle)\"\n (pointerEnd)=\"endConnection()\">\n <ng-container *ngTemplateOutlet=\"handle.template; context: handle.templateContext\" />\n </svg:g>\n }\n\n @if (showMagnet()) {\n <svg:circle\n class=\"magnet\"\n [attr.r]=\"model().magnetRadius\"\n [attr.cx]=\"handle.hostOffset().x\"\n [attr.cy]=\"handle.hostOffset().y\"\n (pointerEnd)=\"endConnection(); resetValidateConnection(handle)\"\n (pointerOver)=\"validateConnection(handle)\"\n (pointerOut)=\"resetValidateConnection(handle)\" />\n }\n}\n\n<!-- Toolbar -->\n@if (toolbar(); as toolbar) {\n <svg:foreignObject\n [attr.width]=\"toolbar.size().width\"\n [attr.height]=\"toolbar.size().height\"\n [attr.transform]=\"toolbar.transform()\">\n <ng-container [ngTemplateOutlet]=\"toolbar.template()\" />\n </svg:foreignObject>\n}\n", styles: [".magnet{opacity:0}.wrapper{display:table-cell}.default-group-node{stroke-width:1.5px;fill-opacity:.05}.default-group-node_selected{stroke-width:2px}.default-handle{stroke:#fff;fill:#1b262c}\n"] }]
2639
2682
  }] });
2640
2683
 
2641
2684
  class ConnectionComponent {
@@ -3116,6 +3159,12 @@ class VflowComponent {
3116
3159
  get connection() {
3117
3160
  return this.flowEntitiesService.connection();
3118
3161
  }
3162
+ /**
3163
+ * Snap grid for node movement. Passes as [x, y]
3164
+ */
3165
+ set snapGrid(value) {
3166
+ this.flowSettingsService.snapGrid.set(value);
3167
+ }
3119
3168
  // #endregion
3120
3169
  // #region MAIN_INPUTS
3121
3170
  /**
@@ -3217,7 +3266,7 @@ class VflowComponent {
3217
3266
  });
3218
3267
  }
3219
3268
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3220
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: false, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: false, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: false, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, optimization: { classPropertyName: "optimization", publicName: "optimization", isSignal: true, isRequired: false, transformFunction: null }, entitiesSelectable: { classPropertyName: "entitiesSelectable", publicName: "entitiesSelectable", isSignal: false, isRequired: false, transformFunction: null }, keyboardShortcuts: { classPropertyName: "keyboardShortcuts", publicName: "keyboardShortcuts", isSignal: false, isRequired: false, transformFunction: null }, connection: { classPropertyName: "connection", publicName: "connection", isSignal: false, isRequired: false, transformFunction: (settings) => new ConnectionModel(settings) }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: false, isRequired: true, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
3269
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: VflowComponent, isStandalone: true, selector: "vflow", inputs: { view: { classPropertyName: "view", publicName: "view", isSignal: false, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: false, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: false, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: false, isRequired: false, transformFunction: null }, optimization: { classPropertyName: "optimization", publicName: "optimization", isSignal: true, isRequired: false, transformFunction: null }, entitiesSelectable: { classPropertyName: "entitiesSelectable", publicName: "entitiesSelectable", isSignal: false, isRequired: false, transformFunction: null }, keyboardShortcuts: { classPropertyName: "keyboardShortcuts", publicName: "keyboardShortcuts", isSignal: false, isRequired: false, transformFunction: null }, connection: { classPropertyName: "connection", publicName: "connection", isSignal: false, isRequired: false, transformFunction: (settings) => new ConnectionModel(settings) }, snapGrid: { classPropertyName: "snapGrid", publicName: "snapGrid", isSignal: false, isRequired: false, transformFunction: null }, nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: false, isRequired: true, transformFunction: null }, edges: { classPropertyName: "edges", publicName: "edges", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { onComponentNodeEvent: "onComponentNodeEvent" }, providers: [
3221
3270
  DraggableService,
3222
3271
  ViewportService,
3223
3272
  FlowStatusService,
@@ -3278,6 +3327,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3278
3327
  args: [{
3279
3328
  transform: (settings) => new ConnectionModel(settings),
3280
3329
  }]
3330
+ }], snapGrid: [{
3331
+ type: Input
3281
3332
  }], nodes: [{
3282
3333
  type: Input,
3283
3334
  args: [{ required: true }]