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/ui.cjs CHANGED
@@ -834,7 +834,7 @@ function forEachSubscriber(signal2, visit) {
834
834
  }
835
835
 
836
836
  // src/reactivity/track.ts
837
- var _runtimeVersion = true ? "4.6.0" : "dev";
837
+ var _runtimeVersion = true ? "4.8.0" : "dev";
838
838
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
839
839
  function resolveReactiveApi() {
840
840
  const g = globalThis;
@@ -890,6 +890,14 @@ var getSubscriberCount2 = API.getSubscriberCount;
890
890
  var getSubscriberDeps2 = API.getSubscriberDeps;
891
891
  var forEachSubscriber2 = API.forEachSubscriber;
892
892
 
893
+ // src/core/devtoolsHook.ts
894
+ function emitDevtools(hook, event, payload) {
895
+ try {
896
+ hook.emit(event, payload);
897
+ } catch {
898
+ }
899
+ }
900
+
893
901
  // src/core/signals/derived.ts
894
902
  function derived(getter, options) {
895
903
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
@@ -916,17 +924,17 @@ function derived(getter, options) {
916
924
  cs._d = false;
917
925
  cs._init = true;
918
926
  };
919
- track2(() => {
920
- let threw = true;
921
- try {
927
+ try {
928
+ track2(() => {
922
929
  cs._v = getter();
923
930
  cs._d = false;
924
931
  cs._init = true;
925
- threw = false;
926
- } finally {
927
- if (threw) cs._d = true;
928
- }
929
- }, markDirty);
932
+ }, markDirty);
933
+ } catch (err) {
934
+ disposed = true;
935
+ cleanup2(markDirty);
936
+ throw err;
937
+ }
930
938
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
931
939
  const validate = () => {
932
940
  if (!cs._d || disposed || pendingError !== void 0) return;
@@ -945,7 +953,7 @@ function derived(getter, options) {
945
953
  if (disposed) cleanup2(markDirty);
946
954
  }
947
955
  if (hook && !disposed && !Object.is(oldValue, cs._v)) {
948
- hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
956
+ emitDevtools(hook, "computed:update", { signal: cs, oldValue, newValue: cs._v });
949
957
  }
950
958
  };
951
959
  cs._validate = validate;
@@ -996,7 +1004,7 @@ function derived(getter, options) {
996
1004
  }
997
1005
  }
998
1006
  };
999
- if (hook) hook.emit("computed:create", { signal: cs, name: debugName, getter: computedGetter });
1007
+ if (hook) emitDevtools(hook, "computed:create", { signal: cs, name: debugName, getter: computedGetter });
1000
1008
  return computedGetter;
1001
1009
  }
1002
1010
 
