From e76d000f5f6890def3813b3284dd17cb51060dc1 Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Tue, 11 Nov 2025 11:20:26 +0200 Subject: [PATCH 01/10] fix(ui5-popover): render block layers in the correct order OpenUI5 dialogs' and the WebC dialogs' block layers are rendered in order Fixes: 12444 --- packages/base/src/css/OpenUI5PopupStyles.css | 5 ++ packages/base/src/features/patchPopup.ts | 76 +++++++++++++++-- .../test/pages/DialogAndOpenUI5Dialog.html | 81 +++++++++++++++++-- 3 files changed, 150 insertions(+), 12 deletions(-) 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/patchPopup.ts b/packages/base/src/features/patchPopup.ts index db07986e49c0..f0d186497556 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -68,16 +68,82 @@ const hasWebComponentPopupAbove = (popup: object) => { return false; }; +const blocks: any[] = []; +const popovers: any[] = []; + const openNativePopover = (domRef: HTMLElement) => { + const block = document.getElementById("sap-ui-blocklayer-popup"); + popovers.push(domRef); + + if (block?.hasAttribute("popover")) { + const newBlock = block?.cloneNode(true); + if (newBlock) { + (newBlock as HTMLElement).setAttribute("id", `sap-ui-blocklayer-popup${blocks.length}`); + const staticArea = document.getElementById("sap-ui-static"); + + staticArea?.appendChild(newBlock); + block?.hidePopover(); + block?.removeAttribute("popover"); + blocks.push(newBlock); + } + + block?.removeAttribute("popover"); + } + + block?.setAttribute("popover", "manual"); + // blocks.push(block); + block?.showPopover(); + domRef.setAttribute("popover", "manual"); domRef.showPopover(); }; -const closeNativePopover = (domRef: HTMLElement) => { - if (domRef.hasAttribute("popover")) { - domRef.hidePopover(); - domRef.removeAttribute("popover"); +const closeNativePopover = () => { + const allPopupsIndex = AllOpenedPopupsRegistry.openedRegistry.findIndex(element => { + const instance = element.instance as { _sInitialFocusId?: string }; + return instance._sInitialFocusId === popovers[popovers.length - 1].id; + }); + if (AllOpenedPopupsRegistry.openedRegistry[allPopupsIndex - 1]?.type === "WebComponent") { + popovers.pop(); + if (popovers.length > 0 && blocks.length > 0) { + popovers[popovers.length - 1].hidePopover(); + blocks[blocks.length - 1].hidePopover(); + document.getElementById("sap-ui-blocklayer-popup")?.hidePopover(); + + (AllOpenedPopupsRegistry.openedRegistry[allPopupsIndex - 1].instance as HTMLElement).addEventListener("ui5-close", () => { + if ((AllOpenedPopupsRegistry.openedRegistry[allPopupsIndex - 2]?.type === "OpenUI5")) { + popovers[1].hidePopover(); + const lastBlock = blocks[blocks.length - 1]; + const lastPopover = popovers[popovers.length - 1]; + + arrangeBlocksAndPopovers(lastBlock as HTMLElement, lastPopover as HTMLElement); + } + }); + return; + } + } + + popovers.pop(); + if (popovers.length > 0 && blocks.length > 0) { + const lastBlock = blocks[blocks.length - 1]; + const lastPopover = popovers[popovers.length - 1]; + arrangeBlocksAndPopovers(lastBlock as HTMLElement, lastPopover as HTMLElement); + } + if (popovers.length === 0 && blocks.length > 0) { + blocks[blocks.length - 1]?.hidePopover(); + } +}; + +const arrangeBlocksAndPopovers = (block: HTMLElement, popover: HTMLElement) => { + block?.hidePopover(); + popover.hidePopover(); + block?.showPopover(); + + const ui5block = document.getElementById("sap-ui-blocklayer-popup"); + if (ui5block) { + ui5block.style.visibility = "hidden"; } + popover.showPopover(); }; const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => { @@ -132,7 +198,7 @@ const patchClosed = (Popup: OpenUI5Popup) => { 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 + closeNativePopover(); // unset the popover attribute and close the native popover, but only if still in DOM } removeOpenedPopup(this); diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 8c118a4f8dd8..fd6b251b9aa6 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -27,6 +27,7 @@ press: function () { new Dialog("openUI5Dialog", { title: "OpenUI5 Dialog", + draggable: true, content: [ new HTML({ content: @@ -112,7 +113,6 @@ } }).placeAt("dialog1content"); - ShortcutHintsMixin.addConfig(button, { event: "press", position: "0 0", @@ -128,20 +128,31 @@ document.getElementById("popoverButtonNoFocus").addEventListener("click", function (event) { openUI5Popover(event.target); }); + + document.getElementById("someButton2").addEventListener("click", function () { + openUI5Dialog2(); + }); + document.getElementById("someButton3").addEventListener("click", function () { + openUI5Dialog3(); + }); + document.getElementById("someButton4").addEventListener("click", function () { + document.getElementById("newDialog4").open = true; + }); } function openUI5Dialog() { sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { new Dialog("openUI5DialogWithButtons", { - title: "OpenUI5 Dialog", + title: "UI5 Dialog 1", + draggable: true, content: [ new Button({ text: "Focus stop" }), new Button("openUI5DialogButton", { - text: "Open WebC Dialog", + text: "Open UI5 Dialog 2", press: function () { - document.getElementById("newDialog1").open = true; + openUI5Dialog2(); } }) ], @@ -152,6 +163,52 @@ }); } + function openUI5Dialog2() { + sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { + new Dialog("openUI5DialogWithButtons2", { + title: "UI5 Dialog 2", + draggable: true, + content: [ + new Button({ + text: "Focus stop" + }), + new Button("openUI5DialogButton2", { + text: "Open WebC Dialog 2", + press: function () { + document.getElementById("newDialog2").open = true; + } + }) + ], + afterClose: function () { + this.destroy(); + } + }).open(); + }); + } + + function openUI5Dialog3() { + sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { + new Dialog("openUI5DialogWithButtons3", { + title: "UI5 Dialog 3", + draggable: true, + content: [ + new Button({ + text: "Focus stop" + }), + new Button("openUI5DialogButton22", { + text: "Open WebC Dialog 3", + press: function () { + document.getElementById("newDialog3").open = true; + } + }) + ], + afterClose: function () { + this.destroy(); + } + }).open(); + }); + } + function openUI5Popover(opener) { sap.ui.require(["sap/m/Popover", "sap/m/Button"], (Popover, Button) => { new Popover("openUI5PopoverSecond", { @@ -189,7 +246,7 @@
Open WebC Dialog
- +

Web Components: @@ -211,8 +268,18 @@ Open UI5 dialog Open UI5 Popover No Initial Focus
- - Some button + + Some button 1 + + + Open UI5 Dialog + + + Some button 3 + + + + Some button 3
Date: Tue, 11 Nov 2025 13:11:36 +0200 Subject: [PATCH 02/10] fix(ui5-popover): render block layers in the correct order Fixes failing test --- packages/base/src/features/patchPopup.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index f0d186497556..d7f3b6f0d17f 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -143,7 +143,11 @@ const arrangeBlocksAndPopovers = (block: HTMLElement, popover: HTMLElement) => { if (ui5block) { ui5block.style.visibility = "hidden"; } - popover.showPopover(); + if (popover.isConnected) { + popover?.showPopover(); + } else if (popovers.length === 1 && AllOpenedPopupsRegistry.openedRegistry[0]?.type === "WebComponent") { + blocks.forEach(b => { b.hidePopover(); }); + } }; const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => { From 598adbba83c530f5ff9228c9a37f8a7a2c5cafb9 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 11 Nov 2025 16:09:52 +0200 Subject: [PATCH 03/10] chore: handle opening and closing --- packages/base/src/features/patchPopup.ts | 93 +++++++----------------- 1 file changed, 25 insertions(+), 68 deletions(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index d7f3b6f0d17f..53171228e1f6 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -37,6 +37,19 @@ const addOpenedPopup = (popupInfo: PopupInfo) => { }; const removeOpenedPopup = (popup: object) => { + if (isNativePopoverOpen()) { + const prevPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 2]; + if (prevPopup && prevPopup.type === "OpenUI5") { + const content = (prevPopup.instance as any).getContent().getDomRef() as HTMLElement; + const block = document.getElementById("sap-ui-blocklayer-popup"); + + content.hidePopover(); + + block?.showPopover(); + content.showPopover(); + } + } + const index = AllOpenedPopupsRegistry.openedRegistry.findIndex(el => el.instance === popup); if (index > -1) { AllOpenedPopupsRegistry.openedRegistry.splice(index, 1); @@ -68,85 +81,29 @@ const hasWebComponentPopupAbove = (popup: object) => { return false; }; -const blocks: any[] = []; -const popovers: any[] = []; - const openNativePopover = (domRef: HTMLElement) => { const block = document.getElementById("sap-ui-blocklayer-popup"); - popovers.push(domRef); - - if (block?.hasAttribute("popover")) { - const newBlock = block?.cloneNode(true); - if (newBlock) { - (newBlock as HTMLElement).setAttribute("id", `sap-ui-blocklayer-popup${blocks.length}`); - const staticArea = document.getElementById("sap-ui-static"); - - staticArea?.appendChild(newBlock); - block?.hidePopover(); - block?.removeAttribute("popover"); - blocks.push(newBlock); - } - - block?.removeAttribute("popover"); - } block?.setAttribute("popover", "manual"); - // blocks.push(block); + block?.hidePopover(); block?.showPopover(); domRef.setAttribute("popover", "manual"); domRef.showPopover(); }; -const closeNativePopover = () => { - const allPopupsIndex = AllOpenedPopupsRegistry.openedRegistry.findIndex(element => { - const instance = element.instance as { _sInitialFocusId?: string }; - return instance._sInitialFocusId === popovers[popovers.length - 1].id; - }); - if (AllOpenedPopupsRegistry.openedRegistry[allPopupsIndex - 1]?.type === "WebComponent") { - popovers.pop(); - if (popovers.length > 0 && blocks.length > 0) { - popovers[popovers.length - 1].hidePopover(); - blocks[blocks.length - 1].hidePopover(); - document.getElementById("sap-ui-blocklayer-popup")?.hidePopover(); - - (AllOpenedPopupsRegistry.openedRegistry[allPopupsIndex - 1].instance as HTMLElement).addEventListener("ui5-close", () => { - if ((AllOpenedPopupsRegistry.openedRegistry[allPopupsIndex - 2]?.type === "OpenUI5")) { - popovers[1].hidePopover(); - const lastBlock = blocks[blocks.length - 1]; - const lastPopover = popovers[popovers.length - 1]; - - arrangeBlocksAndPopovers(lastBlock as HTMLElement, lastPopover as HTMLElement); - } - }); - return; - } - } - - popovers.pop(); - if (popovers.length > 0 && blocks.length > 0) { - const lastBlock = blocks[blocks.length - 1]; - const lastPopover = popovers[popovers.length - 1]; - arrangeBlocksAndPopovers(lastBlock as HTMLElement, lastPopover as HTMLElement); - } - if (popovers.length === 0 && blocks.length > 0) { - blocks[blocks.length - 1]?.hidePopover(); +const closeNativePopover = (domRef: HTMLElement) => { + if (domRef.hasAttribute("popover")) { + domRef.hidePopover(); + domRef.removeAttribute("popover"); } -}; -const arrangeBlocksAndPopovers = (block: HTMLElement, popover: HTMLElement) => { - block?.hidePopover(); - popover.hidePopover(); - block?.showPopover(); - - const ui5block = document.getElementById("sap-ui-blocklayer-popup"); - if (ui5block) { - ui5block.style.visibility = "hidden"; - } - if (popover.isConnected) { - popover?.showPopover(); - } else if (popovers.length === 1 && AllOpenedPopupsRegistry.openedRegistry[0]?.type === "WebComponent") { - blocks.forEach(b => { b.hidePopover(); }); + const lastPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 1]; + if (lastPopup.type === "OpenUI5") { + const block = document.getElementById("sap-ui-blocklayer-popup"); + if (block && block.hasAttribute("popover")) { + block.hidePopover(); + } } }; @@ -202,7 +159,7 @@ const patchClosed = (Popup: OpenUI5Popup) => { const domRef = element instanceof HTMLElement ? element : element?.getDomRef(); _origClosed.apply(this, args); // only then call _close if (domRef) { - closeNativePopover(); // unset the popover attribute and close the native popover, but only if still in DOM + closeNativePopover(domRef); // unset the popover attribute and close the native popover, but only if still in DOM } removeOpenedPopup(this); From f7fd078d2bf5b4793216fefd45c0ca26fc4fdbbe Mon Sep 17 00:00:00 2001 From: Konstantin Kondov Date: Wed, 19 Nov 2025 15:19:32 +0200 Subject: [PATCH 04/10] fix(ui5-popover): render block layers in the correct order Updates samples and tests --- packages/base/src/features/patchPopup.ts | 27 ++-- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 118 ++++++++++++++++++ .../test/pages/DialogAndOpenUI5Dialog.html | 56 +++++++-- 3 files changed, 179 insertions(+), 22 deletions(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index 53171228e1f6..5bc6a8d01852 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -14,6 +14,7 @@ type OpenUI5Popup = { 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 } }; @@ -39,14 +40,17 @@ const addOpenedPopup = (popupInfo: PopupInfo) => { const removeOpenedPopup = (popup: object) => { if (isNativePopoverOpen()) { const prevPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 2]; - if (prevPopup && prevPopup.type === "OpenUI5") { - const content = (prevPopup.instance as any).getContent().getDomRef() as HTMLElement; + if (prevPopup && prevPopup.type === "OpenUI5" && (prevPopup.instance as any).getModal()) { + const content = (prevPopup.instance as any).getContent()?.getDomRef() as HTMLElement; const block = document.getElementById("sap-ui-blocklayer-popup"); - content.hidePopover(); + content?.hidePopover(); - block?.showPopover(); - content.showPopover(); + if ((prevPopup.instance as any).getModal()) { + block?.showPopover(); + } + + content?.showPopover(); } } @@ -81,13 +85,14 @@ const hasWebComponentPopupAbove = (popup: object) => { return false; }; -const openNativePopover = (domRef: HTMLElement) => { +const openNativePopover = (domRef: HTMLElement, popup: object) => { const block = document.getElementById("sap-ui-blocklayer-popup"); - block?.setAttribute("popover", "manual"); - block?.hidePopover(); - block?.showPopover(); - + if ((popup as any).getModal() && block) { + block?.setAttribute("popover", "manual"); + block?.hidePopover(); + block?.showPopover(); + } domRef.setAttribute("popover", "manual"); domRef.showPopover(); }; @@ -140,7 +145,7 @@ const patchOpen = (Popup: OpenUI5Popup) => { if (element) { const domRef = element instanceof HTMLElement ? element : element?.getDomRef(); if (domRef) { - openNativePopover(domRef); + openNativePopover(domRef, this); } } } 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", () => { + + +
{ .should('be.focused'); } + function OpenWebCUI5DialogMixed() { + // Open UI5 Dialog + cy.get("#openUI5Button") + .should('be.visible') + .realClick(); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + // Open WebC Dialog from UI5 Dialog + cy.get("#openWebCDialog") + .should('be.visible') + .realClick(); + + cy.get("#webCDialog1") + .should('be.visible'); + + cy.get("#openUI5Dialog1") + .should('not.be.visible'); + + // Open UI5 Dialog from WebC Dialog + cy.get("#openUI5DialogFromWebC") + .should('be.visible') + .realClick(); + + cy.get("#webCDialog1") + .should('not.be.visible'); + + cy.get("#openUI5Dialog1") + .should('not.be.visible'); + + cy.get("#openUI5DialogWithButtons") + .should('be.visible'); + + // Open UI5 Dialog from UI5 Dialog + cy.get("#openUI5DialogFromUi5") + .should('be.visible') + .realClick(); + + cy.get("#openUI5DialogFinal") + .should('be.visible'); + + cy.get("#openUI5Dialog1") + .should('not.be.visible'); + + cy.get("#webCDialog1") + .should('not.be.visible'); + + cy.get("#openUI5DialogWithButtons") + .should('not.be.visible'); + + // Close all with Escape + cy.realPress("Escape"); + + cy.get("#openUI5DialogFinal") + .should('not.exist'); + + cy.get("#openUI5DialogWithButtons") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5DialogWithButtons") + .should('not.exist'); + + cy.get("#webCDialog1") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openWebCDialog") + .should('not.exist'); + } + it("Keyboard", () => { OpenWebCDialog(); OpenWebCDialogOpenOpenUI5Dialog(); @@ -617,6 +734,7 @@ describe("ui5 and web components integration", () => { OpenUI5DialogWebCDialog(); OpenUI5DialogWebCPopoverNoFocus(); OpenUI5DialogWebCSelect(); + OpenWebCUI5DialogMixed(); // Merge it after OpenUI5 Popup shadow dom focus fix is released // OpenUI5DialogWebCComboBox(); }); diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index fd6b251b9aa6..58fb5629124f 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -63,6 +63,12 @@ press: function () { document.getElementById("respPopoverNoInitialFocus").open = true; } + }), + new Button("openUI5DialogOpener", { + text: "Open UI5 Dialog", + press: function () { + openUI5Dialog2(); + } }) ], afterClose: function () { @@ -138,6 +144,9 @@ document.getElementById("someButton4").addEventListener("click", function () { document.getElementById("newDialog4").open = true; }); + document.getElementById("openUi5PopoverBlockLayer").addEventListener("click", function () { + openUI5Popover(this); + }); } function openUI5Dialog() { @@ -212,17 +221,41 @@ function openUI5Popover(opener) { sap.ui.require(["sap/m/Popover", "sap/m/Button"], (Popover, Button) => { new Popover("openUI5PopoverSecond", { - title: "OpenUI5 Popover", + title: "OpenUI5 Popover 1", content: [ - new Button("someButton", { + new Button({ + text: "Open ui5 Dialog", + press: function () { + openUI5Dialog3(); + } + }), + new Button({ + text: "Open WebC Dialog", + press: function () { + document.getElementById("newDialog4").open = true; + } + }), + new Button("popoverOpener", { text: "Open new OpenUI5 Popover", press: function (oEvent) { - new Popover({ + new Popover("openUI5PopoverThird", { title: "New OpenUI5 Popover", placement: "Bottom", content: [ new Button({ text: "Focus stop" + }), + new Button({ + text: "Open Ui5 Dialog", + press: function () { + openUI5Dialog3(); + } + }), + new Button({ + text: "Open WebC Dialog", + press: function () { + document.getElementById("newDialog4").open = true; + } }) ], initialFocus: "someButton", @@ -246,7 +279,7 @@
Open WebC Dialog
- +

Web Components: @@ -268,29 +301,30 @@ Open UI5 dialog Open UI5 Popover No Initial Focus
- + Some button 1 - + Open UI5 Dialog + Open UI5 Popover - - Some button 3 + + Open WebC Dialog - + Some button 3
+ header-text="This is a Web Component Responsive Popover"> Some button + header-text="This is a Web Component RP with no initial focus"> Some button From 63fe5d381d6fbbf7f5b5c2a3d4d97373e1b4ab1d Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Fri, 21 Nov 2025 16:13:22 +0200 Subject: [PATCH 05/10] chore: improve TypeScript structure --- packages/base/src/features/OpenUI5Support.ts | 4 +- packages/base/src/features/patchPopup.ts | 101 +++++++++++-------- 2 files changed, 60 insertions(+), 45 deletions(-) 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 5bc6a8d01852..de0d7e800beb 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -6,19 +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, - getModal: () => boolean - } + 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, @@ -26,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 @@ -38,21 +43,7 @@ const addOpenedPopup = (popupInfo: PopupInfo) => { }; const removeOpenedPopup = (popup: object) => { - if (isNativePopoverOpen()) { - const prevPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 2]; - if (prevPopup && prevPopup.type === "OpenUI5" && (prevPopup.instance as any).getModal()) { - const content = (prevPopup.instance as any).getContent()?.getDomRef() as HTMLElement; - const block = document.getElementById("sap-ui-blocklayer-popup"); - - content?.hidePopover(); - - if ((prevPopup.instance as any).getModal()) { - block?.showPopover(); - } - - content?.showPopover(); - } - } + arrangeNativePopovers(); const index = AllOpenedPopupsRegistry.openedRegistry.findIndex(el => el.instance === popup); if (index > -1) { @@ -85,18 +76,43 @@ const hasWebComponentPopupAbove = (popup: object) => { return false; }; -const openNativePopover = (domRef: HTMLElement, popup: object) => { - const block = document.getElementById("sap-ui-blocklayer-popup"); +const openNativePopover = (domRef: HTMLElement, popup: OpenUI5Popup) => { + const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); - if ((popup as any).getModal() && block) { - block?.setAttribute("popover", "manual"); - block?.hidePopover(); - block?.showPopover(); + 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(); @@ -105,9 +121,9 @@ const closeNativePopover = (domRef: HTMLElement) => { const lastPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 1]; if (lastPopup.type === "OpenUI5") { - const block = document.getElementById("sap-ui-blocklayer-popup"); - if (block && block.hasAttribute("popover")) { - block.hidePopover(); + const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); + if (openUI5BlockLayer && openUI5BlockLayer.hasAttribute("popover")) { + openUI5BlockLayer.hidePopover(); } } }; @@ -123,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)) { @@ -134,13 +150,12 @@ 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(); @@ -157,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(); @@ -171,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)) { @@ -186,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 @@ -203,4 +218,4 @@ export { getTopmostPopup, }; -export type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo }; +export type { OpenUI5PopupClass, OpenUI5PopupBasedControlClass, PopupInfo }; From b214070a1176ddaafd076609b9813daabfbc56d4 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 24 Nov 2025 14:20:12 +0200 Subject: [PATCH 06/10] chore: improve TypeScript structure, fix some issues --- packages/base/src/features/OpenUI5Support.ts | 8 +-- packages/base/src/features/patchPopup.ts | 60 ++++++++++--------- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 3 +- .../src/popup-utils/OpenedPopupsRegistry.ts | 2 +- .../test/pages/DialogAndOpenUI5Dialog.html | 2 +- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/packages/base/src/features/OpenUI5Support.ts b/packages/base/src/features/OpenUI5Support.ts index a78d019f5bd0..8bf7827e5082 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 { OpenUI5PopupClass, OpenUI5PopupBasedControlClass, 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"; @@ -99,7 +99,7 @@ class OpenUI5Support { OpenUI5Support.initPromise = new Promise(resolve => { window.sap.ui.require(["sap/ui/core/Core"], async (Core: OpenUI5Core) => { const callback = () => { - let deps: Array = ["sap/ui/core/Popup", "sap/m/Dialog", "sap/m/Popover", "sap/ui/core/Patcher", "sap/ui/core/LocaleData"]; + let deps: Array = ["sap/ui/core/Popup", "sap/m/Dialog", "sap/ui/core/Patcher", "sap/ui/core/LocaleData"]; if (OpenUI5Support.isAtLeastVersion116()) { // for versions since 1.116.0 and onward, use the modular core deps = [ ...deps, @@ -110,9 +110,9 @@ class OpenUI5Support { "sap/ui/core/date/CalendarUtils", ]; } - window.sap.ui.require(deps, (Popup: OpenUI5PopupClass, Dialog: OpenUI5PopupBasedControlClass, Popover: OpenUI5PopupBasedControlClass, Patcher: OpenUI5Patcher) => { + window.sap.ui.require(deps, (Popup: OpenUI5PopupClass, Dialog: OpenUI5DialogClass, Patcher: OpenUI5Patcher) => { patchPatcher(Patcher); - patchPopup(Popup, Dialog, Popover); + patchPopup(Popup, Dialog); resolve(); }); }; diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index de0d7e800beb..a084407911c7 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -20,7 +20,7 @@ type OpenUI5PopupClass = { prototype: OpenUI5Popup }; -type OpenUI5PopupBasedControlClass = { +type OpenUI5DialogClass = { prototype: { onsapescape: (...args: any[]) => void, oPopup: OpenUI5Popup, @@ -43,9 +43,12 @@ const addOpenedPopup = (popupInfo: PopupInfo) => { }; const removeOpenedPopup = (popup: object) => { - arrangeNativePopovers(); - const index = AllOpenedPopupsRegistry.openedRegistry.findIndex(el => el.instance === popup); + + if (index === AllOpenedPopupsRegistry.openedRegistry.length - 1) { + fixOpenUI5PopupBelow(); + } + if (index > -1) { AllOpenedPopupsRegistry.openedRegistry.splice(index, 1); } @@ -76,7 +79,7 @@ const hasWebComponentPopupAbove = (popup: object) => { return false; }; -const openNativePopover = (domRef: HTMLElement, popup: OpenUI5Popup) => { +const enableNativePopoverForOpenUI5 = (domRef: HTMLElement, popup: OpenUI5Popup) => { const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); if (popup.getModal() && openUI5BlockLayer) { @@ -89,7 +92,22 @@ const openNativePopover = (domRef: HTMLElement, popup: OpenUI5Popup) => { domRef.showPopover(); }; -const arrangeNativePopovers = () => { +const disableNativePopoverForOpenUI5 = (domRef: HTMLElement) => { + if (domRef.hasAttribute("popover")) { + domRef.hidePopover(); + domRef.removeAttribute("popover"); + } + + const lastPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 1]; + if (lastPopup.type === "OpenUI5" && lastPopup.instance.getModal()) { + const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); + if (openUI5BlockLayer && openUI5BlockLayer.hasAttribute("popover")) { + openUI5BlockLayer.hidePopover(); + } + } +}; + +const fixOpenUI5PopupBelow = () => { if (!isNativePopoverOpen()) { return; } @@ -113,21 +131,6 @@ const arrangeNativePopovers = () => { 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 => { if (root.querySelector(":popover-open")) { return true; @@ -139,9 +142,9 @@ const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => }); }; -const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControlClass) => { - 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; } @@ -160,7 +163,7 @@ const patchOpen = (Popup: OpenUI5PopupClass) => { if (element) { const domRef = element instanceof HTMLElement ? element : element?.getDomRef(); if (domRef) { - openNativePopover(domRef, this); + enableNativePopoverForOpenUI5(domRef, this); } } } @@ -179,7 +182,7 @@ const patchClosed = (Popup: OpenUI5PopupClass) => { 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 + disableNativePopoverForOpenUI5(domRef); // unset the popover attribute and close the native popover, but only if still in DOM } removeOpenedPopup(this); @@ -201,14 +204,13 @@ const createGlobalStyles = () => { document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet]; }; -const patchPopup = (Popup: OpenUI5PopupClass, Dialog: OpenUI5PopupBasedControlClass, Popover: OpenUI5PopupBasedControlClass) => { +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 - patchPopupBasedControl(Popover); // Popover.prototype.onsapescape + patchDialog(Dialog); // Dialog.prototype.onsapescape }; export { @@ -218,4 +220,4 @@ export { getTopmostPopup, }; -export type { OpenUI5PopupClass, OpenUI5PopupBasedControlClass, PopupInfo }; +export type { OpenUI5PopupClass, OpenUI5DialogClass, PopupInfo }; diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index fb5355493235..bc0e095c7ef5 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -735,7 +735,6 @@ describe("ui5 and web components integration", () => { OpenUI5DialogWebCPopoverNoFocus(); OpenUI5DialogWebCSelect(); OpenWebCUI5DialogMixed(); - // Merge it after OpenUI5 Popup shadow dom focus fix is released - // OpenUI5DialogWebCComboBox(); + OpenUI5DialogWebCComboBox(); }); }); \ No newline at end of file diff --git a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts index df330fdb59ea..7d6b4798cb0e 100644 --- a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts +++ b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts @@ -72,7 +72,7 @@ const _keydownListener = (event: KeyboardEvent) => { return; } - event.stopPropagation(); + event.stopImmediatePropagation(); topmostPopup.closePopup(true); } }; diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 58fb5629124f..f13396d95e35 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -235,7 +235,7 @@ document.getElementById("newDialog4").open = true; } }), - new Button("popoverOpener", { + new Button({ text: "Open new OpenUI5 Popover", press: function (oEvent) { new Popover("openUI5PopoverThird", { From d2d83e656ff57f8ce3f130e28e75a47349227fad Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Fri, 28 Nov 2025 16:05:40 +0200 Subject: [PATCH 07/10] chore: improve code --- packages/base/src/features/patchPopup.ts | 62 +++++++++++++----------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index a084407911c7..83f2801ff0b5 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -6,6 +6,7 @@ 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 = { open: (...args: any[]) => void, _closed: (...args: any[]) => void, @@ -15,7 +16,6 @@ type OpenUI5Popup = { 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 }; @@ -79,7 +79,23 @@ const hasWebComponentPopupAbove = (popup: object) => { return false; }; -const enableNativePopoverForOpenUI5 = (domRef: HTMLElement, popup: OpenUI5Popup) => { +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) { @@ -92,14 +108,19 @@ const enableNativePopoverForOpenUI5 = (domRef: HTMLElement, popup: OpenUI5Popup) domRef.showPopover(); }; -const disableNativePopoverForOpenUI5 = (domRef: HTMLElement) => { +const closeNativePopoverForOpenUI5 = (popup: OpenUI5Popup) => { + const domRef = getPopupContentElement(popup); + + if (!domRef) { + return; + } + if (domRef.hasAttribute("popover")) { domRef.hidePopover(); domRef.removeAttribute("popover"); } - const lastPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 1]; - if (lastPopup.type === "OpenUI5" && lastPopup.instance.getModal()) { + if (popup.getModal()) { const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); if (openUI5BlockLayer && openUI5BlockLayer.hasAttribute("popover")) { openUI5BlockLayer.hidePopover(); @@ -113,20 +134,17 @@ const fixOpenUI5PopupBelow = () => { } const prevPopup = AllOpenedPopupsRegistry.openedRegistry[AllOpenedPopupsRegistry.openedRegistry.length - 2]; - if (!prevPopup || prevPopup.type !== "OpenUI5" || !prevPopup.instance.getModal()) { + if (!prevPopup + || prevPopup.type !== "OpenUI5" + || !prevPopup.instance.getModal()) { return; } - const prevPopupContent = prevPopup.instance.getContent()!; - const content = prevPopupContent instanceof HTMLElement ? prevPopupContent : prevPopupContent?.getDomRef(); - + const content = getPopupContentElement(prevPopup.instance); const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); content?.hidePopover(); - - if (prevPopup.instance.getModal()) { - openUI5BlockLayer?.showPopover(); - } + openUI5BlockLayer?.showPopover(); content?.showPopover(); }; @@ -157,16 +175,7 @@ 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 openingInitiated = ["OPENING", "OPEN"].includes(this.getOpenState()); - if (openingInitiated && isNativePopoverOpen()) { - const element = this.getContent(); - if (element) { - const domRef = element instanceof HTMLElement ? element : element?.getDomRef(); - if (domRef) { - enableNativePopoverForOpenUI5(domRef, this); - } - } - } + openNativePopoverForOpenUI5(this); addOpenedPopup({ type: "OpenUI5", @@ -178,13 +187,8 @@ const patchOpen = (Popup: OpenUI5PopupClass) => { 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) { - disableNativePopoverForOpenUI5(domRef); // unset the popover attribute and close the native popover, but only if still in DOM - } - + closeNativePopoverForOpenUI5(this); removeOpenedPopup(this); }; }; From dc01f293e3429f3a931ad48a1f22c20123ae925c Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 1 Dec 2025 10:51:58 +0200 Subject: [PATCH 08/10] chore: address code comments --- packages/base/src/features/patchPopup.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index 83f2801ff0b5..85de776368bc 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -46,7 +46,7 @@ const removeOpenedPopup = (popup: object) => { const index = AllOpenedPopupsRegistry.openedRegistry.findIndex(el => el.instance === popup); if (index === AllOpenedPopupsRegistry.openedRegistry.length - 1) { - fixOpenUI5PopupBelow(); + fixTopmostOpenUI5Popup(); } if (index > -1) { @@ -120,6 +120,15 @@ const closeNativePopoverForOpenUI5 = (popup: OpenUI5Popup) => { domRef.removeAttribute("popover"); } + if (getTopmostPopup() !== popup) { + return; + } + + // The OpenUI5 block layer is only one for all modal OpenUI5 popups, + // and it is displayed above all opened pupups - OpenUI5 and Web Components, + // as a result, we need to hide this block layer. + // If the underlying popup is a Web Component - it is displayed like a native popover, and we don't need to do anything + // If the underlying popup is an OpenUI5 popup, it will be fixed in fixTopmostOpenUI5Popup method. if (popup.getModal()) { const openUI5BlockLayer = document.getElementById("sap-ui-blocklayer-popup"); if (openUI5BlockLayer && openUI5BlockLayer.hasAttribute("popover")) { @@ -128,7 +137,7 @@ const closeNativePopoverForOpenUI5 = (popup: OpenUI5Popup) => { } }; -const fixOpenUI5PopupBelow = () => { +const fixTopmostOpenUI5Popup = () => { if (!isNativePopoverOpen()) { return; } From 75cc691112b1d77993678af819e127b0f7f35e04 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 1 Dec 2025 14:14:23 +0200 Subject: [PATCH 09/10] chore: address code comments --- packages/base/src/features/patchPopup.ts | 2 +- .../test/pages/DialogAndOpenUI5Dialog.html | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index 85de776368bc..cac58ca5dff1 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -196,8 +196,8 @@ const patchOpen = (Popup: OpenUI5PopupClass) => { const patchClosed = (Popup: OpenUI5PopupClass) => { const _origClosed = Popup.prototype._closed; Popup.prototype._closed = function _closed(...args: any[]) { - _origClosed.apply(this, args); // only then call _close closeNativePopoverForOpenUI5(this); + _origClosed.apply(this, args); // only then call _close removeOpenedPopup(this); }; }; diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index f13396d95e35..f241454d1577 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -264,6 +264,37 @@ } }).openBy(oEvent.getSource()); } + }), + new Button({ + text: "Open new OpenUI5 Modal Popover", + press: function (oEvent) { + new Popover({ + title: "New OpenUI5 Modal Popover", + modal: true, + placement: "Bottom", + content: [ + new Button({ + text: "Focus stop" + }), + new Button({ + text: "Open Ui5 Dialog", + press: function () { + openUI5Dialog3(); + } + }), + new Button({ + text: "Open WebC Dialog", + press: function () { + document.getElementById("newDialog4").open = true; + } + }) + ], + initialFocus: "someButton", + afterClose: function () { + this.destroy(); + } + }).openBy(oEvent.getSource()); + } }) ], initialFocus: "popoverButtonNoFocus", From 1204bbe4baeb49c146a4af2023bac6d3f56aff47 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 1 Dec 2025 15:33:02 +0200 Subject: [PATCH 10/10] chore: fix tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index bc0e095c7ef5..b5e62f6e546c 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -538,7 +538,7 @@ describe("ui5 and web components integration", () => { } function OpenUI5DialogWebCPopoverNoFocus() { - cy.get("#openUI5Button") + cy.get("#openUI5Button", { timeout: 10000 }) .should('be.visible') .realClick(); @@ -556,6 +556,9 @@ describe("ui5 and web components integration", () => { cy.get("#respPopoverNoInitialFocus") .should('not.be.visible'); + cy.get("#openResPopoverNoInitialFocusButton") + .should('be.focused'); + cy.get("#openUI5Dialog1") .should('be.visible'); @@ -564,13 +567,13 @@ describe("ui5 and web components integration", () => { cy.get("#openUI5Dialog1") .should('not.be.visible'); - cy.get("#openResPopoverNoInitialFocusButton") + cy.get("#openUI5Button") .should('be.focused'); } function OpenUI5DialogWebCSelect() { - cy.get("#openUI5Button") - .should('be.focused') + cy.get("#openUI5Button", { timeout: 10000 }) + .should('be.visible') .realClick(); cy.get("#openUI5Dialog1") @@ -644,9 +647,11 @@ describe("ui5 and web components integration", () => { } function OpenWebCUI5DialogMixed() { + cy.get("#openUI5Button", { timeout: 10000 }) + .should('be.visible'); + // Open UI5 Dialog cy.get("#openUI5Button") - .should('be.visible') .realClick(); cy.get("#openUI5Dialog1")