@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.
@@ -0,0 +1,88 @@
1
+ import { a as objectOptions, r as create } from "../view--JGPLHEo.js";
2
+ import { r as lower, t as names } from "../names-D_TE3BpM.js";
3
+ import { t as nextId } from "../source-DdFTPjID.js";
4
+ //#region ../glue/src/vanilla.ts
5
+ /**
6
+ * No framework (machine PLAN §5.5): listeners hear when the snapshot moves,
7
+ * and deferred side effects run right after them, so they find the DOM the
8
+ * listeners updated (D13: in vanilla, right after the event). Sources mount
9
+ * on the next microtask, after the code that created them has had a chance
10
+ * to render them once, and `stop()` is the unmount plain code has none of.
11
+ * A class, so each source costs one object and one closure.
12
+ */
13
+ /** Sources waiting for the microtask that mounts them. */
14
+ let waiting;
15
+ function mountWaiting() {
16
+ const sources = waiting;
17
+ waiting = void 0;
18
+ for (const source of sources) source.mount();
19
+ }
20
+ var Vanilla = class {
21
+ source;
22
+ /** The snapshot listeners last heard; nothing is computed while none listens. */
23
+ seen = -1;
24
+ /** Copied on write: a change being told keeps the listeners it started with. */
25
+ listeners;
26
+ /** Unsubscribes from the source. */
27
+ off;
28
+ constructor(source) {
29
+ this.source = source;
30
+ this.off = source.subscribe(() => this.tell());
31
+ }
32
+ tell() {
33
+ const { source, listeners } = this;
34
+ if (listeners) {
35
+ const now = source.snapshot();
36
+ if (now !== this.seen) {
37
+ this.seen = now;
38
+ for (const listener of listeners) listener();
39
+ }
40
+ }
41
+ source.commit();
42
+ }
43
+ /** Calls `listener` after each change the snapshot shows; returns the unsubscribe. */
44
+ subscribe(listener) {
45
+ if (!this.listeners) this.seen = this.source.snapshot();
46
+ this.listeners = this.listeners ? [...this.listeners, listener] : [listener];
47
+ return () => {
48
+ this.listeners = this.listeners?.filter((l) => l !== listener);
49
+ };
50
+ }
51
+ /** What a framework does on unmount: listeners go, side effects stop, and a source not mounted yet never is. */
52
+ stop() {
53
+ this.listeners = void 0;
54
+ this.off();
55
+ const at = waiting ? waiting.indexOf(this.source) : -1;
56
+ if (at >= 0) waiting.splice(at, 1);
57
+ this.source.unmount();
58
+ }
59
+ };
60
+ function useSource(create, props) {
61
+ const source = create(nextId(), props);
62
+ if (!waiting) {
63
+ waiting = [];
64
+ queueMicrotask(mountWaiting);
65
+ }
66
+ waiting.push(source);
67
+ return new Vanilla(source);
68
+ }
69
+ //#endregion
70
+ //#region src/glue/vanilla.ts
71
+ /**
72
+ * `useForm` without a framework (PLAN §5.2): `f.subscribe` hears each
73
+ * change the view read, and `spread(el, f.parts.form)` from
74
+ * `@menapo/machine/dom` puts parts on elements. Attributes are lowercase,
75
+ * as the DOM names them (`oninput`, `onfocusout`); the instance mounts on
76
+ * the next microtask, after the code that created it rendered it once.
77
+ */
78
+ const N = names(lower, false);
79
+ function useForm(source, options) {
80
+ objectOptions(options, "plain code");
81
+ const vanilla = useSource(create(source, N), options);
82
+ const view = vanilla.source;
83
+ view.keep = true;
84
+ view.u = vanilla;
85
+ return view.current();
86
+ }
87
+ //#endregion
88
+ export { useForm };
@@ -0,0 +1,4 @@
1
+ import { i as Form, t as FormViewImpl } from "../view-5_kyMWP3.js";
2
+ //#region src/glue/vue.d.ts
3
+ export declare function useForm(source: Form | FormViewImpl, options?: object | (() => object)): FormViewImpl;
4
+ //#endregion
@@ -0,0 +1,47 @@
1
+ import { r as create } from "../view--JGPLHEo.js";
2
+ import { a as vue, t as names } from "../names-D_TE3BpM.js";
3
+ import { t as nextId } from "../source-DdFTPjID.js";
4
+ import { nextTick, onBeforeUnmount, onMounted, shallowRef, useId, watch } from "vue";
5
+ //#region ../glue/src/vue.ts
6
+ /**
7
+ * Vue 3.5 (machine PLAN §5.5): a `shallowRef` holding the snapshot, which a
8
+ * render reads through the returned tracker and which a change replaces,
9
+ * so only views that read it update. Props given as a function are watched
10
+ * before each render; side effects start once mounted and run after each
11
+ * flush that follows a change (`nextTick`, queued after Vue's own).
12
+ */
13
+ /** Returns the tracker: call it in a render or `computed` to update with the source. */
14
+ function useSource(create, props) {
15
+ const read = typeof props === "function" ? props : void 0;
16
+ const source = create(useId() ?? nextId(), read ? read() : props);
17
+ const version = shallowRef(source.snapshot());
18
+ const commit = () => source.commit();
19
+ const off = source.subscribe(() => {
20
+ version.value = source.snapshot();
21
+ nextTick(commit);
22
+ });
23
+ if (read) watch(read, (next) => source.setProps(next), { flush: "pre" });
24
+ onMounted(() => source.mount());
25
+ onBeforeUnmount(() => {
26
+ off();
27
+ source.unmount();
28
+ });
29
+ return [source, () => version.value];
30
+ }
31
+ //#endregion
32
+ //#region src/glue/vue.ts
33
+ /**
34
+ * `useForm` in Vue (PLAN §5.2): the shared glue's `shallowRef` adapter;
35
+ * every read calls the tracker, and options given as a getter are watched,
36
+ * so pristine fields follow new defaults (D11). Handlers are named
37
+ * `onInput`, `onFocusout` (glue's naming).
38
+ */
39
+ const N = names(vue, false);
40
+ function useForm(source, options) {
41
+ const [view, track] = useSource(create(source, N), options);
42
+ view.k = track;
43
+ view.keep = true;
44
+ return view.current();
45
+ }
46
+ //#endregion
47
+ export { useForm };
@@ -0,0 +1,34 @@
1
+ import { o as form } from "./view-5_kyMWP3.js";
2
+ import { S as UseFormOptions, _ as OutputOf, a as FieldOf, b as ShownError, c as FieldView, d as FormOptions, g as OptionParts, h as InputOf, i as Defaults, l as Fields, m as FormView, n as Attrs, p as FormState, r as Checks, t as ArrayField, u as Form, v as Refreshable, y as RouteFormOptions } from "./types-CdCFdlFp.js";
3
+ //#region src/core/env.d.ts
4
+ /** What DevTools hears (M6): an instance's change, or a submit that ended. */
5
+ type DevtoolsEvent = {
6
+ readonly kind: "change";
7
+ readonly instance: object;
8
+ } | {
9
+ readonly kind: "gone";
10
+ readonly instance: object;
11
+ } | {
12
+ readonly kind: "submit";
13
+ readonly instance: object;
14
+ readonly ms: number;
15
+ readonly error?: unknown;
16
+ };
17
+ //#endregion
18
+ //#region src/core/inspect.d.ts
19
+ /** `listener` hears every change of any rendered form and every submit's end; returns the stop. */
20
+ export declare function inspect(listener: (event: DevtoolsEvent) => void): () => void;
21
+ //#endregion
22
+ //#region src/core/use.d.ts
23
+ interface UseForm {
24
+ /** A parent's view, for a child component: its own subscription to the same form. */
25
+ <F>(view: FormView<F>): FormView<F>;
26
+ /** A form in a component; Vue, Svelte and Solid also take the options as a getter. */
27
+ <F extends {
28
+ readonly name: string;
29
+ }>(form: F, options?: UseFormOptions<InputOf<F>, OutputOf<F>> | (() => UseFormOptions<InputOf<F>, OutputOf<F>>)): FormView<F>;
30
+ }
31
+ /** Reads a form in a component: values, errors, submission and parts (§5.3). */
32
+ export declare const useForm: UseForm;
33
+ //#endregion
34
+ export { type ArrayField, type Attrs, type Checks, type Defaults, type DevtoolsEvent, type FieldOf, type FieldView, type Fields, type Form, type FormOptions, type FormState, type FormView, type OptionParts, type Refreshable, type RouteFormOptions, type ShownError, type UseForm, type UseFormOptions, form };
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ import { i as fail, r as env } from "./instance-C1aijv06.js";
2
+ import { r as form } from "./form-BZ2OH06z.js";
3
+ //#region src/core/inspect.ts
4
+ /**
5
+ * `inspect` (D18): what DevTools reads the forms through, a nullable hook
6
+ * in the core as machine's and query's. An app that never imports it ships
7
+ * none of this; the plugin's dev-only page module is the one caller.
8
+ */
9
+ /** `listener` hears every change of any rendered form and every submit's end; returns the stop. */
10
+ function inspect(listener) {
11
+ const previous = env.devtools;
12
+ env.devtools = (event) => {
13
+ previous?.(event);
14
+ listener(event);
15
+ };
16
+ return () => void (env.devtools = previous);
17
+ }
18
+ //#endregion
19
+ //#region src/core/use.ts
20
+ /**
21
+ * `useForm` as `@menapo/forms` exports it (PLAN §5.2): the Vite plugin
22
+ * resolves each module's import to its framework's glue (`src/glue/`), so
23
+ * this body runs only when the plugin is missing, and says so.
24
+ */
25
+ /** Reads a form in a component: values, errors, submission and parts (§5.3). */
26
+ const useForm = (() => {
27
+ throw fail("NO_PLUGIN", "useForm ran without the Vite plugin; add forms() from \"@menapo/forms/vite\" to vite.config.ts, and reach useForm from a module the plugin transforms, by import.");
28
+ });
29
+ //#endregion
30
+ export { form, inspect, useForm };