|
import * as devalue from 'devalue'; |
|
import { DEV } from 'esm-env'; |
|
import { invalidateAll } from './navigation.js'; |
|
import { applyAction } from '../client/client.js'; |
|
|
|
export { applyAction }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function deserialize(result) { |
|
const parsed = JSON.parse(result); |
|
if (parsed.data) { |
|
parsed.data = devalue.parse(parsed.data); |
|
} |
|
return parsed; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function clone(element) { |
|
return (HTMLElement.prototype.cloneNode.call(element)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function enhance(form_element, submit = () => {}) { |
|
if (DEV && clone(form_element).method !== 'post') { |
|
throw new Error('use:enhance can only be used on <form> fields with method="POST"'); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fallback_callback = async ({ |
|
action, |
|
result, |
|
reset = true, |
|
invalidateAll: shouldInvalidateAll = true |
|
}) => { |
|
if (result.type === 'success') { |
|
if (reset) { |
|
|
|
HTMLFormElement.prototype.reset.call(form_element); |
|
} |
|
if (shouldInvalidateAll) { |
|
await invalidateAll(); |
|
} |
|
} |
|
|
|
|
|
|
|
if ( |
|
location.origin + location.pathname === action.origin + action.pathname || |
|
result.type === 'redirect' || |
|
result.type === 'error' |
|
) { |
|
applyAction(result); |
|
} |
|
}; |
|
|
|
|
|
async function handle_submit(event) { |
|
const method = event.submitter?.hasAttribute('formmethod') |
|
? (event.submitter).formMethod |
|
: clone(form_element).method; |
|
if (method !== 'post') return; |
|
|
|
event.preventDefault(); |
|
|
|
const action = new URL( |
|
|
|
event.submitter?.hasAttribute('formaction') |
|
? (event.submitter).formAction |
|
: clone(form_element).action |
|
); |
|
|
|
const enctype = event.submitter?.hasAttribute('formenctype') |
|
? (event.submitter).formEnctype |
|
: clone(form_element).enctype; |
|
|
|
const form_data = new FormData(form_element); |
|
|
|
if (DEV && enctype !== 'multipart/form-data') { |
|
for (const value of form_data.values()) { |
|
if (value instanceof File) { |
|
throw new Error( |
|
'Your form contains <input type="file"> fields, but is missing the necessary `enctype="multipart/form-data"` attribute. This will lead to inconsistent behavior between enhanced and native forms. For more details, see https://github.com/sveltejs/kit/issues/9819.' |
|
); |
|
} |
|
} |
|
} |
|
|
|
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; |
|
const cancel = () => (cancelled = true); |
|
|
|
const callback = |
|
(await submit({ |
|
action, |
|
cancel, |
|
controller, |
|
formData: form_data, |
|
formElement: form_element, |
|
submitter: event.submitter |
|
})) ?? fallback_callback; |
|
if (cancelled) return; |
|
|
|
|
|
let result; |
|
|
|
try { |
|
const headers = new Headers({ |
|
accept: 'application/json', |
|
'x-sveltekit-action': 'true' |
|
}); |
|
|
|
|
|
|
|
|
|
if (enctype !== 'multipart/form-data') { |
|
headers.set( |
|
'Content-Type', |
|
/^(:?application\/x-www-form-urlencoded|text\/plain)$/.test(enctype) |
|
? enctype |
|
: 'application/x-www-form-urlencoded' |
|
); |
|
} |
|
|
|
|
|
const body = enctype === 'multipart/form-data' ? form_data : new URLSearchParams(form_data); |
|
|
|
const response = await fetch(action, { |
|
method: 'POST', |
|
headers, |
|
cache: 'no-store', |
|
body, |
|
signal: controller.signal |
|
}); |
|
|
|
result = deserialize(await response.text()); |
|
if (result.type === 'error') result.status = response.status; |
|
} catch (error) { |
|
if ( (error)?.name === 'AbortError') return; |
|
result = { type: 'error', error }; |
|
} |
|
|
|
callback({ |
|
action, |
|
formData: form_data, |
|
formElement: form_element, |
|
update: (opts) => |
|
fallback_callback({ |
|
action, |
|
result, |
|
reset: opts?.reset, |
|
invalidateAll: opts?.invalidateAll |
|
}), |
|
|
|
result |
|
}); |
|
} |
|
|
|
|
|
HTMLFormElement.prototype.addEventListener.call(form_element, 'submit', handle_submit); |
|
|
|
return { |
|
destroy() { |
|
|
|
HTMLFormElement.prototype.removeEventListener.call(form_element, 'submit', handle_submit); |
|
} |
|
}; |
|
} |
|
|