@sapui5/sap.suite.ui.commons 1.143.0 → 1.145.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) 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 +26 -14
  5. package/src/sap/suite/ui/commons/CalculationBuilderExpression.js +2 -2
  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/CalculationBuilderInput.js +5 -5
  9. package/src/sap/suite/ui/commons/CalculationBuilderItem.js +1 -1
  10. package/src/sap/suite/ui/commons/CalculationBuilderValidationResult.js +1 -1
  11. package/src/sap/suite/ui/commons/CalculationBuilderVariable.js +1 -1
  12. package/src/sap/suite/ui/commons/CloudFilePicker.js +1 -3
  13. package/src/sap/suite/ui/commons/MicroProcessFlow.js +77 -73
  14. package/src/sap/suite/ui/commons/MicroProcessFlowItem.js +3 -3
  15. package/src/sap/suite/ui/commons/Timeline.js +9 -0
  16. package/src/sap/suite/ui/commons/TimelineNavigator.js +37 -5
  17. package/src/sap/suite/ui/commons/TimelineRenderer.js +3 -3
  18. package/src/sap/suite/ui/commons/collaboration/BaseHelperService.js +0 -1
  19. package/src/sap/suite/ui/commons/collaboration/CollaborationHelper.js +0 -3
  20. package/src/sap/suite/ui/commons/collaboration/CollaborationManagerService.js +0 -1
  21. package/src/sap/suite/ui/commons/collaboration/ContactHelper.js +7 -0
  22. package/src/sap/suite/ui/commons/collaboration/ContactPopover.fragment.xml +4 -1
  23. package/src/sap/suite/ui/commons/collaboration/MinimalContactPopover.fragment.xml +2 -1
  24. package/src/sap/suite/ui/commons/collaboration/TeamsHelperService.js +0 -6
  25. package/src/sap/suite/ui/commons/collaboration/channels/MessageChannel.js +0 -2
  26. package/src/sap/suite/ui/commons/collaboration/flpplugins/msplugin/Component-preload.js +1 -1
  27. package/src/sap/suite/ui/commons/collaboration/flpplugins/msplugin/Component.js +1 -2
  28. package/src/sap/suite/ui/commons/flexibility/changeHandler/PropertyChangeMapper.js +1 -1
  29. package/src/sap/suite/ui/commons/imageeditor/CropCustomShapeHistoryItem.js +1 -1
  30. package/src/sap/suite/ui/commons/imageeditor/CropEllipseHistoryItem.js +1 -1
  31. package/src/sap/suite/ui/commons/imageeditor/CropRectangleHistoryItem.js +1 -1
  32. package/src/sap/suite/ui/commons/imageeditor/CustomSizeItem.js +1 -1
  33. package/src/sap/suite/ui/commons/imageeditor/FilterHistoryItem.js +1 -1
  34. package/src/sap/suite/ui/commons/imageeditor/FlipHistoryItem.js +1 -1
  35. package/src/sap/suite/ui/commons/imageeditor/HistoryItem.js +1 -1
  36. package/src/sap/suite/ui/commons/imageeditor/ImageEditor.js +1 -1
  37. package/src/sap/suite/ui/commons/imageeditor/ImageEditorContainer.js +1 -1
  38. package/src/sap/suite/ui/commons/imageeditor/ImageEditorResponsiveContainer.js +1 -1
  39. package/src/sap/suite/ui/commons/imageeditor/ResizeHistoryItem.js +1 -1
  40. package/src/sap/suite/ui/commons/imageeditor/RotateHistoryItem.js +1 -1
  41. package/src/sap/suite/ui/commons/library.js +100 -3
  42. package/src/sap/suite/ui/commons/messagebundle.properties +76 -10
  43. package/src/sap/suite/ui/commons/messagebundle_ar.properties +51 -5
  44. package/src/sap/suite/ui/commons/messagebundle_bg.properties +62 -16
  45. package/src/sap/suite/ui/commons/messagebundle_ca.properties +52 -6
  46. package/src/sap/suite/ui/commons/messagebundle_cnr.properties +52 -6
  47. package/src/sap/suite/ui/commons/messagebundle_cs.properties +51 -5
  48. package/src/sap/suite/ui/commons/messagebundle_cy.properties +51 -5
  49. package/src/sap/suite/ui/commons/messagebundle_da.properties +51 -5
  50. package/src/sap/suite/ui/commons/messagebundle_de.properties +52 -6
  51. package/src/sap/suite/ui/commons/messagebundle_el.properties +52 -6
  52. package/src/sap/suite/ui/commons/messagebundle_en.properties +50 -7
  53. package/src/sap/suite/ui/commons/messagebundle_en_GB.properties +52 -6
  54. package/src/sap/suite/ui/commons/messagebundle_en_US_saprigi.properties +54 -6
  55. package/src/sap/suite/ui/commons/messagebundle_es.properties +53 -7
  56. package/src/sap/suite/ui/commons/messagebundle_es_MX.properties +54 -8
  57. package/src/sap/suite/ui/commons/messagebundle_et.properties +51 -5
  58. package/src/sap/suite/ui/commons/messagebundle_fi.properties +51 -5
  59. package/src/sap/suite/ui/commons/messagebundle_fr.properties +52 -6
  60. package/src/sap/suite/ui/commons/messagebundle_fr_CA.properties +52 -6
  61. package/src/sap/suite/ui/commons/messagebundle_hi.properties +52 -6
  62. package/src/sap/suite/ui/commons/messagebundle_hr.properties +52 -6
  63. package/src/sap/suite/ui/commons/messagebundle_hu.properties +51 -5
  64. package/src/sap/suite/ui/commons/messagebundle_id.properties +60 -14
  65. package/src/sap/suite/ui/commons/messagebundle_it.properties +53 -7
  66. package/src/sap/suite/ui/commons/messagebundle_iw.properties +51 -5
  67. package/src/sap/suite/ui/commons/messagebundle_ja.properties +52 -6
  68. package/src/sap/suite/ui/commons/messagebundle_kk.properties +51 -5
  69. package/src/sap/suite/ui/commons/messagebundle_ko.properties +51 -5
  70. package/src/sap/suite/ui/commons/messagebundle_lt.properties +51 -5
  71. package/src/sap/suite/ui/commons/messagebundle_lv.properties +52 -6
  72. package/src/sap/suite/ui/commons/messagebundle_mk.properties +51 -5
  73. package/src/sap/suite/ui/commons/messagebundle_ms.properties +59 -13
  74. package/src/sap/suite/ui/commons/messagebundle_nl.properties +51 -5
  75. package/src/sap/suite/ui/commons/messagebundle_no.properties +52 -6
  76. package/src/sap/suite/ui/commons/messagebundle_pl.properties +51 -5
  77. package/src/sap/suite/ui/commons/messagebundle_pt.properties +52 -6
  78. package/src/sap/suite/ui/commons/messagebundle_pt_PT.properties +51 -5
  79. package/src/sap/suite/ui/commons/messagebundle_ro.properties +51 -5
  80. package/src/sap/suite/ui/commons/messagebundle_ru.properties +51 -5
  81. package/src/sap/suite/ui/commons/messagebundle_sh.properties +52 -6
  82. package/src/sap/suite/ui/commons/messagebundle_sk.properties +51 -5
  83. package/src/sap/suite/ui/commons/messagebundle_sl.properties +51 -5
  84. package/src/sap/suite/ui/commons/messagebundle_sr.properties +52 -6
  85. package/src/sap/suite/ui/commons/messagebundle_sv.properties +51 -5
  86. package/src/sap/suite/ui/commons/messagebundle_th.properties +52 -6
  87. package/src/sap/suite/ui/commons/messagebundle_tr.properties +51 -5
  88. package/src/sap/suite/ui/commons/messagebundle_uk.properties +53 -7
  89. package/src/sap/suite/ui/commons/messagebundle_vi.properties +55 -9
  90. package/src/sap/suite/ui/commons/messagebundle_zh_CN.properties +51 -5
  91. package/src/sap/suite/ui/commons/messagebundle_zh_TW.properties +53 -7
  92. package/src/sap/suite/ui/commons/networkgraph/ElementBase.js +19 -1
  93. package/src/sap/suite/ui/commons/networkgraph/Graph.js +371 -29
  94. package/src/sap/suite/ui/commons/networkgraph/GraphRenderer.js +23 -10
  95. package/src/sap/suite/ui/commons/networkgraph/Group.js +43 -22
  96. package/src/sap/suite/ui/commons/networkgraph/KeyboardNavigator.js +54 -6
  97. package/src/sap/suite/ui/commons/networkgraph/Line.js +736 -31
  98. package/src/sap/suite/ui/commons/networkgraph/Node.js +546 -96
  99. package/src/sap/suite/ui/commons/networkgraph/Tooltip.js +5 -0
  100. package/src/sap/suite/ui/commons/networkgraph/layout/NoopLayout.js +28 -5
  101. package/src/sap/suite/ui/commons/networkgraph/util/ConnectionPathUtils.js +1144 -0
  102. package/src/sap/suite/ui/commons/networkgraph/util/CreateConnectionPopover.js +374 -0
  103. package/src/sap/suite/ui/commons/networkgraph/util/DependencyLayoutHelper.js +870 -0
  104. package/src/sap/suite/ui/commons/networkgraph/util/DragDropManager.js +563 -41
  105. package/src/sap/suite/ui/commons/networkgraph/util/PortManager.js +573 -0
  106. package/src/sap/suite/ui/commons/statusindicator/Circle.js +1 -1
  107. package/src/sap/suite/ui/commons/statusindicator/CustomShape.js +1 -1
  108. package/src/sap/suite/ui/commons/statusindicator/DiscreteThreshold.js +1 -1
  109. package/src/sap/suite/ui/commons/statusindicator/FillingOption.js +1 -1
  110. package/src/sap/suite/ui/commons/statusindicator/LibraryShape.js +1 -1
  111. package/src/sap/suite/ui/commons/statusindicator/Path.js +1 -1
  112. package/src/sap/suite/ui/commons/statusindicator/PropertyThreshold.js +1 -1
  113. package/src/sap/suite/ui/commons/statusindicator/Rectangle.js +1 -1
  114. package/src/sap/suite/ui/commons/statusindicator/Shape.js +1 -1
  115. package/src/sap/suite/ui/commons/statusindicator/ShapeGroup.js +1 -1
  116. package/src/sap/suite/ui/commons/statusindicator/SimpleShape.js +1 -1
  117. package/src/sap/suite/ui/commons/statusindicator/StatusIndicator.js +1 -1
  118. package/src/sap/suite/ui/commons/taccount/TAccount.js +1 -1
  119. package/src/sap/suite/ui/commons/taccount/TAccountGroup.js +1 -1
  120. package/src/sap/suite/ui/commons/taccount/TAccountItem.js +1 -1
  121. package/src/sap/suite/ui/commons/taccount/TAccountItemProperty.js +1 -1
  122. package/src/sap/suite/ui/commons/taccount/TAccountPanel.js +1 -1
  123. package/src/sap/suite/ui/commons/themes/base/NetworkGraph.less +13 -13
  124. package/src/sap/suite/ui/commons/themes/base/NetworkGroup.less +34 -2
  125. package/src/sap/suite/ui/commons/themes/base/NetworkLine.less +58 -13
  126. package/src/sap/suite/ui/commons/themes/base/NetworkNode.less +206 -1
