summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/gold-carpets-film.md5
-rw-r--r--packages/astro/components/ViewTransitions.astro68
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro1
-rw-r--r--packages/astro/e2e/view-transitions.test.js16
4 files changed, 68 insertions, 22 deletions
diff --git a/.changeset/gold-carpets-film.md b/.changeset/gold-carpets-film.md
new file mode 100644
index 000000000..dc17f7ab8
--- /dev/null
+++ b/.changeset/gold-carpets-film.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+View Transitions: self link (`href=""`) does not trigger page reload
diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro
index 4b7a46551..be312b1bf 100644
--- a/packages/astro/components/ViewTransitions.astro
+++ b/packages/astro/components/ViewTransitions.astro
@@ -274,30 +274,54 @@ const { fallback = 'animate' } = Astro.props as Props;
// that is going to another page within the same origin. Basically it determines
// same-origin navigation, but omits special key combos for new tabs, etc.
if (
- link &&
- link instanceof HTMLAnchorElement &&
- link.href &&
- (!link.target || link.target === '_self') &&
- link.origin === location.origin &&
- !(
- // Same page means same path and same query params
- (location.pathname === link.pathname && location.search === link.search)
- ) &&
- ev.button === 0 && // left clicks only
- !ev.metaKey && // new tab (mac)
- !ev.ctrlKey && // new tab (windows)
- !ev.altKey && // download
- !ev.shiftKey &&
- !ev.defaultPrevented &&
- transitionEnabledOnThisPage()
- ) {
- ev.preventDefault();
- navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
- const newState: State = { index: currentHistoryIndex, scrollY };
- persistState({ index: currentHistoryIndex - 1, scrollY });
- history.pushState(newState, '', link.href);
+ !link ||
+ !(link instanceof HTMLAnchorElement) ||
+ !link.href ||
+ (link.target && link.target !== '_self') ||
+ link.origin !== location.origin ||
+ ev.button !== 0 || // left clicks only
+ ev.metaKey || // new tab (mac)
+ ev.ctrlKey || // new tab (windows)
+ ev.altKey || // download
+ ev.shiftKey || // new window
+ ev.defaultPrevented ||
+ !transitionEnabledOnThisPage()
+ )
+ // No page transitions in these cases,
+ // Let the browser standard action handle this
+ return;
+
+ // We do not need to handle same page links because there are no page transitions
+ // Same page means same path and same query params (but different hash)
+ if (location.pathname === link.pathname && location.search === link.search) {
+ if (link.hash) {
+ // The browser default action will handle navigations with hash fragments
+ return;
+ } else {
+ // Special case: self link without hash
+ // If handed to the browser it will reload the page
+ // 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
+ ev.preventDefault();
+ persistState({ ...history.state, scrollY });
+ scrollTo({ left: 0, top: 0, behavior: 'instant' });
+ if (location.hash) {
+ // last target was different
+ const newState: State = { index: ++currentHistoryIndex, scrollY: 0 };
+ history.pushState(newState, '', link.href);
+ }
+ return;
+ }
}
+
+ // these are the cases we will handle: same origin, different page
+ ev.preventDefault();
+ navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
+ const newState: State = { index: currentHistoryIndex, scrollY };
+ persistState({ index: currentHistoryIndex - 1, scrollY });
+ history.pushState(newState, '', link.href);
});
+
addEventListener('popstate', (ev) => {
if (!transitionEnabledOnThisPage()) {
// The current page doesn't haven't View Transitions,
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro
index b24338d9d..3f9666c1d 100644
--- a/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/one.astro
@@ -7,6 +7,7 @@ import Layout from '../components/Layout.astro';
<a id="click-two" href="/two">go to 2</a>
<a id="click-three" href="/three">go to 3</a>
<a id="click-longpage" href="/long-page">go to long page</a>
+ <a id="click-self" href="">go to top</a>
<div id="test">test content</div>
</Layout>
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js
index 7aeb6502a..69fa7c55f 100644
--- a/packages/astro/e2e/view-transitions.test.js
+++ b/packages/astro/e2e/view-transitions.test.js
@@ -190,6 +190,22 @@ test.describe('View Transitions', () => {
await expect(p, 'should have content').toHaveText('Page 1');
});
+ test('click self link (w/o hash) does not do navigation', async ({ page, astro }) => {
+ const loads = [];
+ page.addListener('load', (p) => {
+ loads.push(p.title());
+ });
+ // Go to page 1
+ await page.goto(astro.resolveUrl('/one'));
+ const p = page.locator('#one');
+ await expect(p, 'should have content').toHaveText('Page 1');
+
+ // Clicking href="" stays on page
+ await page.click('#click-self');
+ await expect(p, 'should have content').toHaveText('Page 1');
+ expect(loads.length, 'There should only be 1 page load').toEqual(1);
+ });
+
test('Scroll position restored on back button', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/long-page'));