@sveltejs/kit 2.43.8 → 2.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/core/postbuild/prerender.js +2 -1
- package/src/exports/public.d.ts +118 -79
- package/src/runtime/app/server/remote/form.js +51 -25
- package/src/runtime/app/server/remote/shared.js +1 -3
- package/src/runtime/client/remote-functions/command.svelte.js +3 -1
- package/src/runtime/client/remote-functions/form.svelte.js +127 -47
- package/src/runtime/client/remote-functions/shared.svelte.js +9 -1
- package/src/runtime/form-utils.svelte.js +435 -0
- package/src/runtime/server/remote.js +1 -2
- package/src/runtime/server/respond.js +4 -4
- package/src/runtime/utils.js +0 -123
- package/src/types/internal.d.ts +8 -1
- package/src/version.js +1 -1
- package/types/index.d.ts +118 -79
- package/types/index.d.ts.map +12 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltejs/kit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.44.0",
|
|
4
4
|
"description": "SvelteKit is the fastest way to build Svelte apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@types/set-cookie-parser": "^2.4.7",
|
|
42
42
|
"dts-buddy": "^0.6.2",
|
|
43
43
|
"rollup": "^4.14.2",
|
|
44
|
-
"svelte": "^5.39.
|
|
44
|
+
"svelte": "^5.39.8",
|
|
45
45
|
"svelte-preprocess": "^6.0.0",
|
|
46
46
|
"typescript": "^5.3.3",
|
|
47
47
|
"vite": "^6.3.5",
|
|
@@ -320,7 +320,8 @@ async function prerender({ hash, out, manifest_path, metadata, verbose, env }) {
|
|
|
320
320
|
// avoid triggering `filterSerializeResponseHeaders` guard
|
|
321
321
|
const headers = Object.fromEntries(response.headers);
|
|
322
322
|
|
|
323
|
-
if
|
|
323
|
+
// if it's a 200 HTML response, crawl it. Skip error responses, as we don't save those
|
|
324
|
+
if (response.ok && config.prerender.crawl && headers['content-type'] === 'text/html') {
|
|
324
325
|
const { ids, hrefs } = crawl(body.toString(), decoded);
|
|
325
326
|
|
|
326
327
|
actual_hashlinks.set(decoded, ids);
|
package/src/exports/public.d.ts
CHANGED
|
@@ -1813,78 +1813,127 @@ export interface Snapshot<T = any> {
|
|
|
1813
1813
|
// If T is unknown or has an index signature, the types below will recurse indefinitely and create giant unions that TS can't handle
|
|
1814
1814
|
type WillRecurseIndefinitely<T> = unknown extends T ? true : string extends keyof T ? true : false;
|
|
1815
1815
|
|
|
1816
|
-
//
|
|
1817
|
-
type
|
|
1818
|
-
|
|
1819
|
-
:
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
:
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
: T
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1816
|
+
// Input type mappings for form fields
|
|
1817
|
+
type InputTypeMap = {
|
|
1818
|
+
text: string;
|
|
1819
|
+
email: string;
|
|
1820
|
+
password: string;
|
|
1821
|
+
url: string;
|
|
1822
|
+
tel: string;
|
|
1823
|
+
search: string;
|
|
1824
|
+
number: number;
|
|
1825
|
+
range: number;
|
|
1826
|
+
date: string;
|
|
1827
|
+
'datetime-local': string;
|
|
1828
|
+
time: string;
|
|
1829
|
+
month: string;
|
|
1830
|
+
week: string;
|
|
1831
|
+
color: string;
|
|
1832
|
+
checkbox: boolean | string[];
|
|
1833
|
+
radio: string;
|
|
1834
|
+
file: File;
|
|
1835
|
+
hidden: string;
|
|
1836
|
+
submit: string;
|
|
1837
|
+
button: string;
|
|
1838
|
+
reset: string;
|
|
1839
|
+
image: string;
|
|
1840
|
+
select: string;
|
|
1841
|
+
'select multiple': string[];
|
|
1842
|
+
'file multiple': File[];
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
// Valid input types for a given value type
|
|
1846
|
+
export type RemoteFormFieldType<T> = {
|
|
1847
|
+
[K in keyof InputTypeMap]: T extends InputTypeMap[K] ? K : never;
|
|
1848
|
+
}[keyof InputTypeMap];
|
|
1849
|
+
|
|
1850
|
+
// Input element properties based on type
|
|
1851
|
+
type InputElementProps<T extends keyof InputTypeMap> = T extends 'checkbox' | 'radio'
|
|
1852
|
+
? {
|
|
1853
|
+
type: T;
|
|
1854
|
+
'aria-invalid': boolean | 'false' | 'true' | undefined;
|
|
1855
|
+
get checked(): boolean;
|
|
1856
|
+
set checked(value: boolean);
|
|
1857
|
+
}
|
|
1858
|
+
: T extends 'file'
|
|
1859
|
+
? {
|
|
1860
|
+
type: 'file';
|
|
1861
|
+
'aria-invalid': boolean | 'false' | 'true' | undefined;
|
|
1862
|
+
get files(): FileList | null;
|
|
1863
|
+
set files(v: FileList | null);
|
|
1864
|
+
}
|
|
1865
|
+
: {
|
|
1866
|
+
type: T;
|
|
1867
|
+
'aria-invalid': boolean | 'false' | 'true' | undefined;
|
|
1868
|
+
get value(): string | number;
|
|
1869
|
+
set value(v: string | number);
|
|
1870
|
+
};
|
|
1871
|
+
|
|
1872
|
+
type RemoteFormFieldMethods<T> = {
|
|
1873
|
+
/** The values that will be submitted */
|
|
1874
|
+
value(): T;
|
|
1875
|
+
/** Set the values that will be submitted */
|
|
1876
|
+
set(input: T): T;
|
|
1877
|
+
/** Validation issues, if any */
|
|
1878
|
+
issues(): RemoteFormIssue[] | undefined;
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
export type RemoteFormFieldValue = string | string[] | number | boolean | File | File[];
|
|
1882
|
+
|
|
1883
|
+
type AsArgs<Type extends keyof InputTypeMap, Value> = Type extends 'checkbox'
|
|
1884
|
+
? Value extends string[]
|
|
1885
|
+
? [type: 'checkbox', value: Value[number] | (string & {})]
|
|
1886
|
+
: [type: Type]
|
|
1887
|
+
: Type extends 'radio'
|
|
1888
|
+
? [type: 'radio', value: Value | (string & {})]
|
|
1889
|
+
: [type: Type];
|
|
1890
|
+
|
|
1891
|
+
/**
|
|
1892
|
+
* Form field accessor type that provides name(), value(), and issues() methods
|
|
1893
|
+
*/
|
|
1894
|
+
export type RemoteFormField<Value extends RemoteFormFieldValue> = RemoteFormFieldMethods<Value> & {
|
|
1895
|
+
/**
|
|
1896
|
+
* Returns an object that can be spread onto an input element with the correct type attribute,
|
|
1897
|
+
* aria-invalid attribute if the field is invalid, and appropriate value/checked property getters/setters.
|
|
1898
|
+
* @example
|
|
1899
|
+
* ```svelte
|
|
1900
|
+
* <input {...myForm.fields.myString.as('text')} />
|
|
1901
|
+
* <input {...myForm.fields.myNumber.as('number')} />
|
|
1902
|
+
* <input {...myForm.fields.myBoolean.as('checkbox')} />
|
|
1903
|
+
* ```
|
|
1904
|
+
*/
|
|
1905
|
+
as<T extends RemoteFormFieldType<Value>>(...args: AsArgs<T, Value>): InputElementProps<T>;
|
|
1906
|
+
};
|
|
1907
|
+
|
|
1908
|
+
type RemoteFormFieldContainer<Value> = RemoteFormFieldMethods<Value> & {
|
|
1909
|
+
/** Validation issues belonging to this or any of the fields that belong to it, if any */
|
|
1910
|
+
allIssues(): RemoteFormIssue[] | undefined;
|
|
1911
|
+
};
|
|
1912
|
+
|
|
1913
|
+
/**
|
|
1914
|
+
* Recursive type to build form fields structure with proxy access
|
|
1915
|
+
*/
|
|
1916
|
+
type RemoteFormFields<T> =
|
|
1917
|
+
WillRecurseIndefinitely<T> extends true
|
|
1918
|
+
? RecursiveFormFields
|
|
1919
|
+
: NonNullable<T> extends string | number | boolean | File
|
|
1920
|
+
? RemoteFormField<NonNullable<T>>
|
|
1921
|
+
: T extends string[] | File[]
|
|
1922
|
+
? RemoteFormField<T> & { [K in number]: RemoteFormField<T[number]> }
|
|
1923
|
+
: T extends Array<infer U>
|
|
1924
|
+
? RemoteFormFieldContainer<T> & { [K in number]: RemoteFormFields<U> }
|
|
1925
|
+
: RemoteFormFieldContainer<T> & { [K in keyof T]-?: RemoteFormFields<T[K]> };
|
|
1926
|
+
|
|
1927
|
+
// By breaking this out into its own type, we avoid the TS recursion depth limit
|
|
1928
|
+
type RecursiveFormFields = RemoteFormField<any> & { [key: string]: RecursiveFormFields };
|
|
1929
|
+
|
|
1930
|
+
type MaybeArray<T> = T | T[];
|
|
1880
1931
|
|
|
1881
1932
|
export interface RemoteFormInput {
|
|
1882
|
-
[key: string]:
|
|
1933
|
+
[key: string]: MaybeArray<string | number | boolean | File | RemoteFormInput>;
|
|
1883
1934
|
}
|
|
1884
1935
|
|
|
1885
1936
|
export interface RemoteFormIssue {
|
|
1886
|
-
name: string;
|
|
1887
|
-
path: Array<string | number>;
|
|
1888
1937
|
message: string;
|
|
1889
1938
|
}
|
|
1890
1939
|
|
|
@@ -1926,14 +1975,6 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
|
|
|
1926
1975
|
* ```
|
|
1927
1976
|
*/
|
|
1928
1977
|
for(key: string | number | boolean): Omit<RemoteForm<Input, Output>, 'for'>;
|
|
1929
|
-
/**
|
|
1930
|
-
* This method exists to allow you to typecheck `name` attributes. It returns its argument
|
|
1931
|
-
* @example
|
|
1932
|
-
* ```svelte
|
|
1933
|
-
* <input name={login.field('username')} />
|
|
1934
|
-
* ```
|
|
1935
|
-
**/
|
|
1936
|
-
field<Name extends keyof UnionToIntersection<FlattenKeys<Input, ''>>>(string: Name): Name;
|
|
1937
1978
|
/** Preflight checks */
|
|
1938
1979
|
preflight(schema: StandardSchemaV1<Input, any>): RemoteForm<Input, Output>;
|
|
1939
1980
|
/** Validate the form contents programmatically */
|
|
@@ -1946,10 +1987,8 @@ export type RemoteForm<Input extends RemoteFormInput | void, Output> = {
|
|
|
1946
1987
|
get result(): Output | undefined;
|
|
1947
1988
|
/** The number of pending submissions */
|
|
1948
1989
|
get pending(): number;
|
|
1949
|
-
/**
|
|
1950
|
-
|
|
1951
|
-
/** Validation issues */
|
|
1952
|
-
issues: null | UnionToIntersection<FlattenIssues<Input, ''>>;
|
|
1990
|
+
/** Access form fields using object notation */
|
|
1991
|
+
fields: Input extends void ? never : RemoteFormFields<Input>;
|
|
1953
1992
|
/** Spread this onto a `<button>` or `<input type="submit">` */
|
|
1954
1993
|
buttonProps: {
|
|
1955
1994
|
type: 'submit';
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/** @import { RemoteFormInput, RemoteForm } from '@sveltejs/kit' */
|
|
2
|
-
/** @import { MaybePromise, RemoteInfo } from 'types' */
|
|
2
|
+
/** @import { InternalRemoteFormIssue, MaybePromise, RemoteInfo } from 'types' */
|
|
3
3
|
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
4
4
|
import { get_request_store } from '@sveltejs/kit/internal/server';
|
|
5
5
|
import { DEV } from 'esm-env';
|
|
6
|
+
import {
|
|
7
|
+
convert_formdata,
|
|
8
|
+
flatten_issues,
|
|
9
|
+
create_field_proxy,
|
|
10
|
+
set_nested_value,
|
|
11
|
+
throw_on_old_property_access,
|
|
12
|
+
deep_set
|
|
13
|
+
} from '../../../form-utils.svelte.js';
|
|
6
14
|
import { get_cache, run_remote_function } from './shared.js';
|
|
7
|
-
import { convert_formdata, flatten_issues } from '../../../utils.js';
|
|
8
15
|
|
|
9
16
|
/**
|
|
10
17
|
* Creates a form object that can be spread onto a `<form>` element.
|
|
@@ -128,7 +135,7 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
|
|
131
|
-
/** @type {{ input?: Record<string,
|
|
138
|
+
/** @type {{ input?: Record<string, any>, issues?: Record<string, InternalRemoteFormIssue[]>, result: Output }} */
|
|
132
139
|
const output = {};
|
|
133
140
|
|
|
134
141
|
const { event, state } = get_request_store();
|
|
@@ -140,18 +147,27 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
140
147
|
|
|
141
148
|
if (validated?.issues !== undefined) {
|
|
142
149
|
output.issues = flatten_issues(validated.issues);
|
|
143
|
-
output.input = {};
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
// if it was a progressively-enhanced submission, we don't need
|
|
152
|
+
// to return the input — it's already there
|
|
153
|
+
if (!event.isRemoteRequest) {
|
|
154
|
+
output.input = {};
|
|
148
155
|
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
for (let key of form_data.keys()) {
|
|
157
|
+
// redact sensitive fields
|
|
158
|
+
if (/^[.\]]?_/.test(key)) continue;
|
|
151
159
|
|
|
152
|
-
|
|
160
|
+
const is_array = key.endsWith('[]');
|
|
161
|
+
const values = form_data.getAll(key).filter((value) => typeof value === 'string');
|
|
153
162
|
|
|
154
|
-
|
|
163
|
+
if (is_array) key = key.slice(0, -2);
|
|
164
|
+
|
|
165
|
+
output.input = set_nested_value(
|
|
166
|
+
/** @type {Record<string, any>} */ (output.input),
|
|
167
|
+
key,
|
|
168
|
+
is_array ? values : values[0]
|
|
169
|
+
);
|
|
170
|
+
}
|
|
155
171
|
}
|
|
156
172
|
} else {
|
|
157
173
|
if (validated !== undefined) {
|
|
@@ -185,16 +201,30 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
185
201
|
enumerable: true
|
|
186
202
|
});
|
|
187
203
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
Object.defineProperty(instance, 'fields', {
|
|
205
|
+
get() {
|
|
206
|
+
const data = get_cache(__)?.[''];
|
|
207
|
+
return create_field_proxy(
|
|
208
|
+
{},
|
|
209
|
+
() => data?.input ?? {},
|
|
210
|
+
(path, value) => {
|
|
211
|
+
if (data) {
|
|
212
|
+
// don't override a submission
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const input = path.length === 0 ? value : deep_set({}, path.map(String), value);
|
|
217
|
+
|
|
218
|
+
get_cache(__)[''] ??= { input };
|
|
219
|
+
},
|
|
220
|
+
() => data?.issues ?? {}
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// TODO 3.0 remove
|
|
226
|
+
if (DEV) {
|
|
227
|
+
throw_on_old_property_access(instance);
|
|
198
228
|
}
|
|
199
229
|
|
|
200
230
|
Object.defineProperty(instance, 'result', {
|
|
@@ -217,10 +247,6 @@ export function form(validate_or_fn, maybe_fn) {
|
|
|
217
247
|
get: () => 0
|
|
218
248
|
});
|
|
219
249
|
|
|
220
|
-
Object.defineProperty(instance, 'field', {
|
|
221
|
-
value: (/** @type {string} */ name) => name
|
|
222
|
-
});
|
|
223
|
-
|
|
224
250
|
Object.defineProperty(instance, 'preflight', {
|
|
225
251
|
// preflight is a noop on the server
|
|
226
252
|
value: () => instance
|
|
@@ -133,9 +133,7 @@ export async function run_remote_function(event, state, allow_cookies, arg, vali
|
|
|
133
133
|
|
|
134
134
|
return event.cookies.delete(name, opts);
|
|
135
135
|
}
|
|
136
|
-
}
|
|
137
|
-
route: { id: null },
|
|
138
|
-
url: new URL(event.url.origin)
|
|
136
|
+
}
|
|
139
137
|
};
|
|
140
138
|
|
|
141
139
|
// In two parts, each with_event, so that runtimes without async local storage can still get the event at the start of the function
|
|
@@ -40,7 +40,9 @@ export function command(id) {
|
|
|
40
40
|
refreshes: updates.map((u) => u._key)
|
|
41
41
|
}),
|
|
42
42
|
headers: {
|
|
43
|
-
'Content-Type': 'application/json'
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
'x-sveltekit-pathname': location.pathname,
|
|
45
|
+
'x-sveltekit-search': location.search
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
2
2
|
/** @import { RemoteFormInput, RemoteForm, RemoteQueryOverride } from '@sveltejs/kit' */
|
|
3
|
-
/** @import { RemoteFunctionResponse } from 'types' */
|
|
3
|
+
/** @import { InternalRemoteFormIssue, RemoteFunctionResponse } from 'types' */
|
|
4
4
|
/** @import { Query } from './query.svelte.js' */
|
|
5
5
|
import { app_dir, base } from '$app/paths/internal/client';
|
|
6
6
|
import * as devalue from 'devalue';
|
|
@@ -10,7 +10,34 @@ import { app, remote_responses, _goto, set_nearest_error_page, invalidateAll } f
|
|
|
10
10
|
import { tick } from 'svelte';
|
|
11
11
|
import { refresh_queries, release_overrides } from './shared.svelte.js';
|
|
12
12
|
import { createAttachmentKey } from 'svelte/attachments';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
convert_formdata,
|
|
15
|
+
flatten_issues,
|
|
16
|
+
create_field_proxy,
|
|
17
|
+
deep_set,
|
|
18
|
+
set_nested_value,
|
|
19
|
+
throw_on_old_property_access
|
|
20
|
+
} from '../../form-utils.svelte.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Merge client issues into server issues
|
|
24
|
+
* @param {Record<string, InternalRemoteFormIssue[]>} current_issues
|
|
25
|
+
* @param {Record<string, InternalRemoteFormIssue[]>} client_issues
|
|
26
|
+
* @returns {Record<string, InternalRemoteFormIssue[]>}
|
|
27
|
+
*/
|
|
28
|
+
function merge_with_server_issues(current_issues, client_issues) {
|
|
29
|
+
const merged_issues = Object.fromEntries(
|
|
30
|
+
Object.entries(current_issues)
|
|
31
|
+
.map(([key, issue_list]) => [key, issue_list.filter((issue) => issue.server)])
|
|
32
|
+
.filter(([, issue_list]) => issue_list.length > 0)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
for (const [key, new_issue_list] of Object.entries(client_issues)) {
|
|
36
|
+
merged_issues[key] = [...(merged_issues[key] || []), ...new_issue_list];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return merged_issues;
|
|
40
|
+
}
|
|
14
41
|
|
|
15
42
|
/**
|
|
16
43
|
* Client-version of the `form` function from `$app/server`.
|
|
@@ -28,10 +55,14 @@ export function form(id) {
|
|
|
28
55
|
const action_id = id + (key != undefined ? `/${JSON.stringify(key)}` : '');
|
|
29
56
|
const action = '?/remote=' + encodeURIComponent(action_id);
|
|
30
57
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
58
|
+
/**
|
|
59
|
+
* By making this $state.raw() and creating a new object each time we update it,
|
|
60
|
+
* all consumers along the update chain are properly invalidated.
|
|
61
|
+
* @type {Record<string, string | string[] | File | File[]>}
|
|
62
|
+
*/
|
|
63
|
+
let input = $state.raw({});
|
|
33
64
|
|
|
34
|
-
/** @type {Record<string,
|
|
65
|
+
/** @type {Record<string, InternalRemoteFormIssue[]>} */
|
|
35
66
|
let issues = $state.raw({});
|
|
36
67
|
|
|
37
68
|
/** @type {any} */
|
|
@@ -49,6 +80,8 @@ export function form(id) {
|
|
|
49
80
|
/** @type {Record<string, boolean>} */
|
|
50
81
|
let touched = {};
|
|
51
82
|
|
|
83
|
+
let submitted = false;
|
|
84
|
+
|
|
52
85
|
/**
|
|
53
86
|
* @param {HTMLFormElement} form
|
|
54
87
|
* @param {FormData} form_data
|
|
@@ -57,10 +90,13 @@ export function form(id) {
|
|
|
57
90
|
async function handle_submit(form, form_data, callback) {
|
|
58
91
|
const data = convert_formdata(form_data);
|
|
59
92
|
|
|
93
|
+
submitted = true;
|
|
94
|
+
|
|
60
95
|
const validated = await preflight_schema?.['~standard'].validate(data);
|
|
61
96
|
|
|
62
97
|
if (validated?.issues) {
|
|
63
|
-
|
|
98
|
+
const client_issues = flatten_issues(validated.issues, false);
|
|
99
|
+
issues = merge_with_server_issues(issues, client_issues);
|
|
64
100
|
return;
|
|
65
101
|
}
|
|
66
102
|
|
|
@@ -134,7 +170,11 @@ export function form(id) {
|
|
|
134
170
|
|
|
135
171
|
const response = await fetch(`${base}/${app_dir}/remote/${action_id}`, {
|
|
136
172
|
method: 'POST',
|
|
137
|
-
body: data
|
|
173
|
+
body: data,
|
|
174
|
+
headers: {
|
|
175
|
+
'x-sveltekit-pathname': location.pathname,
|
|
176
|
+
'x-sveltekit-search': location.search
|
|
177
|
+
}
|
|
138
178
|
});
|
|
139
179
|
|
|
140
180
|
if (!response.ok) {
|
|
@@ -146,21 +186,25 @@ export function form(id) {
|
|
|
146
186
|
const form_result = /** @type { RemoteFunctionResponse} */ (await response.json());
|
|
147
187
|
|
|
148
188
|
if (form_result.type === 'result') {
|
|
149
|
-
({
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
189
|
+
({ issues = {}, result } = devalue.parse(form_result.result, app.decoders));
|
|
190
|
+
|
|
191
|
+
// Mark server issues with server: true
|
|
192
|
+
for (const issue_list of Object.values(issues)) {
|
|
193
|
+
for (const issue of issue_list) {
|
|
194
|
+
issue.server = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
157
197
|
|
|
158
198
|
if (issues.$) {
|
|
159
199
|
release_overrides(updates);
|
|
160
|
-
} else if (form_result.refreshes) {
|
|
161
|
-
refresh_queries(form_result.refreshes, updates);
|
|
162
200
|
} else {
|
|
163
|
-
|
|
201
|
+
input = {};
|
|
202
|
+
|
|
203
|
+
if (form_result.refreshes) {
|
|
204
|
+
refresh_queries(form_result.refreshes, updates);
|
|
205
|
+
} else {
|
|
206
|
+
void invalidateAll();
|
|
207
|
+
}
|
|
164
208
|
}
|
|
165
209
|
} else if (form_result.type === 'redirect') {
|
|
166
210
|
const refreshes = form_result.refreshes ?? '';
|
|
@@ -275,23 +319,37 @@ export function form(id) {
|
|
|
275
319
|
touched[name] = true;
|
|
276
320
|
|
|
277
321
|
if (is_array) {
|
|
278
|
-
|
|
279
|
-
Array.from(form.querySelectorAll(`[name="${name}[]"]`))
|
|
280
|
-
);
|
|
322
|
+
let value;
|
|
281
323
|
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
324
|
+
if (element.tagName === 'SELECT') {
|
|
325
|
+
value = Array.from(
|
|
326
|
+
element.querySelectorAll('option:checked'),
|
|
327
|
+
(e) => /** @type {HTMLOptionElement} */ (e).value
|
|
328
|
+
);
|
|
329
|
+
} else {
|
|
330
|
+
const elements = /** @type {HTMLInputElement[]} */ (
|
|
331
|
+
Array.from(form.querySelectorAll(`[name="${name}[]"]`))
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
if (DEV) {
|
|
335
|
+
for (const e of elements) {
|
|
336
|
+
if ((e.type === 'file') !== is_file) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Cannot mix and match file and non-file inputs under the same name ("${element.name}")`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
288
341
|
}
|
|
289
342
|
}
|
|
343
|
+
|
|
344
|
+
value = is_file
|
|
345
|
+
? elements.map((input) => Array.from(input.files ?? [])).flat()
|
|
346
|
+
: elements.map((element) => element.value);
|
|
347
|
+
if (element.type === 'checkbox') {
|
|
348
|
+
value = /** @type {string[]} */ (value.filter((_, i) => elements[i].checked));
|
|
349
|
+
}
|
|
290
350
|
}
|
|
291
351
|
|
|
292
|
-
input
|
|
293
|
-
? elements.map((input) => Array.from(input.files ?? [])).flat()
|
|
294
|
-
: elements.map((element) => element.value);
|
|
352
|
+
input = set_nested_value(input, name, value);
|
|
295
353
|
} else if (is_file) {
|
|
296
354
|
if (DEV && element.multiple) {
|
|
297
355
|
throw new Error(
|
|
@@ -302,12 +360,23 @@ export function form(id) {
|
|
|
302
360
|
const file = /** @type {HTMLInputElement & { files: FileList }} */ (element).files[0];
|
|
303
361
|
|
|
304
362
|
if (file) {
|
|
305
|
-
input
|
|
363
|
+
input = set_nested_value(input, name, file);
|
|
306
364
|
} else {
|
|
307
|
-
|
|
365
|
+
// Remove the property by setting to undefined and clean up
|
|
366
|
+
const path_parts = name.split(/\.|\[|\]/).filter(Boolean);
|
|
367
|
+
let current = /** @type {any} */ (input);
|
|
368
|
+
for (let i = 0; i < path_parts.length - 1; i++) {
|
|
369
|
+
if (current[path_parts[i]] == null) return;
|
|
370
|
+
current = current[path_parts[i]];
|
|
371
|
+
}
|
|
372
|
+
delete current[path_parts[path_parts.length - 1]];
|
|
308
373
|
}
|
|
309
374
|
} else {
|
|
310
|
-
input
|
|
375
|
+
input = set_nested_value(
|
|
376
|
+
input,
|
|
377
|
+
name,
|
|
378
|
+
element.type === 'checkbox' && !element.checked ? null : element.value
|
|
379
|
+
);
|
|
311
380
|
}
|
|
312
381
|
});
|
|
313
382
|
|
|
@@ -387,18 +456,29 @@ export function form(id) {
|
|
|
387
456
|
|
|
388
457
|
let validate_id = 0;
|
|
389
458
|
|
|
459
|
+
// TODO 3.0 remove
|
|
460
|
+
if (DEV) {
|
|
461
|
+
throw_on_old_property_access(instance);
|
|
462
|
+
}
|
|
463
|
+
|
|
390
464
|
Object.defineProperties(instance, {
|
|
391
465
|
buttonProps: {
|
|
392
466
|
value: button_props
|
|
393
467
|
},
|
|
394
|
-
|
|
395
|
-
get: () =>
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
468
|
+
fields: {
|
|
469
|
+
get: () =>
|
|
470
|
+
create_field_proxy(
|
|
471
|
+
{},
|
|
472
|
+
() => input,
|
|
473
|
+
(path, value) => {
|
|
474
|
+
if (path.length === 0) {
|
|
475
|
+
input = value;
|
|
476
|
+
} else {
|
|
477
|
+
input = deep_set(input, path.map(String), value);
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
() => issues
|
|
481
|
+
)
|
|
402
482
|
},
|
|
403
483
|
result: {
|
|
404
484
|
get: () => result
|
|
@@ -406,11 +486,8 @@ export function form(id) {
|
|
|
406
486
|
pending: {
|
|
407
487
|
get: () => pending_count
|
|
408
488
|
},
|
|
409
|
-
field: {
|
|
410
|
-
value: (/** @type {string} */ name) => name
|
|
411
|
-
},
|
|
412
489
|
preflight: {
|
|
413
|
-
/** @type {RemoteForm<
|
|
490
|
+
/** @type {RemoteForm<T, U>['preflight']} */
|
|
414
491
|
value: (schema) => {
|
|
415
492
|
preflight_schema = schema;
|
|
416
493
|
return instance;
|
|
@@ -455,7 +532,7 @@ export function form(id) {
|
|
|
455
532
|
}
|
|
456
533
|
}
|
|
457
534
|
|
|
458
|
-
if (!includeUntouched) {
|
|
535
|
+
if (!includeUntouched && !submitted) {
|
|
459
536
|
array = array.filter((issue) => {
|
|
460
537
|
if (issue.path !== undefined) {
|
|
461
538
|
let path = '';
|
|
@@ -475,7 +552,10 @@ export function form(id) {
|
|
|
475
552
|
});
|
|
476
553
|
}
|
|
477
554
|
|
|
478
|
-
|
|
555
|
+
const is_server_validation = !validated?.issues;
|
|
556
|
+
const new_issues = flatten_issues(array, is_server_validation);
|
|
557
|
+
|
|
558
|
+
issues = is_server_validation ? new_issues : merge_with_server_issues(issues, new_issues);
|
|
479
559
|
}
|
|
480
560
|
},
|
|
481
561
|
enhance: {
|