From df36b64536930b15e56b34ebe98dce3314fb4f2a Mon Sep 17 00:00:00 2001 From: Vladislav Tasev Date: Mon, 22 Mar 2021 14:38:27 +0200 Subject: [PATCH] feat(framework): Maintain a common z-index for all UI5 Web Components instances and OpenUI5 (#2980) --- packages/base/src/features/OpenUI5Support.js | 47 ++++++++-- packages/base/src/util/PopupUtils.js | 94 +++++++++++++++++++ packages/main/src/Popover.js | 2 +- packages/main/src/Popup.js | 2 +- packages/main/src/ResponsivePopover.js | 2 +- packages/main/src/Toast.js | 2 +- .../main/src/popup-utils/PopoverRegistry.js | 2 +- packages/main/src/popup-utils/PopupUtils.js | 82 ++-------------- 8 files changed, 149 insertions(+), 84 deletions(-) create mode 100644 packages/base/src/util/PopupUtils.js diff --git a/packages/base/src/features/OpenUI5Support.js b/packages/base/src/features/OpenUI5Support.js index 808b8c0c21d7..3930d4a5f444 100644 --- a/packages/base/src/features/OpenUI5Support.js +++ b/packages/base/src/features/OpenUI5Support.js @@ -1,32 +1,41 @@ import { registerFeature } from "../FeaturesRegistry.js"; import { setTheme } from "../config/Theme.js"; +import { getCurrentZIndex } from "../util/PopupUtils.js"; -const sap = window.sap; -const core = sap && sap.ui && typeof sap.ui.getCore === "function" && sap.ui.getCore(); +const getCore = () => { + const sap = window.sap; + const core = sap && sap.ui && typeof sap.ui.getCore === "function" && sap.ui.getCore(); + return core; +}; const isLoaded = () => { - return !!core; + return !!getCore(); }; const init = () => { + const core = getCore(); if (!core) { return Promise.resolve(); } return new Promise(resolve => { core.attachInit(() => { - sap.ui.require(["sap/ui/core/LocaleData"], resolve); + window.sap.ui.require(["sap/ui/core/LocaleData", "sap/ui/core/Popup"], (LocaleData, Popup) => { + Popup.setInitialZIndex(getCurrentZIndex()); + resolve(); + }); }); }); }; const getConfigurationSettingsObject = () => { + const core = getCore(); if (!core) { return; } const config = core.getConfiguration(); - const LocaleData = sap.ui.require("sap/ui/core/LocaleData"); + const LocaleData = window.sap.ui.require("sap/ui/core/LocaleData"); return { animationMode: config.getAnimationMode(), @@ -41,16 +50,18 @@ const getConfigurationSettingsObject = () => { }; const getLocaleDataObject = () => { + const core = getCore(); if (!core) { return; } const config = core.getConfiguration(); - const LocaleData = sap.ui.require("sap/ui/core/LocaleData"); + const LocaleData = window.sap.ui.require("sap/ui/core/LocaleData"); return LocaleData.getInstance(config.getLocale())._get(); }; const listenForThemeChange = () => { + const core = getCore(); const config = core.getConfiguration(); core.attachThemeChanged(async () => { await setTheme(config.getTheme()); @@ -58,6 +69,7 @@ const listenForThemeChange = () => { }; const attachListeners = () => { + const core = getCore(); if (!core) { return; } @@ -66,6 +78,7 @@ const attachListeners = () => { }; const cssVariablesLoaded = () => { + const core = getCore(); if (!core) { return; } @@ -78,6 +91,26 @@ const cssVariablesLoaded = () => { return !!link.href.match(/\/css(-|_)variables\.css/); }; +const getNextZIndex = () => { + const core = getCore(); + if (!core) { + return; + } + + const Popup = window.sap.ui.require("sap/ui/core/Popup"); + return Popup.getNextZIndex(); +}; + +const setInitialZIndex = () => { + const core = getCore(); + if (!core) { + return; + } + + const Popup = window.sap.ui.require("sap/ui/core/Popup"); + Popup.setInitialZIndex(getCurrentZIndex()); +}; + const OpenUI5Support = { isLoaded, init, @@ -85,6 +118,8 @@ const OpenUI5Support = { getLocaleDataObject, attachListeners, cssVariablesLoaded, + getNextZIndex, + setInitialZIndex, }; registerFeature("OpenUI5Support", OpenUI5Support); diff --git a/packages/base/src/util/PopupUtils.js b/packages/base/src/util/PopupUtils.js new file mode 100644 index 000000000000..d52035573212 --- /dev/null +++ b/packages/base/src/util/PopupUtils.js @@ -0,0 +1,94 @@ +import getSharedResource from "../getSharedResource.js"; +import { getFeature } from "../FeaturesRegistry.js"; +import getActiveElement from "./getActiveElement.js"; + +const PopupUtilsData = getSharedResource("PopupUtilsData", {}); +PopupUtilsData.currentZIndex = PopupUtilsData.currentZIndex || 100; + +const getFocusedElement = () => { + const element = getActiveElement(); + return (element && typeof element.focus === "function") ? element : null; +}; + +const isFocusedElementWithinNode = node => { + const fe = getFocusedElement(); + + if (fe) { + return isNodeContainedWithin(node, fe); + } + + return false; +}; + +const isNodeContainedWithin = (parent, child) => { + let currentNode = parent; + + if (currentNode.shadowRoot) { + currentNode = Array.from(currentNode.shadowRoot.children).find(n => n.localName !== "style"); + } + + if (currentNode === child) { + return true; + } + + const childNodes = currentNode.localName === "slot" ? currentNode.assignedNodes() : currentNode.children; + + if (childNodes) { + return Array.from(childNodes).some(n => isNodeContainedWithin(n, child)); + } +}; + +const isPointInRect = (x, y, rect) => { + return x >= rect.left && x <= rect.right + && y >= rect.top && y <= rect.bottom; +}; + +const isClickInRect = (event, rect) => { + let x; + let y; + + if (event.touches) { + const touch = event.touches[0]; + x = touch.clientX; + y = touch.clientY; + } else { + x = event.clientX; + y = event.clientY; + } + + return isPointInRect(x, y, rect); +}; + +const getClosedPopupParent = el => { + const parent = el.parentElement || (el.getRootNode && el.getRootNode().host); + + if (parent && ((parent.openBy && parent.isUI5Element) || (parent.open && parent.isUI5Element) || parent === document.documentElement)) { + return parent; + } + + return getClosedPopupParent(parent); +}; + + +const getNextZIndex = () => { + const OpenUI5Support = getFeature("OpenUI5Support"); + if (OpenUI5Support && OpenUI5Support.isLoaded()) { // use OpenUI5 for getting z-index values, if loaded + return OpenUI5Support.getNextZIndex(); + } + + PopupUtilsData.currentZIndex += 2; + return PopupUtilsData.currentZIndex; +}; + +const getCurrentZIndex = () => { + return PopupUtilsData.currentZIndex; +}; + +export { + getFocusedElement, + isClickInRect, + getClosedPopupParent, + getNextZIndex, + getCurrentZIndex, + isFocusedElementWithinNode, +}; diff --git a/packages/main/src/Popover.js b/packages/main/src/Popover.js index 30a2b93afb77..22ddafbca0cc 100644 --- a/packages/main/src/Popover.js +++ b/packages/main/src/Popover.js @@ -1,11 +1,11 @@ import Integer from "@ui5/webcomponents-base/dist/types/Integer.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; +import { getClosedPopupParent } from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; import Popup from "./Popup.js"; import PopoverPlacementType from "./types/PopoverPlacementType.js"; import PopoverVerticalAlign from "./types/PopoverVerticalAlign.js"; import PopoverHorizontalAlign from "./types/PopoverHorizontalAlign.js"; import { addOpenedPopover, removeOpenedPopover } from "./popup-utils/PopoverRegistry.js"; -import { getClosedPopupParent } from "./popup-utils/PopupUtils.js"; // Template import PopoverTemplate from "./generated/templates/PopoverTemplate.lit.js"; diff --git a/packages/main/src/Popup.js b/packages/main/src/Popup.js index 344b790dd156..50e6cabc4d8a 100644 --- a/packages/main/src/Popup.js +++ b/packages/main/src/Popup.js @@ -5,9 +5,9 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; import { getFirstFocusableElement, getLastFocusableElement } from "@ui5/webcomponents-base/dist/util/FocusableElements.js"; import createStyleInHead from "@ui5/webcomponents-base/dist/util/createStyleInHead.js"; import { isTabPrevious } from "@ui5/webcomponents-base/dist/Keys.js"; +import { getNextZIndex, getFocusedElement, isFocusedElementWithinNode } from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; import PopupTemplate from "./generated/templates/PopupTemplate.lit.js"; import PopupBlockLayer from "./generated/templates/PopupBlockLayerTemplate.lit.js"; -import { getNextZIndex, getFocusedElement, isFocusedElementWithinNode } from "./popup-utils/PopupUtils.js"; import { addOpenedPopup, removeOpenedPopup } from "./popup-utils/OpenedPopupsRegistry.js"; // Styles diff --git a/packages/main/src/ResponsivePopover.js b/packages/main/src/ResponsivePopover.js index 4b5a507c07db..42fd743c7381 100644 --- a/packages/main/src/ResponsivePopover.js +++ b/packages/main/src/ResponsivePopover.js @@ -1,5 +1,5 @@ import { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; -import { getNextZIndex } from "./popup-utils/PopupUtils.js"; +import { getNextZIndex } from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; import ResponsivePopoverTemplate from "./generated/templates/ResponsivePopoverTemplate.lit.js"; import Popover from "./Popover.js"; import Dialog from "./Dialog.js"; diff --git a/packages/main/src/Toast.js b/packages/main/src/Toast.js index 3cb855fb6795..40a22742406e 100644 --- a/packages/main/src/Toast.js +++ b/packages/main/src/Toast.js @@ -1,8 +1,8 @@ import Integer from "@ui5/webcomponents-base/dist/types/Integer.js"; import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import { getNextZIndex } from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; import ToastPlacement from "./types/ToastPlacement.js"; -import { getNextZIndex } from "./popup-utils/PopupUtils.js"; // Template import ToastTemplate from "./generated/templates/ToastTemplate.lit.js"; diff --git a/packages/main/src/popup-utils/PopoverRegistry.js b/packages/main/src/popup-utils/PopoverRegistry.js index d47719e8e2fd..65d331eb5563 100644 --- a/packages/main/src/popup-utils/PopoverRegistry.js +++ b/packages/main/src/popup-utils/PopoverRegistry.js @@ -1,4 +1,4 @@ -import { isClickInRect } from "./PopupUtils.js"; +import { isClickInRect } from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; import { getOpenedPopups, addOpenedPopup, removeOpenedPopup } from "./OpenedPopupsRegistry.js"; diff --git a/packages/main/src/popup-utils/PopupUtils.js b/packages/main/src/popup-utils/PopupUtils.js index 64d63cb301d4..6104556d5e92 100644 --- a/packages/main/src/popup-utils/PopupUtils.js +++ b/packages/main/src/popup-utils/PopupUtils.js @@ -1,81 +1,17 @@ -import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js"; - -let currentZIndex = 100; - -const getFocusedElement = () => { - const element = getActiveElement(); - return (element && typeof element.focus === "function") ? element : null; -}; - -const isFocusedElementWithinNode = node => { - const fe = getFocusedElement(); - - if (fe) { - return isNodeContainedWithin(node, fe); - } - - return false; -}; - -const isNodeContainedWithin = (parent, child) => { - let currentNode = parent; - - if (currentNode.shadowRoot) { - currentNode = Array.from(currentNode.shadowRoot.children).find(n => n.localName !== "style"); - } - - if (currentNode === child) { - return true; - } - - const childNodes = currentNode.localName === "slot" ? currentNode.assignedNodes() : currentNode.children; - - if (childNodes) { - return Array.from(childNodes).some(n => isNodeContainedWithin(n, child)); - } -}; - -const isPointInRect = (x, y, rect) => { - return x >= rect.left && x <= rect.right - && y >= rect.top && y <= rect.bottom; -}; - -const isClickInRect = (event, rect) => { - let x; - let y; - - if (event.touches) { - const touch = event.touches[0]; - x = touch.clientX; - y = touch.clientY; - } else { - x = event.clientX; - y = event.clientY; - } - - return isPointInRect(x, y, rect); -}; - -const getClosedPopupParent = el => { - const parent = el.parentElement || (el.getRootNode && el.getRootNode().host); - - if (parent && ((parent.openBy && parent.isUI5Element) || (parent.open && parent.isUI5Element) || parent === document.documentElement)) { - return parent; - } - - return getClosedPopupParent(parent); -}; - - -const getNextZIndex = () => { - currentZIndex += 2; - return currentZIndex; -}; +import { + getFocusedElement, + isClickInRect, + getClosedPopupParent, + getNextZIndex, + getCurrentZIndex, + isFocusedElementWithinNode, +} from "@ui5/webcomponents-base/dist/util/PopupUtils.js"; export { getFocusedElement, isClickInRect, getClosedPopupParent, getNextZIndex, + getCurrentZIndex, isFocusedElementWithinNode, };