Skip to content

Commit 43e545a

Browse files
authored
fix(ui5-popover): render block layers in the correct order (#12659)
* fix(ui5-popover): render block layers in the correct order OpenUI5 dialogs' and the WebC dialogs' block layers are rendered in order Fixes: 12444
1 parent f672e13 commit 43e545a

File tree

6 files changed

+383
-58
lines changed

6 files changed

+383
-58
lines changed

packages/base/src/css/OpenUI5PopupStyles.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@
22
border: none;
33
overflow: visible;
44
margin: 0;
5+
}
6+
7+
.sapUiBLy[popover] {
8+
width: 100%;
9+
height: 100%;
510
}

packages/base/src/features/OpenUI5Support.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
removeOpenedPopup,
77
getTopmostPopup,
88
} from "./patchPopup.js";
9-
import type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo } from "./patchPopup.js";
9+
import type { OpenUI5PopupClass, OpenUI5DialogClass, PopupInfo } from "./patchPopup.js";
1010
import { registerFeature } from "../FeaturesRegistry.js";
1111
import { setTheme } from "../config/Theme.js";
1212
import type { CLDRData } from "../asset-registries/LocaleData.js";
@@ -110,7 +110,7 @@ class OpenUI5Support {
110110
"sap/ui/core/date/CalendarUtils",
111111
];
112112
}
113-
window.sap.ui.require(deps, (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl, Patcher: OpenUI5Patcher) => {
113+
window.sap.ui.require(deps, (Popup: OpenUI5PopupClass, Dialog: OpenUI5DialogClass, Patcher: OpenUI5Patcher) => {
114114
patchPatcher(Patcher);
115115
patchPopup(Popup, Dialog);
116116
resolve();

packages/base/src/features/patchPopup.ts

Lines changed: 103 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,31 @@ type Control = {
88

99
// The lifecycle of Popup.js is open -> _opened -> close -> _closed, we're interested in the first (open) and last (_closed)
1010
type OpenUI5Popup = {
11-
prototype: {
12-
open: (...args: any[]) => void,
13-
_closed: (...args: any[]) => void,
14-
getOpenState: () => "CLOSED" | "CLOSING" | "OPEN" | "OPENING",
15-
getContent: () => Control | HTMLElement | null, // this is the OpenUI5 Element/Control instance that opens the Popup (usually sap.m.Popover/sap.m.Dialog)
16-
onFocusEvent: (...args: any[]) => void,
17-
}
11+
open: (...args: any[]) => void,
12+
_closed: (...args: any[]) => void,
13+
getOpenState: () => "CLOSED" | "CLOSING" | "OPEN" | "OPENING",
14+
getContent: () => Control | HTMLElement | null, // this is the OpenUI5 Element/Control instance that opens the Popup (usually sap.m.Popover/sap.m.Dialog)
15+
onFocusEvent: (...args: any[]) => void,
16+
getModal: () => boolean
1817
};
1918

20-
type OpenUI5PopupBasedControl = {
19+
type OpenUI5PopupClass = {
20+
prototype: OpenUI5Popup
21+
};
22+
23+
type OpenUI5DialogClass = {
2124
prototype: {
2225
onsapescape: (...args: any[]) => void,
2326
oPopup: OpenUI5Popup,
2427
}
2528
};
2629

2730
type PopupInfo = {
28-
type: "OpenUI5" | "WebComponent";
31+
type: "WebComponent";
2932
instance: object;
33+
} | {
34+
type: "OpenUI5";
35+
instance: OpenUI5Popup;
3036
};
3137

3238
// contains all OpenUI5 and Web Component popups that are currently opened
@@ -38,12 +44,20 @@ const addOpenedPopup = (popupInfo: PopupInfo) => {
3844

3945
const removeOpenedPopup = (popup: object) => {
4046
const index = AllOpenedPopupsRegistry.openedRegistry.findIndex(el => el.instance === popup);
47+
48+
if (index === AllOpenedPopupsRegistry.openedRegistry.length - 1) {
49+
fixTopmostOpenUI5Popup();
50+
}
51+
4152
if (index > -1) {
4253
AllOpenedPopupsRegistry.openedRegistry.splice(index, 1);
4354
}
4455
};
4556

4657
const getTopmostPopup = () => {
58+
if (AllOpenedPopupsRegistry.openedRegistry.length === 0) {
59+
return null;
60+
}
4761
return AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 1].instance;
4862
};
4963

@@ -68,16 +82,83 @@ const hasWebComponentPopupAbove = (popup: object) => {
6882
return false;
6983
};
7084

71-
const openNativePopover = (domRef: HTMLElement) => {
85+
const getPopupContentElement = (popup: OpenUI5Popup): HTMLElement | null => {
86+
const content = popup.getContent();
87+
return content instanceof HTMLElement ? content : content?.getDomRef() || null;
88+
};
89+
90+
const openNativePopoverForOpenUI5 = (popup: OpenUI5Popup) => {
91+
const openingInitiated = ["OPENING", "OPEN"].includes(popup.getOpenState());
92+
if (!openingInitiated || !isNativePopoverOpen()) {
93+
return;
94+
}
95+
96+
const domRef = getPopupContentElement(popup);
97+
98+
if (!domRef) {
99+
return;
100+
}
101+
102+
const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup");
103+
104+
if (popup.getModal() && openUI5BlockLayer) {
105+
openUI5BlockLayer.setAttribute("popover", "manual");
106+
openUI5BlockLayer.hidePopover();
107+
openUI5BlockLayer.showPopover();
108+
}
109+
72110
domRef.setAttribute("popover", "manual");
73111
domRef.showPopover();
74112
};
75113

76-
const closeNativePopover = (domRef: HTMLElement) => {
114+
const closeNativePopoverForOpenUI5 = (popup: OpenUI5Popup) => {
115+
const domRef = getPopupContentElement(popup);
116+
117+
if (!domRef) {
118+
return;
119+
}
120+
77121
if (domRef.hasAttribute("popover")) {
78122
domRef.hidePopover();
79123
domRef.removeAttribute("popover");
80124
}
125+
126+
if (getTopmostPopup() !== popup) {
127+
return;
128+
}
129+
130+
// The OpenUI5 block layer is only one for all modal OpenUI5 popups,
131+
// and it is displayed above all opened pupups - OpenUI5 and Web Components,
132+
// as a result, we need to hide this block layer.
133+
// If the underlying popup is a Web Component - it is displayed like a native popover, and we don't need to do anything
134+
// If the underlying popup is an OpenUI5 popup, it will be fixed in fixTopmostOpenUI5Popup method.
135+
if (popup.getModal()) {
136+
const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup");
137+
if (openUI5BlockLayer && openUI5BlockLayer.hasAttribute("popover")) {
138+
openUI5BlockLayer.hidePopover();
139+
}
140+
}
141+
};
142+
143+
const fixTopmostOpenUI5Popup = () => {
144+
if (!isNativePopoverOpen()) {
145+
return;
146+
}
147+
148+
const prevPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 2];
149+
if (!prevPopup
150+
|| prevPopup.type !== "OpenUI5"
151+
|| !prevPopup.instance.getModal()) {
152+
return;
153+
}
154+
155+
const content = getPopupContentElement(prevPopup.instance);
156+
const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup");
157+
158+
content?.hidePopover();
159+
openUI5BlockLayer?.showPopover();
160+
161+
content?.showPopover();
81162
};
82163

83164
const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => {
@@ -91,9 +172,9 @@ const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean =>
91172
});
92173
};
93174

94-
const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControl) => {
95-
const origOnsapescape = PopupBasedControl.prototype.onsapescape;
96-
PopupBasedControl.prototype.onsapescape = function onsapescape(...args: any[]) {
175+
const patchDialog = (Dialog: OpenUI5DialogClass) => {
176+
const origOnsapescape = Dialog.prototype.onsapescape;
177+
Dialog.prototype.onsapescape = function onsapescape(...args: any[]) {
97178
if (hasWebComponentPopupAbove(this.oPopup)) {
98179
return;
99180
}
@@ -102,21 +183,11 @@ const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControl) =>
102183
};
103184
};
104185

105-
const patchOpen = (Popup: OpenUI5Popup) => {
186+
const patchOpen = (Popup: OpenUI5PopupClass) => {
106187
const origOpen = Popup.prototype.open;
107188
Popup.prototype.open = function open(...args: any[]) {
108189
origOpen.apply(this, args); // call open first to initiate opening
109-
const topLayerAlreadyInUse = isNativePopoverOpen();
110-
const openingInitiated = ["OPENING", "OPEN"].includes(this.getOpenState());
111-
if (openingInitiated && topLayerAlreadyInUse) {
112-
const element = this.getContent();
113-
if (element) {
114-
const domRef = element instanceof HTMLElement ? element : element?.getDomRef();
115-
if (domRef) {
116-
openNativePopover(domRef);
117-
}
118-
}
119-
}
190+
openNativePopoverForOpenUI5(this);
120191

121192
addOpenedPopup({
122193
type: "OpenUI5",
@@ -125,21 +196,16 @@ const patchOpen = (Popup: OpenUI5Popup) => {
125196
};
126197
};
127198

128-
const patchClosed = (Popup: OpenUI5Popup) => {
199+
const patchClosed = (Popup: OpenUI5PopupClass) => {
129200
const _origClosed = Popup.prototype._closed;
130201
Popup.prototype._closed = function _closed(...args: any[]) {
131-
const element = this.getContent();
132-
const domRef = element instanceof HTMLElement ? element : element?.getDomRef();
202+
closeNativePopoverForOpenUI5(this);
133203
_origClosed.apply(this, args); // only then call _close
134-
if (domRef) {
135-
closeNativePopover(domRef); // unset the popover attribute and close the native popover, but only if still in DOM
136-
}
137-
138204
removeOpenedPopup(this);
139205
};
140206
};
141207

142-
const patchFocusEvent = (Popup: OpenUI5Popup) => {
208+
const patchFocusEvent = (Popup: OpenUI5PopupClass) => {
143209
const origFocusEvent = Popup.prototype.onFocusEvent;
144210
Popup.prototype.onFocusEvent = function onFocusEvent(...args: any[]) {
145211
if (!hasWebComponentPopupAbove(this)) {
@@ -154,13 +220,13 @@ const createGlobalStyles = () => {
154220
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
155221
};
156222

157-
const patchPopup = (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl) => {
223+
const patchPopup = (Popup: OpenUI5PopupClass, Dialog: OpenUI5DialogClass) => {
158224
insertOpenUI5PopupStyles();
159225
patchOpen(Popup); // Popup.prototype.open
160226
patchClosed(Popup); // Popup.prototype._closed
161227
createGlobalStyles(); // Ensures correct popover positioning by OpenUI5 (otherwise 0,0 is the center of the screen)
162228
patchFocusEvent(Popup);// Popup.prototype.onFocusEvent
163-
patchPopupBasedControl(Dialog); // Dialog.prototype.onsapescape
229+
patchDialog(Dialog); // Dialog.prototype.onsapescape
164230
};
165231

166232
export {
@@ -170,4 +236,4 @@ export {
170236
getTopmostPopup,
171237
};
172238

173-
export type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo };
239+
export type { OpenUI5PopupClass, OpenUI5DialogClass, PopupInfo };

0 commit comments

Comments
 (0)