diff --git a/.changeset/nine-houses-attend.md b/.changeset/nine-houses-attend.md new file mode 100644 index 0000000000..a4cbb71449 --- /dev/null +++ b/.changeset/nine-houses-attend.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +View Transitions: handle clicks on SVGAElements and image maps" diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro index 089d8d8e55..a06f1c2a6d 100644 --- a/packages/astro/components/ViewTransitions.astro +++ b/packages/astro/components/ViewTransitions.astro @@ -30,6 +30,7 @@ const { fallback = 'animate', handleForms } = Astro.props; import type { Options } from 'astro:transitions/client'; import { supportsViewTransitions, navigate } from 'astro:transitions/client'; // NOTE: import from `astro/prefetch` as `astro:prefetch` requires the `prefetch` config to be enabled + // @ts-ignore import { init } from 'astro/prefetch'; export type Fallback = 'none' | 'animate' | 'swap'; @@ -42,27 +43,34 @@ const { fallback = 'animate', handleForms } = Astro.props; return 'animate'; } - function isReloadEl(el: HTMLElement): boolean { + function isReloadEl(el: HTMLElement | SVGAElement): boolean { return el.dataset.astroReload !== undefined; } if (supportsViewTransitions || getFallback() !== 'none') { document.addEventListener('click', (ev) => { let link = ev.target; - if (link instanceof Element && link.tagName !== 'A') { - link = link.closest('a'); + if (link instanceof Element) { + link = link.closest('a, area'); } + if ( + !(link instanceof HTMLAnchorElement) && + !(link instanceof SVGAElement) && + !(link instanceof HTMLAreaElement) + ) + return; // This check verifies that the click is happening on an anchor // 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. + const linkTarget = link instanceof HTMLElement ? link.target : link.target.baseVal; + const href = link instanceof HTMLElement ? link.href : link.href.baseVal; + const origin = new URL(href, location.href).origin; if ( - !link || - !(link instanceof HTMLAnchorElement) || isReloadEl(link) || link.hasAttribute('download') || !link.href || - (link.target && link.target !== '_self') || - link.origin !== location.origin || + (linkTarget && linkTarget !== '_self') || + origin !== location.origin || ev.button !== 0 || // left clicks only ev.metaKey || // new tab (mac) ev.ctrlKey || // new tab (windows) @@ -75,7 +83,7 @@ const { fallback = 'animate', handleForms } = Astro.props; return; } ev.preventDefault(); - navigate(link.href, { + navigate(href, { history: link.dataset.astroHistory === 'replace' ? 'replace' : 'auto', }); }); diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/non-html-anchor.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/non-html-anchor.astro new file mode 100644 index 0000000000..8d5ea8d46c --- /dev/null +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/non-html-anchor.astro @@ -0,0 +1,22 @@ +--- +import Layout from '../components/Layout.astro'; +--- + +

SVGA and Image Map links

+ + + + text within a svga + + + + + + logo + + + diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js index 20ea8adbc3..c56abae242 100644 --- a/packages/astro/e2e/view-transitions.test.js +++ b/packages/astro/e2e/view-transitions.test.js @@ -1016,4 +1016,47 @@ test.describe('View Transitions', () => { const result = page.locator('#three-result'); await expect(result, 'should have content').toHaveText('Got: Testing'); }); + + test('click on an svg anchor should trigger navigation', async ({ page, astro }) => { + const loads = []; + page.addListener('load', (p) => { + loads.push(p.title()); + }); + + await page.goto(astro.resolveUrl('/non-html-anchor')); + let locator = page.locator('#insidesvga'); + await expect(locator, 'should have attribute').toHaveAttribute('x', '10'); + await page.click('#svga'); + const p = page.locator('#two'); + await expect(p, 'should have content').toHaveText('Page 2'); + expect(loads.length, 'There should only be 1 page load').toEqual(1); + }); + + test('click inside an svg anchor should trigger navigation', async ({ page, astro }) => { + const loads = []; + page.addListener('load', (p) => { + loads.push(p.title()); + }); + await page.goto(astro.resolveUrl('/non-html-anchor')); + let locator = page.locator('#insidesvga'); + await expect(locator, 'should have content').toHaveText('text within a svga'); + await page.click('#insidesvga'); + const p = page.locator('#two'); + await expect(p, 'should have content').toHaveText('Page 2'); + expect(loads.length, 'There should only be 1 page load').toEqual(1); + }); + + test('click on an area in an image map should trigger navigation', async ({ page, astro }) => { + const loads = []; + page.addListener('load', (p) => { + loads.push(p.title()); + }); + await page.goto(astro.resolveUrl('/non-html-anchor')); + let locator = page.locator('#area'); + await expect(locator, 'should have attribute').toHaveAttribute('shape', 'default'); + await page.click('#logo'); + const p = page.locator('#two'); + await expect(p, 'should have content').toHaveText('Page 2'); + expect(loads.length, 'There should only be 1 page load').toEqual(1); + }); });