sibujs 4.7.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/browser.cjs +45 -4
  2. package/dist/browser.d.cts +23 -0
  3. package/dist/browser.d.ts +23 -0
  4. package/dist/browser.js +6 -5
  5. package/dist/build.cjs +513 -78
  6. package/dist/build.d.cts +22 -8
  7. package/dist/build.d.ts +22 -8
  8. package/dist/build.js +68 -21
  9. package/dist/cdn.dev.global.js +15 -13
  10. package/dist/cdn.full.dev.global.js +15 -13
  11. package/dist/cdn.full.global.js +14 -12
  12. package/dist/cdn.global.js +14 -12
  13. package/dist/chunk-3ACAAQIN.js +224 -0
  14. package/dist/{chunk-DKXRACVN.js → chunk-44MTL7ED.js} +6 -6
  15. package/dist/{chunk-3QWBSL5R.js → chunk-4WMSTZ7J.js} +8 -5
  16. package/dist/{chunk-H3SRKIYX.js → chunk-62BDYGAG.js} +8 -0
  17. package/dist/{chunk-BSY63EM6.js → chunk-C5PEWEGH.js} +20 -5
  18. package/dist/{chunk-BC2SECJD.js → chunk-C7AA2CZX.js} +1 -1
  19. package/dist/{chunk-SLM3IA34.js → chunk-CZPRSWSS.js} +1 -1
  20. package/dist/{chunk-FQRUXCEE.js → chunk-DSID757X.js} +1 -1
  21. package/dist/{chunk-ZVL7TY4K.js → chunk-EGUQLBHE.js} +22 -8
  22. package/dist/{chunk-2INLLLMZ.js → chunk-EXXSN3BW.js} +202 -26
  23. package/dist/{chunk-HURREPU2.js → chunk-FC6KCJSA.js} +87 -150
  24. package/dist/{chunk-Z3OHK6QT.js → chunk-GSGNX35Q.js} +5 -5
  25. package/dist/{chunk-XYV3EDB7.js → chunk-J3YQ457H.js} +37 -20
  26. package/dist/{chunk-VZSG24LS.js → chunk-JUTFX2RM.js} +18 -10
  27. package/dist/{chunk-RUSSKG6G.js → chunk-KJ3UKABJ.js} +1 -1
  28. package/dist/{chunk-UNWRJRKC.js → chunk-LI4W3U6C.js} +1 -1
  29. package/dist/{chunk-FNJXNGYZ.js → chunk-O6TRVMJI.js} +58 -6
  30. package/dist/{chunk-7LUJQAOJ.js → chunk-Q5J2LVKH.js} +1 -1
  31. package/dist/{chunk-4AWA2PVD.js → chunk-QGMSESDD.js} +10 -5
  32. package/dist/{chunk-XZR4PXRE.js → chunk-RGLPEBDE.js} +13 -10
  33. package/dist/{chunk-JWKYU5GV.js → chunk-TQQUN4WR.js} +1 -1
  34. package/dist/{chunk-WOLJZUFQ.js → chunk-UCNP3HVM.js} +12 -8
  35. package/dist/{chunk-WEQ3DMVL.js → chunk-V3LHZSU5.js} +1 -1
  36. package/dist/{chunk-UOL2ECCS.js → chunk-VWT3VXPL.js} +18 -12
  37. package/dist/{chunk-2BPG2XDA.js → chunk-ZVLHNEEL.js} +17 -35
  38. package/dist/{customElement-MmInOW1U.d.ts → customElement-pYDKUzWl.d.cts} +11 -3
  39. package/dist/{customElement-MmInOW1U.d.cts → customElement-pYDKUzWl.d.ts} +11 -3
  40. package/dist/data.cjs +9 -2
  41. package/dist/data.js +6 -6
  42. package/dist/devtools.cjs +1 -1
  43. package/dist/devtools.js +5 -5
  44. package/dist/ecosystem.cjs +400 -37
  45. package/dist/ecosystem.js +9 -8
  46. package/dist/extras.cjs +621 -252
  47. package/dist/extras.d.cts +2 -2
  48. package/dist/extras.d.ts +2 -2
  49. package/dist/extras.js +24 -23
  50. package/dist/index.cjs +464 -70
  51. package/dist/index.d.cts +28 -2
  52. package/dist/index.d.ts +28 -2
  53. package/dist/index.js +16 -12
  54. package/dist/motion.cjs +1 -1
  55. package/dist/motion.js +3 -3
  56. package/dist/patterns.cjs +10 -7
  57. package/dist/patterns.js +6 -6
  58. package/dist/performance.cjs +31 -2
  59. package/dist/performance.d.cts +12 -2
  60. package/dist/performance.d.ts +12 -2
  61. package/dist/performance.js +8 -6
  62. package/dist/plugins.cjs +558 -231
  63. package/dist/plugins.d.cts +8 -3
  64. package/dist/plugins.d.ts +8 -3
  65. package/dist/plugins.js +22 -28
  66. package/dist/{ssr-XOTUASDO.js → ssr-5OWAH3U2.js} +4 -2
  67. package/dist/{ssr-Bli9XRW5.d.ts → ssr-C8hgIAw7.d.cts} +6 -0
  68. package/dist/{ssr-Bli9XRW5.d.cts → ssr-C8hgIAw7.d.ts} +6 -0
  69. package/dist/ssr.cjs +457 -131
  70. package/dist/ssr.d.cts +55 -5
  71. package/dist/ssr.d.ts +55 -5
  72. package/dist/ssr.js +11 -9
  73. package/dist/testing.cjs +3 -3
  74. package/dist/testing.js +4 -4
  75. package/dist/ui.cjs +608 -240
  76. package/dist/ui.d.cts +1 -1
  77. package/dist/ui.d.ts +1 -1
  78. package/dist/ui.js +9 -8
  79. package/dist/widgets.cjs +1 -1
  80. package/dist/widgets.js +7 -7
  81. package/package.json +5 -4
package/dist/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.8.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
  }
@@ -3075,8 +3079,10 @@ function socket(url, options) {
3075
3079
  function stripControlChars(value) {
3076
3080
  return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
3077
3081
  }
3082
+ var ASCII_UPPER = /[A-Z]/;
3083
+ var ASCII_UPPER_G = /[A-Z]/g;
3078
3084
  function canonicalAttrName(name) {
3079
- return name.replace(/[A-Z]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32));
3085
+ return ASCII_UPPER.test(name) ? name.replace(ASCII_UPPER_G, (ch) => String.fromCharCode(ch.charCodeAt(0) + 32)) : name;
3080
3086
  }
