sibujs 4.8.0 → 4.10.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 (66) hide show
  1. package/dist/browser.cjs +44 -6
  2. package/dist/browser.d.cts +3 -2
  3. package/dist/browser.d.ts +3 -2
  4. package/dist/browser.js +4 -4
  5. package/dist/build.cjs +128 -56
  6. package/dist/build.js +10 -10
  7. package/dist/cdn.dev.global.js +14 -14
  8. package/dist/cdn.full.dev.global.js +13 -13
  9. package/dist/cdn.full.global.js +12 -12
  10. package/dist/cdn.global.js +15 -15
  11. package/dist/{chunk-J3YQ457H.js → chunk-2NWHJ2UJ.js} +99 -61
  12. package/dist/{chunk-JUTFX2RM.js → chunk-4DCVJ4YA.js} +42 -15
  13. package/dist/{chunk-QGMSESDD.js → chunk-54F4TPHK.js} +2 -2
  14. package/dist/{chunk-KJ3UKABJ.js → chunk-5PC7OTQW.js} +1 -1
  15. package/dist/{chunk-V3LHZSU5.js → chunk-7WIHYHWD.js} +8 -1
  16. package/dist/{chunk-44MTL7ED.js → chunk-BMCR7TXG.js} +3 -3
  17. package/dist/{chunk-TQQUN4WR.js → chunk-D6HYFOWR.js} +39 -4
  18. package/dist/{chunk-VWT3VXPL.js → chunk-EVI4ZQO3.js} +2 -2
  19. package/dist/{chunk-CZPRSWSS.js → chunk-EVOK663Z.js} +1 -1
  20. package/dist/{chunk-DSID757X.js → chunk-FYUEL5IW.js} +1 -1
  21. package/dist/{chunk-UCNP3HVM.js → chunk-GMMDHJJT.js} +30 -12
  22. package/dist/{chunk-C7AA2CZX.js → chunk-GOL3FU5Q.js} +1 -1
  23. package/dist/{chunk-EGUQLBHE.js → chunk-GTS35FLA.js} +5 -4
  24. package/dist/{chunk-GSGNX35Q.js → chunk-IFRIBZ4O.js} +4 -4
  25. package/dist/{chunk-C5PEWEGH.js → chunk-IL5MMSCL.js} +19 -12
  26. package/dist/{chunk-ZVLHNEEL.js → chunk-ITEL5KPL.js} +6 -6
  27. package/dist/{chunk-RGLPEBDE.js → chunk-JCKUWWHU.js} +3 -3
  28. package/dist/{chunk-Q5J2LVKH.js → chunk-N6U4GOIU.js} +1 -1
  29. package/dist/{chunk-LI4W3U6C.js → chunk-TOPRMXFY.js} +1 -1
  30. package/dist/{chunk-4WMSTZ7J.js → chunk-VWPMWV7O.js} +4 -4
  31. package/dist/{chunk-O6TRVMJI.js → chunk-XUQUQJYE.js} +1 -1
  32. package/dist/data.cjs +67 -12
  33. package/dist/data.d.cts +32 -5
  34. package/dist/data.d.ts +32 -5
  35. package/dist/data.js +5 -5
  36. package/dist/devtools.cjs +41 -4
  37. package/dist/devtools.js +4 -4
  38. package/dist/ecosystem.cjs +41 -4
  39. package/dist/ecosystem.js +7 -7
  40. package/dist/extras.cjs +125 -41
  41. package/dist/extras.js +20 -20
  42. package/dist/index.cjs +128 -56
  43. package/dist/index.d.cts +22 -0
  44. package/dist/index.d.ts +22 -0
  45. package/dist/index.js +10 -10
  46. package/dist/motion.cjs +36 -4
  47. package/dist/motion.js +3 -3
  48. package/dist/patterns.cjs +41 -4
  49. package/dist/patterns.js +5 -5
  50. package/dist/performance.cjs +55 -14
  51. package/dist/performance.js +4 -4
  52. package/dist/plugins.cjs +176 -60
  53. package/dist/plugins.d.cts +62 -2
  54. package/dist/plugins.d.ts +62 -2
  55. package/dist/plugins.js +143 -61
  56. package/dist/ssr.cjs +77 -16
  57. package/dist/ssr.d.cts +4 -0
  58. package/dist/ssr.d.ts +4 -0
  59. package/dist/ssr.js +7 -7
  60. package/dist/testing.cjs +41 -4
  61. package/dist/testing.js +2 -2
  62. package/dist/ui.cjs +42 -5
  63. package/dist/ui.js +7 -7
  64. package/dist/widgets.cjs +36 -4
  65. package/dist/widgets.js +6 -6
  66. package/package.json +1 -1
