sibujs 3.1.0 → 3.2.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 (78) hide show
  1. package/README.md +6 -0
  2. package/dist/browser.cjs +16 -8
  3. package/dist/browser.js +6 -5
  4. package/dist/build.cjs +235 -147
  5. package/dist/build.js +35 -24
  6. package/dist/cdn.global.js +7 -7
  7. package/dist/{chunk-WYU7CYJ3.js → chunk-2C4E3HBM.js} +5 -5
  8. package/dist/{chunk-3DYB5B3S.js → chunk-4JCAUOLN.js} +45 -23
  9. package/dist/{chunk-2HAGQWDV.js → chunk-5N74TKLD.js} +1 -1
  10. package/dist/{chunk-SVVAUX7J.js → chunk-7XDYVJLE.js} +19 -9
  11. package/dist/{chunk-2N2UL7O4.js → chunk-BGNLPNGV.js} +20 -12
  12. package/dist/{chunk-RK4BQG25.js → chunk-C427DVQF.js} +1 -1
  13. package/dist/{chunk-ZIBE2SAT.js → chunk-FDY42FIU.js} +3 -2
  14. package/dist/{chunk-GQ7RRFPU.js → chunk-FOI23UJL.js} +11 -1
  15. package/dist/{chunk-2RA7SHDA.js → chunk-GOJMFRBL.js} +20 -4
  16. package/dist/{chunk-IVOUCSZL.js → chunk-GOUM4JCT.js} +6 -6
  17. package/dist/chunk-H3SRKIYX.js +17 -0
  18. package/dist/{chunk-3DJH25UO.js → chunk-H6PCHJZQ.js} +2 -2
  19. package/dist/{chunk-UCS6AMJ7.js → chunk-HMJFCBRR.js} +26 -3
  20. package/dist/{chunk-JYD2PWXH.js → chunk-HXMS4SNP.js} +22 -15
  21. package/dist/{chunk-SC437AMI.js → chunk-JYXOEYI4.js} +12 -18
  22. package/dist/{chunk-KB3BA2XK.js → chunk-NFYWLRUO.js} +11 -18
  23. package/dist/{chunk-QNQY5DUS.js → chunk-NPIEEKPT.js} +20 -11
  24. package/dist/{chunk-UYX2NDOH.js → chunk-OYLPZO4N.js} +33 -15
  25. package/dist/{chunk-LYTCUZ7H.js → chunk-RDRSWYNP.js} +1 -1
  26. package/dist/{chunk-2ZJ7TSW4.js → chunk-RLUJL2MV.js} +4 -8
  27. package/dist/{chunk-CR4MXPHB.js → chunk-V2MTG5FT.js} +99 -36
  28. package/dist/{chunk-CNZ35WI2.js → chunk-VJE6DDYM.js} +2 -2
  29. package/dist/{chunk-PMSDFTK3.js → chunk-VOCE4NNK.js} +157 -75
  30. package/dist/{chunk-WKUXSE7V.js → chunk-X67UYC74.js} +12 -11
  31. package/dist/{chunk-EFOAE5NC.js → chunk-YFDGQWDA.js} +1 -1
  32. package/dist/{chunk-3U4ZVXVD.js → chunk-Z2FWAE4B.js} +6 -2
  33. package/dist/data.cjs +190 -94
  34. package/dist/data.d.cts +7 -1
  35. package/dist/data.d.ts +7 -1
  36. package/dist/data.js +8 -8
  37. package/dist/devtools.cjs +38 -10
  38. package/dist/devtools.d.cts +1 -1
  39. package/dist/devtools.d.ts +1 -1
  40. package/dist/devtools.js +6 -6
  41. package/dist/ecosystem.cjs +123 -63
  42. package/dist/ecosystem.js +9 -9
  43. package/dist/extras.cjs +380 -196
  44. package/dist/extras.d.cts +2 -2
  45. package/dist/extras.d.ts +2 -2
  46. package/dist/extras.js +27 -24
  47. package/dist/index.cjs +214 -136
  48. package/dist/index.d.cts +15 -2
  49. package/dist/index.d.ts +15 -2
  50. package/dist/index.js +15 -13
  51. package/dist/{introspect-BZWKvQUZ.d.ts → introspect-DOZfmC-4.d.ts} +1 -1
  52. package/dist/{introspect-DsJlDD2T.d.cts → introspect-RjLfIFpL.d.cts} +1 -1
  53. package/dist/motion.cjs +10 -0
  54. package/dist/motion.js +3 -3
  55. package/dist/patterns.cjs +45 -40
  56. package/dist/patterns.js +8 -7
  57. package/dist/performance.cjs +101 -25
  58. package/dist/performance.d.cts +2 -2
  59. package/dist/performance.d.ts +2 -2
  60. package/dist/performance.js +8 -7
  61. package/dist/plugins.cjs +203 -136
  62. package/dist/plugins.d.cts +1 -1
  63. package/dist/plugins.d.ts +1 -1
  64. package/dist/plugins.js +96 -45
  65. package/dist/{ssr-FXD2PPMC.js → ssr-2QDQ27EV.js} +5 -3
  66. package/dist/{ssr-CrVNy6Pa.d.cts → ssr-D62yFwuw.d.cts} +8 -1
  67. package/dist/{ssr-CrVNy6Pa.d.ts → ssr-D62yFwuw.d.ts} +8 -1
  68. package/dist/ssr.cjs +145 -66
  69. package/dist/ssr.d.cts +1 -1
  70. package/dist/ssr.d.ts +1 -1
  71. package/dist/ssr.js +12 -10
  72. package/dist/testing.cjs +9 -4
  73. package/dist/testing.js +3 -3
  74. package/dist/ui.cjs +54 -38
  75. package/dist/ui.js +10 -9
  76. package/dist/widgets.cjs +40 -24
  77. package/dist/widgets.js +8 -8
  78. package/package.json +3 -1
