@sapui5/sap.fe.templates 1.147.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.
- package/package.json +1 -1
- package/src/sap/fe/templates/.library +1 -1
- package/src/sap/fe/templates/AnalyticalListPage/manifest.json +1 -1
- package/src/sap/fe/templates/ListReport/ListReportController.controller.js +4 -14
- package/src/sap/fe/templates/ListReport/ListReportController.controller.ts +2 -12
- package/src/sap/fe/templates/ListReport/controls/MultipleModeControl.js +4 -1
- package/src/sap/fe/templates/ListReport/controls/MultipleModeControl.ts +3 -0
- package/src/sap/fe/templates/ListReport/manifest.json +1 -1
- package/src/sap/fe/templates/ObjectPage/ExtensionAPI.js +134 -24
- package/src/sap/fe/templates/ObjectPage/ExtensionAPI.ts +132 -19
- package/src/sap/fe/templates/ObjectPage/ObjectPageController.controller.js +169 -17
- package/src/sap/fe/templates/ObjectPage/ObjectPageController.controller.ts +195 -19
- package/src/sap/fe/templates/ObjectPage/ObjectPageTemplating.js +13 -8
- package/src/sap/fe/templates/ObjectPage/ObjectPageTemplating.ts +17 -9
- package/src/sap/fe/templates/ObjectPage/components/CollaborationDiscardDialog.js +4 -1
- package/src/sap/fe/templates/ObjectPage/components/CollaborationDiscardDialog.tsx +1 -0
- package/src/sap/fe/templates/ObjectPage/components/CollaborationDraft.js +15 -4
- package/src/sap/fe/templates/ObjectPage/components/CollaborationDraft.tsx +11 -2
- package/src/sap/fe/templates/ObjectPage/controls/StashableHBox.js +28 -1
- package/src/sap/fe/templates/ObjectPage/controls/StashableHBox.ts +31 -0
- package/src/sap/fe/templates/ObjectPage/helpers/SectionNavigationHelper.js +72 -0
- package/src/sap/fe/templates/ObjectPage/helpers/SectionNavigationHelper.ts +75 -0
- package/src/sap/fe/templates/ObjectPage/manifest.json +1 -15
- package/src/sap/fe/templates/ObjectPage/overrides/CollaborationManager.js +29 -20
- package/src/sap/fe/templates/ObjectPage/overrides/CollaborationManager.ts +28 -21
- package/src/sap/fe/templates/ObjectPage/overrides/IntentBasedNavigation.js +4 -3
- package/src/sap/fe/templates/ObjectPage/overrides/IntentBasedNavigation.ts +4 -4
- package/src/sap/fe/templates/ObjectPage/overrides/ViewState.js +93 -17
- package/src/sap/fe/templates/ObjectPage/overrides/ViewState.ts +108 -22
- package/src/sap/fe/templates/ObjectPage/view/fragments/EmphasizedFirstHeaderAction.fragment.xml +1 -0
- package/src/sap/fe/templates/ObjectPage/view/fragments/ExpandedHeading.fragment.xml +4 -4
- package/src/sap/fe/templates/ObjectPage/view/fragments/Heading.fragment.xml +4 -4
- package/src/sap/fe/templates/ObjectPage/view/fragments/ObjectPageHeaderAddress.fragment.xml +1 -1
- package/src/sap/fe/templates/ObjectPage/view/fragments/ObjectPageHeaderContact.fragment.xml +1 -1
- package/src/sap/fe/templates/ObjectPage/view/fragments/ObjectPageHeaderForm.fragment.xml +1 -0
- package/src/sap/fe/templates/library.js +1 -1
- package/src/sap/fe/templates/messagebundle_no.properties +1 -1
- package/src/sap/fe/templates/messagebundle_uk.properties +1 -1
|
@@ -48,6 +48,7 @@ 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";
|
|
@@ -55,6 +56,7 @@ import InstanceManager from "sap/m/InstanceManager";
|
|
|
55
56
|
import type MenuButton from "sap/m/MenuButton";
|
|
56
57
|
import type NavContainer from "sap/m/NavContainer";
|
|
57
58
|
import type Popover from "sap/m/Popover";
|
|
59
|
+
import type SearchField from "sap/m/SearchField";
|
|
58
60
|
import type ToolbarSpacer from "sap/m/ToolbarSpacer";
|
|
59
61
|
import Device from "sap/ui/Device";
|
|
60
62
|
import type UI5Event from "sap/ui/base/Event";
|
|
@@ -104,6 +106,8 @@ export type BindingParams = {
|
|
|
104
106
|
bPersistOPScroll?: boolean;
|
|
105
107
|
listBinding?: ODataListBinding;
|
|
106
108
|
showPlaceholder?: boolean;
|
|
109
|
+
requestOnBinding?: string[];
|
|
110
|
+
targetControlId?: string;
|
|
107
111
|
};
|
|
108
112
|
|
|
109
113
|
const ProgrammingModel = FELibrary.ProgrammingModel;
|
|
@@ -189,6 +193,13 @@ class ObjectPageController extends PageController {
|
|
|
189
193
|
|
|
190
194
|
private previousBindingContextPath?: string;
|
|
191
195
|
|
|
196
|
+
private pendingTargetControlId?: string;
|
|
197
|
+
|
|
198
|
+
private targetSubSection?: ObjectPageSubSection;
|
|
199
|
+
|
|
200
|
+
// Tracks if initial ObjectPage load has completed
|
|
201
|
+
private hasInitialLoadCompleted = false;
|
|
202
|
+
|
|
192
203
|
/**
|
|
193
204
|
* Returns controls (Button / MenuButton) that are marked as "primary action".
|
|
194
205
|
* The marker is set by the converters when an action is emphasized (annotation-based) or
|
|
@@ -429,6 +440,9 @@ class ObjectPageController extends PageController {
|
|
|
429
440
|
}
|
|
430
441
|
|
|
431
442
|
_onBeforeBinding(oContext: ODataV4Context, mParameters: BindingParams = {}): void {
|
|
443
|
+
// Store targetControlId for scrolling after binding completes
|
|
444
|
+
this.pendingTargetControlId = mParameters.targetControlId;
|
|
445
|
+
|
|
432
446
|
// TODO: we should check how this comes together with the transaction helper, same to the change in the afterBinding
|
|
433
447
|
this.previousBindingContextPath = this.getView().getBindingContext()?.getPath();
|
|
434
448
|
const aTables = this._findTables(),
|
|
@@ -690,6 +704,7 @@ class ObjectPageController extends PageController {
|
|
|
690
704
|
/**
|
|
691
705
|
* Set the initial focus in edit mode.
|
|
692
706
|
* @param aSubSections Object page sub sections
|
|
707
|
+
* @param fromTabNavigation Indicates if the focus is set after a tab navigation
|
|
693
708
|
*/
|
|
694
709
|
_updateFocusInEditMode(aSubSections: ObjectPageSubSection[], fromTabNavigation = false): void {
|
|
695
710
|
setTimeout(
|
|
@@ -697,7 +712,7 @@ class ObjectPageController extends PageController {
|
|
|
697
712
|
// We set the focus in a timeeout, otherwise the focus sometimes goes to the TabBar
|
|
698
713
|
const oObjectPage = this._getObjectPageLayoutControl();
|
|
699
714
|
const oMandatoryField = this._getFirstEmptyMandatoryFieldFromSubSection(aSubSections);
|
|
700
|
-
let oFieldToFocus;
|
|
715
|
+
let oFieldToFocus: UI5Element | undefined;
|
|
701
716
|
if (oMandatoryField) {
|
|
702
717
|
if (oMandatoryField.isA("sap.fe.macros.MultiValueField")) {
|
|
703
718
|
oFieldToFocus = (oMandatoryField as unknown as MultiValueFieldBlock).getMultiValueField();
|
|
@@ -713,9 +728,22 @@ class ObjectPageController extends PageController {
|
|
|
713
728
|
const focusInfo = oFieldToFocus.getFocusInfo() as { targetInfo: object };
|
|
714
729
|
focusInfo.targetInfo = { silent: true };
|
|
715
730
|
if (oFieldToFocus.isA("sap.ui.mdc.field.FieldInput")) {
|
|
716
|
-
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);
|
|
717
746
|
}
|
|
718
|
-
oFieldToFocus.focus(focusInfo);
|
|
719
747
|
}
|
|
720
748
|
}.bind(this),
|
|
721
749
|
fromTabNavigation ? 300 : 0
|
|
@@ -777,15 +805,24 @@ class ObjectPageController extends PageController {
|
|
|
777
805
|
|
|
778
806
|
// Function to navigate back, or display the launchpad if we're on the first page of the history
|
|
779
807
|
const navBack = (): void => {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
+
}
|
|
789
826
|
};
|
|
790
827
|
|
|
791
828
|
if (this.getAppComponent().getRouterProxy().checkIfBackHasSameContext()) {
|
|
@@ -826,7 +863,7 @@ class ObjectPageController extends PageController {
|
|
|
826
863
|
this.editFlow.storeSiblingContextData(inputBindingContext);
|
|
827
864
|
}
|
|
828
865
|
|
|
829
|
-
this.
|
|
866
|
+
this.sideEffects.clearFieldGroupsValidity();
|
|
830
867
|
|
|
831
868
|
// TODO: this is only a temp solution as long as the model fix the cache issue and we use this additional
|
|
832
869
|
// binding with ownRequest
|
|
@@ -984,6 +1021,143 @@ class ObjectPageController extends PageController {
|
|
|
984
1021
|
applyAppState,
|
|
985
1022
|
this.previousBindingContextPath !== this.getView().getBindingContext()?.getPath()
|
|
986
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);
|
|
987
1161
|
}
|
|
988
1162
|
|
|
989
1163
|
/**
|
|
@@ -1384,9 +1558,7 @@ class ObjectPageController extends PageController {
|
|
|
1384
1558
|
// If there is at least one global SideEffects for the related entity, execute it/them
|
|
1385
1559
|
if (globalSideEffects.length) {
|
|
1386
1560
|
await this.editFlow.syncTask();
|
|
1387
|
-
return Promise.all(
|
|
1388
|
-
globalSideEffects.map(async (sideEffects) => this._sideEffects.requestSideEffects(sideEffects, context))
|
|
1389
|
-
);
|
|
1561
|
+
return Promise.all(globalSideEffects.map(async (sideEffects) => this.sideEffects.requestSideEffects(sideEffects, context)));
|
|
1390
1562
|
}
|
|
1391
1563
|
}
|
|
1392
1564
|
|
|
@@ -2204,17 +2376,21 @@ class ObjectPageController extends PageController {
|
|
|
2204
2376
|
|
|
2205
2377
|
onNavigateChange(this: ObjectPageController, oEvent: UI5Event<{ subSection: ObjectPageSubSection }>): void {
|
|
2206
2378
|
//will be called always when we click on a section tab
|
|
2207
|
-
this.getExtensionAPI().updateAppState();
|
|
2208
2379
|
this.bSectionNavigated = true;
|
|
2209
2380
|
|
|
2381
|
+
const subSection = oEvent.getParameter("subSection");
|
|
2382
|
+
this.targetSubSection = subSection;
|
|
2383
|
+
|
|
2384
|
+
this.getExtensionAPI().updateAppState();
|
|
2385
|
+
|
|
2210
2386
|
const oInternalModelContext = this.getView().getBindingContext("internal") as InternalModelContext;
|
|
2387
|
+
|
|
2211
2388
|
if (
|
|
2212
2389
|
CommonUtils.getIsEditable(this.getView()) &&
|
|
2213
2390
|
this.getView().getViewData().sectionLayout === "Tabs" &&
|
|
2214
2391
|
oInternalModelContext.getProperty("errorNavigationSectionFlag") === false
|
|
2215
2392
|
) {
|
|
2216
|
-
|
|
2217
|
-
this._updateFocusInEditMode([oSubSection], true);
|
|
2393
|
+
this._updateFocusInEditMode([subSection], true);
|
|
2218
2394
|
}
|
|
2219
2395
|
},
|
|
2220
2396
|
onVariantSelected: function (this: ObjectPageController): void {
|