@tschk/moonshine-vue 0.0.0-stage → 0.4.4

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/src/events.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { getComponentContext } from "@tschk/moonshine-dom/component";
2
+ import {
3
+ errorHandlerKey,
4
+ runWithOwner,
5
+ untrack,
6
+ } from "@tschk/moonshine-dom/reactive";
7
+ type EventMap<E> = { [K in keyof E]: readonly unknown[] };
8
+ type Camel<S extends string> = S extends `${infer A}-${infer B}`
9
+ ? `${A}${Capitalize<Camel<B>>}`
10
+ : S;
11
+ type Listener<A extends readonly unknown[]> = (...args: A) => unknown;
12
+ export type EventListeners<E extends EventMap<E>> = {
13
+ [K in keyof E as K extends string ? `on${Capitalize<Camel<K>>}` : never]?:
14
+ Listener<E[K]> | readonly Listener<E[K]>[];
15
+ } & {
16
+ [
17
+ K in keyof E as K extends string ? `on${Capitalize<Camel<K>>}Once` : never
18
+ ]?: Listener<E[K]> | readonly Listener<E[K]>[];
19
+ };
20
+ export type Emit<E extends EventMap<E>> = <K extends keyof E & string>(
21
+ event: K,
22
+ ...args: E[K]
23
+ ) => void;
24
+ const camel = (name: string) =>
25
+ name.replace(/-(\w)/g, (_, letter: string) => letter.toUpperCase());
26
+ const key = (name: string) =>
27
+ name ? "on" + name[0]!.toUpperCase() + name.slice(1) : "";
28
+ /** Compiler-private declared component events; no DOM dispatch, model modifiers or Vue app error API. */
29
+ export function createEmitter<E extends EventMap<E>>(source: object): Emit<E> {
30
+ const frame = getComponentContext();
31
+ const route = frame.owner.context?.get(errorHandlerKey) as
32
+ ((error: unknown) => boolean) | undefined;
33
+ let emitted: Set<string> | undefined;
34
+ const report = (error: unknown) => {
35
+ if (!route?.(error)) throw error;
36
+ };
37
+ return ((event: string, ...args: readonly unknown[]) => {
38
+ if (frame.mode === "dom" && frame.owner.disposed) return;
39
+ return untrack(() => {
40
+ let name = key(event),
41
+ listener = Reflect.get(source, name, source);
42
+ if (!listener) {
43
+ name = key(camel(event));
44
+ listener = Reflect.get(source, name, source);
45
+ }
46
+ let failed = false,
47
+ failure: unknown;
48
+ const invoke = (value: unknown) => {
49
+ const callbacks = Array.isArray(value) ? value : [value];
50
+ for (const fn of callbacks) {
51
+ if (fn == null) continue;
52
+ try {
53
+ if (typeof fn !== "function")
54
+ throw new TypeError(
55
+ "Component event listeners must be functions",
56
+ );
57
+ const result = Reflect.apply(fn, undefined, args);
58
+ if (
59
+ result &&
60
+ typeof (result as PromiseLike<unknown>).then === "function"
61
+ )
62
+ Promise.resolve(result).then(undefined, (error) => {
63
+ queueMicrotask(() =>
64
+ runWithOwner(frame.owner, () => untrack(() => report(error))),
65
+ );
66
+ });
67
+ } catch (error) {
68
+ try {
69
+ report(error);
70
+ } catch (unrouted) {
71
+ if (!failed) failure = unrouted;
72
+ failed = true;
73
+ }
74
+ }
75
+ }
76
+ };
77
+ invoke(listener);
78
+ const once = Reflect.get(source, name + "Once", source);
79
+ if (once && !emitted?.has(name)) {
80
+ (emitted ??= new Set()).add(name);
81
+ invoke(once);
82
+ }
83
+ if (failed) throw failure;
84
+ });
85
+ }) as Emit<E>;
86
+ }
@@ -0,0 +1 @@
1
+ export { crepusRenderer } from "@tschk/moonshine-dom/framework-lazy";
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @tschk/moonshine-vue
3
+ *
4
+ * Independent signal bridges and lazy native SSR compatibility renderer.
5
+ * Legacy Vue runtime/compiler entries are separate from native `/reactive`.
6
+ */
7
+ import {
8
+ createSignal as msCreateSignal,
9
+ type Signal as MsSignal,
10
+ } from "@tschk/moonshine";
11
+
12
+ export type VueRef<T> = {
13
+ get value(): T;
14
+ set value(next: T);
15
+ };
16
+
17
+ export function fromMoonshineSignal<T>(signal: MsSignal<T>): VueRef<T> {
18
+ return {
19
+ get value() {
20
+ return signal();
21
+ },
22
+ set value(next: T) {
23
+ signal.set(() => next);
24
+ },
25
+ };
26
+ }
27
+
28
+ export function createBridgedRef<T>(initial: T): VueRef<T> {
29
+ return fromMoonshineSignal(msCreateSignal(initial));
30
+ }
31
+
32
+ export { msCreateSignal as createMoonshineSignal };
33
+ export type { MsSignal as MoonshineSignal };
34
+ export {
35
+ createResource,
36
+ createSignal as createMsSignal,
37
+ createMemo,
38
+ batch,
39
+ untrack,
40
+ } from "@tschk/moonshine";
41
+ export type { Resource, ResourceStatus } from "@tschk/moonshine";
42
+ export { crepusRenderer } from "./framework";
@@ -0,0 +1,59 @@
1
+ /** Opt-in lifecycle for native component scopes, not Vue's component runtime ABI. */
2
+ import {
3
+ getRenderScheduler,
4
+ onBeforeDispose,
5
+ onAfterDispose,
6
+ queuePostCommit,
7
+ runWithOwner,
8
+ untrack,
9
+ } from "@tschk/moonshine-dom/reactive";
10
+ import {
11
+ getComponentContext,
12
+ onComponentReady,
13
+ withComponentConsumer,
14
+ } from "@tschk/moonshine-dom/component";
15
+ import { flushComponentReferences } from "@tschk/moonshine-dom/component-instance";
16
+
17
+ function component(fn: () => void) {
18
+ const frame = getComponentContext();
19
+ if (typeof fn !== "function")
20
+ throw new TypeError("Lifecycle callback must be a function");
21
+ return frame;
22
+ }
23
+ export function onMounted(fn: () => void): void {
24
+ const frame = component(fn);
25
+ if (frame.mode === "ssr") return;
26
+ const post = getRenderScheduler()?.post ?? queuePostCommit;
27
+ onComponentReady(() =>
28
+ post(() => {
29
+ if (frame.owner.disposed) return;
30
+ runWithOwner(frame.owner, () =>
31
+ untrack(() => {
32
+ flushComponentReferences();
33
+ if (!frame.owner.disposed) withComponentConsumer(frame, fn);
34
+ }),
35
+ );
36
+ }),
37
+ );
38
+ }
39
+ export function onBeforeUnmount(fn: () => void): void {
40
+ const frame = component(fn);
41
+ if (frame.mode === "ssr") return;
42
+ runWithOwner(frame.owner, () =>
43
+ onBeforeDispose(() => {
44
+ runWithOwner(frame.owner, () =>
45
+ untrack(() => withComponentConsumer(frame, fn)),
46
+ );
47
+ }),
48
+ );
49
+ }
50
+ export function onUnmounted(fn: () => void): void {
51
+ const frame = component(fn);
52
+ if (frame.mode === "ssr") return;
53
+ const post = getRenderScheduler()?.post ?? queuePostCommit;
54
+ runWithOwner(frame.owner, () =>
55
+ onAfterDispose(() => {
56
+ post(() => runWithOwner(frame.owner, () => untrack(fn)));
57
+ }),
58
+ );
59
+ }
@@ -0,0 +1,111 @@
1
+ import { isReactive } from "./reactive";
2
+ type StyleRecord = Record<string, unknown>;
3
+ export function normalizeClass(value: unknown): string {
4
+ if (typeof value === "string") return value.trim();
5
+ let result = "";
6
+ if (Array.isArray(value)) {
7
+ for (const item of value) {
8
+ const part = normalizeClass(item);
9
+ if (part) result += part + " ";
10
+ }
11
+ } else if (value !== null && typeof value === "object") {
12
+ for (const key in value)
13
+ if ((value as Record<string, unknown>)[key]) result += key + " ";
14
+ }
15
+ return result.trim();
16
+ }
17
+ // Vue's narrow declaration normalizer, not a CSS validator/parser: it deliberately
18
+ // does not interpret quotes and preserves the reference's recovery behavior.
19
+ function declarations(text: string): Record<string, string> {
20
+ const result: Record<string, string> = {};
21
+ for (const declaration of text
22
+ .replace(/\/\*[\s\S]*?\*\//g, "")
23
+ .split(/;(?![^(]*\))/g)) {
24
+ const colon = declaration.indexOf(":");
25
+ if (colon !== -1 && colon < declaration.length - 1) {
26
+ Object.defineProperty(result, declaration.slice(0, colon).trim(), {
27
+ value: declaration.slice(colon + 1).trim(),
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ });
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+ type StyleEntry<T> = unknown extends T
37
+ ? unknown
38
+ : T extends string
39
+ ? string
40
+ : T extends readonly unknown[]
41
+ ? StyleEntry<T[number]>
42
+ : T extends object
43
+ ? T[keyof T]
44
+ : never;
45
+ type NormalizedStyle<T> = unknown extends T
46
+ ? string | StyleRecord | undefined
47
+ : T extends (...args: never[]) => unknown
48
+ ? undefined
49
+ : T extends readonly unknown[]
50
+ ? Record<string, StyleEntry<T[number]>>
51
+ : T extends string | object
52
+ ? T
53
+ : undefined;
54
+ export function normalizeStyle<T>(value: T): NormalizedStyle<T>;
55
+ export function normalizeStyle(
56
+ value: unknown,
57
+ ): string | StyleRecord | undefined {
58
+ if (Array.isArray(value)) {
59
+ const result: StyleRecord = {};
60
+ for (const item of value) {
61
+ const style =
62
+ typeof item === "string" ? declarations(item) : normalizeStyle(item);
63
+ if (style && typeof style !== "string")
64
+ for (const key in style) {
65
+ Object.defineProperty(result, key, {
66
+ value: style[key],
67
+ enumerable: true,
68
+ configurable: true,
69
+ writable: true,
70
+ });
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ if (
76
+ typeof value === "string" ||
77
+ (value !== null && typeof value === "object")
78
+ )
79
+ return value as string | StyleRecord;
80
+ }
81
+ export function normalizeStaticStyle(value: string): Record<string, string> {
82
+ return declarations(value);
83
+ }
84
+
85
+ // Compiler-facing VNode policies differ from the public normalization utilities.
86
+ export function normalizeClassSSR<T>(
87
+ value: T,
88
+ ): T extends object ? string : T | string {
89
+ return (
90
+ value && typeof value !== "string" ? normalizeClass(value) : value
91
+ ) as T extends object ? string : T | string;
92
+ }
93
+ function copyReactive<T>(value: T): T {
94
+ if (isReactive(value) && !Array.isArray(value)) return { ...value } as T;
95
+ return value;
96
+ }
97
+ export function normalizeStyleDOM<T>(value: T): NormalizedStyle<T> {
98
+ return copyReactive(normalizeStyle(value));
99
+ }
100
+ type VNodeStyle<T> = T extends (...args: never[]) => unknown
101
+ ? T
102
+ : T extends object
103
+ ? NormalizedStyle<T>
104
+ : T;
105
+ export function normalizeStyleSSR<T>(value: T): VNodeStyle<T> {
106
+ return (
107
+ value !== null && typeof value === "object"
108
+ ? normalizeStyle(copyReactive(value))
109
+ : value
110
+ ) as VNodeStyle<T>;
111
+ }