diff --git a/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs b/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs index 4997d3e8c3f..b8071fa9fc0 100644 --- a/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs +++ b/app/assets/javascripts/discourse/app/components/glimmer-site-header.gjs @@ -22,6 +22,7 @@ import Header from "./header"; let _menuPanelClassesToForceDropdown = []; const PANEL_WIDTH = 340; +const DEBOUNCE_HEADER_DELAY = 10; export default class GlimmerSiteHeader extends Component { @service appEvents; @@ -36,6 +37,7 @@ export default class GlimmerSiteHeader extends Component { @tracked _dockedHeader = false; _animate = false; _headerWrap; + _mainOutletWrapper; _swipeMenuOrigin; _applicationElement; _resizeObserver; @@ -69,42 +71,51 @@ export default class GlimmerSiteHeader extends Component { } @bind - updateHeaderOffset() { - // Safari likes overscolling the page (on both iOS and macOS). - // This shows up as a negative value in window.scrollY. - // We can use this to offset the headerWrap's top offset to avoid - // jitteriness and bad positioning. - const windowOverscroll = Math.min(0, window.scrollY); - - // The headerWrap's top offset can also be a negative value on Safari, - // because of the changing height of the viewport (due to the URL bar). - // For our use case, it's best to ensure this is clamped to 0. - const headerWrapTop = Math.max( + updateOffsets() { + // clamping to 0 to prevent negative values (hello, Safari) + const headerWrapBottom = Math.max( 0, - Math.floor(this._headerWrap.getBoundingClientRect().top) + Math.floor(this._headerWrap.getBoundingClientRect().bottom) + ); + + let mainOutletOffsetTop = Math.max( + 0, + Math.floor(this._mainOutletWrapper.getBoundingClientRect().top) - + headerWrapBottom ); - let offsetTop = headerWrapTop + windowOverscroll; if (DEBUG && isTesting()) { - offsetTop -= document + mainOutletOffsetTop -= document .getElementById("ember-testing-container") .getBoundingClientRect().top; - offsetTop -= 1; // For 1px border on testing container + mainOutletOffsetTop -= 1; // For 1px border on testing container } - const documentStyle = document.documentElement.style; - const currentValue = - parseInt(documentStyle.getPropertyValue("--header-offset"), 10) || 0; - const newValue = this._headerWrap.offsetHeight + offsetTop; - if (currentValue !== newValue) { - documentStyle.setProperty("--header-offset", `${newValue}px`); + const docStyle = document.documentElement.style; + const currentHeaderOffset = + parseInt(docStyle.getPropertyValue("--header-offset"), 10) || 0; + const newHeaderOffset = headerWrapBottom; + if (currentHeaderOffset !== newHeaderOffset) { + docStyle.setProperty("--header-offset", `${newHeaderOffset}px`); + } + + const currentMainOutletOffset = + parseInt(docStyle.getPropertyValue("--main-outlet-offset"), 10) || 0; + const newMainOutletOffset = headerWrapBottom + mainOutletOffsetTop; + if (currentMainOutletOffset !== newMainOutletOffset) { + docStyle.setProperty("--main-outlet-offset", `${newMainOutletOffset}px`); } } + @debounce(DEBOUNCE_HEADER_DELAY) + _recalculateHeaderOffset() { + this._scheduleUpdateOffsets(); + } + @bind - _onScroll() { - schedule("afterRender", this.updateHeaderOffset); + _scheduleUpdateOffsets() { + schedule("afterRender", this.updateOffsets); } @action @@ -119,17 +130,13 @@ export default class GlimmerSiteHeader extends Component { } this._headerWrap = document.querySelector(".d-header-wrap"); + this._mainOutletWrapper = document.querySelector("#main-outlet-wrapper"); if (this._headerWrap) { schedule("afterRender", () => { this.headerElement = this._headerWrap.querySelector("header.d-header"); - this.updateHeaderOffset(); - document.documentElement.style.setProperty( - "--header-top", - `${this.headerElement.offsetTop}px` - ); }); - window.addEventListener("scroll", this._onScroll, { + window.addEventListener("scroll", this._recalculateHeaderOffset, { passive: true, }); @@ -137,20 +144,11 @@ export default class GlimmerSiteHeader extends Component { const dirs = ["up", "down"]; this._itsatrap.bind(dirs, (e) => this._handleArrowKeysNav(e)); - this._resizeObserver = new ResizeObserver((entries) => { - for (let entry of entries) { - if (entry.contentRect) { - const headerTop = this.headerElement?.offsetTop; - document.documentElement.style.setProperty( - "--header-top", - `${headerTop}px` - ); - this.updateHeaderOffset(); - } - } + this._resizeObserver = new ResizeObserver(() => { + this._recalculateHeaderOffset(); }); - this._resizeObserver.observe(this._headerWrap); + this._resizeObserver.observe(document.querySelector(".discourse-root")); } } @@ -410,7 +408,7 @@ export default class GlimmerSiteHeader extends Component { this._itsatrap?.destroy(); this._itsatrap = null; - window.removeEventListener("scroll", this._onScroll); + window.removeEventListener("scroll", this._recalculateHeaderOffset); this._resizeObserver?.disconnect(); } diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 1bb3ae26d1c..91ef237fcef 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -16,7 +16,7 @@ // positions are relative to the .d-header .panel div top: 100%; // directly underneath .panel right: -10px; // 10px to the right of .panel - adjust as needed - max-height: 80vh; + max-height: calc(100dvh - var(--header-offset) - 1em); border-radius: var(--d-border-radius-large); } @@ -660,14 +660,14 @@ width: 100%; position: fixed; background-color: rgba(0, 0, 0, 0.3); - top: var(--header-top); + top: 0; left: 0; display: none; touch-action: pan-y pinch-zoom; } .menu-panel.slide-in { - top: var(--header-top); + top: 0; box-sizing: border-box; // ensure there's always space to click outside on tiny devices max-width: 90vw; @@ -678,11 +678,5 @@ } box-shadow: 0px 0 30px -2px rgba(0, 0, 0, 0.5); - --base-height: calc(var(--100dvh) - var(--header-top)); - - height: var(--base-height); - - html.footer-nav-ipad & { - height: calc(var(--base-height) - var(--footer-nav-height)); - } + height: var(--100dvh); } diff --git a/app/assets/stylesheets/common/base/sidebar.scss b/app/assets/stylesheets/common/base/sidebar.scss index 42135629be5..91e0e31e8e9 100644 --- a/app/assets/stylesheets/common/base/sidebar.scss +++ b/app/assets/stylesheets/common/base/sidebar.scss @@ -45,7 +45,7 @@ display: flex; grid-area: sidebar; position: sticky; - top: var(--header-offset); + top: var(--main-outlet-offset); background: var(--d-sidebar-background); @include unselectable; @@ -57,7 +57,7 @@ } height: calc( - var(--composer-vh, var(--1dvh)) * 100 - var(--header-offset, 0px) + var(--composer-vh, var(--1dvh)) * 100 - var(--main-outlet-offset, 0px) ); align-self: start; diff --git a/app/assets/stylesheets/desktop/menu-panel.scss b/app/assets/stylesheets/desktop/menu-panel.scss index b691f3df7f0..dfb035f2c72 100644 --- a/app/assets/stylesheets/desktop/menu-panel.scss +++ b/app/assets/stylesheets/desktop/menu-panel.scss @@ -5,7 +5,6 @@ &.drop-down { .panel-body { - max-height: calc(100vh - var(--header-offset)); max-width: calc(100vw - 2em); } } diff --git a/plugins/chat/assets/stylesheets/common/base-common.scss b/plugins/chat/assets/stylesheets/common/base-common.scss index 31e1a09cff7..aea8165b6d0 100644 --- a/plugins/chat/assets/stylesheets/common/base-common.scss +++ b/plugins/chat/assets/stylesheets/common/base-common.scss @@ -191,7 +191,7 @@ body.has-full-page-chat { .c-navbar-container { position: sticky; - top: var(--header-offset); + top: var(--main-outlet-offset); } .chat-messages-scroller { diff --git a/plugins/chat/assets/stylesheets/common/chat-composer.scss b/plugins/chat/assets/stylesheets/common/chat-composer.scss index bbc48307d72..7994c48161f 100644 --- a/plugins/chat/assets/stylesheets/common/chat-composer.scss +++ b/plugins/chat/assets/stylesheets/common/chat-composer.scss @@ -87,7 +87,7 @@ resize: none; max-height: calc( ( - var(--100dvh) - var(--header-offset, 0px) - + var(--100dvh) - var(--main-outlet-offset, 0px) - var(--chat-header-offset, 0px) ) / 100 * 25 ); diff --git a/plugins/chat/assets/stylesheets/common/chat-height-mixin.scss b/plugins/chat/assets/stylesheets/common/chat-height-mixin.scss index 1092dc8f8dd..5966430b58b 100644 --- a/plugins/chat/assets/stylesheets/common/chat-height-mixin.scss +++ b/plugins/chat/assets/stylesheets/common/chat-height-mixin.scss @@ -2,7 +2,8 @@ // desktop and mobile // -1px is for the bottom border of the chat navbar $base-height: calc( - var(--composer-vh, 1vh) * 100 - var(--header-offset, 0px) - 1px - $inset + var(--composer-vh, 1vh) * 100 - var(--main-outlet-offset, 0px) - 1px - + $inset ); height: calc($base-height - var(--composer-height, 0px)); diff --git a/plugins/chat/assets/stylesheets/desktop/chat-index-full-page.scss b/plugins/chat/assets/stylesheets/desktop/chat-index-full-page.scss index 1fb47090f77..cfcd6255e48 100644 --- a/plugins/chat/assets/stylesheets/desktop/chat-index-full-page.scss +++ b/plugins/chat/assets/stylesheets/desktop/chat-index-full-page.scss @@ -1,7 +1,7 @@ .has-full-page-chat:not(.discourse-sidebar) { .full-page-chat { .channels-list { - height: calc(100vh - var(--header-offset)); + height: calc(100vh - var(--main-outlet-offset)); border-right: 1px solid var(--primary-low); background: var(--secondary); overflow-y: auto;