sibujs 4.7.0 → 4.9.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 (83) hide show
  1. package/dist/browser.cjs +48 -6
  2. package/dist/browser.d.cts +26 -2
  3. package/dist/browser.d.ts +26 -2
  4. package/dist/browser.js +6 -5
  5. package/dist/build.cjs +539 -96
  6. package/dist/build.d.cts +22 -8
  7. package/dist/build.d.ts +22 -8
  8. package/dist/build.js +68 -21
  9. package/dist/cdn.dev.global.js +15 -13
  10. package/dist/cdn.full.dev.global.js +15 -13
  11. package/dist/cdn.full.global.js +14 -12
  12. package/dist/cdn.global.js +14 -12
  13. package/dist/{chunk-Z3OHK6QT.js → chunk-2K3Z3XO4.js} +5 -5
  14. package/dist/chunk-3ACAAQIN.js +224 -0
  15. package/dist/{chunk-WOLJZUFQ.js → chunk-562UVHMM.js} +32 -15
  16. package/dist/{chunk-UOL2ECCS.js → chunk-5MLWTIQE.js} +18 -12
  17. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  18. package/dist/{chunk-3QWBSL5R.js → chunk-7OV5Q2UU.js} +8 -5
  19. package/dist/{chunk-SLM3IA34.js → chunk-CDBKFGXN.js} +1 -1
  20. package/dist/{chunk-4AWA2PVD.js → chunk-CF2KCWRE.js} +10 -5
  21. package/dist/{chunk-BSY63EM6.js → chunk-DRMLIF6M.js} +20 -5
  22. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  23. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  24. package/dist/{chunk-BC2SECJD.js → chunk-FHCZMHHE.js} +1 -1
  25. package/dist/{chunk-XYV3EDB7.js → chunk-FNOK662L.js} +63 -38
  26. package/dist/{chunk-JWKYU5GV.js → chunk-G2WHYEL7.js} +1 -1
  27. package/dist/{chunk-DKXRACVN.js → chunk-K6K5ACO6.js} +6 -6
  28. package/dist/{chunk-VZSG24LS.js → chunk-KG25XMGP.js} +45 -17
  29. package/dist/{chunk-FNJXNGYZ.js → chunk-MPWDSTYK.js} +58 -6
  30. package/dist/{chunk-UNWRJRKC.js → chunk-NQMKWBKU.js} +1 -1
  31. package/dist/{chunk-7LUJQAOJ.js → chunk-QBKCK6MJ.js} +1 -1
  32. package/dist/{chunk-WEQ3DMVL.js → chunk-SBTZEYNI.js} +1 -1
  33. package/dist/{chunk-FQRUXCEE.js → chunk-SYHGGDNW.js} +1 -1
  34. package/dist/{chunk-XZR4PXRE.js → chunk-TV464N2M.js} +13 -10
  35. package/dist/{chunk-RUSSKG6G.js → chunk-X7NRHFZR.js} +1 -1
  36. package/dist/{chunk-2BPG2XDA.js → chunk-XJVTQUIV.js} +18 -36
  37. package/dist/{chunk-ZVL7TY4K.js → chunk-YW6HQKGO.js} +25 -10
  38. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  39. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  40. package/dist/data.cjs +29 -9
  41. package/dist/data.d.cts +23 -4
  42. package/dist/data.d.ts +23 -4
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +1 -1
  45. package/dist/devtools.js +5 -5
  46. package/dist/ecosystem.cjs +400 -37
  47. package/dist/ecosystem.js +9 -8
  48. package/dist/extras.cjs +672 -269
  49. package/dist/extras.d.cts +2 -2
  50. package/dist/extras.d.ts +2 -2
  51. package/dist/extras.js +24 -23
  52. package/dist/index.cjs +490 -88
  53. package/dist/index.d.cts +34 -2
  54. package/dist/index.d.ts +34 -2
  55. package/dist/index.js +16 -12
  56. package/dist/motion.cjs +1 -1
  57. package/dist/motion.js +3 -3
  58. package/dist/patterns.cjs +10 -7
  59. package/dist/patterns.js +6 -6
  60. package/dist/performance.cjs +31 -2
  61. package/dist/performance.d.cts +12 -2
  62. package/dist/performance.d.ts +12 -2
  63. package/dist/performance.js +8 -6
  64. package/dist/plugins.cjs +571 -240
  65. package/dist/plugins.d.cts +14 -3
  66. package/dist/plugins.d.ts +14 -3
  67. package/dist/plugins.js +35 -37
  68. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  69. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.cts} +6 -0
  70. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.ts} +6 -0
  71. package/dist/ssr.cjs +484 -138
  72. package/dist/ssr.d.cts +59 -5
  73. package/dist/ssr.d.ts +59 -5
  74. package/dist/ssr.js +11 -9
  75. package/dist/testing.cjs +3 -3
  76. package/dist/testing.js +4 -4
  77. package/dist/ui.cjs +609 -241
  78. package/dist/ui.d.cts +1 -1
  79. package/dist/ui.d.ts +1 -1
  80. package/dist/ui.js +9 -8
  81. package/dist/widgets.cjs +1 -1
  82. package/dist/widgets.js +7 -7
  83. package/package.json +5 -4
package/dist/ssr.cjs CHANGED
@@ -313,8 +313,10 @@ function isSSR() {
313
313
  function stripControlChars(value) {
314
314
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
315
315
  }
316
+ var ASCII_UPPER = /[A-Z]/;
317
+ var ASCII_UPPER_G = /[A-Z]/g;
316
318
  function canonicalAttrName(name) {
317
- return name.replace(/[A-Z]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32));
319
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
318
320
  }
