@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
@@ -364,12 +364,28 @@ sap.ui.define([
364
364
  }
365
365
  };
366
366
 
367
+ /**
368
+ * Returns the horizontal scroll position in graph coordinates, accounting for RTL.
369
+ */
370
+ GraphMap.prototype._getScrollLeftForMap = function (oGraph) {
371
+ const fScrollLeft = oGraph.$scroller[0].scrollLeft;
372
+
373
+ if (oGraph._bIsRtl) {
374
+ const iGraphWidth = oGraph.$svg.width() / oGraph._fZoomLevel;
375
+ const iViewportWidth = oGraph.$scroller.width() / oGraph._fZoomLevel;
376
+ const iScrollPosAbs = Math.abs(fScrollLeft) / oGraph._fZoomLevel;
377
+ return iGraphWidth - iScrollPosAbs - iViewportWidth;
378
+ }
379
+
380
+ return fScrollLeft / oGraph._fZoomLevel;
381
+ };
382
+
367
383
  GraphMap.prototype._resize = function () {
368
384
  var oGraph = this.getGraph(),
369
385
  $scroller = oGraph.$scroller,
370
386
  $mapNavigator = this.$("mapNavigator");
371
387
 
372
- $mapNavigator.attr("x", Math.max(NAVIGATORLINESIZE / 2, $scroller[0].scrollLeft / oGraph._fZoomLevel));
388
+ $mapNavigator.attr("x", Math.max(NAVIGATORLINESIZE / 2, this._getScrollLeftForMap(oGraph)));
373
389
  $mapNavigator.attr("y", Math.max(NAVIGATORLINESIZE / 2, $scroller[0].scrollTop / oGraph._fZoomLevel));
374
390
 
375
391
  $mapNavigator.attr("width", $scroller.width() / oGraph._fZoomLevel);
@@ -400,7 +416,7 @@ sap.ui.define([
400
416
  $mapNavigator = this.$("mapNavigator");
401
417
 
402
418
  if (oGraph && $scroller[0]) {
403
- $mapNavigator.attr("x", Math.max(NAVIGATORLINESIZE / 2, $scroller[0].scrollLeft / oGraph._fZoomLevel));
419
+ $mapNavigator.attr("x", Math.max(NAVIGATORLINESIZE / 2, this._getScrollLeftForMap(oGraph)));
404
420
  $mapNavigator.attr("y", Math.max(NAVIGATORLINESIZE / 2, $scroller[0].scrollTop / oGraph._fZoomLevel));
405
421
 
406
422
  this._correctMapNavigator();
@@ -421,7 +437,13 @@ sap.ui.define([
421
437
  fRealStartX = $border.offset().left,
422
438
  fRealStartY = $border.offset().top;
423
439
 
424
- oScroller.scrollLeft = (oScrollData.pageX - fRealStartX) * iRatio - ($scroller.width() / 2);
440
+ const fTargetScrollLeft = (oScrollData.pageX - fRealStartX) * iRatio - ($scroller.width() / 2);
441
+ if (oGraph._bIsRtl) {
442
+ const iScrollRange = oScroller.scrollWidth - oScroller.clientWidth;
443
+ oScroller.scrollLeft = -(iScrollRange - fTargetScrollLeft);
444
+ } else {
445
+ oScroller.scrollLeft = fTargetScrollLeft;
446
+ }
425
447
  oScroller.scrollTop = (oScrollData.pageY - fRealStartY) * iRatio - ($scroller.height() / 2);
426
448
  };
427
449
 
@@ -65,6 +65,14 @@ sap.ui.define([
65
65
  }
66
66
  oRM.openEnd();
67
67
 
68
+ if (oNetworkGraph.getLoading()) {
69
+ this._renderLoading(oRM, oNetworkGraph);
70
+ oRM.close("div");
71
+ oRM.close("div");
72
+ oRM.close("div");
73
+ return;
74
+ }
75
+
68
76
  if (oNetworkGraph.getNoData()) {
69
77
  this._renderNoData(oRM, oNetworkGraph);
70
78
  oRM.close("div");
@@ -165,16 +173,19 @@ sap.ui.define([
165
173
  oRM.class("sapSuiteUiCommonsNetworkGraphNoDataWrapper");
166
174
  oRM.attr("tabindex", "0");
167
175
  oRM.openEnd();
168
- oNetworkGraph._renderHtmlIcon("sap-icon://document", "sapSuiteUiCommonsNetworkGraphNoDataIcon", null, null, null, oRM);
169
-
170
- var sText = oNetworkGraph.getNoDataText(),
171
- sTextInline = sText ? sText : oResourceBundle.getText("NETWORK_GRAPH_NO_DATA");
172
-
176
+ oRM.renderControl(oNetworkGraph._getIllustratedMessage());
177
+ oRM.close("div");
178
+ },
179
+ _renderLoading: function (oRM, oNetworkGraph) {
173
180
  oRM.openStart("div");
174
- oRM.class("sapSuiteUiCommonsNetworkGraphNoDataLabel");
181
+ oRM.class("sapSuiteUiCommonsNetworkGraphLoadingWrapper");
182
+ oRM.attr("tabindex", "0");
175
183
  oRM.openEnd();
176
- oRM.text(sTextInline);
177
- oRM.close("div");
184
+
185
+ // Render the busy indicator
186
+ var oBusyIndicator = oNetworkGraph._getBusyIndicator();
187
+ oRM.renderControl(oBusyIndicator);
188
+
178
189
  oRM.close("div");
179
190
  }
180
191
  };
@@ -18,6 +18,8 @@ sap.ui.define([
18
18
  ], function (jQuery, BaseObject, Group, Node, Line, KeyCodes, containsOrEquals, Log, CoreLib) {
19
19
  "use strict";
20
20
 
21
+ const oResourceBundle = CoreLib.getResourceBundleFor("sap.suite.ui.commons");
22
+
21
23
  var mDirections = {
22
24
  LEFT: "left",
23
25
  RIGHT: "right",
@@ -73,6 +75,13 @@ sap.ui.define([
73
75
  this._oFocusPosition = null;
74
76
  this._oWrapperDom = null;
75
77
  this.isFromFullscreen = null;
78
+ this._oAltNode = null;
79
+ this._oAltConnectors = null;
80
+ this._oAltAnnouncement = null;
81
+ this._oAssociatedControl = null;
82
+ this._oAssociatedContainer = null;
83
+ this._oAssociatedDelegate = null;
84
+ this._bCtrlAltDown = false;
76
85
  }
77
86
  });
78
87
 
@@ -123,6 +132,14 @@ sap.ui.define([
123
132
  this._iColumns = aRow.length;
124
133
  }
125
134
  }, this);
135
+
136
+ const oControl = this._oGraph.getAssociatedControl();
137
+ if (oControl !== this._oAssociatedControl) {
138
+ this._updateAssociatedDelegate(oControl);
139
+ }
140
+ this._oAltNode = null;
141
+ this._oAltConnectors = null;
142
+ this._oAltAnnouncement = null;
126
143
  };
127
144
 
128
145
  KeyboardNavigator.prototype.setWrapperDom = function (oDom) {
@@ -186,13 +203,15 @@ sap.ui.define([
186
203
  };
187
204
 
188
205
  KeyboardNavigator.prototype.onkeyup = function(oEvent) {
189
- if (oEvent.keyCode === KeyCodes.SPACE){
190
- this._handleEnter();
191
- if (this._oGraph.getFocusDomRef() === document.activeElement) {
206
+ if (oEvent.keyCode === KeyCodes.SPACE){
207
+ this._handleEnter();
208
+ if (this._oGraph.getFocusDomRef() === document.activeElement) {
192
209
  oEvent.stopPropagation();
193
210
  oEvent.preventDefault();
194
- }
195
211
  }
212
+ } else if (oEvent.keyCode === KeyCodes.DELETE || oEvent.keyCode === KeyCodes.BACKSPACE) {
213
+ this._handleDelete(oEvent);
214
+ }
196
215
  };
197
216
 
198
217
  KeyboardNavigator.prototype.onsapenter = function (oEvent) {
@@ -226,6 +245,12 @@ sap.ui.define([
226
245
  KeyboardNavigator.prototype.onkeydown = function (oEvent) {
227
246
  var oItem, oBtn,
228
247
  oFocus = this.getFocus();
248
+
249
+ // On Windows, Ctrl+Alt = AltGr — browsers strip ctrlKey/altKey from the character keydown.
250
+ if (oEvent.keyCode === KeyCodes.ALT && oEvent.ctrlKey) {
251
+ this._bCtrlAltDown = true;
252
+ }
253
+
229
254
  if (!oFocus) {
230
255
  return;
231
256
  }
@@ -253,11 +278,7 @@ sap.ui.define([
253
278
  }
254
279
  oEvent.stopPropagation();
255
280
  } else if (oEvent.keyCode === KeyCodes.F6) {
256
- if (oItem && oBtn) {
257
- oFocus.button = null;
258
- this._oGraph.setFocus(oFocus);
259
- }
260
- this._handleArrow(oEvent, oEvent.shiftKey ? mDirections.LEFT : mDirections.RIGHT);
281
+ this._handleF6(oEvent);
261
282
  } else if (oEvent.keyCode === KeyCodes.F7 && !oEvent.shiftKey) {
262
283
  if (oItem && oBtn) {
263
284
  oFocus.button = null;
@@ -269,6 +290,24 @@ sap.ui.define([
269
290
  this._onCtrlPlus(oEvent);
270
291
  } else if (oEvent.ctrlKey && (oEvent.keyCode === KeyCodes.SLASH || oEvent.keyCode === KeyCodes.NUMPAD_MINUS)) {
271
292
  this._onCtrlMinus(oEvent);
293
+ } else if (this._bCtrlAltDown && oEvent.keyCode === KeyCodes.P) {
294
+ this._bCtrlAltDown = false;
295
+ const oAssociatedControl = this._oGraph.getAssociatedControl();
296
+ if (oAssociatedControl) {
297
+ oAssociatedControl.focus();
298
+ oItem._setFocus(false);
299
+ }
300
+ oEvent.preventDefault();
301
+ oEvent.stopPropagation();
302
+ } else if (oEvent.altKey && !oEvent.ctrlKey && !oEvent.shiftKey && oItem instanceof Node) {
303
+ if (oEvent.keyCode === KeyCodes.PLUS || oEvent.keyCode === KeyCodes.NUMPAD_PLUS) {
304
+ this._handleAltAnnounce(oEvent, oItem);
305
+ } else {
306
+ const iDigitIndex = this._getAltDigitIndex(oEvent.keyCode);
307
+ if (iDigitIndex >= 0) {
308
+ this._handleAltConnectorNavigation(oEvent, oItem, iDigitIndex);
309
+ }
310
+ }
272
311
  }
273
312
  };
274
313
 
@@ -276,6 +315,59 @@ sap.ui.define([
276
315
  /* Private methods */
277
316
  /* =========================================================== */
278
317
 
318
+ KeyboardNavigator.prototype._getAltDigitIndex = function (iKeyCode) {
319
+ if (iKeyCode >= KeyCodes.DIGIT_1 && iKeyCode <= KeyCodes.DIGIT_9) {
320
+ return iKeyCode - KeyCodes.DIGIT_1;
321
+ }
322
+ if (iKeyCode >= KeyCodes.NUMPAD_1 && iKeyCode <= KeyCodes.NUMPAD_9) {
323
+ return iKeyCode - KeyCodes.NUMPAD_1;
324
+ }
325
+ return -1;
326
+ };
327
+
328
+ /**
329
+ * Handles F6 / Shift+F6 — skip to next/previous visible group regardless of what is focused.
330
+ * @private
331
+ */
332
+ KeyboardNavigator.prototype._handleF6 = function (oEvent) {
333
+ const bBackward = oEvent.shiftKey,
334
+ oFocus = this.getFocus(),
335
+ oItem = oFocus ? oFocus.item : null,
336
+ bIsRtl = this._oGraph._bIsRtl,
337
+ aGroups = this._oGraph.getGroups().filter(function (oGroup) {
338
+ return !oGroup.isHidden() && oGroup.getVisible();
339
+ }).sort(function (oA, oB) {
340
+ // Top-to-bottom, then by reading direction (RTL: descending x, LTR: ascending x)
341
+ const iDeltaY = oA.getY() - oB.getY();
342
+ if (iDeltaY !== 0) { return iDeltaY; }
343
+ const iDeltaX = oA.getX() - oB.getX();
344
+ return bIsRtl ? -iDeltaX : iDeltaX;
345
+ });
346
+
347
+ if (aGroups.length === 0) {
348
+ return;
349
+ }
350
+
351
+ let iCurrentIndex = -1;
352
+ if (oItem instanceof Group) {
353
+ iCurrentIndex = aGroups.indexOf(oItem);
354
+ } else if (oItem instanceof Node && oItem._oGroup) {
355
+ iCurrentIndex = aGroups.indexOf(oItem._oGroup);
356
+ }
357
+
358
+ const iTargetIndex = bBackward
359
+ ? (iCurrentIndex > 0 ? iCurrentIndex - 1 : aGroups.length - 1)
360
+ : (iCurrentIndex < aGroups.length - 1 ? iCurrentIndex + 1 : 0);
361
+
362
+ // Ensure setFocus lands on the group header, not the menu button
363
+ if (this._oGraph._oFocus) {
364
+ this._oGraph._oFocus.groupInFocused = false;
365
+ }
366
+ this._oGraph.setFocus({ item: aGroups[iTargetIndex], button: null });
367
+ oEvent.preventDefault();
368
+ oEvent.stopPropagation();
369
+ };
370
+
279
371
  KeyboardNavigator.prototype._handleEnter = function () {
280
372
  var oItem, oBtn,
281
373
  oFocus = this.getFocus();
@@ -324,9 +416,71 @@ sap.ui.define([
324
416
  }
325
417
  };
326
418
 
419
+ KeyboardNavigator.prototype._handleDelete = function (oEvent) {
420
+ // Only handle delete when DnD is enabled
421
+ if (!this._oGraph._isDnDEnabled()) {
422
+ return;
423
+ }
424
+
425
+ var oFocus = this.getFocus();
426
+ if (!oFocus) {
427
+ return;
428
+ }
429
+
430
+ var oItem = oFocus.item;
431
+
432
+ // Only allow delete for Node and Line, not Group
433
+ if (oItem instanceof Node || oItem instanceof Line) {
434
+ oItem.setSelected(false);
435
+
436
+ // Clear focus before delete to prevent stale reference issues
437
+ if (this._oGraph._oFocus && this._oGraph._oFocus.item === oItem) {
438
+ this._oGraph._oFocus = null;
439
+ }
440
+
441
+ // Fire the itemDeleted event on the graph
442
+ var bExecuteDefault = this._oGraph.fireEvent("itemDeleted", {
443
+ item: oItem
444
+ }, true);
445
+
446
+ if (bExecuteDefault) {
447
+ oEvent.stopPropagation();
448
+ oEvent.preventDefault();
449
+ }
450
+
451
+ // Restore focus after a small timeout (to let any rerender settle)
452
+ setTimeout(function () {
453
+ this._oGraph.setFocus(oFocus)
454
+ }.bind(this), 0);
455
+ }
456
+ };
457
+
458
+ /**
459
+ * Fires the nodeAdded event and shifts focus to the newly added node after re-render.
460
+ * Only active when DnD is enabled.
461
+ * @private
462
+ */
463
+ KeyboardNavigator.prototype._handleAddNode = function (oEvent) {
464
+ if (!this._oGraph._isDnDEnabled()) {
465
+ return;
466
+ }
467
+ this._oGraph.fireEvent("nodeAdded", {}, false);
468
+ const fnFocus = () => {
469
+ this._oGraph.detachGraphReady(fnFocus);
470
+ const aNodes = this._oGraph.getNodes();
471
+ if (aNodes.length) {
472
+ this._oGraph.setFocus({ item: aNodes.at(-1), button: null });
473
+ }
474
+ };
475
+ this._oGraph.attachGraphReady(fnFocus);
476
+ oEvent.preventDefault();
477
+ oEvent.stopPropagation();
478
+ };
479
+
327
480
  KeyboardNavigator.prototype._handleTab = function (oEvent, sDirection) {
328
- var oResourceBundle = CoreLib.getResourceBundleFor("sap.suite.ui.commons");
329
- this._oWrapperDom.setAttribute("aria-live","assertive");
481
+ const oFocus = this.getFocus(),
482
+ bBackward = sDirection === mDirections.LEFT;
483
+ this._oWrapperDom.setAttribute("aria-live", "assertive");
330
484
  this._oGraph._setAriaLabelForWrapper(oResourceBundle.getText("NETWORK_GRAPH_ACCESSIBILITY_LABEL"));
331
485
  if (this._ignoreEvent(oEvent)) {
332
486
  this._oWrapperDom.setAttribute("aria-live","off");
@@ -348,7 +502,118 @@ sap.ui.define([
348
502
  return;
349
503
  }
350
504
 
351
- this._moveItemFocus(oEvent, sDirection);
505
+ if (oFocus?.item instanceof Node) {
506
+ const bMoved = this._navigateFromNode(oFocus.item, bBackward);
507
+ if (!bMoved && bBackward) {
508
+ const oGroup = oFocus.item._oGroup;
509
+ if (oGroup) {
510
+ this._oGraph.setFocus({ item: oGroup, button: Group.BUTTONS.COLLAPSE });
511
+ oEvent.preventDefault();
512
+ oEvent.stopPropagation();
513
+ } else {
514
+ this._moveItemFocus(oEvent, mDirections.LEFT);
515
+ }
516
+ return;
517
+ }
518
+ } else if (oFocus?.item instanceof Line) {
519
+ this._navigateFromConnector(oFocus.item, bBackward);
520
+ } else {
521
+ this._moveItemFocus(oEvent, sDirection);
522
+ return;
523
+ }
524
+
525
+ oEvent.preventDefault();
526
+ oEvent.stopPropagation();
527
+ };
528
+
529
+ /**
530
+ * Navigate from a node to its first connector.
531
+ * @returns {boolean} True if focus moved to a connector, false otherwise
532
+ * @private
533
+ */
534
+ KeyboardNavigator.prototype._navigateFromNode = function (oNode, bBackward) {
535
+ const aLines = bBackward ? oNode.getParentLines() : oNode.getChildLines();
536
+ const aVisibleLines = this._getVisibleLines(aLines, bBackward);
537
+
538
+ if (aVisibleLines.length > 0) {
539
+ this._oGraph.setFocus({
540
+ item: aVisibleLines[0],
541
+ button: null
542
+ });
543
+ return true;
544
+ }
545
+ return false;
546
+ };
547
+
548
+ /**
549
+ * Navigate from a connector to its connected node
550
+ * @param {Line} oLine - The connector to navigate from
551
+ * @param {boolean} bBackward - True for source node, false for target node
552
+ * @private
553
+ */
554
+ KeyboardNavigator.prototype._navigateFromConnector = function (oLine, bBackward) {
555
+ const oNode = bBackward ? oLine.getFromNode() : oLine.getToNode();
556
+
557
+ if (oNode && !oNode.isHidden()) {
558
+ this._oGraph.setFocus({
559
+ item: oNode,
560
+ button: null
561
+ });
562
+ }
563
+ };
564
+
565
+ /**
566
+ * Returns visible lines sorted top-to-bottom.
567
+ * @param {Array} aLines - Array of lines to filter and sort
568
+ * @param {boolean} bSortBySource - If true, sort by source node Y position; otherwise by target node
569
+ * @private
570
+ */
571
+ KeyboardNavigator.prototype._getVisibleLines = function (aLines, bSortBySource) {
572
+ if (!aLines || aLines.length === 0) {
573
+ return [];
574
+ }
575
+
576
+ const aVisibleLines = aLines.filter(function (oLine) {
577
+ return !oLine.isHidden() && !oLine._isIgnored() && oLine.getVisible();
578
+ });
579
+
580
+ const fnGetSortY = (oLine) => {
581
+ const oNode = bSortBySource ? oLine.getFromNode() : oLine.getToNode();
582
+ if (oNode && !oNode.isHidden()) {
583
+ return oNode.getY();
584
+ }
585
+ const aCoords = oLine.getCoordinates();
586
+ return (aCoords && aCoords.length > 0) ? aCoords[0].getY() : 0;
587
+ };
588
+ return aVisibleLines.sort((oLine1, oLine2) => fnGetSortY(oLine1) - fnGetSortY(oLine2));
589
+ };
590
+
591
+ KeyboardNavigator.prototype._handleAltAnnounce = function (oEvent, oNode) {
592
+ if (this._oAltNode !== oNode) {
593
+ this._oAltNode = oNode;
594
+ this._oAltConnectors = oNode._getAltNavigationConnectors();
595
+ const { aIncomingConnectors, aOutgoingConnectors, aConnectors } = this._oAltConnectors;
596
+ this._oAltAnnouncement = (aIncomingConnectors.length >= 2 || aOutgoingConnectors.length >= 2)
597
+ ? oNode._getAltConnectorsAnnouncement(aConnectors)
598
+ : null;
599
+ }
600
+ if (!this._oAltAnnouncement) { return; }
601
+ this._oGraph._setAccessibilityTitle(this._oAltAnnouncement);
602
+ oEvent.preventDefault();
603
+ oEvent.stopPropagation();
604
+ };
605
+
606
+ KeyboardNavigator.prototype._handleAltConnectorNavigation = function (oEvent, oNode, iIndex) {
607
+ if (this._oAltNode !== oNode || !this._oAltConnectors) {
608
+ this._oAltNode = oNode;
609
+ this._oAltConnectors = oNode._getAltNavigationConnectors();
610
+ }
611
+ const { aConnectors } = this._oAltConnectors;
612
+ if (iIndex < aConnectors.length) {
613
+ this._oGraph.setFocus({ item: aConnectors[iIndex].line, button: null });
614
+ oEvent.preventDefault();
615
+ oEvent.stopPropagation();
616
+ }
352
617
  };
353
618
 
354
619
  KeyboardNavigator.prototype._handleTabOverLinesWithButtons = function (oEvent, sDirection) {
@@ -576,9 +841,60 @@ sap.ui.define([
576
841
  return;
577
842
  }
578
843
 
844
+ const oFocus = this.getFocus();
845
+ const oItem = oFocus.item;
846
+ if (oItem instanceof Line) {
847
+ if (sDirection === mDirections.UP || sDirection === mDirections.DOWN) {
848
+ this._navigateBetweenConnectors(oEvent, sDirection);
849
+ return;
850
+ }
851
+ }
852
+
853
+ // For all other cases, use default behavior
579
854
  this._moveItemFocus(oEvent, sDirection, true);
580
855
  };
581
856
 
857
+ /**
858
+ * Navigate between multiple connectors from the same source node
859
+ * @private
860
+ */
861
+ KeyboardNavigator.prototype._navigateBetweenConnectors = function (oEvent, sDirection) {
862
+ const oFocus = this.getFocus(),
863
+ oLine = oFocus.item;
864
+
865
+ if (!(oLine instanceof Line)) {
866
+ return;
867
+ }
868
+
869
+ // ↑/↓ navigates among the outgoing connectors of this line's source node,
870
+ // giving a consistent vertical ordering regardless of which connector is focused.
871
+ const oSourceNode = oLine.getFromNode();
872
+ if (!oSourceNode) {
873
+ return;
874
+ }
875
+
876
+ const aVisibleLines = this._getVisibleLines(oSourceNode.getChildLines());
877
+ if (aVisibleLines.length <= 1) {
878
+ return;
879
+ }
880
+
881
+ const iCurrentIndex = aVisibleLines.indexOf(oLine);
882
+ if (iCurrentIndex === -1) {
883
+ return;
884
+ }
885
+
886
+ const iTargetIndex = sDirection === mDirections.DOWN ? iCurrentIndex + 1 : iCurrentIndex - 1;
887
+ if (iTargetIndex >= 0 && iTargetIndex < aVisibleLines.length) {
888
+ this._oGraph.setFocus({
889
+ item: aVisibleLines[iTargetIndex],
890
+ button: null
891
+ });
892
+ }
893
+
894
+ oEvent.preventDefault();
895
+ oEvent.stopPropagation();
896
+ };
897
+
582
898
  KeyboardNavigator.prototype._moveItemFocus = function (oEvent, sDirection, bStopOnLast) {
583
899
  var oFocus,
584
900
  bBackTab = (sDirection === mDirections.LEFT && oEvent.key === "Tab"),
@@ -767,6 +1083,45 @@ sap.ui.define([
767
1083
  oEvent.stopPropagation();
768
1084
  };
769
1085
 
1086
+ KeyboardNavigator.prototype._updateAssociatedDelegate = function (oControl) {
1087
+ if (this._oAssociatedContainer) {
1088
+ this._oAssociatedContainer.removeEventDelegate(this._oAssociatedDelegate);
1089
+ this._oAssociatedContainer = null;
1090
+ this._oAssociatedDelegate = null;
1091
+ }
1092
+ const oContainer = oControl && oControl.getParent();
1093
+ if (oContainer) {
1094
+ this._oAssociatedDelegate = {
1095
+ onkeydown: (oEvent) => {
1096
+ if (oEvent.keyCode === KeyCodes.ALT && oEvent.ctrlKey) {
1097
+ this._bCtrlAltDown = true;
1098
+ }
1099
+
1100
+ if (oEvent.shiftKey && oEvent.keyCode === KeyCodes.F10) {
1101
+ this._oGraph._restoreFocusToGraph();
1102
+ oEvent.preventDefault();
1103
+ oEvent.stopPropagation();
1104
+ } else if (oEvent.keyCode === KeyCodes.INSERT || (this._bCtrlAltDown && oEvent.keyCode === KeyCodes.N)) {
1105
+ this._bCtrlAltDown = false;
1106
+ this._handleAddNode(oEvent);
1107
+ }
1108
+ }
1109
+ };
1110
+ this._oAssociatedContainer = oContainer;
1111
+ oContainer.addEventDelegate(this._oAssociatedDelegate);
1112
+ }
1113
+ this._oAssociatedControl = oControl;
1114
+ };
1115
+
1116
+ KeyboardNavigator.prototype.destroy = function () {
1117
+ if (this._oAssociatedContainer) {
1118
+ this._oAssociatedContainer.removeEventDelegate(this._oAssociatedDelegate);
1119
+ this._oAssociatedContainer = null;
1120
+ this._oAssociatedDelegate = null;
1121
+ }
1122
+ BaseObject.prototype.destroy.apply(this, arguments);
1123
+ };
1124
+
770
1125
  KeyboardNavigator.prototype._ignoreEvent = function (oEvent) {
771
1126
  return !containsOrEquals(this._oWrapperDom, oEvent.target);
772
1127
  };