sibujs 4.6.0 → 4.8.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 (94) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +62 -8
  3. package/dist/browser.d.cts +23 -0
  4. package/dist/browser.d.ts +23 -0
  5. package/dist/browser.js +6 -5
  6. package/dist/build.cjs +2388 -1004
  7. package/dist/build.d.cts +147 -44
  8. package/dist/build.d.ts +147 -44
  9. package/dist/build.js +1410 -564
  10. package/dist/cdn.dev.global.js +14 -12
  11. package/dist/cdn.full.dev.global.js +15 -13
  12. package/dist/cdn.full.global.js +14 -12
  13. package/dist/cdn.global.js +12 -10
  14. package/dist/chunk-3ACAAQIN.js +224 -0
  15. package/dist/{chunk-7NCARGJW.js → chunk-44MTL7ED.js} +6 -6
  16. package/dist/{chunk-BAAG6ZTI.js → chunk-4WMSTZ7J.js} +8 -5
  17. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  18. package/dist/{chunk-DKGBBKOF.js → chunk-C5PEWEGH.js} +20 -5
  19. package/dist/{chunk-3ISI6ACU.js → chunk-C7AA2CZX.js} +1 -1
  20. package/dist/{chunk-UZQ6ALFS.js → chunk-CZPRSWSS.js} +1 -1
  21. package/dist/{chunk-D33YTSX5.js → chunk-DSID757X.js} +5 -4
  22. package/dist/{chunk-OXUY2A6L.js → chunk-EGUQLBHE.js} +22 -8
  23. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  24. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  25. package/dist/{chunk-HYXDKS4N.js → chunk-GSGNX35Q.js} +5 -5
  26. package/dist/{chunk-7GQCWFOE.js → chunk-J3YQ457H.js} +171 -85
  27. package/dist/{chunk-ORMZXBKQ.js → chunk-JUTFX2RM.js} +18 -10
  28. package/dist/{chunk-QE4TTDU3.js → chunk-KJ3UKABJ.js} +12 -11
  29. package/dist/{chunk-XRRZKZYX.js → chunk-LI4W3U6C.js} +1 -1
  30. package/dist/{chunk-47M47FOM.js → chunk-O6TRVMJI.js} +58 -6
  31. package/dist/{chunk-JSXPZCET.js → chunk-Q5J2LVKH.js} +1 -1
  32. package/dist/{chunk-5MT6SJ3P.js → chunk-QGMSESDD.js} +10 -5
  33. package/dist/{chunk-AIF3Z2T7.js → chunk-RGLPEBDE.js} +13 -10
  34. package/dist/{chunk-5Q4R7HCL.js → chunk-TQQUN4WR.js} +11 -2
  35. package/dist/{chunk-PK6FK2G2.js → chunk-UCNP3HVM.js} +183 -30
  36. package/dist/{chunk-XC4MEKGA.js → chunk-V3LHZSU5.js} +8 -2
  37. package/dist/{chunk-VVWPJ543.js → chunk-VWT3VXPL.js} +55 -20
  38. package/dist/{chunk-SXXVZMKZ.js → chunk-ZVLHNEEL.js} +17 -35
  39. package/dist/{contracts-DRIuclVT.d.cts → contracts-CLqzJnOV.d.ts} +28 -17
  40. package/dist/{contracts-DRIuclVT.d.ts → contracts-CTOJXu-x.d.cts} +28 -17
  41. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  42. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  43. package/dist/data.cjs +205 -39
  44. package/dist/data.d.cts +119 -3
  45. package/dist/data.d.ts +119 -3
  46. package/dist/data.js +6 -6
  47. package/dist/devtools.cjs +18 -5
  48. package/dist/devtools.js +5 -5
  49. package/dist/ecosystem.cjs +462 -58
  50. package/dist/ecosystem.d.cts +2 -1
  51. package/dist/ecosystem.d.ts +2 -1
  52. package/dist/ecosystem.js +9 -8
  53. package/dist/extras.cjs +852 -296
  54. package/dist/extras.d.cts +5 -4
  55. package/dist/extras.d.ts +5 -4
  56. package/dist/extras.js +24 -23
  57. package/dist/index.cjs +721 -176
  58. package/dist/index.d.cts +220 -169
  59. package/dist/index.d.ts +220 -169
  60. package/dist/index.js +16 -12
  61. package/dist/motion.cjs +12 -4
  62. package/dist/motion.js +3 -3
  63. package/dist/patterns.cjs +37 -21
  64. package/dist/patterns.d.cts +2 -1
  65. package/dist/patterns.d.ts +2 -1
  66. package/dist/patterns.js +6 -6
  67. package/dist/performance.cjs +48 -6
  68. package/dist/performance.d.cts +12 -2
  69. package/dist/performance.d.ts +12 -2
  70. package/dist/performance.js +8 -6
  71. package/dist/plugins.cjs +672 -261
  72. package/dist/plugins.d.cts +131 -13
  73. package/dist/plugins.d.ts +131 -13
  74. package/dist/plugins.js +83 -47
  75. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  76. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.cts} +6 -0
  77. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.ts} +6 -0
  78. package/dist/ssr.cjs +509 -142
  79. package/dist/ssr.d.cts +55 -5
  80. package/dist/ssr.d.ts +55 -5
  81. package/dist/ssr.js +11 -9
  82. package/dist/{tagFactory-DFstCLQV.d.cts → tagFactory-8qL9LCIx.d.cts} +58 -19
  83. package/dist/{tagFactory-DkaNVUNV.d.ts → tagFactory-BL2fymez.d.ts} +58 -19
  84. package/dist/testing.cjs +17 -4
  85. package/dist/testing.js +4 -4
  86. package/dist/types-CJFViL6Q.d.cts +26 -0
  87. package/dist/types-CJFViL6Q.d.ts +26 -0
  88. package/dist/ui.cjs +635 -254
  89. package/dist/ui.d.cts +3 -2
  90. package/dist/ui.d.ts +3 -2
  91. package/dist/ui.js +9 -8
  92. package/dist/widgets.cjs +22 -14
  93. package/dist/widgets.js +7 -7
  94. package/package.json +8 -5
package/dist/plugins.cjs CHANGED
@@ -152,7 +152,7 @@ function stripControlChars(value) {
152
152
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
153
153
  }
154
154
  function canonicalAttrName(name) {
155
- return name.replace(/[A-Z]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32));
155
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
156
156
  }
157
157
  function isEventHandlerAttr(name) {
158
158
  if (name.length < 3) return false;
@@ -163,6 +163,9 @@ function sanitizeUrl(url) {
163
163
  const probe = stripControlChars(url).trim();
164
164
  if (!probe) return "";
165
165
  const output2 = url.replace(OUTER_TRIM, "");
166
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
167
+ return "";
168
+ }
166
169
  const lower = probe.toLowerCase();
167
170
  let schemeEnd = -1;
168
171
  for (let i2 = 0; i2 < lower.length; i2++) {
@@ -179,18 +182,57 @@ function sanitizeUrl(url) {
179
182
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
180
183
  return output2;
181
184
  }
185
+ function isSrcsetWhitespace(ch) {
186
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
187
+ }
182
188
  function sanitizeSrcset(value) {
183
- const parts = value.split(",");
189
+ const input2 = String(value);
184
190
  const out = [];
185
- for (let i2 = 0; i2 < parts.length; i2++) {
186
- const part = parts[i2].trim();
187
- if (!part) continue;
188
- const m = part.match(/^(\S+)(\s+.+)?$/);
189
- if (!m) continue;
190
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
191
- const safe = sanitizeUrl(m[1]);
191
+ let i2 = 0;
192
+ const n = input2.length;
193
+ while (i2 < n) {
194
+ while (i2 < n && (isSrcsetWhitespace(input2[i2]) || input2[i2] === ",")) i2++;
195
+ if (i2 >= n) break;
196
+ const urlStart = i2;
197
+ while (i2 < n && !isSrcsetWhitespace(input2[i2])) i2++;
198
+ let url = input2.slice(urlStart, i2);
199
+ const descriptors = [];
200
+ if (url.endsWith(",")) {
201
+ url = url.replace(/,+$/, "");
202
+ } else {
203
+ let current = "";
204
+ let inParens = false;
205
+ while (i2 < n) {
206
+ const ch = input2[i2];
207
+ if (inParens) {
208
+ current += ch;
209
+ if (ch === ")") inParens = false;
210
+ i2++;
211
+ continue;
212
+ }
213
+ if (isSrcsetWhitespace(ch)) {
214
+ if (current) descriptors.push(current);
215
+ current = "";
216
+ i2++;
217
+ continue;
218
+ }
219
+ if (ch === ",") {
220
+ i2++;
221
+ break;
222
+ }
223
+ if (ch === "(") inParens = true;
224
+ current += ch;
225
+ i2++;
226
+ }
227
+ if (current) descriptors.push(current);
228
+ }
229
+ if (url === "") continue;
230
+ if (descriptors.length > 1) continue;
231
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
232
+ const safe = sanitizeUrl(url);
192
233
  if (!safe) continue;
193
- out.push(m[2] ? `${safe}${m[2]}` : safe);
234
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
235
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
194
236
  }
195
237
  return out.join(", ");
196
238
  }
@@ -259,7 +301,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
259
301
  "behavior: loads and runs a remote HTC script",
260
302
  "@import pulls in a remote stylesheet",
261
303
  "image-set() fetches remote images, the same exfiltration channel as url()",
262
- "filter:progid activates legacy scriptable filters"
304
+ "filter:progid activates legacy scriptable filters",
305
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
306
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
263
307
  ];
264
308
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
265
309
  const where = describeElement(ctx?.element);
@@ -274,27 +318,110 @@ function warnDroppedDeclaration(value, reason, ctx) {
274
318
  return `style declaration "${declaration}" was dropped by the style sanitizer on ${where} \u2014 ${reasons[reason]}. The element renders without it, which usually looks like a missing image rather than an error. Sanctioned alternatives: for a content image use an <img> element (or a <picture>); for decoration, put the rule in a stylesheet and set a class instead \u2014 stylesheet CSS is not sanitized, only inline style attributes are. If this declaration is not yours, it came from untrusted data and the drop just prevented an exfiltration.`;
275
319
  });
276
320
  }
