sibujs 4.6.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.cjs +62 -8
  3. package/dist/browser.d.cts +23 -0
  4. package/dist/browser.d.ts +23 -0
  5. package/dist/browser.js +6 -5
  6. package/dist/build.cjs +2388 -1004
  7. package/dist/build.d.cts +147 -44
  8. package/dist/build.d.ts +147 -44
  9. package/dist/build.js +1410 -564
  10. package/dist/cdn.dev.global.js +14 -12
  11. package/dist/cdn.full.dev.global.js +15 -13
  12. package/dist/cdn.full.global.js +14 -12
  13. package/dist/cdn.global.js +12 -10
  14. package/dist/chunk-3ACAAQIN.js +224 -0
  15. package/dist/{chunk-7NCARGJW.js → chunk-44MTL7ED.js} +6 -6
  16. package/dist/{chunk-BAAG6ZTI.js → chunk-4WMSTZ7J.js} +8 -5
  17. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  18. package/dist/{chunk-DKGBBKOF.js → chunk-C5PEWEGH.js} +20 -5
  19. package/dist/{chunk-3ISI6ACU.js → chunk-C7AA2CZX.js} +1 -1
  20. package/dist/{chunk-UZQ6ALFS.js → chunk-CZPRSWSS.js} +1 -1
  21. package/dist/{chunk-D33YTSX5.js → chunk-DSID757X.js} +5 -4
  22. package/dist/{chunk-OXUY2A6L.js → chunk-EGUQLBHE.js} +22 -8
  23. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  24. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  25. package/dist/{chunk-HYXDKS4N.js → chunk-GSGNX35Q.js} +5 -5
  26. package/dist/{chunk-7GQCWFOE.js → chunk-J3YQ457H.js} +171 -85
  27. package/dist/{chunk-ORMZXBKQ.js → chunk-JUTFX2RM.js} +18 -10
  28. package/dist/{chunk-QE4TTDU3.js → chunk-KJ3UKABJ.js} +12 -11
  29. package/dist/{chunk-XRRZKZYX.js → chunk-LI4W3U6C.js} +1 -1
  30. package/dist/{chunk-47M47FOM.js → chunk-O6TRVMJI.js} +58 -6
  31. package/dist/{chunk-JSXPZCET.js → chunk-Q5J2LVKH.js} +1 -1
  32. package/dist/{chunk-5MT6SJ3P.js → chunk-QGMSESDD.js} +10 -5
  33. package/dist/{chunk-AIF3Z2T7.js → chunk-RGLPEBDE.js} +13 -10
  34. package/dist/{chunk-5Q4R7HCL.js → chunk-TQQUN4WR.js} +11 -2
  35. package/dist/{chunk-PK6FK2G2.js → chunk-UCNP3HVM.js} +183 -30
  36. package/dist/{chunk-XC4MEKGA.js → chunk-V3LHZSU5.js} +8 -2
  37. package/dist/{chunk-VVWPJ543.js → chunk-VWT3VXPL.js} +55 -20
  38. package/dist/{chunk-SXXVZMKZ.js → chunk-ZVLHNEEL.js} +17 -35
  39. package/dist/{contracts-DRIuclVT.d.cts → contracts-CLqzJnOV.d.ts} +28 -17
  40. package/dist/{contracts-DRIuclVT.d.ts → contracts-CTOJXu-x.d.cts} +28 -17
  41. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  42. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  43. package/dist/data.cjs +205 -39
  44. package/dist/data.d.cts +119 -3
  45. package/dist/data.d.ts +119 -3
  46. package/dist/data.js +6 -6
  47. package/dist/devtools.cjs +18 -5
  48. package/dist/devtools.js +5 -5
  49. package/dist/ecosystem.cjs +462 -58
  50. package/dist/ecosystem.d.cts +2 -1
  51. package/dist/ecosystem.d.ts +2 -1
  52. package/dist/ecosystem.js +9 -8
  53. package/dist/extras.cjs +852 -296
  54. package/dist/extras.d.cts +5 -4
  55. package/dist/extras.d.ts +5 -4
  56. package/dist/extras.js +24 -23
  57. package/dist/index.cjs +721 -176
  58. package/dist/index.d.cts +220 -169
  59. package/dist/index.d.ts +220 -169
  60. package/dist/index.js +16 -12
  61. package/dist/motion.cjs +12 -4
  62. package/dist/motion.js +3 -3
  63. package/dist/patterns.cjs +37 -21
  64. package/dist/patterns.d.cts +2 -1
  65. package/dist/patterns.d.ts +2 -1
  66. package/dist/patterns.js +6 -6
  67. package/dist/performance.cjs +48 -6
  68. package/dist/performance.d.cts +12 -2
  69. package/dist/performance.d.ts +12 -2
  70. package/dist/performance.js +8 -6
  71. package/dist/plugins.cjs +672 -261
  72. package/dist/plugins.d.cts +131 -13
  73. package/dist/plugins.d.ts +131 -13
  74. package/dist/plugins.js +83 -47
  75. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  76. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.cts} +6 -0
  77. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.ts} +6 -0
  78. package/dist/ssr.cjs +509 -142
  79. package/dist/ssr.d.cts +55 -5
  80. package/dist/ssr.d.ts +55 -5
  81. package/dist/ssr.js +11 -9
  82. package/dist/{tagFactory-DFstCLQV.d.cts → tagFactory-8qL9LCIx.d.cts} +58 -19
  83. package/dist/{tagFactory-DkaNVUNV.d.ts → tagFactory-BL2fymez.d.ts} +58 -19
  84. package/dist/testing.cjs +17 -4
  85. package/dist/testing.js +4 -4
  86. package/dist/types-CJFViL6Q.d.cts +26 -0
  87. package/dist/types-CJFViL6Q.d.ts +26 -0
  88. package/dist/ui.cjs +635 -254
  89. package/dist/ui.d.cts +3 -2
  90. package/dist/ui.d.ts +3 -2
  91. package/dist/ui.js +9 -8
  92. package/dist/widgets.cjs +22 -14
  93. package/dist/widgets.js +7 -7
  94. package/package.json +8 -5
package/dist/extras.cjs CHANGED
@@ -1077,7 +1077,7 @@ function forEachSubscriber(signal2, visit) {
1077
1077
  }
1078
1078
 
1079
1079
  // src/reactivity/track.ts
1080
- var _runtimeVersion = true ? "4.6.0" : "dev";
1080
+ var _runtimeVersion = true ? "4.8.0" : "dev";
1081
1081
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1082
1082
  function resolveReactiveApi() {
1083
1083
  const g2 = globalThis;
@@ -1133,6 +1133,14 @@ var getSubscriberCount2 = API.getSubscriberCount;
1133
1133
  var getSubscriberDeps2 = API.getSubscriberDeps;
1134
1134
  var forEachSubscriber2 = API.forEachSubscriber;
1135
1135
 
1136
+ // src/core/devtoolsHook.ts
1137
+ function emitDevtools(hook, event, payload) {
1138
+ try {
1139
+ hook.emit(event, payload);
1140
+ } catch {
1141
+ }
1142
+ }
1143
+
1136
1144
  // src/core/signals/derived.ts
1137
1145
  function derived(getter, options) {
1138
1146
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
@@ -1159,17 +1167,17 @@ function derived(getter, options) {
1159
1167
  cs._d = false;
1160
1168
  cs._init = true;
1161
1169
  };
1162
- track2(() => {
1163
- let threw = true;
1164
- try {
1170
+ try {
1171
+ track2(() => {
1165
1172
  cs._v = getter();
1166
1173
  cs._d = false;
1167
1174
  cs._init = true;
1168
- threw = false;
1169
- } finally {
1170
- if (threw) cs._d = true;
1171
- }
1172
- }, markDirty);
1175
+ }, markDirty);
1176
+ } catch (err) {
1177
+ disposed = true;
1178
+ cleanup2(markDirty);
1179
+ throw err;
1180
+ }
1173
1181
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1174
1182
  const validate = () => {
1175
1183
  if (!cs._d || disposed || pendingError !== void 0) return;
@@ -1188,7 +1196,7 @@ function derived(getter, options) {
1188
1196
  if (disposed) cleanup2(markDirty);
1189
1197
  }
1190
1198
  if (hook && !disposed && !Object.is(oldValue, cs._v)) {
1191
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
1199
+ emitDevtools(hook, "computed:update", { signal: cs, oldValue, newValue: cs._v });
1192
1200
  }
1193
1201
  };
1194
1202
  cs._validate = validate;
@@ -1239,7 +1247,7 @@ function derived(getter, options) {
1239
1247
  }
1240
1248
  }
1241
1249
  };
1242
- if (hook) hook.emit("computed:create", { signal: cs, name: debugName, getter: computedGetter });
1250
+ if (hook) emitDevtools(hook, "computed:create", { signal: cs, name: debugName, getter: computedGetter });
1243
1251
  return computedGetter;
1244
1252
  }
1245
1253
 
@@ -1412,12 +1420,17 @@ function effect(effectFn, options) {
1412
1420
  try {
1413
1421
  retrack2(ctx.bodyFn, ctx.subscriber);
1414
1422
  if (ctx.rerunPending) drainReruns(ctx);
1423
+ } catch (err) {
1424
+ ctx.disposed = true;
1425
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
1426
+ cleanup2(ctx.subscriber);
1427
+ throw err;
1415
1428
  } finally {
1416
1429
  ctx.running = false;
1417
1430
  ctx.rerunPending = false;
1418
1431
  }
1419
1432
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1420
- if (hook) hook.emit("effect:create", { effectFn });
1433
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
1421
1434
  return () => disposeEffect(ctx);
1422
1435
  }
1423
1436
 
@@ -1504,7 +1517,7 @@ function signal(initial, options) {
1504
1517
  state2.__v++;
1505
1518
  if (DEV) {
1506
1519
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1507
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1520
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1508
1521
  }
1509
1522
  if (!enqueueBatchedSignal(state2)) {
1510
1523
  notifySubscribers2(state2);
@@ -1518,7 +1531,7 @@ function signal(initial, options) {
1518
1531
  state2.value = newValue;
1519
1532
  state2.__v++;
1520
1533
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1521
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1534
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1522
1535
  if (!enqueueBatchedSignal(state2)) {
1523
1536
  notifySubscribers2(state2);
1524
1537
  }
@@ -1537,7 +1550,7 @@ function signal(initial, options) {
1537
1550
  }
1538
1551
  if (DEV) {
1539
1552
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1540
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1553
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
1541
1554
  }
1542
1555
  return [get, set];
1543
1556
  }
@@ -1659,7 +1672,87 @@ function waitForRetryDelay(delay, signal2) {
1659
1672
  });
1660
1673
  }
1661
1674
 
