Skip to content
Open
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/violet-buckets-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: return `data` to form enhance callback function
5 changes: 4 additions & 1 deletion packages/kit/src/exports/public.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2161,8 +2161,11 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
/** Use the `enhance` method to influence what happens when the form is submitted. */
enhance(
callback: (
form: Omit<RemoteForm<Input, Output>, 'enhance' | 'element'> & {
form: Omit<RemoteForm<Input, Output>, 'enhance' | 'element' | 'data'> & {
/** The <form> element */
readonly element: HTMLFormElement;
/** The data being submitted */
readonly data: Input;
}
) => MaybePromise<void>
): {
Expand Down
53 changes: 28 additions & 25 deletions packages/kit/src/runtime/client/remote-functions/form.svelte.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,17 @@ function merge_with_server_issues(form_data, current_issues, client_issues) {

/**
* Client-version of the `form` function from `$app/server`.
* @template {RemoteFormInput} T
* @template U
* @template {RemoteFormInput} TInput
* @template TOutput
* @param {string} id
* @returns {RemoteForm<T, U>}
* @returns {RemoteForm<TInput, TOutput>}
*/
export function form(id) {
/** @type {Map<any, { count: number, instance: RemoteForm<T, U> }>} */
/** @type {Map<any, { count: number, instance: RemoteForm<TInput, TOutput> }>} */
const instances = new Map();

/** @typedef {Omit<RemoteForm<TInput, TOutput>, 'enhance' | 'element' | 'data'> & { readonly element: HTMLFormElement, readonly data: TInput }} EnhanceCallbackInstance */

/** @param {string | number | boolean} [key] */
function create_instance(key) {
const action_id_without_key = id;
Expand All @@ -80,7 +82,7 @@ export function form(id) {
let preflight_schema = undefined;

/**
* @param {Omit<RemoteForm<T, U>, 'enhance' | 'element'> & { readonly element: HTMLFormElement }} instance
* @param {EnhanceCallbackInstance} instance
*/
let enhance_callback = async (instance) => {
if (await instance.submit()) {
Expand Down Expand Up @@ -142,10 +144,11 @@ export function form(id) {

/**
* @param {FormData} form_data
* @param {Record<string, any>} data
* @param {boolean} should_preflight
* @returns {Promise<boolean> & { updates: (...args: any[]) => Promise<boolean> }}
*/
function submit(form_data, should_preflight) {
function submit(form_data, data, should_preflight) {
// Store a reference to the current instance and increment the usage count for the duration
// of the request. This ensures that the instance is not deleted in case of an optimistic update
// (e.g. when deleting an item in a list) that fails and wants to surface an error to the user afterwards.
Expand Down Expand Up @@ -174,11 +177,11 @@ export function form(id) {
}

if (should_preflight) {
const valid = await preflight(form_data);
const valid = await preflight(form_data, data);
if (!valid) return false;
}

const { blob } = serialize_binary_form(convert(form_data), {
const { blob } = serialize_binary_form(data, {
remote_refreshes: Array.from(refreshes ?? [])
});

Expand Down Expand Up @@ -293,23 +296,21 @@ export function form(id) {
/**
* @param {HTMLFormElement} form
* @param {FormData} form_data
* @returns {Omit<RemoteForm<T, U>, 'enhance' | 'element'> & { readonly element: HTMLFormElement }}
* @param {Record<string, any>} data
* @returns {EnhanceCallbackInstance}
*/
function create_enhance_callback_instance(form, form_data) {
function create_enhance_callback_instance(form, form_data, data) {
const { enhance: _enhance, ...descriptors } = Object.getOwnPropertyDescriptors(instance);
void _enhance;

return /** @type {Omit<RemoteForm<T, U>, 'enhance' | 'element'> & { readonly element: HTMLFormElement }} */ (
return /** @type {EnhanceCallbackInstance} */ (
Object.defineProperties(
{},
{
...descriptors,
data: {
get() {
// TODO 3.0 remove
throw new Error(
`The \`data\` property has been removed from the \`enhance\` callback argument. Use \`instance.fields.value()\` instead.`
);
return data;
}
},
form: {
Expand All @@ -324,7 +325,7 @@ export function form(id) {
value: form
},
submit: {
value: () => submit(form_data, false)
value: () => submit(form_data, data, false)
}
}
)
Expand All @@ -333,9 +334,9 @@ export function form(id) {

/**
* @param {FormData} form_data
* @param {Record<string, any>} data
*/
async function preflight(form_data) {
const data = convert(form_data);
async function preflight(form_data, data) {
const validated = await preflight_schema?.['~standard'].validate(data);

if (validated?.issues) {
Expand All @@ -360,7 +361,7 @@ export function form(id) {
return true;
}

/** @type {RemoteForm<T, U>} */
/** @type {RemoteForm<TInput, TOutput>} */
const instance = {};

instance.method = 'POST';
Expand Down Expand Up @@ -417,17 +418,19 @@ export function form(id) {
validate_form_data(form_data, clone(form).enctype);
}

const data = convert(form_data);

submitted = true;

try {
// Increment pending count immediately so that `pending` reflects
// the in-progress state during async preflight validation
pending_count++;

const valid = await preflight(form_data);
const valid = await preflight(form_data, data);
if (!valid) return;

await enhance_callback(create_enhance_callback_instance(form, form_data));
await enhance_callback(create_enhance_callback_instance(form, form_data, data));
} catch (e) {
const error =
e instanceof HttpError ? e.body : { message: /** @type {any} */ (e).message };
Expand Down Expand Up @@ -580,7 +583,7 @@ export function form(id) {
submitted = true;
pending_count++;

const submission = submit(form_data, true);
const submission = submit(form_data, convert(form_data), true);

void submission.finally(() => {
pending_count--;
Expand Down Expand Up @@ -625,7 +628,7 @@ export function form(id) {
get: () => pending_count
},
preflight: {
/** @type {RemoteForm<T, U>['preflight']} */
/** @type {RemoteForm<TInput, TOutput>['preflight']} */
value: (schema) => {
preflight_schema = schema;
return instance;
Expand Down Expand Up @@ -700,7 +703,7 @@ export function form(id) {
},
enhance: {
/**
* @param {(instance: Omit<RemoteForm<T, U>, 'enhance' | 'element'> & { readonly element: HTMLFormElement }) => any} callback
* @param {(instance: EnhanceCallbackInstance) => any} callback
*/
value: (callback) => {
enhance_callback = callback;
Expand All @@ -715,7 +718,7 @@ export function form(id) {
const instance = create_instance();

Object.defineProperty(instance, 'for', {
/** @type {RemoteForm<T, U>['for']} */
/** @type {RemoteForm<TInput, TOutput>['for']} */
value: (key) => {
const entry = instances.get(key) ?? { count: 0, instance: create_instance(key) };

Expand Down
5 changes: 4 additions & 1 deletion packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2135,8 +2135,11 @@ declare module '@sveltejs/kit' {
/** Use the `enhance` method to influence what happens when the form is submitted. */
enhance(
callback: (
form: Omit<RemoteForm<Input, Output>, 'enhance' | 'element'> & {
form: Omit<RemoteForm<Input, Output>, 'enhance' | 'element' | 'data'> & {
/** The <form> element */
readonly element: HTMLFormElement;
/** The data being submitted */
readonly data: Input;
}
) => MaybePromise<void>
): {
Expand Down
Loading