Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/base/src/css/OpenUI5PopupStyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@
border: none;
overflow: visible;
margin: 0;
}

.sapUiBLy[popover] {
width: 100%;
height: 100%;
}
4 changes: 2 additions & 2 deletions packages/base/src/features/OpenUI5Support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
removeOpenedPopup,
getTopmostPopup,
} from "./patchPopup.js";
import type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo } from "./patchPopup.js";
import type { OpenUI5PopupClass, OpenUI5DialogClass, PopupInfo } from "./patchPopup.js";
import { registerFeature } from "../FeaturesRegistry.js";
import { setTheme } from "../config/Theme.js";
import type { CLDRData } from "../asset-registries/LocaleData.js";
Expand Down Expand Up @@ -110,7 +110,7 @@ class OpenUI5Support {
"sap/ui/core/date/CalendarUtils",
];
}
window.sap.ui.require(deps, (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl, Patcher: OpenUI5Patcher) => {
window.sap.ui.require(deps, (Popup: OpenUI5PopupClass, Dialog: OpenUI5DialogClass, Patcher: OpenUI5Patcher) => {
patchPatcher(Patcher);
patchPopup(Popup, Dialog);
resolve();
Expand Down
128 changes: 91 additions & 37 deletions packages/base/src/features/patchPopup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,31 @@ type Control = {

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

type OpenUI5PopupBasedControl = {
type OpenUI5PopupClass = {
prototype: OpenUI5Popup
};

type OpenUI5DialogClass = {
prototype: {
onsapescape: (...args: any[]) => void,
oPopup: OpenUI5Popup,
}
};

type PopupInfo = {
type: "OpenUI5" | "WebComponent";
type: "WebComponent";
instance: object;
} | {
type: "OpenUI5";
instance: OpenUI5Popup;
};

// contains all OpenUI5 and Web Component popups that are currently opened
Expand All @@ -38,6 +44,11 @@ const addOpenedPopup = (popupInfo: PopupInfo) => {

const removeOpenedPopup = (popup: object) => {
const index = AllOpenedPopupsRegistry.openedRegistry.findIndex(el => el.instance === popup);

if (index === AllOpenedPopupsRegistry.openedRegistry.length - 1) {
fixOpenUI5PopupBelow();
}

if (index > -1) {
AllOpenedPopupsRegistry.openedRegistry.splice(index, 1);
}
Expand Down Expand Up @@ -68,16 +79,74 @@ const hasWebComponentPopupAbove = (popup: object) => {
return false;
};

const openNativePopover = (domRef: HTMLElement) => {
const getPopupContentElement = (popup: OpenUI5Popup): HTMLElement | null => {
const content = popup.getContent()!;
return content instanceof HTMLElement ? content : content?.getDomRef() || null;
};

const openNativePopoverForOpenUI5 = (popup: OpenUI5Popup) => {
const openingInitiated = ["OPENING", "OPEN"].includes(popup.getOpenState());
if (!openingInitiated || !isNativePopoverOpen()) {
return;
}

const domRef = getPopupContentElement(popup);

if (!domRef) {
return;
}

const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup");

if (popup.getModal() && openUI5BlockLayer) {
openUI5BlockLayer.setAttribute("popover", "manual");
openUI5BlockLayer.hidePopover();
openUI5BlockLayer.showPopover();
}

domRef.setAttribute("popover", "manual");
domRef.showPopover();
};

const closeNativePopover = (domRef: HTMLElement) => {
const closeNativePopoverForOpenUI5 = (popup: OpenUI5Popup) => {
const domRef = getPopupContentElement(popup);

if (!domRef) {
return;
}

if (domRef.hasAttribute("popover")) {
domRef.hidePopover();
domRef.removeAttribute("popover");
}

if (popup.getModal()) {
const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup");
if (openUI5BlockLayer && openUI5BlockLayer.hasAttribute("popover")) {
openUI5BlockLayer.hidePopover();
}
}
};

const fixOpenUI5PopupBelow = () => {
if (!isNativePopoverOpen()) {
return;
}

const prevPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 2];
if (!prevPopup
|| prevPopup.type !== "OpenUI5"
|| !prevPopup.instance.getModal()) {
return;
}

const content = getPopupContentElement(prevPopup.instance);
const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup");

content?.hidePopover();
openUI5BlockLayer?.showPopover();

content?.showPopover();
};

const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => {
Expand All @@ -91,9 +160,9 @@ const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean =>
});
};

const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControl) => {
const origOnsapescape = PopupBasedControl.prototype.onsapescape;
PopupBasedControl.prototype.onsapescape = function onsapescape(...args: any[]) {
const patchDialog = (Dialog: OpenUI5DialogClass) => {
const origOnsapescape = Dialog.prototype.onsapescape;
Dialog.prototype.onsapescape = function onsapescape(...args: any[]) {
if (hasWebComponentPopupAbove(this.oPopup)) {
return;
}
Expand All @@ -102,21 +171,11 @@ const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControl) =>
};
};

const patchOpen = (Popup: OpenUI5Popup) => {
const patchOpen = (Popup: OpenUI5PopupClass) => {
const origOpen = Popup.prototype.open;
Popup.prototype.open = function open(...args: any[]) {
origOpen.apply(this, args); // call open first to initiate opening
const topLayerAlreadyInUse = isNativePopoverOpen();
const openingInitiated = ["OPENING", "OPEN"].includes(this.getOpenState());
if (openingInitiated && topLayerAlreadyInUse) {
const element = this.getContent();
if (element) {
const domRef = element instanceof HTMLElement ? element : element?.getDomRef();
if (domRef) {
openNativePopover(domRef);
}
}
}
openNativePopoverForOpenUI5(this);

addOpenedPopup({
type: "OpenUI5",
Expand All @@ -125,21 +184,16 @@ const patchOpen = (Popup: OpenUI5Popup) => {
};
};

const patchClosed = (Popup: OpenUI5Popup) => {
const patchClosed = (Popup: OpenUI5PopupClass) => {
const _origClosed = Popup.prototype._closed;
Popup.prototype._closed = function _closed(...args: any[]) {
const element = this.getContent();
const domRef = element instanceof HTMLElement ? element : element?.getDomRef();
_origClosed.apply(this, args); // only then call _close
if (domRef) {
closeNativePopover(domRef); // unset the popover attribute and close the native popover, but only if still in DOM
}

closeNativePopoverForOpenUI5(this);
removeOpenedPopup(this);
};
};

const patchFocusEvent = (Popup: OpenUI5Popup) => {
const patchFocusEvent = (Popup: OpenUI5PopupClass) => {
const origFocusEvent = Popup.prototype.onFocusEvent;
Popup.prototype.onFocusEvent = function onFocusEvent(...args: any[]) {
if (!hasWebComponentPopupAbove(this)) {
Expand All @@ -154,13 +208,13 @@ const createGlobalStyles = () => {
document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet];
};

const patchPopup = (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl) => {
const patchPopup = (Popup: OpenUI5PopupClass, Dialog: OpenUI5DialogClass) => {
insertOpenUI5PopupStyles();
patchOpen(Popup); // Popup.prototype.open
patchClosed(Popup); // Popup.prototype._closed
createGlobalStyles(); // Ensures correct popover positioning by OpenUI5 (otherwise 0,0 is the center of the screen)
patchFocusEvent(Popup);// Popup.prototype.onFocusEvent
patchPopupBasedControl(Dialog); // Dialog.prototype.onsapescape
patchDialog(Dialog); // Dialog.prototype.onsapescape
};

export {
Expand All @@ -170,4 +224,4 @@ export {
getTopmostPopup,
};

export type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo };
export type { OpenUI5PopupClass, OpenUI5DialogClass, PopupInfo };
Loading
Loading