sibujs 3.4.0 → 4.0.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 (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +118 -15
  3. package/dist/browser.cjs +410 -101
  4. package/dist/browser.d.cts +50 -5
  5. package/dist/browser.d.ts +50 -5
  6. package/dist/browser.js +8 -6
  7. package/dist/build.cjs +777 -385
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-JCO7AHNU.js → chunk-3EKJK5FZ.js} +73 -34
  11. package/dist/chunk-4MFZMLUU.js +147 -0
  12. package/dist/chunk-52XFPGSN.js +123 -0
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-OAVXAFAY.js → chunk-6LG2T7LO.js} +5 -5
  15. package/dist/{chunk-OOUQUCNN.js → chunk-ACBT6UZS.js} +57 -22
  16. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  17. package/dist/chunk-DHDZ7IVN.js +109 -0
  18. package/dist/{chunk-JNGGM2XE.js → chunk-DQVUBVNX.js} +16 -25
  19. package/dist/{chunk-AXFWGY32.js → chunk-DR7H6UGM.js} +232 -67
  20. package/dist/{chunk-WSGLJYFI.js → chunk-E62UADKL.js} +9 -3
  21. package/dist/{chunk-3Z2NGWQI.js → chunk-ES2GERY2.js} +1 -1
  22. package/dist/{chunk-S3V6ZXPI.js → chunk-EWEE7NBF.js} +115 -41
  23. package/dist/{chunk-3DVGCW7O.js → chunk-F64ZPCFE.js} +8 -8
  24. package/dist/{chunk-APFRCIRI.js → chunk-GCSGGA3Q.js} +295 -198
  25. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  26. package/dist/{chunk-ELXMRSVN.js → chunk-NF3LYCQH.js} +259 -124
  27. package/dist/chunk-O3QQ3XOL.js +61 -0
  28. package/dist/{chunk-BUKYCWHX.js → chunk-PBQEW5VW.js} +14 -10
  29. package/dist/{chunk-H5FI5WDP.js → chunk-RRIDJQ5C.js} +3 -3
  30. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  31. package/dist/{chunk-ODXVQ3CS.js → chunk-VFHIVEPF.js} +309 -188
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-KZBEVFXZ.js → chunk-W55YHPEP.js} +5 -3
  34. package/dist/{chunk-FKQG7FTY.js → chunk-WN6WDFCB.js} +4 -4
  35. package/dist/{chunk-CEBWDD63.js → chunk-WOOMUJFT.js} +228 -86
  36. package/dist/{chunk-ITWCTZYH.js → chunk-Z2CE42DB.js} +19 -25
  37. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.ts} +73 -11
  41. package/dist/data.cjs +483 -205
  42. package/dist/data.d.cts +139 -7
  43. package/dist/data.d.ts +139 -7
  44. package/dist/data.js +9 -9
  45. package/dist/devtools.cjs +222 -87
  46. package/dist/devtools.js +8 -8
  47. package/dist/dispose-4FZ6OFXR.js +19 -0
  48. package/dist/ecosystem.cjs +419 -175
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1981 -765
  51. package/dist/extras.d.cts +7 -7
  52. package/dist/extras.d.ts +7 -7
  53. package/dist/extras.js +29 -26
  54. package/dist/index.cjs +784 -385
  55. package/dist/index.d.cts +299 -28
  56. package/dist/index.d.ts +299 -28
  57. package/dist/index.js +33 -18
  58. package/dist/motion.cjs +180 -50
  59. package/dist/motion.d.cts +26 -0
  60. package/dist/motion.d.ts +26 -0
  61. package/dist/motion.js +5 -5
  62. package/dist/patterns.cjs +437 -170
  63. package/dist/patterns.d.cts +60 -13
  64. package/dist/patterns.d.ts +60 -13
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.cjs +333 -148
  67. package/dist/performance.d.cts +55 -7
  68. package/dist/performance.d.ts +55 -7
  69. package/dist/performance.js +9 -9
  70. package/dist/plugins.cjs +1374 -576
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +613 -321
  74. package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
  75. package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
  76. package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
  77. package/dist/ssr.cjs +1104 -443
  78. package/dist/ssr.d.cts +230 -20
  79. package/dist/ssr.d.ts +230 -20
  80. package/dist/ssr.js +15 -12
  81. package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
  82. package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
  83. package/dist/testing.cjs +205 -63
  84. package/dist/testing.d.cts +1 -1
  85. package/dist/testing.d.ts +1 -1
  86. package/dist/testing.js +8 -10
  87. package/dist/ui.cjs +640 -199
  88. package/dist/ui.d.cts +2 -2
  89. package/dist/ui.d.ts +2 -2
  90. package/dist/ui.js +10 -10
  91. package/dist/widgets.cjs +207 -82
  92. package/dist/widgets.js +9 -9
  93. package/package.json +12 -7
  94. package/dist/chunk-5VGSK6D2.js +0 -80
  95. package/dist/chunk-COY6PUD2.js +0 -34
  96. package/dist/chunk-K35BAR4P.js +0 -96
  97. package/dist/chunk-L3GAGWCC.js +0 -111
  98. package/dist/chunk-RT3IYFN2.js +0 -38
