diff --git a/.changeset/stale-poets-clap.md b/.changeset/stale-poets-clap.md new file mode 100644 index 000000000000..d3430c0337d4 --- /dev/null +++ b/.changeset/stale-poets-clap.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: handles form target attribute in remote form redirects diff --git a/packages/kit/src/runtime/client/remote-functions/form.svelte.js b/packages/kit/src/runtime/client/remote-functions/form.svelte.js index cc192c00f546..090fd273f971 100644 --- a/packages/kit/src/runtime/client/remote-functions/form.svelte.js +++ b/packages/kit/src/runtime/client/remote-functions/form.svelte.js @@ -298,6 +298,14 @@ export function form(id) { return; } + const target = event.submitter?.hasAttribute('formtarget') + ? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formTarget + : clone(form).target; + + if (target === '_blank') { + return; + } + event.preventDefault(); const form_data = new FormData(form, event.submitter); diff --git a/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/+page.svelte new file mode 100644 index 000000000000..95de0b093475 --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/+page.svelte @@ -0,0 +1,15 @@ + + +
+ +
+ +
+ +
+ +
+ +
diff --git a/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/destination/+page.svelte b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/destination/+page.svelte new file mode 100644 index 000000000000..3c832468812a --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/destination/+page.svelte @@ -0,0 +1 @@ +

destination

diff --git a/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/form.remote.ts b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/form.remote.ts new file mode 100644 index 000000000000..1977f00fd86c --- /dev/null +++ b/packages/kit/test/apps/async/src/routes/remote/form/redirect-target/form.remote.ts @@ -0,0 +1,12 @@ +import { form } from '$app/server'; +import { redirect } from '@sveltejs/kit'; +import { object, optional, string } from 'valibot'; + +export const redirectForm = form( + object({ + id: optional(string()) + }), + () => { + redirect(303, '/remote/form/redirect-target/destination'); + } +); diff --git a/packages/kit/test/apps/async/test/test.js b/packages/kit/test/apps/async/test/test.js index f9715f83842a..c5af41c86d31 100644 --- a/packages/kit/test/apps/async/test/test.js +++ b/packages/kit/test/apps/async/test/test.js @@ -154,6 +154,52 @@ test.describe('remote functions', () => { await page.waitForURL('/remote'); }); + test('remote form redirect opens in new tab when target=_blank', async ({ page }) => { + await page.goto('/remote/form/redirect-target'); + + const popup_promise = page.waitForEvent('popup', { timeout: 5000 }); + + await page.locator('[data-testid="form-blank"] button').click(); + + const popup = await popup_promise; + await popup.waitForLoadState(); + + expect(popup.url()).toContain('/remote/form/redirect-target/destination'); + + expect(page.url()).toContain('/remote/form/redirect-target'); + expect(page.url()).not.toContain('/destination'); + }); + + test('remote form redirect navigates same tab without target=_blank', async ({ page }) => { + await page.goto('/remote/form/redirect-target'); + + let popup_opened = false; + page.on('popup', () => { + popup_opened = true; + }); + + await page.locator('form:not([target]) button').click(); + await page.waitForURL('**/remote/form/redirect-target/destination'); + + expect(popup_opened).toBe(false); + expect(page.url()).toContain('/remote/form/redirect-target/destination'); + }); + + test('remote form redirect opens in new tab when formtarget=_blank on input', async ({ + page + }) => { + await page.goto('/remote/form/redirect-target'); + + const popup_promise = page.waitForEvent('popup', { timeout: 5000 }); + await page.locator('[data-testid="form-input-blank"] input').click(); + const popup = await popup_promise; + await popup.waitForLoadState(); + + expect(popup.url()).toContain('/remote/form/redirect-target/destination'); + expect(page.url()).toContain('/remote/form/redirect-target'); + expect(page.url()).not.toContain('/destination'); + }); + test('form multiple submit buttons work', async ({ page, javaScriptEnabled }) => { await page.goto('/remote/form/multiple-submit');