Skip to content

Commit 2a090ce

Browse files
committed
fix: scroll on page change
1 parent dda26f4 commit 2a090ce

5 files changed

Lines changed: 48 additions & 26 deletions

File tree

apps/site/pages/components/avatar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Avatar
33
description: An avatar is a component that renders an image as an Avatar
44
---
55

6-
An `avatar` is a component that renders an [raster image](raster.md) as an `Avatar`.
6+
An `avatar` is a component that renders a [raster image](raster.md) as an `Avatar`.
77

88
## Styling Properties
99

apps/site/pages/index.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
2-
title: Create documentation that interacts with your reader
2+
title: Interactive documentation
3+
description: Create documentation that interacts with your reader
34
layout: holy
45
---
56
import {getPages} from "../../../src/resources/rsc/server/handler.tsx";

src/interact/config/configSchema.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@ const faviconImage = z.object({
2121
height: z.coerce.number<number>().describe("The intrinsic height of the image").optional(),
2222
}).describe("An image")
2323
type faviconImageType = z.output<typeof faviconImage>
24-
let relSchema = z.enum(['shortcut icon', 'icon', 'apple-touch-icon']).describe("The link rel (ie the type)");
24+
let relSchema = z.enum(['shortcut icon', 'icon', 'apple-touch-icon', 'preload']).describe("The link rel (ie the type)");
2525
type RelType = z.output<typeof relSchema>
2626
type FaviconType = {
2727
rel: RelType,
2828
image?: faviconImageType,
29+
as?: string
2930
}
3031
const favicon: z.ZodType<FaviconType> = z.object({
3132
rel: relSchema,
32-
image: faviconImage.describe("The image").optional()
33+
image: faviconImage.describe("The image").optional(),
34+
as: z.enum(['image']).optional(),
3335
}).describe("A favicon image")
3436

3537
/**

src/resources/components/heads/LinkFavicons.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ export default function LinkFavicons() {
1313
return;
1414
}
1515
return (
16-
<link key={index}
17-
rel={faviconProperties.rel}
18-
href={faviconProperties.image?.href ? faviconProperties.image.href : `/${faviconPath}`}
19-
type={faviconProperties.image?.type}
20-
sizes={faviconProperties.image?.width && faviconProperties.image?.height ? `${faviconProperties.image.width}x${faviconProperties.image.height}` : ''}
16+
<link
17+
key={index}
18+
rel={faviconProperties.rel}
19+
href={faviconProperties.image?.href ? faviconProperties.image.href : `/${faviconPath}`}
20+
{...(faviconProperties.image?.type && {type: faviconProperties.image.type})}
21+
{...(faviconProperties.image?.width && faviconProperties.image?.height && {sizes: `${faviconProperties.image.width}x${faviconProperties.image.height}`})}
22+
{...(faviconProperties?.as != null && {as: faviconProperties.as})}
2123
/>
2224
)
2325
})}

src/resources/rsc/browser/entry.browser.tsx

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ async function main() {
8484
async function fetchRscPayload() {
8585
const renderRequest = createRscRenderRequest(window.location.href)
8686
const payload = await createFromFetch<RscPayload>(fetch(renderRequest))
87+
// Go to the top if this is not a fragment navigation
88+
if (!window.location.hash) {
89+
window.scrollTo({top: 0, behavior: 'smooth'});
90+
}
8791
setPayload(payload)
8892
}
8993

@@ -138,37 +142,38 @@ async function main() {
138142
}
139143
}
140144

145+
function isAnchorNavigation(link: HTMLAnchorElement) {
146+
return link.href &&
147+
link.origin === window.location.origin &&
148+
link.pathname === window.location.pathname &&
149+
link.hash !== '';
150+
}
151+
141152
// Helper that set up events interception for client side navigation
142-
function listenNavigation(onNavigation: () => void) {
153+
function listenNavigation(fetchRsc: () => void) {
143154

144-
// When the user clicks the back button, navigate with RSC.
145-
window.addEventListener('popstate', onNavigation)
146155

156+
// pushState is called to navigate to a new URL without a full page reload.
157+
// called in the onClick function
147158
const oldPushState = window.history.pushState
148159
window.history.pushState = function (...args) {
149-
const res = oldPushState.apply(this, args)
150-
onNavigation()
151-
return res
160+
oldPushState.apply(this, args)
161+
fetchRsc()
152162
}
153163

164+
// replaceState changes the current URL entry without adding a new history entry (used for redirects or URL cleanups).
154165
const oldReplaceState = window.history.replaceState
155166
window.history.replaceState = function (...args) {
156-
const res = oldReplaceState.apply(this, args)
157-
onNavigation()
158-
return res
167+
oldReplaceState.apply(this, args)
168+
fetchRsc()
159169
}
160170

161171
function onClick(e: MouseEvent) {
162172
let link = (e.target as Element).closest('a')
163173
if (!link) return;
164174

165175
// Navigation in the same page
166-
const isAnchorNav =
167-
link.href &&
168-
link.origin === window.location.origin &&
169-
link.pathname === window.location.pathname &&
170-
link.hash !== '';
171-
176+
const isAnchorNav = isAnchorNavigation(link);
172177
if (
173178
!isAnchorNav &&
174179
link instanceof HTMLAnchorElement &&
@@ -177,7 +182,7 @@ function listenNavigation(onNavigation: () => void) {
177182
link.origin === location.origin &&
178183
!link.hasAttribute('download') &&
179184
e.button === 0 && // left clicks only
180-
!e.metaKey && // open in new tab (mac)
185+
!e.metaKey && // open in new tab (Mac)
181186
!e.ctrlKey && // open in new tab (windows)
182187
!e.altKey && // download
183188
!e.shiftKey &&
@@ -188,11 +193,23 @@ function listenNavigation(onNavigation: () => void) {
188193
}
189194
}
190195

196+
// When the user clicks the back button, navigate with RSC.
197+
// Fire on new URL (anchor link also)
198+
window.addEventListener('popstate', (event) => {
199+
// ignore change for hash-only changes
200+
if ((event.target as Window)?.location.hash) return;
201+
fetchRsc();
202+
})
203+
204+
// On click
191205
document.addEventListener('click', onClick)
192206

193207
return () => {
208+
// click = mouse click
194209
document.removeEventListener('click', onClick)
195-
window.removeEventListener('popstate', onNavigation)
210+
// popstate = back button
211+
window.removeEventListener('popstate', fetchRsc)
212+
// restore initial state
196213
window.history.pushState = oldPushState
197214
window.history.replaceState = oldReplaceState
198215
}

0 commit comments

Comments
 (0)