321
+ function normalizeCssForScan(value) {
322
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
323
+ return normalized.toLowerCase().replace(/\s+/g, "");
324
+ }
277
325
  function blockedCssReason(value) {
278
326
  if (!CSS_DANGER_GATE.test(value)) return -1;
279
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
280
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
327
+ const lower = normalizeCssForScan(value);
281
328
  for (let i2 = 0; i2 < BLOCKED_CSS_NEEDLES.length; i2++) {
282
329
  if (lower.includes(BLOCKED_CSS_NEEDLES[i2])) return i2;
283
330
  }
284
331
  return -1;
285
332
  }
286
- function sanitizeCSSValue(value, context) {
287
- const reason = blockedCssReason(value);
333
+ function sanitizeCSSDeclaration(property, value, context) {
334
+ const reason = blockedDeclarationReason(property, value);
288
335
  if (reason === -1) return value;
289
- warnDroppedDeclaration(value, reason, context);
336
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
290
337
  return "";
291
338
  }
339
+ function blockedDeclarationReason(property, value) {
340
+ const reason = blockedCssReason(value);
341
+ if (reason !== -1) return reason;
342
+ const name = canonicalAttrName(property);
343
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
344
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
345
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
346
+ return CSS_REASON_PROGID;
347
+ }
348
+ return -1;
349
+ }
350
+ function parseStyleDeclarations(css) {
351
+ const chunks = [];
352
+ let buf = "";
353
+ let depth = 0;
354
+ let quote2 = "";
355
+ for (let i2 = 0; i2 < css.length; i2++) {
356
+ const ch = css[i2];
357
+ if (quote2) {
358
+ buf += ch;
359
+ if (ch === "\\" && i2 + 1 < css.length) buf += css[++i2];
360
+ else if (ch === quote2) quote2 = "";
361
+ continue;
362
+ }
363
+ if (ch === "\\") {
364
+ buf += ch;
365
+ if (i2 + 1 < css.length) buf += css[++i2];
366
+ continue;
367
+ }
368
+ if (ch === "/" && css[i2 + 1] === "*") {
369
+ const end = css.indexOf("*/", i2 + 2);
370
+ if (end === -1) return null;
371
+ i2 = end + 1;
372
+ continue;
373
+ }
374
+ if (ch === '"' || ch === "'") quote2 = ch;
375
+ else if (ch === "(" || ch === "[") depth++;
376
+ else if (ch === ")" || ch === "]") {
377
+ if (--depth < 0) return null;
378
+ } else if (ch === "{" || ch === "}") return null;
379
+ else if (ch === ";" && depth === 0) {
380
+ chunks.push(buf);
381
+ buf = "";
382
+ continue;
383
+ }
384
+ buf += ch;
385
+ }
386
+ if (quote2 || depth !== 0) return null;
387
+ chunks.push(buf);
388
+ const out = [];
389
+ for (const chunk of chunks) {
390
+ const colon = chunk.indexOf(":");
391
+ if (colon === -1) continue;
392
+ const rawName = chunk.slice(0, colon).trim();
393
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
394
+ let value = chunk.slice(colon + 1).trim();
395
+ const bang = /!\s*important\s*$/i.exec(value);
396
+ if (bang) value = value.slice(0, bang.index).trim();
397
+ if (value === "") continue;
398
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
399
+ out.push([property, value, bang !== null]);
400
+ }
401
+ return out;
402
+ }
403
+ function sanitizeStyleDeclarationList(cssText, context) {
404
+ const input2 = String(cssText);
405
+ const declarations = parseStyleDeclarations(input2);
406
+ if (declarations === null) {
407
+ if (blockedCssReason(input2) !== -1) warnDroppedDeclaration(input2, blockedCssReason(input2), context);
408
+ return "";
409
+ }
410
+ const kept = [];
411
+ for (const [property, value, important] of declarations) {
412
+ const reason = blockedDeclarationReason(property, value);
413
+ if (reason !== -1) {
414
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
415
+ continue;
416
+ }
417
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
418
+ }
419
+ return kept.join("; ");
420
+ }
292
421
  function sanitizeStyleAttribute(cssText, context) {
293
422
  const input2 = String(cssText);
294
423
  if (input2.trim() === "") return "";
295
- if (typeof document === "undefined") {
296
- return sanitizeCSSValue(input2, context) === "" ? "" : input2;
297
- }
424
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input2, context);
298
425
  const probe = document.createElement("div");
