@tschk/moonshine-svelte 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.
@@ -0,0 +1,46 @@
1
+ /** Erased return metadata supplied only by the native SFC compiler. */
2
+ declare const componentMetadata: unique symbol;
3
+ export type ComponentView<
4
+ View,
5
+ Exports extends object,
6
+ Props extends object,
7
+ > = View & {
8
+ readonly [componentMetadata]?: { props: Props; exports: Exports };
9
+ };
10
+ type Metadata<C extends (...args: never[]) => unknown> = [
11
+ ReturnType<C>,
12
+ ] extends [never]
13
+ ? never
14
+ : typeof componentMetadata extends keyof NonNullable<ReturnType<C>>
15
+ ? NonNullable<NonNullable<ReturnType<C>>[typeof componentMetadata]>
16
+ : never;
17
+ export type ComponentProps<C extends (...args: never[]) => unknown> = [
18
+ Metadata<C>,
19
+ ] extends [never]
20
+ ? never
21
+ : Metadata<C> extends { props: infer P extends object }
22
+ ? P
23
+ : never;
24
+ export type ComponentExports<C extends (...args: never[]) => unknown> = [
25
+ Metadata<C>,
26
+ ] extends [never]
27
+ ? never
28
+ : Metadata<C> extends { exports: infer E extends object }
29
+ ? E
30
+ : never;
31
+ /** Restrict the actual public callable while retaining its native view return type. */
32
+ export type ComponentFactory<C extends (...args: never[]) => unknown> = [
33
+ Metadata<C>,
34
+ ] extends [never]
35
+ ? never
36
+ : {} extends ComponentProps<C>
37
+ ? (props?: ComponentProps<C>) => ReturnType<C>
38
+ : (props: ComponentProps<C>) => ReturnType<C>;
39
+ export type MountOptions<Props> = {
40
+ target: Element | ShadowRoot;
41
+ anchor?: Node;
42
+ context?: ReadonlyMap<unknown, unknown>;
43
+ /** Source transitions remain unsupported; both boolean intro values are accepted. */
44
+ intro?: boolean;
45
+ } & ({} extends Props ? { props?: Props } : { props: Props });
46
+ export type UnmountOptions = { outro?: false };
package/src/mount.ts ADDED
@@ -0,0 +1,100 @@
1
+ import { createComponent } from "@tschk/moonshine-dom/component";
2
+ import {
3
+ createInsertionRange,
4
+ moveInsertionRange,
5
+ render,
6
+ type DOMChild,
7
+ } from "@tschk/moonshine-dom/dom";
8
+ import { withRuneScheduler } from "./pre";
9
+ const mounted = new WeakMap<object, () => void>();
10
+ export type {
11
+ ComponentView,
12
+ ComponentFactory,
13
+ ComponentProps,
14
+ ComponentExports,
15
+ MountOptions,
16
+ UnmountOptions,
17
+ } from "./mount-types";
18
+ import type {
19
+ ComponentExports,
20
+ ComponentProps,
21
+ MountOptions,
22
+ UnmountOptions,
23
+ } from "./mount-types";
24
+ /** Fresh mount for compiler-marked native SFC factories; hydration is not supported. */
25
+ export function mount<C extends (props: never) => DOMChild>(
26
+ component: C & ([ComponentExports<C>] extends [never] ? never : unknown),
27
+ options: MountOptions<ComponentProps<C>>,
28
+ ): ComponentExports<C> {
29
+ const { target, anchor } = options;
30
+ if (
31
+ typeof Element === "undefined" ||
32
+ (!(target instanceof Element) && !(target instanceof ShadowRoot))
33
+ )
34
+ throw new TypeError(
35
+ "Native mount requires a current-realm Element or ShadowRoot target",
36
+ );
37
+ for (const key of Reflect.ownKeys(options)) {
38
+ if (
39
+ key !== "target" &&
40
+ key !== "anchor" &&
41
+ key !== "props" &&
42
+ key !== "context" &&
43
+ key !== "intro"
44
+ )
45
+ throw new TypeError(`Unsupported native mount option: ${String(key)}`);
46
+ }
47
+ if (options.intro !== undefined && typeof options.intro !== "boolean")
48
+ throw new TypeError("Native mount intro must be boolean");
49
+ if (anchor && anchor.parentNode !== target)
50
+ throw new Error("Mount anchor must belong to target");
51
+ let instance: object | undefined,
52
+ range: Node | undefined,
53
+ stop: (() => void) | undefined;
54
+ try {
55
+ stop = render(
56
+ () =>
57
+ withRuneScheduler(() => {
58
+ range = createInsertionRange(() =>
59
+ createComponent(component, (options.props ?? {}) as never, "dom", {
60
+ contexts: options.context,
61
+ capture: (value) => {
62
+ instance = value;
63
+ },
64
+ }),
65
+ );
66
+ return range;
67
+ }),
68
+ target,
69
+ );
70
+ if (anchor) {
71
+ if (anchor.parentNode !== target)
72
+ throw new Error("Mount anchor must still belong to target after setup");
73
+ moveInsertionRange(range!, anchor);
74
+ }
75
+ if (!instance) throw new Error("Component setup was cancelled");
76
+ mounted.set(instance, stop);
77
+ return instance as ComponentExports<C>;
78
+ } catch (error) {
79
+ try {
80
+ stop?.();
81
+ } catch {}
82
+ throw error;
83
+ }
84
+ }
85
+ export async function unmount(
86
+ instance: object,
87
+ options?: UnmountOptions,
88
+ ): Promise<void> {
89
+ for (const key of Reflect.ownKeys(options ?? {})) {
90
+ if (key !== "outro")
91
+ throw new TypeError(`Unsupported native unmount option: ${String(key)}`);
92
+ }
93
+ if (options?.outro !== undefined && options.outro !== false)
94
+ throw new TypeError("Native unmount transitions are unsupported");
95
+ const stop = mounted.get(instance);
96
+ if (stop) {
97
+ mounted.delete(instance);
98
+ stop();
99
+ }
100
+ }