sibujs 4.7.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/browser.cjs +48 -6
  2. package/dist/browser.d.cts +26 -2
  3. package/dist/browser.d.ts +26 -2
  4. package/dist/browser.js +6 -5
  5. package/dist/build.cjs +539 -96
  6. package/dist/build.d.cts +22 -8
  7. package/dist/build.d.ts +22 -8
  8. package/dist/build.js +68 -21
  9. package/dist/cdn.dev.global.js +15 -13
  10. package/dist/cdn.full.dev.global.js +15 -13
  11. package/dist/cdn.full.global.js +14 -12
  12. package/dist/cdn.global.js +14 -12
  13. package/dist/{chunk-Z3OHK6QT.js → chunk-2K3Z3XO4.js} +5 -5
  14. package/dist/chunk-3ACAAQIN.js +224 -0
  15. package/dist/{chunk-WOLJZUFQ.js → chunk-562UVHMM.js} +32 -15
  16. package/dist/{chunk-UOL2ECCS.js → chunk-5MLWTIQE.js} +18 -12
  17. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  18. package/dist/{chunk-3QWBSL5R.js → chunk-7OV5Q2UU.js} +8 -5
  19. package/dist/{chunk-SLM3IA34.js → chunk-CDBKFGXN.js} +1 -1
  20. package/dist/{chunk-4AWA2PVD.js → chunk-CF2KCWRE.js} +10 -5
  21. package/dist/{chunk-BSY63EM6.js → chunk-DRMLIF6M.js} +20 -5
  22. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  23. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  24. package/dist/{chunk-BC2SECJD.js → chunk-FHCZMHHE.js} +1 -1
  25. package/dist/{chunk-XYV3EDB7.js → chunk-FNOK662L.js} +63 -38
  26. package/dist/{chunk-JWKYU5GV.js → chunk-G2WHYEL7.js} +1 -1
  27. package/dist/{chunk-DKXRACVN.js → chunk-K6K5ACO6.js} +6 -6
  28. package/dist/{chunk-VZSG24LS.js → chunk-KG25XMGP.js} +45 -17
  29. package/dist/{chunk-FNJXNGYZ.js → chunk-MPWDSTYK.js} +58 -6
  30. package/dist/{chunk-UNWRJRKC.js → chunk-NQMKWBKU.js} +1 -1
  31. package/dist/{chunk-7LUJQAOJ.js → chunk-QBKCK6MJ.js} +1 -1
  32. package/dist/{chunk-WEQ3DMVL.js → chunk-SBTZEYNI.js} +1 -1
  33. package/dist/{chunk-FQRUXCEE.js → chunk-SYHGGDNW.js} +1 -1
  34. package/dist/{chunk-XZR4PXRE.js → chunk-TV464N2M.js} +13 -10
  35. package/dist/{chunk-RUSSKG6G.js → chunk-X7NRHFZR.js} +1 -1
  36. package/dist/{chunk-2BPG2XDA.js → chunk-XJVTQUIV.js} +18 -36
  37. package/dist/{chunk-ZVL7TY4K.js → chunk-YW6HQKGO.js} +25 -10
  38. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  39. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  40. package/dist/data.cjs +29 -9
  41. package/dist/data.d.cts +23 -4
  42. package/dist/data.d.ts +23 -4
  43. package/dist/data.js +6 -6
  44. package/dist/devtools.cjs +1 -1
  45. package/dist/devtools.js +5 -5
  46. package/dist/ecosystem.cjs +400 -37
  47. package/dist/ecosystem.js +9 -8
  48. package/dist/extras.cjs +672 -269
  49. package/dist/extras.d.cts +2 -2
  50. package/dist/extras.d.ts +2 -2
  51. package/dist/extras.js +24 -23
  52. package/dist/index.cjs +490 -88
  53. package/dist/index.d.cts +34 -2
  54. package/dist/index.d.ts +34 -2
  55. package/dist/index.js +16 -12
  56. package/dist/motion.cjs +1 -1
  57. package/dist/motion.js +3 -3
  58. package/dist/patterns.cjs +10 -7
  59. package/dist/patterns.js +6 -6
  60. package/dist/performance.cjs +31 -2
  61. package/dist/performance.d.cts +12 -2
  62. package/dist/performance.d.ts +12 -2
  63. package/dist/performance.js +8 -6
  64. package/dist/plugins.cjs +571 -240
  65. package/dist/plugins.d.cts +14 -3
  66. package/dist/plugins.d.ts +14 -3
  67. package/dist/plugins.js +35 -37
  68. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  69. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.cts} +6 -0
  70. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.ts} +6 -0
  71. package/dist/ssr.cjs +484 -138
  72. package/dist/ssr.d.cts +59 -5
  73. package/dist/ssr.d.ts +59 -5
  74. package/dist/ssr.js +11 -9
  75. package/dist/testing.cjs +3 -3
  76. package/dist/testing.js +4 -4
  77. package/dist/ui.cjs +609 -241
  78. package/dist/ui.d.cts +1 -1
  79. package/dist/ui.d.ts +1 -1
  80. package/dist/ui.js +9 -8
  81. package/dist/widgets.cjs +1 -1
  82. package/dist/widgets.js +7 -7
  83. package/package.json +5 -4
package/dist/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.7.0" : "dev";
1080
+ var _runtimeVersion = true ? "4.9.0" : "dev";
1081
1081
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
1082
1082
  function resolveReactiveApi() {
1083
1083
  const g2 = globalThis;
@@ -1710,7 +1710,11 @@ function reconcile(prev, next, depth, walk, newRoot) {
1710
1710
  const nextChild = nextRecord[key];
1711
1711
  const hasPrev = Object.hasOwn(prevRecord, key);
1712
1712
  const shared = hasPrev ? reconcile(prevRecord[key], nextChild, depth + 1, walk, false) : nextChild;
1713
- copy[key] = shared;
1713
+ if (key === "__proto__") {
1714
+ Object.defineProperty(copy, key, { value: shared, writable: true, enumerable: true, configurable: true });
1715
+ } else {
1716
+ copy[key] = shared;
1717
+ }
1714
1718
  if (!hasPrev || !Object.is(shared, prevRecord[key])) allFromPrev = false;
1715
1719
  if (!Object.is(shared, nextChild)) allFromNext = false;
1716
1720
  }
@@ -1801,6 +1805,8 @@ function query(key, fetcher, options = {}) {
1801
1805
  structuralSharing = true
1802
1806
  } = options;
1803
1807
  const resolveKey = typeof key === "function" ? key : () => key;
1808
+ const enabledGate = typeof enabled === "function" ? derived(() => Boolean(enabled())) : null;
1809
+ const isEnabled = enabledGate ?? (() => enabled);
1804
1810
  const cache = getActiveQueryCache();
1805
1811
  const [data2, setData] = signal(initialData);
1806
1812
  const [isFetching, setIsFetching] = signal(false);
@@ -1915,7 +1921,7 @@ function query(key, fetcher, options = {}) {
1915
1921
  }
1916
1922
  }