@@ -1,39 +1,195 @@
1
1
  import {
2
+ canonicalAttrName,
2
3
  isEventHandlerAttr,
3
- sanitizeSrcset,
4
+ isHtmlContentAttribute,
5
+ isPolicyAttribute,
6
+ sanitizeAttributeString,
4
7
  sanitizeUrl,
5
8
  stripControlChars
6
- } from "./chunk-L3GAGWCC.js";
9
+ } from "./chunk-5INI7D2L.js";
7
10
  import {
8
11
  getSSRStore
9
- } from "./chunk-S3NFJO6L.js";
12
+ } from "./chunk-TIRZCERI.js";
13
+ import {
14
+ dispose,
15
+ replaceChildrenSafely
16
+ } from "./chunk-DHDZ7IVN.js";
10
17
  import {
11
18
  isDev
12
- } from "./chunk-COY6PUD2.js";
19
+ } from "./chunk-VPP2FONR.js";
20
+
21
+ // src/utils/metaRefresh.ts
22
+ function isAsciiWhitespace(ch) {
23
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
24
+ }
25
+ function trimAsciiWhitespace(value) {
26
+ let start = 0;
27
+ let end = value.length;
28
+ while (start < end && isAsciiWhitespace(value[start])) start++;
29
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
30
+ return value.slice(start, end);
31
+ }
32
+ var forbidden = (reason) => ({ kind: "forbidden", reason });
33
+ function parseMetaRefreshContent(content) {
34
+ const raw = trimAsciiWhitespace(stripControlChars(content));
35
+ if (raw === "") return forbidden("empty content");
36
+ let i = 0;
37
+ while (i < raw.length && raw[i] >= "0" && raw[i] <= "9") i++;
38
+ if (i === 0) return forbidden("missing or non-numeric delay");
39
+ const delay = Number.parseInt(raw.slice(0, i), 10);
40
+ if (!Number.isFinite(delay)) return forbidden("unparseable delay");
41
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
42
+ if (i === raw.length) return { kind: "delay-only", delay };
43
+ if (raw[i] !== ";" && raw[i] !== ",") return forbidden("unexpected text after delay");
44
+ i++;
45
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
46
+ const keyStart = i;
47
+ while (i < raw.length && !isAsciiWhitespace(raw[i]) && raw[i] !== "=") i++;
48
+ const key = raw.slice(keyStart, i).toLowerCase();
49
+ if (key !== "url") return forbidden(`unsupported refresh key ${JSON.stringify(key)}`);
50
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
51
+ if (raw[i] !== "=") return forbidden("missing '=' after url");
52
+ i++;
53
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
54
+ let destination;
55
+ const quote = raw[i];
56
+ if (quote === '"' || quote === "'") {
57
+ const close = raw.indexOf(quote, i + 1);
58
+ if (close === -1) return forbidden("unterminated quoted destination");
59
+ destination = raw.slice(i + 1, close);
60
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
61
+ if (rest !== "") return forbidden("trailing text after quoted destination");
62
+ } else {
63
+ destination = trimAsciiWhitespace(raw.slice(i));
64
+ if (/[;,]/.test(destination)) return forbidden("multiple refresh assignments");
65
+ }
66
+ if (destination === "") return forbidden("empty destination");
67
+ const sanitizedDestination = sanitizeUrl(destination);
68
+ if (sanitizedDestination === "") return forbidden("destination uses a disallowed protocol");
69
+ return { kind: "allowed", delay, destination, sanitizedDestination };
70
+ }
71
+ function resolveMetaRefreshPolicy(attributes) {
72
+ let httpEquiv;
73
+ let content;
74
+ let duplicateName;
75
+ const seen = /* @__PURE__ */ new Set();
76
+ for (const [key, value] of attributes) {
77
+ const canonical = canonicalAttrName(key);
78
+ if (seen.has(canonical)) duplicateName = canonical;
79
+ seen.add(canonical);
80
+ if (canonical === "http-equiv") httpEquiv = value;
81
+ else if (canonical === "content") content = value;
82
+ }
83
+ if (duplicateName !== void 0) {
84
+ return forbidden(`duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}`);
85
+ }
86
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
87
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
88
+ return { kind: "not-refresh" };
89
+ }
90
+ if (typeof content !== "string") return { kind: "not-refresh" };
91
+ return parseMetaRefreshContent(content);
92
+ }
93
+ function normalizeMetaAttributes(props) {
94
+ const out = /* @__PURE__ */ new Map();
95
+ const seen = /* @__PURE__ */ new Set();
96
+ for (const key of Object.keys(props)) {
97
+ const canonical = canonicalAttrName(key);
98
+ if (seen.has(canonical)) return null;
99
+ seen.add(canonical);
100
+ out.set(key, props[key]);
101
+ }
102
+ return out;
103
+ }
104
+ function isForbiddenMetaEntry(props) {
105
+ const normalized = normalizeMetaAttributes(props);
106
+ if (normalized === null) return true;
107
+ return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
108
+ }
109
+
110
+ // src/utils/headEntry.ts
111
+ var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
112
+ function isEmittableHeadAttrName(name) {
113
+ if (!SAFE_HEAD_ATTR_NAME.test(name)) return false;
114
+ const canonical = canonicalAttrName(name);
115
+ if (isEventHandlerAttr(canonical)) return false;
116
+ if (isHtmlContentAttribute(canonical)) return false;
117
+ return true;
118
+ }
119
+ function sanitizeHeadAttrValue(canonical, value) {
120
+ if (!isPolicyAttribute(canonical)) return value;
121
+ const safe = sanitizeAttributeString(canonical, value);
122
+ return safe === "" ? null : safe;
123
+ }
124
+ function findDuplicateAttributeName(names) {
125
+ const seen = /* @__PURE__ */ new Set();
126
+ for (const name of names) {
127
+ const canonical = canonicalAttrName(name);
128
+ if (seen.has(canonical)) return canonical;
129
+ seen.add(canonical);
130
+ }
131
+ return null;
132
+ }
133
+ function planEntry(props, resolver, applyRefreshPolicy) {
134
+ const rawNames = Object.keys(props);
135
+ const duplicate = findDuplicateAttributeName(rawNames);
136
+ if (duplicate !== null) {
137
+ return { kind: "drop", reason: `duplicate case-insensitive attribute ${JSON.stringify(duplicate)}` };
138
+ }
139
+ const attributes = /* @__PURE__ */ new Map();
140
+ let reactive = false;
141
+ for (const name of rawNames) {
142
+ if (!Object.hasOwn(props, name)) continue;
143
+ if (!isEmittableHeadAttrName(name)) continue;
144
+ const authored = props[name];
145
+ if (resolver.isReactiveValue?.(authored)) reactive = true;
146
+ const canonical = canonicalAttrName(name);
147
+ const sanitized = sanitizeHeadAttrValue(canonical, resolver.resolveValue(name, authored));
148
+ if (sanitized === null) continue;
149
+ attributes.set(canonical, sanitized);
150
+ }
151
+ if (attributes.size === 0) return { kind: "drop", reason: "no effective attributes remain" };
152
+ const refresh = applyRefreshPolicy ? resolveMetaRefreshPolicy(attributes) : { kind: "not-refresh" };
153
+ if (refresh.kind === "forbidden") return { kind: "drop", reason: refresh.reason };
154
+ if (reactive && (refresh.kind === "allowed" || refresh.kind === "delay-only")) {
155
+ return { kind: "drop", reason: "a reactive meta entry may not publish a native refresh directive" };
156
+ }
157
+ return { kind: "publish", attributes, refresh };
158
+ }
159
+ function planMetaEntry(props, resolver) {
160
+ return planEntry(props, resolver, true);
161
+ }
162
+ function planHeadElementEntry(props, resolver) {
163
+ return planEntry(props, resolver, false);
164
+ }
165
+ var STATIC_VALUE_RESOLVER = {
166
+ resolveValue: (_name, value) => String(value)
167
+ };
168
+ function escapeHeadAttrValue(value) {
169
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
170
+ }
171
+ function serializeHeadEntry(tag, attrs) {
172
+ const plan = tag === "meta" ? planMetaEntry(attrs, STATIC_VALUE_RESOLVER) : planHeadElementEntry(attrs, STATIC_VALUE_RESOLVER);
173
+ if (plan.kind === "drop") return "";
174
+ const pairs = Array.from(plan.attributes, ([name, value]) => `${name}="${escapeHeadAttrValue(value)}"`).join(" ");
175
+ return `<${tag} ${pairs} />`;
176
+ }
13
177
 
