tanstack-router-cache 0.1.1 → 0.1.3
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/README.md +12 -30
- package/dist/index.cjs +78 -25
- package/dist/index.js +78 -25
- package/docs/releases.md +31 -7
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Route view caching for [`@tanstack/react-router`](https://tanstack.com/router).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Keep selected route trees mounted while they are hidden, then restore them when the user navigates back.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
|
|
10
|
+
npm install tanstack-router-cache
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
|
|
14
|
+
bun add tanstack-router-cache
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Requirements
|
|
18
18
|
|
|
19
|
-
Your app should already
|
|
19
|
+
Your app should already install these packages:
|
|
20
20
|
|
|
21
21
|
| Package | Supported versions |
|
|
22
22
|
| --- | --- |
|
|
@@ -24,27 +24,23 @@ Your app should already use React and TanStack Router. This package keeps them a
|
|
|
24
24
|
| `react-dom` | Match your React version. |
|
|
25
25
|
| `@tanstack/react-router` | `>=1.168.14 <2.0.0` |
|
|
26
26
|
|
|
27
|
-
## Maintenance
|
|
28
|
-
|
|
29
|
-
This package is intended to stay compatible with current TanStack Router 1.x releases. The peer dependency floor is tested against the oldest version supported by the current implementation, while development tracks the latest compatible TanStack Router version.
|
|
30
|
-
|
|
31
27
|
## Usage
|
|
32
28
|
|
|
33
|
-
Wrap
|
|
29
|
+
Wrap your route outlet once:
|
|
34
30
|
|
|
35
31
|
```tsx
|
|
36
32
|
import { RouterCacheOutlet, RouterCacheProvider } from "tanstack-router-cache";
|
|
37
33
|
|
|
38
34
|
export function RootRoute() {
|
|
39
35
|
return (
|
|
40
|
-
<RouterCacheProvider
|
|
36
|
+
<RouterCacheProvider>
|
|
41
37
|
<RouterCacheOutlet />
|
|
42
38
|
</RouterCacheProvider>
|
|
43
39
|
);
|
|
44
40
|
}
|
|
45
41
|
```
|
|
46
42
|
|
|
47
|
-
Enable caching on
|
|
43
|
+
Enable caching on any route that should stay mounted:
|
|
48
44
|
|
|
49
45
|
```tsx
|
|
50
46
|
export const Route = createFileRoute("/customers")({
|
|
@@ -55,29 +51,15 @@ export const Route = createFileRoute("/customers")({
|
|
|
55
51
|
});
|
|
56
52
|
```
|
|
57
53
|
|
|
58
|
-
|
|
54
|
+
For the full API, see [docs](./docs).
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
import { useRouteCacheEffect } from "tanstack-router-cache";
|
|
62
|
-
|
|
63
|
-
function CustomersPage() {
|
|
64
|
-
useRouteCacheEffect(() => {
|
|
65
|
-
const controller = new AbortController();
|
|
66
|
-
|
|
67
|
-
return () => {
|
|
68
|
-
controller.abort();
|
|
69
|
-
};
|
|
70
|
-
}, []);
|
|
71
|
-
|
|
72
|
-
return <CustomersTable />;
|
|
73
|
-
}
|
|
74
|
-
```
|
|
56
|
+
## Examples
|
|
75
57
|
|
|
76
|
-
|
|
58
|
+
- [Demo app](https://github.com/santiago-ramos-02/tanstack-router-cache/tree/main/examples/demo): one Vite app with a basic flow for most users and a power-user flow for edge cases.
|
|
77
59
|
|
|
78
60
|
## Acknowledgements
|
|
79
61
|
|
|
80
|
-
This project originated from [`hemengke1997/tanstack-router-keepalive`](https://github.com/hemengke1997/tanstack-router-keepalive)
|
|
62
|
+
This project originated from [`hemengke1997/tanstack-router-keepalive`](https://github.com/hemengke1997/tanstack-router-keepalive), then diverged with current TanStack Router compatibility, a different API, cache limits, error handling, navigation lifecycle tools, dependency updates, and memory-focused eviction.
|
|
81
63
|
|
|
82
64
|
## License
|
|
83
65
|
|
package/dist/index.cjs
CHANGED
|
@@ -131,8 +131,9 @@ function RouterCacheProviderScope({ children, defaultCachedRoutes = EMPTY_CACHED
|
|
|
131
131
|
maxEntries: normalizeLimit(maxEntries),
|
|
132
132
|
maxEntriesPerRouteId: normalizeLimit(maxEntriesPerRouteId)
|
|
133
133
|
});
|
|
134
|
-
const
|
|
135
|
-
|
|
134
|
+
const initialCachedRoutesRef = (0, react.useRef)(null);
|
|
135
|
+
if (initialCachedRoutesRef.current === null) initialCachedRoutesRef.current = applyCachedRouteLimits(filterRouterCacheRoutes(defaultCachedRoutes), cacheConfigRef.current, /* @__PURE__ */ new Set());
|
|
136
|
+
const [cachedRoutes, setCachedRoutes] = (0, react.useState)(() => initialCachedRoutesRef.current ?? EMPTY_CACHED_ROUTES);
|
|
136
137
|
const [erroredRouteCounts, setErroredRouteCounts] = (0, react.useState)({});
|
|
137
138
|
const updateCachedRoutes = (key, value) => {
|
|
138
139
|
setCachedRoutes((state) => getNextCachedRoutesState({
|
|
@@ -340,7 +341,8 @@ function isProduction() {
|
|
|
340
341
|
function useRouterCacheDebug(cachedRoutes, visiblePathname) {
|
|
341
342
|
const warningThresholdRef = (0, react.useRef)(null);
|
|
342
343
|
const lastWarnedCountRef = (0, react.useRef)(null);
|
|
343
|
-
const lastSnapshotRef = (0, react.useRef)(
|
|
344
|
+
const lastSnapshotRef = (0, react.useRef)(null);
|
|
345
|
+
if (lastSnapshotRef.current === null) lastSnapshotRef.current = buildSnapshot(cachedRoutes, visiblePathname);
|
|
344
346
|
const snapshot = (0, react.useMemo)(() => buildSnapshot(cachedRoutes, visiblePathname), [cachedRoutes, visiblePathname]);
|
|
345
347
|
(0, react.useEffect)(() => {
|
|
346
348
|
if (isProduction()) return;
|
|
@@ -351,7 +353,7 @@ function useRouterCacheDebug(cachedRoutes, visiblePathname) {
|
|
|
351
353
|
return lastSnapshotRef.current;
|
|
352
354
|
};
|
|
353
355
|
const api = {
|
|
354
|
-
getSnapshot: () => lastSnapshotRef.current,
|
|
356
|
+
getSnapshot: () => lastSnapshotRef.current ?? snapshot,
|
|
355
357
|
lastSnapshot: snapshot,
|
|
356
358
|
refresh,
|
|
357
359
|
setWarningThreshold: (nextThreshold) => {
|
|
@@ -369,10 +371,7 @@ function useRouterCacheDebug(cachedRoutes, visiblePathname) {
|
|
|
369
371
|
warningThreshold: warningThresholdRef.current
|
|
370
372
|
};
|
|
371
373
|
const warningThreshold = warningThresholdRef.current;
|
|
372
|
-
if (typeof warningThreshold === "number" && nextSnapshot.totalCachedRouteCount > warningThreshold && lastWarnedCountRef.current !== nextSnapshot.totalCachedRouteCount)
|
|
373
|
-
lastWarnedCountRef.current = nextSnapshot.totalCachedRouteCount;
|
|
374
|
-
console.warn("[tanstack-router-cache] cached route count exceeded threshold", nextSnapshot);
|
|
375
|
-
}
|
|
374
|
+
if (typeof warningThreshold === "number" && nextSnapshot.totalCachedRouteCount > warningThreshold && lastWarnedCountRef.current !== nextSnapshot.totalCachedRouteCount) lastWarnedCountRef.current = nextSnapshot.totalCachedRouteCount;
|
|
376
375
|
});
|
|
377
376
|
return () => {
|
|
378
377
|
globalThis.cancelAnimationFrame(rafId);
|
|
@@ -623,6 +622,30 @@ function dismissTransientUi(container, pathname) {
|
|
|
623
622
|
//#endregion
|
|
624
623
|
//#region src/components/off-screen-in.tsx
|
|
625
624
|
const windowScrollPositions = /* @__PURE__ */ new Map();
|
|
625
|
+
const IMMEDIATE_SCROLL_RESTORE_DELAY = 0;
|
|
626
|
+
const EARLY_SCROLL_RESTORE_DELAY = 80;
|
|
627
|
+
const MIDDLE_SCROLL_RESTORE_DELAY = 240;
|
|
628
|
+
const LATE_SCROLL_RESTORE_DELAY = 600;
|
|
629
|
+
const FINAL_SCROLL_RESTORE_DELAY = 1e3;
|
|
630
|
+
const SCROLL_TRACKING_INTERVAL_MS = 200;
|
|
631
|
+
const SCROLL_KEYS = new Set([
|
|
632
|
+
" ",
|
|
633
|
+
"ArrowDown",
|
|
634
|
+
"ArrowLeft",
|
|
635
|
+
"ArrowRight",
|
|
636
|
+
"ArrowUp",
|
|
637
|
+
"End",
|
|
638
|
+
"Home",
|
|
639
|
+
"PageDown",
|
|
640
|
+
"PageUp"
|
|
641
|
+
]);
|
|
642
|
+
const SCROLL_RESTORE_DELAYS = [
|
|
643
|
+
IMMEDIATE_SCROLL_RESTORE_DELAY,
|
|
644
|
+
EARLY_SCROLL_RESTORE_DELAY,
|
|
645
|
+
MIDDLE_SCROLL_RESTORE_DELAY,
|
|
646
|
+
LATE_SCROLL_RESTORE_DELAY,
|
|
647
|
+
FINAL_SCROLL_RESTORE_DELAY
|
|
648
|
+
];
|
|
626
649
|
function OffScreenIn(props) {
|
|
627
650
|
const { mode, children, containerRef, pathname } = props;
|
|
628
651
|
const localContainerRef = (0, react.useRef)(null);
|
|
@@ -653,26 +676,61 @@ function OffScreenIn(props) {
|
|
|
653
676
|
y: globalThis.scrollY
|
|
654
677
|
});
|
|
655
678
|
};
|
|
656
|
-
if (mode === "hidden")
|
|
657
|
-
saveScrollPosition();
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
679
|
+
if (mode === "hidden") return;
|
|
660
680
|
const savedPosition = windowScrollPositions.get(pathname);
|
|
661
|
-
|
|
681
|
+
let userRequestedScroll = false;
|
|
682
|
+
let scrollTrackingIntervalId;
|
|
683
|
+
const startScrollTracking = () => {
|
|
684
|
+
scrollTrackingIntervalId ??= globalThis.setInterval(saveScrollPosition, SCROLL_TRACKING_INTERVAL_MS);
|
|
685
|
+
};
|
|
686
|
+
const handleScroll = () => {
|
|
687
|
+
if (!savedPosition || userRequestedScroll) saveScrollPosition();
|
|
688
|
+
};
|
|
689
|
+
const markUserRequestedScroll = () => {
|
|
690
|
+
userRequestedScroll = true;
|
|
691
|
+
};
|
|
692
|
+
const handleKeyDown = (event) => {
|
|
693
|
+
if (SCROLL_KEYS.has(event.key)) markUserRequestedScroll();
|
|
694
|
+
};
|
|
662
695
|
globalThis.addEventListener("scroll", handleScroll, { passive: true });
|
|
696
|
+
globalThis.addEventListener("wheel", markUserRequestedScroll, { passive: true });
|
|
697
|
+
globalThis.addEventListener("touchstart", markUserRequestedScroll, { passive: true });
|
|
698
|
+
globalThis.addEventListener("pointerdown", markUserRequestedScroll, { passive: true });
|
|
699
|
+
globalThis.addEventListener("keydown", handleKeyDown);
|
|
663
700
|
if (!savedPosition) {
|
|
701
|
+
startScrollTracking();
|
|
664
702
|
saveScrollPosition();
|
|
665
|
-
return () =>
|
|
703
|
+
return () => {
|
|
704
|
+
if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
|
|
705
|
+
globalThis.removeEventListener("scroll", handleScroll);
|
|
706
|
+
globalThis.removeEventListener("wheel", markUserRequestedScroll);
|
|
707
|
+
globalThis.removeEventListener("touchstart", markUserRequestedScroll);
|
|
708
|
+
globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
|
|
709
|
+
globalThis.removeEventListener("keydown", handleKeyDown);
|
|
710
|
+
};
|
|
666
711
|
}
|
|
712
|
+
const restoreScrollPosition = () => {
|
|
713
|
+
if (userRequestedScroll) return;
|
|
714
|
+
globalThis.scrollTo(savedPosition.x, savedPosition.y);
|
|
715
|
+
saveScrollPosition();
|
|
716
|
+
startScrollTracking();
|
|
717
|
+
};
|
|
718
|
+
const timeoutIds = [];
|
|
667
719
|
let rafId = globalThis.requestAnimationFrame(() => {
|
|
668
720
|
rafId = globalThis.requestAnimationFrame(() => {
|
|
669
|
-
|
|
670
|
-
|
|
721
|
+
restoreScrollPosition();
|
|
722
|
+
for (const delay of SCROLL_RESTORE_DELAYS) timeoutIds.push(globalThis.setTimeout(restoreScrollPosition, delay));
|
|
671
723
|
});
|
|
672
724
|
});
|
|
673
725
|
return () => {
|
|
674
726
|
globalThis.cancelAnimationFrame(rafId);
|
|
727
|
+
for (const timeoutId of timeoutIds) globalThis.clearTimeout(timeoutId);
|
|
728
|
+
if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
|
|
675
729
|
globalThis.removeEventListener("scroll", handleScroll);
|
|
730
|
+
globalThis.removeEventListener("wheel", markUserRequestedScroll);
|
|
731
|
+
globalThis.removeEventListener("touchstart", markUserRequestedScroll);
|
|
732
|
+
globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
|
|
733
|
+
globalThis.removeEventListener("keydown", handleKeyDown);
|
|
676
734
|
};
|
|
677
735
|
}, [mode, pathname]);
|
|
678
736
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Activity, {
|
|
@@ -742,15 +800,8 @@ function getLiveRouterMethodDescriptors(router) {
|
|
|
742
800
|
return [[methodName, { value: method.bind(router) }]];
|
|
743
801
|
}));
|
|
744
802
|
}
|
|
745
|
-
function isRouterSearch(value) {
|
|
746
|
-
return typeof value === "object" && value !== null;
|
|
747
|
-
}
|
|
748
803
|
function toRouterLocation(location) {
|
|
749
|
-
return {
|
|
750
|
-
href: location.href,
|
|
751
|
-
pathname: location.pathname,
|
|
752
|
-
search: isRouterSearch(location.search) ? location.search : void 0
|
|
753
|
-
};
|
|
804
|
+
return { ...location };
|
|
754
805
|
}
|
|
755
806
|
function isReadyCachedRoute(route) {
|
|
756
807
|
return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.matchId && route.routerSnapshot);
|
|
@@ -962,7 +1013,8 @@ function RouteCacheManager() {
|
|
|
962
1013
|
const pendingCachedNavigationRef = (0, react.useRef)(null);
|
|
963
1014
|
const previousPathnameRef = (0, react.useRef)(void 0);
|
|
964
1015
|
const previousHrefRef = (0, react.useRef)(void 0);
|
|
965
|
-
const previousRouteCacheModesRef = (0, react.useRef)(
|
|
1016
|
+
const previousRouteCacheModesRef = (0, react.useRef)(null);
|
|
1017
|
+
if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
|
|
966
1018
|
const previousVisiblePathnameRef = (0, react.useRef)(void 0);
|
|
967
1019
|
const routerLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => toRouterLocation(state.location) });
|
|
968
1020
|
const routerHref = routerLocation.href;
|
|
@@ -1058,6 +1110,7 @@ function RouteCacheManager() {
|
|
|
1058
1110
|
visiblePathname
|
|
1059
1111
|
]);
|
|
1060
1112
|
(0, react.useLayoutEffect)(() => {
|
|
1113
|
+
if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
|
|
1061
1114
|
previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
|
|
1062
1115
|
cachedRoutes,
|
|
1063
1116
|
eventListener,
|
package/dist/index.js
CHANGED
|
@@ -130,8 +130,9 @@ function RouterCacheProviderScope({ children, defaultCachedRoutes = EMPTY_CACHED
|
|
|
130
130
|
maxEntries: normalizeLimit(maxEntries),
|
|
131
131
|
maxEntriesPerRouteId: normalizeLimit(maxEntriesPerRouteId)
|
|
132
132
|
});
|
|
133
|
-
const
|
|
134
|
-
|
|
133
|
+
const initialCachedRoutesRef = useRef(null);
|
|
134
|
+
if (initialCachedRoutesRef.current === null) initialCachedRoutesRef.current = applyCachedRouteLimits(filterRouterCacheRoutes(defaultCachedRoutes), cacheConfigRef.current, /* @__PURE__ */ new Set());
|
|
135
|
+
const [cachedRoutes, setCachedRoutes] = useState(() => initialCachedRoutesRef.current ?? EMPTY_CACHED_ROUTES);
|
|
135
136
|
const [erroredRouteCounts, setErroredRouteCounts] = useState({});
|
|
136
137
|
const updateCachedRoutes = (key, value) => {
|
|
137
138
|
setCachedRoutes((state) => getNextCachedRoutesState({
|
|
@@ -339,7 +340,8 @@ function isProduction() {
|
|
|
339
340
|
function useRouterCacheDebug(cachedRoutes, visiblePathname) {
|
|
340
341
|
const warningThresholdRef = useRef(null);
|
|
341
342
|
const lastWarnedCountRef = useRef(null);
|
|
342
|
-
const lastSnapshotRef = useRef(
|
|
343
|
+
const lastSnapshotRef = useRef(null);
|
|
344
|
+
if (lastSnapshotRef.current === null) lastSnapshotRef.current = buildSnapshot(cachedRoutes, visiblePathname);
|
|
343
345
|
const snapshot = useMemo(() => buildSnapshot(cachedRoutes, visiblePathname), [cachedRoutes, visiblePathname]);
|
|
344
346
|
useEffect(() => {
|
|
345
347
|
if (isProduction()) return;
|
|
@@ -350,7 +352,7 @@ function useRouterCacheDebug(cachedRoutes, visiblePathname) {
|
|
|
350
352
|
return lastSnapshotRef.current;
|
|
351
353
|
};
|
|
352
354
|
const api = {
|
|
353
|
-
getSnapshot: () => lastSnapshotRef.current,
|
|
355
|
+
getSnapshot: () => lastSnapshotRef.current ?? snapshot,
|
|
354
356
|
lastSnapshot: snapshot,
|
|
355
357
|
refresh,
|
|
356
358
|
setWarningThreshold: (nextThreshold) => {
|
|
@@ -368,10 +370,7 @@ function useRouterCacheDebug(cachedRoutes, visiblePathname) {
|
|
|
368
370
|
warningThreshold: warningThresholdRef.current
|
|
369
371
|
};
|
|
370
372
|
const warningThreshold = warningThresholdRef.current;
|
|
371
|
-
if (typeof warningThreshold === "number" && nextSnapshot.totalCachedRouteCount > warningThreshold && lastWarnedCountRef.current !== nextSnapshot.totalCachedRouteCount)
|
|
372
|
-
lastWarnedCountRef.current = nextSnapshot.totalCachedRouteCount;
|
|
373
|
-
console.warn("[tanstack-router-cache] cached route count exceeded threshold", nextSnapshot);
|
|
374
|
-
}
|
|
373
|
+
if (typeof warningThreshold === "number" && nextSnapshot.totalCachedRouteCount > warningThreshold && lastWarnedCountRef.current !== nextSnapshot.totalCachedRouteCount) lastWarnedCountRef.current = nextSnapshot.totalCachedRouteCount;
|
|
375
374
|
});
|
|
376
375
|
return () => {
|
|
377
376
|
globalThis.cancelAnimationFrame(rafId);
|
|
@@ -622,6 +621,30 @@ function dismissTransientUi(container, pathname) {
|
|
|
622
621
|
//#endregion
|
|
623
622
|
//#region src/components/off-screen-in.tsx
|
|
624
623
|
const windowScrollPositions = /* @__PURE__ */ new Map();
|
|
624
|
+
const IMMEDIATE_SCROLL_RESTORE_DELAY = 0;
|
|
625
|
+
const EARLY_SCROLL_RESTORE_DELAY = 80;
|
|
626
|
+
const MIDDLE_SCROLL_RESTORE_DELAY = 240;
|
|
627
|
+
const LATE_SCROLL_RESTORE_DELAY = 600;
|
|
628
|
+
const FINAL_SCROLL_RESTORE_DELAY = 1e3;
|
|
629
|
+
const SCROLL_TRACKING_INTERVAL_MS = 200;
|
|
630
|
+
const SCROLL_KEYS = new Set([
|
|
631
|
+
" ",
|
|
632
|
+
"ArrowDown",
|
|
633
|
+
"ArrowLeft",
|
|
634
|
+
"ArrowRight",
|
|
635
|
+
"ArrowUp",
|
|
636
|
+
"End",
|
|
637
|
+
"Home",
|
|
638
|
+
"PageDown",
|
|
639
|
+
"PageUp"
|
|
640
|
+
]);
|
|
641
|
+
const SCROLL_RESTORE_DELAYS = [
|
|
642
|
+
IMMEDIATE_SCROLL_RESTORE_DELAY,
|
|
643
|
+
EARLY_SCROLL_RESTORE_DELAY,
|
|
644
|
+
MIDDLE_SCROLL_RESTORE_DELAY,
|
|
645
|
+
LATE_SCROLL_RESTORE_DELAY,
|
|
646
|
+
FINAL_SCROLL_RESTORE_DELAY
|
|
647
|
+
];
|
|
625
648
|
function OffScreenIn(props) {
|
|
626
649
|
const { mode, children, containerRef, pathname } = props;
|
|
627
650
|
const localContainerRef = useRef(null);
|
|
@@ -652,26 +675,61 @@ function OffScreenIn(props) {
|
|
|
652
675
|
y: globalThis.scrollY
|
|
653
676
|
});
|
|
654
677
|
};
|
|
655
|
-
if (mode === "hidden")
|
|
656
|
-
saveScrollPosition();
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
678
|
+
if (mode === "hidden") return;
|
|
659
679
|
const savedPosition = windowScrollPositions.get(pathname);
|
|
660
|
-
|
|
680
|
+
let userRequestedScroll = false;
|
|
681
|
+
let scrollTrackingIntervalId;
|
|
682
|
+
const startScrollTracking = () => {
|
|
683
|
+
scrollTrackingIntervalId ??= globalThis.setInterval(saveScrollPosition, SCROLL_TRACKING_INTERVAL_MS);
|
|
684
|
+
};
|
|
685
|
+
const handleScroll = () => {
|
|
686
|
+
if (!savedPosition || userRequestedScroll) saveScrollPosition();
|
|
687
|
+
};
|
|
688
|
+
const markUserRequestedScroll = () => {
|
|
689
|
+
userRequestedScroll = true;
|
|
690
|
+
};
|
|
691
|
+
const handleKeyDown = (event) => {
|
|
692
|
+
if (SCROLL_KEYS.has(event.key)) markUserRequestedScroll();
|
|
693
|
+
};
|
|
661
694
|
globalThis.addEventListener("scroll", handleScroll, { passive: true });
|
|
695
|
+
globalThis.addEventListener("wheel", markUserRequestedScroll, { passive: true });
|
|
696
|
+
globalThis.addEventListener("touchstart", markUserRequestedScroll, { passive: true });
|
|
697
|
+
globalThis.addEventListener("pointerdown", markUserRequestedScroll, { passive: true });
|
|
698
|
+
globalThis.addEventListener("keydown", handleKeyDown);
|
|
662
699
|
if (!savedPosition) {
|
|
700
|
+
startScrollTracking();
|
|
663
701
|
saveScrollPosition();
|
|
664
|
-
return () =>
|
|
702
|
+
return () => {
|
|
703
|
+
if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
|
|
704
|
+
globalThis.removeEventListener("scroll", handleScroll);
|
|
705
|
+
globalThis.removeEventListener("wheel", markUserRequestedScroll);
|
|
706
|
+
globalThis.removeEventListener("touchstart", markUserRequestedScroll);
|
|
707
|
+
globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
|
|
708
|
+
globalThis.removeEventListener("keydown", handleKeyDown);
|
|
709
|
+
};
|
|
665
710
|
}
|
|
711
|
+
const restoreScrollPosition = () => {
|
|
712
|
+
if (userRequestedScroll) return;
|
|
713
|
+
globalThis.scrollTo(savedPosition.x, savedPosition.y);
|
|
714
|
+
saveScrollPosition();
|
|
715
|
+
startScrollTracking();
|
|
716
|
+
};
|
|
717
|
+
const timeoutIds = [];
|
|
666
718
|
let rafId = globalThis.requestAnimationFrame(() => {
|
|
667
719
|
rafId = globalThis.requestAnimationFrame(() => {
|
|
668
|
-
|
|
669
|
-
|
|
720
|
+
restoreScrollPosition();
|
|
721
|
+
for (const delay of SCROLL_RESTORE_DELAYS) timeoutIds.push(globalThis.setTimeout(restoreScrollPosition, delay));
|
|
670
722
|
});
|
|
671
723
|
});
|
|
672
724
|
return () => {
|
|
673
725
|
globalThis.cancelAnimationFrame(rafId);
|
|
726
|
+
for (const timeoutId of timeoutIds) globalThis.clearTimeout(timeoutId);
|
|
727
|
+
if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
|
|
674
728
|
globalThis.removeEventListener("scroll", handleScroll);
|
|
729
|
+
globalThis.removeEventListener("wheel", markUserRequestedScroll);
|
|
730
|
+
globalThis.removeEventListener("touchstart", markUserRequestedScroll);
|
|
731
|
+
globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
|
|
732
|
+
globalThis.removeEventListener("keydown", handleKeyDown);
|
|
675
733
|
};
|
|
676
734
|
}, [mode, pathname]);
|
|
677
735
|
return /* @__PURE__ */ jsx(Activity, {
|
|
@@ -741,15 +799,8 @@ function getLiveRouterMethodDescriptors(router) {
|
|
|
741
799
|
return [[methodName, { value: method.bind(router) }]];
|
|
742
800
|
}));
|
|
743
801
|
}
|
|
744
|
-
function isRouterSearch(value) {
|
|
745
|
-
return typeof value === "object" && value !== null;
|
|
746
|
-
}
|
|
747
802
|
function toRouterLocation(location) {
|
|
748
|
-
return {
|
|
749
|
-
href: location.href,
|
|
750
|
-
pathname: location.pathname,
|
|
751
|
-
search: isRouterSearch(location.search) ? location.search : void 0
|
|
752
|
-
};
|
|
803
|
+
return { ...location };
|
|
753
804
|
}
|
|
754
805
|
function isReadyCachedRoute(route) {
|
|
755
806
|
return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.matchId && route.routerSnapshot);
|
|
@@ -961,7 +1012,8 @@ function RouteCacheManager() {
|
|
|
961
1012
|
const pendingCachedNavigationRef = useRef(null);
|
|
962
1013
|
const previousPathnameRef = useRef(void 0);
|
|
963
1014
|
const previousHrefRef = useRef(void 0);
|
|
964
|
-
const previousRouteCacheModesRef = useRef(
|
|
1015
|
+
const previousRouteCacheModesRef = useRef(null);
|
|
1016
|
+
if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
|
|
965
1017
|
const previousVisiblePathnameRef = useRef(void 0);
|
|
966
1018
|
const routerLocation = useRouterState({ select: (state) => toRouterLocation(state.location) });
|
|
967
1019
|
const routerHref = routerLocation.href;
|
|
@@ -1057,6 +1109,7 @@ function RouteCacheManager() {
|
|
|
1057
1109
|
visiblePathname
|
|
1058
1110
|
]);
|
|
1059
1111
|
useLayoutEffect(() => {
|
|
1112
|
+
if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
|
|
1060
1113
|
previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
|
|
1061
1114
|
cachedRoutes,
|
|
1062
1115
|
eventListener,
|
package/docs/releases.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
-
This package publishes from GitHub Actions after a version tag is pushed.
|
|
3
|
+
This package publishes from GitHub Actions after a version tag is pushed. A regular commit to `main` only runs CI and updates the GitHub repo. It does not publish to npm.
|
|
4
4
|
|
|
5
5
|
## Trusted publishing
|
|
6
6
|
|
|
@@ -14,13 +14,37 @@ npm trust github tanstack-router-cache --repo santiago-ramos-02/tanstack-router-
|
|
|
14
14
|
|
|
15
15
|
## Publishing a version
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
2. Commit the change.
|
|
19
|
-
3. Tag the same version:
|
|
17
|
+
Work in the package repo:
|
|
20
18
|
|
|
21
19
|
```sh
|
|
22
|
-
|
|
23
|
-
git push origin main --tags
|
|
20
|
+
cd packages/tanstack-router-cache
|
|
24
21
|
```
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
For normal changes:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
bun run check
|
|
27
|
+
git add .
|
|
28
|
+
git commit -m "Your change"
|
|
29
|
+
git push origin main
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That updates GitHub and runs CI, but it does not publish to npm.
|
|
33
|
+
|
|
34
|
+
For a release, start from a clean `main` branch after your changes are already committed:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
bun run release:prepare patch
|
|
38
|
+
bun run release:push
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use `minor`, `major`, or an exact version when needed:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
bun run release:prepare minor
|
|
45
|
+
bun run release:prepare 0.2.0
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`release:prepare` updates `package.json`, runs the package checks, commits the version bump, and creates the matching tag locally. `release:push` pushes `main` and that exact tag. The tag starts the npm publish workflow.
|
|
49
|
+
|
|
50
|
+
The workflow checks that the tag matches `package.json` before publishing, so a mismatched tag cannot publish accidentally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tanstack-router-cache",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Route view caching for TanStack Router.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,9 +46,12 @@
|
|
|
46
46
|
"build": "bun run clean && rolldown -c && tsc",
|
|
47
47
|
"typecheck": "tsc --noEmit",
|
|
48
48
|
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
49
|
-
"lint": "biome
|
|
50
|
-
"lint:fix": "biome
|
|
51
|
-
"
|
|
49
|
+
"lint": "biome lint src scripts .github/scripts rolldown.config.ts --skip=lint/performance/noBarrelFile --skip=lint/style/useConsistentTypeDefinitions --skip=lint/suspicious/noConsole",
|
|
50
|
+
"lint:fix": "biome lint src scripts .github/scripts rolldown.config.ts --write --skip=lint/performance/noBarrelFile --skip=lint/style/useConsistentTypeDefinitions --skip=lint/suspicious/noConsole",
|
|
51
|
+
"check": "bun run lint && bun run typecheck && bun run build && bun run pack:dry-run",
|
|
52
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
53
|
+
"release:prepare": "node scripts/release.mjs prepare",
|
|
54
|
+
"release:push": "node scripts/release.mjs push",
|
|
52
55
|
"prepack": "bun run build",
|
|
53
56
|
"prepublishOnly": "bun run lint && bun run typecheck"
|
|
54
57
|
},
|