package/dist/plugins.d.ts CHANGED
@@ -180,6 +180,12 @@ type Params = Record<string, string>;
180
180
  interface RouteContext {
181
181
  readonly path: string;
182
182
  readonly params: Params;
183
+ /**
184
+ * The decoded query (`+` is a space, `%XX` is decoded). A repeated key keeps
185
+ * its last value: `?tag=a&tag=b` is `{ tag: "b" }`. Two locations are the
186
+ * same route — for duplicate detection and `RouterLink` exact-active — when
187
+ * their queries have the same entries in any order.
188
+ */
183
189
  readonly query: Params;
184
190
  readonly hash: string;
185
191
  readonly meta: RouteMeta;
@@ -421,8 +427,11 @@ declare class SibuRouter {
421
427
  * anything. Pair with {@link instantiateComponent} at the commit boundary.
422
428
  */
423
429
  loadPlan(route: RouteDef, routePath: string): Promise<RoutePlan>;
424
- /** Create exactly one real instance from an already-resolved plan. */
425
- instantiateComponent(plan: RoutePlan, route: RouteDef, routePath: string): Promise<Element>;
430
+ /**
431
+ * Create exactly one real instance from an already-resolved plan. With a
432
+ * `scope`, the factory runs in it (see `RouteScope`).
433
+ */
434
+ instantiateComponent(plan: RoutePlan, route: RouteDef, routePath: string, scope?: RouteScope): Promise<Element>;
426
435
  /** Resolve a route's module/factory without creating any component instance. */
427
436
  preloadPlan(route: RouteDef, routePath: string): Promise<void>;
428
437
  beforeEach(guard: NavigationGuard): () => void;
@@ -469,6 +478,50 @@ declare function createRouter(routesOrOptions: RouteDef[] | RouterOptions, optio
469
478
  * Set routes on the global router (replaces existing routes).
470
479
  */
471
480
  declare function setRoutes(routes: RouteDef[]): void;
481
+ /**
482
+ * The route one mounted route instance was committed with.
483
+ *
484
+ * A navigation commits the router's route first; the outlet swaps the
485
+ * component afterwards — a microtask later for a synchronous page, a whole
486
+ * chunk download later for a lazy one. If route instances read the
487
+ * router-global route, the outgoing page re-runs its effects against the
488
+ * INCOMING route's params in that window, and its teardown sees them too.
489
+ *
490
+ * So each instance reads its own scope instead. Outlets create one per
491
+ * instance, run the component factory in it (an owner scope, see
492
+ * reactivity/track-core.ts, so every computation the page creates — now or
493
+ * later, through its directives — inherits it), and then:
494
+ *
495
+ * - an instance the outlet KEEPS across a navigation (same record and instance
496
+ * key: a query/hash-only change, or a layout whose child changed) is handed
497
+ * the new route — it follows the navigation, as before;
498
+ * - an instance being REPLACED is never updated: it keeps observing its own
499
+ * route until it is disposed, and its teardown runs in its scope;
500
+ * - a KeepAlive view keeps its route while cached and gets the new route when
501
+ * it is shown again.
502
+ *
503
+ * Once the instance is disposed the scope is released: a computation that
504
+ * outlived it (an app-lifetime derived that happened to be created while the
505
+ * page rendered) is not the page's, and falls back to the router-global route.
506
+ */
507
+ declare class RouteScope {
508
+ private readonly get;
509
+ private readonly set;
510
+ private released;
511
+ constructor(route: RouteContext);
512
+ /** The instance's route, or `null` once released. Reactive. */
513
+ read(): RouteContext | null;
514
+ /** Hand a kept instance the route of the navigation that kept it. */
515
+ update(route: RouteContext): void;
516
+ /**
517
+ * Called when the instance is disposed. The switch to "no scope" is
518
+ * published a microtask later, once the whole dispose has finished: a
519
+ * teardown the instance registered after this one (an `onMount` effect, say)
520
+ * may still be alive at this point, and must not re-run against the
521
+ * router-global route on its way out.
522
+ */
523
+ release(): void;
524
+ }
472
525
  /**
473
526
  * Read the current route as a plain snapshot.
474
527
  *
@@ -477,6 +530,13 @@ declare function setRoutes(routes: RouteDef[]): void;
477
530
  * `RouteBase.key`). Inside a getter — `() => route().params.id` — the read is
478
531
  * reactive, which is what a long-lived layout or a constant-`key` route needs.
479
532
  *
533
+ * Inside a route instance (anything its component creates, and any effect,
534
+ * derived or binding created from there) this is the route THAT INSTANCE was
535
+ * committed with. An instance the outlet keeps across a navigation follows it;
536
+ * an instance being replaced keeps its own route until it is disposed, so its
537
+ * effects and teardown never observe the next page's params. Everywhere else
538
+ * it is the router's current route.
539
+ *
480
540
  * @returns The active {@link RouteContext} (path, params, query, hash).
481
541
  * @throws If no router has been created yet.
482
542
  */
package/dist/plugins.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  preloadCritical,
20
20
  prerenderRoutes,
21
21
  satisfies
22
- } from "./chunk-QGMSESDD.js";
22
+ } from "./chunk-54F4TPHK.js";
23
23
  import {
24
24
  PluginInstallCancelledError,
25
25
  createPlugin,
@@ -35,7 +35,7 @@ import {
35
35
  } from "./chunk-J6FW5TV6.js";
36
36
  import {
37
37
  span
38
- } from "./chunk-Q5J2LVKH.js";
38
+ } from "./chunk-N6U4GOIU.js";
39
39
  import {
40
40
  escapeScriptJson,
41
41
  renderToString,
@@ -48,17 +48,17 @@ import {
48
48
  import {
49
49
  appendChildren,
50
50
  resolveClassValue
51
- } from "./chunk-VWT3VXPL.js";
51
+ } from "./chunk-EVI4ZQO3.js";
52
52
  import {
53
53
  setSafeAttribute
54
- } from "./chunk-O6TRVMJI.js";
54
+ } from "./chunk-XUQUQJYE.js";
55
55
  import "./chunk-3ACAAQIN.js";
56
56
  import {
57
57
  globalSingleton
58
58
  } from "./chunk-7ZHH77QA.js";
59
59
  import {
60
60
  effect
61
- } from "./chunk-V3LHZSU5.js";
61
+ } from "./chunk-7WIHYHWD.js";
62
62
  import {
63
63
  sanitizeUrl,
64
64
  stripControlChars
@@ -69,11 +69,14 @@ import {
69
69
  import "./chunk-5HZXGZ6T.js";
70
70
  import {
71
71
  signal
72
- } from "./chunk-DSID757X.js";
72
+ } from "./chunk-FYUEL5IW.js";
73
73
  import {
74
+ bindOwnerScope,
75
+ getOwnerScope,
76
+ runWithOwnerScope,
74
77
  track,
75
78
  untracked
76
- } from "./chunk-TQQUN4WR.js";
79
+ } from "./chunk-D6HYFOWR.js";
77
80
  import {
78
81
  devWarn,
79
82
  dispose,
@@ -180,9 +183,14 @@ function normalizePathname(path) {
180
183
  const trimmed = path.length > 1 ? path.replace(/\/+$/, "") : path;
181
184
  return trimmed || "/";
182
185
  }
183
- function normalizeQuery(query) {
184
- const params = new URLSearchParams(query);
185
- return [...params.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
186
+ function parseQuery(query) {
187
+ return Object.fromEntries(new URLSearchParams(query));
188
+ }
189
+ function paramsIdentity(params) {
190
+ return JSON.stringify(Object.entries(params).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
191
+ }
192
+ function queryIdentity(query) {
193
+ return paramsIdentity(typeof query === "string" ? parseQuery(query) : query);
186
194
  }
187
195
  function isPathnameAncestor(target, current) {
188
196
  if (target === current) return true;
@@ -647,12 +655,12 @@ var _ComponentLoader = class _ComponentLoader {
647
655
  * resolved, so the import happens once and later mounts call the module's
648
656
  * factory directly.
649
657
  */
650
- async instantiate(plan, route2, routePath) {
658
+ async instantiate(plan, route2, routePath, scope) {
651
659
  if (plan.kind === "factory") {
652
- return this.instantiateFactory(plan.component, route2, routePath);
660
+ return this.instantiateFactory(plan.component, route2, routePath, scope);
653
661
  }
654
662
  const factory = await this.resolveModuleFactory(plan, route2, routePath);
655
- return this.instantiateFactory(factory, route2, routePath);
663
+ return this.instantiateFactory(factory, route2, routePath, scope);
656
664
  }
657
665
  /**
658
666
  * Resolve a deferred plan's module and cache the factory it exports —
@@ -697,14 +705,14 @@ var _ComponentLoader = class _ComponentLoader {
697
705
  * `depth` bounds that unwrapping; a module whose default is itself a module
698
706
  * is a configuration error, not something to chase.
699
707
  */
700
- async instantiateFactory(component, route2, routePath, depth = 0) {
701
- const result = component();
708
+ async instantiateFactory(component, route2, routePath, scope, depth = 0) {
709
+ const result = scope ? runWithOwnerScope(scope, component) : component();
702
710
  const value = this.isThenable(result) ? await result : result;
703
711
  if (this.isElement(value)) return value;
704
712
  if (depth === 0 && this.isFactoryLike(value)) {
705
713
  const factory = this.extractFactory(value, routePath);
706
714
  this.planCache.set(this.keyFor(route2), { kind: "factory", component: factory });
707
- return this.instantiateFactory(factory, route2, routePath, depth + 1);
715
+ return this.instantiateFactory(factory, route2, routePath, scope, depth + 1);
708
716
  }
709
717
  throw new Error(`Component for route "${routePath}" must return Element, got ${typeof value}`);
710
718
  }
@@ -883,7 +891,7 @@ var _SibuRouter = class _SibuRouter {
883
891
  }
884
892
  createRouteContext(fullPath) {
885
893
  const { path, query: queryString, hash } = splitRouteUrl(fullPath);
886
- const query = Object.fromEntries(new URLSearchParams(queryString));
894
+ const query = parseQuery(queryString);
887
895
  const match = this.matcher.match(path || "/");
888
896
  const params = match?.params || {};
889
897
  const meta = match?.route.meta || {};
@@ -1047,7 +1055,7 @@ var _SibuRouter = class _SibuRouter {
1047
1055
  }
1048
1056
  isSameRoute(from, to) {
1049
1057
  if (from.matched.length === 0 && to.matched.length > 0) return false;
1050
- return from.path === to.path && JSON.stringify(from.params) === JSON.stringify(to.params) && JSON.stringify(from.query) === JSON.stringify(to.query) && from.hash === to.hash;
1058
+ return from.path === to.path && paramsIdentity(from.params) === paramsIdentity(to.params) && queryIdentity(from.query) === queryIdentity(to.query) && from.hash === to.hash;
1051
1059
  }
1052
1060
  updateHistory(to, options) {
1053
1061
  const fullPath = this.options.base + to.path + (Object.keys(to.query).length ? `?${new URLSearchParams(to.query).toString()}` : "") + (to.hash ? `#${to.hash}` : "");
@@ -1109,9 +1117,12 @@ var _SibuRouter = class _SibuRouter {
1109
1117
  async loadPlan(route2, routePath) {
1110
1118
  return this.loader.loadPlan(route2, routePath);
1111
1119
  }
1112
- /** Create exactly one real instance from an already-resolved plan. */
1113
- async instantiateComponent(plan, route2, routePath) {
1114
- return this.loader.instantiate(plan, route2, routePath);
1120
+ /**
1121
+ * Create exactly one real instance from an already-resolved plan. With a
1122
+ * `scope`, the factory runs in it (see `RouteScope`).
1123
+ */
1124
+ async instantiateComponent(plan, route2, routePath, scope) {
1125
+ return this.loader.instantiate(plan, route2, routePath, scope);
1115
1126
  }
1116
1127
  /** Resolve a route's module/factory without creating any component instance. */
1117
1128
  async preloadPlan(route2, routePath) {
@@ -1267,14 +1278,72 @@ function setRoutes(routes) {
1267
1278
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1268
1279
  _routerRef.current.updateRoutes(normalizeRoutes(routes));
1269
1280
  }
1281
+ var RouteScope = class {
1282
+ constructor(route2) {
1283
+ this.released = false;
1284
+ const [get, set] = signal(route2);
1285
+ this.get = get;
1286
+ this.set = set;
1287
+ }
1288
+ /** The instance's route, or `null` once released. Reactive. */
1289
+ read() {
1290
+ return this.get();
1291
+ }
1292
+ /** Hand a kept instance the route of the navigation that kept it. */
1293
+ update(route2) {
1294
+ if (!this.released) this.set(route2);
1295
+ }
1296
+ /**
1297
+ * Called when the instance is disposed. The switch to "no scope" is
1298
+ * published a microtask later, once the whole dispose has finished: a
1299
+ * teardown the instance registered after this one (an `onMount` effect, say)
1300
+ * may still be alive at this point, and must not re-run against the
1301
+ * router-global route on its way out.
1302
+ */
1303
+ release() {
1304
+ if (this.released) return;
1305
+ this.released = true;
1306
+ queueMicrotask(() => this.set(null));
1307
+ }
1308
+ };
1309
+ function activeRouteScope() {
1310
+ const scope = getOwnerScope();
1311
+ return scope instanceof RouteScope ? scope : null;
1312
+ }
1313
+ function scopedRoute(router2) {
1314
+ const scope = activeRouteScope();
1315
+ if (scope) {
1316
+ const committed = scope.read();
1317
+ if (committed !== null) return committed;
1318
+ }
1319
+ return router2.currentRoute;
1320
+ }
1321
+ var instanceScopes = /* @__PURE__ */ new WeakMap();
1322
+ async function instantiateScoped(router2, plan, routeDef, routePath, route2) {
1323
+ const scope = new RouteScope(route2);
1324
+ const node = await router2.instantiateComponent(plan, routeDef, routePath, scope);
1325
+ if (node) {
1326
+ instanceScopes.set(node, scope);
1327
+ registerDisposer(node, () => scope.release());
1328
+ }
1329
+ return node;
1330
+ }
1331
+ function updateInstanceRoute(node, route2) {
1332
+ if (node) instanceScopes.get(node)?.update(route2);
1333
+ }
1334
+ function disposeOwned(node) {
1335
+ const scope = instanceScopes.get(node);
1336
+ if (scope) runWithOwnerScope(scope, () => dispose(node));
1337
+ else dispose(node);
1338
+ }
1270
1339
  function route() {
1271
1340
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1272
- return _routerRef.current.currentRoute;
1341
+ return scopedRoute(_routerRef.current);
1273
1342
  }
1274
1343
  function router() {
1275
1344
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1276
1345
  return {
1277
- currentRoute: _routerRef.current.currentRoute,
1346
+ currentRoute: scopedRoute(_routerRef.current),
1278
1347
  isReady: _routerRef.current.isReady,
1279
1348
  isNavigating: _routerRef.current.isNavigating,
1280
1349
  push: (to) => _routerRef.current?.push(to),
@@ -1360,13 +1429,13 @@ function Route() {
1360
1429
  const commitTarget = (seq) => routeTorn || seq !== navSeq ? null : anchor.parentNode;
1361
1430
  const release = (node) => {
1362
1431
  if (!node) return;
1363
- dispose(node);
1432
+ disposeOwned(node);
1364
1433
  node.parentNode?.removeChild(node);
1365
1434
  };
1366
1435
  const cleanupNodes = () => {
1367
1436
  [currentNode, loadingNode, errorNode].forEach((node) => {
1368
1437
  if (!node) return;
1369
- dispose(node);
1438
+ disposeOwned(node);
1370
1439
  if (node.parentNode) {
1371
1440
  node.parentNode.removeChild(node);
1372
1441
  }
@@ -1442,7 +1511,7 @@ function Route() {
1442
1511
  const update = async () => {
1443
1512
  if (routeTorn || !_routerRef.current) return;
1444
1513
  const seq = ++navSeq;
1445
- const route2 = _routerRef.current.currentRoute;
1514
+ const route2 = scopedRoute(_routerRef.current);
1446
1515
  try {
1447
1516
  const match = _routerRef.current["matcher"].match(route2.path);
1448
1517
  if (!match) {
@@ -1454,6 +1523,7 @@ function Route() {
1454
1523
  const routeDef = match.matched[0] || match.route;
1455
1524
  const key = instanceKey(routeDef, [routeDef], route2);
1456
1525
  if (routeDef === currentTopRoute && key === currentKey && currentNode) {
1526
+ updateInstanceRoute(currentNode, route2);
1457
1527
  return;
1458
1528
  }
1459
1529
  if ("redirect" in routeDef) {
@@ -1473,7 +1543,7 @@ function Route() {
1473
1543
  }
1474
1544
  const plan = await _routerRef.current.loadPlan(routeDef, route2.path);
1475
1545
  if (!commitTarget(seq)) return;
1476
- const node = await _routerRef.current.instantiateComponent(plan, routeDef, route2.path);
1546
+ const node = await instantiateScoped(_routerRef.current, plan, routeDef, route2.path, route2);
1477
1547
  const parent = commitTarget(seq);
1478
1548
  if (!parent) {
1479
1549
  release(node);
@@ -1508,9 +1578,11 @@ function Route() {
1508
1578
  };
1509
1579
  const routeTeardown = track(wrappedUpdate);
1510
1580
  if (!routeInitialized) {
1511
- queueMicrotask(() => {
1512
- if (!routeInitialized && anchor.parentNode) wrappedUpdate();
1513
- });
1581
+ queueMicrotask(
1582
+ bindOwnerScope(() => {
1583
+ if (!routeInitialized && anchor.parentNode) wrappedUpdate();
1584
+ })
1585
+ );
1514
1586
  }
1515
1587
  const routeCleanup = () => {
1516
1588
  if (routeTorn) return;
@@ -1543,7 +1615,7 @@ function KeepAliveRoute(options) {
1543
1615
  if (kaTorn || !_routerRef.current) return;
1544
1616
  const seq = ++updateSeq;
1545
1617
  const router2 = _routerRef.current;
1546
- const route2 = router2.currentRoute;
1618
+ const route2 = scopedRoute(router2);
1547
1619
  const match = router2["matcher"].match(route2.path);
1548
1620
  if (!match) return;
1549
1621
  const { route: routeDef } = match;
@@ -1557,10 +1629,12 @@ function KeepAliveRoute(options) {
1557
1629
  return;
1558
1630
  }
1559
1631
  if (!("component" in routeDef)) return;
1560
- const queryStr = Object.keys(route2.query).length > 0 ? `?${new URLSearchParams(route2.query).toString()}` : "";
1561
- const cacheKey = `${route2.path}${queryStr}${route2.hash ? `#${route2.hash}` : ""}`;
1632
+ const cacheKey = JSON.stringify([route2.path, queryIdentity(route2.query), route2.hash]);
1562
1633
  const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
1563
- if (cacheKey === currentKey && currentNode) return;
1634
+ if (cacheKey === currentKey && currentNode) {
1635
+ updateInstanceRoute(currentNode, route2);
1636
+ return;
1637
+ }
1564
1638
  if (!anchor.parentNode) return;
1565
1639
  let node = null;
1566
1640
  let fromCache = false;
@@ -1571,7 +1645,7 @@ function KeepAliveRoute(options) {
1571
1645
  try {
1572
1646
  const plan = await router2.loadPlan(routeDef, route2.path);
1573
1647
  if (kaTorn || seq !== updateSeq) return;
1574
- node = await router2.instantiateComponent(plan, routeDef, route2.path);
1648
+ node = await instantiateScoped(router2, plan, routeDef, route2.path, route2);
1575
1649
  } catch (error) {
1576
1650
  if (kaTorn || seq !== updateSeq) return;
1577
1651
  console.error("[KeepAliveRoute] Component error:", error);
@@ -1579,20 +1653,21 @@ function KeepAliveRoute(options) {
1579
1653
  }
1580
1654
  if (!node) return;
1581
1655
  if (kaTorn || seq !== updateSeq) {
1582
- dispose(node);
1656
+ disposeOwned(node);
1583
1657
  return;
1584
1658
  }
1585
1659
  }
1586
1660
  const parent = anchor.parentNode;
1587
1661
  if (!parent) {
1588
- if (!fromCache) dispose(node);
1662
+ if (!fromCache) disposeOwned(node);
1589
1663
  return;
1590
1664
  }
1591
1665
  if (currentNode && currentNode !== node) {
1592
1666
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
1593
- if (!currentCached) dispose(currentNode);
1667
+ if (!currentCached) disposeOwned(currentNode);
1594
1668
  }
1595
1669
  if (fromCache) {
1670
+ updateInstanceRoute(node, route2);
1596
1671
  const idx = lruOrder.indexOf(cacheKey);
1597
1672
  if (idx !== -1) lruOrder.splice(idx, 1);
1598
1673
  lruOrder.push(cacheKey);
@@ -1607,7 +1682,7 @@ function KeepAliveRoute(options) {
1607
1682
  }
1608
1683
  const evictNode = cache.get(evictKey);
1609
1684
  if (evictNode) {
1610
- dispose(evictNode);
1685
+ disposeOwned(evictNode);
1611
1686
  if (evictNode.parentNode) evictNode.parentNode.removeChild(evictNode);
1612
1687
  cache.delete(evictKey);
1613
1688
  }
@@ -1625,9 +1700,11 @@ function KeepAliveRoute(options) {
1625
1700
  };
1626
1701
  const kaTeardown = track(wrappedUpdate);
1627
1702
  if (!initialized) {
1628
- queueMicrotask(() => {
1629
- if (!initialized && anchor.parentNode) wrappedUpdate();
1630
- });
1703
+ queueMicrotask(
1704
+ bindOwnerScope(() => {
1705
+ if (!initialized && anchor.parentNode) wrappedUpdate();
1706
+ })
1707
+ );
1631
1708
  }
1632
1709
  const kaCleanup = () => {
1633
1710
  if (kaTorn) return;
@@ -1635,13 +1712,13 @@ function KeepAliveRoute(options) {
1635
1712
  updateSeq++;
1636
1713
  kaTeardown();
1637
1714
  for (const node of cache.values()) {
1638
- dispose(node);
1715
+ disposeOwned(node);
1639
1716
  if (node.parentNode) node.parentNode.removeChild(node);
1640
1717
  }
1641
1718
  cache.clear();
1642
1719
  lruOrder.length = 0;
1643
1720
  if (currentNode?.parentNode) currentNode.parentNode.removeChild(currentNode);
1644
- if (currentNode && !currentCached) dispose(currentNode);
1721
+ if (currentNode && !currentCached) disposeOwned(currentNode);
1645
1722
  currentNode = null;
1646
1723
  currentKey = "";
1647
1724
  currentCached = false;
@@ -1660,7 +1737,7 @@ function RouterLink(props, children) {
1660
1737
  const href = kind === "unsafe" ? "#" : rawHref;
1661
1738
  const { path: hrefPathRaw, query: hrefQueryRaw, hash: hrefHash } = splitRouteUrl(href);
1662
1739
  const targetPath = normalizePathname(hrefPathRaw);
1663
- const targetQuery = normalizeQuery(hrefQueryRaw);
1740
+ const targetQuery = queryIdentity(hrefQueryRaw);
1664
1741
  const link = document.createElement("a");
1665
1742
  link.href = href;
1666
1743
  if (target) {
@@ -1678,7 +1755,7 @@ function RouterLink(props, children) {
1678
1755
  const route2 = routeGetter();
1679
1756
  const currentPath = normalizePathname(route2.path);
1680
1757
  const isActive = kind === "internal" && isPathnameAncestor(targetPath, currentPath);
1681
- const isExactActive = kind === "internal" && currentPath === targetPath && normalizeQuery(route2.query) === targetQuery && route2.hash === hrefHash;
1758
+ const isExactActive = kind === "internal" && currentPath === targetPath && queryIdentity(route2.query) === targetQuery && route2.hash === hrefHash;
1682
1759
  const classes = [];
1683
1760
  if (isActive) {
1684
1761
  if (activeClass) classes.push(activeClass);
@@ -1797,7 +1874,7 @@ function Suspense(props) {
1797
1874
  generation++;
1798
1875
  cleanupNodes();
1799
1876
  });
1800
- queueMicrotask(render);
1877
+ queueMicrotask(bindOwnerScope(render));
1801
1878
  return anchor;
1802
1879
  }
1803
1880
  var LAZY_MARKER = /* @__PURE__ */ Symbol.for("sibujs:lazy");
@@ -1865,7 +1942,7 @@ function Outlet() {
1865
1942
  let outletTorn = false;
1866
1943
  const clearCurrent = () => {
1867
1944
  if (currentNode) {
1868
- dispose(currentNode);
1945
+ disposeOwned(currentNode);
1869
1946
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
1870
1947
  currentNode = null;
1871
1948
  }
@@ -1875,13 +1952,13 @@ function Outlet() {
1875
1952
  const commitTarget = (seq) => outletTorn || seq !== navSeq ? null : anchor.parentNode;
1876
1953
  const release = (node) => {
1877
1954
  if (!node) return;
1878
- dispose(node);
1955
+ disposeOwned(node);
1879
1956
  node.parentNode?.removeChild(node);
1880
1957
  };
1881
1958
  const update = async () => {
1882
1959
  if (outletTorn || !_routerRef.current) return;
1883
1960
  const seq = ++navSeq;
1884
- const route2 = _routerRef.current.currentRoute;
1961
+ const route2 = scopedRoute(_routerRef.current);
1885
1962
  if (route2.matched.length < 2) {
1886
1963
  clearCurrent();
1887
1964
  return;
@@ -1892,12 +1969,15 @@ function Outlet() {
1892
1969
  return;
1893
1970
  }
1894
1971
  const key = instanceKey(childRoute, route2.matched.slice(1), route2);
1895
- if (childRoute === currentChild && key === currentKey && currentNode) return;
1972
+ if (childRoute === currentChild && key === currentKey && currentNode) {
1973
+ updateInstanceRoute(currentNode, route2);
1974
+ return;
1975
+ }
1896
1976
  try {
1897
1977
  const cacheKey = `${route2.path}\0${childRoute.path}`;
1898
1978
  const plan = await _routerRef.current.loadPlan(childRoute, cacheKey);
1899
1979
  if (!commitTarget(seq)) return;
1900
- const node = await _routerRef.current.instantiateComponent(plan, childRoute, cacheKey);
1980
+ const node = await instantiateScoped(_routerRef.current, plan, childRoute, cacheKey, route2);
1901
1981
  const parent = commitTarget(seq);
1902
1982
  if (!parent) {
1903
1983
  release(node);
@@ -1916,9 +1996,11 @@ function Outlet() {
1916
1996
  };
1917
1997
  const outletTeardown = track(update);
1918
1998
  if (!anchor.parentNode) {
1919
- queueMicrotask(() => {
1920
- if (anchor.parentNode) update();
1921
- });
1999
+ queueMicrotask(
2000
+ bindOwnerScope(() => {
2001
+ if (anchor.parentNode) update();
2002
+ })
2003
+ );
1922
2004
  }
1923
2005
  const outletCleanup = () => {
1924
2006
  if (outletTorn) return;
@@ -1926,7 +2008,7 @@ function Outlet() {
1926
2008
  navSeq++;
1927
2009
  outletTeardown();
1928
2010
  if (currentNode) {
1929
- dispose(currentNode);
2011
+ disposeOwned(currentNode);
1930
2012
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
1931
2013
  currentNode = null;
1932
2014
  }
@@ -1949,11 +2031,11 @@ function routerState() {
1949
2031
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
1950
2032
  const router2 = _routerRef.current;
1951
2033
  return {
1952
- currentPath: () => router2.currentRoute.path,
1953
- params: () => router2.currentRoute.params,
1954
- query: () => router2.currentRoute.query,
1955
- hash: () => router2.currentRoute.hash,
1956
- meta: () => router2.currentRoute.meta,
2034
+ currentPath: () => scopedRoute(router2).path,
2035
+ params: () => scopedRoute(router2).params,
2036
+ query: () => scopedRoute(router2).query,
2037
+ hash: () => scopedRoute(router2).hash,
2038
+ meta: () => scopedRoute(router2).meta,
1957
2039
  isNavigating: () => router2.isNavigating,
1958
2040
  isReady: () => router2.isReady
1959
2041
  };