1675
+ // src/data/structuralSharing.ts
1676
+ function isPlainObject(value) {
1677
+ if (value === null || typeof value !== "object") return false;
1678
+ const proto = Object.getPrototypeOf(value);
1679
+ return proto === Object.prototype || proto === null;
1680
+ }
1681
+ function isPlainArray(value) {
1682
+ return Array.isArray(value) && Object.getPrototypeOf(value) === Array.prototype;
1683
+ }
1684
+ function reconcile(prev, next, depth, walk, newRoot) {
1685
+ if (Object.is(prev, next)) return prev;
1686
+ const arrays = isPlainArray(prev) && isPlainArray(next);
1687
+ if (!arrays && !(isPlainObject(prev) && isPlainObject(next))) return next;
1688
+ if (Object.getPrototypeOf(prev) !== Object.getPrototypeOf(next)) return next;
1689
+ const nextObject = next;
1690
+ const seenAt = walk.path.get(nextObject);
1691
+ if (seenAt !== void 0) {
1692
+ walk.cycleTo = Math.min(walk.cycleTo, seenAt);
1693
+ return next;
1694
+ }
1695
+ const prevRecord = prev;
1696
+ const nextRecord = next;
1697
+ const nextKeys = arrays ? Array.from({ length: next.length }, (_, i2) => i2) : Object.keys(nextRecord);
1698
+ const prevSize = arrays ? prev.length : Object.keys(prevRecord).length;
1699
+ const copy = arrays ? new Array(nextKeys.length) : Object.create(Object.getPrototypeOf(next));
1700
+ let allFromPrev = prevSize === nextKeys.length;
1701
+ let allFromNext = true;
1702
+ const outerCycleTo = walk.cycleTo;
1703
+ walk.cycleTo = Number.POSITIVE_INFINITY;
1704
+ walk.path.set(nextObject, depth);
1705
+ for (const key of nextKeys) {
1706
+ if (arrays && !Object.hasOwn(nextRecord, key)) {
1707
+ if (Object.hasOwn(prevRecord, key)) allFromPrev = false;
1708
+ continue;
1709
+ }
1710
+ const nextChild = nextRecord[key];
1711
+ const hasPrev = Object.hasOwn(prevRecord, key);
1712
+ const shared = hasPrev ? reconcile(prevRecord[key], nextChild, depth + 1, walk, false) : nextChild;
1713
+ if (key === "__proto__") {
1714
+ Object.defineProperty(copy, key, { value: shared, writable: true, enumerable: true, configurable: true });
1715
+ } else {
1716
+ copy[key] = shared;
1717
+ }
1718
+ if (!hasPrev || !Object.is(shared, prevRecord[key])) allFromPrev = false;
1719
+ if (!Object.is(shared, nextChild)) allFromNext = false;
1720
+ }
1721
+ walk.path.delete(nextObject);
1722
+ const reached = walk.cycleTo;
1723
+ walk.cycleTo = Math.min(outerCycleTo, reached < depth ? reached : Number.POSITIVE_INFINITY);
1724
+ if (reached <= depth) return next;
1725
+ if (allFromPrev && !newRoot) return prev;
1726
+ if (allFromNext) return next;
1727
+ return copy;
1728
+ }
1729
+ function applyStructuralSharing(option2, prev, next, explicit = false) {
1730
+ if (option2 === false || prev === void 0) return next;
1731
+ const forceNew = explicit && !Object.is(prev, next);
1732
+ if (typeof option2 === "function") {
1733
+ const shared = option2(prev, next);
1734
+ return forceNew && Object.is(shared, prev) ? next : shared;
1735
+ }
1736
+ return reconcile(prev, next, 0, { path: /* @__PURE__ */ new Map(), cycleTo: Number.POSITIVE_INFINITY }, forceNew);
1737
+ }
1738
+
1662
1739
  // src/data/query.ts
1740
+ function shareSafely(option2, prev, next, explicit) {
1741
+ const shared = untracked2(
1742
+ () => runSelect("query structuralSharing", () => applyStructuralSharing(option2, prev, next, explicit))
1743
+ );
1744
+ return shared.ok ? shared.value : next;
1745
+ }
1746
+ function sharedView(entry) {
1747
+ const raw = entry.data;
1748
+ if (raw === void 0) return void 0;
1749
+ if (entry.hasShared && Object.is(entry.sharedFrom, raw)) return entry.shared;
1750
+ const value = shareSafely(true, entry.hasShared ? entry.shared : void 0, raw, entry.explicit);
1751
+ entry.shared = value;
1752
+ entry.sharedFrom = raw;
1753
+ entry.hasShared = true;
1754
+ return value;
1755
+ }
1663
1756
  var globalQueryCache = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.query.cache.v1"), () => /* @__PURE__ */ new Map());
1664
1757
  function getActiveQueryCache() {
1665
1758
  return getRequestScopedCache("query") ?? globalQueryCache;
@@ -1685,7 +1778,11 @@ function getOrCreateEntry(cache, key, initialData) {
1685
1778
  listeners: /* @__PURE__ */ new Set(),
1686
1779
  refetchers: /* @__PURE__ */ new Set(),
1687
1780
  controller: null,
1688
- generation: 0
1781
+ generation: 0,
1782
+ explicit: false,
1783
+ shared: void 0,
1784
+ sharedFrom: void 0,
1785
+ hasShared: false
1689
1786
  };
1690
1787
  cache.set(key, entry);
1691
1788
  }
@@ -1704,13 +1801,24 @@ function query(key, fetcher, options = {}) {
1704
1801
  onSuccess,
1705
1802
  onError,
1706
1803
  onSettled,
1707
- select: select3
1804
+ select: select3,
1805
+ structuralSharing = true
1708
1806
  } = options;
1709
1807
  const resolveKey = typeof key === "function" ? key : () => key;
1710
1808
  const cache = getActiveQueryCache();
1711
1809
  const [data2, setData] = signal(initialData);
1712
1810
  const [isFetching, setIsFetching] = signal(false);
1713
1811
  const [error, setError] = signal(void 0);
1812
+ const [updatedAt, setUpdatedAt] = signal(0);
1813
+ const [base2, setBase] = signal(void 0);
1814
+ let baseEntry = null;
1815
+ let baseValue;
1816
+ let baseExplicit = false;
1817
+ let customEntry = null;
1818
+ let customFrom;
1819
+ let customValue;
1820
+ let selectedEntry = null;
1821
+ let selectedValue;
1714
1822
  let disposed = false;
1715
1823
  let currentKey = null;
1716
1824
  let attachedEntry = null;
