@fluixi/core 1.0.0-alpha.94 → 1.0.0-alpha.95

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 (51) hide show
  1. package/LICENSE +1 -1
  2. package/dist/cdn/core.global.js +2 -2
  3. package/dist/src/cdn/{chunk-7XM7Q5VR.mjs → chunk-IFONZJWJ.mjs} +93 -2
  4. package/dist/src/cdn/core.cjs +99 -3
  5. package/dist/src/cdn/core.mjs +3 -3
  6. package/dist/src/cdn/{router-VOY43YNM.mjs → router-TU5CPI54.mjs} +1 -1
  7. package/dist/src/index.cjs +95 -4
  8. package/dist/src/index.mjs +95 -4
  9. package/dist/src/lib/client/{chunk-TSXM2PNY.mjs → chunk-AHNP3IWU.mjs} +1 -1
  10. package/dist/src/lib/client/index.cjs +100 -4
  11. package/dist/src/lib/client/index.mjs +4 -4
  12. package/dist/src/lib/client/{router-BDVPLLYX.mjs → router-5C6FQJQ4.mjs} +95 -4
  13. package/dist/src/lib/core.cjs +94 -3
  14. package/dist/src/lib/core.mjs +94 -3
  15. package/dist/src/lib/i18n/index.cjs +1 -1
  16. package/dist/src/lib/i18n/index.mjs +1 -1
  17. package/dist/src/lib/index.cjs +1 -1
  18. package/dist/src/lib/index.mjs +1 -1
  19. package/dist/src/lib/jsx-runtime/index.cjs +1 -1
  20. package/dist/src/lib/jsx-runtime/index.mjs +1 -1
  21. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -1
  22. package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -1
  23. package/dist/src/lib/plugins/index.cjs +1 -1
  24. package/dist/src/lib/plugins/index.mjs +1 -1
  25. package/dist/src/lib/render/index.cjs +1 -1
  26. package/dist/src/lib/render/index.mjs +1 -1
  27. package/dist/src/lib/render/versions.cjs +1 -1
  28. package/dist/src/lib/render/versions.mjs +1 -1
  29. package/dist/src/lib/router/index.cjs +94 -3
  30. package/dist/src/lib/router/index.d.ts.map +1 -1
  31. package/dist/src/lib/router/index.js +55 -0
  32. package/dist/src/lib/router/index.mjs +94 -3
  33. package/dist/src/lib/router/scroll.cjs +79 -0
  34. package/dist/src/lib/router/scroll.d.ts +19 -0
  35. package/dist/src/lib/router/scroll.d.ts.map +1 -0
  36. package/dist/src/lib/router/scroll.js +68 -0
  37. package/dist/src/lib/router/scroll.mjs +58 -0
  38. package/dist/src/lib/server/index.cjs +1 -1
  39. package/dist/src/lib/server/index.mjs +1 -1
  40. package/dist/src/lib/storage/index.cjs +1 -1
  41. package/dist/src/lib/storage/index.mjs +1 -1
  42. package/dist/src/routes.cjs +1 -1
  43. package/dist/src/routes.mjs +1 -1
  44. package/dist/src/utils.cjs +1 -1
  45. package/dist/src/utils.mjs +1 -1
  46. package/dist/src/version.generated.cjs +1 -1
  47. package/dist/src/version.generated.d.ts +1 -1
  48. package/dist/src/version.generated.js +1 -1
  49. package/dist/src/version.generated.mjs +1 -1
  50. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  51. package/package.json +16 -16
