@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
@@ -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;
@@ -124,6 +124,68 @@ var init_transition = __esm({
124
124
  }
125
125
  });
126
126
 
127
+ // src/lib/router/scroll.ts
128
+ function keepScroll(initial) {
129
+ const read2 = () => {
130
+ try {
131
+ return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
132
+ } catch {
133
+ return {};
134
+ }
135
+ };
136
+ const positions = read2();
137
+ const write = () => {
138
+ const keys = Object.keys(positions);
139
+ for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
140
+ try {
141
+ sessionStorage.setItem(STORE, JSON.stringify(positions));
142
+ } catch {
143
+ }
144
+ };
145
+ let current = initial.key;
146
+ const save = () => {
147
+ if (current === void 0) return;
148
+ delete positions[current];
149
+ positions[current] = window.scrollY;
150
+ write();
151
+ };
152
+ const restoration = history.scrollRestoration;
153
+ history.scrollRestoration = "manual";
154
+ window.addEventListener("pagehide", save);
155
+ const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
156
+ const toHash = (hash) => {
157
+ if (!hash || hash === "#") return false;
158
+ const target = document.getElementById(decodeURIComponent(hash.slice(1)));
159
+ target?.scrollIntoView({ behavior: "instant" });
160
+ return target !== null;
161
+ };
162
+ return {
163
+ leave: save,
164
+ arrive(location, from) {
165
+ current = location.key;
166
+ if (location.action === "pop" || from === void 0) {
167
+ const y = location.key === void 0 ? void 0 : positions[location.key];
168
+ if (y !== void 0) to(y);
169
+ return;
170
+ }
171
+ if (location.action === "replace" && location.pathname === from.pathname) return;
172
+ if (!toHash(location.hash)) to(0);
173
+ },
174
+ dispose() {
175
+ window.removeEventListener("pagehide", save);
176
+ history.scrollRestoration = restoration;
177
+ }
178
+ };
179
+ }
180
+ var STORE, KEEP;
181
+ var init_scroll = __esm({
182
+ "src/lib/router/scroll.ts"() {
183
+ "use strict";
184
+ STORE = "fx:scroll";
185
+ KEEP = 100;
186
+ }
187
+ });
188
+
127
189
  // src/lib/context/context.ts
128
190
  function createContext3(defaultValue, name) {
129
191
  const context = (0, import_signal7.createContext)(defaultValue, name);
@@ -389,9 +451,20 @@ function renderLayer(m, depth, base) {
389
451
  return router ? router.trackLoad(result) : result;
390
452
  });
391
453
  (0, import_signal10.onCleanup)(onRevalidate(() => void refetch()));
