sibujs 4.13.0 → 4.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.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +15 -2
  3. package/dist/browser.js +9 -7
  4. package/dist/build.cjs +880 -281
  5. package/dist/build.d.cts +68 -3
  6. package/dist/build.d.ts +68 -3
  7. package/dist/build.js +88 -30
  8. package/dist/cdn.dev.global.js +12 -12
  9. package/dist/cdn.full.dev.global.js +13 -13
  10. package/dist/cdn.full.global.js +12 -12
  11. package/dist/cdn.global.js +12 -12
  12. package/dist/{chunk-7K7W4AA5.js → chunk-2TTRW2TE.js} +6 -6
  13. package/dist/{chunk-J6FW5TV6.js → chunk-3HEX6QJQ.js} +5 -3
  14. package/dist/chunk-3HZOSMPX.js +90 -0
  15. package/dist/{chunk-GNSRXK34.js → chunk-4FJSAUKB.js} +11 -3
  16. package/dist/{chunk-IV74C2YX.js → chunk-4TQDTWWN.js} +6 -5
  17. package/dist/{chunk-L3QHS4O4.js → chunk-7ILEHTVX.js} +1 -1
  18. package/dist/{chunk-CK5F2IR5.js → chunk-A454RAEK.js} +4 -4
  19. package/dist/{chunk-BRRRVLHQ.js → chunk-AE32RGWX.js} +10 -6
  20. package/dist/{chunk-EXXSN3BW.js → chunk-B3HOT4TK.js} +1 -1
  21. package/dist/{chunk-F4W7WYXY.js → chunk-BKZ5N6NK.js} +8 -6
  22. package/dist/{chunk-MNEITZ27.js → chunk-BNXRWZZV.js} +49 -8
  23. package/dist/chunk-BOYKGGVD.js +2015 -0
  24. package/dist/{chunk-G6366I2A.js → chunk-CCKUNHVU.js} +2 -2
  25. package/dist/{chunk-346BYZL2.js → chunk-CJ7KS3DF.js} +16 -10
  26. package/dist/chunk-CNPT6NKV.js +1859 -0
  27. package/dist/{chunk-ENFRHCMK.js → chunk-F67F77IA.js} +2 -2
  28. package/dist/{chunk-TUCPL2HB.js → chunk-GIJ33UJZ.js} +1 -1
  29. package/dist/chunk-GVAMIU6K.js +195 -0
  30. package/dist/{chunk-HDKDBC6V.js → chunk-HQIAL7F2.js} +8 -6
  31. package/dist/{chunk-ZE5LF44R.js → chunk-JTFQ7IL4.js} +8 -8
  32. package/dist/{chunk-AM6DZLBM.js → chunk-LJMX4SQ7.js} +2 -2
  33. package/dist/{chunk-HVVRXRGO.js → chunk-MIRKTVH4.js} +7 -14
  34. package/dist/{chunk-YJ57VR6Y.js → chunk-N7A6FKWW.js} +5 -3
  35. package/dist/chunk-NT3ZYB5S.js +167 -0
  36. package/dist/{chunk-Z3EZX6DZ.js → chunk-QS6GMWDD.js} +151 -86
  37. package/dist/chunk-R5U7XKVJ.js +16 -0
  38. package/dist/{chunk-3C56VNCK.js → chunk-RVR5NPFL.js} +3 -3
  39. package/dist/{chunk-F2UWOZ6J.js → chunk-RVWJGWCL.js} +2 -2
  40. package/dist/{chunk-NYNYSPK7.js → chunk-S37PZD6I.js} +30 -45
  41. package/dist/{chunk-QV5WEOFD.js → chunk-SVULV7O4.js} +119 -26
  42. package/dist/{chunk-C2FAO6VQ.js → chunk-TLS4NVIB.js} +5 -5
  43. package/dist/{chunk-M25RIH6Y.js → chunk-UUIKJJIK.js} +10 -8
  44. package/dist/chunk-UYTYNCMZ.js +199 -0
  45. package/dist/chunk-VM2U5LEL.js +2596 -0
  46. package/dist/{chunk-24L5RDPS.js → chunk-Y4LUM2XS.js} +8 -6
  47. package/dist/data.cjs +265 -35
  48. package/dist/data.d.cts +31 -1
  49. package/dist/data.d.ts +31 -1
  50. package/dist/data.js +16 -10
  51. package/dist/devtools.cjs +15 -2
  52. package/dist/devtools.js +9 -7
  53. package/dist/{dispose-GEIG2KOF.js → dispose-EIHHHDC7.js} +3 -1
  54. package/dist/ecosystem.cjs +62 -23
  55. package/dist/ecosystem.js +14 -12
  56. package/dist/extras.cjs +3520 -270
  57. package/dist/extras.d.cts +3 -3
  58. package/dist/extras.d.ts +3 -3
  59. package/dist/extras.js +39 -29
  60. package/dist/index.cjs +442 -253
  61. package/dist/index.d.cts +93 -283
  62. package/dist/index.d.ts +93 -283
  63. package/dist/index.js +26 -16
  64. package/dist/motion.cjs +12 -1
  65. package/dist/motion.js +6 -4
  66. package/dist/patterns.cjs +15 -2
  67. package/dist/patterns.js +10 -8
  68. package/dist/performance.cjs +36 -2
  69. package/dist/performance.d.cts +2 -2
  70. package/dist/performance.d.ts +2 -2
  71. package/dist/performance.js +14 -10
  72. package/dist/plugins.cjs +5225 -1807
  73. package/dist/plugins.d.cts +8 -834
  74. package/dist/plugins.d.ts +8 -834
  75. package/dist/plugins.js +64 -2329
  76. package/dist/prerenderServer-MXJEVWA3.js +350 -0
  77. package/dist/renderRoute-DP20XwMS.d.cts +86 -0
  78. package/dist/renderRoute-fxgsm2Ir.d.ts +86 -0
  79. package/dist/routeHead-CE9SV-cH.d.cts +995 -0
  80. package/dist/routeHead-DzVxPviY.d.ts +995 -0
  81. package/dist/{routeMiddleware-B1sp4P7a.d.ts → routeMiddleware-BySbueoE.d.ts} +0 -25
  82. package/dist/{routeMiddleware-CedQTsLK.d.cts → routeMiddleware-CijZD5eW.d.cts} +0 -25
  83. package/dist/ssr-JpGpHnpC.d.cts +841 -0
  84. package/dist/ssr-JpGpHnpC.d.ts +841 -0
  85. package/dist/{ssr-VBM6J5SJ.js → ssr-NECEU4X4.js} +15 -6
  86. package/dist/ssr-dom.cjs +1862 -0
  87. package/dist/ssr-dom.d.cts +341 -0
  88. package/dist/ssr-dom.d.ts +341 -0
  89. package/dist/ssr-dom.js +19 -0
  90. package/dist/ssr.cjs +7630 -2064
  91. package/dist/ssr.d.cts +63 -5
  92. package/dist/ssr.d.ts +63 -5
  93. package/dist/ssr.js +136 -14
  94. package/dist/testing.cjs +6543 -87
  95. package/dist/testing.d.cts +80 -1
  96. package/dist/testing.d.ts +80 -1
  97. package/dist/testing.js +117 -4
  98. package/dist/ui.cjs +36 -2
  99. package/dist/ui.js +17 -14
  100. package/dist/widgets.cjs +14 -1
  101. package/dist/widgets.js +11 -9
  102. package/package.json +9 -2
  103. package/dist/chunk-2F4PSJ5Z.js +0 -765
  104. package/dist/chunk-7XHATCIH.js +0 -121
  105. package/dist/chunk-B36ADEIR.js +0 -21
  106. package/dist/ssr-Db0Sgdu3.d.cts +0 -245
  107. package/dist/ssr-Db0Sgdu3.d.ts +0 -245
