sibujs 4.7.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 (81) hide show
  1. package/dist/browser.cjs +45 -4
  2. package/dist/browser.d.cts +23 -0
  3. package/dist/browser.d.ts +23 -0
  4. package/dist/browser.js +6 -5
  5. package/dist/build.cjs +513 -78
  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-3ACAAQIN.js +224 -0
  14. package/dist/{chunk-DKXRACVN.js → chunk-44MTL7ED.js} +6 -6
  15. package/dist/{chunk-3QWBSL5R.js → chunk-4WMSTZ7J.js} +8 -5
  16. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  17. package/dist/{chunk-BSY63EM6.js → chunk-C5PEWEGH.js} +20 -5
  18. package/dist/{chunk-BC2SECJD.js → chunk-C7AA2CZX.js} +1 -1
  19. package/dist/{chunk-SLM3IA34.js → chunk-CZPRSWSS.js} +1 -1
  20. package/dist/{chunk-FQRUXCEE.js → chunk-DSID757X.js} +1 -1
  21. package/dist/{chunk-ZVL7TY4K.js → chunk-EGUQLBHE.js} +22 -8
  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-Z3OHK6QT.js → chunk-GSGNX35Q.js} +5 -5
  25. package/dist/{chunk-XYV3EDB7.js → chunk-J3YQ457H.js} +37 -20
  26. package/dist/{chunk-VZSG24LS.js → chunk-JUTFX2RM.js} +18 -10
  27. package/dist/{chunk-RUSSKG6G.js → chunk-KJ3UKABJ.js} +1 -1
  28. package/dist/{chunk-UNWRJRKC.js → chunk-LI4W3U6C.js} +1 -1
  29. package/dist/{chunk-FNJXNGYZ.js → chunk-O6TRVMJI.js} +58 -6
  30. package/dist/{chunk-7LUJQAOJ.js → chunk-Q5J2LVKH.js} +1 -1
  31. package/dist/{chunk-4AWA2PVD.js → chunk-QGMSESDD.js} +10 -5
  32. package/dist/{chunk-XZR4PXRE.js → chunk-RGLPEBDE.js} +13 -10
  33. package/dist/{chunk-JWKYU5GV.js → chunk-TQQUN4WR.js} +1 -1
  34. package/dist/{chunk-WOLJZUFQ.js → chunk-UCNP3HVM.js} +12 -8
  35. package/dist/{chunk-WEQ3DMVL.js → chunk-V3LHZSU5.js} +1 -1
  36. package/dist/{chunk-UOL2ECCS.js → chunk-VWT3VXPL.js} +18 -12
  37. package/dist/{chunk-2BPG2XDA.js → chunk-ZVLHNEEL.js} +17 -35
  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 +9 -2
  41. package/dist/data.js +6 -6
  42. package/dist/devtools.cjs +1 -1
  43. package/dist/devtools.js +5 -5
  44. package/dist/ecosystem.cjs +400 -37
  45. package/dist/ecosystem.js +9 -8
  46. package/dist/extras.cjs +621 -252
  47. package/dist/extras.d.cts +2 -2
  48. package/dist/extras.d.ts +2 -2
  49. package/dist/extras.js +24 -23
  50. package/dist/index.cjs +464 -70
  51. package/dist/index.d.cts +28 -2
  52. package/dist/index.d.ts +28 -2
  53. package/dist/index.js +16 -12
  54. package/dist/motion.cjs +1 -1
  55. package/dist/motion.js +3 -3
  56. package/dist/patterns.cjs +10 -7
  57. package/dist/patterns.js +6 -6
  58. package/dist/performance.cjs +31 -2
  59. package/dist/performance.d.cts +12 -2
  60. package/dist/performance.d.ts +12 -2
  61. package/dist/performance.js +8 -6
  62. package/dist/plugins.cjs +558 -231
  63. package/dist/plugins.d.cts +8 -3
  64. package/dist/plugins.d.ts +8 -3
  65. package/dist/plugins.js +22 -28
  66. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  67. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.cts} +6 -0
  68. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.ts} +6 -0
  69. package/dist/ssr.cjs +457 -131
  70. package/dist/ssr.d.cts +55 -5
  71. package/dist/ssr.d.ts +55 -5
  72. package/dist/ssr.js +11 -9
  73. package/dist/testing.cjs +3 -3
  74. package/dist/testing.js +4 -4
  75. package/dist/ui.cjs +608 -240
  76. package/dist/ui.d.cts +1 -1
  77. package/dist/ui.d.ts +1 -1
  78. package/dist/ui.js +9 -8
  79. package/dist/widgets.cjs +1 -1
  80. package/dist/widgets.js +7 -7
  81. package/package.json +5 -4