@@ -84,6 +84,64 @@ import {
84
84
  import { createContext as createContext2, useContext as useContext2 } from "@fluixi/reactive/signal";
85
85
  import { get } from "@fluixi/utils/object";
86
86
  import { deepEqual } from "@fluixi/utils/compare";
87
+
88
+ // src/lib/router/scroll.ts
89
+ var STORE = "fx:scroll";
90
+ var KEEP = 100;
91
+ function keepScroll(initial) {
92
+ const read2 = () => {
93
+ try {
94
+ return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
95
+ } catch {
96
+ return {};
97
+ }
98
+ };
99
+ const positions = read2();
100
+ const write = () => {
101
+ const keys = Object.keys(positions);
102
+ for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
103
+ try {
104
+ sessionStorage.setItem(STORE, JSON.stringify(positions));
105
+ } catch {
106
+ }
107
+ };
108
+ let current = initial.key;
109
+ const save = () => {
110
+ if (current === void 0) return;
111
+ delete positions[current];
112
+ positions[current] = window.scrollY;
113
+ write();
114
+ };
115
+ const restoration = history.scrollRestoration;
116
+ history.scrollRestoration = "manual";
117
+ window.addEventListener("pagehide", save);
118
+ const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
119
+ const toHash = (hash) => {
120
+ if (!hash || hash === "#") return false;
121
+ const target = document.getElementById(decodeURIComponent(hash.slice(1)));
122
+ target?.scrollIntoView({ behavior: "instant" });
123
+ return target !== null;
124
+ };
125
+ return {
126
+ leave: save,
127
+ arrive(location, from) {
128
+ current = location.key;
129
+ if (location.action === "pop" || from === void 0) {
130
+ const y = location.key === void 0 ? void 0 : positions[location.key];
131
+ if (y !== void 0) to(y);
132
+ return;
133
+ }
134
+ if (location.action === "replace" && location.pathname === from.pathname) return;
135
+ if (!toHash(location.hash)) to(0);
136
+ },
137
+ dispose() {
138
+ window.removeEventListener("pagehide", save);
139
+ history.scrollRestoration = restoration;
140
+ }
141
+ };
142
+ }
143
+
144
+ // src/lib/router/index.ts
87
145
  import {
88
146
  createRouter as createCoreRouter,
89
147
  createBrowserHistory,
@@ -405,9 +463,20 @@ function renderLayer(m, depth, base) {
405
463
  return router ? router.trackLoad(result) : result;
406
464
  });
407
465
  onCleanup(onRevalidate(() => void refetch()));
466
+ let loadedFor = untrack2(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
467
+ createEffect(() => {
468
+ const now = base.matched()[depth];
469
+ if (now?.route !== m.route) return;
470
+ const params = JSON.stringify(now.params ?? {});
471
+ if (params === loadedFor) return;
472
+ loadedFor = params;
473
+ untrack2(() => void refetch());
474
+ });
408
475
  data = () => {
409
476
  const error = resource.error;
410
477
  if (error !== void 0 && error !== null) throw error;
478
+ const r = resource;
479
+ if (r.state === "refreshing") return r.latest;
411
480
  return resource();
412
481
  };
413
482
  Object.defineProperty(data, "loading", { get: () => resource.loading });
@@ -512,16 +581,19 @@ function buildDefaultNotFound() {
512
581
  }
513
582
  var pendingTransitionMode;
514
583
  function Router(props) {
515
- const history = props.history ?? createBrowserHistory();
584
+ const history2 = props.history ?? createBrowserHistory();
516
585
  const router = createCoreRouter({
517
586
  routes: props.routes,
518
587
  base: props.base,
519
- history
588
+ history: history2
520
589
  });
521
590
  const [state, setState] = createSignal2(router.state());
522
591
  const [navigating, setNavigating] = createSignal2(false);
523
592
  const [loads, setLoads] = createSignal2(0);
524
593
  let navigationId = 0;
594
+ const scroll = history2.window ? keepScroll(router.state().location) : void 0;
595
+ let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
596
+ const [arrived, setArrived] = createSignal2(0);
525
597
  const takeMode = () => {
526
598
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
527
599
  pendingTransitionMode = void 0;
@@ -531,6 +603,10 @@ function Router(props) {
531
603
  const id = ++navigationId;
532
604
  const mode = takeMode();
533
605
  setNavigating(true);
606
+ if (scroll) {
607
+ scroll.leave();
608
+ arriving = { id, location: next.location, from: untrack2(state).location };
609
+ }
534
610
  const prepared = prepareComponents(
535
611
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
536
612
  );
@@ -538,6 +614,7 @@ function Router(props) {
538
614
  setState(next);
539
615
  const flushed = flush();
540
616
  if (id === navigationId) setNavigating(false);
617
+ if (scroll) setArrived(id);
541
618
  return flushed;
542
619
  };
543
620
  if (!prepared) {
@@ -551,6 +628,7 @@ function Router(props) {
551
628
  });
552
629
  onCleanup(() => {
553
630
  unsub();
631
+ scroll?.dispose();
554
632
  router.destroy();
555
633
  navigationId++;
556
634
  });
@@ -573,6 +651,19 @@ function Router(props) {
573
651
  return result;
574
652
  }
575
653
  };
654
+ if (scroll) {
655
+ const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
656
+ createEffect(() => {
657
+ const id = arrived();
658
+ if (ctx.isRouting()) return;
659
+ const next = arriving;
660
+ if (!next || next.id !== id || id !== navigationId) return;
661
+ arriving = null;
662
+ nextFrame(() => {
663
+ if (id === navigationId) scroll.arrive(next.location, next.from);
664
+ });
665
+ });
666
+ }
576
667
  const root = {
577
668
  depth: -1,
578
669
  matched,
@@ -184,6 +184,68 @@ var init_transition = __esm({
184
184
  }
185
185
  });
186
186
 
187
+ // src/lib/router/scroll.ts
188
+ function keepScroll(initial) {
189
+ const read2 = () => {
190
+ try {
191
+ return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
192
+ } catch {
193
+ return {};
194
+ }
195
+ };
196
+ const positions = read2();
197
+ const write = () => {
198
+ const keys = Object.keys(positions);
199
+ for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
200
+ try {
201
+ sessionStorage.setItem(STORE, JSON.stringify(positions));
202
+ } catch {
203
+ }
204
+ };
205
+ let current = initial.key;
206
+ const save = () => {
207
+ if (current === void 0) return;
208
+ delete positions[current];
209
+ positions[current] = window.scrollY;
210
+ write();
211
+ };
212
+ const restoration = history.scrollRestoration;
213
+ history.scrollRestoration = "manual";
214
+ window.addEventListener("pagehide", save);
215
+ const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
216
+ const toHash = (hash) => {
217
+ if (!hash || hash === "#") return false;
218
+ const target = document.getElementById(decodeURIComponent(hash.slice(1)));
219
+ target?.scrollIntoView({ behavior: "instant" });
220
+ return target !== null;
221
+ };
222
+ return {
223
+ leave: save,
224
+ arrive(location, from) {
225
+ current = location.key;
226
+ if (location.action === "pop" || from === void 0) {
227
+ const y = location.key === void 0 ? void 0 : positions[location.key];
228
+ if (y !== void 0) to(y);
229
+ return;
230
+ }
231
+ if (location.action === "replace" && location.pathname === from.pathname) return;
232
+ if (!toHash(location.hash)) to(0);
233
+ },
234
+ dispose() {
235
+ window.removeEventListener("pagehide", save);
236
+ history.scrollRestoration = restoration;
237
+ }
238
+ };
239
+ }
240
+ var STORE, KEEP;
241
+ var init_scroll = __esm({
242
+ "src/lib/router/scroll.ts"() {
243
+ "use strict";
244
+ STORE = "fx:scroll";
245
+ KEEP = 100;
246
+ }
247
+ });
248
+
187
249
  // src/lib/router/data.ts
188
250
  function onRevalidate(listener) {
189
251
  revalidationListeners.add(listener);
@@ -419,9 +481,20 @@ function renderLayer(m, depth, base) {
419
481
  return router ? router.trackLoad(result) : result;
420
482
  });
421
483
  (0, import_signal14.onCleanup)(onRevalidate(() => void refetch()));
484
+ let loadedFor = (0, import_signal14.untrack)(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
485
+ (0, import_signal14.createEffect)(() => {
486
+ const now = base.matched()[depth];
487
+ if (now?.route !== m.route) return;
488
+ const params = JSON.stringify(now.params ?? {});
489
+ if (params === loadedFor) return;
490
+ loadedFor = params;
491
+ (0, import_signal14.untrack)(() => void refetch());
492
+ });
422
493
  data = () => {
423
494
  const error = resource.error;
424
495
  if (error !== void 0 && error !== null) throw error;
496
+ const r = resource;
497
+ if (r.state === "refreshing") return r.latest;
425
498
  return resource();
426
499
  };
427
500
  Object.defineProperty(data, "loading", { get: () => resource.loading });
@@ -525,16 +598,19 @@ function buildDefaultNotFound() {
525
598
  return el;
526
599
  }
527
600
  function Router(props) {
528
- const history = props.history ?? (0, import_router.createBrowserHistory)();
601
+ const history2 = props.history ?? (0, import_router.createBrowserHistory)();
529
602
  const router = (0, import_router.createRouter)({
530
603
  routes: props.routes,
531
604
  base: props.base,
532
- history
605
+ history: history2
533
606
  });
534
607
  const [state, setState] = (0, import_signal14.createSignal)(router.state());
535
608
  const [navigating, setNavigating] = (0, import_signal14.createSignal)(false);
536
609
  const [loads, setLoads] = (0, import_signal14.createSignal)(0);
537
610
  let navigationId = 0;
611
+ const scroll = history2.window ? keepScroll(router.state().location) : void 0;
612
+ let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
613
+ const [arrived, setArrived] = (0, import_signal14.createSignal)(0);
538
614
  const takeMode = () => {
539
615
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
540
616
  pendingTransitionMode = void 0;
@@ -544,6 +620,10 @@ function Router(props) {
544
620
  const id = ++navigationId;
545
621
  const mode = takeMode();
546
622
  setNavigating(true);
623
+ if (scroll) {
624
+ scroll.leave();
625
+ arriving = { id, location: next.location, from: (0, import_signal14.untrack)(state).location };
626
+ }
547
627
  const prepared = prepareComponents(
548
628
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
549
629
  );
@@ -551,6 +631,7 @@ function Router(props) {
551
631
  setState(next);
552
632
  const flushed = (0, import_signal14.flush)();
553
633
  if (id === navigationId) setNavigating(false);
634
+ if (scroll) setArrived(id);
554
635
  return flushed;
555
636
  };
556
637
  if (!prepared) {
@@ -564,6 +645,7 @@ function Router(props) {
564
645
  });
565
646
  (0, import_signal14.onCleanup)(() => {
566
647
  unsub();
648
+ scroll?.dispose();
567
649
  router.destroy();
568
650
  navigationId++;
569
651
  });
@@ -586,6 +668,19 @@ function Router(props) {
586
668
  return result;
587
669
  }
588
670
  };
671
+ if (scroll) {
672
+ const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
673
+ (0, import_signal14.createEffect)(() => {
674
+ const id = arrived();
675
+ if (ctx.isRouting()) return;
676
+ const next = arriving;
677
+ if (!next || next.id !== id || id !== navigationId) return;
678
+ arriving = null;
679
+ nextFrame(() => {
680
+ if (id === navigationId) scroll.arrive(next.location, next.from);
681
+ });
682
+ });
683
+ }
589
684
  const root = {
590
685
  depth: -1,
591
686
  matched,
@@ -829,6 +924,7 @@ var init_router = __esm({
829
924
  import_signal15 = require("@fluixi/reactive/signal");
830
925
  import_object = require("@fluixi/utils/object");
831
926
  import_compare = require("@fluixi/utils/compare");
927
+ init_scroll();
832
928
  import_router = require("@fluixi/router");
833
929
  init_data();
834
930
  import_router2 = require("@fluixi/router");
@@ -976,7 +1072,7 @@ var import_signal6 = require("@fluixi/reactive/signal");
976
1072
  var import_dom = require("@fluixi/dom");
977
1073
 
978
1074
  // src/version.generated.ts
979
- var VERSION = "1.0.0-alpha.94";
1075
+ var VERSION = "1.0.0-alpha.95";
980
1076
 
981
1077
  // src/lib/render/versions.ts
982
1078
  var import_dom2 = require("@fluixi/dom");
@@ -20,7 +20,7 @@ import {
20
20
  useParams,
21
21
  useRouter,
22
22
  useSearchParams
23
- } from "./chunk-7XM7Q5VR.mjs";
23
+ } from "./chunk-IFONZJWJ.mjs";
24
24
 
25
25
  // src/lib/index.ts
26
26
  import {
@@ -54,7 +54,7 @@ import { createRoot as createRoot2 } from "@fluixi/reactive/signal";
54
54
  import { registerCoreVersion } from "@fluixi/dom";
55
55
 
56
56
  // src/version.generated.ts
57
- var VERSION = "1.0.0-alpha.94";
57
+ var VERSION = "1.0.0-alpha.95";
58
58
 
59
59
  // src/lib/render/versions.ts
60
60
  import { stampVersions, warnOnVersionSkew, versions } from "@fluixi/dom";
@@ -574,7 +574,7 @@ async function startClient(App, options = {}) {
574
574
  beginAppHydration();
575
575
  try {
576
576
  if (options.routes && options.routes.length) {
577
- const { preloadRoutes } = await import("./router-VOY43YNM.mjs");
577
+ const { preloadRoutes } = await import("./router-TU5CPI54.mjs");
578
578
  await preloadRoutes(options.routes, window.location.pathname);
579
579
  }
580
580
  deserializeHydrationData();
@@ -34,7 +34,7 @@ import {
34
34
  useSearchParams,
35
35
  useSubmission,
36
36
  useSubmissions
37
- } from "./chunk-7XM7Q5VR.mjs";
37
+ } from "./chunk-IFONZJWJ.mjs";
38
38
  export {
39
39
  A,
40
40
  Form,
@@ -1,4 +1,4 @@
1
- /*! @fluixi/core v1.0.0-alpha.94 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/core v1.0.0-alpha.95 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -143,7 +143,7 @@ __export(index_exports, {
143
143
  module.exports = __toCommonJS(index_exports);
144
144
 
145
145
  // src/version.generated.ts
146
- var VERSION = "1.0.0-alpha.94";
146
+ var VERSION = "1.0.0-alpha.95";
147
147
 
148
148
  // src/lib/core.ts
149
149
  var import_signal18 = require("@fluixi/reactive/signal");
@@ -1178,6 +1178,64 @@ var import_dom15 = require("@fluixi/dom");
1178
1178
  var import_signal16 = require("@fluixi/reactive/signal");
1179
1179
  var import_object = require("@fluixi/utils/object");
1180
1180
  var import_compare = require("@fluixi/utils/compare");
1181
+
1182
+ // src/lib/router/scroll.ts
1183
+ var STORE = "fx:scroll";
1184
+ var KEEP = 100;
1185
+ function keepScroll(initial) {
1186
+ const read3 = () => {
1187
+ try {
1188
+ return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
1189
+ } catch {
1190
+ return {};
1191
+ }
1192
+ };
1193
+ const positions = read3();
1194
+ const write = () => {
1195
+ const keys = Object.keys(positions);
1196
+ for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
1197
+ try {
1198
+ sessionStorage.setItem(STORE, JSON.stringify(positions));
1199
+ } catch {
1200
+ }
1201
+ };
1202
+ let current = initial.key;
1203
+ const save = () => {
1204
+ if (current === void 0) return;
1205
+ delete positions[current];
1206
+ positions[current] = window.scrollY;
1207
+ write();
1208
+ };
1209
+ const restoration = history.scrollRestoration;
1210
+ history.scrollRestoration = "manual";
1211
+ window.addEventListener("pagehide", save);
1212
+ const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
1213
+ const toHash = (hash) => {
1214
+ if (!hash || hash === "#") return false;
1215
+ const target = document.getElementById(decodeURIComponent(hash.slice(1)));
1216
+ target?.scrollIntoView({ behavior: "instant" });
1217
+ return target !== null;
1218
+ };
1219
+ return {
1220
+ leave: save,
1221
+ arrive(location, from) {
1222
+ current = location.key;
1223
+ if (location.action === "pop" || from === void 0) {
1224
+ const y = location.key === void 0 ? void 0 : positions[location.key];
1225
+ if (y !== void 0) to(y);
1226
+ return;
1227
+ }
1228
+ if (location.action === "replace" && location.pathname === from.pathname) return;
1229
+ if (!toHash(location.hash)) to(0);
1230
+ },
1231
+ dispose() {
1232
+ window.removeEventListener("pagehide", save);
1233
+ history.scrollRestoration = restoration;
1234
+ }
1235
+ };
1236
+ }
1237
+
1238
+ // src/lib/router/index.ts
1181
1239
  var import_router = require("@fluixi/router");
1182
1240
 
1183
1241
  // src/lib/router/data.ts
@@ -1257,9 +1315,20 @@ function renderLayer(m, depth, base) {
1257
1315
  return router ? router.trackLoad(result) : result;
1258
1316
  });
1259
1317
  (0, import_signal15.onCleanup)(onRevalidate(() => void refetch()));
1318
+ let loadedFor = (0, import_signal15.untrack)(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
1319
+ (0, import_signal15.createEffect)(() => {
1320
+ const now = base.matched()[depth];
1321
+ if (now?.route !== m.route) return;
1322
+ const params = JSON.stringify(now.params ?? {});
1323
+ if (params === loadedFor) return;
1324
+ loadedFor = params;
1325
+ (0, import_signal15.untrack)(() => void refetch());
1326
+ });
1260
1327
  data = () => {
1261
1328
  const error = resource2.error;
1262
1329
  if (error !== void 0 && error !== null) throw error;
1330
+ const r = resource2;
1331
+ if (r.state === "refreshing") return r.latest;
1263
1332
  return resource2();
1264
1333
  };
1265
1334
  Object.defineProperty(data, "loading", { get: () => resource2.loading });
@@ -1364,16 +1433,19 @@ function buildDefaultNotFound() {
1364
1433
  }
1365
1434
  var pendingTransitionMode;
1366
1435
  function Router(props) {
1367
- const history = props.history ?? (0, import_router.createBrowserHistory)();
1436
+ const history2 = props.history ?? (0, import_router.createBrowserHistory)();
1368
1437
  const router = (0, import_router.createRouter)({
1369
1438
  routes: props.routes,
1370
1439
  base: props.base,
1371
- history
1440
+ history: history2
1372
1441
  });
1373
1442
  const [state, setState] = (0, import_signal15.createSignal)(router.state());
1374
1443
  const [navigating, setNavigating] = (0, import_signal15.createSignal)(false);
1375
1444
  const [loads, setLoads] = (0, import_signal15.createSignal)(0);
1376
1445
  let navigationId = 0;
1446
+ const scroll = history2.window ? keepScroll(router.state().location) : void 0;
1447
+ let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
1448
+ const [arrived, setArrived] = (0, import_signal15.createSignal)(0);
1377
1449
  const takeMode = () => {
1378
1450
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
1379
1451
  pendingTransitionMode = void 0;
@@ -1383,6 +1455,10 @@ function Router(props) {
1383
1455
  const id = ++navigationId;
1384
1456
  const mode = takeMode();
1385
1457
  setNavigating(true);
1458
+ if (scroll) {
1459
+ scroll.leave();
1460
+ arriving = { id, location: next.location, from: (0, import_signal15.untrack)(state).location };
1461
+ }
1386
1462
  const prepared = prepareComponents(
1387
1463
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
1388
1464
  );
@@ -1390,6 +1466,7 @@ function Router(props) {
1390
1466
  setState(next);
1391
1467
  const flushed = (0, import_signal15.flush)();
1392
1468
  if (id === navigationId) setNavigating(false);
1469
+ if (scroll) setArrived(id);
1393
1470
  return flushed;
1394
1471
  };
1395
1472
  if (!prepared) {
@@ -1403,6 +1480,7 @@ function Router(props) {
1403
1480
  });
1404
1481
  (0, import_signal15.onCleanup)(() => {
1405
1482
  unsub();
1483
+ scroll?.dispose();
1406
1484
  router.destroy();
1407
1485
  navigationId++;
1408
1486
  });
@@ -1425,6 +1503,19 @@ function Router(props) {
1425
1503
  return result;
1426
1504
  }
1427
1505
  };
1506
+ if (scroll) {
1507
+ const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
1508
+ (0, import_signal15.createEffect)(() => {
1509
+ const id = arrived();
1510
+ if (ctx.isRouting()) return;
1511
+ const next = arriving;
1512
+ if (!next || next.id !== id || id !== navigationId) return;
1513
+ arriving = null;
1514
+ nextFrame(() => {
1515
+ if (id === navigationId) scroll.arrive(next.location, next.from);
1516
+ });
1517
+ });
1518
+ }
1428
1519
  const root = {
1429
1520
  depth: -1,
1430
1521
  matched,
@@ -1,7 +1,7 @@
1
- /*! @fluixi/core v1.0.0-alpha.94 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/core v1.0.0-alpha.95 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/version.generated.ts
4
- var VERSION = "1.0.0-alpha.94";
4
+ var VERSION = "1.0.0-alpha.95";
5
5
 
6
6
  // src/lib/core.ts
7
7
  import {
@@ -1126,6 +1126,64 @@ import {
1126
1126
  import { createContext as createContext5, useContext as useContext5 } from "@fluixi/reactive/signal";
1127
1127
  import { get } from "@fluixi/utils/object";
1128
1128
  import { deepEqual } from "@fluixi/utils/compare";
1129
+
1130
+ // src/lib/router/scroll.ts
1131
+ var STORE = "fx:scroll";
1132
+ var KEEP = 100;
1133
+ function keepScroll(initial) {
1134
+ const read3 = () => {
1135
+ try {
1136
+ return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
1137
+ } catch {
1138
+ return {};
1139
+ }
1140
+ };
1141
+ const positions = read3();
1142
+ const write = () => {
1143
+ const keys = Object.keys(positions);
1144
+ for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
1145
+ try {
1146
+ sessionStorage.setItem(STORE, JSON.stringify(positions));
1147
+ } catch {
1148
+ }
1149
+ };
1150
+ let current = initial.key;
1151
+ const save = () => {
1152
+ if (current === void 0) return;
1153
+ delete positions[current];
1154
+ positions[current] = window.scrollY;
1155
+ write();
1156
+ };
1157
+ const restoration = history.scrollRestoration;
1158
+ history.scrollRestoration = "manual";
1159
+ window.addEventListener("pagehide", save);
1160
+ const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
1161
+ const toHash = (hash) => {
1162
+ if (!hash || hash === "#") return false;
1163
+ const target = document.getElementById(decodeURIComponent(hash.slice(1)));
1164
+ target?.scrollIntoView({ behavior: "instant" });
1165
+ return target !== null;
1166
+ };
1167
+ return {
1168
+ leave: save,
1169
+ arrive(location, from) {
1170
+ current = location.key;
1171
+ if (location.action === "pop" || from === void 0) {
1172
+ const y = location.key === void 0 ? void 0 : positions[location.key];
1173
+ if (y !== void 0) to(y);
1174
+ return;
1175
+ }
1176
+ if (location.action === "replace" && location.pathname === from.pathname) return;
1177
+ if (!toHash(location.hash)) to(0);
1178
+ },
1179
+ dispose() {
1180
+ window.removeEventListener("pagehide", save);
1181
+ history.scrollRestoration = restoration;
1182
+ }
1183
+ };
1184
+ }
1185
+
1186
+ // src/lib/router/index.ts
1129
1187
  import {
1130
1188
  createRouter as createCoreRouter,
1131
1189
  createBrowserHistory,
@@ -1218,9 +1276,20 @@ function renderLayer(m, depth, base) {
1218
1276
  return router ? router.trackLoad(result) : result;
1219
1277
  });
1220
1278
  onCleanup4(onRevalidate(() => void refetch()));
1279
+ let loadedFor = untrack4(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
1280
+ createEffect4(() => {
1281
+ const now = base.matched()[depth];
1282
+ if (now?.route !== m.route) return;
1283
+ const params = JSON.stringify(now.params ?? {});
1284
+ if (params === loadedFor) return;
1285
+ loadedFor = params;
1286
+ untrack4(() => void refetch());
1287
+ });
1221
1288
  data = () => {
1222
1289
  const error = resource2.error;
1223
1290
  if (error !== void 0 && error !== null) throw error;
1291
+ const r = resource2;
1292
+ if (r.state === "refreshing") return r.latest;
1224
1293
  return resource2();
1225
1294
  };
1226
1295
  Object.defineProperty(data, "loading", { get: () => resource2.loading });
@@ -1325,16 +1394,19 @@ function buildDefaultNotFound() {
1325
1394
  }
1326
1395
  var pendingTransitionMode;
1327
1396
  function Router(props) {
1328
- const history = props.history ?? createBrowserHistory();
1397
+ const history2 = props.history ?? createBrowserHistory();
1329
1398
  const router = createCoreRouter({
1330
1399
  routes: props.routes,
1331
1400
  base: props.base,
1332
- history
1401
+ history: history2
1333
1402
  });
1334
1403
  const [state, setState] = createSignal8(router.state());
1335
1404
  const [navigating, setNavigating] = createSignal8(false);
1336
1405
  const [loads, setLoads] = createSignal8(0);
1337
1406
  let navigationId = 0;
1407
+ const scroll = history2.window ? keepScroll(router.state().location) : void 0;
1408
+ let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
1409
+ const [arrived, setArrived] = createSignal8(0);
1338
1410
  const takeMode = () => {
1339
1411
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
1340
1412
  pendingTransitionMode = void 0;
@@ -1344,6 +1416,10 @@ function Router(props) {
1344
1416
  const id = ++navigationId;
1345
1417
  const mode = takeMode();
1346
1418
  setNavigating(true);
1419
+ if (scroll) {
1420
+ scroll.leave();
1421
+ arriving = { id, location: next.location, from: untrack4(state).location };
1422
+ }
1347
1423
  const prepared = prepareComponents(
1348
1424
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
1349
1425
  );
@@ -1351,6 +1427,7 @@ function Router(props) {
1351
1427
  setState(next);
1352
1428
  const flushed = flush();
1353
1429
  if (id === navigationId) setNavigating(false);
1430
+ if (scroll) setArrived(id);
1354
1431
  return flushed;
1355
1432
  };
1356
1433
  if (!prepared) {
@@ -1364,6 +1441,7 @@ function Router(props) {
1364
1441
  });
1365
1442
  onCleanup4(() => {
1366
1443
  unsub();
1444
+ scroll?.dispose();
1367
1445
  router.destroy();
1368
1446
  navigationId++;
1369
1447
  });
@@ -1386,6 +1464,19 @@ function Router(props) {
1386
1464
  return result;
1387
1465
  }
1388
1466
  };
1467
+ if (scroll) {
1468
+ const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
1469
+ createEffect4(() => {
1470
+ const id = arrived();
1471
+ if (ctx.isRouting()) return;
1472
+ const next = arriving;
1473
+ if (!next || next.id !== id || id !== navigationId) return;
1474
+ arriving = null;
1475
+ nextFrame(() => {
1476
+ if (id === navigationId) scroll.arrive(next.location, next.from);
1477
+ });
1478
+ });
1479
+ }
1389
1480
  const root = {
1390
1481
  depth: -1,
1391
1482
  matched,
@@ -1,4 +1,4 @@
1
- /*! @fluixi/core v1.0.0-alpha.94 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/core v1.0.0-alpha.95 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/lib/render/lazy.ts
4
4
  import { createComponent } from "@fluixi/dom";