@@ -0,0 +1,2015 @@
1
+ import {
2
+ ensureServerDom,
3
+ explainServerError,
4
+ watchBrowserApis
5
+ } from "./chunk-CNPT6NKV.js";
6
+ import {
7
+ embedSerializationTail,
8
+ embedSerializedAttribute,
9
+ getEmbedRecord
10
+ } from "./chunk-LJMX4SQ7.js";
11
+ import {
12
+ collectTransfer
13
+ } from "./chunk-3HZOSMPX.js";
14
+ import {
15
+ readOwnGlobal
16
+ } from "./chunk-62BDYGAG.js";
17
+ import {
18
+ isCodeTextElement,
19
+ isForbiddenMetaEntry,
20
+ isForbiddenMetaSnapshot,
21
+ isRefusedAnimationAttribute,
22
+ resolveMetaRefreshPolicy
23
+ } from "./chunk-RVWJGWCL.js";
24
+ import {
25
+ canonicalAttrName,
26
+ isEventHandlerAttr,
27
+ isHtmlContentAttribute,
28
+ resolveAttributeValue,
29
+ sanitizeUrl
30
+ } from "./chunk-B3HOT4TK.js";
31
+ import {
32
+ assertRequestIsolation,
33
+ getRequestStore,
34
+ getSSRStore,
35
+ isServer,
36
+ requestSlot,
37
+ runInBoundary,
38
+ runInSSRContext,
39
+ setBoundaryHook
40
+ } from "./chunk-UYTYNCMZ.js";
41
+ import {
42
+ untracked
43
+ } from "./chunk-MIRKTVH4.js";
44
+ import {
45
+ dispose,
46
+ replaceChildrenSafely
47
+ } from "./chunk-S37PZD6I.js";
48
+ import {
49
+ DEV,
50
+ beginHydration,
51
+ inAdoptScope,
52
+ inHydration,
53
+ inScope
54
+ } from "./chunk-NT3ZYB5S.js";
55
+
56
+ // src/utils/headEntry.ts
57
+ var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
58
+ function isEmittableHeadAttrName(name) {
59
+ if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
60
+ const canonical = canonicalAttrName(name);
61
+ if (isEventHandlerAttr(canonical)) return false;
62
+ if (isHtmlContentAttribute(canonical)) return false;
63
+ return true;
64
+ }
65
+ function sanitizeHeadAttrValue(canonical, value) {
66
+ return resolveAttributeValue(canonical, value);
67
+ }
68
+ function findDuplicateAttributeName(names) {
69
+ const seen = /* @__PURE__ */ new Set();
70
+ for (const name of names) {
71
+ const canonical = canonicalAttrName(name);
72
+ if (seen.has(canonical)) return canonical;
73
+ seen.add(canonical);
74
+ }
75
+ return null;
76
+ }
77
+ function planEntry(props, resolver, applyRefreshPolicy) {
78
+ const rawNames = Object.keys(props);
79
+ const duplicate = findDuplicateAttributeName(rawNames);
80
+ if (duplicate !== null) {
81
+ return { kind: "drop", reason: `duplicate case-insensitive attribute ${JSON.stringify(duplicate)}` };
82
+ }
83
+ const attributes = /* @__PURE__ */ new Map();
84
+ let reactive = false;
85
+ for (const name of rawNames) {
86
+ if (!Object.hasOwn(props, name)) continue;
87
+ if (!isEmittableHeadAttrName(name)) continue;
88
+ const authored = props[name];
89
+ if (resolver.isReactiveValue?.(authored)) reactive = true;
90
+ const canonical = canonicalAttrName(name);
91
+ const sanitized = sanitizeHeadAttrValue(canonical, resolver.resolveValue(name, authored));
92
+ if (sanitized === null) continue;
93
+ attributes.set(canonical, sanitized);
94
+ }
95
+ if (attributes.size === 0) return { kind: "drop", reason: "no effective attributes remain" };
96
+ const refresh = applyRefreshPolicy ? resolveMetaRefreshPolicy(attributes) : { kind: "not-refresh" };
97
+ if (refresh.kind === "forbidden") return { kind: "drop", reason: refresh.reason };
98
+ if (reactive && (refresh.kind === "allowed" || refresh.kind === "delay-only")) {
99
+ return { kind: "drop", reason: "a reactive meta entry may not publish a native refresh directive" };
100
+ }
101
+ return { kind: "publish", attributes, refresh };
102
+ }
103
+ function planMetaEntry(props, resolver) {
104
+ return planEntry(props, resolver, true);
105
+ }
106
+ function planHeadElementEntry(props, resolver) {
107
+ return planEntry(props, resolver, false);
108
+ }
109
+ var STATIC_VALUE_RESOLVER = {
110
+ resolveValue: (_name, value) => String(value)
111
+ };
112
+ function escapeHeadAttrValue(value) {
113
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
114
+ }
115
+ function serializeHeadEntry(tag, attrs) {
116
+ const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
117
+ if (plan.kind === "drop") return "";
118
+ return serializePlannedHeadEntry(tag, plan.attributes);
119
+ }
120
+ function serializePlannedHeadEntry(tag, attributes) {
121
+ const pairs = Array.from(attributes, ([name, value]) => `${name}="${escapeHeadAttrValue(value)}"`).join(" ");
122
+ return `<${tag} ${pairs} />`;
123
+ }
124
+
125
+ // src/platform/serverHead.ts
126
+ function headSlot() {
127
+ return requestSlot("head", () => ({ layers: [] }));
128
+ }
129
+ function recordServerHead(source) {
130
+ const slot = headSlot();
131
+ if (!slot) return null;
132
+ const entry = { source };
133
+ slot.layers.push(entry);
134
+ return () => {
135
+ if (entry.source === null) return;
136
+ entry.source = null;
137
+ const index = slot.layers.indexOf(entry);
138
+ if (index !== -1) slot.layers.splice(index, 1);
139
+ };
140
+ }
141
+ function collectServerHead() {
142
+ const slot = headSlot();
143
+ if (!slot || slot.layers.length === 0) return "";
144
+ let title;
145
+ const order = [];
146
+ const byKey = /* @__PURE__ */ new Map();
147
+ for (const entry of slot.layers.slice()) {
148
+ const source = entry.source;
149
+ if (source === null) continue;
150
+ let layer;
151
+ try {
152
+ layer = typeof source === "function" ? untracked(source) : source;
153
+ } catch (err) {
154
+ const message = err instanceof Error ? err.message : String(err);
155
+ throw new Error(`[SibuJS SSR] a head value threw while the page head was collected: ${message}`, {
156
+ cause: err
157
+ });
158
+ }
159
+ if (!layer) continue;
160
+ if (layer.title !== void 0) title = layer.title;
161
+ for (const tag of layer.tags) {
162
+ if (!byKey.has(tag.key)) order.push(tag.key);
163
+ byKey.set(tag.key, tag.html);
164
+ }
165
+ }
166
+ const out = [];
167
+ if (title != null) out.push(`<title>${escapeText(title)}</title>`);
168
+ for (const key of order) out.push(byKey.get(key));
169
+ return out.join("\n ");
170
+ }
171
+ function escapeText(str) {
172
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
173
+ }
174
+
175
+ // src/platform/ssrAsync.ts
176
+ var DEFAULT_SETTLE_TIMEOUT = 5e3;
177
+ var DEFAULT_TRANSFER_MAX_BYTES = 1024 * 1024;
178
+ var TRANSFER_GLOBAL = "__SIBU_TRANSFER__";
179
+ var macrotask = () => new Promise((resolve) => {
180
+ if (typeof setImmediate === "function") setImmediate(resolve);
181
+ else setTimeout(resolve, 0);
182
+ });
183
+ async function settled(_root, options = {}) {
184
+ const store = getRequestStore();
185
+ const timeout = options.timeout ?? DEFAULT_SETTLE_TIMEOUT;
186
+ const deadline = Date.now() + timeout;
187
+ for (; ; ) {
188
+ await macrotask();
189
+ const pending = store?.pending;
190
+ if (!pending || pending.size === 0) {
191
+ await macrotask();
192
+ if (!pending || pending.size === 0) return { timedOut: false, pending: [] };
193
+ continue;
194
+ }
195
+ const remaining = deadline - Date.now();
196
+ if (remaining <= 0) return { timedOut: true, pending: describePending(pending) };
197
+ await new Promise((resolve) => {
198
+ const done = () => {
199
+ clearTimeout(timer);
200
+ if (store && store.wake === done) store.wake = null;
201
+ resolve();
202
+ };
203
+ const timer = setTimeout(done, remaining);
204
+ if (store) store.wake = done;
205
+ });
206
+ }
207
+ }
208
+ function describePending(pending) {
209
+ const now = Date.now();
210
+ return Array.from(pending, (work) => `${work.label} (${now - work.since} ms)`);
211
+ }
212
+ function formatRenderDiagnostics(d, label = "render") {
213
+ const lines = [
214
+ `[SibuJS SSR] ${label}: ${d.durationMs} ms, ${d.timedOut ? "TIMED OUT (serialized with fallbacks)" : "settled"}`
215
+ ];
216
+ if (d.pending.length > 0) lines.push(` pending at the timeout: ${d.pending.join(", ")}`);
217
+ if (d.browserApis.length > 0) lines.push(` browser-only APIs read: ${d.browserApis.join("; ")}`);
218
+ if (d.stateBytes > 0) {
219
+ lines.push(
220
+ ` transferred state: ${d.stateBytes} B; largest: ${d.largestState.map((e) => `${e.key} (${e.bytes} B)`).join(", ")}`
221
+ );
222
+ }
223
+ if (d.droppedState.length > 0) lines.push(` left out of the state: ${d.droppedState.join(", ")}`);
224
+ return lines.join("\n");
225
+ }
226
+ function describeNodePath(node) {
227
+ const parts = [];
228
+ let current = node;
229
+ while (current && parts.length < 8) {
230
+ if (current.nodeType === 1) {
231
+ const el = current;
232
+ let part = el.localName;
233
+ const id = el.getAttribute("id");
234
+ if (id) part += `#${id}`;
235
+ const cls = el.getAttribute("class")?.trim().split(/\s+/)[0];
236
+ if (cls) part += `.${cls}`;
237
+ parts.unshift(part);
238
+ }
239
+ current = current.parentNode;
240
+ }
241
+ return parts.length > 0 ? parts.join(" > ") : "(detached)";
242
+ }
243
+ function renderFailure(entry) {
244
+ const error = explainServerError(entry.error);
245
+ const message = error instanceof Error ? error.message : String(error);
246
+ const where = entry.context.node ? describeNodePath(entry.context.node) : "(unknown position)";
247
+ const label = entry.context.name ? ` in ${entry.context.name}` : "";
248
+ return new Error(`[SibuJS SSR] render failed${label} at ${where}: ${message}`, { cause: error });
249
+ }
250
+ function byteLength(text) {
251
+ return typeof TextEncoder !== "undefined" ? new TextEncoder().encode(text).byteLength : text.length;
252
+ }
253
+ function buildTransfer(data, maxBytes, nonce) {
254
+ var _a;
255
+ const entries = [];
256
+ const dropped = [];
257
+ for (const section of Object.keys(data)) {
258
+ if (section === "v") continue;
259
+ const sectionEntries = data[section];
260
+ for (const key of Object.keys(sectionEntries)) {
261
+ let json;
262
+ try {
263
+ json = JSON.stringify(sectionEntries[key]);
264
+ } catch {
265
+ json = void 0;
266
+ }
267
+ if (json === void 0) {
268
+ dropped.push(`${section}:${key}`);
269
+ continue;
270
+ }
271
+ entries.push({ section, key, value: sectionEntries[key], bytes: byteLength(json) + byteLength(key) + 4 });
272
+ }
273
+ }
274
+ entries.sort((a, b) => b.bytes - a.bytes);
275
+ const largest = entries.slice(0, 5).map((e) => ({ key: `${e.section}:${e.key}`, bytes: e.bytes }));
276
+ let total = entries.reduce((sum, e) => sum + e.bytes, 0);
277
+ let start = 0;
278
+ while (total > maxBytes && start < entries.length) {
279
+ dropped.push(`${entries[start].section}:${entries[start].key}`);
280
+ total -= entries[start].bytes;
281
+ start++;
282
+ }
283
+ const kept = entries.slice(start);
284
+ if (dropped.length > 0 && DEV) {
285
+ console.warn(
286
+ `[SibuJS SSR] transferred state exceeded ${maxBytes} bytes or was not serializable; left out: ${dropped.join(", ")}. Largest entries: ${largest.map((e) => `${e.key} (${e.bytes} B)`).join(", ")}. Those queries fetch again in the browser. Raise \`maxStateBytes\` or mark them \`ssr: false\`.`
287
+ );
288
+ }
289
+ if (kept.length === 0) return { script: "", data: null, bytes: 0, largest, dropped };
290
+ const out = { v: data.v };
291
+ for (const e of kept) {
292
+ const section = out[_a = e.section] ?? (out[_a] = /* @__PURE__ */ Object.create(null));
293
+ section[e.key] = e.value;
294
+ }
295
+ const script = serializeState(out, nonce, { maxBytes: Math.max(maxBytes * 2, 1024), global: TRANSFER_GLOBAL });
296
+ return { script, data: out, bytes: total, largest, dropped };
297
+ }
298
+ async function renderCore(input, options) {
299
+ const started = Date.now();
300
+ const store = getRequestStore();
301
+ const errors = [];
302
+ if (store) store.errors = errors;
303
+ ensureServerDom();
304
+ watchBrowserApis();
305
+ const ids = options.hydration === "adopt" ? /* @__PURE__ */ new WeakMap() : null;
306
+ const scope = ids ? createServerAdoptScope(ids) : null;
307
+ let root;
308
+ try {
309
+ root = typeof input === "function" ? inScope(scope, input) : input;
310
+ } catch (err) {
311
+ if (store) store.errors = void 0;
312
+ throw explainServerError(err);
313
+ }
314
+ const holder = document.createDocumentFragment();
315
+ holder.appendChild(root);
316
+ const settle = await settled(holder, { timeout: options.timeout });
317
+ if (store) store.errors = void 0;
318
+ const releaseTree = () => {
319
+ for (const child of Array.from(holder.childNodes)) dispose(child);
320
+ };
321
+ if (errors.length > 0) {
322
+ releaseTree();
323
+ throw renderFailure(errors[0]);
324
+ }
325
+ if (settle.timedOut && DEV) {
326
+ console.warn(
327
+ `[SibuJS SSR] render did not settle within ${options.timeout ?? DEFAULT_SETTLE_TIMEOUT} ms; serialized with fallbacks for: ${settle.pending.join(", ")}`
328
+ );
329
+ }
330
+ const content = renderToStringWith(holder, { marker: options.marker ?? "root", ids });
331
+ let head;
332
+ try {
333
+ head = collectServerHead();
334
+ } catch (err) {
335
+ releaseTree();
336
+ throw err;
337
+ }
338
+ let state = "";
339
+ let stateData = null;
340
+ let stateBytes = 0;
341
+ let largestState = [];
342
+ let droppedState = [];
343
+ if (options.transfer !== false) {
344
+ const collected = collectTransfer();
345
+ if (collected) {
346
+ const built = buildTransfer(collected, options.maxStateBytes ?? DEFAULT_TRANSFER_MAX_BYTES, options.nonce);
347
+ state = built.script;
348
+ stateData = built.data;
349
+ stateBytes = built.bytes;
350
+ largestState = built.largest;
351
+ droppedState = built.dropped;
352
+ }
353
+ }
354
+ releaseTree();
355
+ const browserApis = Array.from(store?.slots?.get("browserApis") ?? []);
356
+ const diagnostics = {
357
+ timedOut: settle.timedOut,
358
+ pending: settle.pending,
359
+ browserApis,
360
+ stateBytes,
361
+ largestState,
362
+ droppedState,
363
+ durationMs: Date.now() - started
364
+ };
365
+ if (DEV && options.log) {
366
+ console.info(formatRenderDiagnostics(diagnostics, typeof options.log === "string" ? options.log : void 0));
367
+ }
368
+ return { content, result: { head, state, stateData, status: 200, diagnostics } };
369
+ }
370
+ function inRequest(api, fn) {
371
+ try {
372
+ assertRequestIsolation(api);
373
+ } catch (err) {
374
+ return Promise.reject(err);
375
+ }
376
+ return getRequestStore() ? fn() : runInSSRContext(fn);
377
+ }
378
+ function renderToStringAsync(input, options = {}) {
379
+ return inRequest("renderToStringAsync()", async () => {
380
+ const { content, result } = await renderCore(input, options);
381
+ return { html: content, ...result };
382
+ });
383
+ }
384
+ function renderToDocumentAsync(input, options = {}) {
385
+ return inRequest("renderToDocumentAsync()", async () => {
386
+ const { content, result } = await renderCore(input, options);
387
+ return { html: assembleDocument(content, options, result.head, result.state), ...result };
388
+ });
389
+ }
390
+
391
+ // src/platform/hydrateAdopt.ts
392
+ var ADOPT_ID_ATTR = "data-sibu-h";
393
+ function createServerAdoptScope(ids) {
394
+ const sink = {
395
+ on: true,
396
+ el(tag, ns, id) {
397
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
398
+ ids.set(el, id);
399
+ return el;
400
+ }
401
+ };
402
+ return { p: "", n: 0, s: sink };
403
+ }
404
+ var STICKY = {
405
+ img: ["src", "srcset", "sizes"],
406
+ source: ["src", "srcset", "sizes"],
407
+ video: ["src", "poster"],
408
+ audio: ["src"],
409
+ track: ["src"],
410
+ iframe: ["src"],
411
+ embed: ["src"],
412
+ object: ["data"],
413
+ input: ["value", "checked", "type"],
414
+ textarea: [],
415
+ select: [],
416
+ option: ["value", "selected"],
417
+ details: ["open"],
418
+ dialog: ["open"]
419
+ };
420
+ function snapshotSelect(select) {
421
+ const options = Array.from(select.options);
422
+ const seen = /* @__PURE__ */ new Map();
423
+ const chosen = [];
424
+ for (const node of options) {
425
+ const occurrence = seen.get(node.value) ?? 0;
426
+ seen.set(node.value, occurrence + 1);
427
+ if (node.selected) chosen.push({ node, value: node.value, occurrence });
428
+ }
429
+ return { chosen, server: new Set(options) };
430
+ }
431
+ var nativeAppend = (parent, child) => Node.prototype.appendChild.call(parent, child);
432
+ var nativeInsert = (parent, child, ref) => Node.prototype.insertBefore.call(parent, child, ref);
433
+ var nativeReplace = (parent, child, old) => Node.prototype.replaceChild.call(parent, child, old);
434
+ function sameKind(a, b) {
435
+ return a.nodeType === b.nodeType && (a.nodeType === 3 || a.nodeType === 8);
436
+ }
437
+ function isOptionDisabled(option) {
438
+ const parent = option.parentElement;
439
+ return option.disabled || parent?.localName === "optgroup" && parent.hasAttribute("disabled");
440
+ }
441
+ function defaultSelection(select) {
442
+ const options = Array.from(select.options);
443
+ const parsed = reparsedSelection(select, options);
444
+ if (parsed) return parsed;
445
+ const marked = options.filter((o) => o.hasAttribute("selected"));
446
+ if (select.multiple) return new Set(marked);
447
+ if (marked.length > 0) return /* @__PURE__ */ new Set([marked[marked.length - 1]]);
448
+ if (select.size > 1) return /* @__PURE__ */ new Set();
449
+ const first = options.find((o) => !isOptionDisabled(o));
450
+ return new Set(first ? [first] : []);
451
+ }
452
+ function reparsedSelection(select, options) {
453
+ if (typeof DOMParser !== "function") return null;
454
+ try {
455
+ const doc = new DOMParser().parseFromString(`<!doctype html><body>${select.outerHTML}`, "text/html");
456
+ const copy = doc.querySelector("select");
457
+ if (!copy || copy.options.length !== options.length) return null;
458
+ const chosen = /* @__PURE__ */ new Set();
459
+ Array.from(copy.options).forEach((o, i) => {
460
+ if (o.selected) chosen.add(options[i]);
461
+ });
462
+ return chosen;
463
+ } catch {
464
+ return null;
465
+ }
466
+ }
467
+ function isDirty(el) {
468
+ const tag = el.localName;
469
+ if (tag === "input") {
470
+ const input = el;
471
+ if (input.type === "checkbox" || input.type === "radio") return input.checked !== input.hasAttribute("checked");
472
+ return input.value !== (input.getAttribute("value") ?? "");
473
+ }
474
+ if (tag === "textarea") return el.value !== (el.textContent ?? "");
475
+ if (tag === "select") {
476
+ const select = el;
477
+ const initial = defaultSelection(select);
478
+ return Array.from(select.options).some((o) => o.selected !== initial.has(o));
479
+ }
480
+ return false;
481
+ }
482
+ var AdoptSession = class {
483
+ constructor(root, options) {
484
+ this.root = root;
485
+ this.options = options;
486
+ this.byId = /* @__PURE__ */ new Map();
487
+ this.server = /* @__PURE__ */ new WeakSet();
488
+ this.claimed = /* @__PURE__ */ new Map();
489
+ this.cursors = /* @__PURE__ */ new WeakMap();
490
+ this.patched = [];
491
+ this.touched = /* @__PURE__ */ new Set();
492
+ this.mismatches = [];
493
+ /**
494
+ * The selection of each `<select>` the user changed before takeover. Taken
495
+ * before any client code runs: the client builds a select's options BEFORE
496
+ * the select, and an option's `selected` write changes the selection long
497
+ * before the select itself is claimed. Session-scoped: the references go
498
+ * with the session.
499
+ */
500
+ this.selections = /* @__PURE__ */ new Map();
501
+ /**
502
+ * Checkboxes and radios the user changed before takeover, and their state —
503
+ * for the same reason: checking a radio unchecks its group, so the client
504
+ * writing a sibling (or a sibling joining the group) changes this one first.
505
+ */
506
+ this.checks = /* @__PURE__ */ new Map();
507
+ /** Form controls whose state differed from their markup when the session started: the user's. */
508
+ this.dirty = /* @__PURE__ */ new Set();
509
+ this.created = 0;
510
+ this.removed = 0;
511
+ /** AdoptSink: numbering and claiming are on for the length of the session. */
512
+ this.on = true;
513
+ /** AdoptSink: the hydration session work built in this sink's scopes belongs to. */
514
+ this.h = null;
515
+ this.restored = false;
516
+ const walk = (node) => {
517
+ for (let child = node.firstChild; child; child = child.nextSibling) {
518
+ this.server.add(child);
519
+ if (child.nodeType === 1) {
520
+ const id = child.getAttribute(ADOPT_ID_ATTR);
521
+ if (id !== null && !this.byId.has(id)) this.byId.set(id, child);
522
+ const tag = child.localName;
523
+ if ((tag === "select" || tag === "input" || tag === "textarea") && isDirty(child)) {
524
+ this.dirty.add(child);
525
+ if (tag === "select") {
526
+ this.selections.set(child, snapshotSelect(child));
527
+ } else if (tag === "input") {
528
+ const input = child;
529
+ if (input.type === "checkbox" || input.type === "radio") this.checks.set(input, input.checked);
530
+ }
531
+ }
532
+ walk(child);
533
+ }
534
+ }
535
+ };
536
+ walk(root);
537
+ this.patchParent(root);
538
+ }
539
+ report(m) {
540
+ const message = m.kind === "tag" ? `server rendered <${m.serverValue}>, the client built <${m.clientValue}> \u2014 the subtree is replaced` : m.kind === "missing" ? `the client built <${m.clientValue}>, which the server did not render` : m.kind === "extra" ? `the server rendered ${m.serverValue}, which the client did not build \u2014 removed` : `text differs: server "${m.serverValue}", client "${m.clientValue}"`;
541
+ const report = { ...m, message };
542
+ this.mismatches.push(report);
543
+ if (this.options.onMismatch) this.options.onMismatch(report);
544
+ else if (DEV && this.mismatches.length <= 10) {
545
+ console.warn(
546
+ `[SibuJS hydration] adopt mismatch at ${m.path}${m.id ? ` (id ${m.id})` : ""}: ${message}.
547
+ Likely cause: ${m.cause}`
548
+ );
549
+ }
550
+ }
551
+ // ── AdoptSink: claim the server element with this id ──
552
+ el(tag, ns, id) {
553
+ if (!this.on) return null;
554
+ const candidate = this.byId.get(id);
555
+ const want = ns ?? "http://www.w3.org/1999/xhtml";
556
+ if (candidate && candidate.localName === tag && (candidate.namespaceURI ?? want) === want && !this.claimed.has(candidate)) {
557
+ this.byId.delete(id);
558
+ this.claim(candidate);
559
+ return candidate;
560
+ }
561
+ this.created++;
562
+ if (candidate) {
563
+ this.report({
564
+ kind: "tag",
565
+ path: describeNodePath(candidate),
566
+ id,
567
+ serverValue: candidate.localName,
568
+ clientValue: tag,
569
+ cause: "the server and the client took different branches: a browser-only read (window, matchMedia, storage) during render, data that differs between them, or a non-deterministic render (Date.now(), Math.random()). Render browser-only content with ClientOnly()."
570
+ });
571
+ } else {
572
+ this.report({
573
+ kind: "missing",
574
+ path: "(new element)",
575
+ id,
576
+ serverValue: "(none)",
577
+ clientValue: tag,
578
+ cause: "the client built more than the server: data the server did not have (a query with ssr: false, a timed-out fetch), or a branch that depends on the browser. ClientOnly() renders it after takeover."
579
+ });
580
+ }
581
+ return null;
582
+ }
583
+ claim(el) {
584
+ el.removeAttribute(ADOPT_ID_ATTR);
585
+ const keep = STICKY[el.localName] ?? [];
586
+ const sticky = /* @__PURE__ */ new Set();
587
+ for (const attr of Array.from(el.attributes)) {
588
+ if (keep.includes(attr.name)) {
589
+ const safe = resolveAttributeValue(attr.name, attr.value, { element: el });
590
+ if (safe === null) el.removeAttribute(attr.name);
591
+ else {
592
+ if (safe !== attr.value) el.setAttribute(attr.name, safe);
593
+ sticky.add(attr.name);
594
+ }
595
+ } else el.removeAttribute(attr.name);
596
+ }
597
+ this.claimed.set(el, { sticky });
598
+ if (this.dirty.has(el)) this.touched.add(el);
599
+ this.patchParent(el);
600
+ }
601
+ /**
602
+ * Instance overrides on an adopted parent for the length of the session —
603
+ * the core's own `appendChild` / `insertBefore` calls then place children
604
+ * on the server nodes instead of moving them. Removed when the session ends.
605
+ */
606
+ patchParent(parent) {
607
+ const session = this;
608
+ const record = this.claimed.get(parent);
609
+ this.cursors.set(parent, parent.firstChild);
610
+ this.patched.push(parent);
611
+ const own = parent;
612
+ own.appendChild = function(child) {
613
+ if (inAdoptScope() || child.nodeType === 11) return session.place(this, child);
614
+ return nativeAppend(this, child);
615
+ };
616
+ own.insertBefore = function(child, ref) {
617
+ return session.insertAt(this, child, ref);
618
+ };
619
+ own.replaceChildren = function(...nodes) {
620
+ session.cursors.set(this, this.firstChild);
621
+ for (const n of nodes) session.place(this, typeof n === "string" ? document.createTextNode(n) : n);
622
+ let rest = session.cursors.get(this) ?? null;
623
+ while (rest) {
624
+ const next = rest.nextSibling;
625
+ this.removeChild(rest);
626
+ rest = next;
627
+ }
628
+ };
629
+ const textDesc = Object.getOwnPropertyDescriptor(Node.prototype, "textContent");
630
+ if (textDesc?.set && textDesc.get) {
631
+ Object.defineProperty(parent, "textContent", {
632
+ configurable: true,
633
+ get() {
634
+ return textDesc.get?.call(this);
635
+ },
636
+ set(value) {
637
+ const text = value == null ? "" : String(value);
638
+ const only = this.firstChild;
639
+ if (only && only === this.lastChild && only.nodeType === 3 && only.data === text) {
640
+ session.server.delete(only);
641
+ return;
642
+ }
643
+ textDesc.set?.call(this, value);
644
+ }
645
+ });
646
+ }
647
+ if (record) {
648
+ const note = (name) => record.sticky.delete(String(name).toLowerCase());
649
+ const { getAttribute, setAttribute, removeAttribute } = Element.prototype;
650
+ own.getAttribute = function(name) {
651
+ note(name);
652
+ return getAttribute.call(this, name);
653
+ };
654
+ own.setAttribute = function(name, value) {
655
+ note(name);
656
+ return setAttribute.call(this, name, value);
657
+ };
658
+ own.removeAttribute = function(name) {
659
+ note(name);
660
+ return removeAttribute.call(this, name);
661
+ };
662
+ if (this.touched.has(parent) && parent.localName !== "select") {
663
+ for (const prop of ["value", "checked"]) {
664
+ if (prop in parent) {
665
+ Object.defineProperty(parent, prop, {
666
+ configurable: true,
667
+ get() {
668
+ const desc = findDescriptor(Object.getPrototypeOf(this), prop);
669
+ return desc?.get?.call(this);
670
+ },
671
+ set() {
672
+ }
673
+ });
674
+ }
675
+ }
676
+ }
677
+ }
678
+ }
679
+ /** Put `child` at the cursor of `parent`. */
680
+ place(parent, child) {
681
+ if (child.nodeType === 11) {
682
+ for (const n of Array.from(child.childNodes)) this.place(parent, n);
683
+ return child;
684
+ }
685
+ let cur = this.cursors.has(parent) ? this.cursors.get(parent) ?? null : null;
686
+ if (child === cur) {
687
+ this.cursors.set(parent, child.nextSibling);
688
+ return child;
689
+ }
690
+ if (child.parentNode === parent && this.claimed.has(child) && this.follows(cur, child)) {
691
+ this.cursors.set(parent, child.nextSibling);
692
+ return child;
693
+ }
694
+ if (!child.parentNode && (child.nodeType === 3 || child.nodeType === 8)) {
695
+ const match = this.findCounterpart(cur, child);
696
+ if (match) cur = match;
697
+ }
698
+ if (cur && this.server.has(cur) && !this.claimed.has(cur) && sameKind(cur, child) && !child.parentNode) {
699
+ if (child.nodeType === 3) {
700
+ const serverText = cur;
701
+ const clientText = child.data;
702
+ if (clientText !== "" && serverText.data !== clientText && serverText.data.startsWith(clientText)) {
703
+ serverText.data = serverText.data.slice(clientText.length);
704
+ nativeInsert(parent, child, serverText);
705
+ return child;
706
+ }
707
+ if (serverText.data !== clientText) {
708
+ this.report({
709
+ kind: "text",
710
+ path: describeNodePath(parent),
711
+ id: null,
712
+ serverValue: serverText.data.slice(0, 80),
713
+ clientValue: clientText.slice(0, 80),
714
+ cause: "the text was computed differently: a non-deterministic value (Date.now(), Math.random(), a locale-dependent format) or data that changed between render and takeover. The client text is kept."
715
+ });
716
+ }
717
+ }
718
+ this.server.delete(cur);
719
+ this.cursors.set(parent, cur.nextSibling);
720
+ nativeReplace(parent, child, cur);
721
+ return child;
722
+ }
723
+ nativeInsert(parent, child, cur);
724
+ return child;
725
+ }
726
+ /** Is `node` at or after `from` among its siblings? */
727
+ follows(from, node) {
728
+ for (let n = from; n; n = n.nextSibling) if (n === node) return true;
729
+ return false;
730
+ }
731
+ /**
732
+ * The server node a fresh text / comment child stands for: the first
733
+ * unconsumed server node of the same kind from `from` on (a comment must
734
+ * carry the same text), skipping unclaimed server nodes in between — a
735
+ * preceding directive's branch content — but never an adopted element.
736
+ */
737
+ findCounterpart(from, child) {
738
+ for (let n = from; n; n = n.nextSibling) {
739
+ if (!this.server.has(n)) return null;
740
+ if (n.nodeType === 1 && this.claimed.has(n)) return null;
741
+ if (n.nodeType !== child.nodeType) continue;
742
+ if (n.nodeType === 8 && n.data !== child.data) continue;
743
+ return n;
744
+ }
745
+ return null;
746
+ }
747
+ /** `insertBefore` on an adopted parent: no-op when already in place; a fresh text/comment takes the server one's place. */
748
+ insertAt(parent, child, ref) {
749
+ if (child === ref || child.parentNode === parent && child.nextSibling === ref) return child;
750
+ if (child.nodeType === 11) {
751
+ for (const n of Array.from(child.childNodes)) this.insertAt(parent, n, ref);
752
+ return child;
753
+ }
754
+ if (ref && !child.parentNode && this.server.has(ref) && sameKind(ref, child)) {
755
+ if (child.nodeType === 3 && ref.data !== child.data) {
756
+ this.report({
757
+ kind: "text",
758
+ path: describeNodePath(parent),
759
+ id: null,
760
+ serverValue: ref.data.slice(0, 80),
761
+ clientValue: child.data.slice(0, 80),
762
+ cause: "the text was computed differently: a non-deterministic value (Date.now(), Math.random(), a locale-dependent format) or data that changed between render and takeover. The client text is kept."
763
+ });
764
+ }
765
+ this.server.delete(ref);
766
+ if (this.cursors.get(parent) === ref) this.cursors.set(parent, ref.nextSibling);
767
+ nativeReplace(parent, child, ref);
768
+ return child;
769
+ }
770
+ return nativeInsert(parent, child, ref);
771
+ }
772
+ /** Remove every instance override; idempotent, and also run when the build throws. */
773
+ restore() {
774
+ this.on = false;
775
+ if (this.restored) return;
776
+ this.restored = true;
777
+ for (const node of this.patched) {
778
+ const own = node;
779
+ for (const key of [
780
+ "appendChild",
781
+ "insertBefore",
782
+ "replaceChildren",
783
+ "textContent",
784
+ "getAttribute",
785
+ "setAttribute",
786
+ "removeAttribute",
787
+ "value",
788
+ "checked"
789
+ ]) {
790
+ if (Object.hasOwn(own, key)) delete own[key];
791
+ }
792
+ }
793
+ }
794
+ /** End of the session: restore the instances, release unclaimed server nodes, replay input. */
795
+ finish() {
796
+ this.restore();
797
+ const sweep = (node) => {
798
+ for (let child = node.firstChild; child; ) {
799
+ const next = child.nextSibling;
800
+ if (this.server.has(child) && !this.claimed.has(child)) {
801
+ if (child.nodeType === 1 && child.hasAttribute(ADOPT_ID_ATTR)) {
802
+ this.report({
803
+ kind: "extra",
804
+ path: describeNodePath(child),
805
+ id: child.getAttribute(ADOPT_ID_ATTR),
806
+ serverValue: `<${child.localName}>`,
807
+ clientValue: "(none)",
808
+ cause: "the client built less than the server, or built it under another scope: a branch that renders differently in the browser, or a component whose structure depends on timing."
809
+ });
810
+ }
811
+ child.parentNode?.removeChild(child);
812
+ this.removed++;
813
+ } else if (child.nodeType === 1) sweep(child);
814
+ child = next;
815
+ }
816
+ };
817
+ sweep(this.root);
818
+ for (const [el, record] of this.claimed) {
819
+ for (const name of record.sticky) el.removeAttribute(name);
820
+ }
821
+ reselect(this);
822
+ for (const el of this.touched) {
823
+ el.dispatchEvent(new Event("input", { bubbles: true }));
824
+ el.dispatchEvent(new Event("change", { bubbles: true }));
825
+ }
826
+ }
827
+ };
828
+ function survivingSelection(select, snapshot) {
829
+ const options = Array.from(select.options);
830
+ const present = new Set(options);
831
+ const result = /* @__PURE__ */ new Set();
832
+ const rebuilt = /* @__PURE__ */ new Map();
833
+ for (const o of options) {
834
+ if (snapshot.server.has(o)) continue;
835
+ const list = rebuilt.get(o.value);
836
+ if (list) list.push(o);
837
+ else rebuilt.set(o.value, [o]);
838
+ }
839
+ const lost = [];
840
+ for (const entry of snapshot.chosen) {
841
+ if (present.has(entry.node) && entry.node.value === entry.value) result.add(entry.node);
842
+ else lost.push(entry);
843
+ }
844
+ for (const entry of lost) {
845
+ const candidates = rebuilt.get(entry.value);
846
+ if (!candidates) continue;
847
+ const atOccurrence = candidates[entry.occurrence];
848
+ const match = atOccurrence && !result.has(atOccurrence) ? atOccurrence : candidates.find((o) => !result.has(o));
849
+ if (match) result.add(match);
850
+ }
851
+ return result;
852
+ }
853
+ function reselect(session) {
854
+ for (const [select, snapshot] of session.selections) {
855
+ if (!session.claimed.has(select) || !session.root.contains(select)) {
856
+ session.touched.delete(select);
857
+ continue;
858
+ }
859
+ if (snapshot.chosen.length === 0) {
860
+ select.selectedIndex = -1;
861
+ continue;
862
+ }
863
+ const keep = survivingSelection(select, snapshot);
864
+ if (keep.size === 0) {
865
+ session.touched.delete(select);
866
+ continue;
867
+ }
868
+ if (select.multiple) for (const o of Array.from(select.options)) o.selected = keep.has(o);
869
+ else keep.values().next().value.selected = true;
870
+ }
871
+ const live = Array.from(session.checks).filter(([el]) => session.claimed.has(el) && session.root.contains(el));
872
+ for (const [el, checked] of live) if (!checked) el.checked = false;
873
+ for (const [el, checked] of live) if (checked) el.checked = true;
874
+ }
875
+ function findDescriptor(proto, key) {
876
+ for (let p = proto; p; p = Object.getPrototypeOf(p)) {
877
+ const d = Object.getOwnPropertyDescriptor(p, key);
878
+ if (d) return d;
879
+ }
880
+ return void 0;
881
+ }
882
+ var macrotask2 = () => new Promise((resolve) => {
883
+ if (typeof MessageChannel === "function") {
884
+ const channel = new MessageChannel();
885
+ channel.port1.onmessage = () => {
886
+ channel.port1.close();
887
+ resolve();
888
+ };
889
+ channel.port2.postMessage(null);
890
+ } else setTimeout(resolve, 0);
891
+ });
892
+ async function hydrateAdopt(component, container, options = {}) {
893
+ const hydration = beginHydration();
894
+ const session = new AdoptSession(container, options);
895
+ session.h = hydration;
896
+ const rootScope = { p: "", n: 0, s: session };
897
+ let replacedRoot = false;
898
+ try {
899
+ const tree = inHydration(hydration, () => inScope(rootScope, component));
900
+ if (tree.parentNode !== container) {
901
+ if (tree.nodeType === 1 && !session.claimed.has(tree)) {
902
+ replacedRoot = true;
903
+ if (DEV && !options.onMismatch) {
904
+ console.warn(
905
+ '[SibuJS hydration] adopt: the root element did not match the server markup, so the whole tree is replaced. Was the server render made with { hydration: "adopt" } and the same root component?'
906
+ );
907
+ }
908
+ }
909
+ inScope(rootScope, () => session.place(container, tree));
910
+ }
911
+ const deadline = Date.now() + (options.timeout ?? 1e4);
912
+ const { pending, wake } = hydration;
913
+ for (; ; ) {
914
+ await macrotask2();
915
+ if (pending.size === 0) {
916
+ await macrotask2();
917
+ if (pending.size === 0) break;
918
+ continue;
919
+ }
920
+ const remaining = deadline - Date.now();
921
+ if (remaining <= 0) break;
922
+ await new Promise((resolve) => {
923
+ const done = () => {
924
+ clearTimeout(timer);
925
+ wake.delete(done);
926
+ resolve();
927
+ };
928
+ const timer = setTimeout(done, remaining);
929
+ wake.add(done);
930
+ });
931
+ }
932
+ session.finish();
933
+ } finally {
934
+ session.restore();
935
+ hydration.end();
936
+ }
937
+ container.setAttribute("data-sibu-hydrated", "adopt");
938
+ return {
939
+ adopted: session.claimed.size,
940
+ created: session.created,
941
+ removed: session.removed,
942
+ mismatches: session.mismatches,
943
+ replacedRoot
944
+ };
945
+ }
946
+
947
+ // src/platform/componentIsland.ts
948
+ var ISLAND_PROPS_ATTR = "data-sibu-props";
949
+ var SAFE_NAME = /^[A-Za-z0-9_-]+$/;
950
+ function componentIsland(name, component, props, options = {}) {
951
+ if (!SAFE_NAME.test(name)) {
952
+ throw new Error(`[SibuJS SSR] island: name must match [A-Za-z0-9_-]+ (got: ${JSON.stringify(name.slice(0, 32))})`);
953
+ }
954
+ const wrapper = document.createElement("div");
955
+ wrapper.setAttribute("data-sibu-island", name);
956
+ if (options.load) wrapper.setAttribute("data-sibu-load", options.load);
957
+ if (options.media) wrapper.setAttribute("data-sibu-media", options.media);
958
+ wrapper.setAttribute("style", "display: contents");
959
+ let json;
960
+ try {
961
+ json = JSON.stringify(props ?? null);
962
+ } catch (err) {
963
+ throw new Error(`[SibuJS SSR] island "${name}": props must be JSON-serializable`, { cause: err });
964
+ }
965
+ wrapper.setAttribute(ISLAND_PROPS_ATTR, json);
966
+ const scope = isServer() ? {
967
+ p: "",
968
+ n: 0,
969
+ s: {
970
+ on: true,
971
+ el(tag, ns, id) {
972
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
973
+ el.setAttribute(ADOPT_ID_ATTR, id);
974
+ return el;
975
+ }
976
+ }
977
+ } : null;
978
+ wrapper.appendChild(inScope(scope, () => component(props)));
979
+ return wrapper;
980
+ }
981
+ function readProps(root) {
982
+ const raw = root.getAttribute(ISLAND_PROPS_ATTR);
983
+ if (raw === null) return void 0;
984
+ try {
985
+ return JSON.parse(raw);
986
+ } catch {
987
+ return void 0;
988
+ }
989
+ }
990
+ function islandComponent(component, validate) {
991
+ return (ctx) => {
992
+ const props = readProps(ctx.root);
993
+ if (validate && !validate(props)) {
994
+ throw new Error(`[SibuJS islands] props of "${ctx.root.getAttribute("data-sibu-island")}" failed validation`);
995
+ }
996
+ let disposed = false;
997
+ const settle = () => {
998
+ if (disposed) for (const child of Array.from(ctx.root.childNodes)) dispose(child);
999
+ };
1000
+ hydrateAdopt(() => component(props), ctx.root).then(settle, (err) => {
1001
+ settle();
1002
+ if (typeof console !== "undefined") {
1003
+ console.error(`[SibuJS islands] "${ctx.root.getAttribute("data-sibu-island")}" failed to hydrate:`, err);
1004
+ }
1005
+ });
1006
+ return () => {
1007
+ disposed = true;
1008
+ for (const child of Array.from(ctx.root.childNodes)) dispose(child);
1009
+ };
1010
+ };
1011
+ }
1012
+
1013
+ // src/platform/ssrStream.ts
1014
+ var hookInstalled = false;
1015
+ function installHook() {
1016
+ if (hookInstalled) return;
1017
+ hookInstalled = true;
1018
+ setBoundaryHook((container) => {
1019
+ const state = requestSlot("stream");
1020
+ const store = getRequestStore();
1021
+ if (!state || !store) return null;
1022
+ const id = `sibu-sus-${store.suspenseIdCounter++}`;
1023
+ container.setAttribute("data-sibu-suspense-id", id);
1024
+ const record = {
1025
+ id,
1026
+ container,
1027
+ done: false,
1028
+ emitted: false,
1029
+ fallbackHtml: null,
1030
+ api: {
1031
+ enter: (fn) => {
1032
+ if (record.fallbackHtml === null && state.serialize) {
1033
+ record.fallbackHtml = Array.from(container.childNodes, (n) => state.serialize?.(n) ?? "").join("");
1034
+ }
1035
+ return runInBoundary(record.api, fn);
1036
+ },
1037
+ done: () => {
1038
+ record.done = true;
1039
+ store.wake?.();
1040
+ }
1041
+ }
1042
+ };
1043
+ state.boundaries.push(record);
1044
+ return record.api;
1045
+ });
1046
+ }
1047
+ function createChannel() {
1048
+ const chunks = [];
1049
+ let taken = null;
1050
+ let arrived = null;
1051
+ let ended = false;
1052
+ let cancelled = false;
1053
+ let error;
1054
+ return {
1055
+ get cancelled() {
1056
+ return cancelled;
1057
+ },
1058
+ async push(chunk) {
1059
+ if (cancelled || chunk === "") return;
1060
+ chunks.push(chunk);
1061
+ arrived?.();
1062
+ arrived = null;
1063
+ await new Promise((resolve) => {
1064
+ taken = resolve;
1065
+ });
1066
+ },
1067
+ end() {
1068
+ ended = true;
1069
+ arrived?.();
1070
+ },
1071
+ fail(err) {
1072
+ error = err;
1073
+ ended = true;
1074
+ arrived?.();
1075
+ },
1076
+ /** Called on cancel, so a renderer waiting for a boundary stops at once. */
1077
+ onCancel: null,
1078
+ cancel() {
1079
+ cancelled = true;
1080
+ taken?.();
1081
+ this.onCancel?.();
1082
+ },
1083
+ async take() {
1084
+ for (; ; ) {
1085
+ if (chunks.length > 0) {
1086
+ const value = chunks.shift();
1087
+ const release = taken;
1088
+ taken = null;
1089
+ release?.();
1090
+ return { value, done: false };
1091
+ }
1092
+ if (error !== void 0) throw error;
1093
+ if (ended) return { done: true };
1094
+ await new Promise((resolve) => {
1095
+ arrived = resolve;
1096
+ });
1097
+ }
1098
+ }
1099
+ };
1100
+ }
1101
+ var macrotask3 = () => new Promise((resolve) => {
1102
+ if (typeof setImmediate === "function") setImmediate(resolve);
1103
+ else setTimeout(resolve, 0);
1104
+ });
1105
+ async function waitFor(ready, deadline, stop) {
1106
+ const store = getRequestStore();
1107
+ for (; ; ) {
1108
+ await macrotask3();
1109
+ if (stop()) return false;
1110
+ if (ready()) {
1111
+ await macrotask3();
1112
+ if (ready()) return true;
1113
+ continue;
1114
+ }
1115
+ const remaining = deadline - Date.now();
1116
+ if (remaining <= 0) return false;
1117
+ await new Promise((resolve) => {
1118
+ const done = () => {
1119
+ clearTimeout(timer);
1120
+ if (store && store.wake === done) store.wake = null;
1121
+ resolve();
1122
+ };
1123
+ const timer = setTimeout(done, Math.min(remaining, 50));
1124
+ if (store) store.wake = done;
1125
+ });
1126
+ }
1127
+ }
1128
+ function pendingFor(pending, boundary) {
1129
+ if (!pending) return false;
1130
+ for (const work of pending) if (work.boundary === boundary) return true;
1131
+ return false;
1132
+ }
1133
+ function streamPage(input, options = {}) {
1134
+ assertRequestIsolation("renderToReadableStream()");
1135
+ installHook();
1136
+ const channel = createChannel();
1137
+ const nonce = options.nonce;
1138
+ const doc = options.document === false ? null : options.document ?? {};
1139
+ const MARK_BODY = "<sibu-stream-body>";
1140
+ const MARK_END = "<sibu-stream-end>";
1141
+ void runInSSRContext(async () => {
1142
+ const store = getRequestStore();
1143
+ const state = { boundaries: [], serialize: null };
1144
+ requestSlot("stream", () => state);
1145
+ const errors = [];
1146
+ if (store) store.errors = errors;
1147
+ let holder = null;
1148
+ channel.onCancel = () => store?.wake?.();
1149
+ const failIfErrored = () => {
1150
+ if (errors.length > 0) throw renderFailure(errors[0]);
1151
+ };
1152
+ const stopWaiting = () => errors.length > 0 || channel.cancelled;
1153
+ try {
1154
+ ensureServerDom();
1155
+ watchBrowserApis();
1156
+ const ids = options.hydration === "adopt" ? /* @__PURE__ */ new WeakMap() : null;
1157
+ const scope = ids ? createServerAdoptScope(ids) : null;
1158
+ let root;
1159
+ try {
1160
+ root = inScope(scope, input);
1161
+ } catch (err) {
1162
+ throw explainServerError(err);
1163
+ }
1164
+ const serialize = (node) => renderToStringWith(node, { marker: options.marker ?? "root", ids });
1165
+ state.serialize = serialize;
1166
+ holder = document.createDocumentFragment();
1167
+ holder.appendChild(root);
1168
+ const ready = (b) => b.done && !pendingFor(store?.pending, b.api);
1169
+ const serializeWithFallbacks = (node) => {
1170
+ const held = [];
1171
+ for (const b of state.boundaries) {
1172
+ if (ready(b) || b.container === node || !node.contains(b.container)) continue;
1173
+ if (held.some((h) => h.b.container.contains(b.container))) continue;
1174
+ const nodes = Array.from(b.container.childNodes);
1175
+ for (const n of nodes) b.container.removeChild(n);
1176
+ const mark = document.createComment(`sibu-fallback:${b.id}`);
1177
+ b.container.appendChild(mark);
1178
+ held.push({ b, nodes, mark });
1179
+ }
1180
+ let html = serialize(node);
1181
+ for (const { b, nodes, mark } of held) {
1182
+ b.container.removeChild(mark);
1183
+ for (const n of nodes) b.container.appendChild(n);
1184
+ const fallback = b.fallbackHtml ?? "";
1185
+ html = html.replace(`<!--sibu-fallback:${b.id}-->`, () => fallback);
1186
+ }
1187
+ return html;
1188
+ };
1189
+ const shellDeadline = Date.now() + (options.shellTimeout ?? 5e3);
1190
+ await waitFor(() => !pendingFor(store?.pending, void 0), shellDeadline, stopWaiting);
1191
+ failIfErrored();
1192
+ if (channel.cancelled) return;
1193
+ for (const b of state.boundaries) {
1194
+ const inline = (x) => x.done && !pendingFor(store?.pending, x.api);
1195
+ const enclosing = state.boundaries.filter((a) => a !== b && a.container.contains(b.container));
1196
+ if (inline(b) && enclosing.every(inline)) b.emitted = true;
1197
+ }
1198
+ const shell = serializeWithFallbacks(holder);
1199
+ const head = collectServerHead();
1200
+ let after = "";
1201
+ let between = "";
1202
+ if (doc) {
1203
+ const page = assembleDocument(MARK_BODY, doc, head, MARK_END);
1204
+ const bodyAt = page.indexOf(MARK_BODY);
1205
+ const endAt = page.indexOf(MARK_END, bodyAt + MARK_BODY.length);
1206
+ const start = page.slice(0, bodyAt);
1207
+ between = page.slice(bodyAt + MARK_BODY.length, endAt);
1208
+ after = page.slice(endAt + MARK_END.length);
1209
+ await channel.push(start + shell);
1210
+ } else {
1211
+ await channel.push(shell);
1212
+ }
1213
+ if (channel.cancelled) return;
1214
+ failIfErrored();
1215
+ const deadline = Date.now() + (options.timeout ?? 1e4);
1216
+ const outstanding = () => state.boundaries.filter((b) => !b.emitted);
1217
+ const ancestorsOf = (b) => state.boundaries.filter((a) => a !== b && a.container.contains(b.container));
1218
+ const emittable = (b) => ready(b) && ancestorsOf(b).every((a) => a.emitted);
1219
+ let bodyClosed = false;
1220
+ for (; ; ) {
1221
+ if (outstanding().length === 0) break;
1222
+ const next = outstanding().find(emittable);
1223
+ if (!next) {
1224
+ const progressed = await waitFor(
1225
+ () => outstanding().some(emittable) || outstanding().length === 0,
1226
+ deadline,
1227
+ stopWaiting
1228
+ );
1229
+ if (channel.cancelled) return;
1230
+ failIfErrored();
1231
+ if (!progressed) break;
1232
+ continue;
1233
+ }
1234
+ failIfErrored();
1235
+ if (!bodyClosed && doc) {
1236
+ await channel.push(between);
1237
+ bodyClosed = true;
1238
+ if (channel.cancelled) return;
1239
+ failIfErrored();
1240
+ }
1241
+ for (const b of state.boundaries) {
1242
+ if (b === next || !next.container.contains(b.container) || !ready(b)) continue;
1243
+ const between2 = ancestorsOf(b).filter((a) => a !== next && next.container.contains(a.container));
1244
+ if (between2.every(ready)) b.emitted = true;
1245
+ }
1246
+ next.emitted = true;
1247
+ if (!next.container.isConnected && !holder.contains(next.container)) continue;
1248
+ let html = "";
1249
+ for (const child of Array.from(next.container.childNodes)) html += serializeWithFallbacks(child);
1250
+ await channel.push(
1251
+ `<div hidden id="sibu-resolved-${next.id}">${html}</div>${suspenseSwapScript(next.id, nonce)}`
1252
+ );
1253
+ if (channel.cancelled) return;
1254
+ failIfErrored();
1255
+ }
1256
+ if (DEV && outstanding().length > 0) {
1257
+ console.warn(
1258
+ `[SibuJS SSR] streaming: ${outstanding().length} Suspense boundar${outstanding().length === 1 ? "y" : "ies"} did not resolve within ${options.timeout ?? 1e4} ms and kept the fallback.`
1259
+ );
1260
+ }
1261
+ failIfErrored();
1262
+ let stateScript = "";
1263
+ if (options.transfer !== false) {
1264
+ const data = collectTransfer();
1265
+ if (data) {
1266
+ try {
1267
+ stateScript = serializeState(data, nonce, {
1268
+ maxBytes: options.maxStateBytes ?? DEFAULT_TRANSFER_MAX_BYTES,
1269
+ global: "__SIBU_TRANSFER__"
1270
+ });
1271
+ } catch (err) {
1272
+ if (DEV) console.warn("[SibuJS SSR] streaming: transferred state left out:", err);
1273
+ }
1274
+ }
1275
+ }
1276
+ if (doc) {
1277
+ await channel.push((bodyClosed ? "" : between) + stateScript + after);
1278
+ } else if (stateScript) {
1279
+ await channel.push(stateScript);
1280
+ }
1281
+ channel.end();
1282
+ } catch (err) {
1283
+ channel.fail(err);
1284
+ } finally {
1285
+ if (store) store.errors = void 0;
1286
+ if (holder) for (const child of Array.from(holder.childNodes)) dispose(child);
1287
+ }
1288
+ });
1289
+ return new ReadableStream({
1290
+ async pull(controller) {
1291
+ const { value, done } = await channel.take();
1292
+ if (done) controller.close();
1293
+ else controller.enqueue(value);
1294
+ },
1295
+ cancel() {
1296
+ channel.cancel();
1297
+ }
1298
+ });
1299
+ }
1300
+
1301
+ // src/platform/ssr.ts
1302
+ function isForbiddenSsrAttr(lowerName) {
1303
+ return isHtmlContentAttribute(lowerName);
1304
+ }
1305
+ var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
1306
+ function isSafeAttrName(name) {
1307
+ return SAFE_ATTR_NAME.test(name);
1308
+ }
1309
+ function ssrErrorComment(err) {
1310
+ if (DEV) {
1311
+ const msg = escapeHtml(err instanceof Error ? err.message : String(err));
1312
+ return `<!--SSR error: ${safeCommentText(msg)}-->`;
1313
+ }
1314
+ return "<!--SSR error-->";
1315
+ }
1316
+ function safeCommentText(text) {
1317
+ return text.replace(/-->/g, "--&gt;").replace(/--!>/g, "--!&gt;").replace(/<!--/g, "&lt;!--").replace(/^(-?)>/, "$1&gt;").replace(/<!-$/, "&lt;!-").replace(/--$/g, "--&#45;");
1318
+ }
1319
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1320
+ "area",
1321
+ "base",
1322
+ "br",
1323
+ "col",
1324
+ "embed",
1325
+ "hr",
1326
+ "img",
1327
+ "input",
1328
+ "link",
1329
+ "meta",
1330
+ "param",
1331
+ "source",
1332
+ "track",
1333
+ "wbr"
1334
+ ]);
1335
+ function findNativeGetter(proto, name) {
1336
+ for (let p = proto; p; p = Object.getPrototypeOf(p)) {
1337
+ const get = Object.getOwnPropertyDescriptor(p, name)?.get;
1338
+ if (get) return get;
1339
+ }
1340
+ return null;
1341
+ }
1342
+ var nativeAccessorsByRealm = /* @__PURE__ */ new WeakMap();
1343
+ function nativeAccessors() {
1344
+ const realm = Element;
1345
+ let accessors = nativeAccessorsByRealm.get(realm);
1346
+ if (!accessors) {
1347
+ accessors = {
1348
+ attributes: findNativeGetter(Element.prototype, "attributes"),
1349
+ tagName: findNativeGetter(Element.prototype, "tagName"),
1350
+ childNodes: findNativeGetter(Node.prototype, "childNodes"),
1351
+ getAttribute: Element.prototype.getAttribute
1352
+ };
1353
+ nativeAccessorsByRealm.set(realm, accessors);
1354
+ }
1355
+ return accessors;
1356
+ }
1357
+ function attributesOf(el) {
1358
+ const get = nativeAccessors().attributes;
1359
+ return get ? get.call(el) : el.attributes;
1360
+ }
1361
+ function tagNameOf(el) {
1362
+ const get = nativeAccessors().tagName;
1363
+ return get ? get.call(el) : el.tagName;
1364
+ }
1365
+ function childNodesOf(node) {
1366
+ const get = nativeAccessors().childNodes;
1367
+ return get ? get.call(node) : node.childNodes;
1368
+ }
1369
+ function getAttributeOf(el, name) {
1370
+ return nativeAccessors().getAttribute.call(el, name);
1371
+ }
1372
+ var SVG_NS = "http://www.w3.org/2000/svg";
1373
+ var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
1374
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
1375
+ var XML_NS = "http://www.w3.org/XML/1998/namespace";
1376
+ var XMLNS_NS = "http://www.w3.org/2000/xmlns/";
1377
+ var BREAKOUT_TAGS = /* @__PURE__ */ new Set([
1378
+ "b",
1379
+ "big",
1380
+ "blockquote",
1381
+ "body",
1382
+ "br",
1383
+ "center",
1384
+ "code",
1385
+ "dd",
1386
+ "div",
1387
+ "dl",
1388
+ "dt",
1389
+ "em",
1390
+ "embed",
1391
+ "h1",
1392
+ "h2",
1393
+ "h3",
1394
+ "h4",
1395
+ "h5",
1396
+ "h6",
1397
+ "head",
1398
+ "hr",
1399
+ "i",
1400
+ "img",
1401
+ "li",
1402
+ "listing",
1403
+ "menu",
1404
+ "meta",
1405
+ "nobr",
1406
+ "ol",
1407
+ "p",
1408
+ "pre",
1409
+ "ruby",
1410
+ "s",
1411
+ "small",
1412
+ "span",
1413
+ "strong",
1414
+ "strike",
1415
+ "sub",
1416
+ "sup",
1417
+ "table",
1418
+ "tt",
1419
+ "u",
1420
+ "ul",
1421
+ "var"
1422
+ ]);
1423
+ function isBreakout(lowerName, element) {
1424
+ if (BREAKOUT_TAGS.has(lowerName)) return true;
1425
+ return lowerName === "font" && (element.hasAttribute("color") || element.hasAttribute("face") || element.hasAttribute("size"));
1426
+ }
1427
+ function fitsParseContext(ns, lowerName, element, context) {
1428
+ if (ns === SVG_NS) {
1429
+ if (context === 1 /* Svg */) return !isBreakout(lowerName, element);
1430
+ return lowerName === "svg" && (context === 0 /* Html */ || context === 3 /* Annotation */);
1431
+ }
1432
+ if (ns === MATHML_NS) {
1433
+ if (context === 2 /* Math */ || context === 3 /* Annotation */) return !isBreakout(lowerName, element);
1434
+ return lowerName === "math" && context === 0 /* Html */;
1435
+ }
1436
+ return context === 0 /* Html */;
1437
+ }
1438
+ function childParseContext(ns, lowerName, element) {
1439
+ if (ns === SVG_NS) {
1440
+ return lowerName === "foreignobject" || lowerName === "desc" || lowerName === "title" ? 0 /* Html */ : 1 /* Svg */;
1441
+ }
1442
+ if (ns === MATHML_NS) {
1443
+ if (lowerName === "mi" || lowerName === "mo" || lowerName === "mn" || lowerName === "ms" || lowerName === "mtext") {
1444
+ return 0 /* Html */;
1445
+ }
1446
+ if (lowerName === "annotation-xml") {
1447
+ const encoding = (element.getAttribute("encoding") ?? "").toLowerCase();
1448
+ return encoding === "text/html" || encoding === "application/xhtml+xml" ? 0 /* Html */ : 3 /* Annotation */;
1449
+ }
1450
+ return 2 /* Math */;
1451
+ }
1452
+ return 0 /* Html */;
1453
+ }
1454
+ function foreignAttrName(attr) {
1455
+ const ns = attr.namespaceURI;
1456
+ if (ns === XLINK_NS) return `xlink:${attr.localName}`;
1457
+ if (ns === XML_NS) return `xml:${attr.localName}`;
1458
+ if (ns === XMLNS_NS) return attr.localName === "xmlns" ? "xmlns" : `xmlns:${attr.localName}`;
1459
+ return attr.name;
1460
+ }
1461
+ var LEGACY_SERIALIZE = { marker: "all", ids: null };
1462
+ function serializeOpenTag(element, tag, foreign, opts, depth) {
1463
+ let html = `<${tag}`;
1464
+ const meta = tag === "meta" && !foreign ? /* @__PURE__ */ new Map() : null;
1465
+ const embed = tag === "iframe" && !foreign ? getEmbedRecord(element) : void 0;
1466
+ const embedSeen = embed ? { sandbox: false, referrer: false, title: false, fullscreen: false } : null;
1467
+ const attrs = attributesOf(element);
1468
+ for (let i = 0; i < attrs.length; i++) {
1469
+ const attr = attrs[i];
1470
+ const rawName = foreign ? foreignAttrName(attr) : attr.name;
1471
+ if (!isSafeAttrName(rawName)) continue;
1472
+ if (isEventHandlerAttr(rawName)) continue;
1473
+ if (isForbiddenSsrAttr(rawName)) continue;
1474
+ const lowerName = rawName.toLowerCase();
1475
+ let value = resolveAttributeValue(lowerName, attr.value);
1476
+ if (value === null) continue;
1477
+ if (foreign && isRefusedAnimationAttribute(element, rawName, value)) continue;
1478
+ if (embed && embedSeen) {
1479
+ value = embedSerializedAttribute(embed, embedSeen, lowerName, value);
1480
+ if (value === null) continue;
1481
+ }
1482
+ meta?.set(rawName, value);
1483
+ html += ` ${rawName}="${escapeAttr(value)}"`;
1484
+ }
1485
+ if (meta && isForbiddenMetaSnapshot(meta)) return null;
1486
+ if (embed && embedSeen) {
1487
+ const tail = embedSerializationTail(embed, embedSeen);
1488
+ if (tail === null) return null;
1489
+ html += tail;
1490
+ }
1491
+ const hid = opts.ids?.get(element);
1492
+ if (hid !== void 0) html += ` data-sibu-h="${hid}"`;
1493
+ if ((opts.marker === "all" || opts.marker === "root" && depth === 0) && !getAttributeOf(element, "data-sibu-hydrate")) {
1494
+ html += ` data-sibu-ssr="true"`;
1495
+ }
1496
+ return html;
1497
+ }
1498
+ function serializeNode(node, context, opts = LEGACY_SERIALIZE, depth = 0) {
1499
+ if (node.nodeType === 3) return escapeHtml(node.textContent || "");
1500
+ if (node.nodeType === 8) return `<!--${safeCommentText(node.textContent || "")}-->`;
1501
+ let foreign = false;
1502
+ if (!(node instanceof HTMLElement)) {
1503
+ const ns2 = node.nodeType === 1 ? node.namespaceURI : null;
1504
+ if (ns2 !== SVG_NS && ns2 !== MATHML_NS) return escapeHtml(node.textContent || "");
1505
+ foreign = true;
1506
+ }
1507
+ const element = node;
1508
+ const tag = foreign ? element.localName : tagNameOf(element).toLowerCase();
1509
+ if (isCodeTextElement(tag)) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
1510
+ if (!/^[a-z][a-z0-9-]*$/i.test(tag)) return DEV ? "<!--ssr:invalid-tag-->" : "";
1511
+ const ns = foreign ? element.namespaceURI : null;
1512
+ const lowerName = tag.toLowerCase();
1513
+ if (!fitsParseContext(ns, lowerName, element, context)) return DEV ? `<!--ssr:${tag}-misplaced-->` : "";
1514
+ const openTag = serializeOpenTag(element, tag, foreign, opts, depth);
1515
+ if (openTag === null) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
1516
+ const children = childNodesOf(element);
1517
+ if (foreign ? children.length === 0 : VOID_ELEMENTS.has(tag)) return `${openTag} />`;
1518
+ return {
1519
+ open: `${openTag}>`,
1520
+ close: `</${tag}>`,
1521
+ children: Array.from(children),
1522
+ context: childParseContext(ns, lowerName, element)
1523
+ };
1524
+ }
1525
+ function renderToString(element) {
1526
+ return renderNodeToString(element, 0 /* Html */, LEGACY_SERIALIZE, 0);
1527
+ }
1528
+ function renderToStringWith(element, opts) {
1529
+ return renderNodeToString(element, 0 /* Html */, opts, 0);
1530
+ }
1531
+ function renderNodeToString(element, context, opts, depth) {
1532
+ if (element instanceof DocumentFragment) {
1533
+ let prevText2 = false;
1534
+ return Array.from(element.childNodes).map((child) => {
1535
+ const isText = child.nodeType === 3;
1536
+ const sep = opts.ids && isText && prevText2 ? "<!--|-->" : "";
1537
+ prevText2 = isText;
1538
+ try {
1539
+ return sep + renderNodeToString(child, context, opts, depth);
1540
+ } catch (err) {
1541
+ return ssrErrorComment(err);
1542
+ }
1543
+ }).join("");
1544
+ }
1545
+ const frame = serializeNode(element, context, opts, depth);
1546
+ if (typeof frame === "string") return frame;
1547
+ let html = frame.open;
1548
+ let prevText = false;
1549
+ for (const child of frame.children) {
1550
+ const isText = child.nodeType === 3;
1551
+ if (opts.ids && isText && prevText) html += "<!--|-->";
1552
+ prevText = isText;
1553
+ try {
1554
+ html += renderNodeToString(child, frame.context, opts, depth + 1);
1555
+ } catch (err) {
1556
+ html += ssrErrorComment(err);
1557
+ }
1558
+ }
1559
+ return html + frame.close;
1560
+ }
1561
+ function hydrate(component, target, input = {}) {
1562
+ if (input.strategy === "adopt") return hydrateAdopt(component, target, input);
1563
+ const options = input;
1564
+ const container = target;
1565
+ const clientTree = component();
1566
+ if (options.diagnostics) {
1567
+ const mismatches = [];
1568
+ collectMismatches(container.firstElementChild, clientTree, "", mismatches);
1569
+ if (mismatches.length > 0) {
1570
+ const first = mismatches[0];
1571
+ if (options.onMismatch) {
1572
+ options.onMismatch(first);
1573
+ } else if (DEV) {
1574
+ console.warn(
1575
+ `[SibuJS hydration] ${first.message}
1576
+ at ${first.path}
1577
+ server: ${first.serverValue}
1578
+ client: ${first.clientValue}`
1579
+ );
1580
+ }
1581
+ }
1582
+ }
1583
+ replaceChildrenSafely(container, clientTree);
1584
+ container.setAttribute("data-sibu-hydrated", "true");
1585
+ }
1586
+ function directTextOf(node) {
1587
+ let out = "";
1588
+ for (const child of Array.from(node.childNodes)) {
1589
+ if (child.nodeType === 3) out += child.nodeValue ?? "";
1590
+ }
1591
+ return out.replace(/\s+/g, " ").trim();
1592
+ }
1593
+ function disposeAndReplace(outgoing, next) {
1594
+ dispose(outgoing);
1595
+ outgoing.replaceWith(next);
1596
+ }
1597
+ function collectMismatches(serverNode, clientNode, path, out, max = 5) {
1598
+ if (out.length >= max) return;
1599
+ const nodePath = path || clientNode?.tagName?.toLowerCase() || "(root)";
1600
+ if (!serverNode && clientNode) {
1601
+ out.push({
1602
+ kind: "child-count",
1603
+ path: nodePath,
1604
+ serverValue: "(missing)",
1605
+ clientValue: clientNode.tagName.toLowerCase(),
1606
+ message: "Client rendered a node that the server did not emit."
1607
+ });
1608
+ return;
1609
+ }
1610
+ if (serverNode && !clientNode) {
1611
+ out.push({
1612
+ kind: "child-count",
1613
+ path: nodePath,
1614
+ serverValue: serverNode.tagName.toLowerCase(),
1615
+ clientValue: "(missing)",
1616
+ message: "Server rendered a node that the client did not produce."
1617
+ });
1618
+ return;
1619
+ }
1620
+ if (!serverNode || !clientNode) return;
1621
+ if (serverNode.tagName !== clientNode.tagName) {
1622
+ out.push({
1623
+ kind: "tag",
1624
+ path: nodePath,
1625
+ serverValue: serverNode.tagName.toLowerCase(),
1626
+ clientValue: clientNode.tagName.toLowerCase(),
1627
+ message: "Element tag mismatch \u2014 server and client disagree on the element type."
1628
+ });
1629
+ return;
1630
+ }
1631
+ const skipAttrs = /* @__PURE__ */ new Set(["data-sibu-ssr", "data-sibu-hydrated", "data-sibu-island"]);
1632
+ const serverAttrs = /* @__PURE__ */ new Map();
1633
+ for (const a of Array.from(serverNode.attributes)) {
1634
+ if (!skipAttrs.has(a.name)) serverAttrs.set(a.name, a.value);
1635
+ }
1636
+ const clientAttrs = /* @__PURE__ */ new Map();
1637
+ for (const a of Array.from(clientNode.attributes)) {
1638
+ if (!skipAttrs.has(a.name)) clientAttrs.set(a.name, a.value);
1639
+ }
1640
+ for (const [name, value] of serverAttrs) {
1641
+ if (out.length >= max) return;
1642
+ if (!clientAttrs.has(name)) {
1643
+ out.push({
1644
+ kind: "attribute",
1645
+ path: `${nodePath}[${name}]`,
1646
+ serverValue: value,
1647
+ clientValue: "(missing)",
1648
+ message: `Attribute "${name}" present on server but missing on client.`
1649
+ });
1650
+ } else if (clientAttrs.get(name) !== value) {
1651
+ out.push({
1652
+ kind: "attribute",
1653
+ path: `${nodePath}[${name}]`,
1654
+ serverValue: value,
1655
+ clientValue: clientAttrs.get(name) ?? "",
1656
+ message: `Attribute "${name}" differs between server and client.`
1657
+ });
1658
+ }
1659
+ }
1660
+ for (const [name, value] of clientAttrs) {
1661
+ if (out.length >= max) return;
1662
+ if (!serverAttrs.has(name)) {
1663
+ out.push({
1664
+ kind: "attribute",
1665
+ path: `${nodePath}[${name}]`,
1666
+ serverValue: "(missing)",
1667
+ clientValue: value,
1668
+ message: `Attribute "${name}" present on client but missing on server.`
1669
+ });
1670
+ }
1671
+ }
1672
+ const serverText = directTextOf(serverNode);
1673
+ const clientText = directTextOf(clientNode);
1674
+ if (serverText !== clientText) {
1675
+ out.push({
1676
+ kind: "text",
1677
+ path: nodePath,
1678
+ serverValue: serverText,
1679
+ clientValue: clientText,
1680
+ message: "Text content differs between server and client."
1681
+ });
1682
+ if (out.length >= max) return;
1683
+ }
1684
+ const serverChildren = Array.from(serverNode.children);
1685
+ const clientChildren = Array.from(clientNode.children);
1686
+ const max2 = Math.max(serverChildren.length, clientChildren.length);
1687
+ for (let i = 0; i < max2; i++) {
1688
+ if (out.length >= max) return;
1689
+ const childPath = `${nodePath} > ${clientChildren[i]?.tagName?.toLowerCase() ?? serverChildren[i]?.tagName?.toLowerCase() ?? "?"}:nth-child(${i + 1})`;
1690
+ collectMismatches(serverChildren[i] ?? null, clientChildren[i] ?? null, childPath, out, max);
1691
+ }
1692
+ }
1693
+ function trustHTML(html) {
1694
+ return html;
1695
+ }
1696
+ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
1697
+ if (!attrs) return "";
1698
+ const out = [];
1699
+ for (const rawKey of Object.keys(attrs)) {
1700
+ if (!Object.hasOwn(attrs, rawKey)) continue;
1701
+ if (!isSafeAttrName(rawKey)) continue;
1702
+ if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
1703
+ if (isForbiddenSsrAttr(rawKey)) continue;
1704
+ const value = resolveAttributeValue(rawKey.toLowerCase(), String(attrs[rawKey]));
1705
+ if (value === null) continue;
1706
+ out.push(`${rawKey}="${escapeAttr(value)}"`);
1707
+ }
1708
+ return out.join(" ");
1709
+ }
1710
+ function isDangerousMetaRefresh(metaProps) {
1711
+ return isForbiddenMetaEntry(metaProps);
1712
+ }
1713
+ function renderToDocument(component, options = {}) {
1714
+ let content;
1715
+ try {
1716
+ content = renderToString(component());
1717
+ } catch (err) {
1718
+ content = ssrErrorComment(err);
1719
+ }
1720
+ return assembleDocument(content, options, "", "");
1721
+ }
1722
+ function assembleDocument(content, options, head, bodyEnd) {
1723
+ const metaTags = (options.meta || []).map((attrs) => serializeHeadEntry("meta", attrs)).filter(Boolean).join("\n ");
1724
+ const linkTags = (options.links || []).map((attrs) => serializeHeadEntry("link", attrs)).filter(Boolean).join("\n ");
1725
+ const scriptTags = (options.scripts || []).map((src) => {
1726
+ const safe = sanitizeUrl(String(src));
1727
+ if (!safe) return "";
1728
+ return `<script src="${escapeAttr(safe)}"></script>`;
1729
+ }).filter(Boolean).join("\n ");
1730
+ const bodyAttrPairs = buildAttrString(options.bodyAttrs);
1731
+ const bodyAttrs = bodyAttrPairs ? ` ${bodyAttrPairs}` : "";
1732
+ return `<!DOCTYPE html>
1733
+ <html>
1734
+ <head>
1735
+ <meta charset="UTF-8" />
1736
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1737
+ ${options.title != null ? `<title>${escapeHtml(options.title)}</title>` : ""}
1738
+ ${metaTags}
1739
+ ${linkTags}
1740
+ ${options.headExtra || ""}${head ? `
1741
+ ${head}` : ""}
1742
+ </head>
1743
+ <body${bodyAttrs}>
1744
+ <div id="app">${content}</div>${bodyEnd ? `
1745
+ ${bodyEnd}` : ""}
1746
+ ${scriptTags}
1747
+ </body>
1748
+ </html>`;
1749
+ }
1750
+ function renderToStream(element) {
1751
+ return streamNode(element, 0 /* Html */);
1752
+ }
1753
+ async function* streamNode(element, context) {
1754
+ if (element instanceof DocumentFragment) {
1755
+ for (const child of Array.from(element.childNodes)) {
1756
+ try {
1757
+ yield* streamNode(child, context);
1758
+ } catch (err) {
1759
+ yield ssrErrorComment(err);
1760
+ }
1761
+ }
1762
+ return;
1763
+ }
1764
+ const frame = serializeNode(element, context);
1765
+ if (typeof frame === "string") {
1766
+ if (frame) yield frame;
1767
+ return;
1768
+ }
1769
+ yield frame.open;
1770
+ for (const child of frame.children) {
1771
+ try {
1772
+ yield* streamNode(child, frame.context);
1773
+ } catch (err) {
1774
+ yield ssrErrorComment(err);
1775
+ }
1776
+ }
1777
+ yield frame.close;
1778
+ }
1779
+ async function collectStream(stream) {
1780
+ let result = "";
1781
+ for await (const chunk of stream) {
1782
+ result += chunk;
1783
+ }
1784
+ return result;
1785
+ }
1786
+ function renderToReadableStream(element, options) {
1787
+ if (typeof element === "function") return streamPage(element, options);
1788
+ const generator = renderToStream(element);
1789
+ return new ReadableStream({
1790
+ async pull(controller) {
1791
+ const { value, done } = await generator.next();
1792
+ if (done) {
1793
+ controller.close();
1794
+ } else {
1795
+ controller.enqueue(value);
1796
+ }
1797
+ },
1798
+ async cancel() {
1799
+ await generator.return(void 0);
1800
+ }
1801
+ });
1802
+ }
1803
+ var SAFE_ID = /^[A-Za-z0-9_-]+$/;
1804
+ function island(id, component, ...rest) {
1805
+ if (rest.length > 0) return componentIsland(id, component, rest[0], rest[1]);
1806
+ if (!SAFE_ID.test(id)) {
1807
+ throw new Error(`[SibuJS SSR] island: id must match [A-Za-z0-9_-]+ (got: ${JSON.stringify(id.slice(0, 32))})`);
1808
+ }
1809
+ const el = component();
1810
+ el.setAttribute("data-sibu-island", id);
1811
+ return el;
1812
+ }
1813
+ function isAlreadyHydrated(element) {
1814
+ return element.hasAttribute("data-sibu-hydrated");
1815
+ }
1816
+ function hydrateIslands(container, islands) {
1817
+ const markers = container.querySelectorAll("[data-sibu-island]");
1818
+ for (const marker of Array.from(markers)) {
1819
+ if (isAlreadyHydrated(marker)) continue;
1820
+ const id = marker.getAttribute("data-sibu-island") ?? "";
1821
+ if (!Object.hasOwn(islands, id)) continue;
1822
+ const factory = islands[id];
1823
+ if (typeof factory !== "function") continue;
1824
+ const clientTree = factory();
1825
+ clientTree.setAttribute("data-sibu-island", id);
1826
+ clientTree.setAttribute("data-sibu-hydrated", "true");
1827
+ disposeAndReplace(marker, clientTree);
1828
+ }
1829
+ container.setAttribute("data-sibu-hydrated", "partial");
1830
+ }
1831
+ function hydrateProgressively(container, islands, options) {
1832
+ const markers = container.querySelectorAll("[data-sibu-island]");
1833
+ const cleanups = [];
1834
+ for (const marker of Array.from(markers)) {
1835
+ if (isAlreadyHydrated(marker)) continue;
1836
+ const id = marker.getAttribute("data-sibu-island") ?? "";
1837
+ if (!Object.hasOwn(islands, id)) continue;
1838
+ const factory = islands[id];
1839
+ if (typeof factory !== "function") continue;
1840
+ const observer = new IntersectionObserver(
1841
+ (entries) => {
1842
+ for (const entry of entries) {
1843
+ if (entry.isIntersecting) {
1844
+ observer.disconnect();
1845
+ if (isAlreadyHydrated(marker) || marker.parentNode === null) break;
1846
+ const clientTree = factory();
1847
+ clientTree.setAttribute("data-sibu-island", id);
1848
+ clientTree.setAttribute("data-sibu-hydrated", "true");
1849
+ disposeAndReplace(marker, clientTree);
1850
+ break;
1851
+ }
1852
+ }
1853
+ },
1854
+ { rootMargin: "200px", ...options }
1855
+ );
1856
+ observer.observe(marker);
1857
+ cleanups.push(() => observer.disconnect());
1858
+ }
1859
+ container.setAttribute("data-sibu-hydrated", "progressive");
1860
+ return () => {
1861
+ for (const cleanup of cleanups) cleanup();
1862
+ };
1863
+ }
1864
+ function resetSSRState() {
1865
+ getSSRStore().suspenseIdCounter = 0;
1866
+ }
1867
+ function noop() {
1868
+ }
1869
+ function ssrSuspense(props) {
1870
+ const store = getSSRStore();
1871
+ const id = `sibu-sus-${store.suspenseIdCounter++}`;
1872
+ const timeoutMs = props.timeoutMs ?? 3e4;
1873
+ const fallbackEl = props.fallback();
1874
+ const wrapper = document.createElement("div");
1875
+ wrapper.setAttribute("data-sibu-suspense-id", id);
1876
+ wrapper.appendChild(fallbackEl);
1877
+ const fallbackHtml = renderToString(fallbackEl);
1878
+ let timer;
1879
+ const timeoutPromise = new Promise((_, reject) => {
1880
+ timer = setTimeout(() => reject(new Error(`[SibuJS SSR] ssrSuspense timed out after ${timeoutMs}ms`)), timeoutMs);
1881
+ });
1882
+ let contentPromise;
1883
+ try {
1884
+ contentPromise = Promise.resolve(props.content());
1885
+ } catch (err) {
1886
+ contentPromise = Promise.reject(err);
1887
+ }
1888
+ const toFallback = (err) => {
1889
+ if (DEV) console.warn("[SibuJS SSR] ssrSuspense rejected:", err);
1890
+ return { id, html: fallbackHtml };
1891
+ };
1892
+ const clearTimer = () => {
1893
+ if (timer !== void 0) clearTimeout(timer);
1894
+ };
1895
+ const raced = Promise.race([contentPromise, timeoutPromise]);
1896
+ const promise = raced.then(
1897
+ (resolvedEl) => {
1898
+ clearTimer();
1899
+ try {
1900
+ return { id, html: renderToString(resolvedEl) };
1901
+ } catch (err) {
1902
+ return toFallback(err);
1903
+ }
1904
+ },
1905
+ (err) => {
1906
+ clearTimer();
1907
+ return toFallback(err);
1908
+ }
1909
+ );
1910
+ promise.catch(noop);
1911
+ return { element: wrapper, promise };
1912
+ }
1913
+ var SAFE_SUSPENSE_ID = /^[A-Za-z0-9_-]+$/;
1914
+ function suspenseSwapScript(id, nonce) {
1915
+ if (!SAFE_SUSPENSE_ID.test(id)) {
1916
+ throw new Error(
1917
+ `[SibuJS SSR] suspenseSwapScript: id must match [A-Za-z0-9_-]+ (got: ${JSON.stringify(id.slice(0, 32))})`
1918
+ );
1919
+ }
1920
+ const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
1921
+ return `<script${nonceAttr}>(function(){var t=document.getElementById("sibu-resolved-${id}");var f=document.querySelector('[data-sibu-suspense-id="${id}"]');if(t&&f){while(f.firstChild)f.removeChild(f.firstChild);while(t.firstChild)f.appendChild(t.firstChild);t.remove();f.removeAttribute("data-sibu-suspense-id");}})()</script>`;
1922
+ }
1923
+ async function* renderToSuspenseStream(element, pendingBoundaries = [], options) {
1924
+ yield* renderToStream(element);
1925
+ if (pendingBoundaries.length > 0) {
1926
+ const resolved = await Promise.all(pendingBoundaries);
1927
+ for (const { id, html } of resolved) {
1928
+ if (!SAFE_SUSPENSE_ID.test(id)) continue;
1929
+ yield `<div hidden id="sibu-resolved-${id}">${html}</div>`;
1930
+ yield suspenseSwapScript(id, options?.nonce);
1931
+ }
1932
+ }
1933
+ }
1934
+ var SSR_DATA_ATTR = "__SIBU_SSR_DATA__";
1935
+ function escapeScriptJson(json) {
1936
+ return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1937
+ }
1938
+ var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
1939
+ var SAFE_GLOBAL_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1940
+ function serializeState(state, nonce, options) {
1941
+ const globalName = options?.global ?? SSR_DATA_ATTR;
1942
+ if (!SAFE_GLOBAL_NAME.test(globalName)) {
1943
+ throw new Error(`[SibuJS SSR] serializeState: global must be an identifier (got: ${JSON.stringify(globalName)})`);
1944
+ }
1945
+ const rawJson = JSON.stringify(state);
1946
+ const maxBytes = options?.maxBytes ?? DEFAULT_MAX_SSR_BYTES;
1947
+ const byteLen = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(rawJson).byteLength : Buffer.byteLength(rawJson, "utf8");
1948
+ if (byteLen > maxBytes) {
1949
+ throw new Error(`[SibuJS SSR] serializeState: payload (${byteLen} bytes) exceeds maxBytes (${maxBytes})`);
1950
+ }
1951
+ const value = rawJson.includes('"__proto__":') ? `JSON.parse(${JSON.stringify(rawJson)})` : rawJson;
1952
+ const json = escapeScriptJson(value);
1953
+ const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
1954
+ return `<script${nonceAttr}>window.${globalName}=${json}</script>`;
1955
+ }
1956
+ function deserializeState(validate) {
1957
+ if (typeof window === "undefined") return void 0;
1958
+ if (DEV && !validate) {
1959
+ console.warn(
1960
+ "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
1961
+ );
1962
+ }
1963
+ const raw = readOwnGlobal(SSR_DATA_ATTR);
1964
+ if (raw === void 0) return void 0;
1965
+ if (validate && !validate(raw)) return void 0;
1966
+ return raw;
1967
+ }
1968
+ function escapeHtml(str) {
1969
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1970
+ }
1971
+ function escapeAttr(str) {
1972
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1973
+ }
1974
+
1975
+ export {
1976
+ planMetaEntry,
1977
+ planHeadElementEntry,
1978
+ STATIC_VALUE_RESOLVER,
1979
+ serializeHeadEntry,
1980
+ serializePlannedHeadEntry,
1981
+ recordServerHead,
1982
+ collectServerHead,
1983
+ DEFAULT_SETTLE_TIMEOUT,
1984
+ DEFAULT_TRANSFER_MAX_BYTES,
1985
+ settled,
1986
+ formatRenderDiagnostics,
1987
+ describeNodePath,
1988
+ renderFailure,
1989
+ renderToStringAsync,
1990
+ renderToDocumentAsync,
1991
+ ADOPT_ID_ATTR,
1992
+ ISLAND_PROPS_ATTR,
1993
+ componentIsland,
1994
+ islandComponent,
1995
+ renderToString,
1996
+ renderToStringWith,
1997
+ hydrate,
1998
+ trustHTML,
1999
+ isDangerousMetaRefresh,
2000
+ renderToDocument,
2001
+ assembleDocument,
2002
+ renderToStream,
2003
+ collectStream,
2004
+ renderToReadableStream,
2005
+ island,
2006
+ hydrateIslands,
2007
+ hydrateProgressively,
2008
+ resetSSRState,
2009
+ ssrSuspense,
2010
+ suspenseSwapScript,
2011
+ renderToSuspenseStream,
2012
+ escapeScriptJson,
2013
+ serializeState,
2014
+ deserializeState
2015
+ };