3081
3087
  function isEventHandlerAttr(name) {
3082
3088
  if (name.length < 3) return false;
@@ -3089,6 +3095,9 @@ function sanitizeUrl(url) {
3089
3095
  const probe = stripControlChars(url).trim();
3090
3096
  if (!probe) return "";
3091
3097
  const output2 = url.replace(OUTER_TRIM, "");
3098
+ if (probe.length >= 2 && (probe[0] === "\\" || probe[0] === "/") && (probe[1] === "\\" || probe[1] === "/") && (probe[0] === "\\" || probe[1] === "\\")) {
3099
+ return "";
3100
+ }
3092
3101
  const lower = probe.toLowerCase();
3093
3102
  let schemeEnd = -1;
3094
3103
  for (let i2 = 0; i2 < lower.length; i2++) {
@@ -3105,19 +3114,58 @@ function sanitizeUrl(url) {
3105
3114
  if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
3106
3115
  return output2;
3107
3116
  }
3108
- var SRCSET_DESCRIPTOR = /^\s+\d+(\.\d+)?[wx]$/;
3117
+ var SRCSET_DESCRIPTOR = /^\d+(\.\d+)?[wx]$/;
3118
+ function isSrcsetWhitespace(ch) {
3119
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
3120
+ }
3109
3121
  function sanitizeSrcset(value) {
3110
- const parts = value.split(",");
3122
+ const input2 = String(value);
3111
3123
  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]);
3124
+ let i2 = 0;
3125
+ const n = input2.length;
3126
+ while (i2 < n) {
3127
+ while (i2 < n && (isSrcsetWhitespace(input2[i2]) || input2[i2] === ",")) i2++;
3128
+ if (i2 >= n) break;
3129
+ const urlStart = i2;
3130
+ while (i2 < n && !isSrcsetWhitespace(input2[i2])) i2++;
3131
+ let url = input2.slice(urlStart, i2);
3132
+ const descriptors = [];
3133
+ if (url.endsWith(",")) {
3134
+ url = url.replace(/,+$/, "");
3135
+ } else {
3136
+ let current = "";
3137
+ let inParens = false;
3138
+ while (i2 < n) {
3139
+ const ch = input2[i2];
3140
+ if (inParens) {
3141
+ current += ch;
3142
+ if (ch === ")") inParens = false;
3143
+ i2++;
3144
+ continue;
3145
+ }
3146
+ if (isSrcsetWhitespace(ch)) {
3147
+ if (current) descriptors.push(current);
3148
+ current = "";
3149
+ i2++;
3150
+ continue;
3151
+ }
3152
+ if (ch === ",") {
3153
+ i2++;
3154
+ break;
3155
+ }
3156
+ if (ch === "(") inParens = true;
3157
+ current += ch;
3158
+ i2++;
3159
+ }
3160
+ if (current) descriptors.push(current);
3161
+ }
3162
+ if (url === "") continue;
3163
+ if (descriptors.length > 1) continue;
3164
+ if (descriptors.length === 1 && !SRCSET_DESCRIPTOR.test(descriptors[0])) continue;
3165
+ const safe = sanitizeUrl(url);
3119
3166
  if (!safe) continue;
3120
- out.push(m[2] ? `${safe}${m[2]}` : safe);
3167
+ if (safe.startsWith(",") || safe.endsWith(",")) continue;
3168
+ out.push(descriptors.length === 1 ? `${safe} ${descriptors[0]}` : safe);
3121
3169
  }
3122
3170
  return out.join(", ");
3123
3171
  }
@@ -3176,8 +3224,16 @@ var BLOCKED_CSS_NEEDLES = [
3176
3224
  "behavior:",
3177
3225
  "@import",
3178
3226
  "image-set(",
3179
- "filter:progid"
3227
+ "filter:progid",
3228
+ // CSS Images 4 `image("…")` and CSS Values 5 `src("…")` take a URL as a
3229
+ // plain STRING, so they fetch without ever spelling `url(`. Support is thin
3230
+ // today, which is exactly when a blocklist should get ahead of it.
3231
+ "image(",
3232
+ "src("
3180
3233
  ];
3234
+ var CSS_REASON_MOZ_BINDING = 4;
3235
+ var CSS_REASON_BEHAVIOR = 5;
3236
+ var CSS_REASON_PROGID = 8;
3181
3237
  function describeElement(el) {
3182
3238
  if (!el) return "an element";
3183
3239
  let out = el.tagName.toLowerCase();
@@ -3201,7 +3257,9 @@ function warnDroppedDeclaration(value, reason, ctx) {
3201
3257
  "behavior: loads and runs a remote HTC script",
3202
3258
  "@import pulls in a remote stylesheet",
3203
3259
  "image-set() fetches remote images, the same exfiltration channel as url()",
3204
- "filter:progid activates legacy scriptable filters"
3260
+ "filter:progid activates legacy scriptable filters",
3261
+ "image() fetches its string argument as a URL, the same exfiltration channel as url()",
3262
+ "src() fetches its string argument as a URL, the same exfiltration channel as url()"
3205
3263
  ];
3206
3264
  const declaration = ctx?.property ? `${ctx.property}: ${value}` : value;
3207
3265
  const where = describeElement(ctx?.element);
@@ -3216,27 +3274,145 @@ function warnDroppedDeclaration(value, reason, ctx) {
3216
3274
  return `style declaration "${declaration}" was dropped by the style sanitizer on ${where} \u2014 ${reasons[reason]}. The element renders without it, which usually looks like a missing image rather than an error. Sanctioned alternatives: for a content image use an <img> element (or a <picture>); for decoration, put the rule in a stylesheet and set a class instead \u2014 stylesheet CSS is not sanitized, only inline style attributes are. If this declaration is not yours, it came from untrusted data and the drop just prevented an exfiltration.`;
3217
3275
  });
3218
3276
  }
3277
+ function normalizeCssForScan(value) {
3278
+ const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
3279
+ return normalized.toLowerCase().replace(/\s+/g, "");
3280
+ }
3219
3281
  function blockedCssReason(value) {
3220
3282
  if (!CSS_DANGER_GATE.test(value)) return -1;
3221
- const normalized = value.includes("\\") ? decodeCssEscapes(value) : value;
3222
- const lower = normalized.toLowerCase().replace(/\s+/g, "");
3283
+ const lower = normalizeCssForScan(value);
3223
3284
  for (let i2 = 0; i2 < BLOCKED_CSS_NEEDLES.length; i2++) {
3224
3285
  if (lower.includes(BLOCKED_CSS_NEEDLES[i2])) return i2;
3225
3286
  }
3226
3287
  return -1;
3227
3288
  }
3228
- function sanitizeCSSValue(value, context) {
3229
- const reason = blockedCssReason(value);
3289
+ var STYLESHEET_BLOCKED_FUNCTIONS = ["url", "image-set", "image", "src", "expression"];
3290
+ function stripCssFunctions(css) {
3291
+ const pattern2 = new RegExp(`(?:-webkit-)?(${STYLESHEET_BLOCKED_FUNCTIONS.join("|")})\\s*\\(`, "gi");
3292
+ let out = "";
3293
+ let last = 0;
3294
+ for (let match = pattern2.exec(css); match !== null; match = pattern2.exec(css)) {
3295
+ const before = match.index > 0 ? css[match.index - 1] : "";
3296
+ if (/[\w-]/.test(before)) continue;
3297
+ let depth = 1;
3298
+ let i2 = pattern2.lastIndex;
3299
+ let quote2 = "";
3300
+ for (; i2 < css.length && depth > 0; i2++) {
3301
+ const ch = css[i2];
3302
+ if (quote2) {
3303
+ if (ch === "\\") i2++;
3304
+ else if (ch === quote2) quote2 = "";
3305
+ } else if (ch === '"' || ch === "'") quote2 = ch;
3306
+ else if (ch === "(") depth++;
3307
+ else if (ch === ")") depth--;
3308
+ }
3309
+ out += `${css.slice(last, match.index)}/* ${match[1].toLowerCase()}() removed */`;
3310
+ last = i2;
3311
+ pattern2.lastIndex = i2;
3312
+ }
3313
+ return out + css.slice(last);
3314
+ }
3315
+ function sanitizeStylesheetText(css) {
3316
+ let text2 = css.includes("\\") ? decodeCssEscapes(css) : css;
3317
+ text2 = text2.replace(/@import\b[^;{}]*;?/gi, "/* @import removed */");
3318
+ text2 = stripCssFunctions(text2);
3319
+ text2 = text2.replace(/(^|[;{\s])behavior\s*:[^;}]*;?/gi, "$1/* behavior removed */");
3320
+ text2 = text2.replace(/(^|[;{\s])-moz-binding\s*:[^;}]*;?/gi, "$1/* -moz-binding removed */");
3321
+ text2 = text2.replace(/(^|[;{\s])(-ms-)?filter\s*:[^;}]*progid[^;}]*;?/gi, "$1/* filter:progid removed */");
3322
+ return text2;
3323
+ }
3324
+ function sanitizeCSSDeclaration(property, value, context) {
3325
+ const reason = blockedDeclarationReason(property, value);
3230
3326
  if (reason === -1) return value;
3231
- warnDroppedDeclaration(value, reason, context);
3327
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
3232
3328
  return "";
3233
3329
  }
