sibujs 4.6.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +62 -8
  3. package/dist/browser.d.cts +23 -0
  4. package/dist/browser.d.ts +23 -0
  5. package/dist/browser.js +6 -5
  6. package/dist/build.cjs +2388 -1004
  7. package/dist/build.d.cts +147 -44
  8. package/dist/build.d.ts +147 -44
  9. package/dist/build.js +1410 -564
  10. package/dist/cdn.dev.global.js +14 -12
  11. package/dist/cdn.full.dev.global.js +15 -13
  12. package/dist/cdn.full.global.js +14 -12
  13. package/dist/cdn.global.js +12 -10
  14. package/dist/chunk-3ACAAQIN.js +224 -0
  15. package/dist/{chunk-7NCARGJW.js → chunk-44MTL7ED.js} +6 -6
  16. package/dist/{chunk-BAAG6ZTI.js → chunk-4WMSTZ7J.js} +8 -5
  17. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  18. package/dist/{chunk-DKGBBKOF.js → chunk-C5PEWEGH.js} +20 -5
  19. package/dist/{chunk-3ISI6ACU.js → chunk-C7AA2CZX.js} +1 -1
  20. package/dist/{chunk-UZQ6ALFS.js → chunk-CZPRSWSS.js} +1 -1
  21. package/dist/{chunk-D33YTSX5.js → chunk-DSID757X.js} +5 -4
  22. package/dist/{chunk-OXUY2A6L.js → chunk-EGUQLBHE.js} +22 -8
  23. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  24. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  25. package/dist/{chunk-HYXDKS4N.js → chunk-GSGNX35Q.js} +5 -5
  26. package/dist/{chunk-7GQCWFOE.js → chunk-J3YQ457H.js} +171 -85
  27. package/dist/{chunk-ORMZXBKQ.js → chunk-JUTFX2RM.js} +18 -10
  28. package/dist/{chunk-QE4TTDU3.js → chunk-KJ3UKABJ.js} +12 -11
  29. package/dist/{chunk-XRRZKZYX.js → chunk-LI4W3U6C.js} +1 -1
  30. package/dist/{chunk-47M47FOM.js → chunk-O6TRVMJI.js} +58 -6
  31. package/dist/{chunk-JSXPZCET.js → chunk-Q5J2LVKH.js} +1 -1
  32. package/dist/{chunk-5MT6SJ3P.js → chunk-QGMSESDD.js} +10 -5
  33. package/dist/{chunk-AIF3Z2T7.js → chunk-RGLPEBDE.js} +13 -10
  34. package/dist/{chunk-5Q4R7HCL.js → chunk-TQQUN4WR.js} +11 -2
  35. package/dist/{chunk-PK6FK2G2.js → chunk-UCNP3HVM.js} +183 -30
  36. package/dist/{chunk-XC4MEKGA.js → chunk-V3LHZSU5.js} +8 -2
  37. package/dist/{chunk-VVWPJ543.js → chunk-VWT3VXPL.js} +55 -20
  38. package/dist/{chunk-SXXVZMKZ.js → chunk-ZVLHNEEL.js} +17 -35
  39. package/dist/{contracts-DRIuclVT.d.cts → contracts-CLqzJnOV.d.ts} +28 -17
  40. package/dist/{contracts-DRIuclVT.d.ts → contracts-CTOJXu-x.d.cts} +28 -17
  41. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  42. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  43. package/dist/data.cjs +205 -39
  44. package/dist/data.d.cts +119 -3
  45. package/dist/data.d.ts +119 -3
  46. package/dist/data.js +6 -6
  47. package/dist/devtools.cjs +18 -5
  48. package/dist/devtools.js +5 -5
  49. package/dist/ecosystem.cjs +462 -58
  50. package/dist/ecosystem.d.cts +2 -1
  51. package/dist/ecosystem.d.ts +2 -1
  52. package/dist/ecosystem.js +9 -8
  53. package/dist/extras.cjs +852 -296
  54. package/dist/extras.d.cts +5 -4
  55. package/dist/extras.d.ts +5 -4
  56. package/dist/extras.js +24 -23
  57. package/dist/index.cjs +721 -176
  58. package/dist/index.d.cts +220 -169
  59. package/dist/index.d.ts +220 -169
  60. package/dist/index.js +16 -12
  61. package/dist/motion.cjs +12 -4
  62. package/dist/motion.js +3 -3
  63. package/dist/patterns.cjs +37 -21
  64. package/dist/patterns.d.cts +2 -1
  65. package/dist/patterns.d.ts +2 -1
  66. package/dist/patterns.js +6 -6
  67. package/dist/performance.cjs +48 -6
  68. package/dist/performance.d.cts +12 -2
  69. package/dist/performance.d.ts +12 -2
  70. package/dist/performance.js +8 -6
  71. package/dist/plugins.cjs +672 -261
  72. package/dist/plugins.d.cts +131 -13
  73. package/dist/plugins.d.ts +131 -13
  74. package/dist/plugins.js +83 -47
  75. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  76. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.cts} +6 -0
  77. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.ts} +6 -0
  78. package/dist/ssr.cjs +509 -142
  79. package/dist/ssr.d.cts +55 -5
  80. package/dist/ssr.d.ts +55 -5
  81. package/dist/ssr.js +11 -9
  82. package/dist/{tagFactory-DFstCLQV.d.cts → tagFactory-8qL9LCIx.d.cts} +58 -19
  83. package/dist/{tagFactory-DkaNVUNV.d.ts → tagFactory-BL2fymez.d.ts} +58 -19
  84. package/dist/testing.cjs +17 -4
  85. package/dist/testing.js +4 -4
  86. package/dist/types-CJFViL6Q.d.cts +26 -0
  87. package/dist/types-CJFViL6Q.d.ts +26 -0
  88. package/dist/ui.cjs +635 -254
  89. package/dist/ui.d.cts +3 -2
  90. package/dist/ui.d.ts +3 -2
  91. package/dist/ui.js +9 -8
  92. package/dist/widgets.cjs +22 -14
  93. package/dist/widgets.js +7 -7
  94. package/package.json +8 -5
