sibujs 4.9.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 (62) hide show
  1. package/dist/browser.cjs +41 -4
  2. package/dist/browser.js +4 -4
  3. package/dist/build.cjs +102 -38
  4. package/dist/build.js +10 -10
  5. package/dist/cdn.dev.global.js +14 -14
  6. package/dist/cdn.full.dev.global.js +13 -13
  7. package/dist/cdn.full.global.js +12 -12
  8. package/dist/cdn.global.js +15 -15
  9. package/dist/{chunk-FNOK662L.js → chunk-2NWHJ2UJ.js} +73 -43
  10. package/dist/{chunk-KG25XMGP.js → chunk-4DCVJ4YA.js} +15 -8
  11. package/dist/{chunk-CF2KCWRE.js → chunk-54F4TPHK.js} +2 -2
  12. package/dist/{chunk-X7NRHFZR.js → chunk-5PC7OTQW.js} +1 -1
  13. package/dist/{chunk-SBTZEYNI.js → chunk-7WIHYHWD.js} +8 -1
  14. package/dist/{chunk-K6K5ACO6.js → chunk-BMCR7TXG.js} +3 -3
  15. package/dist/{chunk-G2WHYEL7.js → chunk-D6HYFOWR.js} +39 -4
  16. package/dist/{chunk-5MLWTIQE.js → chunk-EVI4ZQO3.js} +2 -2
  17. package/dist/{chunk-CDBKFGXN.js → chunk-EVOK663Z.js} +1 -1
  18. package/dist/{chunk-SYHGGDNW.js → chunk-FYUEL5IW.js} +1 -1
  19. package/dist/{chunk-562UVHMM.js → chunk-GMMDHJJT.js} +11 -6
  20. package/dist/{chunk-FHCZMHHE.js → chunk-GOL3FU5Q.js} +1 -1
  21. package/dist/{chunk-YW6HQKGO.js → chunk-GTS35FLA.js} +2 -2
  22. package/dist/{chunk-2K3Z3XO4.js → chunk-IFRIBZ4O.js} +4 -4
  23. package/dist/{chunk-DRMLIF6M.js → chunk-IL5MMSCL.js} +19 -12
  24. package/dist/{chunk-XJVTQUIV.js → chunk-ITEL5KPL.js} +5 -5
  25. package/dist/{chunk-TV464N2M.js → chunk-JCKUWWHU.js} +3 -3
  26. package/dist/{chunk-QBKCK6MJ.js → chunk-N6U4GOIU.js} +1 -1
  27. package/dist/{chunk-NQMKWBKU.js → chunk-TOPRMXFY.js} +1 -1
  28. package/dist/{chunk-7OV5Q2UU.js → chunk-VWPMWV7O.js} +4 -4
  29. package/dist/{chunk-MPWDSTYK.js → chunk-XUQUQJYE.js} +1 -1
  30. package/dist/data.cjs +48 -6
  31. package/dist/data.d.cts +9 -1
  32. package/dist/data.d.ts +9 -1
  33. package/dist/data.js +5 -5
  34. package/dist/devtools.cjs +41 -4
  35. package/dist/devtools.js +4 -4
  36. package/dist/ecosystem.cjs +41 -4
  37. package/dist/ecosystem.js +7 -7
  38. package/dist/extras.cjs +75 -25
  39. package/dist/extras.js +20 -20
  40. package/dist/index.cjs +102 -38
  41. package/dist/index.d.cts +16 -0
  42. package/dist/index.d.ts +16 -0
  43. package/dist/index.js +10 -10
  44. package/dist/motion.cjs +36 -4
  45. package/dist/motion.js +3 -3
  46. package/dist/patterns.cjs +41 -4
  47. package/dist/patterns.js +5 -5
  48. package/dist/performance.cjs +55 -14
  49. package/dist/performance.js +4 -4
  50. package/dist/plugins.cjs +163 -51
  51. package/dist/plugins.d.cts +56 -2
  52. package/dist/plugins.d.ts +56 -2
  53. package/dist/plugins.js +130 -52
  54. package/dist/ssr.cjs +50 -9
  55. package/dist/ssr.js +7 -7
  56. package/dist/testing.cjs +41 -4
  57. package/dist/testing.js +2 -2
  58. package/dist/ui.cjs +41 -4
  59. package/dist/ui.js +7 -7
  60. package/dist/widgets.cjs +36 -4
  61. package/dist/widgets.js +6 -6
  62. 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.9.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]();
