sibujs 3.1.0 → 3.2.1

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 +234 -160
  62. package/dist/plugins.d.cts +1 -1
  63. package/dist/plugins.d.ts +1 -1
  64. package/dist/plugins.js +127 -69
  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
@@ -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
 
package/dist/plugins.js CHANGED
@@ -18,7 +18,10 @@ import {
18
18
  preloadCritical,
19
19
  prerenderRoutes,
20
20
  satisfies
21
- } from "./chunk-LYTCUZ7H.js";
21
+ } from "./chunk-RDRSWYNP.js";
22
+ import {
23
+ isUnsafeKey
24
+ } from "./chunk-H3SRKIYX.js";
22
25
  import {
23
26
  createPlugin,
24
27
  createPluginRegistry,
@@ -32,30 +35,34 @@ import {
32
35
  } from "./chunk-3JHCYHWN.js";
33
36
  import {
34
37
  span
35
- } from "./chunk-2HAGQWDV.js";
38
+ } from "./chunk-5N74TKLD.js";
36
39
  import {
37
40
  escapeScriptJson,
41
+ isDangerousMetaRefresh,
38
42
  renderToString
39
- } from "./chunk-JYD2PWXH.js";
40
- import "./chunk-WKUXSE7V.js";
41
- import "./chunk-2ZJ7TSW4.js";
43
+ } from "./chunk-HXMS4SNP.js";
44
+ import "./chunk-X67UYC74.js";
45
+ import "./chunk-RLUJL2MV.js";
42
46
  import {
43
47
  dispose,
44
48
  registerDisposer
45
49
  } from "./chunk-2UPRY23K.js";
46
50
  import {
47
- sanitizeUrl
48
- } from "./chunk-UCS6AMJ7.js";
51
+ isUrlAttribute,
52
+ sanitizeCSSValue,
53
+ sanitizeUrl,
54
+ stripControlChars
55
+ } from "./chunk-HMJFCBRR.js";
49
56
  import {
50
57
  effect
51
- } from "./chunk-ZIBE2SAT.js";
52
- import "./chunk-2RA7SHDA.js";
58
+ } from "./chunk-FDY42FIU.js";
59
+ import "./chunk-GOJMFRBL.js";
53
60
  import {
54
61
  signal
55
- } from "./chunk-RK4BQG25.js";
62
+ } from "./chunk-C427DVQF.js";
56
63
  import {
57
64
  track
58
- } from "./chunk-3U4ZVXVD.js";
65
+ } from "./chunk-Z2FWAE4B.js";
59
66
  import "./chunk-LMLD24FC.js";
60
67
 
61
68
  // src/plugins/i18n.ts
