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.cjs CHANGED
@@ -2008,6 +2008,24 @@ function untracked(fn) {
2008
2008
  resumeTracking();
2009
2009
  }
2010
2010
  }
2011
+ var currentOwnerScope = null;
2012
+ function getOwnerScope() {
2013
+ return currentOwnerScope;
2014
+ }
2015
+ function runWithOwnerScope(scope, fn) {
2016
+ const prev = currentOwnerScope;
2017
+ currentOwnerScope = scope;
2018
+ try {
2019
+ return fn();
2020
+ } finally {
2021
+ currentOwnerScope = prev;
2022
+ }
2023
+ }
2024
+ function bindOwnerScope(fn) {
2025
+ const scope = currentOwnerScope;
2026
+ if (scope === null) return fn;
2027
+ return (...args) => runWithOwnerScope(scope, () => fn(...args));
2028
+ }
2011
2029
  var subscriberEpochCounter = 0;
2012
2030
  function retrack(effectFn, subscriber) {
2013
2031
  const prev = currentSubscriber;
@@ -2023,6 +2041,10 @@ function retrack(effectFn, subscriber) {
2023
2041
  currentSubscriber = subscriber;
2024
2042
  const sub2 = subscriber;
2025
2043
  if (sub2._cap === void 0) sub2._cap = currentDisposerCapture();
2044
+ const prevScope = currentOwnerScope;
2045
+ if (prevScope !== null && !sub2._epoch && sub2._scope === void 0) sub2._scope = prevScope;
2046
+ const scope = sub2._scope === void 0 ? null : sub2._scope;
2047
+ if (scope !== prevScope) currentOwnerScope = scope;
2026
2048
  const epoch = ++subscriberEpochCounter;
2027
2049
  sub2._epoch = epoch;
2028
2050
  sub2._structDirty = false;
@@ -2035,6 +2057,7 @@ function retrack(effectFn, subscriber) {
2035
2057
  effectFn();
2036
2058
  } finally {
2037
2059
  currentSubscriber = prev;
2060
+ currentOwnerScope = prevScope;
2038
2061
  if (savedDepth !== 0) {
2039
2062
  suspendDepth = savedDepth;
2040
2063
  suspendSavedSub = savedSuspendSub;
@@ -2069,23 +2092,26 @@ function track(effectFn, subscriber) {
2069
2092
  trackingSuspended = false;
2070
2093
  }
2071
2094
  currentSubscriber = subscriber;
2095
+ const sub2 = subscriber;
2096
+ if (sub2._scope === void 0) sub2._scope = currentOwnerScope;
2097
+ const prevScope = currentOwnerScope;
2098
+ currentOwnerScope = sub2._scope;
2072
2099
  try {
2073
2100
  effectFn();
2074
2101
  } finally {
2075
2102
  currentSubscriber = prev;
2103
+ currentOwnerScope = prevScope;
2076
2104
  if (savedDepth !== 0) {
2077
2105
  suspendDepth = savedDepth;
2078
2106
  suspendSavedSub = savedSuspendSub;
2079
2107
  trackingSuspended = true;
2080
2108
  }
2081
- const sub3 = subscriber;
2082
- for (let n = sub3.depsHead ?? null; n !== null; n = n.subNext) {
2109
+ for (let n = sub2.depsHead ?? null; n !== null; n = n.subNext) {
2083
2110
  const sig = n.sig;
2084
2111
  sig.__activeNode = n.prevActive;
2085
2112
  n.prevActive = null;
2086
2113
  }
2087
2114
  }
2088
- const sub2 = subscriber;
2089
2115
  return sub2._dispose ?? (sub2._dispose = () => cleanup(subscriber));
2090
2116
  }
2091
2117
  function reactiveBinding(commit, ownerNode) {
@@ -2355,7 +2381,7 @@ function forEachSubscriber(signal2, visit) {
2355
2381
  }
2356
2382
 
2357
2383
  // src/reactivity/track.ts
2358
- var _runtimeVersion = true ? "4.8.0" : "dev";
2384
+ var _runtimeVersion = true ? "4.10.0" : "dev";
2359
2385
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("sibujs.reactive.v1");
2360
2386
  function resolveReactiveApi() {
2361
2387
  const g2 = globalThis;
@@ -2387,6 +2413,9 @@ function resolveReactiveApi() {
2387
2413
  getSubscriberCount,
2388
2414
  getSubscriberDeps,
2389
2415
  forEachSubscriber,
2416
+ getOwnerScope,
2417
+ runWithOwnerScope,
2418
+ bindOwnerScope,
2390
2419
  version: _runtimeVersion
2391
2420
  };
2392
2421
  g2[REGISTRY_KEY] = local;
@@ -2410,6 +2439,9 @@ var notifySubscribers2 = API.notifySubscribers;
2410
2439
  var getSubscriberCount2 = API.getSubscriberCount;
2411
2440
  var getSubscriberDeps2 = API.getSubscriberDeps;
2412
2441
  var forEachSubscriber2 = API.forEachSubscriber;
2442
+ var getOwnerScope2 = API.getOwnerScope ?? getOwnerScope;
2443
+ var runWithOwnerScope2 = API.runWithOwnerScope ?? runWithOwnerScope;
2444
+ var bindOwnerScope2 = API.bindOwnerScope ?? bindOwnerScope;
2413
2445
 
2414
2446
  // src/reactivity/bindAttribute.ts
2415
2447
  function bindAttribute(el, attr, getter) {
@@ -3269,6 +3301,11 @@ function flushUserCleanups(ctx) {
3269
3301
  const list = ctx.userCleanups;
3270
3302
  if (list.length === 0) return;
3271
3303
  ctx.userCleanups = [];
3304
+ const scope = ctx.subscriber._scope ?? null;
3305
+ if (scope === getOwnerScope2()) runCleanups(list);
3306
+ else runWithOwnerScope2(scope, () => runCleanups(list));
3307
+ }
3308
+ function runCleanups(list) {
3272
3309
  for (let i2 = list.length - 1; i2 >= 0; i2--) {
3273
3310
  try {
3274
3311
  list[i2]();
@@ -3418,9 +3455,14 @@ function normalizePathname(path2) {
3418
3455
  const trimmed = path2.length > 1 ? path2.replace(/\/+$/, "") : path2;
3419
3456
  return trimmed || "/";
3420
3457
  }
3421
- function normalizeQuery(query) {
3422
- const params = new URLSearchParams(query);
3423
- return [...params.entries()].sort(([a2], [b2]) => a2 < b2 ? -1 : a2 > b2 ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
3458
+ function parseQuery(query) {
3459
+ return Object.fromEntries(new URLSearchParams(query));
3460
+ }
3461
+ function paramsIdentity(params) {
3462
+ return JSON.stringify(Object.entries(params).sort(([a2], [b2]) => a2 < b2 ? -1 : a2 > b2 ? 1 : 0));
3463
+ }
3464
+ function queryIdentity(query) {
3465
+ return paramsIdentity(typeof query === "string" ? parseQuery(query) : query);
3424
3466
  }
3425
3467
  function isPathnameAncestor(target, current) {
3426
3468
  if (target === current) return true;
@@ -3885,12 +3927,12 @@ var _ComponentLoader = class _ComponentLoader {
3885
3927
  * resolved, so the import happens once and later mounts call the module's
3886
3928
  * factory directly.
3887
3929
  */
3888
- async instantiate(plan, route2, routePath) {
3930
+ async instantiate(plan, route2, routePath, scope) {
3889
3931
  if (plan.kind === "factory") {
3890
- return this.instantiateFactory(plan.component, route2, routePath);
3932
+ return this.instantiateFactory(plan.component, route2, routePath, scope);
3891
3933
  }
3892
3934
  const factory = await this.resolveModuleFactory(plan, route2, routePath);
3893
- return this.instantiateFactory(factory, route2, routePath);
3935
+ return this.instantiateFactory(factory, route2, routePath, scope);
3894
3936
  }
3895
3937
  /**
3896
3938
  * Resolve a deferred plan's module and cache the factory it exports —
@@ -3935,14 +3977,14 @@ var _ComponentLoader = class _ComponentLoader {
3935
3977
  * `depth` bounds that unwrapping; a module whose default is itself a module
3936
3978
  * is a configuration error, not something to chase.
3937
3979
  */
3938
- async instantiateFactory(component, route2, routePath, depth = 0) {
3939
- const result = component();
3980
+ async instantiateFactory(component, route2, routePath, scope, depth = 0) {
3981
+ const result = scope ? runWithOwnerScope2(scope, component) : component();
3940
3982
  const value = this.isThenable(result) ? await result : result;
3941
3983
  if (this.isElement(value)) return value;
3942
3984
  if (depth === 0 && this.isFactoryLike(value)) {
3943
3985
  const factory = this.extractFactory(value, routePath);
3944
3986
  this.planCache.set(this.keyFor(route2), { kind: "factory", component: factory });
3945
- return this.instantiateFactory(factory, route2, routePath, depth + 1);
3987
+ return this.instantiateFactory(factory, route2, routePath, scope, depth + 1);
3946
3988
  }
3947
3989
  throw new Error(`Component for route "${routePath}" must return Element, got ${typeof value}`);
3948
3990
  }
@@ -4121,7 +4163,7 @@ var _SibuRouter = class _SibuRouter {
4121
4163
  }
4122
4164
  createRouteContext(fullPath) {
4123
4165
  const { path: path2, query: queryString, hash } = splitRouteUrl(fullPath);
4124
- const query = Object.fromEntries(new URLSearchParams(queryString));
4166
+ const query = parseQuery(queryString);
4125
4167
  const match = this.matcher.match(path2 || "/");
4126
4168
  const params = match?.params || {};
4127
4169
  const meta2 = match?.route.meta || {};
@@ -4285,7 +4327,7 @@ var _SibuRouter = class _SibuRouter {
4285
4327
  }
4286
4328
  isSameRoute(from, to) {
4287
4329
  if (from.matched.length === 0 && to.matched.length > 0) return false;
4288
- 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;
4330
+ return from.path === to.path && paramsIdentity(from.params) === paramsIdentity(to.params) && queryIdentity(from.query) === queryIdentity(to.query) && from.hash === to.hash;
4289
4331
  }
4290
4332
  updateHistory(to, options) {
4291
4333
  const fullPath = this.options.base + to.path + (Object.keys(to.query).length ? `?${new URLSearchParams(to.query).toString()}` : "") + (to.hash ? `#${to.hash}` : "");
@@ -4347,9 +4389,12 @@ var _SibuRouter = class _SibuRouter {
4347
4389
  async loadPlan(route2, routePath) {
4348
4390
  return this.loader.loadPlan(route2, routePath);
4349
4391
  }
4350
- /** Create exactly one real instance from an already-resolved plan. */
4351
- async instantiateComponent(plan, route2, routePath) {
4352
- return this.loader.instantiate(plan, route2, routePath);
4392
+ /**
4393
+ * Create exactly one real instance from an already-resolved plan. With a
4394
+ * `scope`, the factory runs in it (see `RouteScope`).
4395
+ */
4396
+ async instantiateComponent(plan, route2, routePath, scope) {
4397
+ return this.loader.instantiate(plan, route2, routePath, scope);
4353
4398
  }
4354
4399
  /** Resolve a route's module/factory without creating any component instance. */
4355
4400
  async preloadPlan(route2, routePath) {
@@ -4505,14 +4550,72 @@ function setRoutes(routes) {
4505
4550
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
4506
4551
  _routerRef.current.updateRoutes(normalizeRoutes(routes));
4507
4552
  }
4553
+ var RouteScope = class {
4554
+ constructor(route2) {
4555
+ this.released = false;
4556
+ const [get, set] = signal(route2);
4557
+ this.get = get;
4558
+ this.set = set;
4559
+ }
4560
+ /** The instance's route, or `null` once released. Reactive. */
4561
+ read() {
4562
+ return this.get();
4563
+ }
4564
+ /** Hand a kept instance the route of the navigation that kept it. */
4565
+ update(route2) {
4566
+ if (!this.released) this.set(route2);
4567
+ }
4568
+ /**
4569
+ * Called when the instance is disposed. The switch to "no scope" is
4570
+ * published a microtask later, once the whole dispose has finished: a
4571
+ * teardown the instance registered after this one (an `onMount` effect, say)
4572
+ * may still be alive at this point, and must not re-run against the
4573
+ * router-global route on its way out.
4574
+ */
4575
+ release() {
4576
+ if (this.released) return;
4577
+ this.released = true;
4578
+ queueMicrotask(() => this.set(null));
4579
+ }
4580
+ };
4581
+ function activeRouteScope() {
4582
+ const scope = getOwnerScope2();
4583
+ return scope instanceof RouteScope ? scope : null;
4584
+ }
4585
+ function scopedRoute(router2) {
4586
+ const scope = activeRouteScope();
4587
+ if (scope) {
4588
+ const committed = scope.read();
4589
+ if (committed !== null) return committed;
4590
+ }
4591
+ return router2.currentRoute;
4592
+ }
4593
+ var instanceScopes = /* @__PURE__ */ new WeakMap();
4594
+ async function instantiateScoped(router2, plan, routeDef, routePath, route2) {
4595
+ const scope = new RouteScope(route2);
4596
+ const node = await router2.instantiateComponent(plan, routeDef, routePath, scope);
4597
+ if (node) {
4598
+ instanceScopes.set(node, scope);
4599
+ registerDisposer(node, () => scope.release());
4600
+ }
4601
+ return node;
4602
+ }
4603
+ function updateInstanceRoute(node, route2) {
4604
+ if (node) instanceScopes.get(node)?.update(route2);
4605
+ }
4606
+ function disposeOwned(node) {
4607
+ const scope = instanceScopes.get(node);
4608
+ if (scope) runWithOwnerScope2(scope, () => dispose(node));
4609
+ else dispose(node);
4610
+ }
4508
4611
  function route() {
4509
4612
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
4510
- return _routerRef.current.currentRoute;
4613
+ return scopedRoute(_routerRef.current);
4511
4614
  }
4512
4615
  function router() {
4513
4616
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
4514
4617
  return {
4515
- currentRoute: _routerRef.current.currentRoute,
4618
+ currentRoute: scopedRoute(_routerRef.current),
4516
4619
  isReady: _routerRef.current.isReady,
4517
4620
  isNavigating: _routerRef.current.isNavigating,
4518
4621
  push: (to) => _routerRef.current?.push(to),
@@ -4598,13 +4701,13 @@ function Route() {
4598
4701
  const commitTarget = (seq) => routeTorn || seq !== navSeq ? null : anchor.parentNode;
4599
4702
  const release = (node) => {
4600
4703
  if (!node) return;
4601
- dispose(node);
4704
+ disposeOwned(node);
4602
4705
  node.parentNode?.removeChild(node);
4603
4706
  };
4604
4707
  const cleanupNodes = () => {
4605
4708
  [currentNode, loadingNode, errorNode].forEach((node) => {
4606
4709
  if (!node) return;
4607
- dispose(node);
4710
+ disposeOwned(node);
4608
4711
  if (node.parentNode) {
4609
4712
  node.parentNode.removeChild(node);
4610
4713
  }
@@ -4680,7 +4783,7 @@ function Route() {
4680
4783
  const update = async () => {
4681
4784
  if (routeTorn || !_routerRef.current) return;
4682
4785
  const seq = ++navSeq;
4683
- const route2 = _routerRef.current.currentRoute;
4786
+ const route2 = scopedRoute(_routerRef.current);
4684
4787
  try {
4685
4788
  const match = _routerRef.current["matcher"].match(route2.path);
4686
4789
  if (!match) {
@@ -4692,6 +4795,7 @@ function Route() {
4692
4795
  const routeDef = match.matched[0] || match.route;
4693
4796
  const key = instanceKey(routeDef, [routeDef], route2);
4694
4797
  if (routeDef === currentTopRoute && key === currentKey && currentNode) {
4798
+ updateInstanceRoute(currentNode, route2);
4695
4799
  return;
4696
4800
  }
4697
4801
  if ("redirect" in routeDef) {
@@ -4711,7 +4815,7 @@ function Route() {
4711
4815
  }
4712
4816
  const plan = await _routerRef.current.loadPlan(routeDef, route2.path);
4713
4817
  if (!commitTarget(seq)) return;
4714
- const node = await _routerRef.current.instantiateComponent(plan, routeDef, route2.path);
4818
+ const node = await instantiateScoped(_routerRef.current, plan, routeDef, route2.path, route2);
4715
4819
  const parent = commitTarget(seq);
4716
4820
  if (!parent) {
4717
4821
  release(node);
@@ -4746,9 +4850,11 @@ function Route() {
4746
4850
  };
4747
4851
  const routeTeardown = track2(wrappedUpdate);
4748
4852
  if (!routeInitialized) {
4749
- queueMicrotask(() => {
4750
- if (!routeInitialized && anchor.parentNode) wrappedUpdate();
4751
- });
4853
+ queueMicrotask(
4854
+ bindOwnerScope2(() => {
4855
+ if (!routeInitialized && anchor.parentNode) wrappedUpdate();
4856
+ })
4857
+ );
4752
4858
  }
4753
4859
  const routeCleanup = () => {
4754
4860
  if (routeTorn) return;
@@ -4781,7 +4887,7 @@ function KeepAliveRoute(options) {
4781
4887
  if (kaTorn || !_routerRef.current) return;
4782
4888
  const seq = ++updateSeq;
4783
4889
  const router2 = _routerRef.current;
4784
- const route2 = router2.currentRoute;
4890
+ const route2 = scopedRoute(router2);
4785
4891
  const match = router2["matcher"].match(route2.path);
4786
4892
  if (!match) return;
4787
4893
  const { route: routeDef } = match;
@@ -4795,10 +4901,12 @@ function KeepAliveRoute(options) {
4795
4901
  return;
4796
4902
  }
4797
4903
  if (!("component" in routeDef)) return;
4798
- const queryStr = Object.keys(route2.query).length > 0 ? `?${new URLSearchParams(route2.query).toString()}` : "";
4799
- const cacheKey = `${route2.path}${queryStr}${route2.hash ? `#${route2.hash}` : ""}`;
4904
+ const cacheKey = JSON.stringify([route2.path, queryIdentity(route2.query), route2.hash]);
4800
4905
  const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
4801
- if (cacheKey === currentKey && currentNode) return;
4906
+ if (cacheKey === currentKey && currentNode) {
4907
+ updateInstanceRoute(currentNode, route2);
4908
+ return;
4909
+ }
4802
4910
  if (!anchor.parentNode) return;
4803
4911
  let node = null;
4804
4912
  let fromCache = false;
@@ -4809,7 +4917,7 @@ function KeepAliveRoute(options) {
4809
4917
  try {
4810
4918
  const plan = await router2.loadPlan(routeDef, route2.path);
4811
4919
  if (kaTorn || seq !== updateSeq) return;
4812
- node = await router2.instantiateComponent(plan, routeDef, route2.path);
4920
+ node = await instantiateScoped(router2, plan, routeDef, route2.path, route2);
4813
4921
  } catch (error) {
4814
4922
  if (kaTorn || seq !== updateSeq) return;
4815
4923
  console.error("[KeepAliveRoute] Component error:", error);
@@ -4817,20 +4925,21 @@ function KeepAliveRoute(options) {
4817
4925
  }
4818
4926
  if (!node) return;
4819
4927
  if (kaTorn || seq !== updateSeq) {
4820
- dispose(node);
4928
+ disposeOwned(node);
4821
4929
  return;
4822
4930
  }
4823
4931
  }
4824
4932
  const parent = anchor.parentNode;
4825
4933
  if (!parent) {
4826
- if (!fromCache) dispose(node);
4934
+ if (!fromCache) disposeOwned(node);
4827
4935
  return;
4828
4936
  }
4829
4937
  if (currentNode && currentNode !== node) {
4830
4938
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
4831
- if (!currentCached) dispose(currentNode);
4939
+ if (!currentCached) disposeOwned(currentNode);
4832
4940
  }
4833
4941
  if (fromCache) {
4942
+ updateInstanceRoute(node, route2);
4834
4943
  const idx = lruOrder.indexOf(cacheKey);
4835
4944
  if (idx !== -1) lruOrder.splice(idx, 1);
4836
4945
  lruOrder.push(cacheKey);
@@ -4845,7 +4954,7 @@ function KeepAliveRoute(options) {
4845
4954
  }
4846
4955
  const evictNode = cache.get(evictKey);
4847
4956
  if (evictNode) {
4848
- dispose(evictNode);
4957
+ disposeOwned(evictNode);
4849
4958
  if (evictNode.parentNode) evictNode.parentNode.removeChild(evictNode);
4850
4959
  cache.delete(evictKey);
4851
4960
  }
@@ -4863,9 +4972,11 @@ function KeepAliveRoute(options) {
4863
4972
  };
4864
4973
  const kaTeardown = track2(wrappedUpdate);
4865
4974
  if (!initialized) {
4866
- queueMicrotask(() => {
4867
- if (!initialized && anchor.parentNode) wrappedUpdate();
4868
- });
4975
+ queueMicrotask(
4976
+ bindOwnerScope2(() => {
4977
+ if (!initialized && anchor.parentNode) wrappedUpdate();
4978
+ })
4979
+ );
4869
4980
  }
4870
4981
  const kaCleanup = () => {
4871
4982
  if (kaTorn) return;
@@ -4873,13 +4984,13 @@ function KeepAliveRoute(options) {
4873
4984
  updateSeq++;
4874
4985
  kaTeardown();
4875
4986
  for (const node of cache.values()) {
4876
- dispose(node);
4987
+ disposeOwned(node);
4877
4988
  if (node.parentNode) node.parentNode.removeChild(node);
4878
4989
  }
4879
4990
  cache.clear();
4880
4991
  lruOrder.length = 0;
4881
4992
  if (currentNode?.parentNode) currentNode.parentNode.removeChild(currentNode);
4882
- if (currentNode && !currentCached) dispose(currentNode);
4993
+ if (currentNode && !currentCached) disposeOwned(currentNode);
4883
4994
  currentNode = null;
4884
4995
  currentKey = "";
4885
4996
  currentCached = false;
@@ -4898,7 +5009,7 @@ function RouterLink(props, children) {
4898
5009
  const href = kind === "unsafe" ? "#" : rawHref;
4899
5010
  const { path: hrefPathRaw, query: hrefQueryRaw, hash: hrefHash } = splitRouteUrl(href);
4900
5011
  const targetPath = normalizePathname(hrefPathRaw);
4901
- const targetQuery = normalizeQuery(hrefQueryRaw);
5012
+ const targetQuery = queryIdentity(hrefQueryRaw);
4902
5013
  const link2 = document.createElement("a");
4903
5014
  link2.href = href;
4904
5015
  if (target) {
@@ -4916,7 +5027,7 @@ function RouterLink(props, children) {
4916
5027
  const route2 = routeGetter();
4917
5028
  const currentPath = normalizePathname(route2.path);
4918
5029
  const isActive = kind === "internal" && isPathnameAncestor(targetPath, currentPath);
4919
- const isExactActive = kind === "internal" && currentPath === targetPath && normalizeQuery(route2.query) === targetQuery && route2.hash === hrefHash;
5030
+ const isExactActive = kind === "internal" && currentPath === targetPath && queryIdentity(route2.query) === targetQuery && route2.hash === hrefHash;
4920
5031
  const classes = [];
4921
5032
  if (isActive) {
4922
5033
  if (activeClass) classes.push(activeClass);
@@ -5035,7 +5146,7 @@ function Suspense(props) {
5035
5146
  generation++;
5036
5147
  cleanupNodes();
5037
5148
  });
5038
- queueMicrotask(render);
5149
+ queueMicrotask(bindOwnerScope2(render));
5039
5150
  return anchor;
5040
5151
  }
5041
5152
  var LAZY_MARKER = /* @__PURE__ */ Symbol.for("sibujs:lazy");
@@ -5103,7 +5214,7 @@ function Outlet() {
5103
5214
  let outletTorn = false;
5104
5215
  const clearCurrent = () => {
5105
5216
  if (currentNode) {
5106
- dispose(currentNode);
5217
+ disposeOwned(currentNode);
5107
5218
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
5108
5219
  currentNode = null;
5109
5220
  }
@@ -5113,13 +5224,13 @@ function Outlet() {
5113
5224
  const commitTarget = (seq) => outletTorn || seq !== navSeq ? null : anchor.parentNode;
5114
5225
  const release = (node) => {
5115
5226
  if (!node) return;
5116
- dispose(node);
5227
+ disposeOwned(node);
5117
5228
  node.parentNode?.removeChild(node);
5118
5229
  };
5119
5230
  const update = async () => {
5120
5231
  if (outletTorn || !_routerRef.current) return;
5121
5232
  const seq = ++navSeq;
5122
- const route2 = _routerRef.current.currentRoute;
5233
+ const route2 = scopedRoute(_routerRef.current);
5123
5234
  if (route2.matched.length < 2) {
5124
5235
  clearCurrent();
5125
5236
  return;
@@ -5130,12 +5241,15 @@ function Outlet() {
5130
5241
  return;
5131
5242
  }
5132
5243
  const key = instanceKey(childRoute, route2.matched.slice(1), route2);
5133
- if (childRoute === currentChild && key === currentKey && currentNode) return;
5244
+ if (childRoute === currentChild && key === currentKey && currentNode) {
5245
+ updateInstanceRoute(currentNode, route2);
5246
+ return;
5247
+ }
5134
5248
  try {
5135
5249
  const cacheKey = `${route2.path}\0${childRoute.path}`;
5136
5250
  const plan = await _routerRef.current.loadPlan(childRoute, cacheKey);
5137
5251
  if (!commitTarget(seq)) return;
5138
- const node = await _routerRef.current.instantiateComponent(plan, childRoute, cacheKey);
5252
+ const node = await instantiateScoped(_routerRef.current, plan, childRoute, cacheKey, route2);
5139
5253
  const parent = commitTarget(seq);
5140
5254
  if (!parent) {
5141
5255
  release(node);
@@ -5154,9 +5268,11 @@ function Outlet() {
5154
5268
  };
5155
5269
  const outletTeardown = track2(update);
5156
5270
  if (!anchor.parentNode) {
5157
- queueMicrotask(() => {
5158
- if (anchor.parentNode) update();
5159
- });
5271
+ queueMicrotask(
5272
+ bindOwnerScope2(() => {
5273
+ if (anchor.parentNode) update();
5274
+ })
5275
+ );
5160
5276
  }
5161
5277
  const outletCleanup = () => {
5162
5278
  if (outletTorn) return;
@@ -5164,7 +5280,7 @@ function Outlet() {
5164
5280
  navSeq++;
5165
5281
  outletTeardown();
5166
5282
  if (currentNode) {
5167
- dispose(currentNode);
5283
+ disposeOwned(currentNode);
5168
5284
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
5169
5285
  currentNode = null;
5170
5286
  }
@@ -5187,11 +5303,11 @@ function routerState() {
5187
5303
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
5188
5304
  const router2 = _routerRef.current;
5189
5305
  return {
5190
- currentPath: () => router2.currentRoute.path,
5191
- params: () => router2.currentRoute.params,
5192
- query: () => router2.currentRoute.query,
5193
- hash: () => router2.currentRoute.hash,
5194
- meta: () => router2.currentRoute.meta,
5306
+ currentPath: () => scopedRoute(router2).path,
5307
+ params: () => scopedRoute(router2).params,
5308
+ query: () => scopedRoute(router2).query,
5309
+ hash: () => scopedRoute(router2).hash,
5310
+ meta: () => scopedRoute(router2).meta,
5195
5311
  isNavigating: () => router2.isNavigating,
5196
5312
  isReady: () => router2.isReady
5197
5313
  };
@@ -5905,7 +6021,7 @@ var CDN_ENTRY_POINTS = {
5905
6021
  };
5906
6022
  var packageInfo = {
5907
6023
  name: "sibujs",
5908
- version: true ? "4.8.0" : "dev",
6024
+ version: true ? "4.10.0" : "dev",
5909
6025
  entryPoints: MODULE_ENTRY_POINTS,
5910
6026
  /**
5911
6027
  * Generate the package.json `exports` map. Module entries resolve to the
@@ -6050,7 +6166,7 @@ var env = {
6050
6166
  };
6051
6167
 
6052
6168
  // src/plugins/versioning.ts
6053
- var VERSION = true ? "4.8.0" : "dev";
6169
+ var VERSION = true ? "4.10.0" : "dev";
6054
6170
  var NUM = "(?:0|[1-9]\\d*)";
6055
6171
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
6056
6172
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -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
  */