@@ -3890,12 +3927,12 @@ var _ComponentLoader = class _ComponentLoader {
3890
3927
  * resolved, so the import happens once and later mounts call the module's
3891
3928
  * factory directly.
3892
3929
  */
3893
- async instantiate(plan, route2, routePath) {
3930
+ async instantiate(plan, route2, routePath, scope) {
3894
3931
  if (plan.kind === "factory") {
3895
- return this.instantiateFactory(plan.component, route2, routePath);
3932
+ return this.instantiateFactory(plan.component, route2, routePath, scope);
3896
3933
  }
3897
3934
  const factory = await this.resolveModuleFactory(plan, route2, routePath);
3898
- return this.instantiateFactory(factory, route2, routePath);
3935
+ return this.instantiateFactory(factory, route2, routePath, scope);
3899
3936
  }
3900
3937
  /**
3901
3938
  * Resolve a deferred plan's module and cache the factory it exports —
@@ -3940,14 +3977,14 @@ var _ComponentLoader = class _ComponentLoader {
3940
3977
  * `depth` bounds that unwrapping; a module whose default is itself a module
3941
3978
  * is a configuration error, not something to chase.
3942
3979
  */
3943
- async instantiateFactory(component, route2, routePath, depth = 0) {
3944
- const result = component();
3980
+ async instantiateFactory(component, route2, routePath, scope, depth = 0) {
3981
+ const result = scope ? runWithOwnerScope2(scope, component) : component();
3945
3982
  const value = this.isThenable(result) ? await result : result;
3946
3983
  if (this.isElement(value)) return value;
3947
3984
  if (depth === 0 && this.isFactoryLike(value)) {
3948
3985
  const factory = this.extractFactory(value, routePath);
3949
3986
  this.planCache.set(this.keyFor(route2), { kind: "factory", component: factory });
3950
- return this.instantiateFactory(factory, route2, routePath, depth + 1);
3987
+ return this.instantiateFactory(factory, route2, routePath, scope, depth + 1);
3951
3988
  }
3952
3989
  throw new Error(`Component for route "${routePath}" must return Element, got ${typeof value}`);
3953
3990
  }
@@ -4352,9 +4389,12 @@ var _SibuRouter = class _SibuRouter {
4352
4389
  async loadPlan(route2, routePath) {
4353
4390
  return this.loader.loadPlan(route2, routePath);
4354
4391
  }
4355
- /** Create exactly one real instance from an already-resolved plan. */
4356
- async instantiateComponent(plan, route2, routePath) {
4357
- 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);
4358
4398
  }
4359
4399
  /** Resolve a route's module/factory without creating any component instance. */
4360
4400
  async preloadPlan(route2, routePath) {
@@ -4510,14 +4550,72 @@ function setRoutes(routes) {
4510
4550
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
4511
4551
  _routerRef.current.updateRoutes(normalizeRoutes(routes));
4512
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
+ }
4513
4611
  function route() {
4514
4612
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
4515
- return _routerRef.current.currentRoute;
4613
+ return scopedRoute(_routerRef.current);
4516
4614
  }
4517
4615
  function router() {
4518
4616
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
4519
4617
  return {
4520
- currentRoute: _routerRef.current.currentRoute,
4618
+ currentRoute: scopedRoute(_routerRef.current),
4521
4619
  isReady: _routerRef.current.isReady,
4522
4620
  isNavigating: _routerRef.current.isNavigating,
4523
4621
  push: (to) => _routerRef.current?.push(to),
@@ -4603,13 +4701,13 @@ function Route() {
4603
4701
  const commitTarget = (seq) => routeTorn || seq !== navSeq ? null : anchor.parentNode;
4604
4702
  const release = (node) => {
4605
4703
  if (!node) return;
4606
- dispose(node);
4704
+ disposeOwned(node);
4607
4705
  node.parentNode?.removeChild(node);
4608
4706
  };
4609
4707
  const cleanupNodes = () => {
4610
4708
  [currentNode, loadingNode, errorNode].forEach((node) => {
4611
4709
  if (!node) return;
4612
- dispose(node);
4710
+ disposeOwned(node);
4613
4711
  if (node.parentNode) {
4614
4712
  node.parentNode.removeChild(node);
4615
4713
  }
@@ -4685,7 +4783,7 @@ function Route() {
4685
4783
  const update = async () => {
4686
4784
  if (routeTorn || !_routerRef.current) return;
4687
4785
  const seq = ++navSeq;
4688
- const route2 = _routerRef.current.currentRoute;
4786
+ const route2 = scopedRoute(_routerRef.current);
4689
4787
  try {
4690
4788
  const match = _routerRef.current["matcher"].match(route2.path);
4691
4789
  if (!match) {
@@ -4697,6 +4795,7 @@ function Route() {
4697
4795
  const routeDef = match.matched[0] || match.route;
4698
4796
  const key = instanceKey(routeDef, [routeDef], route2);
4699
4797
  if (routeDef === currentTopRoute && key === currentKey && currentNode) {
4798
+ updateInstanceRoute(currentNode, route2);
4700
4799
  return;
4701
4800
  }
4702
4801
  if ("redirect" in routeDef) {
@@ -4716,7 +4815,7 @@ function Route() {
4716
4815
  }
4717
4816
  const plan = await _routerRef.current.loadPlan(routeDef, route2.path);
4718
4817
  if (!commitTarget(seq)) return;
4719
- const node = await _routerRef.current.instantiateComponent(plan, routeDef, route2.path);
4818
+ const node = await instantiateScoped(_routerRef.current, plan, routeDef, route2.path, route2);
4720
4819
  const parent = commitTarget(seq);
4721
4820
  if (!parent) {
4722
4821
  release(node);
@@ -4751,9 +4850,11 @@ function Route() {
4751
4850
  };
4752
4851
  const routeTeardown = track2(wrappedUpdate);
4753
4852
  if (!routeInitialized) {
4754
- queueMicrotask(() => {
4755
- if (!routeInitialized && anchor.parentNode) wrappedUpdate();
4756
- });
4853
+ queueMicrotask(
4854
+ bindOwnerScope2(() => {
4855
+ if (!routeInitialized && anchor.parentNode) wrappedUpdate();
4856
+ })
4857
+ );
4757
4858
  }
4758
4859
  const routeCleanup = () => {
4759
4860
  if (routeTorn) return;
@@ -4786,7 +4887,7 @@ function KeepAliveRoute(options) {
4786
4887
  if (kaTorn || !_routerRef.current) return;
4787
4888
  const seq = ++updateSeq;
4788
4889
  const router2 = _routerRef.current;
4789
- const route2 = router2.currentRoute;
4890
+ const route2 = scopedRoute(router2);
4790
4891
  const match = router2["matcher"].match(route2.path);
4791
4892
  if (!match) return;
4792
4893
  const { route: routeDef } = match;
@@ -4802,7 +4903,10 @@ function KeepAliveRoute(options) {
4802
4903
  if (!("component" in routeDef)) return;
4803
4904
  const cacheKey = JSON.stringify([route2.path, queryIdentity(route2.query), route2.hash]);
4804
4905
  const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
4805
- if (cacheKey === currentKey && currentNode) return;
4906
+ if (cacheKey === currentKey && currentNode) {
4907
+ updateInstanceRoute(currentNode, route2);
4908
+ return;
4909
+ }
4806
4910
  if (!anchor.parentNode) return;
4807
4911
  let node = null;
4808
4912
  let fromCache = false;
@@ -4813,7 +4917,7 @@ function KeepAliveRoute(options) {
4813
4917
  try {
4814
4918
  const plan = await router2.loadPlan(routeDef, route2.path);
4815
4919
  if (kaTorn || seq !== updateSeq) return;
4816
- node = await router2.instantiateComponent(plan, routeDef, route2.path);
4920
+ node = await instantiateScoped(router2, plan, routeDef, route2.path, route2);
4817
4921
  } catch (error) {
4818
4922
  if (kaTorn || seq !== updateSeq) return;
4819
4923
  console.error("[KeepAliveRoute] Component error:", error);
@@ -4821,20 +4925,21 @@ function KeepAliveRoute(options) {
4821
4925
  }
4822
4926
  if (!node) return;
4823
4927
  if (kaTorn || seq !== updateSeq) {
4824
- dispose(node);
4928
+ disposeOwned(node);
4825
4929
  return;
4826
4930
  }
4827
4931
  }
4828
4932
  const parent = anchor.parentNode;
4829
4933
  if (!parent) {
4830
- if (!fromCache) dispose(node);
4934
+ if (!fromCache) disposeOwned(node);
4831
4935
  return;
4832
4936
  }
4833
4937
  if (currentNode && currentNode !== node) {
4834
4938
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
4835
- if (!currentCached) dispose(currentNode);
4939
+ if (!currentCached) disposeOwned(currentNode);
4836
4940
  }
4837
4941
  if (fromCache) {
4942
+ updateInstanceRoute(node, route2);
4838
4943
  const idx = lruOrder.indexOf(cacheKey);
4839
4944
  if (idx !== -1) lruOrder.splice(idx, 1);
4840
4945
  lruOrder.push(cacheKey);
@@ -4849,7 +4954,7 @@ function KeepAliveRoute(options) {
4849
4954
  }
4850
4955
  const evictNode = cache.get(evictKey);
4851
4956
  if (evictNode) {
4852
- dispose(evictNode);
4957
+ disposeOwned(evictNode);
4853
4958
  if (evictNode.parentNode) evictNode.parentNode.removeChild(evictNode);
4854
4959
  cache.delete(evictKey);
4855
4960
  }
@@ -4867,9 +4972,11 @@ function KeepAliveRoute(options) {
4867
4972
  };
4868
4973
  const kaTeardown = track2(wrappedUpdate);
4869
4974
  if (!initialized) {
4870
- queueMicrotask(() => {
4871
- if (!initialized && anchor.parentNode) wrappedUpdate();
4872
- });
4975
+ queueMicrotask(
4976
+ bindOwnerScope2(() => {
4977
+ if (!initialized && anchor.parentNode) wrappedUpdate();
4978
+ })
4979
+ );
4873
4980
  }
4874
4981
  const kaCleanup = () => {
4875
4982
  if (kaTorn) return;
@@ -4877,13 +4984,13 @@ function KeepAliveRoute(options) {
4877
4984
  updateSeq++;
4878
4985
  kaTeardown();
4879
4986
  for (const node of cache.values()) {
4880
- dispose(node);
4987
+ disposeOwned(node);
4881
4988
  if (node.parentNode) node.parentNode.removeChild(node);
4882
4989
  }
4883
4990
  cache.clear();
4884
4991
  lruOrder.length = 0;
4885
4992
  if (currentNode?.parentNode) currentNode.parentNode.removeChild(currentNode);
4886
- if (currentNode && !currentCached) dispose(currentNode);
4993
+ if (currentNode && !currentCached) disposeOwned(currentNode);
4887
4994
  currentNode = null;
4888
4995
  currentKey = "";
4889
4996
  currentCached = false;
@@ -5039,7 +5146,7 @@ function Suspense(props) {
5039
5146
  generation++;
5040
5147
  cleanupNodes();
5041
5148
  });
5042
- queueMicrotask(render);
5149
+ queueMicrotask(bindOwnerScope2(render));
5043
5150
  return anchor;
5044
5151
  }
5045
5152
  var LAZY_MARKER = /* @__PURE__ */ Symbol.for("sibujs:lazy");
@@ -5107,7 +5214,7 @@ function Outlet() {
5107
5214
  let outletTorn = false;
5108
5215
  const clearCurrent = () => {
5109
5216
  if (currentNode) {
5110
- dispose(currentNode);
5217
+ disposeOwned(currentNode);
5111
5218
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
5112
5219
  currentNode = null;
5113
5220
  }
@@ -5117,13 +5224,13 @@ function Outlet() {
5117
5224
  const commitTarget = (seq) => outletTorn || seq !== navSeq ? null : anchor.parentNode;
5118
5225
  const release = (node) => {
5119
5226
  if (!node) return;
5120
- dispose(node);
5227
+ disposeOwned(node);
5121
5228
  node.parentNode?.removeChild(node);
5122
5229
  };
5123
5230
  const update = async () => {
5124
5231
  if (outletTorn || !_routerRef.current) return;
5125
5232
  const seq = ++navSeq;
5126
- const route2 = _routerRef.current.currentRoute;
5233
+ const route2 = scopedRoute(_routerRef.current);
5127
5234
  if (route2.matched.length < 2) {
5128
5235
  clearCurrent();
5129
5236
  return;
@@ -5134,12 +5241,15 @@ function Outlet() {
5134
5241
  return;
5135
5242
  }
5136
5243
  const key = instanceKey(childRoute, route2.matched.slice(1), route2);
5137
- if (childRoute === currentChild && key === currentKey && currentNode) return;
5244
+ if (childRoute === currentChild && key === currentKey && currentNode) {
5245
+ updateInstanceRoute(currentNode, route2);
5246
+ return;
5247
+ }
5138
5248
  try {
5139
5249
  const cacheKey = `${route2.path}\0${childRoute.path}`;
5140
5250
  const plan = await _routerRef.current.loadPlan(childRoute, cacheKey);
5141
5251
  if (!commitTarget(seq)) return;
5142
- const node = await _routerRef.current.instantiateComponent(plan, childRoute, cacheKey);
5252
+ const node = await instantiateScoped(_routerRef.current, plan, childRoute, cacheKey, route2);
5143
5253
  const parent = commitTarget(seq);
5144
5254
  if (!parent) {
5145
5255
  release(node);
@@ -5158,9 +5268,11 @@ function Outlet() {
5158
5268
  };
5159
5269
  const outletTeardown = track2(update);
5160
5270
  if (!anchor.parentNode) {
5161
- queueMicrotask(() => {
5162
- if (anchor.parentNode) update();
5163
- });
5271
+ queueMicrotask(
5272
+ bindOwnerScope2(() => {
5273
+ if (anchor.parentNode) update();
5274
+ })
5275
+ );
5164
5276
  }
5165
5277
  const outletCleanup = () => {
5166
5278
  if (outletTorn) return;
@@ -5168,7 +5280,7 @@ function Outlet() {
5168
5280
  navSeq++;
5169
5281
  outletTeardown();
5170
5282
  if (currentNode) {
5171
- dispose(currentNode);
5283
+ disposeOwned(currentNode);
5172
5284
  if (currentNode.parentNode) currentNode.parentNode.removeChild(currentNode);
5173
5285
  currentNode = null;
5174
5286
  }
@@ -5191,11 +5303,11 @@ function routerState() {
5191
5303
  if (!_routerRef.current) throw new Error("Router not initialized. Call createRouter() first.");
5192
5304
  const router2 = _routerRef.current;
5193
5305
  return {
5194
- currentPath: () => router2.currentRoute.path,
5195
- params: () => router2.currentRoute.params,
5196
- query: () => router2.currentRoute.query,
5197
- hash: () => router2.currentRoute.hash,
5198
- 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,
5199
5311
  isNavigating: () => router2.isNavigating,
5200
5312
  isReady: () => router2.isReady
5201
5313
  };
@@ -5909,7 +6021,7 @@ var CDN_ENTRY_POINTS = {
5909
6021
  };
5910
6022
  var packageInfo = {
5911
6023
  name: "sibujs",
5912
- version: true ? "4.9.0" : "dev",
6024
+ version: true ? "4.10.0" : "dev",
5913
6025
  entryPoints: MODULE_ENTRY_POINTS,
5914
6026
  /**
5915
6027
  * Generate the package.json `exports` map. Module entries resolve to the
@@ -6054,7 +6166,7 @@ var env = {
6054
6166
  };
6055
6167
 
6056
6168
  // src/plugins/versioning.ts
6057
- var VERSION = true ? "4.9.0" : "dev";
6169
+ var VERSION = true ? "4.10.0" : "dev";
6058
6170
  var NUM = "(?:0|[1-9]\\d*)";
6059
6171
  var PRE_ID = `(?:${NUM}|\\d*[a-zA-Z-][0-9a-zA-Z-]*)`;
6060
6172
  var BUILD_ID = "[0-9a-zA-Z-]+";
@@ -427,8 +427,11 @@ declare class SibuRouter {
427
427
  * anything. Pair with {@link instantiateComponent} at the commit boundary.
428
428
  */
429
429
  loadPlan(route: RouteDef, routePath: string): Promise<RoutePlan>;
430
- /** Create exactly one real instance from an already-resolved plan. */
431
- 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>;
432
435
  /** Resolve a route's module/factory without creating any component instance. */
433
436
  preloadPlan(route: RouteDef, routePath: string): Promise<void>;
434
437
  beforeEach(guard: NavigationGuard): () => void;
@@ -475,6 +478,50 @@ declare function createRouter(routesOrOptions: RouteDef[] | RouterOptions, optio
475
478
  * Set routes on the global router (replaces existing routes).
476
479
  */
477
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
+ }
478
525
  /**
479
526
  * Read the current route as a plain snapshot.
480
527
  *
@@ -483,6 +530,13 @@ declare function setRoutes(routes: RouteDef[]): void;
483
530
  * `RouteBase.key`). Inside a getter — `() => route().params.id` — the read is
484
531
  * reactive, which is what a long-lived layout or a constant-`key` route needs.
485
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
+ *
486
540
  * @returns The active {@link RouteContext} (path, params, query, hash).
487
541
  * @throws If no router has been created yet.
488
542
  */
package/dist/plugins.d.ts CHANGED
@@ -427,8 +427,11 @@ declare class SibuRouter {
427
427
  * anything. Pair with {@link instantiateComponent} at the commit boundary.
428
428
  */
429
429
  loadPlan(route: RouteDef, routePath: string): Promise<RoutePlan>;
430
- /** Create exactly one real instance from an already-resolved plan. */
431
- 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>;
432
435
  /** Resolve a route's module/factory without creating any component instance. */
433
436
  preloadPlan(route: RouteDef, routePath: string): Promise<void>;
434
437
  beforeEach(guard: NavigationGuard): () => void;
@@ -475,6 +478,50 @@ declare function createRouter(routesOrOptions: RouteDef[] | RouterOptions, optio
475
478
  * Set routes on the global router (replaces existing routes).
476
479
  */
477
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
+ }
478
525
  /**
479
526
  * Read the current route as a plain snapshot.
480
527
  *
@@ -483,6 +530,13 @@ declare function setRoutes(routes: RouteDef[]): void;
483
530
  * `RouteBase.key`). Inside a getter — `() => route().params.id` — the read is
484
531
  * reactive, which is what a long-lived layout or a constant-`key` route needs.
485
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
+ *
486
540
  * @returns The active {@link RouteContext} (path, params, query, hash).
487
541
  * @throws If no router has been created yet.
488
542
  */