From 3db8ef004f4fcff1c5e7bf99c6eb87755d9760a3 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Tue, 30 Sep 2025 03:59:07 +0200 Subject: [PATCH] docs(docs-infra): fix app-scroller offset. (#64144) Guide heading need different scroll offsets depending on the width of the screen. (The menu position varies). PR Close #64144 --- adev/shared-docs/styles/_media-queries.scss | 28 +++++++++++----- adev/shared-docs/styles/global-styles.scss | 1 + adev/src/app/app-scroller.ts | 37 ++++++++++++++++++--- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/adev/shared-docs/styles/_media-queries.scss b/adev/shared-docs/styles/_media-queries.scss index a1ad91b46af..ac5866cee72 100644 --- a/adev/shared-docs/styles/_media-queries.scss +++ b/adev/shared-docs/styles/_media-queries.scss @@ -5,6 +5,18 @@ $screen-lg: 1200px; $screen-xl: 1430px; $screen-xxl: 1800px; +@mixin breakpoints-vars { + // The app-scroller also relies on these values to determine the scroll offset. + :root { + --screen-xs: #{$screen-xs}; + --screen-sm: #{$screen-sm}; + --screen-md: #{$screen-md}; + --screen-lg: #{$screen-lg}; + --screen-xl: #{$screen-xl}; + --screen-xxl: #{$screen-xxl}; + } +} + @mixin for-phone-only { @media (max-width: $screen-xs) { @content; @@ -12,43 +24,43 @@ $screen-xxl: 1800px; } @mixin for-tablet-portrait-up { - @media (min-width: calc($screen-xs + .01px)) { + @media (min-width: calc($screen-xs + 0.01px)) { @content; } } @mixin for-tablet { - @media (min-width: calc($screen-xs + .01px)) and (max-width: $screen-md) { + @media (min-width: calc($screen-xs + 0.01px)) and (max-width: $screen-md) { @content; } } @mixin for-tablet-up { - @media (min-width: calc($screen-sm + .01px)) { + @media (min-width: calc($screen-sm + 0.01px)) { @content; } } @mixin for-tablet-landscape-up { - @media (min-width: calc($screen-md + .01px)) { + @media (min-width: calc($screen-md + 0.01px)) { @content; } } @mixin for-desktop-up { - @media (min-width: calc($screen-lg + .01px)) { + @media (min-width: calc($screen-lg + 0.01px)) { @content; } } @mixin for-large-desktop-up { - @media (min-width: calc($screen-xl + .01px)) { + @media (min-width: calc($screen-xl + 0.01px)) { @content; } } @mixin for-extra-large-desktop-up { - @media (min-width: calc($screen-xxl + .01px)) { + @media (min-width: calc($screen-xxl + 0.01px)) { @content; } } @@ -75,4 +87,4 @@ $screen-xxl: 1800px; @media (max-width: $screen-sm) { @content; } -} \ No newline at end of file +} diff --git a/adev/shared-docs/styles/global-styles.scss b/adev/shared-docs/styles/global-styles.scss index 76c2b0940f7..ab4ac9141a4 100644 --- a/adev/shared-docs/styles/global-styles.scss +++ b/adev/shared-docs/styles/global-styles.scss @@ -41,6 +41,7 @@ @include api-item-label.api-item-label(); @include faceted-list.faceted-list(); +@include mq.breakpoints-vars(); @include mq.for-phone-only(); @include mq.for-tablet-portrait-up(); @include mq.for-tablet-landscape-up(); diff --git a/adev/src/app/app-scroller.ts b/adev/src/app/app-scroller.ts index 9ee57fd0414..48603e4a411 100644 --- a/adev/src/app/app-scroller.ts +++ b/adev/src/app/app-scroller.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -import {ViewportScroller} from '@angular/common'; +import {isPlatformBrowser, ViewportScroller} from '@angular/common'; import { Injectable, inject, @@ -13,6 +13,8 @@ import { afterNextRender, EnvironmentInjector, Injector, + DestroyRef, + PLATFORM_ID, } from '@angular/core'; import {Scroll, Router} from '@angular/router'; import {filter, firstValueFrom, map, switchMap, tap} from 'rxjs'; @@ -23,6 +25,7 @@ export class AppScroller { private readonly viewportScroller = inject(ViewportScroller); private readonly appRef = inject(ApplicationRef); private readonly injector = inject(EnvironmentInjector); + private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); private _lastScrollEvent?: Scroll; private canScroll = false; @@ -33,6 +36,26 @@ export class AppScroller { } constructor() { + if (this.isBrowser) { + this.setupScrollRestoration(); + } + } + + private setupScrollRestoration(): void { + let windowWidth = window.innerWidth; + // Setting up a ResizeObserver to update the width on resize. (without triggering a reflow) + const windowSizeObserver = new ResizeObserver((entries) => { + windowWidth = entries[0].contentRect.width; + }); + windowSizeObserver.observe(document.documentElement); + inject(DestroyRef).onDestroy(() => windowSizeObserver.disconnect()); + + const root = document.documentElement; // or any element with the variable + const styles = getComputedStyle(root); + // slice to drop the 'px' + const xsBreakpoint = +styles.getPropertyValue('--screen-xs').slice(0, -2); + const mdBreakpoint = +styles.getPropertyValue('--screen-md').slice(0, -2); + this.viewportScroller.setHistoryScrollRestoration('manual'); this.router.events .pipe( @@ -62,12 +85,16 @@ export class AppScroller { this.scroll(); }); - // This value is primarily intended to offset the scroll position on mobile when the menu is on the top. - // But on desktop, it doesn't hurt to have a small offset either. - this.viewportScroller.setOffset([0, 64]); + if (windowWidth < xsBreakpoint) { + this.viewportScroller.setOffset([0, 64]); + } else if (windowWidth <= mdBreakpoint) { + this.viewportScroller.setOffset([0, 140]); + } else { + this.viewportScroller.setOffset([0, 24]); + } } - scroll(injector?: Injector) { + private scroll(injector?: Injector) { if (!this._lastScrollEvent || !this.canScroll) { return; }