299
426
  try {
300
427
  probe.style.cssText = input2;
@@ -305,8 +432,7 @@ function sanitizeStyleAttribute(cssText, context) {
305
432
  for (let i2 = 0; i2 < probe.style.length; i2++) {
306
433
  const property = probe.style[i2];
307
434
  const value = probe.style.getPropertyValue(property);
308
- let reason = blockedCssReason(value);
309
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
435
+ const reason = blockedDeclarationReason(property, value);
310
436
  if (reason !== -1) {
311
437
  warnDroppedDeclaration(value, reason, { property, element: context?.element });
312
438
  continue;
@@ -333,14 +459,21 @@ function sanitizeAttributeString(attr, value, context) {
333
459
  if (lower === "style") return sanitizeStyleAttribute(value, context);
334
460
  return value;
335
461
  }
336
- var SAFE_URL_PROTOCOLS, OUTER_TRIM, SRCSET_DESCRIPTOR, CSS_DANGER_GATE, BLOCKED_CSS_NEEDLES, warnedDeclarations, MAX_WARNED_DECLARATIONS, declarationCapAnnounced, URL_ATTRIBUTES, HTML_CONTENT_ATTRIBUTES;
462
+ function resolveAttributeValue(attr, value, context) {
463
+ if (!isPolicyAttribute(attr)) return value;
464
+ const safe = sanitizeAttributeString(attr, value, context);
465
+ return safe === "" ? null : safe;
466
+ }
467
+ var ASCII_UPPER, ASCII_UPPER_G, SAFE_URL_PROTOCOLS, OUTER_TRIM, SRCSET_DESCRIPTOR, CSS_DANGER_GATE, BLOCKED_CSS_NEEDLES, CSS_REASON_MOZ_BINDING, CSS_REASON_BEHAVIOR, CSS_REASON_PROGID, warnedDeclarations, MAX_WARNED_DECLARATIONS, declarationCapAnnounced, URL_ATTRIBUTES, HTML_CONTENT_ATTRIBUTES;
337
468
  var init_sanitize = __esm({
338
469
  "src/utils/sanitize.ts"() {
339
470
  "use strict";
340
471
  init_dev();
472
+ ASCII_UPPER = /[A-Z]/;
473
+ ASCII_UPPER_G = /[A-Z]/g;
341
474
  SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
342
475
  OUTER_TRIM = /^[\x00-\x20\x7f-\x9f]+|[\x00-\x20\x7f-\x9f]+$/g;
343
- SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
476
+ SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
344
477
  CSS_DANGER_GATE = /[(:@\\]/;
345
478
  BLOCKED_CSS_NEEDLES = [
346
479
  "url(",
@@ -351,8 +484,16 @@ var init_sanitize = __esm({
351
484
  "behavior:",
352
485
  "@import",
353
486
  "image-set(",
354
- "filter:progid"
487
+ "filter:progid",
488
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
489
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
490
+ // today, which is exactly when a blocklist should get ahead of it.
491
+ "image(",
492
+ "src("
355
493
  ];
494
+ CSS_REASON_MOZ_BINDING = 4;
495
+ CSS_REASON_BEHAVIOR = 5;
496
+ CSS_REASON_PROGID = 8;
356
497
  warnedDeclarations = /* @__PURE__ */ new Set();
357
498
  MAX_WARNED_DECLARATIONS = 100;
358
499
  declarationCapAnnounced = false;
@@ -378,6 +519,224 @@ var init_sanitize = __esm({
378
519
  }
379
520
  });
380
521
 
522
+ // src/utils/metaRefresh.ts
523
+ function isAsciiWhitespace(ch) {
524
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
525
+ }
526
+ function trimAsciiWhitespace(value) {
527
+ let start = 0;
528
+ let end = value.length;
529
+ while (start < end && isAsciiWhitespace(value[start])) start++;
530
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
531
+ return value.slice(start, end);
532
+ }
533
+ function parseMetaRefreshContent(content) {
534
+ const raw = trimAsciiWhitespace(stripControlChars(content));
535
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
536
+ let i2 = 0;
537
+ while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
538
+ if (i2 === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
539
+ const delay = Number.parseInt(raw.slice(0, i2), 10);
540
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
541
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
542
+ if (i2 === raw.length) return { kind: "delay-only", delay };
543
+ if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
544
+ i2++;
545
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
546
+ const keyStart = i2;
547
+ while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
548
+ const key = raw.slice(keyStart, i2).toLowerCase();
549
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
550
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
551
+ if (raw[i2] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
552
+ i2++;
553
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
554
+ let destination;
555
+ const quote2 = raw[i2];
556
+ if (quote2 === '"' || quote2 === "'") {
557
+ const close = raw.indexOf(quote2, i2 + 1);
558
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
559
+ destination = raw.slice(i2 + 1, close);
560
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
561
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
562
+ } else {
563
+ destination = trimAsciiWhitespace(raw.slice(i2));
564
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
565
+ }
566
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
567
+ const sanitizedDestination = sanitizeUrl(destination);
568
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
569
+ return { kind: "allowed", delay, destination, sanitizedDestination };
570
+ }
571
+ function resolveMetaRefreshPolicy(attributes) {
572
+ let httpEquiv;
573
+ let content;
574
+ let duplicateName;
575
+ const seen = /* @__PURE__ */ new Set();
576
+ for (const [key, value] of attributes) {
577
+ const canonical = canonicalAttrName(key);
578
+ if (seen.has(canonical)) duplicateName = canonical;
579
+ seen.add(canonical);
580
+ if (canonical === "http-equiv") httpEquiv = value;
581
+ else if (canonical === "content") content = value;
582
+ }
583
+ if (duplicateName !== void 0) {
584
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
585
+ }
586
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
587
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
588
+ return { kind: "not-refresh" };
589
+ }
590
+ if (typeof content !== "string") return { kind: "not-refresh" };
591
+ return parseMetaRefreshContent(content);
592
+ }
593
+ function normalizeMetaAttributes(props) {
594
+ const out = /* @__PURE__ */ new Map();
595
+ const seen = /* @__PURE__ */ new Set();
596
+ for (const key of Object.keys(props)) {
597
+ const canonical = canonicalAttrName(key);
598
+ if (seen.has(canonical)) return null;
599
+ seen.add(canonical);
600
+ out.set(key, props[key]);
601
+ }
602
+ return out;
603
+ }
604
+ function isForbiddenMetaEntry(props) {
605
+ const normalized = normalizeMetaAttributes(props);
606
+ if (normalized === null) return true;
607
+ return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
608
+ }
609
+ var forbidden;
610
+ var init_metaRefresh = __esm({
611
+ "src/utils/metaRefresh.ts"() {
612
+ "use strict";
613
+ init_dev();
614
+ init_sanitize();
615
+ forbidden = (reason) => ({ kind: "forbidden", reason });
616
+ }
617
+ });
618
+
619
+ // src/utils/elementPolicy.ts
620
+ function canonicalTagName(tag) {
621
+ return canonicalAttrName(tag);
622
+ }
623
+ function isBlockedElement(tag) {
624
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
625
+ }
626
+ function isCodeTextElement(tag) {
627
+ return CODE_TEXT_ELEMENTS.has(canonicalTagName(tag));
628
+ }
629
+ function isHtmlNamespace(el) {
630
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
631
+ }
632
+ function metaSnapshot(el, canonical, value) {
633
+ const snapshot = /* @__PURE__ */ new Map();
634
+ const attrs = el.attributes;
635
+ for (let i2 = 0; i2 < attrs.length; i2++) {
636
+ const attr = attrs[i2];
637
+ if (attr.name === canonical) continue;
638
+ snapshot.set(attr.name, attr.value);
639
+ }
640
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
641
+ return snapshot;
642
+ }
643
+ function claimReactiveMeta(el) {
644
+ if (reactiveMetaElements.has(el)) return;
645
+ reactiveMetaElements.add(el);
646
+ if (el.getAttribute("content") === null) return;
647
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
648
+ el.removeAttribute("content");
649
+ }
650
+ }
651
+ function metaRefusal(el, name, value, origin) {
652
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
653
+ const canonical = canonicalAttrName(name);
654
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
655
+ if (origin === "reactive") claimReactiveMeta(el);
656
+ if (value === null) return ContextualRefusal.None;
657
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
658
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
659
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
660
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
661
+ return ContextualRefusal.None;
662
+ }
663
+ function animationRefusal(name, value) {
664
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
665
+ const target = stripControlChars(value);
666
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
667
+ }
668
+ function relHasStylesheet(rel) {
669
+ if (rel === null) return false;
670
+ const lower = canonicalAttrName(rel);
671
+ if (!lower.includes("stylesheet")) return false;
672
+ const tokens = lower.split(/[\t\n\f\r ]+/);
673
+ for (let i2 = 0; i2 < tokens.length; i2++) if (tokens[i2] === "stylesheet") return true;
674
+ return false;
675
+ }
676
+ function stylesheetRefusal(el, name, value, origin) {
677
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
678
+ const canonical = canonicalAttrName(name);
679
+ if (canonical === "href") {
680
+ if (origin === "static" || value === null) return ContextualRefusal.None;
681
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
682
+ runtimeHrefLinks.add(el);
683
+ return ContextualRefusal.None;
684
+ }
685
+ if (canonical === "rel") {
686
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
687
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
688
+ }
689
+ return ContextualRefusal.None;
690
+ }
691
+ function contextualAttributeRefusal(el, name, value, origin) {
692
+ const local = el.localName;
693
+ if (local === "meta") return metaRefusal(el, name, value, origin);
694
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
695
+ if (local === "script") {
696
+ if (origin === "static" || value === null) return ContextualRefusal.None;
697
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
698
+ }
699
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
700
+ return ContextualRefusal.None;
701
+ }
702
+ function settleAttributeProvenance(el, name, origin) {
703
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
704
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
705
+ else runtimeHrefLinks.delete(el);
706
+ }
707
+ function isForbiddenMetaSnapshot(attributes) {
708
+ return resolveMetaRefreshPolicy(attributes).kind === "forbidden";
709
+ }
710
+ var HTML_NS, SVG_NS, BLOCKED_ELEMENTS, CODE_TEXT_ELEMENTS, ContextualRefusal, SCRIPT_SOURCE_ATTRIBUTES, SVG_ANIMATION_ELEMENTS, reactiveMetaElements, runtimeHrefLinks;
711
+ var init_elementPolicy = __esm({
712
+ "src/utils/elementPolicy.ts"() {
713
+ "use strict";
714
+ init_metaRefresh();
715
+ init_sanitize();
716
+ HTML_NS = "http://www.w3.org/1999/xhtml";
717
+ SVG_NS = "http://www.w3.org/2000/svg";
718
+ BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
719
+ CODE_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
720
+ ContextualRefusal = {
721
+ None: 0,
722
+ /** The element would carry a refresh directive the shared policy forbids. */
723
+ MetaRefreshForbidden: 1,
724
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
725
+ MetaRefreshReactive: 2,
726
+ /** A runtime value may not choose which program a `<script>` runs. */
727
+ ScriptSource: 3,
728
+ /** An SVG animation may not target a URL, handler or document attribute. */
729
+ AnimationTarget: 4,
730
+ /** A runtime-written `href` may not become an applied stylesheet. */
731
+ StylesheetSource: 5
732
+ };
733
+ SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
734
+ SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
735
+ reactiveMetaElements = /* @__PURE__ */ new WeakSet();
736
+ runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
737
+ }
738
+ });
739
+
381
740
  // src/core/rendering/dispose.ts
382
741
  var dispose_exports = {};
383
742
  __export(dispose_exports, {
@@ -607,99 +966,22 @@ var init_ssr_context = __esm({
607
966
  }
608
967
  });
609
968
 
610
- // src/utils/metaRefresh.ts
611
- function isAsciiWhitespace(ch) {
612
- return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
613
- }
614
- function trimAsciiWhitespace(value) {
615
- let start = 0;
616
- let end = value.length;
617
- while (start < end && isAsciiWhitespace(value[start])) start++;
618
- while (end > start && isAsciiWhitespace(value[end - 1])) end--;
619
- return value.slice(start, end);
620
- }
621
- function parseMetaRefreshContent(content) {
622
- const raw = trimAsciiWhitespace(stripControlChars(content));
623
- if (raw === "") return forbidden("empty content");
624
- let i2 = 0;
625
- while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
626
- if (i2 === 0) return forbidden("missing or non-numeric delay");
627
- const delay = Number.parseInt(raw.slice(0, i2), 10);
628
- if (!Number.isFinite(delay)) return forbidden("unparseable delay");
629
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
630
- if (i2 === raw.length) return { kind: "delay-only", delay };
631
- if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden("unexpected text after delay");
632
- i2++;
633
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
634
- const keyStart = i2;
635
- while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
636
- const key = raw.slice(keyStart, i2).toLowerCase();
637
- if (key !== "url") return forbidden(`unsupported refresh key ${JSON.stringify(key)}`);
638
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
639
- if (raw[i2] !== "=") return forbidden("missing '=' after url");
640
- i2++;
641
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
642
- let destination;
643
- const quote2 = raw[i2];
644
- if (quote2 === '"' || quote2 === "'") {
645
- const close = raw.indexOf(quote2, i2 + 1);
646
- if (close === -1) return forbidden("unterminated quoted destination");
647
- destination = raw.slice(i2 + 1, close);
648
- const rest = trimAsciiWhitespace(raw.slice(close + 1));
649
- if (rest !== "") return forbidden("trailing text after quoted destination");
650
- } else {
651
- destination = trimAsciiWhitespace(raw.slice(i2));
652
- if (/[;,]/.test(destination)) return forbidden("multiple refresh assignments");
653
- }
654
- if (destination === "") return forbidden("empty destination");
655
- const sanitizedDestination = sanitizeUrl(destination);
656
- if (sanitizedDestination === "") return forbidden("destination uses a disallowed protocol");
657
- return { kind: "allowed", delay, destination, sanitizedDestination };
658
- }
659
- function resolveMetaRefreshPolicy(attributes) {
660
- let httpEquiv;
661
- let content;
662
- let duplicateName;
663
- const seen = /* @__PURE__ */ new Set();
664
- for (const [key, value] of attributes) {
665
- const canonical = canonicalAttrName(key);
666
- if (seen.has(canonical)) duplicateName = canonical;
667
- seen.add(canonical);
668
- if (canonical === "http-equiv") httpEquiv = value;
669
- else if (canonical === "content") content = value;
670
- }
671
- if (duplicateName !== void 0) {
672
- return forbidden(`duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}`);
673
- }
674
- if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
675
- if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
676
- return { kind: "not-refresh" };
677
- }
678
- if (typeof content !== "string") return { kind: "not-refresh" };
679
- return parseMetaRefreshContent(content);
680
- }
681
- function normalizeMetaAttributes(props) {
682
- const out = /* @__PURE__ */ new Map();
683
- const seen = /* @__PURE__ */ new Set();
684
- for (const key of Object.keys(props)) {
685
- const canonical = canonicalAttrName(key);
686
- if (seen.has(canonical)) return null;
687
- seen.add(canonical);
688
- out.set(key, props[key]);
689
- }
690
- return out;
969
+ // src/utils/guards.ts
970
+ function isUnsafeKey(key) {
971
+ return UNSAFE_KEYS.has(key);
691
972
  }
692
- function isForbiddenMetaEntry(props) {
693
- const normalized = normalizeMetaAttributes(props);
694
- if (normalized === null) return true;
695
- return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
973
+ function readOwnGlobal(key) {
974
+ if (typeof window === "undefined") return void 0;
975
+ if (!Object.hasOwn(window, key)) return void 0;
976
+ const value = window[key];
977
+ if (typeof Node !== "undefined" && value instanceof Node) return void 0;
978
+ return value;
696
979
  }
697
- var forbidden;
698
- var init_metaRefresh = __esm({
699
- "src/utils/metaRefresh.ts"() {
980
+ var UNSAFE_KEYS;
981
+ var init_guards = __esm({
982
+ "src/utils/guards.ts"() {
700
983
  "use strict";
701
- init_sanitize();
702
- forbidden = (reason) => ({ kind: "forbidden", reason });
984
+ UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
703
985
  }
704
986
  });
705
987
 
@@ -712,9 +994,7 @@ function isEmittableHeadAttrName(name) {
712
994
  return true;
713
995
  }
714
996
  function sanitizeHeadAttrValue(canonical, value) {
715
- if (!isPolicyAttribute(canonical)) return value;
716
- const safe = sanitizeAttributeString(canonical, value);
717
- return safe === "" ? null : safe;
997
+ return resolveAttributeValue(canonical, value);
718
998
  }
719
999
  function findDuplicateAttributeName(names) {
720
1000
  const seen = /* @__PURE__ */ new Set();
@@ -801,15 +1081,9 @@ __export(ssr_exports, {
801
1081
  suspenseSwapScript: () => suspenseSwapScript,
802
1082
  trustHTML: () => trustHTML
803
1083
  });
804
- function isPolicyAttr(lowerName) {
805
- return isPolicyAttribute(lowerName);
806
- }
807
1084
  function isForbiddenSsrAttr(lowerName) {
808
1085
  return isHtmlContentAttribute(lowerName);
809
1086
  }
810
- function sanitizeSsrAttributeValue(lowerName, value) {
811
- return sanitizeAttributeString(lowerName, value);
812
- }
813
1087
  function isSafeAttrName(name) {
814
1088
  return SAFE_ATTR_NAME.test(name);
815
1089
  }
@@ -823,6 +1097,63 @@ function ssrErrorComment(err) {
823
1097
  function safeCommentText(text2) {
824
1098
  return text2.replace(/-->/g, "--&gt;").replace(/--!>/g, "--!&gt;").replace(/<!--/g, "&lt;!--").replace(/--$/g, "--&#45;");
825
1099
  }
1100
+ function findNativeGetter(proto, name) {
1101
+ for (let p2 = proto; p2; p2 = Object.getPrototypeOf(p2)) {
1102
+ const get = Object.getOwnPropertyDescriptor(p2, name)?.get;
1103
+ if (get) return get;
1104
+ }
1105
+ return null;
1106
+ }
1107
+ function nativeAccessors() {
1108
+ const realm = Element;
1109
+ let accessors = nativeAccessorsByRealm.get(realm);
1110
+ if (!accessors) {
1111
+ accessors = {
1112
+ attributes: findNativeGetter(Element.prototype, "attributes"),
1113
+ tagName: findNativeGetter(Element.prototype, "tagName"),
1114
+ childNodes: findNativeGetter(Node.prototype, "childNodes"),
1115
+ getAttribute: Element.prototype.getAttribute
1116
+ };
1117
+ nativeAccessorsByRealm.set(realm, accessors);
1118
+ }
1119
+ return accessors;
1120
+ }
1121
+ function attributesOf(el) {
1122
+ const get = nativeAccessors().attributes;
1123
+ return get ? get.call(el) : el.attributes;
1124
+ }
1125
+ function tagNameOf(el) {
1126
+ const get = nativeAccessors().tagName;
1127
+ return get ? get.call(el) : el.tagName;
1128
+ }
1129
+ function childNodesOf(node) {
1130
+ const get = nativeAccessors().childNodes;
1131
+ return get ? get.call(node) : node.childNodes;
1132
+ }
1133
+ function getAttributeOf(el, name) {
1134
+ return nativeAccessors().getAttribute.call(el, name);
1135
+ }
1136
+ function serializeOpenTag(element, tag) {
1137
+ let html2 = `<${tag}`;
1138
+ const meta2 = tag === "meta" ? /* @__PURE__ */ new Map() : null;
1139
+ const attrs = attributesOf(element);
1140
+ for (let i2 = 0; i2 < attrs.length; i2++) {
1141
+ const attr = attrs[i2];
1142
+ const rawName = attr.name;
1143
+ if (!isSafeAttrName(rawName)) continue;
1144
+ if (isEventHandlerAttr(rawName)) continue;
1145
+ if (isForbiddenSsrAttr(rawName)) continue;
1146
+ const value = resolveAttributeValue(rawName.toLowerCase(), attr.value);
1147
+ if (value === null) continue;
1148
+ meta2?.set(rawName, value);
1149
+ html2 += ` ${rawName}="${escapeAttr(value)}"`;
1150
+ }
1151
+ if (meta2 && isForbiddenMetaSnapshot(meta2)) return null;
1152
+ if (!getAttributeOf(element, "data-sibu-hydrate")) {
1153
+ html2 += ` data-sibu-ssr="true"`;
1154
+ }
1155
+ return html2;
1156
+ }
826
1157
  function renderToString(element) {
827
1158
  if (element instanceof DocumentFragment) {
828
1159
  return Array.from(element.childNodes).map((child) => {
@@ -842,35 +1173,21 @@ function renderToString(element) {
842
1173
  if (!(element instanceof HTMLElement)) {
843
1174
  return escapeHtml(element.textContent || "");
844
1175
  }
845
- const tag = element.tagName.toLowerCase();
846
- if (tag === "script" || tag === "style") {
1176
+ const tag = tagNameOf(element).toLowerCase();
1177
+ if (isCodeTextElement(tag)) {
847
1178
  return DEV ? `<!--ssr:${tag}-stripped-->` : "";
848
1179
  }
849
1180
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
850
1181
  return DEV ? "<!--ssr:invalid-tag-->" : "";
851
1182
  }
852
- let html2 = `<${tag}`;
853
- for (const attr of Array.from(element.attributes)) {
854
- const rawName = attr.name;
855
- if (!isSafeAttrName(rawName)) continue;
856
- if (isEventHandlerAttr(rawName)) continue;
857
- if (isForbiddenSsrAttr(rawName)) continue;
858
- const lowerName = rawName.toLowerCase();
859
- let value = attr.value;
860
- if (isPolicyAttr(lowerName)) {
861
- value = sanitizeSsrAttributeValue(lowerName, value);
862
- if (!value) continue;
863
- }
864
- html2 += ` ${rawName}="${escapeAttr(value)}"`;
865
- }
866
- if (element.dataset && !element.dataset.sibuHydrate) {
867
- html2 += ` data-sibu-ssr="true"`;
868
- }
1183
+ const openTag = serializeOpenTag(element, tag);
1184
+ if (openTag === null) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
1185
+ let html2 = openTag;
869
1186
  if (VOID_ELEMENTS.has(tag)) {
870
1187
  return `${html2} />`;
871
1188
  }
872
1189
  html2 += ">";
873
- for (const child of Array.from(element.childNodes)) {
1190
+ for (const child of Array.from(childNodesOf(element))) {
874
1191
  try {
875
1192
  html2 += renderToString(child);
876
1193
  } catch (err) {
@@ -1020,12 +1337,8 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
1020
1337
  if (!isSafeAttrName(rawKey)) continue;
1021
1338
  if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
1022
1339
  if (isForbiddenSsrAttr(rawKey)) continue;
1023
- const lowerKey = rawKey.toLowerCase();
1024
- let value = String(attrs[rawKey]);
1025
- if (isPolicyAttr(lowerKey)) {
1026
- value = sanitizeSsrAttributeValue(lowerKey, value);
1027
- if (!value) continue;
1028
- }
1340
+ const value = resolveAttributeValue(rawKey.toLowerCase(), String(attrs[rawKey]));
1341
+ if (value === null) continue;
1029
1342
  out.push(`${rawKey}="${escapeAttr(value)}"`);
1030
1343
  }
1031
1344
  return out.join(" ");
@@ -1088,8 +1401,8 @@ async function* renderToStream(element) {
1088
1401
  yield escapeHtml(element.textContent || "");
1089
1402
  return;
1090
1403
  }
1091
- const tag = element.tagName.toLowerCase();
1092
- if (tag === "script" || tag === "style") {
1404
+ const tag = tagNameOf(element).toLowerCase();
1405
+ if (isCodeTextElement(tag)) {
1093
1406
  if (DEV) yield `<!--ssr:${tag}-stripped-->`;
1094
1407
  return;
1095
1408
  }
@@ -1097,29 +1410,17 @@ async function* renderToStream(element) {
1097
1410
  if (DEV) yield "<!--ssr:invalid-tag-->";
1098
1411
  return;
1099
1412
  }
1100
- let openTag = `<${tag}`;
1101
- for (const attr of Array.from(element.attributes)) {
1102
- const rawName = attr.name;
1103
- if (!isSafeAttrName(rawName)) continue;
1104
- if (isEventHandlerAttr(rawName)) continue;
1105
- if (isForbiddenSsrAttr(rawName)) continue;
1106
- const lowerName = rawName.toLowerCase();
1107
- let value = attr.value;
1108
- if (isPolicyAttr(lowerName)) {
1109
- value = sanitizeSsrAttributeValue(lowerName, value);
1110
- if (!value) continue;
1111
- }
1112
- openTag += ` ${rawName}="${escapeAttr(value)}"`;
1113
- }
1114
- if (element.dataset && !element.dataset.sibuHydrate) {
1115
- openTag += ` data-sibu-ssr="true"`;
1413
+ const openTag = serializeOpenTag(element, tag);
1414
+ if (openTag === null) {
1415
+ if (DEV) yield `<!--ssr:${tag}-stripped-->`;
1416
+ return;
1116
1417
  }
1117
1418
  if (VOID_ELEMENTS.has(tag)) {
1118
1419
  yield `${openTag} />`;
1119
1420
  return;
1120
1421
  }
1121
1422
  yield `${openTag}>`;
1122
- for (const child of Array.from(element.childNodes)) {
1423
+ for (const child of Array.from(childNodesOf(element))) {
1123
1424
  try {
1124
1425
  yield* renderToStream(child);
1125
1426
  } catch (err) {
@@ -1300,8 +1601,7 @@ function deserializeState(validate) {
1300
1601
  "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
1301
1602
  );
1302
1603
  }
1303
- const w = window;
1304
- const raw = w[SSR_DATA_ATTR];
1604
+ const raw = readOwnGlobal(SSR_DATA_ATTR);
1305
1605
  if (raw === void 0) return void 0;
1306
1606
  if (validate && !validate(raw)) return void 0;
1307
1607
  return raw;
@@ -1312,13 +1612,15 @@ function escapeHtml(str) {
1312
1612
  function escapeAttr(str) {
1313
1613
  return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1314
1614
  }
1315
- var SAFE_ATTR_NAME, VOID_ELEMENTS, SAFE_ID, SAFE_SUSPENSE_ID, SSR_DATA_ATTR, DEFAULT_MAX_SSR_BYTES;
1615
+ var SAFE_ATTR_NAME, VOID_ELEMENTS, nativeAccessorsByRealm, SAFE_ID, SAFE_SUSPENSE_ID, SSR_DATA_ATTR, DEFAULT_MAX_SSR_BYTES;
1316
1616
  var init_ssr = __esm({
1317
1617
  "src/platform/ssr.ts"() {
1318
1618
  "use strict";
1319
1619
  init_dev();
1320
1620
  init_dispose();
1321
1621
  init_ssr_context();
1622
+ init_elementPolicy();
1623
+ init_guards();
1322
1624
  init_headEntry();
1323
1625
  init_metaRefresh();
1324
1626
  init_sanitize();
@@ -1339,6 +1641,7 @@ var init_ssr = __esm({
1339
1641
  "track",
1340
1642
  "wbr"
1341
1643
  ]);
1644
+ nativeAccessorsByRealm = /* @__PURE__ */ new WeakMap();
1342
1645
  SAFE_ID = /^[A-Za-z0-9_-]+$/;
1343
1646
  SAFE_SUSPENSE_ID = /^[A-Za-z0-9_-]+$/;
1344
1647
  SSR_DATA_ATTR = "__SIBU_SSR_DATA__";
@@ -1425,6 +1728,7 @@ __export(plugins_exports, {
1425
1728
  setRouteTransition: () => setRouteTransition,
1426
1729
  setRoutes: () => setRoutes,
1427
1730
  t: () => t,
1731
+ translated: () => translated,
1428
1732
  triggerPluginError: () => triggerPluginError,
1429
1733
  triggerPluginMount: () => triggerPluginMount,
1430
1734
  triggerPluginUnmount: () => triggerPluginUnmount
@@ -1440,26 +1744,45 @@ init_sanitize();
1440
1744
 
1441
1745
  // src/utils/setSafeAttribute.ts
1442
1746
  init_dev();
1747
+ init_elementPolicy();
1443
1748
  init_sanitize();
1444
- var HTML_NS = "http://www.w3.org/1999/xhtml";
1445
- var SVG_NS = "http://www.w3.org/2000/svg";
1749
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
1750
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
1446
1751
  var XLINK_NS = "http://www.w3.org/1999/xlink";
1447
1752
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
1448
1753
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
1449
1754
  function isHtmlElement(el) {
1450
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
1755
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
1756
+ }
1757
+ function warnContextualRefusal(label2, name, refusal) {
1758
+ devWarnLazy(() => {
1759
+ const reasons = [
1760
+ "",
1761
+ 'the element would become a <meta http-equiv="refresh"> directive the shared refresh policy forbids (a destination outside the URL allowlist, or a directive too malformed to read unambiguously)',
1762
+ "a <meta> whose http-equiv or content is reactive may never carry a refresh directive: a browser schedules the navigation as soon as the directive is valid, and nothing can withdraw it when the state changes back",
1763
+ "a runtime value may not choose the program a <script> runs; name the script in static template source, or load it through Head({ script }) as an explicit trust decision",
1764
+ "an SVG animation may not target a URL, event-handler or nested-document attribute; its to / values would be written into that attribute without passing the URL policy",
1765
+ 'a runtime-chosen href may not become an applied <link rel="stylesheet">: a well-formed URL is not a trusted stylesheet. Name the stylesheet in static template source, or load a runtime-chosen one through Head({ link }) as an explicit trust decision'
1766
+ ];
1767
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
1768
+ });
1451
1769
  }
1452
1770
  function setProp(el, key, val) {
1453
1771
  el[key] = val;
1454
1772
  }
1455
1773
  function namespaceFor(el, name) {
1456
- if (el.namespaceURI !== SVG_NS) return null;
1774
+ if (el.namespaceURI !== SVG_NS2) return null;
1457
1775
  const lower = name.toLowerCase();
1458
1776
  if (lower.startsWith("xlink:")) return XLINK_NS;
1459
1777
  if (lower.startsWith("xml:")) return XML_NS;
1460
1778
  return null;
1461
1779
  }
1462
1780
  function setSafeAttribute(el, name, value, options = {}) {
1781
+ const written = commitSafeAttribute(el, name, value, options);
1782
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
1783
+ return written;
1784
+ }
1785
+ function commitSafeAttribute(el, name, value, options) {
1463
1786
  const ns = namespaceFor(el, name);
1464
1787
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
1465
1788
  if (isEventHandlerAttr(name)) {
@@ -1472,6 +1795,14 @@ function setSafeAttribute(el, name, value, options = {}) {
1472
1795
  else el.removeAttribute(name);
1473
1796
  return false;
1474
1797
  }
1798
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
1799
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
1800
+ if (refusal !== ContextualRefusal.None) {
1801
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
1802
+ if (ns) el.removeAttributeNS(ns, localName);
1803
+ else el.removeAttribute(name);
1804
+ return false;
1805
+ }
1475
1806
  if (value == null) {
1476
1807
  if (ns) el.removeAttributeNS(ns, localName);
1477
1808
  else el.removeAttribute(name);
@@ -1515,7 +1846,12 @@ function setSafeAttribute(el, name, value, options = {}) {
1515
1846
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
1516
1847
  return true;
1517
1848
  }
1518
- const safe = sanitizeAttributeString(name, str, { element: el });
1849
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
1850
+ if (safe === null) {
1851
+ if (ns) el.removeAttributeNS(ns, localName);
1852
+ else el.removeAttribute(name);
1853
+ return true;
1854
+ }
1519
1855
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
1520
1856
  if (current === safe) return true;
1521
1857
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -2019,7 +2355,7 @@ function forEachSubscriber(signal2, visit) {
2019
2355
  }
2020
2356
 
2021
2357
  // src/reactivity/track.ts
2022
- var _runtimeVersion = true ? "4.6.0" : "dev";
2358
+ var _runtimeVersion = true ? "4.8.0" : "dev";
2023
2359
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
2024
2360
  function resolveReactiveApi() {
2025
2361
  const g2 = globalThis;
@@ -2090,7 +2426,7 @@ function bindAttribute(el, attr, getter) {
2090
2426
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
2091
2427
  return;
2092
2428
  }
2093
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
2429
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
2094
2430
  }
2095
2431
  return reactiveBinding2(commit, el);
2096
2432
  }
@@ -2270,13 +2606,10 @@ function bindChildNode(placeholder, getter) {
2270
2606
  }
2271
2607
 
2272
2608
  // src/core/rendering/tagFactory.ts
2609
+ init_elementPolicy();
2273
2610
  init_sanitize();
2274
2611
  init_dispose();
2275
- var SVG_NS2 = "http://www.w3.org/2000/svg";
2276
- var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
2277
- function isBlockedTag(tag) {
2278
- return BLOCKED_TAGS.has(tag.toLowerCase());
2279
- }
2612
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
2280
2613
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
2281
2614
  "config",
2282
2615
  "location",
@@ -2303,8 +2636,35 @@ function toKebab(prop) {
2303
2636
  }
2304
2637
  function applyStyle(el, style2) {
2305
2638
  if (typeof style2 === "function") {
2639
+ const getter = style2;
2640
+ let written = [];
2641
+ let wroteString = false;
2306
2642
  const teardown = reactiveBinding2(() => {
2307
- el.setAttribute("style", sanitizeStyleAttribute(String(style2()), { element: el }));
2643
+ const value = getter();
2644
+ if (value === null || typeof value !== "object") {
2645
+ written = [];
2646
+ wroteString = true;
2647
+ el.setAttribute("style", sanitizeStyleAttribute(String(value), { element: el }));
2648
+ return;
2649
+ }
2650
+ const decl = el.style;
2651
+ if (wroteString) {
2652
+ el.removeAttribute("style");
2653
+ wroteString = false;
2654
+ }
2655
+ const next = [];
2656
+ for (const prop in value) {
2657
+ if (!Object.hasOwn(value, prop)) continue;
2658
+ const val = value[prop];
2659
+ if (val == null) continue;
2660
+ const name = toKebab(prop);
2661
+ next.push(name);
2662
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2663
+ }
2664
+ for (let i2 = 0; i2 < written.length; i2++) {
2665
+ if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
2666
+ }
2667
+ written = next;
2308
2668
  }, el);
2309
2669
  registerDisposer(el, teardown);
2310
2670
  return;
@@ -2315,25 +2675,27 @@ function applyStyle(el, style2) {
2315
2675
  }
2316
2676
  const htmlEl = el;
2317
2677
  for (const prop in style2) {
2678
+ if (!Object.hasOwn(style2, prop)) continue;
2318
2679
  const val = style2[prop];
2319
2680
  const name = toKebab(prop);
2320
2681
  if (typeof val === "function") {
2321
2682
  const getter = val;
2322
2683
  const teardown = reactiveBinding2(() => {
2323
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
2684
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
2324
2685
  }, el);
2325
2686
  registerDisposer(el, teardown);
2326
2687
  } else {
2327
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
2688
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2328
2689
  }
2329
2690
  }
2330
2691
  }
2331
2692
  function resolveClassValue(cls) {
2332
2693
  if (typeof cls === "string") return cls;
2333
- if (typeof cls === "function") return cls();
2694
+ if (typeof cls === "function") return cls() || "";
2334
2695
  if (!cls) return "";
2335
2696
  let out = "";
2336
2697
  for (const name in cls) {
2698
+ if (!Object.hasOwn(cls, name)) continue;
2337
2699
  const val = cls[name];
2338
2700
  const active = typeof val === "function" ? val() : val;
2339
2701
  if (active) out = out ? `${out} ${name}` : name;
@@ -2347,7 +2709,9 @@ function applyClass(el, cls) {
2347
2709
  }
2348
2710
  if (typeof cls === "function") {
2349
2711
  const teardown = reactiveBinding2(() => {
2350
- el.setAttribute("class", cls());
2712
+ const value = cls();
2713
+ if (value == null || value === false) el.removeAttribute("class");
2714
+ else el.setAttribute("class", value);
2351
2715
  }, el);
2352
2716
  registerDisposer(el, teardown);
2353
2717
  return;
@@ -2356,6 +2720,7 @@ function applyClass(el, cls) {
2356
2720
  let hasReactive = false;
2357
2721
  let result = "";
2358
2722
  for (const name in obj) {
2723
+ if (!Object.hasOwn(obj, name)) continue;
2359
2724
  const val = obj[name];
2360
2725
  if (typeof val === "function") {
2361
2726
  hasReactive = true;
@@ -2367,6 +2732,7 @@ function applyClass(el, cls) {
2367
2732
  const update = () => {
2368
2733
  let r = "";
2369
2734
  for (const name in obj) {
2735
+ if (!Object.hasOwn(obj, name)) continue;
2370
2736
  const val = obj[name];
2371
2737
  const active = typeof val === "function" ? val() : val;
2372
2738
  if (active) r = r ? `${r} ${name}` : name;
@@ -2429,8 +2795,8 @@ function appendChildren(el, nodes) {
2429
2795
  }
2430
2796
  }
2431
2797
  }
2432
- var tagFactory = (tag, ns) => {
2433
- const blocked = isBlockedTag(tag);
2798
+ function tagFactory(tag, ns) {
2799
+ const blocked = isBlockedElement(tag);
2434
2800
  return (first, second) => {
2435
2801
  if (blocked) {
2436
2802
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -2478,7 +2844,9 @@ var tagFactory = (tag, ns) => {
2478
2844
  const pClass = props.class;
2479
2845
  if (pClass != null) applyClass(el, pClass);
2480
2846
  const pId = props.id;
2481
- if (pId != null) {
2847
+ if (typeof pId === "function") {
2848
+ registerDisposer(el, bindAttribute(el, "id", pId));
2849
+ } else if (pId != null) {
2482
2850
  if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
2483
2851
  devWarn(
2484
2852
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
@@ -2491,6 +2859,7 @@ var tagFactory = (tag, ns) => {
2491
2859
  const pOn = props.on;
2492
2860
  if (pOn) {
2493
2861
  for (const ev in pOn) {
2862
+ if (!Object.hasOwn(pOn, ev)) continue;
2494
2863
  const handler = pOn[ev];
2495
2864
  if (typeof handler === "function") {
2496
2865
  el.addEventListener(ev, handler);
@@ -2517,6 +2886,7 @@ var tagFactory = (tag, ns) => {
2517
2886
  continue;
2518
2887
  // already handled above / below
2519
2888
  default: {
2889
+ if (!Object.hasOwn(props, key)) continue;
2520
2890
  const value = props[key];
2521
2891
  if (value == null) continue;
2522
2892
  if (isEventHandlerAttr(key)) continue;
@@ -2528,12 +2898,10 @@ var tagFactory = (tag, ns) => {
2528
2898
  }
2529
2899
  }
2530
2900
  }
2531
- if (props.onElement && typeof props.onElement === "function") {
2532
- props.onElement(el);
2533
- }
2901
+ if (typeof props.onElement === "function") props.onElement(el);
2534
2902
  return el;
2535
2903
  };
2536
- };
2904
+ }
2537
2905
 
2538
2906
  // src/core/rendering/html.ts
2539
2907
  var html = tagFactory("html");
@@ -2608,7 +2976,7 @@ var param = tagFactory("param");
2608
2976
  var picture = tagFactory("picture");
2609
2977
  var portal = tagFactory("portal");
2610
2978
  var source = tagFactory("source");
2611
- var svg = tagFactory("svg", SVG_NS2);
2979
+ var svg = tagFactory("svg", SVG_NS3);
2612
2980
  var math = tagFactory("math");
2613
2981
  var canvas = tagFactory("canvas");
2614
2982
  var noscript = tagFactory("noscript");
@@ -2649,26 +3017,26 @@ var base = tagFactory("base");
2649
3017
  var link = tagFactory("link");
2650
3018
  var meta = tagFactory("meta");
2651
3019
  var style = tagFactory("style");
2652
- var circle = tagFactory("circle", SVG_NS2);
2653
- var ellipse = tagFactory("ellipse", SVG_NS2);
2654
- var g = tagFactory("g", SVG_NS2);
2655
- var line = tagFactory("line", SVG_NS2);
2656
- var path = tagFactory("path", SVG_NS2);
2657
- var polygon = tagFactory("polygon", SVG_NS2);
2658
- var polyline = tagFactory("polyline", SVG_NS2);
2659
- var rect = tagFactory("rect", SVG_NS2);
2660
- var text = tagFactory("text", SVG_NS2);
2661
- var tspan = tagFactory("tspan", SVG_NS2);
2662
- var defs = tagFactory("defs", SVG_NS2);
2663
- var clipPath = tagFactory("clipPath", SVG_NS2);
2664
- var mask = tagFactory("mask", SVG_NS2);
2665
- var pattern = tagFactory("pattern", SVG_NS2);
2666
- var linearGradient = tagFactory("linearGradient", SVG_NS2);
2667
- var radialGradient = tagFactory("radialGradient", SVG_NS2);
2668
- var stop = tagFactory("stop", SVG_NS2);
2669
- var use = tagFactory("use", SVG_NS2);
2670
- var symbol = tagFactory("symbol", SVG_NS2);
2671
- var marker = tagFactory("marker", SVG_NS2);
3020
+ var circle = tagFactory("circle", SVG_NS3);
3021
+ var ellipse = tagFactory("ellipse", SVG_NS3);
3022
+ var g = tagFactory("g", SVG_NS3);
3023
+ var line = tagFactory("line", SVG_NS3);
3024
+ var path = tagFactory("path", SVG_NS3);
3025
+ var polygon = tagFactory("polygon", SVG_NS3);
3026
+ var polyline = tagFactory("polyline", SVG_NS3);
3027
+ var rect = tagFactory("rect", SVG_NS3);
3028
+ var text = tagFactory("text", SVG_NS3);
3029
+ var tspan = tagFactory("tspan", SVG_NS3);
3030
+ var defs = tagFactory("defs", SVG_NS3);
3031
+ var clipPath = tagFactory("clipPath", SVG_NS3);
3032
+ var mask = tagFactory("mask", SVG_NS3);
3033
+ var pattern = tagFactory("pattern", SVG_NS3);
3034
+ var linearGradient = tagFactory("linearGradient", SVG_NS3);
3035
+ var radialGradient = tagFactory("radialGradient", SVG_NS3);
3036
+ var stop = tagFactory("stop", SVG_NS3);
3037
+ var use = tagFactory("use", SVG_NS3);
3038
+ var symbol = tagFactory("symbol", SVG_NS3);
3039
+ var marker = tagFactory("marker", SVG_NS3);
2672
3040
  var center = tagFactory("center");
2673
3041
  var font = tagFactory("font");
2674
3042
  var marquee = tagFactory("marquee");
@@ -2725,6 +3093,16 @@ var isBatching = API2.isBatching;
2725
3093
 
2726
3094
  // src/core/signals/signal.ts
2727
3095
  init_dev();
3096
+
3097
+ // src/core/devtoolsHook.ts
3098
+ function emitDevtools(hook, event, payload) {
3099
+ try {
3100
+ hook.emit(event, payload);
3101
+ } catch {
3102
+ }
3103
+ }
3104
+
3105
+ // src/core/signals/signal.ts
2728
3106
  var _g = globalThis;
2729
3107
  function signal(initial, options) {
2730
3108
  const state2 = {
@@ -2757,7 +3135,7 @@ function signal(initial, options) {
2757
3135
  state2.__v++;
2758
3136
  if (DEV) {
2759
3137
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2760
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
3138
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
2761
3139
  }
2762
3140
  if (!enqueueBatchedSignal(state2)) {
2763
3141
  notifySubscribers2(state2);
@@ -2771,7 +3149,7 @@ function signal(initial, options) {
2771
3149
  state2.value = newValue;
2772
3150
  state2.__v++;
2773
3151
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2774
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
3152
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
2775
3153
  if (!enqueueBatchedSignal(state2)) {
2776
3154
  notifySubscribers2(state2);
2777
3155
  }
@@ -2790,7 +3168,7 @@ function signal(initial, options) {
2790
3168
  }
2791
3169
  if (DEV) {
2792
3170
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2793
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
3171
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
2794
3172
  }
2795
3173
  return [get, set];
2796
3174
  }
@@ -2807,14 +3185,20 @@ function globalSingleton(key, create) {
2807
3185
  // src/plugins/i18n.ts
2808
3186
  var _i18n = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.i18n.v1"), () => ({
2809
3187
  locale: signal("en"),
2810
- locales: /* @__PURE__ */ Object.create(null)
3188
+ locales: /* @__PURE__ */ Object.create(null),
3189
+ revision: signal(0)
2811
3190
  }));
2812
3191
  var [clientLocale, setClientLocale] = _i18n.locale;
2813
3192
  var locales = _i18n.locales;
3193
+ var [revision, setRevision] = _i18n.revision ?? (_i18n.revision = signal(0));
2814
3194
  function dictionaryFor(locale) {
2815
3195
  return Object.hasOwn(locales, locale) ? locales[locale] : void 0;
2816
3196
  }
3197
+ function trackDictionaries() {
3198
+ if (!getRequestStore()) revision();
3199
+ }
2817
3200
  function lookup(key) {
3201
+ trackDictionaries();
2818
3202
  const dictionary = dictionaryFor(getLocale());
2819
3203
  if (dictionary === void 0 || !Object.hasOwn(dictionary, key)) return void 0;
2820
3204
  const message = dictionary[key];
@@ -2835,6 +3219,7 @@ function getLocale() {
2835
3219
  }
2836
3220
  return clientLocale();
2837
3221
  }
3222
+ _i18n.getLocale ?? (_i18n.getLocale = getLocale);
2838
3223
  function registerTranslations(locale, messages) {
2839
3224
  const prepared = { ...messages };
2840
3225
  const merged = Object.assign(/* @__PURE__ */ Object.create(null), dictionaryFor(locale), prepared);
@@ -2844,18 +3229,29 @@ function registerTranslations(locale, messages) {
2844
3229
  enumerable: true,
2845
3230
  configurable: true
2846
3231
  });
3232
+ setRevision((n) => n + 1);
3233
+ }
3234
+ function resolveParam(params, name) {
3235
+ if (!Object.hasOwn(params, name)) return "";
3236
+ const raw = params[name];
3237
+ const value = typeof raw === "function" ? raw() : raw;
3238
+ return value == null ? "" : String(value);
2847
3239
  }
2848
3240
  function t(key, params) {
2849
3241
  const message = lookup(key) ?? key;
2850
- return params ? message.replace(/\{(\w+)\}/g, (_, p2) => String(params[p2] ?? "")) : message;
3242
+ return params ? message.replace(/\{(\w+)\}/g, (_, p2) => resolveParam(params, p2)) : message;
3243
+ }
3244
+ function translated(key, params) {
3245
+ return () => t(key, params);
2851
3246
  }
2852
3247
  function Trans(key, params) {
2853
- return span(() => t(key, params));
3248
+ return span(translated(key, params));
2854
3249
  }
2855
3250
  function hasTranslation(key) {
2856
3251
  return lookup(key) !== void 0;
2857
3252
  }
2858
3253
  function getAvailableLocales() {
3254
+ trackDictionaries();
2859
3255
  return Object.keys(locales);
2860
3256
  }
2861
3257
 
@@ -2981,12 +3377,17 @@ function effect(effectFn, options) {
2981
3377
  try {
2982
3378
  retrack2(ctx.bodyFn, ctx.subscriber);
2983
3379
  if (ctx.rerunPending) drainReruns(ctx);
3380
+ } catch (err) {
3381
+ ctx.disposed = true;
3382
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
3383
+ cleanup2(ctx.subscriber);
3384
+ throw err;
2984
3385
  } finally {
2985
3386
  ctx.running = false;
2986
3387
  ctx.rerunPending = false;
2987
3388
  }
2988
3389
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2989
- if (hook) hook.emit("effect:create", { effectFn });
3390
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
2990
3391
  return () => disposeEffect(ctx);
2991
3392
  }
2992
3393
 
@@ -4161,6 +4562,30 @@ function afterEach(hook) {
4161
4562
  return _routerRef.current.afterEach(hook);
4162
4563
  }
4163
4564
  var routeCleanups = [];
4565
+ var ownParamNamesCache = /* @__PURE__ */ new WeakMap();
4566
+ function ownParamNames(record) {
4567
+ const cached = ownParamNamesCache.get(record);
4568
+ if (cached) return cached;
4569
+ const aliases = record.alias == null ? [] : Array.isArray(record.alias) ? record.alias : [record.alias];
4570
+ const names = /* @__PURE__ */ new Set();
4571
+ for (const pattern2 of [record.path, ...aliases]) {
4572
+ for (const m of pattern2.matchAll(/\/:([^/]+)/g)) {
4573
+ names.add(m[1].endsWith("?") ? m[1].slice(0, -1) : m[1]);
4574
+ }
4575
+ }
4576
+ const result = [...names];
4577
+ ownParamNamesCache.set(record, result);
4578
+ return result;
4579
+ }
4580
+ function instanceKey(owner, records, route2) {
4581
+ const custom = owner.key;
4582
+ if (custom) return untracked2(() => String(custom(route2)));
4583
+ const parts = [];
4584
+ for (const record of records) {
4585
+ for (const name of ownParamNames(record)) parts.push(name, route2.params[name] ?? null);
4586
+ }
4587
+ return JSON.stringify(parts);
4588
+ }
4164
4589
  function Route() {
4165
4590
  const anchor = document.createComment("route-outlet");
4166
4591
  let currentNode = null;
@@ -4168,6 +4593,7 @@ function Route() {
4168
4593
  let errorNode = null;
4169
4594
  let navSeq = 0;
4170
4595
  let currentTopRoute = null;
4596
+ let currentKey = null;
4171
4597
  let routeTorn = false;
4172
4598
  const commitTarget = (seq) => routeTorn || seq !== navSeq ? null : anchor.parentNode;
4173
4599
  const release = (node) => {
@@ -4259,11 +4685,13 @@ function Route() {
4259
4685
  const match = _routerRef.current["matcher"].match(route2.path);
4260
4686
  if (!match) {
4261
4687
  currentTopRoute = null;
4688
+ currentKey = null;
4262
4689
  cleanupNodes();
4263
4690
  return;
4264
4691
  }
4265
4692
  const routeDef = match.matched[0] || match.route;
4266
- if (routeDef === currentTopRoute && currentNode) {
4693
+ const key = instanceKey(routeDef, [routeDef], route2);
4694
+ if (routeDef === currentTopRoute && key === currentKey && currentNode) {
4267
4695
  return;
4268
4696
  }
4269
4697
  if ("redirect" in routeDef) {
@@ -4291,6 +4719,7 @@ function Route() {
4291
4719
  }
4292
4720
  if (node) {
4293
4721
  currentTopRoute = routeDef;
4722
+ currentKey = key;
4294
4723
  cleanupNodes();
4295
4724
  parent.insertBefore(node, anchor.nextSibling);
4296
4725
  currentNode = node;
@@ -4328,6 +4757,7 @@ function Route() {
4328
4757
  routeTeardown();
4329
4758
  cleanupNodes();
4330
4759
  currentTopRoute = null;
4760
+ currentKey = null;
4331
4761
  };
4332
4762
  registerDisposer(anchor, routeCleanup);
4333
4763
  routeCleanups.push(routeCleanup);
@@ -4503,30 +4933,10 @@ function RouterLink(props, children) {
4503
4933
  const lkey = key.toLowerCase();
4504
4934
  if (lkey === "href" || lkey[0] === "o" && lkey[1] === "n") return;
4505
4935
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4506
- const str = String(value);
4507
- if (isUrlAttribute(lkey)) {
4508
- const safe = sanitizeUrl(str);
4509
- if (safe) link2.setAttribute(key, safe);
4510
- } else if (lkey === "style") {
4511
- link2.setAttribute(key, sanitizeStyleAttribute(str, { element: link2 }));
4512
- } else {
4513
- link2.setAttribute(key, str);
4514
- }
4936
+ setSafeAttribute(link2, key, String(value), { syncValueProperty: false, label: "RouterLink" });
4515
4937
  }
4516
4938
  });
4517
- if (typeof content === "string") {
4518
- link2.textContent = content;
4519
- } else if (content instanceof Node) {
4520
- link2.appendChild(content);
4521
- } else if (Array.isArray(content)) {
4522
- content.forEach((child) => {
4523
- if (typeof child === "string") {
4524
- link2.appendChild(document.createTextNode(child));
4525
- } else if (child instanceof Node) {
4526
- link2.appendChild(child);
4527
- }
4528
- });
4529
- }
4939
+ if (content !== void 0) appendChildren(link2, content);
4530
4940
  const onLinkClick = (e) => {
4531
4941
  if (e.defaultPrevented || target || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) {
4532
4942
  return;
@@ -4688,6 +5098,7 @@ function Outlet() {
4688
5098
  const anchor = document.createComment("route-outlet-nested");
4689
5099
  let currentNode = null;
4690
5100
  let currentChild = null;
5101
+ let currentKey = null;
4691
5102
  let navSeq = 0;
4692
5103
  let outletTorn = false;
4693
5104
  const clearCurrent = () => {
@@ -4697,6 +5108,7 @@ function Outlet() {
4697
5108
  currentNode = null;
4698
5109
  }
4699
5110
  currentChild = null;
5111
+ currentKey = null;
4700
5112
  };
4701
5113
  const commitTarget = (seq) => outletTorn || seq !== navSeq ? null : anchor.parentNode;
4702
5114
  const release = (node) => {
@@ -4717,7 +5129,8 @@ function Outlet() {
4717
5129
  clearCurrent();
4718
5130
  return;
4719
5131
  }
4720
- if (childRoute === currentChild && currentNode) return;
5132
+ const key = instanceKey(childRoute, route2.matched.slice(1), route2);
5133
+ if (childRoute === currentChild && key === currentKey && currentNode) return;
4721
5134
  try {
4722
5135
  const cacheKey = `${route2.path}\0${childRoute.path}`;
4723
5136
  const plan = await _routerRef.current.loadPlan(childRoute, cacheKey);
@@ -4733,6 +5146,7 @@ function Outlet() {
4733
5146
  parent.insertBefore(node, anchor.nextSibling);
4734
5147
  currentNode = node;
4735
5148
  currentChild = childRoute;
5149
+ currentKey = key;
4736
5150
  } catch (error) {
4737
5151
  if (outletTorn || seq !== navSeq) return;
4738
5152
  console.error("[Outlet] Failed to render child route:", error);
@@ -4755,6 +5169,7 @@ function Outlet() {
4755
5169
  currentNode = null;
4756
5170
  }
4757
5171
  currentChild = null;
5172
+ currentKey = null;
4758
5173
  };
4759
5174
  registerDisposer(anchor, outletCleanup);
4760
5175
  routeCleanups.push(outletCleanup);
@@ -4817,14 +5232,7 @@ function createMemoryRouter(routes, _initialPath = "/") {
4817
5232
 
4818
5233
  // src/plugins/routerSSR.ts
4819
5234
  init_ssr();
4820
-
4821
- // src/utils/guards.ts
4822
- var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
4823
- function isUnsafeKey(key) {
4824
- return UNSAFE_KEYS.has(key);
4825
- }
4826
-
4827
- // src/plugins/routerSSR.ts
5235
+ init_guards();
4828
5236
  init_headEntry();
4829
5237
  init_sanitize();
4830
5238
  function safeDecode(raw) {
@@ -5057,8 +5465,7 @@ function serializeRouteState(state2, nonce) {
5057
5465
  return `<script${nonceAttr}>window.${SSR_ROUTE_STATE_KEY}=${json}</script>`;
5058
5466
  }
5059
5467
  function deserializeRouteState() {
5060
- if (typeof window === "undefined") return void 0;
5061
- return window[SSR_ROUTE_STATE_KEY];
5468
+ return readOwnGlobal(SSR_ROUTE_STATE_KEY);
5062
5469
  }
5063
5470
  function hydrateRouter(routes, options) {
5064
5471
  const serverState = deserializeRouteState();
@@ -5498,7 +5905,7 @@ var CDN_ENTRY_POINTS = {
5498
5905
  };
5499
5906
  var packageInfo = {
5500
5907
  name: "sibujs",
5501
- version: true ? "4.6.0" : "dev",
5908
+ version: true ? "4.8.0" : "dev",
5502
5909
  entryPoints: MODULE_ENTRY_POINTS,
5503
5910
  /**
5504
5911
  * Generate the package.json `exports` map. Module entries resolve to the
@@ -5643,7 +6050,7 @@ var env = {
5643
6050
  };
5644
6051
 
5645
6052
  // src/plugins/versioning.ts
5646
- var VERSION = true ? "4.6.0" : "dev";
6053
+ var VERSION = true ? "4.8.0" : "dev";
5647
6054
  var NUM = "(?:0|[1-9]\\d*)";
5648
6055
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
5649
6056
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -5925,15 +6332,18 @@ function extractRangeTarget(range) {
5925
6332
  // src/plugins/startup.ts
5926
6333
  init_errors();
5927
6334
  init_ssr();
6335
+ init_sanitize();
5928
6336
  function preloadCritical(resources) {
5929
6337
  if (typeof document === "undefined") return;
5930
6338
  for (const resource of resources) {
5931
- const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(resource.href) : resource.href.replace(/["\\]/g, "\\$&");
6339
+ const href = sanitizeUrl(resource.href);
6340
+ if (!href) continue;
6341
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(href) : href.replace(/["\\]/g, "\\$&");
5932
6342
  const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
5933
6343
  if (existing) continue;
5934
6344
  const link2 = document.createElement("link");
5935
6345
  link2.rel = "preload";
5936
- link2.href = resource.href;
6346
+ link2.href = href;
5937
6347
  link2.setAttribute("as", resource.as);
5938
6348
  if (resource.type) {
5939
6349
  link2.type = resource.type;
@@ -6241,6 +6651,7 @@ function createBootSequence() {
6241
6651
  setRouteTransition,
6242
6652
  setRoutes,
6243
6653
  t,
6654
+ translated,
6244
6655
  triggerPluginError,
6245
6656
  triggerPluginMount,
6246
6657
  triggerPluginUnmount