@vielzeug/forge 2.0.2 → 2.2.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/README.md +2 -7
- package/dist/_field.cjs +2 -0
- package/dist/_field.cjs.map +1 -0
- package/dist/_field.d.ts +18 -0
- package/dist/_field.d.ts.map +1 -0
- package/dist/_field.js +64 -0
- package/dist/_field.js.map +1 -0
- package/dist/_notify.cjs +2 -0
- package/dist/_notify.cjs.map +1 -0
- package/dist/_notify.d.ts +8 -0
- package/dist/_notify.d.ts.map +1 -0
- package/dist/_notify.js +39 -0
- package/dist/_notify.js.map +1 -0
- package/dist/core/path.cjs +1 -1
- package/dist/core/path.cjs.map +1 -1
- package/dist/core/path.d.ts +14 -9
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +84 -50
- package/dist/core/path.js.map +1 -1
- package/dist/forge.cjs +1 -1
- package/dist/forge.cjs.map +1 -1
- package/dist/forge.iife.js +1 -1
- package/dist/forge.iife.js.map +1 -1
- package/dist/forge.js +1 -1
- package/dist/forge.js.map +1 -1
- package/dist/form-data.cjs +2 -0
- package/dist/form-data.cjs.map +1 -0
- package/dist/form-data.d.ts.map +1 -0
- package/dist/form-data.js +32 -0
- package/dist/form-data.js.map +1 -0
- package/dist/form.cjs +1 -1
- package/dist/form.cjs.map +1 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +106 -162
- package/dist/form.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/spell.cjs +1 -1
- package/dist/spell.cjs.map +1 -1
- package/dist/spell.d.ts +1 -1
- package/dist/spell.d.ts.map +1 -1
- package/dist/spell.js +17 -26
- package/dist/spell.js.map +1 -1
- package/dist/types.d.ts +14 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +56 -53
- package/dist/_utils.cjs +0 -1
- package/dist/_utils.d.ts +0 -3
- package/dist/_utils.d.ts.map +0 -1
- package/dist/_utils.js +0 -2
- package/dist/adapters/form-data.cjs +0 -2
- package/dist/adapters/form-data.cjs.map +0 -1
- package/dist/adapters/form-data.d.ts.map +0 -1
- package/dist/adapters/form-data.js +0 -14
- package/dist/adapters/form-data.js.map +0 -1
- package/dist/devtools.cjs +0 -2
- package/dist/devtools.cjs.map +0 -1
- package/dist/devtools.d.ts +0 -7
- package/dist/devtools.d.ts.map +0 -1
- package/dist/devtools.js +0 -11
- package/dist/devtools.js.map +0 -1
- /package/dist/{adapters/form-data.d.ts → form-data.d.ts} +0 -0
package/dist/form.js
CHANGED
|
@@ -1,243 +1,187 @@
|
|
|
1
1
|
import { ForgeConfigError as e, ForgeDisposedError as t, ForgeSubmitError as n, ForgeValidationError as r } from "./errors.js";
|
|
2
|
-
import { assertSafeKey as i,
|
|
2
|
+
import { assertSafeKey as i, immutable as a, isRecord as o, normalizeErrors as s, resetAtPath as c, touchAll as l, writeAtPath as u, writeMeta as d } from "./core/path.js";
|
|
3
|
+
import { createField as f } from "./_field.js";
|
|
4
|
+
import { createNotifier as p } from "./_notify.js";
|
|
3
5
|
//#region src/form.ts
|
|
4
|
-
function
|
|
5
|
-
let n = e, r = /* @__PURE__ */ new Set();
|
|
6
|
-
return {
|
|
7
|
-
dispose() {
|
|
8
|
-
r.clear();
|
|
9
|
-
},
|
|
10
|
-
read: () => n,
|
|
11
|
-
subscribe(e) {
|
|
12
|
-
return r.add(e), () => r.delete(e);
|
|
13
|
-
},
|
|
14
|
-
write(e) {
|
|
15
|
-
if (e !== n) {
|
|
16
|
-
n = e;
|
|
17
|
-
for (let e of [...r]) try {
|
|
18
|
-
e();
|
|
19
|
-
} catch (e) {
|
|
20
|
-
t(e);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function _(e) {
|
|
27
|
-
queueMicrotask(() => {
|
|
28
|
-
throw e;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
function v(e, t) {
|
|
32
|
-
let n = l(e.value, t), r = l(e.baseline, t);
|
|
6
|
+
function m(e) {
|
|
33
7
|
return Object.freeze({
|
|
34
|
-
dirty: n !== r || a(e.value, t) !== a(e.baseline, t),
|
|
35
|
-
error: u(e.errors, t),
|
|
36
|
-
touched: d(e.touched, t),
|
|
37
|
-
value: n
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
function y(e) {
|
|
41
|
-
return Object.freeze({
|
|
42
|
-
error: e.error,
|
|
43
8
|
errors: e.errors,
|
|
9
|
+
formError: e.formError,
|
|
10
|
+
hasErrors: e.formError !== void 0 || e.errors !== void 0,
|
|
44
11
|
submitCount: e.submitCount,
|
|
45
12
|
submitting: e.isSubmitting,
|
|
46
13
|
touched: Object.keys(e.touched).length > 0,
|
|
47
|
-
|
|
48
|
-
|
|
14
|
+
validating: e.isValidating,
|
|
15
|
+
validity: e.validity
|
|
49
16
|
});
|
|
50
17
|
}
|
|
51
|
-
function
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
if (!a.onSubscriberError) {
|
|
55
|
-
_(e);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
a.onSubscriberError(e);
|
|
60
|
-
} catch (e) {
|
|
61
|
-
_(e);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
let b = g({
|
|
65
|
-
baseline: u,
|
|
66
|
-
error: void 0,
|
|
18
|
+
function h(h) {
|
|
19
|
+
let g = a(h.initialValues), _ = p(h.onSubscriberError), v = new AbortController(), y = !1, b, x = {
|
|
20
|
+
baseline: g,
|
|
67
21
|
errors: void 0,
|
|
22
|
+
formError: void 0,
|
|
68
23
|
isSubmitting: !1,
|
|
69
24
|
isValidating: !1,
|
|
70
25
|
submitCount: 0,
|
|
71
26
|
touched: {},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
27
|
+
validity: "unknown",
|
|
28
|
+
value: g
|
|
29
|
+
};
|
|
30
|
+
function S(e) {
|
|
31
|
+
if (y) throw new t(e);
|
|
32
|
+
}
|
|
33
|
+
function C(e) {
|
|
34
|
+
x = Object.freeze(e(x)), _.notify();
|
|
76
35
|
}
|
|
77
|
-
function
|
|
78
|
-
b
|
|
36
|
+
function w() {
|
|
37
|
+
b?.abort();
|
|
79
38
|
}
|
|
39
|
+
let T = {
|
|
40
|
+
abortValidation: w,
|
|
41
|
+
addListener: _.add,
|
|
42
|
+
ensureActive: S,
|
|
43
|
+
readState: () => x,
|
|
44
|
+
resetValue(e) {
|
|
45
|
+
C((t) => ({
|
|
46
|
+
...t,
|
|
47
|
+
touched: d(t.touched, e, !1),
|
|
48
|
+
validity: "unknown",
|
|
49
|
+
value: c(t.value, t.baseline, e)
|
|
50
|
+
}));
|
|
51
|
+
},
|
|
52
|
+
setTouched(e, t) {
|
|
53
|
+
C((n) => ({
|
|
54
|
+
...n,
|
|
55
|
+
touched: d(n.touched, e, t)
|
|
56
|
+
}));
|
|
57
|
+
},
|
|
58
|
+
setValue(e, t) {
|
|
59
|
+
C((n) => ({
|
|
60
|
+
...n,
|
|
61
|
+
validity: "unknown",
|
|
62
|
+
value: u(n.value, e, t)
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
};
|
|
80
66
|
async function E(e) {
|
|
81
|
-
|
|
67
|
+
S("validate"), w();
|
|
82
68
|
let t = new AbortController(), n = e ? AbortSignal.any([t.signal, e]) : t.signal;
|
|
83
|
-
|
|
69
|
+
b = t, C((e) => ({
|
|
84
70
|
...e,
|
|
85
71
|
isValidating: !0
|
|
86
72
|
}));
|
|
87
73
|
try {
|
|
88
|
-
let e =
|
|
89
|
-
if (n.aborted ||
|
|
90
|
-
let r = e?.fields ?
|
|
91
|
-
return
|
|
74
|
+
let e = h.validate ? await h.validate(x.value, n) : void 0;
|
|
75
|
+
if (n.aborted || b !== t) return Object.freeze({ status: "aborted" });
|
|
76
|
+
let r = e?.fields ? s(a(e.fields)) : void 0, i = e?.formError, o = r === void 0 && i === void 0 ? "valid" : "invalid";
|
|
77
|
+
return C((e) => ({
|
|
92
78
|
...e,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
79
|
+
errors: r,
|
|
80
|
+
formError: i,
|
|
81
|
+
validity: o
|
|
82
|
+
})), Object.freeze(o === "valid" ? { status: "valid" } : {
|
|
96
83
|
errors: r,
|
|
97
84
|
formError: i,
|
|
98
85
|
status: "invalid"
|
|
99
86
|
});
|
|
100
87
|
} catch (e) {
|
|
101
|
-
if (n.aborted ||
|
|
88
|
+
if (n.aborted || b !== t) return Object.freeze({ status: "aborted" });
|
|
102
89
|
throw new r("Form validation failed.", { cause: e });
|
|
103
90
|
} finally {
|
|
104
|
-
|
|
91
|
+
b === t && (b = void 0, y || C((e) => ({
|
|
105
92
|
...e,
|
|
106
93
|
isValidating: !1
|
|
107
94
|
})));
|
|
108
95
|
}
|
|
109
96
|
}
|
|
110
|
-
|
|
111
|
-
return Object.assign({
|
|
112
|
-
get dirty() {
|
|
113
|
-
return v(b.read(), t).dirty;
|
|
114
|
-
},
|
|
115
|
-
get error() {
|
|
116
|
-
return v(b.read(), t).error;
|
|
117
|
-
},
|
|
118
|
-
reset() {
|
|
119
|
-
w("field().reset"), C?.abort(), T((e) => ({
|
|
120
|
-
...e,
|
|
121
|
-
touched: h(e.touched, t, !1),
|
|
122
|
-
value: f(e.value, e.baseline, t)
|
|
123
|
-
}));
|
|
124
|
-
},
|
|
125
|
-
set(e) {
|
|
126
|
-
w("field().set"), C?.abort();
|
|
127
|
-
let n = l(b.read().value, t), r = typeof e == "function" ? e(n) : e;
|
|
128
|
-
T((e) => ({
|
|
129
|
-
...e,
|
|
130
|
-
value: m(e.value, t, r)
|
|
131
|
-
}));
|
|
132
|
-
},
|
|
133
|
-
subscribe(e, n = {}) {
|
|
134
|
-
w("field().subscribe");
|
|
135
|
-
let r = v(b.read(), t);
|
|
136
|
-
return n.immediate && e(r), b.subscribe(() => {
|
|
137
|
-
let n = v(b.read(), t);
|
|
138
|
-
(n.value !== r.value || n.error !== r.error || n.touched !== r.touched || n.dirty !== r.dirty) && (r = n, e(n));
|
|
139
|
-
});
|
|
140
|
-
},
|
|
141
|
-
touch() {
|
|
142
|
-
w("field().touch"), T((e) => ({
|
|
143
|
-
...e,
|
|
144
|
-
touched: h(e.touched, t, !0)
|
|
145
|
-
}));
|
|
146
|
-
},
|
|
147
|
-
get touched() {
|
|
148
|
-
return v(b.read(), t).touched;
|
|
149
|
-
},
|
|
150
|
-
get value() {
|
|
151
|
-
return v(b.read(), t).value;
|
|
152
|
-
}
|
|
153
|
-
}, { field(n) {
|
|
154
|
-
i(n);
|
|
155
|
-
let r = l(b.read().value, t);
|
|
156
|
-
if (r !== void 0 && !s(r)) throw new e(`Cannot select '${n}' because the current field value is not an object.`);
|
|
157
|
-
return D([...t, n]);
|
|
158
|
-
} });
|
|
159
|
-
}
|
|
160
|
-
let O = {
|
|
97
|
+
let D = {
|
|
161
98
|
get disposalSignal() {
|
|
162
|
-
return
|
|
99
|
+
return v.signal;
|
|
163
100
|
},
|
|
164
101
|
dispose() {
|
|
165
|
-
|
|
102
|
+
y || (y = !0, v.abort(), w(), _.clear());
|
|
166
103
|
},
|
|
167
104
|
get disposed() {
|
|
168
|
-
return
|
|
105
|
+
return y;
|
|
169
106
|
},
|
|
170
|
-
field(
|
|
171
|
-
|
|
107
|
+
field(t) {
|
|
108
|
+
if (i(t), !o(x.value)) throw new e("Form value must be an object.");
|
|
109
|
+
return f([t], T);
|
|
172
110
|
},
|
|
173
111
|
reset(e) {
|
|
174
|
-
|
|
175
|
-
let t = e === void 0 ?
|
|
176
|
-
|
|
112
|
+
S("reset"), w();
|
|
113
|
+
let t = e === void 0 ? x.baseline : a(e);
|
|
114
|
+
C((e) => ({
|
|
177
115
|
...e,
|
|
178
116
|
baseline: t,
|
|
179
|
-
error: void 0,
|
|
180
117
|
errors: void 0,
|
|
118
|
+
formError: void 0,
|
|
181
119
|
touched: {},
|
|
120
|
+
validity: "unknown",
|
|
182
121
|
value: t
|
|
183
122
|
}));
|
|
184
123
|
},
|
|
185
124
|
set(e) {
|
|
186
|
-
|
|
187
|
-
let t = typeof e == "function" ? e(
|
|
188
|
-
|
|
125
|
+
S("set"), w();
|
|
126
|
+
let t = typeof e == "function" ? e(D.value) : e;
|
|
127
|
+
C((e) => ({
|
|
189
128
|
...e,
|
|
190
|
-
error: void 0,
|
|
191
129
|
errors: void 0,
|
|
192
|
-
|
|
130
|
+
formError: void 0,
|
|
131
|
+
validity: "unknown",
|
|
132
|
+
value: a(t)
|
|
193
133
|
}));
|
|
194
134
|
},
|
|
195
135
|
get state() {
|
|
196
|
-
return
|
|
136
|
+
return m(x);
|
|
197
137
|
},
|
|
198
|
-
async submit(e) {
|
|
199
|
-
if (
|
|
200
|
-
|
|
138
|
+
async submit(e, t) {
|
|
139
|
+
if (S("submit"), x.isSubmitting) throw new n("submit() called while a submission is already in progress");
|
|
140
|
+
let r = t ? AbortSignal.any([v.signal, t]) : v.signal;
|
|
141
|
+
C((e) => ({
|
|
201
142
|
...e,
|
|
202
143
|
isSubmitting: !0,
|
|
203
144
|
submitCount: e.submitCount + 1,
|
|
204
|
-
touched:
|
|
145
|
+
touched: l(e.value)
|
|
205
146
|
}));
|
|
206
147
|
try {
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
formError: t.formError,
|
|
214
|
-
ok: !1,
|
|
215
|
-
type: "validation"
|
|
216
|
-
}) : Object.freeze({
|
|
217
|
-
ok: !0,
|
|
218
|
-
value: await e(O.value)
|
|
148
|
+
let n = await E(t);
|
|
149
|
+
if (n.status === "aborted") return Object.freeze({ status: "aborted" });
|
|
150
|
+
if (n.status === "invalid") return Object.freeze({
|
|
151
|
+
errors: n.errors,
|
|
152
|
+
formError: n.formError,
|
|
153
|
+
status: "invalid"
|
|
219
154
|
});
|
|
155
|
+
try {
|
|
156
|
+
return Object.freeze({
|
|
157
|
+
status: "ok",
|
|
158
|
+
value: await e(D.value, r)
|
|
159
|
+
});
|
|
160
|
+
} catch (e) {
|
|
161
|
+
if (r.aborted) return Object.freeze({ status: "aborted" });
|
|
162
|
+
throw e;
|
|
163
|
+
}
|
|
220
164
|
} finally {
|
|
221
|
-
|
|
165
|
+
y || C((e) => ({
|
|
222
166
|
...e,
|
|
223
167
|
isSubmitting: !1
|
|
224
168
|
}));
|
|
225
169
|
}
|
|
226
170
|
},
|
|
227
171
|
subscribe(e, t = {}) {
|
|
228
|
-
return
|
|
172
|
+
return S("subscribe"), t.immediate && e(m(x)), _.add(() => e(m(x)));
|
|
229
173
|
},
|
|
230
174
|
[Symbol.dispose]() {
|
|
231
|
-
|
|
175
|
+
D.dispose();
|
|
232
176
|
},
|
|
233
177
|
validate: E,
|
|
234
178
|
get value() {
|
|
235
|
-
return
|
|
179
|
+
return x.value;
|
|
236
180
|
}
|
|
237
181
|
};
|
|
238
|
-
return
|
|
182
|
+
return D;
|
|
239
183
|
}
|
|
240
184
|
//#endregion
|
|
241
|
-
export {
|
|
185
|
+
export { h as createForm };
|
|
242
186
|
|
|
243
187
|
//# sourceMappingURL=form.js.map
|
package/dist/form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.js","names":[],"sources":["../src/form.ts"],"sourcesContent":["import type {\n Field,\n FieldState,\n Form,\n FormErrors,\n FormOptions,\n FormState,\n MaybePromise,\n ReadonlyDeep,\n SubmitResult,\n SubscribeOptions,\n Unsubscribe,\n ValidationErrors,\n ValidationResult,\n} from './types';\n\nimport {\n assertSafeKey,\n hasAtPath,\n immutable,\n isRecord,\n normalizeErrors,\n readAtPath,\n readError,\n readMeta,\n resetAtPath,\n type MetaRoot,\n touchAll,\n writeAtPath,\n writeMeta,\n} from './core/path';\nimport { ForgeConfigError, ForgeDisposedError, ForgeSubmitError, ForgeValidationError } from './errors';\n\ntype InternalState<TValues extends Record<string, unknown>> = Readonly<{\n baseline: TValues;\n error: string | undefined;\n errors: FormErrors<TValues> | undefined;\n isSubmitting: boolean;\n isValidating: boolean;\n submitCount: number;\n touched: MetaRoot;\n value: TValues;\n}>;\n\ntype Store<T> = {\n dispose(): void;\n read(): T;\n subscribe(listener: () => void): Unsubscribe;\n write(next: T): void;\n};\n\nfunction createStore<T>(initial: T, onListenerError: (error: unknown) => void): Store<T> {\n let current = initial;\n const listeners = new Set<() => void>();\n\n return {\n dispose() {\n listeners.clear();\n },\n read: () => current,\n subscribe(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n write(next) {\n if (next === current) return;\n\n current = next;\n\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch (error) {\n onListenerError(error);\n }\n }\n },\n };\n}\n\nfunction rethrowAsync(error: unknown): void {\n queueMicrotask(() => {\n throw error;\n });\n}\n\nfunction makeFieldState<V>(state: InternalState<Record<string, unknown>>, path: readonly string[]): FieldState<V> {\n const value = readAtPath<V>(state.value, path);\n const baseline = readAtPath<V>(state.baseline, path);\n\n return Object.freeze({\n dirty: value !== baseline || hasAtPath(state.value, path) !== hasAtPath(state.baseline, path),\n error: readError(state.errors, path),\n touched: readMeta(state.touched, path),\n value: value as ReadonlyDeep<V>,\n });\n}\n\nfunction makeFormState<TValues extends Record<string, unknown>>(current: InternalState<TValues>): FormState<TValues> {\n return Object.freeze({\n error: current.error,\n errors: current.errors,\n submitCount: current.submitCount,\n submitting: current.isSubmitting,\n touched: Object.keys(current.touched).length > 0,\n valid: current.error === undefined && current.errors === undefined,\n validating: current.isValidating,\n });\n}\n\n/** One immutable value tree and one explicit full-form validator keep form behavior locally understandable. */\nexport function createForm<TValues extends Record<string, unknown>>(options: FormOptions<TValues>): Form<TValues> {\n const initial = immutable(options.initialValues);\n\n function reportListenerError(error: unknown): void {\n if (!options.onSubscriberError) {\n rethrowAsync(error);\n\n return;\n }\n\n try {\n options.onSubscriberError(error);\n } catch (reporterError) {\n rethrowAsync(reporterError);\n }\n }\n\n const store = createStore<InternalState<TValues>>(\n {\n baseline: initial,\n error: undefined,\n errors: undefined,\n isSubmitting: false,\n isValidating: false,\n submitCount: 0,\n touched: {},\n value: initial,\n },\n reportListenerError,\n );\n const disposalController = new AbortController();\n let disposed = false;\n let validationController: AbortController | undefined;\n\n function ensureActive(operation: string): void {\n if (disposed) throw new ForgeDisposedError(operation);\n }\n\n function write(update: (current: InternalState<TValues>) => InternalState<TValues>): void {\n store.write(Object.freeze(update(store.read())));\n }\n\n async function validate(externalSignal?: AbortSignal): Promise<ValidationResult<TValues>> {\n ensureActive('validate');\n validationController?.abort();\n\n const controller = new AbortController();\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n\n validationController = controller;\n write((current) => ({ ...current, isValidating: true }));\n\n try {\n const result: ValidationErrors<TValues> | undefined = options.validate\n ? await options.validate(store.read().value as ReadonlyDeep<TValues>, signal)\n : undefined;\n\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n\n const errors = result?.fields\n ? (normalizeErrors(immutable(result.fields)) as FormErrors<TValues> | undefined)\n : undefined;\n const formError = result?.formError;\n\n write((current) => ({ ...current, error: formError, errors }));\n\n return errors === undefined && formError === undefined\n ? Object.freeze({ status: 'valid' })\n : Object.freeze({ errors, formError, status: 'invalid' });\n } catch (error) {\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n\n throw new ForgeValidationError('Form validation failed.', { cause: error });\n } finally {\n if (validationController === controller) {\n validationController = undefined;\n\n if (!disposed) write((current) => ({ ...current, isValidating: false }));\n }\n }\n }\n\n function createField<V>(path: readonly string[]): Field<V> {\n const common = {\n get dirty() {\n return makeFieldState<V>(store.read() as InternalState<Record<string, unknown>>, path).dirty;\n },\n get error() {\n return makeFieldState<V>(store.read() as InternalState<Record<string, unknown>>, path).error;\n },\n reset() {\n ensureActive('field().reset');\n validationController?.abort();\n write((current) => ({\n ...current,\n touched: writeMeta(current.touched, path, false),\n value: resetAtPath(current.value, current.baseline, path),\n }));\n },\n set(next: V | ((previous: ReadonlyDeep<V>) => V)) {\n ensureActive('field().set');\n validationController?.abort();\n\n const previous = readAtPath<V>(store.read().value, path) as ReadonlyDeep<V>;\n const value = typeof next === 'function' ? (next as (current: ReadonlyDeep<V>) => V)(previous) : next;\n\n write((current) => ({ ...current, value: writeAtPath(current.value, path, value) }));\n },\n subscribe(listener: (state: FieldState<V>) => void, subscribeOptions: SubscribeOptions = {}): Unsubscribe {\n ensureActive('field().subscribe');\n\n let previous = makeFieldState<V>(store.read() as InternalState<Record<string, unknown>>, path);\n\n if (subscribeOptions.immediate) listener(previous);\n\n return store.subscribe(() => {\n const next = makeFieldState<V>(store.read() as InternalState<Record<string, unknown>>, path);\n\n if (\n next.value === previous.value &&\n next.error === previous.error &&\n next.touched === previous.touched &&\n next.dirty === previous.dirty\n ) {\n return;\n }\n\n previous = next;\n listener(next);\n });\n },\n touch() {\n ensureActive('field().touch');\n write((current) => ({ ...current, touched: writeMeta(current.touched, path, true) }));\n },\n get touched() {\n return makeFieldState<V>(store.read() as InternalState<Record<string, unknown>>, path).touched;\n },\n get value() {\n return makeFieldState<V>(store.read() as InternalState<Record<string, unknown>>, path).value;\n },\n };\n\n return Object.assign(common, {\n field<K extends keyof NonNullable<V> & string>(key: K): Field<NonNullable<V>[K]> {\n assertSafeKey(key);\n\n const current = readAtPath(store.read().value, path);\n\n if (current !== undefined && !isRecord(current)) {\n throw new ForgeConfigError(`Cannot select '${key}' because the current field value is not an object.`);\n }\n\n return createField<NonNullable<V>[K]>([...path, key]);\n },\n }) as Field<V>;\n }\n\n const form: Form<TValues> = {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose() {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n validationController?.abort();\n store.dispose();\n },\n get disposed() {\n return disposed;\n },\n field(key) {\n assertSafeKey(key);\n\n return createField<TValues[typeof key]>([key]);\n },\n reset(next) {\n ensureActive('reset');\n validationController?.abort();\n\n const baseline = next === undefined ? store.read().baseline : immutable(next);\n\n write((current) => ({\n ...current,\n baseline,\n error: undefined,\n errors: undefined,\n touched: {},\n value: baseline,\n }));\n },\n set(next) {\n ensureActive('set');\n validationController?.abort();\n\n const value =\n typeof next === 'function' ? (next as (current: ReadonlyDeep<TValues>) => TValues)(form.value) : next;\n\n write((current) => ({ ...current, error: undefined, errors: undefined, value: immutable(value) }));\n },\n get state() {\n return makeFormState(store.read());\n },\n async submit<TResult = void>(\n handler: (values: ReadonlyDeep<TValues>) => MaybePromise<TResult>,\n ): Promise<SubmitResult<TResult, TValues>> {\n ensureActive('submit');\n\n if (store.read().isSubmitting)\n throw new ForgeSubmitError('submit() called while a submission is already in progress');\n\n write((current) => ({\n ...current,\n isSubmitting: true,\n submitCount: current.submitCount + 1,\n touched: touchAll(current.value),\n }));\n\n try {\n const result = await validate();\n\n if (result.status === 'aborted') return Object.freeze({ ok: false, type: 'aborted' });\n\n if (result.status === 'invalid') {\n return Object.freeze({\n errors: result.errors,\n formError: result.formError,\n ok: false,\n type: 'validation',\n });\n }\n\n return Object.freeze({ ok: true, value: await handler(form.value) });\n } finally {\n if (!disposed) write((current) => ({ ...current, isSubmitting: false }));\n }\n },\n subscribe(listener, subscribeOptions: SubscribeOptions = {}): Unsubscribe {\n ensureActive('subscribe');\n\n if (subscribeOptions.immediate) listener(makeFormState(store.read()));\n\n return store.subscribe(() => listener(makeFormState(store.read())));\n },\n [Symbol.dispose]() {\n form.dispose();\n },\n validate,\n get value() {\n return store.read().value as ReadonlyDeep<TValues>;\n },\n };\n\n return form;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"form.js","names":[],"sources":["../src/form.ts"],"sourcesContent":["import { createField, type FieldAccess } from './_field';\nimport { createNotifier } from './_notify';\nimport {\n assertSafeKey,\n immutable,\n isRecord,\n type MetaRoot,\n normalizeErrors,\n resetAtPath,\n touchAll,\n writeAtPath,\n writeMeta,\n} from './core/path';\nimport { ForgeConfigError, ForgeDisposedError, ForgeSubmitError, ForgeValidationError } from './errors';\nimport type {\n Form,\n FormErrors,\n FormOptions,\n FormState,\n MaybePromise,\n ReadonlyDeep,\n SubmitResult,\n Unsubscribe,\n ValidationErrors,\n ValidationResult,\n} from './types';\n\ntype Validity = 'invalid' | 'unknown' | 'valid';\n\ntype InternalState<TValues extends Record<string, unknown>> = Readonly<{\n baseline: TValues;\n errors: FormErrors<TValues> | undefined;\n formError: string | undefined;\n isSubmitting: boolean;\n isValidating: boolean;\n submitCount: number;\n touched: MetaRoot;\n validity: Validity;\n value: TValues;\n}>;\n\nfunction makeFormState<TValues extends Record<string, unknown>>(current: InternalState<TValues>): FormState<TValues> {\n return Object.freeze({\n errors: current.errors,\n formError: current.formError,\n hasErrors: current.formError !== undefined || current.errors !== undefined,\n submitCount: current.submitCount,\n submitting: current.isSubmitting,\n touched: Object.keys(current.touched).length > 0,\n validating: current.isValidating,\n validity: current.validity,\n });\n}\n\n/** One immutable value tree and one explicit full-form validator keep form behavior locally understandable. */\nexport function createForm<TValues extends Record<string, unknown>>(options: FormOptions<TValues>): Form<TValues> {\n const initial = immutable(options.initialValues);\n const notifier = createNotifier(options.onSubscriberError);\n const disposalController = new AbortController();\n let disposed = false;\n let validationController: AbortController | undefined;\n let current: InternalState<TValues> = {\n baseline: initial,\n errors: undefined,\n formError: undefined,\n isSubmitting: false,\n isValidating: false,\n submitCount: 0,\n touched: {},\n validity: 'unknown',\n value: initial,\n };\n\n function ensureActive(operation: string): void {\n if (disposed) throw new ForgeDisposedError(operation);\n }\n\n function write(update: (current: InternalState<TValues>) => InternalState<TValues>): void {\n current = Object.freeze(update(current));\n notifier.notify();\n }\n\n function abortValidation(): void {\n validationController?.abort();\n }\n\n const access: FieldAccess = {\n abortValidation,\n addListener: notifier.add,\n ensureActive,\n readState: () => current,\n resetValue(path) {\n write((c) => ({\n ...c,\n touched: writeMeta(c.touched, path, false),\n validity: 'unknown',\n value: resetAtPath(c.value, c.baseline, path),\n }));\n },\n setTouched(path, touched) {\n write((c) => ({ ...c, touched: writeMeta(c.touched, path, touched) }));\n },\n setValue(path, next) {\n write((c) => ({\n ...c,\n validity: 'unknown',\n value: writeAtPath(c.value, path, next),\n }));\n },\n };\n\n async function validate(externalSignal?: AbortSignal): Promise<ValidationResult<TValues>> {\n ensureActive('validate');\n abortValidation();\n\n const controller = new AbortController();\n const signal = externalSignal ? AbortSignal.any([controller.signal, externalSignal]) : controller.signal;\n\n validationController = controller;\n write((c) => ({ ...c, isValidating: true }));\n\n try {\n const result: ValidationErrors<TValues> | undefined = options.validate\n ? await options.validate(current.value as ReadonlyDeep<TValues>, signal)\n : undefined;\n\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n\n const errors = result?.fields\n ? (normalizeErrors(immutable(result.fields)) as FormErrors<TValues> | undefined)\n : undefined;\n const formError = result?.formError;\n const validity: Validity = errors === undefined && formError === undefined ? 'valid' : 'invalid';\n\n write((c) => ({ ...c, errors, formError, validity }));\n\n return validity === 'valid'\n ? Object.freeze({ status: 'valid' })\n : Object.freeze({ errors, formError, status: 'invalid' });\n } catch (error) {\n if (signal.aborted || validationController !== controller) return Object.freeze({ status: 'aborted' });\n\n throw new ForgeValidationError('Form validation failed.', { cause: error });\n } finally {\n if (validationController === controller) {\n validationController = undefined;\n\n if (!disposed) write((c) => ({ ...c, isValidating: false }));\n }\n }\n }\n\n const form: Form<TValues> = {\n get disposalSignal() {\n return disposalController.signal;\n },\n dispose() {\n if (disposed) return;\n\n disposed = true;\n disposalController.abort();\n abortValidation();\n notifier.clear();\n },\n get disposed() {\n return disposed;\n },\n field(key) {\n assertSafeKey(key);\n if (!isRecord(current.value)) throw new ForgeConfigError('Form value must be an object.');\n\n return createField<TValues[typeof key]>([key], access);\n },\n reset(next) {\n ensureActive('reset');\n abortValidation();\n\n const baseline = next === undefined ? current.baseline : immutable(next);\n\n write((c) => ({\n ...c,\n baseline,\n errors: undefined,\n formError: undefined,\n touched: {},\n validity: 'unknown',\n value: baseline,\n }));\n },\n set(next) {\n ensureActive('set');\n abortValidation();\n\n const value =\n typeof next === 'function' ? (next as (current: ReadonlyDeep<TValues>) => TValues)(form.value) : next;\n\n write((c) => ({ ...c, errors: undefined, formError: undefined, validity: 'unknown', value: immutable(value) }));\n },\n get state() {\n return makeFormState(current);\n },\n async submit<TResult = void>(\n handler: (values: ReadonlyDeep<TValues>, signal: AbortSignal) => MaybePromise<TResult>,\n externalSignal?: AbortSignal,\n ): Promise<SubmitResult<TResult, TValues>> {\n ensureActive('submit');\n\n if (current.isSubmitting) {\n throw new ForgeSubmitError('submit() called while a submission is already in progress');\n }\n\n const signal = externalSignal\n ? AbortSignal.any([disposalController.signal, externalSignal])\n : disposalController.signal;\n\n write((c) => ({\n ...c,\n isSubmitting: true,\n submitCount: c.submitCount + 1,\n touched: touchAll(c.value),\n }));\n\n try {\n const result = await validate(externalSignal);\n\n if (result.status === 'aborted') return Object.freeze({ status: 'aborted' });\n\n if (result.status === 'invalid') {\n return Object.freeze({ errors: result.errors, formError: result.formError, status: 'invalid' });\n }\n\n try {\n return Object.freeze({ status: 'ok', value: await handler(form.value, signal) });\n } catch (error) {\n if (signal.aborted) return Object.freeze({ status: 'aborted' });\n\n throw error;\n }\n } finally {\n if (!disposed) write((c) => ({ ...c, isSubmitting: false }));\n }\n },\n subscribe(listener, subscribeOptions = {}): Unsubscribe {\n ensureActive('subscribe');\n\n if (subscribeOptions.immediate) listener(makeFormState(current));\n\n return notifier.add(() => listener(makeFormState(current)));\n },\n [Symbol.dispose]() {\n form.dispose();\n },\n validate,\n get value() {\n return current.value as ReadonlyDeep<TValues>;\n },\n };\n\n return form;\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./form.cjs")
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./form.cjs");exports.ForgeConfigError=e.ForgeConfigError,exports.ForgeDisposedError=e.ForgeDisposedError,exports.ForgeError=e.ForgeError,exports.ForgeSubmitError=e.ForgeSubmitError,exports.ForgeValidationError=e.ForgeValidationError,exports.createForm=t.createForm;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { ForgeConfigError, ForgeDisposedError, ForgeError, ForgeSubmitError, ForgeValidationError } from './errors';
|
|
2
|
-
export * from './types';
|
|
3
2
|
export { createForm } from './form';
|
|
4
|
-
export
|
|
3
|
+
export * from './types';
|
|
5
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACpH,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACpH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { ForgeConfigError as e, ForgeDisposedError as t, ForgeError as n, ForgeSubmitError as r, ForgeValidationError as i } from "./errors.js";
|
|
2
2
|
import { createForm as a } from "./form.js";
|
|
3
|
-
|
|
4
|
-
export { e as ForgeConfigError, t as ForgeDisposedError, n as ForgeError, r as ForgeSubmitError, i as ForgeValidationError, a as createForm, o as toFormData };
|
|
3
|
+
export { e as ForgeConfigError, t as ForgeDisposedError, n as ForgeError, r as ForgeSubmitError, i as ForgeValidationError, a as createForm };
|
package/dist/spell.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./core/path.cjs");
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./core/path.cjs");function t(e){return e.code===`invalid_union`}function n(e,t){return t.aborted?Promise.resolve(void 0):new Promise((n,r)=>{let i=()=>n(void 0);t.addEventListener(`abort`,i,{once:!0}),e.then(e=>{t.removeEventListener(`abort`,i),n(e)},e=>{t.removeEventListener(`abort`,i),r(e)})})}function r(e,t){return t.map(t=>({...t,path:[...e,...t.path]}))}function i(i){return async(a,o)=>{let s=await n(i.safeParseAsync(a),o);if(!s||s.success)return;let c=s.error.issues.flatMap(e=>{if(!t(e))return[e];let n=s.error.bestMatch(e);return n?r(e.path,n):[e]}),l={},u;for(let t of c){if(t.path.length===0){u??=t.message;continue}l=e.writeError(l,t.path,t.message)}let d={fields:Object.keys(l).length===0?void 0:l,formError:u};return d.fields===void 0&&d.formError===void 0?void 0:d}}exports.customValidator=i;
|
|
2
2
|
//# sourceMappingURL=spell.cjs.map
|
package/dist/spell.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spell.cjs","names":[],"sources":["../src/spell.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"spell.cjs","names":[],"sources":["../src/spell.ts"],"sourcesContent":["import type { Issue, Schema, SchemaMode } from '@vielzeug/spell';\nimport type { ErrorTree } from './core/path';\nimport { writeError } from './core/path';\nimport type { FormErrors, FormValidator, ValidationErrors } from './types';\n\ntype UnionIssue = Extract<Issue, { code: 'invalid_union' }>;\n\nfunction isUnionIssue(issue: Issue): issue is UnionIssue {\n return issue.code === 'invalid_union';\n}\n\nfunction abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T | undefined> {\n if (signal.aborted) return Promise.resolve(undefined);\n\n return new Promise((resolve, reject) => {\n const abort = () => resolve(undefined);\n\n signal.addEventListener('abort', abort, { once: true });\n void promise.then(\n (value) => {\n signal.removeEventListener('abort', abort);\n resolve(value);\n },\n (error: unknown) => {\n signal.removeEventListener('abort', abort);\n reject(error);\n },\n );\n });\n}\n\nfunction prefixIssues(path: readonly (string | number)[], issues: readonly Issue[]): Issue[] {\n return issues.map((issue) => ({ ...issue, path: [...path, ...issue.path] }));\n}\n\n/** Adapts a Spell Schema into Forge's full-form validator without transforming form values. */\nexport function customValidator<TValues extends Record<string, unknown>>(\n schema: Schema<unknown, TValues, SchemaMode>,\n): FormValidator<TValues> {\n return async (values, signal) => {\n const result = await abortable(schema.safeParseAsync(values), signal);\n\n if (!result || result.success) return undefined;\n\n const issues = result.error.issues.flatMap((issue) => {\n if (!isUnionIssue(issue)) return [issue];\n\n const bestMatch = result.error.bestMatch(issue);\n\n return bestMatch ? prefixIssues(issue.path, bestMatch) : [issue];\n });\n let fields: ErrorTree = {};\n let formError: string | undefined;\n\n for (const issue of issues) {\n if (issue.path.length === 0) {\n formError ??= issue.message;\n continue;\n }\n\n fields = writeError(fields, issue.path, issue.message);\n }\n\n const errors: ValidationErrors<TValues> = {\n fields: Object.keys(fields).length === 0 ? undefined : (fields as FormErrors<TValues>),\n formError,\n };\n\n return errors.fields === undefined && errors.formError === undefined ? undefined : errors;\n };\n}\n"],"mappings":"sGAOA,SAAS,EAAa,EAAmC,CACvD,OAAO,EAAM,OAAS,eACxB,CAEA,SAAS,EAAa,EAAqB,EAA6C,CAGtF,OAFI,EAAO,QAAgB,QAAQ,QAAQ,IAAA,EAAS,EAE7C,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,MAAc,EAAQ,IAAA,EAAS,EAErC,EAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EACtD,EAAa,KACV,GAAU,CACT,EAAO,oBAAoB,QAAS,CAAK,EACzC,EAAQ,CAAK,CACf,EACC,GAAmB,CAClB,EAAO,oBAAoB,QAAS,CAAK,EACzC,EAAO,CAAK,CACd,CACF,CACF,CAAC,CACH,CAEA,SAAS,EAAa,EAAoC,EAAmC,CAC3F,OAAO,EAAO,IAAK,IAAW,CAAE,GAAG,EAAO,KAAM,CAAC,GAAG,EAAM,GAAG,EAAM,IAAI,CAAE,EAAE,CAC7E,CAGA,SAAgB,EACd,EACwB,CACxB,OAAO,MAAO,EAAQ,IAAW,CAC/B,IAAM,EAAS,MAAM,EAAU,EAAO,eAAe,CAAM,EAAG,CAAM,EAEpE,GAAI,CAAC,GAAU,EAAO,QAAS,OAE/B,IAAM,EAAS,EAAO,MAAM,OAAO,QAAS,GAAU,CACpD,GAAI,CAAC,EAAa,CAAK,EAAG,MAAO,CAAC,CAAK,EAEvC,IAAM,EAAY,EAAO,MAAM,UAAU,CAAK,EAE9C,OAAO,EAAY,EAAa,EAAM,KAAM,CAAS,EAAI,CAAC,CAAK,CACjE,CAAC,EACG,EAAoB,CAAC,EACrB,EAEJ,IAAK,IAAM,KAAS,EAAQ,CAC1B,GAAI,EAAM,KAAK,SAAW,EAAG,CAC3B,IAAc,EAAM,QACpB,QACF,CAEA,EAAS,EAAA,WAAW,EAAQ,EAAM,KAAM,EAAM,OAAO,CACvD,CAEA,IAAM,EAAoC,CACxC,OAAQ,OAAO,KAAK,CAAM,CAAC,CAAC,SAAW,EAAI,IAAA,GAAa,EACxD,WACF,EAEA,OAAO,EAAO,SAAW,IAAA,IAAa,EAAO,YAAc,IAAA,GAAY,IAAA,GAAY,CACrF,CACF"}
|
package/dist/spell.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Schema,
|
|
1
|
+
import type { Schema, SchemaMode } from '@vielzeug/spell';
|
|
2
2
|
import type { FormValidator } from './types';
|
|
3
3
|
/** Adapts a Spell Schema into Forge's full-form validator without transforming form values. */
|
|
4
4
|
export declare function customValidator<TValues extends Record<string, unknown>>(schema: Schema<unknown, TValues, SchemaMode>): FormValidator<TValues>;
|
package/dist/spell.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spell.d.ts","sourceRoot":"","sources":["../src/spell.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"spell.d.ts","sourceRoot":"","sources":["../src/spell.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,MAAM,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGjE,OAAO,KAAK,EAAc,aAAa,EAAoB,MAAM,SAAS,CAAC;AAgC3E,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,GAC3C,aAAa,CAAC,OAAO,CAAC,CAgCxB"}
|
package/dist/spell.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { writeError as e } from "./core/path.js";
|
|
2
|
-
import "@vielzeug/spell";
|
|
3
2
|
//#region src/spell.ts
|
|
4
3
|
function t(e) {
|
|
5
4
|
return e.code === "invalid_union";
|
|
@@ -14,44 +13,36 @@ function n(e, t) {
|
|
|
14
13
|
});
|
|
15
14
|
});
|
|
16
15
|
}
|
|
17
|
-
function r(e) {
|
|
18
|
-
let t = [];
|
|
19
|
-
for (let n of e) {
|
|
20
|
-
if (typeof n == "number") break;
|
|
21
|
-
t.push(n);
|
|
22
|
-
}
|
|
23
|
-
return t;
|
|
24
|
-
}
|
|
25
|
-
function i(e, t) {
|
|
16
|
+
function r(e, t) {
|
|
26
17
|
return t.map((t) => ({
|
|
27
18
|
...t,
|
|
28
19
|
path: [...e, ...t.path]
|
|
29
20
|
}));
|
|
30
21
|
}
|
|
31
|
-
function
|
|
32
|
-
return async (
|
|
33
|
-
let
|
|
34
|
-
if (!
|
|
35
|
-
let
|
|
22
|
+
function i(i) {
|
|
23
|
+
return async (a, o) => {
|
|
24
|
+
let s = await n(i.safeParseAsync(a), o);
|
|
25
|
+
if (!s || s.success) return;
|
|
26
|
+
let c = s.error.issues.flatMap((e) => {
|
|
36
27
|
if (!t(e)) return [e];
|
|
37
|
-
let n =
|
|
38
|
-
return n ?
|
|
39
|
-
}),
|
|
40
|
-
for (let t of
|
|
28
|
+
let n = s.error.bestMatch(e);
|
|
29
|
+
return n ? r(e.path, n) : [e];
|
|
30
|
+
}), l = {}, u;
|
|
31
|
+
for (let t of c) {
|
|
41
32
|
if (t.path.length === 0) {
|
|
42
|
-
|
|
33
|
+
u ??= t.message;
|
|
43
34
|
continue;
|
|
44
35
|
}
|
|
45
|
-
|
|
36
|
+
l = e(l, t.path, t.message);
|
|
46
37
|
}
|
|
47
|
-
let
|
|
48
|
-
fields: Object.keys(
|
|
49
|
-
formError:
|
|
38
|
+
let d = {
|
|
39
|
+
fields: Object.keys(l).length === 0 ? void 0 : l,
|
|
40
|
+
formError: u
|
|
50
41
|
};
|
|
51
|
-
return
|
|
42
|
+
return d.fields === void 0 && d.formError === void 0 ? void 0 : d;
|
|
52
43
|
};
|
|
53
44
|
}
|
|
54
45
|
//#endregion
|
|
55
|
-
export {
|
|
46
|
+
export { i as customValidator };
|
|
56
47
|
|
|
57
48
|
//# sourceMappingURL=spell.js.map
|
package/dist/spell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spell.js","names":[],"sources":["../src/spell.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"spell.js","names":[],"sources":["../src/spell.ts"],"sourcesContent":["import type { Issue, Schema, SchemaMode } from '@vielzeug/spell';\nimport type { ErrorTree } from './core/path';\nimport { writeError } from './core/path';\nimport type { FormErrors, FormValidator, ValidationErrors } from './types';\n\ntype UnionIssue = Extract<Issue, { code: 'invalid_union' }>;\n\nfunction isUnionIssue(issue: Issue): issue is UnionIssue {\n return issue.code === 'invalid_union';\n}\n\nfunction abortable<T>(promise: Promise<T>, signal: AbortSignal): Promise<T | undefined> {\n if (signal.aborted) return Promise.resolve(undefined);\n\n return new Promise((resolve, reject) => {\n const abort = () => resolve(undefined);\n\n signal.addEventListener('abort', abort, { once: true });\n void promise.then(\n (value) => {\n signal.removeEventListener('abort', abort);\n resolve(value);\n },\n (error: unknown) => {\n signal.removeEventListener('abort', abort);\n reject(error);\n },\n );\n });\n}\n\nfunction prefixIssues(path: readonly (string | number)[], issues: readonly Issue[]): Issue[] {\n return issues.map((issue) => ({ ...issue, path: [...path, ...issue.path] }));\n}\n\n/** Adapts a Spell Schema into Forge's full-form validator without transforming form values. */\nexport function customValidator<TValues extends Record<string, unknown>>(\n schema: Schema<unknown, TValues, SchemaMode>,\n): FormValidator<TValues> {\n return async (values, signal) => {\n const result = await abortable(schema.safeParseAsync(values), signal);\n\n if (!result || result.success) return undefined;\n\n const issues = result.error.issues.flatMap((issue) => {\n if (!isUnionIssue(issue)) return [issue];\n\n const bestMatch = result.error.bestMatch(issue);\n\n return bestMatch ? prefixIssues(issue.path, bestMatch) : [issue];\n });\n let fields: ErrorTree = {};\n let formError: string | undefined;\n\n for (const issue of issues) {\n if (issue.path.length === 0) {\n formError ??= issue.message;\n continue;\n }\n\n fields = writeError(fields, issue.path, issue.message);\n }\n\n const errors: ValidationErrors<TValues> = {\n fields: Object.keys(fields).length === 0 ? undefined : (fields as FormErrors<TValues>),\n formError,\n };\n\n return errors.fields === undefined && errors.formError === undefined ? undefined : errors;\n };\n}\n"],"mappings":";;AAOA,SAAS,EAAa,GAAmC;CACvD,OAAO,EAAM,SAAS;AACxB;AAEA,SAAS,EAAa,GAAqB,GAA6C;CAGtF,OAFI,EAAO,UAAgB,QAAQ,QAAQ,KAAA,CAAS,IAE7C,IAAI,SAAS,GAAS,MAAW;EACtC,IAAM,UAAc,EAAQ,KAAA,CAAS;EAGrC,AADA,EAAO,iBAAiB,SAAS,GAAO,EAAE,MAAM,GAAK,CAAC,GACtD,EAAa,MACV,MAAU;GAET,AADA,EAAO,oBAAoB,SAAS,CAAK,GACzC,EAAQ,CAAK;EACf,IACC,MAAmB;GAElB,AADA,EAAO,oBAAoB,SAAS,CAAK,GACzC,EAAO,CAAK;EACd,CACF;CACF,CAAC;AACH;AAEA,SAAS,EAAa,GAAoC,GAAmC;CAC3F,OAAO,EAAO,KAAK,OAAW;EAAE,GAAG;EAAO,MAAM,CAAC,GAAG,GAAM,GAAG,EAAM,IAAI;CAAE,EAAE;AAC7E;AAGA,SAAgB,EACd,GACwB;CACxB,OAAO,OAAO,GAAQ,MAAW;EAC/B,IAAM,IAAS,MAAM,EAAU,EAAO,eAAe,CAAM,GAAG,CAAM;EAEpE,IAAI,CAAC,KAAU,EAAO,SAAS;EAE/B,IAAM,IAAS,EAAO,MAAM,OAAO,SAAS,MAAU;GACpD,IAAI,CAAC,EAAa,CAAK,GAAG,OAAO,CAAC,CAAK;GAEvC,IAAM,IAAY,EAAO,MAAM,UAAU,CAAK;GAE9C,OAAO,IAAY,EAAa,EAAM,MAAM,CAAS,IAAI,CAAC,CAAK;EACjE,CAAC,GACG,IAAoB,CAAC,GACrB;EAEJ,KAAK,IAAM,KAAS,GAAQ;GAC1B,IAAI,EAAM,KAAK,WAAW,GAAG;IAC3B,MAAc,EAAM;IACpB;GACF;GAEA,IAAS,EAAW,GAAQ,EAAM,MAAM,EAAM,OAAO;EACvD;EAEA,IAAM,IAAoC;GACxC,QAAQ,OAAO,KAAK,CAAM,CAAC,CAAC,WAAW,IAAI,KAAA,IAAa;GACxD;EACF;EAEA,OAAO,EAAO,WAAW,KAAA,KAAa,EAAO,cAAc,KAAA,IAAY,KAAA,IAAY;CACrF;AACF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type MaybePromise<T> = T | PromiseLike<T>;
|
|
|
3
3
|
export type ReadonlyDeep<T> = T extends (...args: never[]) => unknown ? T : T extends readonly (infer Item)[] ? readonly ReadonlyDeep<Item>[] : T extends Record<string, unknown> ? {
|
|
4
4
|
readonly [K in keyof T]: ReadonlyDeep<T[K]>;
|
|
5
5
|
} : T;
|
|
6
|
-
export type FormErrors<T> = T extends readonly
|
|
6
|
+
export type FormErrors<T> = T extends readonly (infer Item)[] ? string | readonly (FormErrors<Item> | undefined)[] : T extends Record<string, unknown> ? string | {
|
|
7
7
|
readonly [K in keyof T]?: FormErrors<T[K]>;
|
|
8
8
|
} : string;
|
|
9
9
|
export type ValidationErrors<TValues extends Record<string, unknown>> = Readonly<{
|
|
@@ -26,12 +26,13 @@ export type FieldState<V> = Readonly<{
|
|
|
26
26
|
value: ReadonlyDeep<V>;
|
|
27
27
|
}>;
|
|
28
28
|
export type FormState<TValues extends Record<string, unknown>> = Readonly<{
|
|
29
|
-
error: string | undefined;
|
|
30
29
|
errors: FormErrors<TValues> | undefined;
|
|
30
|
+
formError: string | undefined;
|
|
31
|
+
hasErrors: boolean;
|
|
31
32
|
submitCount: number;
|
|
32
33
|
submitting: boolean;
|
|
33
34
|
touched: boolean;
|
|
34
|
-
|
|
35
|
+
validity: 'invalid' | 'unknown' | 'valid';
|
|
35
36
|
validating: boolean;
|
|
36
37
|
}>;
|
|
37
38
|
export type ValidationResult<TValues extends Record<string, unknown>> = Readonly<{
|
|
@@ -44,18 +45,18 @@ export type ValidationResult<TValues extends Record<string, unknown>> = Readonly
|
|
|
44
45
|
status: 'invalid';
|
|
45
46
|
}>;
|
|
46
47
|
export type SubmitResult<TResult = void, TValues extends Record<string, unknown> = Record<string, unknown>> = Readonly<{
|
|
47
|
-
|
|
48
|
-
value: TResult;
|
|
49
|
-
}> | Readonly<{
|
|
50
|
-
ok: false;
|
|
51
|
-
type: 'aborted';
|
|
48
|
+
status: 'aborted';
|
|
52
49
|
}> | Readonly<{
|
|
50
|
+
status: 'invalid';
|
|
53
51
|
errors: FormErrors<TValues> | undefined;
|
|
54
52
|
formError: string | undefined;
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
}> | Readonly<{
|
|
54
|
+
status: 'ok';
|
|
55
|
+
value: TResult;
|
|
57
56
|
}>;
|
|
58
|
-
type ChildField<V> = NonNullable<V> extends readonly
|
|
57
|
+
type ChildField<V> = NonNullable<V> extends readonly (infer Item)[] ? {
|
|
58
|
+
field(index: number): Field<Item>;
|
|
59
|
+
} : NonNullable<V> extends Record<string, unknown> ? {
|
|
59
60
|
field<K extends keyof NonNullable<V> & string>(key: K): Field<NonNullable<V>[K]>;
|
|
60
61
|
} : Record<never, never>;
|
|
61
62
|
export type Field<V> = ChildField<V> & {
|
|
@@ -63,6 +64,7 @@ export type Field<V> = ChildField<V> & {
|
|
|
63
64
|
readonly error: string | undefined;
|
|
64
65
|
reset(): void;
|
|
65
66
|
set(next: V | ((previous: ReadonlyDeep<V>) => V)): void;
|
|
67
|
+
readonly state: FieldState<V>;
|
|
66
68
|
subscribe(listener: (state: FieldState<V>) => void, options?: SubscribeOptions): Unsubscribe;
|
|
67
69
|
touch(): void;
|
|
68
70
|
readonly touched: boolean;
|
|
@@ -77,7 +79,7 @@ export type Form<TValues extends Record<string, unknown>> = {
|
|
|
77
79
|
reset(next?: TValues): void;
|
|
78
80
|
set(next: TValues | ((previous: ReadonlyDeep<TValues>) => TValues)): void;
|
|
79
81
|
readonly state: FormState<TValues>;
|
|
80
|
-
submit<TResult = void>(handler: (values: ReadonlyDeep<TValues
|
|
82
|
+
submit<TResult = void>(handler: (values: ReadonlyDeep<TValues>, signal: AbortSignal) => MaybePromise<TResult>, signal?: AbortSignal): Promise<SubmitResult<TResult, TValues>>;
|
|
81
83
|
subscribe(listener: (state: FormState<TValues>) => void, options?: SubscribeOptions): Unsubscribe;
|
|
82
84
|
validate(signal?: AbortSignal): Promise<ValidationResult<TValues>>;
|
|
83
85
|
readonly value: ReadonlyDeep<TValues>;
|