@sapui5/sap.fe.templates 1.146.0 → 1.148.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/package.json +1 -1
  2. package/src/sap/fe/templates/.library +1 -1
  3. package/src/sap/fe/templates/AnalyticalListPage/manifest.json +1 -1
  4. package/src/sap/fe/templates/ListReport/ExtensionAPI.js +1 -1
  5. package/src/sap/fe/templates/ListReport/ExtensionAPI.ts +1 -1
  6. package/src/sap/fe/templates/ListReport/ListReport.view.xml +45 -10
  7. package/src/sap/fe/templates/ListReport/ListReportController.controller.js +43 -16
  8. package/src/sap/fe/templates/ListReport/ListReportController.controller.ts +51 -15
  9. package/src/sap/fe/templates/ListReport/ListReportTemplating.js +39 -3
  10. package/src/sap/fe/templates/ListReport/ListReportTemplating.ts +35 -1
  11. package/src/sap/fe/templates/ListReport/controls/MultipleModeControl.js +4 -1
  12. package/src/sap/fe/templates/ListReport/controls/MultipleModeControl.ts +3 -0
  13. package/src/sap/fe/templates/ListReport/manifest.json +1 -1
  14. package/src/sap/fe/templates/ListReport/overrides/MessageHandler.js +3 -2
  15. package/src/sap/fe/templates/ListReport/overrides/MessageHandler.ts +2 -1
  16. package/src/sap/fe/templates/ObjectPage/Component.js +4 -2
  17. package/src/sap/fe/templates/ObjectPage/Component.ts +3 -1
  18. package/src/sap/fe/templates/ObjectPage/ExtensionAPI.js +163 -28
  19. package/src/sap/fe/templates/ObjectPage/ExtensionAPI.ts +163 -23
  20. package/src/sap/fe/templates/ObjectPage/ObjectPageController.controller.js +246 -35
  21. package/src/sap/fe/templates/ObjectPage/ObjectPageController.controller.ts +286 -42
  22. package/src/sap/fe/templates/ObjectPage/ObjectPageTemplating.js +32 -16
  23. package/src/sap/fe/templates/ObjectPage/ObjectPageTemplating.ts +46 -33
  24. package/src/sap/fe/templates/ObjectPage/components/CollaborationDiscardDialog.js +4 -1
  25. package/src/sap/fe/templates/ObjectPage/components/CollaborationDiscardDialog.tsx +1 -0
  26. package/src/sap/fe/templates/ObjectPage/components/CollaborationDraft.js +15 -4
  27. package/src/sap/fe/templates/ObjectPage/components/CollaborationDraft.tsx +11 -2
  28. package/src/sap/fe/templates/ObjectPage/controls/StashableHBox.js +28 -1
  29. package/src/sap/fe/templates/ObjectPage/controls/StashableHBox.ts +31 -0
  30. package/src/sap/fe/templates/ObjectPage/helpers/SectionNavigationHelper.js +72 -0
  31. package/src/sap/fe/templates/ObjectPage/helpers/SectionNavigationHelper.ts +75 -0
  32. package/src/sap/fe/templates/ObjectPage/manifest.json +1 -15
  33. package/src/sap/fe/templates/ObjectPage/overrides/CollaborationManager.js +29 -20
  34. package/src/sap/fe/templates/ObjectPage/overrides/CollaborationManager.ts +28 -21
  35. package/src/sap/fe/templates/ObjectPage/overrides/IntentBasedNavigation.js +4 -3
  36. package/src/sap/fe/templates/ObjectPage/overrides/IntentBasedNavigation.ts +4 -4
  37. package/src/sap/fe/templates/ObjectPage/overrides/Share.js +2 -2
  38. package/src/sap/fe/templates/ObjectPage/overrides/Share.ts +1 -1
  39. package/src/sap/fe/templates/ObjectPage/overrides/ViewState.js +93 -17
  40. package/src/sap/fe/templates/ObjectPage/overrides/ViewState.ts +108 -22
  41. package/src/sap/fe/templates/ObjectPage/view/fragments/Actions.fragment.xml +12 -2
  42. package/src/sap/fe/templates/ObjectPage/view/fragments/EmphasizedFirstHeaderAction.fragment.xml +182 -0
  43. package/src/sap/fe/templates/ObjectPage/view/fragments/ExpandedHeading.fragment.xml +4 -4
  44. package/src/sap/fe/templates/ObjectPage/view/fragments/Heading.fragment.xml +4 -4
  45. package/src/sap/fe/templates/ObjectPage/view/fragments/ObjectPageHeaderAddress.fragment.xml +1 -1
  46. package/src/sap/fe/templates/ObjectPage/view/fragments/ObjectPageHeaderContact.fragment.xml +1 -1
  47. package/src/sap/fe/templates/ObjectPage/view/fragments/ObjectPageHeaderForm.fragment.xml +1 -0
  48. package/src/sap/fe/templates/ObjectPage/view/fragments/Section.fragment.xml +2 -0
  49. package/src/sap/fe/templates/ObjectPage/view/fragments/SectionContent.fragment.xml +18 -12
  50. package/src/sap/fe/templates/ObjectPage/view/fragments/SectionFormContent.fragment.xml +2 -0
  51. package/src/sap/fe/templates/ObjectPage/view/fragments/SectionMoreFormContent.fragment.xml +2 -0
  52. package/src/sap/fe/templates/library.js +1 -1
  53. package/src/sap/fe/templates/messagebundle.properties +1 -1
  54. package/src/sap/fe/templates/messagebundle_ar.properties +1 -1
  55. package/src/sap/fe/templates/messagebundle_bg.properties +1 -1
  56. package/src/sap/fe/templates/messagebundle_ca.properties +1 -1
  57. package/src/sap/fe/templates/messagebundle_cnr.properties +1 -1
  58. package/src/sap/fe/templates/messagebundle_cs.properties +1 -1
  59. package/src/sap/fe/templates/messagebundle_cy.properties +1 -1
  60. package/src/sap/fe/templates/messagebundle_da.properties +1 -1
  61. package/src/sap/fe/templates/messagebundle_de.properties +1 -1
  62. package/src/sap/fe/templates/messagebundle_el.properties +1 -1
  63. package/src/sap/fe/templates/messagebundle_en.properties +1 -1
  64. package/src/sap/fe/templates/messagebundle_en_GB.properties +1 -1
  65. package/src/sap/fe/templates/messagebundle_en_US_saprigi.properties +1 -1
  66. package/src/sap/fe/templates/messagebundle_es.properties +1 -1
  67. package/src/sap/fe/templates/messagebundle_es_MX.properties +1 -1
  68. package/src/sap/fe/templates/messagebundle_et.properties +1 -1
  69. package/src/sap/fe/templates/messagebundle_fi.properties +1 -1
  70. package/src/sap/fe/templates/messagebundle_fr.properties +1 -1
  71. package/src/sap/fe/templates/messagebundle_fr_CA.properties +1 -1
  72. package/src/sap/fe/templates/messagebundle_hi.properties +1 -1
  73. package/src/sap/fe/templates/messagebundle_hr.properties +1 -1
  74. package/src/sap/fe/templates/messagebundle_hu.properties +1 -1
  75. package/src/sap/fe/templates/messagebundle_id.properties +1 -1
  76. package/src/sap/fe/templates/messagebundle_it.properties +1 -1
  77. package/src/sap/fe/templates/messagebundle_iw.properties +2 -2
  78. package/src/sap/fe/templates/messagebundle_ja.properties +1 -1
  79. package/src/sap/fe/templates/messagebundle_kk.properties +1 -1
  80. package/src/sap/fe/templates/messagebundle_ko.properties +1 -1
  81. package/src/sap/fe/templates/messagebundle_lt.properties +1 -1
  82. package/src/sap/fe/templates/messagebundle_lv.properties +1 -1
  83. package/src/sap/fe/templates/messagebundle_mk.properties +1 -1
  84. package/src/sap/fe/templates/messagebundle_ms.properties +1 -1
  85. package/src/sap/fe/templates/messagebundle_nl.properties +1 -1
  86. package/src/sap/fe/templates/messagebundle_no.properties +2 -2
  87. package/src/sap/fe/templates/messagebundle_pl.properties +1 -1
  88. package/src/sap/fe/templates/messagebundle_pt.properties +1 -1
  89. package/src/sap/fe/templates/messagebundle_pt_PT.properties +1 -1
  90. package/src/sap/fe/templates/messagebundle_ro.properties +1 -1
  91. package/src/sap/fe/templates/messagebundle_ru.properties +1 -1
  92. package/src/sap/fe/templates/messagebundle_sh.properties +1 -1
  93. package/src/sap/fe/templates/messagebundle_sk.properties +1 -1
  94. package/src/sap/fe/templates/messagebundle_sl.properties +1 -1
  95. package/src/sap/fe/templates/messagebundle_sr.properties +1 -1
  96. package/src/sap/fe/templates/messagebundle_sv.properties +1 -1
  97. package/src/sap/fe/templates/messagebundle_th.properties +1 -1
  98. package/src/sap/fe/templates/messagebundle_tr.properties +1 -1
  99. package/src/sap/fe/templates/messagebundle_uk.properties +3 -3
  100. package/src/sap/fe/templates/messagebundle_vi.properties +1 -1
  101. package/src/sap/fe/templates/messagebundle_zh_CN.properties +1 -1
  102. package/src/sap/fe/templates/messagebundle_zh_TW.properties +1 -1