3330
+ function blockedDeclarationReason(property, value) {
3331
+ const reason = blockedCssReason(value);
3332
+ if (reason !== -1) return reason;
3333
+ const name = canonicalAttrName(property);
3334
+ if (name === "behavior") return CSS_REASON_BEHAVIOR;
3335
+ if (name === "-moz-binding") return CSS_REASON_MOZ_BINDING;
3336
+ if ((name === "filter" || name === "-ms-filter") && normalizeCssForScan(value).includes("progid:")) {
3337
+ return CSS_REASON_PROGID;
3338
+ }
3339
+ return -1;
3340
+ }
3341
+ function parseStyleDeclarations(css) {
3342
+ const chunks = [];
3343
+ let buf = "";
3344
+ let depth = 0;
3345
+ let quote2 = "";
3346
+ for (let i2 = 0; i2 < css.length; i2++) {
3347
+ const ch = css[i2];
3348
+ if (quote2) {
3349
+ buf += ch;
3350
+ if (ch === "\\" && i2 + 1 < css.length) buf += css[++i2];
3351
+ else if (ch === quote2) quote2 = "";
3352
+ continue;
3353
+ }
3354
+ if (ch === "\\") {
3355
+ buf += ch;
3356
+ if (i2 + 1 < css.length) buf += css[++i2];
3357
+ continue;
3358
+ }
3359
+ if (ch === "/" && css[i2 + 1] === "*") {
3360
+ const end = css.indexOf("*/", i2 + 2);
3361
+ if (end === -1) return null;
3362
+ i2 = end + 1;
3363
+ continue;
3364
+ }
3365
+ if (ch === '"' || ch === "'") quote2 = ch;
3366
+ else if (ch === "(" || ch === "[") depth++;
3367
+ else if (ch === ")" || ch === "]") {
3368
+ if (--depth < 0) return null;
3369
+ } else if (ch === "{" || ch === "}") return null;
3370
+ else if (ch === ";" && depth === 0) {
3371
+ chunks.push(buf);
3372
+ buf = "";
3373
+ continue;
3374
+ }
3375
+ buf += ch;
3376
+ }
3377
+ if (quote2 || depth !== 0) return null;
3378
+ chunks.push(buf);
3379
+ const out = [];
3380
+ for (const chunk of chunks) {
3381
+ const colon = chunk.indexOf(":");
3382
+ if (colon === -1) continue;
3383
+ const rawName = chunk.slice(0, colon).trim();
3384
+ if (!/^(--[\w-]+|-?[a-zA-Z_][\w-]*)$/.test(rawName)) continue;
3385
+ let value = chunk.slice(colon + 1).trim();
3386
+ const bang = /!\s*important\s*$/i.exec(value);
3387
+ if (bang) value = value.slice(0, bang.index).trim();
3388
+ if (value === "") continue;
3389
+ const property = rawName.startsWith("--") ? rawName : canonicalAttrName(rawName);
3390
+ out.push([property, value, bang !== null]);
3391
+ }
3392
+ return out;
3393
+ }
3394
+ function sanitizeStyleDeclarationList(cssText, context) {
3395
+ const input2 = String(cssText);
3396
+ const declarations = parseStyleDeclarations(input2);
3397
+ if (declarations === null) {
3398
+ if (blockedCssReason(input2) !== -1) warnDroppedDeclaration(input2, blockedCssReason(input2), context);
3399
+ return "";
3400
+ }
3401
+ const kept = [];
3402
+ for (const [property, value, important] of declarations) {
3403
+ const reason = blockedDeclarationReason(property, value);
3404
+ if (reason !== -1) {
3405
+ warnDroppedDeclaration(value, reason, { property, element: context?.element });
3406
+ continue;
3407
+ }
3408
+ kept.push(`${property}: ${value}${important ? " !important" : ""}`);
3409
+ }
3410
+ return kept.join("; ");
3411
+ }
3234
3412
  function sanitizeStyleAttribute(cssText, context) {
3235
3413
  const input2 = String(cssText);
3236
3414
  if (input2.trim() === "") return "";
3237
- if (typeof document === "undefined") {
3238
- return sanitizeCSSValue(input2, context) === "" ? "" : input2;
3239
- }
3415
+ if (typeof document === "undefined") return sanitizeStyleDeclarationList(input2, context);
3240
3416
  const probe = document.createElement("div");
3241
3417
  try {
3242
3418
  probe.style.cssText = input2;
@@ -3247,8 +3423,7 @@ function sanitizeStyleAttribute(cssText, context) {
3247
3423
  for (let i2 = 0; i2 < probe.style.length; i2++) {
3248
3424
  const property = probe.style[i2];
3249
3425
  const value = probe.style.getPropertyValue(property);
3250
- let reason = blockedCssReason(value);
3251
- if (reason === -1) reason = blockedCssReason(`${property}:${value}`);
3426
+ const reason = blockedDeclarationReason(property, value);
3252
3427
  if (reason !== -1) {
3253
3428
  warnDroppedDeclaration(value, reason, { property, element: context?.element });
3254
3429
  continue;
@@ -3296,6 +3471,9 @@ var HTML_CONTENT_ATTRIBUTES = /* @__PURE__ */ new Set(["srcdoc"]);
3296
3471
  function isHtmlContentAttribute(name) {
3297
3472
  return HTML_CONTENT_ATTRIBUTES.has(name.toLowerCase());
3298
3473
  }
3474
+ function isUrlAttribute(attr) {
3475
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
3476
+ }
3299
3477
  function isPolicyAttribute(name) {
3300
3478
  const lower = name.toLowerCase();
3301
3479
  return lower === "style" || URL_ATTRIBUTES.has(lower);
@@ -3307,6 +3485,11 @@ function sanitizeAttributeString(attr, value, context) {
3307
3485
  if (lower === "style") return sanitizeStyleAttribute(value, context);
3308
3486
  return value;
3309
3487
  }
3488
+ function resolveAttributeValue(attr, value, context) {
3489
+ if (!isPolicyAttribute(attr)) return value;
3490
+ const safe = sanitizeAttributeString(attr, value, context);
3491
+ return safe === "" ? null : safe;
3492
+ }
3310
3493
 
3311
3494
  // src/ui/stream.ts
3312
3495
  function validateSseUrl(raw) {
@@ -3730,6 +3913,13 @@ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototyp
3730
3913
  function isUnsafeKey(key) {
3731
3914
  return UNSAFE_KEYS.has(key);
3732
3915
  }
3916
+ function readOwnGlobal(key) {
3917
+ if (typeof window === "undefined") return void 0;
3918
+ if (!Object.hasOwn(window, key)) return void 0;
3919
+ const value = window[key];
3920
+ if (typeof Node !== "undefined" && value instanceof Node) return void 0;
3921
+ return value;
3922
+ }
3733
3923
  function stripUnsafeKeys(obj) {
3734
3924
  const out = {};
3735
3925
  for (const k of Object.keys(obj)) {
@@ -4047,13 +4237,19 @@ function colorScheme() {
4047
4237
  }
4048
4238
 
4049
4239
  // src/browser/format.ts
4240
+ var I18N_STATE = /* @__PURE__ */ Symbol.for("sibujs.i18n.v1");
4241
+ function activeI18nLocale() {
4242
+ const state2 = globalThis[I18N_STATE];
4243
+ if (!state2) return void 0;
4244
+ return state2.getLocale ? state2.getLocale() : state2.locale?.[0]();
4245
+ }
4050
4246
  function formatNumber(value, options) {
4051
4247
  const { locale, ...formatOptions } = options ?? {};
4052
- return new Intl.NumberFormat(locale, formatOptions).format(value);
4248
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), formatOptions).format(value);
4053
4249
  }
4054
4250
  function formatCurrency(value, currency, options) {
4055
4251
  const { locale, ...formatOptions } = options ?? {};
4056
- return new Intl.NumberFormat(locale, {
4252
+ return new Intl.NumberFormat(locale ?? activeI18nLocale(), {
4057
4253
  ...formatOptions,
4058
4254
  style: "currency",
4059
4255
  currency
@@ -5022,6 +5218,11 @@ function imageLoader(src) {
5022
5218
  abandonCurrent();
5023
5219
  resetState();
5024
5220
  if (disposed || token !== startToken) return;
5221
+ const safe = sanitizeUrl(String(url));
5222
+ if (!safe) {
5223
+ setStatus("error");
5224
+ return;
5225
+ }
5025
5226
  const img2 = new Image();
5026
5227
  current = img2;
5027
5228
  currentSettled = false;
@@ -5040,7 +5241,7 @@ function imageLoader(src) {
5040
5241
  currentSettled = true;
5041
5242
  setStatus("error");
5042
5243
  };
5043
- img2.src = url;
5244
+ img2.src = safe;
5044
5245
  }
5045
5246
  let srcEffectTeardown = null;
5046
5247
  if (typeof src === "function") {
@@ -5066,6 +5267,9 @@ function imageLoader(src) {
5066
5267
  }
5067
5268
 
5068
5269
  // src/patterns/machine.ts
5270
+ function ownEntry(table2, key) {
5271
+ return Object.hasOwn(table2, key) ? table2[key] : void 0;
5272
+ }
5069
5273
  function machine(config) {
5070
5274
  const [state2, setState] = signal(config.initial);
5071
5275
  const [context, setContext] = signal(config.context || {});
@@ -5087,9 +5291,9 @@ function machine(config) {
5087
5291
  }
5088
5292
  }
5089
5293
  function step(event) {
5090
- const stateDef = config.states[currentState];
5294
+ const stateDef = ownEntry(config.states, currentState);
5091
5295
  if (!stateDef?.on) return;
5092
- const transition2 = stateDef.on[event];
5296
+ const transition2 = ownEntry(stateDef.on, event);
5093
5297
  if (!transition2) return;
5094
5298
  let target;
5095
5299
  let guard;
@@ -5117,7 +5321,7 @@ function machine(config) {
5117
5321
  if (action) setContext(nextContext);
5118
5322
  setState(target);
5119
5323
  });
5120
- const targetDef = config.states[target];
5324
+ const targetDef = ownEntry(config.states, target);
5121
5325
  if (targetDef?.entry) {
5122
5326
  targetDef.entry(currentContext);
5123
5327
  }
@@ -5127,7 +5331,7 @@ function machine(config) {
5127
5331
  if (processing) return;
5128
5332
  drain();
5129
5333
  }
5130
- const initialDef = config.states[config.initial];
5334
+ const initialDef = ownEntry(config.states, config.initial);
5131
5335
  if (initialDef?.entry) {
5132
5336
  processing = true;
5133
5337
  try {
@@ -5145,9 +5349,9 @@ function machine(config) {
5145
5349
  }
5146
5350
  function can(event) {
5147
5351
  const currentState2 = state2();
5148
- const stateDef = config.states[currentState2];
5352
+ const stateDef = ownEntry(config.states, currentState2);
5149
5353
  if (!stateDef?.on) return false;
5150
- const transition2 = stateDef.on[event];
5354
+ const transition2 = ownEntry(stateDef.on, event);
5151
5355
  if (!transition2) return false;
5152
5356
  if (typeof transition2 === "string") return true;
5153
5357
  if (transition2.guard) {
@@ -7224,31 +7428,6 @@ function announce(message, priority = "polite") {
7224
7428
 
7225
7429
  // src/ui/scopedStyle.ts
7226
7430
  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
7431
  function firstCompoundEnd(selector) {
7253
7432
  let parens = 0;
7254
7433
  let brackets = 0;
@@ -7410,7 +7589,7 @@ function scopeStylesheet(css, attr) {
7410
7589
  function scopedStyle(css) {
7411
7590
  const id = `sibu-s${_scope.n++}`;
7412
7591
  const attr = `data-${id}`;
7413
- const safeCss = sanitizeCSS(css);
7592
+ const safeCss = sanitizeStylesheetText(css);
7414
7593
  const scopedCSS = scopeStylesheet(safeCss, attr);
7415
7594
  if (typeof document !== "undefined") {
7416
7595
  const styleEl = document.createElement("style");
@@ -7436,26 +7615,246 @@ function removeScopedStyle(scopeId) {
7436
7615
  if (el) el.remove();
7437
7616
  }
7438
7617
 
7439
- // src/utils/setSafeAttribute.ts
7618
+ // src/utils/metaRefresh.ts
7619
+ function isAsciiWhitespace(ch) {
7620
+ return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f";
7621
+ }
7622
+ function trimAsciiWhitespace(value) {
7623
+ let start = 0;
7624
+ let end = value.length;
7625
+ while (start < end && isAsciiWhitespace(value[start])) start++;
7626
+ while (end > start && isAsciiWhitespace(value[end - 1])) end--;
7627
+ return value.slice(start, end);
7628
+ }
7629
+ var forbidden = (reason) => ({ kind: "forbidden", reason });
7630
+ function parseMetaRefreshContent(content) {
7631
+ const raw = trimAsciiWhitespace(stripControlChars(content));
7632
+ if (raw === "") return forbidden(DEV ? "empty content" : "");
7633
+ let i2 = 0;
7634
+ while (i2 < raw.length && raw[i2] >= "0" && raw[i2] <= "9") i2++;
7635
+ if (i2 === 0) return forbidden(DEV ? "missing or non-numeric delay" : "");
7636
+ const delay = Number.parseInt(raw.slice(0, i2), 10);
7637
+ if (!Number.isFinite(delay)) return forbidden(DEV ? "unparseable delay" : "");
7638
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7639
+ if (i2 === raw.length) return { kind: "delay-only", delay };
7640
+ if (raw[i2] !== ";" && raw[i2] !== ",") return forbidden(DEV ? "unexpected text after delay" : "");
7641
+ i2++;
7642
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7643
+ const keyStart = i2;
7644
+ while (i2 < raw.length && !isAsciiWhitespace(raw[i2]) && raw[i2] !== "=") i2++;
7645
+ const key = raw.slice(keyStart, i2).toLowerCase();
7646
+ if (key !== "url") return forbidden(DEV ? `unsupported refresh key ${JSON.stringify(key)}` : "");
7647
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7648
+ if (raw[i2] !== "=") return forbidden(DEV ? "missing '=' after url" : "");
7649
+ i2++;
7650
+ while (i2 < raw.length && isAsciiWhitespace(raw[i2])) i2++;
7651
+ let destination;
7652
+ const quote2 = raw[i2];
7653
+ if (quote2 === '"' || quote2 === "'") {
7654
+ const close = raw.indexOf(quote2, i2 + 1);
7655
+ if (close === -1) return forbidden(DEV ? "unterminated quoted destination" : "");
7656
+ destination = raw.slice(i2 + 1, close);
7657
+ const rest = trimAsciiWhitespace(raw.slice(close + 1));
7658
+ if (rest !== "") return forbidden(DEV ? "trailing text after quoted destination" : "");
7659
+ } else {
7660
+ destination = trimAsciiWhitespace(raw.slice(i2));
7661
+ if (/[;,]/.test(destination)) return forbidden(DEV ? "multiple refresh assignments" : "");
7662
+ }
7663
+ if (destination === "") return forbidden(DEV ? "empty destination" : "");
7664
+ const sanitizedDestination = sanitizeUrl(destination);
7665
+ if (sanitizedDestination === "") return forbidden(DEV ? "destination uses a disallowed protocol" : "");
7666
+ return { kind: "allowed", delay, destination, sanitizedDestination };
7667
+ }
7668
+ function resolveMetaRefreshPolicy(attributes) {
7669
+ let httpEquiv;
7670
+ let content;
7671
+ let duplicateName;
7672
+ const seen = /* @__PURE__ */ new Set();
7673
+ for (const [key, value] of attributes) {
7674
+ const canonical = canonicalAttrName(key);
7675
+ if (seen.has(canonical)) duplicateName = canonical;
7676
+ seen.add(canonical);
7677
+ if (canonical === "http-equiv") httpEquiv = value;
7678
+ else if (canonical === "content") content = value;
7679
+ }
7680
+ if (duplicateName !== void 0) {
7681
+ return forbidden(DEV ? `duplicate case-insensitive attribute ${JSON.stringify(duplicateName)}` : "");
7682
+ }
7683
+ if (typeof httpEquiv !== "string") return { kind: "not-refresh" };
7684
+ if (trimAsciiWhitespace(stripControlChars(httpEquiv)).toLowerCase() !== "refresh") {
7685
+ return { kind: "not-refresh" };
7686
+ }
7687
+ if (typeof content !== "string") return { kind: "not-refresh" };
7688
+ return parseMetaRefreshContent(content);
7689
+ }
7690
+ function normalizeMetaAttributes(props) {
7691
+ const out = /* @__PURE__ */ new Map();
7692
+ const seen = /* @__PURE__ */ new Set();
7693
+ for (const key of Object.keys(props)) {
7694
+ const canonical = canonicalAttrName(key);
7695
+ if (seen.has(canonical)) return null;
7696
+ seen.add(canonical);
7697
+ out.set(key, props[key]);
7698
+ }
7699
+ return out;
7700
+ }
7701
+ function isForbiddenMetaEntry(props) {
7702
+ const normalized = normalizeMetaAttributes(props);
7703
+ if (normalized === null) return true;
7704
+ return resolveMetaRefreshPolicy(normalized).kind === "forbidden";
7705
+ }
7706
+
7707
+ // src/utils/elementPolicy.ts
7440
7708
  var HTML_NS = "http://www.w3.org/1999/xhtml";
7441
7709
  var SVG_NS = "http://www.w3.org/2000/svg";
7710
+ var BLOCKED_ELEMENTS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
7711
+ function canonicalTagName(tag) {
7712
+ return canonicalAttrName(tag);
7713
+ }
7714
+ function isBlockedElement(tag) {
7715
+ return BLOCKED_ELEMENTS.has(canonicalTagName(tag));
7716
+ }
7717
+ var CODE_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
7718
+ function isCodeTextElement(tag) {
7719
+ return CODE_TEXT_ELEMENTS.has(canonicalTagName(tag));
7720
+ }
7721
+ var ContextualRefusal = {
7722
+ None: 0,
7723
+ /** The element would carry a refresh directive the shared policy forbids. */
7724
+ MetaRefreshForbidden: 1,
7725
+ /** A `<meta>` with a reactive binding may not carry any refresh directive. */
7726
+ MetaRefreshReactive: 2,
7727
+ /** A runtime value may not choose which program a `<script>` runs. */
7728
+ ScriptSource: 3,
7729
+ /** An SVG animation may not target a URL, handler or document attribute. */
7730
+ AnimationTarget: 4,
7731
+ /** A runtime-written `href` may not become an applied stylesheet. */
7732
+ StylesheetSource: 5
7733
+ };
7734
+ var SCRIPT_SOURCE_ATTRIBUTES = /* @__PURE__ */ new Set(["src", "href", "xlink:href", "type", "language"]);
7735
+ var SVG_ANIMATION_ELEMENTS = /* @__PURE__ */ new Set(["animate", "set", "animateMotion", "animateTransform", "animateColor"]);
7736
+ var reactiveMetaElements = /* @__PURE__ */ new WeakSet();
7737
+ function isHtmlNamespace(el) {
7738
+ return el.namespaceURI === HTML_NS || el.namespaceURI === null;
7739
+ }
7740
+ function metaSnapshot(el, canonical, value) {
7741
+ const snapshot = /* @__PURE__ */ new Map();
7742
+ const attrs = el.attributes;
7743
+ for (let i2 = 0; i2 < attrs.length; i2++) {
7744
+ const attr = attrs[i2];
7745
+ if (attr.name === canonical) continue;
7746
+ snapshot.set(attr.name, attr.value);
7747
+ }
7748
+ if (canonical !== null && value !== null) snapshot.set(canonical, value);
7749
+ return snapshot;
7750
+ }
7751
+ function claimReactiveMeta(el) {
7752
+ if (reactiveMetaElements.has(el)) return;
7753
+ reactiveMetaElements.add(el);
7754
+ if (el.getAttribute("content") === null) return;
7755
+ if (resolveMetaRefreshPolicy(metaSnapshot(el, null, null)).kind !== "not-refresh") {
7756
+ el.removeAttribute("content");
7757
+ }
7758
+ }
7759
+ function metaRefusal(el, name, value, origin) {
7760
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
7761
+ const canonical = canonicalAttrName(name);
7762
+ if (canonical !== "http-equiv" && canonical !== "content") return ContextualRefusal.None;
7763
+ if (origin === "reactive") claimReactiveMeta(el);
7764
+ if (value === null) return ContextualRefusal.None;
7765
+ if (canonical === "content" && !el.hasAttribute("http-equiv")) return ContextualRefusal.None;
7766
+ const decision = resolveMetaRefreshPolicy(metaSnapshot(el, canonical, value));
7767
+ if (decision.kind === "forbidden") return ContextualRefusal.MetaRefreshForbidden;
7768
+ if (decision.kind !== "not-refresh" && reactiveMetaElements.has(el)) return ContextualRefusal.MetaRefreshReactive;
7769
+ return ContextualRefusal.None;
7770
+ }
7771
+ function animationRefusal(name, value) {
7772
+ if (value === null || canonicalAttrName(name) !== "attributename") return ContextualRefusal.None;
7773
+ const target = stripControlChars(value);
7774
+ return isUrlAttribute(target) || isEventHandlerAttr(target) || isHtmlContentAttribute(target) ? ContextualRefusal.AnimationTarget : ContextualRefusal.None;
7775
+ }
7776
+ var runtimeHrefLinks = /* @__PURE__ */ new WeakSet();
7777
+ function relHasStylesheet(rel) {
7778
+ if (rel === null) return false;
7779
+ const lower = canonicalAttrName(rel);
7780
+ if (!lower.includes("stylesheet")) return false;
7781
+ const tokens = lower.split(/[\t\n\f\r ]+/);
7782
+ for (let i2 = 0; i2 < tokens.length; i2++) if (tokens[i2] === "stylesheet") return true;
7783
+ return false;
7784
+ }
7785
+ function stylesheetRefusal(el, name, value, origin) {
7786
+ if (!isHtmlNamespace(el)) return ContextualRefusal.None;
7787
+ const canonical = canonicalAttrName(name);
7788
+ if (canonical === "href") {
7789
+ if (origin === "static" || value === null) return ContextualRefusal.None;
7790
+ if (relHasStylesheet(el.getAttribute("rel"))) return ContextualRefusal.StylesheetSource;
7791
+ runtimeHrefLinks.add(el);
7792
+ return ContextualRefusal.None;
7793
+ }
7794
+ if (canonical === "rel") {
7795
+ if (!relHasStylesheet(value)) return ContextualRefusal.None;
7796
+ return runtimeHrefLinks.has(el) && el.hasAttribute("href") ? ContextualRefusal.StylesheetSource : ContextualRefusal.None;
7797
+ }
7798
+ return ContextualRefusal.None;
7799
+ }
7800
+ function contextualAttributeRefusal(el, name, value, origin) {
7801
+ const local = el.localName;
7802
+ if (local === "meta") return metaRefusal(el, name, value, origin);
7803
+ if (local === "link") return stylesheetRefusal(el, name, value, origin);
7804
+ if (local === "script") {
7805
+ if (origin === "static" || value === null) return ContextualRefusal.None;
7806
+ return SCRIPT_SOURCE_ATTRIBUTES.has(canonicalAttrName(name)) ? ContextualRefusal.ScriptSource : ContextualRefusal.None;
7807
+ }
7808
+ if (SVG_ANIMATION_ELEMENTS.has(local) && el.namespaceURI === SVG_NS) return animationRefusal(name, value);
7809
+ return ContextualRefusal.None;
7810
+ }
7811
+ function settleAttributeProvenance(el, name, origin) {
7812
+ if (el.localName !== "link" || canonicalAttrName(name) !== "href") return;
7813
+ if (origin !== "static" && el.hasAttribute("href")) runtimeHrefLinks.add(el);
7814
+ else runtimeHrefLinks.delete(el);
7815
+ }
7816
+ function isForbiddenMetaSnapshot(attributes) {
7817
+ return resolveMetaRefreshPolicy(attributes).kind === "forbidden";
7818
+ }
7819
+
7820
+ // src/utils/setSafeAttribute.ts
7821
+ var HTML_NS2 = "http://www.w3.org/1999/xhtml";
7822
+ var SVG_NS2 = "http://www.w3.org/2000/svg";
7442
7823
  var XLINK_NS = "http://www.w3.org/1999/xlink";
7443
7824
  var XML_NS = "http://www.w3.org/XML/1998/namespace";
7444
7825
  var BOOLEAN_IDL_ATTRS = /* @__PURE__ */ new Set(["checked", "disabled", "selected"]);
7445
7826
  function isHtmlElement(el) {
7446
- return el.namespaceURI === null || el.namespaceURI === HTML_NS;
7827
+ return el.namespaceURI === null || el.namespaceURI === HTML_NS2;
7828
+ }
7829
+ function warnContextualRefusal(label2, name, refusal) {
7830
+ devWarnLazy(() => {
7831
+ const reasons = [
7832
+ "",
7833
+ 'the element would become a <meta http-equiv="refresh"> directive the shared refresh policy forbids (a destination outside the URL allowlist, or a directive too malformed to read unambiguously)',
7834
+ "a <meta> whose http-equiv or content is reactive may never carry a refresh directive: a browser schedules the navigation as soon as the directive is valid, and nothing can withdraw it when the state changes back",
7835
+ "a runtime value may not choose the program a <script> runs; name the script in static template source, or load it through Head({ script }) as an explicit trust decision",
7836
+ "an SVG animation may not target a URL, event-handler or nested-document attribute; its to / values would be written into that attribute without passing the URL policy",
7837
+ 'a runtime-chosen href may not become an applied <link rel="stylesheet">: a well-formed URL is not a trusted stylesheet. Name the stylesheet in static template source, or load a runtime-chosen one through Head({ link }) as an explicit trust decision'
7838
+ ];
7839
+ return `${label2}: refusing "${name}" in this element's context \u2014 ${reasons[refusal]}. The write was not performed.`;
7840
+ });
7447
7841
  }
7448
7842
  function setProp(el, key, val) {
7449
7843
  el[key] = val;
7450
7844
  }
7451
7845
  function namespaceFor(el, name) {
7452
- if (el.namespaceURI !== SVG_NS) return null;
7846
+ if (el.namespaceURI !== SVG_NS2) return null;
7453
7847
  const lower = name.toLowerCase();
7454
7848
  if (lower.startsWith("xlink:")) return XLINK_NS;
7455
7849
  if (lower.startsWith("xml:")) return XML_NS;
7456
7850
  return null;
7457
7851
  }
7458
7852
  function setSafeAttribute(el, name, value, options = {}) {
7853
+ const written = commitSafeAttribute(el, name, value, options);
7854
+ if (el.localName === "link") settleAttributeProvenance(el, name, options.reactive ? "reactive" : "runtime");
7855
+ return written;
7856
+ }
7857
+ function commitSafeAttribute(el, name, value, options) {
7459
7858
  const ns = namespaceFor(el, name);
7460
7859
  const localName = ns ? name.slice(name.indexOf(":") + 1) : name;
7461
7860
  if (isEventHandlerAttr(name)) {
@@ -7468,6 +7867,14 @@ function setSafeAttribute(el, name, value, options = {}) {
7468
7867
  else el.removeAttribute(name);
7469
7868
  return false;
7470
7869
  }
7870
+ const pending = value == null || value === false ? null : value === true ? "" : String(value);
7871
+ const refusal = contextualAttributeRefusal(el, name, pending, options.reactive ? "reactive" : "runtime");
7872
+ if (refusal !== ContextualRefusal.None) {
7873
+ warnContextualRefusal(options.label ?? "setSafeAttribute", name, refusal);
7874
+ if (ns) el.removeAttributeNS(ns, localName);
7875
+ else el.removeAttribute(name);
7876
+ return false;
7877
+ }
7471
7878
  if (value == null) {
7472
7879
  if (ns) el.removeAttributeNS(ns, localName);
7473
7880
  else el.removeAttribute(name);
@@ -7511,7 +7918,12 @@ function setSafeAttribute(el, name, value, options = {}) {
7511
7918
  setProp(el, idlName, idlName === "checked" ? Boolean(value) : str);
7512
7919
  return true;
7513
7920
  }
7514
- const safe = sanitizeAttributeString(name, str, { element: el });
7921
+ const safe = str === "" ? "" : resolveAttributeValue(name, str, { element: el });
7922
+ if (safe === null) {
7923
+ if (ns) el.removeAttributeNS(ns, localName);
7924
+ else el.removeAttribute(name);
7925
+ return true;
7926
+ }
7515
7927
  const current = ns ? el.getAttributeNS(ns, localName) : el.getAttribute(name);
7516
7928
  if (current === safe) return true;
7517
7929
  if (ns) el.setAttributeNS(ns, name, safe);
@@ -7534,7 +7946,7 @@ function bindAttribute(el, attr, getter) {
7534
7946
  reportError(err, { phase: "binding", name: `bindAttribute("${attr}")`, node: el });
7535
7947
  return;
7536
7948
  }
7537
- setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")` });
7949
+ setSafeAttribute(el, attr, value, { label: `bindAttribute("${attr}")`, reactive: true });
7538
7950
  }
7539
7951
  return reactiveBinding2(commit, el);
7540
7952
  }
@@ -7571,7 +7983,7 @@ function bindBoolAttr(el, attr, getter) {
7571
7983
  reportError(err, { phase: "binding", name: "bindBoolAttr", node: el });
7572
7984
  return;
7573
7985
  }
7574
- setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr" });
7986
+ setSafeAttribute(el, attr, Boolean(value), { label: "bindBoolAttr", reactive: true });
7575
7987
  }
7576
7988
  return reactiveBinding2(commit, el);
7577
7989
  }
@@ -8124,6 +8536,9 @@ var DOMPool = class {
8124
8536
  * Get a recycled element or create a new one.
8125
8537
  */
8126
8538
  acquire(tag) {
8539
+ if (isBlockedElement(tag)) {
8540
+ throw new Error(`DOMPool: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
8541
+ }
8127
8542
  const pool = this.pools.get(tag);
8128
8543
  if (pool && pool.length > 0) {
8129
8544
  const el = pool.pop();
@@ -8229,10 +8644,19 @@ function prefetch(url) {
8229
8644
  }
8230
8645
  function preloadImage(src) {
8231
8646
  return new Promise((resolve, reject) => {
8647
+ const safe = sanitizeUrl(String(src));
8648
+ if (!safe) {
8649
+ reject(
8650
+ new Error(
8651
+ String(src).trim() === "" ? "preloadImage: no image URL was given." : "preloadImage: refusing an image URL outside the URL policy."
8652
+ )
8653
+ );
8654
+ return;
8655
+ }
8232
8656
  const img2 = new Image();
8233
8657
  img2.onload = () => resolve(img2);
8234
8658
  img2.onerror = reject;
8235
- img2.src = src;
8659
+ img2.src = safe;
8236
8660
  });
8237
8661
  }
8238
8662
 
@@ -8258,95 +8682,6 @@ function noSideEffect(fn) {
8258
8682
  return fn;
8259
8683
  }
8260
8684
 
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
8685
  // src/utils/headEntry.ts
8351
8686
  var SAFE_HEAD_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
8352
8687
  function isEmittableHeadAttrName(name) {
@@ -8357,9 +8692,7 @@ function isEmittableHeadAttrName(name) {
8357
8692
  return true;
8358
8693
  }
8359
8694
  function sanitizeHeadAttrValue(canonical, value) {
8360
- if (!isPolicyAttribute(canonical)) return value;
8361
- const safe = sanitizeAttributeString(canonical, value);
8362
- return safe === "" ? null : safe;
8695
+ return resolveAttributeValue(canonical, value);
8363
8696
  }
8364
8697
  function findDuplicateAttributeName(names) {
8365
8698
  const seen = /* @__PURE__ */ new Set();
@@ -8416,15 +8749,9 @@ function serializeHeadEntry(tag, attrs) {
8416
8749
  }
8417
8750
 
8418
8751
  // src/platform/ssr.ts
8419
- function isPolicyAttr(lowerName) {
8420
- return isPolicyAttribute(lowerName);
8421
- }
8422
8752
  function isForbiddenSsrAttr(lowerName) {
8423
8753
  return isHtmlContentAttribute(lowerName);
8424
8754
  }
8425
- function sanitizeSsrAttributeValue(lowerName, value) {
8426
- return sanitizeAttributeString(lowerName, value);
8427
- }
8428
8755
  var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
8429
8756
  function isSafeAttrName(name) {
8430
8757
  return SAFE_ATTR_NAME.test(name);
@@ -8455,6 +8782,64 @@ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
8455
8782
  "track",
8456
8783
  "wbr"
8457
8784
  ]);
8785
+ function findNativeGetter(proto, name) {
8786
+ for (let p2 = proto; p2; p2 = Object.getPrototypeOf(p2)) {
8787
+ const get = Object.getOwnPropertyDescriptor(p2, name)?.get;
8788
+ if (get) return get;
8789
+ }
8790
+ return null;
8791
+ }
8792
+ var nativeAccessorsByRealm = /* @__PURE__ */ new WeakMap();
8793
+ function nativeAccessors() {
8794
+ const realm = Element;
8795
+ let accessors = nativeAccessorsByRealm.get(realm);
8796
+ if (!accessors) {
8797
+ accessors = {
8798
+ attributes: findNativeGetter(Element.prototype, "attributes"),
8799
+ tagName: findNativeGetter(Element.prototype, "tagName"),
8800
+ childNodes: findNativeGetter(Node.prototype, "childNodes"),
8801
+ getAttribute: Element.prototype.getAttribute
8802
+ };
8803
+ nativeAccessorsByRealm.set(realm, accessors);
8804
+ }
8805
+ return accessors;
8806
+ }
8807
+ function attributesOf(el) {
8808
+ const get = nativeAccessors().attributes;
8809
+ return get ? get.call(el) : el.attributes;
8810
+ }
8811
+ function tagNameOf(el) {
8812
+ const get = nativeAccessors().tagName;
8813
+ return get ? get.call(el) : el.tagName;
8814
+ }
8815
+ function childNodesOf(node) {
8816
+ const get = nativeAccessors().childNodes;
8817
+ return get ? get.call(node) : node.childNodes;
8818
+ }
8819
+ function getAttributeOf(el, name) {
8820
+ return nativeAccessors().getAttribute.call(el, name);
8821
+ }
8822
+ function serializeOpenTag(element, tag) {
8823
+ let html2 = `<${tag}`;
8824
+ const meta2 = tag === "meta" ? /* @__PURE__ */ new Map() : null;
8825
+ const attrs = attributesOf(element);
8826
+ for (let i2 = 0; i2 < attrs.length; i2++) {
8827
+ const attr = attrs[i2];
8828
+ const rawName = attr.name;
8829
+ if (!isSafeAttrName(rawName)) continue;
8830
+ if (isEventHandlerAttr(rawName)) continue;
8831
+ if (isForbiddenSsrAttr(rawName)) continue;
8832
+ const value = resolveAttributeValue(rawName.toLowerCase(), attr.value);
8833
+ if (value === null) continue;
8834
+ meta2?.set(rawName, value);
8835
+ html2 += ` ${rawName}="${escapeAttr(value)}"`;
8836
+ }
8837
+ if (meta2 && isForbiddenMetaSnapshot(meta2)) return null;
8838
+ if (!getAttributeOf(element, "data-sibu-hydrate")) {
8839
+ html2 += ` data-sibu-ssr="true"`;
8840
+ }
8841
+ return html2;
8842
+ }
8458
8843
  function renderToString(element) {
8459
8844
  if (element instanceof DocumentFragment) {
8460
8845
  return Array.from(element.childNodes).map((child) => {
@@ -8474,35 +8859,21 @@ function renderToString(element) {
8474
8859
  if (!(element instanceof HTMLElement)) {
8475
8860
  return escapeHtml(element.textContent || "");
8476
8861
  }
8477
- const tag = element.tagName.toLowerCase();
8478
- if (tag === "script" || tag === "style") {
8862
+ const tag = tagNameOf(element).toLowerCase();
8863
+ if (isCodeTextElement(tag)) {
8479
8864
  return DEV ? `<!--ssr:${tag}-stripped-->` : "";
8480
8865
  }
8481
8866
  if (!/^[a-z][a-z0-9-]*$/i.test(tag)) {
8482
8867
  return DEV ? "<!--ssr:invalid-tag-->" : "";
8483
8868
  }
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
- }
8869
+ const openTag = serializeOpenTag(element, tag);
8870
+ if (openTag === null) return DEV ? `<!--ssr:${tag}-stripped-->` : "";
8871
+ let html2 = openTag;
8501
8872
  if (VOID_ELEMENTS.has(tag)) {
8502
8873
  return `${html2} />`;
8503
8874
  }
8504
8875
  html2 += ">";
8505
- for (const child of Array.from(element.childNodes)) {
8876
+ for (const child of Array.from(childNodesOf(element))) {
8506
8877
  try {
8507
8878
  html2 += renderToString(child);
8508
8879
  } catch (err) {
@@ -8652,12 +9023,8 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
8652
9023
  if (!isSafeAttrName(rawKey)) continue;
8653
9024
  if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
8654
9025
  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
- }
9026
+ const value = resolveAttributeValue(rawKey.toLowerCase(), String(attrs[rawKey]));
9027
+ if (value === null) continue;
8661
9028
  out.push(`${rawKey}="${escapeAttr(value)}"`);
8662
9029
  }
8663
9030
  return out.join(" ");
@@ -8720,8 +9087,8 @@ async function* renderToStream(element) {
8720
9087
  yield escapeHtml(element.textContent || "");
8721
9088
  return;
8722
9089
  }
8723
- const tag = element.tagName.toLowerCase();
8724
- if (tag === "script" || tag === "style") {
9090
+ const tag = tagNameOf(element).toLowerCase();
9091
+ if (isCodeTextElement(tag)) {
8725
9092
  if (DEV) yield `<!--ssr:${tag}-stripped-->`;
8726
9093
  return;
8727
9094
  }
@@ -8729,29 +9096,17 @@ async function* renderToStream(element) {
8729
9096
  if (DEV) yield "<!--ssr:invalid-tag-->";
8730
9097
  return;
8731
9098
  }
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"`;
9099
+ const openTag = serializeOpenTag(element, tag);
9100
+ if (openTag === null) {
9101
+ if (DEV) yield `<!--ssr:${tag}-stripped-->`;
9102
+ return;
8748
9103
  }
8749
9104
  if (VOID_ELEMENTS.has(tag)) {
8750
9105
  yield `${openTag} />`;
8751
9106
  return;
8752
9107
  }
8753
9108
  yield `${openTag}>`;
8754
- for (const child of Array.from(element.childNodes)) {
9109
+ for (const child of Array.from(childNodesOf(element))) {
8755
9110
  try {
8756
9111
  yield* renderToStream(child);
8757
9112
  } catch (err) {
@@ -8936,8 +9291,7 @@ function deserializeState(validate) {
8936
9291
  "[SibuJS SSR] deserializeState() called without a validate guard \u2014 tampered SSR payloads will not be detected."
8937
9292
  );
8938
9293
  }
8939
- const w = window;
8940
- const raw = w[SSR_DATA_ATTR];
9294
+ const raw = readOwnGlobal(SSR_DATA_ATTR);
8941
9295
  if (raw === void 0) return void 0;
8942
9296
  if (validate && !validate(raw)) return void 0;
8943
9297
  return raw;
@@ -9524,7 +9878,7 @@ function setCanonical(url) {
9524
9878
  }
9525
9879
 
9526
9880
  // src/platform/customElement.ts
9527
- var SVG_NS2 = "http://www.w3.org/2000/svg";
9881
+ var SVG_NS3 = "http://www.w3.org/2000/svg";
9528
9882
  var MAX_RENDER_PASSES = 10;
9529
9883
  var ABANDONED_RENDER = /* @__PURE__ */ Symbol("sibujs.defineElement.abandonedRender");
9530
9884
  function defineElement(name, component, options = {}) {
@@ -9673,7 +10027,10 @@ function defineElement(name, component, options = {}) {
9673
10027
  customElements.define(name, SibuElement);
9674
10028
  }
9675
10029
  function svgElement(tag, props = {}, ...nodes) {
9676
- const el = document.createElementNS(SVG_NS2, tag);
10030
+ if (isBlockedElement(tag)) {
10031
+ throw new Error(`svgElement: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
10032
+ }
10033
+ const el = document.createElementNS(SVG_NS3, tag);
9677
10034
  for (const [key, value] of Object.entries(props)) {
9678
10035
  if (key === "nodes") continue;
9679
10036
  if (typeof value === "function" && isEventHandlerAttr(key)) {
@@ -9698,7 +10055,16 @@ function toError(err) {
9698
10055
  if (err !== null && typeof err === "object" && "name" in err && "message" in err) return err;
9699
10056
  return new Error(String(err));
9700
10057
  }
10058
+ function workerSource(fn, api) {
10059
+ if (typeof fn !== "function") {
10060
+ throw new TypeError(
10061
+ `${api}: expected a function, got ${typeof fn}. Worker code must be a function defined in your source \u2014 a string would be evaluated as code.`
10062
+ );
10063
+ }
10064
+ return Function.prototype.toString.call(fn);
10065
+ }
9701
10066
  function worker(workerFn2) {
10067
+ const fnBody = workerSource(workerFn2, "worker");
9702
10068
  const [result, setResult] = signal(null);
9703
10069
  const [error, setError] = signal(null);
9704
10070
  const [loading, setLoading] = signal(false);
@@ -9714,7 +10080,6 @@ function worker(workerFn2) {
9714
10080
  if (typeof Worker === "undefined") {
9715
10081
  throw new Error("Web Workers are not supported in this environment");
9716
10082
  }
9717
- const fnBody = workerFn2.toString();
9718
10083
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
9719
10084
  blobUrl = URL.createObjectURL(blob);
9720
10085
  worker2 = new Worker(blobUrl);
@@ -9763,6 +10128,7 @@ function worker(workerFn2) {
9763
10128
  return { post, result, error, loading, terminate };
9764
10129
  }
9765
10130
  function workerFn(fn) {
10131
+ const fnStr = workerSource(fn, "workerFn");
9766
10132
  const [loading, setLoading] = signal(false);
9767
10133
  let worker2 = null;
9768
10134
  let blobUrl = null;
@@ -9777,7 +10143,6 @@ function workerFn(fn) {
9777
10143
  if (typeof Worker === "undefined") {
9778
10144
  throw new Error("Web Workers are not supported in this environment");
9779
10145
  }
9780
- const fnStr = fn.toString();
9781
10146
  const blob = new Blob(
9782
10147
  [
9783
10148
  `self.onmessage = function(e) {
@@ -9837,6 +10202,7 @@ function workerFn(fn) {
9837
10202
  return { run, loading, terminate };
9838
10203
  }
9839
10204
  function createWorkerPool(workerFn2, poolSize) {
10205
+ const fnBody = workerSource(workerFn2, "createWorkerPool");
9840
10206
  const size = poolSize || typeof navigator !== "undefined" && navigator.hardwareConcurrency || 4;
9841
10207
  const workers = [];
9842
10208
  const queues = [];
@@ -9895,7 +10261,6 @@ function createWorkerPool(workerFn2, poolSize) {
9895
10261
  if (typeof Worker === "undefined") {
9896
10262
  throw new Error("Web Workers are not supported in this environment");
9897
10263
  }
9898
- const fnBody = workerFn2.toString();
9899
10264
  const blob = new Blob([`self.onmessage = ${fnBody};`], { type: "application/javascript" });
9900
10265
  blobUrl = URL.createObjectURL(blob);
9901
10266
  for (let i2 = 0; i2 < size; i2++) {
@@ -10292,11 +10657,7 @@ function bindChildNode(placeholder, getter) {
10292
10657
  }
10293
10658
 
10294
10659
  // 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
- }
10660
+ var SVG_NS4 = "http://www.w3.org/2000/svg";
10300
10661
  var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
10301
10662
  "config",
10302
10663
  "location",
@@ -10341,11 +10702,12 @@ function applyStyle(el, style2) {
10341
10702
  }
10342
10703
  const next = [];
10343
10704
  for (const prop in value) {
10705
+ if (!Object.hasOwn(value, prop)) continue;
10344
10706
  const val = value[prop];
10345
10707
  if (val == null) continue;
10346
10708
  const name = toKebab(prop);
10347
10709
  next.push(name);
10348
- decl.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
10710
+ decl.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
10349
10711
  }
10350
10712
  for (let i2 = 0; i2 < written.length; i2++) {
10351
10713
  if (next.indexOf(written[i2]) === -1) decl.removeProperty(written[i2]);
@@ -10361,16 +10723,17 @@ function applyStyle(el, style2) {
10361
10723
  }
10362
10724
  const htmlEl = el;
10363
10725
  for (const prop in style2) {
10726
+ if (!Object.hasOwn(style2, prop)) continue;
10364
10727
  const val = style2[prop];
10365
10728
  const name = toKebab(prop);
10366
10729
  if (typeof val === "function") {
10367
10730
  const getter = val;
10368
10731
  const teardown = reactiveBinding2(() => {
10369
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(getter()), { property: name, element: el }));
10732
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(getter()), { element: el }));
10370
10733
  }, el);
10371
10734
  registerDisposer(el, teardown);
10372
10735
  } else {
10373
- htmlEl.style.setProperty(name, sanitizeCSSValue(String(val), { property: name, element: el }));
10736
+ htmlEl.style.setProperty(name, sanitizeCSSDeclaration(name, String(val), { element: el }));
10374
10737
  }
10375
10738
  }
10376
10739
  }
@@ -10392,6 +10755,7 @@ function applyClass(el, cls) {
10392
10755
  let hasReactive = false;
10393
10756
  let result = "";
10394
10757
  for (const name in obj) {
10758
+ if (!Object.hasOwn(obj, name)) continue;
10395
10759
  const val = obj[name];
10396
10760
  if (typeof val === "function") {
10397
10761
  hasReactive = true;
@@ -10403,6 +10767,7 @@ function applyClass(el, cls) {
10403
10767
  const update = () => {
10404
10768
  let r = "";
10405
10769
  for (const name in obj) {
10770
+ if (!Object.hasOwn(obj, name)) continue;
10406
10771
  const val = obj[name];
10407
10772
  const active = typeof val === "function" ? val() : val;
10408
10773
  if (active) r = r ? `${r} ${name}` : name;
@@ -10466,7 +10831,7 @@ function appendChildren(el, nodes) {
10466
10831
  }
10467
10832
  }
10468
10833
  function tagFactory(tag, ns) {
10469
- const blocked = isBlockedTag(tag);
10834
+ const blocked = isBlockedElement(tag);
10470
10835
  return (first, second) => {
10471
10836
  if (blocked) {
10472
10837
  throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
@@ -10529,6 +10894,7 @@ function tagFactory(tag, ns) {
10529
10894
  const pOn = props.on;
10530
10895
  if (pOn) {
10531
10896
  for (const ev in pOn) {
10897
+ if (!Object.hasOwn(pOn, ev)) continue;
10532
10898
  const handler = pOn[ev];
10533
10899
  if (typeof handler === "function") {
10534
10900
  el.addEventListener(ev, handler);
@@ -10555,6 +10921,7 @@ function tagFactory(tag, ns) {
10555
10921
  continue;
10556
10922
  // already handled above / below
10557
10923
  default: {
10924
+ if (!Object.hasOwn(props, key)) continue;
10558
10925
  const value = props[key];
10559
10926
  if (value == null) continue;
10560
10927
  if (isEventHandlerAttr(key)) continue;
@@ -10644,7 +11011,7 @@ var param = tagFactory("param");
10644
11011
  var picture = tagFactory("picture");
10645
11012
  var portal = tagFactory("portal");
10646
11013
  var source = tagFactory("source");
10647
- var svg = tagFactory("svg", SVG_NS3);
11014
+ var svg = tagFactory("svg", SVG_NS4);
10648
11015
  var math = tagFactory("math");
10649
11016
  var canvas = tagFactory("canvas");
10650
11017
  var noscript = tagFactory("noscript");
@@ -10685,26 +11052,26 @@ var base = tagFactory("base");
10685
11052
  var link = tagFactory("link");
10686
11053
  var meta = tagFactory("meta");
10687
11054
  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);
11055
+ var circle = tagFactory("circle", SVG_NS4);
11056
+ var ellipse = tagFactory("ellipse", SVG_NS4);
11057
+ var g = tagFactory("g", SVG_NS4);
11058
+ var line = tagFactory("line", SVG_NS4);
11059
+ var path = tagFactory("path", SVG_NS4);
11060
+ var polygon = tagFactory("polygon", SVG_NS4);
11061
+ var polyline = tagFactory("polyline", SVG_NS4);
11062
+ var rect = tagFactory("rect", SVG_NS4);
11063
+ var text = tagFactory("text", SVG_NS4);
11064
+ var tspan = tagFactory("tspan", SVG_NS4);
11065
+ var defs = tagFactory("defs", SVG_NS4);
11066
+ var clipPath = tagFactory("clipPath", SVG_NS4);
11067
+ var mask = tagFactory("mask", SVG_NS4);
11068
+ var pattern = tagFactory("pattern", SVG_NS4);
11069
+ var linearGradient = tagFactory("linearGradient", SVG_NS4);
11070
+ var radialGradient = tagFactory("radialGradient", SVG_NS4);
11071
+ var stop = tagFactory("stop", SVG_NS4);
11072
+ var use = tagFactory("use", SVG_NS4);
11073
+ var symbol = tagFactory("symbol", SVG_NS4);
11074
+ var marker = tagFactory("marker", SVG_NS4);
10708
11075
  var center = tagFactory("center");
10709
11076
  var font = tagFactory("font");
10710
11077
  var marquee = tagFactory("marquee");
@@ -11580,7 +11947,7 @@ var CDN_ENTRY_POINTS = {
11580
11947
  };
11581
11948
  var packageInfo = {
11582
11949
  name: "sibujs",
11583
- version: true ? "4.7.0" : "dev",
11950
+ version: true ? "4.8.0" : "dev",
11584
11951
  entryPoints: MODULE_ENTRY_POINTS,
11585
11952
  /**
11586
11953
  * Generate the package.json `exports` map. Module entries resolve to the
@@ -11725,7 +12092,7 @@ var env = {
11725
12092
  };
11726
12093
 
11727
12094
  // src/plugins/versioning.ts
11728
- var VERSION = true ? "4.7.0" : "dev";
12095
+ var VERSION = true ? "4.8.0" : "dev";
11729
12096
  var NUM = "(?:0|[1-9]\\d*)";
11730
12097
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
11731
12098
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -12008,12 +12375,14 @@ function extractRangeTarget(range) {
12008
12375
  function preloadCritical(resources) {
12009
12376
  if (typeof document === "undefined") return;
12010
12377
  for (const resource2 of resources) {
12011
- const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(resource2.href) : resource2.href.replace(/["\\]/g, "\\$&");
12378
+ const href = sanitizeUrl(resource2.href);
12379
+ if (!href) continue;
12380
+ const safeHref = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(href) : href.replace(/["\\]/g, "\\$&");
12012
12381
  const existing = document.querySelector(`link[rel="preload"][href="${safeHref}"]`);
12013
12382
  if (existing) continue;
12014
12383
  const link2 = document.createElement("link");
12015
12384
  link2.rel = "preload";
12016
- link2.href = resource2.href;
12385
+ link2.href = href;
12017
12386
  link2.setAttribute("as", resource2.as);
12018
12387
  if (resource2.type) {
12019
12388
  link2.type = resource2.type;
@@ -15017,7 +15386,7 @@ var _themeLayers = globalSingleton(
15017
15386
  function commitThemeProperty(root, name, record) {
15018
15387
  const top = record.layers[record.layers.length - 1];
15019
15388
  if (top) {
15020
- root.style.setProperty(name, top.value);
15389
+ root.style.setProperty(name, sanitizeCSSDeclaration(name, String(top.value), { element: root }));
15021
15390
  return;
15022
15391
  }
15023
15392
  _themeLayers.get(root)?.delete(name);