0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-24 22:46:02 -05:00

Bugfixes for back navigation in the view transition client-side router (#8491)

* Bugfixes for back navigation in the view transition client-side router

* re-introduced pushState on self links as required for update of browser's address bar

* format
This commit is contained in:
Martin Trapp 2023-09-11 13:58:52 +02:00 committed by GitHub
parent 78b82bb392
commit 0ca332ba4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Bugfixes for back navigation in the view transition client-side router

View file

@ -262,6 +262,9 @@ const { fallback = 'animate' } = Astro.props as Props;
return; return;
} }
// Now we are sure that we will push state, and it is time to create a state if it is still missing.
!state && history.replaceState({ index: currentHistoryIndex, scrollY }, '');
document.documentElement.dataset.astroTransition = dir; document.documentElement.dataset.astroTransition = dir;
if (supportsViewTransitions) { if (supportsViewTransitions) {
finished = document.startViewTransition(() => updateDOM(doc, loc, state)).finished; finished = document.startViewTransition(() => updateDOM(doc, loc, state)).finished;
@ -335,28 +338,28 @@ const { fallback = 'animate' } = Astro.props as Props;
// But we want to handle it like any other same page navigation // But we want to handle it like any other same page navigation
// So we scroll to the top of the page but do not start page transitions // So we scroll to the top of the page but do not start page transitions
ev.preventDefault(); ev.preventDefault();
persistState({ ...history.state, scrollY }); // push state on the first navigation but not if we were here already
scrollTo({ left: 0, top: 0, behavior: 'instant' });
if (location.hash) { if (location.hash) {
// last target was different history.replaceState({ index: currentHistoryIndex, scrollY: -(scrollY + 1) }, '');
const newState: State = { index: ++currentHistoryIndex, scrollY: 0 }; const newState: State = { index: ++currentHistoryIndex, scrollY: 0 };
history.pushState(newState, '', link.href); history.pushState(newState, '', link.href);
} }
scrollTo({ left: 0, top: 0, behavior: 'instant' });
return; return;
} }
} }
// these are the cases we will handle: same origin, different page // these are the cases we will handle: same origin, different page
ev.preventDefault(); ev.preventDefault();
persistState({ index: currentHistoryIndex, scrollY });
navigate('forward', new URL(link.href)); navigate('forward', new URL(link.href));
}); });
addEventListener('popstate', (ev) => { addEventListener('popstate', (ev) => {
if (!transitionEnabledOnThisPage() && ev.state) { if (!transitionEnabledOnThisPage() && ev.state) {
// The current page doesn't haven't View Transitions, // The current page doesn't have View Transitions enabled
// respect that with a full page reload // but the page we navigate to does (because it set the state).
// -- but only for transition managed by us (ev.state is set) // Do a full page refresh to reload the client-side router from the new page.
// Scroll restauration will then happen during the reload when the router's code is re-executed
history.scrollRestoration && (history.scrollRestoration = 'manual'); history.scrollRestoration && (history.scrollRestoration = 'manual');
location.reload(); location.reload();
return; return;
@ -383,7 +386,11 @@ const { fallback = 'animate' } = Astro.props as Props;
const nextIndex = state.index; const nextIndex = state.index;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back'; const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex; currentHistoryIndex = nextIndex;
navigate(direction, new URL(location.href), state); if (state.scrollY < 0) {
scrollTo(0, -(state.scrollY + 1));
} else {
navigate(direction, new URL(location.href), state);
}
}); });
['mouseenter', 'touchstart', 'focus'].forEach((evName) => { ['mouseenter', 'touchstart', 'focus'].forEach((evName) => {

View file

@ -282,6 +282,28 @@ test.describe('View Transitions', () => {
await expect(locator).toBeInViewport(); await expect(locator).toBeInViewport();
}); });
test('Scroll position restored when transitioning back to fragment', async ({ page, astro }) => {
// Go to the long page
await page.goto(astro.resolveUrl('/long-page'));
let locator = page.locator('#longpage');
await expect(locator).toBeInViewport();
// Scroll down to middle fragment
await page.click('#click-scroll-down');
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
// Scroll up to top fragment
await page.click('#click-one-again');
locator = page.locator('#one');
await expect(locator).toHaveText('Page 1');
// Back to middle of the page
await page.goBack();
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
});
test('Scroll position restored on forward button', async ({ page, astro }) => { test('Scroll position restored on forward button', async ({ page, astro }) => {
// Go to page 1 // Go to page 1
await page.goto(astro.resolveUrl('/one')); await page.goto(astro.resolveUrl('/one'));