Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions e2e/app/hash-nav/+page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ export default () => (
Deep Dive Target
</h3>
<p data-testid="hash-nav-target-copy">The target should be scrolled into view.</p>
<p>
<Link href="/counter">Open counter after deep scroll</Link>
</p>
</section>
)
16 changes: 16 additions & 0 deletions e2e/test/example.dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,22 @@ test.describe('example app in dev mode', () => {
.toBe(true)
})

test('resets scroll for client-side Link navigation without a hash target', async ({ page }) => {
await page.goto('/hash-nav')
await waitForResumedRoute(page)

await page.getByRole('link', { name: 'Jump to deep dive' }).click()

await expect(page).toHaveURL(/\/hash-nav#deep-dive$/)
await expect.poll(async () => await page.evaluate(() => window.scrollY)).toBeGreaterThan(1000)

await page.getByRole('link', { name: 'Open counter after deep scroll' }).click()

await expect(page).toHaveURL(/\/counter$/)
await expect.poll(async () => await page.evaluate(() => window.scrollY)).toBe(0)
await expect(page.getByText('Counter page')).toBeVisible()
})

test('updates shared layout-owned location state on Link navigation', async ({ page }) => {
await page.goto('/layout-location/overview')

Expand Down
19 changes: 16 additions & 3 deletions packages/eclipsa/core/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8927,8 +8927,17 @@ const commitBrowserNavigation = (doc: Document, url: URL, mode: NavigationMode)
doc.defaultView.history.pushState(null, '', url.href)
}

const scrollToUrlFragment = (doc: Document, url: URL) => {
const scrollToUrlTarget = (
doc: Document,
url: URL,
options?: {
resetScroll?: boolean
},
) => {
if (!url.hash) {
if (options?.resetScroll) {
doc.defaultView?.scrollTo(0, 0)
}
return
}

Expand Down Expand Up @@ -9224,7 +9233,9 @@ const commitRouteNavigation = (
if (options?.writeLocation !== false) {
writeRouterLocation(router, url)
}
scrollToUrlFragment(doc, url)
scrollToUrlTarget(doc, url, {
resetScroll: mode !== 'pop',
})
Comment on lines +9253 to +9255

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve scroll during forced route refreshes

When a content/HMR route refresh calls refreshRouteContainer, it re-enters navigateContainer with force: true and mode: 'replace' for the current URL. For hash-less pages this new mode !== 'pop' reset makes an in-place refresh jump to the top, whereas the previous no-hash path kept the user's scroll and refreshRouteContainerForHmr still rerenders without scrolling. This only shows up on forced refreshes of the current route, but it makes dev/content updates disruptive for long pages.

Useful? React with 👍 / 👎.

}

const renderAndCommitRouteNavigation = (
Expand Down Expand Up @@ -9391,7 +9402,9 @@ const navigateContainer = async (
if (nextHref !== currentHref) {
commitBrowserNavigation(doc, url, mode)
writeRouterLocation(router, url)
scrollToUrlFragment(doc, url)
scrollToUrlTarget(doc, url, {
resetScroll: mode !== 'pop',
})
}
return
}
Expand Down
Loading