@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,870 @@
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(["sap/suite/ui/commons/library", "sap/base/Log"], function (library, Log) {
8
+ "use strict";
9
+
10
+ const ConnectionType = library.networkgraph.ConnectionType,
11
+ ComponentArrangement = library.networkgraph.ComponentArrangement;
12
+ const DEFAULT_HORIZONTAL_SPACING = 240;
13
+ const DEFAULT_VERTICAL_SPACING = 100;
14
+
15
+ /**
16
+ * Utility class for calculating node positions based on dependency relationships.
17
+ * This utility arranges nodes based on their dependency relationships using topological sorting,
18
+ * cycle detection, and layer-based positioning.
19
+ *
20
+ * @namespace sap.suite.ui.commons.networkgraph.util.DependencyLayoutHelper
21
+ * @public
22
+ * @since 1.144
23
+ */
24
+ const DependencyLayoutHelper = {
25
+ /**
26
+ * WeakMap to store last calculated positions and origin flag for automatic drag detection.
27
+ * Structure: {x: number, y: number, bIsUserPositioned: boolean}
28
+ */
29
+ _lastCalculated: new WeakMap(),
30
+
31
+ /**
32
+ * Calculates and sets positions for nodes without coordinates based on their dependency relationships.
33
+ * Uses topological sorting (Kahn's algorithm) and DFS-based cycle detection to arrange nodes
34
+ * in layers from left to right, ensuring no overlaps and minimal line crossings.
35
+ *
36
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
37
+ * @param {object} [mConfig] - Configuration object
38
+ * @param {number} [mConfig.horizontalSpacing=240] - Horizontal spacing between layers
39
+ * @param {number} [mConfig.verticalSpacing=100] - Vertical spacing between nodes
40
+ * @param {sap.suite.ui.commons.networkgraph.ComponentArrangement} [mConfig.componentArrangement=Horizontal] - Component arrangement: "Horizontal" or "Vertical"
41
+ * @public
42
+ */
43
+ calculatePositions: function (oGraph, mConfig) {
44
+ if (!oGraph) {
45
+ throw new Error("Graph instance is required");
46
+ }
47
+
48
+ const config = Object.assign(
49
+ {
50
+ horizontalSpacing: DEFAULT_HORIZONTAL_SPACING,
51
+ verticalSpacing: DEFAULT_VERTICAL_SPACING,
52
+ componentArrangement: ComponentArrangement.Horizontal,
53
+ },
54
+ mConfig || {}
55
+ );
56
+ try {
57
+ this._detectUserChanges(oGraph);
58
+ this._buildDependencyGraph(oGraph);
59
+ const aComponents = this._identifyComponents(oGraph);
60
+ this._processComponents(aComponents, config);
61
+ this._calculateCoordinates(oGraph, config);
62
+ this._adjustHorizontalSpacingForNodeWidths(oGraph, config);
63
+ this._adjustVerticalSpacingForNodeHeights(oGraph, config);
64
+ this._storeFinalPositions(oGraph);
65
+ } catch (e) {
66
+ Log.error("DependencyLayoutHelper error: " + e.message, e);
67
+ throw new Error("DependencyLayoutHelper error: " + e.message);
68
+ } finally {
69
+ if (this._lineMap) {
70
+ this._lineMap.clear();
71
+ this._lineMap = null;
72
+ }
73
+ }
74
+ },
75
+
76
+ /**
77
+ * Builds the dependency graph structure by analyzing node relationships.
78
+ * Initializes layout data for each node and categorizes connections.
79
+ *
80
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
81
+ * @private
82
+ */
83
+ _buildDependencyGraph: function (oGraph) {
84
+ const aNodes = oGraph.getNodes();
85
+ const aLines = oGraph.getLines();
86
+ const mNodeMap = new Map();
87
+
88
+ aNodes.forEach((oNode) => {
89
+ mNodeMap.set(oNode.getKey(), oNode);
90
+ const fX = oNode.getX();
91
+ const fY = oNode.getY();
92
+ const oLastPos = this._lastCalculated.get(oNode);
93
+ // Only preserve coordinates if they were original or user-dragged
94
+ const bShouldPreserve = oLastPos?.bIsUserPositioned;
95
+
96
+ oNode._layoutData = {
97
+ layer: -1,
98
+ position: -1,
99
+ incoming: [],
100
+ outgoing: [],
101
+ parallelNodes: [],
102
+ backwardEdges: [],
103
+ backwardEdgeLines: [],
104
+ isBackwardSource: false,
105
+ bShouldPreserve,
106
+ userX: fX,
107
+ userY: fY
108
+ };
109
+ });
110
+
111
+ if (this._lineMap) {
112
+ this._lineMap.clear();
113
+ }
114
+ // Store line references for backward edge detection
115
+ this._lineMap = new Map();
116
+
117
+ aLines.forEach((oLine) => {
118
+ const oFromNode = mNodeMap.get(oLine.getFrom());
119
+ const oToNode = mNodeMap.get(oLine.getTo());
120
+
121
+ if (oFromNode && oToNode) {
122
+ const sConnectionType = oLine.getConnectionType();
123
+ const sFromKey = oFromNode.getKey();
124
+ const sToKey = oToNode.getKey();
125
+
126
+ if (!this._lineMap.has(sFromKey)) {
127
+ this._lineMap.set(sFromKey, new Map());
128
+ }
129
+ this._lineMap.get(sFromKey).set(sToKey, oLine);
130
+
131
+ switch (sConnectionType) {
132
+ case ConnectionType.RightToRight:
133
+ case ConnectionType.LeftToLeft:
134
+ oFromNode._layoutData.parallelNodes.push(oToNode);
135
+ oToNode._layoutData.parallelNodes.push(oFromNode);
136
+ break;
137
+ case ConnectionType.RightToLeft:
138
+ case ConnectionType.LeftToRight:
139
+ default:
140
+ oFromNode._layoutData.outgoing.push(oToNode);
141
+ oToNode._layoutData.incoming.push(oFromNode);
142
+ break;
143
+ }
144
+ }
145
+ });
146
+
147
+ this._detectAndRemoveBackwardEdges(aNodes);
148
+ },
149
+
150
+ /**
151
+ * Detects and temporarily removes backward edges that create cycles using DFS.
152
+ * Identifies edges that point to already visited ancestors in the DFS traversal.
153
+ *
154
+ * @param {sap.suite.ui.commons.networkgraph.Node[]} aNodes - Array of graph nodes
155
+ * @private
156
+ */
157
+ _detectAndRemoveBackwardEdges: function (aNodes) {
158
+ const mVisiting = {};
159
+ const mVisited = {};
160
+
161
+ const dfsVisit = (oNode) => {
162
+ const sKey = oNode.getKey();
163
+ if (mVisited[sKey]) {
164
+ return;
165
+ }
166
+
167
+ mVisiting[sKey] = true;
168
+
169
+ const aRemainingOutgoing = [];
170
+ oNode._layoutData.outgoing.forEach((oTargetNode) => {
171
+ const sTargetKey = oTargetNode.getKey();
172
+
173
+ if (mVisiting[sTargetKey]) {
174
+ // Backward edge detected
175
+ oNode._layoutData.isBackwardSource = true;
176
+ oNode._layoutData.backwardEdges.push(oTargetNode);
177
+
178
+ const oLine = this._lineMap.get(sKey)?.get(sTargetKey);
179
+ if (oLine) {
180
+ oNode._layoutData.backwardEdgeLines.push(oLine);
181
+ }
182
+
183
+ const idx = oTargetNode._layoutData.incoming.indexOf(oNode);
184
+ if (idx > -1) {
185
+ oTargetNode._layoutData.incoming.splice(idx, 1);
186
+ }
187
+ } else {
188
+ aRemainingOutgoing.push(oTargetNode);
189
+ if (!mVisited[sTargetKey]) {
190
+ dfsVisit(oTargetNode);
191
+ }
192
+ }
193
+ });
194
+
195
+ oNode._layoutData.outgoing = aRemainingOutgoing;
196
+ mVisiting[sKey] = false;
197
+ mVisited[sKey] = true;
198
+ };
199
+
200
+ aNodes.forEach((oNode) => {
201
+ if (!mVisited[oNode.getKey()]) {
202
+ dfsVisit(oNode);
203
+ }
204
+ });
205
+ },
206
+
207
+ /**
208
+ * Identifies separate connected components in the graph using BFS.
209
+ * A component is a group of nodes that are connected by dependencies.
210
+ *
211
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
212
+ * @returns {Array<sap.suite.ui.commons.networkgraph.Node[]>} Array of components (groups of connected nodes)
213
+ * @private
214
+ */
215
+ _identifyComponents: function (oGraph) {
216
+ const aNodes = oGraph.getNodes();
217
+ const mVisited = {};
218
+ const aComponents = [];
219
+
220
+ const addConnectedNodesToQueue = (oNode, aQueue) => {
221
+ const aConnectionTypes = ["incoming", "outgoing", "parallelNodes"];
222
+ aConnectionTypes.forEach((sType) => {
223
+ oNode._layoutData[sType].forEach((oConnectedNode) => {
224
+ const sKey = oConnectedNode.getKey();
225
+ if (!mVisited[sKey]) {
226
+ aQueue.push(oConnectedNode);
227
+ }
228
+ });
229
+ });
230
+ };
231
+
232
+ aNodes.forEach((oNode) => {
233
+ const sKey = oNode.getKey();
234
+ if (mVisited[sKey]) {
235
+ return;
236
+ }
237
+
238
+ const aComponent = [];
239
+ const aQueue = [oNode];
240
+
241
+ while (aQueue.length > 0) {
242
+ const oCurrent = aQueue.shift();
243
+ const sCurrentKey = oCurrent.getKey();
244
+
245
+ if (!mVisited[sCurrentKey]) {
246
+ mVisited[sCurrentKey] = true;
247
+ aComponent.push(oCurrent);
248
+ addConnectedNodesToQueue(oCurrent, aQueue);
249
+ }
250
+ }
251
+
252
+ aComponents.push(aComponent);
253
+ });
254
+
255
+ return aComponents;
256
+ },
257
+
258
+ /**
259
+ * Processes all components: separates isolated nodes and arranges connected components.
260
+ *
261
+ * @param {Array<sap.suite.ui.commons.networkgraph.Node[]>} aComponents - Array of components (groups of connected nodes)
262
+ * @param {object} config - Configuration object
263
+ * @private
264
+ */
265
+ _processComponents: function (aComponents, { componentArrangement }) {
266
+ const aIsolatedNodes = [];
267
+ const aConnectedComponents = [];
268
+
269
+ aComponents.forEach((aComponentNodes) => {
270
+ const bIsIsolated =
271
+ aComponentNodes.length === 1 &&
272
+ aComponentNodes[0]._layoutData.incoming.length === 0 &&
273
+ aComponentNodes[0]._layoutData.outgoing.length === 0 &&
274
+ !aComponentNodes[0]._layoutData.bShouldPreserve;
275
+
276
+ if (bIsIsolated) {
277
+ aIsolatedNodes.push(aComponentNodes[0]);
278
+ } else {
279
+ aConnectedComponents.push(aComponentNodes);
280
+ }
281
+ });
282
+ const bSideBySide = componentArrangement !== ComponentArrangement.Vertical;
283
+ let iMaxVerticalPosition = 0;
284
+ let iMaxLayerAcrossAll = 0;
285
+
286
+ aConnectedComponents.forEach((aComponentNodes, iCompIndex) => {
287
+ this._assignLayersForComponent(aComponentNodes, iCompIndex);
288
+ this._adjustParallelLayersForComponent(aComponentNodes);
289
+ this._assignVerticalPositionsForComponent(aComponentNodes);
290
+
291
+ if (bSideBySide) {
292
+ aComponentNodes.forEach((oNode) => {
293
+ oNode._layoutData.layer += iMaxLayerAcrossAll;
294
+ oNode._layoutData.componentOffset = 0;
295
+ });
296
+
297
+ let iComponentMaxLayer = -1;
298
+ aComponentNodes.forEach((n) => {
299
+ if (n._layoutData.layer > iComponentMaxLayer) {
300
+ iComponentMaxLayer = n._layoutData.layer;
301
+ }
302
+ });
303
+ iMaxLayerAcrossAll = iComponentMaxLayer + 1;
304
+ } else {
305
+ aComponentNodes.forEach((oNode) => {
306
+ oNode._layoutData.position += iMaxVerticalPosition;
307
+ oNode._layoutData.componentOffset = 0;
308
+ });
309
+
310
+ let iComponentMaxPosition = -1;
311
+ aComponentNodes.forEach((n) => {
312
+ if (n._layoutData.position > iComponentMaxPosition) {
313
+ iComponentMaxPosition = n._layoutData.position;
314
+ }
315
+ });
316
+ iMaxVerticalPosition = iComponentMaxPosition + 1;
317
+
318
+ let iComponentMaxLayer = -1;
319
+ aComponentNodes.forEach((n) => {
320
+ if (n._layoutData.layer > iComponentMaxLayer) {
321
+ iComponentMaxLayer = n._layoutData.layer;
322
+ }
323
+ });
324
+ iMaxLayerAcrossAll = Math.max(iMaxLayerAcrossAll, iComponentMaxLayer);
325
+ }
326
+ });
327
+
328
+ if (aIsolatedNodes.length > 0) {
329
+ const iRightSideOffset = iMaxLayerAcrossAll + 1;
330
+
331
+ aIsolatedNodes.forEach((oNode, idx) => {
332
+ if (bSideBySide) {
333
+ oNode._layoutData.layer = Math.floor(idx / 2);
334
+ oNode._layoutData.position = idx % 2;
335
+ oNode._layoutData.componentOffset = iMaxLayerAcrossAll;
336
+ } else {
337
+ oNode._layoutData.layer = idx % 2;
338
+ oNode._layoutData.position = Math.floor(idx / 2);
339
+ oNode._layoutData.componentOffset = iRightSideOffset;
340
+ }
341
+ });
342
+ }
343
+ },
344
+
345
+ /**
346
+ * Assigns layer numbers to nodes in a component (group of connected nodes) using Kahn's algorithm (topological sorting).
347
+ *
348
+ * @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component
349
+ * @param {number} iComponentId - Component identifier
350
+ * @private
351
+ */
352
+ _assignLayersForComponent: function (aComponentNodes, iComponentId) {
353
+ const aQueue = [];
354
+ const mInDegree = {};
355
+
356
+ aComponentNodes.forEach((oNode) => {
357
+ oNode._layoutData.componentId = iComponentId;
358
+ const iInDegree = oNode._layoutData.incoming.length;
359
+ mInDegree[oNode.getKey()] = iInDegree;
360
+
361
+ if (iInDegree === 0) {
362
+ oNode._layoutData.layer = 0;
363
+ aQueue.push(oNode);
364
+
365
+ oNode._layoutData.parallelNodes.forEach((oParallelNode) => {
366
+ if (oParallelNode._layoutData.layer === -1) {
367
+ oParallelNode._layoutData.layer = 0;
368
+ }
369
+ });
370
+ }
371
+ });
372
+
373
+ while (aQueue.length > 0) {
374
+ const oCurrentNode = aQueue.shift();
375
+
376
+ oCurrentNode._layoutData.outgoing.forEach((oTargetNode) => {
377
+ const sTargetKey = oTargetNode.getKey();
378
+ mInDegree[sTargetKey]--;
379
+
380
+ oTargetNode._layoutData.layer = Math.max(
381
+ oTargetNode._layoutData.layer,
382
+ oCurrentNode._layoutData.layer + 1
383
+ );
384
+
385
+ if (mInDegree[sTargetKey] === 0) {
386
+ aQueue.push(oTargetNode);
387
+ }
388
+ });
389
+ }
390
+
391
+ const aUnprocessedNodes = aComponentNodes.filter(
392
+ (n) => n._layoutData.layer === -1
393
+ );
394
+
395
+ if (aUnprocessedNodes.length > 0) {
396
+ const aProcessedNodes = aComponentNodes.filter(
397
+ (n) => n._layoutData.layer >= 0
398
+ );
399
+ let iMinLayer = 0;
400
+ if (aProcessedNodes.length > 0) {
401
+ let iMaxLayer = -1;
402
+ aProcessedNodes.forEach((n) => {
403
+ if (n._layoutData.layer > iMaxLayer) {
404
+ iMaxLayer = n._layoutData.layer;
405
+ }
406
+ });
407
+ iMinLayer = iMaxLayer + 1;
408
+ }
409
+
410
+ aUnprocessedNodes.forEach((oNode) => {
411
+ oNode._layoutData.layer = iMinLayer;
412
+ });
413
+ }
414
+
415
+ aComponentNodes.forEach((oNode) => {
416
+ if (
417
+ oNode._layoutData.incoming.length === 0 &&
418
+ oNode._layoutData.outgoing.length > 0
419
+ ) {
420
+ const iCurrentLayer = oNode._layoutData.layer;
421
+ let iMaxTargetLayer = -1;
422
+ oNode._layoutData.outgoing.forEach((t) => {
423
+ if (t._layoutData.layer > iMaxTargetLayer) {
424
+ iMaxTargetLayer = t._layoutData.layer;
425
+ }
426
+ });
427
+
428
+ const bTargetHasOtherIncoming = oNode._layoutData.outgoing.some(
429
+ (t) => t._layoutData.incoming.filter((n) => n !== oNode).length > 0
430
+ );
431
+
432
+ if (iMaxTargetLayer > iCurrentLayer && bTargetHasOtherIncoming) {
433
+ oNode._layoutData.layer = Math.max(0, iMaxTargetLayer - 1);
434
+ }
435
+ }
436
+ });
437
+ },
438
+
439
+ /**
440
+ * Ensures parallel nodes (same-layer dependencies) stay in the same layer.
441
+ *
442
+ * @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component (group of connected nodes)
443
+ * @private
444
+ */
445
+ _adjustParallelLayersForComponent: function (aComponentNodes) {
446
+ const ensureParallelNodesInSameLayer = (oNode) => {
447
+ oNode._layoutData.parallelNodes.forEach((oParallelNode) => {
448
+ if (
449
+ oParallelNode._layoutData.layer === -1 ||
450
+ oParallelNode._layoutData.layer > oNode._layoutData.layer
451
+ ) {
452
+ oParallelNode._layoutData.layer = oNode._layoutData.layer;
453
+ }
454
+ });
455
+ };
456
+
457
+ aComponentNodes.forEach((oNode) => {
458
+ if (oNode._layoutData.layer >= 0) {
459
+ ensureParallelNodesInSameLayer(oNode);
460
+ }
461
+ });
462
+
463
+ let bChanged = true;
464
+ while (bChanged) {
465
+ bChanged = false;
466
+
467
+ aComponentNodes.forEach((oNode) => {
468
+ oNode._layoutData.parallelNodes.forEach((oParallelNode) => {
469
+ if (oNode._layoutData.layer !== oParallelNode._layoutData.layer) {
470
+ const iMinLayer = Math.min(
471
+ oNode._layoutData.layer,
472
+ oParallelNode._layoutData.layer
473
+ );
474
+ if (
475
+ oNode._layoutData.layer !== iMinLayer ||
476
+ oParallelNode._layoutData.layer !== iMinLayer
477
+ ) {
478
+ oNode._layoutData.layer = iMinLayer;
479
+ oParallelNode._layoutData.layer = iMinLayer;
480
+ bChanged = true;
481
+ }
482
+ }
483
+ });
484
+ });
485
+ }
486
+ },
487
+
488
+ /**
489
+ * Assigns vertical positions within layers, inheriting parent positions where possible.
490
+ *
491
+ * @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component (group of connected nodes)
492
+ * @private
493
+ */
494
+ _assignVerticalPositionsForComponent: function (aComponentNodes) {
495
+ const mLayers = this._groupNodesByLayer(aComponentNodes);
496
+ const aLayerKeys = Object.keys(mLayers)
497
+ .map((k) => parseInt(k))
498
+ .sort((a, b) => a - b);
499
+
500
+ aLayerKeys.forEach((iLayer) => {
501
+ if (iLayer > 0) {
502
+ this._positionLayerNodes(mLayers, iLayer);
503
+ }
504
+ });
505
+
506
+ if (mLayers[0]) {
507
+ const mUsedPositions = {};
508
+ let iNextAvailablePosition = 0;
509
+
510
+ const updateChainPositions = (
511
+ oNode,
512
+ iNewPosition,
513
+ mVisitedNodes = {}
514
+ ) => {
515
+ if (mVisitedNodes[oNode.getKey()]) {
516
+ return;
517
+ }
518
+ mVisitedNodes[oNode.getKey()] = true;
519
+
520
+ oNode._layoutData.position = iNewPosition;
521
+
522
+ oNode._layoutData.outgoing.forEach((oSuccessor) => {
523
+ const aSuccessorIncoming = oSuccessor._layoutData.incoming;
524
+ if (
525
+ aSuccessorIncoming.length === 1 &&
526
+ aSuccessorIncoming[0] === oNode
527
+ ) {
528
+ updateChainPositions(oSuccessor, iNewPosition, mVisitedNodes);
529
+ }
530
+ });
531
+ };
532
+
533
+ const aRootNodes = mLayers[0].sort((a, b) => {
534
+ const aHasTarget = a._layoutData.outgoing.length > 0;
535
+ const bHasTarget = b._layoutData.outgoing.length > 0;
536
+
537
+ if (!aHasTarget && !bHasTarget) return 0;
538
+ if (!aHasTarget) return 1;
539
+ if (!bHasTarget) return -1;
540
+
541
+ const aTargetPos = a._layoutData.outgoing[0]._layoutData.position;
542
+ const bTargetPos = b._layoutData.outgoing[0]._layoutData.position;
543
+ return aTargetPos - bTargetPos;
544
+ });
545
+
546
+ aRootNodes.forEach((oRootNode) => {
547
+ if (oRootNode._layoutData.outgoing.length > 0) {
548
+ const oFirstTarget = oRootNode._layoutData.outgoing[0];
549
+ const iPreferredPosition = oFirstTarget._layoutData.position;
550
+
551
+ if (!mUsedPositions[iPreferredPosition]) {
552
+ oRootNode._layoutData.position = iPreferredPosition;
553
+ mUsedPositions[iPreferredPosition] = true;
554
+ iNextAvailablePosition = Math.max(
555
+ iNextAvailablePosition,
556
+ iPreferredPosition + 1
557
+ );
558
+ } else {
559
+ const aTargetIncoming = oFirstTarget._layoutData.incoming;
560
+ if (
561
+ aTargetIncoming.length === 1 &&
562
+ aTargetIncoming[0] === oRootNode
563
+ ) {
564
+ let iNewPosition = iPreferredPosition;
565
+ while (mUsedPositions[iNewPosition]) {
566
+ iNewPosition++;
567
+ }
568
+ updateChainPositions(oFirstTarget, iNewPosition);
569
+ oRootNode._layoutData.position = iNewPosition;
570
+ mUsedPositions[iNewPosition] = true;
571
+ iNextAvailablePosition = Math.max(
572
+ iNextAvailablePosition,
573
+ iNewPosition + 1
574
+ );
575
+ } else {
576
+ let iAdjacentPosition = iPreferredPosition + 1;
577
+ while (mUsedPositions[iAdjacentPosition]) {
578
+ iAdjacentPosition++;
579
+ }
580
+ oRootNode._layoutData.position = iAdjacentPosition;
581
+ mUsedPositions[iAdjacentPosition] = true;
582
+ iNextAvailablePosition = Math.max(
583
+ iNextAvailablePosition,
584
+ iAdjacentPosition + 1
585
+ );
586
+ }
587
+ }
588
+ } else {
589
+ while (mUsedPositions[iNextAvailablePosition]) {
590
+ iNextAvailablePosition++;
591
+ }
592
+ oRootNode._layoutData.position = iNextAvailablePosition;
593
+ mUsedPositions[iNextAvailablePosition] = true;
594
+ iNextAvailablePosition++;
595
+ }
596
+ });
597
+ }
598
+ },
599
+
600
+ /**
601
+ * Groups nodes by their assigned layer.
602
+ *
603
+ * @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component (group of connected nodes)
604
+ * @returns {object} Map of layer number to array of nodes
605
+ * @private
606
+ */
607
+ _groupNodesByLayer: function (aComponentNodes) {
608
+ const mLayers = {};
609
+
610
+ aComponentNodes.forEach((oNode) => {
611
+ const iLayer = oNode._layoutData.layer;
612
+ if (!mLayers[iLayer]) {
613
+ mLayers[iLayer] = [];
614
+ }
615
+ mLayers[iLayer].push(oNode);
616
+ oNode._layoutData.position = -1;
617
+ });
618
+
619
+ return mLayers;
620
+ },
621
+
622
+ /**
623
+ * Positions nodes within a specific layer based on their predecessors.
624
+ *
625
+ * @param {object} mLayers - Map of layer number to array of nodes
626
+ * @param {number} iLayer - The layer to position
627
+ * @private
628
+ */
629
+ _positionLayerNodes: function (mLayers, iLayer) {
630
+ const aLayerNodes = mLayers[iLayer];
631
+ const mUsedPositions = {};
632
+ let iNextAvailablePosition = 0;
633
+
634
+ const aPrevLayerNodes = (mLayers[iLayer - 1] || [])
635
+ .filter((oNode) => oNode._layoutData.position >= 0)
636
+ .sort((a, b) => a._layoutData.position - b._layoutData.position);
637
+
638
+ aPrevLayerNodes.forEach((oPrevNode) => {
639
+ const aSuccessors = oPrevNode._layoutData.outgoing
640
+ .filter((oNode) => oNode._layoutData.layer === iLayer)
641
+ .sort((a, b) => a.getKey().localeCompare(b.getKey()));
642
+
643
+ if (aSuccessors.length === 0) {
644
+ return;
645
+ }
646
+
647
+ const iPreferredPosition = oPrevNode._layoutData.position;
648
+ if (
649
+ !mUsedPositions[iPreferredPosition] &&
650
+ aSuccessors[0]._layoutData.position === -1
651
+ ) {
652
+ aSuccessors[0]._layoutData.position = iPreferredPosition;
653
+ mUsedPositions[iPreferredPosition] = true;
654
+ iNextAvailablePosition = Math.max(
655
+ iNextAvailablePosition,
656
+ iPreferredPosition + 1
657
+ );
658
+ }
659
+
660
+ aSuccessors.forEach((oNode) => {
661
+ if (oNode._layoutData.position === -1) {
662
+ while (mUsedPositions[iNextAvailablePosition]) {
663
+ iNextAvailablePosition++;
664
+ }
665
+ oNode._layoutData.position = iNextAvailablePosition;
666
+ mUsedPositions[iNextAvailablePosition] = true;
667
+ iNextAvailablePosition++;
668
+ }
669
+ });
670
+ });
671
+
672
+ aLayerNodes.forEach((oNode) => {
673
+ if (oNode._layoutData.position === -1) {
674
+ while (mUsedPositions[iNextAvailablePosition]) {
675
+ iNextAvailablePosition++;
676
+ }
677
+ oNode._layoutData.position = iNextAvailablePosition;
678
+ mUsedPositions[iNextAvailablePosition] = true;
679
+ iNextAvailablePosition++;
680
+ }
681
+ });
682
+ },
683
+
684
+ /**
685
+ * Calculates final X and Y coordinates for all nodes based on layer and position.
686
+ *
687
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
688
+ * @param {object} config - Configuration with spacing values
689
+ * @private
690
+ */
691
+ _calculateCoordinates: function (oGraph, { horizontalSpacing, verticalSpacing }) {
692
+ const aNodes = oGraph.getNodes();
693
+ const iHSpacing = horizontalSpacing;
694
+ const iVSpacing = verticalSpacing;
695
+
696
+ aNodes.forEach((oNode) => {
697
+ const iComponentOffset = oNode._layoutData.componentOffset || 0;
698
+ oNode.setX(
699
+ iHSpacing + (oNode._layoutData.layer + iComponentOffset) * iHSpacing
700
+ );
701
+ oNode.setY(iVSpacing + oNode._layoutData.position * iVSpacing);
702
+ });
703
+
704
+ aNodes.forEach((oNode) => {
705
+ if (oNode._layoutData.bShouldPreserve) {
706
+ const { userX, userY } = oNode._layoutData;
707
+ oNode.setX(userX);
708
+ oNode.setY(userY);
709
+ }
710
+ });
711
+ },
712
+
713
+ /**
714
+ * Adjusts horizontal spacing to accommodate nodes with varying widths.
715
+ *
716
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
717
+ * @private
718
+ */
719
+ _adjustHorizontalSpacingForNodeWidths: function (oGraph) {
720
+ const aNodes = oGraph.getNodes();
721
+ const mLayerWidths = {};
722
+ let iMaxLayer = -1;
723
+
724
+ aNodes.forEach((oNode) => {
725
+ if (!oNode._layoutData.bShouldPreserve) {
726
+ const iLayer =
727
+ oNode._layoutData.layer + (oNode._layoutData.componentOffset || 0);
728
+ iMaxLayer = Math.max(iMaxLayer, iLayer);
729
+
730
+ if (oNode._iWidth) {
731
+ const iWidth = oNode._iWidth;
732
+ if (!mLayerWidths[iLayer] || mLayerWidths[iLayer] < iWidth) {
733
+ mLayerWidths[iLayer] = iWidth;
734
+ }
735
+ }
736
+ }
737
+ });
738
+
739
+ const mLayerOffsets = {};
740
+ let iCumulativeOffset = 0;
741
+
742
+ for (let iLayer = 0; iLayer <= iMaxLayer; iLayer++) {
743
+ mLayerOffsets[iLayer] = iCumulativeOffset;
744
+ const iActualWidth = mLayerWidths[iLayer];
745
+ if (iActualWidth) {
746
+ iCumulativeOffset += iActualWidth;
747
+ }
748
+ }
749
+
750
+ aNodes.forEach((oNode) => {
751
+ if (!oNode._layoutData.bShouldPreserve) {
752
+ const iLayer =
753
+ oNode._layoutData.layer + (oNode._layoutData.componentOffset || 0);
754
+ const iOffset = mLayerOffsets[iLayer] || 0;
755
+ oNode.setX(oNode.getX() + iOffset);
756
+ }
757
+ });
758
+ },
759
+
760
+ /**
761
+ * Adjusts vertical spacing to accommodate nodes with varying heights.
762
+ *
763
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
764
+ * @private
765
+ */
766
+ _adjustVerticalSpacingForNodeHeights: function (oGraph) {
767
+ const aNodes = oGraph.getNodes();
768
+ const mPositionHeights = {};
769
+ const mAllPositions = {};
770
+
771
+ aNodes.forEach((oNode) => {
772
+ if (!oNode._layoutData.bShouldPreserve) {
773
+ const iPosition = oNode._layoutData.position;
774
+ mAllPositions[iPosition] = true;
775
+
776
+ if (oNode._iHeight) {
777
+ const iHeight = oNode._iHeight;
778
+ if (
779
+ !mPositionHeights[iPosition] ||
780
+ mPositionHeights[iPosition] < iHeight
781
+ ) {
782
+ mPositionHeights[iPosition] = iHeight;
783
+ }
784
+ }
785
+ }
786
+ });
787
+
788
+ const aAllPositionKeys = Object.keys(mAllPositions)
789
+ .map((k) => parseInt(k))
790
+ .sort((a, b) => a - b);
791
+ const mPositionOffsets = {};
792
+ let iCumulativeOffset = 0;
793
+
794
+ aAllPositionKeys.forEach((iPosition) => {
795
+ mPositionOffsets[iPosition] = iCumulativeOffset;
796
+ const iActualHeight = mPositionHeights[iPosition];
797
+ if (iActualHeight) {
798
+ iCumulativeOffset += iActualHeight;
799
+ }
800
+ });
801
+
802
+ aNodes.forEach((oNode) => {
803
+ if (!oNode._layoutData.bShouldPreserve) {
804
+ const iPosition = oNode._layoutData.position;
805
+ const iOffset = mPositionOffsets[iPosition] || 0;
806
+ oNode.setY(oNode.getY() + iOffset);
807
+ }
808
+ });
809
+ },
810
+
811
+ /**
812
+ * Stores final calculated positions for automatic drag detection on next call.
813
+ * Only stores positions for nodes that were calculated (not original/dragged).
814
+ *
815
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
816
+ * @private
817
+ */
818
+ _storeFinalPositions: function (oGraph) {
819
+ const aNodes = oGraph.getNodes();
820
+
821
+ aNodes.forEach((oNode) => {
822
+ if (!oNode._layoutData.bShouldPreserve) {
823
+ this._lastCalculated.set(oNode, {
824
+ x: oNode.getX(),
825
+ y: oNode.getY(),
826
+ bIsUserPositioned: false
827
+ });
828
+ }
829
+ });
830
+ },
831
+
832
+ /**
833
+ * Detects user-made position changes by comparing current positions with last calculated positions.
834
+ * Marks nodes with original coordinates and tracks drag operations.
835
+ *
836
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
837
+ * @private
838
+ */
839
+ _detectUserChanges: function (oGraph) {
840
+ const aNodes = oGraph.getNodes();
841
+
842
+ aNodes.forEach((oNode) => {
843
+ const fX = oNode.getX();
844
+ const fY = oNode.getY();
845
+ // Constraint: Nodes with coordinates (0,0) or negative values are treated as unpositioned.
846
+ // For origin positioning, use (0.1, 0.1) or (1, 1).
847
+ const bHasCoordinates = fX !== undefined && fY !== undefined && (fX !== 0 || fY !== 0);
848
+ const oLastPos = this._lastCalculated.get(oNode);
849
+
850
+ if (!oLastPos && bHasCoordinates) {
851
+ // First time seeing this node with coordinates - mark as original
852
+ this._lastCalculated.set(oNode, {
853
+ x: fX,
854
+ y: fY,
855
+ bIsUserPositioned: true
856
+ });
857
+ } else if (oLastPos) {
858
+ // Check if user dragged it
859
+ if (fX !== oLastPos.x || fY !== oLastPos.y) {
860
+ oLastPos.x = fX;
861
+ oLastPos.y = fY;
862
+ oLastPos.bIsUserPositioned = true;
863
+ }
864
+ }
865
+ });
866
+ },
867
+ };
868
+
869
+ return DependencyLayoutHelper;
870
+ });