@fluixi/core 1.0.0-alpha.93 → 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.
- package/LICENSE +1 -1
- package/dist/cdn/core.global.js +2 -2
- package/dist/src/cdn/{chunk-7XM7Q5VR.mjs → chunk-IFONZJWJ.mjs} +93 -2
- package/dist/src/cdn/core.cjs +99 -3
- package/dist/src/cdn/core.mjs +3 -3
- package/dist/src/cdn/{router-VOY43YNM.mjs → router-TU5CPI54.mjs} +1 -1
- package/dist/src/index.cjs +96 -4
- package/dist/src/index.mjs +97 -5
- package/dist/src/lib/client/{chunk-QT3RFKH6.mjs → chunk-AHNP3IWU.mjs} +1 -1
- package/dist/src/lib/client/index.cjs +100 -4
- package/dist/src/lib/client/index.mjs +4 -4
- package/dist/src/lib/client/{router-ADTS55OP.mjs → router-5C6FQJQ4.mjs} +95 -4
- package/dist/src/lib/core.cjs +95 -3
- package/dist/src/lib/core.d.ts +2 -2
- package/dist/src/lib/core.d.ts.map +1 -1
- package/dist/src/lib/core.js +1 -1
- package/dist/src/lib/core.mjs +96 -4
- package/dist/src/lib/i18n/index.cjs +1 -1
- package/dist/src/lib/i18n/index.mjs +1 -1
- package/dist/src/lib/index.cjs +1 -1
- package/dist/src/lib/index.mjs +1 -1
- package/dist/src/lib/jsx-runtime/index.cjs +1 -1
- package/dist/src/lib/jsx-runtime/index.mjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -1
- package/dist/src/lib/plugins/index.cjs +1 -1
- package/dist/src/lib/plugins/index.mjs +1 -1
- package/dist/src/lib/render/index.cjs +1 -1
- package/dist/src/lib/render/index.mjs +1 -1
- package/dist/src/lib/render/versions.cjs +1 -1
- package/dist/src/lib/render/versions.mjs +1 -1
- package/dist/src/lib/router/index.cjs +94 -3
- package/dist/src/lib/router/index.d.ts.map +1 -1
- package/dist/src/lib/router/index.js +55 -0
- package/dist/src/lib/router/index.mjs +94 -3
- package/dist/src/lib/router/scroll.cjs +79 -0
- package/dist/src/lib/router/scroll.d.ts +19 -0
- package/dist/src/lib/router/scroll.d.ts.map +1 -0
- package/dist/src/lib/router/scroll.js +68 -0
- package/dist/src/lib/router/scroll.mjs +58 -0
- package/dist/src/lib/server/index.cjs +1 -1
- package/dist/src/lib/server/index.mjs +1 -1
- package/dist/src/lib/storage/index.cjs +1 -1
- package/dist/src/lib/storage/index.mjs +1 -1
- package/dist/src/routes.cjs +1 -1
- package/dist/src/routes.mjs +1 -1
- package/dist/src/utils.cjs +1 -1
- package/dist/src/utils.mjs +1 -1
- package/dist/src/version.generated.cjs +1 -1
- package/dist/src/version.generated.d.ts +1 -1
- package/dist/src/version.generated.js +1 -1
- package/dist/src/version.generated.mjs +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +16 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/router/index.ts"],"names":[],"mappings":"AAyBA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAUzB,OAAO,EAAO,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/router/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@ import { insert, createComponent, createNativeElement, setAttribute, } from '@fl
|
|
|
19
19
|
import { createContext, useContext } from '@fluixi/reactive/signal';
|
|
20
20
|
import { get } from '@fluixi/utils/object';
|
|
21
21
|
import { deepEqual } from '@fluixi/utils/compare';
|
|
22
|
+
import { keepScroll } from './scroll.js';
|
|
22
23
|
import { createRouter as createCoreRouter, createBrowserHistory, matchRoutes, matchPath, } from '@fluixi/router';
|
|
23
24
|
const RouterContext = createContext(undefined, 'router');
|
|
24
25
|
const OutletContext = createContext(undefined, 'outlet');
|
|
@@ -61,6 +62,21 @@ function renderLayer(m, depth, base) {
|
|
|
61
62
|
});
|
|
62
63
|
// A revalidated cache entry: load again. The page stays on screen with its current data.
|
|
63
64
|
onCleanup(onRevalidate(() => void refetch()));
|
|
65
|
+
// A navigation that changes only this layer's params keeps the layer, so nothing else
|
|
66
|
+
// would load its data for them. Compared by value: `matched` is rebuilt on every navigation.
|
|
67
|
+
let loadedFor = untrack(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
|
|
68
|
+
createEffect(() => {
|
|
69
|
+
const now = base.matched()[depth];
|
|
70
|
+
// Another route at this depth: this page is on its way out, kept only until the next
|
|
71
|
+
// one has its data, and the new params are not for it.
|
|
72
|
+
if (now?.route !== m.route)
|
|
73
|
+
return;
|
|
74
|
+
const params = JSON.stringify(now.params ?? {});
|
|
75
|
+
if (params === loadedFor)
|
|
76
|
+
return;
|
|
77
|
+
loadedFor = params;
|
|
78
|
+
untrack(() => void refetch());
|
|
79
|
+
});
|
|
64
80
|
// A failed load throws where the page reads it, so the nearest ErrorBoundary shows it.
|
|
65
81
|
// A resource reads as undefined once it has failed, which left the page empty and gave
|
|
66
82
|
// nothing a chance to catch the error.
|
|
@@ -68,6 +84,12 @@ function renderLayer(m, depth, base) {
|
|
|
68
84
|
const error = resource.error;
|
|
69
85
|
if (error !== undefined && error !== null)
|
|
70
86
|
throw error;
|
|
87
|
+
// Loading again (new params, a revalidation): the page keeps its current data. Reading
|
|
88
|
+
// the resource itself would suspend, and a Suspense boundary above would take the page
|
|
89
|
+
// out of the document until the data arrives, losing what the reader had in it.
|
|
90
|
+
const r = resource;
|
|
91
|
+
if (r.state === 'refreshing')
|
|
92
|
+
return r.latest;
|
|
71
93
|
return resource();
|
|
72
94
|
};
|
|
73
95
|
Object.defineProperty(data, 'loading', { get: () => resource.loading });
|
|
@@ -250,6 +272,11 @@ export function Router(props) {
|
|
|
250
272
|
* the page would show B.
|
|
251
273
|
*/
|
|
252
274
|
let navigationId = 0;
|
|
275
|
+
// The window's scroll, for a browser history: where each navigation lands once its page is
|
|
276
|
+
// shown. `arrived` is the navigation whose page was last committed.
|
|
277
|
+
const scroll = history.window ? keepScroll(router.state().location) : undefined;
|
|
278
|
+
let arriving = scroll ? { id: 0, location: router.state().location, from: undefined } : null;
|
|
279
|
+
const [arrived, setArrived] = createSignal(0);
|
|
253
280
|
/**
|
|
254
281
|
* How to present the next commit, set by `useNavigate` immediately before it calls
|
|
255
282
|
* into the core router.
|
|
@@ -268,6 +295,10 @@ export function Router(props) {
|
|
|
268
295
|
const id = ++navigationId;
|
|
269
296
|
const mode = takeMode();
|
|
270
297
|
setNavigating(true);
|
|
298
|
+
if (scroll) {
|
|
299
|
+
scroll.leave();
|
|
300
|
+
arriving = { id, location: next.location, from: untrack(state).location };
|
|
301
|
+
}
|
|
271
302
|
// Load the modules the next route needs before anything is torn down, so a lazy
|
|
272
303
|
// route renders straight through instead of suspending into a fallback with the
|
|
273
304
|
// old page already gone.
|
|
@@ -280,6 +311,8 @@ export function Router(props) {
|
|
|
280
311
|
const flushed = flush();
|
|
281
312
|
if (id === navigationId)
|
|
282
313
|
setNavigating(false);
|
|
314
|
+
if (scroll)
|
|
315
|
+
setArrived(id);
|
|
283
316
|
return flushed;
|
|
284
317
|
};
|
|
285
318
|
if (!prepared) {
|
|
@@ -296,6 +329,7 @@ export function Router(props) {
|
|
|
296
329
|
});
|
|
297
330
|
onCleanup(() => {
|
|
298
331
|
unsub();
|
|
332
|
+
scroll?.dispose();
|
|
299
333
|
router.destroy();
|
|
300
334
|
// Anything still preparing is now stale, so nothing can commit into a torn-down
|
|
301
335
|
// router.
|
|
@@ -319,6 +353,27 @@ export function Router(props) {
|
|
|
319
353
|
return result;
|
|
320
354
|
},
|
|
321
355
|
};
|
|
356
|
+
// Scroll once the page is on screen: after its modules and its data, which is when the
|
|
357
|
+
// router stops routing, and a frame later, before that frame is painted. The page's own
|
|
358
|
+
// effects fill it in after the router's (an article set through innerHTML), and a scroll
|
|
359
|
+
// into a page not yet filled is cut short to its height. A newer navigation takes over
|
|
360
|
+
// from one still loading.
|
|
361
|
+
if (scroll) {
|
|
362
|
+
const nextFrame = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
|
|
363
|
+
createEffect(() => {
|
|
364
|
+
const id = arrived();
|
|
365
|
+
if (ctx.isRouting())
|
|
366
|
+
return;
|
|
367
|
+
const next = arriving;
|
|
368
|
+
if (!next || next.id !== id || id !== navigationId)
|
|
369
|
+
return;
|
|
370
|
+
arriving = null;
|
|
371
|
+
nextFrame(() => {
|
|
372
|
+
if (id === navigationId)
|
|
373
|
+
scroll.arrive(next.location, next.from);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
}
|
|
322
377
|
// Render depth 0 (a virtual parent at depth -1 so the root reuses the same path).
|
|
323
378
|
const root = {
|
|
324
379
|
depth: -1,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/core v1.0.0-alpha.
|
|
1
|
+
/*! @fluixi/core v1.0.0-alpha.95 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
2
|
|
|
3
3
|
// src/lib/router/index.ts
|
|
4
4
|
import {
|
|
@@ -72,6 +72,64 @@ import {
|
|
|
72
72
|
import { createContext as createContext2, useContext as useContext2 } from "@fluixi/reactive/signal";
|
|
73
73
|
import { get } from "@fluixi/utils/object";
|
|
74
74
|
import { deepEqual } from "@fluixi/utils/compare";
|
|
75
|
+
|
|
76
|
+
// src/lib/router/scroll.ts
|
|
77
|
+
var STORE = "fx:scroll";
|
|
78
|
+
var KEEP = 100;
|
|
79
|
+
function keepScroll(initial) {
|
|
80
|
+
const read2 = () => {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
|
|
83
|
+
} catch {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const positions = read2();
|
|
88
|
+
const write = () => {
|
|
89
|
+
const keys = Object.keys(positions);
|
|
90
|
+
for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
|
|
91
|
+
try {
|
|
92
|
+
sessionStorage.setItem(STORE, JSON.stringify(positions));
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
let current = initial.key;
|
|
97
|
+
const save = () => {
|
|
98
|
+
if (current === void 0) return;
|
|
99
|
+
delete positions[current];
|
|
100
|
+
positions[current] = window.scrollY;
|
|
101
|
+
write();
|
|
102
|
+
};
|
|
103
|
+
const restoration = history.scrollRestoration;
|
|
104
|
+
history.scrollRestoration = "manual";
|
|
105
|
+
window.addEventListener("pagehide", save);
|
|
106
|
+
const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
|
|
107
|
+
const toHash = (hash) => {
|
|
108
|
+
if (!hash || hash === "#") return false;
|
|
109
|
+
const target = document.getElementById(decodeURIComponent(hash.slice(1)));
|
|
110
|
+
target?.scrollIntoView({ behavior: "instant" });
|
|
111
|
+
return target !== null;
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
leave: save,
|
|
115
|
+
arrive(location, from) {
|
|
116
|
+
current = location.key;
|
|
117
|
+
if (location.action === "pop" || from === void 0) {
|
|
118
|
+
const y = location.key === void 0 ? void 0 : positions[location.key];
|
|
119
|
+
if (y !== void 0) to(y);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (location.action === "replace" && location.pathname === from.pathname) return;
|
|
123
|
+
if (!toHash(location.hash)) to(0);
|
|
124
|
+
},
|
|
125
|
+
dispose() {
|
|
126
|
+
window.removeEventListener("pagehide", save);
|
|
127
|
+
history.scrollRestoration = restoration;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/lib/router/index.ts
|
|
75
133
|
import {
|
|
76
134
|
createRouter as createCoreRouter,
|
|
77
135
|
createBrowserHistory,
|
|
@@ -363,9 +421,20 @@ function renderLayer(m, depth, base) {
|
|
|
363
421
|
return router ? router.trackLoad(result) : result;
|
|
364
422
|
});
|
|
365
423
|
onCleanup(onRevalidate(() => void refetch()));
|
|
424
|
+
let loadedFor = untrack2(() => JSON.stringify(base.matched()[depth]?.params ?? {}));
|
|
425
|
+
createEffect(() => {
|
|
426
|
+
const now = base.matched()[depth];
|
|
427
|
+
if (now?.route !== m.route) return;
|
|
428
|
+
const params = JSON.stringify(now.params ?? {});
|
|
429
|
+
if (params === loadedFor) return;
|
|
430
|
+
loadedFor = params;
|
|
431
|
+
untrack2(() => void refetch());
|
|
432
|
+
});
|
|
366
433
|
data = () => {
|
|
367
434
|
const error = resource.error;
|
|
368
435
|
if (error !== void 0 && error !== null) throw error;
|
|
436
|
+
const r = resource;
|
|
437
|
+
if (r.state === "refreshing") return r.latest;
|
|
369
438
|
return resource();
|
|
370
439
|
};
|
|
371
440
|
Object.defineProperty(data, "loading", { get: () => resource.loading });
|
|
@@ -470,16 +539,19 @@ function buildDefaultNotFound() {
|
|
|
470
539
|
}
|
|
471
540
|
var pendingTransitionMode;
|
|
472
541
|
function Router(props) {
|
|
473
|
-
const
|
|
542
|
+
const history2 = props.history ?? createBrowserHistory();
|
|
474
543
|
const router = createCoreRouter({
|
|
475
544
|
routes: props.routes,
|
|
476
545
|
base: props.base,
|
|
477
|
-
history
|
|
546
|
+
history: history2
|
|
478
547
|
});
|
|
479
548
|
const [state, setState] = createSignal2(router.state());
|
|
480
549
|
const [navigating, setNavigating] = createSignal2(false);
|
|
481
550
|
const [loads, setLoads] = createSignal2(0);
|
|
482
551
|
let navigationId = 0;
|
|
552
|
+
const scroll = history2.window ? keepScroll(router.state().location) : void 0;
|
|
553
|
+
let arriving = scroll ? { id: 0, location: router.state().location, from: void 0 } : null;
|
|
554
|
+
const [arrived, setArrived] = createSignal2(0);
|
|
483
555
|
const takeMode = () => {
|
|
484
556
|
const mode = pendingTransitionMode ?? (props.transition ?? "auto");
|
|
485
557
|
pendingTransitionMode = void 0;
|
|
@@ -489,6 +561,10 @@ function Router(props) {
|
|
|
489
561
|
const id = ++navigationId;
|
|
490
562
|
const mode = takeMode();
|
|
491
563
|
setNavigating(true);
|
|
564
|
+
if (scroll) {
|
|
565
|
+
scroll.leave();
|
|
566
|
+
arriving = { id, location: next.location, from: untrack2(state).location };
|
|
567
|
+
}
|
|
492
568
|
const prepared = prepareComponents(
|
|
493
569
|
(next.match?.matched ?? []).map((m) => m.component).filter(Boolean)
|
|
494
570
|
);
|
|
@@ -496,6 +572,7 @@ function Router(props) {
|
|
|
496
572
|
setState(next);
|
|
497
573
|
const flushed = flush();
|
|
498
574
|
if (id === navigationId) setNavigating(false);
|
|
575
|
+
if (scroll) setArrived(id);
|
|
499
576
|
return flushed;
|
|
500
577
|
};
|
|
501
578
|
if (!prepared) {
|
|
@@ -509,6 +586,7 @@ function Router(props) {
|
|
|
509
586
|
});
|
|
510
587
|
onCleanup(() => {
|
|
511
588
|
unsub();
|
|
589
|
+
scroll?.dispose();
|
|
512
590
|
router.destroy();
|
|
513
591
|
navigationId++;
|
|
514
592
|
});
|
|
@@ -531,6 +609,19 @@ function Router(props) {
|
|
|
531
609
|
return result;
|
|
532
610
|
}
|
|
533
611
|
};
|
|
612
|
+
if (scroll) {
|
|
613
|
+
const nextFrame = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn) => setTimeout(fn, 0);
|
|
614
|
+
createEffect(() => {
|
|
615
|
+
const id = arrived();
|
|
616
|
+
if (ctx.isRouting()) return;
|
|
617
|
+
const next = arriving;
|
|
618
|
+
if (!next || next.id !== id || id !== navigationId) return;
|
|
619
|
+
arriving = null;
|
|
620
|
+
nextFrame(() => {
|
|
621
|
+
if (id === navigationId) scroll.arrive(next.location, next.from);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
}
|
|
534
625
|
const root = {
|
|
535
626
|
depth: -1,
|
|
536
627
|
matched,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lib/router/scroll.ts
|
|
21
|
+
var scroll_exports = {};
|
|
22
|
+
__export(scroll_exports, {
|
|
23
|
+
keepScroll: () => keepScroll
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(scroll_exports);
|
|
26
|
+
var STORE = "fx:scroll";
|
|
27
|
+
var KEEP = 100;
|
|
28
|
+
function keepScroll(initial) {
|
|
29
|
+
const read = () => {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
|
|
32
|
+
} catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const positions = read();
|
|
37
|
+
const write = () => {
|
|
38
|
+
const keys = Object.keys(positions);
|
|
39
|
+
for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
|
|
40
|
+
try {
|
|
41
|
+
sessionStorage.setItem(STORE, JSON.stringify(positions));
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
let current = initial.key;
|
|
46
|
+
const save = () => {
|
|
47
|
+
if (current === void 0) return;
|
|
48
|
+
delete positions[current];
|
|
49
|
+
positions[current] = window.scrollY;
|
|
50
|
+
write();
|
|
51
|
+
};
|
|
52
|
+
const restoration = history.scrollRestoration;
|
|
53
|
+
history.scrollRestoration = "manual";
|
|
54
|
+
window.addEventListener("pagehide", save);
|
|
55
|
+
const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
|
|
56
|
+
const toHash = (hash) => {
|
|
57
|
+
if (!hash || hash === "#") return false;
|
|
58
|
+
const target = document.getElementById(decodeURIComponent(hash.slice(1)));
|
|
59
|
+
target?.scrollIntoView({ behavior: "instant" });
|
|
60
|
+
return target !== null;
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
leave: save,
|
|
64
|
+
arrive(location, from) {
|
|
65
|
+
current = location.key;
|
|
66
|
+
if (location.action === "pop" || from === void 0) {
|
|
67
|
+
const y = location.key === void 0 ? void 0 : positions[location.key];
|
|
68
|
+
if (y !== void 0) to(y);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (location.action === "replace" && location.pathname === from.pathname) return;
|
|
72
|
+
if (!toHash(location.hash)) to(0);
|
|
73
|
+
},
|
|
74
|
+
dispose() {
|
|
75
|
+
window.removeEventListener("pagehide", save);
|
|
76
|
+
history.scrollRestoration = restoration;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The window's scroll across client navigations, as a full page load would leave it: a new
|
|
3
|
+
* page starts at the top, or at its `#hash`; back and forward return to where the reader
|
|
4
|
+
* was; a reload stays where it was.
|
|
5
|
+
*
|
|
6
|
+
* The router keeps the current page on screen until the next one has loaded its data, so
|
|
7
|
+
* the browser's own restoration, applied when the URL changes, would scroll the page being
|
|
8
|
+
* left. The router turns it off and scrolls once the next page is shown.
|
|
9
|
+
*/
|
|
10
|
+
import type { RouterLocation } from '@fluixi/router';
|
|
11
|
+
export interface ScrollKeeper {
|
|
12
|
+
/** A navigation started: remember where the entry being left was scrolled to. */
|
|
13
|
+
leave(): void;
|
|
14
|
+
/** The page for `location` is on screen. */
|
|
15
|
+
arrive(location: RouterLocation, from: RouterLocation | undefined): void;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function keepScroll(initial: RouterLocation): ScrollKeeper;
|
|
19
|
+
//# sourceMappingURL=scroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../../../../src/lib/router/scroll.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAMrD,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,KAAK,IAAI,IAAI,CAAC;IACd,4CAA4C;IAC5C,MAAM,CAAC,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI,CAAC;IACzE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,CA4DhE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const STORE = 'fx:scroll';
|
|
2
|
+
// Positions kept for this many entries, the most recent ones.
|
|
3
|
+
const KEEP = 100;
|
|
4
|
+
export function keepScroll(initial) {
|
|
5
|
+
const read = () => {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(sessionStorage.getItem(STORE) ?? '{}');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const positions = read();
|
|
14
|
+
const write = () => {
|
|
15
|
+
const keys = Object.keys(positions);
|
|
16
|
+
for (const key of keys.slice(0, Math.max(0, keys.length - KEEP)))
|
|
17
|
+
delete positions[key];
|
|
18
|
+
try {
|
|
19
|
+
sessionStorage.setItem(STORE, JSON.stringify(positions));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Storage full or refused: positions still hold for this page's life.
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
let current = initial.key;
|
|
26
|
+
const save = () => {
|
|
27
|
+
if (current === undefined)
|
|
28
|
+
return;
|
|
29
|
+
delete positions[current];
|
|
30
|
+
positions[current] = window.scrollY;
|
|
31
|
+
write();
|
|
32
|
+
};
|
|
33
|
+
const restoration = history.scrollRestoration;
|
|
34
|
+
history.scrollRestoration = 'manual';
|
|
35
|
+
// A reload or a tab closed and reopened: the entry keeps its position.
|
|
36
|
+
window.addEventListener('pagehide', save);
|
|
37
|
+
// Instant, as a page load lands, whatever `scroll-behavior` the page's CSS sets: a smooth
|
|
38
|
+
// scroll would animate from where the last page was.
|
|
39
|
+
const to = (top) => window.scrollTo({ top, left: 0, behavior: 'instant' });
|
|
40
|
+
const toHash = (hash) => {
|
|
41
|
+
if (!hash || hash === '#')
|
|
42
|
+
return false;
|
|
43
|
+
const target = document.getElementById(decodeURIComponent(hash.slice(1)));
|
|
44
|
+
target?.scrollIntoView({ behavior: 'instant' });
|
|
45
|
+
return target !== null;
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
leave: save,
|
|
49
|
+
arrive(location, from) {
|
|
50
|
+
current = location.key;
|
|
51
|
+
if (location.action === 'pop' || from === undefined) {
|
|
52
|
+
const y = location.key === undefined ? undefined : positions[location.key];
|
|
53
|
+
if (y !== undefined)
|
|
54
|
+
to(y);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// A replace on the same page (a filter in the query) leaves the reader where they are.
|
|
58
|
+
if (location.action === 'replace' && location.pathname === from.pathname)
|
|
59
|
+
return;
|
|
60
|
+
if (!toHash(location.hash))
|
|
61
|
+
to(0);
|
|
62
|
+
},
|
|
63
|
+
dispose() {
|
|
64
|
+
window.removeEventListener('pagehide', save);
|
|
65
|
+
history.scrollRestoration = restoration;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/lib/router/scroll.ts
|
|
2
|
+
var STORE = "fx:scroll";
|
|
3
|
+
var KEEP = 100;
|
|
4
|
+
function keepScroll(initial) {
|
|
5
|
+
const read = () => {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(sessionStorage.getItem(STORE) ?? "{}");
|
|
8
|
+
} catch {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const positions = read();
|
|
13
|
+
const write = () => {
|
|
14
|
+
const keys = Object.keys(positions);
|
|
15
|
+
for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete positions[key];
|
|
16
|
+
try {
|
|
17
|
+
sessionStorage.setItem(STORE, JSON.stringify(positions));
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
let current = initial.key;
|
|
22
|
+
const save = () => {
|
|
23
|
+
if (current === void 0) return;
|
|
24
|
+
delete positions[current];
|
|
25
|
+
positions[current] = window.scrollY;
|
|
26
|
+
write();
|
|
27
|
+
};
|
|
28
|
+
const restoration = history.scrollRestoration;
|
|
29
|
+
history.scrollRestoration = "manual";
|
|
30
|
+
window.addEventListener("pagehide", save);
|
|
31
|
+
const to = (top) => window.scrollTo({ top, left: 0, behavior: "instant" });
|
|
32
|
+
const toHash = (hash) => {
|
|
33
|
+
if (!hash || hash === "#") return false;
|
|
34
|
+
const target = document.getElementById(decodeURIComponent(hash.slice(1)));
|
|
35
|
+
target?.scrollIntoView({ behavior: "instant" });
|
|
36
|
+
return target !== null;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
leave: save,
|
|
40
|
+
arrive(location, from) {
|
|
41
|
+
current = location.key;
|
|
42
|
+
if (location.action === "pop" || from === void 0) {
|
|
43
|
+
const y = location.key === void 0 ? void 0 : positions[location.key];
|
|
44
|
+
if (y !== void 0) to(y);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (location.action === "replace" && location.pathname === from.pathname) return;
|
|
48
|
+
if (!toHash(location.hash)) to(0);
|
|
49
|
+
},
|
|
50
|
+
dispose() {
|
|
51
|
+
window.removeEventListener("pagehide", save);
|
|
52
|
+
history.scrollRestoration = restoration;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
keepScroll
|
|
58
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/core v1.0.0-alpha.
|
|
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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/core v1.0.0-alpha.
|
|
1
|
+
/*! @fluixi/core v1.0.0-alpha.95 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
2
|
|
|
3
3
|
// src/lib/server/hydration.ts
|
|
4
4
|
import { isServer as domIsServer } from "@fluixi/dom";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/core v1.0.0-alpha.
|
|
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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/core v1.0.0-alpha.
|
|
1
|
+
/*! @fluixi/core v1.0.0-alpha.95 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
2
|
|
|
3
3
|
// src/lib/storage/index.ts
|
|
4
4
|
import { appendResponseHeader, getRequestContext, getServerData } from "@fluixi/dom";
|
package/dist/src/routes.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/core v1.0.0-alpha.
|
|
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;
|
package/dist/src/routes.mjs
CHANGED
package/dist/src/utils.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/core v1.0.0-alpha.
|
|
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;
|
package/dist/src/utils.mjs
CHANGED