@@ -21,12 +21,17 @@ sap.ui.define([
21
21
  ArrowOrientation = library.networkgraph.LineArrowOrientation,
22
22
  Shape = library.networkgraph.NodeShape,
23
23
  Orientation = library.networkgraph.Orientation,
24
- SemanticColorType = library.SemanticColorType;
24
+ SemanticColorType = library.SemanticColorType,
25
+ ConnectionType = library.networkgraph.ConnectionType;
25
26
 
26
27
  var BEND_RADIUS = 6, // Bezier 'radius' of smooth bends
27
28
  FOCUS_LANE_WIDTH = 5, // Distance of focus shadow line from the main line
28
29
  RELATIVE_ARROW_POSITION = 0.45,
29
30
  FIXED_ARROW_POSITION = 15,
31
+ MULTIPLE_ARROW_DISTANCE = 44,
32
+ LABEL_BOX_WIDTH = 30,
33
+ LABEL_BOX_HEIGHT = 22,
34
+ ARROW_LENGTH = 11, // Length of arrow from apex to tail
30
35
  ZERO_ANGLE_ARROW_POINTS = {
31
36
  Apex: {x: 5.5, y: 0},
32
37
  Second: {x: -5.5, y: -7.5},
@@ -117,6 +122,34 @@ sap.ui.define([
117
122
  */
118
123
  stretchToCenter: {
119
124
  type: "boolean", group: "Misc", defaultValue: false
125
+ },
126
+ /**
127
+ * Defines the connection type between the source and target nodes.
128
+ * Can be set to RightToLeft, LeftToRight, TopToBottom, or BottomToTop using {@link sap.suite.ui.commons.networkgraph.ConnectionType ConnectionType} enumeration.
129
+ * This property is used to determine how the line connects the source and target nodes. For example, if set to RightToLeft, the line starts from the right side of the source node and end at the left side of the target node.
130
+ * This property is valid only with the NoopLayout and drag and drop enabled network graph.
131
+ * @public
132
+ * @since 1.144
133
+ *
134
+ */
135
+ connectionType: {
136
+ type: "sap.suite.ui.commons.networkgraph.ConnectionType", defaultValue: ConnectionType.RightToLeft
137
+ },
138
+ /**
139
+ * Defines the text label to be displayed on the line.
140
+ *
141
+ * When set, displays the label text in a rounded rectangle box positioned near the target arrow.
142
+ * The label is rendered only when all of the following conditions are met:
143
+ * - This property has a non-empty value
144
+ * - The graph is using NoopLayout
145
+ * - The line's arrowPosition is set to "Both"
146
+ * - The line is long enough to accommodate the label
147
+ * If this property is empty or not set, no label is displayed on the line
148
+ * @public
149
+ * @since 1.144.
150
+ */
151
+ labelName: {
152
+ type: "string", group: "Behavior", defaultValue: ""
120
153
  }
121
154
  },
122
155
  aggregations: {
@@ -192,7 +225,10 @@ sap.ui.define([
192
225
  /* =========================================================== */
193
226
  Line.prototype._afterRendering = function () {
194
227
  this._setupEvents();
195
- if (this.getFromNode()._bIsHidden || this.getToNode()._bIsHidden) {
228
+ // Only check node visibility if data is fully loaded
229
+ var oFromNode = this.getFromNode();
230
+ var oToNode = this.getToNode();
231
+ if (oFromNode && oToNode && (oFromNode._bIsHidden || oToNode._bIsHidden)) {
196
232
  this.$().hide();
197
233
  }
198
234
 
@@ -231,18 +267,20 @@ sap.ui.define([
231
267
  }.bind(this);
232
268
 
233
269
  var fnCreateArrowAttr = function (iIndex, aPoints, sArrowId) {
270
+ var sArrowClass = "sapSuiteUiCommonsNetworkLineArrow";
271
+ if (sColorClass) {
272
+ sArrowClass += " " + sColorClass;
273
+ }
234
274
  return {
235
275
  id: sId + "-" + sArrowId,
236
- "class": "sapSuiteUiCommonsNetworkLineArrow " + sColorClass,
276
+ "class": sArrowClass,
237
277
  style: sColorStyle,
238
278
  d: "M " + aPoints[iIndex + 0].x + "," + aPoints[iIndex + 0].y +
239
279
  " L " + aPoints[iIndex + 1].x + "," + aPoints[iIndex + 1].y +
240
280
  " L " + aPoints[iIndex + 2].x + "," + aPoints[iIndex + 2].y +
241
281
  " Z"
242
282
  };
243
- };
244
-
245
- var fnRenderArrow = function (sOrientation, sPosition, sArrowId) {
283
+ }; var fnRenderArrow = function (sOrientation, sPosition, sArrowId) {
246
284
  var aPoints = this._getArrowPoints(sOrientation, sPosition);
247
285
  this._renderControl("path", fnCreateArrowAttr(0, aPoints, sArrowId || "arrow"), null, oRm);
248
286
 
@@ -300,13 +338,57 @@ sap.ui.define([
300
338
  "data-sap-ui": sId
301
339
  }, false, oRm);
302
340
 
341
+ // Add title for tooltip showing connection type (always shown)
342
+ if (this.getParent()._isNoopLayout() &&
343
+ this.getArrowPosition() === ArrowPosition.Both) {
344
+
345
+ var sConnectionType = this.getConnectionType();
346
+ var oMapping = this.getParent().getConnectionTypeMapping();
347
+ var sTooltipText = "";
348
+
349
+ if (sConnectionType === ConnectionType.LeftToRight) {
350
+ sTooltipText = oMapping.LeftToRight || ConnectionType.LeftToRight;
351
+ } else if (sConnectionType === ConnectionType.RightToLeft) {
352
+ sTooltipText = oMapping.RightToLeft || ConnectionType.RightToLeft;
353
+ } else if (sConnectionType === ConnectionType.LeftToLeft) {
354
+ sTooltipText = oMapping.LeftToLeft || ConnectionType.LeftToLeft;
355
+ } else if (sConnectionType === ConnectionType.RightToRight) {
356
+ sTooltipText = oMapping.RightToRight || ConnectionType.RightToRight;
357
+ } else {
358
+ sTooltipText = oMapping.RightToLeft || ConnectionType.RightToLeft;
359
+ }
360
+
361
+ if (sTooltipText) {
362
+ oRm.openStart("title");
363
+ oRm.openEnd();
364
+ oRm.text(sTooltipText);
365
+ oRm.close("title");
366
+ }
367
+ }
368
+
303
369
  // invisible wrapper for better event handling
304
370
  fnRenderPath("sapSuiteUiCommonsNetworkLineInvisibleWrapper", "invisibleWrapper", true);
305
371
 
306
372
  // path itself
307
- fnRenderPath("", "path");
373
+ fnRenderPath("", "path");
308
374
 
309
- if (this.getArrowOrientation() !== ArrowOrientation.None && this.getCoordinates().length >= 2) {
375
+ if (this.getArrowPosition() === ArrowPosition.Both && this._hasMultipleDirectedArrows() && this.getCoordinates().length >= 2 ) {
376
+ // Calculate total line length to determine arrow placement strategy
377
+ var fTotalLineLength = this._calculateTotalLineLength();
378
+ var bCanFitTwoArrows = this._canLineFitTwoArrows(fTotalLineLength);
379
+
380
+ if (bCanFitTwoArrows) {
381
+ // Line is long enough for both arrows - use smart source arrow logic
382
+ if (this._shouldRenderSourceArrow()) {
383
+ fnRenderArrow(ArrowOrientation.ParentOf, "multipleSource", "arrow-source");
384
+ }
385
+ // Always render target arrow (each incoming line gets its own target arrow)
386
+ fnRenderArrow(ArrowOrientation.ParentOf, "multipleTarget", "arrow-target");
387
+ } else {
388
+ // Line is too short for two arrows - only render target arrow
389
+ fnRenderArrow(ArrowOrientation.ParentOf, "multipleTarget", "arrow-target");
390
+ }
391
+ } else if (this.getArrowOrientation() !== ArrowOrientation.None && this.getCoordinates().length >= 2) {
310
392
  if (this.getArrowOrientation() === ArrowOrientation.Both) {
311
393
  if (this.getArrowPosition() === ArrowPosition.Middle) {
312
394
  // middle arrow is rendered "at once" using two groups of points
@@ -325,18 +407,184 @@ sap.ui.define([
325
407
  var sColorStyleText = sColorStyle ? sColorStyle : "";
326
408
  this._aNipples.forEach(function (oNipple) {
327
409
  oRm.openStart("path");
328
- this.applyStyles(oRm,this.getStyleObject(sColorStyleText));
410
+ this.applyStyles(oRm, this.getStyleObject(sColorStyleText));
329
411
  oRm.class("sapSuiteUiCommonsNetworkLineNipple");
330
412
  ((sColorClass || "").split(" ") || []).forEach((sClass) => {
331
- oRm.class(sClass);
413
+ if (sClass) {
414
+ oRm.class(sClass);
415
+ }
332
416
  })
333
- oRm.attr("d",fnCreateArc(oNipple.x, oNipple.y, oNipple.orientation));
417
+ oRm.attr("d", fnCreateArc(oNipple.x, oNipple.y, oNipple.orientation));
334
418
  oRm.openEnd();
335
419
  oRm.close("path");
336
420
  }.bind(this));
421
+ } oRm.close("g");
422
+ };
423
+
424
+ /**
425
+ * Renders the text label for this line. Used by batch text rendering.
426
+ * @param {sap.ui.core.RenderManager} oRm The RenderManager to use for rendering
427
+ * @param {string} [sIdSuffix] Optional suffix for element ID
428
+ * @private
429
+ */
430
+ Line.prototype._renderLineText = function (oRm, sIdSuffix) {
431
+ // Early return if any condition is not met:
432
+ // - labelName has a non-empty value
433
+ // - Graph must be in NoopLayout
434
+ // - arrowPosition must be Both (labels only shown when both arrows are present)
435
+ if (!this.getLabelName() ||
436
+ !this.getParent()._isNoopLayout() ||
437
+ this.getArrowPosition() !== ArrowPosition.Both) {
438
+ return;
337
439
  }
338
440
 
339
- oRm.close("g");
441
+ // Always use the base line ID without any suffix to ensure consistency
442
+ // with _hideShowLineText which can't know what suffix was used during rendering
443
+ var sId = this.getId();
444
+ var {style: sStyle, class: sStatusClass} = this._getStatusStyle({
445
+ "stroke": ElementBase.ColorType.Border,
446
+ "stroke-width": ElementBase.ColorType.BorderWidth,
447
+ "stroke-dasharray": ElementBase.ColorType.BorderStyle
448
+ });
449
+
450
+ // Get the connection type and convert to display text
451
+ var sConnectionType = this.getConnectionType();
452
+ var oMapping = this.getParent().getConnectionTypeMapping();
453
+ var sFullText = ""; // Store the full text for tooltip
454
+
455
+ // Check if custom mapping exists for this connection type
456
+ switch (sConnectionType) {
457
+ case ConnectionType.LeftToRight:
458
+ sFullText = oMapping.LeftToRight || ConnectionType.LeftToRight;
459
+ break;
460
+ case ConnectionType.RightToLeft:
461
+ sFullText = oMapping.RightToLeft || ConnectionType.RightToLeft;
462
+ break;
463
+ case ConnectionType.LeftToLeft:
464
+ sFullText = oMapping.LeftToLeft || ConnectionType.LeftToLeft;
465
+ break;
466
+ case ConnectionType.RightToRight:
467
+ sFullText = oMapping.RightToRight || ConnectionType.RightToRight;
468
+ break;
469
+ default:
470
+ sFullText = oMapping.RightToLeft || ConnectionType.RightToLeft;
471
+ break;
472
+ }
473
+
474
+ // Calculate position near the ingoing arrow (end of the line)
475
+ // When ArrowPosition.Both, the target arrow is positioned at MULTIPLE_ARROW_DISTANCE (44px) from the end
476
+ var oCoords = this.getCoordinates();
477
+ if (oCoords && oCoords.length >= 2) {
478
+ var aFragmentLengthSum = [], fTotalDistance = 0;
479
+ var iLastIndex = oCoords.length - 1;
480
+
481
+ // Calculate cumulative distances along the path
482
+ for (var i = 0; i < iLastIndex; i++) {
483
+ if (oCoords[i].getX() === oCoords[i + 1].getX()) {
484
+ // Vertical line - only Y changes
485
+ fTotalDistance += Math.abs(oCoords[i + 1].getY() - oCoords[i].getY());
486
+ } else if (oCoords[i].getY() === oCoords[i + 1].getY()) {
487
+ // Horizontal line - only X changes
488
+ fTotalDistance += Math.abs(oCoords[i + 1].getX() - oCoords[i].getX());
489
+ } else {
490
+ // Diagonal line - use Pythagorean theorem If Lines are plotted Diagonally
491
+ fTotalDistance += Math.sqrt(
492
+ Math.pow(oCoords[i + 1].getX() - oCoords[i].getX(), 2) +
493
+ Math.pow(oCoords[i + 1].getY() - oCoords[i].getY(), 2)
494
+ );
495
+ }
496
+ // Store cumulative distance
497
+ aFragmentLengthSum.push(fTotalDistance);
498
+ }
499
+
500
+ // Check if there's enough space for the label box with 8px padding from target arrow
501
+ // When line is long enough for both arrows, check gap between them
502
+ // When line is short, source arrow is hidden and target arrow position is adjusted
503
+ var bCanFitTwoArrows = this._canLineFitTwoArrows(fTotalDistance);
504
+ var fTargetArrowDistance;
505
+ var fAvailableSpace;
506
+
507
+ if (bCanFitTwoArrows) {
508
+ // Both arrows present at fixed positions
509
+ fTargetArrowDistance = MULTIPLE_ARROW_DISTANCE;
510
+ // Available space = total - (source position + source arrow + target position + target arrow + 8px padding)
511
+ fAvailableSpace = fTotalDistance - (MULTIPLE_ARROW_DISTANCE + ARROW_LENGTH + MULTIPLE_ARROW_DISTANCE + ARROW_LENGTH + 8);
512
+ } else {
513
+ // Only target arrow present
514
+ fTargetArrowDistance = Math.min(MULTIPLE_ARROW_DISTANCE, fTotalDistance * 0.3);
515
+ // Available space = total - (target position + target arrow + 8px padding)
516
+ fAvailableSpace = fTotalDistance - (fTargetArrowDistance + ARROW_LENGTH + 8);
517
+ }
518
+
519
+ var fRequiredSpace = LABEL_BOX_WIDTH; // 30px box width
520
+ if (fAvailableSpace < fRequiredSpace) {
521
+ return;
522
+ }
523
+
524
+ // Position label so its end is 8px away from the target arrow
525
+ var fTargetDistance = fTotalDistance - fTargetArrowDistance - 8 - LABEL_BOX_WIDTH; // arrow position + 8px offset + full box width
526
+ var iTargetIndex = 0;
527
+ for (var j = 0; j < iLastIndex; j++) {
528
+ if (aFragmentLengthSum[j] >= fTargetDistance) {
529
+ iTargetIndex = j;
530
+ break;
531
+ }
532
+ }
533
+
534
+ // Calculate the exact position on the segment
535
+ var fSegmentStart = iTargetIndex > 0 ? aFragmentLengthSum[iTargetIndex - 1] : 0;
536
+ var fSegmentLength = aFragmentLengthSum[iTargetIndex] - fSegmentStart;
537
+ var fPositionInSegment = (fTargetDistance - fSegmentStart) / fSegmentLength;
538
+ var fLabelX = oCoords[iTargetIndex].getX() +
539
+ (oCoords[iTargetIndex + 1].getX() - oCoords[iTargetIndex].getX()) * fPositionInSegment;
540
+ var fLabelY = oCoords[iTargetIndex].getY() +
541
+ (oCoords[iTargetIndex + 1].getY() - oCoords[iTargetIndex].getY()) * fPositionInSegment;
542
+
543
+ // Calculate box position (centered on label coordinates)
544
+ var fBoxX = fLabelX - LABEL_BOX_WIDTH / 2;
545
+ var fBoxY = fLabelY - LABEL_BOX_HEIGHT / 2;
546
+
547
+ // Create a group to contain both rectangle and text
548
+ oRm.openStart("g");
549
+ oRm.class("sapSuiteUiCommonsNetworkLineTextBox");
550
+ oRm.attr("id", sId + "-textbox-middle");
551
+ oRm.attr("data-line-id", this._getLineId());
552
+ oRm.openEnd();
553
+
554
+ // Add title for tooltip on the label box group
555
+ oRm.openStart("title");
556
+ oRm.openEnd();
557
+ oRm.text(sFullText);
558
+ oRm.close("title");
559
+
560
+ // Render the rectangle background
561
+ oRm.openStart("rect");
562
+ oRm.class("sapSuiteUiCommonsNetworkLineTextBoxBackground");
563
+ if (sStatusClass) {
564
+ oRm.class(sStatusClass);
565
+ }
566
+ //this.applyStyles(oRm, this.getStyleObject(sStyle)); //ToDo: to be applied if Box should also adhere to status styles
567
+ oRm.attr("x", fBoxX);
568
+ oRm.attr("y", fBoxY);
569
+ oRm.attr("width", LABEL_BOX_WIDTH);
570
+ oRm.attr("height", LABEL_BOX_HEIGHT);
571
+ oRm.attr("rx", 8); // Rounded corners
572
+ oRm.attr("ry", 8);
573
+ oRm.openEnd();
574
+ oRm.close("rect");
575
+
576
+ // Render the text inside the rectangle
577
+ oRm.openStart("text");
578
+ oRm.class("sapSuiteUiCommonsNetworkLineText");
579
+ oRm.attr("x", fLabelX);
580
+ oRm.attr("y", fLabelY); // Center vertically in the box
581
+ oRm.openEnd();
582
+ oRm.text(this.getLabelName());
583
+ oRm.close("text");
584
+
585
+ // Close the group
586
+ oRm.close("g");
587
+ }
340
588
  };
341
589
 
342
590
  Line.prototype._renderFocusWrapper = function () {
@@ -346,7 +594,19 @@ sap.ui.define([
346
594
  "class": "sapSuiteUiCommonsNetworkLineFocus"
347
595
  });
348
596
 
349
- this.$()[0].appendChild(oPath);
597
+ // Find the first text box group to insert before it
598
+ var oContainer = this.getDomRef();
599
+ var oFirstTextBox = oContainer ? oContainer.querySelector(".sapSuiteUiCommonsNetworkLineTextBox") : null;
600
+
601
+ if (oFirstTextBox) {
602
+ // Insert the focus line before the first text box group
603
+ oContainer.insertBefore(oPath, oFirstTextBox);
604
+ } else {
605
+ // Fallback: append to end if no text boxes found
606
+ if (oContainer) {
607
+ oContainer.appendChild(oPath);
608
+ }
609
+ }
350
610
  }.bind(this);
351
611
 
352
612
  if (!this._bFocusRendered) {
@@ -433,6 +693,33 @@ sap.ui.define([
433
693
 
434
694
  sPosition = sPosition || this.getArrowPosition();
435
695
 
696
+ // Handle multiple directed arrows positioning
697
+ if (sPosition === "multipleSource" || sPosition === "multipleTarget") {
698
+ // Check if line is long enough for the requested arrow position
699
+ var fTotalLineLength = this._calculateTotalLineLength();
700
+ var bCanFitTwoArrows = this._canLineFitTwoArrows(fTotalLineLength);
701
+
702
+ // If line is too short and we're trying to render source arrow, skip it
703
+ if (!bCanFitTwoArrows && sPosition === "multipleSource") {
704
+ // Return default fallback to avoid errors
705
+ return {
706
+ center: {x: oCoords[0].getX(), y: oCoords[0].getY()},
707
+ apex: {x: oCoords[1].getX(), y: oCoords[1].getY()}
708
+ };
709
+ }
710
+ // Find optimal segment by traveling 44px along the line path
711
+ var sDirection = sPosition === "multipleSource" ? "forward" : "backward";
712
+ var fDistance = bCanFitTwoArrows ? MULTIPLE_ARROW_DISTANCE : Math.min(MULTIPLE_ARROW_DISTANCE, fTotalLineLength * 0.3);
713
+ var oSegmentInfo = this._findOptimalArrowSegment(sDirection, fDistance);
714
+
715
+ return {
716
+ center: oSegmentInfo.center,
717
+ apex: oSegmentInfo.apex,
718
+ segmentRatio: oSegmentInfo.ratio,
719
+ isMultipleDirected: true
720
+ };
721
+ }
722
+
436
723
  if (this.getBends().length === 0) {
437
724
  iHolyIndex = 0;
438
725
  } else if (sPosition === ArrowPosition.Start) {
@@ -529,8 +816,14 @@ sap.ui.define([
529
816
 
530
817
  var fnCalcArrowPoint = function (oArrowVertex) {
531
818
  var fFixedPosition = FIXED_ARROW_POSITION;
532
- // First calculate where the center of the arrow is
533
- if (sPosition === ArrowPosition.Middle) {
819
+ // Handle multiple directed arrows with precise positioning along segments
820
+ if (sPosition === "multipleSource" || sPosition === "multipleTarget") {
821
+ // Use the exact position calculated to be precisely 44px away
822
+ oArrowCenter = oFragVector.exactPosition || {
823
+ x: oFragVector.center.x + (oFragVector.apex.x - oFragVector.center.x) * (oFragVector.segmentRatio || 0.5),
824
+ y: oFragVector.center.y + (oFragVector.apex.y - oFragVector.center.y) * (oFragVector.segmentRatio || 0.5)
825
+ };
826
+ } else if (sPosition === ArrowPosition.Middle) { // First calculate where the center of the arrow is
534
827
  oArrowCenter = {
535
828
  x: (oFragVector.apex.x - oFragVector.center.x) * RELATIVE_ARROW_POSITION + oFragVector.center.x,
536
829
  y: (oFragVector.apex.y - oFragVector.center.y) * RELATIVE_ARROW_POSITION + oFragVector.center.y
@@ -584,19 +877,43 @@ sap.ui.define([
584
877
  /**
585
878
  * @private
586
879
  */
587
- Line.prototype._getAccessibilityLabel = function () {
588
- var sFromNodeTitle = this.getFromNode().getTitle();
589
- var sFromNodeText = sFromNodeTitle ? sFromNodeTitle : this.getFromNode().getAltText();
880
+ Line.prototype._getAccessibilityLabel = function (oGraph) {
881
+ const aSentenceParts = [];
882
+ const sConnectionType = this.getConnectionType();
883
+ const sConnectionTypeText = oGraph.getConnectionTypeMapping()[sConnectionType];
884
+ const sFromNodeTitle = this.getFromNode().getTitle();
885
+ const sFromNodeText = sFromNodeTitle ? sFromNodeTitle : this.getFromNode().getAltText();
886
+ const sToNodeTitle = this.getToNode().getTitle();
887
+ const sToNodeText = sToNodeTitle ? sToNodeTitle : this.getToNode().getAltText();
888
+ let sConnectionText = oResourceBundle.getText('NETWORK_GRAPH_LINE_ACCESSIBILITY_CONNECTION_FROM_TO', [sFromNodeText, sToNodeText]);
889
+ if (sConnectionTypeText) {
890
+ sConnectionText = `${sConnectionTypeText} ${sConnectionText.charAt(0).toLowerCase()}${sConnectionText.slice(1)}`;
891
+ }
892
+ aSentenceParts.push(sConnectionText);
590
893
 
591
- var sToNodeTitle = this.getToNode().getTitle();
592
- var sToNodeText = sToNodeTitle ? sToNodeTitle : this.getToNode().getAltText();
593
- var sLineState = this.getSelected() ? oResourceBundle.getText("NETWORK_GRAPH_SELECTED_NODE") : "";
594
- var sAccLabel = oResourceBundle.getText("NETWORK_GRAPH_ACCESSIBILITY_LINE_LABEL", [sFromNodeText, sToNodeText]) + " " + this.getTitle();
595
- sAccLabel = sAccLabel.trim();
596
- if(sLineState) {
597
- sAccLabel += " " + sLineState;
894
+ const sStatusText = this._getStatusText(oGraph._oStatuses);
895
+
896
+ if (sStatusText) {
897
+ aSentenceParts.push(
898
+ `${oResourceBundle.getText('NETWORK_GRAPH_LINE_ACCESSIBILITY_STATUS')} ${sStatusText}`
899
+ );
900
+ }
901
+
902
+ if (this.getSelected()) {
903
+ aSentenceParts.push(oResourceBundle.getText('NETWORK_GRAPH_SELECTED_NODE'));
598
904
  }
599
- return sAccLabel + "." + oResourceBundle.getText("NETWORK_GRAPH_ACCESSIBILITY_TOGGLE_STATE");
905
+
906
+ aSentenceParts.push(oResourceBundle.getText('NETWORK_GRAPH_ACCESSIBILITY_TOGGLE_STATE'));
907
+
908
+ const iIncomingConnectors = this.getFromNode().getChildLines().length;
909
+ if (iIncomingConnectors > 1) {
910
+ aSentenceParts.push(oResourceBundle.getText('NETWORK_GRAPH_LINE_NAVIGATION_ARROW_KEYS', [sFromNodeText]));
911
+ }
912
+
913
+ aSentenceParts.push(oResourceBundle.getText('NETWORK_GRAPH_LINE_NAVIGATION_TAB_TO_TARGET', [sToNodeText]));
914
+ aSentenceParts.push(oResourceBundle.getText('NETWORK_GRAPH_LINE_NAVIGATION_SHIFT_TAB_TO_SOURCE', [sFromNodeText]));
915
+
916
+ return aSentenceParts.join('. ');
600
917
  };
601
918
 
602
919
  /* =========================================================== */
@@ -794,10 +1111,39 @@ sap.ui.define([
794
1111
  $line.on("mouseout", function (oEvent) {
795
1112
  this._mouseOut();
796
1113
  }.bind(this));
1114
+
1115
+ // Setup events for text box
1116
+ var oParent = this.getParent();
1117
+ if (oParent) {
1118
+ var sLineId = this._getLineId();
1119
+ var $textBox = oParent.$("line-texts").find('[data-line-id="' + sLineId + '"]');
1120
+
1121
+ $textBox.on("click", function (oEvent) {
1122
+ this._click({
1123
+ ctrlKey: oEvent.ctrlKey,
1124
+ clientX: oEvent.clientX,
1125
+ clientY: oEvent.clientY
1126
+ });
1127
+ }.bind(this));
1128
+
1129
+ $textBox.on("mouseover", function (oEvent) {
1130
+ this._mouseOver();
1131
+ }.bind(this));
1132
+
1133
+ $textBox.on("mouseout", function (oEvent) {
1134
+ this._mouseOut();
1135
+ }.bind(this));
1136
+ }
797
1137
  };
798
1138
 
799
1139
  Line.prototype._mouseOut = function () {
800
1140
  this.$().removeClass(this.HIGHLIGHT_CLASS);
1141
+ // Remove highlight class from text box using data-line-id attribute
1142
+ var oParent = this.getParent();
1143
+ if (oParent) {
1144
+ var sLineId = this._getLineId();
1145
+ oParent.$("line-texts").find('[data-line-id="' + sLineId + '"]').removeClass(this.HIGHLIGHT_CLASS);
1146
+ }
801
1147
  if (!this.getSelected()) {
802
1148
  this._setStatusColors("");
803
1149
  }
@@ -809,6 +1155,12 @@ sap.ui.define([
809
1155
  if (!this.getSelected() && bExecuteDefault) {
810
1156
  this._setStatusColors("Hover");
811
1157
  this.$().addClass(this.HIGHLIGHT_CLASS);
1158
+ // Add highlight class to text box using data-line-id attribute
1159
+ var oParent = this.getParent();
1160
+ if (oParent) {
1161
+ var sLineId = this._getLineId();
1162
+ oParent.$("line-texts").find('[data-line-id="' + sLineId + '"]').addClass(this.HIGHLIGHT_CLASS);
1163
+ }
812
1164
  }
813
1165
  };
814
1166
 
@@ -910,10 +1262,37 @@ sap.ui.define([
910
1262
 
911
1263
  oParent._aShadedNodes = [];
912
1264
 
913
- var sFromNodeTitle = oFrom.getTitle();
1265
+ var sFromNodeTitle = oFrom.getTitle();
914
1266
  var sFromNodeText = sFromNodeTitle ? sFromNodeTitle : oFrom.getAltText();
915
1267
 
916
- var sTitle = "<span class=\"sapSuiteUiCommonsNetworkGraphLineTooltipText\">" + sFromNodeText + "</span>";
1268
+ // Add connection type information (always shown in tooltip)
1269
+ var sConnectionTypeText = "";
1270
+ var sTitle = "";
1271
+
1272
+ if (this.getParent()._isNoopLayout() &&
1273
+ this.getArrowPosition() === ArrowPosition.Both) {
1274
+
1275
+ var sConnectionType = this.getConnectionType();
1276
+ var oMapping = this.getParent().getConnectionTypeMapping();
1277
+
1278
+ if (sConnectionType === ConnectionType.LeftToRight) {
1279
+ sConnectionTypeText = oMapping.LeftToRight || ConnectionType.LeftToRight;
1280
+ } else if (sConnectionType === ConnectionType.RightToLeft) {
1281
+ sConnectionTypeText = oMapping.RightToLeft || ConnectionType.RightToLeft;
1282
+ } else if (sConnectionType === ConnectionType.LeftToLeft) {
1283
+ sConnectionTypeText = oMapping.LeftToLeft || ConnectionType.LeftToLeft;
1284
+ } else if (sConnectionType === ConnectionType.RightToRight) {
1285
+ sConnectionTypeText = oMapping.RightToRight || ConnectionType.RightToRight;
1286
+ } else {
1287
+ sConnectionTypeText = oMapping.RightToLeft || ConnectionType.RightToLeft;
1288
+ }
1289
+
1290
+ if (sConnectionTypeText) {
1291
+ sTitle += "<span class=\"sapSuiteUiCommonsNetworkGraphLineTooltipText\">" + sConnectionTypeText + "</span><br/>";
1292
+ }
1293
+ }
1294
+
1295
+ sTitle += "<span class=\"sapSuiteUiCommonsNetworkGraphLineTooltipText\">" + sFromNodeText + "</span>";
917
1296
 
918
1297
  if (this._isBothArrow()) {
919
1298
  sTitle += "<span class=\"sapSuiteUiCommonsNetworkGraphLineTooltipArrow sapSuiteUiCommonsNetworkGraphLineTooltipDualArrow\"></span>"
@@ -926,7 +1305,7 @@ sap.ui.define([
926
1305
  sTitle += "<span class=\"sapSuiteUiCommonsNetworkGraphLineTooltipArrow\"></span>" + "</br>";
927
1306
  }
928
1307
 
929
- var sToNodeTitle = oTo.getTitle();
1308
+ var sToNodeTitle = oTo.getTitle();
930
1309
  var sToNodeText = sToNodeTitle ? sToNodeTitle : oTo.getAltText();
931
1310
 
932
1311
  sTitle += "<span class=\"sapSuiteUiCommonsNetworkGraphLineTooltipText\">" + sToNodeText + "</span>";
@@ -1095,6 +1474,18 @@ sap.ui.define([
1095
1474
  if (bFocus) {
1096
1475
  this._renderFocusWrapper();
1097
1476
  }
1477
+
1478
+ // Apply focus class to text box for blue border
1479
+ var oParent = this.getParent();
1480
+ if (oParent) {
1481
+ var sLineId = this._getLineId();
1482
+ var $textBox = oParent.$("line-texts").find('[data-line-id="' + sLineId + '"]');
1483
+ if (bFocus) {
1484
+ $textBox.addClass(this.FOCUS_CLASS);
1485
+ } else {
1486
+ $textBox.removeClass(this.FOCUS_CLASS);
1487
+ }
1488
+ }
1098
1489
  };
1099
1490
 
1100
1491
  Line.prototype._isEndPosition = function () {
@@ -1111,9 +1502,29 @@ sap.ui.define([
1111
1502
  if (bCollapse) {
1112
1503
  this.$().hide();
1113
1504
  this._bIsHidden = true;
1505
+ // Also hide the line text label if it exists
1506
+ this._hideShowLineText(true);
1114
1507
  } else if (!this.getToNode()._bIsHidden && !this.getFromNode()._bIsHidden) {
1115
1508
  this.$().show();
1116
1509
  this._bIsHidden = false;
1510
+ // Also show the line text label if it exists
1511
+ this._hideShowLineText(false);
1512
+ }
1513
+ };
1514
+
1515
+ /**
1516
+ * Hides or shows the line text label element
1517
+ * @param {boolean} bHide True to hide, false to show
1518
+ * @private
1519
+ */
1520
+ Line.prototype._hideShowLineText = function (bHide) {
1521
+ // Use the same ID pattern that was set during _renderLineText
1522
+ // The text box ID is: this.getId() + "-textbox-middle"
1523
+ var sTextBoxId = this.getId() + "-textbox-middle";
1524
+ var oTextBox = document.getElementById(sTextBoxId);
1525
+
1526
+ if (oTextBox) {
1527
+ oTextBox.style.display = bHide ? "none" : "";
1117
1528
  }
1118
1529
  };
1119
1530
 
@@ -1223,10 +1634,10 @@ sap.ui.define([
1223
1634
  var LINE_TITLE_LENGTH = 25;
1224
1635
  var sTitle = this.getTitle() ? (this.getTitle() + " ") : "";
1225
1636
 
1226
- var sFromNodeTitle = this.getFromNode().getTitle();
1637
+ var sFromNodeTitle = this.getFromNode().getTitle();
1227
1638
  var sFromNodeText = sFromNodeTitle ? sFromNodeTitle : this.getFromNode().getAltText();
1228
1639
 
1229
- var sToNodeTitle = this.getToNode().getTitle();
1640
+ var sToNodeTitle = this.getToNode().getTitle();
1230
1641
  var sToNodeText = sToNodeTitle ? sToNodeTitle : this.getToNode().getAltText();
1231
1642
 
1232
1643
  return sTitle + "(" + Utils.trimText(sFromNodeText, LINE_TITLE_LENGTH) + " -> "
@@ -1261,5 +1672,299 @@ sap.ui.define([
1261
1672
  return false;
1262
1673
  };
1263
1674
 
1675
+ /**
1676
+ * Calculates the total length of the line by summing up all the segment distances.
1677
+ * @returns {number} Total line length in pixels
1678
+ * @private
1679
+ */
1680
+ Line.prototype._calculateTotalLineLength = function () {
1681
+ var oCoords = this.getCoordinates();
1682
+ var fTotalLength = 0;
1683
+
1684
+ if (!oCoords || oCoords.length < 2) {
1685
+ return 0;
1686
+ }
1687
+
1688
+ // Sum the distance of each line segment
1689
+ for (var i = 0; i < oCoords.length - 1; i++) {
1690
+ var fSegmentLength = Math.sqrt(
1691
+ Math.pow(oCoords[i + 1].getX() - oCoords[i].getX(), 2) +
1692
+ Math.pow(oCoords[i + 1].getY() - oCoords[i].getY(), 2)
1693
+ );
1694
+ fTotalLength += fSegmentLength;
1695
+ }
1696
+
1697
+ return fTotalLength;
1698
+ };
1699
+
1700
+ /**
1701
+ * Determines if a line is long enough to accommodate two arrows at 44px from each anchor
1702
+ * @param {number} fLineLength - Total length of the line
1703
+ * @returns {boolean} True if line can fit two arrows with proper spacing
1704
+ * @private
1705
+ */
1706
+ Line.prototype._canLineFitTwoArrows = function (fLineLength) {
1707
+ // Minimum line length calculation:
1708
+ // - 44px from source anchor
1709
+ // - 44px from target anchor
1710
+ // - 20px minimum spacing between arrows
1711
+ // Total: 108px minimum
1712
+ var fMinLineLength = (MULTIPLE_ARROW_DISTANCE * 2) + 20;
1713
+
1714
+ // Add some buffer for curved/bent lines where the actual path might be longer
1715
+ // but the arrow positions could still overlap
1716
+ var fSafeLineLength = fMinLineLength + 10; // 118px total
1717
+
1718
+ return fLineLength >= fSafeLineLength;
1719
+ };
1720
+
1721
+ Line.prototype._shouldRenderSourceArrow = function () {
1722
+ if (!this._hasMultipleDirectedArrows()) {
1723
+ return false;
1724
+ }
1725
+
1726
+ var oSourceNode = this.getFromNode();
1727
+ if (!oSourceNode) {
1728
+ return true; // Fallback: show arrow if source node not found
1729
+ }
1730
+
1731
+ var aOutgoingLines = oSourceNode.getChildLines();
1732
+
1733
+ // If source node has only one outgoing line, show the arrow
1734
+ if (!aOutgoingLines || aOutgoingLines.length <= 1) {
1735
+ return true;
1736
+ }
1737
+
1738
+ // Filter valid outgoing lines first
1739
+ var aValidOutgoingLines = aOutgoingLines.filter(function(oLine) {
1740
+ return oLine && oLine.getVisible() && !oLine._isIgnored();
1741
+ });
1742
+
1743
+ if (aValidOutgoingLines.length <= 1) {
1744
+ return true;
1745
+ }
1746
+
1747
+ // Group outgoing lines by their direction/side from the source node
1748
+ var oLinesByDirection = this._groupLinesByDirection(aValidOutgoingLines, oSourceNode, "outgoing");
1749
+
1750
+ // Get the direction of this line
1751
+ var sThisLineDirection = this._getLineDirection(this, oSourceNode, "outgoing");
1752
+
1753
+ // Get lines going in the same direction as this line
1754
+ var aLinesInSameDirection = oLinesByDirection[sThisLineDirection] || [];
1755
+
1756
+ if (aLinesInSameDirection.length <= 1) {
1757
+ return true;
1758
+ }
1759
+
1760
+ // Sort lines by their ID to ensure consistent ordering
1761
+ aLinesInSameDirection.sort(function(a, b) {
1762
+ return a._getLineId().localeCompare(b._getLineId());
1763
+ });
1764
+
1765
+ // Show arrow only on the first line in the sorted list for this direction
1766
+ return aLinesInSameDirection[0] === this;
1767
+ };
1768
+
1769
+ /**
1770
+ * Checks if multiple directed arrows feature is enabled at the graph level
1771
+ * @returns {boolean} True if the feature is enabled
1772
+ * @private
1773
+ */
1774
+ Line.prototype._hasMultipleDirectedArrows = function () {
1775
+ var oParent = this.getParent();
1776
+ return oParent && oParent._enableMultipleDirectedArrows === true;
1777
+ };
1778
+
1779
+ /**
1780
+ * Groups lines by their direction relative to a node
1781
+ * @param {array} aLines - Array of lines to group
1782
+ * @param {object} oNode - The reference node
1783
+ * @param {string} sType - "outgoing" or "incoming"
1784
+ * @returns {object} Object with direction as key and array of lines as value
1785
+ * @private
1786
+ */
1787
+ Line.prototype._groupLinesByDirection = function (aLines, oNode, sType) {
1788
+ var oGroupedLines = {
1789
+ "right": [],
1790
+ "left": [],
1791
+ "up": [],
1792
+ "down": []
1793
+ };
1794
+
1795
+ aLines.forEach(function(oLine) {
1796
+ var sDirection = this._getLineDirection(oLine, oNode, sType);
1797
+ if (oGroupedLines[sDirection]) {
1798
+ oGroupedLines[sDirection].push(oLine);
1799
+ }
1800
+ }.bind(this));
1801
+
1802
+ return oGroupedLines;
1803
+ };
1804
+
1805
+ /**
1806
+ * Determines the direction of a line relative to a node based on connection anchors
1807
+ * @param {object} oLine - The line object
1808
+ * @param {object} oNode - The reference node
1809
+ * @param {string} sType - "outgoing" or "incoming"
1810
+ * @returns {string} Direction: "right", "left", "up", or "down"
1811
+ * @private
1812
+ */
1813
+ Line.prototype._getLineDirection = function (oLine, oNode, sType) {
1814
+ // Validate inputs
1815
+ if (!oLine || !oNode) {
1816
+ return "right"; // Default fallback
1817
+ }
1818
+
1819
+ var oOtherNode = sType === "outgoing" ? oLine.getToNode() : oLine.getFromNode();
1820
+ if (!oOtherNode) {
1821
+ return "right"; // Default fallback
1822
+ }
1823
+
1824
+ // Use the actual line coordinates (source/target anchors) instead of node centers
1825
+ var oSourceCoords = oLine.getSource();
1826
+ var oTargetCoords = oLine.getTarget();
1827
+ var oNodeCenter = oNode.getCenterPosition();
1828
+
1829
+ // Validate coordinates
1830
+ if (!oSourceCoords || !oTargetCoords || !oNodeCenter ||
1831
+ typeof oSourceCoords.getX !== 'function' || typeof oSourceCoords.getY !== 'function' ||
1832
+ typeof oTargetCoords.getX !== 'function' || typeof oTargetCoords.getY !== 'function' ||
1833
+ typeof oNodeCenter.x !== 'number' || typeof oNodeCenter.y !== 'number') {
1834
+ return "right"; // Default fallback
1835
+ }
1836
+
1837
+ // Get the anchor point coordinates
1838
+ var fSourceX = oSourceCoords.getX();
1839
+ var fSourceY = oSourceCoords.getY();
1840
+ var fTargetX = oTargetCoords.getX();
1841
+ var fTargetY = oTargetCoords.getY();
1842
+
1843
+ // Determine which anchor point belongs to our reference node
1844
+ var fAnchorX, fAnchorY;
1845
+ if (sType === "outgoing") {
1846
+ // For outgoing lines, use the source anchor (where line starts from our node)
1847
+ fAnchorX = fSourceX;
1848
+ fAnchorY = fSourceY;
1849
+ } else {
1850
+ // For incoming lines, use the target anchor (where line ends at our node)
1851
+ fAnchorX = fTargetX;
1852
+ fAnchorY = fTargetY;
1853
+ }
1854
+
1855
+ // Calculate the direction based on anchor position relative to node center
1856
+ var fDeltaX = fAnchorX - oNodeCenter.x;
1857
+ var fDeltaY = fAnchorY - oNodeCenter.y;
1858
+
1859
+ // Use threshold to handle edge cases
1860
+ var fThreshold = 5.0; // 5px threshold for anchor-based detection
1861
+ var fAbsDeltaX = Math.abs(fDeltaX);
1862
+ var fAbsDeltaY = Math.abs(fDeltaY);
1863
+
1864
+ // If anchor is very close to center, fall back to target direction
1865
+ if (fAbsDeltaX < fThreshold && fAbsDeltaY < fThreshold) {
1866
+ // Use direction to other node as fallback
1867
+ var oOtherCenter = oOtherNode.getCenterPosition();
1868
+ if (oOtherCenter) {
1869
+ fDeltaX = oOtherCenter.x - oNodeCenter.x;
1870
+ fDeltaY = oOtherCenter.y - oNodeCenter.y;
1871
+ fAbsDeltaX = Math.abs(fDeltaX);
1872
+ fAbsDeltaY = Math.abs(fDeltaY);
1873
+ } else {
1874
+ return "right"; // Final fallback
1875
+ }
1876
+ }
1877
+
1878
+ // Determine direction based on larger delta
1879
+ if (fAbsDeltaX > fAbsDeltaY + fThreshold) {
1880
+ return fDeltaX > 0 ? "right" : "left";
1881
+ } else if (fAbsDeltaY > fAbsDeltaX + fThreshold) {
1882
+ return fDeltaY > 0 ? "down" : "up";
1883
+ } else {
1884
+ // When deltas are nearly equal, prefer horizontal direction for consistency
1885
+ return fDeltaX >= 0 ? "right" : "left";
1886
+ }
1887
+ };
1888
+
1889
+ /**
1890
+ * Finds the optimal segment for placing arrows on bent lines by traveling along the path
1891
+ * @param {string} sDirection - "forward" from source or "backward" from target
1892
+ * @param {number} fDistance - Distance to travel along the line (e.g., 44px)
1893
+ * @returns {object} Segment information with exact position 44px away from anchor
1894
+ * @private
1895
+ */
1896
+ Line.prototype._findOptimalArrowSegment = function (sDirection, fDistance) {
1897
+ var oCoords = this.getCoordinates(),
1898
+ iLastIndex = oCoords.length - 1,
1899
+ fTotalDistance = 0,
1900
+ fSegmentDistance,
1901
+ iSegmentIndex,
1902
+ fRemainingDistance,
1903
+ i;
1904
+
1905
+ if (sDirection === "forward") {
1906
+ // Travel from source towards target
1907
+ for (i = 0; i < iLastIndex; i++) {
1908
+ fSegmentDistance = Math.sqrt(
1909
+ Math.pow(oCoords[i + 1].getX() - oCoords[i].getX(), 2) +
1910
+ Math.pow(oCoords[i + 1].getY() - oCoords[i].getY(), 2)
1911
+ );
1912
+
1913
+ if (fTotalDistance + fSegmentDistance >= fDistance) {
1914
+ // Found the segment where 44px distance falls
1915
+ fRemainingDistance = fDistance - fTotalDistance;
1916
+ iSegmentIndex = i;
1917
+ break;
1918
+ }
1919
+ fTotalDistance += fSegmentDistance;
1920
+ }
1921
+ } else {
1922
+ // Travel from target towards source
1923
+ for (i = iLastIndex; i > 0; i--) {
1924
+ fSegmentDistance = Math.sqrt(
1925
+ Math.pow(oCoords[i].getX() - oCoords[i - 1].getX(), 2) +
1926
+ Math.pow(oCoords[i].getY() - oCoords[i - 1].getY(), 2)
1927
+ );
1928
+
1929
+ if (fTotalDistance + fSegmentDistance >= fDistance) {
1930
+ // Found the segment where 44px distance falls
1931
+ fRemainingDistance = fDistance - fTotalDistance;
1932
+ iSegmentIndex = i - 1; // Use the segment index format (i to i+1)
1933
+ break;
1934
+ }
1935
+ fTotalDistance += fSegmentDistance;
1936
+ }
1937
+ }
1938
+
1939
+ // Fallback to first/last segment if distance is too large
1940
+ if (typeof iSegmentIndex === "undefined") {
1941
+ iSegmentIndex = sDirection === "forward" ? 0 : Math.max(0, iLastIndex - 1);
1942
+ fRemainingDistance = sDirection === "forward" ? fDistance : fDistance;
1943
+ }
1944
+
1945
+ // Calculate exact position along the segment
1946
+ var fSegmentLength = Math.sqrt(
1947
+ Math.pow(oCoords[iSegmentIndex + 1].getX() - oCoords[iSegmentIndex].getX(), 2) +
1948
+ Math.pow(oCoords[iSegmentIndex + 1].getY() - oCoords[iSegmentIndex].getY(), 2)
1949
+ );
1950
+
1951
+ var fRatio = fSegmentLength > 0 ? (fRemainingDistance / fSegmentLength) : 0;
1952
+ if (sDirection === "backward") {
1953
+ fRatio = 1 - fRatio; // Reverse the ratio for backward direction
1954
+ }
1955
+
1956
+ // Calculate the exact coordinates that are 44px away from the anchor
1957
+ var fExactX = oCoords[iSegmentIndex].getX() + (oCoords[iSegmentIndex + 1].getX() - oCoords[iSegmentIndex].getX()) * fRatio;
1958
+ var fExactY = oCoords[iSegmentIndex].getY() + (oCoords[iSegmentIndex + 1].getY() - oCoords[iSegmentIndex].getY()) * fRatio;
1959
+
1960
+ return {
1961
+ segmentIndex: iSegmentIndex,
1962
+ ratio: fRatio,
1963
+ center: {x: oCoords[iSegmentIndex].getX(), y: oCoords[iSegmentIndex].getY()},
1964
+ apex: {x: oCoords[iSegmentIndex + 1].getX(), y: oCoords[iSegmentIndex + 1].getY()},
1965
+ exactPosition: {x: fExactX, y: fExactY}
1966
+ };
1967
+ };
1968
+
1264
1969
  return Line;
1265
1970
  });