14
178
  // src/platform/ssr.ts
15
- function sanitizeUrlAttr(name, value) {
16
- return name === "srcset" ? sanitizeSrcset(value) : sanitizeUrl(value);
179
+ function isPolicyAttr(lowerName) {
180
+ return isPolicyAttribute(lowerName);
181
+ }
182
+ function isForbiddenSsrAttr(lowerName) {
183
+ return isHtmlContentAttribute(lowerName);
184
+ }
185
+ function sanitizeSsrAttributeValue(lowerName, value) {
186
+ return sanitizeAttributeString(lowerName, value);
17
187
  }
18
188
  var _isDev = isDev();
19
189
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
20
190
  function isSafeAttrName(name) {
21
191
  return SAFE_ATTR_NAME.test(name);
22
192
  }
23
- var URL_ATTRS = /* @__PURE__ */ new Set([
24
- "href",
25
- "src",
26
- "action",
27
- "formaction",
28
- "cite",
29
- "poster",
30
- "background",
31
- "srcset",
32
- "ping",
33
- "manifest",
34
- "data",
35
- "xlink:href"
36
- ]);
37
193
  function ssrErrorComment(err) {
38
194
  if (_isDev) {
39
195
  const msg = escapeHtml(err instanceof Error ? err.message : String(err));
@@ -91,10 +247,11 @@ function renderToString(element) {
91
247
  const rawName = attr.name;
92
248
  if (!isSafeAttrName(rawName)) continue;
93
249
  if (isEventHandlerAttr(rawName)) continue;
250
+ if (isForbiddenSsrAttr(rawName)) continue;
94
251
  const lowerName = rawName.toLowerCase();
95
252
  let value = attr.value;
96
- if (URL_ATTRS.has(lowerName)) {
97
- value = sanitizeUrlAttr(lowerName, value);
253
+ if (isPolicyAttr(lowerName)) {
254
+ value = sanitizeSsrAttributeValue(lowerName, value);
98
255
  if (!value) continue;
99
256
  }
100
257
  html += ` ${rawName}="${escapeAttr(value)}"`;
@@ -135,9 +292,20 @@ function hydrate(component, container, options = {}) {
135
292
  }
136
293
  }
137
294
  }
138
- container.replaceChildren(clientTree);
295
+ replaceChildrenSafely(container, clientTree);
139
296
  container.setAttribute("data-sibu-hydrated", "true");
140
297
  }
298
+ function directTextOf(node) {
299
+ let out = "";
300
+ for (const child of Array.from(node.childNodes)) {
301
+ if (child.nodeType === 3) out += child.nodeValue ?? "";
302
+ }
303
+ return out.replace(/\s+/g, " ").trim();
304
+ }
305
+ function disposeAndReplace(outgoing, next) {
306
+ dispose(outgoing);
307
+ outgoing.replaceWith(next);
308
+ }
141
309
  function collectMismatches(serverNode, clientNode, path, out, max = 5) {
142
310
  if (out.length >= max) return;
143
311
  const nodePath = path || clientNode?.tagName?.toLowerCase() || "(root)";
@@ -213,6 +381,18 @@ function collectMismatches(serverNode, clientNode, path, out, max = 5) {
213
381
  });
214
382
  }
215
383
  }
