@sapui5/sap.fe.templates 1.148.3 → 1.150.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 (38) 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/ListComponent.js +2 -5
  5. package/src/sap/fe/templates/ListReport/Component.js +2 -5
  6. package/src/sap/fe/templates/ListReport/ExtensionAPI.js +84 -14
  7. package/src/sap/fe/templates/ListReport/ExtensionAPI.ts +88 -13
  8. package/src/sap/fe/templates/ListReport/LRMessageStrip.js +41 -1
  9. package/src/sap/fe/templates/ListReport/LRMessageStrip.ts +43 -0
  10. package/src/sap/fe/templates/ListReport/ListReportController.controller.js +87 -11
  11. package/src/sap/fe/templates/ListReport/ListReportController.controller.ts +74 -2
  12. package/src/sap/fe/templates/ListReport/controls/MultipleModeControl.js +4 -7
  13. package/src/sap/fe/templates/ListReport/manifest.json +1 -1
  14. package/src/sap/fe/templates/ListReport/overrides/IntentBasedNavigation.js +12 -6
  15. package/src/sap/fe/templates/ListReport/overrides/IntentBasedNavigation.ts +14 -6
  16. package/src/sap/fe/templates/ListReport/overrides/ViewState.js +7 -1
  17. package/src/sap/fe/templates/ListReport/overrides/ViewState.ts +5 -0
  18. package/src/sap/fe/templates/ListReport/view/fragments/Table.fragment.xml +1 -0
  19. package/src/sap/fe/templates/ListReport/view/fragments/VariantManagement.fragment.xml +1 -0
  20. package/src/sap/fe/templates/ObjectPage/Component.js +12 -15
  21. package/src/sap/fe/templates/ObjectPage/ExtensionAPI.js +3 -7
  22. package/src/sap/fe/templates/ObjectPage/ObjectPage.view.xml +3 -2
  23. package/src/sap/fe/templates/ObjectPage/ObjectPageController.controller.js +105 -83
  24. package/src/sap/fe/templates/ObjectPage/ObjectPageController.controller.ts +88 -80
  25. package/src/sap/fe/templates/ObjectPage/ObjectPageTemplating.js +5 -18
  26. package/src/sap/fe/templates/ObjectPage/ObjectPageTemplating.ts +2 -15
  27. package/src/sap/fe/templates/ObjectPage/components/CollaborationDraft.js +28 -3
  28. package/src/sap/fe/templates/ObjectPage/components/CollaborationDraft.tsx +29 -1
  29. package/src/sap/fe/templates/ObjectPage/controls/StashableHBox.js +2 -5
  30. package/src/sap/fe/templates/ObjectPage/controls/StashableVBox.js +2 -5
  31. package/src/sap/fe/templates/ObjectPage/controls/SubSectionBlock.js +3 -6
  32. package/src/sap/fe/templates/ObjectPage/manifest.json +1 -1
  33. package/src/sap/fe/templates/ObjectPage/view/fragments/ObjectPageHeaderForm.fragment.xml +1 -0
  34. package/src/sap/fe/templates/ObjectPage/view/fragments/SectionContent.fragment.xml +0 -4
  35. package/src/sap/fe/templates/ObjectPage/view/fragments/SectionFormContent.fragment.xml +0 -1
  36. package/src/sap/fe/templates/ObjectPage/view/fragments/SectionMoreFormContent.fragment.xml +0 -1
  37. package/src/sap/fe/templates/ObjectPage/view/fragments/Table.fragment.xml +1 -0
  38. package/src/sap/fe/templates/library.js +1 -1
@@ -30,15 +30,17 @@ import type { InternalModelContext } from "sap/fe/core/helpers/ModelHelper";
30
30
  import ModelHelper from "sap/fe/core/helpers/ModelHelper";
31
31
  import FELibrary from "sap/fe/core/library";
32
32
  import { type WrappedCard } from "sap/fe/core/services/CollaborationManagerServiceFactory";
33
+ import type { TelemetryVariantEvent } from "sap/fe/core/services/TelemetryServiceFactory";
33
34
  import type ChartBlock from "sap/fe/macros/Chart";
34
35
  import CommonHelper from "sap/fe/macros/CommonHelper";
36
+ import type FormBlock from "sap/fe/macros/Form";
35
37
  import type MacroAPI from "sap/fe/macros/MacroAPI";
36
38
  import type MultiValueFieldBlock from "sap/fe/macros/MultiValueField";
37
39
  import type TableAPI from "sap/fe/macros/Table";
40
+ import VariantManagementBB from "sap/fe/macros/VariantManagement";
38
41
  import type EasyFillButton from "sap/fe/macros/ai/EasyFillButton";
39
42
  import type SummarizationButton from "sap/fe/macros/ai/SummarizationButton";
40
43
  import type SubSection from "sap/fe/macros/controls/section/SubSection";
