@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
@@ -0,0 +1,1144 @@
1
+ /*!
2
+ *
3
+ SAP UI development toolkit for HTML5 (SAPUI5)
4
+ (c) Copyright 2009-2015 SAP SE. All rights reserved
5
+
6
+ */
7
+ sap.ui.define([
8
+ "sap/suite/ui/commons/library"
9
+ ], function (library) {
10
+ "use strict";
11
+ const ConnectionType = library.networkgraph.ConnectionType;
12
+ const GRID_SIZE = 54; // Size of each grid cell
13
+ const MIN_EDGE_LENGTH = 44; // Minimum length of an edge to avoid too short lines
14
+ /**
15
+ * Utility class for calculating connection paths between nodes in network graphs.
16
+ * This utility provides a method to normalize lines and calculate path coordinates
17
+ * based on different connection types.
18
+ *
19
+ * @namespace sap.suite.ui.commons.networkgraph.util.ConnectionPathUtils
20
+ * @public
21
+ * @since 1.144
22
+ */
23
+ var ConnectionPathUtils = {
24
+ /**
25
+ * Normalizes lines in a network graph by calculating path coordinates
26
+ * between source and target nodes based on connection types.
27
+ *
28
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
29
+ * @param {object} [mConfig] - Configuration object
30
+ * @param {number} [mConfig.gridSize=50] - Size of each grid cell
31
+ * @param {number} [mConfig.minEdgeLength=20] - Minimum length of an edge
32
+ * @public
33
+ */
34
+ normalizeLines: function (oGraph, mConfig) {
35
+ const config = Object.assign({
36
+ gridSize: GRID_SIZE,
37
+ minEdgeLength: MIN_EDGE_LENGTH
38
+ }, mConfig || {});
39
+
40
+ // Enable multiple directed arrows feature when using ConnectionPathUtils
41
+ oGraph._enableMultipleDirectedArrows = true;
42
+
43
+ // Iterate over all lines in the graph
44
+ oGraph.getLines().forEach((oLine) => {
45
+ const connectionType = oLine.getConnectionType(); // Get the connection type, if not configured at Line level then the default is Right to Left from source to target.
46
+ const oSourceNode = oLine.getFromNode();
47
+ const oTargetNode = oLine.getToNode();
48
+ // Get anchor coordinates for source and target nodes, usually the center of the respective sides
49
+ const oSourceAnchor = this._getNodeAnchorCoordinates(oLine.getFromNode(), connectionType, "source");
50
+ const oTargetAnchor = this._getNodeAnchorCoordinates(oLine.getToNode(), connectionType, "target");
51
+ const mParams = {
52
+ sourceAnchor: oSourceAnchor,
53
+ targetAnchor: oTargetAnchor,
54
+ sourceNode: oSourceNode,
55
+ targetNode: oTargetNode,
56
+ sConnectionType: connectionType,
57
+ gridSize: config.gridSize,
58
+ minEdgeLength: config.minEdgeLength
59
+ };
60
+ // Calculate the path coordinates between the source and target anchors
61
+ const aCoordinates = this._calculatePathCoordinates(mParams);
62
+ // set the anchor points of the source and target nodes on the line as source and target for drawing the path.
63
+ oLine.setSource({
64
+ x: oSourceAnchor.x,
65
+ y: oSourceAnchor.y
66
+ });
67
+ oLine.setTarget({
68
+ x: oTargetAnchor.x,
69
+ y: oTargetAnchor.y
70
+ });
71
+ // Set the calculated bend points on the line
72
+ oLine.clearBends();
73
+ // Set the calculated bend points on the line, determined by the connection type.
74
+ if (aCoordinates && aCoordinates.length) {
75
+ aCoordinates.forEach((oBend) => {
76
+ oLine.addBend(oBend);
77
+ });
78
+ }
79
+ });
80
+ },
81
+ /**
82
+ * Calculates path coordinates between source and target anchors based on connection type.
83
+ *
84
+ * @param {object} mParams - Parameters for path calculation
85
+ * @param {object} mParams.sourceAnchor - Source anchor coordinates {x, y}
86
+ * @param {object} mParams.targetAnchor - Target anchor coordinates {x, y}
87
+ * @param {object} mParams.sourceNode - Source node object
88
+ * @param {object} mParams.targetNode - Target node object
89
+ * @param {string} mParams.sConnectionType - Connection type
90
+ * @param {number} [mParams.gridSize=50] - Grid size for calculations
91
+ * @param {number} [mParams.minEdgeLength=20] - Minimum edge length
92
+ * @returns {Array<object>} Array of bend point coordinates
93
+ * @private
94
+ */
95
+ _calculatePathCoordinates: function (mParams) {
96
+ const { sConnectionType } = mParams;
97
+ switch (sConnectionType) {
98
+ case ConnectionType.RightToLeft:
99
+ return this._handleRightToLeftConnection(mParams);
100
+ case ConnectionType.LeftToRight:
101
+ return this._handleLeftToRightConnection(mParams);
102
+ case ConnectionType.LeftToLeft:
103
+ return this._handleLeftToLeftConnection(mParams);
104
+ case ConnectionType.RightToRight:
105
+ return this._handleRightToRightConnection(mParams);
106
+ default:
107
+ // default RightToLeft case
108
+ // This is a fallback for any unrecognized connection type
109
+ return this._handleRightToLeftConnection(mParams);
110
+ }
111
+ },
112
+ /**
113
+ * ╔══════════════════════════════════════════════════════════════════════════════╗
114
+ * ║ RIGHT-TO-LEFT CONNECTION HANDLER ║
115
+ * ╠══════════════════════════════════════════════════════════════════════════════╣
116
+ * ║ ║
117
+ * ║ [SRC NODE]→ ········→ ←[TGT NODE] ║
118
+ * ║ ║
119
+ * ║ Calculates optimal path from right anchor of source to left anchor of ║
120
+ * ║ target, creating L-shaped, U-shaped, Z-bend, or S-shaped connections ║
121
+ * ║ based on node positions and grid constraints. ║
122
+ * ║ ║
123
+ * ╚══════════════════════════════════════════════════════════════════════════════╝
124
+ */
125
+ /**
126
+ * Handles RightToLeft connection path calculation.
127
+ * Connection flows from the right side of source node to the left side of target node.
128
+ *
129
+ * @param {object} mParams - Parameters for path calculation
130
+ * @returns {Array<object>} Array of bend point coordinates
131
+ * @private
132
+ */
133
+ _handleRightToLeftConnection: function (mParams) {
134
+ const { sourceNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
135
+ const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
136
+
137
+ // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
138
+ if (y1 === y2) {
139
+ if (x2 > x1) {
140
+ // SCENARIO 1A: Direct horizontal connection (source right → target left)
141
+ // Check if anchors are aligned and connection pattern allows straight line
142
+ const anchorVerticalOffset = Math.abs(AY1 - AY2);
143
+ const shouldUseStraightLine = this._shouldUseHorizontalStraightLine(mParams.sourceNode, mParams.targetNode, mParams.sConnectionType);
144
+
145
+ if (shouldUseStraightLine && anchorVerticalOffset <= 5) {
146
+ // Visual: [SRC]----→[TGT] (perfect alignment)
147
+ return [];
148
+ } else if (anchorVerticalOffset > 5) {
149
+ // Visual: [SRC]→ ←[TGT] (offset anchors - add horizontal correction)
150
+ // | |
151
+ // └─────┘
152
+ const midX = AX1 + (AX2 - AX1) / 2;
153
+ return [
154
+ { x: midX, y: AY1 },
155
+ { x: midX, y: AY2 }
156
+ ];
157
+ } else {
158
+ // Multiple connections - use straight line anyway for now
159
+ return [];
160
+ }
161
+ } else if (x1 >= x2) {
162
+ // SCENARIO 1B: Backward horizontal connection (source overlaps/behind target)
163
+ // Visual: [TGT] [SRC]
164
+ // ↑----→ |
165
+ // | |
166
+ // ←------↓
167
+ // Create U-shaped path going around both nodes
168
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
169
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
170
+ const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
171
+
172
+ return [
173
+ { x: x3, y: AY1 }, // Right from source
174
+ { x: x3, y: y3 }, // Down below both nodes
175
+ { x: x4, y: y3 }, // Left to target column
176
+ { x: x4, y: AY2 } // Up to target
177
+ ];
178
+ }
179
+ } else if (y1 != y2) {
180
+ if (x1 <= x2) {
181
+ // SCENARIO 2A: Source is to the left or aligned with target
182
+ if (dx >= minEdgeLength + sourceNode._iWidth) {
183
+ if (Math.abs(x1 + sourceNode._iWidth - x2) <= gridSize) {
184
+ // SCENARIO 2A1: Nodes are close - use midpoint connection
185
+ // Visual: [SRC]→ ←[TGT]
186
+ // | |
187
+ // ↓ ↑
188
+ // (midpoint)
189
+ const x3 = AX1 + (x2 - (x1 + sourceNode._iWidth)) / 2;
190
+ return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
191
+ } else {
192
+ // SCENARIO 2A2: Nodes are far apart - use grid-aligned Z-bend
193
+ // Visual: [SRC]→ |
194
+ // |
195
+ // ↓
196
+ // ←[TGT]
197
+ const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
198
+ return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
199
+ }
200
+ } else if (dx <= minEdgeLength + sourceNode._iWidth) {
201
+ // SCENARIO 2B: Nodes are too close - create S-shaped path
202
+ if (y1 < y2) {
203
+ // SCENARIO 2B1: Source above target - S-bend going down
204
+ // Visual: [SRC]→ |
205
+ // |
206
+ // ↓----→ |
207
+ // |
208
+ // ↓
209
+ // ←[TGT]
210
+ const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, y1, gridSize, minEdgeLength).x;
211
+ const x4 = this.getLeftGridIntersectionPoint(x2, AY2, gridSize, minEdgeLength).x;
212
+ const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
213
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
214
+ } else if (y1 > y2) {
215
+ // SCENARIO 2B2: Source below target - S-bend going up
216
+ // Visual: ←[TGT]
217
+ // ↑
218
+ // ↑----→ |
219
+ // |
220
+ // |
221
+ // [SRC]→ |
222
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY2, gridSize, minEdgeLength).x;
223
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
224
+ const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
225
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
226
+ }
227
+ }
228
+ } else if (x1 > x2) {
229
+ // SCENARIO 3: Source is to the right of target - create S-shaped path
230
+ if (y1 < y2) {
231
+ // SCENARIO 3A: Source above and right of target - S-bend down and left
232
+ // Visual: [SRC]→ |
233
+ // |
234
+ // ↓
235
+ // ←[TGT] ←---------
236
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
237
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
238
+ const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
239
+ return [
240
+ { x: x3, y: AY1 }, // Right from source
241
+ { x: x3, y: y3 }, // Down past both nodes
242
+ { x: x4, y: y3 }, // Left to target column
243
+ { x: x4, y: AY2 } // Up to target
244
+ ];
245
+ } else if (y1 > y2) {
246
+ // SCENARIO 3B: Source below and right of target - S-bend up and left
247
+ // Visual: ←[TGT] ←---------
248
+ // ↑
249
+ // |
250
+ // [SRC]→ |
251
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
252
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
253
+ const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
254
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
255
+ }
256
+ }
257
+ }
258
+ // DEFAULT: No bends needed (direct connection possible)
259
+ return [];
260
+ },
261
+ /**
262
+ * ╔══════════════════════════════════════════════════════════════════════════════╗
263
+ * ║ LEFT-TO-RIGHT CONNECTION HANDLER ║
264
+ * ╠══════════════════════════════════════════════════════════════════════════════╣
265
+ * ║ ║
266
+ * ║ ←[SRC NODE] ········→ [TGT NODE]→ ║
267
+ * ║ ║
268
+ * ║ Calculates optimal path from left anchor of source to right anchor of ║
269
+ * ║ target, creating direct, U-shaped, Z-bend, or S-shaped connections ║
270
+ * ║ based on node positions and grid alignment. ║
271
+ * ║ ║
272
+ * ╚══════════════════════════════════════════════════════════════════════════════╝
273
+ */
274
+ /**
275
+ * Handles LeftToRight connection path calculation.
276
+ * Connection flows from the left side of source node to the right side of target node.
277
+ *
278
+ * @param {object} mParams - Parameters for path calculation
279
+ * @returns {Array<object>} Array of bend point coordinates
280
+ * @private
281
+ */
282
+ _handleLeftToRightConnection: function (mParams) {
283
+ const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
284
+ const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
285
+ // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
286
+ if (y1 === y2) {
287
+ if (x1 > x2) {
288
+ // SCENARIO 1A: Direct horizontal connection (source left → target right, but source is actually to the right)
289
+ // Check if anchors are aligned and connection pattern allows straight line
290
+ const anchorVerticalOffset = Math.abs(AY1 - AY2);
291
+ const shouldUseStraightLine = this._shouldUseHorizontalStraightLine(mParams.sourceNode, mParams.targetNode, mParams.sConnectionType);
292
+
293
+ if (shouldUseStraightLine && anchorVerticalOffset <= 5) {
294
+ // Visual: [TGT]←----[SRC] (perfect alignment)
295
+ return [];
296
+ } else if (anchorVerticalOffset > 5) {
297
+ // Visual: ←[SRC] [TGT]→ (offset anchors - add horizontal correction)
298
+ // | |
299
+ // └───────────┘
300
+ const midX = AX1 + (AX2 - AX1) / 2;
301
+ return [
302
+ { x: midX, y: AY1 },
303
+ { x: midX, y: AY2 }
304
+ ];
305
+ } else {
306
+ // Multiple connections - use straight line anyway for now
307
+ return [];
308
+ }
309
+ } else if (x1 <= x2) {
310
+ // SCENARIO 1B: Forward horizontal connection but needs U-shape (source overlaps/in front of target)
311
+ // Visual: [SRC] [TGT]
312
+ // ↓----→ |
313
+ // | |
314
+ // ↑------↓
315
+ // Create U-shaped path going around both nodes
316
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
317
+ const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
318
+ const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
319
+
320
+ return [
321
+ { x: x3, y: AY1 }, // Right from source
322
+ { x: x3, y: y3 }, // Down below both nodes
323
+ { x: x4, y: y3 }, // Left to target column
324
+ { x: x4, y: AY2 } // Up to target
325
+ ];
326
+ }
327
+ } else if (y1 != y2) {
328
+ // CASE 2: Source and target nodes are on different horizontal lines (y1 != y2)
329
+ if (x1 >= x2) {
330
+ // SCENARIO 2A: Source is to the right or aligned with target
331
+ if (dx >= minEdgeLength + targetNode._iWidth) {
332
+ if (Math.abs(x1 - (x2 + targetNode._iWidth)) <= gridSize) {
333
+ // SCENARIO 2A1: Nodes are close - use midpoint connection
334
+ // Visual: ←[SRC] [TGT]→
335
+ // | |
336
+ // ↓ ↑
337
+ // (midpoint)
338
+ const x3 = (AX1 + AX2) / 2;
339
+ return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
340
+ } else {
341
+ // SCENARIO 2A2: Nodes are far apart - use grid-aligned Z-bend
342
+ // Visual: ←[SRC] |
343
+ // |
344
+ // ↓
345
+ // [TGT]→
346
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
347
+ return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
348
+ }
349
+ } else if (dx <= minEdgeLength + targetNode._iWidth) {
350
+ // SCENARIO 2B: Nodes are too close - create S-shaped path
351
+ if (y1 < y2) {
352
+ // SCENARIO 2B1: Source above target - S-bend going down
353
+ // Visual: ←[SRC] |
354
+ // |
355
+ // ←---- ↓
356
+ // |
357
+ // ↓
358
+ // [TGT]→
359
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
360
+ const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
361
+ const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
362
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
363
+ } else if (y1 > y2) {
364
+ // SCENARIO 2B2: Source below target - S-bend going up
365
+ // Visual: [TGT]→
366
+ // ↑
367
+ // ←------ |
368
+ // |
369
+ // |
370
+ // ←[SRC] |
371
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY2, gridSize, minEdgeLength).x;
372
+ const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
373
+ const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
374
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
375
+ }
376
+ }
377
+ } else if (x1 < x2) {
378
+ // SCENARIO 3: Source is to the left of target - create S-shaped path
379
+ if (y1 < y2) {
380
+ // SCENARIO 3A: Source above and left of target - S-bend down and right
381
+ // Visual: ←[SRC] |
382
+ // |
383
+ // ↓
384
+ // --------→[TGT]
385
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
386
+ const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
387
+ const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
388
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
389
+ } else if (y1 > y2) {
390
+ // SCENARIO 3B: Source below and left of target - S-bend up and right
391
+ // Visual: --------→[TGT]
392
+ // ↑
393
+ // |
394
+ // ←[SRC] |
395
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
396
+ const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
397
+ const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
398
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
399
+ }
400
+ }
401
+ }
402
+ // DEFAULT: No bends needed (direct connection possible)
403
+ return [];
404
+ },
405
+ /**
406
+ * ╔══════════════════════════════════════════════════════════════════════════════╗
407
+ * ║ LEFT-TO-LEFT CONNECTION HANDLER ║
408
+ * ╠══════════════════════════════════════════════════════════════════════════════╣
409
+ * ║ ║
410
+ * ║ ←[SRC NODE] ········→ ←[TGT NODE] ║
411
+ * ║ ║
412
+ * ║ Calculates optimal path from left anchor of source to left anchor of ║
413
+ * ║ target, creating C-shaped or stepped connections that route around ║
414
+ * ║ the left side of both nodes to avoid overlaps. ║
415
+ * ║ ║
416
+ * ╚══════════════════════════════════════════════════════════════════════════════╝
417
+ */
418
+ /**
419
+ * Handles LeftToLeft connection path calculation.
420
+ * Connection flows from the left side of source node to the left side of target node.
421
+ *
422
+ * @param {object} mParams - Parameters for path calculation
423
+ * @returns {Array<object>} Array of bend point coordinates
424
+ * @private
425
+ */
426
+ _handleLeftToLeftConnection: function (mParams) {
427
+ const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
428
+ const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
429
+ // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
430
+ if (y1 === y2) {
431
+ if (x1 > x2) {
432
+ // SCENARIO 1A: Source to the right of target - create C-shaped path on left side
433
+ // Visual: ←[TGT] ←[SRC]
434
+ // | |
435
+ // ↓ |
436
+ // ←-----------↓
437
+ // Check if nodes are close enough for midpoint, else use grid-aligned path
438
+ const x3 = Math.abs(x1 - x2 + targetNode._iWidth) <= gridSize ?
439
+ (AX1 + (x2 + targetNode._iWidth)) / 2 :
440
+ this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
441
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
442
+ const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
443
+ return [
444
+ { x: x3, y: AY1 }, // Left from source
445
+ { x: x3, y: y3 }, // Down/up to avoid nodes
446
+ { x: x4, y: y3 }, // Across to target column
447
+ { x: x4, y: AY2 } // Up/down to target
448
+ ];
449
+ } else if (x1 <= x2) {
450
+ // SCENARIO 1B: Source to the left of target - create C-shaped path on left side
451
+ // Visual: ←[SRC] ←[TGT]
452
+ // | |
453
+ // ↓ |
454
+ // →-------------↓
455
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
456
+ const x4 = Math.abs(x1 + sourceNode._iWidth - x2) <= gridSize ?
457
+ (AX2 + x2) / 2 :
458
+ this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
459
+ const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
460
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
461
+ }
462
+ } else if (y1 != y2) {
463
+ // CASE 2: Source and target nodes are on different horizontal lines (y1 != y2)
464
+ /**
465
+ * When the source and target nodes are not on the same horizontal line (y1 != y2)
466
+ * Then nodes can be placed in either x1 > x2 or x1 < x2
467
+ * with these positions there can be two cases:
468
+ * 1. y1 < y2: source node is above the target node
469
+ * 2. y1 > y2: source node is below the target node
470
+ * In both cases we need to calculate the vertical bend points based on node's y so that the lines do not overlap with the target node.
471
+ * If x1 < x2 and y1 < y2 then we know that source node is above the target node and to the left of it. Based on AY2 we can calculate above or below bend points.
472
+ */
473
+ if (x1 > x2) {
474
+ // SCENARIO 2A: Source is to the right of target
475
+ if (y1 < y2) {
476
+ // SCENARIO 2A1: Source above and right of target
477
+ if (AY1 < y2) {
478
+ // Simple vertical connection if source anchor is above target node
479
+ // Visual: ←[SRC]
480
+ // |
481
+ // |
482
+ // ↓
483
+ // ←[TGT]
484
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
485
+ return [{ x: x4, y: AY1 }, { x: x4, y: AY2 }];
486
+ } else if (AY1 >= y2) {
487
+ // Need to avoid target node - create stepped path
488
+ // Visual: ←[SRC]
489
+ // |
490
+ // ←[TGT] ←-----↓
491
+ const iDiff = Math.abs(AX1 - (x2 + targetNode._iWidth));
492
+ let x3 = null;
493
+ if (iDiff <= gridSize) {
494
+ x3 = iDiff < minEdgeLength ? (AX1 + (x2 + targetNode._iWidth) / 2) : AX1 - minEdgeLength;
495
+ } else {
496
+ x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
497
+ }
498
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
499
+ const y3 = this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
500
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
501
+ }
502
+ } else if (y1 > y2) {
503
+ // SCENARIO 2A2: Source below and right of target
504
+ if (AY1 > y2 + targetNode._iHeight) {
505
+ // Simple vertical connection if source anchor is below target node
506
+ // Visual: ←[TGT]
507
+ // |
508
+ // |
509
+ // ↑
510
+ // ←[SRC]
511
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
512
+ return [{ x: x4, y: AY1 }, { x: x4, y: AY2 }];
513
+ } else if (AY1 <= y2 + targetNode._iHeight) {
514
+ // Need to avoid target node - create stepped path
515
+ // Visual: ←[TGT] ←-----↑
516
+ // |
517
+ // ←[SRC]
518
+ const iDiff = Math.abs(AX1 - (x2 + targetNode._iWidth));
519
+ let x3 = null;
520
+ if (iDiff <= gridSize) {
521
+ x3 = iDiff < AX1 + minEdgeLength ? (AX1 + (x2 + targetNode._iWidth) / 2) : AX1 + minEdgeLength;
522
+ } else {
523
+ x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
524
+ }
525
+ const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
526
+ const y3 = this.getBottomGridIntersectionPoint(x4, y2 + targetNode._iHeight, gridSize, minEdgeLength).y;
527
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
528
+ }
529
+ }
530
+ } else if (x1 <= x2) {
531
+ // SCENARIO 2B: Source is to the left of target
532
+ if (y1 < y2) {
533
+ // SCENARIO 2B1: Source above and left of target
534
+ // Visual: ←[SRC]
535
+ // |
536
+ // ↓-------→ |
537
+ // ↓
538
+ // ←[TGT]
539
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
540
+ const iDiff = Math.abs(x1 + sourceNode._iWidth - x2);
541
+ let x4 = null;
542
+ if (iDiff <= gridSize) {
543
+ x4 = iDiff < minEdgeLength ? (AX2 + (x1 + sourceNode._iWidth)) / 2 : AX2 + minEdgeLength;
544
+ } else {
545
+ x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
546
+ }
547
+ const y3 = (AY2 > (y1 + sourceNode._iHeight)) ? AY2 : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
548
+ 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 }];
549
+ return aCoordinates;
550
+ } else if (y1 > y2) {
551
+ // SCENARIO 2B2: Source below and left of target
552
+ // Visual: ←[TGT]
553
+ // ↑
554
+ // ←-------→ |
555
+ // |
556
+ // ←[SRC]
557
+ const x3 = this.getLeftGridIntersectionPoint(AX1, AY2, gridSize, minEdgeLength).x;
558
+ const iDiff = Math.abs(x1 + sourceNode._iWidth - x2);
559
+ let x4 = null;
560
+ if (iDiff <= gridSize) {
561
+ x4 = iDiff < minEdgeLength ? (AX2 + (x1 + sourceNode._iWidth)) / 2 : AX2 + minEdgeLength;
562
+ } else {
563
+ x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
564
+ }
565
+ const y3 = (AY2 < y1) ? AY2 : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
566
+ const aCoordinates = AY2 < y1 ? [{ x: x3, y: AY1 }, { x: x3, y: y3 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
567
+ return aCoordinates;
568
+ }
569
+ }
570
+ }
571
+ // DEFAULT: No bends needed (direct connection possible)
572
+ return [];
573
+ },
574
+ /**
575
+ * ╔══════════════════════════════════════════════════════════════════════════════╗
576
+ * ║ RIGHT-TO-RIGHT CONNECTION HANDLER ║
577
+ * ╠══════════════════════════════════════════════════════════════════════════════╣
578
+ * ║ ║
579
+ * ║ [SRC NODE]→ ········→ [TGT NODE]→ ║
580
+ * ║ ║
581
+ * ║ Calculates optimal path from right anchor of source to right anchor of ║
582
+ * ║ target, creating C-shaped or stepped connections that route around ║
583
+ * ║ the right side of both nodes to avoid overlaps. ║
584
+ * ║ ║
585
+ * ╚══════════════════════════════════════════════════════════════════════════════╝
586
+ */
587
+ /**
588
+ * Handles RightToRight connection path calculation.
589
+ * Connection flows from the right side of source node to the right side of target node.
590
+ *
591
+ * @param {object} mParams - Parameters for path calculation
592
+ * @returns {Array<object>} Array of bend point coordinates
593
+ * @private
594
+ */
595
+ _handleRightToRightConnection: function (mParams) {
596
+ const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
597
+ const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
598
+ // CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
599
+ if (y1 === y2) {
600
+ if (x1 > x2) {
601
+ // SCENARIO 1A: Source to the right of target - create C-shaped path on right side
602
+ // Visual: [TGT]→ [SRC]→
603
+ // | |
604
+ // ↓ |
605
+ // →---------↓
606
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
607
+ const iDiff = Math.abs(x1 - AX2);
608
+ const x4 = iDiff <= gridSize ? (x1 + x2) / 2 : this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
609
+ const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
610
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
611
+ } else if (x1 <= x2) {
612
+ // SCENARIO 1B: Source to the left of target - create C-shaped path on right side
613
+ // Visual: [SRC]→ [TGT]→
614
+ // | |
615
+ // ↓ |
616
+ // →-----------------↓
617
+ const iDff = Math.abs(AX1 - x2);
618
+ const x3 = iDff <= gridSize ? (AX1 + x2) / 2 : this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
619
+ const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
620
+ const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
621
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
622
+ }
623
+ } else if (y1 != y2) {
624
+ // CASE 2: Source and target nodes are on different horizontal lines (y1 != y2)
625
+ if (x1 > x2) {
626
+ // SCENARIO 2A: Source is to the right of target
627
+ if (y1 < y2) {
628
+ // SCENARIO 2A1: Source above and right of target
629
+ // Visual: [SRC]→
630
+ // |
631
+ // ↓--------→ |
632
+ // ↓
633
+ // [TGT]→
634
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
635
+ const iDiff = Math.abs(AX2 - x1);
636
+ let x4 = null;
637
+ if (iDiff <= gridSize) {
638
+ x4 = iDiff < minEdgeLength ? (AX2 + x1) / 2 : AX2 + minEdgeLength;
639
+ } else {
640
+ x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
641
+ }
642
+ const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
643
+ if (AY2 > y1 + sourceNode._iHeight) {
644
+ // Simple vertical connection if target anchor is below source node
645
+ // Visual: [SRC]→
646
+ // |
647
+ // ↓
648
+ // [TGT]→
649
+ const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
650
+ return aCords;
651
+ } else {
652
+ // Need to avoid target node - create stepped path
653
+ // Visual: [SRC]→
654
+ // |
655
+ // ↓---- →
656
+ // |
657
+ // [TGT]→
658
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
659
+ }
660
+ } else if (y1 > y2) {
661
+ // SCENARIO 2A2: Source below and right of target
662
+ // Visual: [TGT]→
663
+ // ↑
664
+ // ↑--------→ |
665
+ // |
666
+ // [SRC]→
667
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
668
+ const iDiff = Math.abs(AX2 - x1);
669
+ let x4 = null;
670
+ if (iDiff <= gridSize) {
671
+ x4 = iDiff < minEdgeLength ? (AX2 + x1) / 2 : AX2 + minEdgeLength;
672
+ } else {
673
+ x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
674
+ }
675
+ const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
676
+ if (AY1 > y2 + targetNode._iHeight) {
677
+ // Simple vertical connection if target anchor is below source node
678
+ // Visual: [SRC]→
679
+ // |
680
+ // ↓
681
+ // [TGT]→
682
+ const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
683
+ return aCords;
684
+ } else {
685
+ // Need to avoid target node - create stepped path
686
+ // Visual: [SRC]→
687
+ // |
688
+ // ↓---- →
689
+ // |
690
+ // [TGT]→
691
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
692
+ }
693
+ }
694
+ } else if (x1 <= x2) {
695
+ // SCENARIO 2B: Source is to the left of target
696
+ if (y1 < y2) {
697
+ // SCENARIO 2B1: Source above and left of target
698
+ // Visual: [SRC]→
699
+ // |
700
+ // ↓----------→ |
701
+ // ↓
702
+ // [TGT]→
703
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
704
+ const iDiff = Math.abs(x2 - AX1);
705
+ let x4 = null;
706
+ if (iDiff <= gridSize && !(AY2 > y1 + sourceNode._iHeight)) {
707
+ x4 = iDiff < minEdgeLength ? (x2 + AX1) / 2 : AX1 - minEdgeLength;
708
+ } else {
709
+ x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
710
+ }
711
+ const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
712
+ if (AY2 > y1 + sourceNode._iHeight) {
713
+ // Simple vertical connection if target anchor is below source node
714
+ // Visual: [SRC]→
715
+ // |
716
+ // ↓
717
+ // [TGT]→
718
+ const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
719
+ return aCords;
720
+ } else {
721
+ // Need to avoid target node - create stepped path
722
+ // Visual: [SRC]→
723
+ // |
724
+ // ↓---- →
725
+ // |
726
+ // [TGT]→
727
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
728
+ }
729
+ // const aCoordinates = (AY2 > y1 + sourceNode._iHeight) ? [{ x: x4, y: AY1 }, { x: x4, y: AY2 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
730
+ // return aCoordinates;
731
+ } else if (y1 > y2) {
732
+ // SCENARIO 2B2: Source below and left of target
733
+ // Visual: [TGT]→
734
+ // ↑
735
+ // ↑----------→ |
736
+ // |
737
+ // [SRC]→
738
+ const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
739
+ const iDiff = Math.abs(x2 - AX1);
740
+ let x4 = null;
741
+ if (iDiff <= gridSize && !(AY1 > y2 + targetNode._iHeight)) {
742
+ x4 = iDiff < minEdgeLength ? (x2 + AX1) / 2 : AX1 - minEdgeLength;
743
+ } else {
744
+ x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
745
+ }
746
+ const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
747
+ if (AY1 > y2 + targetNode._iHeight) {
748
+ // Simple vertical connection if target anchor is below source node
749
+ // Visual: [SRC]→
750
+ // |
751
+ // ↓
752
+ // [TGT]→
753
+ const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
754
+ return aCords;
755
+ } else {
756
+ // Need to avoid target node - create stepped path
757
+ // Visual: [SRC]→
758
+ // |
759
+ // ↓---- →
760
+ // |
761
+ // [TGT]→
762
+ return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
763
+ }
764
+ // const aCoordinates = (AY1 > y2 + targetNode._iHeight) ? [{ x: x4, y: AY1 }, { x: x4, y: AY2 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
765
+ // return aCoordinates;
766
+ }
767
+ }
768
+ }
769
+ // DEFAULT: No bends needed (direct connection possible)
770
+ return [];
771
+ },
772
+ /**
773
+ * Calculates the right grid intersection point.
774
+ *
775
+ * @param {number} x - X coordinate
776
+ * @param {number} y - Y coordinate
777
+ * @param {number} [gridSize=50] - Size of grid cells
778
+ * @param {number} [minEdgeLength=20] - Minimum edge length
779
+ * @returns {object} Intersection point {x, y}
780
+ * @public
781
+ */
782
+ getRightGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
783
+ let xIntersection = Math.ceil(x / gridSize) * gridSize;
784
+ if (xIntersection - x < minEdgeLength) {
785
+ xIntersection += gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
786
+ }
787
+ return { x: xIntersection, y: y };
788
+ },
789
+ /**
790
+ * Calculates the left grid intersection point.
791
+ *
792
+ * @param {number} x - X coordinate
793
+ * @param {number} y - Y coordinate
794
+ * @param {number} [gridSize=50] - Size of grid cells
795
+ * @param {number} [minEdgeLength=20] - Minimum edge length
796
+ * @returns {object} Intersection point {x, y}
797
+ * @public
798
+ */
799
+ getLeftGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
800
+ let xIntersection = Math.floor(x / gridSize) * gridSize;
801
+ if (x - xIntersection < minEdgeLength) {
802
+ xIntersection -= gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
803
+ }
804
+ return { x: xIntersection, y: y };
805
+ },
806
+ /**
807
+ * Calculates the top grid intersection point.
808
+ *
809
+ * @param {number} x - X coordinate
810
+ * @param {number} y - Y coordinate
811
+ * @param {number} [gridSize=50] - Size of grid cells
812
+ * @param {number} [minEdgeLength=20] - Minimum edge length
813
+ * @returns {object} Intersection point {x, y}
814
+ * @public
815
+ */
816
+ getTopGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
817
+ let yIntersection = Math.floor(y / gridSize) * gridSize;
818
+ if (y - yIntersection < minEdgeLength) {
819
+ yIntersection -= gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
820
+ }
821
+ return { x: x, y: yIntersection };
822
+ },
823
+ /**
824
+ * Calculates the bottom grid intersection point.
825
+ *
826
+ * @param {number} x - X coordinate
827
+ * @param {number} y - Y coordinate
828
+ * @param {number} [gridSize=50] - Size of grid cells
829
+ * @param {number} [minEdgeLength=20] - Minimum edge length
830
+ * @returns {object} Intersection point {x, y}
831
+ * @public
832
+ */
833
+ getBottomGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
834
+ let yIntersection = Math.ceil(y / gridSize) * gridSize;
835
+ if (yIntersection - y < minEdgeLength) {
836
+ yIntersection += gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
837
+ }
838
+ return { x: x, y: yIntersection };
839
+ },
840
+ /**
841
+ * Gets the anchor coordinates for a node based on connection type and anchor type.
842
+ * Enhanced to handle center positioning for single connections and offset positioning for mixed connections.
843
+ *
844
+ * @param {object} oNode - The node object
845
+ * @param {string} sConnectionType - Connection type
846
+ * @param {string} sAnchorType - Anchor type ("source" or "target")
847
+ * @returns {object} Anchor coordinates {x, y}
848
+ * @private
849
+ */
850
+ _getNodeAnchorCoordinates: function (oNode, sConnectionType, sAnchorType) {
851
+ const x = oNode.getX();
852
+ const y = oNode.getY();
853
+ const width = oNode._iWidth;
854
+ const height = oNode._iHeight;
855
+ const centerY = y + (height / 2);
856
+
857
+ // Enhanced anchor calculation with connection pattern analysis
858
+ const oConnectionAnalysis = this._analyzeNodeConnectionPatterns(oNode);
859
+ // Determine the port position based on the connection type and analysis
860
+ switch (sConnectionType) {
861
+ case "LeftToLeft":
862
+ if (sAnchorType === "source") {
863
+ // Source anchor on left side
864
+ return this._calculateEnhancedAnchor({
865
+ node: { x, y, width, height, centerY },
866
+ side: "left",
867
+ analysis: oConnectionAnalysis,
868
+ anchorType: sAnchorType
869
+ });
870
+ } else {
871
+ // Target anchor on left side
872
+ return this._calculateEnhancedAnchor({
873
+ node: { x, y, width, height, centerY },
874
+ side: "left",
875
+ analysis: oConnectionAnalysis,
876
+ anchorType: sAnchorType
877
+ });
878
+ }
879
+ case "LeftToRight":
880
+ if (sAnchorType === "source") {
881
+ // Source anchor on left side
882
+ return this._calculateEnhancedAnchor({
883
+ node: { x, y, width, height, centerY },
884
+ side: "left",
885
+ analysis: oConnectionAnalysis,
886
+ anchorType: sAnchorType
887
+ });
888
+ } else {
889
+ // Target anchor on right side
890
+ return this._calculateEnhancedAnchor({
891
+ node: { x, y, width, height, centerY },
892
+ side: "right",
893
+ analysis: oConnectionAnalysis,
894
+ anchorType: sAnchorType
895
+ });
896
+ }
897
+ case "RightToLeft":
898
+ if (sAnchorType === "source") {
899
+ // Source anchor on right side
900
+ return this._calculateEnhancedAnchor({
901
+ node: { x, y, width, height, centerY },
902
+ side: "right",
903
+ analysis: oConnectionAnalysis,
904
+ anchorType: sAnchorType
905
+ });
906
+ } else {
907
+ // Target anchor on left side
908
+ return this._calculateEnhancedAnchor({
909
+ node: { x, y, width, height, centerY },
910
+ side: "left",
911
+ analysis: oConnectionAnalysis,
912
+ anchorType: sAnchorType
913
+ });
914
+ }
915
+ case "RightToRight":
916
+ if (sAnchorType === "source") {
917
+ // Source anchor on right side
918
+ return this._calculateEnhancedAnchor({
919
+ node: { x, y, width, height, centerY },
920
+ side: "right",
921
+ analysis: oConnectionAnalysis,
922
+ anchorType: sAnchorType
923
+ });
924
+ } else {
925
+ // Target anchor on right side
926
+ return this._calculateEnhancedAnchor({
927
+ node: { x, y, width, height, centerY },
928
+ side: "right",
929
+ analysis: oConnectionAnalysis,
930
+ anchorType: sAnchorType
931
+ });
932
+ }
933
+ default:
934
+ // Default RightToLeft case
935
+ if (sAnchorType === "source") {
936
+ return this._calculateEnhancedAnchor({
937
+ node: { x, y, width, height, centerY },
938
+ side: "right",
939
+ analysis: oConnectionAnalysis,
940
+ anchorType: sAnchorType
941
+ });
942
+ } else {
943
+ return this._calculateEnhancedAnchor({
944
+ node: { x, y, width, height, centerY },
945
+ side: "left",
946
+ analysis: oConnectionAnalysis,
947
+ anchorType: sAnchorType
948
+ });
949
+ }
950
+ }
951
+ },
952
+ /**
953
+ * Transforms source and target coordinates into a normalized format for calculations.
954
+ *
955
+ * @param {object} mParams - Parameters containing node and anchor information
956
+ * @returns {object} Transformed coordinates object
957
+ * @private
958
+ */
959
+ _transformSourceTargetCoordinates: function (mParams) {
960
+ return {
961
+ x1: mParams.sourceNode.getX(), // x coordinate of the source node
962
+ y1: mParams.sourceNode.getY(), // y coordinate of the source node
963
+ x2: mParams.targetNode.getX(), // x cordinate of the target node
964
+ y2: mParams.targetNode.getY(), // y coordinate of the target node
965
+ AX1: mParams.sourceAnchor.x, // x coordinate of the source anchor
966
+ AY1: mParams.sourceAnchor.y, // y coordinate of the source anchor
967
+ AX2: mParams.targetAnchor.x, // x coordinate of the target anchor
968
+ AY2: mParams.targetAnchor.y, // y coordinate of the target anchor
969
+ connectionType: mParams.connectionType, // type of the connection
970
+ dx: Math.abs(mParams.targetNode.getX() - mParams.sourceNode.getX()), // horizontal distance between source and target nodes
971
+ dy: Math.abs(mParams.targetNode.getY() - mParams.sourceNode.getY()) // vertical distance between source and target nodes
972
+ };
973
+ },
974
+ /**
975
+ * Analyzes the connection patterns for a node to determine optimal anchor placement
976
+ * @param {object} oNode - The node to analyze
977
+ * @returns {object} Analysis object with connection counts per side
978
+ * @private
979
+ */
980
+ _analyzeNodeConnectionPatterns: function (oNode) {
981
+ if (!oNode || !oNode.getParent || !oNode.getParent()) {
982
+ return { leftIncoming: 0, leftOutgoing: 0, rightIncoming: 0, rightOutgoing: 0 };
983
+ }
984
+
985
+ const oGraph = oNode.getParent();
986
+ const aLines = oGraph.getLines();
987
+ const sNodeKey = oNode.getKey();
988
+
989
+ let leftIncoming = 0, leftOutgoing = 0, rightIncoming = 0, rightOutgoing = 0;
990
+
991
+ aLines.forEach(function(oLine) {
992
+ if (!oLine.getVisible() || oLine._isIgnored()) {
993
+ return;
994
+ }
995
+
996
+ const sFromKey = oLine.getFrom();
997
+ const sToKey = oLine.getTo();
998
+ const sLineConnectionType = oLine.getConnectionType();
999
+
1000
+ // Check if this line involves our node
1001
+ if (sFromKey === sNodeKey) {
1002
+ // Outgoing connection from our node
1003
+ switch (sLineConnectionType) {
1004
+ case "LeftToLeft":
1005
+ case "LeftToRight":
1006
+ leftOutgoing++;
1007
+ break;
1008
+ case "RightToLeft":
1009
+ case "RightToRight":
1010
+ rightOutgoing++;
1011
+ break;
1012
+ default:
1013
+ // Default to right for unrecognized connection types
1014
+ rightOutgoing++;
1015
+ break;
1016
+ }
1017
+ } else if (sToKey === sNodeKey) {
1018
+ // Incoming connection to our node
1019
+ switch (sLineConnectionType) {
1020
+ case "LeftToLeft":
1021
+ case "RightToLeft":
1022
+ leftIncoming++;
1023
+ break;
1024
+ case "LeftToRight":
1025
+ case "RightToRight":
1026
+ rightIncoming++;
1027
+ break;
1028
+ default:
1029
+ // Default to left for unrecognized connection types
1030
+ leftIncoming++;
1031
+ break;
1032
+ }
1033
+ }
1034
+ });
1035
+
1036
+ return {
1037
+ leftIncoming: leftIncoming,
1038
+ leftOutgoing: leftOutgoing,
1039
+ rightIncoming: rightIncoming,
1040
+ rightOutgoing: rightOutgoing
1041
+ };
1042
+ },
1043
+
1044
+ /**
1045
+ * Calculates enhanced anchor position based on connection analysis
1046
+ * @param {object} mParams - Parameters for anchor calculation
1047
+ * @param {object} mParams.node - Node position and size info
1048
+ * @param {string} mParams.side - Side of the node ("left" or "right")
1049
+ * @param {object} mParams.analysis - Connection pattern analysis
1050
+ * @param {string} mParams.anchorType - Anchor type ("source" or "target")
1051
+ * @returns {object} Anchor coordinates {x, y}
1052
+ * @private
1053
+ */
1054
+ _calculateEnhancedAnchor: function (mParams) {
1055
+ const { node, side, analysis, anchorType } = mParams;
1056
+ const { x, y, width, height, centerY } = node;
1057
+
1058
+ const offsetDistance = height * 0.15; // 15% of node height for offset positioning
1059
+
1060
+ // Determine base X coordinate for the side
1061
+ const baseX = side === "left" ? x : (x + width);
1062
+
1063
+ // Get connection counts for this side
1064
+ const sideIncoming = side === "left" ? analysis.leftIncoming : analysis.rightIncoming;
1065
+ const sideOutgoing = side === "left" ? analysis.leftOutgoing : analysis.rightOutgoing;
1066
+
1067
+ // Calculate Y position based on connection patterns
1068
+ let anchorY = centerY; // Default to center
1069
+
1070
+ // ENHANCEMENT 1: Single connection type on this side -> use center
1071
+ if ((sideIncoming === 0 && sideOutgoing === 1) || (sideIncoming === 1 && sideOutgoing === 0)) {
1072
+ anchorY = centerY; // Center position for single connection
1073
+ } else if (sideIncoming > 0 && sideOutgoing > 0) {
1074
+ // ENHANCEMENT 2: Mixed connections on this side -> offset positioning
1075
+ if (anchorType === "target") {
1076
+ // Incoming connections (targets) go above center
1077
+ anchorY = centerY - offsetDistance;
1078
+ } else {
1079
+ // Outgoing connections (sources) go below center
1080
+ anchorY = centerY + offsetDistance;
1081
+ }
1082
+ } else {
1083
+ // Multiple connections of same type -> use center (for now)
1084
+ anchorY = centerY;
1085
+ }
1086
+
1087
+ // Ensure anchor stays within node boundaries
1088
+ const minY = y + (height * 0.1); // 10% margin from top
1089
+ const maxY = y + (height * 0.9); // 10% margin from bottom
1090
+ anchorY = Math.max(minY, Math.min(maxY, anchorY));
1091
+
1092
+ return {
1093
+ x: baseX,
1094
+ y: Math.floor(anchorY)
1095
+ };
1096
+ },
1097
+
1098
+ /**
1099
+ * Determines if a horizontal connection should use a straight line based on connection patterns.
1100
+ * Returns true only when both nodes have single dedicated connections (source has single outgoing, target has single incoming).
1101
+ *
1102
+ * @param {object} sourceNode - The source node
1103
+ * @param {object} targetNode - The target node
1104
+ * @param {string} connectionType - The connection type
1105
+ * @returns {boolean} True if straight line should be used, false if bends are needed
1106
+ * @private
1107
+ */
1108
+ _shouldUseHorizontalStraightLine: function (sourceNode, targetNode, connectionType) {
1109
+ const sourceAnalysis = this._analyzeNodeConnectionPatterns(sourceNode);
1110
+ const targetAnalysis = this._analyzeNodeConnectionPatterns(targetNode);
1111
+
1112
+ // Determine which sides are involved based on connection type
1113
+ let sourceOutgoingCount, targetIncomingCount;
1114
+
1115
+ switch (connectionType) {
1116
+ case "RightToLeft":
1117
+ sourceOutgoingCount = sourceAnalysis.rightOutgoing;
1118
+ targetIncomingCount = targetAnalysis.leftIncoming;
1119
+ break;
1120
+ case "LeftToRight":
1121
+ sourceOutgoingCount = sourceAnalysis.leftOutgoing;
1122
+ targetIncomingCount = targetAnalysis.rightIncoming;
1123
+ break;
1124
+ case "LeftToLeft":
1125
+ sourceOutgoingCount = sourceAnalysis.leftOutgoing;
1126
+ targetIncomingCount = targetAnalysis.leftIncoming;
1127
+ break;
1128
+ case "RightToRight":
1129
+ sourceOutgoingCount = sourceAnalysis.rightOutgoing;
1130
+ targetIncomingCount = targetAnalysis.rightIncoming;
1131
+ break;
1132
+ default:
1133
+ // Default to RightToLeft
1134
+ sourceOutgoingCount = sourceAnalysis.rightOutgoing;
1135
+ targetIncomingCount = targetAnalysis.leftIncoming;
1136
+ break;
1137
+ }
1138
+
1139
+ // Allow straight line only when both nodes have single dedicated connections
1140
+ return (sourceOutgoingCount === 1 && targetIncomingCount === 1);
1141
+ }
1142
+ };
1143
+ return ConnectionPathUtils;
1144
+ });