@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
@@ -20,19 +20,28 @@ sap.ui.define([
20
20
  "sap/ui/core/Core",
21
21
  "sap/ui/core/Theming",
22
22
  "sap/ui/core/Lib",
23
- "./Utils"
24
- ], function(jQuery, library, ElementBase, ElementAttribute, IconPool, Device, encodeXML, sanitizeHTML, CheckBox, Text, NodeRenderer, Core, Theming, CoreLib, Utils) {
23
+ "./Utils",
24
+ "sap/base/Log",
25
+ "./util/DragDropManager",
26
+ "./util/PortManager",
27
+ "./util/CreateConnectionPopover",
28
+ "sap/m/Menu",
29
+ "sap/m/MenuItem"
30
+ ], function(jQuery, library, ElementBase, ElementAttribute, IconPool, Device, encodeXML, sanitizeHTML, CheckBox, Text, NodeRenderer, Core, Theming, CoreLib, Utils, Log, DragDropManager, PortManager, CreateConnectionPopover, Menu, MenuItem) {
25
31
  "use strict";
26
32
 
27
33
  var Shape = library.networkgraph.NodeShape,
28
34
  ActionButtonPosition = library.networkgraph.ActionButtonPosition,
29
35
  HeaderCheckboxState = library.networkgraph.HeaderCheckboxState,
30
- SemanticColorType = library.SemanticColorType;
36
+ SemanticColorType = library.SemanticColorType,
37
+ NodePorts = library.networkgraph.NodePorts;
31
38
 
32
39
  var HEADER_SIZE = 32,
33
40
  TITLE_OFFSET = 5,
34
41
  MIN_WIDTH = 160,
35
- MAX_ACTION_BUTTONS = 4;
42
+ MAX_ACTION_BUTTONS = 4,
43
+ ACTION_BUTTONS_OFFSET = -34,
44
+ MAX_VISIBLE_BUTTONS_ON_TOP = 3;
36
45
 
37
46
  var Size = {
38
47
  Circle: {
@@ -91,6 +100,21 @@ sap.ui.define([
91
100
 
92
101
  var oResourceBundle = CoreLib.getResourceBundleFor("sap.suite.ui.commons");
93
102
 
103
+ // Register BusinessSuiteInAppSymbols font for custom icons
104
+ // IconPool.registerFont({
105
+ // fontFamily: "BusinessSuiteInAppSymbols",
106
+ // collectionName: "BusinessSuiteInAppSymbols",
107
+ // fontURI: sap.ui.require.toUrl("sap/ushell/themes/base/fonts/")
108
+ // });
109
+
110
+ // register TNT icon font
111
+ IconPool.registerFont({
112
+ collectionName: "tnt",
113
+ fontFamily: "SAP-icons-TNT",
114
+ fontURI: sap.ui.require.toUrl("sap/tnt/themes/base/fonts"),
115
+ lazy: false
116
+ });
117
+
94
118
  var Status = library.networkgraph.ElementStatus;
95
119
  var oStatusIconMap = {
96
120
  [Status.Success]: 'sap-icon://sys-enter-2',
@@ -116,6 +140,7 @@ sap.ui.define([
116
140
  var Node = ElementBase.extend("sap.suite.ui.commons.networkgraph.Node", {
117
141
  metadata: {
118
142
  library: "sap.suite.ui.commons",
143
+ dnd : { draggable: true, droppable: false },
119
144
  properties: {
120
145
  /**
121
146
  * Defines if the Header checkbox should be displayed and whether it should be selected or not. By default, the checkbox is hidden. Available only for box nodes.
@@ -271,6 +296,27 @@ sap.ui.define([
271
296
  */
272
297
  nodeTitleBackground: {
273
298
  type: "boolean", group: "Appearance", defaultValue: false
299
+ },
300
+ /**
301
+ * Determines the port configuration for this node. When set, it overrides the graph-level nodePorts setting.
302
+ * If not set (defaults to None), the graph-level setting is used.
303
+ * Ports are available only when the layout algorithm is set to "NoopLayout", and when the drag and drop is enabled.
304
+ * @public
305
+ * @since 1.136.19
306
+ */
307
+ nodePorts: {
308
+ type: "sap.suite.ui.commons.networkgraph.NodePorts", group: "Behavior"
309
+ },
310
+ /**
311
+ * Determines whether the 'Create Connection' action button is displayed on this node.
312
+ * When set, it overrides the graph-level showCreateConnectionButton setting.
313
+ * If set to default (true), the graph-level setting is used.
314
+ * The button is used to create connections between nodes as an accessible alternative to node ports.
315
+ * @public
316
+ * @since 1.136.19
317
+ */
318
+ showCreateConnectionButton: {
319
+ type: "boolean", group: "Behavior", defaultValue: true
274
320
  }
275
321
  },
276
322
  aggregations: {
@@ -338,6 +384,13 @@ sap.ui.define([
338
384
  }
339
385
  },
340
386
  onAfterRendering: function() {
387
+ if (this.getDragDropConfig().length === 0) {
388
+ try {
389
+ DragDropManager.injectDnD(this);
390
+ } catch (error) {
391
+ Log.error("Node DnD: ", error);
392
+ }
393
+ }
341
394
  this._afterRenderingBase();
342
395
  }
343
396
  });
@@ -453,15 +506,25 @@ sap.ui.define([
453
506
  }
454
507
  };
455
508
 
509
+ Node.prototype.exit = function () {
510
+ DragDropManager.removeDnD(this);
511
+ // Cleanup connection popover utility
512
+ if (this._oConnectionPopoverUtil) {
513
+ this._oConnectionPopoverUtil.destroy();
514
+ this._oConnectionPopoverUtil = null;
515
+ }
516
+ };
517
+
456
518
  Node.prototype._isInteractiveNode = function() {
457
519
  var bIsShowExpandButton = this.getShowExpandButton() && this._hasVisibleChildren(),
458
520
  bIsShowDetailButton = this.getShowDetailButton() && (this._hasDetailData() || this.getTitle()),
459
521
  bIsShowActionLinksButton = this.getShowActionLinksButton() && this._hasActionLinks(),
460
522
  aActionButtons = this.getActionButtons(),
461
523
  bIsActionButton = aActionButtons && aActionButtons.length > 0,
462
- bIsPress = this.mEventRegistry.press;
524
+ bIsPress = this.mEventRegistry.press,
525
+ bIsPortButton = this._hasPortActionButton();
463
526
 
464
- return bIsShowExpandButton || bIsShowDetailButton || bIsShowActionLinksButton || bIsActionButton || bIsPress;
527
+ return bIsShowExpandButton || bIsShowDetailButton || bIsShowActionLinksButton || bIsActionButton || bIsPress || bIsPortButton;
465
528
 
466
529
  };
467
530
 
@@ -600,32 +663,54 @@ sap.ui.define([
600
663
 
601
664
  Node.prototype._showDivActionButtons = function(bShow) {
602
665
  var that = this,
666
+ $top = this.$("topdivbuttons"),
603
667
  $right = this.$("rightdivbuttons"),
604
- $left = this.$("leftdivbuttons");
668
+ $left = this.$("leftdivbuttons"),
669
+ oGraph = this.getParent(),
670
+ bShouldShowPortButton = this._showCreateConnectionButton() && oGraph?.getEnableDragAndDrop(),
671
+ oLayoutAlgorithm = oGraph.getLayoutAlgorithm(),
672
+ bShowButtonsOnTop = oGraph._isNoopLayout() && oLayoutAlgorithm.getEnableOptimizedLineAlgorithm();
605
673
 
674
+ // Force re-render if buttons haven't been rendered yet
606
675
  if (bShow && !this._bActionButtonsRendered) {
676
+
677
+ $top.html("");
607
678
  $right.html("");
608
679
  $left.html("");
609
680
 
610
- var iButtonRight = 0,
611
- iButtonLeft = 0;
681
+ var iButtonTop = 0,
682
+ iButtonRight = 0,
683
+ iButtonLeft = 0,
684
+ $defaultWrapper = bShowButtonsOnTop ? $top : $right,
685
+ aOverflowButtons = [];
686
+
687
+ // When buttons are on top, only count enabled buttons for overflow calculation
688
+ // Disabled buttons are hidden with display:none so shouldn't count toward limit
612
689
 
613
690
  if (this.getShowExpandButton()) {
614
- this._appendActionButton({
691
+ var oExpandButtonConfig = {
615
692
  "class": "sapSuiteUiCommonsNetworkNodeActionCollapseIcon",
616
693
  icon: this._getExpandIcon(true),
617
694
  enable: this._hasVisibleChildren(),
618
695
  title: this._getExpandStateTitle(),
619
696
  id: this._getDomId("actionCollapse"),
620
697
  click: this._expandClick.bind(this)
621
- }, $right);
698
+ };
622
699
 
623
- iButtonRight++;
700
+ this._appendActionButton(oExpandButtonConfig, $defaultWrapper);
701
+ // Only increment counter if button is enabled (disabled buttons are hidden)
702
+ if (oExpandButtonConfig.enable) {
703
+ if (bShowButtonsOnTop) {
704
+ iButtonTop++;
705
+ } else {
706
+ iButtonRight++;
707
+ }
708
+ }
624
709
  }
625
710
 
626
711
  if (this.getShowDetailButton()) {
627
712
  var sNodeTitle = this.getTitle();
628
- this._appendActionButton({
713
+ var oDetailButtonConfig = {
629
714
  icon: "sap-icon://menu2",
630
715
  enable: this._hasDetailData() || (sNodeTitle ? sNodeTitle : this.getAltText()),
631
716
  id: this._getDomId("actionDetail"),
@@ -633,15 +718,44 @@ sap.ui.define([
633
718
  click: function(evt) {
634
719
  this._detailClick(evt.target);
635
720
  }.bind(this)
636
- }, $right);
721
+ };
722
+
723
+ this._appendActionButton(oDetailButtonConfig, $defaultWrapper);
637
724
  if (document.getElementById(this._getDomId("actionDetail"))) {
638
725
  document.getElementById(this._getDomId("actionDetail")).setAttribute("aria-haspopup", "dialog");
639
726
  }
640
- iButtonRight++;
727
+ // Only increment counter if button is enabled (disabled buttons are hidden)
728
+ if (oDetailButtonConfig.enable) {
729
+ if (bShowButtonsOnTop) {
730
+ iButtonTop++;
731
+ } else {
732
+ iButtonRight++;
733
+ }
734
+ }
735
+ }
736
+
737
+ // Add port action button if nodePorts is not "None"
738
+ if (bShouldShowPortButton) {
739
+ var oPortButtonConfig = {
740
+ icon: "sap-icon://tnt/item-flow",
741
+ enable: true,
742
+ title: oResourceBundle.getText("NETWORK_GRAPH_CREATE_CONNECTION"),
743
+ id: this._getDomId("actionPorts"),
744
+ click: function(evt) {
745
+ this._showConnectionPopover(evt);
746
+ }.bind(this)
747
+ };
748
+
749
+ this._appendActionButton(oPortButtonConfig, $defaultWrapper);
750
+ if (bShowButtonsOnTop) {
751
+ iButtonTop++;
752
+ } else {
753
+ iButtonRight++;
754
+ }
641
755
  }
642
756
 
643
757
  if (this.getShowActionLinksButton()) {
644
- this._appendActionButton({
758
+ var oLinksButtonConfig = {
645
759
  icon: "sap-icon://chain-link",
646
760
  enable: this._hasActionLinks(),
647
761
  title: oResourceBundle.getText("NETWORK_GRAPH_NODE_LINKS"),
@@ -649,38 +763,118 @@ sap.ui.define([
649
763
  click: function(evt) {
650
764
  this._linksClick(evt.target);
651
765
  }.bind(this)
652
- }, $right);
653
- if (document.getElementById(this._getDomId("actionLinks"))) {
654
- document.getElementById(this._getDomId("actionLinks")).setAttribute("aria-haspopup", "dialog");
766
+ };
767
+
768
+ // Overflow logic: button goes to overflow if we've already placed 3 enabled buttons
769
+ if (bShowButtonsOnTop && iButtonTop >= MAX_VISIBLE_BUTTONS_ON_TOP && oLinksButtonConfig.enable) {
770
+ aOverflowButtons.push(oLinksButtonConfig);
771
+ } else {
772
+ this._appendActionButton(oLinksButtonConfig, $defaultWrapper);
773
+ if (document.getElementById(this._getDomId("actionLinks"))) {
774
+ document.getElementById(this._getDomId("actionLinks")).setAttribute("aria-haspopup", "dialog");
775
+ }
776
+ // Only increment counter if button is enabled (disabled buttons are hidden)
777
+ if (oLinksButtonConfig.enable) {
778
+ if (bShowButtonsOnTop) {
779
+ iButtonTop++;
780
+ } else {
781
+ iButtonRight++;
782
+ }
783
+ }
655
784
  }
656
- iButtonRight++;
657
785
  }
658
786
 
659
- for (var i = 0; (iButtonRight + iButtonLeft) < (MAX_ACTION_BUTTONS * 2) && i < this.getActionButtons().length; i++) {
660
- (function(oButton) { // eslint-disable-line
661
- var sPosition = oButton.getPosition(),
662
- iIndex = sPosition === ActionButtonPosition.Right ? iButtonRight : iButtonLeft;
787
+ for (var i = 0; (iButtonTop + iButtonRight + iButtonLeft) < (MAX_ACTION_BUTTONS * 3) && i < this.getActionButtons().length; i++) {
788
+ (function(oButton) { // eslint-disable-line
789
+ var sPosition = oButton.getPosition();
790
+ var iIndex, $wrapper;
791
+
792
+ // When buttons are shown on top, custom action buttons always go to top position
793
+ if (bShowButtonsOnTop) {
794
+ sPosition = "Top";
795
+ iIndex = iButtonTop;
796
+ $wrapper = $top;
797
+ } else {
798
+ if (sPosition === ActionButtonPosition.Right) {
799
+ iIndex = iButtonRight;
800
+ $wrapper = $right;
801
+ } else if (sPosition === ActionButtonPosition.Left) {
802
+ iIndex = iButtonLeft;
803
+ $wrapper = $left;
804
+ } else {
805
+ iIndex = iButtonTop;
806
+ $wrapper = $top;
807
+ }
663
808
 
664
809
  // switch lanes if there is more then 4 items on the line
665
810
  if (iIndex >= MAX_ACTION_BUTTONS) {
666
- sPosition = sPosition === ActionButtonPosition.Left ? ActionButtonPosition.Right : ActionButtonPosition.Left;
811
+ if (sPosition === ActionButtonPosition.Right) {
812
+ sPosition = ActionButtonPosition.Left;
813
+ $wrapper = $left;
814
+ } else if (sPosition === ActionButtonPosition.Left) {
815
+ sPosition = "Top"; // Custom position for top
816
+ $wrapper = $top;
817
+ } else {
818
+ sPosition = ActionButtonPosition.Right;
819
+ $wrapper = $right;
820
+ }
667
821
  }
822
+ }
668
823
 
669
- var $wrapper = sPosition === ActionButtonPosition.Right ? $right : $left;
824
+ var oButtonConfig = {
825
+ icon: oButton.getIcon(),
826
+ enable: oButton.getEnabled(),
827
+ title: oButton.getTitle(),
828
+ id: oButton.getId(),
829
+ click: function(evt) {
830
+ oButton.firePress({
831
+ buttonElement: evt.target
832
+ });
833
+ // Restore focus after a small timeout (to let any rerender settle)
834
+ setTimeout(function () {
835
+ if (oButton && oButton.getDomRef()) {
836
+ that.getParent().setFocus({item:oButton.getParent(), button: oButton.getDomRef()});
837
+ }
838
+ }, 0);
839
+ }
840
+ };
670
841
 
671
- that._appendActionButton({
672
- icon: oButton.getIcon(),
673
- enable: oButton.getEnabled(),
674
- title: oButton.getTitle(),
675
- id: oButton.getId(),
676
- click: function(evt) {
677
- oButton.firePress({
678
- buttonElement: evt.target
679
- });
842
+ // Apply overflow logic only when buttons are shown on top
843
+ // Custom buttons go to overflow if we've already placed 3 enabled buttons
844
+ if (bShowButtonsOnTop && iButtonTop >= MAX_VISIBLE_BUTTONS_ON_TOP && oButtonConfig.enable) {
845
+ aOverflowButtons.push(oButtonConfig);
846
+ } else {
847
+ that._appendActionButton(oButtonConfig, $wrapper);
848
+ // Only increment counter if button is enabled (disabled buttons are hidden)
849
+ if (oButtonConfig.enable) {
850
+ // Increment the correct counter
851
+ if (bShowButtonsOnTop) {
852
+ iButtonTop++;
853
+ } else if (sPosition === ActionButtonPosition.Right) {
854
+ iButtonRight++;
855
+ } else if (sPosition === ActionButtonPosition.Left) {
856
+ iButtonLeft++;
857
+ } else {
858
+ iButtonTop++;
680
859
  }
681
- }, $wrapper);
682
- })(this.getActionButtons()[i]);
860
+ }
861
+ }
862
+ })(this.getActionButtons()[i], i);
863
+ }
864
+
865
+ // Add overflow button if there are overflow items
866
+ if (bShowButtonsOnTop && aOverflowButtons.length > 0) {
867
+ this._appendActionButton({
868
+ icon: "sap-icon://overflow",
869
+ enable: true,
870
+ title: "More Actions",
871
+ id: this._getDomId("actionOverflow"),
872
+ click: function(evt) {
873
+ this._showOverflowPopover(evt.target, aOverflowButtons);
874
+ }.bind(this)
875
+ }, $top);
683
876
  }
877
+
684
878
  this._bActionButtonsRendered = true;
685
879
  }
686
880
 
@@ -715,6 +909,7 @@ sap.ui.define([
715
909
  id: sId,
716
910
  "data-sap-ui": sId,
717
911
  "class": sNodeClass,
912
+ "draggable": !!(this.getParent()._isDnDEnabled())
718
913
  };
719
914
 
720
915
  if (sNodeClass.includes("sapSuiteUiCommonsNetworkBox") && this.getTooltip_AsString()) {
@@ -729,7 +924,8 @@ sap.ui.define([
729
924
  // this may put nodes to the edge of the parent which makes calculation of div maxWidth impossible
730
925
  top: !this._bMainRender && this.getY() ? (this.getY() + "px") : "",
731
926
  left: !this._bMainRender && this.getX() ? (this.getX() + "px") : "",
732
- "min-width": MIN_WIDTH + "px"
927
+ "min-width": MIN_WIDTH + "px",
928
+ "outline-offset": parseFloat(this._getStatusValue(ElementBase.ColorType.BorderWidth)) + .7 + "px"
733
929
  }, oAttributes, mOptions.renderManager);
734
930
 
735
931
  this.renderContent(mOptions);
@@ -780,11 +976,11 @@ sap.ui.define([
780
976
  "border-color": ElementBase.ColorType.Background
781
977
  }, false);
782
978
 
783
- let {style: sBorderColor, class: sBorderClass} = this._getStatusStyle({
784
- "border-color": ElementBase.ColorType.Border,
979
+ let { style: sBorderColor, class: sBorderClass } = this._getStatusStyle({
980
+ "outline-color": ElementBase.ColorType.Border,
785
981
  "background-color": this.getSelected() ? ElementBase.ColorType.SelectedBackground : "",
786
- "border-width": ElementBase.ColorType.BorderWidth,
787
- "border-style": ElementBase.ColorType.BorderStyle
982
+ "outline-width": ElementBase.ColorType.BorderWidth,
983
+ "outline-style": ElementBase.ColorType.BorderStyle
788
984
  }, false);
789
985
 
790
986
  mOptions.renderManager.openStart("div", sId + "-wrapperwithbuttons");
@@ -828,10 +1024,10 @@ sap.ui.define([
828
1024
  } else {
829
1025
  var oWidth = this._getCorrectWidth();
830
1026
  let {style: sBStyle, class: sBClass} = this._getStatusStyle({
831
- "border-color": ElementBase.ColorType.Border,
1027
+ "outline-color": ElementBase.ColorType.Border,
832
1028
  "background-color": this.getSelected() ? ElementBase.ColorType.SelectedBackground : "",
833
- "border-width": ElementBase.ColorType.BorderWidth,
834
- "border-style": ElementBase.ColorType.BorderStyle
1029
+ "outline-width": ElementBase.ColorType.BorderWidth,
1030
+ "outline-style": ElementBase.ColorType.BorderStyle
835
1031
  });
836
1032
  var sMainStyle = this._convertToStyle({
837
1033
  width: oWidth.width,
@@ -1784,21 +1980,21 @@ sap.ui.define([
1784
1980
 
1785
1981
  //Recursive Traversal ensures all shared parents of collapsed descendants have their action buttons re-rendered correctly.
1786
1982
  Node.prototype._invalidateSharedParents = function(oNode, oOrigin = oNode, oVisited = new Set()) {
1787
- if (oVisited.has(oNode)) {
1788
- return;
1789
- }
1790
- oVisited.add(oNode);
1791
-
1792
- oNode.getParentNodes().forEach(parent => {
1793
- if (parent !== oOrigin) {
1794
- parent._bActionButtonsRendered = false;
1983
+ if (oVisited.has(oNode)) {
1984
+ return;
1795
1985
  }
1796
- });
1986
+ oVisited.add(oNode);
1797
1987
 
1798
- oNode.getChildNodes().forEach(oChild => {
1799
- this._invalidateSharedParents(oChild, oOrigin, oVisited);
1800
- });
1801
- }
1988
+ oNode.getParentNodes().forEach(parent => {
1989
+ if (parent !== oOrigin) {
1990
+ parent._bActionButtonsRendered = false;
1991
+ }
1992
+ });
1993
+
1994
+ oNode.getChildNodes().forEach(oChild => {
1995
+ this._invalidateSharedParents(oChild, oOrigin, oVisited);
1996
+ });
1997
+ }
1802
1998
 
1803
1999
  Node.prototype._setTextHeight = function($el, iMaxLines) {
1804
2000
  if ($el[0] && iMaxLines > 0) {
@@ -1963,6 +2159,7 @@ sap.ui.define([
1963
2159
  $wrapper.on("click", function(oEvent) {
1964
2160
  if (oEvent.which === 1) {
1965
2161
  this._onClick(oEvent.ctrlKey);
2162
+ this._togglePortsOnNode();
1966
2163
  }
1967
2164
  oEvent.preventDefault();
1968
2165
  }.bind(this));
@@ -2434,28 +2631,138 @@ sap.ui.define([
2434
2631
  }
2435
2632
  };
2436
2633
 
2437
- Node.prototype._getAccessibilityLabel = function(oGraph) {
2438
- var oParent = oGraph ? oGraph : this.getParent();
2439
- var sNodeTitle = this.getTitle();
2440
- var sNodeTitleText = sNodeTitle ? sNodeTitle : this.getAltText();
2441
- var sLabel = oResourceBundle.getText("NETWORK_GRAPH_NODE") + " " + sNodeTitleText;
2442
- if (this._isBox(oParent)) {
2443
- this.getVisibleAttributes().forEach(function(oAttribute) {
2444
- sLabel += " " + oAttribute.getLabel() + " " + oAttribute.getValue();
2634
+ Node.prototype._getAccessibilityLabel = function (oGraph) {
2635
+ const oParent = oGraph || this.getParent();
2636
+ const sNodeTitle = this.getTitle();
2637
+ const sNodeTitleText = sNodeTitle || this.getAltText();
2638
+ let sLabel = `${oResourceBundle.getText(
2639
+ "NETWORK_GRAPH_NODE"
2640
+ )} ${sNodeTitleText}`;
2641
+ const aSentenceParts = [];
2642
+
2643
+ if (this.getDescription()) {
2644
+ aSentenceParts.push(
2645
+ `${oResourceBundle.getText(
2646
+ "NETWORK_GRAPH_NODE_DESCRIPTION"
2647
+ )} ${this.getDescription()}`
2648
+ );
2649
+ }
2650
+
2651
+ if (this._isBox(oParent) && this.getVisibleAttributes().length > 0) {
2652
+ const aAttributeParts = [
2653
+ oResourceBundle.getText("NETWORK_GRAPH_NODE_ATTRIBUTES"),
2654
+ ];
2655
+ this.getVisibleAttributes().forEach((oAttribute) => {
2656
+ aAttributeParts.push(
2657
+ `${oAttribute.getLabel()} ${oAttribute.getValue()}`
2658
+ );
2445
2659
  });
2660
+ aSentenceParts.push(aAttributeParts.join(" "));
2446
2661
  }
2447
- if (this.getStatus() && this.getStatus() != ""){
2448
- var statusID = this.getStatus();
2449
- if (oParent._oStatuses[statusID]) {
2450
- sLabel += " " + oResourceBundle.getText("PF_ARIA_STATUS") + " " + oParent._oStatuses[statusID].getTitle();
2451
- } else {
2452
- sLabel += " " + oResourceBundle.getText("PF_ARIA_STATUS") + " " + this.getStatus();
2453
- }
2662
+
2663
+ const sStatusText = this._getStatusText(oParent._oStatuses);
2664
+
2665
+ if (sStatusText) {
2666
+ aSentenceParts.push(
2667
+ `${oResourceBundle.getText(
2668
+ "NETWORK_GRAPH_NODE_ACCESSIBILITY_STATUS"
2669
+ )} ${sStatusText}`
2670
+ );
2454
2671
  }
2672
+
2455
2673
  if (this.getSelected()) {
2456
- sLabel += " " + oResourceBundle.getText("NETWORK_GRAPH_SELECTED_NODE");
2674
+ aSentenceParts.push(
2675
+ oResourceBundle.getText("NETWORK_GRAPH_SELECTED_NODE")
2676
+ );
2457
2677
  }
2458
- return sLabel + "." + oResourceBundle.getText("NETWORK_GRAPH_ACCESSIBILITY_TOGGLE_STATE");
2678
+
2679
+ aSentenceParts.push(
2680
+ oResourceBundle.getText("NETWORK_GRAPH_ACCESSIBILITY_TOGGLE_STATE")
2681
+ );
2682
+
2683
+ const iIncomingConnectors = this.getParentLines().length;
2684
+ const sIncomingTextKey = iIncomingConnectors === 1
2685
+ ? "NETWORK_GRAPH_INCOMING_CONNECTOR_SINGULAR"
2686
+ : "NETWORK_GRAPH_INCOMING_CONNECTORS_PLURAL";
2687
+ aSentenceParts.push(oResourceBundle.getText(sIncomingTextKey, [iIncomingConnectors]));
2688
+
2689
+ const iOutgoingConnectors = this.getChildLines().length;
2690
+ const sOutgoingTextKey = iOutgoingConnectors === 1
2691
+ ? "NETWORK_GRAPH_OUTGOING_CONNECTOR_SINGULAR"
2692
+ : "NETWORK_GRAPH_OUTGOING_CONNECTORS_PLURAL";
2693
+ aSentenceParts.push(oResourceBundle.getText(sOutgoingTextKey, [iOutgoingConnectors]));
2694
+
2695
+ if (iOutgoingConnectors > 0) {
2696
+ aSentenceParts.push(oResourceBundle.getText("NETWORK_GRAPH_NAVIGATION_TAB_TO_OUTGOING"));
2697
+ }
2698
+
2699
+ if (iIncomingConnectors > 0) {
2700
+ aSentenceParts.push(oResourceBundle.getText("NETWORK_GRAPH_NAVIGATION_SHIFT_TAB_TO_INCOMING"));
2701
+ }
2702
+
2703
+ const { aIncomingConnectors, aOutgoingConnectors } = this._getAltNavigationConnectors();
2704
+ if (aIncomingConnectors.length >= 2 || aOutgoingConnectors.length >= 2) {
2705
+ aSentenceParts.push(oResourceBundle.getText("NETWORK_GRAPH_NAVIGATION_ALT_CONNECTORS"));
2706
+ }
2707
+
2708
+ return `${sLabel}. ${aSentenceParts.join(". ")}.`;
2709
+ };
2710
+
2711
+ Node.prototype._getAltNavigationConnectors = function () {
2712
+ const iThisX = this.getX();
2713
+ const fnDir = (oNode) => oNode?.getX() < iThisX ? "left" : "right";
2714
+ const fnActive = (oLine) => oLine.getVisible() && !oLine.isHidden() && !oLine._isIgnored();
2715
+
2716
+ // Incoming: all valid lines, sorted top-to-bottom by source node Y, then by node key for stability
2717
+ const aIncomingConnectors = this.getParentLines()
2718
+ .filter((oLine) => fnActive(oLine) && oLine.getFromNode())
2719
+ .sort((a, b) => {
2720
+ const iY = a.getFromNode().getY() - b.getFromNode().getY();
2721
+ return iY !== 0 ? iY : a.getFromNode().getKey().localeCompare(b.getFromNode().getKey());
2722
+ })
2723
+ .map((oLine) => ({ line: oLine, type: "incoming", direction: fnDir(oLine.getFromNode()) }));
2724
+
2725
+ // Outgoing: all valid lines, sorted top-to-bottom by target node Y, then by node key for stability
2726
+ const aOutgoingConnectors = this.getChildLines()
2727
+ .filter((oLine) => fnActive(oLine) && oLine.getToNode())
2728
+ .sort((a, b) => {
2729
+ const iY = a.getToNode().getY() - b.getToNode().getY();
2730
+ return iY !== 0 ? iY : a.getToNode().getKey().localeCompare(b.getToNode().getKey());
2731
+ })
2732
+ .map((oLine) => ({ line: oLine, type: "outgoing", direction: fnDir(oLine.getToNode()) }));
2733
+
2734
+ const aConnectors = ["left", "right"].flatMap((sDir) => [
2735
+ ...aIncomingConnectors.filter((o) => o.direction === sDir),
2736
+ ...aOutgoingConnectors.filter((o) => o.direction === sDir)
2737
+ ]);
2738
+
2739
+ return { aIncomingConnectors, aOutgoingConnectors, aConnectors };
2740
+ };
2741
+
2742
+ Node.prototype._getAltConnectorsAnnouncement = function (aConnectors) {
2743
+ const oGraph = this.getParent();
2744
+ const oMapping = oGraph.getConnectionTypeMapping();
2745
+ const bRTL = oGraph._bIsRtl;
2746
+ return aConnectors.map((oConnector, i) => {
2747
+ const oLine = oConnector.line;
2748
+ const sConnectionType = oLine.getConnectionType();
2749
+ const sResolvedType = bRTL ? (Utils.mRTLSwap[sConnectionType] || sConnectionType) : sConnectionType;
2750
+ const sConnectionTypeText = oMapping[sResolvedType] || sResolvedType;
2751
+ const sFromNodeTitle = oLine.getFromNode().getTitle() || oLine.getFromNode().getAltText();
2752
+ const sToNodeTitle = oLine.getToNode().getTitle() || oLine.getToNode().getAltText();
2753
+ // Determine the physical port side from connection type (Left key = left port, Right key = right port)
2754
+ const sSide = oConnector.type === "outgoing"
2755
+ ? (sResolvedType.startsWith("Left") ? "left" : "right")
2756
+ : (sResolvedType.endsWith("Left") ? "left" : "right");
2757
+ return oResourceBundle.getText("NETWORK_GRAPH_NAVIGATION_ALT_CONNECTOR_ANNOUNCE", [
2758
+ oConnector.type,
2759
+ sSide,
2760
+ sConnectionTypeText,
2761
+ sFromNodeTitle,
2762
+ sToNodeTitle,
2763
+ i + 1
2764
+ ]);
2765
+ }).join(". ");
2459
2766
  };
2460
2767
 
2461
2768
  Node.prototype._setStatusColors = function(sType) {
@@ -2519,10 +2826,10 @@ sap.ui.define([
2519
2826
  $titleText = this.$().find(".sapSuiteUiCommonsNetworkGraphDivNodeTitleText");
2520
2827
 
2521
2828
  if(SemanticColorType.hasOwnProperty(sBackgroundColor)){
2522
- $wrapper.addClass(Utils.SEMANTIC_CLASS_NAME.BORDER + sBackgroundColor);
2829
+ $wrapper.addClass(Utils.SEMANTIC_CLASS_NAME.OUTLINE + sBackgroundColor);
2523
2830
  } else {
2524
2831
  /** @deprecated As of 1.120 */
2525
- $wrapper.css("border-color", sBorderColor);
2832
+ $wrapper.css("outline-color", sBorderColor);
2526
2833
  }
2527
2834
 
2528
2835
  let sFocusColor = this._getColor(ElementBase.ColorType[sType + "Focus"])
@@ -2834,12 +3141,21 @@ sap.ui.define([
2834
3141
  */
2835
3142
  Node.prototype.renderHtmlActionButtons = function(mOptions) {
2836
3143
  mOptions = mOptions || {};
2837
- var sRightId = this._getElementId(mOptions.idSufix) + "-rightdivbuttons",
3144
+ var sTopId = this._getElementId(mOptions.idSufix) + "-topdivbuttons",
3145
+ sRightId = this._getElementId(mOptions.idSufix) + "-rightdivbuttons",
2838
3146
  sLeftId = this._getElementId(mOptions.idSufix) + "-leftdivbuttons",
3147
+ bIsRtl = this.getParent()._bIsRtl,
3148
+ fBorderWidth = parseFloat(this._getStatusValue(ElementBase.ColorType.BorderWidth)) || 0,
3149
+ sOffsetPosition = (ACTION_BUTTONS_OFFSET - fBorderWidth) + "px",
2839
3150
  oRm = mOptions.renderManager;
2840
3151
 
3152
+ oRm.openStart("div").attr("id", sTopId).class("sapSuiteUiCommonsNetworkGraphDivActionButtons").class("sapSuiteUiCommonsNetworkGraphDivActionButtonsTop").openEnd().close("div");
2841
3153
  oRm.openStart("div").attr("id", sLeftId).class("sapSuiteUiCommonsNetworkGraphDivActionButtons").class("sapSuiteUiCommonsNetworkGraphDivActionButtonsLeft").openEnd().close("div");
2842
- oRm.openStart("div").attr("id", sRightId).class("sapSuiteUiCommonsNetworkGraphDivActionButtons").class("sapSuiteUiCommonsNetworkGraphDivActionButtonsRight").openEnd().close("div");
3154
+ oRm.openStart("div").attr("id", sRightId).class("sapSuiteUiCommonsNetworkGraphDivActionButtons").class("sapSuiteUiCommonsNetworkGraphDivActionButtonsRight");
3155
+ if (fBorderWidth) {
3156
+ oRm.style(bIsRtl ? "left" : "right", sOffsetPosition);
3157
+ }
3158
+ oRm.openEnd().close("div");
2843
3159
  };
2844
3160
 
2845
3161
  /**
@@ -3726,5 +4042,183 @@ sap.ui.define([
3726
4042
  return this;
3727
4043
  };
3728
4044
 
4045
+ /**
4046
+ * Checks if the port action button should be shown.
4047
+ * @returns {boolean} True if the port action button should be displayed
4048
+ * @private
4049
+ */
4050
+ Node.prototype._hasPortActionButton = function() {
4051
+ return this._showCreateConnectionButton();
4052
+ };
4053
+
4054
+ /**
4055
+ * Toggles ports on the node when the port action button is clicked.
4056
+ * @private
4057
+ */
4058
+ Node.prototype._togglePortsOnNode = function() {
4059
+ // Check if the layout algorithm supports node ports
4060
+ if (this._supportsNodePortsForNode()) {
4061
+ // Ports are only available for supported layout algorithms
4062
+ this._addPortsToNode(true);
4063
+ }
4064
+ };
4065
+
4066
+ /**
4067
+ * Adds ports to the node or removes them if they are already present.
4068
+ * @param {boolean} bTriggerNode Indicates whether this node is the trigger node for ports.
4069
+ * @private
4070
+ */
4071
+ Node.prototype._addPortsToNode = function (bTriggerNode) {
4072
+ var oGraph = this.getParent();
4073
+
4074
+ // Check if ports are enabled for this node
4075
+ if (!oGraph || this._getNodePorts() === "None") {
4076
+ PortManager.removeAllPorts();
4077
+ return;
4078
+ }
4079
+
4080
+ var sCurrentNodeKey = this.getKey();
4081
+ var sCurrentTriggerNodeKey = PortManager.getCurrentTriggerNodeKey();
4082
+
4083
+ // Scenario 1: Clicking on the same node that already has trigger ports -> remove ports
4084
+ if (sCurrentTriggerNodeKey === sCurrentNodeKey) {
4085
+ PortManager.removeAllPorts();
4086
+ return;
4087
+ }
4088
+
4089
+ // Scenario 2: Clicking on a different node (or no current trigger node) -> remove all ports and add to this node
4090
+ PortManager.removeAllPorts();
4091
+ PortManager.addPortsToNode(this, true);
4092
+ };
4093
+
4094
+ /**
4095
+ * Uses node-level setting if explicitly set, otherwise it reverts to the graph-level setting.
4096
+ * If the node-level property is set to any value (including None), it takes precedence over the graph-level setting.
4097
+ * @returns {string} The effective nodePorts value
4098
+ * @private
4099
+ */
4100
+ Node.prototype._getNodePorts = function() {
4101
+ // Check if the property was explicitly set on the node (stored in mProperties)
4102
+ var mProperties = this.mProperties;
4103
+ if (mProperties && mProperties.hasOwnProperty("nodePorts")) {
4104
+ var sNodePorts = this.getNodePorts();
4105
+ // Validate that the value is one of the valid NodePorts enum values
4106
+ var bIsValidValue = Object.values(NodePorts).indexOf(sNodePorts) !== -1;
4107
+ if (bIsValidValue) {
4108
+ // Use node-level setting if it's a valid enum value
4109
+ return sNodePorts;
4110
+ }
4111
+ }
4112
+ // Otherwise, fall back to graph-level setting
4113
+ var oGraph = this.getParent();
4114
+ return oGraph ? oGraph.getNodePorts() : NodePorts.None;
4115
+ };
4116
+
4117
+ /**
4118
+ * Gets the effective showCreateConnectionButton setting for this node.
4119
+ * Uses node-level setting if explicitly set, otherwise falls back to graph-level setting.
4120
+ * @returns {boolean} The effective showCreateConnectionButton value
4121
+ * @private
4122
+ */
4123
+ Node.prototype._showCreateConnectionButton = function() {
4124
+ // Check if the property was explicitly set on the node (stored in mProperties)
4125
+ var mProperties = this.mProperties;
4126
+ if (mProperties && mProperties.hasOwnProperty("showCreateConnectionButton")) {
4127
+ return this.getShowCreateConnectionButton();
4128
+ }
4129
+ // Otherwise, fall back to graph-level setting
4130
+ var oGraph = this.getParent();
4131
+ return oGraph ? oGraph.getShowCreateConnectionButton() : true;
4132
+ };
4133
+
4134
+ /**
4135
+ * Checks if node ports are supported for this node based on node-level or graph-level settings.
4136
+ * @returns {boolean} True if node ports are supported
4137
+ * @private
4138
+ */
4139
+ Node.prototype._supportsNodePortsForNode = function() {
4140
+ var oGraph = this.getParent();
4141
+ if (!oGraph) {
4142
+ return false;
4143
+ }
4144
+ return this._getNodePorts() !== "None" && oGraph.getEnableDragAndDrop();
4145
+ };
4146
+
4147
+ /**
4148
+ * Shows the connection popover instead of toggling ports.
4149
+ * @param {Event} oEvent - The click event from the action button
4150
+ * @private
4151
+ */
4152
+ Node.prototype._showConnectionPopover = function(oEvent) {
4153
+ // Create connection popover utility if it doesn't exist
4154
+ if (!this._oConnectionPopoverUtil) {
4155
+ this._oConnectionPopoverUtil = new CreateConnectionPopover();
4156
+ }
4157
+
4158
+ // Show the connection popover
4159
+ this._oConnectionPopoverUtil.show({
4160
+ sourceNode: this,
4161
+ event: oEvent,
4162
+ onConnectionCreate: function(oConnectionData) {
4163
+ var oGraph = this.getParent();
4164
+ if (oGraph) {
4165
+ // Fire the connectionCreated event
4166
+ oGraph.fireConnectionCreated(oConnectionData);
4167
+ }
4168
+ }.bind(this)
4169
+ });
4170
+ };
4171
+
4172
+ /**
4173
+ * Shows the overflow menu with additional action buttons.
4174
+ * @param {sap.ui.core.Control} oControl The control to open the menu next to
4175
+ * @param {Array} aOverflowButtons Array of button configurations to display in the overflow
4176
+ * @private
4177
+ */
4178
+ Node.prototype._showOverflowPopover = function(oControl, aOverflowButtons) {
4179
+ var that = this;
4180
+
4181
+ // Filter out disabled buttons (same behavior as _appendActionButton which hides disabled buttons)
4182
+ var aEnabledButtons = aOverflowButtons.filter(function(oButtonConfig) {
4183
+ return oButtonConfig.enable !== false;
4184
+ });
4185
+
4186
+ // If no enabled buttons, don't show the menu
4187
+ if (aEnabledButtons.length === 0) {
4188
+ return;
4189
+ }
4190
+
4191
+ // Create menu items for each enabled overflow item
4192
+ var aMenuItems = aEnabledButtons.map(function(oButtonConfig) {
4193
+ return new MenuItem({
4194
+ id: oButtonConfig.id ? oButtonConfig.id + "-overflow" : undefined,
4195
+ text: oButtonConfig.title,
4196
+ icon: oButtonConfig.icon,
4197
+ customData: [new sap.ui.core.CustomData({
4198
+ key: "buttonConfig",
4199
+ value: oButtonConfig
4200
+ })]
4201
+ });
4202
+ });
4203
+
4204
+ // Create the menu
4205
+ var oMenu = new Menu({
4206
+ items: aMenuItems,
4207
+ itemSelected: function(oEvent) {
4208
+ var oItem = oEvent.getParameter("item");
4209
+ var oButtonConfig = oItem.data("buttonConfig");
4210
+ if (oButtonConfig && oButtonConfig.click) {
4211
+ // Call the click handler with the overflow button as the target (not the menu item)
4212
+ oButtonConfig.click({ target: oControl });
4213
+ }
4214
+ },
4215
+ closed: function() {
4216
+ oMenu.destroy();
4217
+ }
4218
+ });
4219
+
4220
+ oMenu.openBy(oControl);
4221
+ };
4222
+
3729
4223
  return Node;
3730
4224
  });