From 410fc45eafa0f55f11eb7e69005b529110d19c52 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Thu, 4 Sep 2025 15:52:07 +0300 Subject: [PATCH 01/24] chore: improve test page --- .../test/pages/DialogAndOpenUI5Dialog.html | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index de3c6d473964..d2cdc1ead730 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -14,20 +14,29 @@ // delete Document.prototype.adoptedStyleSheets - @@ -123,6 +169,7 @@
Open UI5 dialog + Open UI5 Popover No Initial Focus
Some button @@ -133,5 +180,11 @@ header-text="This is an WebC Responsive Popover"> Some button + + Some button + From c5ca2a0092285f8a6bd63570dc655c93e0de92e9 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 9 Sep 2025 16:35:52 +0300 Subject: [PATCH 02/24] fix(OpenUI5Support): fix closing popups with escape key --- .../src/util/openui5support/eventMarking.ts | 20 +++++++++++++++++++ .../src/popup-utils/OpenedPopupsRegistry.ts | 3 ++- .../test/pages/DialogAndOpenUI5Dialog.html | 19 ++++++++++++++++-- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 packages/base/src/util/openui5support/eventMarking.ts diff --git a/packages/base/src/util/openui5support/eventMarking.ts b/packages/base/src/util/openui5support/eventMarking.ts new file mode 100644 index 000000000000..e3419cd5b235 --- /dev/null +++ b/packages/base/src/util/openui5support/eventMarking.ts @@ -0,0 +1,20 @@ +const defaultKey = "handledByControl"; + +const setMark = (event: any, key: string, value: any = true) => { + key = key || defaultKey; + event[`_sapui_${key}`] = value; +}; + +const isMarked = (event: any, key?: string) => { + return !!getMark(event, key); +}; + +const getMark = (event: any, key: string = defaultKey): any => { + return event[`_sapui_${key}`]; +}; + +export { + isMarked, + setMark, + getMark, +}; diff --git a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts index f280d47015af..026890e59847 100644 --- a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts +++ b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts @@ -1,6 +1,7 @@ import getSharedResource from "@ui5/webcomponents-base/dist/getSharedResource.js"; import { isEscape } from "@ui5/webcomponents-base/dist/Keys.js"; import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js"; +import { isMarked } from "@ui5/webcomponents-base/dist/util/openui5support/eventMarking.js"; import type OpenUI5Support from "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; import type Popup from "../Popup.js"; import type { PopupInfo } from "@ui5/webcomponents-base/dist/features/patchPopup.js"; @@ -64,7 +65,7 @@ const _keydownListener = (event: KeyboardEvent) => { return; } - if (isEscape(event)) { + if (isEscape(event) && !isMarked(event)) { const topmostPopup = OpenedPopupsRegistry.openedRegistry[OpenedPopupsRegistry.openedRegistry.length - 1].instance; if (openUI5Support && topmostPopup !== openUI5Support.getTopmostPopup()) { diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index d2cdc1ead730..ca7810ad978f 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -90,8 +90,23 @@ new Popover({ title: "OpenUI5 Popover", content: [ - new Button({ - text: "Some button" + new Button("someButton", { + text: "Open new OpenUI5 Popover", + press: function (oEvent) { + new Popover({ + title: "New OpenUI5 Popover", + placement: "Bottom", + content: [ + new Button({ + text: "Focus stop" + }) + ], + initialFocus: "someButton", + afterClose: function () { + this.destroy(); + } + }).openBy(oEvent.getSource()); + } }) ], initialFocus: "popoverButtonNoFocus", From 24ca8a688dc8baf9639b72fc4850acf5cc39e48a Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Wed, 10 Sep 2025 16:36:48 +0300 Subject: [PATCH 03/24] fix: handle more cases --- packages/main/src/ComboBox.ts | 5 +++- .../src/popup-utils/OpenedPopupsRegistry.ts | 2 +- .../test/pages/DialogAndOpenUI5Dialog.html | 23 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index 9c0d0ce0ddcf..633ae4bebafb 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -960,7 +960,10 @@ class ComboBox extends UI5Element implements IFormInputElement { if (isEscape(e)) { this.focused = true; - this.value = !this.open ? this._lastValue : this.value; + if (!this.open && this.value !== this._lastValue) { + this.value = this._lastValue; + e.stopPropagation(); + } } if ((isTabNext(e) || isTabPrevious(e)) && this.open) { diff --git a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts index 026890e59847..8c1301fc681b 100644 --- a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts +++ b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts @@ -78,7 +78,7 @@ const _keydownListener = (event: KeyboardEvent) => { }; const attachGlobalListener = () => { - document.addEventListener("keydown", _keydownListener); + document.addEventListener("keydown", _keydownListener, true); }; const detachGlobalListener = () => { diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index ca7810ad978f..bc612e136422 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -183,6 +183,23 @@
+
+ Web Components: +
+ + Option 1 + Option 2 + Option 3 + Option 4 + Option 5 + + + + + + +
+
Open UI5 dialog Open UI5 Popover No Initial Focus
@@ -201,5 +218,11 @@ header-text="This is an WebC RP with no initial focus"> Some button + + + + + + From cb6f02ef29005bc82f3dbeb58f4456b4769d00cd Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Fri, 12 Sep 2025 14:42:08 +0300 Subject: [PATCH 04/24] chore: patch OpenUI5 Element class --- packages/base/src/features/OpenUI5Support.ts | 8 +-- packages/base/src/features/patchPopup.ts | 52 +++++++++++++++---- .../src/popup-utils/OpenedPopupsRegistry.ts | 2 +- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/packages/base/src/features/OpenUI5Support.ts b/packages/base/src/features/OpenUI5Support.ts index cc4d49be4690..21f1ad920503 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, PopupInfo } from "./patchPopup.js"; +import type { OpenUI5Element, OpenUI5Popup, 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/ui/core/Patcher", "sap/ui/core/LocaleData"]; + let deps: Array = ["sap/ui/core/Element", "sap/ui/core/Popup", "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: OpenUI5Popup, Patcher: OpenUI5Patcher) => { + window.sap.ui.require(deps, (Element: OpenUI5Element, Popup: OpenUI5Popup, Patcher: OpenUI5Patcher) => { patchPatcher(Patcher); - patchPopup(Popup); + patchPopup(Element, Popup); resolve(); }); }; diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index a50cf88d4e73..d40633f18115 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -16,11 +16,28 @@ type OpenUI5Popup = { } }; +type OpenUI5Element = { + prototype: { + _handleEvent: (e: Event) => void, + isA: (type: string) => boolean, + oPopup: OpenUI5Popup, + } +}; + type PopupInfo = { type: "OpenUI5" | "WebComponent"; instance: object; }; +const openUI5PopupTypes = [ + "sap.m.Popover", + "sap.m.Dialog", +]; + +const openUI5Events = [ + "sapescape", +]; + // contains all OpenUI5 and Web Component popups that are currently opened const AllOpenedPopupsRegistry = getSharedResource<{ openedRegistry: Array }>("AllOpenedPopupsRegistry", { openedRegistry: [] }); @@ -40,17 +57,16 @@ const getTopmostPopup = () => { }; /** - * Original OpenUI5 popup focus event is triggered only - * if there are no Web Component popups opened on top of it. + * Checks if Web Component popups opened on top of it. * - * @param {object} popup - The popup instance to check. - * @returns {boolean} True if the focus event should be triggered, false otherwise. + * @param {object} popup The popup instance to check against. + * @returns {boolean} `true` if a Web Component popup is stacked above; otherwise `false`. */ -const shouldCallOpenUI5FocusEvent = (popup: object) => { +const hasWebComponentPopupAbove = (popup: object) => { for (let i = AllOpenedPopupsRegistry.openedRegistry.length - 1; i >= 0; i--) { const popupInfo = AllOpenedPopupsRegistry.openedRegistry[i]; - if (popupInfo.type !== "OpenUI5") { - return false; + if (popupInfo.type === "WebComponent") { + return true; } if (popupInfo.instance === popup) { @@ -58,7 +74,7 @@ const shouldCallOpenUI5FocusEvent = (popup: object) => { } } - return true; + return false; }; const openNativePopover = (domRef: HTMLElement) => { @@ -84,6 +100,19 @@ const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => }); }; +const patchElementHandleEvent = (Element: OpenUI5Element) => { + const origHandleEvent = Element.prototype._handleEvent; + Element.prototype._handleEvent = function _handleEvent(e: Event) { + if (openUI5Events.includes(e.type) + && openUI5PopupTypes.some(PopupType => this.isA(PopupType)) + && hasWebComponentPopupAbove(this.oPopup)) { + return; + } + + origHandleEvent.call(this, e); + }; +}; + const patchOpen = (Popup: OpenUI5Popup) => { const origOpen = Popup.prototype.open; Popup.prototype.open = function open(...args: any[]) { @@ -124,7 +153,7 @@ const patchClosed = (Popup: OpenUI5Popup) => { const patchFocusEvent = (Popup: OpenUI5Popup) => { const origFocusEvent = Popup.prototype.onFocusEvent; Popup.prototype.onFocusEvent = function onFocusEvent(e: FocusEvent) { - if (shouldCallOpenUI5FocusEvent(this)) { + if (!hasWebComponentPopupAbove(this)) { origFocusEvent.call(this, e); } }; @@ -136,7 +165,8 @@ const createGlobalStyles = () => { document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet]; }; -const patchPopup = (Popup: OpenUI5Popup) => { +const patchPopup = (Element: OpenUI5Element, Popup: OpenUI5Popup) => { + patchElementHandleEvent(Element); // Element.prototype._handleEvent 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) @@ -150,4 +180,4 @@ export { getTopmostPopup, }; -export type { OpenUI5Popup, PopupInfo }; +export type { OpenUI5Element, OpenUI5Popup, PopupInfo }; diff --git a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts index 8c1301fc681b..026890e59847 100644 --- a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts +++ b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts @@ -78,7 +78,7 @@ const _keydownListener = (event: KeyboardEvent) => { }; const attachGlobalListener = () => { - document.addEventListener("keydown", _keydownListener, true); + document.addEventListener("keydown", _keydownListener); }; const detachGlobalListener = () => { From d6e0b99c403f76490f04eda4b8d45d93a9a7cdbb Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Mon, 15 Sep 2025 17:00:09 +0300 Subject: [PATCH 05/24] chore: add tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 264 ++++++++++++++++++ .../test/pages/DialogAndOpenUI5Dialog.html | 118 ++++---- 2 files changed, 322 insertions(+), 60 deletions(-) create mode 100644 packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx new file mode 100644 index 000000000000..53a1388a036d --- /dev/null +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -0,0 +1,264 @@ +import "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; +import Button from "@ui5/webcomponents/dist/Button.js"; +import Dialog from "@ui5/webcomponents/dist/Dialog.js"; +import Select from "@ui5/webcomponents/dist/Select.js"; +import Option from "@ui5/webcomponents/dist/Option.js"; +import ComboBox from "@ui5/webcomponents/dist/ComboBox.js"; +import ComboBoxItem from "@ui5/webcomponents/dist/ComboBoxItem.js"; +import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js"; + + +function onOpenUI5InitMethod(win) { + (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Input"], (HTML, Button, Dialog, Input) => { + new Button("openUI5Button", { + text: "Open OpenUI5 Dialog", + press: function () { + new Dialog({ + title: "OpenUI5 Dialog", + content: [ + new HTML({ + content: ` + Option 1 + Option 2 + Option 3 + Option 4 + Option 5 + ` + }), + new Input(), + new Button({ + text: "Focus stop" + }), + new Button("openResPopoverButton", { + text: "Open WebC Responsive Popover", + press: function () { + (document.getElementById("respPopover") as any).open = true; + } + }), + new Button("openResPopoverNoInitialFocusButton", { + text: "Open WebC RP with NO Initial Focus", + press: function () { + (document.getElementById("respPopoverNoInitialFocus") as any).open = true; + } + }) + ], + afterClose: function () { + this.destroy(); + } + }).open(); + } + }).placeAt("content"); + }); + + document.getElementById("myButton").addEventListener("click", function() { + (document.getElementById("dialog1") as any).open = true; + }); + + (win as any).sap.ui.require(["sap/m/Select", + "sap/m/ComboBox", + "sap/m/Button", + "sap/ui/core/Item", + "sap/ui/core/ShortcutHintsMixin"], + (Select, + ComboBox, + Button, + Item, + ShortcutHintsMixin) => { + new Select({ + items: [ + new Item({ text: "Item 1" }), + new Item({ text: "Item 2" }), + new Item({ text: "Item 3" }) + ], + change: function (oEvent) { + console.error("Selected item:", oEvent.getParameter("selectedItem").getText()); + } + }).placeAt("dialog1content"); + + new ComboBox({ + items: [ + new Item({ text: "Item 1" }), + new Item({ text: "Item 2" }), + new Item({ text: "Item 3" }) + ] + }).placeAt("dialog1content"); + + const button = new Button({ + text: "OpenUI5 with Shortcut (Ctrl+S)", + press: function () { + openUI5Dialog(win); + } + }).placeAt("dialog1content"); + + + ShortcutHintsMixin.addConfig(button, { + event: "press", + position: "0 0", + addAccessibilityLabel: true, + message: "Save" + }, button); + }); + + document.getElementById("dialogButton").addEventListener("click", function () { + openUI5Dialog(win); + }); + + document.getElementById("popoverButtonNoFocus").addEventListener("click", function (event) { + openUI5Popover(win, event.target); + }); +} + +function openUI5Dialog(win) { + (win as any).sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { + new Dialog({ + title: "OpenUI5 Dialog", + content: [ + new Button({ + text: "Focus stop" + }), + new Button("openUI5DialogButton", { + text: "Open WebC Dialog", + press: function () { + (document.getElementById("newDialog1") as any).open = true; + } + }) + ], + afterClose: function () { + this.destroy(); + } + }).open(); + }); +} + +function openUI5Popover(win, opener) { + (win as any).sap.ui.require(["sap/m/Popover", "sap/m/Button"], (Popover, Button) => { + new Popover({ + title: "OpenUI5 Popover", + content: [ + new Button("someButton", { + text: "Open new OpenUI5 Popover", + press: function (oEvent) { + new Popover({ + title: "New OpenUI5 Popover", + placement: "Bottom", + content: [ + new Button({ + text: "Focus stop" + }) + ], + initialFocus: "someButton", + afterClose: function () { + this.destroy(); + } + }).openBy(oEvent.getSource()); + } + }) + ], + initialFocus: "popoverButtonNoFocus", + afterClose: function () { + this.destroy(); + } + }).openBy(opener); + }); +} + +describe("ui5 and web components integration", () => { + beforeEach(() => { + // mount the components + cy.mount( + <> +
+ +
+ +
+
+ Web Components: +
+ + + + + + +
+
+ + +
+ + + +
+ + + + + + + + + + + + + + ); + + // inject ui5 configuration + cy.document().then((doc) => { + const configScript = doc.createElement('script'); + configScript.setAttribute('data-ui5-config', ''); + configScript.type = 'application/json'; + configScript.textContent = JSON.stringify({ + language: 'EN' + }); + doc.head.appendChild(configScript); + }); + + // define initialization function before loading ui5 + cy.window().then((win) => { + (win as any).onOpenUI5Init = function () { + onOpenUI5InitMethod(win); + }; + }); + + // add ui5 bootstrap + cy.document().then((doc) => { + const ui5Script = doc.createElement('script'); + ui5Script.src = 'https://sapui5untested.int.sap.eu2.hana.ondemand.com/resources/sap-ui-core.js'; + ui5Script.id = 'sap-ui-bootstrap'; + ui5Script.setAttribute('data-sap-ui-libs', 'sap.m'); + ui5Script.setAttribute('data-sap-ui-oninit', 'onOpenUI5Init'); + doc.head.appendChild(ui5Script); + }); + }); + + it("User interaction", () => { + // act: open WebC Dialog + cy.get('#myButton') + .should('be.visible') + .realClick(); + + cy.get("#dialog1").ui5DialogOpened(); + + cy.realPress("Escape"); + + cy.get('#dialog1').should('not.be.visible'); + }); +}); \ No newline at end of file diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index bc612e136422..2019256ac5a2 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -61,6 +61,63 @@ } }).placeAt("content"); }); + + document.getElementById("myButton").addEventListener("click", function() { + document.getElementById("dialog1").open = true; + }); + + sap.ui.require(["sap/m/Select", + "sap/m/ComboBox", + "sap/m/Button", + "sap/ui/core/Item", + "sap/ui/core/ShortcutHintsMixin"], + (Select, + ComboBox, + Button, + Item, + ShortcutHintsMixin) => { + new Select({ + items: [ + new Item({ text: "Item 1" }), + new Item({ text: "Item 2" }), + new Item({ text: "Item 3" }) + ], + change: function (oEvent) { + console.error("Selected item:", oEvent.getParameter("selectedItem").getText()); + } + }).placeAt("dialog1content"); + + new ComboBox({ + items: [ + new Item({ text: "Item 1" }), + new Item({ text: "Item 2" }), + new Item({ text: "Item 3" }) + ] + }).placeAt("dialog1content"); + + const button = new Button({ + text: "OpenUI5 with Shortcut (Ctrl+S)", + press: function () { + openUI5Dialog(); + } + }).placeAt("dialog1content"); + + + ShortcutHintsMixin.addConfig(button, { + event: "press", + position: "0 0", + addAccessibilityLabel: true, + message: "Save" + }, button); + }); + + document.getElementById("dialogButton").addEventListener("click", function () { + openUI5Dialog(); + }); + + document.getElementById("popoverButtonNoFocus").addEventListener("click", function (event) { + openUI5Popover(event.target); + }); } function openUI5Dialog() { @@ -116,68 +173,9 @@ }).openBy(opener); }); } - - function init() { - document.getElementById("myButton").addEventListener("click", function() { - document.getElementById("dialog1").open = true; - }); - - sap.ui.require(["sap/m/Select", - "sap/m/ComboBox", - "sap/m/Button", - "sap/ui/core/Item", - "sap/ui/core/ShortcutHintsMixin"], - (Select, - ComboBox, - Button, - Item, - ShortcutHintsMixin) => { - new Select({ - items: [ - new Item({ text: "Item 1" }), - new Item({ text: "Item 2" }), - new Item({ text: "Item 3" }) - ], - change: function (oEvent) { - console.error("Selected item:", oEvent.getParameter("selectedItem").getText()); - } - }).placeAt("dialog1content"); - - new ComboBox({ - items: [ - new Item({ text: "Item 1" }), - new Item({ text: "Item 2" }), - new Item({ text: "Item 3" }) - ] - }).placeAt("dialog1content"); - - const button = new Button({ - text: "OpenUI5 with Shortcut (Ctrl+S)", - press: function () { - openUI5Dialog(); - } - }).placeAt("dialog1content"); - - - ShortcutHintsMixin.addConfig(button, { - event: "press", - position: "0 0", - addAccessibilityLabel: true, - message: "Save" - }, button); - }); - - document.getElementById("dialogButton").addEventListener("click", function () { - openUI5Dialog(); - }); - - document.getElementById("popoverButtonNoFocus").addEventListener("click", function (event) { - openUI5Popover(event.target); - }); - } - +
Open WebC Dialog
From 0154a4af92fb47983ed126947e72c87a8e3926c7 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 16 Sep 2025 10:39:46 +0300 Subject: [PATCH 06/24] chore: change patched controls --- packages/base/src/features/OpenUI5Support.ts | 8 ++--- packages/base/src/features/patchPopup.ts | 32 +++++++------------ .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 5 ++- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/packages/base/src/features/OpenUI5Support.ts b/packages/base/src/features/OpenUI5Support.ts index 21f1ad920503..721a4840b62a 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 { OpenUI5Element, OpenUI5Popup, PopupInfo } from "./patchPopup.js"; +import type { OpenUI5Popup, OpenUI5PopupControl, 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/Element", "sap/ui/core/Popup", "sap/ui/core/Patcher", "sap/ui/core/LocaleData"]; + let deps: Array = ["sap/ui/core/Popup", "sap/m/Dialog", "sap/m/Popover", "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, (Element: OpenUI5Element, Popup: OpenUI5Popup, Patcher: OpenUI5Patcher) => { + window.sap.ui.require(deps, (Popup: OpenUI5Popup, Dialog: OpenUI5PopupControl, Popover: OpenUI5PopupControl, Patcher: OpenUI5Patcher) => { patchPatcher(Patcher); - patchPopup(Element, Popup); + patchPopup(Popup, Dialog, Popover); resolve(); }); }; diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index d40633f18115..e6665599c784 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -16,9 +16,9 @@ type OpenUI5Popup = { } }; -type OpenUI5Element = { +type OpenUI5PopupControl = { prototype: { - _handleEvent: (e: Event) => void, + onsapescape: (e: Event) => void, isA: (type: string) => boolean, oPopup: OpenUI5Popup, } @@ -29,15 +29,6 @@ type PopupInfo = { instance: object; }; -const openUI5PopupTypes = [ - "sap.m.Popover", - "sap.m.Dialog", -]; - -const openUI5Events = [ - "sapescape", -]; - // contains all OpenUI5 and Web Component popups that are currently opened const AllOpenedPopupsRegistry = getSharedResource<{ openedRegistry: Array }>("AllOpenedPopupsRegistry", { openedRegistry: [] }); @@ -100,16 +91,14 @@ const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => }); }; -const patchElementHandleEvent = (Element: OpenUI5Element) => { - const origHandleEvent = Element.prototype._handleEvent; - Element.prototype._handleEvent = function _handleEvent(e: Event) { - if (openUI5Events.includes(e.type) - && openUI5PopupTypes.some(PopupType => this.isA(PopupType)) - && hasWebComponentPopupAbove(this.oPopup)) { +const patchPopupControl = (PopupControl: OpenUI5PopupControl) => { + const origOnsapescape = PopupControl.prototype.onsapescape; + PopupControl.prototype.onsapescape = function onsapescape(e: Event) { + if (hasWebComponentPopupAbove(this.oPopup)) { return; } - origHandleEvent.call(this, e); + origOnsapescape.call(this, e); }; }; @@ -165,12 +154,13 @@ const createGlobalStyles = () => { document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet]; }; -const patchPopup = (Element: OpenUI5Element, Popup: OpenUI5Popup) => { - patchElementHandleEvent(Element); // Element.prototype._handleEvent +const patchPopup = (Popup: OpenUI5Popup, Dialog: OpenUI5PopupControl, Popover: OpenUI5PopupControl) => { 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 + patchPopupControl(Dialog); // Dialog.prototype.onsapescape + patchPopupControl(Popover); // Popover.prototype.onsapescape }; export { @@ -180,4 +170,4 @@ export { getTopmostPopup, }; -export type { OpenUI5Element, OpenUI5Popup, PopupInfo }; +export type { OpenUI5Popup, OpenUI5PopupControl, PopupInfo }; diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 53a1388a036d..b1723743caeb 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -241,7 +241,7 @@ describe("ui5 and web components integration", () => { // add ui5 bootstrap cy.document().then((doc) => { const ui5Script = doc.createElement('script'); - ui5Script.src = 'https://sapui5untested.int.sap.eu2.hana.ondemand.com/resources/sap-ui-core.js'; + ui5Script.src = 'https://openui5.hana.ondemand.com/resources/sap-ui-core.js'; ui5Script.id = 'sap-ui-bootstrap'; ui5Script.setAttribute('data-sap-ui-libs', 'sap.m'); ui5Script.setAttribute('data-sap-ui-oninit', 'onOpenUI5Init'); @@ -249,8 +249,7 @@ describe("ui5 and web components integration", () => { }); }); - it("User interaction", () => { - // act: open WebC Dialog + it("Open WebC dialog", () => { cy.get('#myButton') .should('be.visible') .realClick(); From 318a60a3f9d41a031b3afb58cc931b03214f27c5 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 16 Sep 2025 13:25:02 +0300 Subject: [PATCH 07/24] chore: add more test --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 77 +++++++++++++++++-- .../test/pages/DialogAndOpenUI5Dialog.html | 4 +- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index b1723743caeb..7dc8345dcd85 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -1,4 +1,4 @@ -import "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; +import OpenUI5Support from "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; import Button from "@ui5/webcomponents/dist/Button.js"; import Dialog from "@ui5/webcomponents/dist/Dialog.js"; import Select from "@ui5/webcomponents/dist/Select.js"; @@ -9,6 +9,8 @@ import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js"; function onOpenUI5InitMethod(win) { + OpenUI5Support.init(); + (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Input"], (HTML, Button, Dialog, Input) => { new Button("openUI5Button", { text: "Open OpenUI5 Dialog", @@ -64,7 +66,7 @@ function onOpenUI5InitMethod(win) { Button, Item, ShortcutHintsMixin) => { - new Select({ + new Select("openUI5Select1", { items: [ new Item({ text: "Item 1" }), new Item({ text: "Item 2" }), @@ -75,7 +77,7 @@ function onOpenUI5InitMethod(win) { } }).placeAt("dialog1content"); - new ComboBox({ + new ComboBox("openUI5Combobox1", { items: [ new Item({ text: "Item 1" }), new Item({ text: "Item 2" }), @@ -249,7 +251,7 @@ describe("ui5 and web components integration", () => { }); }); - it("Open WebC dialog", () => { + function OpenWebCDialog() { cy.get('#myButton') .should('be.visible') .realClick(); @@ -258,6 +260,71 @@ describe("ui5 and web components integration", () => { cy.realPress("Escape"); - cy.get('#dialog1').should('not.be.visible'); + cy.get('#dialog1') + .should('not.be.visible'); + } + + function OpenWebCDialogOpenUI5Select() { + cy.get('#myButton') + .should('be.visible') + .realClick(); + + cy.get("#dialog1").ui5DialogOpened(); + + cy.get('#openUI5Select1') + .should('be.visible') + .realClick(); + + cy.get("#__popover0") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#__popover0") + .should('not.be.visible'); + + cy.realPress("Escape"); + + cy.get('#dialog1') + .should('not.be.visible'); + } + + function OpenWebCDialogOpenUI5ComboBox() { + cy.get('#myButton') + .should('be.visible') + .realClick(); + + cy.get("#dialog1").ui5DialogOpened(); + + cy.get('#openUI5Combobox1') + .should('be.visible') + .realClick() + .type("I"); + + cy.get("#openUI5Combobox1-popup") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5Combobox1-popup") + .should('not.be.visible'); + + cy.get("#dialog1").ui5DialogOpened(); + + cy.realPress("Escape"); + + // comobo box value is reset, dialog stays open + cy.get("#dialog1").ui5DialogOpened(); + + cy.realPress("Escape"); + + cy.get('#dialog1') + .should('not.be.visible'); + } + + it("Open WebC dialog", () => { + OpenWebCDialog(); + OpenWebCDialogOpenUI5Select(); + OpenWebCDialogOpenUI5ComboBox(); }); }); \ No newline at end of file diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 2019256ac5a2..05ee67c2619c 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -76,7 +76,7 @@ Button, Item, ShortcutHintsMixin) => { - new Select({ + new Select("openUI5Select1", { items: [ new Item({ text: "Item 1" }), new Item({ text: "Item 2" }), @@ -87,7 +87,7 @@ } }).placeAt("dialog1content"); - new ComboBox({ + new ComboBox("openUI5Combobox1", { items: [ new Item({ text: "Item 1" }), new Item({ text: "Item 2" }), From c9b1f362c637cb2fedb75dcb1983274b9103d7f0 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 16 Sep 2025 15:00:53 +0300 Subject: [PATCH 08/24] chore: remove unused code --- packages/base/src/features/patchPopup.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index e6665599c784..34c426988d9b 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -19,7 +19,6 @@ type OpenUI5Popup = { type OpenUI5PopupControl = { prototype: { onsapescape: (e: Event) => void, - isA: (type: string) => boolean, oPopup: OpenUI5Popup, } }; From 317470eac393abd042244b71b08c5e8a89c7292a Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 16 Sep 2025 15:14:46 +0300 Subject: [PATCH 09/24] chore: address copilot comments --- .../src/util/openui5support/eventMarking.ts | 19 +++---------------- packages/main/src/ComboBox.ts | 3 ++- .../src/popup-utils/OpenedPopupsRegistry.ts | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/packages/base/src/util/openui5support/eventMarking.ts b/packages/base/src/util/openui5support/eventMarking.ts index e3419cd5b235..11551b79fc01 100644 --- a/packages/base/src/util/openui5support/eventMarking.ts +++ b/packages/base/src/util/openui5support/eventMarking.ts @@ -1,20 +1,7 @@ const defaultKey = "handledByControl"; -const setMark = (event: any, key: string, value: any = true) => { - key = key || defaultKey; - event[`_sapui_${key}`] = value; +const isMarked = (event: any, key: string = defaultKey) => { + return !!event[`_sapui_${key}`]; }; -const isMarked = (event: any, key?: string) => { - return !!getMark(event, key); -}; - -const getMark = (event: any, key: string = defaultKey): any => { - return event[`_sapui_${key}`]; -}; - -export { - isMarked, - setMark, - getMark, -}; +export default isMarked; diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index a1706ba47b38..52fe87ef379f 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -976,7 +976,8 @@ class ComboBox extends UI5Element implements IFormInputElement { if (isEscape(e)) { this.focused = true; - if (!this.open && this.value !== this._lastValue) { + const shouldResetValueAndStopPropagation = !this.open && this.value !== this._lastValue; + if (shouldResetValueAndStopPropagation) { this.value = this._lastValue; e.stopPropagation(); } diff --git a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts index 026890e59847..144dff94fc58 100644 --- a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts +++ b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts @@ -1,7 +1,7 @@ import getSharedResource from "@ui5/webcomponents-base/dist/getSharedResource.js"; import { isEscape } from "@ui5/webcomponents-base/dist/Keys.js"; import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js"; -import { isMarked } from "@ui5/webcomponents-base/dist/util/openui5support/eventMarking.js"; +import isMarked from "@ui5/webcomponents-base/dist/util/openui5support/eventMarking.js"; import type OpenUI5Support from "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; import type Popup from "../Popup.js"; import type { PopupInfo } from "@ui5/webcomponents-base/dist/features/patchPopup.js"; From ce11d6be1f3ad2268d1c6ad474720552d147a332 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 16 Sep 2025 15:18:05 +0300 Subject: [PATCH 10/24] chore: fix tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 7dc8345dcd85..0cada71f0777 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -252,9 +252,8 @@ describe("ui5 and web components integration", () => { }); function OpenWebCDialog() { - cy.get('#myButton') - .should('be.visible') - .realClick(); + cy.get('#myButton').should('be.visible'); + cy.get('#myButton').realClick(); cy.get("#dialog1").ui5DialogOpened(); @@ -265,9 +264,8 @@ describe("ui5 and web components integration", () => { } function OpenWebCDialogOpenUI5Select() { - cy.get('#myButton') - .should('be.visible') - .realClick(); + cy.get('#myButton').should('be.visible'); + cy.get('#myButton').realClick(); cy.get("#dialog1").ui5DialogOpened(); @@ -290,9 +288,8 @@ describe("ui5 and web components integration", () => { } function OpenWebCDialogOpenUI5ComboBox() { - cy.get('#myButton') - .should('be.visible') - .realClick(); + cy.get('#myButton').should('be.visible'); + cy.get('#myButton').realClick(); cy.get("#dialog1").ui5DialogOpened(); @@ -313,7 +310,7 @@ describe("ui5 and web components integration", () => { cy.realPress("Escape"); - // comobo box value is reset, dialog stays open + // combo box value is reset, dialog stays open cy.get("#dialog1").ui5DialogOpened(); cy.realPress("Escape"); From 89d631180930f554123089c63cdcb08193e47d8b Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 16 Sep 2025 15:35:56 +0300 Subject: [PATCH 11/24] chore: fix tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 0cada71f0777..7a2612d76be9 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -252,8 +252,12 @@ describe("ui5 and web components integration", () => { }); function OpenWebCDialog() { - cy.get('#myButton').should('be.visible'); - cy.get('#myButton').realClick(); + cy.get("#openUI5Button") + .should('be.visible'); + + cy.get('#myButton') + .should('be.visible') + .realClick(); cy.get("#dialog1").ui5DialogOpened(); @@ -264,8 +268,9 @@ describe("ui5 and web components integration", () => { } function OpenWebCDialogOpenUI5Select() { - cy.get('#myButton').should('be.visible'); - cy.get('#myButton').realClick(); + cy.get('#myButton') + .should('be.visible') + .realClick(); cy.get("#dialog1").ui5DialogOpened(); @@ -288,8 +293,9 @@ describe("ui5 and web components integration", () => { } function OpenWebCDialogOpenUI5ComboBox() { - cy.get('#myButton').should('be.visible'); - cy.get('#myButton').realClick(); + cy.get('#myButton') + .should('be.visible') + .realClick(); cy.get("#dialog1").ui5DialogOpened(); @@ -310,7 +316,7 @@ describe("ui5 and web components integration", () => { cy.realPress("Escape"); - // combo box value is reset, dialog stays open + // comobo box value is reset, dialog stays open cy.get("#dialog1").ui5DialogOpened(); cy.realPress("Escape"); From e1b4d5f1a0709a38b959df369058d12bc421f077 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 16 Sep 2025 16:58:56 +0300 Subject: [PATCH 12/24] chore: add more tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 65 ++++++++++++++----- .../test/pages/DialogAndOpenUI5Dialog.html | 2 +- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 7a2612d76be9..4b123e41b763 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -9,13 +9,14 @@ import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js"; function onOpenUI5InitMethod(win) { - OpenUI5Support.init(); + (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Popover", "sap/m/Input"], (HTML, Button, Dialog, Popover, Input) => { + + OpenUI5Support.init(); - (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Input"], (HTML, Button, Dialog, Input) => { new Button("openUI5Button", { text: "Open OpenUI5 Dialog", press: function () { - new Dialog({ + new Dialog("openUI5Dialog1", { title: "OpenUI5 Dialog", content: [ new HTML({ @@ -222,17 +223,6 @@ describe("ui5 and web components integration", () => { ); - // inject ui5 configuration - cy.document().then((doc) => { - const configScript = doc.createElement('script'); - configScript.setAttribute('data-ui5-config', ''); - configScript.type = 'application/json'; - configScript.textContent = JSON.stringify({ - language: 'EN' - }); - doc.head.appendChild(configScript); - }); - // define initialization function before loading ui5 cy.window().then((win) => { (win as any).onOpenUI5Init = function () { @@ -325,9 +315,54 @@ describe("ui5 and web components integration", () => { .should('not.be.visible'); } - it("Open WebC dialog", () => { + function OpenUI5Dialog() { + cy.get("#openUI5Button") + .should('be.visible') + .realClick(); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5Dialog1") + .should('not.be.visible'); + } + + function OpenUI5DialogWebCDialogNoFocus() { + cy.get("#openUI5Button") + .should('be.visible') + .realClick(); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.get("#openResPopoverNoInitialFocusButton") + .should('be.visible') + .realClick(); + + cy.get("#respPopoverNoInitialFocus").ui5DialogOpened(); + + cy.realPress("Escape"); + + cy.get("#respPopoverNoInitialFocus") + .should('not.be.visible'); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5Dialog1") + .should('not.be.visible'); + } + + it("Keyboard", () => { OpenWebCDialog(); OpenWebCDialogOpenUI5Select(); OpenWebCDialogOpenUI5ComboBox(); + + OpenUI5Dialog(); + // OpenUI5DialogWebCDialogNoFocus(); }); }); \ No newline at end of file diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 05ee67c2619c..888ea1c5883f 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -122,7 +122,7 @@ function openUI5Dialog() { sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { - new Dialog({ + new Dialog("openUI5Dialog1", { title: "OpenUI5 Dialog", content: [ new Button({ From 48435c624ef0537447ca5d7e6d078eafc6b721ab Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Wed, 17 Sep 2025 15:48:57 +0300 Subject: [PATCH 13/24] chore: resolves merge conflicts --- packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 4b123e41b763..31ee77af7a6b 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -11,7 +11,7 @@ import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js"; function onOpenUI5InitMethod(win) { (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Popover", "sap/m/Input"], (HTML, Button, Dialog, Popover, Input) => { - OpenUI5Support.init(); + // OpenUI5Support.init(); new Button("openUI5Button", { text: "Open OpenUI5 Dialog", @@ -306,7 +306,7 @@ describe("ui5 and web components integration", () => { cy.realPress("Escape"); - // comobo box value is reset, dialog stays open + // combo box value is reset, dialog stays open cy.get("#dialog1").ui5DialogOpened(); cy.realPress("Escape"); From 8db7a90d55a88772a51c96cb1a285cda3bfafe2b Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Wed, 17 Sep 2025 16:05:26 +0300 Subject: [PATCH 14/24] chore: fix lint error --- packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 31ee77af7a6b..6c08f31f75a8 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -1,4 +1,4 @@ -import OpenUI5Support from "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; +import "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; import Button from "@ui5/webcomponents/dist/Button.js"; import Dialog from "@ui5/webcomponents/dist/Dialog.js"; import Select from "@ui5/webcomponents/dist/Select.js"; @@ -7,12 +7,9 @@ import ComboBox from "@ui5/webcomponents/dist/ComboBox.js"; import ComboBoxItem from "@ui5/webcomponents/dist/ComboBoxItem.js"; import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js"; - function onOpenUI5InitMethod(win) { (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Popover", "sap/m/Input"], (HTML, Button, Dialog, Popover, Input) => { - // OpenUI5Support.init(); - new Button("openUI5Button", { text: "Open OpenUI5 Dialog", press: function () { From d7b6f6ce52893edc68fa9ea3bfafe026b299a3be Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Thu, 18 Sep 2025 15:55:07 +0300 Subject: [PATCH 15/24] chore: update tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 6c08f31f75a8..2cca849b445a 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -1,14 +1,16 @@ -import "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; -import Button from "@ui5/webcomponents/dist/Button.js"; -import Dialog from "@ui5/webcomponents/dist/Dialog.js"; -import Select from "@ui5/webcomponents/dist/Select.js"; -import Option from "@ui5/webcomponents/dist/Option.js"; -import ComboBox from "@ui5/webcomponents/dist/ComboBox.js"; -import ComboBoxItem from "@ui5/webcomponents/dist/ComboBoxItem.js"; -import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js"; +import OpenUI5Support from "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; +import Button from "../../src/Button.js"; +import Dialog from "../../src/Dialog.js"; +import Select from "../../src/Select.js"; +import Option from "../../src/Option.js"; +import ComboBox from "../../src/ComboBox.js"; +import ComboBoxItem from "../../src/ComboBoxItem.js"; +import ResponsivePopover from "../../src/ResponsivePopover.js"; function onOpenUI5InitMethod(win) { - (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Popover", "sap/m/Input"], (HTML, Button, Dialog, Popover, Input) => { + (win as any).sap.ui.require(["sap/ui/core/HTML", "sap/m/Button", "sap/m/Dialog", "sap/m/Popover", "sap/m/Input"], async (HTML, Button, Dialog, Popover, Input) => { + + await OpenUI5Support.init(); new Button("openUI5Button", { text: "Open OpenUI5 Dialog", @@ -355,11 +357,11 @@ describe("ui5 and web components integration", () => { } it("Keyboard", () => { - OpenWebCDialog(); - OpenWebCDialogOpenUI5Select(); - OpenWebCDialogOpenUI5ComboBox(); - - OpenUI5Dialog(); - // OpenUI5DialogWebCDialogNoFocus(); + // OpenWebCDialog(); + // OpenWebCDialogOpenUI5Select(); + // OpenWebCDialogOpenUI5ComboBox(); + // + // OpenUI5Dialog(); + OpenUI5DialogWebCDialogNoFocus(); }); }); \ No newline at end of file From 2f2c6e11d3de05bf7d9cfc0aec06989e6ef58506 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Thu, 18 Sep 2025 16:48:53 +0300 Subject: [PATCH 16/24] chore: add all tests --- .../main/cypress/specs/OpenUI5andWebCPopups.cy.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 2cca849b445a..61e7144190b0 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -357,11 +357,11 @@ describe("ui5 and web components integration", () => { } it("Keyboard", () => { - // OpenWebCDialog(); - // OpenWebCDialogOpenUI5Select(); - // OpenWebCDialogOpenUI5ComboBox(); - // - // OpenUI5Dialog(); + OpenWebCDialog(); + OpenWebCDialogOpenUI5Select(); + OpenWebCDialogOpenUI5ComboBox(); + + OpenUI5Dialog(); OpenUI5DialogWebCDialogNoFocus(); }); }); \ No newline at end of file From 0ce22b98baa3ab7a51968dfaf2fd43a51f1dcfcf Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Fri, 19 Sep 2025 15:06:51 +0300 Subject: [PATCH 17/24] chore: add tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 131 +++++++++++++++--- .../test/pages/DialogAndOpenUI5Dialog.html | 25 ++-- 2 files changed, 127 insertions(+), 29 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 61e7144190b0..e774c31e562c 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -19,13 +19,20 @@ function onOpenUI5InitMethod(win) { title: "OpenUI5 Dialog", content: [ new HTML({ - content: ` - Option 1 - Option 2 - Option 3 - Option 4 - Option 5 - ` + content: +` + Option 1 + Option 2 + Option 3 + Option 4 + Option 5 + + + + + + +

` }), new Input(), new Button({ @@ -85,7 +92,7 @@ function onOpenUI5InitMethod(win) { ] }).placeAt("dialog1content"); - const button = new Button({ + const button = new Button("openUI5ButtonWithHint", { text: "OpenUI5 with Shortcut (Ctrl+S)", press: function () { openUI5Dialog(win); @@ -112,7 +119,7 @@ function onOpenUI5InitMethod(win) { function openUI5Dialog(win) { (win as any).sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { - new Dialog({ + new Dialog("openUI5DialogWithButtons", { title: "OpenUI5 Dialog", content: [ new Button({ @@ -213,12 +220,6 @@ describe("ui5 and web components integration", () => { > - - - - - - ); @@ -257,6 +258,9 @@ describe("ui5 and web components integration", () => { } function OpenWebCDialogOpenUI5Select() { + cy.get("#openUI5Button") + .should('be.visible'); + cy.get('#myButton') .should('be.visible') .realClick(); @@ -282,6 +286,9 @@ describe("ui5 and web components integration", () => { } function OpenWebCDialogOpenUI5ComboBox() { + cy.get("#openUI5Button") + .should('be.visible'); + cy.get('#myButton') .should('be.visible') .realClick(); @@ -314,6 +321,56 @@ describe("ui5 and web components integration", () => { .should('not.be.visible'); } + function OpenWebCDialogOpenUI5ComboBoxNewOpenUI5DialogFromButtonWithHint() { + cy.get("#openUI5Button") + .should('be.visible'); + + cy.get('#myButton') + .should('be.visible') + .realClick(); + + cy.get("#dialog1").ui5DialogOpened(); + + cy.get('#openUI5Combobox1') + .should('be.visible') + .realClick() + .type("I"); + + cy.get("#openUI5Combobox1-popup") + .should('be.visible'); + + cy.get('#openUI5ButtonWithHint') + .should('be.visible') + .realClick(); + + cy.get("#openUI5Combobox1-popup") + .should('not.be.visible'); + + cy.get("#openUI5DialogWithButtons") + .should("be.visible"); + + cy.realPress("Escape"); + + cy.get("#openUI5DialogWithButtons") + .should("not.be.visible"); + + cy.get("#dialog1").ui5DialogOpened(); + + + cy.get('#openUI5Combobox1') + .find('input') + .focus(); + + cy.get('#openUI5Combobox1') + .find('input') + .realPress("Escape"); + + cy.realPress("Escape"); + + cy.get('#dialog1') + .should('not.be.visible'); + } + function OpenUI5Dialog() { cy.get("#openUI5Button") .should('be.visible') @@ -328,6 +385,34 @@ describe("ui5 and web components integration", () => { .should('not.be.visible'); } + function OpenUI5DialogWebCDialog() { + cy.get("#openUI5Button") + .should('be.visible') + .realClick(); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.get("#openResPopoverButton") + .should('be.visible') + .realClick(); + + cy.get("#respPopover").ui5DialogOpened(); + + cy.realPress("Escape"); + + cy.get("#respPopover") + .should('not.be.visible'); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5Dialog1") + .should('not.be.visible'); + } + function OpenUI5DialogWebCDialogNoFocus() { cy.get("#openUI5Button") .should('be.visible') @@ -357,11 +442,17 @@ describe("ui5 and web components integration", () => { } it("Keyboard", () => { - OpenWebCDialog(); - OpenWebCDialogOpenUI5Select(); - OpenWebCDialogOpenUI5ComboBox(); + // OpenWebCDialog(); + // OpenWebCDialogOpenUI5Select(); + // OpenWebCDialogOpenUI5ComboBox(); + + OpenWebCDialogOpenUI5ComboBoxNewOpenUI5DialogFromButtonWithHint(); + - OpenUI5Dialog(); - OpenUI5DialogWebCDialogNoFocus(); + // OpenUI5Dialog(); + // cy.wait(500); + // OpenUI5DialogWebCDialog(); + // cy.wait(500); + // OpenUI5DialogWebCDialogNoFocus(); }); }); \ No newline at end of file diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 888ea1c5883f..6c2147bfa108 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -25,17 +25,24 @@ new Button("openUI5Button", { text: "Open OpenUI5 Dialog", press: function () { - new Dialog({ + new Dialog("openUI5Dialog", { title: "OpenUI5 Dialog", content: [ new HTML({ - content: ` - Option 1 - Option 2 - Option 3 - Option 4 - Option 5 - ` + content: +` + Option 1 + Option 2 + Option 3 + Option 4 + Option 5 + + + + + + +

` }), new Input(), new Button({ @@ -122,7 +129,7 @@ function openUI5Dialog() { sap.ui.require(["sap/m/Button", "sap/m/Dialog"], (Button, Dialog) => { - new Dialog("openUI5Dialog1", { + new Dialog("openUI5DialogWithButtons", { title: "OpenUI5 Dialog", content: [ new Button({ From 6a006948a257535835bf76ffe847fb9bdffed972 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 23 Sep 2025 13:59:50 +0300 Subject: [PATCH 18/24] chore: add more tests --- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 122 +++++++++++++++--- .../test/pages/DialogAndOpenUI5Dialog.html | 2 +- 2 files changed, 106 insertions(+), 18 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index e774c31e562c..112c27976aa8 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -141,7 +141,7 @@ function openUI5Dialog(win) { function openUI5Popover(win, opener) { (win as any).sap.ui.require(["sap/m/Popover", "sap/m/Button"], (Popover, Button) => { - new Popover({ + new Popover("openUI5PopoverSecond", { title: "OpenUI5 Popover", content: [ new Button("someButton", { @@ -255,6 +255,9 @@ describe("ui5 and web components integration", () => { cy.get('#dialog1') .should('not.be.visible'); + + cy.get('#myButton') + .should('be.focused'); } function OpenWebCDialogOpenUI5Select() { @@ -283,6 +286,9 @@ describe("ui5 and web components integration", () => { cy.get('#dialog1') .should('not.be.visible'); + + cy.get('#myButton') + .should('be.focused'); } function OpenWebCDialogOpenUI5ComboBox() { @@ -319,6 +325,79 @@ describe("ui5 and web components integration", () => { cy.get('#dialog1') .should('not.be.visible'); + + cy.get('#myButton') + .should('be.focused'); + } + + function OpenWebCDialogOpenOpenUI5Dialog() { + cy.get("#openUI5Button") + .should('be.visible'); + + cy.get('#myButton') + .should('be.visible') + .realClick(); + + cy.get("#dialog1").ui5DialogOpened(); + + cy.get('#dialogButton') + .should('be.visible') + .realClick(); + + cy.get("#openUI5DialogWithButtons") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5DialogWithButtons") + .should('not.be.visible'); + + cy.get('#dialogButton') + .should('be.focused'); + + cy.realPress("Escape"); + + cy.get('#dialog1') + .should('not.be.visible'); + + cy.get('#myButton') + .should('be.focused'); + } + + function OpenWebCDialogOpenOpenUI5PopoverNoFocus() { + cy.get("#openUI5Button") + .should('be.visible'); + + cy.get('#myButton') + .should('be.visible') + .realClick(); + + cy.get("#dialog1").ui5DialogOpened(); + + cy.get('#popoverButtonNoFocus') + .should('be.visible') + .realClick(); + + cy.get("#openUI5PopoverSecond") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5PopoverSecond") + .should('not.exist'); + + cy.realPress(["Shift", "Tab"]); + + cy.get('#dialogButton') + .should('be.focused'); + + cy.realPress("Escape"); + + cy.get('#dialog1') + .should('not.be.visible'); + + cy.get('#myButton') + .should('be.focused'); } function OpenWebCDialogOpenUI5ComboBoxNewOpenUI5DialogFromButtonWithHint() { @@ -352,23 +431,24 @@ describe("ui5 and web components integration", () => { cy.realPress("Escape"); cy.get("#openUI5DialogWithButtons") - .should("not.be.visible"); + .should("not.exist"); cy.get("#dialog1").ui5DialogOpened(); + cy.get('#openUI5ButtonWithHint') + .should('be.focused') cy.get('#openUI5Combobox1') .find('input') .focus(); - cy.get('#openUI5Combobox1') - .find('input') - .realPress("Escape"); - cy.realPress("Escape"); cy.get('#dialog1') .should('not.be.visible'); + + cy.get('#myButton') + .should('be.focused'); } function OpenUI5Dialog() { @@ -382,7 +462,10 @@ describe("ui5 and web components integration", () => { cy.realPress("Escape"); cy.get("#openUI5Dialog1") - .should('not.be.visible'); + .should('not.exist'); + + cy.get("#openUI5Button") + .should('be.focused'); } function OpenUI5DialogWebCDialog() { @@ -410,7 +493,10 @@ describe("ui5 and web components integration", () => { cy.realPress("Escape"); cy.get("#openUI5Dialog1") - .should('not.be.visible'); + .should('not.exist'); + + cy.get("#openUI5Button") + .should('be.focused'); } function OpenUI5DialogWebCDialogNoFocus() { @@ -439,20 +525,22 @@ describe("ui5 and web components integration", () => { cy.get("#openUI5Dialog1") .should('not.be.visible'); + + cy.get("#openResPopoverNoInitialFocusButton") + .should('be.focused'); } it("Keyboard", () => { - // OpenWebCDialog(); - // OpenWebCDialogOpenUI5Select(); - // OpenWebCDialogOpenUI5ComboBox(); + OpenWebCDialog(); + OpenWebCDialogOpenUI5Select(); + OpenWebCDialogOpenUI5ComboBox(); + OpenWebCDialogOpenOpenUI5Dialog(); + OpenWebCDialogOpenOpenUI5PopoverNoFocus(); OpenWebCDialogOpenUI5ComboBoxNewOpenUI5DialogFromButtonWithHint(); - - // OpenUI5Dialog(); - // cy.wait(500); - // OpenUI5DialogWebCDialog(); - // cy.wait(500); - // OpenUI5DialogWebCDialogNoFocus(); + OpenUI5Dialog(); + OpenUI5DialogWebCDialog(); + OpenUI5DialogWebCDialogNoFocus(); }); }); \ No newline at end of file diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 6c2147bfa108..50af3d146f96 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -151,7 +151,7 @@ function openUI5Popover(opener) { sap.ui.require(["sap/m/Popover", "sap/m/Button"], (Popover, Button) => { - new Popover({ + new Popover("openUI5PopoverSecond", { title: "OpenUI5 Popover", content: [ new Button("someButton", { From 5b023ffaa280ec456c07ef77416563ca89157fff Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 23 Sep 2025 15:17:42 +0300 Subject: [PATCH 19/24] chore: improve tests and samples --- packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx | 9 ++++----- packages/main/test/pages/DialogAndOpenUI5Dialog.html | 7 +++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 112c27976aa8..331adbbcdb3a 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -499,7 +499,7 @@ describe("ui5 and web components integration", () => { .should('be.focused'); } - function OpenUI5DialogWebCDialogNoFocus() { + function OpenUI5DialogWebCPopoverNoFocus() { cy.get("#openUI5Button") .should('be.visible') .realClick(); @@ -532,15 +532,14 @@ describe("ui5 and web components integration", () => { it("Keyboard", () => { OpenWebCDialog(); - OpenWebCDialogOpenUI5Select(); - OpenWebCDialogOpenUI5ComboBox(); - OpenWebCDialogOpenOpenUI5Dialog(); OpenWebCDialogOpenOpenUI5PopoverNoFocus(); + OpenWebCDialogOpenUI5Select(); + OpenWebCDialogOpenUI5ComboBox(); OpenWebCDialogOpenUI5ComboBoxNewOpenUI5DialogFromButtonWithHint(); OpenUI5Dialog(); OpenUI5DialogWebCDialog(); - OpenUI5DialogWebCDialogNoFocus(); + OpenUI5DialogWebCPopoverNoFocus(); }); }); \ No newline at end of file diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 50af3d146f96..6124c0b67238 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -30,7 +30,8 @@ content: [ new HTML({ content: -` +`
Web Components:
+ Option 1 Option 2 Option 3 @@ -42,7 +43,9 @@ -

` +WebC Button +

+
OpenUI5 Controls:
` }), new Input(), new Button({ From 8181a188215ac6c57b43588d74e2af89d968b8aa Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 23 Sep 2025 15:55:45 +0300 Subject: [PATCH 20/24] chore: rename and move eventMarking.ts file --- packages/base/src/util/isEventMarked.ts | 7 +++++++ packages/base/src/util/openui5support/eventMarking.ts | 7 ------- packages/main/src/popup-utils/OpenedPopupsRegistry.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 packages/base/src/util/isEventMarked.ts delete mode 100644 packages/base/src/util/openui5support/eventMarking.ts diff --git a/packages/base/src/util/isEventMarked.ts b/packages/base/src/util/isEventMarked.ts new file mode 100644 index 000000000000..764eab1b0042 --- /dev/null +++ b/packages/base/src/util/isEventMarked.ts @@ -0,0 +1,7 @@ +const defaultOpenUI5Key = "handledByControl"; + +const isEventMarked = (event: any, key: string = defaultOpenUI5Key) => { + return !!event[`_sapui_${key}`]; +}; + +export default isEventMarked; diff --git a/packages/base/src/util/openui5support/eventMarking.ts b/packages/base/src/util/openui5support/eventMarking.ts deleted file mode 100644 index 11551b79fc01..000000000000 --- a/packages/base/src/util/openui5support/eventMarking.ts +++ /dev/null @@ -1,7 +0,0 @@ -const defaultKey = "handledByControl"; - -const isMarked = (event: any, key: string = defaultKey) => { - return !!event[`_sapui_${key}`]; -}; - -export default isMarked; diff --git a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts index 144dff94fc58..df330fdb59ea 100644 --- a/packages/main/src/popup-utils/OpenedPopupsRegistry.ts +++ b/packages/main/src/popup-utils/OpenedPopupsRegistry.ts @@ -1,7 +1,7 @@ import getSharedResource from "@ui5/webcomponents-base/dist/getSharedResource.js"; import { isEscape } from "@ui5/webcomponents-base/dist/Keys.js"; import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js"; -import isMarked from "@ui5/webcomponents-base/dist/util/openui5support/eventMarking.js"; +import isEventMarked from "@ui5/webcomponents-base/dist/util/isEventMarked.js"; import type OpenUI5Support from "@ui5/webcomponents-base/dist/features/OpenUI5Support.js"; import type Popup from "../Popup.js"; import type { PopupInfo } from "@ui5/webcomponents-base/dist/features/patchPopup.js"; @@ -65,7 +65,7 @@ const _keydownListener = (event: KeyboardEvent) => { return; } - if (isEscape(event) && !isMarked(event)) { + if (isEscape(event) && !isEventMarked(event)) { const topmostPopup = OpenedPopupsRegistry.openedRegistry[OpenedPopupsRegistry.openedRegistry.length - 1].instance; if (openUI5Support && topmostPopup !== openUI5Support.getTopmostPopup()) { From f7035109a3c0390ef998e6b8ae7de3265ca5ad00 Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Tue, 30 Sep 2025 10:15:53 +0300 Subject: [PATCH 21/24] chore: address code comments --- packages/base/src/features/patchPopup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/base/src/features/patchPopup.ts b/packages/base/src/features/patchPopup.ts index 6fa636fa7004..1a0de1b10fff 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -48,10 +48,10 @@ const getTopmostPopup = () => { }; /** - * Checks if Web Component popups opened on top of it. + * Determines whether there is a Web Component popup opened above (a specified popup). * * @param {object} popup The popup instance to check against. - * @returns {boolean} `true` if a Web Component popup is stacked above; otherwise `false`. + * @returns {boolean} `true` if a Web Component popup is opened above (the given popup instance); otherwise `false`. */ const hasWebComponentPopupAbove = (popup: object) => { for (let i = AllOpenedPopupsRegistry.openedRegistry.length - 1; i >= 0; i--) { From 865141132595e2455c9c8bfd8242e594ae3df7bd Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Thu, 2 Oct 2025 15:58:43 +0300 Subject: [PATCH 22/24] chore: address code comments and add more tests --- packages/base/src/features/OpenUI5Support.ts | 4 +- packages/base/src/features/patchPopup.ts | 16 ++-- .../cypress/specs/OpenUI5andWebCPopups.cy.tsx | 87 +++++++++++++++++-- .../test/pages/DialogAndOpenUI5Dialog.html | 6 +- 4 files changed, 95 insertions(+), 18 deletions(-) diff --git a/packages/base/src/features/OpenUI5Support.ts b/packages/base/src/features/OpenUI5Support.ts index 721a4840b62a..6e5856afa6e3 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, OpenUI5PopupControl, PopupInfo } from "./patchPopup.js"; +import type { OpenUI5Popup, OpenUI5PopupBasedControl, 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: OpenUI5PopupControl, Popover: OpenUI5PopupControl, Patcher: OpenUI5Patcher) => { + window.sap.ui.require(deps, (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl, Popover: OpenUI5PopupBasedControl, 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 1a0de1b10fff..5bac3ab5e604 100644 --- a/packages/base/src/features/patchPopup.ts +++ b/packages/base/src/features/patchPopup.ts @@ -17,7 +17,7 @@ type OpenUI5Popup = { } }; -type OpenUI5PopupControl = { +type OpenUI5PopupBasedControl = { prototype: { onsapescape: (e: Event) => void, oPopup: OpenUI5Popup, @@ -91,9 +91,9 @@ const isNativePopoverOpen = (root: Document | ShadowRoot = document): boolean => }); }; -const patchPopupControl = (PopupControl: OpenUI5PopupControl) => { - const origOnsapescape = PopupControl.prototype.onsapescape; - PopupControl.prototype.onsapescape = function onsapescape(e: Event) { +const patchPopupBasedControl = (PopupBasedControl: OpenUI5PopupBasedControl) => { + const origOnsapescape = PopupBasedControl.prototype.onsapescape; + PopupBasedControl.prototype.onsapescape = function onsapescape(e: Event) { if (hasWebComponentPopupAbove(this.oPopup)) { return; } @@ -154,14 +154,14 @@ const createGlobalStyles = () => { document.adoptedStyleSheets = [...document.adoptedStyleSheets, stylesheet]; }; -const patchPopup = (Popup: OpenUI5Popup, Dialog: OpenUI5PopupControl, Popover: OpenUI5PopupControl) => { +const patchPopup = (Popup: OpenUI5Popup, Dialog: OpenUI5PopupBasedControl, Popover: OpenUI5PopupBasedControl) => { 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 - patchPopupControl(Dialog); // Dialog.prototype.onsapescape - patchPopupControl(Popover); // Popover.prototype.onsapescape + patchPopupBasedControl(Dialog); // Dialog.prototype.onsapescape + patchPopupBasedControl(Popover); // Popover.prototype.onsapescape }; export { @@ -171,4 +171,4 @@ export { getTopmostPopup, }; -export type { OpenUI5Popup, OpenUI5PopupControl, PopupInfo }; +export type { OpenUI5Popup, OpenUI5PopupBasedControl, PopupInfo }; diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index 331adbbcdb3a..a5b97ddebd93 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -28,9 +28,9 @@ function onOpenUI5InitMethod(win) { Option 5
- - - + + +

` }), @@ -233,7 +233,7 @@ describe("ui5 and web components integration", () => { // add ui5 bootstrap cy.document().then((doc) => { const ui5Script = doc.createElement('script'); - ui5Script.src = 'https://openui5.hana.ondemand.com/resources/sap-ui-core.js'; + ui5Script.src = 'https://sapui5untested.int.sap.eu2.hana.ondemand.com/resources/sap-ui-core.js'; ui5Script.id = 'sap-ui-bootstrap'; ui5Script.setAttribute('data-sap-ui-libs', 'sap.m'); ui5Script.setAttribute('data-sap-ui-oninit', 'onOpenUI5Init'); @@ -242,7 +242,7 @@ describe("ui5 and web components integration", () => { }); function OpenWebCDialog() { - cy.get("#openUI5Button") + cy.get("#openUI5Button", { timeout: 3000 }) .should('be.visible'); cy.get('#myButton') @@ -530,6 +530,81 @@ describe("ui5 and web components integration", () => { .should('be.focused'); } + function OpenUI5DialogWebCSelect() { + cy.get("#openUI5Button") + .should('be.visible') + .realClick(); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.get("#webCSelect1") + .should('be.visible') + .realClick(); + + cy.get("#webCSelect1") + .shadow() + .find("[ui5-responsive-popover]").ui5DialogOpened(); + + cy.realPress("Escape"); + + cy.get("#webCSelect1") + .shadow() + .find("[ui5-responsive-popover]") + .should('not.be.visible'); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5Dialog1") + .should('not.exist'); + + cy.get("#openUI5Button") + .should('be.focused'); + } + + function OpenUI5DialogWebCComboBox() { + cy.get("#openUI5Button") + .should('be.visible') + .realClick(); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.get("#webCComboBox1") + .should('be.visible'); + + cy.get("#webCComboBox1") + .shadow() + .find('input') + .realClick() + .type("A"); + + cy.get("#webCComboBox1") + .shadow() + .find("[ui5-responsive-popover]").ui5DialogOpened(); + + cy.realPress("Escape"); + + cy.get("#webCComboBox1") + .shadow() + .find("[ui5-responsive-popover]") + .should('not.be.visible'); + + cy.get("#openUI5Dialog1") + .should('be.visible'); + + cy.realPress("Escape"); + + cy.get("#openUI5Dialog1") + .should('not.exist'); + + cy.get("#openUI5Button") + .should('be.focused'); + } + it("Keyboard", () => { OpenWebCDialog(); OpenWebCDialogOpenOpenUI5Dialog(); @@ -541,5 +616,7 @@ describe("ui5 and web components integration", () => { OpenUI5Dialog(); OpenUI5DialogWebCDialog(); OpenUI5DialogWebCPopoverNoFocus(); + OpenUI5DialogWebCSelect(); + OpenUI5DialogWebCComboBox(); }); }); \ No newline at end of file diff --git a/packages/main/test/pages/DialogAndOpenUI5Dialog.html b/packages/main/test/pages/DialogAndOpenUI5Dialog.html index 6124c0b67238..8c118a4f8dd8 100644 --- a/packages/main/test/pages/DialogAndOpenUI5Dialog.html +++ b/packages/main/test/pages/DialogAndOpenUI5Dialog.html @@ -39,9 +39,9 @@ Option 5 - - - + + + WebC Button

From ee7c17b3606a89e617f8aa708eecbaad95bb59ab Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Thu, 2 Oct 2025 16:25:03 +0300 Subject: [PATCH 23/24] chore: fix tests --- .../main/cypress/specs/OpenUI5andWebCPopups.cy.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx index a5b97ddebd93..f11c71d574f2 100644 --- a/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx +++ b/packages/main/cypress/specs/OpenUI5andWebCPopups.cy.tsx @@ -233,7 +233,7 @@ describe("ui5 and web components integration", () => { // add ui5 bootstrap cy.document().then((doc) => { const ui5Script = doc.createElement('script'); - ui5Script.src = 'https://sapui5untested.int.sap.eu2.hana.ondemand.com/resources/sap-ui-core.js'; + ui5Script.src = 'https://ui5.sap.com/resources/sap-ui-core.js'; ui5Script.id = 'sap-ui-bootstrap'; ui5Script.setAttribute('data-sap-ui-libs', 'sap.m'); ui5Script.setAttribute('data-sap-ui-oninit', 'onOpenUI5Init'); @@ -242,7 +242,7 @@ describe("ui5 and web components integration", () => { }); function OpenWebCDialog() { - cy.get("#openUI5Button", { timeout: 3000 }) + cy.get("#openUI5Button", { timeout: 10000 }) .should('be.visible'); cy.get('#myButton') @@ -532,7 +532,7 @@ describe("ui5 and web components integration", () => { function OpenUI5DialogWebCSelect() { cy.get("#openUI5Button") - .should('be.visible') + .should('be.focused') .realClick(); cy.get("#openUI5Dialog1") @@ -567,7 +567,7 @@ describe("ui5 and web components integration", () => { function OpenUI5DialogWebCComboBox() { cy.get("#openUI5Button") - .should('be.visible') + .should('be.focused') .realClick(); cy.get("#openUI5Dialog1") @@ -617,6 +617,7 @@ describe("ui5 and web components integration", () => { OpenUI5DialogWebCDialog(); OpenUI5DialogWebCPopoverNoFocus(); OpenUI5DialogWebCSelect(); - OpenUI5DialogWebCComboBox(); + // Merge it after OpenUI5 Popup shadow dom focus fix is released + // OpenUI5DialogWebCComboBox(); }); }); \ No newline at end of file From 0b0f4de796ae03020ca1eaece826235cf63537ed Mon Sep 17 00:00:00 2001 From: Teodor Taushanov Date: Fri, 3 Oct 2025 16:29:03 +0300 Subject: [PATCH 24/24] chore: add code comments --- packages/main/src/ComboBox.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/main/src/ComboBox.ts b/packages/main/src/ComboBox.ts index 6bb5c16d2786..efc2eb1ec86f 100644 --- a/packages/main/src/ComboBox.ts +++ b/packages/main/src/ComboBox.ts @@ -980,6 +980,7 @@ class ComboBox extends UI5Element implements IFormInputElement { const shouldResetValueAndStopPropagation = !this.open && this.value !== this._lastValue; if (shouldResetValueAndStopPropagation) { this.value = this._lastValue; + // stop propagation to prevent closing the popup when using the combobox inside it e.stopPropagation(); } }