@menapo/forms 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +45 -2
- package/dist/array.d.ts +5 -0
- package/dist/array.js +101 -0
- package/dist/checks.d.ts +4 -0
- package/dist/checks.js +53 -0
- package/dist/factories/fn.d.ts +8 -0
- package/dist/factories/fn.js +5 -0
- package/dist/factories/route.d.ts +5 -0
- package/dist/factories/route.js +31 -0
- package/dist/form-BZ2OH06z.js +48 -0
- package/dist/glue/preact.d.ts +4 -0
- package/dist/glue/preact.js +59 -0
- package/dist/glue/react.d.ts +4 -0
- package/dist/glue/react.js +55 -0
- package/dist/glue/solid.d.ts +4 -0
- package/dist/glue/solid.js +48 -0
- package/dist/glue/svelte.svelte.d.ts +4 -0
- package/dist/glue/svelte.svelte.js +62 -0
- package/dist/glue/vanilla.d.ts +4 -0
- package/dist/glue/vanilla.js +88 -0
- package/dist/glue/vue.d.ts +4 -0
- package/dist/glue/vue.js +47 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +30 -0
- package/dist/instance-C1aijv06.js +683 -0
- package/dist/names-D_TE3BpM.js +62 -0
- package/dist/paths-DqNafpnl.js +73 -0
- package/dist/route/files.d.ts +5 -0
- package/dist/route/files.js +34 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.js +79 -0
- package/dist/source-DdFTPjID.js +15 -0
- package/dist/types-CdCFdlFp.d.ts +200 -0
- package/dist/view--JGPLHEo.js +308 -0
- package/dist/view-5_kyMWP3.d.ts +331 -0
- package/dist/vite.d.ts +20 -0
- package/dist/vite.js +1154 -0
- package/package.json +98 -3
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { n as DEV, t as Instance } from "./instance-C1aijv06.js";
|
|
2
|
+
import { n as get } from "./paths-DqNafpnl.js";
|
|
3
|
+
//#region src/core/view.ts
|
|
4
|
+
var View = class {
|
|
5
|
+
i;
|
|
6
|
+
/** The view that made the instance: it owns its lifecycle and options. */
|
|
7
|
+
own;
|
|
8
|
+
v = 0;
|
|
9
|
+
ls = [];
|
|
10
|
+
/** Kinds read per path, and form kinds read, since the last reset. */
|
|
11
|
+
rp = /* @__PURE__ */ new Map();
|
|
12
|
+
rb = 0;
|
|
13
|
+
/** The framework's tracker (Vue, Svelte, Solid). */
|
|
14
|
+
k = void 0;
|
|
15
|
+
/** The view object handed out, and the version it was made at. */
|
|
16
|
+
f = void 0;
|
|
17
|
+
fv = -1;
|
|
18
|
+
/** One view object for the component's life (Vue, Svelte, Solid, plain code). */
|
|
19
|
+
keep = false;
|
|
20
|
+
/** Plain code's subscription, given by the vanilla glue. */
|
|
21
|
+
u = void 0;
|
|
22
|
+
constructor(instance, own) {
|
|
23
|
+
this.i = instance;
|
|
24
|
+
this.own = own;
|
|
25
|
+
if (own) instance.ls.push(this);
|
|
26
|
+
}
|
|
27
|
+
hear(paths, mask, form) {
|
|
28
|
+
let moved = (form & this.rb) !== 0;
|
|
29
|
+
if (!moved) {
|
|
30
|
+
for (const path of paths) if ((this.rp.get(path) ?? 0) & mask) {
|
|
31
|
+
moved = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!moved) return;
|
|
36
|
+
this.v++;
|
|
37
|
+
for (const listener of this.ls) listener();
|
|
38
|
+
}
|
|
39
|
+
read(path, kind) {
|
|
40
|
+
this.rp.set(path, (this.rp.get(path) ?? 0) | kind);
|
|
41
|
+
this.k?.();
|
|
42
|
+
}
|
|
43
|
+
readForm(kind) {
|
|
44
|
+
this.rb |= kind;
|
|
45
|
+
this.k?.();
|
|
46
|
+
}
|
|
47
|
+
/** A render starts: what it reads is what it shows. */
|
|
48
|
+
restart() {
|
|
49
|
+
this.rp.clear();
|
|
50
|
+
this.rb = 0;
|
|
51
|
+
}
|
|
52
|
+
current() {
|
|
53
|
+
if (!this.f || !this.keep && this.fv !== this.v) {
|
|
54
|
+
this.f = new FormViewImpl(this);
|
|
55
|
+
this.fv = this.v;
|
|
56
|
+
}
|
|
57
|
+
return this.f;
|
|
58
|
+
}
|
|
59
|
+
subscribe(listener) {
|
|
60
|
+
this.ls = [...this.ls, listener];
|
|
61
|
+
return () => {
|
|
62
|
+
this.ls = this.ls.filter((l) => l !== listener);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
snapshot() {
|
|
66
|
+
return this.v;
|
|
67
|
+
}
|
|
68
|
+
setProps(props) {
|
|
69
|
+
if (this.own) this.i.options(props);
|
|
70
|
+
}
|
|
71
|
+
mount() {
|
|
72
|
+
if (!this.i.ls.includes(this)) this.i.ls.push(this);
|
|
73
|
+
if (this.own) this.i.mount();
|
|
74
|
+
}
|
|
75
|
+
unmount() {
|
|
76
|
+
const at = this.i.ls.indexOf(this);
|
|
77
|
+
if (at >= 0) this.i.ls.splice(at, 1);
|
|
78
|
+
if (this.own) this.i.unmount();
|
|
79
|
+
}
|
|
80
|
+
commit() {
|
|
81
|
+
if (this.own) this.i.commit();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
/** The view object: `f.form`, `f.fields`, `f.errors`… (§5.3). */
|
|
85
|
+
var FormViewImpl = class {
|
|
86
|
+
$;
|
|
87
|
+
/** Field views of this version by path, and groups by prefix (`""`, `"address."`). */
|
|
88
|
+
fs = /* @__PURE__ */ new Map();
|
|
89
|
+
constructor(view) {
|
|
90
|
+
this.$ = view;
|
|
91
|
+
}
|
|
92
|
+
get form() {
|
|
93
|
+
this.$.readForm(1);
|
|
94
|
+
return this.$.i.formParts().form;
|
|
95
|
+
}
|
|
96
|
+
get submit() {
|
|
97
|
+
this.$.readForm(1);
|
|
98
|
+
return this.$.i.formParts().submit;
|
|
99
|
+
}
|
|
100
|
+
get summary() {
|
|
101
|
+
return this.$.i.formParts().summary;
|
|
102
|
+
}
|
|
103
|
+
get fields() {
|
|
104
|
+
return group(this, "", shapeOf(this.$.i));
|
|
105
|
+
}
|
|
106
|
+
get errors() {
|
|
107
|
+
this.$.readForm(4);
|
|
108
|
+
return this.$.i.errors();
|
|
109
|
+
}
|
|
110
|
+
get error() {
|
|
111
|
+
this.$.readForm(2);
|
|
112
|
+
return this.$.i.error;
|
|
113
|
+
}
|
|
114
|
+
get pending() {
|
|
115
|
+
this.$.readForm(1);
|
|
116
|
+
return this.$.i.pending;
|
|
117
|
+
}
|
|
118
|
+
get progress() {
|
|
119
|
+
this.$.readForm(32);
|
|
120
|
+
return this.$.i.progress;
|
|
121
|
+
}
|
|
122
|
+
get dirty() {
|
|
123
|
+
this.$.readForm(8);
|
|
124
|
+
return this.$.i.dp.size > 0;
|
|
125
|
+
}
|
|
126
|
+
get data() {
|
|
127
|
+
this.$.readForm(16);
|
|
128
|
+
return this.$.i.data;
|
|
129
|
+
}
|
|
130
|
+
requestSubmit() {
|
|
131
|
+
return this.$.i.submit();
|
|
132
|
+
}
|
|
133
|
+
reset(values) {
|
|
134
|
+
this.$.i.reset(values);
|
|
135
|
+
}
|
|
136
|
+
subscribe(listener) {
|
|
137
|
+
const { $ } = this;
|
|
138
|
+
if ($.u) return $.u.subscribe(listener);
|
|
139
|
+
return $.subscribe(listener);
|
|
140
|
+
}
|
|
141
|
+
stop() {
|
|
142
|
+
if (this.$.u) this.$.u.stop();
|
|
143
|
+
else this.$.unmount();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
var FieldViewImpl = class {
|
|
147
|
+
$;
|
|
148
|
+
r;
|
|
149
|
+
constructor(view, rec) {
|
|
150
|
+
this.$ = view;
|
|
151
|
+
this.r = rec;
|
|
152
|
+
}
|
|
153
|
+
get name() {
|
|
154
|
+
return this.r.p;
|
|
155
|
+
}
|
|
156
|
+
get id() {
|
|
157
|
+
return this.$.i.fid(this.r.p);
|
|
158
|
+
}
|
|
159
|
+
get value() {
|
|
160
|
+
this.$.read(this.r.p, 1);
|
|
161
|
+
return get(this.$.i.v, this.r.p);
|
|
162
|
+
}
|
|
163
|
+
get message() {
|
|
164
|
+
this.$.read(this.r.p, 2);
|
|
165
|
+
return this.r.e;
|
|
166
|
+
}
|
|
167
|
+
get touched() {
|
|
168
|
+
this.$.read(this.r.p, 4);
|
|
169
|
+
return this.r.t;
|
|
170
|
+
}
|
|
171
|
+
get dirty() {
|
|
172
|
+
this.$.read(this.r.p, 8);
|
|
173
|
+
const { dp } = this.$.i;
|
|
174
|
+
if (dp.has(this.r.p)) return true;
|
|
175
|
+
const prefix = `${this.r.p}.`;
|
|
176
|
+
for (const path of dp) if (path.startsWith(prefix)) return true;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
get validating() {
|
|
180
|
+
this.$.read(this.r.p, 16);
|
|
181
|
+
return this.r.vd > 0;
|
|
182
|
+
}
|
|
183
|
+
get control() {
|
|
184
|
+
return parts(this).control;
|
|
185
|
+
}
|
|
186
|
+
get label() {
|
|
187
|
+
return parts(this).label;
|
|
188
|
+
}
|
|
189
|
+
get error() {
|
|
190
|
+
return parts(this).error;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Read, the hint is rendered, so the control links it. The control may
|
|
194
|
+
* have been spread already this render: the parts move and the views
|
|
195
|
+
* that showed them hear it after the render, once per field.
|
|
196
|
+
*/
|
|
197
|
+
get hint() {
|
|
198
|
+
const { r, $ } = this;
|
|
199
|
+
if (!r.h) {
|
|
200
|
+
r.h = true;
|
|
201
|
+
r.ver++;
|
|
202
|
+
queueMicrotask(() => $.i.tell([r.p], 32, 0));
|
|
203
|
+
}
|
|
204
|
+
return parts(this).hint;
|
|
205
|
+
}
|
|
206
|
+
get group() {
|
|
207
|
+
return parts(this).group;
|
|
208
|
+
}
|
|
209
|
+
set(value) {
|
|
210
|
+
const { i } = this.$;
|
|
211
|
+
if (!this.r.t) {
|
|
212
|
+
this.r.t = true;
|
|
213
|
+
i.tell([this.r.p], 4, 0);
|
|
214
|
+
}
|
|
215
|
+
i.set(this.r.p, value, false);
|
|
216
|
+
}
|
|
217
|
+
option(value) {
|
|
218
|
+
this.$.read(this.r.p, 34);
|
|
219
|
+
return this.$.i.optionParts(this.r, value);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
/** A field's parts, read: they show its baseline, its hint and its error. */
|
|
223
|
+
function parts(field) {
|
|
224
|
+
field.$.read(field.r.p, 34);
|
|
225
|
+
return field.$.i.fieldParts(field.r);
|
|
226
|
+
}
|
|
227
|
+
/** The field view at `path`, cached per version; `Field` is the array module's for an array. */
|
|
228
|
+
function fieldAt(form, path, Field = FieldViewImpl) {
|
|
229
|
+
const { $ } = form;
|
|
230
|
+
let field = form.fs.get(path);
|
|
231
|
+
if (!field || field.r !== $.i.r.get(path)) {
|
|
232
|
+
const rec = $.i.rec(path);
|
|
233
|
+
rec.k = true;
|
|
234
|
+
form.fs.set(path, field = new Field($, rec));
|
|
235
|
+
}
|
|
236
|
+
return field;
|
|
237
|
+
}
|
|
238
|
+
/** A group's form view and prefix, under a symbol: no input key collides with it. */
|
|
239
|
+
const G = Symbol();
|
|
240
|
+
function define(Class, key, node) {
|
|
241
|
+
Object.defineProperty(Class.prototype, key, { get() {
|
|
242
|
+
return at(this[G][0], this[G][1] + key, node);
|
|
243
|
+
} });
|
|
244
|
+
}
|
|
245
|
+
function child(node, key) {
|
|
246
|
+
const keys = node.o ??= Object.create(null);
|
|
247
|
+
if (!keys[key]) {
|
|
248
|
+
keys[key] = {};
|
|
249
|
+
if (node.c) define(node.c, key, keys[key]);
|
|
250
|
+
}
|
|
251
|
+
return keys[key];
|
|
252
|
+
}
|
|
253
|
+
function fromValue(node, value) {
|
|
254
|
+
if (Array.isArray(value)) fromValue(node.a ??= {}, value[0]);
|
|
255
|
+
else if (value?.constructor === Object) for (const key in value) fromValue(child(node, key), value[key]);
|
|
256
|
+
}
|
|
257
|
+
function shapeOf(i) {
|
|
258
|
+
if (i.sh) return i.sh;
|
|
259
|
+
const F = i.F;
|
|
260
|
+
let root = F.sh;
|
|
261
|
+
if (!root) {
|
|
262
|
+
root = F.sh = {};
|
|
263
|
+
for (const pattern in F.f) {
|
|
264
|
+
let node = root;
|
|
265
|
+
for (const key of pattern.split(".")) node = key === "*" ? node.a ??= {} : child(node, key);
|
|
266
|
+
if (F.f[pattern].t === "array") node.a ??= {};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
fromValue(root, i.v);
|
|
270
|
+
return i.sh = root;
|
|
271
|
+
}
|
|
272
|
+
/** The fields under `prefix`, cached per version. */
|
|
273
|
+
function group(form, prefix, node) {
|
|
274
|
+
let found = form.fs.get(prefix);
|
|
275
|
+
if (!found) {
|
|
276
|
+
let Class = node.c;
|
|
277
|
+
if (!Class) {
|
|
278
|
+
Class = node.c = class {
|
|
279
|
+
[G];
|
|
280
|
+
constructor(form, prefix) {
|
|
281
|
+
this[G] = [form, prefix];
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
for (const key in node.o) define(Class, key, node.o[key]);
|
|
285
|
+
}
|
|
286
|
+
form.fs.set(prefix, found = new Class(form, prefix));
|
|
287
|
+
}
|
|
288
|
+
return found;
|
|
289
|
+
}
|
|
290
|
+
/** What lives at `path`: an array's field (with the array module), an object's fields, or a field. */
|
|
291
|
+
function at(form, path, node) {
|
|
292
|
+
const array = form.$.i.F.a;
|
|
293
|
+
if (node.a && array) return array(form, path, node.a);
|
|
294
|
+
if (node.o && !node.a) return group(form, `${path}.`, node);
|
|
295
|
+
return fieldAt(form, path);
|
|
296
|
+
}
|
|
297
|
+
/** What the glue's adapters create: the owner's view of a new instance, or a child's of a parent view's. */
|
|
298
|
+
const create = (source, N) => (id, props) => {
|
|
299
|
+
if (source instanceof FormViewImpl) return new View(source.$.i, false);
|
|
300
|
+
if (DEV && !(source instanceof Object && "x" in source)) throw new Error("[forms:NOT_A_FORM] useForm takes a form(…) definition or a parent's useForm view.");
|
|
301
|
+
return new View(new Instance(source, id, N, props), true);
|
|
302
|
+
};
|
|
303
|
+
/** Dev only: options as a getter where the framework reads an object on each render. */
|
|
304
|
+
function objectOptions(options, framework) {
|
|
305
|
+
if (DEV && typeof options === "function") throw new Error(`[forms:OPTIONS] useForm was given options as a function, which only Vue, Svelte and Solid read; in ${framework} pass the options object on each render: useForm(form, { … }).`);
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
export { objectOptions as a, fieldAt as i, at as n, create as r, FieldViewImpl as t };
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { S as UseFormOptions, b as ShownError, d as FormOptions, f as FormParts, n as Attrs, o as FieldParts, s as FieldSpec, u as Form$1, x as Spec, y as RouteFormOptions } from "./types-CdCFdlFp.js";
|
|
2
|
+
import { ApiErrorLike, InputOf, MutationRoute, OutputOf, WriteOp } from "@menapo/query";
|
|
3
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
|
+
//#region ../glue/src/source.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* What a glue adapter binds to a framework (Adapter pattern): a store that
|
|
7
|
+
* tells of changes and numbers what a view shows. `@menapo/machine`'s
|
|
8
|
+
* components implement it; `@menapo/query`, the store and forms will too,
|
|
9
|
+
* so each framework's subscription code exists once (machine PLAN §5.1).
|
|
10
|
+
*
|
|
11
|
+
* The store filters its own change mask: `snapshot()` moves only when
|
|
12
|
+
* something a view shows changed, so the adapters never compare values.
|
|
13
|
+
*/
|
|
14
|
+
interface Source {
|
|
15
|
+
/** Calls `listener` after every change; returns the unsubscribe. */
|
|
16
|
+
subscribe(listener: () => void): () => void;
|
|
17
|
+
/** A number that moves when what a view shows changed. */
|
|
18
|
+
snapshot(): number;
|
|
19
|
+
/** The owner's props, as the component passes them. */
|
|
20
|
+
setProps(props: object): void;
|
|
21
|
+
/** After the first commit (and again after React's StrictMode remount): side effects may start. */
|
|
22
|
+
mount(): void;
|
|
23
|
+
/** Before the component goes: side effects stop. */
|
|
24
|
+
unmount(): void;
|
|
25
|
+
/** After the framework committed the DOM for a change: deferred side effects run. */
|
|
26
|
+
commit(): void;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/core/form.d.ts
|
|
30
|
+
/** What a call to submit takes: query's `CallOptions`. */
|
|
31
|
+
interface Call {
|
|
32
|
+
readonly signal: AbortSignal;
|
|
33
|
+
readonly progress?: ((ratio: number) => void) | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* How a definition submits (Strategy): a route form sends the raw input,
|
|
37
|
+
* which the server validates; a function form the validated output (D10).
|
|
38
|
+
*/
|
|
39
|
+
type Kind = (form: Form, input: unknown, output: unknown, call: Call) => Promise<unknown>;
|
|
40
|
+
/** The checks module (D8, D17), present only on definitions with `checks`. */
|
|
41
|
+
interface Checker {
|
|
42
|
+
/** A field's value changed: check it, debounced. */
|
|
43
|
+
run(instance: object, path: string, value: unknown): void;
|
|
44
|
+
/** Before a submit: every check due now, and every one running, settled. */
|
|
45
|
+
settle(instance: object): Promise<void>;
|
|
46
|
+
/** The instance unmounted: timers and calls stop. */
|
|
47
|
+
stop(instance: object): void;
|
|
48
|
+
}
|
|
49
|
+
/** The array module (D14): an array's field, with its items and edits. */
|
|
50
|
+
type ArrayModule = (form: FormViewImpl, path: string, item: Shape) => object;
|
|
51
|
+
/** The plugin's hidden argument: `{ n: "src/forms.ts#editProject", s, f, c, a }`. */
|
|
52
|
+
interface Meta {
|
|
53
|
+
readonly n?: string;
|
|
54
|
+
/** The browser schema: the operation's own input (D4), or the function form's. */
|
|
55
|
+
readonly s?: StandardSchemaV1 | undefined;
|
|
56
|
+
readonly f?: Spec;
|
|
57
|
+
readonly c?: Checker;
|
|
58
|
+
/** Present when the input has an array (the spec's or the defaults'). */
|
|
59
|
+
readonly a?: ArrayModule;
|
|
60
|
+
}
|
|
61
|
+
type Submit = (values: unknown, options: {
|
|
62
|
+
readonly signal: AbortSignal;
|
|
63
|
+
}) => unknown;
|
|
64
|
+
declare class Form {
|
|
65
|
+
readonly "~types"?: {
|
|
66
|
+
input: unknown;
|
|
67
|
+
output: unknown;
|
|
68
|
+
error: unknown;
|
|
69
|
+
};
|
|
70
|
+
/** The name, for DevTools and errors. */
|
|
71
|
+
readonly n: string;
|
|
72
|
+
readonly s: StandardSchemaV1 | undefined;
|
|
73
|
+
readonly f: Spec;
|
|
74
|
+
readonly o: RouteFormOptions<Record<string, unknown>>;
|
|
75
|
+
readonly c: Checker | undefined;
|
|
76
|
+
readonly a: ArrayModule | undefined;
|
|
77
|
+
readonly x: Kind;
|
|
78
|
+
/** A route form's route, or a function form's submit. */
|
|
79
|
+
readonly r: string | Submit;
|
|
80
|
+
/** The input's shape, for `fields`; each instance's values extend it (view.ts). */
|
|
81
|
+
sh: Shape | undefined;
|
|
82
|
+
/** A route form's mutation, made by its kind. */
|
|
83
|
+
m: ((input: unknown, call: Call) => Promise<unknown>) | undefined;
|
|
84
|
+
constructor(source: string | Submit, schema: StandardSchemaV1 | undefined, options: FormOptions<never> | undefined, meta: Meta | undefined, kind: Kind, init?: (form: Form) => void);
|
|
85
|
+
get name(): string;
|
|
86
|
+
}
|
|
87
|
+
type Input<S> = S extends StandardSchemaV1<infer I, unknown> ? I : never;
|
|
88
|
+
type Output<S> = S extends StandardSchemaV1<unknown, infer O> ? O : never;
|
|
89
|
+
/**
|
|
90
|
+
* A form for an operation of the app's API, typed from query's `Register`
|
|
91
|
+
* (D3), or for anything else, from its Standard Schema.
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* export const editProject = form("PATCH /projects/:id");
|
|
95
|
+
* export const contact = form(Contact, (values, { signal }) => send(values, signal));
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
declare function form<S extends MutationRoute>(route: S, options?: RouteFormOptions<InputOf<WriteOp<S>>>): Form$1<InputOf<WriteOp<S>>, OutputOf<WriteOp<S>>, ApiErrorLike>;
|
|
99
|
+
declare function form<S extends StandardSchemaV1, T>(schema: S, submit: (values: Output<S>, options: {
|
|
100
|
+
readonly signal: AbortSignal;
|
|
101
|
+
}) => T | Promise<T>, options?: FormOptions<Input<S>>): Form$1<Input<S>, Awaited<T>, Error>;
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/core/names.d.ts
|
|
104
|
+
interface Names {
|
|
105
|
+
readonly id: string;
|
|
106
|
+
readonly name: string;
|
|
107
|
+
readonly htmlFor: string;
|
|
108
|
+
readonly tabIndex: string;
|
|
109
|
+
readonly method: string;
|
|
110
|
+
readonly encType: string;
|
|
111
|
+
readonly type: string;
|
|
112
|
+
readonly value: string;
|
|
113
|
+
readonly checked: string;
|
|
114
|
+
readonly required: string;
|
|
115
|
+
readonly minLength: string;
|
|
116
|
+
readonly maxLength: string;
|
|
117
|
+
readonly min: string;
|
|
118
|
+
readonly max: string;
|
|
119
|
+
readonly accept: string;
|
|
120
|
+
readonly multiple: string;
|
|
121
|
+
readonly onSubmit: string;
|
|
122
|
+
readonly onReset: string;
|
|
123
|
+
readonly onClick: string;
|
|
124
|
+
/** The keys the delegated input handler goes under. */
|
|
125
|
+
readonly input: readonly string[];
|
|
126
|
+
readonly blur: string;
|
|
127
|
+
/** Where a part carries its text (an error's message, a hint's description): JSX's `children`. */
|
|
128
|
+
readonly text: string | undefined;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/core/instance.d.ts
|
|
132
|
+
/** One field: its error, flags and cached parts. Values live in the instance's tree. */
|
|
133
|
+
declare class Rec {
|
|
134
|
+
/** Its path: renamed when an array moves its item (D14). */
|
|
135
|
+
p: string;
|
|
136
|
+
e: string | undefined;
|
|
137
|
+
s: number;
|
|
138
|
+
t: boolean;
|
|
139
|
+
/** Checks or async validations running. */
|
|
140
|
+
vd: number;
|
|
141
|
+
/** A view read the hint: it is rendered, so the control links it. */
|
|
142
|
+
h: boolean;
|
|
143
|
+
/** A view asked for it, or the DOM has its control: issues at its path are its own. */
|
|
144
|
+
k: boolean;
|
|
145
|
+
/** Moves with what the parts show. */
|
|
146
|
+
ver: number;
|
|
147
|
+
pc: FieldParts | undefined;
|
|
148
|
+
pv: number;
|
|
149
|
+
oc: Map<unknown, {
|
|
150
|
+
control: Attrs;
|
|
151
|
+
label: Attrs;
|
|
152
|
+
}> | undefined;
|
|
153
|
+
constructor(path: string);
|
|
154
|
+
}
|
|
155
|
+
/** What an instance tells its views: the paths that moved, the field kinds and the form kinds. */
|
|
156
|
+
interface Listener {
|
|
157
|
+
hear(paths: readonly string[], mask: number, form: number): void;
|
|
158
|
+
}
|
|
159
|
+
type Control = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
160
|
+
declare class Instance {
|
|
161
|
+
#private;
|
|
162
|
+
readonly F: Form;
|
|
163
|
+
/** The DOM id the form's ids derive from. */
|
|
164
|
+
readonly id: string;
|
|
165
|
+
readonly N: Names;
|
|
166
|
+
/** Values: an immutable tree (paths.ts). */
|
|
167
|
+
v: Record<string, unknown>;
|
|
168
|
+
/** What `dirty` compares against: the defaults, then the last success's values. */
|
|
169
|
+
b: Record<string, unknown>;
|
|
170
|
+
/** The merged defaults (D11). */
|
|
171
|
+
d: Record<string, unknown>;
|
|
172
|
+
/** The hook's own `defaults`, as last given: a new object is followed. */
|
|
173
|
+
hd: unknown;
|
|
174
|
+
readonly r: Map<string, Rec>;
|
|
175
|
+
/** Paths whose value differs from the baseline. */
|
|
176
|
+
readonly dp: Set<string>;
|
|
177
|
+
readonly ls: Listener[];
|
|
178
|
+
onSuccess: ((data: unknown) => void) | undefined;
|
|
179
|
+
pending: boolean;
|
|
180
|
+
error: unknown;
|
|
181
|
+
/** The form error came from the schema (an issue with no field), so a valid run clears it. */
|
|
182
|
+
ce: boolean;
|
|
183
|
+
data: unknown;
|
|
184
|
+
progress: number;
|
|
185
|
+
/** A submit was attempted: errors now update on input and blur (D8). */
|
|
186
|
+
at: boolean;
|
|
187
|
+
/** The order of paths in the last issues, for `errors`. */
|
|
188
|
+
ord: string[];
|
|
189
|
+
/** `errors`, cached until an error changes. */
|
|
190
|
+
ec: ShownError[] | undefined;
|
|
191
|
+
/** Full validations started; a result for an older one is dropped. */
|
|
192
|
+
vv: number;
|
|
193
|
+
ac: AbortController | undefined;
|
|
194
|
+
/** After the next commit: focus the summary or the first invalid control (D12). */
|
|
195
|
+
fo: boolean;
|
|
196
|
+
/** Messages to announce after the next commit. */
|
|
197
|
+
an: string[];
|
|
198
|
+
/** Inside a submit: errors are announced by focus, not the live region. */
|
|
199
|
+
inSubmit: boolean;
|
|
200
|
+
mounted: boolean;
|
|
201
|
+
/** The form parts, cached by `pending`. */
|
|
202
|
+
fp: FormParts | undefined;
|
|
203
|
+
/** The delegated handlers, made with the first parts so their identity never changes (D6). */
|
|
204
|
+
hs: Readonly<Record<"i" | "b" | "s" | "r" | "c", (event: Event) => void>> | undefined;
|
|
205
|
+
fpv: boolean | undefined;
|
|
206
|
+
/** The array module's stable keys, by array path (D14). */
|
|
207
|
+
ak: Map<string, string[]> | undefined;
|
|
208
|
+
/** The checks module's state (D8). */
|
|
209
|
+
ck: unknown;
|
|
210
|
+
/** The definition's shape, once these values extended it (view.ts). */
|
|
211
|
+
sh: Shape | undefined;
|
|
212
|
+
constructor(F: Form, id: string, N: Names, options: UseFormOptions<unknown, unknown> | undefined);
|
|
213
|
+
rec(path: string): Rec;
|
|
214
|
+
spec(path: string): FieldSpec | undefined;
|
|
215
|
+
fid(path: string): string;
|
|
216
|
+
tell(paths: readonly string[], mask: number, form: number): void;
|
|
217
|
+
/** Every view moves: a reset, new defaults. */
|
|
218
|
+
all(): void;
|
|
219
|
+
/** Recomputes whether `path` is dirty; the form's bit when the set changed size between empty and not. */
|
|
220
|
+
dirty(path: string): number;
|
|
221
|
+
/** Writes a value: from the DOM (`dom`), or from code, which also writes the control. */
|
|
222
|
+
set(path: string, value: unknown, dom: boolean): void;
|
|
223
|
+
blur(el: Control): void;
|
|
224
|
+
/** Sets a field's error: one place, so the live region and the summary follow every change. */
|
|
225
|
+
error_(rec: Rec, message: string | undefined, source: number): boolean;
|
|
226
|
+
submit(): Promise<unknown>;
|
|
227
|
+
reset(values?: unknown): void;
|
|
228
|
+
/** The hook's options again (D11): pristine fields follow new defaults; edited ones keep their text. */
|
|
229
|
+
options(options: UseFormOptions<unknown, unknown> | undefined): void;
|
|
230
|
+
mount(): void;
|
|
231
|
+
unmount(): void;
|
|
232
|
+
/** After the framework committed: focus after a failed submit, then announcements (D12). */
|
|
233
|
+
commit(): void;
|
|
234
|
+
formParts(): FormParts;
|
|
235
|
+
/** A field's parts, rebuilt only when what they show moved; each one made when first read. */
|
|
236
|
+
fieldParts(rec: Rec): FieldParts;
|
|
237
|
+
/**
|
|
238
|
+
* The control's attributes (§5.3): id, name, value, aria wiring and
|
|
239
|
+
* constraints (D13). The value is the current one, not the baseline, so
|
|
240
|
+
* a server's `state` renders what was posted.
|
|
241
|
+
*/
|
|
242
|
+
control(rec: Rec): Attrs;
|
|
243
|
+
/** One radio, or one checkbox of an array field. */
|
|
244
|
+
optionParts(rec: Rec, value: unknown): {
|
|
245
|
+
control: Attrs;
|
|
246
|
+
label: Attrs;
|
|
247
|
+
};
|
|
248
|
+
/** The errors shown, in the order the last issues listed them. */
|
|
249
|
+
errors(): ShownError[];
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/core/view.d.ts
|
|
253
|
+
declare class View implements Source, Listener {
|
|
254
|
+
readonly i: Instance;
|
|
255
|
+
/** The view that made the instance: it owns its lifecycle and options. */
|
|
256
|
+
readonly own: boolean;
|
|
257
|
+
v: number;
|
|
258
|
+
ls: readonly (() => void)[];
|
|
259
|
+
/** Kinds read per path, and form kinds read, since the last reset. */
|
|
260
|
+
readonly rp: Map<string, number>;
|
|
261
|
+
rb: number;
|
|
262
|
+
/** The framework's tracker (Vue, Svelte, Solid). */
|
|
263
|
+
k: (() => unknown) | undefined;
|
|
264
|
+
/** The view object handed out, and the version it was made at. */
|
|
265
|
+
f: FormViewImpl | undefined;
|
|
266
|
+
fv: number;
|
|
267
|
+
/** One view object for the component's life (Vue, Svelte, Solid, plain code). */
|
|
268
|
+
keep: boolean;
|
|
269
|
+
/** Plain code's subscription, given by the vanilla glue. */
|
|
270
|
+
u: {
|
|
271
|
+
subscribe(listener: () => void): () => void;
|
|
272
|
+
stop(): void;
|
|
273
|
+
} | undefined;
|
|
274
|
+
constructor(instance: Instance, own: boolean);
|
|
275
|
+
hear(paths: readonly string[], mask: number, form: number): void;
|
|
276
|
+
read(path: string, kind: number): void;
|
|
277
|
+
readForm(kind: number): void;
|
|
278
|
+
/** A render starts: what it reads is what it shows. */
|
|
279
|
+
restart(): void;
|
|
280
|
+
current(): FormViewImpl;
|
|
281
|
+
subscribe(listener: () => void): () => void;
|
|
282
|
+
snapshot(): number;
|
|
283
|
+
setProps(props: object): void;
|
|
284
|
+
mount(): void;
|
|
285
|
+
unmount(): void;
|
|
286
|
+
commit(): void;
|
|
287
|
+
}
|
|
288
|
+
/** The view object: `f.form`, `f.fields`, `f.errors`… (§5.3). */
|
|
289
|
+
declare class FormViewImpl {
|
|
290
|
+
readonly $: View;
|
|
291
|
+
/** Field views of this version by path, and groups by prefix (`""`, `"address."`). */
|
|
292
|
+
readonly fs: Map<string, object>;
|
|
293
|
+
constructor(view: View);
|
|
294
|
+
get form(): Attrs;
|
|
295
|
+
get submit(): Attrs;
|
|
296
|
+
get summary(): Attrs;
|
|
297
|
+
get fields(): object;
|
|
298
|
+
get errors(): readonly {
|
|
299
|
+
path: string;
|
|
300
|
+
message: string;
|
|
301
|
+
href: string;
|
|
302
|
+
}[];
|
|
303
|
+
get error(): unknown;
|
|
304
|
+
get pending(): boolean;
|
|
305
|
+
get progress(): number;
|
|
306
|
+
get dirty(): boolean;
|
|
307
|
+
get data(): unknown;
|
|
308
|
+
requestSubmit(): Promise<unknown>;
|
|
309
|
+
reset(values?: unknown): void;
|
|
310
|
+
subscribe(listener: () => void): () => void;
|
|
311
|
+
stop(): void;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The input's shape (D7): which keys are leaves, objects or arrays, from
|
|
315
|
+
* the field spec's patterns (the schema's) and the values (the defaults,
|
|
316
|
+
* or a server's `state`). One per definition, which each instance's values
|
|
317
|
+
* extend. Each object node gets a class whose keys are getters on its
|
|
318
|
+
* prototype, so `f.fields` costs one object and a read of `f.fields.name`
|
|
319
|
+
* makes that field's view only. Getters, not a Proxy: a key read stays a
|
|
320
|
+
* plain property read, and the keys are known up front.
|
|
321
|
+
*/
|
|
322
|
+
interface Shape {
|
|
323
|
+
/** An object's keys; no prototype, so a key named `constructor` is a key. */
|
|
324
|
+
o?: Record<string, Shape>;
|
|
325
|
+
/** An array's item. */
|
|
326
|
+
a?: Shape;
|
|
327
|
+
/** An object node's fields class, once made. */
|
|
328
|
+
c?: new (form: FormViewImpl, prefix: string) => object;
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
export { Meta as a, Form as i, Shape as n, form as o, Checker as r, FormViewImpl as t };
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
//#region src/vite/index.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The frameworks `useForm` has glue for. Written out because the published
|
|
5
|
+
* types cannot name the private `@menapo/glue`; the check below fails the
|
|
6
|
+
* type-check the moment the two differ.
|
|
7
|
+
*/
|
|
8
|
+
export type Framework = "react" | "preact" | "vue" | "svelte" | "solid" | "vanilla";
|
|
9
|
+
export interface FormsPluginOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The framework of plain `.js`/`.ts` modules that call `useForm`.
|
|
12
|
+
* Default: the framework the root `package.json` depends on, else none
|
|
13
|
+
* (vanilla). `.vue`, `.svelte` and JSX modules go by their own.
|
|
14
|
+
*/
|
|
15
|
+
readonly framework?: Framework;
|
|
16
|
+
}
|
|
17
|
+
/** `plugins: [forms()]` in a Vite config, beside api's and query's. */
|
|
18
|
+
export declare function forms(options?: FormsPluginOptions): Plugin[];
|
|
19
|
+
//#endregion
|
|
20
|
+
export { forms as default };
|