ngx-vflow 1.1.2 → 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.
@@ -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 {
@@ -1758,6 +1780,7 @@ class EdgeLabelComponent {
1758
1780
  constructor() {
1759
1781
  this.zone = inject(NgZone);
1760
1782
  this.destroyRef = inject(DestroyRef);
1783
+ this.settingsService = inject(FlowSettingsService);
1761
1784
  // TODO: too many inputs
1762
1785
  this.model = input.required();
1763
1786
  this.edgeModel = input.required();
@@ -1777,12 +1800,29 @@ class EdgeLabelComponent {
1777
1800
  y: point.y - height / 2,
1778
1801
  };
1779
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
+ });
1780
1819
  }
1781
1820
  ngAfterViewInit() {
1782
- resizable([this.edgeLabelWrapperRef().nativeElement], this.zone)
1821
+ const labelElement = this.edgeLabelWrapperRef().nativeElement;
1822
+ resizable([labelElement], this.zone)
1783
1823
  .pipe(startWith(null), tap(() => {
1784
- const width = this.edgeLabelWrapperRef().nativeElement.clientWidth + MAGIC_NUMBER_TO_FIX_GLITCH_IN_CHROME;
1785
- const height = this.edgeLabelWrapperRef().nativeElement.clientHeight + MAGIC_NUMBER_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;
1786
1826
  this.model().size.set({ width, height });
1787
1827
  }), takeUntilDestroyed(this.destroyRef))
1788
1828
  .subscribe();
@@ -1796,11 +1836,11 @@ class EdgeLabelComponent {
1796
1836
  };
1797
1837
  }
1798
1838
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1799
- 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 }); }
1800
1840
  }
1801
1841
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EdgeLabelComponent, decorators: [{
1802
1842
  type: Component,
1803
- 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"] }]
1804
1844
  }] });
1805
1845
 
1806
1846
  class EdgeComponent {
@@ -3119,6 +3159,12 @@ class VflowComponent {
3119
3159
  get connection() {
3120
3160
  return this.flowEntitiesService.connection();
3121
3161
  }
3162
+ /**
3163
+ * Snap grid for node movement. Passes as [x, y]
3164
+ */
3165
+ set snapGrid(value) {
3166
+ this.flowSettingsService.snapGrid.set(value);
3167
+ }
3122
3168
  // #endregion
3123
3169
  // #region MAIN_INPUTS
3124
3170
  /**
@@ -3220,7 +3266,7 @@ class VflowComponent {
3220
3266
  });
3221
3267
  }
3222
3268
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3223
- 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: [
3224
3270
  DraggableService,
3225
3271
  ViewportService,
3226
3272
  FlowStatusService,
@@ -3281,6 +3327,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3281
3327
  args: [{
3282
3328
  transform: (settings) => new ConnectionModel(settings),
3283
3329
  }]
3330
+ }], snapGrid: [{
3331
+ type: Input
3284
3332
  }], nodes: [{
3285
3333
  type: Input,
3286
3334
  args: [{ required: true }]