@sapui5/sap.suite.ui.commons 1.136.13 → 1.136.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/package.json +1 -1
  2. package/src/sap/suite/ui/commons/.library +1 -1
  3. package/src/sap/suite/ui/commons/AriaProperties.js +1 -1
  4. package/src/sap/suite/ui/commons/CalculationBuilder.js +1 -1
  5. package/src/sap/suite/ui/commons/CalculationBuilderExpression.js +1 -1
  6. package/src/sap/suite/ui/commons/CalculationBuilderFunction.js +1 -1
  7. package/src/sap/suite/ui/commons/CalculationBuilderGroup.js +1 -1
  8. package/src/sap/suite/ui/commons/CalculationBuilderItem.js +1 -1
  9. package/src/sap/suite/ui/commons/CalculationBuilderValidationResult.js +1 -1
  10. package/src/sap/suite/ui/commons/CalculationBuilderVariable.js +1 -1
  11. package/src/sap/suite/ui/commons/CloudFilePicker.js +1 -1
  12. package/src/sap/suite/ui/commons/MicroProcessFlow.js +1 -1
  13. package/src/sap/suite/ui/commons/MicroProcessFlowItem.js +1 -1
  14. package/src/sap/suite/ui/commons/flexibility/changeHandler/PropertyChangeMapper.js +1 -1
  15. package/src/sap/suite/ui/commons/imageeditor/CropCustomShapeHistoryItem.js +1 -1
  16. package/src/sap/suite/ui/commons/imageeditor/CropEllipseHistoryItem.js +1 -1
  17. package/src/sap/suite/ui/commons/imageeditor/CropRectangleHistoryItem.js +1 -1
  18. package/src/sap/suite/ui/commons/imageeditor/CustomSizeItem.js +1 -1
  19. package/src/sap/suite/ui/commons/imageeditor/FilterHistoryItem.js +1 -1
  20. package/src/sap/suite/ui/commons/imageeditor/FlipHistoryItem.js +1 -1
  21. package/src/sap/suite/ui/commons/imageeditor/HistoryItem.js +1 -1
  22. package/src/sap/suite/ui/commons/imageeditor/ImageEditor.js +1 -1
  23. package/src/sap/suite/ui/commons/imageeditor/ImageEditorContainer.js +1 -1
  24. package/src/sap/suite/ui/commons/imageeditor/ImageEditorResponsiveContainer.js +1 -1
  25. package/src/sap/suite/ui/commons/imageeditor/ResizeHistoryItem.js +1 -1
  26. package/src/sap/suite/ui/commons/imageeditor/RotateHistoryItem.js +1 -1
  27. package/src/sap/suite/ui/commons/library.js +1 -1
  28. package/src/sap/suite/ui/commons/messagebundle_ar.properties +39 -5
  29. package/src/sap/suite/ui/commons/messagebundle_bg.properties +39 -5
  30. package/src/sap/suite/ui/commons/messagebundle_ca.properties +39 -5
  31. package/src/sap/suite/ui/commons/messagebundle_cnr.properties +39 -5
  32. package/src/sap/suite/ui/commons/messagebundle_cs.properties +38 -4
  33. package/src/sap/suite/ui/commons/messagebundle_cy.properties +39 -5
  34. package/src/sap/suite/ui/commons/messagebundle_da.properties +38 -4
  35. package/src/sap/suite/ui/commons/messagebundle_de.properties +39 -5
  36. package/src/sap/suite/ui/commons/messagebundle_el.properties +39 -5
  37. package/src/sap/suite/ui/commons/messagebundle_en.properties +39 -5
  38. package/src/sap/suite/ui/commons/messagebundle_en_GB.properties +39 -5
  39. package/src/sap/suite/ui/commons/messagebundle_en_US_sappsd.properties +99 -28
  40. package/src/sap/suite/ui/commons/messagebundle_en_US_saptrc.properties +600 -537
  41. package/src/sap/suite/ui/commons/messagebundle_es.properties +40 -6
  42. package/src/sap/suite/ui/commons/messagebundle_es_MX.properties +38 -4
  43. package/src/sap/suite/ui/commons/messagebundle_et.properties +38 -4
  44. package/src/sap/suite/ui/commons/messagebundle_fi.properties +38 -4
  45. package/src/sap/suite/ui/commons/messagebundle_fr.properties +39 -5
  46. package/src/sap/suite/ui/commons/messagebundle_fr_CA.properties +39 -5
  47. package/src/sap/suite/ui/commons/messagebundle_hi.properties +39 -5
  48. package/src/sap/suite/ui/commons/messagebundle_hr.properties +39 -5
  49. package/src/sap/suite/ui/commons/messagebundle_hu.properties +39 -5
  50. package/src/sap/suite/ui/commons/messagebundle_id.properties +39 -5
  51. package/src/sap/suite/ui/commons/messagebundle_it.properties +44 -10
  52. package/src/sap/suite/ui/commons/messagebundle_iw.properties +39 -5
  53. package/src/sap/suite/ui/commons/messagebundle_ja.properties +39 -5
  54. package/src/sap/suite/ui/commons/messagebundle_kk.properties +38 -4
  55. package/src/sap/suite/ui/commons/messagebundle_ko.properties +38 -4
  56. package/src/sap/suite/ui/commons/messagebundle_lt.properties +39 -5
  57. package/src/sap/suite/ui/commons/messagebundle_lv.properties +39 -5
  58. package/src/sap/suite/ui/commons/messagebundle_mk.properties +39 -5
  59. package/src/sap/suite/ui/commons/messagebundle_ms.properties +39 -5
  60. package/src/sap/suite/ui/commons/messagebundle_nl.properties +38 -4
  61. package/src/sap/suite/ui/commons/messagebundle_no.properties +39 -5
  62. package/src/sap/suite/ui/commons/messagebundle_pl.properties +39 -5
  63. package/src/sap/suite/ui/commons/messagebundle_pt.properties +39 -5
  64. package/src/sap/suite/ui/commons/messagebundle_pt_PT.properties +39 -5
  65. package/src/sap/suite/ui/commons/messagebundle_ro.properties +39 -5
  66. package/src/sap/suite/ui/commons/messagebundle_ru.properties +39 -5
  67. package/src/sap/suite/ui/commons/messagebundle_sh.properties +39 -5
  68. package/src/sap/suite/ui/commons/messagebundle_sk.properties +39 -5
  69. package/src/sap/suite/ui/commons/messagebundle_sl.properties +38 -4
  70. package/src/sap/suite/ui/commons/messagebundle_sr.properties +39 -5
  71. package/src/sap/suite/ui/commons/messagebundle_sv.properties +38 -4
  72. package/src/sap/suite/ui/commons/messagebundle_th.properties +39 -5
  73. package/src/sap/suite/ui/commons/messagebundle_tr.properties +39 -5
  74. package/src/sap/suite/ui/commons/messagebundle_uk.properties +38 -4
  75. package/src/sap/suite/ui/commons/messagebundle_vi.properties +39 -5
  76. package/src/sap/suite/ui/commons/messagebundle_zh_CN.properties +39 -5
  77. package/src/sap/suite/ui/commons/messagebundle_zh_TW.properties +39 -5
  78. package/src/sap/suite/ui/commons/networkgraph/Graph.js +8 -0
  79. package/src/sap/suite/ui/commons/networkgraph/util/ConnectionPathUtils.js +315 -69
  80. package/src/sap/suite/ui/commons/networkgraph/util/DependencyLayoutHelper.js +21 -6
  81. package/src/sap/suite/ui/commons/statusindicator/Circle.js +1 -1
  82. package/src/sap/suite/ui/commons/statusindicator/CustomShape.js +1 -1
  83. package/src/sap/suite/ui/commons/statusindicator/DiscreteThreshold.js +1 -1
  84. package/src/sap/suite/ui/commons/statusindicator/FillingOption.js +1 -1
  85. package/src/sap/suite/ui/commons/statusindicator/LibraryShape.js +1 -1
  86. package/src/sap/suite/ui/commons/statusindicator/Path.js +1 -1
  87. package/src/sap/suite/ui/commons/statusindicator/PropertyThreshold.js +1 -1
  88. package/src/sap/suite/ui/commons/statusindicator/Rectangle.js +1 -1
  89. package/src/sap/suite/ui/commons/statusindicator/Shape.js +1 -1
  90. package/src/sap/suite/ui/commons/statusindicator/ShapeGroup.js +1 -1
  91. package/src/sap/suite/ui/commons/statusindicator/SimpleShape.js +1 -1
  92. package/src/sap/suite/ui/commons/statusindicator/StatusIndicator.js +1 -1
  93. package/src/sap/suite/ui/commons/taccount/TAccount.js +1 -1
  94. package/src/sap/suite/ui/commons/taccount/TAccountGroup.js +1 -1
  95. package/src/sap/suite/ui/commons/taccount/TAccountItem.js +1 -1
  96. package/src/sap/suite/ui/commons/taccount/TAccountItemProperty.js +1 -1
  97. package/src/sap/suite/ui/commons/taccount/TAccountPanel.js +1 -1
