Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/soft-badgers-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Preserve existing nested scroll positions across SPA navigations that create new restoration keys.
86 changes: 86 additions & 0 deletions e2e/react-start/scroll-restoration/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as testsWithSearchRouteImport } from './routes/(tests)/with-search'
import { Route as testsWithLoaderRouteImport } from './routes/(tests)/with-loader'
import { Route as testsNormalPageRouteImport } from './routes/(tests)/normal-page'
import { Route as testsNestedScrollSearchRouteImport } from './routes/(tests)/nested-scroll-search'
import { Route as testsNestedScrollCarryOverBRouteImport } from './routes/(tests)/nested-scroll-carry-over-b'
import { Route as testsNestedScrollCarryOverARouteImport } from './routes/(tests)/nested-scroll-carry-over-a'
import { Route as testsNestedScrollAwayRouteImport } from './routes/(tests)/nested-scroll-away'
import { Route as testsHashScrollReproRouteImport } from './routes/(tests)/hash-scroll-repro'
import { Route as testsHashScrollAboutRouteImport } from './routes/(tests)/hash-scroll-about'

Expand All @@ -36,6 +40,28 @@ const testsNormalPageRoute = testsNormalPageRouteImport.update({
path: '/normal-page',
getParentRoute: () => rootRouteImport,
} as any)
const testsNestedScrollSearchRoute = testsNestedScrollSearchRouteImport.update({
id: '/(tests)/nested-scroll-search',
path: '/nested-scroll-search',
getParentRoute: () => rootRouteImport,
} as any)
const testsNestedScrollCarryOverBRoute =
testsNestedScrollCarryOverBRouteImport.update({
id: '/(tests)/nested-scroll-carry-over-b',
path: '/nested-scroll-carry-over-b',
getParentRoute: () => rootRouteImport,
} as any)
const testsNestedScrollCarryOverARoute =
testsNestedScrollCarryOverARouteImport.update({
id: '/(tests)/nested-scroll-carry-over-a',
path: '/nested-scroll-carry-over-a',
getParentRoute: () => rootRouteImport,
} as any)
const testsNestedScrollAwayRoute = testsNestedScrollAwayRouteImport.update({
id: '/(tests)/nested-scroll-away',
path: '/nested-scroll-away',
getParentRoute: () => rootRouteImport,
} as any)
const testsHashScrollReproRoute = testsHashScrollReproRouteImport.update({
id: '/(tests)/hash-scroll-repro',
path: '/hash-scroll-repro',
Expand All @@ -51,6 +77,10 @@ export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/hash-scroll-about': typeof testsHashScrollAboutRoute
'/hash-scroll-repro': typeof testsHashScrollReproRoute
'/nested-scroll-away': typeof testsNestedScrollAwayRoute
'/nested-scroll-carry-over-a': typeof testsNestedScrollCarryOverARoute
'/nested-scroll-carry-over-b': typeof testsNestedScrollCarryOverBRoute
'/nested-scroll-search': typeof testsNestedScrollSearchRoute
'/normal-page': typeof testsNormalPageRoute
'/with-loader': typeof testsWithLoaderRoute
'/with-search': typeof testsWithSearchRoute
Expand All @@ -59,6 +89,10 @@ export interface FileRoutesByTo {
'/': typeof IndexRoute
'/hash-scroll-about': typeof testsHashScrollAboutRoute
'/hash-scroll-repro': typeof testsHashScrollReproRoute
'/nested-scroll-away': typeof testsNestedScrollAwayRoute
'/nested-scroll-carry-over-a': typeof testsNestedScrollCarryOverARoute
'/nested-scroll-carry-over-b': typeof testsNestedScrollCarryOverBRoute
'/nested-scroll-search': typeof testsNestedScrollSearchRoute
'/normal-page': typeof testsNormalPageRoute
'/with-loader': typeof testsWithLoaderRoute
'/with-search': typeof testsWithSearchRoute
Expand All @@ -68,6 +102,10 @@ export interface FileRoutesById {
'/': typeof IndexRoute
'/(tests)/hash-scroll-about': typeof testsHashScrollAboutRoute
'/(tests)/hash-scroll-repro': typeof testsHashScrollReproRoute
'/(tests)/nested-scroll-away': typeof testsNestedScrollAwayRoute
'/(tests)/nested-scroll-carry-over-a': typeof testsNestedScrollCarryOverARoute
'/(tests)/nested-scroll-carry-over-b': typeof testsNestedScrollCarryOverBRoute
'/(tests)/nested-scroll-search': typeof testsNestedScrollSearchRoute
'/(tests)/normal-page': typeof testsNormalPageRoute
'/(tests)/with-loader': typeof testsWithLoaderRoute
'/(tests)/with-search': typeof testsWithSearchRoute
Expand All @@ -78,6 +116,10 @@ export interface FileRouteTypes {
| '/'
| '/hash-scroll-about'
| '/hash-scroll-repro'
| '/nested-scroll-away'
| '/nested-scroll-carry-over-a'
| '/nested-scroll-carry-over-b'
| '/nested-scroll-search'
| '/normal-page'
| '/with-loader'
| '/with-search'
Expand All @@ -86,6 +128,10 @@ export interface FileRouteTypes {
| '/'
| '/hash-scroll-about'
| '/hash-scroll-repro'
| '/nested-scroll-away'
| '/nested-scroll-carry-over-a'
| '/nested-scroll-carry-over-b'
| '/nested-scroll-search'
| '/normal-page'
| '/with-loader'
| '/with-search'
Expand All @@ -94,6 +140,10 @@ export interface FileRouteTypes {
| '/'
| '/(tests)/hash-scroll-about'
| '/(tests)/hash-scroll-repro'
| '/(tests)/nested-scroll-away'
| '/(tests)/nested-scroll-carry-over-a'
| '/(tests)/nested-scroll-carry-over-b'
| '/(tests)/nested-scroll-search'
| '/(tests)/normal-page'
| '/(tests)/with-loader'
| '/(tests)/with-search'
Expand All @@ -103,6 +153,10 @@ export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
testsHashScrollAboutRoute: typeof testsHashScrollAboutRoute
testsHashScrollReproRoute: typeof testsHashScrollReproRoute
testsNestedScrollAwayRoute: typeof testsNestedScrollAwayRoute
testsNestedScrollCarryOverARoute: typeof testsNestedScrollCarryOverARoute
testsNestedScrollCarryOverBRoute: typeof testsNestedScrollCarryOverBRoute
testsNestedScrollSearchRoute: typeof testsNestedScrollSearchRoute
testsNormalPageRoute: typeof testsNormalPageRoute
testsWithLoaderRoute: typeof testsWithLoaderRoute
testsWithSearchRoute: typeof testsWithSearchRoute
Expand Down Expand Up @@ -138,6 +192,34 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof testsNormalPageRouteImport
parentRoute: typeof rootRouteImport
}
'/(tests)/nested-scroll-search': {
id: '/(tests)/nested-scroll-search'
path: '/nested-scroll-search'
fullPath: '/nested-scroll-search'
preLoaderRoute: typeof testsNestedScrollSearchRouteImport
parentRoute: typeof rootRouteImport
}
'/(tests)/nested-scroll-carry-over-b': {
id: '/(tests)/nested-scroll-carry-over-b'
path: '/nested-scroll-carry-over-b'
fullPath: '/nested-scroll-carry-over-b'
preLoaderRoute: typeof testsNestedScrollCarryOverBRouteImport
parentRoute: typeof rootRouteImport
}
'/(tests)/nested-scroll-carry-over-a': {
id: '/(tests)/nested-scroll-carry-over-a'
path: '/nested-scroll-carry-over-a'
fullPath: '/nested-scroll-carry-over-a'
preLoaderRoute: typeof testsNestedScrollCarryOverARouteImport
parentRoute: typeof rootRouteImport
}
'/(tests)/nested-scroll-away': {
id: '/(tests)/nested-scroll-away'
path: '/nested-scroll-away'
fullPath: '/nested-scroll-away'
preLoaderRoute: typeof testsNestedScrollAwayRouteImport
parentRoute: typeof rootRouteImport
}
'/(tests)/hash-scroll-repro': {
id: '/(tests)/hash-scroll-repro'
path: '/hash-scroll-repro'
Expand All @@ -159,6 +241,10 @@ const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
testsHashScrollAboutRoute: testsHashScrollAboutRoute,
testsHashScrollReproRoute: testsHashScrollReproRoute,
testsNestedScrollAwayRoute: testsNestedScrollAwayRoute,
testsNestedScrollCarryOverARoute: testsNestedScrollCarryOverARoute,
testsNestedScrollCarryOverBRoute: testsNestedScrollCarryOverBRoute,
testsNestedScrollSearchRoute: testsNestedScrollSearchRoute,
testsNormalPageRoute: testsNormalPageRoute,
testsWithLoaderRoute: testsWithLoaderRoute,
testsWithSearchRoute: testsWithSearchRoute,
Expand Down
1 change: 1 addition & 0 deletions e2e/react-start/scroll-restoration/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export function getRouter() {
const router = createRouter({
routeTree,
scrollRestoration: true,
scrollToTopSelectors: ['[data-scroll-restoration-id="carry-over-reset"]'],
getScrollRestorationKey: (location) => {
if (location.pathname === '/hash-scroll-repro') {
return location.pathname
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/(tests)/nested-scroll-away')({
component: Component,
})

function Component() {
return (
<div className="grid gap-4 p-2">
<h3>nested-scroll-away</h3>
<p>This route intentionally has no nested scroll container.</p>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Link, createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/(tests)/nested-scroll-carry-over-a')({
component: Component,
})

function ScrollBox({
restorationId,
testId,
}: {
restorationId: string
testId: string
}) {
return (
<div
data-scroll-restoration-id={restorationId}
data-testid={testId}
className="h-24 overflow-auto rounded border p-2"
>
{Array.from({ length: 20 }).map((_, i) => (
<div key={i}>Source row {i}</div>
))}
</div>
)
}

function Component() {
return (
<div className="grid gap-4 p-2">
<h3>nested-scroll-carry-over-a</h3>
<Link
to="/nested-scroll-carry-over-b"
data-testid="nested-scroll-carry-over-link"
>
Go to target
</Link>

<ScrollBox
restorationId="carry-over-preserved"
testId="carry-over-preserved"
/>
<ScrollBox restorationId="carry-over-reset" testId="carry-over-reset" />
<ScrollBox
restorationId="carry-over-source-only"
testId="carry-over-source-only"
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/(tests)/nested-scroll-carry-over-b')({
component: Component,
})

function ScrollBox({
restorationId,
testId,
}: {
restorationId: string
testId: string
}) {
return (
<div
data-scroll-restoration-id={restorationId}
data-testid={testId}
className="h-24 overflow-auto rounded border p-2"
>
{Array.from({ length: 20 }).map((_, i) => (
<div key={i}>Target row {i}</div>
))}
</div>
)
}

function Component() {
return (
<div className="grid gap-4 p-2">
<h3>nested-scroll-carry-over-b</h3>
<ScrollBox
restorationId="carry-over-preserved"
testId="carry-over-preserved"
/>
<ScrollBox restorationId="carry-over-reset" testId="carry-over-reset" />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Link, createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'

export const Route = createFileRoute('/(tests)/nested-scroll-search')({
validateSearch: zodValidator(
z.object({
query: z.string().optional(),
}),
),
component: Component,
})

function Component() {
const search = Route.useSearch()
const query = search.query ?? 'none'

return (
<div className="grid gap-4 p-2">
<h3>nested-scroll-search</h3>
<p data-testid="nested-scroll-search-query">query: {query}</p>
<div className="flex gap-2">
<Link
to="/nested-scroll-search"
search={{ query: 'xyz' }}
data-testid="nested-scroll-search-link"
>
Set query
</Link>
<Link to="/nested-scroll-away" data-testid="nested-scroll-away-link">
Away
</Link>
</div>
<div
data-scroll-restoration-id="nested-scroll-search-container"
data-testid="nested-scroll-search-container"
className="h-24 overflow-auto rounded border p-2"
>
{Array.from({ length: 20 }).map((_, i) => (
<div key={i}>
Query {query} row {i}
</div>
))}
</div>
</div>
)
}
14 changes: 14 additions & 0 deletions e2e/react-start/scroll-restoration/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ function HomeComponent() {
</p>
</div>
))}
<div className="border p-2">
<h4>scroll restoration repro routes</h4>
<p>
<Link to="/hash-scroll-repro">/hash-scroll-repro</Link>
</p>
<p>
<Link to="/nested-scroll-carry-over-a">
/nested-scroll-carry-over-a
</Link>
</p>
<p>
<Link to="/nested-scroll-search">/nested-scroll-search</Link>
</p>
</div>
</div>
)
}
Loading
Loading