diff --git a/app/assets/javascripts/discourse/app/components/site-header.js b/app/assets/javascripts/discourse/app/components/site-header.js index 66e85aa7660..8638b540c3b 100644 --- a/app/assets/javascripts/discourse/app/components/site-header.js +++ b/app/assets/javascripts/discourse/app/components/site-header.js @@ -54,6 +54,12 @@ const SiteHeaderComponent = MountWidget.extend( }, _animateOpening(panel) { + window.requestAnimationFrame( + this._setAnimateOpeningProperties.bind(this, panel) + ); + }, + + _setAnimateOpeningProperties(panel) { const headerCloak = document.querySelector(".header-cloak"); panel.classList.add("animate"); headerCloak.classList.add("animate"); @@ -67,13 +73,16 @@ const SiteHeaderComponent = MountWidget.extend( }, _animateClosing(panel, menuOrigin) { - const windowWidth = document.body.offsetWidth; this._animate = true; const headerCloak = document.querySelector(".header-cloak"); panel.classList.add("animate"); headerCloak.classList.add("animate"); - const offsetDirection = menuOrigin === "left" ? -1 : 1; - panel.style.setProperty("--offset", `${offsetDirection * windowWidth}px`); + if (menuOrigin === "left") { + panel.style.setProperty("--offset", `-100vw`); + } else { + panel.style.setProperty("--offset", `100vw`); + } + headerCloak.style.setProperty("--opacity", 0); this._scheduledRemoveAnimate = discourseLater(() => { panel.classList.remove("animate"); @@ -365,7 +374,6 @@ const SiteHeaderComponent = MountWidget.extend( return; } - const windowWidth = document.body.offsetWidth; const viewMode = this.site.mobileView || this.site.narrowDesktopView ? "slide-in" @@ -374,9 +382,6 @@ const SiteHeaderComponent = MountWidget.extend( menuPanels.forEach((panel) => { const headerCloak = document.querySelector(".header-cloak"); let width = parseInt(panel.getAttribute("data-max-width"), 10) || 300; - if (windowWidth - width < 50) { - width = windowWidth - 50; - } if (this._panMenuOffset) { this._panMenuOffset = -width; } @@ -384,77 +389,23 @@ const SiteHeaderComponent = MountWidget.extend( panel.classList.remove("drop-down"); panel.classList.remove("slide-in"); panel.classList.add(viewMode); + if (this._animate || this._panMenuOffset !== 0) { if ( (this.site.mobileView || this.site.narrowDesktopView) && panel.parentElement.classList.contains(this._leftMenuClass()) ) { this._panMenuOrigin = "left"; - panel.style.setProperty("--offset", `${-windowWidth}px`); + panel.style.setProperty("--offset", `-100vw`); } else { this._panMenuOrigin = "right"; - panel.style.setProperty("--offset", `${windowWidth}px`); + panel.style.setProperty("--offset", `100vw`); } headerCloak.style.setProperty("--opacity", 0); } - const panelBody = panel.querySelector(".panel-body"); - - // We use a mutationObserver to check for style changes, so it's important - // we don't set it if it doesn't change. Same goes for the panelBody! - - if (!this.site.mobileView && !this.site.narrowDesktopView) { - const buttonPanel = document.querySelectorAll("header ul.icons"); - if (buttonPanel.length === 0) { - return; - } - - // These values need to be set here, not in the css file - this is to deal with the - // possibility of the window being resized and the menu changing from .slide-in to .drop-down. - if (panel.style.top !== "100%" || panel.style.height !== "auto") { - panel.style.setProperty("top", "100%"); - panel.style.setProperty("height", "auto"); - } - } else { + if (viewMode === "slide-in") { headerCloak.style.display = "block"; - - const menuTop = headerTop(); - - const winHeightOffset = this.currentUser?.redesigned_user_menu_enabled - ? 0 - : 16; - let initialWinHeight = window.innerHeight; - const winHeight = initialWinHeight - winHeightOffset; - - let height = winHeight - menuTop; - - const isIPadApp = document.body.classList.contains("footer-nav-ipad"), - heightProp = isIPadApp ? "max-height" : "height", - iPadOffset = 10; - - if (isIPadApp) { - height = winHeight - menuTop - iPadOffset; - } - - if (panelBody.style.height !== "100%") { - panelBody.style.setProperty("height", "100%"); - } - if ( - panel.style.top !== `${menuTop}px` || - panel.style[heightProp] !== `${height}px` - ) { - panel.style.top = `${menuTop}px`; - panel.style.setProperty(heightProp, `${height}px`); - if (headerCloak) { - headerCloak.style.top = `${menuTop}px`; - } - } - } - - // TODO: remove the if condition when redesigned_user_menu_enabled is - // removed - if (!panel.classList.contains("revamped")) { - panel.style.setProperty("width", `${width}px`); } if (this._animate) { this._animateOpening(panel); @@ -488,12 +439,20 @@ export default SiteHeaderComponent.extend({ this.appEvents.on("site-header:force-refresh", this, "queueRerender"); - const header = document.querySelector(".d-header-wrap"); - if (header) { + const headerWrap = document.querySelector(".d-header-wrap"); + let header; + if (headerWrap) { schedule("afterRender", () => { + header = headerWrap.querySelector("header.d-header"); + const headerOffset = headerWrap.offsetHeight; + const headerTop = header.offsetTop; document.documentElement.style.setProperty( "--header-offset", - `${header.offsetHeight}px` + `${headerOffset}px` + ); + document.documentElement.style.setProperty( + "--header-top", + `${headerTop}px` ); }); } @@ -502,15 +461,21 @@ export default SiteHeaderComponent.extend({ this._resizeObserver = new ResizeObserver((entries) => { for (let entry of entries) { if (entry.contentRect) { + const headerOffset = entry.contentRect.height; + const headerTop = header.offsetTop; document.documentElement.style.setProperty( "--header-offset", - entry.contentRect.height + "px" + `${headerOffset}px` + ); + document.documentElement.style.setProperty( + "--header-top", + `${headerTop}px` ); } } }); - this._resizeObserver.observe(header); + this._resizeObserver.observe(headerWrap); } }, @@ -521,8 +486,3 @@ export default SiteHeaderComponent.extend({ this.appEvents.off("site-header:force-refresh", this, "queueRerender"); }, }); - -export function headerTop() { - const header = document.querySelector("header.d-header"); - return header.offsetTop ? header.offsetTop : 0; -} diff --git a/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js b/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js index 6fc8ea63c20..7b0b70539fb 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/mobile-pan-test.js @@ -7,6 +7,10 @@ import { import { click, triggerEvent, visit } from "@ember/test-helpers"; async function triggerSwipeStart(touchTarget) { + const emberTesting = document.querySelector("#ember-testing-container"); + emberTesting.scrollTop = 0; + emberTesting.scrollLeft = 0; + // Other tests are shown in a transformed viewport, and this is a multiple for the offsets let scale = parseFloat( window diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index e64cf59f103..ab6d7d781fb 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -29,6 +29,8 @@ overflow: hidden; display: flex; flex-direction: column; + box-sizing: border-box; + hr { margin: 3px 0; } @@ -689,7 +691,7 @@ body.footer-nav-ipad { background-color: black; --opacity: 0.5; opacity: var(--opacity); - top: 0; + top: var(--header-top); left: 0; display: none; touch-action: pan-y pinch-zoom; @@ -702,6 +704,23 @@ body.footer-nav-ipad { } .menu-panel.slide-in { + top: var(--header-top); + box-sizing: border-box; + + /* Use dvh where supported, with fallback to vh */ + --100dvh: 100vh; + --100dvh: 100dvh; + + --base-height: calc( + var(--100dvh) - var(--header-top) - env(safe-area-inset-bottom, 0px) + ); + + height: var(--base-height); + + body.footer-nav-ipad & { + height: calc(var(--base-height) - var(--footer-nav-height)); + } + transform: translateX(var(--offset)); @media (prefers-reduced-motion: no-preference) { &.animate {