@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.
- package/README.md +473 -2
- package/package.json +57 -4
- package/src/await.ts +123 -0
- package/src/binding.ts +115 -0
- package/src/class.ts +21 -0
- package/src/const-derived.ts +47 -0
- package/src/derived.ts +276 -0
- package/src/directives.ts +129 -0
- package/src/each.ts +306 -0
- package/src/effect.ts +243 -0
- package/src/framework.ts +1 -0
- package/src/history.ts +104 -0
- package/src/index.ts +55 -0
- package/src/mount-types.ts +46 -0
- package/src/mount.ts +100 -0
- package/src/pre.ts +671 -0
- package/src/private-class-state.ts +54 -0
- package/src/prop.ts +64 -0
- package/src/raw-snippet-dom.ts +214 -0
- package/src/raw-snippet-ssr.ts +75 -0
- package/src/raw-snippet.ts +36 -0
- package/src/raw-ssr-lifecycle.ts +8 -0
- package/src/raw-state.ts +19 -0
- package/src/runes.ts +11 -0
- package/src/server-mount.ts +31 -0
- package/src/server-prop.ts +29 -0
- package/src/server-store.ts +113 -0
- package/src/spread-props.ts +153 -0
- package/src/ssr.ts +50 -0
- package/src/state.ts +492 -0
- package/src/store-binding.ts +125 -0
- package/src/store-internal.ts +34 -0
- package/src/store-write.ts +38 -0
- package/src/store.ts +208 -0
- package/src/svelte.ts +152 -0
- package/src/tick-queue.ts +31 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** Component-owned store reads; compiler owns assignments and mutation lowering. */
|
|
2
|
+
import {
|
|
3
|
+
createSignal,
|
|
4
|
+
getOwner,
|
|
5
|
+
onAfterDispose,
|
|
6
|
+
onCleanup,
|
|
7
|
+
runWithOwner,
|
|
8
|
+
untrack,
|
|
9
|
+
} from "@tschk/moonshine-dom/reactive";
|
|
10
|
+
import { subscribeTo, changed } from "./store-internal";
|
|
11
|
+
import { withHistoryMutation, recordHistory, readHistory } from "./history";
|
|
12
|
+
export {
|
|
13
|
+
setStoreValue,
|
|
14
|
+
mutateStoreValue,
|
|
15
|
+
updateStoreValue,
|
|
16
|
+
} from "./store-write";
|
|
17
|
+
export type StoreSource<T> = {
|
|
18
|
+
subscribe(
|
|
19
|
+
run: (value: T) => void,
|
|
20
|
+
): void | (() => void) | { unsubscribe(): void };
|
|
21
|
+
};
|
|
22
|
+
export type StoreReference<T> = {
|
|
23
|
+
get(store: StoreSource<T> | null | undefined): T | undefined;
|
|
24
|
+
releaseIfChanged<S extends StoreSource<T> | null | undefined>(store: S): S;
|
|
25
|
+
/** Compiler calls this at the end of successful instance setup. */
|
|
26
|
+
finish(): void;
|
|
27
|
+
};
|
|
28
|
+
const noop = () => {};
|
|
29
|
+
export function createStoreReference<T>(): StoreReference<T> {
|
|
30
|
+
const owner = getOwner();
|
|
31
|
+
if (!owner || owner.disposed)
|
|
32
|
+
throw Error("Store references require a live native owner");
|
|
33
|
+
let store: StoreSource<T> | null = null,
|
|
34
|
+
current: T | undefined,
|
|
35
|
+
stop: () => void = noop,
|
|
36
|
+
closed = false,
|
|
37
|
+
finished = false,
|
|
38
|
+
releasing = false;
|
|
39
|
+
const [version, bump] = createSignal(0);
|
|
40
|
+
function release() {
|
|
41
|
+
const previous = stop;
|
|
42
|
+
stop = noop;
|
|
43
|
+
const outer = releasing;
|
|
44
|
+
releasing = true;
|
|
45
|
+
try {
|
|
46
|
+
previous();
|
|
47
|
+
} finally {
|
|
48
|
+
releasing = outer;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function temporary(next: StoreSource<T> | null | undefined): T | undefined {
|
|
52
|
+
if (next == null) return undefined;
|
|
53
|
+
let value: T | undefined;
|
|
54
|
+
const unsubscribe = untrack(() => subscribeTo(next, (v) => (value = v)));
|
|
55
|
+
unsubscribe();
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function close() {
|
|
59
|
+
if (closed) return;
|
|
60
|
+
closed = true;
|
|
61
|
+
release();
|
|
62
|
+
}
|
|
63
|
+
// A setup exception before finish() must still release acquired subscriptions.
|
|
64
|
+
onAfterDispose(close);
|
|
65
|
+
return {
|
|
66
|
+
get(next) {
|
|
67
|
+
// Unsubscribe reads cannot replace the subscription currently being released.
|
|
68
|
+
if (releasing)
|
|
69
|
+
return store === (next ?? null)
|
|
70
|
+
? readHistory(version, "value", () => current)
|
|
71
|
+
: temporary(next);
|
|
72
|
+
if (closed || (owner.disposed && !owner.disposing)) {
|
|
73
|
+
if (next == null) return current;
|
|
74
|
+
return temporary(next);
|
|
75
|
+
}
|
|
76
|
+
if (store !== next) {
|
|
77
|
+
release();
|
|
78
|
+
if (closed || (owner.disposed && !owner.disposing))
|
|
79
|
+
return next == null ? current : temporary(next);
|
|
80
|
+
store = next ?? null;
|
|
81
|
+
if (!next) current = undefined;
|
|
82
|
+
else {
|
|
83
|
+
let synchronous = true,
|
|
84
|
+
active = true,
|
|
85
|
+
unsubscribe: (() => void) | undefined;
|
|
86
|
+
stop = () => {
|
|
87
|
+
active = false;
|
|
88
|
+
unsubscribe?.();
|
|
89
|
+
};
|
|
90
|
+
unsubscribe = untrack(() =>
|
|
91
|
+
subscribeTo(next, (value) => {
|
|
92
|
+
if (!active || closed || (owner.disposed && !owner.disposing))
|
|
93
|
+
return;
|
|
94
|
+
const previous = current;
|
|
95
|
+
if (synchronous || !changed(previous, value)) {
|
|
96
|
+
current = value;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
withHistoryMutation(() => {
|
|
100
|
+
recordHistory(version, "value", previous, value);
|
|
101
|
+
current = value;
|
|
102
|
+
if (!owner.disposed) bump((v) => v + 1);
|
|
103
|
+
});
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
synchronous = false;
|
|
107
|
+
if (!active || closed || (owner.disposed && !owner.disposing))
|
|
108
|
+
unsubscribe();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
version();
|
|
112
|
+
return readHistory(version, "value", () => current);
|
|
113
|
+
},
|
|
114
|
+
finish() {
|
|
115
|
+
if (finished) return;
|
|
116
|
+
finished = true;
|
|
117
|
+
if (owner.disposed) close();
|
|
118
|
+
else runWithOwner(owner, () => onCleanup(close));
|
|
119
|
+
},
|
|
120
|
+
releaseIfChanged(next) {
|
|
121
|
+
if (store !== next) release();
|
|
122
|
+
return next;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Subscriber, Unsubscriber } from "./store";
|
|
2
|
+
/** Observable subscriptions may return an object whose method needs its receiver. */
|
|
3
|
+
export function subscribeTo<T>(
|
|
4
|
+
store: { subscribe(run: Subscriber<T>, invalidate?: () => void): unknown },
|
|
5
|
+
run: Subscriber<T>,
|
|
6
|
+
invalidate?: () => void,
|
|
7
|
+
): Unsubscriber {
|
|
8
|
+
const subscription: unknown = store.subscribe(run, invalidate);
|
|
9
|
+
let active = true;
|
|
10
|
+
return () => {
|
|
11
|
+
if (!active) return;
|
|
12
|
+
active = false;
|
|
13
|
+
if (typeof subscription === "function") subscription();
|
|
14
|
+
else if (
|
|
15
|
+
subscription &&
|
|
16
|
+
typeof subscription === "object" &&
|
|
17
|
+
"unsubscribe" in subscription &&
|
|
18
|
+
typeof subscription.unsubscribe === "function"
|
|
19
|
+
)
|
|
20
|
+
subscription.unsubscribe();
|
|
21
|
+
else if (subscription !== undefined)
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
"Store subscription must return a function or unsubscribe object",
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function changed<T>(previous: T, next: T): boolean {
|
|
29
|
+
return previous !== previous
|
|
30
|
+
? next === next
|
|
31
|
+
: previous !== next ||
|
|
32
|
+
(previous !== null && typeof previous === "object") ||
|
|
33
|
+
typeof previous === "function";
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StoreSource } from "./store-binding.js";
|
|
2
|
+
export interface WritableStoreSource<T> extends StoreSource<T> {
|
|
3
|
+
set(value: T): void;
|
|
4
|
+
}
|
|
5
|
+
export function setStoreValue<T, V extends T>(
|
|
6
|
+
store: WritableStoreSource<T>,
|
|
7
|
+
value: V,
|
|
8
|
+
): V {
|
|
9
|
+
store.set(value);
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
export function mutateStoreValue<T, R>(
|
|
13
|
+
store: WritableStoreSource<T>,
|
|
14
|
+
expression: R,
|
|
15
|
+
payload: NoInfer<T>,
|
|
16
|
+
): R {
|
|
17
|
+
store.set(payload);
|
|
18
|
+
return expression;
|
|
19
|
+
}
|
|
20
|
+
export function updateStoreValue<T>(
|
|
21
|
+
store: WritableStoreSource<T>,
|
|
22
|
+
old: NoInfer<T>,
|
|
23
|
+
delta: 1 | -1,
|
|
24
|
+
prefix: boolean,
|
|
25
|
+
): T {
|
|
26
|
+
// Svelte's store update helpers use addition, including its string/BigInt behavior.
|
|
27
|
+
const value = ((old as unknown as number) + delta) as T;
|
|
28
|
+
store.set(value);
|
|
29
|
+
return prefix ? value : old;
|
|
30
|
+
}
|
|
31
|
+
export function mutateServerStore<T, R>(
|
|
32
|
+
reference: { get(store: StoreSource<T>): T | undefined },
|
|
33
|
+
store: WritableStoreSource<T>,
|
|
34
|
+
expression: R,
|
|
35
|
+
): R {
|
|
36
|
+
store.set(reference.get(store)!);
|
|
37
|
+
return expression;
|
|
38
|
+
}
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/** Independent synchronous Svelte store protocol; no component runtime needed. */
|
|
2
|
+
import { subscribeTo, changed } from "./store-internal";
|
|
3
|
+
export type Subscriber<T> = (value: T) => void;
|
|
4
|
+
export type Invalidator<T> = (value?: T) => void;
|
|
5
|
+
export type Unsubscriber = () => void;
|
|
6
|
+
export type Updater<T> = (value: T) => T;
|
|
7
|
+
export type StartStopNotifier<T> = (
|
|
8
|
+
set: (value: T) => void,
|
|
9
|
+
update: (fn: Updater<T>) => void,
|
|
10
|
+
) => void | Unsubscriber;
|
|
11
|
+
export interface Readable<T> {
|
|
12
|
+
subscribe(run: Subscriber<T>, invalidate?: Invalidator<T>): Unsubscriber;
|
|
13
|
+
}
|
|
14
|
+
export interface Writable<T> extends Readable<T> {
|
|
15
|
+
set(value: T): void;
|
|
16
|
+
update(fn: Updater<T>): void;
|
|
17
|
+
}
|
|
18
|
+
type Subscription<T> = { run: Subscriber<T>; invalidate: Invalidator<T> };
|
|
19
|
+
const notifications: Array<() => void> = [];
|
|
20
|
+
const noop = () => {};
|
|
21
|
+
|
|
22
|
+
export function writable<T>(
|
|
23
|
+
initial?: T,
|
|
24
|
+
start: StartStopNotifier<T> = noop,
|
|
25
|
+
): Writable<T> {
|
|
26
|
+
let value = initial as T;
|
|
27
|
+
let stop: Unsubscriber | undefined;
|
|
28
|
+
const subscribers = new Set<Subscription<T>>();
|
|
29
|
+
function set(next: T): void {
|
|
30
|
+
if (!changed(value, next)) return;
|
|
31
|
+
value = next;
|
|
32
|
+
if (!stop) return;
|
|
33
|
+
const flush = notifications.length === 0;
|
|
34
|
+
let failure: unknown;
|
|
35
|
+
let failed = false;
|
|
36
|
+
for (const subscriber of subscribers) {
|
|
37
|
+
try {
|
|
38
|
+
subscriber.invalidate();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (!failed) failure = error;
|
|
41
|
+
failed = true;
|
|
42
|
+
}
|
|
43
|
+
const current = value;
|
|
44
|
+
notifications.push(() => subscriber.run(current));
|
|
45
|
+
}
|
|
46
|
+
if (flush) {
|
|
47
|
+
try {
|
|
48
|
+
for (let index = 0; index < notifications.length; index++) {
|
|
49
|
+
try {
|
|
50
|
+
notifications[index]!();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (!failed) failure = error;
|
|
53
|
+
failed = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} finally {
|
|
57
|
+
notifications.length = 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (failed) throw failure;
|
|
61
|
+
}
|
|
62
|
+
const update = (fn: Updater<T>) => set(fn(value));
|
|
63
|
+
function subscribe(
|
|
64
|
+
run: Subscriber<T>,
|
|
65
|
+
invalidate: Invalidator<T> = noop,
|
|
66
|
+
): Unsubscriber {
|
|
67
|
+
const subscriber = { run, invalidate };
|
|
68
|
+
subscribers.add(subscriber);
|
|
69
|
+
try {
|
|
70
|
+
if (subscribers.size === 1) stop = start(set, update) || noop;
|
|
71
|
+
run(value);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
subscribers.delete(subscriber);
|
|
74
|
+
if (!subscribers.size) {
|
|
75
|
+
const cleanup = stop;
|
|
76
|
+
stop = undefined;
|
|
77
|
+
try {
|
|
78
|
+
cleanup?.();
|
|
79
|
+
} catch {
|
|
80
|
+
/* Preserve subscription failure. */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
return () => {
|
|
86
|
+
if (!subscribers.delete(subscriber)) return;
|
|
87
|
+
if (!subscribers.size) {
|
|
88
|
+
const cleanup = stop;
|
|
89
|
+
stop = undefined;
|
|
90
|
+
cleanup?.();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return { subscribe, set, update };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function readable<T>(
|
|
98
|
+
initial?: T,
|
|
99
|
+
start?: StartStopNotifier<T>,
|
|
100
|
+
): Readable<T> {
|
|
101
|
+
return { subscribe: writable(initial, start).subscribe };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type Stores = Readable<any> | readonly Readable<any>[];
|
|
105
|
+
type Values<S> =
|
|
106
|
+
S extends Readable<infer T>
|
|
107
|
+
? T
|
|
108
|
+
: { [K in keyof S]: S[K] extends Readable<infer T> ? T : never };
|
|
109
|
+
export function derived<S extends Stores, T>(
|
|
110
|
+
stores: S,
|
|
111
|
+
fn: (values: Values<S>) => T,
|
|
112
|
+
initial?: T,
|
|
113
|
+
): Readable<T>;
|
|
114
|
+
export function derived<S extends Stores, T>(
|
|
115
|
+
stores: S,
|
|
116
|
+
fn: (
|
|
117
|
+
values: Values<S>,
|
|
118
|
+
set: (value: T) => void,
|
|
119
|
+
update: (fn: Updater<T>) => void,
|
|
120
|
+
) => void | Unsubscriber,
|
|
121
|
+
initial?: T,
|
|
122
|
+
): Readable<T>;
|
|
123
|
+
export function derived<S extends Stores, T>(
|
|
124
|
+
stores: S,
|
|
125
|
+
fn: (
|
|
126
|
+
values: Values<S>,
|
|
127
|
+
set: (value: T) => void,
|
|
128
|
+
update: (fn: Updater<T>) => void,
|
|
129
|
+
) => T | void | Unsubscriber,
|
|
130
|
+
initial?: T,
|
|
131
|
+
): Readable<T> {
|
|
132
|
+
const multiple = Array.isArray(stores);
|
|
133
|
+
const inputs = (multiple ? stores : [stores]) as readonly Readable<unknown>[];
|
|
134
|
+
if (!inputs.every((store) => store && typeof store.subscribe === "function"))
|
|
135
|
+
throw new TypeError("derived expects readable stores");
|
|
136
|
+
const automatic = fn.length < 2;
|
|
137
|
+
return readable(initial, (set, update) => {
|
|
138
|
+
const values: unknown[] = [];
|
|
139
|
+
const pending = new Set<number>();
|
|
140
|
+
const subscriptions: Unsubscriber[] = [];
|
|
141
|
+
let active = false;
|
|
142
|
+
let cleanup: Unsubscriber = noop;
|
|
143
|
+
function sync(): void {
|
|
144
|
+
if (pending.size) return;
|
|
145
|
+
const previous = cleanup;
|
|
146
|
+
cleanup = noop;
|
|
147
|
+
previous();
|
|
148
|
+
const result = fn(
|
|
149
|
+
(multiple ? values : values[0]) as Values<S>,
|
|
150
|
+
set,
|
|
151
|
+
update,
|
|
152
|
+
);
|
|
153
|
+
if (automatic) set(result as T);
|
|
154
|
+
else if (typeof result === "function") cleanup = result as Unsubscriber;
|
|
155
|
+
}
|
|
156
|
+
function release(): void {
|
|
157
|
+
active = false;
|
|
158
|
+
let failed = false;
|
|
159
|
+
let failure: unknown;
|
|
160
|
+
for (const unsubscribe of [...subscriptions, cleanup]) {
|
|
161
|
+
try {
|
|
162
|
+
unsubscribe();
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (!failed) failure = error;
|
|
165
|
+
failed = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
subscriptions.length = 0;
|
|
169
|
+
cleanup = noop;
|
|
170
|
+
if (failed) throw failure;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
inputs.forEach((store, index) =>
|
|
174
|
+
subscriptions.push(
|
|
175
|
+
subscribeTo(
|
|
176
|
+
store,
|
|
177
|
+
(value) => {
|
|
178
|
+
values[index] = value;
|
|
179
|
+
pending.delete(index);
|
|
180
|
+
if (active) sync();
|
|
181
|
+
},
|
|
182
|
+
() => {
|
|
183
|
+
pending.add(index);
|
|
184
|
+
},
|
|
185
|
+
),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
active = true;
|
|
189
|
+
sync();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
try {
|
|
192
|
+
release();
|
|
193
|
+
} catch {
|
|
194
|
+
/* Preserve derivation failure. */
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
return release;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function get<T>(store: Readable<T>): T {
|
|
203
|
+
let value!: T;
|
|
204
|
+
subscribeTo(store, (current) => {
|
|
205
|
+
value = current;
|
|
206
|
+
})();
|
|
207
|
+
return value;
|
|
208
|
+
}
|
package/src/svelte.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { rawSSRRegistrar, type Registrar } from "./raw-ssr-lifecycle";
|
|
2
|
+
import { isSSRRendering } from "@tschk/moonshine-dom/ssr-owner";
|
|
3
|
+
import {
|
|
4
|
+
captureSSRRenderAttempt,
|
|
5
|
+
isSSRRenderAccepted,
|
|
6
|
+
} from "@tschk/moonshine-dom/ssr-attempt";
|
|
7
|
+
import {
|
|
8
|
+
componentEvents,
|
|
9
|
+
getComponentContext,
|
|
10
|
+
peekComponentContext,
|
|
11
|
+
getContext as nativeGetContext,
|
|
12
|
+
setContext as nativeSetContext,
|
|
13
|
+
hasContext as nativeHasContext,
|
|
14
|
+
} from "@tschk/moonshine-dom/component";
|
|
15
|
+
import { createRuneEffect } from "./effect";
|
|
16
|
+
import { drainTickWork } from "./tick-queue";
|
|
17
|
+
import { withHistoryCleanup } from "./history";
|
|
18
|
+
|
|
19
|
+
/** Source snippet parameters; compiled render cells are private to Crepus. */
|
|
20
|
+
export type Snippet<Parameters extends unknown[] = []> = (
|
|
21
|
+
...args: number extends Parameters["length"] ? never : Parameters
|
|
22
|
+
) => unknown;
|
|
23
|
+
|
|
24
|
+
/** Native owner lifecycle helpers; Svelte compiler output is not supported. */
|
|
25
|
+
import {
|
|
26
|
+
assertNotCleanupComputation,
|
|
27
|
+
getOwner,
|
|
28
|
+
onCleanup,
|
|
29
|
+
runWithOwner,
|
|
30
|
+
untrack,
|
|
31
|
+
} from "@tschk/moonshine-dom/reactive";
|
|
32
|
+
|
|
33
|
+
function componentOwner(hook: string) {
|
|
34
|
+
assertNotCleanupComputation();
|
|
35
|
+
const owner = getOwner();
|
|
36
|
+
const frame = peekComponentContext();
|
|
37
|
+
if (!owner || owner.disposed || (frame && !frame.active))
|
|
38
|
+
throw new Error(
|
|
39
|
+
`${hook} must be called during native component initialization`,
|
|
40
|
+
);
|
|
41
|
+
return owner;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function onMount(fn: () => unknown): void {
|
|
45
|
+
componentOwner("onMount");
|
|
46
|
+
if (isSSRRendering() || peekComponentContext()?.mode === "ssr") return;
|
|
47
|
+
createRuneEffect(() => {
|
|
48
|
+
const cleanup = untrack(fn);
|
|
49
|
+
if (typeof cleanup === "function") return cleanup as () => void;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function onDestroy(fn: () => void): void {
|
|
54
|
+
const owner = componentOwner("onDestroy");
|
|
55
|
+
const server = isSSRRendering() || peekComponentContext()?.mode === "ssr";
|
|
56
|
+
const registrar = getOwner()?.context?.get(rawSSRRegistrar) as
|
|
57
|
+
Registrar | undefined;
|
|
58
|
+
if (server && registrar && registrar.consumer === peekComponentContext()) {
|
|
59
|
+
registrar.register(fn);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const attempt = server ? captureSSRRenderAttempt() : undefined;
|
|
63
|
+
const cleanup = server
|
|
64
|
+
? () => {
|
|
65
|
+
if (isSSRRenderAccepted(attempt)) fn();
|
|
66
|
+
}
|
|
67
|
+
: () => runWithOwner(owner, () => withHistoryCleanup(fn));
|
|
68
|
+
// Server hooks belong to the authored component, not compiler memo scopes.
|
|
69
|
+
const consumer = server ? peekComponentContext()?.owner : undefined;
|
|
70
|
+
runWithOwner(consumer ?? owner, () => onCleanup(cleanup));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Commit pending native rune work; future promise/timer updates are separate. */
|
|
74
|
+
export async function tick(): Promise<void> {
|
|
75
|
+
await Promise.resolve();
|
|
76
|
+
drainTickWork();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type EventDispatcher<EventMap extends Record<string, any>> = <
|
|
80
|
+
Type extends keyof EventMap,
|
|
81
|
+
>(
|
|
82
|
+
type: Type,
|
|
83
|
+
...args: undefined extends EventMap[Type]
|
|
84
|
+
? [detail?: EventMap[Type], options?: { cancelable?: boolean }]
|
|
85
|
+
: null extends EventMap[Type]
|
|
86
|
+
? [detail?: EventMap[Type], options?: { cancelable?: boolean }]
|
|
87
|
+
: [detail: EventMap[Type], options?: { cancelable?: boolean }]
|
|
88
|
+
) => boolean;
|
|
89
|
+
|
|
90
|
+
export function createEventDispatcher<
|
|
91
|
+
EventMap extends Record<string, any> = any,
|
|
92
|
+
>(): EventDispatcher<EventMap> {
|
|
93
|
+
const frame = getComponentContext();
|
|
94
|
+
return ((
|
|
95
|
+
type: string,
|
|
96
|
+
detail?: unknown,
|
|
97
|
+
options?: { cancelable?: boolean },
|
|
98
|
+
) => {
|
|
99
|
+
if (frame.owner.disposed) return true;
|
|
100
|
+
return untrack(() => {
|
|
101
|
+
const props = frame.props;
|
|
102
|
+
if (!props || (typeof props !== "object" && typeof props !== "function"))
|
|
103
|
+
return true;
|
|
104
|
+
const events = Reflect.get(props, componentEvents);
|
|
105
|
+
if (!events || !Object.hasOwn(events, type)) return true;
|
|
106
|
+
const listeners = events[type];
|
|
107
|
+
if (listeners == null) return true;
|
|
108
|
+
const callbacks = Array.isArray(listeners)
|
|
109
|
+
? listeners.slice()
|
|
110
|
+
: [listeners];
|
|
111
|
+
if (!callbacks.every((listener) => typeof listener === "function"))
|
|
112
|
+
throw new TypeError("Component event listeners must be functions");
|
|
113
|
+
const event = new CustomEvent(type, {
|
|
114
|
+
detail,
|
|
115
|
+
cancelable: options?.cancelable ?? false,
|
|
116
|
+
bubbles: false,
|
|
117
|
+
});
|
|
118
|
+
// Native function components have no legacy exported-instance `this`.
|
|
119
|
+
for (const callback of callbacks)
|
|
120
|
+
Reflect.apply(callback, undefined, [event]);
|
|
121
|
+
return !event.defaultPrevented;
|
|
122
|
+
});
|
|
123
|
+
}) as EventDispatcher<EventMap>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function getContext<T>(key: unknown): T {
|
|
127
|
+
return nativeGetContext<T>(key) as T;
|
|
128
|
+
}
|
|
129
|
+
export function setContext<T>(key: unknown, value: T): T {
|
|
130
|
+
return nativeSetContext(key, value);
|
|
131
|
+
}
|
|
132
|
+
export function hasContext(key: unknown): boolean {
|
|
133
|
+
return nativeHasContext(key);
|
|
134
|
+
}
|
|
135
|
+
/** The live per-instance map, including inherited entries. */
|
|
136
|
+
export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T {
|
|
137
|
+
return getComponentContext().contexts as T;
|
|
138
|
+
}
|
|
139
|
+
export function createContext<T>(): [() => T, (value: T) => T, () => boolean] {
|
|
140
|
+
const key = Symbol("svelte-context");
|
|
141
|
+
return [
|
|
142
|
+
() => {
|
|
143
|
+
if (!hasContext(key))
|
|
144
|
+
throw new Error(
|
|
145
|
+
"Context has not been set in this component or its ancestors",
|
|
146
|
+
);
|
|
147
|
+
return getContext<T>(key);
|
|
148
|
+
},
|
|
149
|
+
(value) => setContext(key, value),
|
|
150
|
+
() => hasContext(key),
|
|
151
|
+
];
|
|
152
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Private coordination of the optional rune queues; no kernel scheduling policy. */
|
|
2
|
+
const pending = /* @__PURE__ */ new Map<() => void, () => void>();
|
|
3
|
+
|
|
4
|
+
export function queueTickWork(run: () => void, cancel: () => void): void {
|
|
5
|
+
pending.set(run, cancel);
|
|
6
|
+
}
|
|
7
|
+
export function removeTickWork(run: () => void): void {
|
|
8
|
+
pending.delete(run);
|
|
9
|
+
}
|
|
10
|
+
export function drainTickWork(): void {
|
|
11
|
+
const counts = new Map<() => void, number>();
|
|
12
|
+
let failed = false,
|
|
13
|
+
failure: unknown;
|
|
14
|
+
while (pending.size) {
|
|
15
|
+
const [run, cancel] = pending.entries().next().value!;
|
|
16
|
+
pending.delete(run);
|
|
17
|
+
const count = (counts.get(run) ?? 0) + 1;
|
|
18
|
+
counts.set(run, count);
|
|
19
|
+
try {
|
|
20
|
+
if (count > 1000) {
|
|
21
|
+
if (!failed) failure = Error("Native tick update limit exceeded");
|
|
22
|
+
failed = true;
|
|
23
|
+
cancel();
|
|
24
|
+
} else run();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (!failed) failure = error;
|
|
27
|
+
failed = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (failed) throw failure;
|
|
31
|
+
}
|