@@ -48,17 +48,22 @@ import type { default as ObjectPageExtensionAPI } from "sap/fe/templates/ObjectP
48
48
  import { default as ExtensionAPI } from "sap/fe/templates/ObjectPage/ExtensionAPI";
49
49
  import CollaborationDiscard from "sap/fe/templates/ObjectPage/components/CollaborationDiscardDialog";
50
50
  import type SubSectionBlock from "sap/fe/templates/ObjectPage/controls/SubSectionBlock";
51
+ import { pollForSectionStability } from "sap/fe/templates/ObjectPage/helpers/SectionNavigationHelper";
51
52
  import TableScroller from "sap/fe/templates/TableScroller";
52
53
  import type Button from "sap/m/Button";
53
54
  import type InputBase from "sap/m/InputBase";
54
55
  import InstanceManager from "sap/m/InstanceManager";
56
+ import type MenuButton from "sap/m/MenuButton";
55
57
  import type NavContainer from "sap/m/NavContainer";
56
58
  import type Popover from "sap/m/Popover";
59
+ import type SearchField from "sap/m/SearchField";
57
60
  import type ToolbarSpacer from "sap/m/ToolbarSpacer";
58
61
  import Device from "sap/ui/Device";
59
62
  import type UI5Event from "sap/ui/base/Event";
