Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nine-months-start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: include the value of form submitters on `form` remote functions
2 changes: 1 addition & 1 deletion documentation/docs/20-core-concepts/30-form-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ We can also implement progressive enhancement ourselves, without `use:enhance`,
/** @param {SubmitEvent & { currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
event.preventDefault();
const data = new FormData(event.currentTarget);
const data = new FormData(event.currentTarget, event.submitter);

const response = await fetch(event.currentTarget.action, {
method: 'POST',
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/20-core-concepts/60-remote-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ For client-side validation, you can specify a _preflight_ schema which will popu

const schema = v.object({
title: v.pipe(v.string(), v.nonEmpty()),
content:v.pipe(v.string(), v.nonEmpty())
content: v.pipe(v.string(), v.nonEmpty())
});
</script>

Expand Down
2 changes: 1 addition & 1 deletion packages/kit/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2151,7 +2151,7 @@

### Patch Changes

- fix: add `submitter` type to `SumbitFunction` ([#9484](https://github.com/sveltejs/kit/pull/9484))
- fix: add `submitter` type to `SubmitFunction` ([#9484](https://github.com/sveltejs/kit/pull/9484))

## 1.13.0

Expand Down
6 changes: 5 additions & 1 deletion packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1937,7 +1937,11 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
/** Preflight checks */
preflight(schema: StandardSchemaV1<Input, any>): RemoteForm<Input, Output>;
/** Validate the form contents programmatically */
validate(options?: { includeUntouched?: boolean }): Promise<void>;
validate(options?: {
includeUntouched?: boolean;
/** Perform validation as if the form was submitted by the given button. */
submitter?: HTMLButtonElement | HTMLInputElement;
}): Promise<void>;
/** The result of the form submission */
get result(): Output | undefined;
/** The number of pending submissions */
Expand Down
7 changes: 1 addition & 6 deletions packages/kit/src/runtime/app/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function enhance(form_element, submit = () => {}) {
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formEnctype
: clone(form_element).enctype;

const form_data = new FormData(form_element);
const form_data = new FormData(form_element, event.submitter);

if (DEV && enctype !== 'multipart/form-data') {
for (const value of form_data.values()) {
Expand All @@ -148,11 +148,6 @@ export function enhance(form_element, submit = () => {}) {
}
}

const submitter_name = event.submitter?.getAttribute('name');
if (submitter_name) {
form_data.append(submitter_name, event.submitter?.getAttribute('value') ?? '');
}

const controller = new AbortController();

let cancelled = false;
Expand Down
7 changes: 1 addition & 6 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2536,12 +2536,7 @@ function _start_router() {
event.preventDefault();
event.stopPropagation();

const data = new FormData(event_form);

const submitter_name = submitter?.getAttribute('name');
if (submitter_name) {
data.append(submitter_name, submitter?.getAttribute('value') ?? '');
}
const data = new FormData(event_form, submitter);

// @ts-expect-error `URLSearchParams(fd)` is kosher, but typescript doesn't know that
url.search = new URLSearchParams(data).toString();
Expand Down
12 changes: 4 additions & 8 deletions packages/kit/src/runtime/client/remote-functions/form.svelte.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export function form(id) {

event.preventDefault();

const form_data = new FormData(form);
const form_data = new FormData(form, event.submitter);

if (DEV) {
validate_form_data(form_data, clone(form).enctype);
Expand Down Expand Up @@ -347,7 +347,7 @@ export function form(id) {
event.stopPropagation();
event.preventDefault();

const form_data = new FormData(form);
const form_data = new FormData(form, target);

if (DEV) {
const enctype = target.hasAttribute('formenctype')
Expand All @@ -357,10 +357,6 @@ export function form(id) {
validate_form_data(form_data, enctype);
}

if (target.name) {
form_data.append(target.name, target?.getAttribute('value') ?? '');
}

await handle_submit(form, form_data, callback);
};
};
Expand Down Expand Up @@ -429,12 +425,12 @@ export function form(id) {
},
validate: {
/** @type {RemoteForm<any, any>['validate']} */
value: async ({ includeUntouched = false } = {}) => {
value: async ({ includeUntouched = false, submitter } = {}) => {
if (!element) return;

const id = ++validate_id;

const form_data = new FormData(element);
const form_data = new FormData(element, submitter);

/** @type {readonly StandardSchemaV1.Issue[]} */
let array = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export const set_message = form(
message: v.picklist(
['hello', 'goodbye', 'unexpected error', 'expected error', 'redirect'],
'message is invalid'
)
),
uppercase: v.optional(v.string())
}),
async (data) => {
if (data.message === 'unexpected error') {
Expand All @@ -29,7 +30,7 @@ export const set_message = form(
redirect(303, '/remote');
}

message = data.message;
message = data.uppercase === 'true' ? data.message.toUpperCase() : data.message;

if (getRequestEvent().isRemoteRequest) {
const deferred = Promise.withResolvers();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
import { my_form } from './form.remote.js';
</script>

<form {...my_form}>
<button name={my_form.field('submitter')} value="hello">submit</button>
</form>

<p id="result">{my_form.result}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { form } from '$app/server';
import * as v from 'valibot';

export const my_form = form(
v.object({
submitter: v.string()
}),
async (data) => {
return data.submitter;
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

const schema = v.object({
foo: v.picklist(['a', 'b']),
bar: v.picklist(['d', 'e'])
bar: v.picklist(['d', 'e']),
button: v.literal('submitter')
});
let submitter;
$inspect(my_form.issues);
</script>

<form {...my_form.preflight(schema)} oninput={() => my_form.validate()}>
Expand All @@ -21,5 +24,16 @@

<input name={my_form.field('bar')} />

<button>submit</button>
<button bind:this={submitter} name={my_form.field('button')} value="incorrect_value">
submit
</button>
{#if my_form.issues.button}
<p>{my_form.issues.button[0].message}</p>
{/if}
</form>
<button
id="trigger-validate"
onclick={() => my_form.validate({ includeUntouched: true, submitter })}
>
trigger validation
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as v from 'valibot';
export const my_form = form(
v.object({
foo: v.picklist(['a', 'b', 'c']),
bar: v.picklist(['d', 'e', 'f'])
bar: v.picklist(['d', 'e', 'f']),
button: v.literal('submitter')
}),
async (data) => {
console.log(data);
Expand Down
13 changes: 13 additions & 0 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1660,6 +1660,14 @@
await expect(page.locator('[data-unscoped] input')).toHaveValue('');
});

test('form submitters work', async ({ page }) => {
await page.goto('/remote/form/submitter');

await page.locator('button').click();

await expect(page.locator('#result')).toHaveText('hello');
});

test('form updates inputs live', async ({ page, javaScriptEnabled }) => {
await page.goto('/remote/form');

Expand Down Expand Up @@ -1747,7 +1755,7 @@
if (javaScriptEnabled) {
await expect(page.getByText('scoped.pending:')).toHaveText('scoped.pending: 1');
await page.getByText('resolve deferreds').click();
await expect(page.getByText('scoped.pending:')).toHaveText('scoped.pending: 0');

Check warning on line 1758 in packages/kit/test/apps/basics/test/test.js

View workflow job for this annotation

GitHub Actions / test-kit (22, ubuntu-latest, chromium)

flaky test: form scoping with for(...) works

retries: 2

await page.getByText('message.current: hello').waitFor();
await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
Expand Down Expand Up @@ -1820,6 +1828,11 @@

await bar.fill('d');
await expect(page.locator('form')).not.toContainText('Invalid type: Expected');

await page.locator('#trigger-validate').click();
await expect(page.locator('form')).toContainText(
'Invalid type: Expected "submitter" but received "incorrect_value"'
);
});

test('form inputs excludes underscore-prefixed fields', async ({ page, javaScriptEnabled }) => {
Expand Down
6 changes: 5 additions & 1 deletion packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1913,7 +1913,11 @@ declare module '@sveltejs/kit' {
/** Preflight checks */
preflight(schema: StandardSchemaV1<Input, any>): RemoteForm<Input, Output>;
/** Validate the form contents programmatically */
validate(options?: { includeUntouched?: boolean }): Promise<void>;
validate(options?: {
includeUntouched?: boolean;
/** Perform validation as if the form was submitted by the given button. */
submitter?: HTMLButtonElement | HTMLInputElement;
}): Promise<void>;
/** The result of the form submission */
get result(): Output | undefined;
/** The number of pending submissions */
Expand Down
Loading