454
+ let loadedFor = (0, import_signal10.untrack)(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
455
+ (0, import_signal10.createEffect)(() => {
456
+ const now = base.matched()[depth];
457
+ if (now?.route !== m.route) return;
458
+ const params = JSON.stringify(now.params ?? {});
459
+ if (params === loadedFor) return;
460
+ loadedFor = params;
461
+ (0, import_signal10.untrack)(() => void refetch());
462
+ });
392
463
  data = () => {
393
464
  const error = resource.error;
394
465
  if (error !== void 0 && error !== null) throw error;
466
+ const r = resource;
467
+ if (r.state === "refreshing") return r.latest;
395
468
  return resource();
396
469
  };
397
470
  Object.defineProperty(data, "loading", { get: () => resource.loading });
@@ -495,16 +568,19 @@ function buildDefaultNotFound() {
495
568
  return el;
496
569
  }
497
570
  function Router(props) {
498
- const history = props.history ?? (0, import_router.createBrowserHistory)();
571
+ const history2 = props.history ?? (0, import_router.createBrowserHistory)();
499
572
  const router = (0, import_router.createRouter)({
500
573
  routes: props.routes,
501
574
  base: props.base,
502
- history
575
+ history: history2
503
576
  });
504
577
  const [state, setState] = (0, import_signal10.createSignal)(router.state());
505
578
  const [navigating, setNavigating] = (0, import_signal10.createSignal)(false);
506
579
  const [loads, setLoads] = (0, import_signal10.createSignal)(0);
507
580
  let navigationId = 0;
581
+ const scroll = history2.window ? keepScroll(router.state().location) : void 0;
582
+ let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
583
+ const [arrived, setArrived] = (0, import_signal10.createSignal)(0);
508
584
  const takeMode = () => {
509
585
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
510
586
  pendingTransitionMode = void 0;
@@ -514,6 +590,10 @@ function Router(props) {
514
590
  const id = ++navigationId;
515
591
  const mode = takeMode();
516
592
  setNavigating(true);
593
+ if (scroll) {
594
+ scroll.leave();
595
+ arriving = { id, location: next.location, from: (0, import_signal10.untrack)(state).location };
596
+ }
517
597
  const prepared = prepareComponents(
518
598
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
519
599
  );
@@ -521,6 +601,7 @@ function Router(props) {
521
601
  setState(next);
522
602
  const flushed = (0, import_signal10.flush)();
523
603
  if (id === navigationId) setNavigating(false);
604
+ if (scroll) setArrived(id);
524
605
  return flushed;
525
606
  };
526
607
  if (!prepared) {
@@ -534,6 +615,7 @@ function Router(props) {
534
615
  });
535
616
  (0, import_signal10.onCleanup)(() => {
536
617
  unsub();
618
+ scroll?.dispose();
537
619
  router.destroy();
538
620
  navigationId++;
539
621
  });
@@ -556,6 +638,19 @@ function Router(props) {
556
638
  return result;
557
639
  }
558
640
  };
641
+ if (scroll) {
642
+ const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
643
+ (0, import_signal10.createEffect)(() => {
644
+ const id = arrived();
645
+ if (ctx.isRouting()) return;
646
+ const next = arriving;
647
+ if (!next || next.id !== id || id !== navigationId) return;
648
+ arriving = null;
649
+ nextFrame(() => {
650
+ if (id === navigationId) scroll.arrive(next.location, next.from);
651
+ });
652
+ });
653
+ }
559
654
  const root = {
560
655
  depth: -1,
561
656
  matched,
@@ -799,6 +894,7 @@ var init_router = __esm({
799
894
  import_signal11 = require("@fluixi/reactive/signal");
800
895
  import_object = require("@fluixi/utils/object");
801
896
  import_compare = require("@fluixi/utils/compare");
897
+ init_scroll();
802
898
  import_router = require("@fluixi/router");
803
899
  init_data();
804
900
  import_router2 = require("@fluixi/router");
@@ -847,7 +943,7 @@ var import_dom13 = require("@fluixi/dom");
847
943
  var import_dom = require("@fluixi/dom");
848
944
 
849
945
  // src/version.generated.ts
850
- var VERSION = "1.0.0-alpha.94";
946
+ var VERSION = "1.0.0-alpha.95";
851
947
 
852
948
  // src/lib/render/versions.ts
853
949
  var import_dom2 = require("@fluixi/dom");
@@ -1,5 +1,5 @@
1
- /*! @fluixi/core v1.0.0-alpha.94 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
- import "./chunk-TSXM2PNY.mjs";
1
+ /*! @fluixi/core v1.0.0-alpha.95 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ import "./chunk-AHNP3IWU.mjs";
3
3
 
4
4
  // src/lib/client/index.ts
5
5
  import {
@@ -15,7 +15,7 @@ import {
15
15
  import { registerCoreVersion } from "@fluixi/dom";
16
16
 
17
17
  // src/version.generated.ts
18
- var VERSION = "1.0.0-alpha.94";
18
+ var VERSION = "1.0.0-alpha.95";
19
19
 
20
20
  // src/lib/render/versions.ts
21
21
  import { stampVersions, warnOnVersionSkew, versions } from "@fluixi/dom";
@@ -246,7 +246,7 @@ async function startClient(App, options = {}) {
246
246
  beginAppHydration();
247
247
  try {
248
248
  if (options.routes && options.routes.length) {
249
- const { preloadRoutes } = await import("./router-BDVPLLYX.mjs");
249
+ const { preloadRoutes } = await import("./router-5C6FQJQ4.mjs");
250
250
  await preloadRoutes(options.routes, window.location.pathname);
251
251
  }
252
252
  deserializeHydrationData();
@@ -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
  import {
3
3
  lazy
4
- } from "./chunk-TSXM2PNY.mjs";
4
+ } from "./chunk-AHNP3IWU.mjs";
5
5
 
6
6
  // src/lib/router/index.ts
7
7
  import {
@@ -75,6 +75,64 @@ import {
75
75
  import { createContext as createContext2, useContext as useContext2 } from "@fluixi/reactive/signal";
76
76
  import { get } from "@fluixi/utils/object";
77
77
  import { deepEqual } from "@fluixi/utils/compare";
78
+
79
+ // src/lib/router/scroll.ts
80
+ var STORE = "fx:scroll";
81
+ var KEEP = 100;
82
+ function keepScroll(initial) {
83
+ const read2 = () => {
84
+ try {
85
+ return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
86
+ } catch {
87
+ return {};
88
+ }
89
+ };
90
+ const positions = read2();
91
+ const write = () => {
92
+ const keys = Object.keys(positions);
93
+ for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
94
+ try {
95
+ sessionStorage.setItem(STORE, JSON.stringify(positions));
96
+ } catch {
97
+ }
98
+ };
99
+ let current = initial.key;
100
+ const save = () => {
101
+ if (current === void 0) return;
102
+ delete positions[current];
103
+ positions[current] = window.scrollY;
104
+ write();
105
+ };
106
+ const restoration = history.scrollRestoration;
107
+ history.scrollRestoration = "manual";
108
+ window.addEventListener("pagehide", save);
109
+ const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
110
+ const toHash = (hash) => {
111
+ if (!hash || hash === "#") return false;
112
+ const target = document.getElementById(decodeURIComponent(hash.slice(1)));
113
+ target?.scrollIntoView({ behavior: "instant" });
114
+ return target !== null;
115
+ };
116
+ return {
117
+ leave: save,
118
+ arrive(location, from) {
119
+ current = location.key;
120
+ if (location.action === "pop" || from === void 0) {
121
+ const y = location.key === void 0 ? void 0 : positions[location.key];
122
+ if (y !== void 0) to(y);
123
+ return;
124
+ }
125
+ if (location.action === "replace" && location.pathname === from.pathname) return;
126
+ if (!toHash(location.hash)) to(0);
127
+ },
128
+ dispose() {
129
+ window.removeEventListener("pagehide", save);
130
+ history.scrollRestoration = restoration;
131
+ }
132
+ };
133
+ }
134
+
135
+ // src/lib/router/index.ts
78
136
  import {
79
137
  createRouter as createCoreRouter,
80
138
  createBrowserHistory,
@@ -316,9 +374,20 @@ function renderLayer(m, depth, base) {
316
374
  return router ? router.trackLoad(result) : result;
317
375
  });
318
376
  onCleanup(onRevalidate(() => void refetch()));
377
+ let loadedFor = untrack(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
378
+ createEffect(() => {
379
+ const now = base.matched()[depth];
380
+ if (now?.route !== m.route) return;
381
+ const params = JSON.stringify(now.params ?? {});
382
+ if (params === loadedFor) return;
383
+ loadedFor = params;
384
+ untrack(() => void refetch());
385
+ });
319
386
  data = () => {
320
387
  const error = resource.error;
321
388
  if (error !== void 0 && error !== null) throw error;
389
+ const r = resource;
390
+ if (r.state === "refreshing") return r.latest;
322
391
  return resource();
323
392
  };
324
393
  Object.defineProperty(data, "loading", { get: () => resource.loading });
@@ -423,16 +492,19 @@ function buildDefaultNotFound() {
423
492
  }
424
493
  var pendingTransitionMode;
425
494
  function Router(props) {
426
- const history = props.history ?? createBrowserHistory();
495
+ const history2 = props.history ?? createBrowserHistory();
427
496
  const router = createCoreRouter({
428
497
  routes: props.routes,
429
498
  base: props.base,
430
- history
499
+ history: history2
431
500
  });
432
501
  const [state, setState] = createSignal2(router.state());
433
502
  const [navigating, setNavigating] = createSignal2(false);
434
503
  const [loads, setLoads] = createSignal2(0);
435
504
  let navigationId = 0;
505
+ const scroll = history2.window ? keepScroll(router.state().location) : void 0;
506
+ let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
507
+ const [arrived, setArrived] = createSignal2(0);
436
508
  const takeMode = () => {
437
509
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
438
510
  pendingTransitionMode = void 0;
@@ -442,6 +514,10 @@ function Router(props) {
442
514
  const id = ++navigationId;
443
515
  const mode = takeMode();
444
516
  setNavigating(true);
517
+ if (scroll) {
518
+ scroll.leave();
519
+ arriving = { id, location: next.location, from: untrack(state).location };
520
+ }
445
521
  const prepared = prepareComponents(
446
522
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
447
523
  );
@@ -449,6 +525,7 @@ function Router(props) {
449
525
  setState(next);
450
526
  const flushed = flush();
451
527
  if (id === navigationId) setNavigating(false);
528
+ if (scroll) setArrived(id);
452
529
  return flushed;
453
530
  };
454
531
  if (!prepared) {
@@ -462,6 +539,7 @@ function Router(props) {
462
539
  });
463
540
  onCleanup(() => {
464
541
  unsub();
542
+ scroll?.dispose();
465
543
  router.destroy();
466
544
  navigationId++;
467
545
  });
@@ -484,6 +562,19 @@ function Router(props) {
484
562
  return result;
485
563
  }
486
564
  };
565
+ if (scroll) {
566
+ const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
567
+ createEffect(() => {
568
+ const id = arrived();
569
+ if (ctx.isRouting()) return;
570
+ const next = arriving;
571
+ if (!next || next.id !== id || id !== navigationId) return;
572
+ arriving = null;
573
+ nextFrame(() => {
574
+ if (id === navigationId) scroll.arrive(next.location, next.from);
575
+ });
576
+ });
577
+ }
487
578
  const root = {
488
579
  depth: -1,
489
580
  matched,
@@ -152,7 +152,7 @@ var import_signal6 = require("@fluixi/reactive/signal");
152
152
  var import_dom = require("@fluixi/dom");
153
153
 
154
154
  // src/version.generated.ts
155
- var VERSION = "1.0.0-alpha.94";
155
+ var VERSION = "1.0.0-alpha.95";
156
156
 
157
157
  // src/lib/render/versions.ts
158
158
  var import_dom2 = require("@fluixi/dom");
@@ -1177,6 +1177,64 @@ var import_dom15 = require("@fluixi/dom");
1177
1177
  var import_signal16 = require("@fluixi/reactive/signal");
1178
1178
  var import_object = require("@fluixi/utils/object");
1179
1179
  var import_compare = require("@fluixi/utils/compare");
1180
+
1181
+ // src/lib/router/scroll.ts
1182
+ var STORE = "fx:scroll";
1183
+ var KEEP = 100;
1184
+ function keepScroll(initial) {
1185
+ const read3 = () => {
1186
+ try {
1187
+ return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
1188
+ } catch {
1189
+ return {};
1190
+ }
1191
+ };
1192
+ const positions = read3();
1193
+ const write = () => {
1194
+ const keys = Object.keys(positions);
1195
+ for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
1196
+ try {
1197
+ sessionStorage.setItem(STORE, JSON.stringify(positions));
1198
+ } catch {
1199
+ }
1200
+ };
1201
+ let current = initial.key;
1202
+ const save = () => {
1203
+ if (current === void 0) return;
1204
+ delete positions[current];
1205
+ positions[current] = window.scrollY;
1206
+ write();
1207
+ };
1208
+ const restoration = history.scrollRestoration;
1209
+ history.scrollRestoration = "manual";
1210
+ window.addEventListener("pagehide", save);
1211
+ const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
1212
+ const toHash = (hash) => {
1213
+ if (!hash || hash === "#") return false;
1214
+ const target = document.getElementById(decodeURIComponent(hash.slice(1)));
1215
+ target?.scrollIntoView({ behavior: "instant" });
1216
+ return target !== null;
1217
+ };
1218
+ return {
1219
+ leave: save,
1220
+ arrive(location, from) {
1221
+ current = location.key;
1222
+ if (location.action === "pop" || from === void 0) {
1223
+ const y = location.key === void 0 ? void 0 : positions[location.key];
1224
+ if (y !== void 0) to(y);
1225
+ return;
1226
+ }
1227
+ if (location.action === "replace" && location.pathname === from.pathname) return;
1228
+ if (!toHash(location.hash)) to(0);
1229
+ },
1230
+ dispose() {
1231
+ window.removeEventListener("pagehide", save);
1232
+ history.scrollRestoration = restoration;
1233
+ }
1234
+ };
1235
+ }
1236
+
1237
+ // src/lib/router/index.ts
1180
1238
  var import_router = require("@fluixi/router");
1181
1239
 
1182
1240
  // src/lib/router/data.ts
@@ -1256,9 +1314,20 @@ function renderLayer(m, depth, base) {
1256
1314
  return router ? router.trackLoad(result) : result;
1257
1315
  });
1258
1316
  (0, import_signal15.onCleanup)(onRevalidate(() => void refetch()));
1317
+ let loadedFor = (0, import_signal15.untrack)(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
1318
+ (0, import_signal15.createEffect)(() => {
1319
+ const now = base.matched()[depth];
1320
+ if (now?.route !== m.route) return;
1321
+ const params = JSON.stringify(now.params ?? {});
1322
+ if (params === loadedFor) return;
1323
+ loadedFor = params;
1324
+ (0, import_signal15.untrack)(() => void refetch());
1325
+ });
1259
1326
  data = () => {
1260
1327
  const error = resource2.error;
1261
1328
  if (error !== void 0 && error !== null) throw error;
1329
+ const r = resource2;
1330
+ if (r.state === "refreshing") return r.latest;
1262
1331
  return resource2();
1263
1332
  };
1264
1333
  Object.defineProperty(data, "loading", { get: () => resource2.loading });
@@ -1363,16 +1432,19 @@ function buildDefaultNotFound() {
1363
1432
  }
1364
1433
  var pendingTransitionMode;
1365
1434
  function Router(props) {
1366
- const history = props.history ?? (0, import_router.createBrowserHistory)();
1435
+ const history2 = props.history ?? (0, import_router.createBrowserHistory)();
1367
1436
  const router = (0, import_router.createRouter)({
1368
1437
  routes: props.routes,
1369
1438
  base: props.base,
1370
- history
1439
+ history: history2
1371
1440
  });
1372
1441
  const [state, setState] = (0, import_signal15.createSignal)(router.state());
1373
1442
  const [navigating, setNavigating] = (0, import_signal15.createSignal)(false);
1374
1443
  const [loads, setLoads] = (0, import_signal15.createSignal)(0);
1375
1444
  let navigationId = 0;
1445
+ const scroll = history2.window ? keepScroll(router.state().location) : void 0;
1446
+ let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
1447
+ const [arrived, setArrived] = (0, import_signal15.createSignal)(0);
1376
1448
  const takeMode = () => {
1377
1449
  const mode = pendingTransitionMode ?? (props.transition ?? "auto");
1378
1450
  pendingTransitionMode = void 0;
@@ -1382,6 +1454,10 @@ function Router(props) {
1382
1454
  const id = ++navigationId;
1383
1455
  const mode = takeMode();
1384
1456
  setNavigating(true);
1457
+ if (scroll) {
1458
+ scroll.leave();
1459
+ arriving = { id, location: next.location, from: (0, import_signal15.untrack)(state).location };
1460
+ }
1385
1461
  const prepared = prepareComponents(
1386
1462
  (next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
1387
1463
  );
@@ -1389,6 +1465,7 @@ function Router(props) {
1389
1465
  setState(next);
1390
1466
  const flushed = (0, import_signal15.flush)();
1391
1467
  if (id === navigationId) setNavigating(false);
1468
+ if (scroll) setArrived(id);
1392
1469
  return flushed;
1393
1470
  };
1394
1471
  if (!prepared) {
@@ -1402,6 +1479,7 @@ function Router(props) {
1402
1479
  });
1403
1480
  (0, import_signal15.onCleanup)(() => {
1404
1481
  unsub();
1482
+ scroll?.dispose();
1405
1483
  router.destroy();
1406
1484
  navigationId++;
1407
1485
  });
@@ -1424,6 +1502,19 @@ function Router(props) {
1424
1502
  return result;
1425
1503
  }
1426
1504
  };
1505
+ if (scroll) {
1506
+ const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
1507
+ (0, import_signal15.createEffect)(() => {
1508
+ const id = arrived();
1509
+ if (ctx.isRouting()) return;
1510
+ const next = arriving;
1511
+ if (!next || next.id !== id || id !== navigationId) return;
1512
+ arriving = null;
1513
+ nextFrame(() => {
1514
+ if (id === navigationId) scroll.arrive(next.location, next.from);
1515
+ });
1516
+ });
1517
+ }
1427
1518
  const root = {
1428
1519
  depth: -1,
1429
1520
  matched,
@@ -38,7 +38,7 @@ import { createRoot as createRoot2 } from "@fluixi/reactive/signal";
38
38
  import { registerCoreVersion } from "@fluixi/dom";
39
39
 
40
40
  // src/version.generated.ts
41
- var VERSION = "1.0.0-alpha.94";
41
+ var VERSION = "1.0.0-alpha.95";
42
42
 
43
43
  // src/lib/render/versions.ts
44
44
  import { stampVersions, warnOnVersionSkew, versions } from "@fluixi/dom";
@@ -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
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;