@@ -716,7 +716,7 @@ function forEachSubscriber(signal2, visit) {
716
716
  }
717
717
 
718
718
  // src/reactivity/track.ts
719
- var _runtimeVersion = true ? "4.6.0" : "dev";
719
+ var _runtimeVersion = true ? "4.8.0" : "dev";
720
720
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
721
721
  function resolveReactiveApi() {
722
722
  const g = globalThis;
@@ -772,6 +772,14 @@ var getSubscriberCount2 = API.getSubscriberCount;
772
772
  var getSubscriberDeps2 = API.getSubscriberDeps;
773
773
  var forEachSubscriber2 = API.forEachSubscriber;
774
774
 
775
+ // src/core/devtoolsHook.ts
776
+ function emitDevtools(hook, event, payload) {
777
+ try {
778
+ hook.emit(event, payload);
779
+ } catch {
780
+ }
781
+ }
782
+
775
783
  // src/core/ssr-context.ts
776
784
  var SSR_KEY = /* @__PURE__ */ Symbol.for("sibujs.ssr.v1");
777
785
  function detectSSRShared() {
@@ -926,12 +934,17 @@ function effect(effectFn, options) {
926
934
  try {
927
935
  retrack2(ctx.bodyFn, ctx.subscriber);
928
936
  if (ctx.rerunPending) drainReruns(ctx);
937
+ } catch (err) {
938
+ ctx.disposed = true;
939
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
940
+ cleanup2(ctx.subscriber);
941
+ throw err;
929
942
  } finally {
930
943
  ctx.running = false;
931
944
  ctx.rerunPending = false;
932
945
  }
933
946
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
934
- if (hook) hook.emit("effect:create", { effectFn });
947
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
935
948
  return () => disposeEffect(ctx);
936
949
  }
937
950
 
@@ -1018,7 +1031,7 @@ function signal(initial, options) {
1018
1031
  state2.__v++;
1019
1032
  if (DEV) {
1020
1033
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1021
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1034
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1022
1035
  }
1023
1036
  if (!enqueueBatchedSignal(state2)) {
1024
1037
  notifySubscribers2(state2);
@@ -1032,7 +1045,7 @@ function signal(initial, options) {
1032
1045
  state2.value = newValue;
1033
1046
  state2.__v++;
1034
1047
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1035
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1048
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1036
1049
  if (!enqueueBatchedSignal(state2)) {
1037
1050
  notifySubscribers2(state2);
1038
1051
  }
@@ -1051,7 +1064,7 @@ function signal(initial, options) {
1051
1064
  }
1052
1065
  if (DEV) {
1053
1066
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1054
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1067
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
1055
1068
  }
1056
1069
  return [get, set];
1057
1070
  }
@@ -1334,17 +1347,17 @@ function derived(getter, options) {
1334
1347
  cs._d = false;
1335
1348
  cs._init = true;
1336
1349
  };
1337
- track2(() => {
1338
- let threw = true;
1339
- try {
1350
+ try {
1351
+ track2(() => {
1340
1352
  cs._v = getter();
1341
1353
  cs._d = false;
1342
1354
  cs._init = true;
1343
- threw = false;
1344
- } finally {
1345
- if (threw) cs._d = true;
1346
- }
1347
- }, markDirty);
1355
+ }, markDirty);
1356
+ } catch (err) {
1357
+ disposed = true;
1358
+ cleanup2(markDirty);
1359
+ throw err;
1360
+ }
1348
1361
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1349
1362
  const validate = () => {
1350
1363
  if (!cs._d || disposed || pendingError !== void 0) return;
@@ -1363,7 +1376,7 @@ function derived(getter, options) {
1363
1376
  if (disposed) cleanup2(markDirty);
1364
1377
  }
1365
1378
  if (hook && !disposed && !Object.is(oldValue, cs._v)) {
1366
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
1379
+ emitDevtools(hook, "computed:update", { signal: cs, oldValue, newValue: cs._v });
1367
1380
  }
1368
1381
  };
1369
1382
  cs._validate = validate;
@@ -1414,7 +1427,7 @@ function derived(getter, options) {
1414
1427
  }
1415
1428
  }
1416
1429
  };
1417
- if (hook) hook.emit("computed:create", { signal: cs, name: debugName, getter: computedGetter });
1430
+ if (hook) emitDevtools(hook, "computed:create", { signal: cs, name: debugName, getter: computedGetter });
1418
1431
  return computedGetter;
1419
1432
  }
1420
1433
 
@@ -1471,6 +1484,11 @@ function zustandAdapter(options) {
1471
1484
  function stripControlChars(value) {
1472
1485
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
1473
1486
  }
1487
+ var ASCII_UPPER = /[A-Z]/;
1488
+ var ASCII_UPPER_G = /[A-Z]/g;
1489
+ function canonicalAttrName(name) {
1490
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
1491
+ }
1474
1492
  function isEventHandlerAttr(name) {
1475
1493
  if (name.length < 3) return false;
1476
1494
  const lower = name.toLowerCase();
@@ -1482,6 +1500,9 @@ function sanitizeUrl(url) {
1482
1500
  const probe = stripControlChars(url).trim();
1483
1501
  if (!probe) return "";
1484
1502
  const output = url.replace(OUTER_TRIM, "");
1503
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
1504
+ return "";
1505
+ }
1485
1506
  const lower = probe.toLowerCase();
1486
1507
  let schemeEnd = -1;
1487
1508
  for (let i = 0; i < lower.length; i++) {
@@ -1498,19 +1519,58 @@ function sanitizeUrl(url) {
1498
1519
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1499
1520
  return output;
1500
1521
  }
1501
- var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
1522
+ var SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
1523
+ function isSrcsetWhitespace(ch) {
1524
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
1525
+ }
1502
1526
  function sanitizeSrcset(value) {
1503
- const parts = value.split(",");
1527
+ const input = String(value);
1504
1528
  const out = [];
1505
- for (let i = 0; i < parts.length; i++) {
1506
- const part = parts[i].trim();
1507
- if (!part) continue;
1508
- const m = part.match(/^(\S+)(\s+.+)?$/);
1509
- if (!m) continue;
1510
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
1511
- const safe = sanitizeUrl(m[1]);
1529
+ let i = 0;
1530
+ const n = input.length;
1531
+ while (i < n) {
1532
+ while (i < n && (isSrcsetWhitespace(input[i]) || input[i] === ",")) i++;
1533
+ if (i >= n) break;
1534
+ const urlStart = i;
1535
+ while (i < n && !isSrcsetWhitespace(input[i])) i++;
1536
+ let url = input.slice(urlStart, i);
1537
+ const descriptors = [];
1538
+ if (url.endsWith(",")) {
1539
+ url = url.replace(/,+$/, "");
1540
+ } else {
1541
+ let current = "";
1542
+ let inParens = false;
1543
+ while (i < n) {
1544
+ const ch = input[i];
1545
+ if (inParens) {
1546
+ current += ch;
1547
+ if (ch === ")") inParens = false;
1548
+ i++;
1549
+ continue;
1550
+ }
1551
+ if (isSrcsetWhitespace(ch)) {
1552
+ if (current) descriptors.push(current);
1553
+ current = "";
1554
+ i++;
1555
+ continue;
1556
+ }
1557
+ if (ch === ",") {
1558
+ i++;
1559
+ break;
1560
+ }
1561
+ if (ch === "(") inParens = true;
1562
+ current += ch;
1563
+ i++;
1564
+ }
1565
+ if (current) descriptors.push(current);
1566
+ }
1567
+ if (url === "") continue;
1568
+ if (descriptors.length > 1) continue;
1569
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
1570
+ const safe = sanitizeUrl(url);
1512
1571
  if (!safe) continue;
1513
- out.push(m[2] ? `${safe}${m[2]}` : safe);
1572
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
1573
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
1514
1574
  }
1515
1575
  return out.join(", ");
1516
1576
  }
@@ -1569,8 +1629,16 @@ var BLOCKED_CSS_NEEDLES = [
1569
1629
  "behavior:",
1570
1630
  "@import",
1571
1631
  "image-set(",
1572
- "filter:progid"
1632
+ "filter:progid",
1633
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
1634
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
1635
+ // today, which is exactly when a blocklist should get ahead of it.
1636
+ "image(",
1637
+ "src("
1573
1638
  ];
1639
+ var CSS_REASON_MOZ_BINDING = 4;
1640
+ var CSS_REASON_BEHAVIOR = 5;
1641
+ var CSS_REASON_PROGID = 8;
1574
1642
  function describeElement(el) {
1575
1643
  if (!el) return "an element";
1576
1644
  let out = el.tagName.toLowerCase();
@@ -1594,7 +1662,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
1594
1662
  "behavior: loads and runs a remote HTC script",
1595
1663
  "@import pulls in a remote stylesheet",
1596
1664
  "image-set() fetches remote images, the same exfiltration channel as url()",
1597
- "filter:progid activates legacy scriptable filters"
1665
+ "filter:progid activates legacy scriptable filters",
1666
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
1667
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
1598
1668
  ];
1599
1669
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
1600
1670
  const where = describeElement(ctx?.element);
@@ -1609,27 +1679,110 @@ function warnDroppedDeclaration(value, reason, ctx) {
1609
1679
  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.`;
1610
1680
  });
1611
1681
  }
1682
+ function normalizeCssForScan(value) {
1683
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
1684
+ return normalized.toLowerCase().replace(/\s+/g, "");
1685
+ }
1612
1686
  function blockedCssReason(value) {
1613
1687
  if (!CSS_DANGER_GATE.test(value)) return -1;
1614
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
1615
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
1688
+ const lower = normalizeCssForScan(value);
1616
1689
  for (let i = 0; i < BLOCKED_CSS_NEEDLES.length; i++) {
1617
1690
  if (lower.includes(BLOCKED_CSS_NEEDLES[i])) return i;
1618
1691
  }
1619
1692
  return -1;
1620
1693
  }
1621
- function sanitizeCSSValue(value, context) {
1622
- const reason = blockedCssReason(value);
1694
+ function sanitizeCSSDeclaration(property, value, context) {
1695
+ const reason = blockedDeclarationReason(property, value);
1623
1696
  if (reason === -1) return value;
1624
- warnDroppedDeclaration(value, reason, context);
1697
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
1625
1698
  return "";
1626
1699
  }
1700
+ function blockedDeclarationReason(property, value) {
1701
+ const reason = blockedCssReason(value);
1702
+ if (reason !== -1) return reason;
1703
+ const name = canonicalAttrName(property);
1704
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
1705
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
1706
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
1707
+ return CSS_REASON_PROGID;
1708
+ }
1709
+ return -1;
1710
+ }
1711
+ function parseStyleDeclarations(css) {
1712
+ const chunks = [];
1713
+ let buf = "";
1714
+ let depth = 0;
1715
+ let quote2 = "";
1716
+ for (let i = 0; i < css.length; i++) {
1717
+ const ch = css[i];
1718
+ if (quote2) {
1719
+ buf += ch;
1720
+ if (ch === "\\" && i + 1 < css.length) buf += css[++i];
1721
+ else if (ch === quote2) quote2 = "";
1722
+ continue;
1723
+ }
1724
+ if (ch === "\\") {
1725
+ buf += ch;
1726
+ if (i + 1 < css.length) buf += css[++i];
1727
+ continue;
1728
+ }
1729
+ if (ch === "/" && css[i + 1] === "*") {
1730
+ const end = css.indexOf("*/", i + 2);
1731
+ if (end === -1) return null;
1732
+ i = end + 1;
1733
+ continue;
1734
+ }
1735
+ if (ch === '"' || ch === "'") quote2 = ch;
1736
+ else if (ch === "(" || ch === "[") depth++;
1737
+ else if (ch === ")" || ch === "]") {
1738
+ if (--depth < 0) return null;
1739
+ } else if (ch === "{" || ch === "}") return null;
1740
+ else if (ch === ";" && depth === 0) {
1741
+ chunks.push(buf);
1742
+ buf = "";
1743
+ continue;
1744
+ }
1745
+ buf += ch;
1746
+ }
1747
+ if (quote2 || depth !== 0) return null;
1748
+ chunks.push(buf);
1749
+ const out = [];
1750
+ for (const chunk of chunks) {
1751
+ const colon = chunk.indexOf(":");
1752
+ if (colon === -1) continue;
1753
+ const rawName = chunk.slice(0, colon).trim();
1754
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
1755
+ let value = chunk.slice(colon + 1).trim();
1756
+ const bang = /!\s*important\s*$/i.exec(value);
1757
+ if (bang) value = value.slice(0, bang.index).trim();
1758
+ if (value === "") continue;
1759
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
1760
+ out.push([property, value, bang !== null]);
1761
+ }
1762
+ return out;
1763
+ }
1764
+ function sanitizeStyleDeclarationList(cssText, context) {
1765
+ const input = String(cssText);
1766
+ const declarations = parseStyleDeclarations(input);
1767
+ if (declarations === null) {
1768
+ if (blockedCssReason(input) !== -1) warnDroppedDeclaration(input, blockedCssReason(input), context);
1769
+ return "";
1770
+ }
1771
+ const kept = [];
1772
+ for (const [property, value, important] of declarations) {
1773
+ const reason = blockedDeclarationReason(property, value);
1774
+ if (reason !== -1) {
1775
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
1776
+ continue;
1777
+ }
1778
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
1779
+ }
1780
+ return kept.join("; ");
1781
+ }
1627
1782
  function sanitizeStyleAttribute(cssText, context) {
1628
1783
  const input = String(cssText);
1629
1784
  if (input.trim() === "") return "";
1630
- if (typeof document === "undefined") {
1631
- return sanitizeCSSValue(input, context) === "" ? "" : input;
1632
- }
1785
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input, context);
1633
1786
  const probe = document.createElement("div");
1634
1787
  try {
1635
1788
  probe.style.cssText = input;
@@ -1640,8 +1793,7 @@ function sanitizeStyleAttribute(cssText, context) {
1640
1793
  for (let i = 0; i < probe.style.length; i++) {
1641
1794
  const property = probe.style[i];
1642
1795
  const value = probe.style.getPropertyValue(property);
1643
- let reason = blockedCssReason(value);
1644
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
1796
+ const reason = blockedDeclarationReason(property, value);
1645
1797
  if (reason !== -1) {
1646
1798
  warnDroppedDeclaration(value, reason, { property, element: context?.element });
1647
1799
  continue;
@@ -1673,6 +1825,13 @@ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
1673
1825
  function isHtmlContentAttribute(name) {
1674
1826
  return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
1675
1827
  }
1828
+ function isUrlAttribute(attr) {
1829
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
1830
+ }
1831
+ function isPolicyAttribute(name) {
1832
+ const lower = name.toLowerCase();
1833
+ return lower === "style" || URL_ATTRIBUTES.has(lower);
1834
+ }
1676
1835
  function sanitizeAttributeString(attr, value, context) {
1677
1836
  const lower = attr.toLowerCase();
1678
1837
  if (lower === "srcset") return sanitizeSrcset(value);
@@ -1680,27 +1839,229 @@ function sanitizeAttributeString(attr, value, context) {
1680
1839
  if (lower === "style") return sanitizeStyleAttribute(value, context);
1681
1840
  return value;
1682
1841
  }
1842
+ function resolveAttributeValue(attr, value, context) {
1843
+ if (!isPolicyAttribute(attr)) return value;
1844
+ const safe = sanitizeAttributeString(attr, value, context);
1845
+ return safe === "" ? null : safe;
1846
+ }
1683
1847
 
1684
- // src/utils/setSafeAttribute.ts
1848
+ // src/utils/metaRefresh.ts
1849
+ function isAsciiWhitespace(ch) {
1850
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
1851
+ }
1852
+ function trimAsciiWhitespace(value) {
1853
+ let start = 0;
1854
+ let end = value.length;
1855
+ while (start < end && isAsciiWhitespace(value[start])) start++;
1856
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
1857
+ return value.slice(start, end);
1858
+ }
1859
+ var forbidden = (reason) => ({ kind: "forbidden", reason });
1860
+ function parseMetaRefreshContent(content) {
1861
+ const raw = trimAsciiWhitespace(stripControlChars(content));
1862
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
1863
+ let i = 0;
1864
+ while (i < raw.length && raw[i] >= "0" && raw[i] <= "9") i++;
1865
+ if (i === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
1866
+ const delay = Number.parseInt(raw.slice(0, i), 10);
1867
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
1868
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
1869
+ if (i === raw.length) return { kind: "delay-only", delay };
1870
+ if (raw[i] !== ";" && raw[i] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
1871
+ i++;
1872
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
1873
+ const keyStart = i;
1874
+ while (i < raw.length && !isAsciiWhitespace(raw[i]) && raw[i] !== "=") i++;
1875
+ const key = raw.slice(keyStart, i).toLowerCase();
1876
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
1877
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
1878
+ if (raw[i] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
1879
+ i++;
1880
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
1881
+ let destination;
1882
+ const quote2 = raw[i];
1883
+ if (quote2 === '"' || quote2 === "'") {
1884
+ const close = raw.indexOf(quote2, i + 1);
1885
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
1886
+ destination = raw.slice(i + 1, close);
1887
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
1888
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
1889
+ } else {
1890
+ destination = trimAsciiWhitespace(raw.slice(i));
1891
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
1892
+ }
1893
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
1894
+ const sanitizedDestination = sanitizeUrl(destination);
1895
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
1896
+ return { kind: "allowed", delay, destination, sanitizedDestination };
1897
+ }
1898
+ function resolveMetaRefreshPolicy(attributes) {
1899
+ let httpEquiv;
1900
+ let content;
1901
+ let duplicateName;
1902
+ const seen = /* @__PURE__ */ new Set();
1903
+ for (const [key, value] of attributes) {
1904
+ const canonical = canonicalAttrName(key);
1905
+ if (seen.has(canonical)) duplicateName = canonical;
1906
+ seen.add(canonical);
1907
+ if (canonical === "http-equiv") httpEquiv = value;
1908
+ else if (canonical === "content") content = value;
1909
+ }
1910
+ if (duplicateName !== void 0) {
1911
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
1912
+ }
1913
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
1914
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
1915
+ return { kind: "not-refresh" };
1916
+ }
1917
+ if (typeof content !== "string") return { kind: "not-refresh" };
1918
+ return parseMetaRefreshContent(content);
1919
+ }
1920
+
1921
+ // src/utils/elementPolicy.ts
1685
1922
  var HTML_NS = "http://www.w3.org/1999/xhtml";
1686
1923
  var SVG_NS = "http://www.w3.org/2000/svg";
1924
+ var BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1925
+ function canonicalTagName(tag) {
1926
+ return canonicalAttrName(tag);
1927
+ }
1928
+ function isBlockedElement(tag) {
1929
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
1930
+ }
1931
+ var ContextualRefusal = {
1932
+ None: 0,
1933
+ /** The element would carry a refresh directive the shared policy forbids. */
1934
+ MetaRefreshForbidden: 1,
1935
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
1936
+ MetaRefreshReactive: 2,
1937
+ /** A runtime value may not choose which program a `<script>` runs. */
1938
+ ScriptSource: 3,
1939
+ /** An SVG animation may not target a URL, handler or document attribute. */
1940
+ AnimationTarget: 4,
1941
+ /** A runtime-written `href` may not become an applied stylesheet. */
1942
+ StylesheetSource: 5
1943
+ };
1944
+ var SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
1945
+ var SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
1946
+ var reactiveMetaElements = /* @__PURE__ */ new WeakSet();
1947
+ function isHtmlNamespace(el) {
1948
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
1949
+ }
1950
+ function metaSnapshot(el, canonical, value) {
1951
+ const snapshot = /* @__PURE__ */ new Map();
1952
+ const attrs = el.attributes;
1953
+ for (let i = 0; i < attrs.length; i++) {
1954
+ const attr = attrs[i];
1955
+ if (attr.name === canonical) continue;
1956
+ snapshot.set(attr.name, attr.value);
1957
+ }
1958
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
1959
+ return snapshot;
1960
+ }
1961
+ function claimReactiveMeta(el) {
1962
+ if (reactiveMetaElements.has(el)) return;
1963
+ reactiveMetaElements.add(el);
1964
+ if (el.getAttribute("content") === null) return;
1965
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
1966
+ el.removeAttribute("content");
1967
+ }
1968
+ }
1969
+ function metaRefusal(el, name, value, origin) {
1970
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
1971
+ const canonical = canonicalAttrName(name);
1972
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
1973
+ if (origin === "reactive") claimReactiveMeta(el);
1974
+ if (value === null) return ContextualRefusal.None;
1975
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
1976
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
1977
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
1978
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
1979
+ return ContextualRefusal.None;
1980
+ }
1981
+ function animationRefusal(name, value) {
1982
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
1983
+ const target = stripControlChars(value);
1984
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
1985
+ }
1986
+ var runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
1987
+ function relHasStylesheet(rel) {
1988
+ if (rel === null) return false;
1989
+ const lower = canonicalAttrName(rel);
1990
+ if (!lower.includes("stylesheet")) return false;
1991
+ const tokens = lower.split(/[\t\n\f\r ]+/);
1992
+ for (let i = 0; i < tokens.length; i++) if (tokens[i] === "stylesheet") return true;
1993
+ return false;
1994
+ }
1995
+ function stylesheetRefusal(el, name, value, origin) {
1996
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
1997
+ const canonical = canonicalAttrName(name);
1998
+ if (canonical === "href") {
1999
+ if (origin === "static" || value === null) return ContextualRefusal.None;
2000
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
2001
+ runtimeHrefLinks.add(el);
2002
+ return ContextualRefusal.None;
2003
+ }
2004
+ if (canonical === "rel") {
2005
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
2006
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
2007
+ }
2008
+ return ContextualRefusal.None;
2009
+ }
2010
+ function contextualAttributeRefusal(el, name, value, origin) {
2011
+ const local = el.localName;
2012
+ if (local === "meta") return metaRefusal(el, name, value, origin);
2013
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
2014
+ if (local === "script") {
2015
+ if (origin === "static" || value === null) return ContextualRefusal.None;
2016
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
2017
+ }
2018
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
2019
+ return ContextualRefusal.None;
2020
+ }
2021
+ function settleAttributeProvenance(el, name, origin) {
2022
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
2023
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
2024
+ else runtimeHrefLinks.delete(el);
2025
+ }
2026
+
2027
+ // src/utils/setSafeAttribute.ts
2028
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
2029
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
1687
2030
  var XLINK_NS = "http://www.w3.org/1999/xlink";
1688
2031
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
1689
2032
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
1690
2033
  function isHtmlElement(el) {
1691
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
2034
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
2035
+ }
2036
+ function warnContextualRefusal(label, name, refusal) {
2037
+ devWarnLazy(() => {
2038
+ const reasons = [
2039
+ "",
2040
+ '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)',
2041
+ "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",
2042
+ "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",
2043
+ "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",
2044
+ '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'
2045
+ ];
2046
+ return `${label}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
2047
+ });
1692
2048
  }
1693
2049
  function setProp(el, key, val) {
1694
2050
  el[key] = val;
1695
2051
  }
1696
2052
  function namespaceFor(el, name) {
1697
- if (el.namespaceURI !== SVG_NS) return null;
2053
+ if (el.namespaceURI !== SVG_NS2) return null;
1698
2054
  const lower = name.toLowerCase();
1699
2055
  if (lower.startsWith("xlink:")) return XLINK_NS;
1700
2056
  if (lower.startsWith("xml:")) return XML_NS;
1701
2057
  return null;
1702
2058
  }
1703
2059
  function setSafeAttribute(el, name, value, options = {}) {
2060
+ const written = commitSafeAttribute(el, name, value, options);
2061
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
2062
+ return written;
2063
+ }
2064
+ function commitSafeAttribute(el, name, value, options) {
1704
2065
  const ns = namespaceFor(el, name);
1705
2066
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
1706
2067
  if (isEventHandlerAttr(name)) {
@@ -1713,6 +2074,14 @@ function setSafeAttribute(el, name, value, options = {}) {
1713
2074
  else el.removeAttribute(name);
1714
2075
  return false;
1715
2076
  }
2077
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
2078
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
2079
+ if (refusal !== ContextualRefusal.None) {
2080
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
2081
+ if (ns) el.removeAttributeNS(ns, localName);
2082
+ else el.removeAttribute(name);
2083
+ return false;
2084
+ }
1716
2085
  if (value == null) {
1717
2086
  if (ns) el.removeAttributeNS(ns, localName);
1718
2087
  else el.removeAttribute(name);
@@ -1756,7 +2125,12 @@ function setSafeAttribute(el, name, value, options = {}) {
1756
2125
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
1757
2126
  return true;
1758
2127
  }
1759
- const safe = sanitizeAttributeString(name, str, { element: el });
2128
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
2129
+ if (safe === null) {
2130
+ if (ns) el.removeAttributeNS(ns, localName);
2131
+ else el.removeAttribute(name);
2132
+ return true;
2133
+ }
1760
2134
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
1761
2135
  if (current === safe) return true;
1762
2136
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -1779,7 +2153,7 @@ function bindAttribute(el, attr, getter) {
1779
2153
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
1780
2154
  return;
1781
2155
  }
1782
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
2156
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
1783
2157
  }
1784
2158
  return reactiveBinding2(commit, el);
1785
2159
  }
@@ -1953,10 +2327,6 @@ function bindChildNode(placeholder, getter) {
1953
2327
  }
1954
2328
 
1955
2329
  // src/core/rendering/tagFactory.ts
1956
- var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1957
- function isBlockedTag(tag) {
1958
- return BLOCKED_TAGS.has(tag.toLowerCase());
1959
- }
1960
2330
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1961
2331
  "config",
1962
2332
  "location",
@@ -1983,8 +2353,35 @@ function toKebab(prop) {
1983
2353
  }
1984
2354
  function applyStyle(el, style) {
1985
2355
  if (typeof style === "function") {
2356
+ const getter = style;
2357
+ let written = [];
2358
+ let wroteString = false;
1986
2359
  const teardown = reactiveBinding2(() => {
1987
- el.setAttribute("style", sanitizeStyleAttribute(String(style()), { element: el }));
2360
+ const value = getter();
2361
+ if (value === null || typeof value !== "object") {
2362
+ written = [];
2363
+ wroteString = true;
2364
+ el.setAttribute("style", sanitizeStyleAttribute(String(value), { element: el }));
2365
+ return;
2366
+ }
2367
+ const decl = el.style;
2368
+ if (wroteString) {
2369
+ el.removeAttribute("style");
2370
+ wroteString = false;
2371
+ }
2372
+ const next = [];
2373
+ for (const prop in value) {
2374
+ if (!Object.hasOwn(value, prop)) continue;
2375
+ const val = value[prop];
2376
+ if (val == null) continue;
2377
+ const name = toKebab(prop);
2378
+ next.push(name);
2379
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2380
+ }
2381
+ for (let i = 0; i < written.length; i++) {
2382
+ if (next.indexOf(written[i]) === -1) decl.removeProperty(written[i]);
2383
+ }
2384
+ written = next;
1988
2385
  }, el);
1989
2386
  registerDisposer(el, teardown);
1990
2387
  return;
@@ -1995,16 +2392,17 @@ function applyStyle(el, style) {
1995
2392
  }
1996
2393
  const htmlEl = el;
1997
2394
  for (const prop in style) {
2395
+ if (!Object.hasOwn(style, prop)) continue;
1998
2396
  const val = style[prop];
1999
2397
  const name = toKebab(prop);
2000
2398
  if (typeof val === "function") {
2001
2399
  const getter = val;
2002
2400
  const teardown = reactiveBinding2(() => {
2003
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
2401
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
2004
2402
  }, el);
2005
2403
  registerDisposer(el, teardown);
2006
2404
  } else {
2007
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
2405
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
2008
2406
  }
2009
2407
  }
2010
2408
  }
@@ -2015,7 +2413,9 @@ function applyClass(el, cls) {
2015
2413
  }
2016
2414
  if (typeof cls === "function") {
2017
2415
  const teardown = reactiveBinding2(() => {
2018
- el.setAttribute("class", cls());
2416
+ const value = cls();
2417
+ if (value == null || value === false) el.removeAttribute("class");
2418
+ else el.setAttribute("class", value);
2019
2419
  }, el);
2020
2420
  registerDisposer(el, teardown);
2021
2421
  return;
@@ -2024,6 +2424,7 @@ function applyClass(el, cls) {
2024
2424
  let hasReactive = false;
2025
2425
  let result = "";
2026
2426
  for (const name in obj) {
2427
+ if (!Object.hasOwn(obj, name)) continue;
2027
2428
  const val = obj[name];
2028
2429
  if (typeof val === "function") {
2029
2430
  hasReactive = true;
@@ -2035,6 +2436,7 @@ function applyClass(el, cls) {
2035
2436
  const update = () => {
2036
2437
  let r = "";
2037
2438
  for (const name in obj) {
2439
+ if (!Object.hasOwn(obj, name)) continue;
2038
2440
  const val = obj[name];
2039
2441
  const active = typeof val === "function" ? val() : val;
2040
2442
  if (active) r = r ? `${r} ${name}` : name;
@@ -2097,8 +2499,8 @@ function appendChildren(el, nodes) {
2097
2499
  }
2098
2500
  }
2099
2501
  }
2100
- var tagFactory = (tag, ns) => {
2101
- const blocked = isBlockedTag(tag);
2502
+ function tagFactory(tag, ns) {
2503
+ const blocked = isBlockedElement(tag);
2102
2504
  return (first, second) => {
2103
2505
  if (blocked) {
2104
2506
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -2146,7 +2548,9 @@ var tagFactory = (tag, ns) => {
2146
2548
  const pClass = props.class;
2147
2549
  if (pClass != null) applyClass(el, pClass);
2148
2550
  const pId = props.id;
2149
- if (pId != null) {
2551
+ if (typeof pId === "function") {
2552
+ registerDisposer(el, bindAttribute(el, "id", pId));
2553
+ } else if (pId != null) {
2150
2554
  if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
2151
2555
  devWarn(
2152
2556
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
@@ -2159,6 +2563,7 @@ var tagFactory = (tag, ns) => {
2159
2563
  const pOn = props.on;
2160
2564
  if (pOn) {
2161
2565
  for (const ev in pOn) {
2566
+ if (!Object.hasOwn(pOn, ev)) continue;
2162
2567
  const handler = pOn[ev];
2163
2568
  if (typeof handler === "function") {
2164
2569
  el.addEventListener(ev, handler);
@@ -2185,6 +2590,7 @@ var tagFactory = (tag, ns) => {
2185
2590
  continue;
2186
2591
  // already handled above / below
2187
2592
  default: {
2593
+ if (!Object.hasOwn(props, key)) continue;
2188
2594
  const value = props[key];
2189
2595
  if (value == null) continue;
2190
2596
  if (isEventHandlerAttr(key)) continue;
@@ -2196,12 +2602,10 @@ var tagFactory = (tag, ns) => {
2196
2602
  }
2197
2603
  }
2198
2604
  }
2199
- if (props.onElement && typeof props.onElement === "function") {
2200
- props.onElement(el);
2201
- }
2605
+ if (typeof props.onElement === "function") props.onElement(el);
2202
2606
  return el;
2203
2607
  };
2204
- };
2608
+ }
2205
2609
 
2206
2610
  // src/ecosystem/ui/componentAdapter.ts
2207
2611
  var _themeLayers = globalSingleton(
@@ -2211,7 +2615,7 @@ var _themeLayers = globalSingleton(
2211
2615
  function commitThemeProperty(root, name, record) {
2212
2616
  const top = record.layers[record.layers.length - 1];
2213
2617
  if (top) {
2214
- root.style.setProperty(name, top.value);
2618
+ root.style.setProperty(name, sanitizeCSSDeclaration(name, String(top.value), { element: root }));
2215
2619
  return;
2216
2620
  }
2217
2621
  _themeLayers.get(root)?.delete(name);