ngx-vflow 1.3.1 → 1.4.1

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.
@@ -150,6 +150,7 @@ function clamp(value, min = 0, max = 1) {
150
150
  class FlowSettingsService {
151
151
  constructor() {
152
152
  this.entitiesSelectable = signal(true);
153
+ this.elevateNodesOnSelect = signal(true);
153
154
  /**
154
155
  * @see {VflowComponent.view}
155
156
  */
@@ -1613,14 +1614,15 @@ class NodeRenderingService {
1613
1614
  });
1614
1615
  }
1615
1616
  pullNode(node) {
1616
- // TODO do not pull when the node is already on top
1617
+ const isAlreadyOnTop = node.renderOrder() !== 0 && this.maxOrder() === node.renderOrder();
1618
+ // TODO: does not work for group nodes
1619
+ if (isAlreadyOnTop) {
1620
+ return;
1621
+ }
1617
1622
  // pull node
1618
1623
  node.renderOrder.set(this.maxOrder() + 1);
1619
1624
  // pull children
1620
- this.flowEntitiesService
1621
- .nodes()
1622
- .filter((n) => n.parent() === node)
1623
- .forEach((n) => this.pullNode(n));
1625
+ node.children().forEach((n) => this.pullNode(n));
1624
1626
  }
1625
1627
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1626
1628
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NodeRenderingService }); }
@@ -2241,10 +2243,9 @@ class ResizableComponent {
2241
2243
  resize(event) {
2242
2244
  if (!this.resizeSide)
2243
2245
  return;
2244
- if (this.isResizeConstrained(event))
2245
- return;
2246
2246
  const offset = calcOffset(event.movementX, event.movementY, this.zoom());
2247
- const { x, y, width, height } = constrainRect(applyResize(this.resizeSide, this.model, offset), this.model, this.resizeSide, this.minWidth, this.minHeight);
2247
+ const resized = applyResize(this.resizeSide, this.model, offset, this.getDistanceToEdge(event));
2248
+ const { x, y, width, height } = constrainRect(resized, this.model, this.resizeSide, this.minWidth, this.minHeight);
2248
2249
  this.model.setPoint({ x, y });
2249
2250
  this.model.width.set(width);
2250
2251
  this.model.height.set(height);
@@ -2253,41 +2254,15 @@ class ResizableComponent {
2253
2254
  this.resizeSide = null;
2254
2255
  this.model.resizing.set(false);
2255
2256
  }
2256
- isResizeConstrained({ x, y, movementX, movementY }) {
2257
- const flowPoint = this.spacePointContext.documentPointToFlowPoint({ x, y });
2258
- if (this.resizeSide?.includes('right')) {
2259
- if (movementX > 0 && flowPoint.x < this.model.point().x + this.model.size().width) {
2260
- return true;
2261
- }
2262
- if (movementX < 0 && flowPoint.x > this.model.point().x + this.model.size().width) {
2263
- return true;
2264
- }
2265
- }
2266
- if (this.resizeSide?.includes('left')) {
2267
- if (movementX < 0 && flowPoint.x > this.model.point().x) {
2268
- return true;
2269
- }
2270
- if (movementX > 0 && flowPoint.x < this.model.point().x) {
2271
- return true;
2272
- }
2273
- }
2274
- if (this.resizeSide?.includes('bottom')) {
2275
- if (movementY > 0 && flowPoint.y < this.model.point().y + this.model.size().height) {
2276
- return true;
2277
- }
2278
- if (movementY < 0 && flowPoint.y > this.model.point().y + this.model.size().height) {
2279
- return true;
2280
- }
2281
- }
2282
- if (this.resizeSide?.includes('top')) {
2283
- if (movementY < 0 && flowPoint.y > this.model.point().y) {
2284
- return true;
2285
- }
2286
- if (movementY > 0 && flowPoint.y < this.model.point().y) {
2287
- return true;
2288
- }
2289
- }
2290
- return false;
2257
+ getDistanceToEdge(event) {
2258
+ const flowPoint = this.spacePointContext.documentPointToFlowPoint({ x: event.x, y: event.y });
2259
+ const { x, y } = this.model.globalPoint();
2260
+ return {
2261
+ left: flowPoint.x - x,
2262
+ right: flowPoint.x - (x + this.model.width()),
2263
+ top: flowPoint.y - y,
2264
+ bottom: flowPoint.y - (y + this.model.height()),
2265
+ };
2291
2266
  }
2292
2267
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ResizableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2293
2268
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "17.3.12", type: ResizableComponent, isStandalone: true, selector: "[resizable]", inputs: { resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, resizerColor: { classPropertyName: "resizerColor", publicName: "resizerColor", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "resizer", first: true, predicate: ["resizer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #resizer>\n <svg:g>\n <!-- top line -->\n <svg:line\n class=\"top\"\n stroke-width=\"2\"\n [attr.x1]=\"lineGap\"\n [attr.y1]=\"-gap()\"\n [attr.x2]=\"model.size().width - lineGap\"\n [attr.y2]=\"-gap()\"\n [attr.stroke]=\"resizerColor()\"\n (pointerStart)=\"startResize('top', $event)\" />\n <!-- Left line -->\n <svg:line\n class=\"left\"\n stroke-width=\"2\"\n [attr.x1]=\"-gap()\"\n [attr.y1]=\"lineGap\"\n [attr.x2]=\"-gap()\"\n [attr.y2]=\"model.size().height - lineGap\"\n [attr.stroke]=\"resizerColor()\"\n (pointerStart)=\"startResize('left', $event)\" />\n <!-- Bottom line -->\n <svg:line\n class=\"bottom\"\n stroke-width=\"2\"\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 (pointerStart)=\"startResize('bottom', $event)\" />\n <!-- Right line -->\n <svg:line\n class=\"right\"\n stroke-width=\"2\"\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 (pointerStart)=\"startResize('right', $event)\" />\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 <!-- 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 <!-- 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 <!-- 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 </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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
@@ -2305,43 +2280,44 @@ function calcOffset(movementX, movementY, zoom) {
2305
2280
  offsetY: round(movementY / zoom),
2306
2281
  };
2307
2282
  }
2308
- function applyResize(side, model, offset) {
2283
+ function applyResize(side, model, offset, distanceToEdge) {
2309
2284
  const { offsetX, offsetY } = offset;
2310
2285
  const { x, y } = model.point();
2311
- const { width, height } = model.size();
2286
+ const width = model.width();
2287
+ const height = model.height();
2312
2288
  // Handle each case of resizing (top, bottom, left, right, corners)
2313
2289
  switch (side) {
2314
2290
  case 'left':
2315
- return { x: x + offsetX, y, width: width - offsetX, height };
2291
+ return { x: x + offsetX + distanceToEdge.left, y, width: width - offsetX - distanceToEdge.left, height };
2316
2292
  case 'right':
2317
- return { x, y, width: width + offsetX, height };
2293
+ return { x, y, width: width + offsetX + distanceToEdge.right, height };
2318
2294
  case 'top':
2319
- return { x, y: y + offsetY, width, height: height - offsetY };
2295
+ return { x, y: y + offsetY + distanceToEdge.top, width, height: height - offsetY - distanceToEdge.top };
2320
2296
  case 'bottom':
2321
- return { x, y, width, height: height + offsetY };
2297
+ return { x, y, width, height: height + offsetY + distanceToEdge.bottom };
2322
2298
  case 'top-left':
2323
2299
  return {
2324
- x: x + offsetX,
2325
- y: y + offsetY,
2326
- width: width - offsetX,
2327
- height: height - offsetY,
2300
+ x: x + offsetX + distanceToEdge.left,
2301
+ y: y + offsetY + distanceToEdge.top,
2302
+ width: width - offsetX - distanceToEdge.left,
2303
+ height: height - offsetY - distanceToEdge.top,
2328
2304
  };
2329
2305
  case 'top-right':
2330
2306
  return {
2331
2307
  x,
2332
- y: y + offsetY,
2333
- width: width + offsetX,
2334
- height: height - offsetY,
2308
+ y: y + offsetY + distanceToEdge.top,
2309
+ width: width + offsetX + distanceToEdge.right,
2310
+ height: height - offsetY - distanceToEdge.top,
2335
2311
  };
2336
2312
  case 'bottom-left':
2337
2313
  return {
2338
- x: x + offsetX,
2314
+ x: x + offsetX + distanceToEdge.left,
2339
2315
  y,
2340
- width: width - offsetX,
2341
- height: height + offsetY,
2316
+ width: width - offsetX - distanceToEdge.left,
2317
+ height: height + offsetY + distanceToEdge.bottom,
2342
2318
  };
2343
2319
  case 'bottom-right':
2344
- return { x, y, width: width + offsetX, height: height + offsetY };
2320
+ return { x, y, width: width + offsetX + distanceToEdge.right, height: height + offsetY + distanceToEdge.bottom };
2345
2321
  }
2346
2322
  }
2347
2323
  function constrainRect(rect, model, side, minWidth, minHeight) {
@@ -2353,27 +2329,33 @@ function constrainRect(rect, model, side, minWidth, minHeight) {
2353
2329
  width = Math.max(minWidth, width);
2354
2330
  height = Math.max(minHeight, height);
2355
2331
  // Apply left/top constraints based on minimum size
2356
- x = Math.min(x, model.point().x + model.size().width - minWidth);
2357
- y = Math.min(y, model.point().y + model.size().height - minHeight);
2332
+ x = Math.min(x, model.point().x + model.width() - minWidth);
2333
+ y = Math.min(y, model.point().y + model.height() - minHeight);
2358
2334
  const parent = model.parent();
2359
2335
  // 3. Apply maximum size constraints based on parent size (if exists)
2360
2336
  if (parent) {
2361
- x = Math.max(x, 0); // Left boundary of the parent
2362
- y = Math.max(y, 0); // Top boundary of the parent
2363
- if (x === 0) {
2364
- width = model.point().x + model.size().width;
2337
+ const parentWidth = parent.width();
2338
+ const parentHeight = parent.height();
2339
+ const modelX = model.point().x;
2340
+ const modelY = model.point().y;
2341
+ x = Math.max(x, 0);
2342
+ y = Math.max(y, 0);
2343
+ // Stop resizing when hitting left or top boundary
2344
+ if (side.includes('left') && x === 0) {
2345
+ width = Math.min(width, modelX + model.width());
2365
2346
  }
2366
- if (y === 0) {
2367
- height = model.point().y + model.size().height;
2347
+ if (side.includes('top') && y === 0) {
2348
+ height = Math.min(height, modelY + model.height());
2368
2349
  }
2369
- width = Math.min(width, parent.size().width - model.point().x);
2370
- height = Math.min(height, parent.size().height - model.point().y);
2350
+ // Allow right/bottom resizing without being blocked
2351
+ width = Math.min(width, parentWidth - x);
2352
+ height = Math.min(height, parentHeight - y);
2371
2353
  }
2372
2354
  const bounds = getNodesBounds(model.children());
2373
2355
  // 4. Apply child node constraints (if children exist)
2374
2356
  if (bounds) {
2375
2357
  if (side.includes('left')) {
2376
- x = Math.min(x, model.point().x + model.size().width - (bounds.x + bounds.width));
2358
+ x = Math.min(x, model.point().x + model.width() - (bounds.x + bounds.width));
2377
2359
  width = Math.max(width, bounds.x + bounds.width);
2378
2360
  }
2379
2361
  if (side.includes('right')) {
@@ -2383,7 +2365,7 @@ function constrainRect(rect, model, side, minWidth, minHeight) {
2383
2365
  height = Math.max(height, bounds.y + bounds.height);
2384
2366
  }
2385
2367
  if (side.includes('top')) {
2386
- y = Math.min(y, model.point().y + model.size().height - (bounds.y + bounds.height));
2368
+ y = Math.min(y, model.point().y + model.height() - (bounds.y + bounds.height));
2387
2369
  height = Math.max(height, bounds.y + bounds.height);
2388
2370
  }
2389
2371
  }
@@ -2658,7 +2640,9 @@ class NodeComponent {
2658
2640
  this.connectionController?.endConnection();
2659
2641
  }
2660
2642
  pullNode() {
2661
- this.nodeRenderingService.pullNode(this.model());
2643
+ if (this.flowSettingsService.elevateNodesOnSelect()) {
2644
+ this.nodeRenderingService.pullNode(this.model());
2645
+ }
2662
2646
  }
2663
2647
  selectNode() {
2664
2648
  if (this.flowSettingsService.entitiesSelectable()) {
@@ -3172,6 +3156,12 @@ class VflowComponent {
3172
3156
  set snapGrid(value) {
3173
3157
  this.flowSettingsService.snapGrid.set(value);
3174
3158
  }
3159
+ /**
3160
+ * Raizing z-index for selected node
3161
+ */
3162
+ set elevateNodesOnSelect(value) {
3163
+ this.flowSettingsService.elevateNodesOnSelect.set(value);
3164
+ }
3175
3165
  // #endregion
3176
3166
  // #region MAIN_INPUTS
3177
3167
  /**
@@ -3273,7 +3263,7 @@ class VflowComponent {
3273
3263
  });
3274
3264
  }
3275
3265
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VflowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3276
- 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: [
3266
+ 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 }, elevateNodesOnSelect: { classPropertyName: "elevateNodesOnSelect", publicName: "elevateNodesOnSelect", 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: [
3277
3267
  DraggableService,
3278
3268
  ViewportService,
3279
3269
  FlowStatusService,
@@ -3336,6 +3326,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
3336
3326
  }]
3337
3327
  }], snapGrid: [{
3338
3328
  type: Input
3329
+ }], elevateNodesOnSelect: [{
3330
+ type: Input
3339
3331
  }], nodes: [{
3340
3332
  type: Input,
3341
3333
  args: [{ required: true }]