-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
forms.js
105 lines (90 loc) · 2.41 KB
/
forms.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { invalidateAll } from './navigation.js';
import { client } from '../client/singletons.js';
/**
* @param {string} name
*/
function guard(name) {
return () => {
throw new Error(`Cannot call ${name}(...) on the server`);
};
}
const ssr = import.meta.env.SSR;
/** @type {import('$app/forms').applyAction} */
export const applyAction = ssr ? guard('applyAction') : client.apply_action;
/** @type {import('$app/forms').enhance} */
export function enhance(form, submit = () => {}) {
/**
* @param {{
* action: string;
* result: import('types').ActionResult;
* }} opts
*/
const fallback_callback = async ({ action, result }) => {
if (result.type === 'success') {
await invalidateAll();
}
if (location.origin + location.pathname === action.split('?')[0]) {
applyAction(result);
}
};
/** @param {SubmitEvent} event */
async function handle_submit(event) {
event.preventDefault();
// We can't do submitter.formAction directly because that property is always set
const action = event.submitter?.hasAttribute('formaction')
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formAction
: form.action;
const data = new FormData(form);
const controller = new AbortController();
let cancelled = false;
const cancel = () => (cancelled = true);
const callback =
submit({
action,
cancel,
controller,
data,
form
}) ?? fallback_callback;
if (cancelled) return;
/** @type {import('types').ActionResult} */
let result;
try {
const response = await fetch(action, {
method: 'POST',
headers: {
accept: 'application/json'
},
body: data,
signal: controller.signal
});
result = await response.json();
} catch (error) {
if (/** @type {any} */ (error)?.name === 'AbortError') return;
result = { type: 'error', error };
}
callback({
action,
data,
form,
// @ts-expect-error generic constraints stuff we don't care about
result,
// TODO remove for 1.0
get type() {
throw new Error('(result) => {...} has changed to ({ result }) => {...}');
},
get location() {
throw new Error('(result) => {...} has changed to ({ result }) => {...}');
},
get error() {
throw new Error('(result) => {...} has changed to ({ result }) => {...}');
}
});
}
form.addEventListener('submit', handle_submit);
return {
destroy() {
form.removeEventListener('submit', handle_submit);
}
};
}