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

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 (68) hide show
  1. package/package.json +1 -1
  2. package/src/sap/suite/ui/commons/.library +1 -1
  3. package/src/sap/suite/ui/commons/AriaProperties.js +1 -1
  4. package/src/sap/suite/ui/commons/CalculationBuilder.js +1 -1
  5. package/src/sap/suite/ui/commons/CalculationBuilderExpression.js +1 -1
  6. package/src/sap/suite/ui/commons/CalculationBuilderFunction.js +1 -1
  7. package/src/sap/suite/ui/commons/CalculationBuilderGroup.js +1 -1
  8. package/src/sap/suite/ui/commons/CalculationBuilderItem.js +1 -1
  9. package/src/sap/suite/ui/commons/CalculationBuilderValidationResult.js +1 -1
  10. package/src/sap/suite/ui/commons/CalculationBuilderVariable.js +1 -1
  11. package/src/sap/suite/ui/commons/CloudFilePicker.js +1 -1
  12. package/src/sap/suite/ui/commons/MicroProcessFlow.js +1 -1
  13. package/src/sap/suite/ui/commons/MicroProcessFlowItem.js +1 -1
  14. package/src/sap/suite/ui/commons/TimelineRenderManager.js +8 -4
  15. package/src/sap/suite/ui/commons/flexibility/changeHandler/PropertyChangeMapper.js +1 -1
  16. package/src/sap/suite/ui/commons/imageeditor/CropCustomShapeHistoryItem.js +1 -1
  17. package/src/sap/suite/ui/commons/imageeditor/CropEllipseHistoryItem.js +1 -1
  18. package/src/sap/suite/ui/commons/imageeditor/CropRectangleHistoryItem.js +1 -1
  19. package/src/sap/suite/ui/commons/imageeditor/CustomSizeItem.js +1 -1
  20. package/src/sap/suite/ui/commons/imageeditor/FilterHistoryItem.js +1 -1
  21. package/src/sap/suite/ui/commons/imageeditor/FlipHistoryItem.js +1 -1
  22. package/src/sap/suite/ui/commons/imageeditor/HistoryItem.js +1 -1
  23. package/src/sap/suite/ui/commons/imageeditor/ImageEditor.js +1 -1
  24. package/src/sap/suite/ui/commons/imageeditor/ImageEditorContainer.js +1 -1
  25. package/src/sap/suite/ui/commons/imageeditor/ImageEditorResponsiveContainer.js +1 -1
  26. package/src/sap/suite/ui/commons/imageeditor/ResizeHistoryItem.js +1 -1
  27. package/src/sap/suite/ui/commons/imageeditor/RotateHistoryItem.js +1 -1
  28. package/src/sap/suite/ui/commons/library.js +100 -3
  29. package/src/sap/suite/ui/commons/messagebundle.properties +73 -12
  30. package/src/sap/suite/ui/commons/messagebundle_en_US_saprigi.properties +39 -5
  31. package/src/sap/suite/ui/commons/messagebundle_fr_CA.properties +1 -1
  32. package/src/sap/suite/ui/commons/networkgraph/ElementBase.js +19 -1
  33. package/src/sap/suite/ui/commons/networkgraph/Graph.js +554 -45
  34. package/src/sap/suite/ui/commons/networkgraph/GraphMap.js +25 -3
  35. package/src/sap/suite/ui/commons/networkgraph/GraphRenderer.js +19 -8
  36. package/src/sap/suite/ui/commons/networkgraph/KeyboardNavigator.js +367 -12
  37. package/src/sap/suite/ui/commons/networkgraph/Line.js +814 -22
  38. package/src/sap/suite/ui/commons/networkgraph/Node.js +573 -79
  39. package/src/sap/suite/ui/commons/networkgraph/Tooltip.js +4 -0
  40. package/src/sap/suite/ui/commons/networkgraph/Utils.js +249 -10
  41. package/src/sap/suite/ui/commons/networkgraph/layout/NoopLayout.js +77 -7
  42. package/src/sap/suite/ui/commons/networkgraph/util/ConnectionPathUtils.js +1174 -0
  43. package/src/sap/suite/ui/commons/networkgraph/util/CreateConnectionPopover.js +374 -0
  44. package/src/sap/suite/ui/commons/networkgraph/util/DependencyLayoutHelper.js +1017 -0
  45. package/src/sap/suite/ui/commons/networkgraph/util/DragDropManager.js +721 -0
  46. package/src/sap/suite/ui/commons/networkgraph/util/PortManager.js +582 -0
  47. package/src/sap/suite/ui/commons/statusindicator/Circle.js +1 -1
  48. package/src/sap/suite/ui/commons/statusindicator/CustomShape.js +1 -1
  49. package/src/sap/suite/ui/commons/statusindicator/DiscreteThreshold.js +1 -1
  50. package/src/sap/suite/ui/commons/statusindicator/FillingOption.js +1 -1
  51. package/src/sap/suite/ui/commons/statusindicator/LibraryShape.js +1 -1
  52. package/src/sap/suite/ui/commons/statusindicator/Path.js +1 -1
  53. package/src/sap/suite/ui/commons/statusindicator/PropertyThreshold.js +1 -1
  54. package/src/sap/suite/ui/commons/statusindicator/Rectangle.js +1 -1
  55. package/src/sap/suite/ui/commons/statusindicator/Shape.js +1 -1
  56. package/src/sap/suite/ui/commons/statusindicator/ShapeGroup.js +1 -1
  57. package/src/sap/suite/ui/commons/statusindicator/SimpleShape.js +1 -1
  58. package/src/sap/suite/ui/commons/statusindicator/StatusIndicator.js +1 -1
  59. package/src/sap/suite/ui/commons/taccount/TAccount.js +1 -1
  60. package/src/sap/suite/ui/commons/taccount/TAccountGroup.js +1 -1
  61. package/src/sap/suite/ui/commons/taccount/TAccountItem.js +1 -1
  62. package/src/sap/suite/ui/commons/taccount/TAccountItemProperty.js +1 -1
  63. package/src/sap/suite/ui/commons/taccount/TAccountPanel.js +1 -1
  64. package/src/sap/suite/ui/commons/themes/base/NetworkGraph.less +26 -13
  65. package/src/sap/suite/ui/commons/themes/base/NetworkGroup.less +4 -0
  66. package/src/sap/suite/ui/commons/themes/base/NetworkLine.less +58 -13
  67. package/src/sap/suite/ui/commons/themes/base/NetworkNode.less +251 -47
  68. package/src/sap/suite/ui/commons/themes/base/SemanticColorMixins.less +55 -0