384
+ const serverText = directTextOf(serverNode);
385
+ const clientText = directTextOf(clientNode);
386
+ if (serverText !== clientText) {
387
+ out.push({
388
+ kind: "text",
389
+ path: nodePath,
390
+ serverValue: serverText,
391
+ clientValue: clientText,
392
+ message: "Text content differs between server and client."
393
+ });
394
+ if (out.length >= max) return;
395
+ }
216
396
  const serverChildren = Array.from(serverNode.children);
217
397
  const clientChildren = Array.from(clientNode.children);
218
398
  const max2 = Math.max(serverChildren.length, clientChildren.length);
@@ -232,10 +412,11 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
232
412
  if (!Object.hasOwn(attrs, rawKey)) continue;
233
413
  if (!isSafeAttrName(rawKey)) continue;
234
414
  if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
415
+ if (isForbiddenSsrAttr(rawKey)) continue;
235
416
  const lowerKey = rawKey.toLowerCase();
236
417
  let value = String(attrs[rawKey]);
237
- if (URL_ATTRS.has(lowerKey)) {
238
- value = sanitizeUrlAttr(lowerKey, value);
418
+ if (isPolicyAttr(lowerKey)) {
419
+ value = sanitizeSsrAttributeValue(lowerKey, value);
239
420
  if (!value) continue;
240
421
  }
241
422
  out.push(`${rawKey}="${escapeAttr(value)}"`);
@@ -243,18 +424,7 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
243
424
  return out.join(" ");
244
425
  }
