diff --git a/src/Components/Web.JS/src/Services/NavigationEnhancement.ts b/src/Components/Web.JS/src/Services/NavigationEnhancement.ts index 9dcd7540d866..c39a00bd0337 100644 --- a/src/Components/Web.JS/src/Services/NavigationEnhancement.ts +++ b/src/Components/Web.JS/src/Services/NavigationEnhancement.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { synchronizeDomContent } from '../Rendering/DomMerging/DomSync'; -import { attachProgrammaticEnhancedNavigationHandler, handleClickForNavigationInterception, hasInteractiveRouter, isForSamePath, isSamePageWithHash, notifyEnhancedNavigationListeners, performScrollToElementOnTheSamePage } from './NavigationUtils'; +import { attachProgrammaticEnhancedNavigationHandler, handleClickForNavigationInterception, hasInteractiveRouter, isForSamePath, notifyEnhancedNavigationListeners, performScrollToElementOnTheSamePage, isSamePageWithHash } from './NavigationUtils'; import { resetScrollAfterNextBatch, resetScrollIfNeeded } from '../Rendering/Renderer'; /* @@ -99,7 +99,7 @@ function onDocumentClick(event: MouseEvent) { handleClickForNavigationInterception(event, absoluteInternalHref => { const originalLocation = location.href; - const shouldScrollToHash = isSamePageWithHash(absoluteInternalHref); + const shouldScrollToHash = isSamePageWithHash(originalLocation, absoluteInternalHref); history.pushState(null, /* ignored title */ '', absoluteInternalHref); if (shouldScrollToHash) { @@ -120,6 +120,11 @@ function onPopState(state: PopStateEvent) { return; } + if (state.state == null && isSamePageWithHash(currentContentUrl, location.href)) { + currentContentUrl = location.href; + return; + } + // load the new page performEnhancedPageLoad(location.href, /* interceptedLink */ false); } diff --git a/src/Components/Web.JS/src/Services/NavigationManager.ts b/src/Components/Web.JS/src/Services/NavigationManager.ts index b3352b399f55..8e2de809505a 100644 --- a/src/Components/Web.JS/src/Services/NavigationManager.ts +++ b/src/Components/Web.JS/src/Services/NavigationManager.ts @@ -150,7 +150,7 @@ function performExternalNavigation(uri: string, replace: boolean) { async function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean, replace: boolean, state: string | undefined = undefined, skipLocationChangingCallback = false) { ignorePendingNavigation(); - if (isSamePageWithHash(absoluteInternalHref)) { + if (isSamePageWithHash(location.href, absoluteInternalHref)) { saveToBrowserHistory(absoluteInternalHref, replace, state); performScrollToElementOnTheSamePage(absoluteInternalHref); return; diff --git a/src/Components/Web.JS/src/Services/NavigationUtils.ts b/src/Components/Web.JS/src/Services/NavigationUtils.ts index bc58636c39c6..9976eafc898c 100644 --- a/src/Components/Web.JS/src/Services/NavigationUtils.ts +++ b/src/Components/Web.JS/src/Services/NavigationUtils.ts @@ -47,9 +47,11 @@ export function isWithinBaseUriSpace(href: string) { && (nextChar === '' || nextChar === '/' || nextChar === '?' || nextChar === '#'); } -export function isSamePageWithHash(absoluteHref: string): boolean { - const url = new URL(absoluteHref); - return url.hash !== '' && location.origin === url.origin && location.pathname === url.pathname && location.search === url.search; +export function isSamePageWithHash(oldUrl: string, newUrl: string): boolean { + const a = new URL(oldUrl); + const b = new URL(newUrl); + return a.origin === b.origin && a.pathname === b.pathname + && a.search === b.search && b.hash !== ''; } export function isForSamePath(url1: string, url2: string) { diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs index 49ca00e06915..c11a30786c2e 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.InternalTesting; using OpenQA.Selenium; using OpenQA.Selenium.BiDi.Communication; +using OpenQA.Selenium.DevTools; using OpenQA.Selenium.Support.Extensions; using TestServer; using Xunit.Abstractions; @@ -195,6 +196,40 @@ public void CanScrollToHashWithoutPerformingFullNavigation() .EndsWith("scroll-to-hash", StringComparison.Ordinal)); } + [Fact] + public void NonEnhancedNavCanScrollToHashWithoutFetchingPageAnchor() + { + Navigate($"{ServerPathBase}/nav/scroll-to-hash"); + var originalTextElem = Browser.Exists(By.CssSelector("#anchor #text")); + Browser.Equal("Text", () => originalTextElem.Text); + + Browser.Exists(By.CssSelector("#anchor #scroll-anchor")).Click(); + Browser.True(() => Browser.GetScrollY() > 500); + Browser.True(() => Browser + .Exists(By.CssSelector("#anchor #uri-on-page-load")) + .GetDomAttribute("data-value") + .EndsWith("scroll-to-hash", StringComparison.Ordinal)); + + Browser.Equal("Text", () => originalTextElem.Text); + } + + [Fact] + public void NonEnhancedNavCanScrollToHashWithoutFetchingPageNavLink() + { + Navigate($"{ServerPathBase}/nav/scroll-to-hash"); + var originalTextElem = Browser.Exists(By.CssSelector("#navlink #text")); + Browser.Equal("Text", () => originalTextElem.Text); + + Browser.Exists(By.CssSelector("#navlink #scroll-anchor")).Click(); + Browser.True(() => Browser.GetScrollY() > 500); + Browser.True(() => Browser + .Exists(By.CssSelector("#navlink #uri-on-page-load")) + .GetDomAttribute("data-value") + .EndsWith("scroll-to-hash", StringComparison.Ordinal)); + + Browser.Equal("Text", () => originalTextElem.Text); + } + [Theory] [InlineData("server")] [InlineData("webassembly")] diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/PageForScrollingToHash.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/PageForScrollingToHash.razor index 102e18a84807..22597ce37be3 100644 --- a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/PageForScrollingToHash.razor +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/PageForScrollingToHash.razor @@ -1,6 +1,7 @@ @page "/nav/scroll-to-hash" @attribute [StreamRendering] @inject NavigationManager NavigationManager +@using Microsoft.AspNetCore.Components.Forms Page for scrolling to hash @@ -13,6 +14,18 @@

+
+ Scroll via anchor + +

Text

+
+ + +
spacer
@if (showContent)