diff --git a/.changeset/four-carpets-wear.md b/.changeset/four-carpets-wear.md
new file mode 100644
index 000000000000..7e322a469441
--- /dev/null
+++ b/.changeset/four-carpets-wear.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Allows the ClientRouter to open new tabs or windows when submitting forms by clicking while holding the Cmd, Ctrl, or Shift key.
diff --git a/packages/astro/components/ClientRouter.astro b/packages/astro/components/ClientRouter.astro
index 8bda7b780fa0..df7a37da3d79 100644
--- a/packages/astro/components/ClientRouter.astro
+++ b/packages/astro/components/ClientRouter.astro
@@ -37,6 +37,7 @@ const { fallback = 'animate' } = Astro.props;
import { init } from 'astro/virtual-modules/prefetch.js';
type Fallback = 'none' | 'animate' | 'swap';
+ let lastClickedElementLeavingWindow: EventTarget | null = null;
function getFallback(): Fallback {
const el = document.querySelector('[name="astro-view-transitions-fallback"]');
@@ -50,6 +51,13 @@ const { fallback = 'animate' } = Astro.props;
return el.dataset.astroReload !== undefined;
}
+ const leavesWindow = (ev: MouseEvent) =>
+ (ev.button && ev.button !== 0) || // left clicks only
+ ev.metaKey || // new tab (mac)
+ ev.ctrlKey || // new tab (windows)
+ ev.altKey || // download
+ ev.shiftKey; // new window
+
if (supportsViewTransitions || getFallback() !== 'none') {
if (import.meta.env.DEV && window.matchMedia('(prefers-reduced-motion)').matches) {
console.warn(
@@ -58,6 +66,9 @@ const { fallback = 'animate' } = Astro.props;
}
document.addEventListener('click', (ev) => {
let link = ev.target;
+
+ lastClickedElementLeavingWindow = leavesWindow(ev) ? link : null;
+
if (ev.composed) {
link = ev.composedPath()[0];
}
@@ -82,11 +93,7 @@ const { fallback = 'animate' } = Astro.props;
!link.href ||
(linkTarget && linkTarget !== '_self') ||
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
+ lastClickedElementLeavingWindow ||
ev.defaultPrevented
) {
// No page transitions in these cases,
@@ -102,11 +109,15 @@ const { fallback = 'animate' } = Astro.props;
document.addEventListener('submit', (ev) => {
let el = ev.target as HTMLElement;
- if (el.tagName !== 'FORM' || ev.defaultPrevented || isReloadEl(el)) {
+ const submitter = ev.submitter;
+
+ const clickedWithKeys = submitter && submitter === lastClickedElementLeavingWindow;
+ lastClickedElementLeavingWindow = null;
+
+ if (el.tagName !== 'FORM' || ev.defaultPrevented || isReloadEl(el) || clickedWithKeys) {
return;
}
const form = el as HTMLFormElement;
- const submitter = ev.submitter;
const formData = new FormData(form, submitter);
// form.action and form.method can point to an or
// in which case should fallback to the form attribute
diff --git a/packages/astro/e2e/fixtures/view-transitions/public/favicon.ico b/packages/astro/e2e/fixtures/view-transitions/public/favicon.ico
new file mode 100644
index 000000000000..578ad458b890
Binary files /dev/null and b/packages/astro/e2e/fixtures/view-transitions/public/favicon.ico differ