63
+ import type ManagedObject from "sap/ui/base/ManagedObject";
60
64
  import type { CommandExecution$ExecuteEvent } from "sap/ui/core/CommandExecution";
61
65
  import type Control from "sap/ui/core/Control";
66
+ import type CustomData from "sap/ui/core/CustomData";
62
67
  import UI5Element from "sap/ui/core/Element";
63
68
  import type InvisibleText from "sap/ui/core/InvisibleText";
64
69
  import Library from "sap/ui/core/Lib";
@@ -101,6 +106,8 @@ export type BindingParams = {
101
106
  bPersistOPScroll?: boolean;
102
107
  listBinding?: ODataListBinding;
103
108
  showPlaceholder?: boolean;
109
+ requestOnBinding?: string[];
110
+ targetControlId?: string;
104
111
  };
105
112
 
106
113
  const ProgrammingModel = FELibrary.ProgrammingModel;
@@ -186,6 +193,44 @@ class ObjectPageController extends PageController {
186
193
 
187
194
  private previousBindingContextPath?: string;
188
195
 
196
+ private pendingTargetControlId?: string;
197
+
198
+ private targetSubSection?: ObjectPageSubSection;
199
+
200
+ // Tracks if initial ObjectPage load has completed
201
+ private hasInitialLoadCompleted = false;
202
+
203
+ /**
204
+ * Returns controls (Button / MenuButton) that are marked as "primary action".
205
+ * The marker is set by the converters when an action is emphasized (annotation-based) or
206
+ * configured as primaryAction (custom action defined in the manifest).
207
+ * @param root Root control to start the search from
208
+ * @param onlyActionable If true, only actionable (enabled) candidates are returned
209
+ * @returns An array of controls that are candidates for primary action shortcuts
210
+ */
211
+ private _getPrimaryActionShortcutCandidates(root: ManagedObject, onlyActionable = false): Array<Button | MenuButton> {
212
+ return root.findAggregatedObjects(true, (object: ManagedObject) => {
213
+ const isButton = object.isA("sap.m.Button");
214
+ const isMenuButton = object.isA("sap.m.MenuButton");
215
+ if (!isButton && !isMenuButton) {
216
+ return false;
217
+ }
218
+ const ctrl = object as Button | MenuButton;
219
+ const flag = ctrl
220
+ .getCustomData()
221
+ .find((customData: CustomData) => customData.getKey() === "fePrimaryActionShortcut")
222
+ ?.getValue();
223
+ if (flag !== true && flag !== "true") {
224
+ return false;
225
+ }
226
+ if (!ctrl.getVisible()) {
227
+ return false;
228
+ }
229
+ // For focus placement we must ignore disabled candidates.
230
+ return onlyActionable ? ctrl.getEnabled() : true;
231
+ }) as Array<Button | MenuButton>;
232
+ }
233
+
189
234
  @publicExtension()
190
235
  @finalExtension()
191
236
  getExtensionAPI(sId?: string): ExtensionAPI {
@@ -298,12 +343,13 @@ class ObjectPageController extends PageController {
298
343
  }
299
344
 
300
345
  /**
301
- * Find the last visible subsection and add the sapUxAPObjectPageSubSectionFitContainer CSS class if it contains only a GridTable or a TreeTable.
346
+ * Add the sapUxAPObjectPageSubSectionFitContainer CSS class to every visible subsection that contains only a single GridTable or TreeTable.
347
+ * This makes the table fill the full height of its container so that the scrollbar appears on the table itself rather than on the page.
302
348
  * @param subSections The sub sections to look for
303
349
  */
304
350
  private checkSectionsForNonResponsiveTable(subSections: ObjectPageSubSection[]): void {
305
- const changeClassForTables = (event: Event, lastVisibleSubSection: ObjectPageSubSection): void => {
306
- const blocks = [...lastVisibleSubSection.getBlocks(), ...lastVisibleSubSection.getMoreBlocks()];
351
+ const changeClassForTables = (event: Event, visibleSubSection: ObjectPageSubSection): void => {
352
+ const blocks = [...visibleSubSection.getBlocks(), ...visibleSubSection.getMoreBlocks()];
307
353
  if (blocks.length === 1) {
308
354
  const table = this.searchTableInBlock(blocks[0] as SubSectionBlock);
309
355
  if (!table) {
@@ -317,17 +363,15 @@ class ObjectPageController extends PageController {
317
363
  tableAPI?.getTableDefinition().control.rowCountMode === "Auto"
318
364
  ) {
319
365
  //In case there is only a single table in a subSection we fit that to the whole page so that the scrollbar comes only on table and not on page
320
- lastVisibleSubSection.addStyleClass("sapUxAPObjectPageSubSectionFitContainer");
321
- lastVisibleSubSection.detachEvent("modelContextChange", changeClassForTables, this);
366
+ visibleSubSection.addStyleClass("sapUxAPObjectPageSubSectionFitContainer");
367
+ visibleSubSection.detachEvent("modelContextChange", changeClassForTables, this);
322
368
  }
323
369
  }
324
370
  };
325
- for (let subSectionIndex = subSections.length - 1; subSectionIndex >= 0; subSectionIndex--) {
326
- if (subSections[subSectionIndex].getVisible()) {
327
- const lastVisibleSubSection = subSections[subSectionIndex];
371
+ for (const subSection of subSections) {
372
+ if (subSection.getVisible()) {
328
373
  // We need to attach this event in order to manage the Object Page Lazy Loading mechanism
329
- lastVisibleSubSection.attachEvent("modelContextChange", lastVisibleSubSection, changeClassForTables, this);
330
- break;
374
+ subSection.attachEvent("modelContextChange", subSection, changeClassForTables, this);
331
375
  }
332
376
  }
333
377
  }
@@ -396,6 +440,9 @@ class ObjectPageController extends PageController {
396
440
  }
397
441
 
398
442
  _onBeforeBinding(oContext: ODataV4Context, mParameters: BindingParams = {}): void {
443
+ // Store targetControlId for scrolling after binding completes
444
+ this.pendingTargetControlId = mParameters.targetControlId;
445
+
399
446
  // TODO: we should check how this comes together with the transaction helper, same to the change in the afterBinding
400
447
  this.previousBindingContextPath = this.getView().getBindingContext()?.getPath();
401
448
  const aTables = this._findTables(),
@@ -507,12 +554,21 @@ class ObjectPageController extends PageController {
507
554
  }
508
555
 
509
556
  /**
510
- * Get the first clickable element in the header of the object page.
557
+ * Get the first clickable element in the object page.
511
558
  * @private
512
559
  * @param objectPage Object Page Layout control
513
- * @returns The first clickable element found in the header
560
+ * @returns The first clickable element found
514
561
  */
515
562
  private getFirstClickableElement(objectPage: ObjectPageLayout): UI5Element | undefined {
563
+ // In display mode, if a visible primary action with the emphasis exists in the footer, it must be the first clickable element on the page.
564
+ const footer = objectPage.getFooter();
565
+ const footerPrimaryAction = footer
566
+ ? this._getPrimaryActionShortcutCandidates(footer as unknown as ManagedObject, true)[0]
567
+ : undefined;
568
+ if (footerPrimaryAction) {
569
+ return footerPrimaryAction;
570
+ }
571
+ // Fallback: first visible & enabled header action (Edit, etc.)
516
572
  let firstClickableElement;
517
573
  const actions = objectPage.getHeaderTitle() && (objectPage.getHeaderTitle() as ObjectPageDynamicHeaderTitle).getActions();
518
574
  if (actions?.length) {
@@ -648,6 +704,7 @@ class ObjectPageController extends PageController {
648
704
  /**
649
705
  * Set the initial focus in edit mode.
650
706
  * @param aSubSections Object page sub sections
707
+ * @param fromTabNavigation Indicates if the focus is set after a tab navigation
651
708
  */
652
709
  _updateFocusInEditMode(aSubSections: ObjectPageSubSection[], fromTabNavigation = false): void {
653
710
  setTimeout(
@@ -655,7 +712,7 @@ class ObjectPageController extends PageController {
655
712
  // We set the focus in a timeeout, otherwise the focus sometimes goes to the TabBar
656
713
  const oObjectPage = this._getObjectPageLayoutControl();
657
714
  const oMandatoryField = this._getFirstEmptyMandatoryFieldFromSubSection(aSubSections);
658
- let oFieldToFocus;
715
+ let oFieldToFocus: UI5Element | undefined;
659
716
  if (oMandatoryField) {
660
717
  if (oMandatoryField.isA("sap.fe.macros.MultiValueField")) {
661
718
  oFieldToFocus = (oMandatoryField as unknown as MultiValueFieldBlock).getMultiValueField();
@@ -671,9 +728,22 @@ class ObjectPageController extends PageController {
671
728
  const focusInfo = oFieldToFocus.getFocusInfo() as { targetInfo: object };
672
729
  focusInfo.targetInfo = { silent: true };
673
730
  if (oFieldToFocus.isA("sap.ui.mdc.field.FieldInput")) {
674
- oFieldToFocus = oFieldToFocus.getParent();
731
+ oFieldToFocus = oFieldToFocus.getParent() as UI5Element | undefined;
732
+ }
733
+
734
+ if (
735
+ oFieldToFocus?.isA<SearchField>("sap.m.SearchField") &&
736
+ oFieldToFocus.getEnableSuggestions() &&
737
+ !oFieldToFocus.isBound("enableSuggestions")
738
+ ) {
739
+ // In case the focus is set on a search field with suggestions, we temporarily disable the suggestions to avoid triggering them on focus
740
+ // unless this property is bound (to avoid modifying something in a model)
741
+ oFieldToFocus.setEnableSuggestions(false);
742
+ oFieldToFocus.focus(focusInfo);
743
+ oFieldToFocus.setEnableSuggestions(true);
744
+ } else {
745
+ oFieldToFocus?.focus(focusInfo);
675
746
  }
676
- oFieldToFocus.focus(focusInfo);
677
747
  }
678
748
  }.bind(this),
679
749
  fromTabNavigation ? 300 : 0
@@ -735,15 +805,24 @@ class ObjectPageController extends PageController {
735
805
 
736
806
  // Function to navigate back, or display the launchpad if we're on the first page of the history
737
807
  const navBack = (): void => {
738
- const currentURL = document.URL;
739
- history.back();
740
- // In case there is no previous page in the history, history.back does nothing.
741
- // In this case, we need to use navigateBackFromContext, that will display the home page
742
- setTimeout(() => {
743
- if (document.URL === currentURL) {
744
- this._routing.navigateBackFromContext(oContext);
745
- }
746
- }, 500);
808
+ // Use the new Navigation API if it's available in the browser, to check if we can go back in history.
809
+ const canGoBack = (window as { navigation?: { canGoBack?: boolean } }).navigation?.canGoBack;
810
+ if (canGoBack === true) {
811
+ history.back();
812
+ } else if (canGoBack === false) {
813
+ this._routing.navigateBackFromContext(oContext);
814
+ } else {
815
+ // Fallback in case the browser doesn't support the Navigation API
816
+ const currentURL = document.URL;
817
+ history.back();
818
+ // In case there is no previous page in the history, history.back does nothing.
819
+ // In this case, we need to use navigateBackFromContext, that will display the home page
820
+ setTimeout(() => {
821
+ if (document.URL === currentURL) {
822
+ this._routing.navigateBackFromContext(oContext);
823
+ }
824
+ }, 500);
825
+ }
747
826
  };
748
827
 
749
828
  if (this.getAppComponent().getRouterProxy().checkIfBackHasSameContext()) {
@@ -775,13 +854,16 @@ class ObjectPageController extends PageController {
775
854
  }
776
855
 
777
856
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
778
- _onAfterBinding(inputBindingContext: Context | undefined, mParameters: BindingParams | undefined): void {
857
+ _onAfterBinding(inputBindingContext: ODataV4Context | undefined, mParameters: BindingParams | undefined): void {
779
858
  const view = this.getView();
780
859
  const viewLevel = view?.getViewData()?.viewLevel;
781
860
  const oObjectPage = this._getObjectPageLayoutControl();
782
861
  const aTables = this._findTables();
862
+ if ([NavigationReason.EditFlowAction, NavigationReason.RowPress].includes(mParameters?.reason as NavigationReason)) {
863
+ this.editFlow.storeSiblingContextData(inputBindingContext);
864
+ }
783
865
 
784
- this._sideEffects.clearFieldGroupsValidity();
866
+ this.sideEffects.clearFieldGroupsValidity();
785
867
 
786
868
  // TODO: this is only a temp solution as long as the model fix the cache issue and we use this additional
787
869
  // binding with ownRequest
@@ -939,6 +1021,143 @@ class ObjectPageController extends PageController {
939
1021
  applyAppState,
940
1022
  this.previousBindingContextPath !== this.getView().getBindingContext()?.getPath()
941
1023
  );
1024
+
1025
+ // Capture synchronously to avoid race with subsequent navigations
1026
+ const pendingTargetControlId = this.pendingTargetControlId;
1027
+ this.pendingTargetControlId = undefined;
1028
+
1029
+ this.handlePendingTargetControlIdNavigation(applyAppState, oObjectPage, pendingTargetControlId);
1030
+ }
1031
+
1032
+ /**
1033
+ * Handles navigation to a target section or subsection after app state is applied.
1034
+ * This coordinates targetControlId navigation from two sources:
1035
+ * - In-app navigation: pendingTargetControlId captured before applyAppState.
1036
+ * - Cross-app navigation: xAppState targetControlId stored by AppStateHandler during LR to OP transition.
1037
+ * @param applyAppState Promise that resolves when app state has been applied.
1038
+ * @param oObjectPage The ObjectPageLayout control.
1039
+ * @param pendingTargetControlId The targetControlId captured before applyAppState, if any.
1040
+ */
1041
+ private handlePendingTargetControlIdNavigation(
1042
+ applyAppState: Promise<void | object>,
1043
+ oObjectPage: ObjectPageLayout,
1044
+ pendingTargetControlId: string | undefined
1045
+ ): void {
1046
+ applyAppState
1047
+ .then(async () => {
1048
+ const xAppStateTargetControlId = this.getAppComponent().getAppStateHandler()?.consumePendingTargetControlId();
1049
+ const targetControlId = pendingTargetControlId ?? xAppStateTargetControlId;
1050
+ if (!targetControlId) {
1051
+ return;
1052
+ }
1053
+
1054
+ // Wait for visibility bindings (needed for lazy loading scenarios)
1055
+ try {
1056
+ await Promise.all(this.waitForVisibilityBindings);
1057
+ } catch {
1058
+ // Some visibility bindings failed to resolve, proceeding anyway
1059
+ }
1060
+
1061
+ // Wait for pageReady to ensure ObjectPage is fully initialized
1062
+ await this.pageReady.waitPageReady();
1063
+
1064
+ // Navigate to target section
1065
+ this.navigateToTargetSection(oObjectPage, targetControlId);
1066
+ return; // Required by linter rule promise/always-return
1067
+ })
1068
+ .catch(() => {
1069
+ // applyAppState failed - skip targetControlId navigation
1070
+ });
1071
+ }
1072
+
1073
+ /**
1074
+ * Navigates to a target section with automatic re-navigation if UX rules reset the selection.
1075
+ * Polls the section state since setSelectedSection() doesn't fire the navigate event.
1076
+ * @param oObjectPage The ObjectPageLayout control.
1077
+ * @param targetControlId The local ID of the target section or subsection.
1078
+ */
1079
+ private navigateToTargetSection(oObjectPage: ObjectPageLayout, targetControlId: string): void {
1080
+ // Determine target section ID for comparison
1081
+ const targetControl = this.getView().byId(targetControlId);
1082
+ const isSection = targetControl?.isA("sap.uxap.ObjectPageSection");
1083
+ const targetSectionId = isSection ? targetControl?.getId() : (targetControl?.getParent() as ObjectPageSection)?.getId();
1084
+
1085
+ // Navigate function with guard to prevent duplicate calls when already on target
1086
+ const navigateToTarget = (): void => {
1087
+ if (oObjectPage.getSelectedSection() === targetSectionId) {
1088
+ return; // Already on target section
1089
+ }
1090
+ this.navigateWithConfirmation(targetControlId);
1091
+ };
1092
+
1093
+ // Navigate immediately
1094
+ navigateToTarget();
1095
+
1096
+ // Skip polling if already on target section (navigation was no-op or succeeded immediately)
1097
+ if (oObjectPage.getSelectedSection() === targetSectionId) {
1098
+ this.hasInitialLoadCompleted = true;
1099
+ return;
1100
+ }
1101
+
1102
+ // Skip polling on subsequent navigations
1103
+ if (this.hasInitialLoadCompleted) {
1104
+ return;
1105
+ }
1106
+
1107
+ // Poll to detect UX rules reset during initial load
1108
+ // Uses shared utility from helpers/SectionNavigationHelper.ts
1109
+ pollForSectionStability({
1110
+ getSelectedSection: () => oObjectPage.getSelectedSection(),
1111
+ targetSectionId,
1112
+ onResetDetected: navigateToTarget
1113
+ })
1114
+ .then(() => {
1115
+ this.hasInitialLoadCompleted = true;
1116
+ return; // Required by linter rule promise/always-return
1117
+ })
1118
+ .catch(() => {
1119
+ // Required by linter rule promise/catch-or-return - polling failures are non-critical
1120
+ });
1121
+ }
1122
+
1123
+ /**
1124
+ * Sets targetSubSection based on the target control ID for app state persistence.
1125
+ * @param targetControlId The local ID of the target section or subsection.
1126
+ */
1127
+ private setTargetSubSection(targetControlId: string): void {
1128
+ const targetControl = this.getView().byId(targetControlId);
1129
+ if (targetControl?.isA<ObjectPageSubSection>("sap.uxap.ObjectPageSubSection")) {
1130
+ this.targetSubSection = targetControl;
1131
+ } else if (targetControl?.isA<ObjectPageSection>("sap.uxap.ObjectPageSection")) {
1132
+ const subSections = targetControl.getSubSections();
1133
+ this.targetSubSection = subSections[0];
1134
+ }
1135
+ }
1136
+
1137
+ /**
1138
+ * Performs navigation to the target section and then updates the app state.
1139
+ * @param targetControlId The local ID of the target section or subsection.
1140
+ */
1141
+ private navigateWithConfirmation(targetControlId: string): void {
1142
+ // Set targetSubSection BEFORE navigation for app state saving.
1143
+ // scrollToSection() doesn't fire the navigate event, so onNavigateChange won't be called.
1144
+ this.setTargetSubSection(targetControlId);
1145
+
1146
+ this.getExtensionAPI().navigateToSubSection(targetControlId);
1147
+
1148
+ // Wait for ExtensionAPI polling to complete (~100ms) plus scroll to finish
1149
+ // before updating app state and setting focus.
1150
+ setTimeout(() => {
1151
+ this.viewState.updateAppStateDebounced();
1152
+
1153
+ // Set focus if in edit mode (for in-app navigation to sub-object page in edit mode)
1154
+ // This is needed because scrollToSection() doesn't fire onNavigateChange,
1155
+ // so we need to manually trigger focus setting here
1156
+ const isInEditMode = CommonUtils.getIsEditable(this.getView());
1157
+ if (isInEditMode && this.targetSubSection) {
1158
+ this._updateFocusInEditMode([this.targetSubSection], false);
1159
+ }
1160
+ }, 150);
942
1161
  }
943
1162
 
944
1163
  /**
@@ -1339,9 +1558,7 @@ class ObjectPageController extends PageController {
1339
1558
  // If there is at least one global SideEffects for the related entity, execute it/them
1340
1559
  if (globalSideEffects.length) {
1341
1560
  await this.editFlow.syncTask();
1342
- return Promise.all(
1343
- globalSideEffects.map(async (sideEffects) => this._sideEffects.requestSideEffects(sideEffects, context))
1344
- );
1561
+ return Promise.all(globalSideEffects.map(async (sideEffects) => this.sideEffects.requestSideEffects(sideEffects, context)));
1345
1562
  }
1346
1563
  }
1347
1564
 
@@ -1415,10 +1632,11 @@ class ObjectPageController extends PageController {
1415
1632
  );
1416
1633
  }
1417
1634
  });
1418
- const isSkipBindingToView = skipBindingToView as unknown as CommandExecution$ExecuteEvent;
1419
- const isStandardSave =
1420
- isSkipBindingToView && typeof isSkipBindingToView == "object" && isSkipBindingToView.getSource().getCommand() === "Save";
1421
-
1635
+ let isStandardSave: boolean | undefined;
1636
+ if (skipBindingToView && typeof skipBindingToView === "object") {
1637
+ const command = (skipBindingToView as CommandExecution$ExecuteEvent)?.getSource?.()?.getCommand?.();
1638
+ isStandardSave = command === "Save";
1639
+ }
1422
1640
  const bindings = await Promise.all(awaitCreateDocuments);
1423
1641
  // We need to either reject or resolve a promise here and return it since this save
1424
1642
  // function is not only called when pressing the save button in the footer, but also
@@ -1961,8 +2179,27 @@ class ObjectPageController extends PageController {
1961
2179
  }
1962
2180
  ): void {
1963
2181
  /**
1964
- * Invokes the page primary action on press of Ctrl+Enter.
2182
+ * Invokes the page primary action when the user presses Ctrl+Enter (Cmd+Enter on macOS).
1965
2183
  */
2184
+
2185
+ const primaryCandidate = oController._getPrimaryActionShortcutCandidates(oView)[0];
2186
+
2187
+ /* By design, there must be at most one candidate due to the "single emphasis per page" rule.
2188
+ If a primary-enter target is found, it must fully take over Ctrl/Cmd+Enter.*/
2189
+
2190
+ if (primaryCandidate) {
2191
+ if (!primaryCandidate.getEnabled()) {
2192
+ // Take over Ctrl/Cmd+Enter, but do not trigger anything when the primary action is disabled.
2193
+ return;
2194
+ }
2195
+ if (primaryCandidate.isA("sap.m.Button")) {
2196
+ (primaryCandidate as Button).firePress();
2197
+ }
2198
+ if ((primaryCandidate as MenuButton).isA("sap.m.MenuButton")) {
2199
+ (primaryCandidate as MenuButton).fireDefaultAction();
2200
+ }
2201
+ return;
2202
+ }
1966
2203
  const iViewLevel = oController.getView().getViewData().viewLevel;
1967
2204
  if (mConditions.positiveActionVisible) {
1968
2205
  if (mConditions.positiveActionEnabled) {
@@ -1990,7 +2227,7 @@ class ObjectPageController extends PageController {
1990
2227
  const tableAPI = event.getSource();
1991
2228
  const table = tableAPI.content;
1992
2229
  const currentActionPromise = this.editFlow.getCurrentActionPromise();
1993
- const tableContexts = this._getTableBinding(table)?.getCurrentContexts();
2230
+ const tableContexts = this._getTableBinding(table)?.getAllCurrentContexts();
1994
2231
 
1995
2232
  if (currentActionPromise && tableContexts?.length) {
1996
2233
  try {
@@ -1998,10 +2235,13 @@ class ObjectPageController extends PageController {
1998
2235
  if (actionResponse?.controlId === table.getId()) {
1999
2236
  const actionData = actionResponse.oData;
2000
2237
  const keys = actionResponse.keys;
2001
- const newItem = tableContexts.findIndex((tableContext: Context) => {
2002
- const tableData = tableContext.getObject();
2003
- return keys.every((key: string) => tableData[key] === actionData[key]);
2004
- });
2238
+ const newItem =
2239
+ tableContexts
2240
+ .find((tableContext: Context) => {
2241
+ const tableData = tableContext.getObject();
2242
+ return keys.every((key: string) => tableData[key] === actionData[key]);
2243
+ })
2244
+ ?.getIndex() ?? -1;
2005
2245
  if (newItem !== -1) {
2006
2246
  const dialog = InstanceManager.getOpenDialogs().find((dialog) => dialog.data("FullScreenDialog") !== true);
2007
2247
  if (dialog) {
@@ -2136,17 +2376,21 @@ class ObjectPageController extends PageController {
2136
2376
 
2137
2377
  onNavigateChange(this: ObjectPageController, oEvent: UI5Event<{ subSection: ObjectPageSubSection }>): void {
2138
2378
  //will be called always when we click on a section tab
2139
- this.getExtensionAPI().updateAppState();
2140
2379
  this.bSectionNavigated = true;
2141
2380
 
2381
+ const subSection = oEvent.getParameter("subSection");
2382
+ this.targetSubSection = subSection;
2383
+
2384
+ this.getExtensionAPI().updateAppState();
2385
+
2142
2386
  const oInternalModelContext = this.getView().getBindingContext("internal") as InternalModelContext;
2387
+
2143
2388
  if (
2144
2389
  CommonUtils.getIsEditable(this.getView()) &&
2145
2390
  this.getView().getViewData().sectionLayout === "Tabs" &&
2146
2391
  oInternalModelContext.getProperty("errorNavigationSectionFlag") === false
2147
2392
  ) {
2148
- const oSubSection = oEvent.getParameter("subSection");
2149
- this._updateFocusInEditMode([oSubSection], true);
2393
+ this._updateFocusInEditMode([subSection], true);
2150
2394
  }
2151
2395
  },
2152
2396
  onVariantSelected: function (this: ObjectPageController): void {