319
321
  function isEventHandlerAttr(name) {
320
322
  if (name.length < 3) return false;
@@ -327,6 +329,9 @@ function sanitizeUrl(url) {
327
329
  const probe = stripControlChars(url).trim();
328
330
  if (!probe) return "";
329
331
  const output2 = url.replace(OUTER_TRIM, "");
332
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
333
+ return "";
334
+ }
330
335
  const lower = probe.toLowerCase();
331
336
  let schemeEnd = -1;
332
337
  for (let i2 = 0; i2 < lower.length; i2++) {
@@ -343,19 +348,58 @@ function sanitizeUrl(url) {
343
348
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
344
349
  return output2;
345
350
  }
346
- var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
351
+ var SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
352
+ function isSrcsetWhitespace(ch) {
353
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
354
+ }
347
355
  function sanitizeSrcset(value) {
348
- const parts = value.split(",");
356
+ const input2 = String(value);
349
357
  const out = [];
350
- for (let i2 = 0; i2 < parts.length; i2++) {
351
- const part = parts[i2].trim();
352
- if (!part) continue;
353
- const m = part.match(/^(\S+)(\s+.+)?$/);
354
- if (!m) continue;
355
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
356
- const safe = sanitizeUrl(m[1]);
358
+ let i2 = 0;
359
+ const n = input2.length;
360
+ while (i2 < n) {
361
+ while (i2 < n && (isSrcsetWhitespace(input2[i2]) || input2[i2] === ",")) i2++;
362
+ if (i2 >= n) break;
363
+ const urlStart = i2;
364
+ while (i2 < n && !isSrcsetWhitespace(input2[i2])) i2++;
365
+ let url = input2.slice(urlStart, i2);
366
+ const descriptors = [];
367
+ if (url.endsWith(",")) {
368
+ url = url.replace(/,+$/, "");
369
+ } else {
370
+ let current = "";
371
+ let inParens = false;
372
+ while (i2 < n) {
373
+ const ch = input2[i2];
374
+ if (inParens) {
375
+ current += ch;
376
+ if (ch === ")") inParens = false;
377
+ i2++;
378
+ continue;
379
+ }
380
+ if (isSrcsetWhitespace(ch)) {
381
+ if (current) descriptors.push(current);
382
+ current = "";
383
+ i2++;
384
+ continue;
385
+ }
386
+ if (ch === ",") {
387
+ i2++;
388
+ break;
389
+ }
390
+ if (ch === "(") inParens = true;
391
+ current += ch;
392
+ i2++;
393
+ }
394
+ if (current) descriptors.push(current);
395
+ }
396
+ if (url === "") continue;
397
+ if (descriptors.length > 1) continue;
398
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
399
+ const safe = sanitizeUrl(url);
357
400
  if (!safe) continue;
358
- out.push(m[2] ? `${safe}${m[2]}` : safe);
401
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
402
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
359
403
  }
360
404
  return out.join(", ");
361
405
  }
@@ -414,8 +458,16 @@ var BLOCKED_CSS_NEEDLES = [
414
458
  "behavior:",
415
459
  "@import",
416
460
  "image-set(",
417
- "filter:progid"
461
+ "filter:progid",
462
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
463
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
464
+ // today, which is exactly when a blocklist should get ahead of it.
465
+ "image(",
466
+ "src("
418
467
  ];
468
+ var CSS_REASON_MOZ_BINDING = 4;
469
+ var CSS_REASON_BEHAVIOR = 5;
470
+ var CSS_REASON_PROGID = 8;
419
471
  function describeElement(el) {
420
472
  if (!el) return "an element";
421
473
  let out = el.tagName.toLowerCase();
@@ -439,7 +491,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
439
491
  "behavior: loads and runs a remote HTC script",
440
492
  "@import pulls in a remote stylesheet",
441
493
  "image-set() fetches remote images, the same exfiltration channel as url()",
442
- "filter:progid activates legacy scriptable filters"
494
+ "filter:progid activates legacy scriptable filters",
495
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
496
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
443
497
  ];
444
498
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
445
499
  const where = describeElement(ctx?.element);
@@ -454,27 +508,110 @@ function warnDroppedDeclaration(value, reason, ctx) {
454
508
  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.`;
455
509
  });
456
510
  }
511
+ function normalizeCssForScan(value) {
512
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
513
+ return normalized.toLowerCase().replace(/\s+/g, "");
514
+ }
457
515
  function blockedCssReason(value) {
458
516
  if (!CSS_DANGER_GATE.test(value)) return -1;
459
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
460
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
517
+ const lower = normalizeCssForScan(value);
461
518
  for (let i2 = 0; i2 < BLOCKED_CSS_NEEDLES.length; i2++) {
462
519
  if (lower.includes(BLOCKED_CSS_NEEDLES[i2])) return i2;
463
520
  }
464
521
  return -1;
465
522
  }
466
- function sanitizeCSSValue(value, context) {
467
- const reason = blockedCssReason(value);
523
+ function sanitizeCSSDeclaration(property, value, context) {
524
+ const reason = blockedDeclarationReason(property, value);
468
525
  if (reason === -1) return value;
469
- warnDroppedDeclaration(value, reason, context);
526
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
470
527
  return "";
471
528
  }
529
+ function blockedDeclarationReason(property, value) {
530
+ const reason = blockedCssReason(value);
531
+ if (reason !== -1) return reason;
532
+ const name = canonicalAttrName(property);
533
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
534
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
535
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
536
+ return CSS_REASON_PROGID;
537
+ }
538
+ return -1;
539
+ }
540
+ function parseStyleDeclarations(css) {
541
+ const chunks = [];
542
+ let buf = "";
543
+ let depth = 0;
544
+ let quote2 = "";
545
+ for (let i2 = 0; i2 < css.length; i2++) {
546
+ const ch = css[i2];
547
+ if (quote2) {
548
+ buf += ch;
549
+ if (ch === "\\" && i2 + 1 < css.length) buf += css[++i2];
550
+ else if (ch === quote2) quote2 = "";
551
+ continue;
552
+ }
553
+ if (ch === "\\") {
554
+ buf += ch;
555
+ if (i2 + 1 < css.length) buf += css[++i2];
556
+ continue;
557
+ }
558
+ if (ch === "/" && css[i2 + 1] === "*") {
559
+ const end = css.indexOf("*/", i2 + 2);
560
+ if (end === -1) return null;
561
+ i2 = end + 1;
562
+ continue;
563
+ }
564
+ if (ch === '"' || ch === "'") quote2 = ch;
565
+ else if (ch === "(" || ch === "[") depth++;
566
+ else if (ch === ")" || ch === "]") {
567
+ if (--depth < 0) return null;
568
+ } else if (ch === "{" || ch === "}") return null;
569
+ else if (ch === ";" && depth === 0) {
570
+ chunks.push(buf);
571
+ buf = "";
572
+ continue;
573
+ }
574
+ buf += ch;
575
+ }
576
+ if (quote2 || depth !== 0) return null;
577
+ chunks.push(buf);
578
+ const out = [];
579
+ for (const chunk of chunks) {
580
+ const colon = chunk.indexOf(":");
581
+ if (colon === -1) continue;
582
+ const rawName = chunk.slice(0, colon).trim();
583
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
584
+ let value = chunk.slice(colon + 1).trim();
585
+ const bang = /!\s*important\s*$/i.exec(value);
586
+ if (bang) value = value.slice(0, bang.index).trim();
587
+ if (value === "") continue;
588
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
589
+ out.push([property, value, bang !== null]);
590
+ }
591
+ return out;
592
+ }
593
+ function sanitizeStyleDeclarationList(cssText, context) {
594
+ const input2 = String(cssText);
595
+ const declarations = parseStyleDeclarations(input2);
596
+ if (declarations === null) {
597
+ if (blockedCssReason(input2) !== -1) warnDroppedDeclaration(input2, blockedCssReason(input2), context);
598
+ return "";
599
+ }
600
+ const kept = [];
601
+ for (const [property, value, important] of declarations) {
602
+ const reason = blockedDeclarationReason(property, value);
603
+ if (reason !== -1) {
604
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
605
+ continue;
606
+ }
607
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
608
+ }
609
+ return kept.join("; ");
610
+ }
472
611
  function sanitizeStyleAttribute(cssText, context) {
473
612
  const input2 = String(cssText);
474
613
  if (input2.trim() === "") return "";
475
- if (typeof document === "undefined") {
476
- return sanitizeCSSValue(input2, context) === "" ? "" : input2;
477
- }
614
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input2, context);
478
615
  const probe = document.createElement("div");
479
616
  try {
480
617
  probe.style.cssText = input2;
@@ -485,8 +622,7 @@ function sanitizeStyleAttribute(cssText, context) {
485
622
  for (let i2 = 0; i2 < probe.style.length; i2++) {
486
623
  const property = probe.style[i2];
487
624
  const value = probe.style.getPropertyValue(property);
488
- let reason = blockedCssReason(value);
489
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
625
+ const reason = blockedDeclarationReason(property, value);
490
626
  if (reason !== -1) {
491
627
  warnDroppedDeclaration(value, reason, { property, element: context?.element });
492
628
  continue;
@@ -518,6 +654,9 @@ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
518
654
  function isHtmlContentAttribute(name) {
519
655
  return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
520
656
  }
657
+ function isUrlAttribute(attr) {
658
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
659
+ }
521
660
  function isPolicyAttribute(name) {
522
661
  const lower = name.toLowerCase();
523
662
  return lower === "style" || URL_ATTRIBUTES.has(lower);
@@ -529,6 +668,11 @@ function sanitizeAttributeString(attr, value, context) {
529
668
  if (lower === "style") return sanitizeStyleAttribute(value, context);
530
669
  return value;
531
670
  }
671
+ function resolveAttributeValue(attr, value, context) {
672
+ if (!isPolicyAttribute(attr)) return value;
673
+ const safe = sanitizeAttributeString(attr, value, context);
674
+ return safe === "" ? null : safe;
675
+ }
532
676
 
533
677
  // src/utils/metaRefresh.ts
534
678
  function isAsciiWhitespace(ch) {
@@ -544,40 +688,40 @@ function trimAsciiWhitespace(value) {
544
688
  var forbidden = (reason) => ({ kind: "forbidden", reason });
545
689
  function parseMetaRefreshContent(content) {
546
690
  const raw = trimAsciiWhitespace(stripControlChars(content));
547
- if (raw === "") return forbidden("empty content");
691
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
548
692
  let i2 = 0;
549
693
  while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
550
- if (i2 === 0) return forbidden("missing or non-numeric delay");
694
+ if (i2 === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
551
695
  const delay = Number.parseInt(raw.slice(0, i2), 10);
552
- if (!Number.isFinite(delay)) return forbidden("unparseable delay");
696
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
553
697
  while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
554
698
  if (i2 === raw.length) return { kind: "delay-only", delay };
555
- if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden("unexpected text after delay");
699
+ if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
556
700
  i2++;
557
701
  while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
558
702
  const keyStart = i2;
559
703
  while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
560
704
  const key = raw.slice(keyStart, i2).toLowerCase();
561
- if (key !== "url") return forbidden(`unsupported refresh key ${JSON.stringify(key)}`);
705
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
562
706
  while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
563
- if (raw[i2] !== "=") return forbidden("missing '=' after url");
707
+ if (raw[i2] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
564
708
  i2++;
565
709
  while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
566
710
  let destination;
567
711
  const quote2 = raw[i2];
568
712
  if (quote2 === '"' || quote2 === "'") {
569
713
  const close = raw.indexOf(quote2, i2 + 1);
570
- if (close === -1) return forbidden("unterminated quoted destination");
714
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
571
715
  destination = raw.slice(i2 + 1, close);
572
716
  const rest = trimAsciiWhitespace(raw.slice(close + 1));
573
- if (rest !== "") return forbidden("trailing text after quoted destination");
717
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
574
718
  } else {
575
719
  destination = trimAsciiWhitespace(raw.slice(i2));
576
- if (/[;,]/.test(destination)) return forbidden("multiple refresh assignments");
720
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
577
721
  }
578
- if (destination === "") return forbidden("empty destination");
722
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
579
723
  const sanitizedDestination = sanitizeUrl(destination);
580
- if (sanitizedDestination === "") return forbidden("destination uses a disallowed protocol");
724
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
581
725
  return { kind: "allowed", delay, destination, sanitizedDestination };
582
726
  }
583
727
  function resolveMetaRefreshPolicy(attributes) {
@@ -593,7 +737,7 @@ function resolveMetaRefreshPolicy(attributes) {
593
737
  else if (canonical === "content") content = value;
594
738
  }
595
739
  if (duplicateName !== void 0) {
596
- return forbidden(`duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}`);
740
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
597
741
  }
598
742
  if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
599
743
  if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
@@ -619,6 +763,128 @@ function isForbiddenMetaEntry(props) {
619
763
  return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
620
764
  }
621
765
 
766
+ // src/utils/elementPolicy.ts
767
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
768
+ var SVG_NS = "http://www.w3.org/2000/svg";
769
+ var BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
770
+ function canonicalTagName(tag) {
771
+ return canonicalAttrName(tag);
772
+ }
773
+ function isBlockedElement(tag) {
774
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
775
+ }
776
+ var CODE_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
777
+ function isCodeTextElement(tag) {
778
+ return CODE_TEXT_ELEMENTS.has(canonicalTagName(tag));
779
+ }
780
+ var ContextualRefusal = {
781
+ None: 0,
782
+ /** The element would carry a refresh directive the shared policy forbids. */
783
+ MetaRefreshForbidden: 1,
784
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
785
+ MetaRefreshReactive: 2,
786
+ /** A runtime value may not choose which program a `<script>` runs. */
787
+ ScriptSource: 3,
788
+ /** An SVG animation may not target a URL, handler or document attribute. */
789
+ AnimationTarget: 4,
790
+ /** A runtime-written `href` may not become an applied stylesheet. */
791
+ StylesheetSource: 5
792
+ };
793
+ var SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
794
+ var SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
795
+ var reactiveMetaElements = /* @__PURE__ */ new WeakSet();
796
+ function isHtmlNamespace(el) {
797
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
798
+ }
799
+ function metaSnapshot(el, canonical, value) {
800
+ const snapshot = /* @__PURE__ */ new Map();
801
+ const attrs = el.attributes;
802
+ for (let i2 = 0; i2 < attrs.length; i2++) {
803
+ const attr = attrs[i2];
804
+ if (attr.name === canonical) continue;
805
+ snapshot.set(attr.name, attr.value);
806
+ }
807
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
808
+ return snapshot;
809
+ }
810
+ function claimReactiveMeta(el) {
811
+ if (reactiveMetaElements.has(el)) return;
812
+ reactiveMetaElements.add(el);
813
+ if (el.getAttribute("content") === null) return;
814
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
815
+ el.removeAttribute("content");
816
+ }
817
+ }
818
+ function metaRefusal(el, name, value, origin) {
819
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
820
+ const canonical = canonicalAttrName(name);
821
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
822
+ if (origin === "reactive") claimReactiveMeta(el);
823
+ if (value === null) return ContextualRefusal.None;
824
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
825
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
826
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
827
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
828
+ return ContextualRefusal.None;
829
+ }
830
+ function animationRefusal(name, value) {
831
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
832
+ const target = stripControlChars(value);
833
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
834
+ }
835
+ var runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
836
+ function relHasStylesheet(rel) {
837
+ if (rel === null) return false;
838
+ const lower = canonicalAttrName(rel);
839
+ if (!lower.includes("stylesheet")) return false;
840
+ const tokens = lower.split(/[\t\n\f\r ]+/);
841
+ for (let i2 = 0; i2 < tokens.length; i2++) if (tokens[i2] === "stylesheet") return true;
842
+ return false;
843
+ }
844
+ function stylesheetRefusal(el, name, value, origin) {
845
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
846
+ const canonical = canonicalAttrName(name);
847
+ if (canonical === "href") {
848
+ if (origin === "static" || value === null) return ContextualRefusal.None;
849
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
850
+ runtimeHrefLinks.add(el);
851
+ return ContextualRefusal.None;
852
+ }
853
+ if (canonical === "rel") {
854
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
855
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
856
+ }
857
+ return ContextualRefusal.None;
858
+ }
859
+ function contextualAttributeRefusal(el, name, value, origin) {
860
+ const local = el.localName;
861
+ if (local === "meta") return metaRefusal(el, name, value, origin);
862
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
863
+ if (local === "script") {
864
+ if (origin === "static" || value === null) return ContextualRefusal.None;
865
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
866
+ }
867
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
868
+ return ContextualRefusal.None;
869
+ }
870
+ function settleAttributeProvenance(el, name, origin) {
871
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
872
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
873
+ else runtimeHrefLinks.delete(el);
874
+ }
875
+ function isForbiddenMetaSnapshot(attributes) {
876
+ return resolveMetaRefreshPolicy(attributes).kind === "forbidden";
877
+ }
878
+
879
+ // src/utils/guards.ts
880
+ function readOwnGlobal(key) {
881
+ if (typeof window === "undefined") return void 0;
882
+ if (!Object.hasOwn(window, key)) return void 0;
883
+ const value = window[key];
884
+ if (typeof Node !== "undefined" && value instanceof Node) return void 0;
885
+ return value;
886
+ }
887
+
622
888
  // src/utils/headEntry.ts
623
889
  var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
624
890
  function isEmittableHeadAttrName(name) {
@@ -629,9 +895,7 @@ function isEmittableHeadAttrName(name) {
629
895
  return true;
630
896
  }
631
897
  function sanitizeHeadAttrValue(canonical, value) {
632
- if (!isPolicyAttribute(canonical)) return value;
633
- const safe = sanitizeAttributeString(canonical, value);
634
- return safe === "" ? null : safe;
898
+ return resolveAttributeValue(canonical, value);
635
899
  }
636
900
  function findDuplicateAttributeName(names) {
637
901
  const seen = /* @__PURE__ */ new Set();
@@ -688,15 +952,9 @@ function serializeHeadEntry(tag, attrs) {
688
952
  }
689
953
 
690
954
  // src/platform/ssr.ts
691
- function isPolicyAttr(lowerName) {
692
- return isPolicyAttribute(lowerName);
693
- }
694
955
  function isForbiddenSsrAttr(lowerName) {
695
956
  return isHtmlContentAttribute(lowerName);
696
957
  }
697
- function sanitizeSsrAttributeValue(lowerName, value) {
698
- return sanitizeAttributeString(lowerName, value);
699
- }
700
958
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
701
959
  function isSafeAttrName(name) {
702
960
  return SAFE_ATTR_NAME.test(name);
@@ -727,6 +985,64 @@ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
727
985
  "track",
728
986
  "wbr"
729
987
  ]);
988
+ function findNativeGetter(proto, name) {
989
+ for (let p2 = proto; p2; p2 = Object.getPrototypeOf(p2)) {
990
+ const get = Object.getOwnPropertyDescriptor(p2, name)?.get;
991
+ if (get) return get;
992
+ }
993
+ return null;
994
+ }
995
+ var nativeAccessorsByRealm = /* @__PURE__ */ new WeakMap();
996
+ function nativeAccessors() {
997
+ const realm = Element;
998
+ let accessors = nativeAccessorsByRealm.get(realm);
999
+ if (!accessors) {
1000
+ accessors = {
1001
+ attributes: findNativeGetter(Element.prototype, "attributes"),
1002
+ tagName: findNativeGetter(Element.prototype, "tagName"),
1003
+ childNodes: findNativeGetter(Node.prototype, "childNodes"),
1004
+ getAttribute: Element.prototype.getAttribute
1005
+ };
1006
+ nativeAccessorsByRealm.set(realm, accessors);
1007
+ }
1008
+ return accessors;
1009
+ }
1010
+ function attributesOf(el) {
1011
+ const get = nativeAccessors().attributes;
1012
+ return get ? get.call(el) : el.attributes;
1013
+ }
1014
+ function tagNameOf(el) {
1015
+ const get = nativeAccessors().tagName;
1016
+ return get ? get.call(el) : el.tagName;
1017
+ }
1018
+ function childNodesOf(node) {
1019
+ const get = nativeAccessors().childNodes;
1020
+ return get ? get.call(node) : node.childNodes;
1021
+ }
1022
+ function getAttributeOf(el, name) {
1023
+ return nativeAccessors().getAttribute.call(el, name);
1024
+ }
1025
+ function serializeOpenTag(element, tag) {
1026
+ let html2 = `<${tag}`;
1027
+ const meta2 = tag === "meta" ? /* @__PURE__ */ new Map() : null;
1028
+ const attrs = attributesOf(element);
1029
+ for (let i2 = 0; i2 < attrs.length; i2++) {
1030
+ const attr = attrs[i2];
1031
+ const rawName = attr.name;
1032
+ if (!isSafeAttrName(rawName)) continue;
1033
+ if (isEventHandlerAttr(rawName)) continue;
1034
+ if (isForbiddenSsrAttr(rawName)) continue;
1035
+ const value = resolveAttributeValue(rawName.toLowerCase(), attr.value);
1036
+ if (value === null) continue;
1037
+ meta2?.set(rawName, value);
1038
+ html2 += ` ${rawName}="${escapeAttr(value)}"`;
1039
+ }
1040
+ if (meta2 && isForbiddenMetaSnapshot(meta2)) return null;
1041
+ if (!getAttributeOf(element, "data-sibu-hydrate")) {
1042
+ html2 += ` data-sibu-ssr="true"`;
1043
+ }
1044
+ return html2;
1045
+ }
730
1046
  function renderToString(element) {
731
1047
  if (element instanceof DocumentFragment) {
732
1048
  return Array.from(element.childNodes).map((child) => {
@@ -746,35 +1062,21 @@ function renderToString(element) {
746
1062
  if (!(element instanceof HTMLElement)) {
747
1063
  return escapeHtml(element.textContent || "");
748
1064
  }
749
- const tag = element.tagName.toLowerCase();
750
- if (tag === "script" || tag === "style") {
1065
+ const tag = tagNameOf(element).toLowerCase();
1066
+ if (isCodeTextElement(tag)) {
751
1067
  return DEV ? `<!--ssr:${tag}-stripped-->` : "";
752
1068
  }
753
1069
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
754
1070
  return DEV ? "<!--ssr:invalid-tag-->" : "";
755
1071
  }
756
- let html2 = `<${tag}`;
757
- for (const attr of Array.from(element.attributes)) {
758
- const rawName = attr.name;
759
- if (!isSafeAttrName(rawName)) continue;
760
- if (isEventHandlerAttr(rawName)) continue;
761
- if (isForbiddenSsrAttr(rawName)) continue;
762
- const lowerName = rawName.toLowerCase();
763
- let value = attr.value;
764
- if (isPolicyAttr(lowerName)) {
765
- value = sanitizeSsrAttributeValue(lowerName, value);
766
- if (!value) continue;
767
- }
768
- html2 += ` ${rawName}="${escapeAttr(value)}"`;
769
- }
770
- if (element.dataset && !element.dataset.sibuHydrate) {
771
- html2 += ` data-sibu-ssr="true"`;
772
- }
1072
+ const openTag = serializeOpenTag(element, tag);
1073
+ if (openTag === null) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
1074
+ let html2 = openTag;
773
1075
  if (VOID_ELEMENTS.has(tag)) {
774
1076
  return `${html2} />`;
775
1077
  }
776
1078
  html2 += ">";
777
- for (const child of Array.from(element.childNodes)) {
1079
+ for (const child of Array.from(childNodesOf(element))) {
778
1080
  try {
779
1081
  html2 += renderToString(child);
780
1082
  } catch (err) {
@@ -924,12 +1226,8 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
924
1226
  if (!isSafeAttrName(rawKey)) continue;
925
1227
  if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
926
1228
  if (isForbiddenSsrAttr(rawKey)) continue;
927
- const lowerKey = rawKey.toLowerCase();
928
- let value = String(attrs[rawKey]);
929
- if (isPolicyAttr(lowerKey)) {
930
- value = sanitizeSsrAttributeValue(lowerKey, value);
931
- if (!value) continue;
932
- }
1229
+ const value = resolveAttributeValue(rawKey.toLowerCase(), String(attrs[rawKey]));
1230
+ if (value === null) continue;
933
1231
  out.push(`${rawKey}="${escapeAttr(value)}"`);
934
1232
  }
935
1233
  return out.join(" ");
@@ -992,8 +1290,8 @@ async function* renderToStream(element) {
992
1290
  yield escapeHtml(element.textContent || "");
993
1291
  return;
994
1292
  }
995
- const tag = element.tagName.toLowerCase();
996
- if (tag === "script" || tag === "style") {
1293
+ const tag = tagNameOf(element).toLowerCase();
1294
+ if (isCodeTextElement(tag)) {
997
1295
  if (DEV) yield `<!--ssr:${tag}-stripped-->`;
998
1296
  return;
999
1297
  }
@@ -1001,29 +1299,17 @@ async function* renderToStream(element) {
1001
1299
  if (DEV) yield "<!--ssr:invalid-tag-->";
1002
1300
  return;
1003
1301
  }
1004
- let openTag = `<${tag}`;
1005
- for (const attr of Array.from(element.attributes)) {
1006
- const rawName = attr.name;
1007
- if (!isSafeAttrName(rawName)) continue;
1008
- if (isEventHandlerAttr(rawName)) continue;
1009
- if (isForbiddenSsrAttr(rawName)) continue;
1010
- const lowerName = rawName.toLowerCase();
1011
- let value = attr.value;
1012
- if (isPolicyAttr(lowerName)) {
1013
- value = sanitizeSsrAttributeValue(lowerName, value);
1014
- if (!value) continue;
1015
- }
1016
- openTag += ` ${rawName}="${escapeAttr(value)}"`;
1017
- }
1018
- if (element.dataset && !element.dataset.sibuHydrate) {
1019
- openTag += ` data-sibu-ssr="true"`;
1302
+ const openTag = serializeOpenTag(element, tag);
1303
+ if (openTag === null) {
1304
+ if (DEV) yield `<!--ssr:${tag}-stripped-->`;
1305
+ return;
1020
1306
  }
1021
1307
  if (VOID_ELEMENTS.has(tag)) {
1022
1308
  yield `${openTag} />`;
1023
1309
  return;
1024
1310
  }
1025
1311
  yield `${openTag}>`;
1026
- for (const child of Array.from(element.childNodes)) {
1312
+ for (const child of Array.from(childNodesOf(element))) {
1027
1313
  try {
1028
1314
  yield* renderToStream(child);
1029
1315
  } catch (err) {
@@ -1208,8 +1494,7 @@ function deserializeState(validate) {
1208
1494
  "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
1209
1495
  );
1210
1496
  }
1211
- const w = window;
1212
- const raw = w[SSR_DATA_ATTR];
1497
+ const raw = readOwnGlobal(SSR_DATA_ATTR);
1213
1498
  if (raw === void 0) return void 0;
1214
1499
  if (validate && !validate(raw)) return void 0;
1215
1500
  return raw;
@@ -1712,7 +1997,7 @@ function forEachSubscriber(signal2, visit) {
1712
1997
  }
1713
1998
 
1714
1999
  // src/reactivity/track.ts
1715
- var _runtimeVersion = true ? "4.7.0" : "dev";
2000
+ var _runtimeVersion = true ? "4.9.0" : "dev";
1716
2001
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1717
2002
  function resolveReactiveApi() {
1718
2003
  const g2 = globalThis;
@@ -2316,7 +2601,7 @@ function createISR(options) {
2316
2601
  const [data2, setData] = signal(initialData);
2317
2602
  const [stale, setStale] = signal(initialData === void 0);
2318
2603
  const controller = new AbortController();
2319
- let inFlight = false;
2604
+ let inFlight = null;
2320
2605
  let disposed = false;
2321
2606
  let deadline;
2322
2607
  const clearDeadline = () => {
@@ -2336,10 +2621,7 @@ function createISR(options) {
2336
2621
  });
2337
2622
  }, revalidateAfter);
2338
2623
  };
2339
- const revalidate = async () => {
2340
- if (disposed || inFlight) return;
2341
- if (controller.signal.aborted) return;
2342
- inFlight = true;
2624
+ const run = async () => {
2343
2625
  try {
2344
2626
  const result = await fetcher({ signal: controller.signal });
2345
2627
  if (disposed || controller.signal.aborted) return;
@@ -2351,10 +2633,33 @@ function createISR(options) {
2351
2633
  } catch (err) {
2352
2634
  if (!disposed && !controller.signal.aborted) armDeadline();
2353
2635
  throw err;
2354
- } finally {
2355
- inFlight = false;
2356
2636
  }
2357
2637
  };
2638
+ const revalidate = () => {
2639
+ if (disposed || controller.signal.aborted) return Promise.resolve();
2640
+ if (inFlight) return inFlight;
2641
+ let resolve;
2642
+ let reject;
2643
+ const current = new Promise((res, rej) => {
2644
+ resolve = res;
2645
+ reject = rej;
2646
+ });
2647
+ inFlight = current;
2648
+ const release = () => {
2649
+ if (inFlight === current) inFlight = null;
2650
+ };
2651
+ run().then(
2652
+ () => {
2653
+ release();
2654
+ resolve();
2655
+ },
2656
+ (err) => {
2657
+ release();
2658
+ reject(err);
2659
+ }
2660
+ );
2661
+ return current;
2662
+ };
2358
2663
  if (initialData === void 0) {
2359
2664
  revalidate().catch((err) => {
2360
2665
  if (typeof console !== "undefined") console.warn("[SibuJS ISR] initial fetch failed", err);
@@ -2523,25 +2828,43 @@ function createMiddlewareChain() {
2523
2828
  }
2524
2829
 
2525
2830
  // src/utils/setSafeAttribute.ts
2526
- var HTML_NS = "http://www.w3.org/1999/xhtml";
2527
- var SVG_NS = "http://www.w3.org/2000/svg";
2831
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
2832
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
2528
2833
  var XLINK_NS = "http://www.w3.org/1999/xlink";
2529
2834
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
2530
2835
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
2531
2836
  function isHtmlElement(el) {
2532
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
2837
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
2838
+ }
2839
+ function warnContextualRefusal(label2, name, refusal) {
2840
+ devWarnLazy(() => {
2841
+ const reasons = [
2842
+ "",
2843
+ '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)',
2844
+ "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",
2845
+ "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",
2846
+ "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",
2847
+ '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'
2848
+ ];
2849
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
2850
+ });
2533
2851
  }
2534
2852
  function setProp(el, key, val) {
2535
2853
  el[key] = val;
2536
2854
  }
2537
2855
  function namespaceFor(el, name) {
2538
- if (el.namespaceURI !== SVG_NS) return null;
2856
+ if (el.namespaceURI !== SVG_NS2) return null;
2539
2857
  const lower = name.toLowerCase();
2540
2858
  if (lower.startsWith("xlink:")) return XLINK_NS;
2541
2859
  if (lower.startsWith("xml:")) return XML_NS;
2542
2860
  return null;
2543
2861
  }
2544
2862
  function setSafeAttribute(el, name, value, options = {}) {
2863
+ const written = commitSafeAttribute(el, name, value, options);
2864
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
2865
+ return written;
2866
+ }
2867
+ function commitSafeAttribute(el, name, value, options) {
2545
2868
  const ns = namespaceFor(el, name);
2546
2869
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
2547
2870
  if (isEventHandlerAttr(name)) {
@@ -2554,6 +2877,14 @@ function setSafeAttribute(el, name, value, options = {}) {
2554
2877
  else el.removeAttribute(name);
2555
2878
  return false;
2556
2879
  }
2880
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
2881
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
2882
+ if (refusal !== ContextualRefusal.None) {
2883
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
2884
+ if (ns) el.removeAttributeNS(ns, localName);
2885
+ else el.removeAttribute(name);
2886
+ return false;
2887
+ }
2557
2888
  if (value == null) {
2558
2889
  if (ns) el.removeAttributeNS(ns, localName);
2559
2890
  else el.removeAttribute(name);
@@ -2597,7 +2928,12 @@ function setSafeAttribute(el, name, value, options = {}) {
2597
2928
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
2598
2929
  return true;
2599
2930
  }
2600
- const safe = sanitizeAttributeString(name, str, { element: el });
2931
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
2932
+ if (safe === null) {
2933
+ if (ns) el.removeAttributeNS(ns, localName);
2934
+ else el.removeAttribute(name);
2935
+ return true;
2936
+ }
2601
2937
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
2602
2938
  if (current === safe) return true;
2603
2939
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -2620,7 +2956,7 @@ function bindAttribute(el, attr, getter) {
2620
2956
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
2621
2957
  return;
2622
2958
  }
2623
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
2959
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
2624
2960
  }
2625
2961
  return reactiveBinding2(commit, el);
2626
2962
  }
@@ -2794,11 +3130,7 @@ function bindChildNode(placeholder, getter) {
2794
3130
  }
2795
3131
 
2796
3132
  // src/core/rendering/tagFactory.ts
2797
- var SVG_NS2 = "http://www.w3.org/2000/svg";
2798
- var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
2799
- function isBlockedTag(tag) {
2800
- return BLOCKED_TAGS.has(tag.toLowerCase());
2801
- }
3133
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
2802
3134
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
2803
3135
  "config",
2804
3136
  "location",
@@ -2843,11 +3175,12 @@ function applyStyle(el, style2) {
2843
3175
  }
2844
3176
  const next = [];
2845
3177
  for (const prop in value) {
3178
+ if (!Object.hasOwn(value, prop)) continue;
2846
3179
  const val = value[prop];
2847
3180
  if (val == null) continue;
2848
3181
  const name = toKebab(prop);
2849
3182
  next.push(name);
2850
- decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
3183
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2851
3184
  }
2852
3185
  for (let i2 = 0; i2 < written.length; i2++) {
2853
3186
  if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
@@ -2863,16 +3196,17 @@ function applyStyle(el, style2) {
2863
3196
  }
2864
3197
  const htmlEl = el;
2865
3198
  for (const prop in style2) {
3199
+ if (!Object.hasOwn(style2, prop)) continue;
2866
3200
  const val = style2[prop];
2867
3201
  const name = toKebab(prop);
2868
3202
  if (typeof val === "function") {
2869
3203
  const getter = val;
2870
3204
  const teardown = reactiveBinding2(() => {
2871
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
3205
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
2872
3206
  }, el);
2873
3207
  registerDisposer(el, teardown);
2874
3208
  } else {
2875
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
3209
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2876
3210
  }
2877
3211
  }
2878
3212
  }
@@ -2894,6 +3228,7 @@ function applyClass(el, cls) {
2894
3228
  let hasReactive = false;
2895
3229
  let result = "";
2896
3230
  for (const name in obj) {
3231
+ if (!Object.hasOwn(obj, name)) continue;
2897
3232
  const val = obj[name];
2898
3233
  if (typeof val === "function") {
2899
3234
  hasReactive = true;
@@ -2905,6 +3240,7 @@ function applyClass(el, cls) {
2905
3240
  const update = () => {
2906
3241
  let r = "";
2907
3242
  for (const name in obj) {
3243
+ if (!Object.hasOwn(obj, name)) continue;
2908
3244
  const val = obj[name];
2909
3245
  const active = typeof val === "function" ? val() : val;
2910
3246
  if (active) r = r ? `${r} ${name}` : name;
@@ -2968,7 +3304,7 @@ function appendChildren(el, nodes) {
2968
3304
  }
2969
3305
  }
2970
3306
  function tagFactory(tag, ns) {
2971
- const blocked = isBlockedTag(tag);
3307
+ const blocked = isBlockedElement(tag);
2972
3308
  return (first, second) => {
2973
3309
  if (blocked) {
2974
3310
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -3031,6 +3367,7 @@ function tagFactory(tag, ns) {
3031
3367
  const pOn = props.on;
3032
3368
  if (pOn) {
3033
3369
  for (const ev in pOn) {
3370
+ if (!Object.hasOwn(pOn, ev)) continue;
3034
3371
  const handler = pOn[ev];
3035
3372
  if (typeof handler === "function") {
3036
3373
  el.addEventListener(ev, handler);
@@ -3057,6 +3394,7 @@ function tagFactory(tag, ns) {
3057
3394
  continue;
3058
3395
  // already handled above / below
3059
3396
  default: {
3397
+ if (!Object.hasOwn(props, key)) continue;
3060
3398
  const value = props[key];
3061
3399
  if (value == null) continue;
3062
3400
  if (isEventHandlerAttr(key)) continue;
@@ -3146,7 +3484,7 @@ var param = tagFactory("param");
3146
3484
  var picture = tagFactory("picture");
3147
3485
  var portal = tagFactory("portal");
3148
3486
  var source = tagFactory("source");
3149
- var svg = tagFactory("svg", SVG_NS2);
3487
+ var svg = tagFactory("svg", SVG_NS3);
3150
3488
  var math = tagFactory("math");
3151
3489
  var canvas = tagFactory("canvas");
3152
3490
  var noscript = tagFactory("noscript");
@@ -3187,26 +3525,26 @@ var base = tagFactory("base");
3187
3525
  var link = tagFactory("link");
3188
3526
  var meta = tagFactory("meta");
3189
3527
  var style = tagFactory("style");
3190
- var circle = tagFactory("circle", SVG_NS2);
3191
- var ellipse = tagFactory("ellipse", SVG_NS2);
3192
- var g = tagFactory("g", SVG_NS2);
3193
- var line = tagFactory("line", SVG_NS2);
3194
- var path = tagFactory("path", SVG_NS2);
3195
- var polygon = tagFactory("polygon", SVG_NS2);
3196
- var polyline = tagFactory("polyline", SVG_NS2);
3197
- var rect = tagFactory("rect", SVG_NS2);
3198
- var text = tagFactory("text", SVG_NS2);
3199
- var tspan = tagFactory("tspan", SVG_NS2);
3200
- var defs = tagFactory("defs", SVG_NS2);
3201
- var clipPath = tagFactory("clipPath", SVG_NS2);
3202
- var mask = tagFactory("mask", SVG_NS2);
3203
- var pattern = tagFactory("pattern", SVG_NS2);
3204
- var linearGradient = tagFactory("linearGradient", SVG_NS2);
3205
- var radialGradient = tagFactory("radialGradient", SVG_NS2);
3206
- var stop = tagFactory("stop", SVG_NS2);
3207
- var use = tagFactory("use", SVG_NS2);
3208
- var symbol = tagFactory("symbol", SVG_NS2);
3209
- var marker = tagFactory("marker", SVG_NS2);
3528
+ var circle = tagFactory("circle", SVG_NS3);
3529
+ var ellipse = tagFactory("ellipse", SVG_NS3);
3530
+ var g = tagFactory("g", SVG_NS3);
3531
+ var line = tagFactory("line", SVG_NS3);
3532
+ var path = tagFactory("path", SVG_NS3);
3533
+ var polygon = tagFactory("polygon", SVG_NS3);
3534
+ var polyline = tagFactory("polyline", SVG_NS3);
3535
+ var rect = tagFactory("rect", SVG_NS3);
3536
+ var text = tagFactory("text", SVG_NS3);
3537
+ var tspan = tagFactory("tspan", SVG_NS3);
3538
+ var defs = tagFactory("defs", SVG_NS3);
3539
+ var clipPath = tagFactory("clipPath", SVG_NS3);
3540
+ var mask = tagFactory("mask", SVG_NS3);
3541
+ var pattern = tagFactory("pattern", SVG_NS3);
3542
+ var linearGradient = tagFactory("linearGradient", SVG_NS3);
3543
+ var radialGradient = tagFactory("radialGradient", SVG_NS3);
3544
+ var stop = tagFactory("stop", SVG_NS3);
3545
+ var use = tagFactory("use", SVG_NS3);
3546
+ var symbol = tagFactory("symbol", SVG_NS3);
3547
+ var marker = tagFactory("marker", SVG_NS3);
3210
3548
  var center = tagFactory("center");
3211
3549
  var font = tagFactory("font");
3212
3550
  var marquee = tagFactory("marquee");
@@ -3487,7 +3825,16 @@ function toError(err) {
3487
3825
  if (err !== null && typeof err === "object" && "name" in err && "message" in err) return err;
3488
3826
  return new Error(String(err));
3489
3827
  }
3828
+ function workerSource(fn, api) {
3829
+ if (typeof fn !== "function") {
3830
+ throw new TypeError(
3831
+ `${api}: expected a function, got ${typeof fn}. Worker code must be a function defined in your source \u2014 a string would be evaluated as code.`
3832
+ );
3833
+ }
3834
+ return Function.prototype.toString.call(fn);
3835
+ }
3490
3836
  function worker(workerFn2) {
3837
+ const fnBody = workerSource(workerFn2, "worker");
3491
3838
  const [result, setResult] = signal(null);
3492
3839
  const [error, setError] = signal(null);
3493
3840
  const [loading, setLoading] = signal(false);
@@ -3503,7 +3850,6 @@ function worker(workerFn2) {
3503
3850
  if (typeof Worker === "undefined") {
3504
3851
  throw new Error("Web Workers are not supported in this environment");
3505
3852
  }
3506
- const fnBody = workerFn2.toString();
3507
3853
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
3508
3854
  blobUrl = URL.createObjectURL(blob);
3509
3855
  worker2 = new Worker(blobUrl);
@@ -3552,6 +3898,7 @@ function worker(workerFn2) {
3552
3898
  return { post, result, error, loading, terminate };
3553
3899
  }
3554
3900
  function workerFn(fn) {
3901
+ const fnStr = workerSource(fn, "workerFn");
3555
3902
  const [loading, setLoading] = signal(false);
3556
3903
  let worker2 = null;
3557
3904
  let blobUrl = null;
@@ -3566,7 +3913,6 @@ function workerFn(fn) {
3566
3913
  if (typeof Worker === "undefined") {
3567
3914
  throw new Error("Web Workers are not supported in this environment");
3568
3915
  }
3569
- const fnStr = fn.toString();
3570
3916
  const blob = new Blob(
3571
3917
  [
3572
3918
  `self.onmessage = function(e) {
@@ -3626,6 +3972,7 @@ function workerFn(fn) {
3626
3972
  return { run, loading, terminate };
3627
3973
  }
3628
3974
  function createWorkerPool(workerFn2, poolSize) {
3975
+ const fnBody = workerSource(workerFn2, "createWorkerPool");
3629
3976
  const size = poolSize || typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
3630
3977
  const workers = [];
3631
3978
  const queues = [];
@@ -3684,7 +4031,6 @@ function createWorkerPool(workerFn2, poolSize) {
3684
4031
  if (typeof Worker === "undefined") {
3685
4032
  throw new Error("Web Workers are not supported in this environment");
3686
4033
  }
3687
- const fnBody = workerFn2.toString();
3688
4034
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
3689
4035
  blobUrl = URL.createObjectURL(blob);
3690
4036
  for (let i2 = 0; i2 < size; i2++) {