ilha 0.14.9 → 0.15.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/dist/define.d.ts +3 -4
- package/dist/define.js +1 -1
- package/dist/index.d.ts +23 -31
- package/dist/index.js +3 -2
- package/dist/jsx-dev-runtime.d.ts +2 -2
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +6 -6
- package/dist/jsx-runtime.js +1 -1
- package/dist/mount-qJHX_CfF.js +482 -0
- package/dist/{mount-BzNrfVW4.js → paint-core-Cw1HbMp6.js} +138 -572
- package/dist/renderer.d.ts +119 -0
- package/dist/renderer.js +75 -0
- package/dist/runtime-B-sEGtAa.d.ts +95 -0
- package/dist/{types-CJF2qMK6.d.ts → types-DsdtZMhG.d.ts} +29 -2
- package/dist/{vnode-M-oJMKcv.js → vnode-BHUY61Rm.js} +11 -2
- package/dist/{vnode-CUTNGblA.d.ts → vnode-CpSQZnLM.d.ts} +1 -1
- package/package.json +7 -3
package/dist/define.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { i as Component } from "./types-
|
|
1
|
+
import { i as Component } from "./types-DsdtZMhG.js";
|
|
2
2
|
//#region src/define.d.ts
|
|
3
|
-
declare const define: (name: string, component: Component) => void;
|
|
4
|
-
//#endregion
|
|
5
|
-
export { define };
|
|
3
|
+
export declare const define: (name: string, component: Component) => void;
|
|
4
|
+
//#endregion
|
package/dist/define.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { S as
|
|
2
|
-
import { t as h } from "./vnode-
|
|
1
|
+
import { C as WatchOnceCleanup, S as WatchContext, _ as ResourceOptions, b as View, c as EventSourceStatus, d as Instruction, g as ResourceFetcher, h as Resource, i as Component, l as Fragment, n as AtomHandle, o as EventSourceFeed, r as AtomOptions, s as EventSourceOptions, t as AtomEquality, v as UnsafeHtml, w as Yielded, x as WatchCallback } from "./types-DsdtZMhG.js";
|
|
2
|
+
import { t as h } from "./vnode-CpSQZnLM.js";
|
|
3
|
+
import { r as watch } from "./runtime-B-sEGtAa.js";
|
|
3
4
|
import * as Atom from "effect/reactivity/Atom";
|
|
4
5
|
import { Equal } from "effect";
|
|
5
6
|
import * as Effect$1 from "effect/Effect";
|
|
6
7
|
import * as Stream$1 from "effect/Stream";
|
|
7
8
|
import { AtomRegistry } from "effect/reactivity/AtomRegistry";
|
|
8
|
-
import "effect/Scope";
|
|
9
9
|
//#region src/context.d.ts
|
|
10
10
|
interface ContextProviderProps<A> {
|
|
11
11
|
value: A;
|
|
@@ -16,9 +16,9 @@ interface IlhaContext<A> {
|
|
|
16
16
|
readonly defaultValue: A;
|
|
17
17
|
readonly Provider: (props: ContextProviderProps<A>) => View;
|
|
18
18
|
}
|
|
19
|
-
declare const createContext: <A>(defaultValue: A) => IlhaContext<A>;
|
|
19
|
+
export declare const createContext: <A>(defaultValue: A) => IlhaContext<A>;
|
|
20
20
|
/** Read a context value as an atom handle. Subscribes the active fiber on read. */
|
|
21
|
-
declare const context: <A>(ctx: IlhaContext<A>) => AtomHandle<A>;
|
|
21
|
+
export declare const context: <A>(ctx: IlhaContext<A>) => AtomHandle<A>;
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region src/error-boundary.d.ts
|
|
24
24
|
interface ErrorFallbackProps {
|
|
@@ -32,42 +32,30 @@ interface ErrorBoundaryProps {
|
|
|
32
32
|
children?: View;
|
|
33
33
|
}
|
|
34
34
|
/** Catch subtree failures and paint a fallback. Supports reset(). */
|
|
35
|
-
declare const ErrorBoundary: (props: ErrorBoundaryProps) => View;
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region src/watch.d.ts
|
|
38
|
-
/** Values delivered to watch callbacks. */
|
|
39
|
-
type WatchValue = string | number | boolean | bigint | null | undefined | readonly WatchValue[] | WatchRecord;
|
|
40
|
-
interface WatchRecord {
|
|
41
|
-
readonly [key: string]: WatchValue | undefined;
|
|
42
|
-
}
|
|
43
|
-
interface WatchFn {
|
|
44
|
-
<A extends WatchValue>(source: AtomHandle<A> | Atom.Atom<A> | Stream$1.Stream<A, unknown, unknown>, fn: WatchCallback<A>): Instruction<undefined>;
|
|
45
|
-
once: (fn: (ctx: WatchContext) => WatchOnceCleanup | undefined | Promise<WatchOnceCleanup | undefined>) => Instruction<undefined>;
|
|
46
|
-
}
|
|
47
|
-
declare const watch: WatchFn;
|
|
35
|
+
export declare const ErrorBoundary: (props: ErrorBoundaryProps) => View;
|
|
48
36
|
//#endregion
|
|
49
37
|
//#region src/atom.d.ts
|
|
50
38
|
/** Key-order-insensitive deep equality for JSON-shaped values. */
|
|
51
|
-
declare const structuralEqual: typeof Equal.equals;
|
|
39
|
+
export declare const structuralEqual: typeof Equal.equals;
|
|
52
40
|
/** Read atoms inside `fn` without subscribing the active render. Sync only. */
|
|
53
|
-
declare const untrack: <A>(fn: () => A) => A;
|
|
41
|
+
export declare const untrack: <A>(fn: () => A) => A;
|
|
54
42
|
interface AtomFn {
|
|
55
43
|
<A>(init: A | Atom.Atom<A> | Effect$1.Effect<A, unknown, AtomRegistry> | Stream$1.Stream<A, unknown, AtomRegistry>, options?: AtomOptions<A>): AtomHandle<A>;
|
|
56
44
|
lazy: <A>(init: () => A) => AtomHandle<A>;
|
|
57
45
|
}
|
|
58
|
-
declare const atom: AtomFn;
|
|
59
|
-
declare const batch: (f: () => void) => void;
|
|
46
|
+
export declare const atom: AtomFn;
|
|
47
|
+
export declare const batch: (f: () => void) => void;
|
|
60
48
|
//#endregion
|
|
61
49
|
//#region src/resource.d.ts
|
|
62
50
|
/** Drop the cached value for `key` and refetch it in live components. */
|
|
63
|
-
declare const invalidate: (key: string) => void;
|
|
51
|
+
export declare const invalidate: (key: string) => void;
|
|
64
52
|
/**
|
|
65
53
|
* Fetch-once, share-everywhere async state for `key`. Concurrent mounts
|
|
66
54
|
* dedupe on one in-flight request; remounts seed from the shared cache
|
|
67
55
|
* (and revalidate when `staleWhileRevalidate` holds). Call unconditionally
|
|
68
56
|
* at the top level of a sync component, before any `await`.
|
|
69
57
|
*/
|
|
70
|
-
declare const resource: <T>(key: string, fetcher: ResourceFetcher<T>, opts?: ResourceOptions) => Resource<T>;
|
|
58
|
+
export declare const resource: <T>(key: string, fetcher: ResourceFetcher<T>, opts?: ResourceOptions) => Resource<T>;
|
|
71
59
|
//#endregion
|
|
72
60
|
//#region src/sse.d.ts
|
|
73
61
|
/**
|
|
@@ -76,10 +64,10 @@ declare const resource: <T>(key: string, fetcher: ResourceFetcher<T>, opts?: Res
|
|
|
76
64
|
* an empty stream with status `"closed"`. Call unconditionally at the top
|
|
77
65
|
* level of a component.
|
|
78
66
|
*/
|
|
79
|
-
declare const fromEventSource: <T = string>(url: string, opts?: EventSourceOptions<T>) => EventSourceFeed<T>;
|
|
67
|
+
export declare const fromEventSource: <T = string>(url: string, opts?: EventSourceOptions<T>) => EventSourceFeed<T>;
|
|
80
68
|
//#endregion
|
|
81
69
|
//#region src/when.d.ts
|
|
82
|
-
declare const when: <A, E = never, R = never>(stream: Stream$1.Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | undefined, View>) => Instruction<undefined, E>;
|
|
70
|
+
export declare const when: <A, E = never, R = never>(stream: Stream$1.Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | undefined, View>) => Instruction<undefined, E>;
|
|
83
71
|
//#endregion
|
|
84
72
|
//#region src/mount.d.ts
|
|
85
73
|
interface RenderToStringOptions {
|
|
@@ -90,10 +78,14 @@ interface RenderToStringOptions {
|
|
|
90
78
|
}
|
|
91
79
|
interface MountOptions {
|
|
92
80
|
hydrate?: boolean;
|
|
93
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Called with a failing root component, and with a nested component no
|
|
83
|
+
* `ErrorBoundary` caught, before the error view paints.
|
|
84
|
+
*/
|
|
85
|
+
onError?: (error: Error) => void;
|
|
94
86
|
}
|
|
95
|
-
declare const mount: (el: Element, fn: Component, opts?: MountOptions) => (() => void);
|
|
96
|
-
declare const renderToString: (fn: Component, opts?: RenderToStringOptions) => Promise<string>;
|
|
87
|
+
export declare const mount: (el: Element, fn: Component, opts?: MountOptions) => (() => void);
|
|
88
|
+
export declare const renderToString: (fn: Component, opts?: RenderToStringOptions) => Promise<string>;
|
|
97
89
|
//#endregion
|
|
98
90
|
//#region src/unsafe.d.ts
|
|
99
91
|
/**
|
|
@@ -104,6 +96,6 @@ declare const renderToString: (fn: Component, opts?: RenderToStringOptions) => P
|
|
|
104
96
|
* scripts inserted this way do not run, but event attributes and other
|
|
105
97
|
* markup still apply.
|
|
106
98
|
*/
|
|
107
|
-
declare const unsafe: (html: string) => UnsafeHtml;
|
|
99
|
+
export declare const unsafe: (html: string) => UnsafeHtml;
|
|
108
100
|
//#endregion
|
|
109
|
-
export { type AtomEquality, type AtomHandle, type AtomOptions, type Component, type ContextProviderProps,
|
|
101
|
+
export { type AtomEquality, type AtomHandle, type AtomOptions, type Component, type ContextProviderProps, type ErrorBoundaryProps, type ErrorFallback, type ErrorFallbackProps, type EventSourceFeed, type EventSourceOptions, type EventSourceStatus, Fragment, type IlhaContext, type MountOptions, type RenderToStringOptions, type Resource, type ResourceFetcher, type ResourceOptions, type UnsafeHtml, type View, type WatchCallback, type WatchContext, type WatchOnceCleanup, h, watch };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import { d as
|
|
1
|
+
import { _ as isString, f as errorView, i as Fragment, m as isFunction, t as h } from "./vnode-BHUY61Rm.js";
|
|
2
|
+
import { C as makeFiber, S as getFiber, _ as structuralEqual, b as clearFiberView, d as interpret, f as atom, g as isAtomHandle, h as instr, m as handleOwner, n as unsafe, p as batch, u as runSetup, v as untrack, x as closeFiber, y as wrapHandle } from "./paint-core-Cw1HbMp6.js";
|
|
3
|
+
import { n as renderToString, t as mount } from "./mount-qJHX_CfF.js";
|
|
3
4
|
import * as Atom from "effect/reactivity/Atom";
|
|
4
5
|
import * as Effect from "effect/Effect";
|
|
5
6
|
import * as Stream from "effect/Stream";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as Fragment } from "./types-
|
|
2
|
-
import { n as JSX } from "./vnode-
|
|
1
|
+
import { l as Fragment } from "./types-DsdtZMhG.js";
|
|
2
|
+
import { n as JSX } from "./vnode-CpSQZnLM.js";
|
|
3
3
|
import { jsx, jsxDEV, jsxs } from "./jsx-runtime.js";
|
|
4
4
|
export { Fragment, type JSX, jsx, jsxDEV, jsxs };
|
package/dist/jsx-dev-runtime.js
CHANGED
package/dist/jsx-runtime.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as ComponentFn,
|
|
2
|
-
import { n as JSX, t as h } from "./vnode-
|
|
1
|
+
import { a as ComponentFn, b as View, f as JsxComponent, l as Fragment, p as PropBag } from "./types-DsdtZMhG.js";
|
|
2
|
+
import { n as JSX, t as h } from "./vnode-CpSQZnLM.js";
|
|
3
3
|
//#region src/jsx-runtime.d.ts
|
|
4
|
-
declare const jsx: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
|
|
5
|
-
declare const jsxs:
|
|
6
|
-
declare const jsxDEV:
|
|
4
|
+
export declare const jsx: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
|
|
5
|
+
export declare const jsxs: typeof jsx;
|
|
6
|
+
export declare const jsxDEV: typeof jsx;
|
|
7
7
|
//#endregion
|
|
8
|
-
export { Fragment, type JSX, type View, h
|
|
8
|
+
export { Fragment, type JSX, type View, h };
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { _ as isString, c as KEEP, d as defer, g as isObject, m as isFunction, r as isVNode, u as SLOT_ATTR, y as unwrap } from "./vnode-BHUY61Rm.js";
|
|
2
|
+
import { T as makeRuntime, a as morphInner, c as bindEvents, i as insertRendered, l as eventTypeFromProp, o as placeChildren, r as committedElement, s as standInFor, t as createPainter, u as runSetup, w as makeRootFiber, x as closeFiber } from "./paint-core-Cw1HbMp6.js";
|
|
3
|
+
|
|
4
|
+
//#region src/paint.ts
|
|
5
|
+
const noopDisconnect = () => {};
|
|
6
|
+
const core$1 = createPainter({
|
|
7
|
+
appendRoot: (root, node) => insertRendered(root, node, null),
|
|
8
|
+
asElement: (el) => el,
|
|
9
|
+
asNode: (host) => host,
|
|
10
|
+
asRoot: (el) => el,
|
|
11
|
+
bindEvents,
|
|
12
|
+
committed: committedElement,
|
|
13
|
+
createElement: (tag, ns) => ns ? document.createElementNS(ns, tag) : document.createElement(tag),
|
|
14
|
+
createRaw: (html, parent) => {
|
|
15
|
+
const range = document.createRange();
|
|
16
|
+
range.selectNodeContents(parent);
|
|
17
|
+
return [...range.createContextualFragment(html).childNodes];
|
|
18
|
+
},
|
|
19
|
+
createSlotHost: (slotId) => {
|
|
20
|
+
const host = document.createElement("span");
|
|
21
|
+
if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
|
|
22
|
+
host.style.display = "contents";
|
|
23
|
+
return host;
|
|
24
|
+
},
|
|
25
|
+
createText: (text) => document.createTextNode(text),
|
|
26
|
+
disconnect: noopDisconnect,
|
|
27
|
+
placeChildren: (root, nodes) => {
|
|
28
|
+
if (root instanceof Element) placeChildren(root, nodes);
|
|
29
|
+
},
|
|
30
|
+
reuseNode: (host) => host instanceof Element && host.isConnected ? standInFor(host) : host,
|
|
31
|
+
setFormControl: (el, key, v) => {
|
|
32
|
+
const node = el;
|
|
33
|
+
if (key === "value") {
|
|
34
|
+
if (v === null || v === void 0) {
|
|
35
|
+
el.removeAttribute("value");
|
|
36
|
+
node.value = "";
|
|
37
|
+
} else {
|
|
38
|
+
el.setAttribute("value", String(v));
|
|
39
|
+
node.value = String(v);
|
|
40
|
+
}
|
|
41
|
+
} else if (key === "checked") {
|
|
42
|
+
node.checked = Boolean(v);
|
|
43
|
+
if (v) el.setAttribute("checked", "");
|
|
44
|
+
else el.removeAttribute("checked");
|
|
45
|
+
} else {
|
|
46
|
+
node.selected = Boolean(v);
|
|
47
|
+
if (v) el.setAttribute("selected", "");
|
|
48
|
+
else el.removeAttribute("selected");
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
setStyle: (el, css) => {
|
|
52
|
+
el.style.cssText = css;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const refreshAtomHost = (fiber, atom, host, hole) => {
|
|
56
|
+
if (hole.closed || !host.isConnected) return;
|
|
57
|
+
const next = unwrap(fiber.registry.get(atom));
|
|
58
|
+
if (next === KEEP) return;
|
|
59
|
+
core$1.paintHole(hole, next);
|
|
60
|
+
};
|
|
61
|
+
const refreshAllAtomHosts = (fiber) => {
|
|
62
|
+
for (const h of fiber.holes) if (h.atom && h.host && h.holeFiber) refreshAtomHost(fiber, h.atom, h.host, h.holeFiber);
|
|
63
|
+
};
|
|
64
|
+
const pruneDisconnectedAtomHoles = (fiber) => {
|
|
65
|
+
fiber.holes = fiber.holes.filter((h) => {
|
|
66
|
+
if (!h.atom || !h.host || !h.holeFiber) return true;
|
|
67
|
+
if (h.host.isConnected) return true;
|
|
68
|
+
h.dispose();
|
|
69
|
+
return false;
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const canHydrate = (fiber, view) => {
|
|
73
|
+
if (!isVNode(view) || !isString(view.type)) return false;
|
|
74
|
+
const el = fiber.root.firstElementChild;
|
|
75
|
+
return !!el && el.tagName === view.type.toUpperCase();
|
|
76
|
+
};
|
|
77
|
+
const paintHydrate = (fiber, view) => {
|
|
78
|
+
const el = fiber.root.firstElementChild;
|
|
79
|
+
if (!el) return;
|
|
80
|
+
fiber.hydrate = false;
|
|
81
|
+
fiber.liveEl = el;
|
|
82
|
+
core$1.applyProps(el, view.props, fiber);
|
|
83
|
+
const childFiber = {
|
|
84
|
+
...fiber,
|
|
85
|
+
holes: fiber.holes,
|
|
86
|
+
root: el
|
|
87
|
+
};
|
|
88
|
+
el.replaceChildren();
|
|
89
|
+
for (const c of view.children) for (const n of core$1.materialize(c, childFiber)) insertRendered(el, n, null);
|
|
90
|
+
};
|
|
91
|
+
const paintMorphLive = (fiber, view) => {
|
|
92
|
+
if (!fiber.liveEl) return;
|
|
93
|
+
core$1.disposeHoles(fiber, { morph: true });
|
|
94
|
+
core$1.applyProps(fiber.liveEl, view.props, fiber);
|
|
95
|
+
const liveNs = fiber.liveEl.namespaceURI;
|
|
96
|
+
const tmp = liveNs && liveNs !== "http://www.w3.org/1999/xhtml" ? document.createElementNS(liveNs, fiber.liveEl.localName) : document.createElement(fiber.liveEl.localName);
|
|
97
|
+
const childFiber = {
|
|
98
|
+
...fiber,
|
|
99
|
+
holes: fiber.holes,
|
|
100
|
+
root: tmp
|
|
101
|
+
};
|
|
102
|
+
for (const c of view.children) for (const n of core$1.materialize(c, childFiber)) tmp.append(n);
|
|
103
|
+
morphInner(fiber.liveEl, tmp);
|
|
104
|
+
refreshAllAtomHosts(fiber);
|
|
105
|
+
pruneDisconnectedAtomHoles(fiber);
|
|
106
|
+
};
|
|
107
|
+
const paintRoot = (fiber, view) => {
|
|
108
|
+
fiber.islandFrame ??= {
|
|
109
|
+
i: 0,
|
|
110
|
+
slots: []
|
|
111
|
+
};
|
|
112
|
+
if (fiber.hydrate) {
|
|
113
|
+
if (canHydrate(fiber, view)) {
|
|
114
|
+
paintHydrate(fiber, view);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.warn("[ilha] hydrate mismatch, full mount");
|
|
118
|
+
fiber.hydrate = false;
|
|
119
|
+
if (fiber.root instanceof Element) fiber.root.replaceChildren();
|
|
120
|
+
}
|
|
121
|
+
if (fiber.liveEl && isVNode(view) && isString(view.type) && fiber.liveEl.localName === view.type) {
|
|
122
|
+
paintMorphLive(fiber, view);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
core$1.disposeHoles(fiber, { morph: true });
|
|
126
|
+
if (fiber.root instanceof Element && fiber.root.childNodes.length > 0) {
|
|
127
|
+
const tmp = document.createElement("div");
|
|
128
|
+
for (const n of core$1.materialize(view, fiber)) tmp.append(n);
|
|
129
|
+
morphInner(fiber.root, tmp);
|
|
130
|
+
const first = fiber.root.firstElementChild;
|
|
131
|
+
if (first) fiber.liveEl = first;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
core$1.insert(fiber, core$1.materialize(view, fiber));
|
|
135
|
+
const first = fiber.root instanceof Element ? fiber.root.firstElementChild : null;
|
|
136
|
+
if (first) fiber.liveEl = first;
|
|
137
|
+
};
|
|
138
|
+
const paint$1 = (fiber, view) => {
|
|
139
|
+
core$1.commit(() => {
|
|
140
|
+
core$1.pass(fiber, () => {
|
|
141
|
+
paintRoot(fiber, view);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/snapshot.ts
|
|
148
|
+
const MAX_SNAPSHOT_CHARS = 262144;
|
|
149
|
+
const MAX_SNAPSHOT_DEPTH = 32;
|
|
150
|
+
const exceedsMaxDepth = (value, depth) => {
|
|
151
|
+
if (depth > MAX_SNAPSHOT_DEPTH) return true;
|
|
152
|
+
if (value === null || !isObject(value)) {
|
|
153
|
+
if (Array.isArray(value)) {
|
|
154
|
+
for (const item of value) if (exceedsMaxDepth(item, depth + 1)) return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const obj = value;
|
|
159
|
+
for (const key of Object.keys(obj)) if (exceedsMaxDepth(obj[key], depth + 1)) return true;
|
|
160
|
+
return false;
|
|
161
|
+
};
|
|
162
|
+
const deleteUnsafeKey = (obj, key) => {
|
|
163
|
+
if (Object.hasOwn(obj, key)) Reflect.deleteProperty(obj, key);
|
|
164
|
+
};
|
|
165
|
+
const stripUnsafeKeys = (value) => {
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
for (const item of value) stripUnsafeKeys(item);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (value === null || !isObject(value)) return;
|
|
171
|
+
const obj = value;
|
|
172
|
+
deleteUnsafeKey(obj, "__proto__");
|
|
173
|
+
deleteUnsafeKey(obj, "constructor");
|
|
174
|
+
deleteUnsafeKey(obj, "prototype");
|
|
175
|
+
for (const key of Object.getOwnPropertyNames(obj)) stripUnsafeKeys(obj[key]);
|
|
176
|
+
};
|
|
177
|
+
const safeParseSnapshot = (raw) => {
|
|
178
|
+
if (raw.length > MAX_SNAPSHOT_CHARS) return;
|
|
179
|
+
let parsed;
|
|
180
|
+
try {
|
|
181
|
+
parsed = JSON.parse(raw);
|
|
182
|
+
} catch {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (exceedsMaxDepth(parsed, 1)) return;
|
|
186
|
+
if (!isObject(parsed) || Array.isArray(parsed)) return;
|
|
187
|
+
stripUnsafeKeys(parsed);
|
|
188
|
+
return parsed;
|
|
189
|
+
};
|
|
190
|
+
const encodeSnapshot = (values) => JSON.stringify({ v: values });
|
|
191
|
+
const decodeSnapshot = (raw) => {
|
|
192
|
+
const values = safeParseSnapshot(raw)?.v;
|
|
193
|
+
return Array.isArray(values) ? values : void 0;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/ssr-dom.ts
|
|
198
|
+
const VOID = /* @__PURE__ */ new Set([
|
|
199
|
+
"area",
|
|
200
|
+
"base",
|
|
201
|
+
"br",
|
|
202
|
+
"col",
|
|
203
|
+
"embed",
|
|
204
|
+
"hr",
|
|
205
|
+
"img",
|
|
206
|
+
"input",
|
|
207
|
+
"link",
|
|
208
|
+
"meta",
|
|
209
|
+
"param",
|
|
210
|
+
"source",
|
|
211
|
+
"track",
|
|
212
|
+
"wbr"
|
|
213
|
+
]);
|
|
214
|
+
/** Tags whose children are raw text — do not HTML-escape content. */
|
|
215
|
+
const RAW_TEXT = /* @__PURE__ */ new Set(["script", "style"]);
|
|
216
|
+
const escapeText = (s) => s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
217
|
+
const escapeAttr = (s) => escapeText(s).replaceAll("\"", """);
|
|
218
|
+
const createSsrRaw = (html) => ({
|
|
219
|
+
html,
|
|
220
|
+
kind: "raw"
|
|
221
|
+
});
|
|
222
|
+
const createSsrText = (text) => ({
|
|
223
|
+
kind: "text",
|
|
224
|
+
text
|
|
225
|
+
});
|
|
226
|
+
const serializeRawText = (tag, text) => {
|
|
227
|
+
if (new RegExp(`</${tag}\\b`, "iu").test(text)) throw new Error(`ilha: unsafe raw text in <${tag}> (contains "</${tag}")`);
|
|
228
|
+
return text;
|
|
229
|
+
};
|
|
230
|
+
const serializeNode = (node) => {
|
|
231
|
+
if (node.kind === "text") return escapeText(node.text);
|
|
232
|
+
if (node.kind === "raw") return node.html;
|
|
233
|
+
const parts = [];
|
|
234
|
+
for (const [name, value] of node.attrs) parts.push(`${name}="${escapeAttr(value)}"`);
|
|
235
|
+
const attr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
236
|
+
const { tag } = node;
|
|
237
|
+
if (VOID.has(tag)) return `<${tag}${attr}>`;
|
|
238
|
+
if (RAW_TEXT.has(tag)) return `<${tag}${attr}>${serializeRawText(tag, node.textContent)}</${tag}>`;
|
|
239
|
+
return `<${tag}${attr}>${node.children.map(serializeNode).join("")}</${tag}>`;
|
|
240
|
+
};
|
|
241
|
+
const toDataAttr = (key) => `data-${key.replaceAll(/[A-Z]/gu, (ch) => `-${ch.toLowerCase()}`)}`;
|
|
242
|
+
const isSymbolKey = (value) => Object.prototype.toString.call(value) === "[object Symbol]";
|
|
243
|
+
const createSsrElement = (tag) => {
|
|
244
|
+
const lower = tag.toLowerCase();
|
|
245
|
+
const children = [];
|
|
246
|
+
const el = {
|
|
247
|
+
append: (...nodes) => {
|
|
248
|
+
children.push(...nodes);
|
|
249
|
+
},
|
|
250
|
+
appendChild: (node) => {
|
|
251
|
+
children.push(node);
|
|
252
|
+
},
|
|
253
|
+
attrs: /* @__PURE__ */ new Map(),
|
|
254
|
+
children,
|
|
255
|
+
dataset: new Proxy({}, {
|
|
256
|
+
get: (_t, prop) => {
|
|
257
|
+
if (prop === "undefined" || isSymbolKey(prop)) return;
|
|
258
|
+
return el.getAttribute(toDataAttr(prop)) ?? void 0;
|
|
259
|
+
},
|
|
260
|
+
set: (_t, prop, value) => {
|
|
261
|
+
if (isSymbolKey(prop)) return false;
|
|
262
|
+
const attr = toDataAttr(prop);
|
|
263
|
+
if (value === void 0 || value === null) el.removeAttribute(attr);
|
|
264
|
+
else el.setAttribute(attr, String(value));
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
}),
|
|
268
|
+
getAttribute: (name) => {
|
|
269
|
+
const value = el.attrs.get(name);
|
|
270
|
+
return value === void 0 ? null : value;
|
|
271
|
+
},
|
|
272
|
+
hasAttribute: (name) => el.attrs.has(name),
|
|
273
|
+
isConnected: true,
|
|
274
|
+
kind: "element",
|
|
275
|
+
localName: lower,
|
|
276
|
+
removeAttribute: (name) => {
|
|
277
|
+
el.attrs.delete(name);
|
|
278
|
+
},
|
|
279
|
+
replaceChildren: (...nodes) => {
|
|
280
|
+
children.length = 0;
|
|
281
|
+
children.push(...nodes);
|
|
282
|
+
},
|
|
283
|
+
setAttribute: (name, value) => {
|
|
284
|
+
el.attrs.set(name, value);
|
|
285
|
+
},
|
|
286
|
+
tag: lower,
|
|
287
|
+
tagName: lower.toUpperCase(),
|
|
288
|
+
get textContent() {
|
|
289
|
+
return children.map((n) => {
|
|
290
|
+
if (n.kind === "text") return n.text;
|
|
291
|
+
if (n.kind === "raw") return n.html;
|
|
292
|
+
return n.textContent;
|
|
293
|
+
}).join("");
|
|
294
|
+
},
|
|
295
|
+
set textContent(value) {
|
|
296
|
+
children.length = 0;
|
|
297
|
+
children.push(createSsrText(value));
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
return el;
|
|
301
|
+
};
|
|
302
|
+
const createSsrRoot = () => {
|
|
303
|
+
const children = [];
|
|
304
|
+
return {
|
|
305
|
+
append: (...nodes) => {
|
|
306
|
+
children.push(...nodes);
|
|
307
|
+
},
|
|
308
|
+
appendChild: (node) => {
|
|
309
|
+
children.push(node);
|
|
310
|
+
},
|
|
311
|
+
children,
|
|
312
|
+
get innerHTML() {
|
|
313
|
+
return children.map(serializeNode).join("");
|
|
314
|
+
},
|
|
315
|
+
kind: "root",
|
|
316
|
+
replaceChildren: () => {
|
|
317
|
+
children.length = 0;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/ssr-paint.ts
|
|
324
|
+
const ssrAs = (n) => n;
|
|
325
|
+
const ACTION_CALL = Symbol.for("ilha.actionCall");
|
|
326
|
+
const bindSsrEvents = (el, props, fiber) => {
|
|
327
|
+
const seen = /* @__PURE__ */ new Set();
|
|
328
|
+
for (const key of Object.keys(props)) {
|
|
329
|
+
const type = eventTypeFromProp(key);
|
|
330
|
+
if (!type || seen.has(type)) continue;
|
|
331
|
+
seen.add(type);
|
|
332
|
+
const fn = props[key];
|
|
333
|
+
if (!isFunction(fn)) continue;
|
|
334
|
+
const branded = fn[ACTION_CALL];
|
|
335
|
+
const rec = branded?.k ? {
|
|
336
|
+
a: branded.a ?? [],
|
|
337
|
+
k: branded.k
|
|
338
|
+
} : void 0;
|
|
339
|
+
if (!rec) continue;
|
|
340
|
+
const id = `${type}:${fiber.runtime.ssrEventI}`;
|
|
341
|
+
fiber.runtime.ssrEventI += 1;
|
|
342
|
+
fiber.runtime.ssrActions[id] = rec;
|
|
343
|
+
const existing = el.dataset.ilhaOn;
|
|
344
|
+
el.dataset.ilhaOn = existing ? `${existing},${id}` : id;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
const core = createPainter({
|
|
348
|
+
appendRoot: (root, node) => ssrAs(root).append(node),
|
|
349
|
+
asElement: (el) => ssrAs(el),
|
|
350
|
+
asNode: (host) => ssrAs(host),
|
|
351
|
+
asRoot: (el) => ssrAs(el),
|
|
352
|
+
bindEvents: bindSsrEvents,
|
|
353
|
+
committed: (el) => el,
|
|
354
|
+
createElement: createSsrElement,
|
|
355
|
+
createRaw: (html, _parent) => [createSsrRaw(html)],
|
|
356
|
+
createSlotHost: (slotId) => {
|
|
357
|
+
const host = createSsrElement("span");
|
|
358
|
+
if (slotId !== null) host.setAttribute(SLOT_ATTR, slotId);
|
|
359
|
+
host.setAttribute("style", "display:contents");
|
|
360
|
+
return host;
|
|
361
|
+
},
|
|
362
|
+
createText: createSsrText,
|
|
363
|
+
disconnect: (el) => {
|
|
364
|
+
el.isConnected = false;
|
|
365
|
+
},
|
|
366
|
+
placeChildren: (root, nodes) => {
|
|
367
|
+
const host = ssrAs(root);
|
|
368
|
+
host.replaceChildren();
|
|
369
|
+
for (const n of nodes) host.append(n);
|
|
370
|
+
},
|
|
371
|
+
reuseNode: (host) => ssrAs(host),
|
|
372
|
+
setFormControl: (el, key, v) => {
|
|
373
|
+
if (key === "value") el.setAttribute("value", String(v ?? ""));
|
|
374
|
+
else if (v) el.setAttribute(key, "");
|
|
375
|
+
else el.removeAttribute(key);
|
|
376
|
+
},
|
|
377
|
+
setStyle: (el, css) => el.setAttribute("style", css)
|
|
378
|
+
});
|
|
379
|
+
const paint = (fiber, view) => {
|
|
380
|
+
fiber.islandFrame ??= {
|
|
381
|
+
i: 0,
|
|
382
|
+
slots: []
|
|
383
|
+
};
|
|
384
|
+
core.pass(fiber, () => {
|
|
385
|
+
core.disposeHoles(fiber);
|
|
386
|
+
ssrAs(fiber.root).replaceChildren();
|
|
387
|
+
core.insert(fiber, core.materialize(view, fiber));
|
|
388
|
+
});
|
|
389
|
+
};
|
|
390
|
+
const attachSsr = (fn, opts) => {
|
|
391
|
+
const root = createSsrRoot();
|
|
392
|
+
const runtime = makeRuntime({
|
|
393
|
+
onError: opts?.onError,
|
|
394
|
+
ssr: true,
|
|
395
|
+
ssrCapture: opts?.ssrCapture === true
|
|
396
|
+
});
|
|
397
|
+
const { promise: ready, resolve } = defer();
|
|
398
|
+
const fiber = makeRootFiber({
|
|
399
|
+
paint,
|
|
400
|
+
root: ssrAs(root),
|
|
401
|
+
runtime,
|
|
402
|
+
settle: resolve
|
|
403
|
+
});
|
|
404
|
+
runtime.begin();
|
|
405
|
+
runtime.setIdle(resolve);
|
|
406
|
+
runSetup(fiber, fn);
|
|
407
|
+
runtime.end();
|
|
408
|
+
return {
|
|
409
|
+
ready,
|
|
410
|
+
root,
|
|
411
|
+
runtime,
|
|
412
|
+
unmount: () => {
|
|
413
|
+
closeFiber(fiber);
|
|
414
|
+
runtime.close();
|
|
415
|
+
root.replaceChildren();
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region src/mount.ts
|
|
422
|
+
const readHydrate = (el) => {
|
|
423
|
+
const raw = (el.matches("[data-ilha]") ? el : el.querySelector("[data-ilha]"))?.dataset.ilhaState;
|
|
424
|
+
if (raw) return decodeSnapshot(raw);
|
|
425
|
+
const first = el.firstElementChild;
|
|
426
|
+
if (first?.tagName === "TEMPLATE" && first.matches("[data-ilha-state]")) {
|
|
427
|
+
const tplRaw = first.dataset.ilhaState;
|
|
428
|
+
first.remove();
|
|
429
|
+
return tplRaw ? decodeSnapshot(tplRaw) : void 0;
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
const delay = (ms) => {
|
|
433
|
+
const { promise, resolve } = defer();
|
|
434
|
+
setTimeout(resolve, ms);
|
|
435
|
+
return promise;
|
|
436
|
+
};
|
|
437
|
+
const attach = (el, fn, opts) => {
|
|
438
|
+
if (!opts?.hydrate) el.innerHTML = "";
|
|
439
|
+
const runtime = makeRuntime({
|
|
440
|
+
hydrate: opts?.hydrate ? readHydrate(el) : void 0,
|
|
441
|
+
onError: opts?.onError
|
|
442
|
+
});
|
|
443
|
+
const { promise: ready, resolve } = defer();
|
|
444
|
+
const fiber = makeRootFiber({
|
|
445
|
+
paint: paint$1,
|
|
446
|
+
root: el,
|
|
447
|
+
runtime,
|
|
448
|
+
settle: resolve
|
|
449
|
+
});
|
|
450
|
+
if (opts?.hydrate) fiber.hydrate = true;
|
|
451
|
+
runtime.begin();
|
|
452
|
+
runtime.setIdle(resolve);
|
|
453
|
+
runSetup(fiber, fn);
|
|
454
|
+
runtime.end();
|
|
455
|
+
return {
|
|
456
|
+
ready,
|
|
457
|
+
runtime,
|
|
458
|
+
unmount: () => {
|
|
459
|
+
closeFiber(fiber);
|
|
460
|
+
runtime.close();
|
|
461
|
+
if (!opts?.hydrate) el.innerHTML = "";
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
const mount = (el, fn, opts) => attach(el, fn, opts).unmount;
|
|
466
|
+
const renderToString = async (fn, opts) => {
|
|
467
|
+
const snapshot = opts?.snapshot !== false;
|
|
468
|
+
const markers = opts?.markers !== false;
|
|
469
|
+
const { unmount, ready, runtime, root } = attachSsr(fn, { ssrCapture: opts?.captureActions === true });
|
|
470
|
+
const timeout = opts?.timeout;
|
|
471
|
+
await (timeout === void 0 ? ready : Promise.race([ready, delay(timeout)]));
|
|
472
|
+
let inner = root.innerHTML;
|
|
473
|
+
const actions = runtime.ssrActions;
|
|
474
|
+
if (Object.keys(actions).length > 0) inner = `<template data-ilha-actions="${escapeAttr(JSON.stringify(actions))}"></template>${inner}`;
|
|
475
|
+
const snap = snapshot ? encodeSnapshot(runtime.ssrValues) : void 0;
|
|
476
|
+
unmount();
|
|
477
|
+
if (!markers) return snap ? `<template data-ilha-state="${escapeAttr(snap)}"></template>${inner}` : inner;
|
|
478
|
+
return `<div data-ilha${snap ? ` data-ilha-state="${escapeAttr(snap)}"` : ""}>${inner}</div>`;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
//#endregion
|
|
482
|
+
export { renderToString as n, mount as t };
|