1917
1923
  async function doFetch() {
1918
- if (disposed || !currentKey || !enabled) return;
1924
+ if (disposed || !currentKey || !untracked2(isEnabled)) return;
1919
1925
  const key2 = currentKey;
1920
1926
  const entry = getOrCreateEntry(cache, key2);
1921
1927
  attachToEntry(entry, key2);
@@ -2017,10 +2023,17 @@ function query(key, fetcher, options = {}) {
2017
2023
  }
2018
2024
  const effectCleanup = effect(() => {
2019
2025
  const key2 = resolveKey();
2026
+ const on = isEnabled();
2020
2027
  const keyChanged = currentKey !== key2;
2021
2028
  currentKey = key2;
2029
+ if (key2 === "") {
2030
+ detachFromEntry();
2031
+ setIsFetching(false);
2032
+ return;
2033
+ }
2022
2034
  const entry = getOrCreateEntry(cache, key2, initialData);
2023
2035
  attachToEntry(entry, key2);
2036
+ if (keyChanged) setIsFetching(entry.promise !== null);
2024
2037
  if (entry.data !== void 0) {
2025
2038
  const view = viewOf(entry);
2026
2039
  batch(() => {
@@ -2031,17 +2044,17 @@ function query(key, fetcher, options = {}) {
2031
2044
  }
2032
2045
  if (!keyChanged && currentKey === key2 && entry.data !== void 0) {
2033
2046
  const isDataStale2 = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
2034
- if (enabled && isDataStale2 && !entry.promise) doFetch();
2047
+ if (on && isDataStale2 && !entry.promise) doFetch();
2035
2048
  return;
2036
2049
  }
2037
2050
  const isDataStale = entry.dataUpdatedAt === 0 || Date.now() - entry.dataUpdatedAt >= staleTime;
2038
- if (enabled && (entry.data === void 0 || isDataStale)) {
2051
+ if (on && (entry.data === void 0 || isDataStale)) {
2039
2052
  doFetch();
2040
2053
  }
2041
2054
  });
2042
2055
  if (refetchInterval && refetchInterval > 0) {
2043
2056
  intervalTimer = setInterval(() => {
2044
- if (!disposed && currentKey && enabled) doFetch();
2057
+ if (!disposed && currentKey) doFetch();
2045
2058
  }, refetchInterval);
2046
2059
  }
2047
2060
  let focusHandler = null;
@@ -2049,13 +2062,13 @@ function query(key, fetcher, options = {}) {
2049
2062
  if (typeof globalThis !== "undefined" && typeof globalThis.addEventListener === "function") {
2050
2063
  if (refetchOnWindowFocus) {
2051
2064
  focusHandler = () => {
2052
- if (!disposed && currentKey && enabled) doFetch();
2065
+ if (!disposed && currentKey) doFetch();
2053
2066
  };
2054
2067
  globalThis.addEventListener("focus", focusHandler);
2055
2068
  }
2056
2069
  if (refetchOnReconnect) {
2057
2070
  onlineHandler = () => {
2058
- if (!disposed && currentKey && enabled) doFetch();
2071
+ if (!disposed && currentKey) doFetch();
2059
2072
  };
2060
2073
  globalThis.addEventListener("online", onlineHandler);
2061
2074
  }
@@ -2067,6 +2080,7 @@ function query(key, fetcher, options = {}) {
2067
2080
  stopSelect?.();
2068
2081
  loading.dispose();
2069
2082
  isStale.dispose();
2083
+ enabledGate?.dispose();
2070
2084
  if (intervalTimer) clearInterval(intervalTimer);
2071
2085
  detachFromEntry();
2072
2086
  if (focusHandler && typeof globalThis.removeEventListener === "function") {
@@ -2246,6 +2260,8 @@ function infiniteQuery(key, fetcher, options) {
2246
2260
  onError
2247
2261
  } = options;
2248
2262
  const resolveKey = typeof key === "function" ? key : () => key;
2263
+ const enabledGate = typeof enabled === "function" ? derived(() => Boolean(enabled())) : null;
2264
+ const isEnabled = enabledGate ?? (() => enabled);
2249
2265
  const [pages, setPages] = signal([]);
2250
2266
  const [isFetching, setIsFetching] = signal(false);
2251
2267
  const [isFetchingNext, setIsFetchingNext] = signal(false);
@@ -2324,7 +2340,7 @@ function infiniteQuery(key, fetcher, options) {
2324
2340
  }
2325
2341
  const effectCleanup = effect(() => {
2326
2342
  resolveKey();
2327
- if (enabled) {
2343
+ if (isEnabled()) {
2328
2344
  abortController?.abort();
2329
2345
  batch(() => {
2330
2346
  setPages([]);
@@ -2365,6 +2381,7 @@ function infiniteQuery(key, fetcher, options) {
2365
2381
  loading.dispose();
2366
2382
  hasNextPage.dispose();
2367
2383
  hasPreviousPage.dispose();
2384
+ enabledGate?.dispose();
2368
2385
  }
2369
2386
  return {
2370
2387
  data: data2,
@@ -3075,8 +3092,10 @@ function socket(url, options) {
3075
3092
  function stripControlChars(value) {
3076
3093
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
3077
3094
  }
3095
+ var ASCII_UPPER = /[A-Z]/;
3096
+ var ASCII_UPPER_G = /[A-Z]/g;
3078
3097
  function canonicalAttrName(name) {
3079
- return name.replace(/[A-Z]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32));
3098
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
3080
3099
  }
3081
3100
  function isEventHandlerAttr(name) {
3082
3101
  if (name.length < 3) return false;
@@ -3089,6 +3108,9 @@ function sanitizeUrl(url) {
3089
3108
  const probe = stripControlChars(url).trim();
3090
3109
  if (!probe) return "";
3091
3110
  const output2 = url.replace(OUTER_TRIM, "");
3111
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
3112
+ return "";
3113
+ }
3092
3114
  const lower = probe.toLowerCase();
3093
3115
  let schemeEnd = -1;
3094
3116
  for (let i2 = 0; i2 < lower.length; i2++) {
@@ -3105,19 +3127,58 @@ function sanitizeUrl(url) {
3105
3127
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
3106
3128
  return output2;
3107
3129
  }
3108
- var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
3130
+ var SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
3131
+ function isSrcsetWhitespace(ch) {
3132
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
3133
+ }
3109
3134
  function sanitizeSrcset(value) {
3110
- const parts = value.split(",");
3135
+ const input2 = String(value);
3111
3136
  const out = [];
3112
- for (let i2 = 0; i2 < parts.length; i2++) {
3113
- const part = parts[i2].trim();
3114
- if (!part) continue;
3115
- const m = part.match(/^(\S+)(\s+.+)?$/);
3116
- if (!m) continue;
3117
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
3118
- const safe = sanitizeUrl(m[1]);
3137
+ let i2 = 0;
3138
+ const n = input2.length;
3139
+ while (i2 < n) {
3140
+ while (i2 < n && (isSrcsetWhitespace(input2[i2]) || input2[i2] === ",")) i2++;
3141
+ if (i2 >= n) break;
3142
+ const urlStart = i2;
3143
+ while (i2 < n && !isSrcsetWhitespace(input2[i2])) i2++;
3144
+ let url = input2.slice(urlStart, i2);
3145
+ const descriptors = [];
3146
+ if (url.endsWith(",")) {
3147
+ url = url.replace(/,+$/, "");
3148
+ } else {
3149
+ let current = "";
3150
+ let inParens = false;
3151
+ while (i2 < n) {
3152
+ const ch = input2[i2];
3153
+ if (inParens) {
3154
+ current += ch;
3155
+ if (ch === ")") inParens = false;
3156
+ i2++;
3157
+ continue;
3158
+ }
3159
+ if (isSrcsetWhitespace(ch)) {
3160
+ if (current) descriptors.push(current);
3161
+ current = "";
3162
+ i2++;
3163
+ continue;
3164
+ }
3165
+ if (ch === ",") {
3166
+ i2++;
3167
+ break;
3168
+ }
3169
+ if (ch === "(") inParens = true;
3170
+ current += ch;
3171
+ i2++;
3172
+ }
3173
+ if (current) descriptors.push(current);
3174
+ }
3175
+ if (url === "") continue;
3176
+ if (descriptors.length > 1) continue;
3177
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
3178
+ const safe = sanitizeUrl(url);
3119
3179
  if (!safe) continue;
3120
- out.push(m[2] ? `${safe}${m[2]}` : safe);
3180
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
3181
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
3121
3182
  }
3122
3183
  return out.join(", ");
3123
3184
  }
@@ -3176,8 +3237,16 @@ var BLOCKED_CSS_NEEDLES = [
3176
3237
  "behavior:",
3177
3238
  "@import",
3178
3239
  "image-set(",
3179
- "filter:progid"
3240
+ "filter:progid",
3241
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
3242
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
3243
+ // today, which is exactly when a blocklist should get ahead of it.
3244
+ "image(",
3245
+ "src("
3180
3246
  ];
3247
+ var CSS_REASON_MOZ_BINDING = 4;
3248
+ var CSS_REASON_BEHAVIOR = 5;
3249
+ var CSS_REASON_PROGID = 8;
3181
3250
  function describeElement(el) {
3182
3251
  if (!el) return "an element";
3183
3252
  let out = el.tagName.toLowerCase();
@@ -3201,7 +3270,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
3201
3270
  "behavior: loads and runs a remote HTC script",
3202
3271
  "@import pulls in a remote stylesheet",
3203
3272
  "image-set() fetches remote images, the same exfiltration channel as url()",
3204
- "filter:progid activates legacy scriptable filters"
3273
+ "filter:progid activates legacy scriptable filters",
3274
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
3275
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
3205
3276
  ];
3206
3277
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
3207
3278
  const where = describeElement(ctx?.element);
@@ -3216,27 +3287,145 @@ function warnDroppedDeclaration(value, reason, ctx) {
3216
3287
  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.`;
3217
3288
  });
3218
3289
  }
3290
+ function normalizeCssForScan(value) {
3291
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
3292
+ return normalized.toLowerCase().replace(/\s+/g, "");
3293
+ }
3219
3294
  function blockedCssReason(value) {
3220
3295
  if (!CSS_DANGER_GATE.test(value)) return -1;
3221
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
3222
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
3296
+ const lower = normalizeCssForScan(value);
3223
3297
  for (let i2 = 0; i2 < BLOCKED_CSS_NEEDLES.length; i2++) {
3224
3298
  if (lower.includes(BLOCKED_CSS_NEEDLES[i2])) return i2;
3225
3299
  }
3226
3300
  return -1;
3227
3301
  }
3228
- function sanitizeCSSValue(value, context) {
3229
- const reason = blockedCssReason(value);
3302
+ var STYLESHEET_BLOCKED_FUNCTIONS = ["url", "image-set", "image", "src", "expression"];
3303
+ function stripCssFunctions(css) {
3304
+ const pattern2 = new RegExp(`(?:-webkit-)?(${STYLESHEET_BLOCKED_FUNCTIONS.join("|")})\\s*\\(`, "gi");
3305
+ let out = "";
3306
+ let last = 0;
3307
+ for (let match = pattern2.exec(css); match !== null; match = pattern2.exec(css)) {
3308
+ const before = match.index > 0 ? css[match.index - 1] : "";
3309
+ if (/[\w-]/.test(before)) continue;
3310
+ let depth = 1;
3311
+ let i2 = pattern2.lastIndex;
3312
+ let quote2 = "";
3313
+ for (; i2 < css.length && depth > 0; i2++) {
3314
+ const ch = css[i2];
3315
+ if (quote2) {
3316
+ if (ch === "\\") i2++;
3317
+ else if (ch === quote2) quote2 = "";
3318
+ } else if (ch === '"' || ch === "'") quote2 = ch;
3319
+ else if (ch === "(") depth++;
3320
+ else if (ch === ")") depth--;
3321
+ }
3322
+ out += `${css.slice(last, match.index)}/* ${match[1].toLowerCase()}() removed */`;
3323
+ last = i2;
3324
+ pattern2.lastIndex = i2;
3325
+ }
3326
+ return out + css.slice(last);
3327
+ }
3328
+ function sanitizeStylesheetText(css) {
3329
+ let text2 = css.includes("\\") ? decodeCssEscapes(css) : css;
3330
+ text2 = text2.replace(/@import\b[^;{}]*;?/gi, "/* @import removed */");
3331
+ text2 = stripCssFunctions(text2);
3332
+ text2 = text2.replace(/(^|[;{\s])behavior\s*:[^;}]*;?/gi, "$1/* behavior removed */");
3333
+ text2 = text2.replace(/(^|[;{\s])-moz-binding\s*:[^;}]*;?/gi, "$1/* -moz-binding removed */");
3334
+ text2 = text2.replace(/(^|[;{\s])(-ms-)?filter\s*:[^;}]*progid[^;}]*;?/gi, "$1/* filter:progid removed */");
3335
+ return text2;
3336
+ }
3337
+ function sanitizeCSSDeclaration(property, value, context) {
3338
+ const reason = blockedDeclarationReason(property, value);
3230
3339
  if (reason === -1) return value;
3231
- warnDroppedDeclaration(value, reason, context);
3340
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
3232
3341
  return "";
3233
3342
  }
3343
+ function blockedDeclarationReason(property, value) {
3344
+ const reason = blockedCssReason(value);
3345
+ if (reason !== -1) return reason;
3346
+ const name = canonicalAttrName(property);
3347
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
3348
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
3349
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
3350
+ return CSS_REASON_PROGID;
3351
+ }
3352
+ return -1;
3353
+ }
3354
+ function parseStyleDeclarations(css) {
3355
+ const chunks = [];
3356
+ let buf = "";
3357
+ let depth = 0;
3358
+ let quote2 = "";
3359
+ for (let i2 = 0; i2 < css.length; i2++) {
3360
+ const ch = css[i2];
3361
+ if (quote2) {
3362
+ buf += ch;
3363
+ if (ch === "\\" && i2 + 1 < css.length) buf += css[++i2];
3364
+ else if (ch === quote2) quote2 = "";
3365
+ continue;
3366
+ }
3367
+ if (ch === "\\") {
3368
+ buf += ch;
3369
+ if (i2 + 1 < css.length) buf += css[++i2];
3370
+ continue;
3371
+ }
3372
+ if (ch === "/" && css[i2 + 1] === "*") {
3373
+ const end = css.indexOf("*/", i2 + 2);
3374
+ if (end === -1) return null;
3375
+ i2 = end + 1;
3376
+ continue;
3377
+ }
3378
+ if (ch === '"' || ch === "'") quote2 = ch;
3379
+ else if (ch === "(" || ch === "[") depth++;
3380
+ else if (ch === ")" || ch === "]") {
3381
+ if (--depth < 0) return null;
3382
+ } else if (ch === "{" || ch === "}") return null;
3383
+ else if (ch === ";" && depth === 0) {
3384
+ chunks.push(buf);
3385
+ buf = "";
3386
+ continue;
3387
+ }
3388
+ buf += ch;
3389
+ }
3390
+ if (quote2 || depth !== 0) return null;
3391
+ chunks.push(buf);
3392
+ const out = [];
3393
+ for (const chunk of chunks) {
3394
+ const colon = chunk.indexOf(":");
3395
+ if (colon === -1) continue;
3396
+ const rawName = chunk.slice(0, colon).trim();
3397
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
3398
+ let value = chunk.slice(colon + 1).trim();
3399
+ const bang = /!\s*important\s*$/i.exec(value);
3400
+ if (bang) value = value.slice(0, bang.index).trim();
3401
+ if (value === "") continue;
3402
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
3403
+ out.push([property, value, bang !== null]);
3404
+ }
3405
+ return out;
3406
+ }
3407
+ function sanitizeStyleDeclarationList(cssText, context) {
3408
+ const input2 = String(cssText);
3409
+ const declarations = parseStyleDeclarations(input2);
3410
+ if (declarations === null) {
3411
+ if (blockedCssReason(input2) !== -1) warnDroppedDeclaration(input2, blockedCssReason(input2), context);
3412
+ return "";
3413
+ }
3414
+ const kept = [];
3415
+ for (const [property, value, important] of declarations) {
3416
+ const reason = blockedDeclarationReason(property, value);
3417
+ if (reason !== -1) {
3418
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
3419
+ continue;
3420
+ }
3421
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
3422
+ }
3423
+ return kept.join("; ");
3424
+ }
3234
3425
  function sanitizeStyleAttribute(cssText, context) {
3235
3426
  const input2 = String(cssText);
3236
3427
  if (input2.trim() === "") return "";
3237
- if (typeof document === "undefined") {
3238
- return sanitizeCSSValue(input2, context) === "" ? "" : input2;
3239
- }
3428
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input2, context);
3240
3429
  const probe = document.createElement("div");
3241
3430
  try {
3242
3431
  probe.style.cssText = input2;
@@ -3247,8 +3436,7 @@ function sanitizeStyleAttribute(cssText, context) {
3247
3436
  for (let i2 = 0; i2 < probe.style.length; i2++) {
3248
3437
  const property = probe.style[i2];
3249
3438
  const value = probe.style.getPropertyValue(property);
3250
- let reason = blockedCssReason(value);
3251
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
3439
+ const reason = blockedDeclarationReason(property, value);
3252
3440
  if (reason !== -1) {
3253
3441
  warnDroppedDeclaration(value, reason, { property, element: context?.element });
3254
3442
  continue;
@@ -3296,6 +3484,9 @@ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
3296
3484
  function isHtmlContentAttribute(name) {
3297
3485
  return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
3298
3486
  }
3487
+ function isUrlAttribute(attr) {
3488
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
3489
+ }
3299
3490
  function isPolicyAttribute(name) {
3300
3491
  const lower = name.toLowerCase();
3301
3492
  return lower === "style" || URL_ATTRIBUTES.has(lower);
@@ -3307,6 +3498,11 @@ function sanitizeAttributeString(attr, value, context) {
3307
3498
  if (lower === "style") return sanitizeStyleAttribute(value, context);
3308
3499
  return value;
3309
3500
  }
3501
+ function resolveAttributeValue(attr, value, context) {
3502
+ if (!isPolicyAttribute(attr)) return value;
3503
+ const safe = sanitizeAttributeString(attr, value, context);
3504
+ return safe === "" ? null : safe;
3505
+ }
3310
3506
 
3311
3507
  // src/ui/stream.ts
3312
3508
  function validateSseUrl(raw) {
@@ -3730,6 +3926,13 @@ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototyp
3730
3926
  function isUnsafeKey(key) {
3731
3927
  return UNSAFE_KEYS.has(key);
3732
3928
  }
3929
+ function readOwnGlobal(key) {
3930
+ if (typeof window === "undefined") return void 0;
3931
+ if (!Object.hasOwn(window, key)) return void 0;
3932
+ const value = window[key];
3933
+ if (typeof Node !== "undefined" && value instanceof Node) return void 0;
3934
+ return value;
3935
+ }
3733
3936
  function stripUnsafeKeys(obj) {
3734
3937
  const out = {};
3735
3938
  for (const k of Object.keys(obj)) {
@@ -4047,13 +4250,19 @@ function colorScheme() {
4047
4250
  }
4048
4251
 
4049
4252
  // src/browser/format.ts
4253
+ var I18N_STATE = /* @__PURE__ */ Symbol.for("sibujs.i18n.v1");
4254
+ function activeI18nLocale() {
4255
+ const state2 = globalThis[I18N_STATE];
4256
+ if (!state2) return void 0;
4257
+ return state2.getLocale ? state2.getLocale() : state2.locale?.[0]();
4258
+ }
4050
4259
  function formatNumber(value, options) {
4051
4260
  const { locale, ...formatOptions } = options ?? {};
4052
- return new Intl.NumberFormat(locale, formatOptions).format(value);
4261
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), formatOptions).format(value);
4053
4262
  }
4054
4263
  function formatCurrency(value, currency, options) {
4055
4264
  const { locale, ...formatOptions } = options ?? {};
4056
- return new Intl.NumberFormat(locale, {
4265
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), {
4057
4266
  ...formatOptions,
4058
4267
  style: "currency",
4059
4268
  currency
@@ -4647,6 +4856,7 @@ function keyboard(options = {}) {
4647
4856
  };
4648
4857
  }
4649
4858
  const target = options.target ?? window;
4859
+ const view = options.target?.ownerDocument?.defaultView ?? window;
4650
4860
  const filter = options.keys ? new Set(options.keys) : null;
4651
4861
  const onDown = (e) => {
4652
4862
  if (filter && !filter.has(e.key)) return;
@@ -4669,14 +4879,14 @@ function keyboard(options = {}) {
4669
4879
  const onBlur = () => setPressed(/* @__PURE__ */ new Set());
4670
4880
  target.addEventListener("keydown", onDown);
4671
4881
  target.addEventListener("keyup", onUp);
4672
- window.addEventListener("blur", onBlur);
4882
+ view.addEventListener("blur", onBlur);
4673
4883
  function isPressed(key) {
4674
4884
  return pressed().has(key);
4675
4885
  }
4676
4886
  function dispose2() {
4677
4887
  target.removeEventListener("keydown", onDown);
4678
4888
  target.removeEventListener("keyup", onUp);
4679
- window.removeEventListener("blur", onBlur);
4889
+ view.removeEventListener("blur", onBlur);
4680
4890
  }
4681
4891
  return { pressed, isPressed, dispose: dispose2 };
4682
4892
  }
@@ -5022,6 +5232,11 @@ function imageLoader(src) {
5022
5232
  abandonCurrent();
5023
5233
  resetState();
5024
5234
  if (disposed || token !== startToken) return;
5235
+ const safe = sanitizeUrl(String(url));
5236
+ if (!safe) {
5237
+ setStatus("error");
5238
+ return;
5239
+ }
5025
5240
  const img2 = new Image();
5026
5241
  current = img2;
5027
5242
  currentSettled = false;
@@ -5040,7 +5255,7 @@ function imageLoader(src) {
5040
5255
  currentSettled = true;
5041
5256
  setStatus("error");
5042
5257
  };
5043
- img2.src = url;
5258
+ img2.src = safe;
5044
5259
  }
5045
5260
  let srcEffectTeardown = null;
5046
5261
  if (typeof src === "function") {
@@ -5066,6 +5281,9 @@ function imageLoader(src) {
5066
5281
  }
5067
5282
 
5068
5283
  // src/patterns/machine.ts
5284
+ function ownEntry(table2, key) {
5285
+ return Object.hasOwn(table2, key) ? table2[key] : void 0;
5286
+ }
5069
5287
  function machine(config) {
5070
5288
  const [state2, setState] = signal(config.initial);
5071
5289
  const [context, setContext] = signal(config.context || {});
@@ -5087,9 +5305,9 @@ function machine(config) {
5087
5305
  }
5088
5306
  }
5089
5307
  function step(event) {
5090
- const stateDef = config.states[currentState];
5308
+ const stateDef = ownEntry(config.states, currentState);
5091
5309
  if (!stateDef?.on) return;
5092
- const transition2 = stateDef.on[event];
5310
+ const transition2 = ownEntry(stateDef.on, event);
5093
5311
  if (!transition2) return;
5094
5312
  let target;
5095
5313
  let guard;
@@ -5117,7 +5335,7 @@ function machine(config) {
5117
5335
  if (action) setContext(nextContext);
5118
5336
  setState(target);
5119
5337
  });
5120
- const targetDef = config.states[target];
5338
+ const targetDef = ownEntry(config.states, target);
5121
5339
  if (targetDef?.entry) {
5122
5340
  targetDef.entry(currentContext);
5123
5341
  }
@@ -5127,7 +5345,7 @@ function machine(config) {
5127
5345
  if (processing) return;
5128
5346
  drain();
5129
5347
  }
5130
- const initialDef = config.states[config.initial];
5348
+ const initialDef = ownEntry(config.states, config.initial);
5131
5349
  if (initialDef?.entry) {
5132
5350
  processing = true;
5133
5351
  try {
@@ -5145,9 +5363,9 @@ function machine(config) {
5145
5363
  }
5146
5364
  function can(event) {
5147
5365
  const currentState2 = state2();
5148
- const stateDef = config.states[currentState2];
5366
+ const stateDef = ownEntry(config.states, currentState2);
5149
5367
  if (!stateDef?.on) return false;
5150
- const transition2 = stateDef.on[event];
5368
+ const transition2 = ownEntry(stateDef.on, event);
5151
5369
  if (!transition2) return false;
5152
5370
  if (typeof transition2 === "string") return true;
5153
5371
  if (transition2.guard) {
@@ -7224,31 +7442,6 @@ function announce(message, priority = "polite") {
7224
7442
 
7225
7443
  // src/ui/scopedStyle.ts
7226
7444
  var _scope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.scopedStyle.v1"), () => ({ n: 0 }));
7227
- function decodeCssEscapes2(css) {
7228
- return css.replace(/\\([0-9a-f]{1,6})[ \t\n\r\f]?|\\([^\n])/gi, (_match, hex, ch) => {
7229
- if (hex) {
7230
- const code2 = Number.parseInt(hex, 16);
7231
- if (Number.isFinite(code2) && code2 > 0 && code2 <= 1114111) {
7232
- try {
7233
- return String.fromCodePoint(code2);
7234
- } catch {
7235
- return "";
7236
- }
7237
- }
7238
- return "";
7239
- }
7240
- return ch || "";
7241
- });
7242
- }
7243
- function sanitizeCSS(css) {
7244
- let sanitized = decodeCssEscapes2(css);
7245
- sanitized = sanitized.replace(/@import\s+[^;]+;/gi, "/* @import removed */");
7246
- sanitized = sanitized.replace(/url\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* url() removed */");
7247
- sanitized = sanitized.replace(/expression\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* expression() removed */");
7248
- sanitized = sanitized.replace(/-moz-binding\s*:[^;]+;/gi, "/* -moz-binding removed */");
7249
- sanitized = sanitized.replace(/behavior\s*:[^;]+;/gi, "/* behavior removed */");
7250
- return sanitized;
7251
- }
7252
7445
  function firstCompoundEnd(selector) {
7253
7446
  let parens = 0;
7254
7447
  let brackets = 0;
@@ -7410,7 +7603,7 @@ function scopeStylesheet(css, attr) {
7410
7603
  function scopedStyle(css) {
7411
7604
  const id = `sibu-s${_scope.n++}`;
7412
7605
  const attr = `data-${id}`;
7413
- const safeCss = sanitizeCSS(css);
7606
+ const safeCss = sanitizeStylesheetText(css);
7414
7607
  const scopedCSS = scopeStylesheet(safeCss, attr);
7415
7608
  if (typeof document !== "undefined") {
7416
7609
  const styleEl = document.createElement("style");
@@ -7436,26 +7629,246 @@ function removeScopedStyle(scopeId) {
7436
7629
  if (el) el.remove();
7437
7630
  }
7438
7631
 
7439
- // src/utils/setSafeAttribute.ts
7632
+ // src/utils/metaRefresh.ts
7633
+ function isAsciiWhitespace(ch) {
7634
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
7635
+ }
7636
+ function trimAsciiWhitespace(value) {
7637
+ let start = 0;
7638
+ let end = value.length;
7639
+ while (start < end && isAsciiWhitespace(value[start])) start++;
7640
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
7641
+ return value.slice(start, end);
7642
+ }
7643
+ var forbidden = (reason) => ({ kind: "forbidden", reason });
7644
+ function parseMetaRefreshContent(content) {
7645
+ const raw = trimAsciiWhitespace(stripControlChars(content));
7646
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
7647
+ let i2 = 0;
7648
+ while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
7649
+ if (i2 === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
7650
+ const delay = Number.parseInt(raw.slice(0, i2), 10);
7651
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
7652
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7653
+ if (i2 === raw.length) return { kind: "delay-only", delay };
7654
+ if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
7655
+ i2++;
7656
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7657
+ const keyStart = i2;
7658
+ while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
7659
+ const key = raw.slice(keyStart, i2).toLowerCase();
7660
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
7661
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7662
+ if (raw[i2] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
7663
+ i2++;
7664
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7665
+ let destination;
7666
+ const quote2 = raw[i2];
7667
+ if (quote2 === '"' || quote2 === "'") {
7668
+ const close = raw.indexOf(quote2, i2 + 1);
7669
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
7670
+ destination = raw.slice(i2 + 1, close);
7671
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
7672
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
7673
+ } else {
7674
+ destination = trimAsciiWhitespace(raw.slice(i2));
7675
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
7676
+ }
7677
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
7678
+ const sanitizedDestination = sanitizeUrl(destination);
7679
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
7680
+ return { kind: "allowed", delay, destination, sanitizedDestination };
7681
+ }
7682
+ function resolveMetaRefreshPolicy(attributes) {
7683
+ let httpEquiv;
7684
+ let content;
7685
+ let duplicateName;
7686
+ const seen = /* @__PURE__ */ new Set();
7687
+ for (const [key, value] of attributes) {
7688
+ const canonical = canonicalAttrName(key);
7689
+ if (seen.has(canonical)) duplicateName = canonical;
7690
+ seen.add(canonical);
7691
+ if (canonical === "http-equiv") httpEquiv = value;
7692
+ else if (canonical === "content") content = value;
7693
+ }
7694
+ if (duplicateName !== void 0) {
7695
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
7696
+ }
7697
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
7698
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
7699
+ return { kind: "not-refresh" };
7700
+ }
7701
+ if (typeof content !== "string") return { kind: "not-refresh" };
7702
+ return parseMetaRefreshContent(content);
7703
+ }
7704
+ function normalizeMetaAttributes(props) {
7705
+ const out = /* @__PURE__ */ new Map();
7706
+ const seen = /* @__PURE__ */ new Set();
7707
+ for (const key of Object.keys(props)) {
7708
+ const canonical = canonicalAttrName(key);
7709
+ if (seen.has(canonical)) return null;
7710
+ seen.add(canonical);
7711
+ out.set(key, props[key]);
7712
+ }
7713
+ return out;
7714
+ }
7715
+ function isForbiddenMetaEntry(props) {
7716
+ const normalized = normalizeMetaAttributes(props);
7717
+ if (normalized === null) return true;
7718
+ return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
7719
+ }
7720
+
7721
+ // src/utils/elementPolicy.ts
7440
7722
  var HTML_NS = "http://www.w3.org/1999/xhtml";
7441
7723
  var SVG_NS = "http://www.w3.org/2000/svg";
7724
+ var BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
7725
+ function canonicalTagName(tag) {
7726
+ return canonicalAttrName(tag);
7727
+ }
7728
+ function isBlockedElement(tag) {
7729
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
7730
+ }
7731
+ var CODE_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
7732
+ function isCodeTextElement(tag) {
7733
+ return CODE_TEXT_ELEMENTS.has(canonicalTagName(tag));
7734
+ }
7735
+ var ContextualRefusal = {
7736
+ None: 0,
7737
+ /** The element would carry a refresh directive the shared policy forbids. */
7738
+ MetaRefreshForbidden: 1,
7739
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
7740
+ MetaRefreshReactive: 2,
7741
+ /** A runtime value may not choose which program a `<script>` runs. */
7742
+ ScriptSource: 3,
7743
+ /** An SVG animation may not target a URL, handler or document attribute. */
7744
+ AnimationTarget: 4,
7745
+ /** A runtime-written `href` may not become an applied stylesheet. */
7746
+ StylesheetSource: 5
7747
+ };
7748
+ var SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
7749
+ var SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
7750
+ var reactiveMetaElements = /* @__PURE__ */ new WeakSet();
7751
+ function isHtmlNamespace(el) {
7752
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
7753
+ }
7754
+ function metaSnapshot(el, canonical, value) {
7755
+ const snapshot = /* @__PURE__ */ new Map();
7756
+ const attrs = el.attributes;
7757
+ for (let i2 = 0; i2 < attrs.length; i2++) {
7758
+ const attr = attrs[i2];
7759
+ if (attr.name === canonical) continue;
7760
+ snapshot.set(attr.name, attr.value);
7761
+ }
7762
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
7763
+ return snapshot;
7764
+ }
7765
+ function claimReactiveMeta(el) {
7766
+ if (reactiveMetaElements.has(el)) return;
7767
+ reactiveMetaElements.add(el);
7768
+ if (el.getAttribute("content") === null) return;
7769
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
7770
+ el.removeAttribute("content");
7771
+ }
7772
+ }
7773
+ function metaRefusal(el, name, value, origin) {
7774
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
7775
+ const canonical = canonicalAttrName(name);
7776
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
7777
+ if (origin === "reactive") claimReactiveMeta(el);
7778
+ if (value === null) return ContextualRefusal.None;
7779
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
7780
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
7781
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
7782
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
7783
+ return ContextualRefusal.None;
7784
+ }
7785
+ function animationRefusal(name, value) {
7786
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
7787
+ const target = stripControlChars(value);
7788
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
7789
+ }
7790
+ var runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
7791
+ function relHasStylesheet(rel) {
7792
+ if (rel === null) return false;
7793
+ const lower = canonicalAttrName(rel);
7794
+ if (!lower.includes("stylesheet")) return false;
7795
+ const tokens = lower.split(/[\t\n\f\r ]+/);
7796
+ for (let i2 = 0; i2 < tokens.length; i2++) if (tokens[i2] === "stylesheet") return true;
7797
+ return false;
7798
+ }
7799
+ function stylesheetRefusal(el, name, value, origin) {
7800
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
7801
+ const canonical = canonicalAttrName(name);
7802
+ if (canonical === "href") {
7803
+ if (origin === "static" || value === null) return ContextualRefusal.None;
7804
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
7805
+ runtimeHrefLinks.add(el);
7806
+ return ContextualRefusal.None;
7807
+ }
7808
+ if (canonical === "rel") {
7809
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
7810
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
7811
+ }
7812
+ return ContextualRefusal.None;
7813
+ }
7814
+ function contextualAttributeRefusal(el, name, value, origin) {
7815
+ const local = el.localName;
7816
+ if (local === "meta") return metaRefusal(el, name, value, origin);
7817
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
7818
+ if (local === "script") {
7819
+ if (origin === "static" || value === null) return ContextualRefusal.None;
7820
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
7821
+ }
7822
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
7823
+ return ContextualRefusal.None;
7824
+ }
7825
+ function settleAttributeProvenance(el, name, origin) {
7826
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
7827
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
7828
+ else runtimeHrefLinks.delete(el);
7829
+ }
7830
+ function isForbiddenMetaSnapshot(attributes) {
7831
+ return resolveMetaRefreshPolicy(attributes).kind === "forbidden";
7832
+ }
7833
+
7834
+ // src/utils/setSafeAttribute.ts
7835
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
7836
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
7442
7837
  var XLINK_NS = "http://www.w3.org/1999/xlink";
7443
7838
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
7444
7839
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
7445
7840
  function isHtmlElement(el) {
7446
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
7841
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
7842
+ }
7843
+ function warnContextualRefusal(label2, name, refusal) {
7844
+ devWarnLazy(() => {
7845
+ const reasons = [
7846
+ "",
7847
+ '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)',
7848
+ "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",
7849
+ "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",
7850
+ "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",
7851
+ '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'
7852
+ ];
7853
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
7854
+ });
7447
7855
  }
7448
7856
  function setProp(el, key, val) {
7449
7857
  el[key] = val;
7450
7858
  }
7451
7859
  function namespaceFor(el, name) {
7452
- if (el.namespaceURI !== SVG_NS) return null;
7860
+ if (el.namespaceURI !== SVG_NS2) return null;
7453
7861
  const lower = name.toLowerCase();
7454
7862
  if (lower.startsWith("xlink:")) return XLINK_NS;
7455
7863
  if (lower.startsWith("xml:")) return XML_NS;
7456
7864
  return null;
7457
7865
  }
7458
7866
  function setSafeAttribute(el, name, value, options = {}) {
7867
+ const written = commitSafeAttribute(el, name, value, options);
7868
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
7869
+ return written;
7870
+ }
7871
+ function commitSafeAttribute(el, name, value, options) {
7459
7872
  const ns = namespaceFor(el, name);
7460
7873
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
7461
7874
  if (isEventHandlerAttr(name)) {
@@ -7468,6 +7881,14 @@ function setSafeAttribute(el, name, value, options = {}) {
7468
7881
  else el.removeAttribute(name);
7469
7882
  return false;
7470
7883
  }
7884
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
7885
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
7886
+ if (refusal !== ContextualRefusal.None) {
7887
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
7888
+ if (ns) el.removeAttributeNS(ns, localName);
7889
+ else el.removeAttribute(name);
7890
+ return false;
7891
+ }
7471
7892
  if (value == null) {
7472
7893
  if (ns) el.removeAttributeNS(ns, localName);
7473
7894
  else el.removeAttribute(name);
@@ -7511,7 +7932,12 @@ function setSafeAttribute(el, name, value, options = {}) {
7511
7932
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
7512
7933
  return true;
7513
7934
  }
7514
- const safe = sanitizeAttributeString(name, str, { element: el });
7935
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
7936
+ if (safe === null) {
7937
+ if (ns) el.removeAttributeNS(ns, localName);
7938
+ else el.removeAttribute(name);
7939
+ return true;
7940
+ }
7515
7941
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
7516
7942
  if (current === safe) return true;
7517
7943
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -7534,7 +7960,7 @@ function bindAttribute(el, attr, getter) {
7534
7960
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
7535
7961
  return;
7536
7962
  }
7537
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
7963
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
7538
7964
  }
7539
7965
  return reactiveBinding2(commit, el);
7540
7966
  }
@@ -7571,7 +7997,7 @@ function bindBoolAttr(el, attr, getter) {
7571
7997
  reportError(err, { phase: "binding", name: "bindBoolAttr", node: el });
7572
7998
  return;
7573
7999
  }
7574
- setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr" });
8000
+ setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr", reactive: true });
7575
8001
  }
7576
8002
  return reactiveBinding2(commit, el);
7577
8003
  }
@@ -8124,6 +8550,9 @@ var DOMPool = class {
8124
8550
  * Get a recycled element or create a new one.
8125
8551
  */
8126
8552
  acquire(tag) {
8553
+ if (isBlockedElement(tag)) {
8554
+ throw new Error(`DOMPool: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
8555
+ }
8127
8556
  const pool = this.pools.get(tag);
8128
8557
  if (pool && pool.length > 0) {
8129
8558
  const el = pool.pop();
@@ -8229,10 +8658,19 @@ function prefetch(url) {
8229
8658
  }
8230
8659
  function preloadImage(src) {
8231
8660
  return new Promise((resolve, reject) => {
8661
+ const safe = sanitizeUrl(String(src));
8662
+ if (!safe) {
8663
+ reject(
8664
+ new Error(
8665
+ String(src).trim() === "" ? "preloadImage: no image URL was given." : "preloadImage: refusing an image URL outside the URL policy."
8666
+ )
8667
+ );
8668
+ return;
8669
+ }
8232
8670
  const img2 = new Image();
8233
8671
  img2.onload = () => resolve(img2);
8234
8672
  img2.onerror = reject;
8235
- img2.src = src;
8673
+ img2.src = safe;
8236
8674
  });
8237
8675
  }
8238
8676
 
@@ -8258,95 +8696,6 @@ function noSideEffect(fn) {
8258
8696
  return fn;
8259
8697
  }
8260
8698
 
8261
- // src/utils/metaRefresh.ts
8262
- function isAsciiWhitespace(ch) {
8263
- return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
8264
- }
8265
- function trimAsciiWhitespace(value) {
8266
- let start = 0;
8267
- let end = value.length;
8268
- while (start < end && isAsciiWhitespace(value[start])) start++;
8269
- while (end > start && isAsciiWhitespace(value[end - 1])) end--;
8270
- return value.slice(start, end);
8271
- }
8272
- var forbidden = (reason) => ({ kind: "forbidden", reason });
8273
- function parseMetaRefreshContent(content) {
8274
- const raw = trimAsciiWhitespace(stripControlChars(content));
8275
- if (raw === "") return forbidden("empty content");
8276
- let i2 = 0;
8277
- while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
8278
- if (i2 === 0) return forbidden("missing or non-numeric delay");
8279
- const delay = Number.parseInt(raw.slice(0, i2), 10);
8280
- if (!Number.isFinite(delay)) return forbidden("unparseable delay");
8281
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8282
- if (i2 === raw.length) return { kind: "delay-only", delay };
8283
- if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden("unexpected text after delay");
8284
- i2++;
8285
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8286
- const keyStart = i2;
8287
- while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
8288
- const key = raw.slice(keyStart, i2).toLowerCase();
8289
- if (key !== "url") return forbidden(`unsupported refresh key ${JSON.stringify(key)}`);
8290
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8291
- if (raw[i2] !== "=") return forbidden("missing '=' after url");
8292
- i2++;
8293
- while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
8294
- let destination;
8295
- const quote2 = raw[i2];
8296
- if (quote2 === '"' || quote2 === "'") {
8297
- const close = raw.indexOf(quote2, i2 + 1);
8298
- if (close === -1) return forbidden("unterminated quoted destination");
8299
- destination = raw.slice(i2 + 1, close);
8300
- const rest = trimAsciiWhitespace(raw.slice(close + 1));
8301
- if (rest !== "") return forbidden("trailing text after quoted destination");
8302
- } else {
8303
- destination = trimAsciiWhitespace(raw.slice(i2));
8304
- if (/[;,]/.test(destination)) return forbidden("multiple refresh assignments");
8305
- }
8306
- if (destination === "") return forbidden("empty destination");
8307
- const sanitizedDestination = sanitizeUrl(destination);
8308
- if (sanitizedDestination === "") return forbidden("destination uses a disallowed protocol");
8309
- return { kind: "allowed", delay, destination, sanitizedDestination };
8310
- }
8311
- function resolveMetaRefreshPolicy(attributes) {
8312
- let httpEquiv;
8313
- let content;
8314
- let duplicateName;
8315
- const seen = /* @__PURE__ */ new Set();
8316
- for (const [key, value] of attributes) {
8317
- const canonical = canonicalAttrName(key);
8318
- if (seen.has(canonical)) duplicateName = canonical;
8319
- seen.add(canonical);
8320
- if (canonical === "http-equiv") httpEquiv = value;
8321
- else if (canonical === "content") content = value;
8322
- }
8323
- if (duplicateName !== void 0) {
8324
- return forbidden(`duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}`);
8325
- }
8326
- if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
8327
- if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
8328
- return { kind: "not-refresh" };
8329
- }
8330
- if (typeof content !== "string") return { kind: "not-refresh" };
8331
- return parseMetaRefreshContent(content);
8332
- }
8333
- function normalizeMetaAttributes(props) {
8334
- const out = /* @__PURE__ */ new Map();
8335
- const seen = /* @__PURE__ */ new Set();
8336
- for (const key of Object.keys(props)) {
8337
- const canonical = canonicalAttrName(key);
8338
- if (seen.has(canonical)) return null;
8339
- seen.add(canonical);
8340
- out.set(key, props[key]);
8341
- }
8342
- return out;
8343
- }
8344
- function isForbiddenMetaEntry(props) {
8345
- const normalized = normalizeMetaAttributes(props);
8346
- if (normalized === null) return true;
8347
- return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
8348
- }
8349
-
8350
8699
  // src/utils/headEntry.ts
8351
8700
  var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
8352
8701
  function isEmittableHeadAttrName(name) {
@@ -8357,9 +8706,7 @@ function isEmittableHeadAttrName(name) {
8357
8706
  return true;
8358
8707
  }
8359
8708
  function sanitizeHeadAttrValue(canonical, value) {
8360
- if (!isPolicyAttribute(canonical)) return value;
8361
- const safe = sanitizeAttributeString(canonical, value);
8362
- return safe === "" ? null : safe;
8709
+ return resolveAttributeValue(canonical, value);
8363
8710
  }
8364
8711
  function findDuplicateAttributeName(names) {
8365
8712
  const seen = /* @__PURE__ */ new Set();
@@ -8416,15 +8763,9 @@ function serializeHeadEntry(tag, attrs) {
8416
8763
  }
8417
8764
 
8418
8765
  // src/platform/ssr.ts
8419
- function isPolicyAttr(lowerName) {
8420
- return isPolicyAttribute(lowerName);
8421
- }
8422
8766
  function isForbiddenSsrAttr(lowerName) {
8423
8767
  return isHtmlContentAttribute(lowerName);
8424
8768
  }
8425
- function sanitizeSsrAttributeValue(lowerName, value) {
8426
- return sanitizeAttributeString(lowerName, value);
8427
- }
8428
8769
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
8429
8770
  function isSafeAttrName(name) {
8430
8771
  return SAFE_ATTR_NAME.test(name);
@@ -8455,6 +8796,64 @@ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
8455
8796
  "track",
8456
8797
  "wbr"
8457
8798
  ]);
8799
+ function findNativeGetter(proto, name) {
8800
+ for (let p2 = proto; p2; p2 = Object.getPrototypeOf(p2)) {
8801
+ const get = Object.getOwnPropertyDescriptor(p2, name)?.get;
8802
+ if (get) return get;
8803
+ }
8804
+ return null;
8805
+ }
8806
+ var nativeAccessorsByRealm = /* @__PURE__ */ new WeakMap();
8807
+ function nativeAccessors() {
8808
+ const realm = Element;
8809
+ let accessors = nativeAccessorsByRealm.get(realm);
8810
+ if (!accessors) {
8811
+ accessors = {
8812
+ attributes: findNativeGetter(Element.prototype, "attributes"),
8813
+ tagName: findNativeGetter(Element.prototype, "tagName"),
8814
+ childNodes: findNativeGetter(Node.prototype, "childNodes"),
8815
+ getAttribute: Element.prototype.getAttribute
8816
+ };
8817
+ nativeAccessorsByRealm.set(realm, accessors);
8818
+ }
8819
+ return accessors;
8820
+ }
8821
+ function attributesOf(el) {
8822
+ const get = nativeAccessors().attributes;
8823
+ return get ? get.call(el) : el.attributes;
8824
+ }
8825
+ function tagNameOf(el) {
8826
+ const get = nativeAccessors().tagName;
8827
+ return get ? get.call(el) : el.tagName;
8828
+ }
8829
+ function childNodesOf(node) {
8830
+ const get = nativeAccessors().childNodes;
8831
+ return get ? get.call(node) : node.childNodes;
8832
+ }
8833
+ function getAttributeOf(el, name) {
8834
+ return nativeAccessors().getAttribute.call(el, name);
8835
+ }
8836
+ function serializeOpenTag(element, tag) {
8837
+ let html2 = `<${tag}`;
8838
+ const meta2 = tag === "meta" ? /* @__PURE__ */ new Map() : null;
8839
+ const attrs = attributesOf(element);
8840
+ for (let i2 = 0; i2 < attrs.length; i2++) {
8841
+ const attr = attrs[i2];
8842
+ const rawName = attr.name;
8843
+ if (!isSafeAttrName(rawName)) continue;
8844
+ if (isEventHandlerAttr(rawName)) continue;
8845
+ if (isForbiddenSsrAttr(rawName)) continue;
8846
+ const value = resolveAttributeValue(rawName.toLowerCase(), attr.value);
8847
+ if (value === null) continue;
8848
+ meta2?.set(rawName, value);
8849
+ html2 += ` ${rawName}="${escapeAttr(value)}"`;
8850
+ }
8851
+ if (meta2 && isForbiddenMetaSnapshot(meta2)) return null;
8852
+ if (!getAttributeOf(element, "data-sibu-hydrate")) {
8853
+ html2 += ` data-sibu-ssr="true"`;
8854
+ }
8855
+ return html2;
8856
+ }
8458
8857
  function renderToString(element) {
8459
8858
  if (element instanceof DocumentFragment) {
8460
8859
  return Array.from(element.childNodes).map((child) => {
@@ -8474,35 +8873,21 @@ function renderToString(element) {
8474
8873
  if (!(element instanceof HTMLElement)) {
8475
8874
  return escapeHtml(element.textContent || "");
8476
8875
  }
8477
- const tag = element.tagName.toLowerCase();
8478
- if (tag === "script" || tag === "style") {
8876
+ const tag = tagNameOf(element).toLowerCase();
8877
+ if (isCodeTextElement(tag)) {
8479
8878
  return DEV ? `<!--ssr:${tag}-stripped-->` : "";
8480
8879
  }
8481
8880
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
8482
8881
  return DEV ? "<!--ssr:invalid-tag-->" : "";
8483
8882
  }
8484
- let html2 = `<${tag}`;
8485
- for (const attr of Array.from(element.attributes)) {
8486
- const rawName = attr.name;
8487
- if (!isSafeAttrName(rawName)) continue;
8488
- if (isEventHandlerAttr(rawName)) continue;
8489
- if (isForbiddenSsrAttr(rawName)) continue;
8490
- const lowerName = rawName.toLowerCase();
8491
- let value = attr.value;
8492
- if (isPolicyAttr(lowerName)) {
8493
- value = sanitizeSsrAttributeValue(lowerName, value);
8494
- if (!value) continue;
8495
- }
8496
- html2 += ` ${rawName}="${escapeAttr(value)}"`;
8497
- }
8498
- if (element.dataset && !element.dataset.sibuHydrate) {
8499
- html2 += ` data-sibu-ssr="true"`;
8500
- }
8883
+ const openTag = serializeOpenTag(element, tag);
8884
+ if (openTag === null) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
8885
+ let html2 = openTag;
8501
8886
  if (VOID_ELEMENTS.has(tag)) {
8502
8887
  return `${html2} />`;
8503
8888
  }
8504
8889
  html2 += ">";
8505
- for (const child of Array.from(element.childNodes)) {
8890
+ for (const child of Array.from(childNodesOf(element))) {
8506
8891
  try {
8507
8892
  html2 += renderToString(child);
8508
8893
  } catch (err) {
@@ -8652,12 +9037,8 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
8652
9037
  if (!isSafeAttrName(rawKey)) continue;
8653
9038
  if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
8654
9039
  if (isForbiddenSsrAttr(rawKey)) continue;
8655
- const lowerKey = rawKey.toLowerCase();
8656
- let value = String(attrs[rawKey]);
8657
- if (isPolicyAttr(lowerKey)) {
8658
- value = sanitizeSsrAttributeValue(lowerKey, value);
8659
- if (!value) continue;
8660
- }
9040
+ const value = resolveAttributeValue(rawKey.toLowerCase(), String(attrs[rawKey]));
9041
+ if (value === null) continue;
8661
9042
  out.push(`${rawKey}="${escapeAttr(value)}"`);
8662
9043
  }
8663
9044
  return out.join(" ");
@@ -8720,8 +9101,8 @@ async function* renderToStream(element) {
8720
9101
  yield escapeHtml(element.textContent || "");
8721
9102
  return;
8722
9103
  }
8723
- const tag = element.tagName.toLowerCase();
8724
- if (tag === "script" || tag === "style") {
9104
+ const tag = tagNameOf(element).toLowerCase();
9105
+ if (isCodeTextElement(tag)) {
8725
9106
  if (DEV) yield `<!--ssr:${tag}-stripped-->`;
8726
9107
  return;
8727
9108
  }
@@ -8729,29 +9110,17 @@ async function* renderToStream(element) {
8729
9110
  if (DEV) yield "<!--ssr:invalid-tag-->";
8730
9111
  return;
8731
9112
  }
8732
- let openTag = `<${tag}`;
8733
- for (const attr of Array.from(element.attributes)) {
8734
- const rawName = attr.name;
8735
- if (!isSafeAttrName(rawName)) continue;
8736
- if (isEventHandlerAttr(rawName)) continue;
8737
- if (isForbiddenSsrAttr(rawName)) continue;
8738
- const lowerName = rawName.toLowerCase();
8739
- let value = attr.value;
8740
- if (isPolicyAttr(lowerName)) {
8741
- value = sanitizeSsrAttributeValue(lowerName, value);
8742
- if (!value) continue;
8743
- }
8744
- openTag += ` ${rawName}="${escapeAttr(value)}"`;
8745
- }
8746
- if (element.dataset && !element.dataset.sibuHydrate) {
8747
- openTag += ` data-sibu-ssr="true"`;
9113
+ const openTag = serializeOpenTag(element, tag);
9114
+ if (openTag === null) {
9115
+ if (DEV) yield `<!--ssr:${tag}-stripped-->`;
9116
+ return;
8748
9117
  }
8749
9118
  if (VOID_ELEMENTS.has(tag)) {
8750
9119
  yield `${openTag} />`;
8751
9120
  return;
8752
9121
  }
8753
9122
  yield `${openTag}>`;
8754
- for (const child of Array.from(element.childNodes)) {
9123
+ for (const child of Array.from(childNodesOf(element))) {
8755
9124
  try {
8756
9125
  yield* renderToStream(child);
8757
9126
  } catch (err) {
@@ -8936,8 +9305,7 @@ function deserializeState(validate) {
8936
9305
  "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
8937
9306
  );
8938
9307
  }
8939
- const w = window;
8940
- const raw = w[SSR_DATA_ATTR];
9308
+ const raw = readOwnGlobal(SSR_DATA_ATTR);
8941
9309
  if (raw === void 0) return void 0;
8942
9310
  if (validate && !validate(raw)) return void 0;
8943
9311
  return raw;
@@ -9524,7 +9892,7 @@ function setCanonical(url) {
9524
9892
  }
9525
9893
 
9526
9894
  // src/platform/customElement.ts
9527
- var SVG_NS2 = "http://www.w3.org/2000/svg";
9895
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
9528
9896
  var MAX_RENDER_PASSES = 10;
9529
9897
  var ABANDONED_RENDER = /* @__PURE__ */ Symbol("sibujs.defineElement.abandonedRender");
9530
9898
  function defineElement(name, component, options = {}) {
@@ -9673,7 +10041,10 @@ function defineElement(name, component, options = {}) {
9673
10041
  customElements.define(name, SibuElement);
9674
10042
  }
9675
10043
  function svgElement(tag, props = {}, ...nodes) {
9676
- const el = document.createElementNS(SVG_NS2, tag);
10044
+ if (isBlockedElement(tag)) {
10045
+ throw new Error(`svgElement: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
10046
+ }
10047
+ const el = document.createElementNS(SVG_NS3, tag);
9677
10048
  for (const [key, value] of Object.entries(props)) {
9678
10049
  if (key === "nodes") continue;
9679
10050
  if (typeof value === "function" && isEventHandlerAttr(key)) {
@@ -9685,7 +10056,7 @@ function svgElement(tag, props = {}, ...nodes) {
9685
10056
  for (const child of nodes) {
9686
10057
  if (typeof child === "string") {
9687
10058
  el.appendChild(document.createTextNode(child));
9688
- } else if (child instanceof Node) {
10059
+ } else if (typeof child?.nodeType === "number") {
9689
10060
  el.appendChild(child);
9690
10061
  }
9691
10062
  }
@@ -9698,7 +10069,16 @@ function toError(err) {
9698
10069
  if (err !== null && typeof err === "object" && "name" in err && "message" in err) return err;
9699
10070
  return new Error(String(err));
9700
10071
  }
10072
+ function workerSource(fn, api) {
10073
+ if (typeof fn !== "function") {
10074
+ throw new TypeError(
10075
+ `${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.`
10076
+ );
10077
+ }
10078
+ return Function.prototype.toString.call(fn);
10079
+ }
9701
10080
  function worker(workerFn2) {
10081
+ const fnBody = workerSource(workerFn2, "worker");
9702
10082
  const [result, setResult] = signal(null);
9703
10083
  const [error, setError] = signal(null);
9704
10084
  const [loading, setLoading] = signal(false);
@@ -9714,7 +10094,6 @@ function worker(workerFn2) {
9714
10094
  if (typeof Worker === "undefined") {
9715
10095
  throw new Error("Web Workers are not supported in this environment");
9716
10096
  }
9717
- const fnBody = workerFn2.toString();
9718
10097
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
9719
10098
  blobUrl = URL.createObjectURL(blob);
9720
10099
  worker2 = new Worker(blobUrl);
@@ -9763,6 +10142,7 @@ function worker(workerFn2) {
9763
10142
  return { post, result, error, loading, terminate };
9764
10143
  }
9765
10144
  function workerFn(fn) {
10145
+ const fnStr = workerSource(fn, "workerFn");
9766
10146
  const [loading, setLoading] = signal(false);
9767
10147
  let worker2 = null;
9768
10148
  let blobUrl = null;
@@ -9777,7 +10157,6 @@ function workerFn(fn) {
9777
10157
  if (typeof Worker === "undefined") {
9778
10158
  throw new Error("Web Workers are not supported in this environment");
9779
10159
  }
9780
- const fnStr = fn.toString();
9781
10160
  const blob = new Blob(
9782
10161
  [
9783
10162
  `self.onmessage = function(e) {
@@ -9837,6 +10216,7 @@ function workerFn(fn) {
9837
10216
  return { run, loading, terminate };
9838
10217
  }
9839
10218
  function createWorkerPool(workerFn2, poolSize) {
10219
+ const fnBody = workerSource(workerFn2, "createWorkerPool");
9840
10220
  const size = poolSize || typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
9841
10221
  const workers = [];
9842
10222
  const queues = [];
@@ -9895,7 +10275,6 @@ function createWorkerPool(workerFn2, poolSize) {
9895
10275
  if (typeof Worker === "undefined") {
9896
10276
  throw new Error("Web Workers are not supported in this environment");
9897
10277
  }
9898
- const fnBody = workerFn2.toString();
9899
10278
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
9900
10279
  blobUrl = URL.createObjectURL(blob);
9901
10280
  for (let i2 = 0; i2 < size; i2++) {
@@ -10292,11 +10671,7 @@ function bindChildNode(placeholder, getter) {
10292
10671
  }
10293
10672
 
10294
10673
  // src/core/rendering/tagFactory.ts
10295
- var SVG_NS3 = "http://www.w3.org/2000/svg";
10296
- var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
10297
- function isBlockedTag(tag) {
10298
- return BLOCKED_TAGS.has(tag.toLowerCase());
10299
- }
10674
+ var SVG_NS4 = "http://www.w3.org/2000/svg";
10300
10675
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
10301
10676
  "config",
10302
10677
  "location",
@@ -10341,11 +10716,12 @@ function applyStyle(el, style2) {
10341
10716
  }
10342
10717
  const next = [];
10343
10718
  for (const prop in value) {
10719
+ if (!Object.hasOwn(value, prop)) continue;
10344
10720
  const val = value[prop];
10345
10721
  if (val == null) continue;
10346
10722
  const name = toKebab(prop);
10347
10723
  next.push(name);
10348
- decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
10724
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
10349
10725
  }
10350
10726
  for (let i2 = 0; i2 < written.length; i2++) {
10351
10727
  if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
@@ -10361,16 +10737,17 @@ function applyStyle(el, style2) {
10361
10737
  }
10362
10738
  const htmlEl = el;
10363
10739
  for (const prop in style2) {
10740
+ if (!Object.hasOwn(style2, prop)) continue;
10364
10741
  const val = style2[prop];
10365
10742
  const name = toKebab(prop);
10366
10743
  if (typeof val === "function") {
10367
10744
  const getter = val;
10368
10745
  const teardown = reactiveBinding2(() => {
10369
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
10746
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
10370
10747
  }, el);
10371
10748
  registerDisposer(el, teardown);
10372
10749
  } else {
10373
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
10750
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
10374
10751
  }
10375
10752
  }
10376
10753
  }
@@ -10392,6 +10769,7 @@ function applyClass(el, cls) {
10392
10769
  let hasReactive = false;
10393
10770
  let result = "";
10394
10771
  for (const name in obj) {
10772
+ if (!Object.hasOwn(obj, name)) continue;
10395
10773
  const val = obj[name];
10396
10774
  if (typeof val === "function") {
10397
10775
  hasReactive = true;
@@ -10403,6 +10781,7 @@ function applyClass(el, cls) {
10403
10781
  const update = () => {
10404
10782
  let r = "";
10405
10783
  for (const name in obj) {
10784
+ if (!Object.hasOwn(obj, name)) continue;
10406
10785
  const val = obj[name];
10407
10786
  const active = typeof val === "function" ? val() : val;
10408
10787
  if (active) r = r ? `${r} ${name}` : name;
@@ -10466,7 +10845,7 @@ function appendChildren(el, nodes) {
10466
10845
  }
10467
10846
  }
10468
10847
  function tagFactory(tag, ns) {
10469
- const blocked = isBlockedTag(tag);
10848
+ const blocked = isBlockedElement(tag);
10470
10849
  return (first, second) => {
10471
10850
  if (blocked) {
10472
10851
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -10529,6 +10908,7 @@ function tagFactory(tag, ns) {
10529
10908
  const pOn = props.on;
10530
10909
  if (pOn) {
10531
10910
  for (const ev in pOn) {
10911
+ if (!Object.hasOwn(pOn, ev)) continue;
10532
10912
  const handler = pOn[ev];
10533
10913
  if (typeof handler === "function") {
10534
10914
  el.addEventListener(ev, handler);
@@ -10555,6 +10935,7 @@ function tagFactory(tag, ns) {
10555
10935
  continue;
10556
10936
  // already handled above / below
10557
10937
  default: {
10938
+ if (!Object.hasOwn(props, key)) continue;
10558
10939
  const value = props[key];
10559
10940
  if (value == null) continue;
10560
10941
  if (isEventHandlerAttr(key)) continue;
@@ -10644,7 +11025,7 @@ var param = tagFactory("param");
10644
11025
  var picture = tagFactory("picture");
10645
11026
  var portal = tagFactory("portal");
10646
11027
  var source = tagFactory("source");
10647
- var svg = tagFactory("svg", SVG_NS3);
11028
+ var svg = tagFactory("svg", SVG_NS4);
10648
11029
  var math = tagFactory("math");
10649
11030
  var canvas = tagFactory("canvas");
10650
11031
  var noscript = tagFactory("noscript");
@@ -10685,26 +11066,26 @@ var base = tagFactory("base");
10685
11066
  var link = tagFactory("link");
10686
11067
  var meta = tagFactory("meta");
10687
11068
  var style = tagFactory("style");
10688
- var circle = tagFactory("circle", SVG_NS3);
10689
- var ellipse = tagFactory("ellipse", SVG_NS3);
10690
- var g = tagFactory("g", SVG_NS3);
10691
- var line = tagFactory("line", SVG_NS3);
10692
- var path = tagFactory("path", SVG_NS3);
10693
- var polygon = tagFactory("polygon", SVG_NS3);
10694
- var polyline = tagFactory("polyline", SVG_NS3);
10695
- var rect = tagFactory("rect", SVG_NS3);
10696
- var text = tagFactory("text", SVG_NS3);
10697
- var tspan = tagFactory("tspan", SVG_NS3);
10698
- var defs = tagFactory("defs", SVG_NS3);
10699
- var clipPath = tagFactory("clipPath", SVG_NS3);
10700
- var mask = tagFactory("mask", SVG_NS3);
10701
- var pattern = tagFactory("pattern", SVG_NS3);
10702
- var linearGradient = tagFactory("linearGradient", SVG_NS3);
10703
- var radialGradient = tagFactory("radialGradient", SVG_NS3);
10704
- var stop = tagFactory("stop", SVG_NS3);
10705
- var use = tagFactory("use", SVG_NS3);
10706
- var symbol = tagFactory("symbol", SVG_NS3);
10707
- var marker = tagFactory("marker", SVG_NS3);
11069
+ var circle = tagFactory("circle", SVG_NS4);
11070
+ var ellipse = tagFactory("ellipse", SVG_NS4);
11071
+ var g = tagFactory("g", SVG_NS4);
11072
+ var line = tagFactory("line", SVG_NS4);
11073
+ var path = tagFactory("path", SVG_NS4);
11074
+ var polygon = tagFactory("polygon", SVG_NS4);
11075
+ var polyline = tagFactory("polyline", SVG_NS4);
11076
+ var rect = tagFactory("rect", SVG_NS4);
11077
+ var text = tagFactory("text", SVG_NS4);
11078
+ var tspan = tagFactory("tspan", SVG_NS4);
11079
+ var defs = tagFactory("defs", SVG_NS4);
11080
+ var clipPath = tagFactory("clipPath", SVG_NS4);
11081
+ var mask = tagFactory("mask", SVG_NS4);
11082
+ var pattern = tagFactory("pattern", SVG_NS4);
11083
+ var linearGradient = tagFactory("linearGradient", SVG_NS4);
11084
+ var radialGradient = tagFactory("radialGradient", SVG_NS4);
11085
+ var stop = tagFactory("stop", SVG_NS4);
11086
+ var use = tagFactory("use", SVG_NS4);
11087
+ var symbol = tagFactory("symbol", SVG_NS4);
11088
+ var marker = tagFactory("marker", SVG_NS4);
10708
11089
  var center = tagFactory("center");
10709
11090
  var font = tagFactory("font");
10710
11091
  var marquee = tagFactory("marquee");
@@ -11007,7 +11388,7 @@ function createISR(options) {
11007
11388
  const [data2, setData] = signal(initialData);
11008
11389
  const [stale, setStale] = signal(initialData === void 0);
11009
11390
  const controller = new AbortController();
11010
- let inFlight = false;
11391
+ let inFlight = null;
11011
11392
  let disposed = false;
11012
11393
  let deadline;
11013
11394
  const clearDeadline = () => {
@@ -11027,10 +11408,7 @@ function createISR(options) {
11027
11408
  });
11028
11409
  }, revalidateAfter);
11029
11410
  };
11030
- const revalidate = async () => {
11031
- if (disposed || inFlight) return;
11032
- if (controller.signal.aborted) return;
11033
- inFlight = true;
11411
+ const run = async () => {
11034
11412
  try {
11035
11413
  const result = await fetcher({ signal: controller.signal });
11036
11414
  if (disposed || controller.signal.aborted) return;
@@ -11042,10 +11420,33 @@ function createISR(options) {
11042
11420
  } catch (err) {
11043
11421
  if (!disposed && !controller.signal.aborted) armDeadline();
11044
11422
  throw err;
11045
- } finally {
11046
- inFlight = false;
11047
11423
  }
11048
11424
  };
11425
+ const revalidate = () => {
11426
+ if (disposed || controller.signal.aborted) return Promise.resolve();
11427
+ if (inFlight) return inFlight;
11428
+ let resolve;
11429
+ let reject;
11430
+ const current = new Promise((res, rej) => {
11431
+ resolve = res;
11432
+ reject = rej;
11433
+ });
11434
+ inFlight = current;
11435
+ const release = () => {
11436
+ if (inFlight === current) inFlight = null;
11437
+ };
11438
+ run().then(
11439
+ () => {
11440
+ release();
11441
+ resolve();
11442
+ },
11443
+ (err) => {
11444
+ release();
11445
+ reject(err);
11446
+ }
11447
+ );
11448
+ return current;
11449
+ };
11049
11450
  if (initialData === void 0) {
11050
11451
  revalidate().catch((err) => {
11051
11452
  if (typeof console !== "undefined") console.warn("[SibuJS ISR] initial fetch failed", err);
@@ -11580,7 +11981,7 @@ var CDN_ENTRY_POINTS = {
11580
11981
  };
11581
11982
  var packageInfo = {
11582
11983
  name: "sibujs",
11583
- version: true ? "4.7.0" : "dev",
11984
+ version: true ? "4.9.0" : "dev",
11584
11985
  entryPoints: MODULE_ENTRY_POINTS,
11585
11986
  /**
11586
11987
  * Generate the package.json `exports` map. Module entries resolve to the
@@ -11725,7 +12126,7 @@ var env = {
11725
12126
  };
11726
12127
 
11727
12128
  // src/plugins/versioning.ts
11728
- var VERSION = true ? "4.7.0" : "dev";
12129
+ var VERSION = true ? "4.9.0" : "dev";
11729
12130
  var NUM = "(?:0|[1-9]\\d*)";
11730
12131
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
11731
12132
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -12008,12 +12409,14 @@ function extractRangeTarget(range) {
12008
12409
  function preloadCritical(resources) {
12009
12410
  if (typeof document === "undefined") return;
12010
12411
  for (const resource2 of resources) {
12011
- const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(resource2.href) : resource2.href.replace(/["\\]/g, "\\$&");
12412
+ const href = sanitizeUrl(resource2.href);
12413
+ if (!href) continue;
12414
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(href) : href.replace(/["\\]/g, "\\$&");
12012
12415
  const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
12013
12416
  if (existing) continue;
12014
12417
  const link2 = document.createElement("link");
12015
12418
  link2.rel = "preload";
12016
- link2.href = resource2.href;
12419
+ link2.href = href;
12017
12420
  link2.setAttribute("as", resource2.as);
12018
12421
  if (resource2.type) {
12019
12422
  link2.type = resource2.type;
@@ -15017,7 +15420,7 @@ var _themeLayers = globalSingleton(
15017
15420
  function commitThemeProperty(root, name, record) {
15018
15421
  const top = record.layers[record.layers.length - 1];
15019
15422
  if (top) {
15020
- root.style.setProperty(name, top.value);
15423
+ root.style.setProperty(name, sanitizeCSSDeclaration(name, String(top.value), { element: root }));
15021
15424
  return;
15022
15425
  }
15023
15426
  _themeLayers.get(root)?.delete(name);