@@ -0,0 +1,721 @@
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/ui/core/dnd/DragInfo",
9
+ "sap/ui/core/dnd/DropInfo",
10
+ "sap/base/Log",
11
+ "sap/suite/ui/commons/networkgraph/Utils"
12
+ ], function (DragInfo, DropInfo, Log, Utils) {
13
+ "use strict";
14
+
15
+ // constants
16
+ const RESIZE_CONFIG = {
17
+ WIDTH_INCREMENT: 150,
18
+ HEIGHT_INCREMENT: 150,
19
+ PROXIMITY_THRESHOLD: 50, // TODO: update according to figma take node's width /height into consideration
20
+ RESIZE_DEBOUNCE_DELAY: 150,
21
+ // Auto-scroll configuration
22
+ AUTO_SCROLL_THRESHOLD: 50, // Distance from scroller edge to trigger auto-scroll
23
+ AUTO_SCROLL_SPEED: 10, // Pixels to scroll per frame (16ms)
24
+ };
25
+
26
+ /**
27
+ * Minimum position offsets to ensure action buttons remain visible.
28
+ * LEFT: 60px accounts for line and port
29
+ * TOP: 40px accounts for top action buttons (-2.25rem ≈ 36px) plus margin
30
+ */
31
+ const POSITION_CLAMP_OFFSET = {
32
+ LEFT: 60,
33
+ TOP: 40
34
+ };
35
+
36
+ // Instance object: Only one will exist
37
+ var instance = null;
38
+ var sDragSessionKey = "networkGraphDragSession";
39
+ var oUtilityFunctions = {
40
+ diff: function (a, b) {
41
+ return Math.abs(a - b);
42
+ },
43
+ clampPosition: function (x, y) {
44
+ /**
45
+ * Clamps position coordinates to minimum thresholds.
46
+ * Prevents nodes from being positioned where action buttons would be off-screen.
47
+ */
48
+ return {
49
+ x: Math.max(POSITION_CLAMP_OFFSET.LEFT, x),
50
+ y: Math.max(POSITION_CLAMP_OFFSET.TOP, y)
51
+ };
52
+ }
53
+ };
54
+
55
+ /**
56
+ * Constructor for DragDropManager.
57
+ * This class is for internal use only
58
+ *
59
+ * @class
60
+ * Manages drag and drop functionality for network graph components.
61
+ * This class handles drag start, drag end, drag over, and drop events
62
+ * for both Graph and Node controls in the network graph library.
63
+ *
64
+ * @private
65
+ */
66
+ function DragDropManager() {
67
+ this.bIsDragging = false;
68
+ this._iWidthResizeDebounceTimeout = null;
69
+ this._iHeightResizeDebounceTimeout = null;
70
+ this._iRequestAnimationFrameId = null;
71
+ this._iLastWidthResizeTime = 0;
72
+ this._iLastHeightResizeTime = 0;
73
+ // Auto-scroll state
74
+ this._iAutoScrollIntervalId = null;
75
+ this._oAutoScrollDirection = { x: 0, y: 0 };
76
+ }
77
+
78
+ /**
79
+ * Debounced function to resize the inner scroller width when dragging near right edge.
80
+ * Uses debouncing to prevent excessive resize operations during drag operations.
81
+ *
82
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
83
+ * @param {number} incrementBy - The amount in pixels to increment the width
84
+ * @private
85
+ */
86
+ DragDropManager.prototype._resizeInnerScrollerWidth = function (oGraph, incrementBy) {
87
+ const currentTime = Date.now();
88
+
89
+ // Clear any existing timeout
90
+ if (this._iWidthResizeDebounceTimeout) {
91
+ clearTimeout(this._iWidthResizeDebounceTimeout);
92
+ }
93
+
94
+ // If enough time has passed since last resize, execute immediately
95
+ if (currentTime - this._iLastWidthResizeTime > RESIZE_CONFIG.RESIZE_DEBOUNCE_DELAY) {
96
+ this._executeWidthResize(oGraph, incrementBy);
97
+ this._iLastWidthResizeTime = currentTime;
98
+ } else {
99
+ // Otherwise, debounce the call
100
+ this._iWidthResizeDebounceTimeout = setTimeout(() => {
101
+ this._executeWidthResize(oGraph, incrementBy);
102
+ this._iLastWidthResizeTime = Date.now();
103
+ this._iWidthResizeDebounceTimeout = null;
104
+ }, RESIZE_CONFIG.RESIZE_DEBOUNCE_DELAY);
105
+ }
106
+ };
107
+
108
+ /**
109
+ * Executes the actual width resize in the DOM.
110
+ * Forces a reflow and updates the width-related CSS properties of the inner scroller.
111
+ *
112
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
113
+ * @param {number} incrementBy - The amount in pixels to increment the width
114
+ * @private
115
+ */
116
+ DragDropManager.prototype._executeWidthResize = function (oGraph, incrementBy) {
117
+ // Guard check: Ensure graph and css for innerscroller exist before proceeding
118
+ if (!oGraph || !oGraph._$innerscroller || !oGraph._$innerscroller.css) {
119
+ return;
120
+ }
121
+
122
+ /**
123
+ * Force a reflow to get the most current width
124
+ * When you read a calculated property like clientHeight, offsetWidth etc.,
125
+ * the browser has to pause the JavaScript code and reflow the page so it can return an accurate number.”
126
+ **/
127
+ oGraph._$innerscroller[0].offsetWidth;
128
+
129
+ const currentWidth = parseFloat(oGraph._$innerscroller.css("width")) ||
130
+ oGraph._$innerscroller[0].getBoundingClientRect().width;
131
+ const newWidth = currentWidth + incrementBy;
132
+
133
+ oGraph._$innerscroller[0].style.setProperty("width", newWidth + "px");
134
+ oGraph._$innerscroller[0].style.setProperty("min-width", newWidth + "px");
135
+ oGraph._$innerscroller[0].style.setProperty("max-width", "none");
136
+ };
137
+
138
+ /**
139
+ * Debounced function to resize the inner scroller height when dragging near bottom edge.
140
+ * Uses debouncing to prevent excessive resize operations during drag operations.
141
+ *
142
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
143
+ * @param {number} incrementBy - The amount in pixels to increment the height
144
+ * @private
145
+ */
146
+ DragDropManager.prototype._resizeInnerScrollerHeight = function (oGraph, incrementBy) {
147
+ const currentTime = Date.now();
148
+
149
+ // Clear any existing timeout
150
+ if (this._iHeightResizeDebounceTimeout) {
151
+ clearTimeout(this._iHeightResizeDebounceTimeout);
152
+ }
153
+
154
+ // If enough time has passed since last resize, execute immediately
155
+ if (currentTime - this._iLastHeightResizeTime > RESIZE_CONFIG.RESIZE_DEBOUNCE_DELAY) {
156
+ this._executeHeightResize(oGraph, incrementBy);
157
+ this._iLastHeightResizeTime = currentTime;
158
+ } else {
159
+ // Otherwise, debounce the call
160
+ this._iHeightResizeDebounceTimeout = setTimeout(() => {
161
+ this._executeHeightResize(oGraph, incrementBy);
162
+ this._iLastHeightResizeTime = Date.now();
163
+ this._iHeightResizeDebounceTimeout = null;
164
+ }, RESIZE_CONFIG.RESIZE_DEBOUNCE_DELAY);
165
+ }
166
+ };
167
+
168
+ /**
169
+ * Executes the actual height resize in the DOM.
170
+ * Forces a reflow and updates the height-related CSS properties of the inner scroller.
171
+ *
172
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
173
+ * @param {number} incrementBy - The amount in pixels to increment the height
174
+ * @private
175
+ */
176
+ DragDropManager.prototype._executeHeightResize = function (oGraph, incrementBy) {
177
+ // Guard check: Ensure graph and css for innerscroller exist before proceeding
178
+ if (!oGraph || !oGraph._$innerscroller || !oGraph._$innerscroller.css) {
179
+ return;
180
+ }
181
+
182
+ // Force a reflow to get the most current height
183
+ oGraph._$innerscroller[0].offsetHeight;
184
+
185
+ const currentHeight = parseFloat(oGraph._$innerscroller.css("height")) ||
186
+ oGraph._$innerscroller[0].getBoundingClientRect().height;
187
+ const newHeight = currentHeight + incrementBy;
188
+
189
+ oGraph._$innerscroller[0].style.setProperty("height", newHeight + "px");
190
+ oGraph._$innerscroller[0].style.setProperty("min-height", newHeight + "px");
191
+ oGraph._$innerscroller[0].style.setProperty("max-height", "none");
192
+ };
193
+
194
+ /**
195
+ * Checks if the scroller can scroll in the given direction.
196
+ *
197
+ * @param {HTMLElement} scrollerElement - The scroller DOM element
198
+ * @param {string} direction - Direction to check: 'up', 'down', 'left', 'right'
199
+ * @returns {boolean} True if scrolling is possible in that direction
200
+ * @private
201
+ */
202
+ DragDropManager.prototype._canScroll = function (scrollerElement, direction) {
203
+ if (!scrollerElement) {
204
+ return false;
205
+ }
206
+
207
+ switch (direction) {
208
+ case 'up':
209
+ return scrollerElement.scrollTop > 0;
210
+ case 'down':
211
+ return scrollerElement.scrollTop < (scrollerElement.scrollHeight - scrollerElement.clientHeight);
212
+ case 'left':
213
+ return scrollerElement.scrollLeft > 0;
214
+ case 'right':
215
+ return scrollerElement.scrollLeft < (scrollerElement.scrollWidth - scrollerElement.clientWidth);
216
+ default:
217
+ return false;
218
+ }
219
+ };
220
+
221
+ /**
222
+ * Starts or updates auto-scrolling of the viewport.
223
+ *
224
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
225
+ * @param {number} scrollX - Horizontal scroll direction (-1: left, 0: none, 1: right)
226
+ * @param {number} scrollY - Vertical scroll direction (-1: up, 0: none, 1: down)
227
+ * @private
228
+ */
229
+ DragDropManager.prototype._startAutoScroll = function (oGraph, scrollX, scrollY) {
230
+ // Update scroll direction
231
+ this._oAutoScrollDirection = { x: scrollX, y: scrollY };
232
+
233
+ // If already scrolling, direction is updated, no need to restart interval
234
+ if (this._iAutoScrollIntervalId) {
235
+ return;
236
+ }
237
+
238
+ // Start auto-scroll loop at ~60fps
239
+ this._iAutoScrollIntervalId = setInterval(() => {
240
+ if (!this.bIsDragging) {
241
+ this._stopAutoScroll();
242
+ return;
243
+ }
244
+
245
+ const scrollerElement = oGraph.$scroller?.[0];
246
+ if (!scrollerElement) {
247
+ this._stopAutoScroll();
248
+ return;
249
+ }
250
+
251
+ // Apply scrolling
252
+ if (this._oAutoScrollDirection.x !== 0) {
253
+ scrollerElement.scrollLeft += this._oAutoScrollDirection.x * RESIZE_CONFIG.AUTO_SCROLL_SPEED;
254
+ }
255
+ if (this._oAutoScrollDirection.y !== 0) {
256
+ scrollerElement.scrollTop += this._oAutoScrollDirection.y * RESIZE_CONFIG.AUTO_SCROLL_SPEED;
257
+ }
258
+ }, 16); // ~60fps
259
+ };
260
+
261
+ /**
262
+ * Stops auto-scrolling.
263
+ * @private
264
+ */
265
+ DragDropManager.prototype._stopAutoScroll = function () {
266
+ if (this._iAutoScrollIntervalId) {
267
+ clearInterval(this._iAutoScrollIntervalId);
268
+ this._iAutoScrollIntervalId = null;
269
+ }
270
+ this._oAutoScrollDirection = { x: 0, y: 0 };
271
+ };
272
+
273
+ /**
274
+ * Validates if the given control is supported for drag and drop operations.
275
+ * Checks if the control is a valid Graph or Node instance.
276
+ *
277
+ * @param {object} oControl - The control to validate
278
+ * @returns {boolean} True if the control is eligible for drag and drop, otherwise false
279
+ * @private
280
+ */
281
+ DragDropManager.prototype.isValidControl = function (oControl) {
282
+ if (!oControl || typeof oControl !== 'object') {
283
+ return false;
284
+ }
285
+
286
+ // Check if it's an empty object
287
+ if (Object.keys(oControl).length === 0 && oControl.constructor === Object) {
288
+ return false;
289
+ }
290
+
291
+ // Check if it has the required isA method
292
+ if (typeof oControl.isA !== 'function') {
293
+ return false;
294
+ }
295
+
296
+ return oControl.isA("sap.suite.ui.commons.networkgraph.Graph") ||
297
+ oControl.isA("sap.suite.ui.commons.networkgraph.Node");
298
+ }
299
+
300
+ /**
301
+ * Handles the drag start event for nodes.
302
+ *
303
+ * @param {sap.ui.base.Event} oEvent - The drag start event handler
304
+ * @private
305
+ */
306
+ DragDropManager.prototype._handleDragStart = function (oEvent) {
307
+ let dragSession = oEvent.getParameter("dragSession");
308
+ let browserEvent = oEvent.getParameter("browserEvent");
309
+ let oGraph = oEvent.getParameter("target")?.getParent();
310
+ let oNode = oEvent.getParameter("target");
311
+ let boundingRectScroller = oGraph?.$scroller?.[0]?.getBoundingClientRect() || { x: 0, y: 0 };
312
+ let boundingRectInnerScroller = oGraph?._$innerscroller?.[0]?.getBoundingClientRect() || { x: 0, y: 0 };
313
+
314
+ // Capture initial scroll positions
315
+ let scrollLeft = oGraph?._$innerscroller?.[0]?.scrollLeft || 0;
316
+ let scrollTop = oGraph?._$innerscroller?.[0]?.scrollTop || 0;
317
+ this.bIsDragging = true;
318
+
319
+ // Stop any existing auto-scroll from previous drag operations
320
+ this._stopAutoScroll();
321
+
322
+ // Create custom drag image that includes the border
323
+ this._oDragImageNode = Utils.createCustomDragImage(oNode, browserEvent);
324
+
325
+ dragSession.setComplexData(sDragSessionKey, {
326
+ // Mouse cursor position when drag starts (in viewport coordinates)
327
+ cursorX: browserEvent.clientX,
328
+ cursorY: browserEvent.clientY,
329
+ // Position of the inner scroller container when drag starts (relative to viewport)
330
+ // This changes if the graph content is scrolled during drag
331
+ innerScrollerX: boundingRectInnerScroller.x,
332
+ innerScrollerY: boundingRectInnerScroller.y,
333
+ // Position of the outer scroller container when drag starts (relative to viewport)
334
+ // This is usually stable but included for completeness
335
+ scrollerX: boundingRectScroller.x,
336
+ scrollerY: boundingRectScroller.y,
337
+ // How much the graph content was already scrolled when drag starts
338
+ // Used to maintain scroll position after drop to prevent viewport jumping
339
+ initialScrollLeft: scrollLeft,
340
+ initialScrollTop: scrollTop
341
+ });
342
+ this._iRequestAnimationFrameId && cancelAnimationFrame(this._iRequestAnimationFrameId);
343
+ }
344
+
345
+ /**
346
+ * Handles the drag end event for nodes.
347
+ *
348
+ * @param {sap.ui.base.Event} oEvent - The drag End event handler
349
+ * @private
350
+ */
351
+ DragDropManager.prototype._handleDragEnd = function (oEvent) {
352
+ if (!this._validateEvent(oEvent)) {
353
+ return;
354
+ }
355
+
356
+ // Stop auto-scrolling when drag ends
357
+ this._stopAutoScroll();
358
+
359
+ // Clean up the drag image node
360
+ if (this._oDragImageNode && this._oDragImageNode.parentNode) {
361
+ this._oDragImageNode.parentNode.removeChild(this._oDragImageNode);
362
+ this._oDragImageNode = null;
363
+ }
364
+ };
365
+
366
+ /**
367
+ * Handles the drag over event for nodes.
368
+ *
369
+ * @param {sap.ui.base.Event} oEvent - The drag over event handler
370
+ * @private
371
+ */
372
+ DragDropManager.prototype._handleDragOver = function (oEvent) {
373
+ if (!this.bIsDragging) {
374
+ // not dragging - skip dragover handling
375
+ return;
376
+ }
377
+ if (!this._validateEvent(oEvent)) {
378
+ return;
379
+ }
380
+
381
+ const oGraph = oEvent.getParameter("target");
382
+ const oBrowserEvent = oEvent.getParameter("browserEvent");
383
+ const oScrollerElement = oGraph?.$scroller?.[0];
384
+ const oInnerScrollerElement = oGraph?._$innerscroller?.[0];
385
+
386
+ if (!oScrollerElement || !oInnerScrollerElement) {
387
+ return;
388
+ }
389
+
390
+ // Get cursor position
391
+ const iCursorX = oBrowserEvent?.clientX;
392
+ const iCursorY = oBrowserEvent?.clientY;
393
+
394
+ // Get scroller's bounding rect to know viewport edges
395
+ const oScrollerRect = oScrollerElement.getBoundingClientRect();
396
+
397
+ // ========================================
398
+ // AUTO-SCROLL LOGIC (Decision Tree)
399
+ // ========================================
400
+
401
+ let scrollX = 0;
402
+ let scrollY = 0;
403
+
404
+ // Check horizontal auto-scroll
405
+ const distanceFromScrollerLeft = iCursorX - oScrollerRect.left;
406
+ const distanceFromScrollerRight = oScrollerRect.right - iCursorX;
407
+
408
+ if (distanceFromScrollerRight <= RESIZE_CONFIG.AUTO_SCROLL_THRESHOLD &&
409
+ this._canScroll(oScrollerElement, 'right')) {
410
+ scrollX = 1; // Scroll right
411
+ } else if (distanceFromScrollerLeft <= RESIZE_CONFIG.AUTO_SCROLL_THRESHOLD &&
412
+ this._canScroll(oScrollerElement, 'left')) {
413
+ scrollX = -1; // Scroll left
414
+ }
415
+
416
+ // Check vertical auto-scroll
417
+ const distanceFromScrollerTop = iCursorY - oScrollerRect.top;
418
+ const distanceFromScrollerBottom = oScrollerRect.bottom - iCursorY;
419
+
420
+ if (distanceFromScrollerBottom <= RESIZE_CONFIG.AUTO_SCROLL_THRESHOLD &&
421
+ this._canScroll(oScrollerElement, 'down')) {
422
+ scrollY = 1; // Scroll down
423
+ } else if (distanceFromScrollerTop <= RESIZE_CONFIG.AUTO_SCROLL_THRESHOLD &&
424
+ this._canScroll(oScrollerElement, 'up')) {
425
+ scrollY = -1; // Scroll up
426
+ }
427
+
428
+ // Start/update/stop auto-scroll based on calculated direction
429
+ if (scrollX !== 0 || scrollY !== 0) {
430
+ this._startAutoScroll(oGraph, scrollX, scrollY);
431
+ } else {
432
+ this._stopAutoScroll();
433
+ }
434
+
435
+ // Get dimensions and scroll positions
436
+ const iScrollerWidth = oScrollerElement.clientWidth;
437
+ const iScrollerHeight = oScrollerElement.clientHeight;
438
+ const iInnerScrollerWidth = oInnerScrollerElement.scrollWidth || oInnerScrollerElement.clientWidth;
439
+ const iInnerScrollerHeight = oInnerScrollerElement.scrollHeight || oInnerScrollerElement.clientHeight;
440
+ const iScrollLeft = oScrollerElement.scrollLeft;
441
+ const iScrollTop = oScrollerElement.scrollTop;
442
+
443
+ // Check proximity to right edge for width expansion
444
+ // TODO: Consider adding a maximum width/height limit to prevent excessive growth
445
+ // TODO: Consider adding factor of cursor position being at the end of the screen
446
+ const bShouldExpandWidth = this._shouldExpandDimension(
447
+ iInnerScrollerWidth,
448
+ iScrollerWidth,
449
+ iScrollLeft,
450
+ iCursorX - oScrollerRect.left, // Cursor X relative to scroller
451
+ RESIZE_CONFIG.PROXIMITY_THRESHOLD
452
+ );
453
+
454
+ if (bShouldExpandWidth) {
455
+ this._resizeInnerScrollerWidth(oGraph, RESIZE_CONFIG.WIDTH_INCREMENT);
456
+ }
457
+
458
+ // Check proximity to bottom edge for height expansion
459
+ const bShouldExpandHeight = this._shouldExpandDimension(
460
+ iInnerScrollerHeight,
461
+ iScrollerHeight,
462
+ iScrollTop,
463
+ iCursorY - oScrollerRect.top, // Cursor Y relative to scroller
464
+ RESIZE_CONFIG.PROXIMITY_THRESHOLD
465
+ );
466
+
467
+ if (bShouldExpandHeight) {
468
+ this._resizeInnerScrollerHeight(oGraph, RESIZE_CONFIG.HEIGHT_INCREMENT);
469
+ }
470
+ };
471
+
472
+ /**
473
+ * Determines if the inner scroller dimension should be expanded based on cursor proximity to viewport edge
474
+ * and available content space.
475
+ * Handles two cases:
476
+ * Case 1: InnerScroller smaller than Scroller (not scrolled) - cursor must be near viewport edge AND near innerScroller's edge
477
+ * Case 2: InnerScroller larger than Scroller (scrolled) - cursor must be near viewport edge AND running out of content
478
+ *
479
+ * @param {number} iInnerDimension - The total dimension of inner scroller (width or height)
480
+ * @param {number} iScrollerDimension - The viewport dimension of scroller (width or height)
481
+ * @param {number} iScrollPosition - Current scroll position (scrollLeft or scrollTop)
482
+ * @param {number} iCursorPosition - Cursor position relative to scroller (clientX/Y - scrollerRect.left/top)
483
+ * @param {number} iProximityThreshold - Distance from edge to trigger expansion
484
+ * @returns {boolean} True if should expand, false otherwise
485
+ * @private
486
+ */
487
+ DragDropManager.prototype._shouldExpandDimension = function (iInnerDimension, iScrollerDimension, iScrollPosition, iCursorPosition, iProximityThreshold) {
488
+ // Case 1: InnerScroller is smaller than or equal to Scroller (not scrollable or minimal scroll)
489
+ // In this case, cursor proximity is checked against innerScroller's edge, not viewport edge
490
+ if (iInnerDimension <= iScrollerDimension) {
491
+ // Check if cursor is near the innerScroller's content edge
492
+ const bCursorNearInnerScrollerEdge = iCursorPosition >= (iInnerDimension - iProximityThreshold);
493
+ return bCursorNearInnerScrollerEdge;
494
+ }
495
+
496
+ // Case 2: InnerScroller is larger than Scroller (content is scrollable)
497
+ // Check if cursor is near viewport edge AND we're running out of content
498
+ const bCursorNearViewportEdge = iCursorPosition >= (iScrollerDimension - iProximityThreshold);
499
+
500
+ if (!bCursorNearViewportEdge) {
501
+ // Cursor is not near the viewport edge, no need to expand
502
+ return false;
503
+ }
504
+
505
+ // Cursor is near viewport edge, now check if we're running out of content
506
+ const iVisibleEdgePosition = iScrollPosition + iScrollerDimension;
507
+ const iContentEdgeThreshold = iInnerDimension - iProximityThreshold;
508
+
509
+ return iVisibleEdgePosition >= iContentEdgeThreshold;
510
+ };
511
+
512
+ /**
513
+ * Handles the drop event for node elements.
514
+ * Calculates the new position of the node based on cursor movement and scroll changes,
515
+ * then fires the nodeDropped event with the updated coordinates.
516
+ *
517
+ * @param {sap.ui.base.Event} oEvent - The drop event
518
+ * @param {object} dropData - The extracted drop data containing drag information
519
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
520
+ * @private
521
+ */
522
+ DragDropManager.prototype._handleNodeDrop = function (oEvent, dropData, oGraph) {
523
+ let dragSessionComplexData = dropData.dragSessionComplexData,
524
+ deltaX = dropData.clientX - (dragSessionComplexData?.cursorX || 0), // Calculate the change in X position for the node being dragged in viewport
525
+ deltaY = dropData.clientY - (dragSessionComplexData?.cursorY || 0), // Calculate the change in Y position for the node being dragged in viewport
526
+ prevInnerScrollerX = dragSessionComplexData?.innerScrollerX || 0, // Previous X position of the inner scroller at drag start
527
+ prevInnerScrollerY = dragSessionComplexData?.innerScrollerY || 0; // Previous Y position of the inner scroller at drag start
528
+
529
+ let boundingRectInnerScroller = oGraph?._$innerscroller[0]?.getBoundingClientRect() || { x: 0, y: 0 },
530
+ deltaInnerScrollerX = oUtilityFunctions.diff(prevInnerScrollerX, boundingRectInnerScroller.x || 0), // Change in X position of the inner scroller during drag, to track scrolling
531
+ deltaInnerScrollerY = oUtilityFunctions.diff(prevInnerScrollerY, boundingRectInnerScroller.y || 0), // Change in Y position of the inner scroller during drag, to track scrolling
532
+ deltaSignatureX = prevInnerScrollerX >= boundingRectInnerScroller.x ? 1 : -1, // Determine scroll direction in X axis
533
+ deltaSignatureY = prevInnerScrollerY >= boundingRectInnerScroller.y ? 1 : -1; // Determine scroll direction in Y axis
534
+
535
+ let newX = dropData.draggedControl.getX() + deltaX + (deltaSignatureX * deltaInnerScrollerX),
536
+ newY = dropData.draggedControl.getY() + deltaY + (deltaSignatureY * deltaInnerScrollerY);
537
+
538
+ // Clamp position to prevent nodes from going off-screen (especially for action buttons)
539
+ var oClampedPosition = oUtilityFunctions.clampPosition(newX, newY);
540
+
541
+ // Store current scroll position before any DOM changes
542
+ let currentScrollLeft = oGraph?.$scroller?.[0]?.scrollLeft || 0;
543
+ let currentScrollTop = oGraph?.$scroller?.[0]?.scrollTop || 0;
544
+
545
+ oGraph.fireEvent("nodeDropped", {
546
+ ...oEvent.getParameters(),
547
+ node: dropData.draggedControl,
548
+ newX: oClampedPosition.x,
549
+ newY: oClampedPosition.y
550
+ });
551
+ // Preserve scroll position after event
552
+ Utils.preserveScrollPosition(oGraph, currentScrollLeft, currentScrollTop, 0);
553
+ };
554
+
555
+ /**
556
+ * Handles the drop event for external items (non-node elements).
557
+ * Calculates the drop position relative to the inner scroller and fires the nodeDropped event.
558
+ *
559
+ * @param {sap.ui.base.Event} oEvent - The drop event
560
+ * @param {object} dropData - The extracted drop data containing drag information
561
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
562
+ * @private
563
+ */
564
+ DragDropManager.prototype._handleExternalItemDrop = function (oEvent, dropData, oGraph) {
565
+ let boundingRect = oGraph?._$innerscroller[0]?.getBoundingClientRect() || { left: 0, top: 0 },
566
+ x = dropData.clientX - boundingRect.left,
567
+ y = dropData.clientY - boundingRect.top;
568
+
569
+ // Clamp position to prevent nodes from going off-screen (especially for action buttons)
570
+ var oClampedPosition = oUtilityFunctions.clampPosition(x, y);
571
+
572
+ // Store current scroll position before any DOM changes
573
+ let currentScrollLeft = oGraph?.$scroller?.[0]?.scrollLeft || 0;
574
+ let currentScrollTop = oGraph?.$scroller?.[0]?.scrollTop || 0;
575
+
576
+ oGraph.fireEvent("nodeDropped", {
577
+ ...oEvent.getParameters(),
578
+ node: dropData.draggedControl,
579
+ newX: oClampedPosition.x,
580
+ newY: oClampedPosition.y
581
+ });
582
+
583
+ // Preserve scroll position after event
584
+ Utils.preserveScrollPosition(oGraph, currentScrollLeft, currentScrollTop, 0);
585
+ };
586
+
587
+ /**
588
+ * Validates if the event contains required browser event data and if DnD is enabled.
589
+ * Logs a warning if validation fails.
590
+ *
591
+ * @param {sap.ui.base.Event} oEvent - The event to validate
592
+ * @param {sap.suite.ui.commons.networkgraph.Graph} [oGraph] - Optional graph control to check if DnD is enabled
593
+ * @returns {boolean} True if the event is valid, false otherwise
594
+ * @private
595
+ */
596
+ DragDropManager.prototype._validateEvent = function (oEvent, oGraph = undefined) {
597
+ let oBrowserEvent = oEvent.getParameter("browserEvent") || undefined;
598
+ if (!oBrowserEvent || (!!oGraph && !oGraph._isDnDEnabled())) {
599
+ Log.warning("No native browser event available in drop event.");
600
+ return false;
601
+ }
602
+ return true;
603
+ };
604
+
605
+ /**
606
+ * Checks if the given control is a Node type.
607
+ *
608
+ * @param {object} [control={}] - The control to check
609
+ * @returns {boolean} True if the control is a Node, false otherwise
610
+ * @private
611
+ */
612
+ DragDropManager.prototype._isTypeNode = function (control = {}) {
613
+ return control?.isA && control.isA("sap.suite.ui.commons.networkgraph.Node");
614
+ };
615
+
616
+ /**
617
+ * Extracts and returns the relevant drop data from the event.
618
+ * Includes dragged control, cursor position, and drag session data.
619
+ *
620
+ * @param {sap.ui.base.Event} oEvent - The drop event
621
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
622
+ * @returns {object} Object containing draggedControl, clientX, clientY, and dragSessionComplexData
623
+ * @private
624
+ */
625
+ DragDropManager.prototype._extractDropData = function (oEvent, oGraph) {
626
+ let oBrowserEvent = oEvent.getParameter("browserEvent") || {};
627
+ return {
628
+ draggedControl: oEvent.getParameter("draggedControl") || {},
629
+ clientX: oBrowserEvent.clientX || 0,
630
+ clientY: oBrowserEvent.clientY || 0,
631
+ dragSessionComplexData: oEvent.getParameter("dragSession")?.getComplexData(sDragSessionKey) || {},
632
+ };
633
+ };
634
+
635
+ /**
636
+ * Handles the drop event.
637
+ *
638
+ * @param {sap.ui.base.Event} oEvent - The drop event
639
+ * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
640
+ * @private
641
+ */
642
+ DragDropManager.prototype._handleDrop = function (oEvent, oGraph) {
643
+ this.bIsDragging = false;
644
+ if (!this._validateEvent(oEvent, oGraph)) {
645
+ return;
646
+ }
647
+
648
+ let dropData = this._extractDropData(oEvent, oGraph);
649
+ if (this._isTypeNode(dropData.draggedControl)) {
650
+ this._handleNodeDrop(oEvent, dropData, oGraph);
651
+ } else {
652
+ this._handleExternalItemDrop(oEvent, dropData, oGraph);
653
+ }
654
+ };
655
+
656
+ /**
657
+ * Injects drag and drop configuration into the given control.
658
+ * Adds appropriate DragInfo and DropInfo based on control type (Graph or Node).
659
+ *
660
+ * @param {sap.ui.core.Control} oControl - The control to inject DnD functionality into
661
+ * @throws {Error} If the control is not valid or not supported for DnD
662
+ * @public
663
+ */
664
+ DragDropManager.prototype.injectDnD = function (oControl) {
665
+ if (!this.isValidControl(oControl)) {
666
+ throw new Error("DragDropManager: Control is not valid for DnD");
667
+ }
668
+ var sControlType = oControl.getMetadata().getName();
669
+ switch (sControlType) {
670
+ case "sap.suite.ui.commons.networkgraph.Graph":
671
+ var dragInfo = new DragInfo({
672
+ sourceAggregation: "nodes",
673
+ dragEnd: this._handleDragEnd.bind(this)
674
+ });
675
+ var dropInfo = new DropInfo({
676
+ dropPosition: "On",
677
+ drop: function (oEvent) {
678
+ this._handleDrop(oEvent, oControl);
679
+ }.bind(this),
680
+ dragOver: this._handleDragOver.bind(this),
681
+ dragEnter: function (oEvent) {
682
+ oEvent.getParameter("dragSession").setIndicatorConfig({
683
+ display: "none"
684
+ });
685
+ }
686
+ });
687
+
688
+ oControl.addDragDropConfig(dragInfo);
689
+ oControl.addDragDropConfig(dropInfo);
690
+ break;
691
+ case "sap.suite.ui.commons.networkgraph.Node":
692
+ var dragInfo = new DragInfo({
693
+ dragStart: this._handleDragStart.bind(this),
694
+ });
695
+ oControl.addDragDropConfig(dragInfo);
696
+ break;
697
+ default:
698
+ throw new Error("DragDropManager: Control type " + sControlType + " is not supported");
699
+ }
700
+ };
701
+
702
+ // Remove DnD config
703
+ DragDropManager.prototype.removeDnD = function (oControl) {
704
+ if (!this.isValidControl(oControl)) {
705
+ throw new Error("DragDropManager: Control is not valid for DnD");
706
+ }
707
+ // Check if control has any drag drop configurations
708
+ if (!oControl.getDragDropConfig || oControl.getDragDropConfig().length === 0) {
709
+ return true; // Not an error, just nothing to remove
710
+ }
711
+
712
+ oControl?.destroyDragDropConfig();
713
+ };
714
+
715
+ // Exported singleton object - instantiate on first access
716
+ if (!instance) {
717
+ instance = new DragDropManager();
718
+ }
719
+
720
+ return instance;
721
+ }, /* bExport= */true);