@zerolith/forms 0.1.0-alpha.5 → 0.1.0-alpha.6
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/dist/form.d.ts +3 -0
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +88 -14
- package/dist/form.js.map +1 -1
- package/dist/schema.d.ts +4 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -3
- package/dist/schema.js.map +1 -1
- package/package.json +3 -3
- package/src/form.ts +117 -16
- package/src/schema.ts +6 -3
package/dist/form.d.ts
CHANGED
|
@@ -28,7 +28,9 @@ export interface Submission {
|
|
|
28
28
|
export interface SubmissionOutcome {
|
|
29
29
|
/** The parsed body: JSON when the response says so, text otherwise, undefined for an empty body. */
|
|
30
30
|
readonly result: unknown;
|
|
31
|
+
/** The successful response, as `fetch` or a custom `send` returned it; its body has already been read into `result` unless it was redirected. */
|
|
31
32
|
readonly response: Response;
|
|
33
|
+
/** The form that was submitted, already reset when the `reset` option is set. */
|
|
32
34
|
readonly element: HTMLFormElement;
|
|
33
35
|
}
|
|
34
36
|
/** Options for `createForm`. */
|
|
@@ -63,6 +65,7 @@ export interface Field {
|
|
|
63
65
|
}
|
|
64
66
|
/** The enhanced form. */
|
|
65
67
|
export interface Form {
|
|
68
|
+
/** The current submission's stage: `pending` while in flight, then `success`, `failure` with a problem, or `error` when it did not complete; `idle` before the first submission and after `reset()`. */
|
|
66
69
|
readonly status: ReadableSignal<FormStatus>;
|
|
67
70
|
/** True while a submission is in flight. */
|
|
68
71
|
readonly pending: ReadableSignal<boolean>;
|
package/dist/form.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAsB,KAAK,OAAO,EAAE,KAAK,cAAc,EAAsC,MAAM,UAAU,CAAC;AACrH,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,aAAa,CAAC;AAEhE,iDAAiD;AACjD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9E,6DAA6D;AAC7D,MAAM,WAAW,UAAU;IAEvB,mCAAmC;IACnC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IACvC,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IACrB,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAChC;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAE9B,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,iJAAiJ;IACjJ,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;CACrC;AAED,gCAAgC;AAChC,MAAM,WAAW,WAAW;IAExB,qGAAqG;IACrG,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,iHAAiH;IACjH,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrD,4CAA4C;IAC5C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,0IAA0I;IAC1I,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACjD,8EAA8E;IAC9E,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACjE,4CAA4C;IAC5C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;CAChE;AAED,gDAAgD;AAChD,MAAM,WAAW,KAAK;IAElB,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IACnD,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACnD,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;CAC7C;AAED,yBAAyB;AACzB,MAAM,WAAW,IAAI;IAEjB,wMAAwM;IACxM,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC5C,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACtD,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,wEAAwE;IACxE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IAC3B,kDAAkD;IAClD,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,oEAAoE;IACpE,KAAK,IAAI,IAAI,CAAC;CACjB;AAuJD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,WAAgB,GAAG,IAAI,CAkJ1D"}
|
package/dist/form.js
CHANGED
|
@@ -8,6 +8,78 @@
|
|
|
8
8
|
import { PROBLEM_MEDIA_TYPE, batch, computed, isProblem, signal } from "zerolith";
|
|
9
9
|
import { validateForm } from "./schema.js";
|
|
10
10
|
const EMPTY = [];
|
|
11
|
+
/** Calls an application callback, reporting what it throws as an uncaught error would be, without changing the form's state. */
|
|
12
|
+
function guarded(callback) {
|
|
13
|
+
try {
|
|
14
|
+
callback();
|
|
15
|
+
}
|
|
16
|
+
catch (thrown) {
|
|
17
|
+
if (typeof reportError === "function") {
|
|
18
|
+
reportError(thrown);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
queueMicrotask(() => {
|
|
22
|
+
throw thrown;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The `FormData` implementation probed, and whether its constructor adds the submitter's entry, which browsers before Chrome 112 and Firefox 111 skip. */
|
|
28
|
+
let probed;
|
|
29
|
+
/** The form's data as the browser submits it, with the submitter's entry added where `new FormData(form, submitter)` leaves it out. */
|
|
30
|
+
function submittedData(element, submitter) {
|
|
31
|
+
const data = new FormData(element, submitter ?? undefined);
|
|
32
|
+
if (submitter === null) {
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
if (probed?.implementation !== FormData) {
|
|
36
|
+
const form = document.createElement("form");
|
|
37
|
+
const button = document.createElement("button");
|
|
38
|
+
button.name = "probe";
|
|
39
|
+
button.value = "kept";
|
|
40
|
+
form.append(button);
|
|
41
|
+
probed = { implementation: FormData, keepsSubmitter: new FormData(form, button).get("probe") === "kept" };
|
|
42
|
+
}
|
|
43
|
+
const name = submitter.getAttribute("name");
|
|
44
|
+
if (probed.keepsSubmitter || name === null || name === "") {
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
if (submitter instanceof HTMLInputElement && submitter.type === "image") {
|
|
48
|
+
data.append(`${name}.x`, "0");
|
|
49
|
+
data.append(`${name}.y`, "0");
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
data.append(name, submitter.value);
|
|
53
|
+
}
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The problem a failed response describes: its body when that is a problem
|
|
58
|
+
* document, or one built from the status. The server is a trust boundary,
|
|
59
|
+
* so `fields` keeps only own keys whose value is a message or a list of
|
|
60
|
+
* messages, a lone message becoming a list of one.
|
|
61
|
+
*/
|
|
62
|
+
function problemOf(body, response) {
|
|
63
|
+
if (!isProblem(body)) {
|
|
64
|
+
return { status: response.status, title: response.statusText === "" ? `HTTP ${response.status}` : response.statusText };
|
|
65
|
+
}
|
|
66
|
+
if (body.fields === undefined) {
|
|
67
|
+
return body;
|
|
68
|
+
}
|
|
69
|
+
const fields = Object.create(null);
|
|
70
|
+
for (const [name, value] of Object.entries(body.fields)) {
|
|
71
|
+
if (typeof value === "string") {
|
|
72
|
+
fields[name] = [value];
|
|
73
|
+
}
|
|
74
|
+
else if (Array.isArray(value)) {
|
|
75
|
+
const messages = value.filter((message) => typeof message === "string");
|
|
76
|
+
if (messages.length > 0) {
|
|
77
|
+
fields[name] = messages;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { ...body, fields };
|
|
82
|
+
}
|
|
11
83
|
function entries(data) {
|
|
12
84
|
return [...data].map(([name, value]) => [name, typeof value === "string" ? value : value.name]);
|
|
13
85
|
}
|
|
@@ -23,7 +95,7 @@ async function defaultSend(submission, headers, fetchImpl) {
|
|
|
23
95
|
url.search = new URLSearchParams(entries(data)).toString();
|
|
24
96
|
return fetchImpl(url, init);
|
|
25
97
|
}
|
|
26
|
-
const enctype = submitter
|
|
98
|
+
const enctype = submitter !== null && submitter.hasAttribute("formenctype") ? submitter.formEnctype : element.enctype;
|
|
27
99
|
if (enctype === "multipart/form-data") {
|
|
28
100
|
init.body = data;
|
|
29
101
|
}
|
|
@@ -65,10 +137,10 @@ export function createForm(options = {}) {
|
|
|
65
137
|
problem.set(found);
|
|
66
138
|
status.set("failure");
|
|
67
139
|
});
|
|
68
|
-
options.onFailure?.(found, element);
|
|
140
|
+
guarded(() => options.onFailure?.(found, element));
|
|
69
141
|
};
|
|
70
142
|
const run = async (element, submitter, method, action, current) => {
|
|
71
|
-
const data =
|
|
143
|
+
const data = submittedData(element, submitter);
|
|
72
144
|
batch(() => {
|
|
73
145
|
status.set("pending");
|
|
74
146
|
problem.set(undefined);
|
|
@@ -89,14 +161,13 @@ export function createForm(options = {}) {
|
|
|
89
161
|
if (current.signal.aborted) {
|
|
90
162
|
return;
|
|
91
163
|
}
|
|
92
|
-
const type = response.headers.get("content-type") ?? "";
|
|
93
|
-
if (type.includes(PROBLEM_MEDIA_TYPE)) {
|
|
94
|
-
fail((await response.json()), element);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (!response.ok) {
|
|
164
|
+
const type = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
165
|
+
if (type.includes(PROBLEM_MEDIA_TYPE) || !response.ok) {
|
|
98
166
|
const body = type.includes("json") ? await response.json().catch(() => undefined) : undefined;
|
|
99
|
-
|
|
167
|
+
if (current.signal.aborted) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
fail(problemOf(body, response), element);
|
|
100
171
|
return;
|
|
101
172
|
}
|
|
102
173
|
const parsed = response.redirected ? undefined : await parseBody(response);
|
|
@@ -110,7 +181,7 @@ export function createForm(options = {}) {
|
|
|
110
181
|
if (options.reset === true) {
|
|
111
182
|
element.reset();
|
|
112
183
|
}
|
|
113
|
-
options.onSuccess?.({ result: parsed, response, element });
|
|
184
|
+
guarded(() => options.onSuccess?.({ result: parsed, response, element }));
|
|
114
185
|
if (response.redirected) {
|
|
115
186
|
(options.navigate ?? ((url) => location.assign(url)))(response.url);
|
|
116
187
|
}
|
|
@@ -124,7 +195,10 @@ export function createForm(options = {}) {
|
|
|
124
195
|
field(name) {
|
|
125
196
|
let field = fields.get(name);
|
|
126
197
|
if (field === undefined) {
|
|
127
|
-
const errors = computed(() =>
|
|
198
|
+
const errors = computed(() => {
|
|
199
|
+
const found = problem.get()?.fields;
|
|
200
|
+
return found !== undefined && Object.hasOwn(found, name) ? found[name] ?? EMPTY : EMPTY;
|
|
201
|
+
});
|
|
128
202
|
field = {
|
|
129
203
|
errors,
|
|
130
204
|
error: computed(() => errors.get()[0]),
|
|
@@ -137,7 +211,7 @@ export function createForm(options = {}) {
|
|
|
137
211
|
submit(event) {
|
|
138
212
|
const element = event.currentTarget;
|
|
139
213
|
const submitter = event.submitter;
|
|
140
|
-
const method =
|
|
214
|
+
const method = submitter !== null && submitter.hasAttribute("formmethod") ? submitter.formMethod : element.method;
|
|
141
215
|
if (method === "dialog") {
|
|
142
216
|
return;
|
|
143
217
|
}
|
|
@@ -154,7 +228,7 @@ export function createForm(options = {}) {
|
|
|
154
228
|
error.set(thrown);
|
|
155
229
|
status.set("error");
|
|
156
230
|
});
|
|
157
|
-
options.onError?.(thrown, element);
|
|
231
|
+
guarded(() => options.onError?.(thrown, element));
|
|
158
232
|
});
|
|
159
233
|
},
|
|
160
234
|
reset() {
|
package/dist/form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAqC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACrH,OAAO,EAAuB,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
import type { Problem } from "zerolith";
|
|
8
8
|
/** One segment of an issue path in object form. */
|
|
9
9
|
export interface StandardPathSegment {
|
|
10
|
+
/** The key representing the path segment; `problemFromIssues` converts it to a string and joins the path's keys with dots to name the field. */
|
|
10
11
|
readonly key: PropertyKey;
|
|
11
12
|
}
|
|
12
13
|
/** One validation failure. */
|
|
13
14
|
export interface StandardIssue {
|
|
15
|
+
/** The error message of the issue; `problemFromIssues` lists it under the field its path names, or in `detail` when the path is absent or empty. */
|
|
14
16
|
readonly message: string;
|
|
15
17
|
/** The path to the failing value; absent for an issue about the whole input. */
|
|
16
18
|
readonly path?: ReadonlyArray<PropertyKey | StandardPathSegment> | undefined;
|
|
@@ -24,6 +26,7 @@ export type StandardResult<Output> = {
|
|
|
24
26
|
};
|
|
25
27
|
/** The Standard Schema V1 interface, as published at standardschema.dev. */
|
|
26
28
|
export interface StandardSchema<Input = unknown, Output = Input> {
|
|
29
|
+
/** The Standard properties: the `version` of the specification, the `vendor` name of the schema library, the optional inferred `types`, and the `validate` function that `validateForm` calls and awaits. */
|
|
27
30
|
readonly "~standard": {
|
|
28
31
|
readonly version: 1;
|
|
29
32
|
readonly vendor: string;
|
|
@@ -46,7 +49,7 @@ export type Validation<Output> = {
|
|
|
46
49
|
readonly value?: undefined;
|
|
47
50
|
readonly problem: Problem;
|
|
48
51
|
};
|
|
49
|
-
/** Turns `FormData` into a plain object. A name that appears once maps to its value; a name that appears more than once maps to an array of its values, in order. */
|
|
52
|
+
/** Turns `FormData` into a plain object with no prototype, so a field named `constructor` or `__proto__` is a field like any other. A name that appears once maps to its value; a name that appears more than once maps to an array of its values, in order. */
|
|
50
53
|
export declare function formValues(data: FormData): FormValues;
|
|
51
54
|
/** Groups issues into a problem: path-bearing issues under `fields`, the rest in `detail`. */
|
|
52
55
|
export declare function problemFromIssues(issues: ReadonlyArray<StandardIssue>): Problem;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAe,OAAO,EAAE,MAAM,UAAU,CAAC;AAErD,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAEhC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;CAC7B;AAED,8BAA8B;AAC9B,MAAM,WAAW,aAAa;IAE1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,GAAG,mBAAmB,CAAC,GAAG,SAAS,CAAC;CAChF;AAED,uDAAuD;AACvD,MAAM,MAAM,cAAc,CAAC,MAAM,IAC3B;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAAE,CAAC;AAExD,4EAA4E;AAC5E,MAAM,WAAW,cAAc,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;IAE3D,QAAQ,CAAC,WAAW,EAAE;QAClB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,cAAc,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QAChG,QAAQ,CAAC,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAC;KACnF,CAAC;CACL;AAED,mCAAmC;AACnC,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,cAAc,IAAI,CAAC,SAAS,cAAc,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC;AAErH,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,EAAE,CAAC,CAAC;AAEnF,iFAAiF;AACjF,MAAM,MAAM,UAAU,CAAC,MAAM,IACvB;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhE,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAe,OAAO,EAAE,MAAM,UAAU,CAAC;AAErD,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAEhC,gJAAgJ;IAChJ,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;CAC7B;AAED,8BAA8B;AAC9B,MAAM,WAAW,aAAa;IAE1B,oJAAoJ;IACpJ,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,GAAG,mBAAmB,CAAC,GAAG,SAAS,CAAC;CAChF;AAED,uDAAuD;AACvD,MAAM,MAAM,cAAc,CAAC,MAAM,IAC3B;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,CAAA;CAAE,CAAC;AAExD,4EAA4E;AAC5E,MAAM,WAAW,cAAc,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,KAAK;IAE3D,6MAA6M;IAC7M,QAAQ,CAAC,WAAW,EAAE;QAClB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,cAAc,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QAChG,QAAQ,CAAC,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAC;KACnF,CAAC;CACL;AAED,mCAAmC;AACnC,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,cAAc,IAAI,CAAC,SAAS,cAAc,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC;AAErH,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,EAAE,CAAC,CAAC;AAEnF,iFAAiF;AACjF,MAAM,MAAM,UAAU,CAAC,MAAM,IACvB;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhE,gQAAgQ;AAChQ,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAoBrD;AAWD,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,OAAO,CAoB/E;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,SAAS,cAAc,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CASrJ"}
|
package/dist/schema.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** Turns `FormData` into a plain object. A name that appears once maps to its value; a name that appears more than once maps to an array of its values, in order. */
|
|
1
|
+
/** Turns `FormData` into a plain object with no prototype, so a field named `constructor` or `__proto__` is a field like any other. A name that appears once maps to its value; a name that appears more than once maps to an array of its values, in order. */
|
|
2
2
|
export function formValues(data) {
|
|
3
|
-
const values =
|
|
3
|
+
const values = Object.create(null);
|
|
4
4
|
for (const [name, value] of data) {
|
|
5
5
|
const current = values[name];
|
|
6
6
|
if (current === undefined) {
|
|
@@ -23,7 +23,7 @@ function fieldName(path) {
|
|
|
23
23
|
}
|
|
24
24
|
/** Groups issues into a problem: path-bearing issues under `fields`, the rest in `detail`. */
|
|
25
25
|
export function problemFromIssues(issues) {
|
|
26
|
-
const fields =
|
|
26
|
+
const fields = Object.create(null);
|
|
27
27
|
const general = [];
|
|
28
28
|
for (const issue of issues) {
|
|
29
29
|
const name = fieldName(issue.path);
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAoDA,gQAAgQ;AAChQ,MAAM,UAAU,UAAU,CAAC,IAAc;IAErC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAe,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAChC,CAAC;QACG,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,OAAO,KAAK,SAAS,EACzB,CAAC;YACG,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACzB,CAAC;aACI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAC/B,CAAC;YACG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;aAED,CAAC;YACG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,SAAS,SAAS,CAAC,IAAkE;IAEjF,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAC3C,CAAC;QACG,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxG,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,iBAAiB,CAAC,MAAoC;IAElE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA6B,CAAC;IAC/D,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAC1B,CAAC;QACG,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,IAAI,KAAK,SAAS,EACtB,CAAC;YACG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,SAAS;QACb,CAAC;QACD,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,OAAO,GAAY,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,mCAAmC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAqB,EAAE,CAAC;IACzI,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EACtB,CAAC;QACG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAA2B,MAAS,EAAE,IAAwC;IAE5G,MAAM,KAAK,GAAG,IAAI,YAAY,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAC3D,CAAC;QACG,OAAO,EAAE,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IACzD,CAAC;IACD,OAAO,EAAE,KAAK,EAAG,MAAoC,CAAC,KAAK,EAAE,CAAC;AAClE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerolith/forms",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.6",
|
|
4
4
|
"description": "Forms for zerolith: native forms that post FormData and work without JavaScript, enhanced with fetch, pending state, and RFC 9457 problem+json field errors when it runs, with Standard Schema validation shared by client and server",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "molexxxx",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"build": "tsc -p tsconfig.build.json"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"zerolith": "^0.1.0-alpha.
|
|
45
|
+
"zerolith": "^0.1.0-alpha.6"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"zerolith": "^0.1.0-alpha.
|
|
48
|
+
"zerolith": "^0.1.0-alpha.6"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/src/form.ts
CHANGED
|
@@ -33,7 +33,9 @@ export interface SubmissionOutcome
|
|
|
33
33
|
{
|
|
34
34
|
/** The parsed body: JSON when the response says so, text otherwise, undefined for an empty body. */
|
|
35
35
|
readonly result: unknown;
|
|
36
|
+
/** The successful response, as `fetch` or a custom `send` returned it; its body has already been read into `result` unless it was redirected. */
|
|
36
37
|
readonly response: Response;
|
|
38
|
+
/** The form that was submitted, already reset when the `reset` option is set. */
|
|
37
39
|
readonly element: HTMLFormElement;
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -74,6 +76,7 @@ export interface Field
|
|
|
74
76
|
/** The enhanced form. */
|
|
75
77
|
export interface Form
|
|
76
78
|
{
|
|
79
|
+
/** The current submission's stage: `pending` while in flight, then `success`, `failure` with a problem, or `error` when it did not complete; `idle` before the first submission and after `reset()`. */
|
|
77
80
|
readonly status: ReadableSignal<FormStatus>;
|
|
78
81
|
/** True while a submission is in flight. */
|
|
79
82
|
readonly pending: ReadableSignal<boolean>;
|
|
@@ -93,6 +96,101 @@ export interface Form
|
|
|
93
96
|
|
|
94
97
|
const EMPTY: readonly string[] = [];
|
|
95
98
|
|
|
99
|
+
/** Calls an application callback, reporting what it throws as an uncaught error would be, without changing the form's state. */
|
|
100
|
+
function guarded(callback: () => void): void
|
|
101
|
+
{
|
|
102
|
+
try
|
|
103
|
+
{
|
|
104
|
+
callback();
|
|
105
|
+
}
|
|
106
|
+
catch (thrown)
|
|
107
|
+
{
|
|
108
|
+
if (typeof reportError === "function")
|
|
109
|
+
{
|
|
110
|
+
reportError(thrown);
|
|
111
|
+
}
|
|
112
|
+
else
|
|
113
|
+
{
|
|
114
|
+
queueMicrotask(() =>
|
|
115
|
+
{
|
|
116
|
+
throw thrown;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The `FormData` implementation probed, and whether its constructor adds the submitter's entry, which browsers before Chrome 112 and Firefox 111 skip. */
|
|
123
|
+
let probed: { implementation: typeof FormData; keepsSubmitter: boolean } | undefined;
|
|
124
|
+
|
|
125
|
+
/** The form's data as the browser submits it, with the submitter's entry added where `new FormData(form, submitter)` leaves it out. */
|
|
126
|
+
function submittedData(element: HTMLFormElement, submitter: HTMLElement | null): FormData
|
|
127
|
+
{
|
|
128
|
+
const data = new FormData(element, submitter ?? undefined);
|
|
129
|
+
if (submitter === null)
|
|
130
|
+
{
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
if (probed?.implementation !== FormData)
|
|
134
|
+
{
|
|
135
|
+
const form = document.createElement("form");
|
|
136
|
+
const button = document.createElement("button");
|
|
137
|
+
button.name = "probe";
|
|
138
|
+
button.value = "kept";
|
|
139
|
+
form.append(button);
|
|
140
|
+
probed = { implementation: FormData, keepsSubmitter: new FormData(form, button).get("probe") === "kept" };
|
|
141
|
+
}
|
|
142
|
+
const name = submitter.getAttribute("name");
|
|
143
|
+
if (probed.keepsSubmitter || name === null || name === "")
|
|
144
|
+
{
|
|
145
|
+
return data;
|
|
146
|
+
}
|
|
147
|
+
if (submitter instanceof HTMLInputElement && submitter.type === "image")
|
|
148
|
+
{
|
|
149
|
+
data.append(`${name}.x`, "0");
|
|
150
|
+
data.append(`${name}.y`, "0");
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
{
|
|
154
|
+
data.append(name, (submitter as HTMLButtonElement | HTMLInputElement).value);
|
|
155
|
+
}
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The problem a failed response describes: its body when that is a problem
|
|
161
|
+
* document, or one built from the status. The server is a trust boundary,
|
|
162
|
+
* so `fields` keeps only own keys whose value is a message or a list of
|
|
163
|
+
* messages, a lone message becoming a list of one.
|
|
164
|
+
*/
|
|
165
|
+
function problemOf(body: unknown, response: Response): Problem
|
|
166
|
+
{
|
|
167
|
+
if (!isProblem(body))
|
|
168
|
+
{
|
|
169
|
+
return { status: response.status, title: response.statusText === "" ? `HTTP ${response.status}` : response.statusText };
|
|
170
|
+
}
|
|
171
|
+
if (body.fields === undefined)
|
|
172
|
+
{
|
|
173
|
+
return body;
|
|
174
|
+
}
|
|
175
|
+
const fields: Record<string, readonly string[]> = Object.create(null) as Record<string, readonly string[]>;
|
|
176
|
+
for (const [name, value] of Object.entries(body.fields as Record<string, unknown>))
|
|
177
|
+
{
|
|
178
|
+
if (typeof value === "string")
|
|
179
|
+
{
|
|
180
|
+
fields[name] = [value];
|
|
181
|
+
}
|
|
182
|
+
else if (Array.isArray(value))
|
|
183
|
+
{
|
|
184
|
+
const messages = value.filter((message): message is string => typeof message === "string");
|
|
185
|
+
if (messages.length > 0)
|
|
186
|
+
{
|
|
187
|
+
fields[name] = messages;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { ...body, fields };
|
|
192
|
+
}
|
|
193
|
+
|
|
96
194
|
function entries(data: FormData): [string, string][]
|
|
97
195
|
{
|
|
98
196
|
return [...data].map(([name, value]) => [name, typeof value === "string" ? value : value.name]);
|
|
@@ -113,7 +211,7 @@ async function defaultSend(submission: Submission, headers: HeadersInit | undefi
|
|
|
113
211
|
url.search = new URLSearchParams(entries(data)).toString();
|
|
114
212
|
return fetchImpl(url, init);
|
|
115
213
|
}
|
|
116
|
-
const enctype = submitter
|
|
214
|
+
const enctype = submitter !== null && submitter.hasAttribute("formenctype") ? (submitter as HTMLButtonElement).formEnctype : element.enctype;
|
|
117
215
|
if (enctype === "multipart/form-data")
|
|
118
216
|
{
|
|
119
217
|
init.body = data;
|
|
@@ -167,12 +265,12 @@ export function createForm(options: FormOptions = {}): Form
|
|
|
167
265
|
problem.set(found);
|
|
168
266
|
status.set("failure");
|
|
169
267
|
});
|
|
170
|
-
options.onFailure?.(found, element);
|
|
268
|
+
guarded(() => options.onFailure?.(found, element));
|
|
171
269
|
};
|
|
172
270
|
|
|
173
271
|
const run = async (element: HTMLFormElement, submitter: HTMLElement | null, method: string, action: URL, current: AbortController): Promise<void> =>
|
|
174
272
|
{
|
|
175
|
-
const data =
|
|
273
|
+
const data = submittedData(element, submitter);
|
|
176
274
|
batch(() =>
|
|
177
275
|
{
|
|
178
276
|
status.set("pending");
|
|
@@ -198,16 +296,15 @@ export function createForm(options: FormOptions = {}): Form
|
|
|
198
296
|
{
|
|
199
297
|
return;
|
|
200
298
|
}
|
|
201
|
-
const type = response.headers.get("content-type") ?? "";
|
|
202
|
-
if (type.includes(PROBLEM_MEDIA_TYPE))
|
|
203
|
-
{
|
|
204
|
-
fail((await response.json()) as Problem, element);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (!response.ok)
|
|
299
|
+
const type = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
300
|
+
if (type.includes(PROBLEM_MEDIA_TYPE) || !response.ok)
|
|
208
301
|
{
|
|
209
|
-
const body = type.includes("json") ? await response.json().catch(() => undefined) : undefined;
|
|
210
|
-
|
|
302
|
+
const body: unknown = type.includes("json") ? await response.json().catch(() => undefined) : undefined;
|
|
303
|
+
if (current.signal.aborted)
|
|
304
|
+
{
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
fail(problemOf(body, response), element);
|
|
211
308
|
return;
|
|
212
309
|
}
|
|
213
310
|
const parsed = response.redirected ? undefined : await parseBody(response);
|
|
@@ -224,7 +321,7 @@ export function createForm(options: FormOptions = {}): Form
|
|
|
224
321
|
{
|
|
225
322
|
element.reset();
|
|
226
323
|
}
|
|
227
|
-
options.onSuccess?.({ result: parsed, response, element });
|
|
324
|
+
guarded(() => options.onSuccess?.({ result: parsed, response, element }));
|
|
228
325
|
if (response.redirected)
|
|
229
326
|
{
|
|
230
327
|
(options.navigate ?? ((url) => location.assign(url)))(response.url);
|
|
@@ -242,7 +339,11 @@ export function createForm(options: FormOptions = {}): Form
|
|
|
242
339
|
let field = fields.get(name);
|
|
243
340
|
if (field === undefined)
|
|
244
341
|
{
|
|
245
|
-
const errors = computed<readonly string[]>(() =>
|
|
342
|
+
const errors = computed<readonly string[]>(() =>
|
|
343
|
+
{
|
|
344
|
+
const found = problem.get()?.fields;
|
|
345
|
+
return found !== undefined && Object.hasOwn(found, name) ? found[name] ?? EMPTY : EMPTY;
|
|
346
|
+
});
|
|
246
347
|
field = {
|
|
247
348
|
errors,
|
|
248
349
|
error: computed(() => errors.get()[0]),
|
|
@@ -256,7 +357,7 @@ export function createForm(options: FormOptions = {}): Form
|
|
|
256
357
|
{
|
|
257
358
|
const element = event.currentTarget as HTMLFormElement;
|
|
258
359
|
const submitter = event.submitter;
|
|
259
|
-
const method =
|
|
360
|
+
const method = submitter !== null && submitter.hasAttribute("formmethod") ? (submitter as HTMLButtonElement).formMethod : element.method;
|
|
260
361
|
if (method === "dialog")
|
|
261
362
|
{
|
|
262
363
|
return;
|
|
@@ -277,7 +378,7 @@ export function createForm(options: FormOptions = {}): Form
|
|
|
277
378
|
error.set(thrown);
|
|
278
379
|
status.set("error");
|
|
279
380
|
});
|
|
280
|
-
options.onError?.(thrown, element);
|
|
381
|
+
guarded(() => options.onError?.(thrown, element));
|
|
281
382
|
});
|
|
282
383
|
},
|
|
283
384
|
reset(): void
|
package/src/schema.ts
CHANGED
|
@@ -9,12 +9,14 @@ import type { FieldErrors, Problem } from "zerolith";
|
|
|
9
9
|
/** One segment of an issue path in object form. */
|
|
10
10
|
export interface StandardPathSegment
|
|
11
11
|
{
|
|
12
|
+
/** The key representing the path segment; `problemFromIssues` converts it to a string and joins the path's keys with dots to name the field. */
|
|
12
13
|
readonly key: PropertyKey;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/** One validation failure. */
|
|
16
17
|
export interface StandardIssue
|
|
17
18
|
{
|
|
19
|
+
/** The error message of the issue; `problemFromIssues` lists it under the field its path names, or in `detail` when the path is absent or empty. */
|
|
18
20
|
readonly message: string;
|
|
19
21
|
/** The path to the failing value; absent for an issue about the whole input. */
|
|
20
22
|
readonly path?: ReadonlyArray<PropertyKey | StandardPathSegment> | undefined;
|
|
@@ -28,6 +30,7 @@ export type StandardResult<Output> =
|
|
|
28
30
|
/** The Standard Schema V1 interface, as published at standardschema.dev. */
|
|
29
31
|
export interface StandardSchema<Input = unknown, Output = Input>
|
|
30
32
|
{
|
|
33
|
+
/** The Standard properties: the `version` of the specification, the `vendor` name of the schema library, the optional inferred `types`, and the `validate` function that `validateForm` calls and awaits. */
|
|
31
34
|
readonly "~standard": {
|
|
32
35
|
readonly version: 1;
|
|
33
36
|
readonly vendor: string;
|
|
@@ -47,10 +50,10 @@ export type Validation<Output> =
|
|
|
47
50
|
| { readonly value: Output; readonly problem?: undefined }
|
|
48
51
|
| { readonly value?: undefined; readonly problem: Problem };
|
|
49
52
|
|
|
50
|
-
/** Turns `FormData` into a plain object. A name that appears once maps to its value; a name that appears more than once maps to an array of its values, in order. */
|
|
53
|
+
/** Turns `FormData` into a plain object with no prototype, so a field named `constructor` or `__proto__` is a field like any other. A name that appears once maps to its value; a name that appears more than once maps to an array of its values, in order. */
|
|
51
54
|
export function formValues(data: FormData): FormValues
|
|
52
55
|
{
|
|
53
|
-
const values
|
|
56
|
+
const values = Object.create(null) as FormValues;
|
|
54
57
|
for (const [name, value] of data)
|
|
55
58
|
{
|
|
56
59
|
const current = values[name];
|
|
@@ -82,7 +85,7 @@ function fieldName(path: ReadonlyArray<PropertyKey | StandardPathSegment> | unde
|
|
|
82
85
|
/** Groups issues into a problem: path-bearing issues under `fields`, the rest in `detail`. */
|
|
83
86
|
export function problemFromIssues(issues: ReadonlyArray<StandardIssue>): Problem
|
|
84
87
|
{
|
|
85
|
-
const fields
|
|
88
|
+
const fields = Object.create(null) as Record<string, string[]>;
|
|
86
89
|
const general: string[] = [];
|
|
87
90
|
for (const issue of issues)
|
|
88
91
|
{
|