@@ -1083,7 +1091,7 @@ function signal(initial, options) {
1083
1091
  state2.__v++;
1084
1092
  if (DEV) {
1085
1093
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1086
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1094
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1087
1095
  }
1088
1096
  if (!enqueueBatchedSignal(state2)) {
1089
1097
  notifySubscribers2(state2);
@@ -1097,7 +1105,7 @@ function signal(initial, options) {
1097
1105
  state2.value = newValue;
1098
1106
  state2.__v++;
1099
1107
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1100
- if (hook) hook.emit("signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1108
+ if (hook) emitDevtools(hook, "signal:update", { signal: state2, name: debugName, oldValue: prev, newValue });
1101
1109
  if (!enqueueBatchedSignal(state2)) {
1102
1110
  notifySubscribers2(state2);
1103
1111
  }
@@ -1116,7 +1124,7 @@ function signal(initial, options) {
1116
1124
  }
1117
1125
  if (DEV) {
1118
1126
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1119
- if (hook) hook.emit("signal:create", { signal: state2, name: debugName, getter: get, initial });
1127
+ if (hook) emitDevtools(hook, "signal:create", { signal: state2, name: debugName, getter: get, initial });
1120
1128
  }
1121
1129
  return [get, set];
1122
1130
  }
@@ -2171,33 +2179,402 @@ function createDialogAria(element, options = {}) {
2171
2179
  return { titleId, descriptionId };
2172
2180
  }
2173
2181
 
2174
- // src/ui/scopedStyle.ts
2175
- var _scope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.scopedStyle.v1"), () => ({ n: 0 }));
2176
- function decodeCssEscapes(css) {
2177
- return css.replace(/\\([0-9a-f]{1,6})[ \t\n\r\f]?|\\([^\n])/gi, (_match, hex, ch) => {
2178
- if (hex) {
2179
- const code = Number.parseInt(hex, 16);
2180
- if (Number.isFinite(code) && code > 0 && code <= 1114111) {
2181
- try {
2182
- return String.fromCodePoint(code);
2183
- } catch {
2184
- return "";
2182
+ // src/utils/sanitize.ts
2183
+ function stripControlChars(value) {
2184
+ return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
2185
+ }
2186
+ var ASCII_UPPER = /[A-Z]/;
2187
+ var ASCII_UPPER_G = /[A-Z]/g;
2188
+ function canonicalAttrName(name) {
2189
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
2190
+ }
2191
+ function isEventHandlerAttr(name) {
2192
+ if (name.length < 3) return false;
2193
+ const lower = name.toLowerCase();
2194
+ return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
2195
+ }
2196
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
2197
+ var OUTER_TRIM = /^[\x00-\x20\x7f-\x9f]+|[\x00-\x20\x7f-\x9f]+$/g;
2198
+ function sanitizeUrl(url) {
2199
+ const probe = stripControlChars(url).trim();
2200
+ if (!probe) return "";
2201
+ const output = url.replace(OUTER_TRIM, "");
2202
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
2203
+ return "";
2204
+ }
2205
+ const lower = probe.toLowerCase();
2206
+ let schemeEnd = -1;
2207
+ for (let i = 0; i < lower.length; i++) {
2208
+ const ch = lower.charCodeAt(i);
2209
+ if (ch === 58) {
2210
+ schemeEnd = i;
2211
+ break;
2212
+ }
2213
+ if (ch === 47 || ch === 63 || ch === 35) break;
2214
+ }
2215
+ if (schemeEnd === -1) return output;
2216
+ const scheme = lower.slice(0, schemeEnd + 1);
2217
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return output;
2218
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
2219
+ return output;
2220
+ }
2221
+ var SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
2222
+ function isSrcsetWhitespace(ch) {
2223
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
2224
+ }
2225
+ function sanitizeSrcset(value) {
2226
+ const input = String(value);
2227
+ const out = [];
2228
+ let i = 0;
2229
+ const n = input.length;
2230
+ while (i < n) {
2231
+ while (i < n && (isSrcsetWhitespace(input[i]) || input[i] === ",")) i++;
2232
+ if (i >= n) break;
2233
+ const urlStart = i;
2234
+ while (i < n && !isSrcsetWhitespace(input[i])) i++;
2235
+ let url = input.slice(urlStart, i);
2236
+ const descriptors = [];
2237
+ if (url.endsWith(",")) {
2238
+ url = url.replace(/,+$/, "");
2239
+ } else {
2240
+ let current = "";
2241
+ let inParens = false;
2242
+ while (i < n) {
2243
+ const ch = input[i];
2244
+ if (inParens) {
2245
+ current += ch;
2246
+ if (ch === ")") inParens = false;
2247
+ i++;
2248
+ continue;
2249
+ }
2250
+ if (isSrcsetWhitespace(ch)) {
2251
+ if (current) descriptors.push(current);
2252
+ current = "";
2253
+ i++;
2254
+ continue;
2185
2255
  }
2256
+ if (ch === ",") {
2257
+ i++;
2258
+ break;
2259
+ }
2260
+ if (ch === "(") inParens = true;
2261
+ current += ch;
2262
+ i++;
2263
+ }
2264
+ if (current) descriptors.push(current);
2265
+ }
2266
+ if (url === "") continue;
2267
+ if (descriptors.length > 1) continue;
2268
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
2269
+ const safe = sanitizeUrl(url);
2270
+ if (!safe) continue;
2271
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
2272
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
2273
+ }
2274
+ return out.join(", ");
2275
+ }
2276
+ var CSS_DANGER_GATE = /[(:@\\]/;
2277
+ function isCssWhitespace(ch) {
2278
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
2279
+ }
2280
+ function isHexDigit(ch) {
2281
+ return ch >= "0" && ch <= "9" || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
2282
+ }
2283
+ function decodeCssEscapes(value) {
2284
+ let out = "";
2285
+ for (let i = 0; i < value.length; i++) {
2286
+ const ch = value[i];
2287
+ if (ch !== "\\") {
2288
+ out += ch;
2289
+ continue;
2290
+ }
2291
+ if (i + 1 >= value.length) break;
2292
+ const next = value[i + 1];
2293
+ if (next === "\n" || next === "\f") {
2294
+ i += 1;
2295
+ continue;
2296
+ }
2297
+ if (next === "\r") {
2298
+ i += value[i + 2] === "\n" ? 2 : 1;
2299
+ continue;
2300
+ }
2301
+ if (isHexDigit(next)) {
2302
+ let hex = "";
2303
+ let j = i + 1;
2304
+ while (j < value.length && hex.length < 6 && isHexDigit(value[j])) {
2305
+ hex += value[j];
2306
+ j++;
2307
+ }
2308
+ if (j < value.length && isCssWhitespace(value[j])) {
2309
+ j += value[j] === "\r" && value[j + 1] === "\n" ? 2 : 1;
2186
2310
  }
2187
- return "";
2311
+ const code = Number.parseInt(hex, 16);
2312
+ const invalid = code === 0 || code > 1114111 || code >= 55296 && code <= 57343;
2313
+ out += invalid ? "\uFFFD" : String.fromCodePoint(code);
2314
+ i = j - 1;
2315
+ continue;
2316
+ }
2317
+ out += next;
2318
+ i += 1;
2319
+ }
2320
+ return out;
2321
+ }
2322
+ var BLOCKED_CSS_NEEDLES = [
2323
+ "url(",
2324
+ "expression(",
2325
+ "javascript:",
2326
+ "vbscript:",
2327
+ "-moz-binding",
2328
+ "behavior:",
2329
+ "@import",
2330
+ "image-set(",
2331
+ "filter:progid",
2332
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
2333
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
2334
+ // today, which is exactly when a blocklist should get ahead of it.
2335
+ "image(",
2336
+ "src("
2337
+ ];
2338
+ var CSS_REASON_MOZ_BINDING = 4;
2339
+ var CSS_REASON_BEHAVIOR = 5;
2340
+ var CSS_REASON_PROGID = 8;
2341
+ function describeElement(el) {
2342
+ if (!el) return "an element";
2343
+ let out = el.tagName.toLowerCase();
2344
+ const id = el.getAttribute("id");
2345
+ if (id) out += `#${id}`;
2346
+ const cls = el.getAttribute("class");
2347
+ if (cls) out += `.${cls.trim().split(/\s+/).slice(0, 3).join(".")}`;
2348
+ return `<${out}>`;
2349
+ }
2350
+ var warnedDeclarations = /* @__PURE__ */ new Set();
2351
+ var MAX_WARNED_DECLARATIONS = 100;
2352
+ var declarationCapAnnounced = false;
2353
+ function warnDroppedDeclaration(value, reason, ctx) {
2354
+ devWarnLazy(() => {
2355
+ const reasons = [
2356
+ "url() in an inline style is an exfiltration channel: the browser fetches it immediately and the URL's path or query can carry page data off-origin",
2357
+ "expression() executes arbitrary script in legacy engines",
2358
+ "the javascript: scheme executes script",
2359
+ "the vbscript: scheme executes script",
2360
+ "-moz-binding loads and runs remote XBL",
2361
+ "behavior: loads and runs a remote HTC script",
2362
+ "@import pulls in a remote stylesheet",
2363
+ "image-set() fetches remote images, the same exfiltration channel as url()",
2364
+ "filter:progid activates legacy scriptable filters",
2365
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
2366
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
2367
+ ];
2368
+ const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
2369
+ const where = describeElement(ctx?.element);
2370
+ const key = `${where}|${declaration}`;
2371
+ if (warnedDeclarations.has(key)) return "";
2372
+ if (warnedDeclarations.size >= MAX_WARNED_DECLARATIONS) {
2373
+ if (declarationCapAnnounced) return "";
2374
+ declarationCapAnnounced = true;
2375
+ return `${MAX_WARNED_DECLARATIONS} distinct dropped style declarations have been reported; suppressing further ones for the rest of this session so they cannot flood the console. The declarations are still dropped \u2014 only the reporting stops. Fix the reported ones and reload to see any that remain.`;
2188
2376
  }
2189
- return ch || "";
2377
+ warnedDeclarations.add(key);
2378
+ 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.`;
2190
2379
  });
2191
2380
  }
2192
- function sanitizeCSS(css) {
2193
- let sanitized = decodeCssEscapes(css);
2194
- sanitized = sanitized.replace(/@import\s+[^;]+;/gi, "/* @import removed */");
2195
- sanitized = sanitized.replace(/url\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* url() removed */");
2196
- sanitized = sanitized.replace(/expression\s*\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)/gi, "/* expression() removed */");
2197
- sanitized = sanitized.replace(/-moz-binding\s*:[^;]+;/gi, "/* -moz-binding removed */");
2198
- sanitized = sanitized.replace(/behavior\s*:[^;]+;/gi, "/* behavior removed */");
2199
- return sanitized;
2381
+ function normalizeCssForScan(value) {
2382
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
2383
+ return normalized.toLowerCase().replace(/\s+/g, "");
2384
+ }
2385
+ function blockedCssReason(value) {
2386
+ if (!CSS_DANGER_GATE.test(value)) return -1;
2387
+ const lower = normalizeCssForScan(value);
2388
+ for (let i = 0; i < BLOCKED_CSS_NEEDLES.length; i++) {
2389
+ if (lower.includes(BLOCKED_CSS_NEEDLES[i])) return i;
2390
+ }
2391
+ return -1;
2200
2392
  }
2393
+ var STYLESHEET_BLOCKED_FUNCTIONS = ["url", "image-set", "image", "src", "expression"];
2394
+ function stripCssFunctions(css) {
2395
+ const pattern = new RegExp(`(?:-webkit-)?(${STYLESHEET_BLOCKED_FUNCTIONS.join("|")})\\s*\\(`, "gi");
2396
+ let out = "";
2397
+ let last = 0;
2398
+ for (let match = pattern.exec(css); match !== null; match = pattern.exec(css)) {
2399
+ const before = match.index > 0 ? css[match.index - 1] : "";
2400
+ if (/[\w-]/.test(before)) continue;
2401
+ let depth = 1;
2402
+ let i = pattern.lastIndex;
2403
+ let quote = "";
2404
+ for (; i < css.length && depth > 0; i++) {
2405
+ const ch = css[i];
2406
+ if (quote) {
2407
+ if (ch === "\\") i++;
2408
+ else if (ch === quote) quote = "";
2409
+ } else if (ch === '"' || ch === "'") quote = ch;
2410
+ else if (ch === "(") depth++;
2411
+ else if (ch === ")") depth--;
2412
+ }
2413
+ out += `${css.slice(last, match.index)}/* ${match[1].toLowerCase()}() removed */`;
2414
+ last = i;
2415
+ pattern.lastIndex = i;
2416
+ }
2417
+ return out + css.slice(last);
2418
+ }
2419
+ function sanitizeStylesheetText(css) {
2420
+ let text = css.includes("\\") ? decodeCssEscapes(css) : css;
2421
+ text = text.replace(/@import\b[^;{}]*;?/gi, "/* @import removed */");
2422
+ text = stripCssFunctions(text);
2423
+ text = text.replace(/(^|[;{\s])behavior\s*:[^;}]*;?/gi, "$1/* behavior removed */");
2424
+ text = text.replace(/(^|[;{\s])-moz-binding\s*:[^;}]*;?/gi, "$1/* -moz-binding removed */");
2425
+ text = text.replace(/(^|[;{\s])(-ms-)?filter\s*:[^;}]*progid[^;}]*;?/gi, "$1/* filter:progid removed */");
2426
+ return text;
2427
+ }
2428
+ function blockedDeclarationReason(property, value) {
2429
+ const reason = blockedCssReason(value);
2430
+ if (reason !== -1) return reason;
2431
+ const name = canonicalAttrName(property);
2432
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
2433
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
2434
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
2435
+ return CSS_REASON_PROGID;
2436
+ }
2437
+ return -1;
2438
+ }
2439
+ function parseStyleDeclarations(css) {
2440
+ const chunks = [];
2441
+ let buf = "";
2442
+ let depth = 0;
2443
+ let quote = "";
2444
+ for (let i = 0; i < css.length; i++) {
2445
+ const ch = css[i];
2446
+ if (quote) {
2447
+ buf += ch;
2448
+ if (ch === "\\" && i + 1 < css.length) buf += css[++i];
2449
+ else if (ch === quote) quote = "";
2450
+ continue;
2451
+ }
2452
+ if (ch === "\\") {
2453
+ buf += ch;
2454
+ if (i + 1 < css.length) buf += css[++i];
2455
+ continue;
2456
+ }
2457
+ if (ch === "/" && css[i + 1] === "*") {
2458
+ const end = css.indexOf("*/", i + 2);
2459
+ if (end === -1) return null;
2460
+ i = end + 1;
2461
+ continue;
2462
+ }
2463
+ if (ch === '"' || ch === "'") quote = ch;
2464
+ else if (ch === "(" || ch === "[") depth++;
2465
+ else if (ch === ")" || ch === "]") {
2466
+ if (--depth < 0) return null;
2467
+ } else if (ch === "{" || ch === "}") return null;
2468
+ else if (ch === ";" && depth === 0) {
2469
+ chunks.push(buf);
2470
+ buf = "";
2471
+ continue;
2472
+ }
2473
+ buf += ch;
2474
+ }
2475
+ if (quote || depth !== 0) return null;
2476
+ chunks.push(buf);
2477
+ const out = [];
2478
+ for (const chunk of chunks) {
2479
+ const colon = chunk.indexOf(":");
2480
+ if (colon === -1) continue;
2481
+ const rawName = chunk.slice(0, colon).trim();
2482
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
2483
+ let value = chunk.slice(colon + 1).trim();
2484
+ const bang = /!\s*important\s*$/i.exec(value);
2485
+ if (bang) value = value.slice(0, bang.index).trim();
2486
+ if (value === "") continue;
2487
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
2488
+ out.push([property, value, bang !== null]);
2489
+ }
2490
+ return out;
2491
+ }
2492
+ function sanitizeStyleDeclarationList(cssText, context) {
2493
+ const input = String(cssText);
2494
+ const declarations = parseStyleDeclarations(input);
2495
+ if (declarations === null) {
2496
+ if (blockedCssReason(input) !== -1) warnDroppedDeclaration(input, blockedCssReason(input), context);
2497
+ return "";
2498
+ }
2499
+ const kept = [];
2500
+ for (const [property, value, important] of declarations) {
2501
+ const reason = blockedDeclarationReason(property, value);
2502
+ if (reason !== -1) {
2503
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
2504
+ continue;
2505
+ }
2506
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
2507
+ }
2508
+ return kept.join("; ");
2509
+ }
2510
+ function sanitizeStyleAttribute(cssText, context) {
2511
+ const input = String(cssText);
2512
+ if (input.trim() === "") return "";
2513
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input, context);
2514
+ const probe = document.createElement("div");
2515
+ try {
2516
+ probe.style.cssText = input;
2517
+ } catch {
2518
+ return "";
2519
+ }
2520
+ const declarations = [];
2521
+ for (let i = 0; i < probe.style.length; i++) {
2522
+ const property = probe.style[i];
2523
+ const value = probe.style.getPropertyValue(property);
2524
+ const reason = blockedDeclarationReason(property, value);
2525
+ if (reason !== -1) {
2526
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
2527
+ continue;
2528
+ }
2529
+ const priority = probe.style.getPropertyPriority(property);
2530
+ declarations.push(`${property}: ${value}${priority ? ` !${priority}` : ""}`);
2531
+ }
2532
+ return declarations.join("; ");
2533
+ }
2534
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
2535
+ "href",
2536
+ "xlink:href",
2537
+ "src",
2538
+ "action",
2539
+ "formaction",
2540
+ "formtarget",
2541
+ "cite",
2542
+ "poster",
2543
+ "background",
2544
+ "srcset",
2545
+ "ping",
2546
+ "data",
2547
+ // `<html manifest>` is a URL sink too. It was carried only by the SSR
2548
+ // serializers' private copy of this set; folding that copy into this one would
2549
+ // otherwise have silently dropped its coverage.
2550
+ "manifest"
2551
+ ]);
2552
+ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
2553
+ function isHtmlContentAttribute(name) {
2554
+ return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
2555
+ }
2556
+ function isUrlAttribute(attr) {
2557
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
2558
+ }
2559
+ function isPolicyAttribute(name) {
2560
+ const lower = name.toLowerCase();
2561
+ return lower === "style" || URL_ATTRIBUTES.has(lower);
2562
+ }
2563
+ function sanitizeAttributeString(attr, value, context) {
2564
+ const lower = attr.toLowerCase();
2565
+ if (lower === "srcset") return sanitizeSrcset(value);
2566
+ if (URL_ATTRIBUTES.has(lower)) return sanitizeUrl(value);
2567
+ if (lower === "style") return sanitizeStyleAttribute(value, context);
2568
+ return value;
2569
+ }
2570
+ function resolveAttributeValue(attr, value, context) {
2571
+ if (!isPolicyAttribute(attr)) return value;
2572
+ const safe = sanitizeAttributeString(attr, value, context);
2573
+ return safe === "" ? null : safe;
2574
+ }
2575
+
2576
+ // src/ui/scopedStyle.ts
2577
+ var _scope = globalSingleton(/* @__PURE__ */ Symbol.for("sibujs.scopedStyle.v1"), () => ({ n: 0 }));
2201
2578
  function firstCompoundEnd(selector) {
2202
2579
  let parens = 0;
2203
2580
  let brackets = 0;
@@ -2359,7 +2736,7 @@ function scopeStylesheet(css, attr) {
2359
2736
  function scopedStyle(css) {
2360
2737
  const id = `sibu-s${_scope.n++}`;
2361
2738
  const attr = `data-${id}`;
2362
- const safeCss = sanitizeCSS(css);
2739
+ const safeCss = sanitizeStylesheetText(css);
2363
2740
  const scopedCSS = scopeStylesheet(safeCss, attr);
2364
2741
  if (typeof document !== "undefined") {
2365
2742
  const styleEl = document.createElement("style");
@@ -2385,240 +2762,223 @@ function removeScopedStyle(scopeId) {
2385
2762
  if (el) el.remove();
2386
2763
  }
2387
2764
 
2388
- // src/utils/sanitize.ts
2389
- function stripControlChars(value) {
2390
- return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
2391
- }
2392
- function isEventHandlerAttr(name) {
2393
- if (name.length < 3) return false;
2394
- const lower = name.toLowerCase();
2395
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
2396
- }
2397
- var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
2398
- var OUTER_TRIM = /^[\x00-\x20\x7f-\x9f]+|[\x00-\x20\x7f-\x9f]+$/g;
2399
- function sanitizeUrl(url) {
2400
- const probe = stripControlChars(url).trim();
2401
- if (!probe) return "";
2402
- const output = url.replace(OUTER_TRIM, "");
2403
- const lower = probe.toLowerCase();
2404
- let schemeEnd = -1;
2405
- for (let i = 0; i < lower.length; i++) {
2406
- const ch = lower.charCodeAt(i);
2407
- if (ch === 58) {
2408
- schemeEnd = i;
2409
- break;
2410
- }
2411
- if (ch === 47 || ch === 63 || ch === 35) break;
2412
- }
2413
- if (schemeEnd === -1) return output;
2414
- const scheme = lower.slice(0, schemeEnd + 1);
2415
- if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return output;
2416
- if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
2417
- return output;
2418
- }
2419
- var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
2420
- function sanitizeSrcset(value) {
2421
- const parts = value.split(",");
2422
- const out = [];
2423
- for (let i = 0; i < parts.length; i++) {
2424
- const part = parts[i].trim();
2425
- if (!part) continue;
2426
- const m = part.match(/^(\S+)(\s+.+)?$/);
2427
- if (!m) continue;
2428
- if (m[2] !== void 0 && !SRCSET_DESCRIPTOR.test(m[2])) continue;
2429
- const safe = sanitizeUrl(m[1]);
2430
- if (!safe) continue;
2431
- out.push(m[2] ? `${safe}${m[2]}` : safe);
2432
- }
2433
- return out.join(", ");
2434
- }
2435
- var CSS_DANGER_GATE = /[(:@\\]/;
2436
- function isCssWhitespace(ch) {
2765
+ // src/utils/metaRefresh.ts
2766
+ function isAsciiWhitespace(ch) {
2437
2767
  return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
2438
2768
  }
2439
- function isHexDigit(ch) {
2440
- return ch >= "0" && ch <= "9" || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
2441
- }
2442
- function decodeCssEscapes2(value) {
2443
- let out = "";
2444
- for (let i = 0; i < value.length; i++) {
2445
- const ch = value[i];
2446
- if (ch !== "\\") {
2447
- out += ch;
2448
- continue;
2449
- }
2450
- if (i + 1 >= value.length) break;
2451
- const next = value[i + 1];
2452
- if (next === "\n" || next === "\f") {
2453
- i += 1;
2454
- continue;
2455
- }
2456
- if (next === "\r") {
2457
- i += value[i + 2] === "\n" ? 2 : 1;
2458
- continue;
2459
- }
2460
- if (isHexDigit(next)) {
2461
- let hex = "";
2462
- let j = i + 1;
2463
- while (j < value.length && hex.length < 6 && isHexDigit(value[j])) {
2464
- hex += value[j];
2465
- j++;
2466
- }
2467
- if (j < value.length && isCssWhitespace(value[j])) {
2468
- j += value[j] === "\r" && value[j + 1] === "\n" ? 2 : 1;
2469
- }
2470
- const code = Number.parseInt(hex, 16);
2471
- const invalid = code === 0 || code > 1114111 || code >= 55296 && code <= 57343;
2472
- out += invalid ? "\uFFFD" : String.fromCodePoint(code);
2473
- i = j - 1;
2474
- continue;
2475
- }
2476
- out += next;
2477
- i += 1;
2478
- }
2479
- return out;
2480
- }
2481
- var BLOCKED_CSS_NEEDLES = [
2482
- "url(",
2483
- "expression(",
2484
- "javascript:",
2485
- "vbscript:",
2486
- "-moz-binding",
2487
- "behavior:",
2488
- "@import",
2489
- "image-set(",
2490
- "filter:progid"
2491
- ];
2492
- function describeElement(el) {
2493
- if (!el) return "an element";
2494
- let out = el.tagName.toLowerCase();
2495
- const id = el.getAttribute("id");
2496
- if (id) out += `#${id}`;
2497
- const cls = el.getAttribute("class");
2498
- if (cls) out += `.${cls.trim().split(/\s+/).slice(0, 3).join(".")}`;
2499
- return `<${out}>`;
2500
- }
2501
- var warnedDeclarations = /* @__PURE__ */ new Set();
2502
- var MAX_WARNED_DECLARATIONS = 100;
2503
- var declarationCapAnnounced = false;
2504
- function warnDroppedDeclaration(value, reason, ctx) {
2505
- devWarnLazy(() => {
2506
- const reasons = [
2507
- "url() in an inline style is an exfiltration channel: the browser fetches it immediately and the URL's path or query can carry page data off-origin",
2508
- "expression() executes arbitrary script in legacy engines",
2509
- "the javascript: scheme executes script",
2510
- "the vbscript: scheme executes script",
2511
- "-moz-binding loads and runs remote XBL",
2512
- "behavior: loads and runs a remote HTC script",
2513
- "@import pulls in a remote stylesheet",
2514
- "image-set() fetches remote images, the same exfiltration channel as url()",
2515
- "filter:progid activates legacy scriptable filters"
2516
- ];
2517
- const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
2518
- const where = describeElement(ctx?.element);
2519
- const key = `${where}|${declaration}`;
2520
- if (warnedDeclarations.has(key)) return "";
2521
- if (warnedDeclarations.size >= MAX_WARNED_DECLARATIONS) {
2522
- if (declarationCapAnnounced) return "";
2523
- declarationCapAnnounced = true;
2524
- return `${MAX_WARNED_DECLARATIONS} distinct dropped style declarations have been reported; suppressing further ones for the rest of this session so they cannot flood the console. The declarations are still dropped \u2014 only the reporting stops. Fix the reported ones and reload to see any that remain.`;
2525
- }
2526
- warnedDeclarations.add(key);
2527
- 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.`;
2528
- });
2529
- }
2530
- function blockedCssReason(value) {
2531
- if (!CSS_DANGER_GATE.test(value)) return -1;
2532
- const normalized = value.includes("\\") ? decodeCssEscapes2(value) : value;
2533
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
2534
- for (let i = 0; i < BLOCKED_CSS_NEEDLES.length; i++) {
2535
- if (lower.includes(BLOCKED_CSS_NEEDLES[i])) return i;
2536
- }
2537
- return -1;
2538
- }
2539
- function sanitizeCSSValue(value, context) {
2540
- const reason = blockedCssReason(value);
2541
- if (reason === -1) return value;
2542
- warnDroppedDeclaration(value, reason, context);
2543
- return "";
2544
- }
2545
- function sanitizeStyleAttribute(cssText, context) {
2546
- const input = String(cssText);
2547
- if (input.trim() === "") return "";
2548
- if (typeof document === "undefined") {
2549
- return sanitizeCSSValue(input, context) === "" ? "" : input;
2550
- }
2551
- const probe = document.createElement("div");
2552
- try {
2553
- probe.style.cssText = input;
2554
- } catch {
2555
- return "";
2556
- }
2557
- const declarations = [];
2558
- for (let i = 0; i < probe.style.length; i++) {
2559
- const property = probe.style[i];
2560
- const value = probe.style.getPropertyValue(property);
2561
- let reason = blockedCssReason(value);
2562
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
2563
- if (reason !== -1) {
2564
- warnDroppedDeclaration(value, reason, { property, element: context?.element });
2565
- continue;
2566
- }
2567
- const priority = probe.style.getPropertyPriority(property);
2568
- declarations.push(`${property}: ${value}${priority ? ` !${priority}` : ""}`);
2569
- }
2570
- return declarations.join("; ");
2769
+ function trimAsciiWhitespace(value) {
2770
+ let start = 0;
2771
+ let end = value.length;
2772
+ while (start < end && isAsciiWhitespace(value[start])) start++;
2773
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
2774
+ return value.slice(start, end);
2775
+ }
2776
+ var forbidden = (reason) => ({ kind: "forbidden", reason });
2777
+ function parseMetaRefreshContent(content) {
2778
+ const raw = trimAsciiWhitespace(stripControlChars(content));
2779
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
2780
+ let i = 0;
2781
+ while (i < raw.length && raw[i] >= "0" && raw[i] <= "9") i++;
2782
+ if (i === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
2783
+ const delay = Number.parseInt(raw.slice(0, i), 10);
2784
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
2785
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
2786
+ if (i === raw.length) return { kind: "delay-only", delay };
2787
+ if (raw[i] !== ";" && raw[i] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
2788
+ i++;
2789
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
2790
+ const keyStart = i;
2791
+ while (i < raw.length && !isAsciiWhitespace(raw[i]) && raw[i] !== "=") i++;
2792
+ const key = raw.slice(keyStart, i).toLowerCase();
2793
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
2794
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
2795
+ if (raw[i] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
2796
+ i++;
2797
+ while (i < raw.length && isAsciiWhitespace(raw[i])) i++;
2798
+ let destination;
2799
+ const quote = raw[i];
2800
+ if (quote === '"' || quote === "'") {
2801
+ const close = raw.indexOf(quote, i + 1);
2802
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
2803
+ destination = raw.slice(i + 1, close);
2804
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
2805
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
2806
+ } else {
2807
+ destination = trimAsciiWhitespace(raw.slice(i));
2808
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
2809
+ }
2810
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
2811
+ const sanitizedDestination = sanitizeUrl(destination);
2812
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
2813
+ return { kind: "allowed", delay, destination, sanitizedDestination };
2814
+ }
2815
+ function resolveMetaRefreshPolicy(attributes) {
2816
+ let httpEquiv;
2817
+ let content;
2818
+ let duplicateName;
2819
+ const seen = /* @__PURE__ */ new Set();
2820
+ for (const [key, value] of attributes) {
2821
+ const canonical = canonicalAttrName(key);
2822
+ if (seen.has(canonical)) duplicateName = canonical;
2823
+ seen.add(canonical);
2824
+ if (canonical === "http-equiv") httpEquiv = value;
2825
+ else if (canonical === "content") content = value;
2826
+ }
2827
+ if (duplicateName !== void 0) {
2828
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
2829
+ }
2830
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
2831
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
2832
+ return { kind: "not-refresh" };
2833
+ }
2834
+ if (typeof content !== "string") return { kind: "not-refresh" };
2835
+ return parseMetaRefreshContent(content);
2571
2836
  }
2572
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
2573
- "href",
2574
- "xlink:href",
2575
- "src",
2576
- "action",
2577
- "formaction",
2578
- "formtarget",
2579
- "cite",
2580
- "poster",
2581
- "background",
2582
- "srcset",
2583
- "ping",
2584
- "data",
2585
- // `<html manifest>` is a URL sink too. It was carried only by the SSR
2586
- // serializers' private copy of this set; folding that copy into this one would
2587
- // otherwise have silently dropped its coverage.
2588
- "manifest"
2589
- ]);
2590
- var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
2591
- function isHtmlContentAttribute(name) {
2592
- return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
2837
+
2838
+ // src/utils/elementPolicy.ts
2839
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
2840
+ var SVG_NS = "http://www.w3.org/2000/svg";
2841
+ var BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
2842
+ function canonicalTagName(tag) {
2843
+ return canonicalAttrName(tag);
2844
+ }
2845
+ function isBlockedElement(tag) {
2846
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
2847
+ }
2848
+ var ContextualRefusal = {
2849
+ None: 0,
2850
+ /** The element would carry a refresh directive the shared policy forbids. */
2851
+ MetaRefreshForbidden: 1,
2852
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
2853
+ MetaRefreshReactive: 2,
2854
+ /** A runtime value may not choose which program a `<script>` runs. */
2855
+ ScriptSource: 3,
2856
+ /** An SVG animation may not target a URL, handler or document attribute. */
2857
+ AnimationTarget: 4,
2858
+ /** A runtime-written `href` may not become an applied stylesheet. */
2859
+ StylesheetSource: 5
2860
+ };
2861
+ var SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
2862
+ var SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
2863
+ var reactiveMetaElements = /* @__PURE__ */ new WeakSet();
2864
+ function isHtmlNamespace(el) {
2865
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
2866
+ }
2867
+ function metaSnapshot(el, canonical, value) {
2868
+ const snapshot = /* @__PURE__ */ new Map();
2869
+ const attrs = el.attributes;
2870
+ for (let i = 0; i < attrs.length; i++) {
2871
+ const attr = attrs[i];
2872
+ if (attr.name === canonical) continue;
2873
+ snapshot.set(attr.name, attr.value);
2874
+ }
2875
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
2876
+ return snapshot;
2877
+ }
2878
+ function claimReactiveMeta(el) {
2879
+ if (reactiveMetaElements.has(el)) return;
2880
+ reactiveMetaElements.add(el);
2881
+ if (el.getAttribute("content") === null) return;
2882
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
2883
+ el.removeAttribute("content");
2884
+ }
2885
+ }
2886
+ function metaRefusal(el, name, value, origin) {
2887
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
2888
+ const canonical = canonicalAttrName(name);
2889
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
2890
+ if (origin === "reactive") claimReactiveMeta(el);
2891
+ if (value === null) return ContextualRefusal.None;
2892
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
2893
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
2894
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
2895
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
2896
+ return ContextualRefusal.None;
2897
+ }
2898
+ function animationRefusal(name, value) {
2899
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
2900
+ const target = stripControlChars(value);
2901
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
2902
+ }
2903
+ var runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
2904
+ function relHasStylesheet(rel) {
2905
+ if (rel === null) return false;
2906
+ const lower = canonicalAttrName(rel);
2907
+ if (!lower.includes("stylesheet")) return false;
2908
+ const tokens = lower.split(/[\t\n\f\r ]+/);
2909
+ for (let i = 0; i < tokens.length; i++) if (tokens[i] === "stylesheet") return true;
2910
+ return false;
2593
2911
  }
2594
- function sanitizeAttributeString(attr, value, context) {
2595
- const lower = attr.toLowerCase();
2596
- if (lower === "srcset") return sanitizeSrcset(value);
2597
- if (URL_ATTRIBUTES.has(lower)) return sanitizeUrl(value);
2598
- if (lower === "style") return sanitizeStyleAttribute(value, context);
2599
- return value;
2912
+ function stylesheetRefusal(el, name, value, origin) {
2913
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
2914
+ const canonical = canonicalAttrName(name);
2915
+ if (canonical === "href") {
2916
+ if (origin === "static" || value === null) return ContextualRefusal.None;
2917
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
2918
+ runtimeHrefLinks.add(el);
2919
+ return ContextualRefusal.None;
2920
+ }
2921
+ if (canonical === "rel") {
2922
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
2923
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
2924
+ }
2925
+ return ContextualRefusal.None;
2926
+ }
2927
+ function contextualAttributeRefusal(el, name, value, origin) {
2928
+ const local = el.localName;
2929
+ if (local === "meta") return metaRefusal(el, name, value, origin);
2930
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
2931
+ if (local === "script") {
2932
+ if (origin === "static" || value === null) return ContextualRefusal.None;
2933
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
2934
+ }
2935
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
2936
+ return ContextualRefusal.None;
2937
+ }
2938
+ function settleAttributeProvenance(el, name, origin) {
2939
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
2940
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
2941
+ else runtimeHrefLinks.delete(el);
2600
2942
  }
2601
2943
 
2602
2944
  // src/utils/setSafeAttribute.ts
2603
- var HTML_NS = "http://www.w3.org/1999/xhtml";
2604
- var SVG_NS = "http://www.w3.org/2000/svg";
2945
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
2946
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
2605
2947
  var XLINK_NS = "http://www.w3.org/1999/xlink";
2606
2948
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
2607
2949
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
2608
2950
  function isHtmlElement(el) {
2609
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
2951
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
2952
+ }
2953
+ function warnContextualRefusal(label, name, refusal) {
2954
+ devWarnLazy(() => {
2955
+ const reasons = [
2956
+ "",
2957
+ '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)',
2958
+ "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",
2959
+ "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",
2960
+ "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",
2961
+ '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'
2962
+ ];
2963
+ return `${label}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
2964
+ });
2610
2965
  }
2611
2966
  function setProp(el, key, val) {
2612
2967
  el[key] = val;
2613
2968
  }
2614
2969
  function namespaceFor(el, name) {
2615
- if (el.namespaceURI !== SVG_NS) return null;
2970
+ if (el.namespaceURI !== SVG_NS2) return null;
2616
2971
  const lower = name.toLowerCase();
2617
2972
  if (lower.startsWith("xlink:")) return XLINK_NS;
2618
2973
  if (lower.startsWith("xml:")) return XML_NS;
2619
2974
  return null;
2620
2975
  }
2621
2976
  function setSafeAttribute(el, name, value, options = {}) {
2977
+ const written = commitSafeAttribute(el, name, value, options);
2978
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
2979
+ return written;
2980
+ }
2981
+ function commitSafeAttribute(el, name, value, options) {
2622
2982
  const ns = namespaceFor(el, name);
2623
2983
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
2624
2984
  if (isEventHandlerAttr(name)) {
@@ -2631,6 +2991,14 @@ function setSafeAttribute(el, name, value, options = {}) {
2631
2991
  else el.removeAttribute(name);
2632
2992
  return false;
2633
2993
  }
2994
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
2995
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
2996
+ if (refusal !== ContextualRefusal.None) {
2997
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
2998
+ if (ns) el.removeAttributeNS(ns, localName);
2999
+ else el.removeAttribute(name);
3000
+ return false;
3001
+ }
2634
3002
  if (value == null) {
2635
3003
  if (ns) el.removeAttributeNS(ns, localName);
2636
3004
  else el.removeAttribute(name);
@@ -2674,7 +3042,12 @@ function setSafeAttribute(el, name, value, options = {}) {
2674
3042
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
2675
3043
  return true;
2676
3044
  }
2677
- const safe = sanitizeAttributeString(name, str, { element: el });
3045
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
3046
+ if (safe === null) {
3047
+ if (ns) el.removeAttributeNS(ns, localName);
3048
+ else el.removeAttribute(name);
3049
+ return true;
3050
+ }
2678
3051
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
2679
3052
  if (current === safe) return true;
2680
3053
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -2697,7 +3070,7 @@ function bindAttribute(el, attr, getter) {
2697
3070
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
2698
3071
  return;
2699
3072
  }
2700
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
3073
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
2701
3074
  }
2702
3075
  return reactiveBinding2(commit, el);
2703
3076
  }
@@ -2734,7 +3107,7 @@ function bindBoolAttr(el, attr, getter) {
2734
3107
  reportError(err, { phase: "binding", name: "bindBoolAttr", node: el });
2735
3108
  return;
2736
3109
  }
2737
- setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr" });
3110
+ setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr", reactive: true });
2738
3111
  }
2739
3112
  return reactiveBinding2(commit, el);
2740
3113
  }
@@ -3182,12 +3555,17 @@ function effect(effectFn, options) {
3182
3555
  try {
3183
3556
  retrack2(ctx.bodyFn, ctx.subscriber);
3184
3557
  if (ctx.rerunPending) drainReruns(ctx);
3558
+ } catch (err) {
3559
+ ctx.disposed = true;
3560
+ if (ctx.userCleanups.length > 0) flushUserCleanups(ctx);
3561
+ cleanup2(ctx.subscriber);
3562
+ throw err;
3185
3563
  } finally {
3186
3564
  ctx.running = false;
3187
3565
  ctx.rerunPending = false;
3188
3566
  }
3189
3567
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3190
- if (hook) hook.emit("effect:create", { effectFn });
3568
+ if (hook) emitDevtools(hook, "effect:create", { effectFn });
3191
3569
  return () => disposeEffect(ctx);
3192
3570
  }
3193
3571
 
@@ -3375,7 +3753,7 @@ function scrollLock() {
3375
3753
  }
3376
3754
 
3377
3755
  // src/platform/customElement.ts
3378
- var SVG_NS2 = "http://www.w3.org/2000/svg";
3756
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
3379
3757
  var MAX_RENDER_PASSES = 10;
3380
3758
  var ABANDONED_RENDER = /* @__PURE__ */ Symbol("sibujs.defineElement.abandonedRender");
3381
3759
  function defineElement(name, component, options = {}) {
@@ -3524,7 +3902,10 @@ function defineElement(name, component, options = {}) {
3524
3902
  customElements.define(name, SibuElement);
3525
3903
  }
3526
3904
  function svgElement(tag, props = {}, ...nodes) {
3527
- const el = document.createElementNS(SVG_NS2, tag);
3905
+ if (isBlockedElement(tag)) {
3906
+ throw new Error(`svgElement: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
3907
+ }
3908
+ const el = document.createElementNS(SVG_NS3, tag);
3528
3909
  for (const [key, value] of Object.entries(props)) {
3529
3910
  if (key === "nodes") continue;
3530
3911
  if (typeof value === "function" && isEventHandlerAttr(key)) {