package/dist/plugins.cjs CHANGED
@@ -43,8 +43,16 @@ var init_dev = __esm({
43
43
  });
44
44
 
45
45
  // src/utils/sanitize.ts
46
+ function stripControlChars(value) {
47
+ return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
48
+ }
49
+ function isEventHandlerAttr(name) {
50
+ if (name.length < 3) return false;
51
+ const lower = name.toLowerCase();
52
+ return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
53
+ }
46
54
  function sanitizeUrl(url) {
47
- const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
55
+ const trimmed = stripControlChars(url).trim();
48
56
  if (!trimmed) return "";
49
57
  const lower = trimmed.toLowerCase();
50
58
  let schemeEnd = -1;
@@ -93,7 +101,7 @@ function sanitizeCSSValue(value) {
93
101
  return value;
94
102
  }
95
103
  function isUrlAttribute(attr) {
96
- return URL_ATTRIBUTES.has(attr);
104
+ return URL_ATTRIBUTES.has(attr.toLowerCase());
97
105
  }
98
106
  var SAFE_URL_PROTOCOLS, URL_ATTRIBUTES;
99
107
  var init_sanitize = __esm({
@@ -135,11 +143,15 @@ var init_ssr_context = __esm({
135
143
  als = null;
136
144
  try {
137
145
  if (typeof process !== "undefined" && process.versions && process.versions.node) {
138
- const req = Function("return typeof require==='function'?require:null")();
139
- if (req) {
140
- const mod = req("node:async_hooks");
141
- als = new mod.AsyncLocalStorage();
146
+ let mod = null;
147
+ const getBuiltin = process.getBuiltinModule;
148
+ if (typeof getBuiltin === "function") {
149
+ mod = getBuiltin("node:async_hooks");
150
+ } else {
151
+ const req = Function("return typeof require==='function'?require:null")();
152
+ if (req) mod = req("node:async_hooks");
142
153
  }
154
+ if (mod) als = new mod.AsyncLocalStorage();
143
155
  }
144
156
  } catch {
145
157
  als = null;
@@ -157,6 +169,7 @@ __export(ssr_exports, {
157
169
  hydrate: () => hydrate,
158
170
  hydrateIslands: () => hydrateIslands,
159
171
  hydrateProgressively: () => hydrateProgressively,
172
+ isDangerousMetaRefresh: () => isDangerousMetaRefresh,
160
173
  island: () => island,
161
174
  renderToDocument: () => renderToDocument,
162
175
  renderToReadableStream: () => renderToReadableStream,
@@ -169,14 +182,12 @@ __export(ssr_exports, {
169
182
  suspenseSwapScript: () => suspenseSwapScript,
170
183
  trustHTML: () => trustHTML
171
184
  });
185
+ function sanitizeUrlAttr(name, value) {
186
+ return name === "srcset" ? sanitizeSrcset(value) : sanitizeUrl(value);
187
+ }
172
188
  function isSafeAttrName(name) {
173
189
  return SAFE_ATTR_NAME.test(name);
174
190
  }
175
- function isEventHandlerAttr2(name) {
176
- if (name.length < 3) return false;
177
- const lower = name.toLowerCase();
178
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
179
- }
180
191
  function ssrErrorComment(err) {
181
192
  if (_isDev8) {
182
193
  const msg = escapeHtml(err instanceof Error ? err.message : String(err));
@@ -217,11 +228,11 @@ function renderToString(element) {
217
228
  for (const attr of Array.from(element.attributes)) {
218
229
  const rawName = attr.name;
219
230
  if (!isSafeAttrName(rawName)) continue;
220
- if (isEventHandlerAttr2(rawName)) continue;
231
+ if (isEventHandlerAttr(rawName)) continue;
221
232
  const lowerName = rawName.toLowerCase();
222
233
  let value = attr.value;
223
234
  if (URL_ATTRS.has(lowerName)) {
224
- value = sanitizeUrl(value);
235
+ value = sanitizeUrlAttr(lowerName, value);
225
236
  if (!value) continue;
226
237
  }
227
238
  html2 += ` ${rawName}="${escapeAttr(value)}"`;
@@ -358,11 +369,11 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
358
369
  for (const rawKey of Object.keys(attrs)) {
359
370
  if (!Object.hasOwn(attrs, rawKey)) continue;
360
371
  if (!isSafeAttrName(rawKey)) continue;
361
- if (!allowEventHandlers && isEventHandlerAttr2(rawKey)) continue;
372
+ if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
362
373
  const lowerKey = rawKey.toLowerCase();
363
374
  let value = String(attrs[rawKey]);
364
375
  if (URL_ATTRS.has(lowerKey)) {
365
- value = sanitizeUrl(value);
376
+ value = sanitizeUrlAttr(lowerKey, value);
366
377
  if (!value) continue;
367
378
  }
368
379
  out.push(`${rawKey}="${escapeAttr(value)}"`);
@@ -370,12 +381,17 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
370
381
  return out.join(" ");
371
382
  }
372
383
  function isDangerousMetaRefresh(metaProps) {
373
- const httpEquiv = metaProps["http-equiv"];
384
+ let httpEquiv;
385
+ let content;
386
+ for (const k in metaProps) {
387
+ const lk = k.toLowerCase();
388
+ if (lk === "http-equiv") httpEquiv = metaProps[k];
389
+ else if (lk === "content") content = metaProps[k];
390
+ }
374
391
  if (typeof httpEquiv !== "string") return false;
375
392
  if (httpEquiv.toLowerCase() !== "refresh") return false;
376
- const content = metaProps.content;
377
393
  if (typeof content !== "string") return false;
378
- const normalized = content.replace(/[\x00-\x20\x7f-\x9f]+/g, "").toLowerCase();
394
+ const normalized = stripControlChars(content).toLowerCase();
379
395
  return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
380
396
  }
381
397
  function renderToDocument(component, options = {}) {
@@ -453,11 +469,11 @@ async function* renderToStream(element) {
453
469
  for (const attr of Array.from(element.attributes)) {
454
470
  const rawName = attr.name;
455
471
  if (!isSafeAttrName(rawName)) continue;
456
- if (isEventHandlerAttr2(rawName)) continue;
472
+ if (isEventHandlerAttr(rawName)) continue;
457
473
  const lowerName = rawName.toLowerCase();
458
474
  let value = attr.value;
459
475
  if (URL_ATTRS.has(lowerName)) {
460
- value = sanitizeUrl(value);
476
+ value = sanitizeUrlAttr(lowerName, value);
461
477
  if (!value) continue;
462
478
  }
463
479
  openTag += ` ${rawName}="${escapeAttr(value)}"`;
@@ -533,11 +549,11 @@ function hydrateProgressively(container, islands, options) {
533
549
  (entries) => {
534
550
  for (const entry of entries) {
535
551
  if (entry.isIntersecting) {
552
+ observer.disconnect();
536
553
  const clientTree = factory();
537
554
  clientTree.setAttribute("data-sibu-island", id);
538
555
  clientTree.setAttribute("data-sibu-hydrated", "true");
539
556
  marker2.replaceWith(clientTree);
540
- observer.disconnect();
541
557
  break;
542
558
  }
543
559
  }
@@ -922,7 +938,7 @@ function track(effectFn, subscriber) {
922
938
  function reactiveBinding(commit) {
923
939
  const run = () => {
924
940
  const s2 = subscriber;
925
- if (s2._reentrant) return;
941
+ if (s2._disposed || s2._reentrant) return;
926
942
  s2._reentrant = true;
927
943
  try {
928
944
  retrack(commit, subscriber);
@@ -938,8 +954,12 @@ function reactiveBinding(commit) {
938
954
  subscriber._runEpoch = 0;
939
955
  subscriber._runs = 0;
940
956
  subscriber._reentrant = false;
957
+ subscriber._disposed = false;
941
958
  run();
942
- return subscriber._dispose ?? (subscriber._dispose = () => cleanup(subscriber));
959
+ return subscriber._dispose ?? (subscriber._dispose = () => {
960
+ subscriber._disposed = true;
961
+ cleanup(subscriber);
962
+ });
943
963
  }
944
964
  function recordDependency(signal2) {
945
965
  if (!currentSubscriber) return;
@@ -1097,11 +1117,6 @@ var _isDev3 = isDev();
1097
1117
  function setProp(el, key, val) {
1098
1118
  el[key] = val;
1099
1119
  }
1100
- function isEventHandlerAttr(name) {
1101
- if (name.length < 3) return false;
1102
- const lower = name.toLowerCase();
1103
- return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
1104
- }
1105
1120
  function bindAttribute(el, attr, getter) {
1106
1121
  if (isEventHandlerAttr(attr)) {
1107
1122
  if (_isDev3)
@@ -1142,7 +1157,71 @@ function bindAttribute(el, attr, getter) {
1142
1157
 
1143
1158
  // src/reactivity/bindChildNode.ts
1144
1159
  init_dev();
1160
+
1161
+ // src/core/rendering/dispose.ts
1162
+ init_dev();
1163
+ var elementDisposers = /* @__PURE__ */ new WeakMap();
1145
1164
  var _isDev4 = isDev();
1165
+ var activeBindingCount = 0;
1166
+ function registerDisposer(node, teardown) {
1167
+ let disposers = elementDisposers.get(node);
1168
+ if (!disposers) {
1169
+ disposers = [];
1170
+ elementDisposers.set(node, disposers);
1171
+ }
1172
+ disposers.push(teardown);
1173
+ if (_isDev4) activeBindingCount++;
1174
+ }
1175
+ function dispose(node) {
1176
+ const stack = [node];
1177
+ const order = [];
1178
+ while (stack.length > 0) {
1179
+ const current = stack.pop();
1180
+ order.push(current);
1181
+ const children = Array.from(current.childNodes);
1182
+ for (let i2 = 0; i2 < children.length; i2++) {
1183
+ stack.push(children[i2]);
1184
+ }
1185
+ }
1186
+ for (let i2 = order.length - 1; i2 >= 0; i2--) {
1187
+ const current = order[i2];
1188
+ const disposers = elementDisposers.get(current);
1189
+ if (disposers) {
1190
+ const snapshot = disposers.slice();
1191
+ elementDisposers.delete(current);
1192
+ if (_isDev4) activeBindingCount -= snapshot.length;
1193
+ for (const d of snapshot) {
1194
+ try {
1195
+ d();
1196
+ } catch (err) {
1197
+ if (_isDev4 && typeof console !== "undefined") {
1198
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1199
+ }
1200
+ }
1201
+ }
1202
+ let extraPasses = 0;
1203
+ while (extraPasses++ < 8) {
1204
+ const added = elementDisposers.get(current);
1205
+ if (!added || added.length === 0) break;
1206
+ const moreSnapshot = added.slice();
1207
+ elementDisposers.delete(current);
1208
+ if (_isDev4) activeBindingCount -= moreSnapshot.length;
1209
+ for (const d of moreSnapshot) {
1210
+ try {
1211
+ d();
1212
+ } catch (err) {
1213
+ if (_isDev4 && typeof console !== "undefined") {
1214
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1215
+ }
1216
+ }
1217
+ }
1218
+ }
1219
+ }
1220
+ }
1221
+ }
1222
+
1223
+ // src/reactivity/bindChildNode.ts
1224
+ var _isDev5 = isDev();
1146
1225
  function bindChildNode(placeholder, getter) {
1147
1226
  let lastNodes = [];
1148
1227
  function commit() {
@@ -1150,12 +1229,13 @@ function bindChildNode(placeholder, getter) {
1150
1229
  try {
1151
1230
  result = getter();
1152
1231
  } catch (err) {
1153
- if (_isDev4) devWarn(`bindChildNode: getter threw: ${err instanceof Error ? err.message : String(err)}`);
1232
+ if (_isDev5) devWarn(`bindChildNode: getter threw: ${err instanceof Error ? err.message : String(err)}`);
1154
1233
  return;
1155
1234
  }
1156
1235
  if (result == null || typeof result === "boolean") {
1157
1236
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
1158
1237
  const node = lastNodes[i2];
1238
+ dispose(node);
1159
1239
  if (node.parentNode) node.parentNode.removeChild(node);
1160
1240
  }
1161
1241
  lastNodes.length = 0;
@@ -1175,7 +1255,7 @@ function bindChildNode(placeholder, getter) {
1175
1255
  if (item == null || typeof item === "boolean") continue;
1176
1256
  const node = item instanceof Node ? item : document.createTextNode(String(item));
1177
1257
  if (seen.has(node)) {
1178
- if (_isDev4)
1258
+ if (_isDev5)
1179
1259
  devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1180
1260
  continue;
1181
1261
  }
@@ -1197,18 +1277,16 @@ function bindChildNode(placeholder, getter) {
1197
1277
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
1198
1278
  const node = lastNodes[i2];
1199
1279
  if (reused?.has(node)) continue;
1280
+ dispose(node);
1200
1281
  if (node.parentNode) node.parentNode.removeChild(node);
1201
1282
  }
1202
- const anchor = placeholder.nextSibling;
1283
+ let prev = placeholder;
1203
1284
  for (let i2 = 0; i2 < newNodes.length; i2++) {
1204
1285
  const node = newNodes[i2];
1205
- if (reused?.has(node) && node.parentNode === parent) {
1206
- if (node.nextSibling !== anchor) {
1207
- parent.insertBefore(node, anchor);
1208
- }
1209
- } else {
1210
- parent.insertBefore(node, anchor);
1286
+ if (prev.nextSibling !== node) {
1287
+ parent.insertBefore(node, prev.nextSibling);
1211
1288
  }
1289
+ prev = node;
1212
1290
  }
1213
1291
  lastNodes = newNodes;
1214
1292
  }
@@ -1217,70 +1295,6 @@ function bindChildNode(placeholder, getter) {
1217
1295
 
1218
1296
  // src/core/rendering/tagFactory.ts
1219
1297
  init_sanitize();
1220
-
1221
- // src/core/rendering/dispose.ts
1222
- init_dev();
1223
- var elementDisposers = /* @__PURE__ */ new WeakMap();
1224
- var _isDev5 = isDev();
1225
- var activeBindingCount = 0;
1226
- function registerDisposer(node, teardown) {
1227
- let disposers = elementDisposers.get(node);
1228
- if (!disposers) {
1229
- disposers = [];
1230
- elementDisposers.set(node, disposers);
1231
- }
1232
- disposers.push(teardown);
1233
- if (_isDev5) activeBindingCount++;
1234
- }
1235
- function dispose(node) {
1236
- const stack = [node];
1237
- const order = [];
1238
- while (stack.length > 0) {
1239
- const current = stack.pop();
1240
- order.push(current);
1241
- const children = Array.from(current.childNodes);
1242
- for (let i2 = 0; i2 < children.length; i2++) {
1243
- stack.push(children[i2]);
1244
- }
1245
- }
1246
- for (let i2 = order.length - 1; i2 >= 0; i2--) {
1247
- const current = order[i2];
1248
- const disposers = elementDisposers.get(current);
1249
- if (disposers) {
1250
- const snapshot = disposers.slice();
1251
- elementDisposers.delete(current);
1252
- if (_isDev5) activeBindingCount -= snapshot.length;
1253
- for (const d of snapshot) {
1254
- try {
1255
- d();
1256
- } catch (err) {
1257
- if (_isDev5 && typeof console !== "undefined") {
1258
- console.warn("[SibuJS] Disposer threw during cleanup:", err);
1259
- }
1260
- }
1261
- }
1262
- let extraPasses = 0;
1263
- while (extraPasses++ < 8) {
1264
- const added = elementDisposers.get(current);
1265
- if (!added || added.length === 0) break;
1266
- const moreSnapshot = added.slice();
1267
- elementDisposers.delete(current);
1268
- if (_isDev5) activeBindingCount -= moreSnapshot.length;
1269
- for (const d of moreSnapshot) {
1270
- try {
1271
- d();
1272
- } catch (err) {
1273
- if (_isDev5 && typeof console !== "undefined") {
1274
- console.warn("[SibuJS] Disposer threw during cleanup:", err);
1275
- }
1276
- }
1277
- }
1278
- }
1279
- }
1280
- }
1281
- }
1282
-
1283
- // src/core/rendering/tagFactory.ts
1284
1298
  var SVG_NS = "http://www.w3.org/2000/svg";
1285
1299
  var _isDev6 = isDev();
1286
1300
  var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
@@ -1515,7 +1529,7 @@ var tagFactory = (tag, ns) => {
1515
1529
  const value = props[key];
1516
1530
  if (value == null) continue;
1517
1531
  const lkey = key.toLowerCase();
1518
- if (lkey[0] === "o" && lkey[1] === "n") continue;
1532
+ if (isEventHandlerAttr(key)) continue;
1519
1533
  if (typeof value === "function") {
1520
1534
  registerDisposer(el, bindAttribute(el, key, value));
1521
1535
  } else if (typeof value === "boolean") {
@@ -1879,6 +1893,7 @@ function effect(effectFn, options) {
1879
1893
  ctx.fn(ctx.onCleanup);
1880
1894
  };
1881
1895
  const sub2 = (() => {
1896
+ if (ctx.disposed) return;
1882
1897
  if (ctx.running) {
1883
1898
  ctx.rerunPending = true;
1884
1899
  return;
@@ -1918,7 +1933,9 @@ function effect(effectFn, options) {
1918
1933
  init_sanitize();
1919
1934
  function isSafeNavigationTarget(path2) {
1920
1935
  if (path2 === "") return true;
1921
- return sanitizeUrl(path2) !== "";
1936
+ const normalized = stripControlChars(path2).replace(/\\/g, "/");
1937
+ if (normalized.startsWith("//")) return false;
1938
+ return sanitizeUrl(normalized) !== "";
1922
1939
  }
1923
1940
  var LRUCache = class {
1924
1941
  constructor(maxSize = 100) {
@@ -2055,8 +2072,13 @@ var RouteMatcher = class {
2055
2072
  if (match) {
2056
2073
  const params = {};
2057
2074
  compiled.keys.forEach((key, i2) => {
2058
- if (match[i2 + 1] !== void 0) {
2059
- params[key] = decodeURIComponent(match[i2 + 1]);
2075
+ const raw = match[i2 + 1];
2076
+ if (raw !== void 0) {
2077
+ try {
2078
+ params[key] = decodeURIComponent(raw);
2079
+ } catch {
2080
+ params[key] = raw;
2081
+ }
2060
2082
  }
2061
2083
  });
2062
2084
  return { params };
@@ -2107,14 +2129,23 @@ var RouteMatcher = class {
2107
2129
  }
2108
2130
  }
2109
2131
  removeRoute(path2) {
2110
- this.routeTrie.delete(path2);
2111
- this.parentChain.delete(path2);
2112
2132
  this.compiledPatterns.clear();
2113
- for (const [name, route2] of this.namedRoutes) {
2114
- if (route2.path === path2) {
2115
- this.namedRoutes.delete(name);
2116
- break;
2117
- }
2133
+ const root = this.routeTrie.get(path2);
2134
+ if (!root) return;
2135
+ const removed = /* @__PURE__ */ new Set();
2136
+ const collect = (r) => {
2137
+ removed.add(r);
2138
+ if (r.children) for (const child of r.children) collect(child);
2139
+ };
2140
+ collect(root);
2141
+ for (const [key, route2] of [...this.routeTrie]) {
2142
+ if (removed.has(route2)) this.routeTrie.delete(key);
2143
+ }
2144
+ for (const [key, chain] of [...this.parentChain]) {
2145
+ if (chain.length > 0 && removed.has(chain[chain.length - 1])) this.parentChain.delete(key);
2146
+ }
2147
+ for (const [name, route2] of [...this.namedRoutes]) {
2148
+ if (removed.has(route2)) this.namedRoutes.delete(name);
2118
2149
  }
2119
2150
  }
2120
2151
  };
@@ -2226,42 +2257,61 @@ var GuardManager = class {
2226
2257
  this.afterEachHooks = [];
2227
2258
  }
2228
2259
  };
2229
- var ComponentLoader = class {
2260
+ var _ComponentLoader = class _ComponentLoader {
2230
2261
  constructor(cacheSize = 50, retryDelay = 1e3) {
2231
2262
  this.errorCache = /* @__PURE__ */ new Map();
2232
2263
  this.loadingPromises = /* @__PURE__ */ new Map();
2264
+ // Stable per-route-definition id. Caching by the RESOLVED path (e.g.
2265
+ // /users/123) gave the cache one entry per visited URL, so parameterized
2266
+ // routes thrashed/evicted and the component was reloaded every navigation.
2267
+ // Keying by route-definition identity makes the cache effective again.
2268
+ this.routeKeys = /* @__PURE__ */ new WeakMap();
2269
+ this.keyCounter = 0;
2233
2270
  this.componentCache = new LRUCache(cacheSize);
2234
2271
  this.retryDelay = retryDelay;
2235
2272
  }
2273
+ keyFor(route2) {
2274
+ let key = this.routeKeys.get(route2);
2275
+ if (key === void 0) {
2276
+ key = `route#${this.keyCounter++}`;
2277
+ this.routeKeys.set(route2, key);
2278
+ }
2279
+ return key;
2280
+ }
2236
2281
  async loadComponent(route2, routePath) {
2237
2282
  if (!("component" in route2)) {
2238
2283
  throw new Error(`Route ${routePath} does not have a component`);
2239
2284
  }
2240
2285
  const comp = route2.component;
2241
- const cached = this.componentCache.get(routePath);
2286
+ const cacheKey = this.keyFor(route2);
2287
+ const cached = this.componentCache.get(cacheKey);
2242
2288
  if (cached) return cached;
2243
- const existingPromise = this.loadingPromises.get(routePath);
2289
+ const existingPromise = this.loadingPromises.get(cacheKey);
2244
2290
  if (existingPromise) return existingPromise;
2245
- const errorInfo = this.errorCache.get(routePath);
2291
+ const errorInfo = this.errorCache.get(cacheKey);
2246
2292
  if (errorInfo && Date.now() - errorInfo.timestamp < this.retryDelay) {
2247
2293
  throw new Error(`Component loading failed recently, retry in ${this.retryDelay}ms`);
2248
2294
  }
2249
2295
  const loadingPromise = this.doLoadComponent(comp, routePath);
2250
- this.loadingPromises.set(routePath, loadingPromise);
2296
+ this.loadingPromises.set(cacheKey, loadingPromise);
2251
2297
  try {
2252
2298
  const component = await loadingPromise;
2253
- this.componentCache.set(routePath, component);
2254
- this.errorCache.delete(routePath);
2299
+ this.componentCache.set(cacheKey, component);
2300
+ this.errorCache.delete(cacheKey);
2255
2301
  return component;
2256
2302
  } catch (error) {
2257
- const currentError = this.errorCache.get(routePath) || { timestamp: 0, count: 0 };
2258
- this.errorCache.set(routePath, {
2303
+ const currentError = this.errorCache.get(cacheKey) || { timestamp: 0, count: 0 };
2304
+ if (!this.errorCache.has(cacheKey) && this.errorCache.size >= _ComponentLoader.MAX_ERROR_ENTRIES) {
2305
+ const oldest = this.errorCache.keys().next().value;
2306
+ if (oldest !== void 0) this.errorCache.delete(oldest);
2307
+ }
2308
+ this.errorCache.set(cacheKey, {
2259
2309
  timestamp: Date.now(),
2260
2310
  count: currentError.count + 1
2261
2311
  });
2262
2312
  throw error;
2263
2313
  } finally {
2264
- this.loadingPromises.delete(routePath);
2314
+ this.loadingPromises.delete(cacheKey);
2265
2315
  }
2266
2316
  }
2267
2317
  async doLoadComponent(comp, routePath) {
@@ -2315,6 +2365,8 @@ var ComponentLoader = class {
2315
2365
  this.loadingPromises.clear();
2316
2366
  }
2317
2367
  };
2368
+ _ComponentLoader.MAX_ERROR_ENTRIES = 256;
2369
+ var ComponentLoader = _ComponentLoader;
2318
2370
  var _SibuRouter = class _SibuRouter {
2319
2371
  constructor(routes, options = {}) {
2320
2372
  // Event listeners cleanup
@@ -2380,7 +2432,7 @@ var _SibuRouter = class _SibuRouter {
2380
2432
  return window.location.hash.slice(1) || "/";
2381
2433
  }
2382
2434
  let path2 = window.location.pathname;
2383
- if (base2 && path2.startsWith(base2)) {
2435
+ if (base2 && (path2 === base2 || path2.startsWith(`${base2}/`))) {
2384
2436
  path2 = path2.slice(base2.length);
2385
2437
  }
2386
2438
  return (path2 || "/") + window.location.search + window.location.hash;
@@ -2513,7 +2565,7 @@ var _SibuRouter = class _SibuRouter {
2513
2565
  }
2514
2566
  if (to.params) {
2515
2567
  for (const [key, value] of Object.entries(to.params)) {
2516
- path2 = path2.replace(`:${key}`, encodeURIComponent(value));
2568
+ path2 = path2.replace(new RegExp(`:${key}(?=[/?#]|$)`, "g"), encodeURIComponent(value));
2517
2569
  }
2518
2570
  }
2519
2571
  if (to.query && Object.keys(to.query).length > 0) {
@@ -2943,7 +2995,8 @@ function KeepAliveRoute(options) {
2943
2995
  return;
2944
2996
  }
2945
2997
  if (!("component" in routeDef)) return;
2946
- const cacheKey = route2.path;
2998
+ const queryStr = Object.keys(route2.query).length > 0 ? `?${new URLSearchParams(route2.query).toString()}` : "";
2999
+ const cacheKey = `${route2.path}${queryStr}${route2.hash ? `#${route2.hash}` : ""}`;
2947
3000
  const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
2948
3001
  if (cacheKey === currentKey && currentNode) return;
2949
3002
  isUpdating = true;
@@ -3033,7 +3086,8 @@ function RouterLink(props) {
3033
3086
  const { to, replace: replace2 = false, activeClass, exactActiveClass, nodes, target, rel, class: classAttr, ...attrs } = props;
3034
3087
  const baseClass = typeof classAttr === "string" ? classAttr : "";
3035
3088
  const routeGetter = globalRouter.routeGetter;
3036
- const href = globalRouter["resolvePath"](to);
3089
+ const rawHref = globalRouter["resolvePath"](to);
3090
+ const href = isSafeNavigationTarget(rawHref) ? rawHref : "#";
3037
3091
  const hrefPath = href.split("?")[0].split("#")[0];
3038
3092
  const link2 = document.createElement("a");
3039
3093
  link2.href = href;
@@ -3065,9 +3119,18 @@ function RouterLink(props) {
3065
3119
  });
3066
3120
  registerDisposer(link2, effectCleanup);
3067
3121
  Object.entries(attrs).forEach(([key, value]) => {
3068
- if (key.startsWith("on") || key === "href") return;
3122
+ const lkey = key.toLowerCase();
3123
+ if (lkey === "href" || lkey[0] === "o" && lkey[1] === "n") return;
3069
3124
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3070
- link2.setAttribute(key, String(value));
3125
+ const str = String(value);
3126
+ if (isUrlAttribute(lkey)) {
3127
+ const safe = sanitizeUrl(str);
3128
+ if (safe) link2.setAttribute(key, safe);
3129
+ } else if (lkey === "style") {
3130
+ link2.setAttribute(key, sanitizeCSSValue(str));
3131
+ } else {
3132
+ link2.setAttribute(key, str);
3133
+ }
3071
3134
  }
3072
3135
  });
3073
3136
  if (typeof nodes === "string") {
@@ -3318,10 +3381,14 @@ function createMemoryRouter(routes, _initialPath = "/") {
3318
3381
 
3319
3382
  // src/plugins/routerSSR.ts
3320
3383
  init_ssr();
3321
- var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3322
- function isForbiddenKey(key) {
3323
- return FORBIDDEN_KEYS.has(key);
3384
+
3385
+ // src/utils/guards.ts
3386
+ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3387
+ function isUnsafeKey(key) {
3388
+ return UNSAFE_KEYS.has(key);
3324
3389
  }
3390
+
3391
+ // src/plugins/routerSSR.ts
3325
3392
  function safeDecode(raw) {
3326
3393
  try {
3327
3394
  return decodeURIComponent(raw);
@@ -3362,7 +3429,7 @@ function parseURL(url) {
3362
3429
  key = safeDecode(pair.slice(0, eqIndex));
3363
3430
  value = safeDecode(pair.slice(eqIndex + 1));
3364
3431
  }
3365
- if (isForbiddenKey(key)) continue;
3432
+ if (isUnsafeKey(key)) continue;
3366
3433
  query[key] = value;
3367
3434
  }
3368
3435
  }
@@ -3424,7 +3491,7 @@ function matchRoute(path2, routes, parentPath = "", parentChain = []) {
3424
3491
  const params = nullObject();
3425
3492
  for (let i2 = 0; i2 < compiled.keys.length; i2++) {
3426
3493
  const key = compiled.keys[i2];
3427
- if (isForbiddenKey(key)) continue;
3494
+ if (isUnsafeKey(key)) continue;
3428
3495
  if (match[i2 + 1] !== void 0) {
3429
3496
  params[key] = safeDecode(match[i2 + 1]);
3430
3497
  }
@@ -3521,7 +3588,7 @@ function renderRouteToString(url, routes, _options) {
3521
3588
  function renderRouteToDocument(url, routes, options) {
3522
3589
  const { html: html2, state } = renderRouteToString(url, routes, options);
3523
3590
  const opts = options || {};
3524
- const metaTags = (opts.meta || []).map((attrs) => {
3591
+ const metaTags = (opts.meta || []).filter((attrs) => !isDangerousMetaRefresh(attrs)).map((attrs) => {
3525
3592
  const pairs = buildSafeAttrString(attrs);
3526
3593
  return pairs ? `<meta ${pairs} />` : "";
3527
3594
  }).filter(Boolean).join("\n ");
@@ -3601,7 +3668,7 @@ var SAFE_ATTR_NAME2 = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
3601
3668
  function isSafeAttrName2(name) {
3602
3669
  return SAFE_ATTR_NAME2.test(name);
3603
3670
  }
3604
- function isEventHandlerAttr3(name) {
3671
+ function isEventHandlerAttr2(name) {
3605
3672
  if (name.length < 3) return false;
3606
3673
  const lower = name.toLowerCase();
3607
3674
  return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
@@ -3634,7 +3701,7 @@ function buildSafeAttrString(attrs) {
3634
3701
  for (const rawKey of Object.keys(attrs)) {
3635
3702
  if (!Object.hasOwn(attrs, rawKey)) continue;
3636
3703
  if (!isSafeAttrName2(rawKey)) continue;
3637
- if (isEventHandlerAttr3(rawKey)) continue;
3704
+ if (isEventHandlerAttr2(rawKey)) continue;
3638
3705
  const lowerKey = rawKey.toLowerCase();
3639
3706
  let value = String(attrs[rawKey]);
3640
3707
  if (URL_ATTRS2.has(lowerKey)) {
@@ -1,4 +1,4 @@
1
- import { T as TrustedHTML } from './ssr-CrVNy6Pa.cjs';
1
+ import { T as TrustedHTML } from './ssr-D62yFwuw.cjs';
2
2
  export { P as PluginContext, a as PluginRegistry, S as SibuPlugin, c as createPlugin, b as createPluginRegistry, i as inject, p as plugin, r as resetPlugins, s as setDefaultPluginRegistry, t as triggerPluginError, d as triggerPluginMount, e as triggerPluginUnmount } from './plugin-D30wlGW5.cjs';
3
3
  export { M as Migration, S as SemVer, V as VERSION, b as bundlerMetadata, c as checkCompatibility, a as compareSemVer, d as createBootSequence, e as createBundle, f as createMigrationRunner, g as createModuleRegistry, h as createSSRCache, i as createTestHarness, j as deferNonCritical, k as env, l as healthCheck, m as lazyModule, p as packageInfo, n as parseSemVer, o as preloadCritical, q as prerenderRoutes, s as satisfies } from './startup-0Qv6aosO.cjs';
4
4
 
package/dist/plugins.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { T as TrustedHTML } from './ssr-CrVNy6Pa.js';
1
+ import { T as TrustedHTML } from './ssr-D62yFwuw.js';
2
2
  export { P as PluginContext, a as PluginRegistry, S as SibuPlugin, c as createPlugin, b as createPluginRegistry, i as inject, p as plugin, r as resetPlugins, s as setDefaultPluginRegistry, t as triggerPluginError, d as triggerPluginMount, e as triggerPluginUnmount } from './plugin-D30wlGW5.js';
3
3
  export { M as Migration, S as SemVer, V as VERSION, b as bundlerMetadata, c as checkCompatibility, a as compareSemVer, d as createBootSequence, e as createBundle, f as createMigrationRunner, g as createModuleRegistry, h as createSSRCache, i as createTestHarness, j as deferNonCritical, k as env, l as healthCheck, m as lazyModule, p as packageInfo, n as parseSemVer, o as preloadCritical, q as prerenderRoutes, s as satisfies } from './startup-0Qv6aosO.js';
4
4