diff --git a/packages/base/src/css/OpenUI5PopupStyles.css b/packages/base/src/css/OpenUI5PopupStyles.css index 49dad81fd254..d523159cf4fd 100644 --- a/packages/base/src/css/OpenUI5PopupStyles.css +++ b/packages/base/src/css/OpenUI5PopupStyles.css @@ -2,4 +2,9 @@ border: none; overflow: visible; margin: 0; +} + +.sapUiBLy[popover] { + width: 100%; + height: 100%; } \ No newline at end of file diff --git a/packages/base/src/features/OpenUI5Support.ts b/packages/base/src/features/OpenUI5Support.ts index 6e5856afa6e3..a78d019f5bd0 100644 --- a/packages/base/src/features/OpenUI5Support.ts +++ b/packages/base/src/features/OpenUI5Support.ts @@ -6,7 +6,7 @@ import { removeOpenedPopup, getTopmostPopup, } from "./patchPopup.js"; -import type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo } from "./patchPopup.js"; +import type { OpenUI5PopupClass, OpenUI5PopupBasedControlClass, PopupInfo } from "./patchPopup.js"; import { registerFeature } from "../FeaturesRegistry.js"; import { setTheme } from "../config/Theme.js"; import type { CLDRData } from "../asset-registries/LocaleData.js"; @@ -110,7 +110,7 @@ class OpenUI5Support { "sap/ui/core/date/CalendarUtils", ]; } - window.sap.ui.require(deps, (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl, Popover: OpenUI5PopupBasedControl, Patcher: OpenUI5Patcher) => { + window.sap.ui.require(deps, (Popup: OpenUI5PopupClass, Dialog: OpenUI5PopupBasedControlClass, Popover: OpenUI5PopupBasedControlClass, Patcher: OpenUI5Patcher) => { patchPatcher(Patcher); patchPopup(Popup, Dialog, Popover); resolve(); diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index db07986e49c0..de0d7e800beb 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -6,18 +6,21 @@ type Control = { getDomRef: () => HTMLElement | null, } -// 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 +}; + +// The lifecycle of Popup.js is open -> _opened -> close -> _closed, we're interested in the first (open) and last (_closed) +type OpenUI5PopupClass = { + prototype: OpenUI5Popup }; -type OpenUI5PopupBasedControl = { +type OpenUI5PopupBasedControlClass = { prototype: { onsapescape: (...args: any[]) => void, oPopup: OpenUI5Popup, @@ -25,8 +28,11 @@ type OpenUI5PopupBasedControl = { }; type PopupInfo = { - type: "OpenUI5" | "WebComponent"; + type: "WebComponent"; instance: object; +} | { + type: "OpenUI5"; + instance: OpenUI5Popup; }; // contains all OpenUI5 and Web Component popups that are currently opened @@ -37,6 +43,8 @@ const addOpenedPopup = (popupInfo: PopupInfo) => { }; const removeOpenedPopup = (popup: object) => { + arrangeNativePopovers(); + const index = AllOpenedPopupsRegistry.openedRegistry.findIndex(el => el.instance === popup); if (index > -1) { AllOpenedPopupsRegistry.openedRegistry.splice(index, 1); @@ -68,16 +76,56 @@ const hasWebComponentPopupAbove = (popup: object) => { return false; }; -const openNativePopover = (domRef: HTMLElement) => { +const openNativePopover = (domRef: HTMLElement, popup: OpenUI5Popup) => { + 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 arrangeNativePopovers = () => { + if (!isNativePopoverOpen()) { + return; + } + + const prevPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 2]; + if (!prevPopup || prevPopup.type !== "OpenUI5" || !prevPopup.instance.getModal()) { + return; + } + + const prevPopupContent = prevPopup.instance.getContent()!; + const content = prevPopupContent instanceof HTMLElement ? prevPopupContent : prevPopupContent?.getDomRef(); + + const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); + + content?.hidePopover(); + + if (prevPopup.instance.getModal()) { + openUI5BlockLayer?.showPopover(); + } + + content?.showPopover(); +}; + const closeNativePopover = (domRef: HTMLElement) => { if (domRef.hasAttribute("popover")) { domRef.hidePopover(); domRef.removeAttribute("popover"); } + + const lastPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 1]; + if (lastPopup.type === "OpenUI5") { + const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); + if (openUI5BlockLayer && openUI5BlockLayer.hasAttribute("popover")) { + openUI5BlockLayer.hidePopover(); + } + } }; const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => { @@ -91,7 +139,7 @@ const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => }); }; -const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControl) => { +const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControlClass) => { const origOnsapescape = PopupBasedControl.prototype.onsapescape; PopupBasedControl.prototype.onsapescape = function onsapescape(...args: any[]) { if (hasWebComponentPopupAbove(this.oPopup)) { @@ -102,18 +150,17 @@ 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) { + if (openingInitiated && isNativePopoverOpen()) { const element = this.getContent(); if (element) { const domRef = element instanceof HTMLElement ? element : element?.getDomRef(); if (domRef) { - openNativePopover(domRef); + openNativePopover(domRef, this); } } } @@ -125,7 +172,7 @@ 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(); @@ -139,7 +186,7 @@ const patchClosed = (Popup: OpenUI5Popup) => { }; }; -const patchFocusEvent = (Popup: OpenUI5Popup) => { +const patchFocusEvent = (Popup: OpenUI5PopupClass) => { const origFocusEvent = Popup.prototype.onFocusEvent; Popup.prototype.onFocusEvent = function onFocusEvent(...args: any[]) { if (!hasWebComponentPopupAbove(this)) { @@ -154,7 +201,7 @@ const createGlobalStyles = () => { document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet]; }; -const patchPopup = (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl, Popover: OpenUI5PopupBasedControl) => { +const patchPopup = (Popup: OpenUI5PopupClass, Dialog: OpenUI5PopupBasedControlClass, Popover: OpenUI5PopupBasedControlClass) => { insertOpenUI5PopupStyles(); patchOpen(Popup); // Popup.prototype.open patchClosed(Popup); // Popup.prototype._closed @@ -171,4 +218,4 @@ export { getTopmostPopup, }; -export type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo }; +export type { OpenUI5PopupClass, OpenUI5PopupBasedControlClass, PopupInfo }; diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index f11c71d574f2..fb5355493235 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -17,6 +17,7 @@ function onOpenUI5InitMethod(win) { press: function () { new Dialog("openUI5Dialog1", { title: "OpenUI5 Dialog", + draggable: true, content: [ new HTML({ content: @@ -49,6 +50,12 @@ function onOpenUI5InitMethod(win) { press: function () { (document.getElementById("respPopoverNoInitialFocus") as any).open = true; } + }), + new Button("openWebCDialog", { + text: "Open WebC Dialog", + press: function () { + (document.getElementById("webCDialog1") as any).open = true; + } }) ], afterClose: function () { @@ -112,6 +119,10 @@ function onOpenUI5InitMethod(win) { openUI5Dialog(win); }); + document.getElementById("openUI5DialogFromWebC").addEventListener("click", function () { + openUI5Dialog(win); + }); + document.getElementById("popoverButtonNoFocus").addEventListener("click", function (event) { openUI5Popover(win, event.target); }); @@ -121,6 +132,7 @@ function openUI5Dialog(win) { (win as any).sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { new Dialog("openUI5DialogWithButtons", { title: "OpenUI5 Dialog", + draggable: true, content: [ new Button({ text: "Focus stop" @@ -130,6 +142,29 @@ function openUI5Dialog(win) { press: function () { (document.getElementById("newDialog1") as any).open = true; } + }), + new Button("openUI5DialogFromUi5", { + text: "Open UI5 Dialog", + press: function () { + openUI5DialogFromUi5(win) + } + }) + ], + afterClose: function () { + this.destroy(); + } + }).open(); + }); +} + +function openUI5DialogFromUi5(win) { + (win as any).sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { + new Dialog("openUI5DialogFinal", { + title: "OpenUI5 Dialog", + draggable: true, + content: [ + new Button({ + text: "Focus stop" }) ], afterClose: function () { @@ -204,6 +239,9 @@ describe("ui5 and web components integration", () => { +