@@ -1719,11 +1827,57 @@ function query(key, fetcher, options = {}) {
1719
1827
  const loading = derived(() => isFetching() && data2() === void 0);
1720
1828
  const isStale = derived(() => {
1721
1829
  data2();
1830
+ updatedAt();
1722
1831
  if (!currentKey) return true;
1723
1832
  const entry = cache.get(currentKey);
1724
1833
  if (!entry || entry.dataUpdatedAt === 0) return true;
1725
1834
  return Date.now() - entry.dataUpdatedAt >= staleTime;
1726
1835
  });
1836
+ function viewOf(entry) {
1837
+ const raw = entry.data;
1838
+ if (raw === void 0) return void 0;
1839
+ if (structuralSharing === false) return raw;
1840
+ if (typeof structuralSharing !== "function") return sharedView(entry);
1841
+ const sameEntry = customEntry === entry;
1842
+ if (sameEntry && Object.is(customFrom, raw)) return customValue;
1843
+ const value = shareSafely(structuralSharing, sameEntry ? customValue : void 0, raw, entry.explicit);
1844
+ customEntry = entry;
1845
+ customFrom = raw;
1846
+ customValue = value;
1847
+ return value;
1848
+ }
1849
+ function commitBase(entry, value) {
1850
+ const changed = !Object.is(value, baseValue);
1851
+ baseEntry = entry;
1852
+ baseValue = value;
1853
+ if (!select3) {
1854
+ setData(value);
1855
+ return;
1856
+ }
1857
+ if (!changed) return;
1858
+ baseExplicit = entry?.explicit === true;
1859
+ setBase(value);
1860
+ }
1861
+ const stopSelect = select3 ? effect(() => {
1862
+ const raw = base2();
1863
+ const entry = baseEntry;
1864
+ const explicit = baseExplicit;
1865
+ baseExplicit = false;
1866
+ if (entry === null && selectedEntry === null) return;
1867
+ if (raw === void 0) {
1868
+ selectedEntry = entry;
1869
+ selectedValue = void 0;
1870
+ setData(void 0);
1871
+ return;
1872
+ }
1873
+ const selected = runSelect("query select", () => select3(raw));
1874
+ if (!selected.ok) return;
1875
+ const prev = selectedEntry === entry ? selectedValue : void 0;
1876
+ const value = shareSafely(structuralSharing, prev, selected.value, explicit);
1877
+ selectedEntry = entry;
1878
+ selectedValue = value;
1879
+ setData(value);
1880
+ }) : null;
1727
1881
  function detachFromEntry() {
1728
1882
  const entry = attachedEntry;
1729
1883
  const key2 = attachedKey;
@@ -1782,7 +1936,7 @@ function query(key, fetcher, options = {}) {
1782
1936
  const settledError = entry.error;
1783
1937
  runCallback("query onError", () => onError?.(settledError));
1784
1938
  } else if (entry.data !== void 0) {
1785
- const settledData = entry.data;
1939
+ const settledData = viewOf(entry);
1786
1940
  runCallback("query onSuccess", () => onSuccess?.(settledData));
1787
1941
  }
1788
1942
  runCallback("query onSettled", () => onSettled?.());
@@ -1813,17 +1967,14 @@ function query(key, fetcher, options = {}) {
1813
1967
  entry.promise = null;
1814
1968
  entry.controller = null;
1815
1969
  entry.data = result;
1970
+ entry.explicit = false;
1816
1971
  entry.dataUpdatedAt = Date.now();
1817
1972
  entry.error = void 0;
1818
1973
  notifyListeners(entry.listeners, "query cache listener");
1819
1974
  if (disposed || currentKey !== key2) return;
1820
- const selected = runSelect("query select", () => select3 ? select3(result) : result);
1821
- batch(() => {
1822
- if (selected.ok) setData(selected.value);
1823
- setIsFetching(false);
1824
- setError(void 0);
1825
- });
1826
- runCallback("query onSuccess", () => onSuccess?.(result));
1975
+ if (!entry.listeners.has(onCacheUpdate)) onCacheUpdate();
1976
+ const committedResult = viewOf(entry);
1977
+ runCallback("query onSuccess", () => onSuccess?.(committedResult));
1827
1978
  } catch (err) {
1828
1979
  if (entry.generation !== generation) return;
1829
1980
  entry.promise = null;
@@ -1853,18 +2004,19 @@ function query(key, fetcher, options = {}) {
1853
2004
  const entry = cache.get(currentKey);
1854
2005
  if (!entry) {
1855
2006
  batch(() => {
1856
- setData(void 0);
2007
+ commitBase(null, void 0);
1857
2008
  setError(void 0);
1858
2009
  setIsFetching(false);
2010
+ setUpdatedAt(0);
1859
2011
  });
1860
2012
  return;
1861
2013
  }
1862
- const raw = entry.data;
1863
- const selected = raw !== void 0 && select3 ? runSelect("query select", () => select3(raw)) : { ok: true, value: raw };
2014
+ const view = viewOf(entry);
1864
2015
  batch(() => {
1865
- if (selected.ok) setData(selected.value);
2016
+ commitBase(entry, view);
1866
2017
  setError(entry.error);
1867
2018
  if (!entry.promise) setIsFetching(false);
2019
+ setUpdatedAt(entry.dataUpdatedAt);
1868
2020
  });
1869
2021
  }
1870
2022
  const effectCleanup = effect(() => {
@@ -1874,11 +2026,11 @@ function query(key, fetcher, options = {}) {
1874
2026
  const entry = getOrCreateEntry(cache, key2, initialData);
1875
2027
  attachToEntry(entry, key2);
1876
2028
  if (entry.data !== void 0) {
1877
- const raw = entry.data;
1878
- const selected = runSelect("query select", () => select3 ? select3(raw) : raw);
2029
+ const view = viewOf(entry);
1879
2030
  batch(() => {
1880
- if (selected.ok) setData(selected.value);
2031
+ commitBase(entry, view);
1881
2032
  setError(entry.error);
2033
+ setUpdatedAt(entry.dataUpdatedAt);
1882
2034
  });
1883
2035
  }
1884
2036
  if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
@@ -1916,6 +2068,7 @@ function query(key, fetcher, options = {}) {
1916
2068
  if (disposed) return;
1917
2069
  disposed = true;
1918
2070
  effectCleanup();
2071
+ stopSelect?.();
1919
2072
  loading.dispose();
1920
2073
  isStale.dispose();
1921
2074
  if (intervalTimer) clearInterval(intervalTimer);
@@ -1947,13 +2100,15 @@ function invalidateQueries(keyOrPredicate) {
1947
2100
  }
1948
2101
  }
1949
2102
  function getQueryData(key) {
1950
- return getActiveQueryCache().get(key)?.data;
2103
+ const entry = getActiveQueryCache().get(key);
2104
+ return entry ? sharedView(entry) : void 0;
1951
2105
  }
1952
2106
  function setQueryData(key, data2) {
1953
2107
  const entry = getActiveQueryCache().get(key);
1954
2108
  if (!entry) return;
1955
- const newData = typeof data2 === "function" ? data2(entry.data) : data2;
2109
+ const newData = typeof data2 === "function" ? data2(sharedView(entry)) : data2;
1956
2110
  entry.data = newData;
2111
+ entry.explicit = true;
1957
2112
  entry.dataUpdatedAt = Date.now();
1958
2113
  notifyListeners(entry.listeners, "query cache listener");
1959
2114
  }
@@ -2337,6 +2492,15 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
2337
2492
  const [loading, setLoading] = signal(false);
2338
2493
  const [error, setError] = signal(void 0);
2339
2494
  let currentData = options.initialValue;
2495
+ function commitData(next, explicit) {
2496
+ const sharing = options.structuralSharing ?? true;
2497
+ const shared = untracked2(
2498
+ () => runSelect("resource structuralSharing", () => applyStructuralSharing(sharing, currentData, next, explicit))
2499
+ );
2500
+ currentData = shared.ok ? shared.value : next;
2501
+ setData(currentData);
2502
+ return currentData;
2503
+ }
2340
2504
  let abortController = null;
2341
2505
  let disposed = false;
2342
2506
  let effectCleanup = null;
@@ -2356,12 +2520,12 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
2356
2520
  try {
2357
2521
  const result = await withRetry(() => fetcher(sourceValue, { signal: signal2, prev }), options.retry, void 0, signal2);
2358
2522
  if (version !== fetchVersion || disposed) return;
2359
- currentData = result;
2523
+ let committed = result;
2360
2524
  batch(() => {
2361
- setData(result);
2525
+ committed = commitData(result, false);
2362
2526
  setLoading(false);
2363
2527
  });
2364
- runCallback("resource onSuccess", () => options.onSuccess?.(result));
2528
+ runCallback("resource onSuccess", () => options.onSuccess?.(committed));
2365
2529
  } catch (err) {
2366
2530
  if (version !== fetchVersion || disposed) return;
2367
2531
  if (isAbortError(err)) {
@@ -2395,8 +2559,7 @@ function resource(sourceOrFetcher, fetcherOrOptions, maybeOptions) {
2395
2559
  refetch: () => doFetch(source2 ? source2() : void 0),
2396
2560
  mutate: (value) => {
2397
2561
  const newValue = typeof value === "function" ? value(currentData) : value;
2398
- currentData = newValue;
2399
- setData(newValue);
2562
+ commitData(newValue, true);
2400
2563
  },
2401
2564
  abort: () => abortController?.abort(),
2402
2565
  dispose: () => {
@@ -2916,8 +3079,10 @@ function socket(url, options) {
2916
3079
  function stripControlChars(value) {
2917
3080
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
2918
3081
  }
3082
+ var ASCII_UPPER = /[A-Z]/;
3083
+ var ASCII_UPPER_G = /[A-Z]/g;
2919
3084
  function canonicalAttrName(name) {
2920
- return name.replace(/[A-Z]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32));
3085
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
2921
3086
  }
2922
3087
  function isEventHandlerAttr(name) {
2923
3088
  if (name.length < 3) return false;
@@ -2930,6 +3095,9 @@ function sanitizeUrl(url) {
2930
3095
  const probe = stripControlChars(url).trim();
2931
3096
  if (!probe) return "";
2932
3097
  const output2 = url.replace(OUTER_TRIM, "");
3098
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
3099
+ return "";
3100
+ }
2933
3101
  const lower = probe.toLowerCase();
2934
3102
  let schemeEnd = -1;
2935
3103
  for (let i2 = 0; i2 < lower.length; i2++) {
@@ -2946,19 +3114,58 @@ function sanitizeUrl(url) {
2946
3114
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
2947
3115
  return output2;
2948
3116
  }
2949
- var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
3117
+ var SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
3118
+ function isSrcsetWhitespace(ch) {
3119
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
3120
+ }
2950
3121
  function sanitizeSrcset(value) {
2951
- const parts = value.split(",");
3122
+ const input2 = String(value);
2952
3123
  const out = [];
2953
- for (let i2 = 0; i2 < parts.length; i2++) {
2954
- const part = parts[i2].trim();
2955
- if (!part) continue;
2956
- const m = part.match(/^(\S+)(\s+.+)?$/);
2957
- if (!m) continue;
2958
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
2959
- const safe = sanitizeUrl(m[1]);
3124
+ let i2 = 0;
3125
+ const n = input2.length;
3126
+ while (i2 < n) {
3127
+ while (i2 < n && (isSrcsetWhitespace(input2[i2]) || input2[i2] === ",")) i2++;
3128
+ if (i2 >= n) break;
3129
+ const urlStart = i2;
3130
+ while (i2 < n && !isSrcsetWhitespace(input2[i2])) i2++;
3131
+ let url = input2.slice(urlStart, i2);
3132
+ const descriptors = [];
3133
+ if (url.endsWith(",")) {
3134
+ url = url.replace(/,+$/, "");
3135
+ } else {
3136
+ let current = "";
3137
+ let inParens = false;
3138
+ while (i2 < n) {
3139
+ const ch = input2[i2];
3140
+ if (inParens) {
3141
+ current += ch;
3142
+ if (ch === ")") inParens = false;
3143
+ i2++;
3144
+ continue;
3145
+ }
3146
+ if (isSrcsetWhitespace(ch)) {
3147
+ if (current) descriptors.push(current);
3148
+ current = "";
3149
+ i2++;
3150
+ continue;
3151
+ }
3152
+ if (ch === ",") {
3153
+ i2++;
3154
+ break;
3155
+ }
3156
+ if (ch === "(") inParens = true;
3157
+ current += ch;
3158
+ i2++;
3159
+ }
3160
+ if (current) descriptors.push(current);
3161
+ }
3162
+ if (url === "") continue;
3163
+ if (descriptors.length > 1) continue;
3164
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
3165
+ const safe = sanitizeUrl(url);
2960
3166
  if (!safe) continue;
2961
- out.push(m[2] ? `${safe}${m[2]}` : safe);
3167
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
3168
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
2962
3169
  }
2963
3170
  return out.join(", ");
2964
3171
  }
@@ -3017,8 +3224,16 @@ var BLOCKED_CSS_NEEDLES = [
3017
3224
  "behavior:",
3018
3225
  "@import",
3019
3226
  "image-set(",
3020
- "filter:progid"
3227
+ "filter:progid",
3228
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
3229
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
3230
+ // today, which is exactly when a blocklist should get ahead of it.
3231
+ "image(",
3232
+ "src("
3021
3233
  ];
3234
+ var CSS_REASON_MOZ_BINDING = 4;
3235
+ var CSS_REASON_BEHAVIOR = 5;
3236
+ var CSS_REASON_PROGID = 8;
3022
3237
  function describeElement(el) {
3023
3238
  if (!el) return "an element";
3024
3239
  let out = el.tagName.toLowerCase();
@@ -3042,7 +3257,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
3042
3257
  "behavior: loads and runs a remote HTC script",
3043
3258
  "@import pulls in a remote stylesheet",
3044
3259
  "image-set() fetches remote images, the same exfiltration channel as url()",
3045
- "filter:progid activates legacy scriptable filters"
3260
+ "filter:progid activates legacy scriptable filters",
3261
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
3262
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
3046
3263
  ];
3047
3264
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
3048
3265
  const where = describeElement(ctx?.element);
@@ -3057,27 +3274,145 @@ function warnDroppedDeclaration(value, reason, ctx) {
3057
3274
  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.`;
3058
3275
  });
3059
3276
  }
3277
+ function normalizeCssForScan(value) {
3278
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
3279
+ return normalized.toLowerCase().replace(/\s+/g, "");
3280
+ }
3060
3281
  function blockedCssReason(value) {
3061
3282
  if (!CSS_DANGER_GATE.test(value)) return -1;
3062
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
3063
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
3283
+ const lower = normalizeCssForScan(value);
3064
3284
  for (let i2 = 0; i2 < BLOCKED_CSS_NEEDLES.length; i2++) {
3065
3285
  if (lower.includes(BLOCKED_CSS_NEEDLES[i2])) return i2;
3066
3286
  }
3067
3287
  return -1;
3068
3288
  }
3069
- function sanitizeCSSValue(value, context) {
3070
- const reason = blockedCssReason(value);
3289
+ var STYLESHEET_BLOCKED_FUNCTIONS = ["url", "image-set", "image", "src", "expression"];
3290
+ function stripCssFunctions(css) {
3291
+ const pattern2 = new RegExp(`(?:-webkit-)?(${STYLESHEET_BLOCKED_FUNCTIONS.join("|")})\\s*\\(`, "gi");
3292
+ let out = "";
3293
+ let last = 0;
3294
+ for (let match = pattern2.exec(css); match !== null; match = pattern2.exec(css)) {
3295
+ const before = match.index > 0 ? css[match.index - 1] : "";
3296
+ if (/[\w-]/.test(before)) continue;
3297
+ let depth = 1;
3298
+ let i2 = pattern2.lastIndex;
3299
+ let quote2 = "";
3300
+ for (; i2 < css.length && depth > 0; i2++) {
3301
+ const ch = css[i2];
3302
+ if (quote2) {
3303
+ if (ch === "\\") i2++;
3304
+ else if (ch === quote2) quote2 = "";
3305
+ } else if (ch === '"' || ch === "'") quote2 = ch;
3306
+ else if (ch === "(") depth++;
3307
+ else if (ch === ")") depth--;
3308
+ }
3309
+ out += `${css.slice(last, match.index)}/* ${match[1].toLowerCase()}() removed */`;
3310
+ last = i2;
3311
+ pattern2.lastIndex = i2;
3312
+ }
3313
+ return out + css.slice(last);
3314
+ }
3315
+ function sanitizeStylesheetText(css) {
3316
+ let text2 = css.includes("\\") ? decodeCssEscapes(css) : css;
3317
+ text2 = text2.replace(/@import\b[^;{}]*;?/gi, "/* @import removed */");
3318
+ text2 = stripCssFunctions(text2);
3319
+ text2 = text2.replace(/(^|[;{\s])behavior\s*:[^;}]*;?/gi, "$1/* behavior removed */");
3320
+ text2 = text2.replace(/(^|[;{\s])-moz-binding\s*:[^;}]*;?/gi, "$1/* -moz-binding removed */");
3321
+ text2 = text2.replace(/(^|[;{\s])(-ms-)?filter\s*:[^;}]*progid[^;}]*;?/gi, "$1/* filter:progid removed */");
3322
+ return text2;
3323
+ }
3324
+ function sanitizeCSSDeclaration(property, value, context) {
3325
+ const reason = blockedDeclarationReason(property, value);
3071
3326
  if (reason === -1) return value;
3072
- warnDroppedDeclaration(value, reason, context);
3327
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
3073
3328
  return "";
3074
3329
  }
3330
+ function blockedDeclarationReason(property, value) {
3331
+ const reason = blockedCssReason(value);
3332
+ if (reason !== -1) return reason;
3333
+ const name = canonicalAttrName(property);
3334
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
3335
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
3336
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
3337
+ return CSS_REASON_PROGID;
3338
+ }
3339
+ return -1;
3340
+ }
3341
+ function parseStyleDeclarations(css) {
3342
+ const chunks = [];
3343
+ let buf = "";
3344
+ let depth = 0;
3345
+ let quote2 = "";
3346
+ for (let i2 = 0; i2 < css.length; i2++) {
3347
+ const ch = css[i2];
3348
+ if (quote2) {
3349
+ buf += ch;
3350
+ if (ch === "\\" && i2 + 1 < css.length) buf += css[++i2];
3351
+ else if (ch === quote2) quote2 = "";
3352
+ continue;
3353
+ }
3354
+ if (ch === "\\") {
3355
+ buf += ch;
3356
+ if (i2 + 1 < css.length) buf += css[++i2];
3357
+ continue;
3358
+ }
3359
+ if (ch === "/" && css[i2 + 1] === "*") {
3360
+ const end = css.indexOf("*/", i2 + 2);
3361
+ if (end === -1) return null;
3362
+ i2 = end + 1;
3363
+ continue;
3364
+ }
3365
+ if (ch === '"' || ch === "'") quote2 = ch;
3366
+ else if (ch === "(" || ch === "[") depth++;
3367
+ else if (ch === ")" || ch === "]") {
3368
+ if (--depth < 0) return null;
3369
+ } else if (ch === "{" || ch === "}") return null;
3370
+ else if (ch === ";" && depth === 0) {
3371
+ chunks.push(buf);
3372
+ buf = "";
3373
+ continue;
3374
+ }
3375
+ buf += ch;
3376
+ }
3377
+ if (quote2 || depth !== 0) return null;
3378
+ chunks.push(buf);
3379
+ const out = [];
3380
+ for (const chunk of chunks) {
3381
+ const colon = chunk.indexOf(":");
3382
+ if (colon === -1) continue;
3383
+ const rawName = chunk.slice(0, colon).trim();
3384
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
3385
+ let value = chunk.slice(colon + 1).trim();
3386
+ const bang = /!\s*important\s*$/i.exec(value);
3387
+ if (bang) value = value.slice(0, bang.index).trim();
3388
+ if (value === "") continue;
3389
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
3390
+ out.push([property, value, bang !== null]);
3391
+ }
3392
+ return out;
3393
+ }
3394
+ function sanitizeStyleDeclarationList(cssText, context) {
3395
+ const input2 = String(cssText);
3396
+ const declarations = parseStyleDeclarations(input2);
3397
+ if (declarations === null) {
3398
+ if (blockedCssReason(input2) !== -1) warnDroppedDeclaration(input2, blockedCssReason(input2), context);
3399
+ return "";
3400
+ }
3401
+ const kept = [];
3402
+ for (const [property, value, important] of declarations) {
3403
+ const reason = blockedDeclarationReason(property, value);
3404
+ if (reason !== -1) {
3405
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
3406
+ continue;
3407
+ }
3408
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
3409
+ }
3410
+ return kept.join("; ");
3411
+ }
3075
3412
  function sanitizeStyleAttribute(cssText, context) {
3076
3413
  const input2 = String(cssText);
3077
3414
  if (input2.trim() === "") return "";
3078
- if (typeof document === "undefined") {
3079
- return sanitizeCSSValue(input2, context) === "" ? "" : input2;
3080
- }
3415
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input2, context);
3081
3416
  const probe = document.createElement("div");
3082
3417
  try {
3083
3418
  probe.style.cssText = input2;
@@ -3088,8 +3423,7 @@ function sanitizeStyleAttribute(cssText, context) {
3088
3423
  for (let i2 = 0; i2 < probe.style.length; i2++) {
3089
3424
  const property = probe.style[i2];
3090
3425
  const value = probe.style.getPropertyValue(property);
3091
- let reason = blockedCssReason(value);
3092
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
3426
+ const reason = blockedDeclarationReason(property, value);
3093
3427
  if (reason !== -1) {
3094
3428
  warnDroppedDeclaration(value, reason, { property, element: context?.element });
3095
3429
  continue;
@@ -3137,6 +3471,9 @@ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
3137
3471
  function isHtmlContentAttribute(name) {
3138
3472
  return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
3139
3473
  }
3474
+ function isUrlAttribute(attr) {
3475
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
3476
+ }
3140
3477
  function isPolicyAttribute(name) {
3141
3478
  const lower = name.toLowerCase();
3142
3479
  return lower === "style" || URL_ATTRIBUTES.has(lower);
@@ -3148,6 +3485,11 @@ function sanitizeAttributeString(attr, value, context) {
3148
3485
  if (lower === "style") return sanitizeStyleAttribute(value, context);
3149
3486
  return value;
3150
3487
  }
3488
+ function resolveAttributeValue(attr, value, context) {
3489
+ if (!isPolicyAttribute(attr)) return value;
3490
+ const safe = sanitizeAttributeString(attr, value, context);
3491
+ return safe === "" ? null : safe;
3492
+ }
3151
3493
 
3152
3494
  // src/ui/stream.ts
3153
3495
  function validateSseUrl(raw) {
@@ -3571,6 +3913,13 @@ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototyp
3571
3913
  function isUnsafeKey(key) {
3572
3914
  return UNSAFE_KEYS.has(key);
3573
3915
  }
3916
+ function readOwnGlobal(key) {
3917
+ if (typeof window === "undefined") return void 0;
3918
+ if (!Object.hasOwn(window, key)) return void 0;
3919
+ const value = window[key];
3920
+ if (typeof Node !== "undefined" && value instanceof Node) return void 0;
3921
+ return value;
3922
+ }
3574
3923
  function stripUnsafeKeys(obj) {
3575
3924
  const out = {};
3576
3925
  for (const k of Object.keys(obj)) {
@@ -3888,13 +4237,19 @@ function colorScheme() {
3888
4237
  }
3889
4238
 
3890
4239
  // src/browser/format.ts
4240
+ var I18N_STATE = /* @__PURE__ */ Symbol.for("sibujs.i18n.v1");
4241
+ function activeI18nLocale() {
4242
+ const state2 = globalThis[I18N_STATE];
4243
+ if (!state2) return void 0;
4244
+ return state2.getLocale ? state2.getLocale() : state2.locale?.[0]();
4245
+ }
3891
4246
  function formatNumber(value, options) {
3892
4247
  const { locale, ...formatOptions } = options ?? {};
3893
- return new Intl.NumberFormat(locale, formatOptions).format(value);
4248
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), formatOptions).format(value);
3894
4249
  }
3895
4250
  function formatCurrency(value, currency, options) {
3896
4251
  const { locale, ...formatOptions } = options ?? {};
3897
- return new Intl.NumberFormat(locale, {
4252
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), {
3898
4253
  ...formatOptions,
3899
4254
  style: "currency",
3900
4255
  currency
@@ -4863,6 +5218,11 @@ function imageLoader(src) {
4863
5218
  abandonCurrent();
4864
5219
  resetState();
4865
5220
  if (disposed || token !== startToken) return;
5221
+ const safe = sanitizeUrl(String(url));
5222
+ if (!safe) {
5223
+ setStatus("error");
5224
+ return;
5225
+ }
4866
5226
  const img2 = new Image();
4867
5227
  current = img2;
4868
5228
  currentSettled = false;
@@ -4881,7 +5241,7 @@ function imageLoader(src) {
4881
5241
  currentSettled = true;
4882
5242
  setStatus("error");
4883
5243
  };
4884
- img2.src = url;
5244
+ img2.src = safe;
4885
5245
  }
4886
5246
  let srcEffectTeardown = null;
4887
5247
  if (typeof src === "function") {
@@ -4907,6 +5267,9 @@ function imageLoader(src) {
4907
5267
  }
4908
5268
 
4909
5269
  // src/patterns/machine.ts
5270
+ function ownEntry(table2, key) {
5271
+ return Object.hasOwn(table2, key) ? table2[key] : void 0;
5272
+ }
4910
5273
  function machine(config) {
4911
5274
  const [state2, setState] = signal(config.initial);
4912
5275
  const [context, setContext] = signal(config.context || {});
@@ -4928,9 +5291,9 @@ function machine(config) {
4928
5291
  }
4929
5292
  }
4930
5293
  function step(event) {
4931
- const stateDef = config.states[currentState];
5294
+ const stateDef = ownEntry(config.states, currentState);
4932
5295
  if (!stateDef?.on) return;
4933
- const transition2 = stateDef.on[event];
5296
+ const transition2 = ownEntry(stateDef.on, event);
4934
5297
  if (!transition2) return;
4935
5298
  let target;
4936
5299
  let guard;
@@ -4958,7 +5321,7 @@ function machine(config) {
4958
5321
  if (action) setContext(nextContext);
4959
5322
  setState(target);
4960
5323
  });
4961
- const targetDef = config.states[target];
5324
+ const targetDef = ownEntry(config.states, target);
4962
5325
  if (targetDef?.entry) {
4963
5326
  targetDef.entry(currentContext);
4964
5327
  }
@@ -4968,7 +5331,7 @@ function machine(config) {
4968
5331
  if (processing) return;
4969
5332
  drain();
4970
5333
  }
4971
- const initialDef = config.states[config.initial];
5334
+ const initialDef = ownEntry(config.states, config.initial);
4972
5335
  if (initialDef?.entry) {
4973
5336
  processing = true;
4974
5337
  try {
@@ -4986,9 +5349,9 @@ function machine(config) {
4986
5349
  }
4987
5350
  function can(event) {
4988
5351
  const currentState2 = state2();
4989
- const stateDef = config.states[currentState2];
5352
+ const stateDef = ownEntry(config.states, currentState2);
4990
5353
  if (!stateDef?.on) return false;
4991
- const transition2 = stateDef.on[event];
5354
+ const transition2 = ownEntry(stateDef.on, event);
4992
5355
  if (!transition2) return false;
4993
5356
  if (typeof transition2 === "string") return true;
4994
5357
  if (transition2.guard) {
@@ -7065,31 +7428,6 @@ function announce(message, priority = "polite") {
7065
7428
 
7066
7429
  // src/ui/scopedStyle.ts
7067
7430
  var _scope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.scopedStyle.v1"), () => ({ n: 0 }));
7068
- function decodeCssEscapes2(css) {
7069
- return css.replace(/\\([0-9a-f]{1,6})[ \t\n\r\f]?|\\([^\n])/gi, (_match, hex, ch) => {
7070
- if (hex) {
7071
- const code2 = Number.parseInt(hex, 16);
7072
- if (Number.isFinite(code2) && code2 > 0 && code2 <= 1114111) {
7073
- try {
7074
- return String.fromCodePoint(code2);
7075
- } catch {
7076
- return "";
7077
- }
7078
- }
7079
- return "";
7080
- }
7081
- return ch || "";
7082
- });
7083
- }
7084
- function sanitizeCSS(css) {
7085
- let sanitized = decodeCssEscapes2(css);
7086
- sanitized = sanitized.replace(/@import\s+[^;]+;/gi, "/* @import removed */");
7087
- sanitized = sanitized.replace(/url\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* url() removed */");
7088
- sanitized = sanitized.replace(/expression\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* expression() removed */");
7089
- sanitized = sanitized.replace(/-moz-binding\s*:[^;]+;/gi, "/* -moz-binding removed */");
7090
- sanitized = sanitized.replace(/behavior\s*:[^;]+;/gi, "/* behavior removed */");
7091
- return sanitized;
7092
- }
7093
7431
  function firstCompoundEnd(selector) {
7094
7432
  let parens = 0;
7095
7433
  let brackets = 0;
@@ -7251,7 +7589,7 @@ function scopeStylesheet(css, attr) {
7251
7589
  function scopedStyle(css) {
7252
7590
  const id = `sibu-s${_scope.n++}`;
7253
7591
  const attr = `data-${id}`;
7254
- const safeCss = sanitizeCSS(css);
7592
+ const safeCss = sanitizeStylesheetText(css);
7255
7593
  const scopedCSS = scopeStylesheet(safeCss, attr);
7256
7594
  if (typeof document !== "undefined") {
7257
7595
  const styleEl = document.createElement("style");
@@ -7277,26 +7615,246 @@ function removeScopedStyle(scopeId) {
7277
7615
  if (el) el.remove();
7278
7616
  }
7279
7617
 
7280
- // src/utils/setSafeAttribute.ts
7618
+ // src/utils/metaRefresh.ts
7619
+ function isAsciiWhitespace(ch) {
7620
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
7621
+ }
7622
+ function trimAsciiWhitespace(value) {
7623
+ let start = 0;
7624
+ let end = value.length;
7625
+ while (start < end && isAsciiWhitespace(value[start])) start++;
7626
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
7627
+ return value.slice(start, end);
7628
+ }
7629
+ var forbidden = (reason) => ({ kind: "forbidden", reason });
7630
+ function parseMetaRefreshContent(content) {
7631
+ const raw = trimAsciiWhitespace(stripControlChars(content));
7632
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
7633
+ let i2 = 0;
7634
+ while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
7635
+ if (i2 === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
7636
+ const delay = Number.parseInt(raw.slice(0, i2), 10);
7637
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
7638
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7639
+ if (i2 === raw.length) return { kind: "delay-only", delay };
7640
+ if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
7641
+ i2++;
7642
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7643
+ const keyStart = i2;
7644
+ while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
7645
+ const key = raw.slice(keyStart, i2).toLowerCase();
7646
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
7647
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7648
+ if (raw[i2] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
7649
+ i2++;
7650
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7651
+ let destination;
7652
+ const quote2 = raw[i2];
7653
+ if (quote2 === '"' || quote2 === "'") {
7654
+ const close = raw.indexOf(quote2, i2 + 1);
7655
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
7656
+ destination = raw.slice(i2 + 1, close);
7657
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
7658
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
7659
+ } else {
7660
+ destination = trimAsciiWhitespace(raw.slice(i2));
7661
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
7662
+ }
7663
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
7664
+ const sanitizedDestination = sanitizeUrl(destination);
7665
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
7666
+ return { kind: "allowed", delay, destination, sanitizedDestination };
7667
+ }
7668
+ function resolveMetaRefreshPolicy(attributes) {
7669
+ let httpEquiv;
7670
+ let content;
7671
+ let duplicateName;
7672
+ const seen = /* @__PURE__ */ new Set();
7673
+ for (const [key, value] of attributes) {
7674
+ const canonical = canonicalAttrName(key);
7675
+ if (seen.has(canonical)) duplicateName = canonical;
7676
+ seen.add(canonical);
7677
+ if (canonical === "http-equiv") httpEquiv = value;
7678
+ else if (canonical === "content") content = value;
7679
+ }
7680
+ if (duplicateName !== void 0) {
7681
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
7682
+ }
7683
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
7684
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
7685
+ return { kind: "not-refresh" };
7686
+ }
7687
+ if (typeof content !== "string") return { kind: "not-refresh" };
7688
+ return parseMetaRefreshContent(content);
7689
+ }
7690
+ function normalizeMetaAttributes(props) {
7691
+ const out = /* @__PURE__ */ new Map();
7692
+ const seen = /* @__PURE__ */ new Set();
7693
+ for (const key of Object.keys(props)) {
7694
+ const canonical = canonicalAttrName(key);
7695
+ if (seen.has(canonical)) return null;
7696
+ seen.add(canonical);
7697
+ out.set(key, props[key]);
7698
+ }
7699
+ return out;
7700
+ }
7701
+ function isForbiddenMetaEntry(props) {
7702
+ const normalized = normalizeMetaAttributes(props);
7703
+ if (normalized === null) return true;
7704
+ return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
7705
+ }
7706
+
7707
+ // src/utils/elementPolicy.ts
7281
7708
  var HTML_NS = "http://www.w3.org/1999/xhtml";
7282
7709
  var SVG_NS = "http://www.w3.org/2000/svg";
7710
+ var BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
7711
+ function canonicalTagName(tag) {
7712
+ return canonicalAttrName(tag);
7713
+ }
7714
+ function isBlockedElement(tag) {
7715
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
7716
+ }
7717
+ var CODE_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
7718
+ function isCodeTextElement(tag) {
7719
+ return CODE_TEXT_ELEMENTS.has(canonicalTagName(tag));
7720
+ }
7721
+ var ContextualRefusal = {
7722
+ None: 0,
7723
+ /** The element would carry a refresh directive the shared policy forbids. */
7724
+ MetaRefreshForbidden: 1,
7725
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
7726
+ MetaRefreshReactive: 2,
7727
+ /** A runtime value may not choose which program a `<script>` runs. */
7728
+ ScriptSource: 3,
7729
+ /** An SVG animation may not target a URL, handler or document attribute. */
7730
+ AnimationTarget: 4,
7731
+ /** A runtime-written `href` may not become an applied stylesheet. */
7732
+ StylesheetSource: 5
7733
+ };
7734
+ var SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
7735
+ var SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
7736
+ var reactiveMetaElements = /* @__PURE__ */ new WeakSet();
7737
+ function isHtmlNamespace(el) {
7738
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
7739
+ }
7740
+ function metaSnapshot(el, canonical, value) {
7741
+ const snapshot = /* @__PURE__ */ new Map();
7742
+ const attrs = el.attributes;
7743
+ for (let i2 = 0; i2 < attrs.length; i2++) {
7744
+ const attr = attrs[i2];
7745
+ if (attr.name === canonical) continue;
7746
+ snapshot.set(attr.name, attr.value);
7747
+ }
7748
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
7749
+ return snapshot;
7750
+ }
7751
+ function claimReactiveMeta(el) {
7752
+ if (reactiveMetaElements.has(el)) return;
7753
+ reactiveMetaElements.add(el);
7754
+ if (el.getAttribute("content") === null) return;
7755
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
7756
+ el.removeAttribute("content");
7757
+ }
7758
+ }
7759
+ function metaRefusal(el, name, value, origin) {
7760
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
7761
+ const canonical = canonicalAttrName(name);
7762
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
7763
+ if (origin === "reactive") claimReactiveMeta(el);
7764
+ if (value === null) return ContextualRefusal.None;
7765
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
7766
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
7767
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
7768
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
7769
+ return ContextualRefusal.None;
7770
+ }
7771
+ function animationRefusal(name, value) {
7772
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
7773
+ const target = stripControlChars(value);
7774
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
7775
+ }
7776
+ var runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
7777
+ function relHasStylesheet(rel) {
7778
+ if (rel === null) return false;
7779
+ const lower = canonicalAttrName(rel);
7780
+ if (!lower.includes("stylesheet")) return false;
7781
+ const tokens = lower.split(/[\t\n\f\r ]+/);
7782
+ for (let i2 = 0; i2 < tokens.length; i2++) if (tokens[i2] === "stylesheet") return true;
7783
+ return false;
7784
+ }
7785
+ function stylesheetRefusal(el, name, value, origin) {
7786
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
7787
+ const canonical = canonicalAttrName(name);
7788
+ if (canonical === "href") {
7789
+ if (origin === "static" || value === null) return ContextualRefusal.None;
7790
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
7791
+ runtimeHrefLinks.add(el);
7792
+ return ContextualRefusal.None;
7793
+ }
7794
+ if (canonical === "rel") {
7795
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
7796
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
7797
+ }
7798
+ return ContextualRefusal.None;
7799
+ }
7800
+ function contextualAttributeRefusal(el, name, value, origin) {
7801
+ const local = el.localName;
7802
+ if (local === "meta") return metaRefusal(el, name, value, origin);
7803
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
7804
+ if (local === "script") {
7805
+ if (origin === "static" || value === null) return ContextualRefusal.None;
7806
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
7807
+ }
7808
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
7809
+ return ContextualRefusal.None;
7810
+ }
7811
+ function settleAttributeProvenance(el, name, origin) {
7812
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
7813
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
7814
+ else runtimeHrefLinks.delete(el);
7815
+ }
7816
+ function isForbiddenMetaSnapshot(attributes) {
7817
+ return resolveMetaRefreshPolicy(attributes).kind === "forbidden";
7818
+ }
7819
+
7820
+ // src/utils/setSafeAttribute.ts
7821
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
7822
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
7283
7823
  var XLINK_NS = "http://www.w3.org/1999/xlink";
7284
7824
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
7285
7825
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
7286
7826
  function isHtmlElement(el) {
7287
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
7827
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
7828
+ }
7829
+ function warnContextualRefusal(label2, name, refusal) {
7830
+ devWarnLazy(() => {
7831
+ const reasons = [
7832
+ "",
7833
+ '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)',
7834
+ "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",
7835
+ "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",
7836
+ "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",
7837
+ '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'
7838
+ ];
7839
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
7840
+ });
7288
7841
  }
7289
7842
  function setProp(el, key, val) {
7290
7843
  el[key] = val;
7291
7844
  }
7292
7845
  function namespaceFor(el, name) {
7293
- if (el.namespaceURI !== SVG_NS) return null;
7846
+ if (el.namespaceURI !== SVG_NS2) return null;
7294
7847
  const lower = name.toLowerCase();
7295
7848
  if (lower.startsWith("xlink:")) return XLINK_NS;
7296
7849
  if (lower.startsWith("xml:")) return XML_NS;
7297
7850
  return null;
7298
7851
  }
7299
7852
  function setSafeAttribute(el, name, value, options = {}) {
7853
+ const written = commitSafeAttribute(el, name, value, options);
7854
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
7855
+ return written;
7856
+ }
7857
+ function commitSafeAttribute(el, name, value, options) {
7300
7858
  const ns = namespaceFor(el, name);
7301
7859
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
7302
7860
  if (isEventHandlerAttr(name)) {
@@ -7309,6 +7867,14 @@ function setSafeAttribute(el, name, value, options = {}) {
7309
7867
  else el.removeAttribute(name);
7310
7868
  return false;
7311
7869
  }
7870
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
7871
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
7872
+ if (refusal !== ContextualRefusal.None) {
7873
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
7874
+ if (ns) el.removeAttributeNS(ns, localName);
7875
+ else el.removeAttribute(name);
7876
+ return false;
7877
+ }
7312
7878
  if (value == null) {
7313
7879
  if (ns) el.removeAttributeNS(ns, localName);
7314
7880
  else el.removeAttribute(name);
@@ -7352,7 +7918,12 @@ function setSafeAttribute(el, name, value, options = {}) {
7352
7918
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
7353
7919
  return true;
7354
7920
  }
7355
- const safe = sanitizeAttributeString(name, str, { element: el });
7921
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
7922
+ if (safe === null) {
7923
+ if (ns) el.removeAttributeNS(ns, localName);
7924
+ else el.removeAttribute(name);
7925
+ return true;
7926
+ }
7356
7927
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
7357
7928
  if (current === safe) return true;
7358
7929
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -7375,7 +7946,7 @@ function bindAttribute(el, attr, getter) {
7375
7946
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
7376
7947
  return;
7377
7948
  }
7378
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
7949
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
7379
7950
  }
7380
7951
  return reactiveBinding2(commit, el);
7381
7952
  }
@@ -7412,7 +7983,7 @@ function bindBoolAttr(el, attr, getter) {
7412
7983
  reportError(err, { phase: "binding", name: "bindBoolAttr", node: el });
7413
7984
  return;
7414
7985
  }
7415
- setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr" });
7986
+ setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr", reactive: true });
7416
7987
  }
7417
7988
  return reactiveBinding2(commit, el);
7418
7989
  }
@@ -7965,6 +8536,9 @@ var DOMPool = class {
7965
8536
  * Get a recycled element or create a new one.
7966
8537
  */
7967
8538
  acquire(tag) {
8539
+ if (isBlockedElement(tag)) {
8540
+ throw new Error(`DOMPool: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
8541
+ }
7968
8542
  const pool = this.pools.get(tag);
7969
8543
  if (pool && pool.length > 0) {
7970
8544
  const el = pool.pop();
@@ -8070,10 +8644,19 @@ function prefetch(url) {
8070
8644
  }
8071
8645
  function preloadImage(src) {
8072
8646
  return new Promise((resolve, reject) => {
8647
+ const safe = sanitizeUrl(String(src));
8648
+ if (!safe) {
8649
+ reject(
8650
+ new Error(
8651
+ String(src).trim() === "" ? "preloadImage: no image URL was given." : "preloadImage: refusing an image URL outside the URL policy."
8652
+ )
8653
+ );
8654
+ return;
8655
+ }
8073
8656
  const img2 = new Image();
8074
8657
  img2.onload = () => resolve(img2);
8075
8658
  img2.onerror = reject;
8076
- img2.src = src;
8659
+ img2.src = safe;
8077
8660
  });
8078
8661
  }
8079
8662
 
@@ -8099,95 +8682,6 @@ function noSideEffect(fn) {
8099
8682
  return fn;
8100
8683
  }
8101
8684
 
8102
- // src/utils/metaRefresh.ts
8103
- function isAsciiWhitespace(ch) {
8104
- return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
8105
- }
8106
- function trimAsciiWhitespace(value) {
8107
- let start = 0;
8108
- let end = value.length;
8109
- while (start < end && isAsciiWhitespace(value[start])) start++;
8110
- while (end > start && isAsciiWhitespace(value[end - 1])) end--;
8111
- return value.slice(start, end);
8112
- }
8113
- var forbidden = (reason) => ({ kind: "forbidden", reason });
8114
- function parseMetaRefreshContent(content) {
8115
- const raw = trimAsciiWhitespace(stripControlChars(content));
8116
- if (raw === "") return forbidden("empty content");
8117
- let i2 = 0;
8118
- while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
8119
- if (i2 === 0) return forbidden("missing or non-numeric delay");
8120
- const delay = Number.parseInt(raw.slice(0, i2), 10);
8121
- if (!Number.isFinite(delay)) return forbidden("unparseable delay");
8122
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8123
- if (i2 === raw.length) return { kind: "delay-only", delay };
8124
- if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden("unexpected text after delay");
8125
- i2++;
8126
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8127
- const keyStart = i2;
8128
- while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
8129
- const key = raw.slice(keyStart, i2).toLowerCase();
8130
- if (key !== "url") return forbidden(`unsupported refresh key ${JSON.stringify(key)}`);
8131
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8132
- if (raw[i2] !== "=") return forbidden("missing '=' after url");
8133
- i2++;
8134
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8135
- let destination;
8136
- const quote2 = raw[i2];
8137
- if (quote2 === '"' || quote2 === "'") {
8138
- const close = raw.indexOf(quote2, i2 + 1);
8139
- if (close === -1) return forbidden("unterminated quoted destination");
8140
- destination = raw.slice(i2 + 1, close);
8141
- const rest = trimAsciiWhitespace(raw.slice(close + 1));
8142
- if (rest !== "") return forbidden("trailing text after quoted destination");
8143
- } else {
8144
- destination = trimAsciiWhitespace(raw.slice(i2));
8145
- if (/[;,]/.test(destination)) return forbidden("multiple refresh assignments");
8146
- }
8147
- if (destination === "") return forbidden("empty destination");
8148
- const sanitizedDestination = sanitizeUrl(destination);
8149
- if (sanitizedDestination === "") return forbidden("destination uses a disallowed protocol");
8150
- return { kind: "allowed", delay, destination, sanitizedDestination };
8151
- }
8152
- function resolveMetaRefreshPolicy(attributes) {
8153
- let httpEquiv;
8154
- let content;
8155
- let duplicateName;
8156
- const seen = /* @__PURE__ */ new Set();
8157
- for (const [key, value] of attributes) {
8158
- const canonical = canonicalAttrName(key);
8159
- if (seen.has(canonical)) duplicateName = canonical;
8160
- seen.add(canonical);
8161
- if (canonical === "http-equiv") httpEquiv = value;
8162
- else if (canonical === "content") content = value;
8163
- }
8164
- if (duplicateName !== void 0) {
8165
- return forbidden(`duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}`);
8166
- }
8167
- if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
8168
- if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
8169
- return { kind: "not-refresh" };
8170
- }
8171
- if (typeof content !== "string") return { kind: "not-refresh" };
8172
- return parseMetaRefreshContent(content);
8173
- }
8174
- function normalizeMetaAttributes(props) {
8175
- const out = /* @__PURE__ */ new Map();
8176
- const seen = /* @__PURE__ */ new Set();
8177
- for (const key of Object.keys(props)) {
8178
- const canonical = canonicalAttrName(key);
8179
- if (seen.has(canonical)) return null;
8180
- seen.add(canonical);
8181
- out.set(key, props[key]);
8182
- }
8183
- return out;
8184
- }
8185
- function isForbiddenMetaEntry(props) {
8186
- const normalized = normalizeMetaAttributes(props);
8187
- if (normalized === null) return true;
8188
- return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
8189
- }
8190
-
8191
8685
  // src/utils/headEntry.ts
8192
8686
  var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
8193
8687
  function isEmittableHeadAttrName(name) {
@@ -8198,9 +8692,7 @@ function isEmittableHeadAttrName(name) {
8198
8692
  return true;
8199
8693
  }
8200
8694
  function sanitizeHeadAttrValue(canonical, value) {
8201
- if (!isPolicyAttribute(canonical)) return value;
8202
- const safe = sanitizeAttributeString(canonical, value);
8203
- return safe === "" ? null : safe;
8695
+ return resolveAttributeValue(canonical, value);
8204
8696
  }
8205
8697
  function findDuplicateAttributeName(names) {
8206
8698
  const seen = /* @__PURE__ */ new Set();
@@ -8257,15 +8749,9 @@ function serializeHeadEntry(tag, attrs) {
8257
8749
  }
8258
8750
 
8259
8751
  // src/platform/ssr.ts
8260
- function isPolicyAttr(lowerName) {
8261
- return isPolicyAttribute(lowerName);
8262
- }
8263
8752
  function isForbiddenSsrAttr(lowerName) {
8264
8753
  return isHtmlContentAttribute(lowerName);
8265
8754
  }
8266
- function sanitizeSsrAttributeValue(lowerName, value) {
8267
- return sanitizeAttributeString(lowerName, value);
8268
- }
8269
8755
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
8270
8756
  function isSafeAttrName(name) {
8271
8757
  return SAFE_ATTR_NAME.test(name);
@@ -8296,6 +8782,64 @@ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
8296
8782
  "track",
8297
8783
  "wbr"
8298
8784
  ]);
8785
+ function findNativeGetter(proto, name) {
8786
+ for (let p2 = proto; p2; p2 = Object.getPrototypeOf(p2)) {
8787
+ const get = Object.getOwnPropertyDescriptor(p2, name)?.get;
8788
+ if (get) return get;
8789
+ }
8790
+ return null;
8791
+ }
8792
+ var nativeAccessorsByRealm = /* @__PURE__ */ new WeakMap();
8793
+ function nativeAccessors() {
8794
+ const realm = Element;
8795
+ let accessors = nativeAccessorsByRealm.get(realm);
8796
+ if (!accessors) {
8797
+ accessors = {
8798
+ attributes: findNativeGetter(Element.prototype, "attributes"),
8799
+ tagName: findNativeGetter(Element.prototype, "tagName"),
8800
+ childNodes: findNativeGetter(Node.prototype, "childNodes"),
8801
+ getAttribute: Element.prototype.getAttribute
8802
+ };
8803
+ nativeAccessorsByRealm.set(realm, accessors);
8804
+ }
8805
+ return accessors;
8806
+ }
8807
+ function attributesOf(el) {
8808
+ const get = nativeAccessors().attributes;
8809
+ return get ? get.call(el) : el.attributes;
8810
+ }
8811
+ function tagNameOf(el) {
8812
+ const get = nativeAccessors().tagName;
8813
+ return get ? get.call(el) : el.tagName;
8814
+ }
8815
+ function childNodesOf(node) {
8816
+ const get = nativeAccessors().childNodes;
8817
+ return get ? get.call(node) : node.childNodes;
8818
+ }
8819
+ function getAttributeOf(el, name) {
8820
+ return nativeAccessors().getAttribute.call(el, name);
8821
+ }
8822
+ function serializeOpenTag(element, tag) {
8823
+ let html2 = `<${tag}`;
8824
+ const meta2 = tag === "meta" ? /* @__PURE__ */ new Map() : null;
8825
+ const attrs = attributesOf(element);
8826
+ for (let i2 = 0; i2 < attrs.length; i2++) {
8827
+ const attr = attrs[i2];
8828
+ const rawName = attr.name;
8829
+ if (!isSafeAttrName(rawName)) continue;
8830
+ if (isEventHandlerAttr(rawName)) continue;
8831
+ if (isForbiddenSsrAttr(rawName)) continue;
8832
+ const value = resolveAttributeValue(rawName.toLowerCase(), attr.value);
8833
+ if (value === null) continue;
8834
+ meta2?.set(rawName, value);
8835
+ html2 += ` ${rawName}="${escapeAttr(value)}"`;
8836
+ }
8837
+ if (meta2 && isForbiddenMetaSnapshot(meta2)) return null;
8838
+ if (!getAttributeOf(element, "data-sibu-hydrate")) {
8839
+ html2 += ` data-sibu-ssr="true"`;
8840
+ }
8841
+ return html2;
8842
+ }
8299
8843
  function renderToString(element) {
8300
8844
  if (element instanceof DocumentFragment) {
8301
8845
  return Array.from(element.childNodes).map((child) => {
@@ -8315,35 +8859,21 @@ function renderToString(element) {
8315
8859
  if (!(element instanceof HTMLElement)) {
8316
8860
  return escapeHtml(element.textContent || "");
8317
8861
  }
8318
- const tag = element.tagName.toLowerCase();
8319
- if (tag === "script" || tag === "style") {
8862
+ const tag = tagNameOf(element).toLowerCase();
8863
+ if (isCodeTextElement(tag)) {
8320
8864
  return DEV ? `<!--ssr:${tag}-stripped-->` : "";
8321
8865
  }
8322
8866
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
8323
8867
  return DEV ? "<!--ssr:invalid-tag-->" : "";
8324
8868
  }
8325
- let html2 = `<${tag}`;
8326
- for (const attr of Array.from(element.attributes)) {
8327
- const rawName = attr.name;
8328
- if (!isSafeAttrName(rawName)) continue;
8329
- if (isEventHandlerAttr(rawName)) continue;
8330
- if (isForbiddenSsrAttr(rawName)) continue;
8331
- const lowerName = rawName.toLowerCase();
8332
- let value = attr.value;
8333
- if (isPolicyAttr(lowerName)) {
8334
- value = sanitizeSsrAttributeValue(lowerName, value);
8335
- if (!value) continue;
8336
- }
8337
- html2 += ` ${rawName}="${escapeAttr(value)}"`;
8338
- }
8339
- if (element.dataset && !element.dataset.sibuHydrate) {
8340
- html2 += ` data-sibu-ssr="true"`;
8341
- }
8869
+ const openTag = serializeOpenTag(element, tag);
8870
+ if (openTag === null) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
8871
+ let html2 = openTag;
8342
8872
  if (VOID_ELEMENTS.has(tag)) {
8343
8873
  return `${html2} />`;
8344
8874
  }
8345
8875
  html2 += ">";
8346
- for (const child of Array.from(element.childNodes)) {
8876
+ for (const child of Array.from(childNodesOf(element))) {
8347
8877
  try {
8348
8878
  html2 += renderToString(child);
8349
8879
  } catch (err) {
@@ -8493,12 +9023,8 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
8493
9023
  if (!isSafeAttrName(rawKey)) continue;
8494
9024
  if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
8495
9025
  if (isForbiddenSsrAttr(rawKey)) continue;
8496
- const lowerKey = rawKey.toLowerCase();
8497
- let value = String(attrs[rawKey]);
8498
- if (isPolicyAttr(lowerKey)) {
8499
- value = sanitizeSsrAttributeValue(lowerKey, value);
8500
- if (!value) continue;
8501
- }
9026
+ const value = resolveAttributeValue(rawKey.toLowerCase(), String(attrs[rawKey]));
9027
+ if (value === null) continue;
8502
9028
  out.push(`${rawKey}="${escapeAttr(value)}"`);
8503
9029
  }
8504
9030
  return out.join(" ");
@@ -8561,8 +9087,8 @@ async function* renderToStream(element) {
8561
9087
  yield escapeHtml(element.textContent || "");
8562
9088
  return;
8563
9089
  }
8564
- const tag = element.tagName.toLowerCase();
8565
- if (tag === "script" || tag === "style") {
9090
+ const tag = tagNameOf(element).toLowerCase();
9091
+ if (isCodeTextElement(tag)) {
8566
9092
  if (DEV) yield `<!--ssr:${tag}-stripped-->`;
8567
9093
  return;
8568
9094
  }
@@ -8570,29 +9096,17 @@ async function* renderToStream(element) {
8570
9096
  if (DEV) yield "<!--ssr:invalid-tag-->";
8571
9097
  return;
8572
9098
  }
8573
- let openTag = `<${tag}`;
8574
- for (const attr of Array.from(element.attributes)) {
8575
- const rawName = attr.name;
8576
- if (!isSafeAttrName(rawName)) continue;
8577
- if (isEventHandlerAttr(rawName)) continue;
8578
- if (isForbiddenSsrAttr(rawName)) continue;
8579
- const lowerName = rawName.toLowerCase();
8580
- let value = attr.value;
8581
- if (isPolicyAttr(lowerName)) {
8582
- value = sanitizeSsrAttributeValue(lowerName, value);
8583
- if (!value) continue;
8584
- }
8585
- openTag += ` ${rawName}="${escapeAttr(value)}"`;
8586
- }
8587
- if (element.dataset && !element.dataset.sibuHydrate) {
8588
- openTag += ` data-sibu-ssr="true"`;
9099
+ const openTag = serializeOpenTag(element, tag);
9100
+ if (openTag === null) {
9101
+ if (DEV) yield `<!--ssr:${tag}-stripped-->`;
9102
+ return;
8589
9103
  }
8590
9104
  if (VOID_ELEMENTS.has(tag)) {
8591
9105
  yield `${openTag} />`;
8592
9106
  return;
8593
9107
  }
8594
9108
  yield `${openTag}>`;
8595
- for (const child of Array.from(element.childNodes)) {
9109
+ for (const child of Array.from(childNodesOf(element))) {
8596
9110
  try {
8597
9111
  yield* renderToStream(child);
8598
9112
  } catch (err) {
@@ -8777,8 +9291,7 @@ function deserializeState(validate) {
8777
9291
  "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
8778
9292
  );
8779
9293
  }
8780
- const w = window;
8781
- const raw = w[SSR_DATA_ATTR];
9294
+ const raw = readOwnGlobal(SSR_DATA_ATTR);
8782
9295
  if (raw === void 0) return void 0;
8783
9296
  if (validate && !validate(raw)) return void 0;
8784
9297
  return raw;
@@ -9365,7 +9878,7 @@ function setCanonical(url) {
9365
9878
  }
9366
9879
 
9367
9880
  // src/platform/customElement.ts
9368
- var SVG_NS2 = "http://www.w3.org/2000/svg";
9881
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
9369
9882
  var MAX_RENDER_PASSES = 10;
9370
9883
  var ABANDONED_RENDER = /* @__PURE__ */ Symbol("sibujs.defineElement.abandonedRender");
9371
9884
  function defineElement(name, component, options = {}) {
@@ -9514,7 +10027,10 @@ function defineElement(name, component, options = {}) {
9514
10027
  customElements.define(name, SibuElement);
9515
10028
  }
9516
10029
  function svgElement(tag, props = {}, ...nodes) {
9517
- const el = document.createElementNS(SVG_NS2, tag);
10030
+ if (isBlockedElement(tag)) {
10031
+ throw new Error(`svgElement: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
10032
+ }
10033
+ const el = document.createElementNS(SVG_NS3, tag);
9518
10034
  for (const [key, value] of Object.entries(props)) {
9519
10035
  if (key === "nodes") continue;
9520
10036
  if (typeof value === "function" && isEventHandlerAttr(key)) {
@@ -9539,7 +10055,16 @@ function toError(err) {
9539
10055
  if (err !== null && typeof err === "object" && "name" in err && "message" in err) return err;
9540
10056
  return new Error(String(err));
9541
10057
  }
10058
+ function workerSource(fn, api) {
10059
+ if (typeof fn !== "function") {
10060
+ throw new TypeError(
10061
+ `${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.`
10062
+ );
10063
+ }
10064
+ return Function.prototype.toString.call(fn);
10065
+ }
9542
10066
  function worker(workerFn2) {
10067
+ const fnBody = workerSource(workerFn2, "worker");
9543
10068
  const [result, setResult] = signal(null);
9544
10069
  const [error, setError] = signal(null);
9545
10070
  const [loading, setLoading] = signal(false);
@@ -9555,7 +10080,6 @@ function worker(workerFn2) {
9555
10080
  if (typeof Worker === "undefined") {
9556
10081
  throw new Error("Web Workers are not supported in this environment");
9557
10082
  }
9558
- const fnBody = workerFn2.toString();
9559
10083
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
9560
10084
  blobUrl = URL.createObjectURL(blob);
9561
10085
  worker2 = new Worker(blobUrl);
@@ -9604,6 +10128,7 @@ function worker(workerFn2) {
9604
10128
  return { post, result, error, loading, terminate };
9605
10129
  }
9606
10130
  function workerFn(fn) {
10131
+ const fnStr = workerSource(fn, "workerFn");
9607
10132
  const [loading, setLoading] = signal(false);
9608
10133
  let worker2 = null;
9609
10134
  let blobUrl = null;
@@ -9618,7 +10143,6 @@ function workerFn(fn) {
9618
10143
  if (typeof Worker === "undefined") {
9619
10144
  throw new Error("Web Workers are not supported in this environment");
9620
10145
  }
9621
- const fnStr = fn.toString();
9622
10146
  const blob = new Blob(
9623
10147
  [
9624
10148
  `self.onmessage = function(e) {
@@ -9678,6 +10202,7 @@ function workerFn(fn) {
9678
10202
  return { run, loading, terminate };
9679
10203
  }
9680
10204
  function createWorkerPool(workerFn2, poolSize) {
10205
+ const fnBody = workerSource(workerFn2, "createWorkerPool");
9681
10206
  const size = poolSize || typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
9682
10207
  const workers = [];
9683
10208
  const queues = [];
@@ -9736,7 +10261,6 @@ function createWorkerPool(workerFn2, poolSize) {
9736
10261
  if (typeof Worker === "undefined") {
9737
10262
  throw new Error("Web Workers are not supported in this environment");
9738
10263
  }
9739
- const fnBody = workerFn2.toString();
9740
10264
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
9741
10265
  blobUrl = URL.createObjectURL(blob);
9742
10266
  for (let i2 = 0; i2 < size; i2++) {
@@ -10133,11 +10657,7 @@ function bindChildNode(placeholder, getter) {
10133
10657
  }
10134
10658
 
10135
10659
  // src/core/rendering/tagFactory.ts
10136
- var SVG_NS3 = "http://www.w3.org/2000/svg";
10137
- var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
10138
- function isBlockedTag(tag) {
10139
- return BLOCKED_TAGS.has(tag.toLowerCase());
10140
- }
10660
+ var SVG_NS4 = "http://www.w3.org/2000/svg";
10141
10661
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
10142
10662
  "config",
10143
10663
  "location",
@@ -10164,8 +10684,35 @@ function toKebab(prop) {
10164
10684
  }
10165
10685
  function applyStyle(el, style2) {
10166
10686
  if (typeof style2 === "function") {
10687
+ const getter = style2;
10688
+ let written = [];
10689
+ let wroteString = false;
10167
10690
  const teardown = reactiveBinding2(() => {
10168
- el.setAttribute("style", sanitizeStyleAttribute(String(style2()), { element: el }));
10691
+ const value = getter();
10692
+ if (value === null || typeof value !== "object") {
10693
+ written = [];
10694
+ wroteString = true;
10695
+ el.setAttribute("style", sanitizeStyleAttribute(String(value), { element: el }));
10696
+ return;
10697
+ }
10698
+ const decl = el.style;
10699
+ if (wroteString) {
10700
+ el.removeAttribute("style");
10701
+ wroteString = false;
10702
+ }
10703
+ const next = [];
10704
+ for (const prop in value) {
10705
+ if (!Object.hasOwn(value, prop)) continue;
10706
+ const val = value[prop];
10707
+ if (val == null) continue;
10708
+ const name = toKebab(prop);
10709
+ next.push(name);
10710
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
10711
+ }
10712
+ for (let i2 = 0; i2 < written.length; i2++) {
10713
+ if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
10714
+ }
10715
+ written = next;
10169
10716
  }, el);
10170
10717
  registerDisposer(el, teardown);
10171
10718
  return;
@@ -10176,16 +10723,17 @@ function applyStyle(el, style2) {
10176
10723
  }
10177
10724
  const htmlEl = el;
10178
10725
  for (const prop in style2) {
10726
+ if (!Object.hasOwn(style2, prop)) continue;
10179
10727
  const val = style2[prop];
10180
10728
  const name = toKebab(prop);
10181
10729
  if (typeof val === "function") {
10182
10730
  const getter = val;
10183
10731
  const teardown = reactiveBinding2(() => {
10184
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
10732
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
10185
10733
  }, el);
10186
10734
  registerDisposer(el, teardown);
10187
10735
  } else {
10188
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
10736
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
10189
10737
  }
10190
10738
  }
10191
10739
  }
@@ -10196,7 +10744,9 @@ function applyClass(el, cls) {
10196
10744
  }
10197
10745
  if (typeof cls === "function") {
10198
10746
  const teardown = reactiveBinding2(() => {
10199
- el.setAttribute("class", cls());
10747
+ const value = cls();
10748
+ if (value == null || value === false) el.removeAttribute("class");
10749
+ else el.setAttribute("class", value);
10200
10750
  }, el);
10201
10751
  registerDisposer(el, teardown);
10202
10752
  return;
@@ -10205,6 +10755,7 @@ function applyClass(el, cls) {
10205
10755
  let hasReactive = false;
10206
10756
  let result = "";
10207
10757
  for (const name in obj) {
10758
+ if (!Object.hasOwn(obj, name)) continue;
10208
10759
  const val = obj[name];
10209
10760
  if (typeof val === "function") {
10210
10761
  hasReactive = true;
@@ -10216,6 +10767,7 @@ function applyClass(el, cls) {
10216
10767
  const update = () => {
10217
10768
  let r = "";
10218
10769
  for (const name in obj) {
10770
+ if (!Object.hasOwn(obj, name)) continue;
10219
10771
  const val = obj[name];
10220
10772
  const active = typeof val === "function" ? val() : val;
10221
10773
  if (active) r = r ? `${r} ${name}` : name;
@@ -10278,8 +10830,8 @@ function appendChildren(el, nodes) {
10278
10830
  }
10279
10831
  }
10280
10832
  }
10281
- var tagFactory = (tag, ns) => {
10282
- const blocked = isBlockedTag(tag);
10833
+ function tagFactory(tag, ns) {
10834
+ const blocked = isBlockedElement(tag);
10283
10835
  return (first, second) => {
10284
10836
  if (blocked) {
10285
10837
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -10327,7 +10879,9 @@ var tagFactory = (tag, ns) => {
10327
10879
  const pClass = props.class;
10328
10880
  if (pClass != null) applyClass(el, pClass);
10329
10881
  const pId = props.id;
10330
- if (pId != null) {
10882
+ if (typeof pId === "function") {
10883
+ registerDisposer(el, bindAttribute(el, "id", pId));
10884
+ } else if (pId != null) {
10331
10885
  if (DEV && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
10332
10886
  devWarn(
10333
10887
  `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
@@ -10340,6 +10894,7 @@ var tagFactory = (tag, ns) => {
10340
10894
  const pOn = props.on;
10341
10895
  if (pOn) {
10342
10896
  for (const ev in pOn) {
10897
+ if (!Object.hasOwn(pOn, ev)) continue;
10343
10898
  const handler = pOn[ev];
10344
10899
  if (typeof handler === "function") {
10345
10900
  el.addEventListener(ev, handler);
@@ -10366,6 +10921,7 @@ var tagFactory = (tag, ns) => {
10366
10921
  continue;
10367
10922
  // already handled above / below
10368
10923
  default: {
10924
+ if (!Object.hasOwn(props, key)) continue;
10369
10925
  const value = props[key];
10370
10926
  if (value == null) continue;
10371
10927
  if (isEventHandlerAttr(key)) continue;
@@ -10377,12 +10933,10 @@ var tagFactory = (tag, ns) => {
10377
10933
  }
10378
10934
  }
10379
10935
  }
10380
- if (props.onElement && typeof props.onElement === "function") {
10381
- props.onElement(el);
10382
- }
10936
+ if (typeof props.onElement === "function") props.onElement(el);
10383
10937
  return el;
10384
10938
  };
10385
- };
10939
+ }
10386
10940
 
10387
10941
  // src/core/rendering/html.ts
10388
10942
  var html = tagFactory("html");
@@ -10457,7 +11011,7 @@ var param = tagFactory("param");
10457
11011
  var picture = tagFactory("picture");
10458
11012
  var portal = tagFactory("portal");
10459
11013
  var source = tagFactory("source");
10460
- var svg = tagFactory("svg", SVG_NS3);
11014
+ var svg = tagFactory("svg", SVG_NS4);
10461
11015
  var math = tagFactory("math");
10462
11016
  var canvas = tagFactory("canvas");
10463
11017
  var noscript = tagFactory("noscript");
@@ -10498,26 +11052,26 @@ var base = tagFactory("base");
10498
11052
  var link = tagFactory("link");
10499
11053
  var meta = tagFactory("meta");
10500
11054
  var style = tagFactory("style");
10501
- var circle = tagFactory("circle", SVG_NS3);
10502
- var ellipse = tagFactory("ellipse", SVG_NS3);
10503
- var g = tagFactory("g", SVG_NS3);
10504
- var line = tagFactory("line", SVG_NS3);
10505
- var path = tagFactory("path", SVG_NS3);
10506
- var polygon = tagFactory("polygon", SVG_NS3);
10507
- var polyline = tagFactory("polyline", SVG_NS3);
10508
- var rect = tagFactory("rect", SVG_NS3);
10509
- var text = tagFactory("text", SVG_NS3);
10510
- var tspan = tagFactory("tspan", SVG_NS3);
10511
- var defs = tagFactory("defs", SVG_NS3);
10512
- var clipPath = tagFactory("clipPath", SVG_NS3);
10513
- var mask = tagFactory("mask", SVG_NS3);
10514
- var pattern = tagFactory("pattern", SVG_NS3);
10515
- var linearGradient = tagFactory("linearGradient", SVG_NS3);
10516
- var radialGradient = tagFactory("radialGradient", SVG_NS3);
10517
- var stop = tagFactory("stop", SVG_NS3);
10518
- var use = tagFactory("use", SVG_NS3);
10519
- var symbol = tagFactory("symbol", SVG_NS3);
10520
- var marker = tagFactory("marker", SVG_NS3);
11055
+ var circle = tagFactory("circle", SVG_NS4);
11056
+ var ellipse = tagFactory("ellipse", SVG_NS4);
11057
+ var g = tagFactory("g", SVG_NS4);
11058
+ var line = tagFactory("line", SVG_NS4);
11059
+ var path = tagFactory("path", SVG_NS4);
11060
+ var polygon = tagFactory("polygon", SVG_NS4);
11061
+ var polyline = tagFactory("polyline", SVG_NS4);
11062
+ var rect = tagFactory("rect", SVG_NS4);
11063
+ var text = tagFactory("text", SVG_NS4);
11064
+ var tspan = tagFactory("tspan", SVG_NS4);
11065
+ var defs = tagFactory("defs", SVG_NS4);
11066
+ var clipPath = tagFactory("clipPath", SVG_NS4);
11067
+ var mask = tagFactory("mask", SVG_NS4);
11068
+ var pattern = tagFactory("pattern", SVG_NS4);
11069
+ var linearGradient = tagFactory("linearGradient", SVG_NS4);
11070
+ var radialGradient = tagFactory("radialGradient", SVG_NS4);
11071
+ var stop = tagFactory("stop", SVG_NS4);
11072
+ var use = tagFactory("use", SVG_NS4);
11073
+ var symbol = tagFactory("symbol", SVG_NS4);
11074
+ var marker = tagFactory("marker", SVG_NS4);
10521
11075
  var center = tagFactory("center");
10522
11076
  var font = tagFactory("font");
10523
11077
  var marquee = tagFactory("marquee");
@@ -11393,7 +11947,7 @@ var CDN_ENTRY_POINTS = {
11393
11947
  };
11394
11948
  var packageInfo = {
11395
11949
  name: "sibujs",
11396
- version: true ? "4.6.0" : "dev",
11950
+ version: true ? "4.8.0" : "dev",
11397
11951
  entryPoints: MODULE_ENTRY_POINTS,
11398
11952
  /**
11399
11953
  * Generate the package.json `exports` map. Module entries resolve to the
@@ -11538,7 +12092,7 @@ var env = {
11538
12092
  };
11539
12093
 
11540
12094
  // src/plugins/versioning.ts
11541
- var VERSION = true ? "4.6.0" : "dev";
12095
+ var VERSION = true ? "4.8.0" : "dev";
11542
12096
  var NUM = "(?:0|[1-9]\\d*)";
11543
12097
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
11544
12098
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -11821,12 +12375,14 @@ function extractRangeTarget(range) {
11821
12375
  function preloadCritical(resources) {
11822
12376
  if (typeof document === "undefined") return;
11823
12377
  for (const resource2 of resources) {
11824
- const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(resource2.href) : resource2.href.replace(/["\\]/g, "\\$&");
12378
+ const href = sanitizeUrl(resource2.href);
12379
+ if (!href) continue;
12380
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(href) : href.replace(/["\\]/g, "\\$&");
11825
12381
  const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
11826
12382
  if (existing) continue;
11827
12383
  const link2 = document.createElement("link");
11828
12384
  link2.rel = "preload";
11829
- link2.href = resource2.href;
12385
+ link2.href = href;
11830
12386
  link2.setAttribute("as", resource2.as);
11831
12387
  if (resource2.type) {
11832
12388
  link2.type = resource2.type;
@@ -14830,7 +15386,7 @@ var _themeLayers = globalSingleton(
14830
15386
  function commitThemeProperty(root, name, record) {
14831
15387
  const top = record.layers[record.layers.length - 1];
14832
15388
  if (top) {
14833
- root.style.setProperty(name, top.value);
15389
+ root.style.setProperty(name, sanitizeCSSDeclaration(name, String(top.value), { element: root }));
14834
15390
  return;
14835
15391
  }
14836
15392
  _themeLayers.get(root)?.delete(name);