package/dist/build.cjs CHANGED
@@ -791,6 +791,11 @@ function devWarn(message) {
791
791
  function stripControlChars(value) {
792
792
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
793
793
  }
794
+ var ASCII_UPPER = /[A-Z]/;
795
+ var ASCII_UPPER_G = /[A-Z]/g;
796
+ function canonicalAttrName(name) {
797
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
798
+ }
794
799
  function isEventHandlerAttr(name) {
795
800
  if (name.length < 3) return false;
796
801
  const lower = name.toLowerCase();
@@ -802,6 +807,9 @@ function sanitizeUrl(url) {
802
807
  const probe = stripControlChars(url).trim();
803
808
  if (!probe) return "";
804
809
  const output2 = url.replace(OUTER_TRIM, "");
810
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
811
+ return "";
812
+ }
805
813
  const lower = probe.toLowerCase();
806
814
  let schemeEnd = -1;
807
815
  for (let i2 = 0; i2 < lower.length; i2++) {
@@ -818,19 +826,58 @@ function sanitizeUrl(url) {
818
826
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
819
827
  return output2;
820
828
  }
821
- var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
829
+ var SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
830
+ function isSrcsetWhitespace(ch) {
831
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
832
+ }
822
833
  function sanitizeSrcset(value) {
823
- const parts = value.split(",");
834
+ const input2 = String(value);
824
835
  const out = [];
825
- for (let i2 = 0; i2 < parts.length; i2++) {
826
- const part = parts[i2].trim();
827
- if (!part) continue;
828
- const m = part.match(/^(\S+)(\s+.+)?$/);
829
- if (!m) continue;
830
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
831
- const safe = sanitizeUrl(m[1]);
836
+ let i2 = 0;
837
+ const n = input2.length;
838
+ while (i2 < n) {
839
+ while (i2 < n && (isSrcsetWhitespace(input2[i2]) || input2[i2] === ",")) i2++;
840
+ if (i2 >= n) break;
841
+ const urlStart = i2;
842
+ while (i2 < n && !isSrcsetWhitespace(input2[i2])) i2++;
843
+ let url = input2.slice(urlStart, i2);
844
+ const descriptors = [];
845
+ if (url.endsWith(",")) {
846
+ url = url.replace(/,+$/, "");
847
+ } else {
848
+ let current = "";
849
+ let inParens = false;
850
+ while (i2 < n) {
851
+ const ch = input2[i2];
852
+ if (inParens) {
853
+ current += ch;
854
+ if (ch === ")") inParens = false;
855
+ i2++;
856
+ continue;
857
+ }
858
+ if (isSrcsetWhitespace(ch)) {
859
+ if (current) descriptors.push(current);
860
+ current = "";
861
+ i2++;
862
+ continue;
863
+ }
864
+ if (ch === ",") {
865
+ i2++;
866
+ break;
867
+ }
868
+ if (ch === "(") inParens = true;
869
+ current += ch;
870
+ i2++;
871
+ }
872
+ if (current) descriptors.push(current);
873
+ }
874
+ if (url === "") continue;
875
+ if (descriptors.length > 1) continue;
876
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
877
+ const safe = sanitizeUrl(url);
832
878
  if (!safe) continue;
833
- out.push(m[2] ? `${safe}${m[2]}` : safe);
879
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
880
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
834
881
  }
835
882
  return out.join(", ");
836
883
  }
@@ -889,8 +936,16 @@ var BLOCKED_CSS_NEEDLES = [
889
936
  "behavior:",
890
937
  "@import",
891
938
  "image-set(",
892
- "filter:progid"
939
+ "filter:progid",
940
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
941
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
942
+ // today, which is exactly when a blocklist should get ahead of it.
943
+ "image(",
944
+ "src("
893
945
  ];
946
+ var CSS_REASON_MOZ_BINDING = 4;
947
+ var CSS_REASON_BEHAVIOR = 5;
948
+ var CSS_REASON_PROGID = 8;
894
949
  function describeElement(el) {
895
950
  if (!el) return "an element";
896
951
  let out = el.tagName.toLowerCase();
@@ -914,7 +969,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
914
969
  "behavior: loads and runs a remote HTC script",
915
970
  "@import pulls in a remote stylesheet",
916
971
  "image-set() fetches remote images, the same exfiltration channel as url()",
917
- "filter:progid activates legacy scriptable filters"
972
+ "filter:progid activates legacy scriptable filters",
973
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
974
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
918
975
  ];
919
976
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
920
977
  const where = describeElement(ctx?.element);
@@ -929,27 +986,110 @@ function warnDroppedDeclaration(value, reason, ctx) {
929
986
  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.`;
930
987
  });
931
988
  }
989
+ function normalizeCssForScan(value) {
990
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
991
+ return normalized.toLowerCase().replace(/\s+/g, "");
992
+ }
932
993
  function blockedCssReason(value) {
933
994
  if (!CSS_DANGER_GATE.test(value)) return -1;
934
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
935
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
995
+ const lower = normalizeCssForScan(value);
936
996
  for (let i2 = 0; i2 < BLOCKED_CSS_NEEDLES.length; i2++) {
937
997
  if (lower.includes(BLOCKED_CSS_NEEDLES[i2])) return i2;
938
998
  }
939
999
  return -1;
940
1000
  }
941
- function sanitizeCSSValue(value, context2) {
942
- const reason = blockedCssReason(value);
1001
+ function sanitizeCSSDeclaration(property, value, context2) {
1002
+ const reason = blockedDeclarationReason(property, value);
943
1003
  if (reason === -1) return value;
944
- warnDroppedDeclaration(value, reason, context2);
1004
+ warnDroppedDeclaration(value, reason, { property, element: context2?.element });
945
1005
  return "";
946
1006
  }
1007
+ function blockedDeclarationReason(property, value) {
1008
+ const reason = blockedCssReason(value);
1009
+ if (reason !== -1) return reason;
1010
+ const name = canonicalAttrName(property);
1011
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
1012
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
1013
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
1014
+ return CSS_REASON_PROGID;
1015
+ }
1016
+ return -1;
1017
+ }
1018
+ function parseStyleDeclarations(css) {
1019
+ const chunks = [];
1020
+ let buf = "";
1021
+ let depth = 0;
1022
+ let quote2 = "";
1023
+ for (let i2 = 0; i2 < css.length; i2++) {
1024
+ const ch = css[i2];
1025
+ if (quote2) {
1026
+ buf += ch;
1027
+ if (ch === "\\" && i2 + 1 < css.length) buf += css[++i2];
1028
+ else if (ch === quote2) quote2 = "";
1029
+ continue;
1030
+ }
1031
+ if (ch === "\\") {
1032
+ buf += ch;
1033
+ if (i2 + 1 < css.length) buf += css[++i2];
1034
+ continue;
1035
+ }
1036
+ if (ch === "/" && css[i2 + 1] === "*") {
1037
+ const end = css.indexOf("*/", i2 + 2);
1038
+ if (end === -1) return null;
1039
+ i2 = end + 1;
1040
+ continue;
1041
+ }
1042
+ if (ch === '"' || ch === "'") quote2 = ch;
1043
+ else if (ch === "(" || ch === "[") depth++;
1044
+ else if (ch === ")" || ch === "]") {
1045
+ if (--depth < 0) return null;
1046
+ } else if (ch === "{" || ch === "}") return null;
1047
+ else if (ch === ";" && depth === 0) {
1048
+ chunks.push(buf);
1049
+ buf = "";
1050
+ continue;
1051
+ }
1052
+ buf += ch;
1053
+ }
1054
+ if (quote2 || depth !== 0) return null;
1055
+ chunks.push(buf);
1056
+ const out = [];
1057
+ for (const chunk of chunks) {
1058
+ const colon = chunk.indexOf(":");
1059
+ if (colon === -1) continue;
1060
+ const rawName = chunk.slice(0, colon).trim();
1061
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
1062
+ let value = chunk.slice(colon + 1).trim();
1063
+ const bang = /!\s*important\s*$/i.exec(value);
1064
+ if (bang) value = value.slice(0, bang.index).trim();
1065
+ if (value === "") continue;
1066
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
1067
+ out.push([property, value, bang !== null]);
1068
+ }
1069
+ return out;
1070
+ }
1071
+ function sanitizeStyleDeclarationList(cssText, context2) {
1072
+ const input2 = String(cssText);
1073
+ const declarations = parseStyleDeclarations(input2);
1074
+ if (declarations === null) {
1075
+ if (blockedCssReason(input2) !== -1) warnDroppedDeclaration(input2, blockedCssReason(input2), context2);
1076
+ return "";
1077
+ }
1078
+ const kept = [];
1079
+ for (const [property, value, important] of declarations) {
1080
+ const reason = blockedDeclarationReason(property, value);
1081
+ if (reason !== -1) {
1082
+ warnDroppedDeclaration(value, reason, { property, element: context2?.element });
1083
+ continue;
1084
+ }
1085
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
1086
+ }
1087
+ return kept.join("; ");
1088
+ }
947
1089
  function sanitizeStyleAttribute(cssText, context2) {
948
1090
  const input2 = String(cssText);
949
1091
  if (input2.trim() === "") return "";
950
- if (typeof document === "undefined") {
951
- return sanitizeCSSValue(input2, context2) === "" ? "" : input2;
952
- }
1092
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input2, context2);
953
1093
  const probe = document.createElement("div");
954
1094
  try {
955
1095
  probe.style.cssText = input2;
@@ -960,8 +1100,7 @@ function sanitizeStyleAttribute(cssText, context2) {
960
1100
  for (let i2 = 0; i2 < probe.style.length; i2++) {
961
1101
  const property = probe.style[i2];
962
1102
  const value = probe.style.getPropertyValue(property);
963
- let reason = blockedCssReason(value);
964
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
1103
+ const reason = blockedDeclarationReason(property, value);
965
1104
  if (reason !== -1) {
966
1105
  warnDroppedDeclaration(value, reason, { property, element: context2?.element });
967
1106
  continue;
@@ -996,6 +1135,10 @@ function isHtmlContentAttribute(name) {
996
1135
  function isUrlAttribute(attr) {
997
1136
  return URL_ATTRIBUTES.has(attr.toLowerCase());
998
1137
  }
1138
+ function isPolicyAttribute(name) {
1139
+ const lower = name.toLowerCase();
1140
+ return lower === "style" || URL_ATTRIBUTES.has(lower);
1141
+ }
999
1142
  function sanitizeAttributeString(attr, value, context2) {
1000
1143
  const lower = attr.toLowerCase();
1001
1144
  if (lower === "srcset") return sanitizeSrcset(value);
@@ -1003,6 +1146,11 @@ function sanitizeAttributeString(attr, value, context2) {
1003
1146
  if (lower === "style") return sanitizeStyleAttribute(value, context2);
1004
1147
  return value;
1005
1148
  }
1149
+ function resolveAttributeValue(attr, value, context2) {
1150
+ if (!isPolicyAttribute(attr)) return value;
1151
+ const safe = sanitizeAttributeString(attr, value, context2);
1152
+ return safe === "" ? null : safe;
1153
+ }
1006
1154
 
1007
1155
  // src/build/staticAnalysis.ts
1008
1156
  var STATIC_TAGS = /* @__PURE__ */ new Set([
@@ -1456,6 +1604,11 @@ function generateBody(roots, h) {
1456
1604
  const q2 = jsStringLiteral;
1457
1605
  const P = h.prefix;
1458
1606
  function genElement(el) {
1607
+ const tag = el.tag.toLowerCase();
1608
+ if (tag === "meta" || tag === "link") return null;
1609
+ for (const attr of el.attrs) {
1610
+ if ((attr.t === 0 || attr.t === 4) && attr.name.toLowerCase() === "srcdoc") return null;
1611
+ }
1459
1612
  const v = `e${n++}`;
1460
1613
  h.tags.add(el.tag);
1461
1614
  if (el.svg) h.svg = true;
@@ -1547,6 +1700,9 @@ function helperSource(h) {
1547
1700
  out.push(
1548
1701
  `function ${P}on(el, name, handler) {
1549
1702
  if (typeof handler === "function") el.addEventListener(name, handler);
1703
+ else if (typeof __SIBU_DEV__ !== "undefined" && __SIBU_DEV__) {
1704
+ console.warn(\`[SibuJS] html: on:\${name} handler is not a function (got \${typeof handler}). Event listener was not attached.\`);
1705
+ }
1550
1706
  }`
1551
1707
  );
1552
1708
  }
@@ -1554,7 +1710,10 @@ function helperSource(h) {
1554
1710
  out.push(
1555
1711
  `function ${P}child(el, value) {
1556
1712
  if (typeof value === "function") {
1557
- el.appendChild(${P}Fragment([value]));
1713
+ const frag = ${P}Fragment([value]);
1714
+ const ph = frag.firstChild;
1715
+ el.appendChild(frag);
1716
+ ${P}registerDisposer(el, () => ${P}dispose(ph));
1558
1717
  } else if (value instanceof Node) {
1559
1718
  el.appendChild(value);
1560
1719
  } else if (Array.isArray(value)) {
@@ -1574,13 +1733,22 @@ function helperSource(h) {
1574
1733
  function importSource(h) {
1575
1734
  const P = h.prefix;
1576
1735
  const core = [];
1577
- if (h.used.has("child")) core.push(`Fragment as ${P}Fragment`);
1578
- if (h.used.has("attr")) core.push(`registerDisposer as ${P}registerDisposer`);
1736
+ if (h.used.has("child")) core.push(`Fragment as ${P}Fragment`, `dispose as ${P}dispose`);
1737
+ if (h.used.has("attr") || h.used.has("child")) core.push(`registerDisposer as ${P}registerDisposer`);
1738
+ if (h.used.has("run")) core.push(`__renderParsedTemplate as ${P}run`);
1579
1739
  const lines = [];
1580
1740
  if (core.length > 0) lines.push(`import { ${core.join(", ")} } from "sibujs";`);
1581
1741
  if (h.used.has("attr")) lines.push(`import { bindAttrs as ${P}bindAttrs } from "sibujs/ui";`);
1582
1742
  return lines.join("\n");
1583
1743
  }
1744
+ function collectTags(children, h) {
1745
+ for (const child of children) {
1746
+ if (child.t !== 0) continue;
1747
+ h.tags.add(child.el.tag);
1748
+ if (child.el.svg) h.svg = true;
1749
+ collectTags(child.el.children, h);
1750
+ }
1751
+ }
1584
1752
  function planHtmlTemplates(code2) {
1585
1753
  const plan = {
1586
1754
  edits: [],
@@ -1608,24 +1776,44 @@ function planHtmlTemplates(code2) {
1608
1776
  const strings = [];
1609
1777
  for (const qs of tmpl.quasis) {
1610
1778
  const cooked = cookEscapes(code2.slice(qs.start, qs.end));
1611
- if (cooked === null) return null;
1779
+ if (cooked === null) {
1780
+ if (tmpl.quasis.length === 1) return null;
1781
+ strings.push("undefined");
1782
+ continue;
1783
+ }
1612
1784
  strings.push(cooked);
1613
1785
  }
1786
+ const name = `${h.prefix}t${functions.length}`;
1787
+ const params = tmpl.exprs.map((_, i2) => `v${i2}`).join(", ");
1614
1788
  let roots;
1615
1789
  try {
1616
1790
  roots = parseTemplate(strings);
1617
1791
  } catch (err) {
1618
- if (err instanceof RuntimeParseError) return null;
1792
+ if (err instanceof RuntimeParseError) {
1793
+ const message = `html: dynamic \${...} expressions are not allowed inside <${err.message}> (raw-text context). Build the content separately and append it as a Node.`;
1794
+ functions.push(`function ${name}(${params}) {
1795
+ throw new Error(${jsStringLiteral(message)});
1796
+ }`);
1797
+ return name;
1798
+ }
1619
1799
  throw err;
1620
1800
  }
1621
1801
  const scratch = { prefix: h.prefix, used: /* @__PURE__ */ new Set(), tags: /* @__PURE__ */ new Set(), svg: false };
1622
1802
  const body2 = generateBody(roots, scratch);
1623
- if (body2 === null) return null;
1803
+ if (body2 === null) {
1804
+ const tree = JSON.stringify(roots).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1805
+ h.used.add("run");
1806
+ collectTags(roots, h);
1807
+ functions.push(
1808
+ `function ${name}(${params}) {
1809
+ return ${h.prefix}run(${name}.tree || (${name}.tree = ${tree}), [${params}]);
1810
+ }`
1811
+ );
1812
+ return name;
1813
+ }
1624
1814
  for (const u2 of scratch.used) h.used.add(u2);
1625
1815
  for (const t of scratch.tags) h.tags.add(t);
1626
1816
  if (scratch.svg) h.svg = true;
1627
- const name = `${h.prefix}t${functions.length}`;
1628
- const params = tmpl.exprs.map((_, i2) => `v${i2}`).join(", ");
1629
1817
  functions.push(`function ${name}(${params}) {
1630
1818
  ${body2}
1631
1819
  }`);
@@ -1903,6 +2091,11 @@ function sibuWebpackPlugin(options = {}) {
1903
2091
  return {
1904
2092
  name: "SibuWebpackPlugin",
1905
2093
  apply(compiler) {
2094
+ if (options.pureAnnotations === true) {
2095
+ console.warn(
2096
+ '[SibuWebpackPlugin] `pureAnnotations` is not applied by the plugin: webpack needs a loader file. Create sibu-pure-loader.cjs containing `module.exports = require("sibujs/build").createPureAnnotationsLoader();` and add a module rule `{ test: /\\.[cm]?[jt]sx?$/, exclude: /node_modules/, use: "./sibu-pure-loader.cjs" }`.'
2097
+ );
2098
+ }
1906
2099
  compiler.hooks?.compilation?.tap("SibuWebpackPlugin", (compilation) => {
1907
2100
  const comp = compilation;
1908
2101
  if (comp.hooks?.optimizeModules) {
@@ -2079,8 +2272,9 @@ __export(index_exports, {
2079
2272
  Loading: () => Loading,
2080
2273
  MAX_DRAIN_TEARDOWNS: () => MAX_DRAIN_TEARDOWNS,
2081
2274
  Portal: () => Portal,
2082
- SVG_NS: () => SVG_NS3,
2275
+ SVG_NS: () => SVG_NS4,
2083
2276
  Suspense: () => Suspense,
2277
+ __renderParsedTemplate: () => __renderParsedTemplate,
2084
2278
  __resetIdCounter: () => __resetIdCounter,
2085
2279
  a: () => a,
2086
2280
  abbr: () => abbr,
@@ -2552,26 +2746,227 @@ function checkLeaks(warnThreshold = 0) {
2552
2746
  return activeBindingCount;
2553
2747
  }
2554
2748
 
2555
- // src/utils/setSafeAttribute.ts
2749
+ // src/utils/metaRefresh.ts
2750
+ function isAsciiWhitespace(ch) {
2751
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
2752
+ }
2753
+ function trimAsciiWhitespace(value) {
2754
+ let start = 0;
2755
+ let end = value.length;
2756
+ while (start < end && isAsciiWhitespace(value[start])) start++;
2757
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
2758
+ return value.slice(start, end);
2759
+ }
2760
+ var forbidden = (reason) => ({ kind: "forbidden", reason });
2761
+ function parseMetaRefreshContent(content) {
2762
+ const raw = trimAsciiWhitespace(stripControlChars(content));
2763
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
2764
+ let i2 = 0;
2765
+ while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
2766
+ if (i2 === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
2767
+ const delay = Number.parseInt(raw.slice(0, i2), 10);
2768
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
2769
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
2770
+ if (i2 === raw.length) return { kind: "delay-only", delay };
2771
+ if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
2772
+ i2++;
2773
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
2774
+ const keyStart = i2;
2775
+ while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
2776
+ const key = raw.slice(keyStart, i2).toLowerCase();
2777
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
2778
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
2779
+ if (raw[i2] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
2780
+ i2++;
2781
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
2782
+ let destination;
2783
+ const quote2 = raw[i2];
2784
+ if (quote2 === '"' || quote2 === "'") {
2785
+ const close = raw.indexOf(quote2, i2 + 1);
2786
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
2787
+ destination = raw.slice(i2 + 1, close);
2788
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
2789
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
2790
+ } else {
2791
+ destination = trimAsciiWhitespace(raw.slice(i2));
2792
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
2793
+ }
2794
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
2795
+ const sanitizedDestination = sanitizeUrl(destination);
2796
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
2797
+ return { kind: "allowed", delay, destination, sanitizedDestination };
2798
+ }
2799
+ function resolveMetaRefreshPolicy(attributes) {
2800
+ let httpEquiv;
2801
+ let content;
2802
+ let duplicateName;
2803
+ const seen = /* @__PURE__ */ new Set();
2804
+ for (const [key, value] of attributes) {
2805
+ const canonical = canonicalAttrName(key);
2806
+ if (seen.has(canonical)) duplicateName = canonical;
2807
+ seen.add(canonical);
2808
+ if (canonical === "http-equiv") httpEquiv = value;
2809
+ else if (canonical === "content") content = value;
2810
+ }
2811
+ if (duplicateName !== void 0) {
2812
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
2813
+ }
2814
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
2815
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
2816
+ return { kind: "not-refresh" };
2817
+ }
2818
+ if (typeof content !== "string") return { kind: "not-refresh" };
2819
+ return parseMetaRefreshContent(content);
2820
+ }
2821
+
2822
+ // src/utils/elementPolicy.ts
2556
2823
  var HTML_NS = "http://www.w3.org/1999/xhtml";
2557
2824
  var SVG_NS2 = "http://www.w3.org/2000/svg";
2825
+ var BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
2826
+ function canonicalTagName(tag) {
2827
+ return canonicalAttrName(tag);
2828
+ }
2829
+ function isBlockedElement(tag) {
2830
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
2831
+ }
2832
+ var CODE_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
2833
+ function isCodeTextElement(tag) {
2834
+ return CODE_TEXT_ELEMENTS.has(canonicalTagName(tag));
2835
+ }
2836
+ var ContextualRefusal = {
2837
+ None: 0,
2838
+ /** The element would carry a refresh directive the shared policy forbids. */
2839
+ MetaRefreshForbidden: 1,
2840
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
2841
+ MetaRefreshReactive: 2,
2842
+ /** A runtime value may not choose which program a `<script>` runs. */
2843
+ ScriptSource: 3,
2844
+ /** An SVG animation may not target a URL, handler or document attribute. */
2845
+ AnimationTarget: 4,
2846
+ /** A runtime-written `href` may not become an applied stylesheet. */
2847
+ StylesheetSource: 5
2848
+ };
2849
+ var SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
2850
+ var SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
2851
+ var reactiveMetaElements = /* @__PURE__ */ new WeakSet();
2852
+ function isHtmlNamespace(el) {
2853
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
2854
+ }
2855
+ function metaSnapshot(el, canonical, value) {
2856
+ const snapshot = /* @__PURE__ */ new Map();
2857
+ const attrs = el.attributes;
2858
+ for (let i2 = 0; i2 < attrs.length; i2++) {
2859
+ const attr = attrs[i2];
2860
+ if (attr.name === canonical) continue;
2861
+ snapshot.set(attr.name, attr.value);
2862
+ }
2863
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
2864
+ return snapshot;
2865
+ }
2866
+ function claimReactiveMeta(el) {
2867
+ if (reactiveMetaElements.has(el)) return;
2868
+ reactiveMetaElements.add(el);
2869
+ if (el.getAttribute("content") === null) return;
2870
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
2871
+ el.removeAttribute("content");
2872
+ }
2873
+ }
2874
+ function metaRefusal(el, name, value, origin) {
2875
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
2876
+ const canonical = canonicalAttrName(name);
2877
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
2878
+ if (origin === "reactive") claimReactiveMeta(el);
2879
+ if (value === null) return ContextualRefusal.None;
2880
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
2881
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
2882
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
2883
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
2884
+ return ContextualRefusal.None;
2885
+ }
2886
+ function animationRefusal(name, value) {
2887
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
2888
+ const target = stripControlChars(value);
2889
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
2890
+ }
2891
+ var runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
2892
+ function relHasStylesheet(rel) {
2893
+ if (rel === null) return false;
2894
+ const lower = canonicalAttrName(rel);
2895
+ if (!lower.includes("stylesheet")) return false;
2896
+ const tokens = lower.split(/[\t\n\f\r ]+/);
2897
+ for (let i2 = 0; i2 < tokens.length; i2++) if (tokens[i2] === "stylesheet") return true;
2898
+ return false;
2899
+ }
2900
+ function stylesheetRefusal(el, name, value, origin) {
2901
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
2902
+ const canonical = canonicalAttrName(name);
2903
+ if (canonical === "href") {
2904
+ if (origin === "static" || value === null) return ContextualRefusal.None;
2905
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
2906
+ runtimeHrefLinks.add(el);
2907
+ return ContextualRefusal.None;
2908
+ }
2909
+ if (canonical === "rel") {
2910
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
2911
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
2912
+ }
2913
+ return ContextualRefusal.None;
2914
+ }
2915
+ function contextualAttributeRefusal(el, name, value, origin) {
2916
+ const local = el.localName;
2917
+ if (local === "meta") return metaRefusal(el, name, value, origin);
2918
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
2919
+ if (local === "script") {
2920
+ if (origin === "static" || value === null) return ContextualRefusal.None;
2921
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
2922
+ }
2923
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS2) return animationRefusal(name, value);
2924
+ return ContextualRefusal.None;
2925
+ }
2926
+ function settleAttributeProvenance(el, name, origin) {
2927
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
2928
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
2929
+ else runtimeHrefLinks.delete(el);
2930
+ }
2931
+
2932
+ // src/utils/setSafeAttribute.ts
2933
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
2934
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
2558
2935
  var XLINK_NS = "http://www.w3.org/1999/xlink";
2559
2936
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
2560
2937
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
2561
2938
  function isHtmlElement(el) {
2562
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
2939
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
2940
+ }
2941
+ function warnContextualRefusal(label2, name, refusal) {
2942
+ devWarnLazy(() => {
2943
+ const reasons = [
2944
+ "",
2945
+ '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)',
2946
+ "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",
2947
+ "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",
2948
+ "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",
2949
+ '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'
2950
+ ];
2951
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
2952
+ });
2563
2953
  }
2564
2954
  function setProp(el, key, val) {
2565
2955
  el[key] = val;
2566
2956
  }
2567
2957
  function namespaceFor(el, name) {
2568
- if (el.namespaceURI !== SVG_NS2) return null;
2958
+ if (el.namespaceURI !== SVG_NS3) return null;
2569
2959
  const lower = name.toLowerCase();
2570
2960
  if (lower.startsWith("xlink:")) return XLINK_NS;
2571
2961
  if (lower.startsWith("xml:")) return XML_NS;
2572
2962
  return null;
2573
2963
  }
2574
2964
  function setSafeAttribute(el, name, value, options = {}) {
2965
+ const written = commitSafeAttribute(el, name, value, options);
2966
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
2967
+ return written;
2968
+ }
2969
+ function commitSafeAttribute(el, name, value, options) {
2575
2970
  const ns = namespaceFor(el, name);
2576
2971
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
2577
2972
  if (isEventHandlerAttr(name)) {
@@ -2584,6 +2979,14 @@ function setSafeAttribute(el, name, value, options = {}) {
2584
2979
  else el.removeAttribute(name);
2585
2980
  return false;
2586
2981
  }
2982
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
2983
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
2984
+ if (refusal !== ContextualRefusal.None) {
2985
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
2986
+ if (ns) el.removeAttributeNS(ns, localName);
2987
+ else el.removeAttribute(name);
2988
+ return false;
2989
+ }
2587
2990
  if (value == null) {
2588
2991
  if (ns) el.removeAttributeNS(ns, localName);
2589
2992
  else el.removeAttribute(name);
@@ -2627,13 +3030,32 @@ function setSafeAttribute(el, name, value, options = {}) {
2627
3030
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
2628
3031
  return true;
2629
3032
  }
2630
- const safe = sanitizeAttributeString(name, str, { element: el });
3033
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
3034
+ if (safe === null) {
3035
+ if (ns) el.removeAttributeNS(ns, localName);
3036
+ else el.removeAttribute(name);
3037
+ return true;
3038
+ }
2631
3039
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
2632
3040
  if (current === safe) return true;
2633
3041
  if (ns) el.setAttributeNS(ns, name, safe);
2634
3042
  else el.setAttribute(name, safe);
2635
3043
  return true;
2636
3044
  }
3045
+ function setTrustedAttribute(el, name, value) {
3046
+ if (isHtmlContentAttribute(name)) {
3047
+ if (DEV) devWarn(`html: refusing static attribute "${name}" \u2014 the browser parses it as a nested document.`);
3048
+ return false;
3049
+ }
3050
+ const refusal = contextualAttributeRefusal(el, name, value, "static");
3051
+ if (refusal !== ContextualRefusal.None) {
3052
+ warnContextualRefusal("html (static attribute)", name, refusal);
3053
+ return false;
3054
+ }
3055
+ el.setAttribute(name, value);
3056
+ if (el.localName === "link") settleAttributeProvenance(el, name, "static");
3057
+ return true;
3058
+ }
2637
3059
 
2638
3060
  // src/reactivity/track-core.ts
2639
3061
  var POOL_MAX = 4096;
@@ -3126,7 +3548,7 @@ function forEachSubscriber(signal2, visit) {
3126
3548
  }
3127
3549
 
3128
3550
  // src/reactivity/track.ts
3129
- var _runtimeVersion = true ? "4.7.0" : "dev";
3551
+ var _runtimeVersion = true ? "4.8.0" : "dev";
3130
3552
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
3131
3553
  function resolveReactiveApi() {
3132
3554
  const g2 = globalThis;
@@ -3197,7 +3619,7 @@ function bindAttribute(el, attr, getter) {
3197
3619
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
3198
3620
  return;
3199
3621
  }
3200
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
3622
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
3201
3623
  }
3202
3624
  return reactiveBinding2(commit, el);
3203
3625
  }
@@ -3222,7 +3644,7 @@ function bindDynamic(el, nameGetter, valueGetter) {
3222
3644
  el.removeAttribute(prevName);
3223
3645
  prevName = null;
3224
3646
  }
3225
- if (!setSafeAttribute(el, name, value, { label: "bindDynamic" })) return;
3647
+ if (!setSafeAttribute(el, name, value, { label: "bindDynamic", reactive: true })) return;
3226
3648
  prevName = name;
3227
3649
  }
3228
3650
  const teardown = reactiveBinding2(commit, el);
@@ -3403,11 +3825,7 @@ function bindChildNode(placeholder, getter) {
3403
3825
  }
3404
3826
 
3405
3827
  // src/core/rendering/tagFactory.ts
3406
- var SVG_NS3 = "http://www.w3.org/2000/svg";
3407
- var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
3408
- function isBlockedTag(tag) {
3409
- return BLOCKED_TAGS.has(tag.toLowerCase());
3410
- }
3828
+ var SVG_NS4 = "http://www.w3.org/2000/svg";
3411
3829
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
3412
3830
  "config",
3413
3831
  "location",
@@ -3452,11 +3870,12 @@ function applyStyle(el, style2) {
3452
3870
  }
3453
3871
  const next = [];
3454
3872
  for (const prop in value) {
3873
+ if (!Object.hasOwn(value, prop)) continue;
3455
3874
  const val = value[prop];
3456
3875
  if (val == null) continue;
3457
3876
  const name = toKebab(prop);
3458
3877
  next.push(name);
3459
- decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
3878
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
3460
3879
  }
3461
3880
  for (let i2 = 0; i2 < written.length; i2++) {
3462
3881
  if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
@@ -3472,16 +3891,17 @@ function applyStyle(el, style2) {
3472
3891
  }
3473
3892
  const htmlEl = el;
3474
3893
  for (const prop in style2) {
3894
+ if (!Object.hasOwn(style2, prop)) continue;
3475
3895
  const val = style2[prop];
3476
3896
  const name = toKebab(prop);
3477
3897
  if (typeof val === "function") {
3478
3898
  const getter = val;
3479
3899
  const teardown = reactiveBinding2(() => {
3480
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
3900
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
3481
3901
  }, el);
3482
3902
  registerDisposer(el, teardown);
3483
3903
  } else {
3484
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
3904
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
3485
3905
  }
3486
3906
  }
3487
3907
  }
@@ -3503,6 +3923,7 @@ function applyClass(el, cls) {
3503
3923
  let hasReactive = false;
3504
3924
  let result = "";
3505
3925
  for (const name in obj) {
3926
+ if (!Object.hasOwn(obj, name)) continue;
3506
3927
  const val = obj[name];
3507
3928
  if (typeof val === "function") {
3508
3929
  hasReactive = true;
@@ -3514,6 +3935,7 @@ function applyClass(el, cls) {
3514
3935
  const update = () => {
3515
3936
  let r = "";
3516
3937
  for (const name in obj) {
3938
+ if (!Object.hasOwn(obj, name)) continue;
3517
3939
  const val = obj[name];
3518
3940
  const active = typeof val === "function" ? val() : val;
3519
3941
  if (active) r = r ? `${r} ${name}` : name;
@@ -3577,7 +3999,7 @@ function appendChildren(el, nodes) {
3577
3999
  }
3578
4000
  }
3579
4001
  function tagFactory(tag, ns) {
3580
- const blocked = isBlockedTag(tag);
4002
+ const blocked = isBlockedElement(tag);
3581
4003
  return (first, second) => {
3582
4004
  if (blocked) {
3583
4005
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -3640,6 +4062,7 @@ function tagFactory(tag, ns) {
3640
4062
  const pOn = props.on;
3641
4063
  if (pOn) {
3642
4064
  for (const ev in pOn) {
4065
+ if (!Object.hasOwn(pOn, ev)) continue;
3643
4066
  const handler = pOn[ev];
3644
4067
  if (typeof handler === "function") {
3645
4068
  el.addEventListener(ev, handler);
@@ -3666,6 +4089,7 @@ function tagFactory(tag, ns) {
3666
4089
  continue;
3667
4090
  // already handled above / below
3668
4091
  default: {
4092
+ if (!Object.hasOwn(props, key)) continue;
3669
4093
  const value = props[key];
3670
4094
  if (value == null) continue;
3671
4095
  if (isEventHandlerAttr(key)) continue;
@@ -3755,7 +4179,7 @@ var param = tagFactory("param");
3755
4179
  var picture = tagFactory("picture");
3756
4180
  var portal = tagFactory("portal");
3757
4181
  var source = tagFactory("source");
3758
- var svg = tagFactory("svg", SVG_NS3);
4182
+ var svg = tagFactory("svg", SVG_NS4);
3759
4183
  var math = tagFactory("math");
3760
4184
  var canvas = tagFactory("canvas");
3761
4185
  var noscript = tagFactory("noscript");
@@ -3796,26 +4220,26 @@ var base = tagFactory("base");
3796
4220
  var link = tagFactory("link");
3797
4221
  var meta = tagFactory("meta");
3798
4222
  var style = tagFactory("style");
3799
- var circle = tagFactory("circle", SVG_NS3);
3800
- var ellipse = tagFactory("ellipse", SVG_NS3);
3801
- var g = tagFactory("g", SVG_NS3);
3802
- var line = tagFactory("line", SVG_NS3);
3803
- var path = tagFactory("path", SVG_NS3);
3804
- var polygon = tagFactory("polygon", SVG_NS3);
3805
- var polyline = tagFactory("polyline", SVG_NS3);
3806
- var rect = tagFactory("rect", SVG_NS3);
3807
- var text = tagFactory("text", SVG_NS3);
3808
- var tspan = tagFactory("tspan", SVG_NS3);
3809
- var defs = tagFactory("defs", SVG_NS3);
3810
- var clipPath = tagFactory("clipPath", SVG_NS3);
3811
- var mask = tagFactory("mask", SVG_NS3);
3812
- var pattern = tagFactory("pattern", SVG_NS3);
3813
- var linearGradient = tagFactory("linearGradient", SVG_NS3);
3814
- var radialGradient = tagFactory("radialGradient", SVG_NS3);
3815
- var stop = tagFactory("stop", SVG_NS3);
3816
- var use = tagFactory("use", SVG_NS3);
3817
- var symbol = tagFactory("symbol", SVG_NS3);
3818
- var marker = tagFactory("marker", SVG_NS3);
4223
+ var circle = tagFactory("circle", SVG_NS4);
4224
+ var ellipse = tagFactory("ellipse", SVG_NS4);
4225
+ var g = tagFactory("g", SVG_NS4);
4226
+ var line = tagFactory("line", SVG_NS4);
4227
+ var path = tagFactory("path", SVG_NS4);
4228
+ var polygon = tagFactory("polygon", SVG_NS4);
4229
+ var polyline = tagFactory("polyline", SVG_NS4);
4230
+ var rect = tagFactory("rect", SVG_NS4);
4231
+ var text = tagFactory("text", SVG_NS4);
4232
+ var tspan = tagFactory("tspan", SVG_NS4);
4233
+ var defs = tagFactory("defs", SVG_NS4);
4234
+ var clipPath = tagFactory("clipPath", SVG_NS4);
4235
+ var mask = tagFactory("mask", SVG_NS4);
4236
+ var pattern = tagFactory("pattern", SVG_NS4);
4237
+ var linearGradient = tagFactory("linearGradient", SVG_NS4);
4238
+ var radialGradient = tagFactory("radialGradient", SVG_NS4);
4239
+ var stop = tagFactory("stop", SVG_NS4);
4240
+ var use = tagFactory("use", SVG_NS4);
4241
+ var symbol = tagFactory("symbol", SVG_NS4);
4242
+ var marker = tagFactory("marker", SVG_NS4);
3819
4243
  var center = tagFactory("center");
3820
4244
  var font = tagFactory("font");
3821
4245
  var marquee = tagFactory("marquee");
@@ -5629,6 +6053,10 @@ function attachBranch(parent, anchor, branch, attachedOnce, where) {
5629
6053
  parent.insertBefore(node, anchor.nextSibling);
5630
6054
  return { node, owned };
5631
6055
  }
6056
+ function releaseBranchNode(node, owned) {
6057
+ if (owned) dispose(node);
6058
+ node.parentNode?.removeChild(node);
6059
+ }
5632
6060
  function when(condition, thenBranch, elseBranch) {
5633
6061
  const anchor = document.createComment("when");
5634
6062
  let currentNode = null;
@@ -5646,8 +6074,7 @@ function when(condition, thenBranch, elseBranch) {
5646
6074
  lastCondition = show2;
5647
6075
  const focused = currentNode ? captureFocusWithin([currentNode]) : null;
5648
6076
  if (currentNode?.parentNode) {
5649
- if (currentNodeOwned) dispose(currentNode);
5650
- currentNode.parentNode.removeChild(currentNode);
6077
+ releaseBranchNode(currentNode, currentNodeOwned);
5651
6078
  currentNode = null;
5652
6079
  }
5653
6080
  const branch = show2 ? thenBranch : elseBranch !== void 0 ? elseBranch : null;
@@ -5661,6 +6088,8 @@ function when(condition, thenBranch, elseBranch) {
5661
6088
  registerDisposer(anchor, () => {
5662
6089
  disposed = true;
5663
6090
  stopBinding();
6091
+ if (currentNode) releaseBranchNode(currentNode, currentNodeOwned);
6092
+ currentNode = null;
5664
6093
  });
5665
6094
  if (!initialized) {
5666
6095
  queueMicrotask(() => {
@@ -5686,8 +6115,7 @@ function match(value, cases, fallback) {
5686
6115
  lastKey = key;
5687
6116
  const focused = currentNode ? captureFocusWithin([currentNode]) : null;
5688
6117
  if (currentNode?.parentNode) {
5689
- if (currentNodeOwned) dispose(currentNode);
5690
- currentNode.parentNode.removeChild(currentNode);
6118
+ releaseBranchNode(currentNode, currentNodeOwned);
5691
6119
  currentNode = null;
5692
6120
  }
5693
6121
  const branch = Object.hasOwn(cases, key) ? cases[key] : fallback;
@@ -5701,6 +6129,8 @@ function match(value, cases, fallback) {
5701
6129
  registerDisposer(anchor, () => {
5702
6130
  disposed = true;
5703
6131
  stopBinding();
6132
+ if (currentNode) releaseBranchNode(currentNode, currentNodeOwned);
6133
+ currentNode = null;
5704
6134
  });
5705
6135
  if (!initialized) {
5706
6136
  queueMicrotask(() => {
@@ -6043,7 +6473,6 @@ function appendChild(frag, child) {
6043
6473
  }
6044
6474
 
6045
6475
  // src/core/rendering/htm.ts
6046
- var RAW_TEXT_TAGS2 = /* @__PURE__ */ new Set(["script", "style"]);
6047
6476
  var VOID_ELEMENTS3 = /* @__PURE__ */ new Set([
6048
6477
  "area",
6049
6478
  "base",
@@ -6266,7 +6695,7 @@ function parseTemplate2(strings) {
6266
6695
  children.push({ t: 0, el: { tag, svg: SVG_TAGS2.has(tag), attrs, children: [] } });
6267
6696
  } else {
6268
6697
  const inner = parseChildren();
6269
- if (RAW_TEXT_TAGS2.has(tag.toLowerCase())) {
6698
+ if (isCodeTextElement(tag)) {
6270
6699
  for (let i2 = 0; i2 < inner.length; i2++) {
6271
6700
  if (inner[i2].t === 2) {
6272
6701
  throw new Error(
@@ -6292,12 +6721,12 @@ function parseTemplate2(strings) {
6292
6721
  return parseChildren();
6293
6722
  }
6294
6723
  function executeElement(tmpl, values) {
6295
- const el = tmpl.svg ? document.createElementNS(SVG_NS3, tmpl.tag) : document.createElement(tmpl.tag);
6724
+ const el = tmpl.svg ? document.createElementNS(SVG_NS4, tmpl.tag) : document.createElement(tmpl.tag);
6296
6725
  for (let i2 = 0; i2 < tmpl.attrs.length; i2++) {
6297
6726
  const attr = tmpl.attrs[i2];
6298
6727
  switch (attr.t) {
6299
6728
  case 0:
6300
- el.setAttribute(attr.name, attr.value);
6729
+ setTrustedAttribute(el, attr.name, attr.value);
6301
6730
  break;
6302
6731
  case 1: {
6303
6732
  const name = attr.name;
@@ -6330,7 +6759,7 @@ function executeElement(tmpl, values) {
6330
6759
  break;
6331
6760
  }
6332
6761
  case 4:
6333
- el.setAttribute(attr.name, "");
6762
+ setTrustedAttribute(el, attr.name, "");
6334
6763
  break;
6335
6764
  }
6336
6765
  }
@@ -6375,6 +6804,12 @@ function html2(strings, ...values) {
6375
6804
  tmpl = parseTemplate2(strings);
6376
6805
  cache.set(strings, tmpl);
6377
6806
  }
6807
+ return renderTemplate(tmpl, values);
6808
+ }
6809
+ function __renderParsedTemplate(tree, values) {
6810
+ return renderTemplate(tree, values);
6811
+ }
6812
+ function renderTemplate(tmpl, values) {
6378
6813
  if (tmpl.length === 1 && tmpl[0].t === 0) {
6379
6814
  return executeElement(tmpl[0].el, values);
6380
6815
  }
@@ -7463,7 +7898,7 @@ function enhance(target, setup) {
7463
7898
  bindNode(el, () => {
7464
7899
  const v = value();
7465
7900
  const next = v == null ? null : String(v);
7466
- setSafeAttribute(el, name, next, { syncValueProperty: false, label: "enhance attr()" });
7901
+ setSafeAttribute(el, name, next, { syncValueProperty: false, label: "enhance attr()", reactive: true });
7467
7902
  })
7468
7903
  );
7469
7904
  });