diff options
-rw-r--r-- | .changeset/nine-houses-attend.md | 5 | ||||
-rw-r--r-- | packages/astro/components/ViewTransitions.astro | 24 | ||||
-rw-r--r-- | packages/astro/e2e/fixtures/view-transitions/src/pages/non-html-anchor.astro | 22 | ||||
-rw-r--r-- | packages/astro/e2e/view-transitions.test.js | 43 |
4 files changed, 86 insertions, 8 deletions
diff --git a/.changeset/nine-houses-attend.md b/.changeset/nine-houses-attend.md new file mode 100644 index 000000000..a4cbb7144 --- /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 089d8d8e5..a06f1c2a6 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 000000000..8d5ea8d46 --- /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'; +--- +<Layout> +<h1>SVGA and Image Map links</h1> + +<svg viewBox="0 0 160 40" xmlns="http://www.w3.org/2000/svg"> + <a href="/two" id="svga"> + <text x="10" y="25" id="insidesvga">text within a svga</text> + </a> +</svg> + + +<map name="map"> + <area shape="default" href="/two" alt="logo" id="area"/> +</map> +<img id="logo" usemap="#map" src="/logo.svg" alt="logo" /> +<style> + body { + background: #888; + } +</style> diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js index 20ea8adbc..c56abae24 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); + }); }); |