@@ -67,46 +67,89 @@ sap.ui.define([
67
67
 
68
68
  // Detect RTL mode from graph
69
69
  const bIsRTL = oGraph._bIsRtl || false;
70
+ const bEnhanced = oGraph.getEnableEnhancedLineRouting ? oGraph.getEnableEnhancedLineRouting() : false;
70
71
 
71
- // Iterate over all lines in the graph
72
- oGraph.getLines().forEach((oLine) => {
73
- const configuredConnectionType = oLine.getConnectionType(); // Get the connection type, if not configured at Line level then the default is Right to Left from source to target.
74
- // In RTL mode, use effective connection type (semantically reversed)
75
- const effectiveConnectionType = this._getEffectiveConnectionType(configuredConnectionType, bIsRTL);
76
- const oSourceNode = oLine.getFromNode();
77
- const oTargetNode = oLine.getToNode();
78
- // Get anchor coordinates for source and target nodes, usually the center of the respective sides
79
- const oSourceAnchor = this._getNodeAnchorCoordinates(oLine.getFromNode(), effectiveConnectionType, "source");
80
- const oTargetAnchor = this._getNodeAnchorCoordinates(oLine.getToNode(), effectiveConnectionType, "target");
81
- const mParams = {
82
- sourceAnchor: oSourceAnchor,
83
- targetAnchor: oTargetAnchor,
84
- sourceNode: oSourceNode,
85
- targetNode: oTargetNode,
86
- sConnectionType: effectiveConnectionType,
87
- gridSize: config.gridSize,
88
- minEdgeLength: config.minEdgeLength
89
- };
90
- // Calculate the path coordinates between the source and target anchors
91
- const aCoordinates = this._calculatePathCoordinates(mParams);
92
- // set the anchor points of the source and target nodes on the line as source and target for drawing the path.
93
- oLine.setSource({
94
- x: oSourceAnchor.x,
95
- y: oSourceAnchor.y
96
- });
97
- oLine.setTarget({
98
- x: oTargetAnchor.x,
99
- y: oTargetAnchor.y
100
- });
101
- // Set the calculated bend points on the line
102
- oLine.clearBends();
103
- // Set the calculated bend points on the line, determined by the connection type.
104
- if (aCoordinates && aCoordinates.length) {
105
- aCoordinates.forEach((oBend) => {
106
- oLine.addBend(oBend);
107
- });
72
+ try {
73
+ if (bEnhanced) {
74
+ this._routingRowCache = this._buildRoutingRowCache(oGraph.getNodes(), config.gridSize);
75
+
76
+ // Precompute per-node connection counts once to avoid rescanning all lines on each call.
77
+ // Keys are normalized to strings on both write and read — node keys may be numeric
78
+ // while Line#getFrom/getTo return them as-is, and Map.get() uses strict key identity.
79
+ this._analysisCache = new Map();
80
+ oGraph.getNodes().forEach(function(oNode) {
81
+ this._analysisCache.set(String(oNode.getKey()), { leftIncoming: 0, leftOutgoing: 0, rightIncoming: 0, rightOutgoing: 0 });
82
+ }, this);
83
+ oGraph.getLines().forEach(function(oLine) {
84
+ if (!oLine.getVisible() || oLine._isIgnored()) { return; }
85
+ var sFrom = oLine.getFrom();
86
+ var sTo = oLine.getTo();
87
+ var sType = oLine.getConnectionType();
88
+ var oFrom = this._analysisCache.get(String(sFrom));
89
+ var oTo = this._analysisCache.get(String(sTo));
90
+ if (oFrom) {
91
+ switch (sType) {
92
+ case "LeftToLeft": case "LeftToRight": oFrom.leftOutgoing++; break;
93
+ default: oFrom.rightOutgoing++; break;
94
+ }
95
+ }
96
+ if (oTo) {
97
+ switch (sType) {
98
+ case "LeftToLeft": case "RightToLeft": oTo.leftIncoming++; break;
99
+ default: oTo.rightIncoming++; break;
100
+ }
101
+ }
102
+ }, this);
108
103
  }
109
- });
104
+ // Iterate over all lines in the graph
105
+ oGraph.getLines().forEach((oLine) => {
106
+ const configuredConnectionType = oLine.getConnectionType(); // Get the connection type, if not configured at Line level then the default is Right to Left from source to target.
107
+ // In RTL mode, use effective connection type (semantically reversed)
108
+ const effectiveConnectionType = this._getEffectiveConnectionType(configuredConnectionType, bIsRTL);
109
+ const oSourceNode = oLine.getFromNode();
110
+ const oTargetNode = oLine.getToNode();
111
+ // Get anchor coordinates for source and target nodes, usually the center of the respective sides
112
+ const oSourceAnchor = this._getNodeAnchorCoordinates(oLine.getFromNode(), effectiveConnectionType, "source");
113
+ const oTargetAnchor = this._getNodeAnchorCoordinates(oLine.getToNode(), effectiveConnectionType, "target");
114
+
115
+ const mParams = {
116
+ sourceAnchor: oSourceAnchor,
117
+ targetAnchor: oTargetAnchor,
118
+ sourceNode: oSourceNode,
119
+ targetNode: oTargetNode,
120
+ sConnectionType: effectiveConnectionType,
121
+ gridSize: config.gridSize,
122
+ minEdgeLength: config.minEdgeLength,
123
+ bEnhanced: bEnhanced
124
+ };
125
+ // Calculate the path coordinates between the source and target anchors
126
+ const aCoordinates = this._calculatePathCoordinates(mParams);
127
+ // set the anchor points of the source and target nodes on the line as source and target for drawing the path.
128
+ oLine.setSource({
129
+ x: oSourceAnchor.x,
130
+ y: oSourceAnchor.y
131
+ });
132
+ oLine.setTarget({
133
+ x: oTargetAnchor.x,
134
+ y: oTargetAnchor.y
135
+ });
136
+ // Set the calculated bend points on the line
137
+ oLine.clearBends();
138
+ // Set the calculated bend points on the line, determined by the connection type.
139
+ if (aCoordinates && aCoordinates.length) {
140
+ aCoordinates.forEach((oBend) => {
141
+ oLine.addBend(oBend);
142
+ });
143
+ }
144
+ });
145
+ } finally {
146
+ this._routingRowCache = null;
147
+ this._analysisCache = null;
148
+ }
149
+
150
+ if (bEnhanced) {
151
+ this._resolveLineNodeCollisions(oGraph.getLines(), oGraph.getNodes(), config.gridSize);
152
+ }
110
153
  },
111
154
  /**
112
155
  * Calculates path coordinates between source and target anchors based on connection type.
@@ -161,7 +204,7 @@ sap.ui.define([
161
204
  * @private
162
205
  */
163
206
  _handleRightToLeftConnection: function (mParams) {
164
- const { sourceNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
207
+ const { sourceNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
165
208
  const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
166
209
 
167
210
  // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
@@ -176,16 +219,39 @@ sap.ui.define([
176
219
  // Visual: [SRC]----→[TGT] (perfect alignment)
177
220
  return [];
178
221
  } else if (anchorVerticalOffset > 5) {
179
- // Visual: [SRC]→ ←[TGT] (offset anchors - add horizontal correction)
180
- // | |
181
- // └─────┘
222
+ // Offset anchors due to different node heights in the same row.
223
+ // For skip-layer (intermediate nodes exist), route using corridor to avoid
224
+ // the vertical Z-bend passing through intermediate nodes.
225
+ if (bEnhanced) {
226
+ const iNodeWidthOff = sourceNode._iWidth || 0;
227
+ const iHorizontalGapOff = x2 - (x1 + iNodeWidthOff);
228
+ if (iHorizontalGapOff > Math.max(iNodeWidthOff, gridSize)) {
229
+ const xDrop = this.getRightGridIntersectionPoint(x1 + iNodeWidthOff, AY1, gridSize, minEdgeLength).x;
230
+ const xRise = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
231
+ const y3 = this._getRoutingYBelow(sourceNode, gridSize);
232
+ return [{ x: xDrop, y: AY1 }, { x: xDrop, y: y3 }, { x: xRise, y: y3 }, { x: xRise, y: AY2 }];
233
+ }
234
+ }
235
+ // Adjacent nodes — simple Z-bend through the gap between them
182
236
  const midX = AX1 + (AX2 - AX1) / 2;
183
237
  return [
184
238
  { x: midX, y: AY1 },
185
239
  { x: midX, y: AY2 }
186
240
  ];
187
241
  } else {
188
- // Multiple connections - use straight line anyway for now
242
+ // Multiple connections from same side.
243
+ if (bEnhanced) {
244
+ // If the gap is large enough to suggest intermediate nodes, route below
245
+ // to avoid the line passing through them visually.
246
+ const iNodeWidth = sourceNode._iWidth || 0;
247
+ const iHorizontalGap = x2 - (x1 + iNodeWidth);
248
+ if (iHorizontalGap > Math.max(iNodeWidth, gridSize)) {
249
+ const xDrop = this.getRightGridIntersectionPoint(x1 + iNodeWidth, AY1, gridSize, minEdgeLength).x;
250
+ const xRise = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
251
+ const y3 = this._getRoutingYBelow(sourceNode, gridSize);
252
+ return [{ x: xDrop, y: AY1 }, { x: xDrop, y: y3 }, { x: xRise, y: y3 }, { x: xRise, y: AY2 }];
253
+ }
254
+ }
189
255
  return [];
190
256
  }
191
257
  } else if (x1 >= x2) {
@@ -197,7 +263,7 @@ sap.ui.define([
197
263
  // Create U-shaped path going around both nodes
198
264
  const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
199
265
  const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
200
- const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
266
+ const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
201
267
 
202
268
  return [
203
269
  { x: x3, y: AY1 }, // Right from source
@@ -219,13 +285,42 @@ sap.ui.define([
219
285
  const x3 = AX1 + (x2 - (x1 + sourceNode._iWidth)) / 2;
220
286
  return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
221
287
  } else {
222
- // SCENARIO 2A2: Nodes are far apart - use grid-aligned Z-bend
223
- // Visual: [SRC]→ |
224
- // |
225
- //
226
- // ←[TGT]
227
- const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
228
- return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
288
+ // SCENARIO 2A2: Nodes are far apart
289
+ // If the horizontal gap is larger than one node width, intermediate nodes
290
+ // likely occupy the space — route the vertical segments at source/target
291
+ // column boundaries to avoid passing through them.
292
+ const iGap = x2 - (x1 + sourceNode._iWidth);
293
+ if (bEnhanced && iGap > sourceNode._iWidth) {
294
+ // SCENARIO 2A2-SKIP: Skip-layer connection.
295
+ // First go right to the nearest grid point (so the line visually exits the
296
+ // source from the center-right side), then drop/rise outside intermediate
297
+ // columns, then go horizontally to the target.
298
+ // Visual (y1 ≤ y2): [SRC]→——| Visual (y1 > y2): [SRC]→——|
299
+ // | ↑
300
+ // ↓————————→ | ←———————————|
301
+ // ↑
302
+ // ←[TGT]
303
+ const xDrop = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
304
+ const xRise = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
305
+ let y3;
306
+ if (y1 <= y2) {
307
+ y3 = this._getRoutingYBelow(sourceNode, gridSize);
308
+ } else {
309
+ y3 = this.getTopGridIntersectionPoint(xDrop, Math.min(y1, y2), gridSize, minEdgeLength).y;
310
+ if (y3 <= 0) {
311
+ y3 = this._getRoutingYBelow(sourceNode, gridSize);
312
+ }
313
+ }
314
+ return [{ x: xDrop, y: AY1 }, { x: xDrop, y: y3 }, { x: xRise, y: y3 }, { x: xRise, y: AY2 }];
315
+ } else {
316
+ // SCENARIO 2A2: Adjacent layers — grid-aligned Z-bend
317
+ // Visual: [SRC]→ |
318
+ // |
319
+ // ↓
320
+ // ←[TGT]
321
+ const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
322
+ return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
323
+ }
229
324
  }
230
325
  } else if (dx <= minEdgeLength + sourceNode._iWidth) {
231
326
  // SCENARIO 2B: Nodes are too close - create S-shaped path
@@ -239,7 +334,7 @@ sap.ui.define([
239
334
  // ←[TGT]
240
335
  const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, y1, gridSize, minEdgeLength).x;
241
336
  const x4 = this.getLeftGridIntersectionPoint(x2, AY2, gridSize, minEdgeLength).x;
242
- const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
337
+ const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
243
338
  return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
244
339
  } else if (y1 > y2) {
245
340
  // SCENARIO 2B2: Source below target - S-bend going up
@@ -265,7 +360,7 @@ sap.ui.define([
265
360
  // ←[TGT] ←---------
266
361
  const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
267
362
  const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
268
- const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
363
+ const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
269
364
  return [
270
365
  { x: x3, y: AY1 }, // Right from source
271
366
  { x: x3, y: y3 }, // Down past both nodes
@@ -310,7 +405,7 @@ sap.ui.define([
310
405
  * @private
311
406
  */
312
407
  _handleLeftToRightConnection: function (mParams) {
313
- const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
408
+ const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
314
409
  const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
315
410
  // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
316
411
  if (y1 === y2) {
@@ -345,7 +440,7 @@ sap.ui.define([
345
440
  // Create U-shaped path going around both nodes
346
441
  const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
347
442
  const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
348
- const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
443
+ const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
349
444
 
350
445
  return [
351
446
  { x: x3, y: AY1 }, // Right from source
@@ -388,7 +483,7 @@ sap.ui.define([
388
483
  // [TGT]→
389
484
  const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
390
485
  const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
391
- const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
486
+ const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
392
487
  return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
393
488
  } else if (y1 > y2) {
394
489
  // SCENARIO 2B2: Source below target - S-bend going up
@@ -414,7 +509,7 @@ sap.ui.define([
414
509
  // --------→[TGT]
415
510
  const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
416
511
  const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
417
- const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
512
+ const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
418
513
  return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
419
514
  } else if (y1 > y2) {
420
515
  // SCENARIO 3B: Source below and left of target - S-bend up and right
@@ -454,7 +549,7 @@ sap.ui.define([
454
549
  * @private
455
550
  */
456
551
  _handleLeftToLeftConnection: function (mParams) {
457
- const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
552
+ const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
458
553
  const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
459
554
  // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
460
555
  if (y1 === y2) {
@@ -469,7 +564,7 @@ sap.ui.define([
469
564
  (AX1 + (x2 + targetNode._iWidth)) / 2 :
470
565
  this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
471
566
  const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
472
- const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
567
+ const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
473
568
  return [
474
569
  { x: x3, y: AY1 }, // Left from source
475
570
  { x: x3, y: y3 }, // Down/up to avoid nodes
@@ -486,7 +581,7 @@ sap.ui.define([
486
581
  const x4 = Math.abs(x1 + sourceNode._iWidth - x2) <= gridSize ?
487
582
  (AX2 + x2) / 2 :
488
583
  this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
489
- const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
584
+ const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
490
585
  return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
491
586
  }
492
587
  } else if (y1 != y2) {
@@ -553,7 +648,7 @@ sap.ui.define([
553
648
  x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
554
649
  }
555
650
  const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
556
- const y3 = this.getBottomGridIntersectionPoint(x4, y2 + targetNode._iHeight, gridSize, minEdgeLength).y;
651
+ const y3 = bEnhanced ? this._getRoutingYBelow(targetNode, gridSize) : this.getBottomGridIntersectionPoint(x4, y2 + targetNode._iHeight, gridSize, minEdgeLength).y;
557
652
  return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
558
653
  }
559
654
  }
@@ -574,7 +669,7 @@ sap.ui.define([
574
669
  } else {
575
670
  x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
576
671
  }
577
- const y3 = (AY2 > (y1 + sourceNode._iHeight)) ? AY2 : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
672
+ const y3 = (AY2 > (y1 + sourceNode._iHeight)) ? AY2 : (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y);
578
673
  const aCoordinates = AY2 > (y1 + sourceNode._iHeight) ? [{ x: x3, y: AY1 }, { x: x3, y: y3 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
579
674
  return aCoordinates;
580
675
  } else if (y1 > y2) {
@@ -623,7 +718,7 @@ sap.ui.define([
623
718
  * @private
624
719
  */
625
720
  _handleRightToRightConnection: function (mParams) {
626
- const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
721
+ const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
627
722
  const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
628
723
  // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
629
724
  if (y1 === y2) {
@@ -636,7 +731,7 @@ sap.ui.define([
636
731
  const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
637
732
  const iDiff = Math.abs(x1 - AX2);
638
733
  const x4 = iDiff <= gridSize ? (x1 + x2) / 2 : this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
639
- const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
734
+ const y3 = y1 < gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
640
735
  return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
641
736
  } else if (x1 <= x2) {
642
737
  // SCENARIO 1B: Source to the left of target - create C-shaped path on right side
@@ -647,7 +742,7 @@ sap.ui.define([
647
742
  const iDff = Math.abs(AX1 - x2);
648
743
  const x3 = iDff <= gridSize ? (AX1 + x2) / 2 : this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
649
744
  const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
650
- const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
745
+ const y3 = y1 < gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
651
746
  return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
652
747
  }
653
748
  } else if (y1 != y2) {
@@ -669,7 +764,7 @@ sap.ui.define([
669
764
  } else {
670
765
  x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
671
766
  }
672
- const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
767
+ const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
673
768
  if (AY2 > y1 + sourceNode._iHeight) {
674
769
  // Simple vertical connection if target anchor is below source node
675
770
  // Visual: [SRC]→
@@ -702,7 +797,7 @@ sap.ui.define([
702
797
  } else {
703
798
  x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
704
799
  }
705
- const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
800
+ const y3 = y2 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength)) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
706
801
  if (AY1 > y2 + targetNode._iHeight) {
707
802
  // Simple vertical connection if target anchor is below source node
708
803
  // Visual: [SRC]→
@@ -738,7 +833,7 @@ sap.ui.define([
738
833
  } else {
739
834
  x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
740
835
  }
741
- const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
836
+ const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
742
837
  if (AY2 > y1 + sourceNode._iHeight) {
743
838
  // Simple vertical connection if target anchor is below source node
744
839
  // Visual: [SRC]→
@@ -773,7 +868,7 @@ sap.ui.define([
773
868
  } else {
774
869
  x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
775
870
  }
776
- const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
871
+ const y3 = y2 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength)) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
777
872
  if (AY1 > y2 + targetNode._iHeight) {
778
873
  // Simple vertical connection if target anchor is below source node
779
874
  // Visual: [SRC]→
@@ -867,6 +962,150 @@ sap.ui.define([
867
962
  }
868
963
  return { x: x, y: yIntersection };
869
964
  },
965
+ /**
966
+ * Post-processing pass: detects horizontal line segments that pass through a node's
967
+ * bounding box and shifts them below the collision.
968
+ * Only interior segments are adjusted, source and target anchors are never moved.
969
+ *
970
+ * @param {sap.suite.ui.commons.networkgraph.Line[]} aLines
971
+ * @param {sap.suite.ui.commons.networkgraph.Node[]} aNodes
972
+ * @param {number} gridSize
973
+ * @private
974
+ */
975
+ _resolveLineNodeCollisions: function(aLines, aNodes, gridSize) {
976
+ var aBounds = aNodes.map(function(oNode) {
977
+ return {
978
+ x: oNode.getX(),
979
+ y: oNode.getY(),
980
+ r: oNode.getX() + (oNode._iWidth || 0),
981
+ b: oNode.getY() + (oNode._iHeight || 0)
982
+ };
983
+ }).filter(function(b) { return b.r > b.x && b.b > b.y; });
984
+
985
+ aLines.forEach(function(oLine) {
986
+ if (oLine.getVisible && !oLine.getVisible()) { return; }
987
+ if (oLine._isIgnored && oLine._isIgnored()) { return; }
988
+
989
+ var aBends = oLine.getBends();
990
+ var aPoints = [{ x: oLine.getSource().getX(), y: oLine.getSource().getY() }];
991
+ aBends.forEach(function(oBend) { aPoints.push({ x: oBend.getX(), y: oBend.getY() }); });
992
+ aPoints.push({ x: oLine.getTarget().getX(), y: oLine.getTarget().getY() });
993
+
994
+ var iPointCount = aPoints.length;
995
+ var bChanged = false;
996
+
997
+ // Only check interior segments — never move source (0) or target (iPointCount-1)
998
+ for (var i = 1; i < iPointCount - 2; i++) {
999
+ var p1 = aPoints[i], p2 = aPoints[i + 1];
1000
+ if (p1.y !== p2.y) { continue; } // skip vertical segments
1001
+
1002
+ var y = p1.y;
1003
+ var xMin = Math.min(p1.x, p2.x);
1004
+ var xMax = Math.max(p1.x, p2.x);
1005
+
1006
+ var iMaxBottom = -Infinity;
1007
+ var bCollision = false;
1008
+ aBounds.forEach(function(oBound) {
1009
+ if (y > oBound.y && y <= oBound.b && xMin < oBound.r && xMax > oBound.x) {
1010
+ bCollision = true;
1011
+ if (oBound.b > iMaxBottom) { iMaxBottom = oBound.b; }
1012
+ }
1013
+ });
1014
+
1015
+ if (bCollision) {
1016
+ var iNewY = iMaxBottom + Math.round(gridSize * 0.5);
1017
+ aPoints[i].y = iNewY;
1018
+ aPoints[i + 1].y = iNewY;
1019
+ bChanged = true;
1020
+ }
1021
+ }
1022
+
1023
+ if (bChanged) {
1024
+ oLine.clearBends();
1025
+ for (var j = 1; j < iPointCount - 1; j++) {
1026
+ oLine.addBend(aPoints[j]);
1027
+ }
1028
+ }
1029
+ });
1030
+ },
1031
+
1032
+ /**
1033
+ * Builds a WeakMap of per-node row boundary data used by _getRoutingYBelow.
1034
+ * Uses two passes to avoid the single-pass bug where iRowBottom is not yet finalised
1035
+ * when a next-row candidate is evaluated.
1036
+ *
1037
+ * @param {object[]} aNodes - All graph nodes
1038
+ * @param {number} gridSize - Grid size used for same-row proximity threshold
1039
+ * @returns {WeakMap} node → { rowBottom, nextRowTop }
1040
+ * @private
1041
+ */
1042
+ _buildRoutingRowCache: function(aNodes, gridSize) {
1043
+ var mCache = new WeakMap();
1044
+ if (!aNodes.length) { return mCache; }
1045
+
1046
+ // Pass 1: group nodes by row (center within gridSize/2) and compute rowBottom per group.
1047
+ // The representative centerY is intentionally kept as the first node's center, a deliberate
1048
+ // approximation that avoids updating all prior comparisons when new nodes join.
1049
+ var aRows = [];
1050
+ aNodes.forEach(function(oNode) {
1051
+ var h = oNode._iHeight || 0;
1052
+ var centerY = oNode.getY() + h / 2;
1053
+ var oRow = null;
1054
+ for (var i = 0; i < aRows.length; i++) {
1055
+ if (Math.abs(aRows[i].centerY - centerY) < gridSize / 2) {
1056
+ oRow = aRows[i];
1057
+ break;
1058
+ }
1059
+ }
1060
+ if (oRow) {
1061
+ oRow.nodes.push(oNode);
1062
+ oRow.rowBottom = Math.max(oRow.rowBottom, oNode.getY() + h);
1063
+ oRow.rowTop = Math.min(oRow.rowTop, oNode.getY());
1064
+ } else {
1065
+ aRows.push({ centerY: centerY, nodes: [oNode], rowBottom: oNode.getY() + h, rowTop: oNode.getY() });
1066
+ }
1067
+ });
1068
+
1069
+ // Pass 2: for each row, find the nearest next rowTop. rowTop isn't monotonic with
1070
+ // rowBottom if the heights vary, so the scan can't stop at the first match. It must
1071
+ // keep the minimum qualifying rowTop across all subsequent rows.
1072
+ aRows.sort(function(a, b) { return a.rowBottom - b.rowBottom; });
1073
+ aRows.forEach(function(oRow, iRowIdx) {
1074
+ var iNextRowTop = Infinity;
1075
+ for (var k = iRowIdx + 1; k < aRows.length; k++) {
1076
+ if (aRows[k].rowTop > oRow.rowBottom && aRows[k].rowTop < iNextRowTop) {
1077
+ iNextRowTop = aRows[k].rowTop;
1078
+ }
1079
+ }
1080
+ oRow.nodes.forEach(function(oNode) {
1081
+ mCache.set(oNode, { rowBottom: oRow.rowBottom, nextRowTop: iNextRowTop });
1082
+ });
1083
+ });
1084
+
1085
+ return mCache;
1086
+ },
1087
+
1088
+ /**
1089
+ * Returns the Y coordinate of the horizontal routing corridor below a node's row.
1090
+ * Uses pre-computed row boundary data from _buildRoutingRowCache (O(1) lookup).
1091
+ * Falls back to node-bottom + 0.75 * gridSize when called outside normalizeLines.
1092
+ *
1093
+ * @param {object} oNode - Source/target node
1094
+ * @param {number} gridSize - Grid size for fallback spacing
1095
+ * @returns {number} Y coordinate of the routing corridor
1096
+ * @private
1097
+ */
1098
+ _getRoutingYBelow: function(oNode, gridSize) {
1099
+ var oData = this._routingRowCache && this._routingRowCache.get(oNode);
1100
+ var iRowBottom = oData ? oData.rowBottom : oNode.getY() + (oNode._iHeight || 0);
1101
+ var iNextRowTop = oData ? oData.nextRowTop : Infinity;
1102
+
1103
+ if (iNextRowTop === Infinity) {
1104
+ return iRowBottom + Math.round(gridSize * 0.75);
1105
+ }
1106
+ return Math.round((iRowBottom + iNextRowTop) / 2);
1107
+ },
1108
+
870
1109
  /**
871
1110
  * Gets the anchor coordinates for a node based on connection type and anchor type.
872
1111
  * Enhanced to handle center positioning for single connections and offset positioning for mixed connections.
@@ -1012,6 +1251,13 @@ sap.ui.define([
1012
1251
  return { leftIncoming: 0, leftOutgoing: 0, rightIncoming: 0, rightOutgoing: 0 };
1013
1252
  }
1014
1253
 
1254
+ if (this._analysisCache) {
1255
+ var sCachedKey = oNode.getKey && oNode.getKey();
1256
+ if (sCachedKey !== undefined && this._analysisCache.has(String(sCachedKey))) {
1257
+ return this._analysisCache.get(String(sCachedKey));
1258
+ }
1259
+ }
1260
+
1015
1261
  const oGraph = oNode.getParent();
1016
1262
  const aLines = oGraph.getLines();
1017
1263
  const sNodeKey = oNode.getKey();
@@ -94,7 +94,8 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
94
94
  horizontalSpacing: DEFAULT_HORIZONTAL_SPACING,
95
95
  verticalSpacing: DEFAULT_VERTICAL_SPACING,
96
96
  componentArrangement: oComponentArrangement.Horizontal,
97
- clearCache: false
97
+ clearCache: false,
98
+ bEnhancedLineRouting: oGraph.getEnableEnhancedLineRouting ? oGraph.getEnableEnhancedLineRouting() : false
98
99
  },
99
100
  mConfig || {}
100
101
  );
@@ -112,9 +113,11 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
112
113
  this._calculateCoordinates(oGraph, config);
113
114
  this._adjustHorizontalSpacingForNodeWidths(oGraph, config);
114
115
  this._adjustVerticalSpacingForNodeHeights(oGraph, config);
116
+
115
117
  if (oGraph._bIsRtl) {
116
118
  this._horizontalMirror(oGraph, config.horizontalSpacing);
117
119
  }
120
+
118
121
  this._storeFinalPositions(oGraph, config.horizontalSpacing);
119
122
  } catch (e) {
120
123
  Log.error("DependencyLayoutHelper error: " + e.message, e);
@@ -308,7 +311,7 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
308
311
  * @param {object} config - Configuration object
309
312
  * @private
310
313
  */
311
- _processComponents: function (aComponents, { componentArrangement }) {
314
+ _processComponents: function (aComponents, { componentArrangement, bEnhancedLineRouting }) {
312
315
  const aIsolatedNodes = [];
313
316
  const aConnectedComponents = [];
314
317
 
@@ -329,7 +332,7 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
329
332
  let iMaxLayerAcrossAll = 0;
330
333
 
331
334
  aConnectedComponents.forEach((aComponentNodes, iCompIndex) => {
332
- this._assignLayersForComponent(aComponentNodes, iCompIndex);
335
+ this._assignLayersForComponent(aComponentNodes, iCompIndex, bEnhancedLineRouting);
333
336
  this._adjustParallelLayersForComponent(aComponentNodes);
334
337
  this._assignVerticalPositionsForComponent(aComponentNodes);
335
338
 
@@ -393,7 +396,7 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
393
396
  * @param {number} iComponentId - Component identifier
394
397
  * @private
395
398
  */
396
- _assignLayersForComponent: function (aComponentNodes, iComponentId) {
399
+ _assignLayersForComponent: function (aComponentNodes, iComponentId, bEnhancedLineRouting) {
397
400
  const aQueue = [];
398
401
  const mInDegree = {};
399
402
 
@@ -463,18 +466,30 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
463
466
  ) {
464
467
  const iCurrentLayer = oNode._layoutData.layer;
465
468
  let iMaxTargetLayer = -1;
469
+ let iMinTargetLayer = Infinity;
466
470
  oNode._layoutData.outgoing.forEach((t) => {
467
471
  if (t._layoutData.layer > iMaxTargetLayer) {
468
472
  iMaxTargetLayer = t._layoutData.layer;
469
473
  }
474
+ if (t._layoutData.layer < iMinTargetLayer) {
475
+ iMinTargetLayer = t._layoutData.layer;
476
+ }
470
477
  });
471
478
 
472
479
  const bTargetHasOtherIncoming = oNode._layoutData.outgoing.some(
473
480
  (t) => t._layoutData.incoming.filter((n) => n !== oNode).length > 0
474
481
  );
475
482
 
476
- if (iMaxTargetLayer > iCurrentLayer && bTargetHasOtherIncoming) {
477
- oNode._layoutData.layer = Math.max(0, iMaxTargetLayer - 1);
483
+ // Guard: skip if any target is at or below the proposed layer that would create same-layer edges.
484
+ const iProposedLayer = Math.max(0, iMaxTargetLayer - 1);
485
+ if (bEnhancedLineRouting) {
486
+ if (iMaxTargetLayer > iCurrentLayer && bTargetHasOtherIncoming && iMinTargetLayer > iProposedLayer) {
487
+ oNode._layoutData.layer = iProposedLayer;
488
+ }
489
+ } else {
490
+ if (iMaxTargetLayer > iCurrentLayer && bTargetHasOtherIncoming) {
491
+ oNode._layoutData.layer = iProposedLayer;
492
+ }
478
493
  }
479
494
  }
480
495
  });