245
426
  function isDangerousMetaRefresh(metaProps) {
246
- let httpEquiv;
247
- let content;
248
- for (const k in metaProps) {
249
- const lk = k.toLowerCase();
250
- if (lk === "http-equiv") httpEquiv = metaProps[k];
251
- else if (lk === "content") content = metaProps[k];
252
- }
253
- if (typeof httpEquiv !== "string") return false;
254
- if (httpEquiv.toLowerCase() !== "refresh") return false;
255
- if (typeof content !== "string") return false;
256
- const normalized = stripControlChars(content).toLowerCase();
257
- return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
427
+ return isForbiddenMetaEntry(metaProps);
258
428
  }
259
429
  function renderToDocument(component, options = {}) {
260
430
  let content;
@@ -263,15 +433,8 @@ function renderToDocument(component, options = {}) {
263
433
  } catch (err) {
264
434
  content = ssrErrorComment(err);
265
435
  }
266
- const metaTags = (options.meta || []).map((attrs) => {
267
- if (isDangerousMetaRefresh(attrs)) return "";
268
- const pairs = buildAttrString(attrs);
269
- return pairs ? `<meta ${pairs} />` : "";
270
- }).filter(Boolean).join("\n ");
271
- const linkTags = (options.links || []).map((attrs) => {
272
- const pairs = buildAttrString(attrs);
273
- return pairs ? `<link ${pairs} />` : "";
274
- }).filter(Boolean).join("\n ");
436
+ const metaTags = (options.meta || []).map((attrs) => serializeHeadEntry("meta", attrs)).filter(Boolean).join("\n ");
437
+ const linkTags = (options.links || []).map((attrs) => serializeHeadEntry("link", attrs)).filter(Boolean).join("\n ");
275
438
  const scriptTags = (options.scripts || []).map((src) => {
276
439
  const safe = sanitizeUrl(String(src));
277
440
  if (!safe) return "";
@@ -332,14 +495,18 @@ async function* renderToStream(element) {
332
495
  const rawName = attr.name;
333
496
  if (!isSafeAttrName(rawName)) continue;
334
497
  if (isEventHandlerAttr(rawName)) continue;
498
+ if (isForbiddenSsrAttr(rawName)) continue;
335
499
  const lowerName = rawName.toLowerCase();
336
500
  let value = attr.value;
337
- if (URL_ATTRS.has(lowerName)) {
338
- value = sanitizeUrlAttr(lowerName, value);
501
+ if (isPolicyAttr(lowerName)) {
502
+ value = sanitizeSsrAttributeValue(lowerName, value);
339
503
  if (!value) continue;
340
504
  }
341
505
  openTag += ` ${rawName}="${escapeAttr(value)}"`;
342
506
  }
507
+ if (element.dataset && !element.dataset.sibuHydrate) {
508
+ openTag += ` data-sibu-ssr="true"`;
509
+ }
343
510
  if (VOID_ELEMENTS.has(tag)) {
344
511
  yield `${openTag} />`;
345
512
  return;
@@ -386,9 +553,13 @@ function island(id, component) {
386
553
  el.setAttribute("data-sibu-island", id);
387
554
  return el;
388
555
  }
556
+ function isAlreadyHydrated(element) {
557
+ return element.hasAttribute("data-sibu-hydrated");
558
+ }
389
559
  function hydrateIslands(container, islands) {
390
560
  const markers = container.querySelectorAll("[data-sibu-island]");
391
561
  for (const marker of Array.from(markers)) {
562
+ if (isAlreadyHydrated(marker)) continue;
392
563
  const id = marker.getAttribute("data-sibu-island") ?? "";
393
564
  if (!Object.hasOwn(islands, id)) continue;
394
565
  const factory = islands[id];
@@ -396,7 +567,7 @@ function hydrateIslands(container, islands) {
396
567
  const clientTree = factory();
397
568
  clientTree.setAttribute("data-sibu-island", id);
398
569
  clientTree.setAttribute("data-sibu-hydrated", "true");
399
- marker.replaceWith(clientTree);
570
+ disposeAndReplace(marker, clientTree);
400
571
  }
401
572
  container.setAttribute("data-sibu-hydrated", "partial");
402
573
  }
@@ -404,6 +575,7 @@ function hydrateProgressively(container, islands, options) {
404
575
  const markers = container.querySelectorAll("[data-sibu-island]");
405
576
  const cleanups = [];
406
577
  for (const marker of Array.from(markers)) {
578
+ if (isAlreadyHydrated(marker)) continue;
407
579
  const id = marker.getAttribute("data-sibu-island") ?? "";
408
580
  if (!Object.hasOwn(islands, id)) continue;
409
581
  const factory = islands[id];
@@ -413,10 +585,11 @@ function hydrateProgressively(container, islands, options) {
413
585
  for (const entry of entries) {
414
586
  if (entry.isIntersecting) {
415
587
  observer.disconnect();
588
+ if (isAlreadyHydrated(marker) || marker.parentNode === null) break;
416
589
  const clientTree = factory();
417
590
  clientTree.setAttribute("data-sibu-island", id);
418
591
  clientTree.setAttribute("data-sibu-hydrated", "true");
419
- marker.replaceWith(clientTree);
592
+ disposeAndReplace(marker, clientTree);
420
593
  break;
421
594
  }
422
595
  }
@@ -472,7 +645,7 @@ function suspenseSwapScript(id, nonce) {
472
645
  );
473
646
  }
474
647
  const nonceAttr = nonce ? ` nonce="${escapeAttr(nonce)}"` : "";
475
- return `<script${nonceAttr}>(function(){var t=document.getElementById("sibu-resolved-${id}");var f=document.querySelector('[data-sibu-suspense-id="${id}"]');if(t&&f){while(t.firstChild)f.appendChild(t.firstChild);t.remove();f.removeAttribute("data-sibu-suspense-id");}})()</script>`;
648
+ 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>`;
476
649
  }
477
650
  async function* renderToSuspenseStream(element, pendingBoundaries = [], options) {
478
651
  yield* renderToStream(element);
@@ -522,6 +695,10 @@ function escapeAttr(str) {
522
695
  }
523
696
 
524
697
  export {
698
+ planMetaEntry,
699
+ planHeadElementEntry,
700
+ STATIC_VALUE_RESOLVER,
701
+ serializeHeadEntry,
525
702
  renderToString,
526
703
  hydrate,
527
704
  trustHTML,