@@ -89,7 +96,9 @@ function getAvailableLocales() {
89
96
  // src/plugins/router.ts
90
97
  function isSafeNavigationTarget(path) {
91
98
  if (path === "") return true;
92
- return sanitizeUrl(path) !== "";
99
+ const normalized = stripControlChars(path).replace(/\\/g, "/");
100
+ if (normalized.startsWith("//")) return false;
101
+ return sanitizeUrl(normalized) !== "";
93
102
  }
94
103
  var LRUCache = class {
95
104
  constructor(maxSize = 100) {
@@ -226,8 +235,13 @@ var RouteMatcher = class {
226
235
  if (match) {
227
236
  const params = {};
228
237
  compiled.keys.forEach((key, i) => {
229
- if (match[i + 1] !== void 0) {
230
- params[key] = decodeURIComponent(match[i + 1]);
238
+ const raw = match[i + 1];
239
+ if (raw !== void 0) {
240
+ try {
241
+ params[key] = decodeURIComponent(raw);
242
+ } catch {
243
+ params[key] = raw;
244
+ }
231
245
  }
232
246
  });
233
247
  return { params };
@@ -278,14 +292,23 @@ var RouteMatcher = class {
278
292
  }
279
293
  }
280
294
  removeRoute(path) {
281
- this.routeTrie.delete(path);
282
- this.parentChain.delete(path);
283
295
  this.compiledPatterns.clear();
284
- for (const [name, route2] of this.namedRoutes) {
285
- if (route2.path === path) {
286
- this.namedRoutes.delete(name);
287
- break;
288
- }
296
+ const root = this.routeTrie.get(path);
297
+ if (!root) return;
298
+ const removed = /* @__PURE__ */ new Set();
299
+ const collect = (r) => {
300
+ removed.add(r);
301
+ if (r.children) for (const child of r.children) collect(child);
302
+ };
303
+ collect(root);
304
+ for (const [key, route2] of [...this.routeTrie]) {
305
+ if (removed.has(route2)) this.routeTrie.delete(key);
306
+ }
307
+ for (const [key, chain] of [...this.parentChain]) {
308
+ if (chain.length > 0 && removed.has(chain[chain.length - 1])) this.parentChain.delete(key);
309
+ }
310
+ for (const [name, route2] of [...this.namedRoutes]) {
311
+ if (removed.has(route2)) this.namedRoutes.delete(name);
289
312
  }
290
313
  }
291
314
  };
@@ -397,42 +420,61 @@ var GuardManager = class {
397
420
  this.afterEachHooks = [];
398
421
  }
399
422
  };
400
- var ComponentLoader = class {
423
+ var _ComponentLoader = class _ComponentLoader {
401
424
  constructor(cacheSize = 50, retryDelay = 1e3) {
402
425
  this.errorCache = /* @__PURE__ */ new Map();
403
426
  this.loadingPromises = /* @__PURE__ */ new Map();
427
+ // Stable per-route-definition id. Caching by the RESOLVED path (e.g.
428
+ // /users/123) gave the cache one entry per visited URL, so parameterized
429
+ // routes thrashed/evicted and the component was reloaded every navigation.
430
+ // Keying by route-definition identity makes the cache effective again.
431
+ this.routeKeys = /* @__PURE__ */ new WeakMap();
432
+ this.keyCounter = 0;
404
433
  this.componentCache = new LRUCache(cacheSize);
405
434
  this.retryDelay = retryDelay;
406
435
  }
436
+ keyFor(route2) {
437
+ let key = this.routeKeys.get(route2);
438
+ if (key === void 0) {
439
+ key = `route#${this.keyCounter++}`;
440
+ this.routeKeys.set(route2, key);
441
+ }
442
+ return key;
443
+ }
407
444
  async loadComponent(route2, routePath) {
408
445
  if (!("component" in route2)) {
409
446
  throw new Error(`Route ${routePath} does not have a component`);
410
447
  }
411
448
  const comp = route2.component;
412
- const cached = this.componentCache.get(routePath);
449
+ const cacheKey = this.keyFor(route2);
450
+ const cached = this.componentCache.get(cacheKey);
413
451
  if (cached) return cached;
414
- const existingPromise = this.loadingPromises.get(routePath);
452
+ const existingPromise = this.loadingPromises.get(cacheKey);
415
453
  if (existingPromise) return existingPromise;
416
- const errorInfo = this.errorCache.get(routePath);
454
+ const errorInfo = this.errorCache.get(cacheKey);
417
455
  if (errorInfo && Date.now() - errorInfo.timestamp < this.retryDelay) {
418
456
  throw new Error(`Component loading failed recently, retry in ${this.retryDelay}ms`);
419
457
  }
420
458
  const loadingPromise = this.doLoadComponent(comp, routePath);
421
- this.loadingPromises.set(routePath, loadingPromise);
459
+ this.loadingPromises.set(cacheKey, loadingPromise);
422
460
  try {
423
461
  const component = await loadingPromise;
424
- this.componentCache.set(routePath, component);
425
- this.errorCache.delete(routePath);
462
+ this.componentCache.set(cacheKey, component);
463
+ this.errorCache.delete(cacheKey);
426
464
  return component;
427
465
  } catch (error) {
428
- const currentError = this.errorCache.get(routePath) || { timestamp: 0, count: 0 };
429
- this.errorCache.set(routePath, {
466
+ const currentError = this.errorCache.get(cacheKey) || { timestamp: 0, count: 0 };
467
+ if (!this.errorCache.has(cacheKey) && this.errorCache.size >= _ComponentLoader.MAX_ERROR_ENTRIES) {
468
+ const oldest = this.errorCache.keys().next().value;
469
+ if (oldest !== void 0) this.errorCache.delete(oldest);
470
+ }
471
+ this.errorCache.set(cacheKey, {
430
472
  timestamp: Date.now(),
431
473
  count: currentError.count + 1
432
474
  });
433
475
  throw error;
434
476
  } finally {
435
- this.loadingPromises.delete(routePath);
477
+ this.loadingPromises.delete(cacheKey);
436
478
  }
437
479
  }
438
480
  async doLoadComponent(comp, routePath) {
@@ -486,6 +528,8 @@ var ComponentLoader = class {
486
528
  this.loadingPromises.clear();
487
529
  }
488
530
  };
531
+ _ComponentLoader.MAX_ERROR_ENTRIES = 256;
532
+ var ComponentLoader = _ComponentLoader;
489
533
  var _SibuRouter = class _SibuRouter {
490
534
  constructor(routes, options = {}) {
491
535
  // Event listeners cleanup
@@ -551,7 +595,7 @@ var _SibuRouter = class _SibuRouter {
551
595
  return window.location.hash.slice(1) || "/";
552
596
  }
553
597
  let path = window.location.pathname;
554
- if (base && path.startsWith(base)) {
598
+ if (base && (path === base || path.startsWith(`${base}/`))) {
555
599
  path = path.slice(base.length);
556
600
  }
557
601
  return (path || "/") + window.location.search + window.location.hash;
@@ -684,7 +728,7 @@ var _SibuRouter = class _SibuRouter {
684
728
  }
685
729
  if (to.params) {
686
730
  for (const [key, value] of Object.entries(to.params)) {
687
- path = path.replace(`:${key}`, encodeURIComponent(value));
731
+ path = path.replace(new RegExp(`:${key}(?=[/?#]|$)`, "g"), encodeURIComponent(value));
688
732
  }
689
733
  }
690
734
  if (to.query && Object.keys(to.query).length > 0) {
@@ -920,8 +964,7 @@ function Route() {
920
964
  let currentNode = null;
921
965
  let loadingNode = null;
922
966
  let errorNode = null;
923
- let isUpdating = false;
924
- let currentPath = "";
967
+ let navSeq = 0;
925
968
  let currentTopRoute = null;
926
969
  const cleanupNodes = () => {
927
970
  [currentNode, loadingNode, errorNode].forEach((node) => {
@@ -999,30 +1042,21 @@ function Route() {
999
1042
  errorNode.appendChild(retryButton);
1000
1043
  anchor.parentNode.insertBefore(errorNode, anchor.nextSibling);
1001
1044
  };
1002
- let pendingUpdate = false;
1003
1045
  const update = async () => {
1004
1046
  if (!globalRouter) return;
1005
- if (isUpdating) {
1006
- pendingUpdate = true;
1007
- return;
1008
- }
1047
+ const seq = ++navSeq;
1009
1048
  const route2 = globalRouter.currentRoute;
1010
1049
  try {
1011
1050
  const match = globalRouter["matcher"].match(route2.path);
1012
1051
  if (!match) {
1013
- currentPath = route2.path;
1014
1052
  currentTopRoute = null;
1015
1053
  cleanupNodes();
1016
1054
  return;
1017
1055
  }
1018
1056
  const routeDef = match.matched[0] || match.route;
1019
1057
  if (routeDef === currentTopRoute && currentNode) {
1020
- currentPath = route2.path;
1021
1058
  return;
1022
1059
  }
1023
- isUpdating = true;
1024
- currentPath = route2.path;
1025
- currentTopRoute = routeDef;
1026
1060
  if ("redirect" in routeDef) {
1027
1061
  const redirectPath = typeof routeDef.redirect === "function" ? routeDef.redirect(route2) : routeDef.redirect;
1028
1062
  queueMicrotask(() => {
@@ -1039,27 +1073,25 @@ function Route() {
1039
1073
  showLoading();
1040
1074
  }
1041
1075
  const component = await globalRouter.loadComponent(routeDef, route2.path);
1076
+ if (seq !== navSeq) return;
1042
1077
  const node = component();
1043
- if (node && anchor.parentNode && route2.path === currentPath) {
1078
+ if (node && anchor.parentNode) {
1079
+ currentTopRoute = routeDef;
1044
1080
  cleanupNodes();
1045
1081
  anchor.parentNode.insertBefore(node, anchor.nextSibling);
1046
1082
  currentNode = node;
1047
1083
  }
1048
1084
  } catch (error) {
1085
+ if (seq !== navSeq) return;
1049
1086
  hideLoading();
1050
1087
  console.error("[Route] Component error:", error);
1051
1088
  showError(error instanceof Error ? error : new Error(String(error)), routeDef);
1052
1089
  }
1053
1090
  }
1054
1091
  } catch (error) {
1092
+ if (seq !== navSeq) return;
1055
1093
  console.error("[Route] Update failed:", error);
1056
1094
  showError(error instanceof Error ? error : new Error(String(error)));
1057
- } finally {
1058
- isUpdating = false;
1059
- if (pendingUpdate) {
1060
- pendingUpdate = false;
1061
- update();
1062
- }
1063
1095
  }
1064
1096
  };
1065
1097
  let routeInitialized = false;
@@ -1114,7 +1146,8 @@ function KeepAliveRoute(options) {
1114
1146
  return;
1115
1147
  }
1116
1148
  if (!("component" in routeDef)) return;
1117
- const cacheKey = route2.path;
1149
+ const queryStr = Object.keys(route2.query).length > 0 ? `?${new URLSearchParams(route2.query).toString()}` : "";
1150
+ const cacheKey = `${route2.path}${queryStr}${route2.hash ? `#${route2.hash}` : ""}`;
1118
1151
  const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
1119
1152
  if (cacheKey === currentKey && currentNode) return;
1120
1153
  isUpdating = true;
@@ -1204,7 +1237,8 @@ function RouterLink(props) {
1204
1237
  const { to, replace: replace2 = false, activeClass, exactActiveClass, nodes, target, rel, class: classAttr, ...attrs } = props;
1205
1238
  const baseClass = typeof classAttr === "string" ? classAttr : "";
1206
1239
  const routeGetter = globalRouter.routeGetter;
1207
- const href = globalRouter["resolvePath"](to);
1240
+ const rawHref = globalRouter["resolvePath"](to);
1241
+ const href = isSafeNavigationTarget(rawHref) ? rawHref : "#";
1208
1242
  const hrefPath = href.split("?")[0].split("#")[0];
1209
1243
  const link = document.createElement("a");
1210
1244
  link.href = href;
@@ -1236,9 +1270,18 @@ function RouterLink(props) {
1236
1270
  });
1237
1271
  registerDisposer(link, effectCleanup);
1238
1272
  Object.entries(attrs).forEach(([key, value]) => {
1239
- if (key.startsWith("on") || key === "href") return;
1273
+ const lkey = key.toLowerCase();
1274
+ if (lkey === "href" || lkey[0] === "o" && lkey[1] === "n") return;
1240
1275
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1241
- link.setAttribute(key, String(value));
1276
+ const str = String(value);
1277
+ if (isUrlAttribute(lkey)) {
1278
+ const safe = sanitizeUrl(str);
1279
+ if (safe) link.setAttribute(key, safe);
1280
+ } else if (lkey === "style") {
1281
+ link.setAttribute(key, sanitizeCSSValue(str));
1282
+ } else {
1283
+ link.setAttribute(key, str);
1284
+ }
1242
1285
  }
1243
1286
  });
1244
1287
  if (typeof nodes === "string") {
@@ -1395,24 +1438,43 @@ function __removeRouterPagehideHandler() {
1395
1438
  function Outlet() {
1396
1439
  const anchor = document.createComment("route-outlet-nested");
1397
1440
  let currentNode = null;
1441
+ let currentChild = null;
1442
+ let navSeq = 0;
1443
+ const clearCurrent = () => {
1444
+ if (currentNode) {
1445
+ dispose(currentNode);
1446
+ if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
1447
+ currentNode = null;
1448
+ }
1449
+ currentChild = null;
1450
+ };
1398
1451
  const update = async () => {
1399
1452
  if (!globalRouter) return;
1453
+ const seq = ++navSeq;
1400
1454
  const route2 = globalRouter.currentRoute;
1401
- if (route2.matched.length < 2) return;
1455
+ if (route2.matched.length < 2) {
1456
+ clearCurrent();
1457
+ return;
1458
+ }
1402
1459
  const childRoute = route2.matched[route2.matched.length - 1];
1403
- if (!childRoute || !("component" in childRoute)) return;
1460
+ if (!childRoute || !("component" in childRoute)) {
1461
+ clearCurrent();
1462
+ return;
1463
+ }
1464
+ if (childRoute === currentChild && currentNode) return;
1404
1465
  try {
1405
1466
  const cacheKey = `${route2.path}\0${childRoute.path}`;
1406
1467
  const component = await globalRouter.loadComponent(childRoute, cacheKey);
1468
+ if (seq !== navSeq) return;
1407
1469
  const node = component();
1408
1470
  if (node && anchor.parentNode) {
1409
- if (currentNode?.parentNode) {
1410
- currentNode.parentNode.removeChild(currentNode);
1411
- }
1471
+ clearCurrent();
1412
1472
  anchor.parentNode.insertBefore(node, anchor.nextSibling);
1413
1473
  currentNode = node;
1474
+ currentChild = childRoute;
1414
1475
  }
1415
1476
  } catch (error) {
1477
+ if (seq !== navSeq) return;
1416
1478
  console.error("[Outlet] Failed to render child route:", error);
1417
1479
  }
1418
1480
  };
@@ -1488,10 +1550,6 @@ function createMemoryRouter(routes, _initialPath = "/") {
1488
1550
  }
1489
1551
 
1490
1552
  // src/plugins/routerSSR.ts
1491
- var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1492
- function isForbiddenKey(key) {
1493
- return FORBIDDEN_KEYS.has(key);
1494
- }
1495
1553
  function safeDecode(raw) {
1496
1554
  try {
1497
1555
  return decodeURIComponent(raw);
@@ -1532,7 +1590,7 @@ function parseURL(url) {
1532
1590
  key = safeDecode(pair.slice(0, eqIndex));
1533
1591
  value = safeDecode(pair.slice(eqIndex + 1));
1534
1592
  }
1535
- if (isForbiddenKey(key)) continue;
1593
+ if (isUnsafeKey(key)) continue;
1536
1594
  query[key] = value;
1537
1595
  }
1538
1596
  }
@@ -1594,7 +1652,7 @@ function matchRoute(path, routes, parentPath = "", parentChain = []) {
1594
1652
  const params = nullObject();
1595
1653
  for (let i = 0; i < compiled.keys.length; i++) {
1596
1654
  const key = compiled.keys[i];
1597
- if (isForbiddenKey(key)) continue;
1655
+ if (isUnsafeKey(key)) continue;
1598
1656
  if (match[i + 1] !== void 0) {
1599
1657
  params[key] = safeDecode(match[i + 1]);
1600
1658
  }
@@ -1691,7 +1749,7 @@ function renderRouteToString(url, routes, _options) {
1691
1749
  function renderRouteToDocument(url, routes, options) {
1692
1750
  const { html, state } = renderRouteToString(url, routes, options);
1693
1751
  const opts = options || {};
1694
- const metaTags = (opts.meta || []).map((attrs) => {
1752
+ const metaTags = (opts.meta || []).filter((attrs) => !isDangerousMetaRefresh(attrs)).map((attrs) => {
1695
1753
  const pairs = buildSafeAttrString(attrs);
1696
1754
  return pairs ? `<meta ${pairs} />` : "";
1697
1755
  }).filter(Boolean).join("\n ");
@@ -1742,7 +1800,7 @@ function hydrateRouter(routes, options) {
1742
1800
  if (container && serverState.path) {
1743
1801
  const resolved = resolveServerRoute(serverState.path, routes);
1744
1802
  if (resolved.component) {
1745
- import("./ssr-FXD2PPMC.js").then(({ hydrate }) => {
1803
+ import("./ssr-2QDQ27EV.js").then(({ hydrate }) => {
1746
1804
  if (resolved.component) {
1747
1805
  hydrate(resolved.component, container);
1748
1806
  }
@@ -5,6 +5,7 @@ import {
5
5
  hydrate,
6
6
  hydrateIslands,
7
7
  hydrateProgressively,
8
+ isDangerousMetaRefresh,
8
9
  island,
9
10
  renderToDocument,
10
11
  renderToReadableStream,
@@ -16,9 +17,9 @@ import {
16
17
  ssrSuspense,
17
18
  suspenseSwapScript,
18
19
  trustHTML
19
- } from "./chunk-JYD2PWXH.js";
20
- import "./chunk-UCS6AMJ7.js";
21
- import "./chunk-2RA7SHDA.js";
20
+ } from "./chunk-HXMS4SNP.js";
21
+ import "./chunk-HMJFCBRR.js";
22
+ import "./chunk-GOJMFRBL.js";
22
23
  import "./chunk-LMLD24FC.js";
23
24
  export {
24
25
  collectStream,
@@ -27,6 +28,7 @@ export {
27
28
  hydrate,
28
29
  hydrateIslands,
29
30
  hydrateProgressively,
31
+ isDangerousMetaRefresh,
30
32
  island,
31
33
  renderToDocument,
32
34
  renderToReadableStream,
@@ -57,6 +57,13 @@ type TrustedHTML = string & {
57
57
  * ```
58
58
  */
59
59
  declare function trustHTML(html: string): TrustedHTML;
60
+ /**
61
+ * Detect `<meta http-equiv="refresh" content="0;url=javascript:...">`.
62
+ * Returns true if the props describe a refresh directive whose URL uses
63
+ * a dangerous protocol — in which case the entire meta entry must be
64
+ * dropped to avoid an XSS vector via the browser refresh mechanism.
65
+ */
66
+ declare function isDangerousMetaRefresh(metaProps: Record<string, string>): boolean;
60
67
  /**
61
68
  * Renders a component to a full HTML document string.
62
69
  *
@@ -192,4 +199,4 @@ declare function serializeState(state: Record<string, unknown>, nonce?: string,
192
199
  */
193
200
  declare function deserializeState<T = Record<string, unknown>>(validate?: (data: unknown) => data is T): T | undefined;
194
201
 
195
- export { type HydrateOptions as H, type TrustedHTML as T, type HydrationMismatch as a, hydrateIslands as b, collectStream as c, deserializeState as d, escapeScriptJson as e, hydrateProgressively as f, renderToReadableStream as g, hydrate as h, island as i, renderToStream as j, renderToString as k, renderToSuspenseStream as l, resetSSRState as m, ssrSuspense as n, suspenseSwapScript as o, renderToDocument as r, serializeState as s, trustHTML as t };
202
+ export { type HydrateOptions as H, type TrustedHTML as T, type HydrationMismatch as a, hydrateIslands as b, collectStream as c, deserializeState as d, escapeScriptJson as e, hydrateProgressively as f, island as g, hydrate as h, isDangerousMetaRefresh as i, renderToReadableStream as j, renderToStream as k, renderToString as l, renderToSuspenseStream as m, resetSSRState as n, ssrSuspense as o, suspenseSwapScript as p, renderToDocument as r, serializeState as s, trustHTML as t };
@@ -57,6 +57,13 @@ type TrustedHTML = string & {
57
57
  * ```
58
58
  */
59
59
  declare function trustHTML(html: string): TrustedHTML;
60
+ /**
61
+ * Detect `<meta http-equiv="refresh" content="0;url=javascript:...">`.
62
+ * Returns true if the props describe a refresh directive whose URL uses
63
+ * a dangerous protocol — in which case the entire meta entry must be
64
+ * dropped to avoid an XSS vector via the browser refresh mechanism.
65
+ */
66
+ declare function isDangerousMetaRefresh(metaProps: Record<string, string>): boolean;
60
67
  /**
61
68
  * Renders a component to a full HTML document string.
62
69
  *
@@ -192,4 +199,4 @@ declare function serializeState(state: Record<string, unknown>, nonce?: string,
192
199
  */
193
200
  declare function deserializeState<T = Record<string, unknown>>(validate?: (data: unknown) => data is T): T | undefined;
194
201
 
195
- export { type HydrateOptions as H, type TrustedHTML as T, type HydrationMismatch as a, hydrateIslands as b, collectStream as c, deserializeState as d, escapeScriptJson as e, hydrateProgressively as f, renderToReadableStream as g, hydrate as h, island as i, renderToStream as j, renderToString as k, renderToSuspenseStream as l, resetSSRState as m, ssrSuspense as n, suspenseSwapScript as o, renderToDocument as r, serializeState as s, trustHTML as t };
202
+ export { type HydrateOptions as H, type TrustedHTML as T, type HydrationMismatch as a, hydrateIslands as b, collectStream as c, deserializeState as d, escapeScriptJson as e, hydrateProgressively as f, island as g, hydrate as h, isDangerousMetaRefresh as i, renderToReadableStream as j, renderToStream as k, renderToString as l, renderToSuspenseStream as m, resetSSRState as n, ssrSuspense as o, suspenseSwapScript as p, renderToDocument as r, serializeState as s, trustHTML as t };