ilha 0.12.1 → 0.14.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/README.md +96 -302
- package/dist/define.d.ts +5 -0
- package/dist/define.js +18 -0
- package/dist/index.d.ts +35 -2
- package/dist/index.js +51 -2970
- package/dist/jsx-dev-runtime.d.ts +4 -2
- package/dist/jsx-dev-runtime.js +3 -2
- package/dist/jsx-runtime.d.ts +8 -2
- package/dist/jsx-runtime.js +7 -232
- package/dist/mount-Dcy4teaf.js +992 -0
- package/dist/types-DU46exHq.d.ts +33 -0
- package/dist/vnode-BZLcG8lC.d.ts +20 -0
- package/dist/vnode-DHN2EFHi.js +37 -0
- package/package.json +19 -15
- package/dist/index-BETRqz-0.d.ts +0 -377
- package/dist/internal.d.ts +0 -69
- package/dist/internal.js +0 -49
- package/dist/jsx-runtime-qnxoD2YJ.d.ts +0 -631
package/dist/index.js
CHANGED
|
@@ -1,2984 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as atom, c as isAtomHandle, d as makeFiber, i as interpret, l as closeFiber, n as renderToString, o as handleOwner, r as paint, s as instr, t as mount, u as getFiber } from "./mount-Dcy4teaf.js";
|
|
2
|
+
import { i as Fragment, t as h } from "./vnode-DHN2EFHi.js";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Stream from "effect/Stream";
|
|
5
|
+
import * as Scope from "effect/Scope";
|
|
6
|
+
import * as Deferred from "effect/Deferred";
|
|
3
7
|
|
|
4
|
-
//#region src/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
for (const item of value) if (exceedsMaxDepth(item, depth + 1)) return true;
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
for (const key in value) {
|
|
16
|
-
if (!Object.hasOwn(value, key)) continue;
|
|
17
|
-
if (exceedsMaxDepth(value[key], depth + 1)) return true;
|
|
18
|
-
}
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
const UNSAFE_SNAPSHOT_KEYS = /* @__PURE__ */ new Set([
|
|
22
|
-
"__proto__",
|
|
23
|
-
"constructor",
|
|
24
|
-
"prototype"
|
|
25
|
-
]);
|
|
26
|
-
function stripUnsafeKeys(value) {
|
|
27
|
-
if (value === null || typeof value !== "object") return;
|
|
28
|
-
if (Array.isArray(value)) {
|
|
29
|
-
for (const item of value) stripUnsafeKeys(item);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
for (const key of Object.getOwnPropertyNames(value)) if (UNSAFE_SNAPSHOT_KEYS.has(key)) delete value[key];
|
|
33
|
-
else stripUnsafeKeys(value[key]);
|
|
34
|
-
}
|
|
35
|
-
function safeParseSnapshot(raw, label) {
|
|
36
|
-
if (raw.length > MAX_SNAPSHOT_CHARS) {
|
|
37
|
-
`${label}`;
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
let parsed;
|
|
41
|
-
try {
|
|
42
|
-
parsed = JSON.parse(raw);
|
|
43
|
-
} catch {
|
|
44
|
-
`${label}`;
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (exceedsMaxDepth(parsed, 1)) {
|
|
48
|
-
`${label}`;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
52
|
-
`${label}`;
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
stripUnsafeKeys(parsed);
|
|
56
|
-
return parsed;
|
|
57
|
-
}
|
|
58
|
-
function shallowEqualInput(a, b) {
|
|
59
|
-
if (Object.is(a, b)) return true;
|
|
60
|
-
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
|
|
61
|
-
const ak = Object.keys(a);
|
|
62
|
-
const bk = Object.keys(b);
|
|
63
|
-
if (ak.length !== bk.length) return false;
|
|
64
|
-
for (const k of ak) {
|
|
65
|
-
if (!Object.hasOwn(b, k)) return false;
|
|
66
|
-
if (!Object.is(a[k], b[k])) return false;
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
function parseHtmlFragment(html) {
|
|
71
|
-
const tpl = document.createElement("template");
|
|
72
|
-
tpl.innerHTML = html;
|
|
73
|
-
return tpl.content;
|
|
74
|
-
}
|
|
75
|
-
function extractDirectChildSlotIdsInOrder(root) {
|
|
76
|
-
const ids = [];
|
|
77
|
-
for (const el of root.querySelectorAll("[data-ilha-slot]")) {
|
|
78
|
-
if (!(el instanceof Element)) continue;
|
|
79
|
-
let parent = el.parentElement ?? el.parentNode;
|
|
80
|
-
let nested = false;
|
|
81
|
-
while (parent && parent !== root) {
|
|
82
|
-
if (parent instanceof Element && parent.hasAttribute("data-ilha-slot")) {
|
|
83
|
-
nested = true;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
parent = parent instanceof Element ? parent.parentElement ?? parent.parentNode : parent.parentNode;
|
|
87
|
-
}
|
|
88
|
-
if (!nested) ids.push(el.getAttribute("data-ilha-slot"));
|
|
89
|
-
}
|
|
90
|
-
return ids;
|
|
91
|
-
}
|
|
92
|
-
function extractSlotPropsAttr(root, slotId) {
|
|
93
|
-
for (const el of root.querySelectorAll(`[${SLOT_ATTR}]`)) if (el.getAttribute(SLOT_ATTR) === slotId) return el.getAttribute(PROPS_ATTR) ?? "";
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
const SLOT_TAG_NAME_RE = /^[a-z][a-z0-9-]*$/i;
|
|
97
|
-
function assertValidSlotTagName(tag) {
|
|
98
|
-
const trimmed = tag.trim();
|
|
99
|
-
if (trimmed.length === 0) throw new Error("The { as } option requires a non-empty HTML tag name.");
|
|
100
|
-
if (!SLOT_TAG_NAME_RE.test(trimmed)) throw new Error(`The { as } option tag must be a valid HTML element name (got "${tag}"). Use names like "span", "div", or "li".`);
|
|
101
|
-
return trimmed.toLowerCase();
|
|
102
|
-
}
|
|
103
|
-
function getIslandSlotTag(island) {
|
|
104
|
-
const tag = island[ISLAND_SLOT_TAG];
|
|
105
|
-
if (typeof tag === "string" && tag.length > 0) return tag;
|
|
106
|
-
return "div";
|
|
107
|
-
}
|
|
108
|
-
function wrapIslandSlotHtml({ tag, id, propsAttr, inner }) {
|
|
109
|
-
return `<${tag} ${SLOT_ATTR}="${escapeHtml(id)}"${propsAttr}>${inner}</${tag}>`;
|
|
110
|
-
}
|
|
111
|
-
function isStableInlineSlotMount({ initialHtml, renderedHtml, slotIds }) {
|
|
112
|
-
if (typeof document === "undefined") return false;
|
|
113
|
-
const initialRoot = parseHtmlFragment(initialHtml);
|
|
114
|
-
const renderedRoot = parseHtmlFragment(renderedHtml);
|
|
115
|
-
const initialOrder = extractDirectChildSlotIdsInOrder(initialRoot);
|
|
116
|
-
const renderedOrder = extractDirectChildSlotIdsInOrder(renderedRoot);
|
|
117
|
-
if (initialOrder.length !== renderedOrder.length || initialOrder.some((id, i) => id !== renderedOrder[i])) return false;
|
|
118
|
-
for (const id of slotIds) {
|
|
119
|
-
const initialProps = extractSlotPropsAttr(initialRoot, id);
|
|
120
|
-
const renderedProps = extractSlotPropsAttr(renderedRoot, id);
|
|
121
|
-
if (initialProps === null || renderedProps === null) return false;
|
|
122
|
-
if (initialProps !== renderedProps) return false;
|
|
123
|
-
}
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Props safe to embed in `data-ilha-props`.
|
|
128
|
-
* Functions cannot round-trip through JSON; children are owned by the live slot
|
|
129
|
-
* map (client) and/or the inlined SSR subtree — never depend on the attr for them.
|
|
130
|
-
* RawHtml is tagged so a rare attr-only mount can revive `Symbol.for("ilha.raw")`.
|
|
131
|
-
*/
|
|
132
|
-
function slotPropsForAttr(props, captureActions = false) {
|
|
133
|
-
if (props === void 0) return void 0;
|
|
134
|
-
let out;
|
|
135
|
-
for (const key of Object.keys(props)) {
|
|
136
|
-
if (key === "children") continue;
|
|
137
|
-
const value = props[key];
|
|
138
|
-
if (typeof value === "function") {
|
|
139
|
-
const id = captureActions && !isRawHtmlValue(value) ? actionManifestId(value) : void 0;
|
|
140
|
-
if (id) {
|
|
141
|
-
const a = actionBoundArgs(value);
|
|
142
|
-
(out ??= {})[key] = Array.isArray(a) ? {
|
|
143
|
-
__ilha: "action",
|
|
144
|
-
k: id,
|
|
145
|
-
a
|
|
146
|
-
} : {
|
|
147
|
-
__ilha: "action",
|
|
148
|
-
k: id
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if (typeof value === "symbol") continue;
|
|
154
|
-
const encoded = encodeSlotPropValue(value);
|
|
155
|
-
if (encoded === void 0 && value !== void 0 && value !== null) continue;
|
|
156
|
-
(out ??= {})[key] = encoded;
|
|
157
|
-
}
|
|
158
|
-
return out;
|
|
159
|
-
}
|
|
160
|
-
const SLOT_RAW = Symbol.for("ilha.raw");
|
|
161
|
-
function isRawHtmlValue(v) {
|
|
162
|
-
return !!(v && typeof v === "object" && SLOT_RAW in v);
|
|
163
|
-
}
|
|
164
|
-
function encodeSlotPropValue(value, seen) {
|
|
165
|
-
if (value == null) return value;
|
|
166
|
-
if (typeof value === "function" || typeof value === "symbol") return void 0;
|
|
167
|
-
if (typeof value !== "object") return value;
|
|
168
|
-
if (isRawHtmlValue(value)) return {
|
|
169
|
-
__ilha: "raw",
|
|
170
|
-
value: value.value
|
|
171
|
-
};
|
|
172
|
-
const visited = seen ?? /* @__PURE__ */ new WeakSet();
|
|
173
|
-
if (visited.has(value)) throw new TypeError("encodeSlotPropValue: circular reference in slot props");
|
|
174
|
-
visited.add(value);
|
|
175
|
-
if (Array.isArray(value)) return value.map((item) => encodeSlotPropValue(item, visited)).filter((item) => item !== void 0);
|
|
176
|
-
if (Object.getPrototypeOf(value) !== Object.prototype) return void 0;
|
|
177
|
-
const obj = value;
|
|
178
|
-
const encoded = {};
|
|
179
|
-
for (const key of Object.keys(obj)) {
|
|
180
|
-
const next = encodeSlotPropValue(obj[key], visited);
|
|
181
|
-
if (next !== void 0 || obj[key] === null) encoded[key] = next;
|
|
182
|
-
}
|
|
183
|
-
return encoded;
|
|
184
|
-
}
|
|
185
|
-
function makeRawHtml(value) {
|
|
186
|
-
return {
|
|
187
|
-
[SLOT_RAW]: true,
|
|
188
|
-
value
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
function reviveSlotPropValue(value) {
|
|
192
|
-
if (value == null || typeof value !== "object") return value;
|
|
193
|
-
if (Array.isArray(value)) return value.map(reviveSlotPropValue);
|
|
194
|
-
const obj = value;
|
|
195
|
-
if (obj.__ilha === "raw" && typeof obj.value === "string") return makeRawHtml(obj.value);
|
|
196
|
-
const out = {};
|
|
197
|
-
for (const key of Object.keys(obj)) out[key] = reviveSlotPropValue(obj[key]);
|
|
198
|
-
return out;
|
|
199
|
-
}
|
|
200
|
-
/** Revive attr-parsed props, restoring tagged `__ilha:raw` RawHtml values. */
|
|
201
|
-
function reviveSlotProps(props) {
|
|
202
|
-
if (typeof props !== "object" || props === null || Array.isArray(props)) return {};
|
|
203
|
-
return reviveSlotPropValue(props);
|
|
204
|
-
}
|
|
205
|
-
/** Serialized form of slot props, matching the decoded `data-ilha-props` attr. */
|
|
206
|
-
function serializeSlotProps(props) {
|
|
207
|
-
const safe = slotPropsForAttr(props);
|
|
208
|
-
return safe === void 0 ? "" : JSON.stringify(safe);
|
|
209
|
-
}
|
|
210
|
-
const MORPH_CONTROLLER_SLOT_SELECTOR = "[data-slot]";
|
|
211
|
-
const MORPH_CONTROLLER_ATTRS = /* @__PURE__ */ new Set([
|
|
212
|
-
"data-checked",
|
|
213
|
-
"data-unchecked",
|
|
214
|
-
"data-indeterminate",
|
|
215
|
-
"aria-checked",
|
|
216
|
-
"data-open",
|
|
217
|
-
"data-closed",
|
|
218
|
-
"data-state",
|
|
219
|
-
"aria-expanded",
|
|
220
|
-
"aria-hidden",
|
|
221
|
-
"data-selected",
|
|
222
|
-
"data-panel-open"
|
|
223
|
-
]);
|
|
224
|
-
/**
|
|
225
|
-
* Opt-in contract for controller-owned attributes: an element carrying
|
|
226
|
-
* `data-morph-preserve="attr-a attr-b class"` keeps those attributes exactly
|
|
227
|
-
* as they are in the live DOM — the morph neither overwrites nor removes them.
|
|
228
|
-
* The Areia list above stays as a built-in default for `[data-slot]` elements.
|
|
229
|
-
*/
|
|
230
|
-
const MORPH_PRESERVE_ATTR = "data-morph-preserve";
|
|
231
|
-
function shouldPreserveMorphAttr(el, name) {
|
|
232
|
-
if (name === MORPH_PRESERVE_ATTR) return el.hasAttribute(MORPH_PRESERVE_ATTR);
|
|
233
|
-
const custom = el.getAttribute(MORPH_PRESERVE_ATTR);
|
|
234
|
-
if (custom !== null) {
|
|
235
|
-
for (const token of custom.split(/\s+/)) if (token === name) return true;
|
|
236
|
-
}
|
|
237
|
-
return el.matches(MORPH_CONTROLLER_SLOT_SELECTOR) && MORPH_CONTROLLER_ATTRS.has(name);
|
|
238
|
-
}
|
|
239
|
-
function syncAttributes(from, to) {
|
|
240
|
-
for (const { name, value } of to.attributes) {
|
|
241
|
-
if (shouldPreserveMorphAttr(from, name)) continue;
|
|
242
|
-
if (from.getAttribute(name) !== value) from.setAttribute(name, value);
|
|
243
|
-
}
|
|
244
|
-
for (const { name } of Array.from(from.attributes)) {
|
|
245
|
-
if (shouldPreserveMorphAttr(from, name)) continue;
|
|
246
|
-
if (!to.hasAttribute(name)) from.removeAttribute(name);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Morph identity key: explicit `data-key`, or the child-island slot id.
|
|
251
|
-
* Slot hosts are matched by id like keyed list items so a mounted child
|
|
252
|
-
* island's host element is reconciled IN PLACE on parent re-renders — never
|
|
253
|
-
* detached and reinserted, which would blur a focused element inside it.
|
|
254
|
-
*/
|
|
255
|
-
function morphKeyOf(el) {
|
|
256
|
-
const k = el.getAttribute("data-key");
|
|
257
|
-
if (k !== null) return `k:${k}`;
|
|
258
|
-
const s = el.getAttribute(SLOT_ATTR);
|
|
259
|
-
return s === null ? null : `s:${s}`;
|
|
260
|
-
}
|
|
261
|
-
function morphChildren(fromParent, toParent) {
|
|
262
|
-
const toNodes = Array.from(toParent.childNodes);
|
|
263
|
-
let fromKeyed = null;
|
|
264
|
-
for (const child of fromParent.children) {
|
|
265
|
-
const k = morphKeyOf(child);
|
|
266
|
-
if (k !== null && !(fromKeyed ??= /* @__PURE__ */ new Map()).has(k)) fromKeyed.set(k, child);
|
|
267
|
-
}
|
|
268
|
-
let toKeys = null;
|
|
269
|
-
if (fromKeyed !== null) {
|
|
270
|
-
toKeys = /* @__PURE__ */ new Set();
|
|
271
|
-
for (const child of toParent.children) {
|
|
272
|
-
const k = morphKeyOf(child);
|
|
273
|
-
if (k !== null) toKeys.add(k);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
for (let i = 0; i < toNodes.length; i++) {
|
|
277
|
-
const toNode = toNodes[i];
|
|
278
|
-
let fromNode = fromParent.childNodes[i];
|
|
279
|
-
if (fromKeyed !== null) {
|
|
280
|
-
const toKey = toNode.nodeType === 1 ? morphKeyOf(toNode) : null;
|
|
281
|
-
if (toKey !== null) {
|
|
282
|
-
const match = fromKeyed.get(toKey);
|
|
283
|
-
if (match) {
|
|
284
|
-
fromKeyed.delete(toKey);
|
|
285
|
-
if (match !== fromNode) {
|
|
286
|
-
fromParent.insertBefore(match, fromNode ?? null);
|
|
287
|
-
fromNode = match;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (fromNode instanceof Element) {
|
|
292
|
-
const fromKey = morphKeyOf(fromNode);
|
|
293
|
-
if (fromKey !== null && fromKey !== toKey && toKeys.has(fromKey)) {
|
|
294
|
-
fromParent.insertBefore(toNode.cloneNode(true), fromNode);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (!fromNode) {
|
|
300
|
-
fromParent.appendChild(toNode.cloneNode(true));
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
if (fromNode.nodeType !== toNode.nodeType) {
|
|
304
|
-
fromParent.replaceChild(toNode.cloneNode(true), fromNode);
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
if (fromNode.nodeType === 3 || fromNode.nodeType === 8) {
|
|
308
|
-
if (fromNode.nodeValue !== toNode.nodeValue) fromNode.nodeValue = toNode.nodeValue;
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
if (fromNode.nodeType === 1) {
|
|
312
|
-
const fromEl = fromNode;
|
|
313
|
-
const toEl = toNode;
|
|
314
|
-
if (fromEl.localName !== toEl.localName || fromEl.namespaceURI !== toEl.namespaceURI) {
|
|
315
|
-
fromParent.replaceChild(toEl.cloneNode(true), fromEl);
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
if (fromEl.hasAttribute(SLOT_ATTR) && !fromEl.isConnected) {
|
|
319
|
-
fromParent.replaceChild(toEl.cloneNode(true), fromEl);
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
{
|
|
323
|
-
const slotId = toEl.getAttribute(SLOT_ATTR);
|
|
324
|
-
if (slotId !== null && fromEl.getAttribute(SLOT_ATTR) === slotId) {
|
|
325
|
-
const props = toEl.getAttribute(PROPS_ATTR);
|
|
326
|
-
if (props !== null && fromEl.getAttribute(PROPS_ATTR) !== props) fromEl.setAttribute(PROPS_ATTR, props);
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
if (fromEl.localName === "input" && fromEl.type !== toEl.type) {
|
|
331
|
-
fromParent.replaceChild(toEl.cloneNode(true), fromEl);
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
if (fromEl.localName === "input") {
|
|
335
|
-
const hadChecked = fromEl.hasAttribute("checked");
|
|
336
|
-
const hadValue = fromEl.getAttribute("value");
|
|
337
|
-
syncAttributes(fromEl, toEl);
|
|
338
|
-
const hasChecked = toEl.hasAttribute("checked");
|
|
339
|
-
if (hasChecked !== hadChecked) fromEl.checked = hasChecked;
|
|
340
|
-
const newValue = toEl.getAttribute("value");
|
|
341
|
-
if (newValue !== hadValue) fromEl.value = newValue ?? "";
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
if (fromEl.localName === "select") {
|
|
345
|
-
const before = /* @__PURE__ */ new Map();
|
|
346
|
-
for (const o of fromEl.options) before.set(o, {
|
|
347
|
-
attr: o.hasAttribute("selected"),
|
|
348
|
-
live: o.selected
|
|
349
|
-
});
|
|
350
|
-
syncAttributes(fromEl, toEl);
|
|
351
|
-
morphChildren(fromEl, toEl);
|
|
352
|
-
const options = Array.from(fromEl.options);
|
|
353
|
-
if (options.some((o) => o.hasAttribute("selected") !== (before.get(o)?.attr ?? false))) for (const o of options) o.selected = o.hasAttribute("selected");
|
|
354
|
-
else for (const o of options) {
|
|
355
|
-
const prev = before.get(o);
|
|
356
|
-
if (prev && o.selected !== prev.live) o.selected = prev.live;
|
|
357
|
-
}
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
syncAttributes(fromEl, toEl);
|
|
361
|
-
if (fromEl.localName === "textarea") {
|
|
362
|
-
const newText = toEl.textContent ?? "";
|
|
363
|
-
if (fromEl.textContent !== newText) {
|
|
364
|
-
fromEl.textContent = newText;
|
|
365
|
-
fromEl.value = newText;
|
|
366
|
-
}
|
|
367
|
-
} else morphChildren(fromEl, toEl);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
while (fromParent.childNodes.length > toNodes.length) fromParent.lastChild.remove();
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Invariant: the morph must never lose focus for an element that survives it.
|
|
374
|
-
* Surviving slot hosts and keyed elements are patched in place, but a genuine
|
|
375
|
-
* reorder still detaches-and-reinserts an ancestor of `document.activeElement`,
|
|
376
|
-
* which blurs it in real engines — snapshot before, restore after.
|
|
377
|
-
*/
|
|
378
|
-
function snapshotFocusForMorph() {
|
|
379
|
-
if (typeof document === "undefined") return null;
|
|
380
|
-
const active = document.activeElement;
|
|
381
|
-
if (!(active instanceof HTMLElement) || active === document.body) return null;
|
|
382
|
-
let selection = null;
|
|
383
|
-
let range = null;
|
|
384
|
-
try {
|
|
385
|
-
if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) selection = {
|
|
386
|
-
start: active.selectionStart,
|
|
387
|
-
end: active.selectionEnd,
|
|
388
|
-
dir: active.selectionDirection
|
|
389
|
-
};
|
|
390
|
-
else if (active.isContentEditable) {
|
|
391
|
-
const sel = window.getSelection();
|
|
392
|
-
if (sel && sel.rangeCount > 0) range = sel.getRangeAt(0).cloneRange();
|
|
393
|
-
}
|
|
394
|
-
} catch {}
|
|
395
|
-
return {
|
|
396
|
-
active,
|
|
397
|
-
selection,
|
|
398
|
-
range
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
function restoreFocusAfterMorph(snapshot) {
|
|
402
|
-
if (!snapshot) return;
|
|
403
|
-
const { active, selection, range } = snapshot;
|
|
404
|
-
if (!active.isConnected) return;
|
|
405
|
-
try {
|
|
406
|
-
if (document.activeElement !== active) active.focus({ preventScroll: true });
|
|
407
|
-
if (selection && selection.start !== null) {
|
|
408
|
-
const el = active;
|
|
409
|
-
if (el.selectionStart !== selection.start || el.selectionEnd !== selection.end) el.setSelectionRange(selection.start, selection.end, selection.dir ?? "none");
|
|
410
|
-
}
|
|
411
|
-
if (range && range.startContainer.isConnected) {
|
|
412
|
-
const sel = window.getSelection();
|
|
413
|
-
if (sel) {
|
|
414
|
-
const current = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
|
|
415
|
-
if (!(current !== null && current.startContainer === range.startContainer && current.startOffset === range.startOffset && current.endContainer === range.endContainer && current.endOffset === range.endOffset)) {
|
|
416
|
-
sel.removeAllRanges();
|
|
417
|
-
sel.addRange(range);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
} catch {}
|
|
422
|
-
}
|
|
423
|
-
function morphInner(from, to) {
|
|
424
|
-
if (from.localName !== to.localName || from.namespaceURI !== to.namespaceURI) throw new Error("[ilha] morph: elements must match");
|
|
425
|
-
const focus = snapshotFocusForMorph();
|
|
426
|
-
morphChildren(from, to);
|
|
427
|
-
restoreFocusAfterMorph(focus);
|
|
428
|
-
}
|
|
429
|
-
function isStandardSchema(value) {
|
|
430
|
-
if (value == null || typeof value !== "object") return false;
|
|
431
|
-
const std = value["~standard"];
|
|
432
|
-
return std != null && typeof std.validate === "function" && std.version === 1;
|
|
433
|
-
}
|
|
434
|
-
function validateSchema(schema, value) {
|
|
435
|
-
const result = schema["~standard"].validate(value);
|
|
436
|
-
if (result instanceof Promise) throw new Error("[ilha] Async schemas are not supported.");
|
|
437
|
-
if (result.issues) throw new Error(`[ilha] Validation failed:\n${result.issues.map((i) => ` - ${i.message}`).join("\n")}`);
|
|
438
|
-
return result.value;
|
|
439
|
-
}
|
|
440
|
-
const ESC = {
|
|
441
|
-
"&": "&",
|
|
442
|
-
"<": "<",
|
|
443
|
-
">": ">",
|
|
444
|
-
"\"": """,
|
|
445
|
-
"'": "'"
|
|
446
|
-
};
|
|
447
|
-
function escapeHtml(value) {
|
|
448
|
-
return String(value).replace(/[&<>"']/g, (c) => ESC[c]);
|
|
449
|
-
}
|
|
450
|
-
const SAFE_CSS_PROP_RE = /^(-{2}[a-zA-Z][a-zA-Z0-9-]*|-?[a-zA-Z][a-zA-Z0-9-]*)$/;
|
|
451
|
-
const URL_ATTRS = /* @__PURE__ */ new Set([
|
|
452
|
-
"href",
|
|
453
|
-
"src",
|
|
454
|
-
"srcset",
|
|
455
|
-
"imagesrcset",
|
|
456
|
-
"action",
|
|
457
|
-
"formaction",
|
|
458
|
-
"cite",
|
|
459
|
-
"data",
|
|
460
|
-
"poster"
|
|
461
|
-
]);
|
|
462
|
-
const SAFE_DATA_IMAGE_RE = /^data:image\/(png|jpe?g|gif|webp|avif)[;,]/i;
|
|
463
|
-
const UNSAFE_SCHEME_RE = /^(?:javascript|vbscript):/i;
|
|
464
|
-
const DATA_IMAGE_CONTEXTS = /* @__PURE__ */ new Set([
|
|
465
|
-
"img:src",
|
|
466
|
-
"source:srcset",
|
|
467
|
-
"link:imagesrcset"
|
|
468
|
-
]);
|
|
469
|
-
function normalizeUrl(value) {
|
|
470
|
-
return value.replace(/[\u0000-\u0020]/g, "");
|
|
471
|
-
}
|
|
472
|
-
function checkUrl(normalized, allowDataImage) {
|
|
473
|
-
if (UNSAFE_SCHEME_RE.test(normalized)) return false;
|
|
474
|
-
if (/^data:/i.test(normalized)) return allowDataImage && SAFE_DATA_IMAGE_RE.test(normalized);
|
|
475
|
-
return true;
|
|
476
|
-
}
|
|
477
|
-
/** Strict single-URL check — all data: URLs rejected. */
|
|
478
|
-
function isSafeUrl(value) {
|
|
479
|
-
return checkUrl(normalizeUrl(value), false);
|
|
480
|
-
}
|
|
481
|
-
/** Element- and attribute-aware URL policy for rendered attributes.
|
|
482
|
-
* `srcset`/`imagesrcset` validate each comma-separated candidate URL. */
|
|
483
|
-
function isSafeUrlAttrValue(tagName, attrName, value) {
|
|
484
|
-
const attr = attrName.toLowerCase();
|
|
485
|
-
const allowDataImage = DATA_IMAGE_CONTEXTS.has(`${tagName.toLowerCase()}:${attr}`) && !attr.startsWith("srcset") && attr !== "imagesrcset";
|
|
486
|
-
if (/^(?:srcset|imagesrcset)$/i.test(attr)) return value.split(",").every((candidate) => {
|
|
487
|
-
const url = candidate.trim().split(/\s+/)[0] ?? "";
|
|
488
|
-
return url === "" || checkUrl(normalizeUrl(url), false);
|
|
489
|
-
});
|
|
490
|
-
return checkUrl(normalizeUrl(value), allowDataImage);
|
|
491
|
-
}
|
|
492
|
-
function isUrlAttributeName(name) {
|
|
493
|
-
const lower = name.toLowerCase();
|
|
494
|
-
return URL_ATTRS.has(lower) || /:(href|src|srcset|imagesrcset|action|formaction|cite|data|poster)$/.test(lower);
|
|
495
|
-
}
|
|
496
|
-
function isPlainObject(v) {
|
|
497
|
-
return v !== null && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype;
|
|
498
|
-
}
|
|
499
|
-
function serializeStyle(value) {
|
|
500
|
-
return Object.entries(value).map(([k, v]) => {
|
|
501
|
-
if (!SAFE_CSS_PROP_RE.test(k)) return "";
|
|
502
|
-
const prop = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
503
|
-
const str = String(v);
|
|
504
|
-
if (/[<>{};]/.test(str) || /expression\(/i.test(str) || /javascript:/i.test(str)) return "";
|
|
505
|
-
return `${prop}:${str}`;
|
|
506
|
-
}).filter(Boolean).join(";");
|
|
507
|
-
}
|
|
508
|
-
function serializeScriptJson(value) {
|
|
509
|
-
return JSON.stringify(value).replace(/</g, "\\u003C");
|
|
510
|
-
}
|
|
511
|
-
function escapeStyleText(text) {
|
|
512
|
-
return text.replace(/</g, "\\3C ");
|
|
513
|
-
}
|
|
514
|
-
function dedentString(str) {
|
|
515
|
-
if (str.length === 0 || str[0] !== "\n") return str;
|
|
516
|
-
const lines = str.split("\n");
|
|
517
|
-
while (lines.length && lines[0].trim() === "") lines.shift();
|
|
518
|
-
while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
|
|
519
|
-
if (!lines.length) return "";
|
|
520
|
-
const indent = Math.min(...lines.filter((l) => l.trim() !== "").map((l) => l.match(/^(\s*)/)[1].length));
|
|
521
|
-
return lines.map((l) => l.slice(indent)).join("\n");
|
|
522
|
-
}
|
|
523
|
-
const RAW = Symbol.for("ilha.raw");
|
|
524
|
-
const SIGNAL_ACCESSOR = Symbol.for("ilha.signalAccessor");
|
|
525
|
-
const ISLAND = Symbol.for("ilha.island");
|
|
526
|
-
const ISLAND_CALL = Symbol.for("ilha.islandCall");
|
|
527
|
-
const ASTRO_RENDERER_GLOBAL = Symbol.for("ilha.astroRenderer");
|
|
528
|
-
const ISLAND_SLOT_TAG = Symbol.for("ilha.islandSlotTag");
|
|
529
|
-
const ISLAND_CLIENT_REF = Symbol.for("ilha.clientRef");
|
|
530
|
-
/** @internal Sync re-render of an island from a state snapshot. Attached by
|
|
531
|
-
* render(); used by server-frame endpoints to produce HTML for streamed
|
|
532
|
-
* updates without a live mount. Not part of the public surface. */
|
|
533
|
-
const ISLAND_RENDER_STATE = Symbol.for("ilha.renderState");
|
|
534
|
-
/** @internal Async SSR render that records the event→action manifest into the
|
|
535
|
-
* provided map. Used by emitIslandSlot so nested server islands ship their
|
|
536
|
-
* interactive surface alongside their inlined HTML. */
|
|
537
|
-
const ISLAND_SSR_MANIFEST = Symbol.for("ilha.ssrManifest");
|
|
538
|
-
const SLOT_ATTR = "data-ilha-slot";
|
|
539
|
-
const PROPS_ATTR = "data-ilha-props";
|
|
540
|
-
const CLIENT_REF_ATTR = "data-ilha-client-ref";
|
|
541
|
-
const STATE_ATTR = "data-ilha-state";
|
|
542
|
-
function normalizeElseOutput(value) {
|
|
543
|
-
if (value == null || value === false) return value;
|
|
544
|
-
return Array.isArray(value) ? value : [value];
|
|
545
|
-
}
|
|
546
|
-
function createEachResult(items, mapFn) {
|
|
547
|
-
const mapped = items.length === 0 ? [] : items.map(mapFn);
|
|
548
|
-
const result = mapped;
|
|
549
|
-
Object.defineProperty(result, "else", {
|
|
550
|
-
value(fallback) {
|
|
551
|
-
if (items.length === 0) return normalizeElseOutput(typeof fallback === "function" ? fallback(items) : fallback);
|
|
552
|
-
return mapped;
|
|
553
|
-
},
|
|
554
|
-
enumerable: false
|
|
555
|
-
});
|
|
556
|
-
return result;
|
|
557
|
-
}
|
|
558
|
-
function createEachBuilder(items) {
|
|
559
|
-
return {
|
|
560
|
-
key(keyFn) {
|
|
561
|
-
return { as(mapFn) {
|
|
562
|
-
return createEachResult(items, (item, index) => mapFn(item, index, keyFn(item, index)));
|
|
563
|
-
} };
|
|
564
|
-
},
|
|
565
|
-
as(mapFn) {
|
|
566
|
-
return createEachResult(items, mapFn);
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Svelte-style `{#each}` helper for mapping collections to rendered output
|
|
572
|
-
* with an optional empty fallback.
|
|
573
|
-
*
|
|
574
|
-
* @example — plain list (empty → [])
|
|
575
|
-
* each(items).as((item) => html`<li>${item.name}</li>`)
|
|
576
|
-
*
|
|
577
|
-
* @example — keyed list feeding island.key(), with empty fallback
|
|
578
|
-
* each(items)
|
|
579
|
-
* .key((item) => item.id)
|
|
580
|
-
* .as((item, _i, id) => Row.key(id)({ item }))
|
|
581
|
-
* .else(() => html`<EmptyState />`)
|
|
582
|
-
*/
|
|
583
|
-
function each(items) {
|
|
584
|
-
if (typeof items === "function") throw new TypeError("[ilha] each() expected an array but received a function. Call accessors first (each(state.items())) or pass a snapshot from a reactive render.");
|
|
585
|
-
return createEachBuilder(Array.isArray(items) ? items : []);
|
|
586
|
-
}
|
|
587
|
-
/** Marks SSR action stubs so event registration can recognize action
|
|
588
|
-
* references without ever invoking them (executing a handler body during
|
|
589
|
-
* render would run its side effects server-side). Symbol.for brands keep
|
|
590
|
-
* this recognizable across duplicate ilha copies and external action
|
|
591
|
-
* implementations (e.g. oxidejs) in the same realm. */
|
|
592
|
-
const SSR_ACTION_STUB = Symbol.for("ilha.ssrActionStub");
|
|
593
|
-
const ACTION_MANIFEST_ID = Symbol.for("ilha.actionManifestId");
|
|
594
|
-
const ACTION_BOUND_ARGS = Symbol.for("ilha.actionBoundArgs");
|
|
595
|
-
/** Serialized size ceiling for `.with()` payloads (JSON chars). */
|
|
596
|
-
const MAX_BOUND_ARGS_CHARS = 8192;
|
|
597
|
-
/** Manifest id for a brand-recognized action reference, else undefined.
|
|
598
|
-
* Anything that isn't a branded action stub is rejected outright — event
|
|
599
|
-
* handlers are never executed during SSR to discover what they call. */
|
|
600
|
-
function actionManifestId(fn) {
|
|
601
|
-
if (typeof fn !== "function" || !(SSR_ACTION_STUB in fn)) return void 0;
|
|
602
|
-
return fn[ACTION_MANIFEST_ID];
|
|
603
|
-
}
|
|
604
|
-
/** Bound args recorded by `.with()` on an action reference, else undefined. */
|
|
605
|
-
function actionBoundArgs(fn) {
|
|
606
|
-
if (typeof fn !== "function" || !(ACTION_BOUND_ARGS in fn)) return void 0;
|
|
607
|
-
const args = fn[ACTION_BOUND_ARGS];
|
|
608
|
-
return Array.isArray(args) ? args : void 0;
|
|
609
|
-
}
|
|
610
|
-
/** Attach `.with(...args)` to an action accessor. Returns an explicit,
|
|
611
|
-
* serializable action REFERENCE — never a closure to inspect or probe.
|
|
612
|
-
* Arguments are validated JSON-safe at bind time; the hydration manifest
|
|
613
|
-
* stores `{ actionId, args }` directly and the client RPC path sends them
|
|
614
|
-
* only after a real user event. On the client, calling the bound reference
|
|
615
|
-
* invokes the accessor with the bound payload. */
|
|
616
|
-
/** Keys that must never appear in a bound payload — they become dangerous
|
|
617
|
-
* if an RPC handler or downstream library merges the object into another. */
|
|
618
|
-
const UNSAFE_KEYS = /* @__PURE__ */ new Set([
|
|
619
|
-
"__proto__",
|
|
620
|
-
"constructor",
|
|
621
|
-
"prototype"
|
|
622
|
-
]);
|
|
623
|
-
/** Strict JSON-safety validator: accept, or throw — never transform.
|
|
624
|
-
* Rejects undefined, functions, symbols, bigint, non-finite numbers,
|
|
625
|
-
* Dates/custom prototypes/Map/Set, circular values, and unsafe keys so a
|
|
626
|
-
* bound payload is byte-identical to what the author wrote. */
|
|
627
|
-
function assertJsonSafe(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
628
|
-
if (value === null) return;
|
|
629
|
-
switch (typeof value) {
|
|
630
|
-
case "string":
|
|
631
|
-
case "boolean": return;
|
|
632
|
-
case "number":
|
|
633
|
-
if (Number.isFinite(value)) return;
|
|
634
|
-
throw new TypeError("with() rejects non-finite numbers.");
|
|
635
|
-
case "object": break;
|
|
636
|
-
default: throw new TypeError(`with() rejects ${typeof value} values.`);
|
|
637
|
-
}
|
|
638
|
-
if (seen.has(value)) throw new TypeError("with() rejects circular values.");
|
|
639
|
-
seen.add(value);
|
|
640
|
-
if (Array.isArray(value)) {
|
|
641
|
-
for (const item of value) assertJsonSafe(item, seen);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
if (Object.getPrototypeOf(value) !== Object.prototype) throw new TypeError("with() accepts plain objects only.");
|
|
645
|
-
for (const [key, child] of Object.entries(value)) {
|
|
646
|
-
if (UNSAFE_KEYS.has(key)) throw new TypeError(`with() rejects unsafe key "${key}".`);
|
|
647
|
-
assertJsonSafe(child, seen);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
/** Deep-clone an already-validated JSON-safe value. */
|
|
651
|
-
function cloneJsonSafe(value) {
|
|
652
|
-
return structuredClone(value);
|
|
653
|
-
}
|
|
654
|
-
function attachWith(accessor) {
|
|
655
|
-
Object.defineProperty(accessor, "with", {
|
|
656
|
-
enumerable: false,
|
|
657
|
-
value: (...args) => {
|
|
658
|
-
for (const arg of args) assertJsonSafe(arg);
|
|
659
|
-
const serialized = cloneJsonSafe(args);
|
|
660
|
-
if (JSON.stringify(serialized).length > MAX_BOUND_ARGS_CHARS) throw new RangeError(`with() payload exceeds ${MAX_BOUND_ARGS_CHARS} JSON characters.`);
|
|
661
|
-
const manifestId = actionManifestId(accessor);
|
|
662
|
-
const bound = (...runtimeArgs) => {
|
|
663
|
-
return accessor(serialized.length > 0 ? serialized.length === 1 ? serialized[0] : serialized : runtimeArgs.length === 1 ? runtimeArgs[0] : runtimeArgs);
|
|
664
|
-
};
|
|
665
|
-
const brand = bound;
|
|
666
|
-
brand[SSR_ACTION_STUB] = true;
|
|
667
|
-
brand[ACTION_MANIFEST_ID] = manifestId;
|
|
668
|
-
brand[ACTION_BOUND_ARGS] = serialized;
|
|
669
|
-
return bound;
|
|
670
|
-
}
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
setServerActionBinder(function bindExternalAction(fn, manifestId) {
|
|
674
|
-
const action = fn;
|
|
675
|
-
action[SSR_ACTION_STUB] = true;
|
|
676
|
-
action[ACTION_MANIFEST_ID] = manifestId;
|
|
677
|
-
attachWith(action);
|
|
678
|
-
return action;
|
|
679
|
-
});
|
|
680
|
-
const RENDER_CTX_STACK = Symbol.for("ilha.renderCtxStack");
|
|
681
|
-
function renderCtxStack() {
|
|
682
|
-
const g = globalThis;
|
|
683
|
-
return g[RENDER_CTX_STACK] ??= [];
|
|
684
|
-
}
|
|
685
|
-
function pushRenderCtx(liveHost, asyncChildren, manifest) {
|
|
686
|
-
const ctx = {
|
|
687
|
-
slots: /* @__PURE__ */ new Map(),
|
|
688
|
-
positional: 0,
|
|
689
|
-
liveHost,
|
|
690
|
-
pending: asyncChildren ? /* @__PURE__ */ new Map() : void 0,
|
|
691
|
-
binds: [],
|
|
692
|
-
events: [],
|
|
693
|
-
manifest
|
|
694
|
-
};
|
|
695
|
-
renderCtxStack().push(ctx);
|
|
696
|
-
return ctx;
|
|
697
|
-
}
|
|
698
|
-
function popRenderCtx() {
|
|
699
|
-
renderCtxStack().pop();
|
|
700
|
-
}
|
|
701
|
-
function currentRenderCtx() {
|
|
702
|
-
const stack = renderCtxStack();
|
|
703
|
-
return stack[stack.length - 1];
|
|
704
|
-
}
|
|
705
|
-
function isIsland(v) {
|
|
706
|
-
return typeof v === "function" && ISLAND in v;
|
|
707
|
-
}
|
|
708
|
-
function isIslandCall(v) {
|
|
709
|
-
if (v == null || typeof v !== "object" && typeof v !== "function") return false;
|
|
710
|
-
if (ISLAND_CALL in v) return true;
|
|
711
|
-
return typeof v === "object" && "island" in v && isIsland(v.island);
|
|
712
|
-
}
|
|
713
|
-
function emitIslandSlot({ island, props, key }) {
|
|
714
|
-
const ctx = currentRenderCtx();
|
|
715
|
-
let id;
|
|
716
|
-
if (key === void 0) id = ctx ? `p:${ctx.positional++}` : "p:0";
|
|
717
|
-
else id = `k:${key}`;
|
|
718
|
-
if (ctx) ctx.slots.set(id, {
|
|
719
|
-
island,
|
|
720
|
-
props
|
|
721
|
-
});
|
|
722
|
-
const slotTag = getIslandSlotTag(island);
|
|
723
|
-
const clientRef = island[ISLAND_CLIENT_REF];
|
|
724
|
-
const attrProps = slotPropsForAttr(props, typeof clientRef === "string" && ctx?.manifest === true);
|
|
725
|
-
const propsAttr = (attrProps ? ` ${PROPS_ATTR}='${escapeHtml(JSON.stringify(attrProps))}'` : "") + (typeof clientRef === "string" ? ` ${CLIENT_REF_ATTR}="${escapeHtml(clientRef)}"` : "");
|
|
726
|
-
if (ctx?.liveHost) return wrapIslandSlotHtml({
|
|
727
|
-
tag: slotTag,
|
|
728
|
-
id,
|
|
729
|
-
propsAttr,
|
|
730
|
-
inner: ""
|
|
731
|
-
});
|
|
732
|
-
if (ctx?.pending) {
|
|
733
|
-
popRenderCtx();
|
|
734
|
-
try {
|
|
735
|
-
const manifest = /* @__PURE__ */ new Map();
|
|
736
|
-
const ssrWithManifest = island[ISLAND_SSR_MANIFEST];
|
|
737
|
-
const result = ssrWithManifest ? ssrWithManifest(props, manifest) : island.toStringAsync(props);
|
|
738
|
-
const prefix = () => serializeServerManifest(manifest);
|
|
739
|
-
if (result instanceof Promise) {
|
|
740
|
-
ctx.pending.set(id, result.then(String).then((html) => prefix() + html));
|
|
741
|
-
return wrapIslandSlotHtml({
|
|
742
|
-
tag: slotTag,
|
|
743
|
-
id,
|
|
744
|
-
propsAttr,
|
|
745
|
-
inner: asyncSlotMarker(id)
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
return wrapIslandSlotHtml({
|
|
749
|
-
tag: slotTag,
|
|
750
|
-
id,
|
|
751
|
-
propsAttr,
|
|
752
|
-
inner: prefix() + String(result)
|
|
753
|
-
});
|
|
754
|
-
} finally {
|
|
755
|
-
renderCtxStack().push(ctx);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
const inner = island.toString(props);
|
|
759
|
-
return wrapIslandSlotHtml({
|
|
760
|
-
tag: slotTag,
|
|
761
|
-
id,
|
|
762
|
-
propsAttr,
|
|
763
|
-
inner
|
|
764
|
-
});
|
|
765
|
-
}
|
|
766
|
-
function asyncSlotMarker(id) {
|
|
767
|
-
return `<!--ilha-async:${escapeHtml(id)}-->`;
|
|
768
|
-
}
|
|
769
|
-
async function resolveAsyncChildren(html, pending) {
|
|
770
|
-
for (const [id, promise] of pending) {
|
|
771
|
-
const inner = await promise;
|
|
772
|
-
html = html.split(asyncSlotMarker(id)).join(inner);
|
|
773
|
-
}
|
|
774
|
-
return html;
|
|
775
|
-
}
|
|
776
|
-
function resolveSignalSetter(read, value) {
|
|
777
|
-
if (typeof value !== "function") return value;
|
|
778
|
-
const prevSub = setActiveSub(void 0);
|
|
779
|
-
try {
|
|
780
|
-
return value(read());
|
|
781
|
-
} finally {
|
|
782
|
-
setActiveSub(prevSub);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
function markSignalAccessor(fn) {
|
|
786
|
-
fn[SIGNAL_ACCESSOR] = true;
|
|
787
|
-
const accessor = fn;
|
|
788
|
-
accessor.select = ((...args) => createSelectAccessor(accessor, args));
|
|
789
|
-
return accessor;
|
|
790
|
-
}
|
|
791
|
-
function isSignalAccessor(v) {
|
|
792
|
-
return typeof v === "function" && SIGNAL_ACCESSOR in v;
|
|
793
|
-
}
|
|
794
|
-
function getAtPath(obj, path) {
|
|
795
|
-
let cur = obj;
|
|
796
|
-
for (const seg of path) {
|
|
797
|
-
if (cur == null) return void 0;
|
|
798
|
-
cur = cur[seg];
|
|
799
|
-
}
|
|
800
|
-
return cur;
|
|
801
|
-
}
|
|
802
|
-
function setAtPath({ object, path, value }) {
|
|
803
|
-
const obj = object;
|
|
804
|
-
if (path.length === 0) return value;
|
|
805
|
-
const [head, ...rest] = path;
|
|
806
|
-
if (Array.isArray(obj)) {
|
|
807
|
-
const idx = head;
|
|
808
|
-
if (idx < 0 || idx >= obj.length) return obj;
|
|
809
|
-
const next = obj[idx];
|
|
810
|
-
const updated = rest.length === 0 ? value : setAtPath({
|
|
811
|
-
object: next,
|
|
812
|
-
path: rest,
|
|
813
|
-
value
|
|
814
|
-
});
|
|
815
|
-
if (Object.is(next, updated)) return obj;
|
|
816
|
-
const copy = obj.slice();
|
|
817
|
-
copy[idx] = updated;
|
|
818
|
-
return copy;
|
|
819
|
-
}
|
|
820
|
-
if (obj !== null && typeof obj === "object") {
|
|
821
|
-
const record = obj;
|
|
822
|
-
const key = String(head);
|
|
823
|
-
const next = record[key];
|
|
824
|
-
const updated = rest.length === 0 ? value : setAtPath({
|
|
825
|
-
object: next,
|
|
826
|
-
path: rest,
|
|
827
|
-
value
|
|
828
|
-
});
|
|
829
|
-
if (rest.length === 0) {
|
|
830
|
-
if (Object.is(next, value)) return obj;
|
|
831
|
-
} else if (Object.is(next, updated)) return obj;
|
|
832
|
-
return {
|
|
833
|
-
...record,
|
|
834
|
-
[key]: updated
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
return rest.length === 0 ? value : setAtPath({
|
|
838
|
-
object: void 0,
|
|
839
|
-
path: rest,
|
|
840
|
-
value
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
function toPathSegment(prop) {
|
|
844
|
-
if (typeof prop === "symbol") return null;
|
|
845
|
-
if (prop === "length") return null;
|
|
846
|
-
if (/^\d+$/.test(prop)) return Number(prop);
|
|
847
|
-
return prop;
|
|
848
|
-
}
|
|
849
|
-
function trackSelectPath(rootState, selector) {
|
|
850
|
-
const path = [];
|
|
851
|
-
const track = (value) => {
|
|
852
|
-
if (value === null || typeof value !== "object" && typeof value !== "function") return value;
|
|
853
|
-
return new Proxy(value, { get(target, prop, receiver) {
|
|
854
|
-
const seg = toPathSegment(prop);
|
|
855
|
-
if (seg != null) path.push(seg);
|
|
856
|
-
const next = Reflect.get(target, prop, receiver);
|
|
857
|
-
return seg != null && next !== null && typeof next === "object" ? track(next) : next;
|
|
8
|
+
//#region src/when.ts
|
|
9
|
+
function when(stream, body) {
|
|
10
|
+
return instr(Effect.gen(function* () {
|
|
11
|
+
const parent = getFiber();
|
|
12
|
+
let child;
|
|
13
|
+
parent.holes.push({ dispose() {
|
|
14
|
+
if (child) closeFiber(child);
|
|
15
|
+
child = void 0;
|
|
858
16
|
} });
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
if (selector) {
|
|
867
|
-
path = trackSelectPath(root(), selector);
|
|
868
|
-
const selected = selector(root());
|
|
869
|
-
const resolved = path.length === 0 ? root() : getAtPath(root(), path);
|
|
870
|
-
if (!Object.is(selected, resolved)) throw new Error("select(): selector must only traverse nested properties or array indexes — derived or transformed values are not supported.");
|
|
871
|
-
} else path = input;
|
|
872
|
-
const read = (from) => selector ? path.length === 0 ? selector(from) : getAtPath(from, path) : getAtPath(from, path);
|
|
873
|
-
return markSignalAccessor((...args) => {
|
|
874
|
-
if (args.length === 0) return read(root());
|
|
875
|
-
const previousRoot = root();
|
|
876
|
-
const previousSelected = read(previousRoot);
|
|
877
|
-
const value = resolveSignalSetter(() => previousSelected, args[0]);
|
|
878
|
-
const next = path.length === 0 && selector ? value : setAtPath({
|
|
879
|
-
object: previousRoot,
|
|
880
|
-
path,
|
|
881
|
-
value
|
|
882
|
-
});
|
|
883
|
-
if (!Object.is(previousRoot, next)) root(() => next);
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
function ilhaRaw(value) {
|
|
887
|
-
return {
|
|
888
|
-
[RAW]: true,
|
|
889
|
-
value
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
/**
|
|
893
|
-
* Serialize a JSON-safe value into executable `<script>` content safely. `<`
|
|
894
|
-
* is escaped as `\u003C`, so a value can never close the script block or open
|
|
895
|
-
* the `<!--` escape hatch. Use it for script-element data:
|
|
896
|
-
*
|
|
897
|
-
* ```ts
|
|
898
|
-
* html`<script>const d = ${json(payload)};<\/script>`
|
|
899
|
-
* ```
|
|
900
|
-
*
|
|
901
|
-
* Like `raw()`, `json()` is for element *content* only — interpolating it into
|
|
902
|
-
* an attribute is author error. It does not protect against values that are
|
|
903
|
-
* not valid JSON (functions, symbols, undefined, cyclic objects are dropped
|
|
904
|
-
* or throw, matching `JSON.stringify`).
|
|
905
|
-
*/
|
|
906
|
-
function json(value) {
|
|
907
|
-
return ilhaRaw(serializeScriptJson(value));
|
|
908
|
-
}
|
|
909
|
-
/**
|
|
910
|
-
* Escape text for a `<style>` element body. `<` becomes the CSS hex escape
|
|
911
|
-
* `\3C `, so the value can never close the style block or open the `<!--`
|
|
912
|
-
* escape hatch while surviving CSS parsing:
|
|
913
|
-
*
|
|
914
|
-
* ```ts
|
|
915
|
-
* html`<style>${css(cssSource)}</style>`
|
|
916
|
-
* ```
|
|
917
|
-
*
|
|
918
|
-
* Like `raw()`, `css()` is for element *content* only — interpolating it into
|
|
919
|
-
* an attribute is author error.
|
|
920
|
-
*/
|
|
921
|
-
function css(value) {
|
|
922
|
-
return ilhaRaw(escapeStyleText(value));
|
|
923
|
-
}
|
|
924
|
-
function interpolateValue(v) {
|
|
925
|
-
if (v == null || v === true || v === false) return "";
|
|
926
|
-
if (Array.isArray(v)) return v.map(interpolateValue).join("");
|
|
927
|
-
if (isRawHtml(v)) return v.value;
|
|
928
|
-
if (isIslandCall(v)) {
|
|
929
|
-
const call = typeof v === "function" ? v() : v;
|
|
930
|
-
return emitIslandSlot({
|
|
931
|
-
island: call.island,
|
|
932
|
-
props: call.props,
|
|
933
|
-
key: call.key
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
if (isIsland(v)) return emitIslandSlot({
|
|
937
|
-
island: v,
|
|
938
|
-
props: void 0,
|
|
939
|
-
key: void 0
|
|
940
|
-
});
|
|
941
|
-
if (isSignalAccessor(v)) return escapeHtml(v());
|
|
942
|
-
if (typeof v === "function") return escapeHtml(v());
|
|
943
|
-
return escapeHtml(v);
|
|
944
|
-
}
|
|
945
|
-
const BIND_VALID_KINDS = /* @__PURE__ */ new Set([
|
|
946
|
-
"value",
|
|
947
|
-
"checked",
|
|
948
|
-
"files",
|
|
949
|
-
"open",
|
|
950
|
-
"group",
|
|
951
|
-
"this"
|
|
952
|
-
]);
|
|
953
|
-
const BIND_PREFIX_RE = /\bbind:([a-zA-Z]+)\s*=\s*("|')?$/;
|
|
954
|
-
const EVENT_PREFIX_RE = /(?:^|\s)on([a-zA-Z][a-zA-Z0-9-]*)(?::([a-zA-Z][a-zA-Z0-9-]*))?\s*=\s*("|')?$/;
|
|
955
|
-
const NATIVE_EVENT_MODIFIERS = /* @__PURE__ */ new Set([
|
|
956
|
-
"abortable",
|
|
957
|
-
"once",
|
|
958
|
-
"capture",
|
|
959
|
-
"passive"
|
|
960
|
-
]);
|
|
961
|
-
const FULL_VALUE_ATTR_RE = /(?:^|\s)([a-zA-Z_:][a-zA-Z0-9:._-]*)\s*=\s*("|')?$/;
|
|
962
|
-
const UNQUOTED_HAZARD_RE = /[\s=]|`/;
|
|
963
|
-
function resolveTemplateValue(v) {
|
|
964
|
-
if (isSignalAccessor(v)) return v();
|
|
965
|
-
if (typeof v === "function") return v();
|
|
966
|
-
return v;
|
|
967
|
-
}
|
|
968
|
-
function openTagName(source, index) {
|
|
969
|
-
const open = source.lastIndexOf("<", index);
|
|
970
|
-
if (open === -1) return "element";
|
|
971
|
-
return (source.slice(open).match(/^<\s*([a-zA-Z][a-zA-Z0-9:._-]*)/)?.[1] ?? "element").toLowerCase();
|
|
972
|
-
}
|
|
973
|
-
function isEventAttributePosition(source, index) {
|
|
974
|
-
let open = -1;
|
|
975
|
-
let quote = null;
|
|
976
|
-
for (let i = 0; i < index; i++) {
|
|
977
|
-
const char = source[i];
|
|
978
|
-
if (open === -1) {
|
|
979
|
-
if (char === "<") open = i;
|
|
980
|
-
continue;
|
|
981
|
-
}
|
|
982
|
-
if (quote) {
|
|
983
|
-
if (char === quote) quote = null;
|
|
984
|
-
} else if (char === "\"" || char === "'") quote = char;
|
|
985
|
-
else if (char === ">") open = -1;
|
|
986
|
-
}
|
|
987
|
-
if (open === -1 || quote !== null) return false;
|
|
988
|
-
return !/^\s*[!/?]/.test(source.slice(open + 1, index));
|
|
989
|
-
}
|
|
990
|
-
function findTagCloseIndex(chunk, initialQuote) {
|
|
991
|
-
let quote = initialQuote;
|
|
992
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
993
|
-
const ch = chunk[i];
|
|
994
|
-
if (quote !== null) {
|
|
995
|
-
if (ch === quote) quote = null;
|
|
996
|
-
} else if (ch === "\"" || ch === "'") quote = ch;
|
|
997
|
-
else if (ch === ">") return {
|
|
998
|
-
index: i,
|
|
999
|
-
quote
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
return {
|
|
1003
|
-
index: -1,
|
|
1004
|
-
quote
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
function extractElementStaticValue(prefix) {
|
|
1008
|
-
const lastOpen = prefix.lastIndexOf("<");
|
|
1009
|
-
if (lastOpen === -1) return null;
|
|
1010
|
-
const tagPrefix = prefix.slice(lastOpen);
|
|
1011
|
-
if (tagPrefix.includes(">")) return null;
|
|
1012
|
-
const m = tagPrefix.match(/\bvalue\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/);
|
|
1013
|
-
if (!m) return null;
|
|
1014
|
-
return m[2] ?? m[3] ?? m[4] ?? null;
|
|
1015
|
-
}
|
|
1016
|
-
function emitBindSSR({ kind, index, accessor, prefixForStaticPeek }) {
|
|
1017
|
-
const spec = `${kind}:${index}`;
|
|
1018
|
-
let v;
|
|
1019
|
-
try {
|
|
1020
|
-
v = accessor();
|
|
1021
|
-
} catch {
|
|
1022
|
-
v = void 0;
|
|
1023
|
-
}
|
|
1024
|
-
switch (kind) {
|
|
1025
|
-
case "value": return [` value="${escapeHtml(v ?? "")}"`, spec];
|
|
1026
|
-
case "checked": return [v ? ` checked` : ``, spec];
|
|
1027
|
-
case "open": return [v ? ` open` : ``, spec];
|
|
1028
|
-
case "files": return [``, spec];
|
|
1029
|
-
case "this": return [``, spec];
|
|
1030
|
-
case "group": {
|
|
1031
|
-
const optionValue = extractElementStaticValue(prefixForStaticPeek);
|
|
1032
|
-
if (optionValue == null) return [``, spec];
|
|
1033
|
-
return [(Array.isArray(v) ? v.map(String).includes(optionValue) : v != null && String(v) === optionValue) ? ` checked` : ``, spec];
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
function ilhaHtml(strings, ...values) {
|
|
1038
|
-
let result = "";
|
|
1039
|
-
let stripLeadingQuote = null;
|
|
1040
|
-
let pendingBindSpecs = "";
|
|
1041
|
-
let pendingEventSpecs = "";
|
|
1042
|
-
let pendingQuote = null;
|
|
1043
|
-
const ctx = currentRenderCtx();
|
|
1044
|
-
for (let i = 0; i < strings.length; i++) {
|
|
1045
|
-
let chunk = strings[i];
|
|
1046
|
-
if (stripLeadingQuote !== null) {
|
|
1047
|
-
if (chunk.startsWith(stripLeadingQuote)) chunk = chunk.slice(1);
|
|
1048
|
-
stripLeadingQuote = null;
|
|
1049
|
-
}
|
|
1050
|
-
if (pendingBindSpecs !== "" || pendingEventSpecs !== "") {
|
|
1051
|
-
const { index: gtIdx, quote } = findTagCloseIndex(chunk, pendingQuote);
|
|
1052
|
-
if (gtIdx === -1) pendingQuote = quote;
|
|
1053
|
-
else {
|
|
1054
|
-
const head = chunk.slice(0, gtIdx).replace(/\s*\/\s*$/, "");
|
|
1055
|
-
const bindAttr = pendingBindSpecs ? ` data-ilha-bind="${pendingBindSpecs}"` : "";
|
|
1056
|
-
const eventAttr = pendingEventSpecs ? ` data-ilha-on="${pendingEventSpecs}"` : "";
|
|
1057
|
-
chunk = head + bindAttr + eventAttr + ">" + chunk.slice(gtIdx + 1);
|
|
1058
|
-
pendingBindSpecs = "";
|
|
1059
|
-
pendingEventSpecs = "";
|
|
1060
|
-
pendingQuote = null;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
if (i >= values.length) {
|
|
1064
|
-
result += chunk;
|
|
1065
|
-
continue;
|
|
1066
|
-
}
|
|
1067
|
-
const value = values[i];
|
|
1068
|
-
const eventMatch = chunk.match(EVENT_PREFIX_RE);
|
|
1069
|
-
const eventMatchStart = eventMatch ? chunk.length - eventMatch[0].length : -1;
|
|
1070
|
-
if (!eventMatch && typeof value === "function") {
|
|
1071
|
-
const ambiguous = chunk.match(/(?:^|\s)on([a-zA-Z][a-zA-Z0-9-]*)(?::[a-zA-Z][a-zA-Z0-9-]*)?\s*=\s*(?:(?:"[^"]*)|(?:'[^']*)|(?:[^\s"'=<>`]+))$/);
|
|
1072
|
-
const start = ambiguous ? chunk.length - ambiguous[0].length : -1;
|
|
1073
|
-
if (ambiguous && isEventAttributePosition(result + chunk, result.length + start)) throw new Error(`[ilha] on${ambiguous[1]} handler must occupy the entire attribute value. Write on${ambiguous[1]}=\${handler}.`);
|
|
1074
|
-
}
|
|
1075
|
-
const eventAttributeFragment = eventMatch !== null && chunk.slice(0, eventMatchStart).trim() === "";
|
|
1076
|
-
if (eventMatch && (eventAttributeFragment || isEventAttributePosition(result + chunk, result.length + eventMatchStart))) {
|
|
1077
|
-
const eventName = eventMatch[1].toLowerCase();
|
|
1078
|
-
const rawModifier = eventMatch[2]?.toLowerCase();
|
|
1079
|
-
const modifier = rawModifier !== void 0 && NATIVE_EVENT_MODIFIERS.has(rawModifier) ? rawModifier : void 0;
|
|
1080
|
-
const openQuote = eventMatch[3] ?? null;
|
|
1081
|
-
const nextChunk = strings[i + 1] ?? "";
|
|
1082
|
-
if (!(openQuote ? nextChunk.startsWith(openQuote) : /^(?:\s|\/?>|$)/.test(nextChunk))) throw new Error(`[ilha] on${eventName} handler must occupy the entire attribute value. Write on${eventName}=\${handler}.`);
|
|
1083
|
-
const cleanPrefix = chunk.slice(0, eventMatchStart).replace(/\s+$/, "");
|
|
1084
|
-
result += cleanPrefix;
|
|
1085
|
-
typeof value === "function" && SSR_ACTION_STUB in value;
|
|
1086
|
-
if (ctx && typeof value === "function") {
|
|
1087
|
-
const index = ctx.events.length;
|
|
1088
|
-
if (rawModifier && !modifier) {} else {
|
|
1089
|
-
ctx.events.push({
|
|
1090
|
-
type: eventName,
|
|
1091
|
-
handler: value,
|
|
1092
|
-
modifier
|
|
1093
|
-
});
|
|
1094
|
-
pendingEventSpecs += (pendingEventSpecs ? "," : "") + `${eventName}:${index}`;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
1098
|
-
continue;
|
|
1099
|
-
}
|
|
1100
|
-
const m = chunk.match(BIND_PREFIX_RE);
|
|
1101
|
-
if (m && isSignalAccessor(value)) {
|
|
1102
|
-
const name = m[1];
|
|
1103
|
-
const openQuote = m[2] ?? null;
|
|
1104
|
-
if (!BIND_VALID_KINDS.has(name)) {
|
|
1105
|
-
result += chunk + interpolateValue(value);
|
|
1106
|
-
continue;
|
|
1107
|
-
}
|
|
1108
|
-
if (!ctx) {
|
|
1109
|
-
const stripped = chunk.slice(0, chunk.length - m[0].length);
|
|
1110
|
-
const quote = openQuote ?? "\"";
|
|
1111
|
-
result += stripped + name + "=" + quote + interpolateValue(value) + quote;
|
|
1112
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
const matchStart = chunk.length - m[0].length;
|
|
1116
|
-
const cleanPrefix = chunk.slice(0, matchStart).replace(/\s+$/, "");
|
|
1117
|
-
const index = ctx.binds.length;
|
|
1118
|
-
ctx.binds.push({
|
|
1119
|
-
kind: name,
|
|
1120
|
-
accessor: value
|
|
1121
|
-
});
|
|
1122
|
-
const [valueAttrs, spec] = emitBindSSR({
|
|
1123
|
-
kind: name,
|
|
1124
|
-
index,
|
|
1125
|
-
accessor: value,
|
|
1126
|
-
prefixForStaticPeek: cleanPrefix
|
|
1127
|
-
});
|
|
1128
|
-
result += cleanPrefix + valueAttrs;
|
|
1129
|
-
pendingBindSpecs += (pendingBindSpecs ? "," : "") + spec;
|
|
1130
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
const attrMatch = chunk.match(FULL_VALUE_ATTR_RE);
|
|
1134
|
-
if (attrMatch && isEventAttributePosition(result + chunk, result.length + attrMatch.index)) {
|
|
1135
|
-
const attrName = attrMatch[1];
|
|
1136
|
-
const openQuote = attrMatch[2] ?? null;
|
|
1137
|
-
if (openQuote) stripLeadingQuote = openQuote;
|
|
1138
|
-
const cleanPrefix = chunk.slice(0, attrMatch.index).replace(/\s+$/, "");
|
|
1139
|
-
if (attrName.length > 2 && /^on/i.test(attrName)) {
|
|
1140
|
-
result += cleanPrefix;
|
|
1141
|
-
continue;
|
|
1142
|
-
}
|
|
1143
|
-
if (attrName.toLowerCase() === "srcdoc") {
|
|
1144
|
-
result += cleanPrefix;
|
|
1145
|
-
continue;
|
|
1146
|
-
}
|
|
1147
|
-
if (isIsland(value) || isIslandCall(value)) {
|
|
1148
|
-
stripLeadingQuote = null;
|
|
1149
|
-
result += chunk + interpolateValue(value);
|
|
1150
|
-
continue;
|
|
1151
|
-
}
|
|
1152
|
-
if (isUrlAttributeName(attrName)) {
|
|
1153
|
-
if (isRawHtml(value)) {
|
|
1154
|
-
result += cleanPrefix + ` ${attrName}="${value.value}"`;
|
|
1155
|
-
continue;
|
|
1156
|
-
}
|
|
1157
|
-
const resolved = resolveTemplateValue(value);
|
|
1158
|
-
if (resolved == null || resolved === false) {
|
|
1159
|
-
result += cleanPrefix;
|
|
1160
|
-
continue;
|
|
1161
|
-
}
|
|
1162
|
-
const url = String(resolved);
|
|
1163
|
-
if (!isSafeUrlAttrValue(openTagName(result + chunk, result.length + attrMatch.index), attrName, url)) {
|
|
1164
|
-
result += cleanPrefix;
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
const quotedUrl = openQuote !== null || UNQUOTED_HAZARD_RE.test(url);
|
|
1168
|
-
result += cleanPrefix + ` ${attrName}=` + (quotedUrl ? `"${escapeHtml(url)}"` : escapeHtml(url));
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
if (attrName.toLowerCase() === "style") {
|
|
1172
|
-
if (!isRawHtml(value)) {
|
|
1173
|
-
const resolved = resolveTemplateValue(value);
|
|
1174
|
-
if (isPlainObject(resolved)) {
|
|
1175
|
-
result += cleanPrefix + ` style="${escapeHtml(serializeStyle(resolved))}"`;
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
if (isRawHtml(value)) {
|
|
1181
|
-
stripLeadingQuote = null;
|
|
1182
|
-
result += chunk + interpolateValue(value);
|
|
1183
|
-
continue;
|
|
1184
|
-
}
|
|
1185
|
-
const resolved = resolveTemplateValue(value);
|
|
1186
|
-
if (resolved == null || resolved === false) {
|
|
1187
|
-
result += cleanPrefix;
|
|
1188
|
-
continue;
|
|
1189
|
-
}
|
|
1190
|
-
if (resolved === true) {
|
|
1191
|
-
result += cleanPrefix + ` ${attrName}`;
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
const generic = String(resolved);
|
|
1195
|
-
if (openQuote !== null || UNQUOTED_HAZARD_RE.test(generic)) result += cleanPrefix + ` ${attrName}="${escapeHtml(generic)}"`;
|
|
1196
|
-
else result += cleanPrefix + ` ${attrName}=${escapeHtml(generic)}`;
|
|
1197
|
-
continue;
|
|
1198
|
-
}
|
|
1199
|
-
result += chunk + interpolateValue(value);
|
|
1200
|
-
}
|
|
1201
|
-
if (pendingBindSpecs !== "") result += ` data-ilha-bind="${pendingBindSpecs}"`;
|
|
1202
|
-
if (pendingEventSpecs !== "") result += ` data-ilha-on="${pendingEventSpecs}"`;
|
|
1203
|
-
return {
|
|
1204
|
-
[RAW]: true,
|
|
1205
|
-
value: dedentString(result)
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
function isRawHtml(v) {
|
|
1209
|
-
if (typeof v !== "object" || v === null) return false;
|
|
1210
|
-
return RAW in v && typeof v.value === "string";
|
|
1211
|
-
}
|
|
1212
|
-
function unwrapHtml(v) {
|
|
1213
|
-
return isRawHtml(v) ? v.value : v;
|
|
1214
|
-
}
|
|
1215
|
-
const contextRegistry = /* @__PURE__ */ new Map();
|
|
1216
|
-
function ilhaContextFn(key, initial) {
|
|
1217
|
-
if (contextRegistry.has(key)) return contextRegistry.get(key);
|
|
1218
|
-
const s = signal(initial);
|
|
1219
|
-
const accessor = markSignalAccessor((...args) => {
|
|
1220
|
-
if (args.length === 0) return s();
|
|
1221
|
-
s(resolveSignalSetter(() => s(), args[0]));
|
|
1222
|
-
});
|
|
1223
|
-
contextRegistry.set(key, accessor);
|
|
1224
|
-
return accessor;
|
|
1225
|
-
}
|
|
1226
|
-
const ilhaContext = Object.assign(ilhaContextFn, {
|
|
1227
|
-
/** Remove a context signal from the registry. Returns true if it existed. */
|
|
1228
|
-
delete(key) {
|
|
1229
|
-
return contextRegistry.delete(key);
|
|
1230
|
-
},
|
|
1231
|
-
/** Remove all context signals from the registry (e.g. between tests). */
|
|
1232
|
-
clear() {
|
|
1233
|
-
contextRegistry.clear();
|
|
1234
|
-
}
|
|
1235
|
-
});
|
|
1236
|
-
/**
|
|
1237
|
-
* Create a free-standing reactive signal that lives outside any island.
|
|
1238
|
-
* Internal: used by state()'s slot-drift fallback. The public way to create
|
|
1239
|
-
* free-standing shared state is `context(key, initial)`.
|
|
1240
|
-
*/
|
|
1241
|
-
function ilhaSignal(initial) {
|
|
1242
|
-
if (currentFrame()?.creating && __DEV__);
|
|
1243
|
-
const s = signal(initial);
|
|
1244
|
-
return markSignalAccessor((...args) => {
|
|
1245
|
-
if (args.length === 0) return s();
|
|
1246
|
-
s(resolveSignalSetter(() => s(), args[0]));
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Run a free-standing reactive effect outside any island. `fn` runs once
|
|
1251
|
-
* immediately and again whenever a signal it read changes. It may return a
|
|
1252
|
-
* cleanup function, invoked before each re-run and on stop. Signal writes
|
|
1253
|
-
* inside the effect are batched. Returns a stop function that disposes the
|
|
1254
|
-
* effect and runs the final cleanup.
|
|
1255
|
-
*
|
|
1256
|
-
* ```ts
|
|
1257
|
-
* const stop = effect(() => {
|
|
1258
|
-
* document.title = `${cart.count()} items`;
|
|
1259
|
-
* });
|
|
1260
|
-
* ```
|
|
1261
|
-
*/
|
|
1262
|
-
function ilhaEffect(fn) {
|
|
1263
|
-
let cleanup;
|
|
1264
|
-
const runCleanup = () => {
|
|
1265
|
-
if (typeof cleanup === "function") {
|
|
1266
|
-
try {
|
|
1267
|
-
cleanup();
|
|
1268
|
-
} catch (err) {
|
|
1269
|
-
console.error(err);
|
|
1270
|
-
}
|
|
1271
|
-
cleanup = void 0;
|
|
1272
|
-
}
|
|
1273
|
-
};
|
|
1274
|
-
const stop = effect$1(() => {
|
|
1275
|
-
runCleanup();
|
|
1276
|
-
startBatch();
|
|
1277
|
-
try {
|
|
1278
|
-
cleanup = fn();
|
|
1279
|
-
} catch (err) {
|
|
1280
|
-
console.error(err);
|
|
1281
|
-
} finally {
|
|
1282
|
-
endBatch();
|
|
1283
|
-
}
|
|
1284
|
-
});
|
|
1285
|
-
return () => {
|
|
1286
|
-
stop();
|
|
1287
|
-
runCleanup();
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Run `fn` with reactive tracking suspended. Reading signals inside `fn`
|
|
1292
|
-
* returns their current value without subscribing the surrounding scope.
|
|
1293
|
-
* Use this in effects/deriveds when you want to peek at state without
|
|
1294
|
-
* causing a re-run on its changes.
|
|
1295
|
-
*/
|
|
1296
|
-
function untrack(fn) {
|
|
1297
|
-
const prev = setActiveSub(void 0);
|
|
1298
|
-
try {
|
|
1299
|
-
return fn();
|
|
1300
|
-
} finally {
|
|
1301
|
-
setActiveSub(prev);
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
/**
|
|
1305
|
-
* Run `fn` as an atomic batch — multiple signal writes inside the callback
|
|
1306
|
-
* produce a single propagation pass, so dependents (effects, deriveds,
|
|
1307
|
-
* island re-renders) see the final state and run once instead of once per
|
|
1308
|
-
* write. Returns whatever `fn` returns.
|
|
1309
|
-
*
|
|
1310
|
-
* Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
|
|
1311
|
-
* you only need this when triggering multiple writes from outside an
|
|
1312
|
-
* island (e.g. from a top-level event listener or async callback).
|
|
1313
|
-
*/
|
|
1314
|
-
function batch(fn) {
|
|
1315
|
-
startBatch();
|
|
1316
|
-
try {
|
|
1317
|
-
return fn();
|
|
1318
|
-
} finally {
|
|
1319
|
-
endBatch();
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
/**
|
|
1323
|
-
* Keep a standalone signal in sync with persistent storage:
|
|
1324
|
-
*
|
|
1325
|
-
* 1. On call, reads `key` and writes the stored value into the signal.
|
|
1326
|
-
* 2. Subscribes to the signal and writes its value back on every change.
|
|
1327
|
-
* 3. Optionally mirrors writes from other tabs (`storage` events).
|
|
1328
|
-
*
|
|
1329
|
-
* No-op on the server (returns an inert unsubscribe). Call the returned
|
|
1330
|
-
* unsubscribe to stop syncing.
|
|
1331
|
-
*
|
|
1332
|
-
* ```ts
|
|
1333
|
-
* const cart = signal([] as string[]);
|
|
1334
|
-
* persist(cart, "cart");
|
|
1335
|
-
* ```
|
|
1336
|
-
*/
|
|
1337
|
-
function persist(accessor, key, options = {}) {
|
|
1338
|
-
if (typeof window === "undefined") return () => {};
|
|
1339
|
-
const storage = options.storage ?? window.localStorage;
|
|
1340
|
-
const serialize = options.serialize ?? JSON.stringify;
|
|
1341
|
-
const deserialize = options.deserialize ?? ((raw) => {
|
|
1342
|
-
try {
|
|
1343
|
-
return JSON.parse(raw);
|
|
1344
|
-
} catch {
|
|
1345
|
-
return null;
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
const label = `[ilha] persist("${key}")`;
|
|
1349
|
-
const applyRaw = (raw) => {
|
|
1350
|
-
if (raw == null) return;
|
|
1351
|
-
try {
|
|
1352
|
-
const value = deserialize(raw);
|
|
1353
|
-
if (value !== null) {
|
|
1354
|
-
const prevSub = setActiveSub(void 0);
|
|
1355
|
-
try {
|
|
1356
|
-
accessor(() => value);
|
|
1357
|
-
} finally {
|
|
1358
|
-
setActiveSub(prevSub);
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
} catch (err) {
|
|
1362
|
-
console.error(`${label}: failed to restore`, err);
|
|
1363
|
-
}
|
|
1364
|
-
};
|
|
1365
|
-
try {
|
|
1366
|
-
applyRaw(storage.getItem(key));
|
|
1367
|
-
} catch (err) {
|
|
1368
|
-
console.error(`${label}: failed to read storage`, err);
|
|
1369
|
-
}
|
|
1370
|
-
const stopEffect = ilhaEffect(() => {
|
|
1371
|
-
try {
|
|
1372
|
-
storage.setItem(key, serialize(accessor()));
|
|
1373
|
-
} catch (err) {
|
|
1374
|
-
console.error(`${label}: failed to write storage`, err);
|
|
1375
|
-
}
|
|
1376
|
-
});
|
|
1377
|
-
let onStorage = null;
|
|
1378
|
-
if (options.crossTab !== false && storage === window.localStorage) {
|
|
1379
|
-
onStorage = (e) => {
|
|
1380
|
-
if (e.key !== key) return;
|
|
1381
|
-
applyRaw(e.newValue);
|
|
1382
|
-
};
|
|
1383
|
-
window.addEventListener("storage", onStorage);
|
|
1384
|
-
}
|
|
1385
|
-
return () => {
|
|
1386
|
-
stopEffect();
|
|
1387
|
-
if (onStorage) window.removeEventListener("storage", onStorage);
|
|
1388
|
-
};
|
|
1389
|
-
}
|
|
1390
|
-
function createDerivedAccessor(read, write) {
|
|
1391
|
-
const accessor = markSignalAccessor((...args) => {
|
|
1392
|
-
if (args.length > 0) {
|
|
1393
|
-
if (write) write(args[0]);
|
|
1394
|
-
return;
|
|
1395
|
-
}
|
|
1396
|
-
return read().value;
|
|
1397
|
-
});
|
|
1398
|
-
return new Proxy(accessor, { get(target, prop, receiver) {
|
|
1399
|
-
if (prop === "loading" || prop === "value" || prop === "error") return read()[prop];
|
|
1400
|
-
return Reflect.get(target, prop, receiver);
|
|
1401
|
-
} });
|
|
1402
|
-
}
|
|
1403
|
-
function defaultDerivedAccessor() {
|
|
1404
|
-
return createDerivedAccessor(() => ({
|
|
1405
|
-
loading: false,
|
|
1406
|
-
value: void 0,
|
|
1407
|
-
error: void 0
|
|
17
|
+
const base = stream;
|
|
18
|
+
const src = parent.runtime.ssr ? base.pipe(Stream.take(1)) : base;
|
|
19
|
+
yield* Stream.runForEach(src, (value) => Effect.sync(() => {
|
|
20
|
+
if (child) closeFiber(child);
|
|
21
|
+
child = makeFiber(parent.runtime, parent.root, paint, { onFail: parent.fail });
|
|
22
|
+
interpret((() => body(value)), child);
|
|
23
|
+
}));
|
|
1408
24
|
}));
|
|
1409
25
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
const
|
|
1416
|
-
if (
|
|
1417
|
-
|
|
1418
|
-
if (!Number.isInteger(i) || i < 0) continue;
|
|
1419
|
-
out.push({
|
|
1420
|
-
kind,
|
|
1421
|
-
index: i
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
return out;
|
|
1425
|
-
}
|
|
1426
|
-
function resolveBindOps(el, kind) {
|
|
1427
|
-
const input = el;
|
|
1428
|
-
switch (kind) {
|
|
1429
|
-
case "value": return {
|
|
1430
|
-
event: el.tagName === "SELECT" ? "change" : "input",
|
|
1431
|
-
read: (el) => el.value,
|
|
1432
|
-
write: (el, v) => el.value = v == null ? "" : String(v)
|
|
1433
|
-
};
|
|
1434
|
-
case "checked": return {
|
|
1435
|
-
event: "change",
|
|
1436
|
-
read: (el) => el.checked,
|
|
1437
|
-
write: (el, v) => el.checked = Boolean(v)
|
|
1438
|
-
};
|
|
1439
|
-
case "files": return {
|
|
1440
|
-
event: "change",
|
|
1441
|
-
read: (el) => el.files,
|
|
1442
|
-
write: () => {}
|
|
1443
|
-
};
|
|
1444
|
-
case "open": return {
|
|
1445
|
-
event: "toggle",
|
|
1446
|
-
read: (el) => el.open,
|
|
1447
|
-
write: (el, v) => el.open = Boolean(v)
|
|
1448
|
-
};
|
|
1449
|
-
case "this": return {
|
|
1450
|
-
event: null,
|
|
1451
|
-
read: () => void 0,
|
|
1452
|
-
write: () => {}
|
|
1453
|
-
};
|
|
1454
|
-
case "group": {
|
|
1455
|
-
const isCheckbox = input.type === "checkbox";
|
|
1456
|
-
return {
|
|
1457
|
-
event: "change",
|
|
1458
|
-
read: (el) => {
|
|
1459
|
-
const i = el;
|
|
1460
|
-
if (isCheckbox) return {
|
|
1461
|
-
__ilhaGroup: true,
|
|
1462
|
-
value: i.value,
|
|
1463
|
-
checked: i.checked
|
|
1464
|
-
};
|
|
1465
|
-
return i.checked ? i.value : void 0;
|
|
1466
|
-
},
|
|
1467
|
-
write: (el, v) => {
|
|
1468
|
-
const i = el;
|
|
1469
|
-
if (isCheckbox) i.checked = (Array.isArray(v) ? v.map(String) : []).includes(i.value);
|
|
1470
|
-
else i.checked = v != null && String(v) === i.value;
|
|
1471
|
-
}
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
function templateElementBelongsToHost(host, candidate) {
|
|
1477
|
-
if (candidate === host) return true;
|
|
1478
|
-
let current = candidate;
|
|
1479
|
-
while (current && current !== host) {
|
|
1480
|
-
if (current.hasAttribute(SLOT_ATTR) || current.hasAttribute("data-ilha")) return false;
|
|
1481
|
-
current = current.parentElement;
|
|
1482
|
-
}
|
|
1483
|
-
return current === host;
|
|
1484
|
-
}
|
|
1485
|
-
function applyJsxEvents({ host, records, reportError, unmountSignal, onceFired }) {
|
|
1486
|
-
if (records.length === 0) return () => {};
|
|
1487
|
-
const cleanups = [];
|
|
1488
|
-
const elements = [];
|
|
1489
|
-
if (host.hasAttribute(EVENT_SENTINEL_ATTR)) elements.push(host);
|
|
1490
|
-
elements.push(...Array.from(host.querySelectorAll(`[${EVENT_SENTINEL_ATTR}]`)).filter((element) => templateElementBelongsToHost(host, element)));
|
|
1491
|
-
for (const el of elements) {
|
|
1492
|
-
const sentinel = el.getAttribute(EVENT_SENTINEL_ATTR) ?? "";
|
|
1493
|
-
for (const part of sentinel.split(",")) {
|
|
1494
|
-
const separator = part.lastIndexOf(":");
|
|
1495
|
-
if (separator < 1) continue;
|
|
1496
|
-
const type = part.slice(0, separator);
|
|
1497
|
-
const index = Number(part.slice(separator + 1));
|
|
1498
|
-
if (!Number.isInteger(index) || index < 0) continue;
|
|
1499
|
-
const record = records[index];
|
|
1500
|
-
if (!record || record.type !== type) continue;
|
|
1501
|
-
if (record.modifier === "once" && onceFired.get(el)?.has(type)) continue;
|
|
1502
|
-
const listenerController = new AbortController();
|
|
1503
|
-
let invocationController;
|
|
1504
|
-
const options = {
|
|
1505
|
-
once: record.modifier === "once",
|
|
1506
|
-
capture: record.modifier === "capture",
|
|
1507
|
-
passive: record.modifier === "passive"
|
|
1508
|
-
};
|
|
1509
|
-
const reportHandlerError = (error) => {
|
|
1510
|
-
if (error?.name !== "AbortError") reportError(error);
|
|
1511
|
-
};
|
|
1512
|
-
const listener = (event) => {
|
|
1513
|
-
if (record.modifier === "once") {
|
|
1514
|
-
let fired = onceFired.get(el);
|
|
1515
|
-
if (!fired) onceFired.set(el, fired = /* @__PURE__ */ new Set());
|
|
1516
|
-
fired.add(type);
|
|
1517
|
-
}
|
|
1518
|
-
if (record.modifier === "abortable") {
|
|
1519
|
-
invocationController?.abort();
|
|
1520
|
-
invocationController = new AbortController();
|
|
1521
|
-
}
|
|
1522
|
-
const signal = invocationController ? AbortSignal.any([listenerController.signal, invocationController.signal]) : listenerController.signal;
|
|
1523
|
-
let result;
|
|
1524
|
-
startBatch();
|
|
1525
|
-
try {
|
|
1526
|
-
result = record.handler(event, { signal });
|
|
1527
|
-
} catch (error) {
|
|
1528
|
-
reportHandlerError(error);
|
|
1529
|
-
return;
|
|
1530
|
-
} finally {
|
|
1531
|
-
endBatch();
|
|
1532
|
-
}
|
|
1533
|
-
if (result != null && typeof result.then === "function") Promise.resolve(result).catch(reportHandlerError);
|
|
1534
|
-
};
|
|
1535
|
-
const stop = () => {
|
|
1536
|
-
listenerController.abort();
|
|
1537
|
-
invocationController?.abort();
|
|
1538
|
-
el.removeEventListener(type, listener, options);
|
|
1539
|
-
};
|
|
1540
|
-
el.addEventListener(type, listener, options);
|
|
1541
|
-
unmountSignal.addEventListener("abort", stop, { once: true });
|
|
1542
|
-
cleanups.push(() => {
|
|
1543
|
-
unmountSignal.removeEventListener("abort", stop);
|
|
1544
|
-
stop();
|
|
1545
|
-
});
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
return () => cleanups.forEach((cleanup) => cleanup());
|
|
1549
|
-
}
|
|
1550
|
-
function applyTemplateBindings(host, binds, phase = "all") {
|
|
1551
|
-
if (binds.length === 0) return () => {};
|
|
1552
|
-
const cleanups = [];
|
|
1553
|
-
const elements = [];
|
|
1554
|
-
if (host.hasAttribute(BIND_SENTINEL_ATTR)) elements.push(host);
|
|
1555
|
-
for (const el of host.querySelectorAll(`[${BIND_SENTINEL_ATTR}]`)) if (templateElementBelongsToHost(host, el)) elements.push(el);
|
|
1556
|
-
for (const el of elements) {
|
|
1557
|
-
const specs = parseBindSentinel(el.getAttribute(BIND_SENTINEL_ATTR));
|
|
1558
|
-
for (const spec of specs) {
|
|
1559
|
-
const record = binds[spec.index];
|
|
1560
|
-
if (!record) continue;
|
|
1561
|
-
if (record.kind !== spec.kind) continue;
|
|
1562
|
-
const { event, read, write } = resolveBindOps(el, spec.kind);
|
|
1563
|
-
const accessor = record.accessor;
|
|
1564
|
-
if (spec.kind === "this") {
|
|
1565
|
-
if (phase !== "listen") {
|
|
1566
|
-
accessor(el);
|
|
1567
|
-
cleanups.push(() => accessor(null));
|
|
1568
|
-
}
|
|
1569
|
-
continue;
|
|
1570
|
-
}
|
|
1571
|
-
if (phase !== "listen") try {
|
|
1572
|
-
write(el, accessor());
|
|
1573
|
-
} catch (err) {}
|
|
1574
|
-
if (phase === "reflect" || event === null) continue;
|
|
1575
|
-
const listener = () => {
|
|
1576
|
-
const raw = read(el);
|
|
1577
|
-
if (spec.kind === "group") {
|
|
1578
|
-
const groupRead = raw;
|
|
1579
|
-
if (groupRead === void 0) return;
|
|
1580
|
-
if (typeof groupRead === "object" && groupRead.__ilhaGroup) {
|
|
1581
|
-
const currentArr = accessor();
|
|
1582
|
-
const arr = Array.isArray(currentArr) ? [...currentArr] : [];
|
|
1583
|
-
const idx = arr.findIndex((x) => String(x) === groupRead.value);
|
|
1584
|
-
if (groupRead.checked && idx === -1) {
|
|
1585
|
-
let coercedVal = groupRead.value;
|
|
1586
|
-
const templateVal = Array.isArray(currentArr) && currentArr.length > 0 ? currentArr[0] : void 0;
|
|
1587
|
-
if (templateVal !== void 0) {
|
|
1588
|
-
if (typeof templateVal === "number") {
|
|
1589
|
-
const n = Number(coercedVal);
|
|
1590
|
-
coercedVal = Number.isNaN(n) ? coercedVal : n;
|
|
1591
|
-
} else if (typeof templateVal === "boolean") coercedVal = Boolean(coercedVal);
|
|
1592
|
-
}
|
|
1593
|
-
arr.push(coercedVal);
|
|
1594
|
-
} else if (!groupRead.checked && idx !== -1) arr.splice(idx, 1);
|
|
1595
|
-
accessor(arr);
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
const currentVal = accessor();
|
|
1599
|
-
let coerced = groupRead;
|
|
1600
|
-
if (typeof currentVal === "number" && typeof groupRead === "string") {
|
|
1601
|
-
const n = Number(groupRead);
|
|
1602
|
-
coerced = Number.isNaN(n) ? 0 : n;
|
|
1603
|
-
} else if (typeof currentVal === "boolean") coerced = Boolean(groupRead);
|
|
1604
|
-
accessor(coerced);
|
|
1605
|
-
return;
|
|
1606
|
-
}
|
|
1607
|
-
const currentVal = accessor();
|
|
1608
|
-
let value = raw;
|
|
1609
|
-
if (typeof currentVal === "number" && typeof raw === "string") {
|
|
1610
|
-
const n = Number(raw);
|
|
1611
|
-
value = Number.isNaN(n) ? 0 : n;
|
|
1612
|
-
} else if (typeof currentVal === "boolean") value = Boolean(raw);
|
|
1613
|
-
accessor(value);
|
|
1614
|
-
};
|
|
1615
|
-
el.addEventListener(event, listener);
|
|
1616
|
-
cleanups.push(() => el.removeEventListener(event, listener));
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
return () => cleanups.forEach((c) => c());
|
|
1620
|
-
}
|
|
1621
|
-
const globalErrorHandlers = /* @__PURE__ */ new Set();
|
|
1622
|
-
function reportToGlobal(error, source) {
|
|
1623
|
-
if (globalErrorHandlers.size === 0) return false;
|
|
1624
|
-
for (const handler of globalErrorHandlers) try {
|
|
1625
|
-
handler(error, source);
|
|
1626
|
-
} catch (handlerErr) {
|
|
1627
|
-
console.error(handlerErr);
|
|
1628
|
-
}
|
|
1629
|
-
return true;
|
|
1630
|
-
}
|
|
1631
|
-
/**
|
|
1632
|
-
* Register a global error handler invoked when any island reports an error
|
|
1633
|
-
* and has no local onError() handler. Returns an unsubscribe function.
|
|
1634
|
-
*/
|
|
1635
|
-
function onUncaughtError(fn) {
|
|
1636
|
-
globalErrorHandlers.add(fn);
|
|
1637
|
-
return () => {
|
|
1638
|
-
globalErrorHandlers.delete(fn);
|
|
1639
|
-
};
|
|
1640
|
-
}
|
|
1641
|
-
const FRAME_STACK = Symbol.for("ilha.primitiveFrameStack");
|
|
1642
|
-
function frameStack() {
|
|
1643
|
-
const g = globalThis;
|
|
1644
|
-
return g[FRAME_STACK] ??= [];
|
|
1645
|
-
}
|
|
1646
|
-
function currentFrame() {
|
|
1647
|
-
const stack = frameStack();
|
|
1648
|
-
return stack[stack.length - 1];
|
|
1649
|
-
}
|
|
1650
|
-
function withFrame(frame, fn) {
|
|
1651
|
-
frameStack().push(frame);
|
|
1652
|
-
try {
|
|
1653
|
-
return fn();
|
|
1654
|
-
} finally {
|
|
1655
|
-
frameStack().pop();
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
function freshFrame(options) {
|
|
1659
|
-
return {
|
|
1660
|
-
slots: [],
|
|
1661
|
-
cursor: 0,
|
|
1662
|
-
creating: true,
|
|
1663
|
-
ssr: options?.ssr ?? false,
|
|
1664
|
-
instance: options?.instance ?? null,
|
|
1665
|
-
stateIndex: 0,
|
|
1666
|
-
derivedIndex: 0,
|
|
1667
|
-
actionIndex: 0,
|
|
1668
|
-
pendingDerived: options?.ssr ? [] : null,
|
|
1669
|
-
actionManifest: options?.ssr ? /* @__PURE__ */ new Map() : null
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
const PRIMITIVE_NAMES = {
|
|
1673
|
-
state: "state",
|
|
1674
|
-
derived: "derived",
|
|
1675
|
-
action: "action",
|
|
1676
|
-
effect: "effect",
|
|
1677
|
-
once: "effect.once",
|
|
1678
|
-
error: "onError"
|
|
1679
|
-
};
|
|
1680
|
-
/** Reserve the next slot for a primitive call. Throws outside an island
|
|
1681
|
-
* render; warns in dev on hook count/kind drift during re-renders. */
|
|
1682
|
-
function acquireSlot(kind) {
|
|
1683
|
-
const frame = currentFrame();
|
|
1684
|
-
if (!frame) throw new Error(`[ilha] ${PRIMITIVE_NAMES[kind]}() called outside an island render. Primitives are only valid while an island component (or a plain component owned by one) is rendering.`);
|
|
1685
|
-
const index = frame.cursor++;
|
|
1686
|
-
if (frame.creating) return void 0;
|
|
1687
|
-
const existing = frame.slots[index];
|
|
1688
|
-
return existing && existing.kind === kind ? existing : void 0;
|
|
1689
|
-
}
|
|
1690
|
-
/**
|
|
1691
|
-
* Declare island-local reactive state at this call position. The initializer
|
|
1692
|
-
* applies only when the instance is created — later renders reuse the same
|
|
1693
|
-
* underlying signal, so prop-driven initializers never reset user state.
|
|
1694
|
-
*
|
|
1695
|
-
* A function argument is treated as a lazy initializer:
|
|
1696
|
-
* const count = state(() => expensiveInitialValue());
|
|
1697
|
-
* To store a function VALUE, return it from the updater wrapper on write:
|
|
1698
|
-
* setCallback(() => nextCallback);
|
|
1699
|
-
*/
|
|
1700
|
-
function state(init) {
|
|
1701
|
-
const frame = currentFrame();
|
|
1702
|
-
const existing = acquireSlot("state");
|
|
1703
|
-
if (existing) return existing.acc;
|
|
1704
|
-
const f = frame;
|
|
1705
|
-
if (!f.creating) return ilhaSignal(void 0);
|
|
1706
|
-
const index = f.stateIndex++;
|
|
1707
|
-
const snap = f.ssr ? void 0 : f.instance?.stateSnapshot?.s?.[index];
|
|
1708
|
-
const initial = snap === void 0 ? typeof init === "function" ? init() : init : snap;
|
|
1709
|
-
const s = signal(initial);
|
|
1710
|
-
const acc = markSignalAccessor((...args) => {
|
|
1711
|
-
if (args.length === 0) return s();
|
|
1712
|
-
const next = resolveSignalSetter(() => s(), args[0]);
|
|
1713
|
-
s(next);
|
|
1714
|
-
});
|
|
1715
|
-
f.slots[f.cursor - 1] = {
|
|
1716
|
-
kind: "state",
|
|
1717
|
-
acc
|
|
1718
|
-
};
|
|
1719
|
-
return acc;
|
|
1720
|
-
}
|
|
1721
|
-
/** True for async iterables (incl. async generators). */
|
|
1722
|
-
function isAsyncIterable(value) {
|
|
1723
|
-
return value !== null && typeof value[Symbol.asyncIterator] === "function";
|
|
1724
|
-
}
|
|
1725
|
-
/**
|
|
1726
|
-
* Declare derived state at this call position: synchronous computations,
|
|
1727
|
-
* Promises, or async generators. Reads track their dependencies; async work
|
|
1728
|
-
* races latest-run-wins and aborts stale runs through `ctx.signal`.
|
|
1729
|
-
*/
|
|
1730
|
-
function derived(fn) {
|
|
1731
|
-
const frame = currentFrame();
|
|
1732
|
-
const existing = acquireSlot("derived");
|
|
1733
|
-
const f = frame;
|
|
1734
|
-
if (existing) {
|
|
1735
|
-
const slot = existing;
|
|
1736
|
-
slot.fn = fn;
|
|
1737
|
-
if (f.ssr && !f.creating && !slot.ssrAsync) {
|
|
1738
|
-
const ac = new AbortController();
|
|
1739
|
-
const prevSub = setActiveSub(void 0);
|
|
1740
|
-
try {
|
|
1741
|
-
const result = slot.fn({ signal: ac.signal });
|
|
1742
|
-
if (!(result instanceof Promise) && !isAsyncIterable(result)) slot.env({
|
|
1743
|
-
loading: false,
|
|
1744
|
-
value: result,
|
|
1745
|
-
error: void 0
|
|
1746
|
-
});
|
|
1747
|
-
} catch (err) {
|
|
1748
|
-
slot.env({
|
|
1749
|
-
loading: false,
|
|
1750
|
-
value: void 0,
|
|
1751
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1752
|
-
});
|
|
1753
|
-
} finally {
|
|
1754
|
-
setActiveSub(prevSub);
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
return slot.acc;
|
|
1758
|
-
}
|
|
1759
|
-
if (!f.creating) return defaultDerivedAccessor();
|
|
1760
|
-
const index = f.derivedIndex++;
|
|
1761
|
-
const env = signal({
|
|
1762
|
-
loading: false,
|
|
1763
|
-
value: void 0,
|
|
1764
|
-
error: void 0
|
|
1765
|
-
});
|
|
1766
|
-
let fromSnapshot = false;
|
|
1767
|
-
if (!f.ssr) {
|
|
1768
|
-
const snap = f.instance?.stateSnapshot?.d?.[index];
|
|
1769
|
-
if (snap) {
|
|
1770
|
-
env({ ...snap });
|
|
1771
|
-
fromSnapshot = true;
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
const accessor = createDerivedAccessor(() => env(), (value) => {
|
|
1775
|
-
const prevSub = setActiveSub(void 0);
|
|
1776
|
-
try {
|
|
1777
|
-
env({
|
|
1778
|
-
loading: false,
|
|
1779
|
-
value,
|
|
1780
|
-
error: void 0
|
|
1781
|
-
});
|
|
1782
|
-
} finally {
|
|
1783
|
-
setActiveSub(prevSub);
|
|
1784
|
-
}
|
|
1785
|
-
});
|
|
1786
|
-
const slot = {
|
|
1787
|
-
kind: "derived",
|
|
1788
|
-
env,
|
|
1789
|
-
acc: accessor,
|
|
1790
|
-
fn,
|
|
1791
|
-
ssrAsync: false,
|
|
1792
|
-
fromSnapshot
|
|
1793
|
-
};
|
|
1794
|
-
if (f.ssr) {
|
|
1795
|
-
const ac = new AbortController();
|
|
1796
|
-
const prevSub = setActiveSub(void 0);
|
|
1797
|
-
let result;
|
|
1798
|
-
let threw = false;
|
|
1799
|
-
let syncError;
|
|
1800
|
-
try {
|
|
1801
|
-
result = slot.fn({ signal: ac.signal });
|
|
1802
|
-
} catch (err) {
|
|
1803
|
-
threw = true;
|
|
1804
|
-
syncError = err;
|
|
1805
|
-
} finally {
|
|
1806
|
-
setActiveSub(prevSub);
|
|
1807
|
-
}
|
|
1808
|
-
let pull = null;
|
|
1809
|
-
if (threw) env({
|
|
1810
|
-
loading: false,
|
|
1811
|
-
value: void 0,
|
|
1812
|
-
error: syncError instanceof Error ? syncError : new Error(String(syncError))
|
|
1813
|
-
});
|
|
1814
|
-
else if (result instanceof Promise) {
|
|
1815
|
-
env({
|
|
1816
|
-
loading: true,
|
|
1817
|
-
value: void 0,
|
|
1818
|
-
error: void 0
|
|
1819
|
-
});
|
|
1820
|
-
pull = result.then((value) => env({
|
|
1821
|
-
loading: false,
|
|
1822
|
-
value,
|
|
1823
|
-
error: void 0
|
|
1824
|
-
}), (err) => env({
|
|
1825
|
-
loading: false,
|
|
1826
|
-
value: void 0,
|
|
1827
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
1828
|
-
}));
|
|
1829
|
-
result.catch(() => {});
|
|
1830
|
-
} else if (isAsyncIterable(result)) {
|
|
1831
|
-
env({
|
|
1832
|
-
loading: true,
|
|
1833
|
-
value: void 0,
|
|
1834
|
-
error: void 0
|
|
1835
|
-
});
|
|
1836
|
-
const iterable = result;
|
|
1837
|
-
pull = (async () => {
|
|
1838
|
-
const it = iterable[Symbol.asyncIterator]();
|
|
1839
|
-
try {
|
|
1840
|
-
const first = await it.next();
|
|
1841
|
-
env({
|
|
1842
|
-
loading: false,
|
|
1843
|
-
value: first.done ? void 0 : first.value,
|
|
1844
|
-
error: void 0
|
|
1845
|
-
});
|
|
1846
|
-
} finally {
|
|
1847
|
-
await it.return?.(void 0);
|
|
1848
|
-
}
|
|
1849
|
-
})();
|
|
1850
|
-
} else env({
|
|
1851
|
-
loading: false,
|
|
1852
|
-
value: result,
|
|
1853
|
-
error: void 0
|
|
1854
|
-
});
|
|
1855
|
-
slot.ssrAsync = pull !== null;
|
|
1856
|
-
if (pull) f.pendingDerived.push({
|
|
1857
|
-
index,
|
|
1858
|
-
promise: pull.catch(() => {})
|
|
1859
|
-
});
|
|
1860
|
-
}
|
|
1861
|
-
f.slots[f.cursor - 1] = slot;
|
|
1862
|
-
return accessor;
|
|
1863
|
-
}
|
|
1864
|
-
/**
|
|
1865
|
-
* Declare a reactive operation at this call position. Use plain functions
|
|
1866
|
-
* for ordinary operations; action() adds pending/data/error tracking,
|
|
1867
|
-
* concurrent-invocation bookkeeping, and lifecycle cancellation.
|
|
1868
|
-
*/
|
|
1869
|
-
function action(fn) {
|
|
1870
|
-
const frame = currentFrame();
|
|
1871
|
-
const existing = acquireSlot("action");
|
|
1872
|
-
const f = frame;
|
|
1873
|
-
if (existing) return existing.acc;
|
|
1874
|
-
if (!f.creating) {
|
|
1875
|
-
const noop = (() => {});
|
|
1876
|
-
Object.defineProperties(noop, {
|
|
1877
|
-
pending: { get: () => false },
|
|
1878
|
-
data: { get: () => void 0 },
|
|
1879
|
-
error: { get: () => void 0 }
|
|
1880
|
-
});
|
|
1881
|
-
attachWith(noop);
|
|
1882
|
-
return noop;
|
|
1883
|
-
}
|
|
1884
|
-
const manifestId = `a${f.actionIndex++}`;
|
|
1885
|
-
if (f.ssr) {
|
|
1886
|
-
const invoke = (..._callArgs) => {};
|
|
1887
|
-
invoke[SSR_ACTION_STUB] = true;
|
|
1888
|
-
invoke[ACTION_MANIFEST_ID] = manifestId;
|
|
1889
|
-
Object.defineProperties(invoke, {
|
|
1890
|
-
pending: {
|
|
1891
|
-
get: () => false,
|
|
1892
|
-
enumerable: true
|
|
1893
|
-
},
|
|
1894
|
-
data: {
|
|
1895
|
-
get: () => void 0,
|
|
1896
|
-
enumerable: true
|
|
1897
|
-
},
|
|
1898
|
-
error: {
|
|
1899
|
-
get: () => void 0,
|
|
1900
|
-
enumerable: true
|
|
1901
|
-
}
|
|
1902
|
-
});
|
|
1903
|
-
f.actionManifest.set(invoke, manifestId);
|
|
1904
|
-
f.slots[f.cursor - 1] = {
|
|
1905
|
-
kind: "action",
|
|
1906
|
-
acc: invoke,
|
|
1907
|
-
fn
|
|
1908
|
-
};
|
|
1909
|
-
const ssrAccessor = invoke;
|
|
1910
|
-
attachWith(ssrAccessor);
|
|
1911
|
-
return ssrAccessor;
|
|
1912
|
-
}
|
|
1913
|
-
const instance = f.instance;
|
|
1914
|
-
const envelope = signal({
|
|
1915
|
-
pending: 0,
|
|
1916
|
-
data: void 0,
|
|
1917
|
-
error: void 0
|
|
1918
|
-
});
|
|
1919
|
-
const readEnvelope = () => untrack(() => envelope());
|
|
1920
|
-
let latestRun = 0;
|
|
1921
|
-
const invoke = (payload) => {
|
|
1922
|
-
if (currentRenderCtx()) return;
|
|
1923
|
-
if (instance.disposed) return;
|
|
1924
|
-
const run = ++latestRun;
|
|
1925
|
-
const runSignal = instance.unmountController.signal;
|
|
1926
|
-
let result;
|
|
1927
|
-
let isAsync = false;
|
|
1928
|
-
startBatch();
|
|
1929
|
-
try {
|
|
1930
|
-
result = slot.fn(payload, { signal: runSignal });
|
|
1931
|
-
isAsync = result != null && typeof result.then === "function";
|
|
1932
|
-
const current = readEnvelope();
|
|
1933
|
-
envelope(isAsync ? {
|
|
1934
|
-
pending: current.pending + 1,
|
|
1935
|
-
data: current.data,
|
|
1936
|
-
error: void 0
|
|
1937
|
-
} : {
|
|
1938
|
-
pending: current.pending,
|
|
1939
|
-
data: result,
|
|
1940
|
-
error: void 0
|
|
1941
|
-
});
|
|
1942
|
-
} catch (error) {
|
|
1943
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
1944
|
-
envelope({
|
|
1945
|
-
pending: readEnvelope().pending,
|
|
1946
|
-
data: void 0,
|
|
1947
|
-
error: normalized
|
|
1948
|
-
});
|
|
1949
|
-
instance.reportError(normalized, "action");
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/watch.ts
|
|
29
|
+
function watch(source, fn) {
|
|
30
|
+
return instr(Effect.sync(() => {
|
|
31
|
+
const fiber = (isAtomHandle(source) ? handleOwner.get(source) : void 0) ?? getFiber();
|
|
32
|
+
if (Stream.isStream(source)) {
|
|
33
|
+
fiber.run(Stream.runForEach(source, (v) => Effect.sync(() => fn(v))));
|
|
1950
34
|
return;
|
|
1951
|
-
} finally {
|
|
1952
|
-
endBatch();
|
|
1953
|
-
}
|
|
1954
|
-
if (!isAsync) return;
|
|
1955
|
-
Promise.resolve(result).then((data) => {
|
|
1956
|
-
if (instance.disposed || runSignal.aborted || run !== latestRun) return;
|
|
1957
|
-
const value = readEnvelope();
|
|
1958
|
-
envelope({
|
|
1959
|
-
pending: value.pending,
|
|
1960
|
-
data,
|
|
1961
|
-
error: void 0
|
|
1962
|
-
});
|
|
1963
|
-
}).catch((error) => {
|
|
1964
|
-
if (instance.disposed || runSignal.aborted || error?.name === "AbortError") return;
|
|
1965
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
1966
|
-
if (run === latestRun) {
|
|
1967
|
-
const value = readEnvelope();
|
|
1968
|
-
envelope({
|
|
1969
|
-
pending: value.pending,
|
|
1970
|
-
data: void 0,
|
|
1971
|
-
error: normalized
|
|
1972
|
-
});
|
|
1973
|
-
}
|
|
1974
|
-
instance.reportError(normalized, "action");
|
|
1975
|
-
}).finally(() => {
|
|
1976
|
-
if (instance.disposed) return;
|
|
1977
|
-
const value = readEnvelope();
|
|
1978
|
-
envelope({
|
|
1979
|
-
...value,
|
|
1980
|
-
pending: Math.max(0, value.pending - 1)
|
|
1981
|
-
});
|
|
1982
|
-
});
|
|
1983
|
-
};
|
|
1984
|
-
const slot = {
|
|
1985
|
-
kind: "action",
|
|
1986
|
-
acc: invoke,
|
|
1987
|
-
fn
|
|
1988
|
-
};
|
|
1989
|
-
Object.defineProperties(invoke, {
|
|
1990
|
-
pending: {
|
|
1991
|
-
get: () => envelope().pending > 0,
|
|
1992
|
-
enumerable: true
|
|
1993
|
-
},
|
|
1994
|
-
data: {
|
|
1995
|
-
get: () => envelope().data,
|
|
1996
|
-
enumerable: true
|
|
1997
|
-
},
|
|
1998
|
-
error: {
|
|
1999
|
-
get: () => envelope().error,
|
|
2000
|
-
enumerable: true
|
|
2001
35
|
}
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
clientAccessor[ACTION_MANIFEST_ID] = manifestId;
|
|
2007
|
-
attachWith(clientAccessor);
|
|
2008
|
-
return clientAccessor;
|
|
2009
|
-
}
|
|
2010
|
-
/**
|
|
2011
|
-
* Reactive side effect. Inside an island render, registers an ordered effect
|
|
2012
|
-
* slot that tracks signals read in the body, reruns on change, cleans up
|
|
2013
|
-
* before rerun and on unmount, and is client-only. Outside an island render,
|
|
2014
|
-
* behaves as the standalone reactive effect and returns a stop function.
|
|
2015
|
-
*
|
|
2016
|
-
* @returns Inside an island render, `void` (an effect slot is registered and
|
|
2017
|
-
* cleaned up automatically). At module scope, a stop function that disposes
|
|
2018
|
-
* the effect and runs its final cleanup. The union return type is deliberate:
|
|
2019
|
-
* the same call shape cannot be discriminated at the call site.
|
|
2020
|
-
*/
|
|
2021
|
-
const effect = Object.assign((fn) => {
|
|
2022
|
-
const frame = currentFrame();
|
|
2023
|
-
if (!frame) {
|
|
2024
|
-
let cleanup;
|
|
2025
|
-
let controller = null;
|
|
2026
|
-
const runCleanup = () => {
|
|
2027
|
-
if (typeof cleanup === "function") {
|
|
2028
|
-
try {
|
|
2029
|
-
cleanup();
|
|
2030
|
-
} catch (err) {
|
|
2031
|
-
console.error(err);
|
|
2032
|
-
}
|
|
2033
|
-
cleanup = void 0;
|
|
2034
|
-
}
|
|
2035
|
-
};
|
|
2036
|
-
const stop = ilhaEffect(() => {
|
|
2037
|
-
runCleanup();
|
|
2038
|
-
if (controller) controller.abort();
|
|
2039
|
-
controller = new AbortController();
|
|
2040
|
-
startBatch();
|
|
2041
|
-
try {
|
|
2042
|
-
cleanup = fn({ signal: controller.signal });
|
|
2043
|
-
} catch (err) {
|
|
2044
|
-
console.error(err);
|
|
2045
|
-
} finally {
|
|
2046
|
-
endBatch();
|
|
2047
|
-
}
|
|
2048
|
-
});
|
|
2049
|
-
return () => {
|
|
2050
|
-
stop();
|
|
2051
|
-
runCleanup();
|
|
2052
|
-
controller?.abort();
|
|
2053
|
-
};
|
|
2054
|
-
}
|
|
2055
|
-
const existing = acquireSlot("effect");
|
|
2056
|
-
if (existing) {
|
|
2057
|
-
existing.fn = fn;
|
|
2058
|
-
return;
|
|
2059
|
-
}
|
|
2060
|
-
if (!frame.creating) return;
|
|
2061
|
-
frame.slots[frame.cursor - 1] = {
|
|
2062
|
-
kind: "effect",
|
|
2063
|
-
fn
|
|
2064
|
-
};
|
|
2065
|
-
}, { once(fn) {
|
|
2066
|
-
if (acquireSlot("once")) return;
|
|
2067
|
-
const f = currentFrame();
|
|
2068
|
-
if (!f) throw new Error("[ilha] effect.once() called outside an island render. It registers per-instance setup and is only valid while an island component is rendering.");
|
|
2069
|
-
if (!f.creating) return;
|
|
2070
|
-
f.slots[f.cursor - 1] = {
|
|
2071
|
-
kind: "once",
|
|
2072
|
-
fn
|
|
2073
|
-
};
|
|
2074
|
-
} });
|
|
2075
|
-
/**
|
|
2076
|
-
* Register an error handler slot for this island. Handlers run in
|
|
2077
|
-
* declaration order; state, derived values, actions, and props should be
|
|
2078
|
-
* accessed through lexical closure rather than the context object.
|
|
2079
|
-
*/
|
|
2080
|
-
function onError(fn) {
|
|
2081
|
-
if (!currentFrame()) throw new Error("[ilha] onError() called outside an island render. Register it while an island component is rendering.");
|
|
2082
|
-
const existing = acquireSlot("error");
|
|
2083
|
-
if (existing) {
|
|
2084
|
-
existing.fn = fn;
|
|
2085
|
-
return;
|
|
2086
|
-
}
|
|
2087
|
-
const f = currentFrame();
|
|
2088
|
-
if (!f || !f.creating) return;
|
|
2089
|
-
f.slots[f.cursor - 1] = {
|
|
2090
|
-
kind: "error",
|
|
2091
|
-
fn
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
function parseStateSnapshot(raw) {
|
|
2095
|
-
const parsed = safeParseSnapshot(raw, STATE_ATTR);
|
|
2096
|
-
if (parsed === void 0) return void 0;
|
|
2097
|
-
const rec = parsed;
|
|
2098
|
-
if (rec["v"] !== 2) return;
|
|
2099
|
-
const out = { skipOnce: rec["_skipOnMount"] === true };
|
|
2100
|
-
if (Array.isArray(rec["s"])) {
|
|
2101
|
-
stripUnsafeKeys(rec["s"]);
|
|
2102
|
-
out.s = rec["s"];
|
|
2103
|
-
}
|
|
2104
|
-
if (Array.isArray(rec["d"])) {
|
|
2105
|
-
stripUnsafeKeys(rec["d"]);
|
|
2106
|
-
out.d = rec["d"].map((entry) => {
|
|
2107
|
-
const e = entry ?? {};
|
|
2108
|
-
const errorValue = e.error;
|
|
2109
|
-
return {
|
|
2110
|
-
loading: e.loading === true,
|
|
2111
|
-
value: e.value,
|
|
2112
|
-
error: errorValue == null ? void 0 : errorValue instanceof Error ? errorValue : new Error(String(errorValue))
|
|
2113
|
-
};
|
|
2114
|
-
});
|
|
2115
|
-
}
|
|
2116
|
-
return out;
|
|
36
|
+
const a = isAtomHandle(source) ? source.atom : source;
|
|
37
|
+
const unsub = fiber.registry.subscribe(a, fn, { immediate: true });
|
|
38
|
+
Effect.runSync(Scope.addFinalizer(fiber.scope, Effect.sync(unsub)));
|
|
39
|
+
}));
|
|
2117
40
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
const configuredSlotTag = options?.as === void 0 ? "div" : assertValidSlotTagName(options.as);
|
|
2134
|
-
function resolveInput(props) {
|
|
2135
|
-
const merged = { ...props };
|
|
2136
|
-
if (!schema) return merged;
|
|
2137
|
-
return validateSchema(schema, merged);
|
|
2138
|
-
}
|
|
2139
|
-
function renderWithCtx({ render, liveHost, asyncChildren, manifest }) {
|
|
2140
|
-
const ctx = pushRenderCtx(liveHost, asyncChildren, manifest);
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/wait.ts
|
|
44
|
+
function wait(body) {
|
|
45
|
+
return instr(Effect.gen(function* () {
|
|
46
|
+
const parent = getFiber();
|
|
47
|
+
const deferred = yield* Deferred.make();
|
|
48
|
+
let settled = false;
|
|
49
|
+
const done = (value) => {
|
|
50
|
+
if (settled) return;
|
|
51
|
+
settled = true;
|
|
52
|
+
Effect.runSync(Deferred.succeed(deferred, value));
|
|
53
|
+
};
|
|
54
|
+
const child = makeFiber(parent.runtime, parent.root, paint, { onFail: parent.fail });
|
|
55
|
+
interpret((() => body(done)), child);
|
|
2141
56
|
try {
|
|
2142
|
-
|
|
2143
|
-
const slots = ctx.slots;
|
|
2144
|
-
const binds = ctx.binds;
|
|
2145
|
-
const events = ctx.events;
|
|
2146
|
-
if (ctx.pending && ctx.pending.size > 0) {
|
|
2147
|
-
const pending = ctx.pending;
|
|
2148
|
-
return (async () => {
|
|
2149
|
-
return {
|
|
2150
|
-
html: await resolveAsyncChildren(html, pending),
|
|
2151
|
-
slots,
|
|
2152
|
-
binds,
|
|
2153
|
-
events
|
|
2154
|
-
};
|
|
2155
|
-
})();
|
|
2156
|
-
}
|
|
2157
|
-
return {
|
|
2158
|
-
html,
|
|
2159
|
-
slots,
|
|
2160
|
-
binds,
|
|
2161
|
-
events
|
|
2162
|
-
};
|
|
57
|
+
return yield* Deferred.await(deferred);
|
|
2163
58
|
} finally {
|
|
2164
|
-
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
/** Invoke the component inside the given frame + render context. */
|
|
2168
|
-
function runInFrame(frame, props) {
|
|
2169
|
-
return () => withFrame(frame, () => component(props));
|
|
2170
|
-
}
|
|
2171
|
-
function recordEventsManifest(eventsOut, events, frame) {
|
|
2172
|
-
if (!eventsOut || events.length === 0) return;
|
|
2173
|
-
events.forEach((record, index) => {
|
|
2174
|
-
const id = frame.actionManifest?.get(record.handler) ?? actionManifestId(record.handler);
|
|
2175
|
-
if (!id) return;
|
|
2176
|
-
const args = actionBoundArgs(record.handler);
|
|
2177
|
-
eventsOut.set(`${record.type}:${index}`, Array.isArray(args) ? {
|
|
2178
|
-
k: id,
|
|
2179
|
-
a: args
|
|
2180
|
-
} : id);
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
function renderToString(props, sync = false, eventsOut, forceAsyncChildren = false) {
|
|
2184
|
-
const input = resolveInput(props);
|
|
2185
|
-
const frame = freshFrame({ ssr: true });
|
|
2186
|
-
const first = forceAsyncChildren ? renderWithCtx({
|
|
2187
|
-
render: runInFrame(frame, input),
|
|
2188
|
-
asyncChildren: true,
|
|
2189
|
-
manifest: eventsOut !== void 0
|
|
2190
|
-
}) : renderWithCtx({
|
|
2191
|
-
render: runInFrame(frame, input),
|
|
2192
|
-
manifest: eventsOut !== void 0
|
|
2193
|
-
});
|
|
2194
|
-
const finish = (firstHtml) => firstHtml.html;
|
|
2195
|
-
const needsAsync = forceAsyncChildren || (frame.pendingDerived?.length ?? 0) > 0;
|
|
2196
|
-
if (first instanceof Promise) return first.then((out) => {
|
|
2197
|
-
recordEventsManifest(eventsOut, out.events, frame);
|
|
2198
|
-
return needsAsync ? awaitPending(out) : out.html;
|
|
2199
|
-
});
|
|
2200
|
-
recordEventsManifest(eventsOut, first.events, frame);
|
|
2201
|
-
if (!needsAsync || sync) {
|
|
2202
|
-
if (sync && (frame.pendingDerived?.length ?? 0) > 0 && __DEV__);
|
|
2203
|
-
return finish(first);
|
|
2204
|
-
}
|
|
2205
|
-
return awaitPending(first);
|
|
2206
|
-
function awaitPending(out) {
|
|
2207
|
-
const pendings = frame.pendingDerived ?? [];
|
|
2208
|
-
if (pendings.length === 0) return Promise.resolve(out.html);
|
|
2209
|
-
return Promise.all(pendings.map((p) => p.promise)).then(() => {
|
|
2210
|
-
frame.creating = false;
|
|
2211
|
-
frame.cursor = 0;
|
|
2212
|
-
const second = forceAsyncChildren ? renderWithCtx({
|
|
2213
|
-
render: runInFrame(frame, input),
|
|
2214
|
-
asyncChildren: true,
|
|
2215
|
-
manifest: eventsOut !== void 0
|
|
2216
|
-
}) : renderWithCtx({
|
|
2217
|
-
render: runInFrame(frame, input),
|
|
2218
|
-
manifest: eventsOut !== void 0
|
|
2219
|
-
});
|
|
2220
|
-
const done = (o) => {
|
|
2221
|
-
recordEventsManifest(eventsOut, o.events, frame);
|
|
2222
|
-
return o.html;
|
|
2223
|
-
};
|
|
2224
|
-
return second instanceof Promise ? second.then(done) : done(second);
|
|
2225
|
-
});
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
function mountIsland(host, props) {
|
|
2229
|
-
return mountIslandInternal(host, props).unmount;
|
|
2230
|
-
}
|
|
2231
|
-
function mountIslandInternal(host, props) {
|
|
2232
|
-
if (props === void 0) {
|
|
2233
|
-
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
2234
|
-
if (rawProps) {
|
|
2235
|
-
const parsed = safeParseSnapshot(rawProps, PROPS_ATTR);
|
|
2236
|
-
if (parsed !== void 0) props = reviveSlotProps(parsed);
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
const inputSignal = signal(resolveInput(props));
|
|
2240
|
-
const derivedPropsVersion = signal(0);
|
|
2241
|
-
const input = new Proxy({}, {
|
|
2242
|
-
get(_t, key) {
|
|
2243
|
-
return inputSignal()[key];
|
|
2244
|
-
},
|
|
2245
|
-
has(_t, key) {
|
|
2246
|
-
return key in inputSignal();
|
|
2247
|
-
},
|
|
2248
|
-
ownKeys() {
|
|
2249
|
-
const prevSub = setActiveSub(void 0);
|
|
2250
|
-
try {
|
|
2251
|
-
return Reflect.ownKeys(inputSignal());
|
|
2252
|
-
} finally {
|
|
2253
|
-
setActiveSub(prevSub);
|
|
2254
|
-
}
|
|
2255
|
-
},
|
|
2256
|
-
getOwnPropertyDescriptor(_t, key) {
|
|
2257
|
-
const prevSub = setActiveSub(void 0);
|
|
2258
|
-
try {
|
|
2259
|
-
return Reflect.getOwnPropertyDescriptor(inputSignal(), key);
|
|
2260
|
-
} finally {
|
|
2261
|
-
setActiveSub(prevSub);
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
});
|
|
2265
|
-
const snapshotRaw = host.getAttribute(STATE_ATTR);
|
|
2266
|
-
const snapshot = snapshotRaw ? parseStateSnapshot(snapshotRaw) : void 0;
|
|
2267
|
-
const hydrated = snapshot != null;
|
|
2268
|
-
const shouldSkipOnce = snapshot?.skipOnce === true;
|
|
2269
|
-
const cleanups = [];
|
|
2270
|
-
const unmountController = new AbortController();
|
|
2271
|
-
cleanups.push(() => unmountController.abort());
|
|
2272
|
-
const errorSlots = [];
|
|
2273
|
-
const reportError = (err, source) => {
|
|
2274
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
2275
|
-
if (errorSlots.length === 0) {
|
|
2276
|
-
if (!reportToGlobal(error, source)) console.error(error);
|
|
2277
|
-
return;
|
|
2278
|
-
}
|
|
2279
|
-
for (const slot of errorSlots) try {
|
|
2280
|
-
slot.fn({
|
|
2281
|
-
error,
|
|
2282
|
-
source,
|
|
2283
|
-
host
|
|
2284
|
-
});
|
|
2285
|
-
} catch (handlerErr) {
|
|
2286
|
-
console.error(handlerErr);
|
|
2287
|
-
}
|
|
2288
|
-
};
|
|
2289
|
-
const instance = {
|
|
2290
|
-
host,
|
|
2291
|
-
inputSignal,
|
|
2292
|
-
input,
|
|
2293
|
-
unmountController,
|
|
2294
|
-
cleanups,
|
|
2295
|
-
reportError,
|
|
2296
|
-
actionIds: /* @__PURE__ */ new Map(),
|
|
2297
|
-
stateSnapshot: snapshot ? {
|
|
2298
|
-
s: snapshot.s,
|
|
2299
|
-
d: snapshot.d
|
|
2300
|
-
} : void 0,
|
|
2301
|
-
hydrated,
|
|
2302
|
-
disposed: false,
|
|
2303
|
-
frame: null
|
|
2304
|
-
};
|
|
2305
|
-
cleanups.push(() => {
|
|
2306
|
-
instance.disposed = true;
|
|
2307
|
-
});
|
|
2308
|
-
const frame = freshFrame({
|
|
2309
|
-
ssr: false,
|
|
2310
|
-
instance
|
|
2311
|
-
});
|
|
2312
|
-
instance.frame = frame;
|
|
2313
|
-
const mountedSlots = /* @__PURE__ */ new Map();
|
|
2314
|
-
function teardownMountedSlot(id, entry) {
|
|
2315
|
-
mountedSlots.delete(id);
|
|
2316
|
-
entry.el.remove();
|
|
2317
|
-
entry.unmount();
|
|
2318
|
-
}
|
|
2319
|
-
function slotBelongsToHost(candidate) {
|
|
2320
|
-
let el = candidate.parentElement;
|
|
2321
|
-
while (el && el !== host) {
|
|
2322
|
-
if (el.hasAttribute(SLOT_ATTR) || el.hasAttribute("data-ilha")) return false;
|
|
2323
|
-
el = el.parentElement;
|
|
2324
|
-
}
|
|
2325
|
-
return el === host;
|
|
2326
|
-
}
|
|
2327
|
-
function buildSlotIndex() {
|
|
2328
|
-
const index = /* @__PURE__ */ new Map();
|
|
2329
|
-
for (const candidate of host.querySelectorAll(`[${SLOT_ATTR}]`)) {
|
|
2330
|
-
const id = candidate.getAttribute(SLOT_ATTR);
|
|
2331
|
-
if (id === null || index.has(id)) continue;
|
|
2332
|
-
if (slotBelongsToHost(candidate)) index.set(id, candidate);
|
|
2333
|
-
}
|
|
2334
|
-
return index;
|
|
2335
|
-
}
|
|
2336
|
-
function pushUpdatedProps(nextSlots) {
|
|
2337
|
-
for (const [id, entry] of mountedSlots) {
|
|
2338
|
-
const next = nextSlots.get(id);
|
|
2339
|
-
if (next) entry.updateProps(next.props);
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
function mountSlots(slotMap) {
|
|
2343
|
-
for (const [id, entry] of mountedSlots) if (!slotMap.has(id)) teardownMountedSlot(id, entry);
|
|
2344
|
-
for (const [id, entry] of mountedSlots) {
|
|
2345
|
-
const next = slotMap.get(id);
|
|
2346
|
-
if (next && next.island !== entry.island) teardownMountedSlot(id, entry);
|
|
2347
|
-
}
|
|
2348
|
-
let slotIndex = null;
|
|
2349
|
-
for (const [id, { island: childIsland, props: childProps }] of slotMap) {
|
|
2350
|
-
const existing = mountedSlots.get(id);
|
|
2351
|
-
if (existing) {
|
|
2352
|
-
existing.updateProps(childProps);
|
|
2353
|
-
continue;
|
|
2354
|
-
}
|
|
2355
|
-
if (slotIndex === null) slotIndex = buildSlotIndex();
|
|
2356
|
-
const slotEl = slotIndex.get(id) ?? null;
|
|
2357
|
-
if (!slotEl) continue;
|
|
2358
|
-
let slotProps = childProps;
|
|
2359
|
-
if (slotProps === void 0) {
|
|
2360
|
-
const rawProps = slotEl.getAttribute(PROPS_ATTR) ?? slotEl.getAttribute("data-props");
|
|
2361
|
-
if (rawProps) {
|
|
2362
|
-
const parsed = safeParseSnapshot(rawProps, `props on [${SLOT_ATTR}="${id}"]`);
|
|
2363
|
-
if (parsed !== void 0) slotProps = reviveSlotProps(parsed);
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
const prevSub = setActiveSub(void 0);
|
|
2367
|
-
let handle;
|
|
2368
|
-
try {
|
|
2369
|
-
const internal = childIsland[ISLAND_MOUNT_INTERNAL];
|
|
2370
|
-
handle = internal ? internal(slotEl, slotProps) : {
|
|
2371
|
-
unmount: childIsland.mount(slotEl, slotProps),
|
|
2372
|
-
updateProps: () => {}
|
|
2373
|
-
};
|
|
2374
|
-
} finally {
|
|
2375
|
-
setActiveSub(prevSub);
|
|
2376
|
-
}
|
|
2377
|
-
mountedSlots.set(id, {
|
|
2378
|
-
el: slotEl,
|
|
2379
|
-
island: childIsland,
|
|
2380
|
-
...handle
|
|
2381
|
-
});
|
|
2382
|
-
}
|
|
59
|
+
closeFiber(child);
|
|
2383
60
|
}
|
|
2384
|
-
const nativeOnceFired = /* @__PURE__ */ new WeakMap();
|
|
2385
|
-
let stopJsxEvents = () => {};
|
|
2386
|
-
let stopBindings = () => {};
|
|
2387
|
-
/** Register derived scopes before the render effect so source updates
|
|
2388
|
-
* settle derived envelopes before a render can consume them. */
|
|
2389
|
-
function startDerivedScopes() {
|
|
2390
|
-
for (const slot of frame.slots) if (slot.kind === "derived") startDerivedScope(slot);
|
|
2391
|
-
}
|
|
2392
|
-
/** Register side-effecting scopes after the render effect so prop-change
|
|
2393
|
-
* renders refresh their closures before those scopes execute. */
|
|
2394
|
-
function startEffectScopes() {
|
|
2395
|
-
for (const slot of frame.slots) if (slot.kind === "effect") startEffectScope(slot);
|
|
2396
|
-
else if (slot.kind === "once") startOnceScope(slot);
|
|
2397
|
-
else if (slot.kind === "error") errorSlots.push(slot);
|
|
2398
|
-
}
|
|
2399
|
-
function startDerivedScope(slot) {
|
|
2400
|
-
let ac = new AbortController();
|
|
2401
|
-
let skipFirst = slot.fromSnapshot;
|
|
2402
|
-
const stopScope = effect$1(() => {
|
|
2403
|
-
ac.abort();
|
|
2404
|
-
ac = new AbortController();
|
|
2405
|
-
const currentAc = ac;
|
|
2406
|
-
derivedPropsVersion();
|
|
2407
|
-
let result;
|
|
2408
|
-
try {
|
|
2409
|
-
result = slot.fn({ signal: currentAc.signal });
|
|
2410
|
-
} catch (err) {
|
|
2411
|
-
if (skipFirst) {
|
|
2412
|
-
skipFirst = false;
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
writeEnvelope(currentAc, {
|
|
2416
|
-
loading: false,
|
|
2417
|
-
value: void 0,
|
|
2418
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
2419
|
-
});
|
|
2420
|
-
return;
|
|
2421
|
-
}
|
|
2422
|
-
if (skipFirst) {
|
|
2423
|
-
skipFirst = false;
|
|
2424
|
-
if (result instanceof Promise) result.catch(() => {});
|
|
2425
|
-
return;
|
|
2426
|
-
}
|
|
2427
|
-
if (!(result instanceof Promise)) {
|
|
2428
|
-
if (isAsyncIterable(result)) {
|
|
2429
|
-
const iterable = result;
|
|
2430
|
-
writeEnvelope(currentAc, {
|
|
2431
|
-
loading: true,
|
|
2432
|
-
value: readEnv().value,
|
|
2433
|
-
error: void 0
|
|
2434
|
-
});
|
|
2435
|
-
(async () => {
|
|
2436
|
-
try {
|
|
2437
|
-
for await (const value of iterable) if (writeEnvelope(currentAc, {
|
|
2438
|
-
loading: false,
|
|
2439
|
-
value,
|
|
2440
|
-
error: void 0
|
|
2441
|
-
})) break;
|
|
2442
|
-
} catch (err) {
|
|
2443
|
-
writeEnvelope(currentAc, {
|
|
2444
|
-
loading: false,
|
|
2445
|
-
value: void 0,
|
|
2446
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
2447
|
-
});
|
|
2448
|
-
}
|
|
2449
|
-
})();
|
|
2450
|
-
return;
|
|
2451
|
-
}
|
|
2452
|
-
writeEnvelope(currentAc, {
|
|
2453
|
-
loading: false,
|
|
2454
|
-
value: result,
|
|
2455
|
-
error: void 0
|
|
2456
|
-
});
|
|
2457
|
-
return;
|
|
2458
|
-
}
|
|
2459
|
-
writeEnvelope(currentAc, {
|
|
2460
|
-
loading: true,
|
|
2461
|
-
value: readEnv().value,
|
|
2462
|
-
error: void 0
|
|
2463
|
-
});
|
|
2464
|
-
result.then((value) => {
|
|
2465
|
-
writeEnvelope(currentAc, {
|
|
2466
|
-
loading: false,
|
|
2467
|
-
value,
|
|
2468
|
-
error: void 0
|
|
2469
|
-
});
|
|
2470
|
-
}).catch((err) => {
|
|
2471
|
-
writeEnvelope(currentAc, {
|
|
2472
|
-
loading: false,
|
|
2473
|
-
value: void 0,
|
|
2474
|
-
error: err instanceof Error ? err : new Error(String(err))
|
|
2475
|
-
});
|
|
2476
|
-
});
|
|
2477
|
-
});
|
|
2478
|
-
cleanups.push(() => {
|
|
2479
|
-
stopScope();
|
|
2480
|
-
ac.abort();
|
|
2481
|
-
});
|
|
2482
|
-
function readEnv() {
|
|
2483
|
-
const prevSub = setActiveSub(void 0);
|
|
2484
|
-
try {
|
|
2485
|
-
return slot.env();
|
|
2486
|
-
} finally {
|
|
2487
|
-
setActiveSub(prevSub);
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
function writeEnvelope(ac, patch) {
|
|
2491
|
-
if (ac.signal.aborted) return true;
|
|
2492
|
-
const prevSub = setActiveSub(void 0);
|
|
2493
|
-
try {
|
|
2494
|
-
slot.env(patch);
|
|
2495
|
-
} finally {
|
|
2496
|
-
setActiveSub(prevSub);
|
|
2497
|
-
}
|
|
2498
|
-
return false;
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
function startEffectScope(slot) {
|
|
2502
|
-
let userCleanup;
|
|
2503
|
-
let runController = null;
|
|
2504
|
-
const stopScope = effect$1(() => {
|
|
2505
|
-
if (userCleanup) {
|
|
2506
|
-
try {
|
|
2507
|
-
userCleanup();
|
|
2508
|
-
} catch (err) {
|
|
2509
|
-
reportError(err, "effect");
|
|
2510
|
-
}
|
|
2511
|
-
userCleanup = void 0;
|
|
2512
|
-
}
|
|
2513
|
-
if (runController) runController.abort();
|
|
2514
|
-
runController = new AbortController();
|
|
2515
|
-
const runSignal = AbortSignal.any([unmountController.signal, runController.signal]);
|
|
2516
|
-
inputSignal();
|
|
2517
|
-
startBatch();
|
|
2518
|
-
try {
|
|
2519
|
-
userCleanup = slot.fn({ signal: runSignal });
|
|
2520
|
-
} catch (err) {
|
|
2521
|
-
reportError(err, "effect");
|
|
2522
|
-
} finally {
|
|
2523
|
-
endBatch();
|
|
2524
|
-
}
|
|
2525
|
-
});
|
|
2526
|
-
cleanups.push(() => {
|
|
2527
|
-
stopScope();
|
|
2528
|
-
if (userCleanup) try {
|
|
2529
|
-
userCleanup();
|
|
2530
|
-
} catch (err) {
|
|
2531
|
-
reportError(err, "effect");
|
|
2532
|
-
}
|
|
2533
|
-
if (runController) runController.abort();
|
|
2534
|
-
});
|
|
2535
|
-
}
|
|
2536
|
-
function startOnceScope(slot) {
|
|
2537
|
-
if (shouldSkipOnce) return;
|
|
2538
|
-
const prevSub = setActiveSub(void 0);
|
|
2539
|
-
let userCleanup;
|
|
2540
|
-
try {
|
|
2541
|
-
userCleanup = slot.fn({
|
|
2542
|
-
host,
|
|
2543
|
-
hydrated,
|
|
2544
|
-
signal: unmountController.signal
|
|
2545
|
-
});
|
|
2546
|
-
} catch (err) {
|
|
2547
|
-
reportError(err, "once");
|
|
2548
|
-
} finally {
|
|
2549
|
-
setActiveSub(prevSub);
|
|
2550
|
-
}
|
|
2551
|
-
if (userCleanup) {
|
|
2552
|
-
const teardown = userCleanup;
|
|
2553
|
-
cleanups.push(() => {
|
|
2554
|
-
try {
|
|
2555
|
-
teardown();
|
|
2556
|
-
} catch (err) {
|
|
2557
|
-
reportError(err, "once");
|
|
2558
|
-
}
|
|
2559
|
-
});
|
|
2560
|
-
}
|
|
2561
|
-
}
|
|
2562
|
-
const preserveSSRDom = hydrated && host.childNodes.length > 0;
|
|
2563
|
-
const initial = renderWithCtx({
|
|
2564
|
-
render: runInFrame(frame, input),
|
|
2565
|
-
liveHost: preserveSSRDom ? host : void 0
|
|
2566
|
-
});
|
|
2567
|
-
if (!preserveSSRDom) host.innerHTML = initial.html;
|
|
2568
|
-
const stopInitialBindingReflection = applyTemplateBindings(host, initial.binds, "reflect");
|
|
2569
|
-
let stopInitialBindingListeners = null;
|
|
2570
|
-
stopBindings = stopInitialBindingReflection;
|
|
2571
|
-
cleanups.push(() => stopBindings());
|
|
2572
|
-
stopJsxEvents = applyJsxEvents({
|
|
2573
|
-
host,
|
|
2574
|
-
records: initial.events,
|
|
2575
|
-
reportError: (error) => reportError(error, "event"),
|
|
2576
|
-
unmountSignal: unmountController.signal,
|
|
2577
|
-
onceFired: nativeOnceFired
|
|
2578
|
-
});
|
|
2579
|
-
cleanups.push(() => stopJsxEvents());
|
|
2580
|
-
mountSlots(initial.slots);
|
|
2581
|
-
cleanups.push(() => mountedSlots.forEach((entry) => entry.unmount()));
|
|
2582
|
-
stopInitialBindingListeners = applyTemplateBindings(host, initial.binds, "listen");
|
|
2583
|
-
stopBindings = () => {
|
|
2584
|
-
stopInitialBindingListeners();
|
|
2585
|
-
stopInitialBindingReflection();
|
|
2586
|
-
};
|
|
2587
|
-
let initialized = false;
|
|
2588
|
-
const initialRenderedHtml = initial.html;
|
|
2589
|
-
let lastRendered = null;
|
|
2590
|
-
let renderEpoch = 0;
|
|
2591
|
-
const renderReactive = () => {
|
|
2592
|
-
const epoch = ++renderEpoch;
|
|
2593
|
-
frame.creating = false;
|
|
2594
|
-
frame.cursor = 0;
|
|
2595
|
-
const { html: rendered, slots: newSlotMap, binds: newBinds, events: newEvents } = renderWithCtx({
|
|
2596
|
-
render: runInFrame(frame, input),
|
|
2597
|
-
liveHost: host
|
|
2598
|
-
});
|
|
2599
|
-
stopJsxEvents();
|
|
2600
|
-
stopJsxEvents = applyJsxEvents({
|
|
2601
|
-
host,
|
|
2602
|
-
records: newEvents,
|
|
2603
|
-
reportError: (error) => reportError(error, "event"),
|
|
2604
|
-
unmountSignal: unmountController.signal,
|
|
2605
|
-
onceFired: nativeOnceFired
|
|
2606
|
-
});
|
|
2607
|
-
if (!initialized) {
|
|
2608
|
-
initialized = true;
|
|
2609
|
-
if (preserveSSRDom) {
|
|
2610
|
-
pushUpdatedProps(newSlotMap);
|
|
2611
|
-
if (rendered === initialRenderedHtml) {
|
|
2612
|
-
lastRendered = rendered;
|
|
2613
|
-
return;
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
if (rendered === initialRenderedHtml) {
|
|
2617
|
-
lastRendered = rendered;
|
|
2618
|
-
return;
|
|
2619
|
-
}
|
|
2620
|
-
if (mountedSlots.size > 0 && mountedSlots.size === newSlotMap.size && [...newSlotMap.keys()].every((id) => mountedSlots.has(id)) && isStableInlineSlotMount({
|
|
2621
|
-
initialHtml: initialRenderedHtml,
|
|
2622
|
-
renderedHtml: rendered,
|
|
2623
|
-
slotIds: newSlotMap.keys()
|
|
2624
|
-
})) {
|
|
2625
|
-
pushUpdatedProps(newSlotMap);
|
|
2626
|
-
return;
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
if (rendered === lastRendered && mountedSlots.size === newSlotMap.size && [...newSlotMap].every(([id, next]) => mountedSlots.get(id)?.island === next.island)) {
|
|
2630
|
-
pushUpdatedProps(newSlotMap);
|
|
2631
|
-
return;
|
|
2632
|
-
}
|
|
2633
|
-
stopBindings();
|
|
2634
|
-
stopJsxEvents();
|
|
2635
|
-
const prevMountedCount = mountedSlots.size;
|
|
2636
|
-
for (const [id, entry] of mountedSlots) if (!newSlotMap.has(id)) teardownMountedSlot(id, entry);
|
|
2637
|
-
for (const [id, entry] of mountedSlots) {
|
|
2638
|
-
const next = newSlotMap.get(id);
|
|
2639
|
-
if (next && next.island !== entry.island) teardownMountedSlot(id, entry);
|
|
2640
|
-
}
|
|
2641
|
-
const applyMorph = () => {
|
|
2642
|
-
if (epoch !== renderEpoch) return;
|
|
2643
|
-
if (newSlotMap.size < prevMountedCount) {
|
|
2644
|
-
let divergeFrom = Infinity;
|
|
2645
|
-
for (const [id, entry] of mountedSlots) {
|
|
2646
|
-
if (!id.startsWith("p:")) continue;
|
|
2647
|
-
const next = newSlotMap.get(id);
|
|
2648
|
-
if (!next) continue;
|
|
2649
|
-
const index = Number(id.slice(2));
|
|
2650
|
-
if (Number.isNaN(index) || index >= divergeFrom) continue;
|
|
2651
|
-
if (serializeSlotProps(next.props) !== (entry.el.getAttribute(PROPS_ATTR) ?? "")) divergeFrom = index;
|
|
2652
|
-
}
|
|
2653
|
-
for (const [id, entry] of mountedSlots) {
|
|
2654
|
-
if (!newSlotMap.has(id)) continue;
|
|
2655
|
-
if (!entry.el.isConnected) continue;
|
|
2656
|
-
if (id.startsWith("p:") && Number(id.slice(2)) >= divergeFrom) teardownMountedSlot(id, entry);
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
const tpl = document.createElement("template");
|
|
2660
|
-
const morphRootTag = host.tagName.toLowerCase();
|
|
2661
|
-
tpl.innerHTML = `<${morphRootTag}>${rendered}</${morphRootTag}>`;
|
|
2662
|
-
morphInner(host, tpl.content.firstElementChild);
|
|
2663
|
-
lastRendered = rendered;
|
|
2664
|
-
let rehomeIndex = null;
|
|
2665
|
-
for (const [id, entry] of mountedSlots) {
|
|
2666
|
-
if (!newSlotMap.has(id) || entry.el.isConnected) continue;
|
|
2667
|
-
rehomeIndex ??= buildSlotIndex();
|
|
2668
|
-
const stub = rehomeIndex.get(id);
|
|
2669
|
-
if (stub && stub !== entry.el) stub.replaceWith(entry.el);
|
|
2670
|
-
}
|
|
2671
|
-
stopBindings = applyTemplateBindings(host, newBinds);
|
|
2672
|
-
stopJsxEvents = applyJsxEvents({
|
|
2673
|
-
host,
|
|
2674
|
-
records: newEvents,
|
|
2675
|
-
reportError: (error) => reportError(error, "event"),
|
|
2676
|
-
unmountSignal: unmountController.signal,
|
|
2677
|
-
onceFired: nativeOnceFired
|
|
2678
|
-
});
|
|
2679
|
-
mountSlots(newSlotMap);
|
|
2680
|
-
};
|
|
2681
|
-
applyMorph();
|
|
2682
|
-
};
|
|
2683
|
-
let stopRender = effect$1(renderReactive);
|
|
2684
|
-
startDerivedScopes();
|
|
2685
|
-
stopRender();
|
|
2686
|
-
stopRender = effect$1(renderReactive);
|
|
2687
|
-
startEffectScopes();
|
|
2688
|
-
let tornDown = false;
|
|
2689
|
-
const unmount = () => {
|
|
2690
|
-
if (tornDown) return;
|
|
2691
|
-
tornDown = true;
|
|
2692
|
-
instance.disposed = true;
|
|
2693
|
-
unmountController.abort();
|
|
2694
|
-
ISLAND_MOUNT_HANDLES.delete(host);
|
|
2695
|
-
stopRender();
|
|
2696
|
-
for (const [, entry] of mountedSlots) entry.unmount();
|
|
2697
|
-
for (const c of cleanups) c();
|
|
2698
|
-
};
|
|
2699
|
-
const updateProps = (nextProps) => {
|
|
2700
|
-
if (tornDown) return;
|
|
2701
|
-
const next = resolveInput(nextProps);
|
|
2702
|
-
if (shallowEqualInput(inputSignal(), next)) return;
|
|
2703
|
-
inputSignal(next);
|
|
2704
|
-
derivedPropsVersion(derivedPropsVersion() + 1);
|
|
2705
|
-
};
|
|
2706
|
-
const handle = {
|
|
2707
|
-
unmount,
|
|
2708
|
-
updateProps
|
|
2709
|
-
};
|
|
2710
|
-
ISLAND_MOUNT_HANDLES.set(host, handle);
|
|
2711
|
-
return handle;
|
|
2712
|
-
}
|
|
2713
|
-
const island = ((props) => ({
|
|
2714
|
-
[ISLAND_CALL]: true,
|
|
2715
|
-
island,
|
|
2716
|
-
props,
|
|
2717
|
-
key: void 0
|
|
2718
61
|
}));
|
|
2719
|
-
island.toString = (props) => renderToString(props, true);
|
|
2720
|
-
island.toStringAsync = (props) => Promise.resolve(renderToString(props, false));
|
|
2721
|
-
island.mount = (host, props) => mountIsland(host, props);
|
|
2722
|
-
island[ISLAND_MOUNT_INTERNAL] = (host, props) => mountIslandInternal(host, props);
|
|
2723
|
-
island[ISLAND_SLOT_TAG] = configuredSlotTag;
|
|
2724
|
-
island[ISLAND_RENDER_STATE] = async (props) => {
|
|
2725
|
-
const manifest = /* @__PURE__ */ new Map();
|
|
2726
|
-
const html = await renderToString(props ?? void 0, false, manifest, true);
|
|
2727
|
-
return serializeServerManifest(manifest) + html;
|
|
2728
|
-
};
|
|
2729
|
-
island[ISLAND_SSR_MANIFEST] = (props, eventsOut) => renderToString(props, false, eventsOut, true);
|
|
2730
|
-
island.key = (key) => {
|
|
2731
|
-
if (typeof key !== "string" || key.trim().length === 0) throw new Error("island.key() requires a non-empty string.");
|
|
2732
|
-
if (key.includes(":")) throw new Error(`island.key() key cannot contain the slot separator ":" (got "${key}").`);
|
|
2733
|
-
const keyed = ((props) => ({
|
|
2734
|
-
[ISLAND_CALL]: true,
|
|
2735
|
-
island,
|
|
2736
|
-
props,
|
|
2737
|
-
key
|
|
2738
|
-
}));
|
|
2739
|
-
keyed[ISLAND_CALL] = true;
|
|
2740
|
-
return keyed;
|
|
2741
|
-
};
|
|
2742
|
-
island[ISLAND] = true;
|
|
2743
|
-
const astroRendererName = globalThis[ASTRO_RENDERER_GLOBAL];
|
|
2744
|
-
if (typeof astroRendererName === "string") island[Symbol.for("astro:renderer")] = astroRendererName;
|
|
2745
|
-
island.define = (tagName, options) => {
|
|
2746
|
-
if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") {
|
|
2747
|
-
`${tagName}`;
|
|
2748
|
-
return;
|
|
2749
|
-
}
|
|
2750
|
-
const CE_RESERVED = /* @__PURE__ */ new Set([
|
|
2751
|
-
"annotation-xml",
|
|
2752
|
-
"color-profile",
|
|
2753
|
-
"font-face",
|
|
2754
|
-
"font-face-src",
|
|
2755
|
-
"font-face-uri",
|
|
2756
|
-
"font-face-format",
|
|
2757
|
-
"font-face-name",
|
|
2758
|
-
"missing-glyph"
|
|
2759
|
-
]);
|
|
2760
|
-
if (typeof tagName !== "string" || !/^[a-z][a-z0-9._-]*-[a-z0-9._-]*$/.test(tagName) || CE_RESERVED.has(tagName)) {
|
|
2761
|
-
`${tagName}`;
|
|
2762
|
-
return;
|
|
2763
|
-
}
|
|
2764
|
-
if (customElements.get(tagName)) {
|
|
2765
|
-
`${tagName}`;
|
|
2766
|
-
return;
|
|
2767
|
-
}
|
|
2768
|
-
const observe = options?.observe ?? [];
|
|
2769
|
-
class IlhaIslandElement extends HTMLElement {
|
|
2770
|
-
static observedAttributes = observe;
|
|
2771
|
-
_handle = null;
|
|
2772
|
-
_props;
|
|
2773
|
-
_unmounting = false;
|
|
2774
|
-
_reconnect = false;
|
|
2775
|
-
get props() {
|
|
2776
|
-
return this._props;
|
|
2777
|
-
}
|
|
2778
|
-
set props(p) {
|
|
2779
|
-
this._props = p;
|
|
2780
|
-
if (this._handle && !this._unmounting) this._handle.updateProps(this._mergedProps());
|
|
2781
|
-
}
|
|
2782
|
-
_mergedProps() {
|
|
2783
|
-
const attrProps = {};
|
|
2784
|
-
let hasAttrs = false;
|
|
2785
|
-
for (const name of observe) {
|
|
2786
|
-
const v = this.getAttribute(name);
|
|
2787
|
-
if (v !== null) {
|
|
2788
|
-
attrProps[name] = v;
|
|
2789
|
-
hasAttrs = true;
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
if (!hasAttrs && this._props === void 0) return void 0;
|
|
2793
|
-
return {
|
|
2794
|
-
...attrProps,
|
|
2795
|
-
...this._props
|
|
2796
|
-
};
|
|
2797
|
-
}
|
|
2798
|
-
connectedCallback() {
|
|
2799
|
-
if (this._unmounting) {
|
|
2800
|
-
this._reconnect = true;
|
|
2801
|
-
return;
|
|
2802
|
-
}
|
|
2803
|
-
if (this._handle) return;
|
|
2804
|
-
this._handle = mountIslandInternal(this, this._mergedProps());
|
|
2805
|
-
}
|
|
2806
|
-
disconnectedCallback() {
|
|
2807
|
-
if (!this._handle || this._unmounting) return;
|
|
2808
|
-
this._unmounting = true;
|
|
2809
|
-
this._reconnect = false;
|
|
2810
|
-
Promise.resolve(this._handle.unmount()).finally(() => {
|
|
2811
|
-
this._handle = null;
|
|
2812
|
-
this._unmounting = false;
|
|
2813
|
-
if (this._reconnect) {
|
|
2814
|
-
this._reconnect = false;
|
|
2815
|
-
if (this.isConnected) this._handle = mountIslandInternal(this, this._mergedProps());
|
|
2816
|
-
}
|
|
2817
|
-
});
|
|
2818
|
-
}
|
|
2819
|
-
attributeChangedCallback() {
|
|
2820
|
-
if (this._handle && !this._unmounting) this._handle.updateProps(this._mergedProps());
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
customElements.define(tagName, IlhaIslandElement);
|
|
2824
|
-
};
|
|
2825
|
-
island.hydratable = async (props, opts) => {
|
|
2826
|
-
const { name, as: rawTag = "div", snapshot = false, skipOnMount: explicitSkipOnMount } = opts;
|
|
2827
|
-
const tag = assertValidSlotTagName(rawTag);
|
|
2828
|
-
const resolvedProps = props ?? {};
|
|
2829
|
-
const innerPromise = renderToString(resolvedProps, false, void 0, true);
|
|
2830
|
-
let stateAttr = "";
|
|
2831
|
-
if (snapshot !== false) {
|
|
2832
|
-
const doState = snapshot === true || snapshot.state !== false;
|
|
2833
|
-
const doDerived = snapshot === true || snapshot.derived !== false;
|
|
2834
|
-
const doSkipOnMount = explicitSkipOnMount ?? false;
|
|
2835
|
-
const snapshotData = { v: 2 };
|
|
2836
|
-
const input = resolveInput(resolvedProps);
|
|
2837
|
-
const probeFrame = freshFrame({ ssr: true });
|
|
2838
|
-
withFrame(probeFrame, () => {
|
|
2839
|
-
component(input);
|
|
2840
|
-
});
|
|
2841
|
-
if (doState) {
|
|
2842
|
-
const states = [];
|
|
2843
|
-
for (const slot of probeFrame.slots) {
|
|
2844
|
-
if (slot.kind !== "state") continue;
|
|
2845
|
-
const prevSub = setActiveSub(void 0);
|
|
2846
|
-
try {
|
|
2847
|
-
states.push(slot.acc());
|
|
2848
|
-
} finally {
|
|
2849
|
-
setActiveSub(prevSub);
|
|
2850
|
-
}
|
|
2851
|
-
}
|
|
2852
|
-
snapshotData["s"] = states;
|
|
2853
|
-
}
|
|
2854
|
-
if (doDerived) {
|
|
2855
|
-
const probePendings = probeFrame.pendingDerived ?? [];
|
|
2856
|
-
if (probePendings.length > 0) {
|
|
2857
|
-
await Promise.all(probePendings.map((p) => p.promise));
|
|
2858
|
-
probeFrame.creating = false;
|
|
2859
|
-
probeFrame.cursor = 0;
|
|
2860
|
-
withFrame(probeFrame, () => {
|
|
2861
|
-
component(input);
|
|
2862
|
-
});
|
|
2863
|
-
}
|
|
2864
|
-
const derivedEntries = [];
|
|
2865
|
-
for (const slot of probeFrame.slots) {
|
|
2866
|
-
if (slot.kind !== "derived") continue;
|
|
2867
|
-
const prevSub = setActiveSub(void 0);
|
|
2868
|
-
let envelope;
|
|
2869
|
-
try {
|
|
2870
|
-
envelope = { ...slot.env() };
|
|
2871
|
-
} finally {
|
|
2872
|
-
setActiveSub(prevSub);
|
|
2873
|
-
}
|
|
2874
|
-
const entry = {
|
|
2875
|
-
loading: false,
|
|
2876
|
-
value: envelope.value
|
|
2877
|
-
};
|
|
2878
|
-
if (envelope.error) entry["error"] = envelope.error.message;
|
|
2879
|
-
derivedEntries.push(entry);
|
|
2880
|
-
}
|
|
2881
|
-
snapshotData["d"] = derivedEntries;
|
|
2882
|
-
}
|
|
2883
|
-
if (doSkipOnMount) snapshotData["_skipOnMount"] = true;
|
|
2884
|
-
stateAttr = ` ${STATE_ATTR}='${escapeHtml(JSON.stringify(snapshotData))}'`;
|
|
2885
|
-
}
|
|
2886
|
-
const inner = await innerPromise;
|
|
2887
|
-
return `<${tag} data-ilha="${escapeHtml(name)}" ${PROPS_ATTR}='${escapeHtml(JSON.stringify(resolvedProps))}'${stateAttr}>${inner}</${tag}>`;
|
|
2888
|
-
};
|
|
2889
|
-
return island;
|
|
2890
|
-
});
|
|
2891
|
-
function mountAll(registry, options = {}) {
|
|
2892
|
-
const root = options.root ?? document.body;
|
|
2893
|
-
const lazy = options.lazy ?? false;
|
|
2894
|
-
const unmounts = [];
|
|
2895
|
-
function activateEl(host) {
|
|
2896
|
-
const name = host.getAttribute("data-ilha");
|
|
2897
|
-
if (!name) return;
|
|
2898
|
-
const island = registry[name];
|
|
2899
|
-
if (!island) {
|
|
2900
|
-
`${name}${Object.keys(registry).join(", ")}`;
|
|
2901
|
-
return;
|
|
2902
|
-
}
|
|
2903
|
-
let props = {};
|
|
2904
|
-
const rawProps = host.getAttribute(PROPS_ATTR);
|
|
2905
|
-
if (rawProps) {
|
|
2906
|
-
const parsed = safeParseSnapshot(rawProps, `${PROPS_ATTR} on [data-ilha="${name}"]`);
|
|
2907
|
-
if (parsed !== void 0) props = reviveSlotProps(parsed);
|
|
2908
|
-
}
|
|
2909
|
-
unmounts.push(island.mount(host, props));
|
|
2910
|
-
}
|
|
2911
|
-
const els = Array.from(root.querySelectorAll("[data-ilha]"));
|
|
2912
|
-
if (lazy && typeof IntersectionObserver !== "undefined") {
|
|
2913
|
-
let disposed = false;
|
|
2914
|
-
const io = new IntersectionObserver((entries) => {
|
|
2915
|
-
if (disposed) return;
|
|
2916
|
-
for (const entry of entries) if (entry.isIntersecting) {
|
|
2917
|
-
activateEl(entry.target);
|
|
2918
|
-
io.unobserve(entry.target);
|
|
2919
|
-
}
|
|
2920
|
-
});
|
|
2921
|
-
els.forEach((el) => io.observe(el));
|
|
2922
|
-
unmounts.push(() => {
|
|
2923
|
-
disposed = true;
|
|
2924
|
-
io.disconnect();
|
|
2925
|
-
});
|
|
2926
|
-
} else els.forEach(activateEl);
|
|
2927
|
-
return { unmount: () => {
|
|
2928
|
-
const pending = [];
|
|
2929
|
-
for (const u of unmounts) {
|
|
2930
|
-
const result = u();
|
|
2931
|
-
if (result instanceof Promise) pending.push(result);
|
|
2932
|
-
}
|
|
2933
|
-
if (pending.length > 0) return Promise.all(pending).then(() => {});
|
|
2934
|
-
} };
|
|
2935
|
-
}
|
|
2936
|
-
setJsxRuntimeBridge({
|
|
2937
|
-
registerEvent({ type, handler, modifier }) {
|
|
2938
|
-
const ctx = currentRenderCtx();
|
|
2939
|
-
if (!ctx) return void 0;
|
|
2940
|
-
SSR_ACTION_STUB in handler;
|
|
2941
|
-
const index = ctx.events.length;
|
|
2942
|
-
ctx.events.push({
|
|
2943
|
-
type,
|
|
2944
|
-
handler,
|
|
2945
|
-
modifier
|
|
2946
|
-
});
|
|
2947
|
-
return index;
|
|
2948
|
-
},
|
|
2949
|
-
slot({ island, props, key }) {
|
|
2950
|
-
return ilhaRaw(emitIslandSlot({
|
|
2951
|
-
island,
|
|
2952
|
-
props,
|
|
2953
|
-
key
|
|
2954
|
-
}));
|
|
2955
|
-
}
|
|
2956
|
-
});
|
|
2957
|
-
/**
|
|
2958
|
-
* Morph an element's children toward `html`, patching in place: surviving
|
|
2959
|
-
* elements keep their identity, listeners, and focus (see the morph engine).
|
|
2960
|
-
* Falls back to an innerHTML write when the root tags don't match.
|
|
2961
|
-
*/
|
|
2962
|
-
function morph(host, html) {
|
|
2963
|
-
if (typeof document === "undefined") return;
|
|
2964
|
-
const openTag = host.localName ?? "div";
|
|
2965
|
-
const tpl = document.createElement("template");
|
|
2966
|
-
tpl.innerHTML = `<${openTag}>${html}</${openTag}>`;
|
|
2967
|
-
const next = tpl.content.firstElementChild;
|
|
2968
|
-
if (!next || next.localName !== host.localName) {
|
|
2969
|
-
host.innerHTML = html;
|
|
2970
|
-
return;
|
|
2971
|
-
}
|
|
2972
|
-
try {
|
|
2973
|
-
morphInner(host, next);
|
|
2974
|
-
} catch {
|
|
2975
|
-
host.innerHTML = html;
|
|
2976
|
-
}
|
|
2977
62
|
}
|
|
2978
|
-
const html = ilhaHtml;
|
|
2979
|
-
const raw = ilhaRaw;
|
|
2980
|
-
const mount = mountAll;
|
|
2981
|
-
const context = ilhaContext;
|
|
2982
63
|
|
|
2983
64
|
//#endregion
|
|
2984
|
-
export {
|
|
65
|
+
export { Fragment, atom, h, mount, renderToString, wait, watch, when };
|