41
- import type FormAPI from "sap/fe/macros/form/FormAPI";
42
44
  import type MessageButton from "sap/fe/macros/messages/MessageButton";
43
45
  import type ShareAPI from "sap/fe/macros/share/ShareAPI";
44
46
  import type TableCreationOptions from "sap/fe/macros/table/TableCreationOptions";
@@ -100,7 +102,7 @@ import MessageHandlerOverride from "./overrides/MessageHandler";
100
102
  import PaginatorOverride from "./overrides/Paginator";
101
103
  import ShareOverrides from "./overrides/Share";
102
104
  import ViewStateOverrides from "./overrides/ViewState";
103
-
105
+ const { VariantType } = VariantManagementBB;
104
106
  export type BindingParams = {
105
107
  redirectedToNonDraft?: string | undefined;
106
108
  reason?: NavigationReason;
@@ -656,6 +658,10 @@ class ObjectPageController extends PageController {
656
658
  // SubSection's contents binding is disabled.
657
659
  blocks.forEach((block) => {
658
660
  if (block.isA<SubSectionBlock>("sap.fe.templates.ObjectPage.controls.SubSectionBlock")) {
661
+ // Set binding context to null on both the block and its content
662
+ // to support both: subsections with UI.Hidden annotation (content needs to be cleared)
663
+ // and customer controller extensions that set binding context on the block level
664
+ block.setBindingContext(undefined);
659
665
  block.content.setBindingContext?.(null);
660
666
  } else {
661
667
  block.setBindingContext(null);
@@ -671,7 +677,7 @@ class ObjectPageController extends PageController {
671
677
  _getFirstEmptyMandatoryFieldFromSubSection(aSubSections: ObjectPageSubSection[]): InputBase | undefined {
672
678
  if (aSubSections.length === 0) return undefined;
673
679
  for (const subSection of aSubSections) {
674
- const aBlocks = subSection.getBlocks() as (Form | FormAPI)[];
680
+ const aBlocks = subSection.getBlocks() as (Form | FormBlock)[];
675
681
 
676
682
  if (aBlocks) {
677
683
  for (const blockControl of aBlocks) {
@@ -682,8 +688,8 @@ class ObjectPageController extends PageController {
682
688
  aFormContainers = blockControl.getFormContainers();
683
689
  } else if (blockControl.getContent && blockControl.getContent()?.isA<Form>("sap.ui.layout.form.Form")) {
684
690
  aFormContainers = (blockControl.getContent() as Form).getFormContainers();
685
- } else if (blockControl?.getContent?.()?.isA<FormAPI>("sap.fe.macros.form.FormAPI")) {
686
- aFormContainers = ((blockControl.getContent() as FormAPI).getContent() as Form).getFormContainers();
691
+ } else if (blockControl?.getContent?.()?.isA<FormBlock>("sap.fe.macros.Form")) {
692
+ aFormContainers = ((blockControl.getContent() as FormBlock).getContent() as Form).getFormContainers();
687
693
  }
688
694
  if (aFormContainers) {
689
695
  for (const formContainer of aFormContainers) {
@@ -822,6 +828,10 @@ class ObjectPageController extends PageController {
822
828
  const blocks = oSubSection.getBlocks();
823
829
  blocks.forEach((block) => {
824
830
  if (block.isA<SubSectionBlock>("sap.fe.templates.ObjectPage.controls.SubSectionBlock")) {
831
+ // Set binding context to undefined on both the block and its content
832
+ // to support both: subsections with UI.Hidden annotation (content needs to be cleared)
833
+ // and customer controller extensions that set binding context on the block level
834
+ block.setBindingContext(undefined);
825
835
  block.content.setBindingContext?.(undefined);
826
836
  } else {
827
837
  block.setBindingContext(undefined);
@@ -831,59 +841,6 @@ class ObjectPageController extends PageController {
831
841
  oSubSection.getActions().forEach((actions) => actions.setBindingContext(undefined));
832
842
  }
833
843
 
834
- _onBackNavigationInDraft(oContext: ODataV4Context): void {
835
- this.messageHandler.removeTransitionMessages();
836
-
837
- // Function to navigate back, or display the launchpad if we're on the first page of the history
838
- const navBack = (): void => {
839
- // Use the new Navigation API if it's available in the browser, to check if we can go back in history.
840
- const canGoBack = (window as { navigation?: { canGoBack?: boolean } }).navigation?.canGoBack;
841
- if (canGoBack === true) {
842
- history.back();
843
- } else if (canGoBack === false) {
844
- this._routing.navigateBackFromContext(oContext);
845
- } else {
846
- // Fallback in case the browser doesn't support the Navigation API
847
- const currentURL = document.URL;
848
- history.back();
849
- // In case there is no previous page in the history, history.back does nothing.
850
- // In this case, we need to use navigateBackFromContext, that will display the home page
851
- setTimeout(() => {
852
- if (document.URL === currentURL) {
853
- this._routing.navigateBackFromContext(oContext);
854
- }
855
- }, 500);
856
- }
857
- };
858
-
859
- if (this.getAppComponent().getRouterProxy().checkIfBackHasSameContext()) {
860
- // Back nav will keep the same context --> no need to display the dialog
861
- history.back();
862
- } else if (!this.getView().getBindingContext()) {
863
- // This object page doesn't have a binding context, but still handles the shell back navigation --> pform the nav back and remove the navback handler as it makes no sense any longer
864
- this.getAppComponent()
865
- .getShellServices()
866
- .setBackNavigation(undefined)
867
- .then(navBack)
868
- .catch((e) => {
869
- Log.warning("Error while setting back navigation", e);
870
- });
871
- } else {
872
- const hiddenDraftEnabled = (this.getAppComponent()?.getEnvironmentCapabilities()?.getCapabilities()?.HiddenDraft as HiddenDraft)
873
- ?.enabled;
874
- draft.processDataLossOrDraftDiscardConfirmation(
875
- navBack,
876
- Function.prototype,
877
- oContext,
878
- this,
879
- true,
880
- draft.NavigationType.BackNavigation,
881
- undefined,
882
- hiddenDraftEnabled ? true : undefined
883
- );
884
- }
885
- }
886
-
887
844
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
888
845
  _onAfterBinding(inputBindingContext: ODataV4Context | undefined, mParameters: BindingParams | undefined): void {
889
846
  const view = this.getView();
@@ -1081,6 +1038,13 @@ class ObjectPageController extends PageController {
1081
1038
  if (!targetControlId) {
1082
1039
  return;
1083
1040
  }
1041
+ // Reset stale flag so ViewState.apply() is not blocked when the OP is re-activated
1042
+ // via shell keep-alive (cross-app re-entry) without _onBeforeBinding firing.
1043
+ // Intentional ordering: reset to false here before pollForSectionStability (below) sets it back
1044
+ // to true, so any concurrent ViewState.apply() triggered on re-entry is unblocked immediately.
1045
+ if (xAppStateTargetControlId) {
1046
+ this.skipViewStateSectionRestore = false;
1047
+ }
1084
1048
 
1085
1049
  // Wait for visibility bindings (needed for lazy loading scenarios)
1086
1050
  try {
@@ -1130,22 +1094,22 @@ class ObjectPageController extends PageController {
1130
1094
  return;
1131
1095
  }
1132
1096
 
1133
- // Poll to detect UX rules reset only on new entity binding.
1134
- // _adjustSelectedSectionByUXRules() only runs when ObjectPageLayout re-initializes with a new entity.
1135
- if (this.isNewBindingContext) {
1136
- pollForSectionStability({
1137
- getSelectedSection: () => oObjectPage.getSelectedSection(),
1138
- targetSectionId,
1139
- onResetDetected: navigateToTarget
1097
+ // Poll to detect UX rules reset. The guard on isNewBindingContext was removed because
1098
+ // _adjustSelectedSectionByUXRules() can also reset the section on cross-app re-entry to
1099
+ // the same entity (shell keep-alive). Polling is lightweight (max 10 × 50 ms) and safe to
1100
+ // run unconditionally whenever we need to hold a target section.
1101
+ pollForSectionStability({
1102
+ getSelectedSection: () => oObjectPage.getSelectedSection(),
1103
+ targetSectionId,
1104
+ onResetDetected: navigateToTarget
1105
+ })
1106
+ .then(() => {
1107
+ this.skipViewStateSectionRestore = true;
1108
+ return; // Required by linter rule promise/always-return
1140
1109
  })
1141
- .then(() => {
1142
- this.skipViewStateSectionRestore = true;
1143
- return; // Required by linter rule promise/always-return
1144
- })
1145
- .catch(() => {
1146
- // Required by linter rule promise/catch-or-return - polling failures are non-critical
1147
- });
1148
- }
1110
+ .catch(() => {
1111
+ // Required by linter rule promise/catch-or-return - polling failures are non-critical
1112
+ });
1149
1113
  }
1150
1114
 
1151
1115
  /**
@@ -1163,9 +1127,48 @@ class ObjectPageController extends PageController {
1163
1127
  }
1164
1128
 
1165
1129
  /**
1166
- * Performs navigation to the target section and then updates the app state.
1167
- * @param targetControlId The local ID of the target section or subsection.
1130
+ * Tracks telemetry when a new variant is saved.
1131
+ * @param event The save event.
1132
+ */
1133
+ private async trackVariantSavedTelemetry(event: UI5Event<Record<string, unknown>>): Promise<void> {
1134
+ if (event.getParameter("overwrite")) {
1135
+ return;
1136
+ }
1137
+ if (!event.getSource().isA("sap.fe.macros.Table")) {
1138
+ return;
1139
+ }
1140
+ if (!(await this.getAppComponent()?.getTelemetryService()?.isEnabled())) {
1141
+ return;
1142
+ }
1143
+ const tableAPI = event.getSource() as unknown as TableAPI;
1144
+ this.fireTelemetry(tableAPI?.getContent?.()?.getVariant?.()?.getVariants?.()?.length ?? 0, VariantType.OBJECT_PAGE_TABLE);
1145
+ }
1146
+
1147
+ /**
1148
+ * Tracks telemetry when variants are managed (e.g. deleted).
1149
+ * @param event The manage event.
1168
1150
  */
1151
+ private async trackVariantManagedTelemetry(event: UI5Event<Record<string, unknown>>): Promise<void> {
1152
+ const deletedCount: number = ((event.getParameter("deleted") as string[] | undefined) ?? []).length;
1153
+ if (deletedCount === 0) {
1154
+ return;
1155
+ }
1156
+ if (!(await this.getAppComponent()?.getTelemetryService()?.isEnabled())) {
1157
+ return;
1158
+ }
1159
+ this.fireTelemetry(deletedCount, VariantType.OBJECT_PAGE_TABLE);
1160
+ }
1161
+
1162
+ /**
1163
+ * Stores a FE.Variant telemetry event.
1164
+ * @param countVariants Number of variants affected by the action.
1165
+ * @param variantType The variant type discriminator.
1166
+ */
1167
+ private fireTelemetry(countVariants: number, variantType: TelemetryVariantEvent["parameters"]["variantType"]): void {
1168
+ const telemetryEvent: TelemetryVariantEvent = { type: "FE.Variant", parameters: { countVariants, variantType } };
1169
+ this.getAppComponent()?.getTelemetryService()?.storeAction(telemetryEvent);
1170
+ }
1171
+
1169
1172
  private navigateWithConfirmation(targetControlId: string): void {
1170
1173
  // Set targetSubSection BEFORE navigation for app state saving.
1171
1174
  // scrollToSection() doesn't fire the navigate event, so onNavigateChange won't be called.
@@ -1321,12 +1324,13 @@ class ObjectPageController extends PageController {
1321
1324
  const oView = this.getView();
1322
1325
  const oInternalModelContext = oView.getBindingContext("internal") as InternalModelContext;
1323
1326
  const oBindingContext = oView.getBindingContext();
1324
- //Show popup while navigating back from object page in case of draft
1327
+ // Show popup while navigating back from object page in case of draft (incl. hidden draft).
1328
+ // Sticky's shell back is registered centrally from EditFlow.registerEditLifecycleCallbacks.
1325
1329
  if (oBindingContext) {
1326
- const bIsStickyMode = ModelHelper.isStickySessionSupported(oBindingContext.getModel().getMetaModel());
1327
- if (!bIsStickyMode) {
1330
+ const isDraftSupported = ModelHelper.isDraftSupported(oBindingContext.getModel().getMetaModel(), oBindingContext.getPath());
1331
+ if (isDraftSupported) {
1328
1332
  const oAppComponent = CommonUtils.getAppComponent(oView);
1329
- await oAppComponent.getShellServices().setBackNavigation(() => this._onBackNavigationInDraft(oBindingContext));
1333
+ await oAppComponent.getShellServices().setBackNavigation(() => this.editFlow.onBackNavigationInDraft(oBindingContext));
1330
1334
  }
1331
1335
  }
1332
1336
  // do not request recommendations action if we are in Display mode
@@ -2445,11 +2449,15 @@ class ObjectPageController extends PageController {
2445
2449
  onVariantSelected: function (this: ObjectPageController): void {
2446
2450
  this.getExtensionAPI().updateAppState();
2447
2451
  },
2448
- onVariantSaved: function (this: ObjectPageController): void {
2452
+ onVariantSaved: function (this: ObjectPageController, event: UI5Event<Record<string, unknown>>): void {
2449
2453
  //TODO: Should remove this setTimeOut once Variant Management provides an api to fetch the current variant key on save
2450
2454
  setTimeout(() => {
2451
2455
  this.getExtensionAPI().updateAppState();
2452
2456
  }, 2000);
2457
+ this.trackVariantSavedTelemetry(event);
2458
+ },
2459
+ onVariantManaged: function (this: ObjectPageController, event: UI5Event<Record<string, unknown>>): void {
2460
+ this.trackVariantManagedTelemetry(event);
2453
2461
  },
2454
2462
  navigateToSubSection: function (oController: ObjectPageController, vDetailConfig: string | object): void {
2455
2463
  const oDetailConfig = typeof vDetailConfig === "string" ? JSON.parse(vDetailConfig) : vDetailConfig;