tanstack-router-cache 0.1.7 → 0.1.9

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 (50) hide show
  1. package/dist/components/cached-outlet.cjs +12 -0
  2. package/dist/components/cached-outlet.js +12 -0
  3. package/dist/components/off-screen-in.cjs +130 -0
  4. package/dist/components/off-screen-in.js +130 -0
  5. package/dist/components/off-screen.cjs +8 -0
  6. package/dist/components/off-screen.js +8 -0
  7. package/dist/components/restore-cached-href.cjs +28 -0
  8. package/dist/components/restore-cached-href.js +28 -0
  9. package/dist/components/route-cache-manager.cjs +485 -0
  10. package/dist/components/route-cache-manager.js +485 -0
  11. package/dist/components/router-cache-outlet.cjs +9 -0
  12. package/dist/components/router-cache-outlet.js +9 -0
  13. package/dist/contexts/router-cache.cjs +237 -0
  14. package/dist/contexts/router-cache.d.ts +40 -0
  15. package/dist/contexts/router-cache.js +235 -0
  16. package/dist/dom/dismiss-transient-ui.cjs +230 -0
  17. package/dist/dom/dismiss-transient-ui.js +228 -0
  18. package/dist/hooks/use-event-listener.cjs +76 -0
  19. package/dist/hooks/use-event-listener.js +76 -0
  20. package/dist/hooks/use-route-cache-active.cjs +19 -0
  21. package/dist/hooks/use-route-cache-active.js +19 -0
  22. package/dist/hooks/use-route-cache-activity.cjs +12 -0
  23. package/dist/hooks/use-route-cache-activity.js +12 -0
  24. package/dist/hooks/use-route-cache-effect.cjs +38 -0
  25. package/dist/hooks/use-route-cache-effect.js +38 -0
  26. package/dist/hooks/use-route-cache-error-boundary.cjs +23 -0
  27. package/dist/hooks/use-route-cache-error-boundary.js +23 -0
  28. package/dist/hooks/use-route-cache-navigation.cjs +36 -0
  29. package/dist/hooks/use-route-cache-navigation.js +36 -0
  30. package/dist/hooks/use-router-cache-debug.cjs +85 -0
  31. package/dist/hooks/use-router-cache-debug.js +85 -0
  32. package/dist/hooks/use-router-cache.cjs +32 -0
  33. package/dist/hooks/use-router-cache.js +32 -0
  34. package/dist/hooks/use-update.cjs +8 -0
  35. package/dist/hooks/use-update.js +8 -0
  36. package/dist/index.cjs +18 -1402
  37. package/dist/index.d.ts +9 -1
  38. package/dist/index.js +10 -1395
  39. package/dist/pathname.cjs +8 -0
  40. package/dist/pathname.js +8 -0
  41. package/dist/route-cache-static-data.cjs +43 -0
  42. package/dist/route-cache-static-data.d.ts +45 -0
  43. package/dist/route-cache-static-data.js +41 -0
  44. package/dist/types.d.ts +28 -0
  45. package/docs/architecture.md +8 -5
  46. package/docs/cache-behavior.md +17 -0
  47. package/docs/components.md +1 -2
  48. package/docs/getting-started.md +38 -3
  49. package/docs/types.md +6 -0
  50. package/package.json +2 -2
@@ -0,0 +1,12 @@
1
+ let _tanstack_react_router = require("@tanstack/react-router");
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ //#region src/components/cached-outlet.tsx
4
+ function CachedOutlet(props) {
5
+ const { matchId, routerSnapshot } = props;
6
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.RouterContextProvider, {
7
+ router: routerSnapshot,
8
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.Match, { matchId })
9
+ });
10
+ }
11
+ //#endregion
12
+ exports.default = CachedOutlet;
@@ -0,0 +1,12 @@
1
+ import { Match, RouterContextProvider } from "@tanstack/react-router";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/cached-outlet.tsx
4
+ function CachedOutlet(props) {
5
+ const { matchId, routerSnapshot } = props;
6
+ return /* @__PURE__ */ jsx(RouterContextProvider, {
7
+ router: routerSnapshot,
8
+ children: /* @__PURE__ */ jsx(Match, { matchId })
9
+ });
10
+ }
11
+ //#endregion
12
+ export { CachedOutlet as default };
@@ -0,0 +1,130 @@
1
+ const require_dismiss_transient_ui = require("../dom/dismiss-transient-ui.cjs");
2
+ let react = require("react");
3
+ let react_jsx_runtime = require("react/jsx-runtime");
4
+ //#region src/components/off-screen-in.tsx
5
+ const windowScrollPositions = /* @__PURE__ */ new Map();
6
+ const IMMEDIATE_SCROLL_RESTORE_DELAY = 0;
7
+ const EARLY_SCROLL_RESTORE_DELAY = 80;
8
+ const MIDDLE_SCROLL_RESTORE_DELAY = 240;
9
+ const LATE_SCROLL_RESTORE_DELAY = 600;
10
+ const FINAL_SCROLL_RESTORE_DELAY = 1e3;
11
+ const SCROLL_TRACKING_INTERVAL_MS = 200;
12
+ const SCROLL_KEYS = /* @__PURE__ */ new Set([
13
+ " ",
14
+ "ArrowDown",
15
+ "ArrowLeft",
16
+ "ArrowRight",
17
+ "ArrowUp",
18
+ "End",
19
+ "Home",
20
+ "PageDown",
21
+ "PageUp"
22
+ ]);
23
+ const SCROLL_RESTORE_DELAYS = [
24
+ IMMEDIATE_SCROLL_RESTORE_DELAY,
25
+ EARLY_SCROLL_RESTORE_DELAY,
26
+ MIDDLE_SCROLL_RESTORE_DELAY,
27
+ LATE_SCROLL_RESTORE_DELAY,
28
+ FINAL_SCROLL_RESTORE_DELAY
29
+ ];
30
+ function OffScreenIn(props) {
31
+ const { mode, children, containerRef, pathname } = props;
32
+ const localContainerRef = (0, react.useRef)(null);
33
+ const attachContainerRef = (node) => {
34
+ localContainerRef.current = node;
35
+ if (containerRef) containerRef.current = node;
36
+ };
37
+ (0, react.useLayoutEffect)(() => {
38
+ if (typeof document === "undefined") return;
39
+ require_dismiss_transient_ui.initializeTransientUiTracking(document);
40
+ }, []);
41
+ (0, react.useLayoutEffect)(() => {
42
+ if (!pathname) return;
43
+ require_dismiss_transient_ui.syncTransientUiRouteActivity(pathname, mode);
44
+ }, [mode, pathname]);
45
+ (0, react.useLayoutEffect)(() => {
46
+ const visibleContainer = localContainerRef.current;
47
+ return () => {
48
+ if (!pathname || mode !== "visible") return;
49
+ require_dismiss_transient_ui.dismissTransientUi(visibleContainer, pathname);
50
+ };
51
+ }, [mode, pathname]);
52
+ (0, react.useLayoutEffect)(() => {
53
+ if (typeof globalThis === "undefined" || !pathname) return;
54
+ const saveScrollPosition = () => {
55
+ windowScrollPositions.set(pathname, {
56
+ x: globalThis.scrollX,
57
+ y: globalThis.scrollY
58
+ });
59
+ };
60
+ if (mode === "hidden") return;
61
+ const savedPosition = windowScrollPositions.get(pathname);
62
+ let userRequestedScroll = false;
63
+ let scrollTrackingIntervalId;
64
+ const startScrollTracking = () => {
65
+ scrollTrackingIntervalId ??= globalThis.setInterval(saveScrollPosition, SCROLL_TRACKING_INTERVAL_MS);
66
+ };
67
+ const handleScroll = () => {
68
+ if (!savedPosition || userRequestedScroll) saveScrollPosition();
69
+ };
70
+ const markUserRequestedScroll = () => {
71
+ userRequestedScroll = true;
72
+ };
73
+ const handleKeyDown = (event) => {
74
+ if (SCROLL_KEYS.has(event.key)) markUserRequestedScroll();
75
+ };
76
+ globalThis.addEventListener("scroll", handleScroll, { passive: true });
77
+ globalThis.addEventListener("wheel", markUserRequestedScroll, { passive: true });
78
+ globalThis.addEventListener("touchstart", markUserRequestedScroll, { passive: true });
79
+ globalThis.addEventListener("pointerdown", markUserRequestedScroll, { passive: true });
80
+ globalThis.addEventListener("keydown", handleKeyDown);
81
+ if (!savedPosition) {
82
+ startScrollTracking();
83
+ saveScrollPosition();
84
+ return () => {
85
+ if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
86
+ globalThis.removeEventListener("scroll", handleScroll);
87
+ globalThis.removeEventListener("wheel", markUserRequestedScroll);
88
+ globalThis.removeEventListener("touchstart", markUserRequestedScroll);
89
+ globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
90
+ globalThis.removeEventListener("keydown", handleKeyDown);
91
+ };
92
+ }
93
+ const restoreScrollPosition = () => {
94
+ if (userRequestedScroll) return;
95
+ globalThis.scrollTo(savedPosition.x, savedPosition.y);
96
+ saveScrollPosition();
97
+ startScrollTracking();
98
+ };
99
+ const timeoutIds = [];
100
+ let rafId = globalThis.requestAnimationFrame(() => {
101
+ rafId = globalThis.requestAnimationFrame(() => {
102
+ restoreScrollPosition();
103
+ for (const delay of SCROLL_RESTORE_DELAYS) timeoutIds.push(globalThis.setTimeout(restoreScrollPosition, delay));
104
+ });
105
+ });
106
+ return () => {
107
+ globalThis.cancelAnimationFrame(rafId);
108
+ for (const timeoutId of timeoutIds) globalThis.clearTimeout(timeoutId);
109
+ if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
110
+ globalThis.removeEventListener("scroll", handleScroll);
111
+ globalThis.removeEventListener("wheel", markUserRequestedScroll);
112
+ globalThis.removeEventListener("touchstart", markUserRequestedScroll);
113
+ globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
114
+ globalThis.removeEventListener("keydown", handleKeyDown);
115
+ };
116
+ }, [mode, pathname]);
117
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Activity, {
118
+ mode,
119
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
120
+ className: "flex min-h-full w-full flex-1 flex-col",
121
+ "data-router-cache-container": "true",
122
+ "data-router-cache-mode": mode,
123
+ "data-router-cache-pathname": pathname,
124
+ ref: attachContainerRef,
125
+ children
126
+ })
127
+ });
128
+ }
129
+ //#endregion
130
+ exports.default = OffScreenIn;
@@ -0,0 +1,130 @@
1
+ import { dismissTransientUi, initializeTransientUiTracking, syncTransientUiRouteActivity } from "../dom/dismiss-transient-ui.js";
2
+ import { Activity, useLayoutEffect, useRef } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/components/off-screen-in.tsx
5
+ const windowScrollPositions = /* @__PURE__ */ new Map();
6
+ const IMMEDIATE_SCROLL_RESTORE_DELAY = 0;
7
+ const EARLY_SCROLL_RESTORE_DELAY = 80;
8
+ const MIDDLE_SCROLL_RESTORE_DELAY = 240;
9
+ const LATE_SCROLL_RESTORE_DELAY = 600;
10
+ const FINAL_SCROLL_RESTORE_DELAY = 1e3;
11
+ const SCROLL_TRACKING_INTERVAL_MS = 200;
12
+ const SCROLL_KEYS = /* @__PURE__ */ new Set([
13
+ " ",
14
+ "ArrowDown",
15
+ "ArrowLeft",
16
+ "ArrowRight",
17
+ "ArrowUp",
18
+ "End",
19
+ "Home",
20
+ "PageDown",
21
+ "PageUp"
22
+ ]);
23
+ const SCROLL_RESTORE_DELAYS = [
24
+ IMMEDIATE_SCROLL_RESTORE_DELAY,
25
+ EARLY_SCROLL_RESTORE_DELAY,
26
+ MIDDLE_SCROLL_RESTORE_DELAY,
27
+ LATE_SCROLL_RESTORE_DELAY,
28
+ FINAL_SCROLL_RESTORE_DELAY
29
+ ];
30
+ function OffScreenIn(props) {
31
+ const { mode, children, containerRef, pathname } = props;
32
+ const localContainerRef = useRef(null);
33
+ const attachContainerRef = (node) => {
34
+ localContainerRef.current = node;
35
+ if (containerRef) containerRef.current = node;
36
+ };
37
+ useLayoutEffect(() => {
38
+ if (typeof document === "undefined") return;
39
+ initializeTransientUiTracking(document);
40
+ }, []);
41
+ useLayoutEffect(() => {
42
+ if (!pathname) return;
43
+ syncTransientUiRouteActivity(pathname, mode);
44
+ }, [mode, pathname]);
45
+ useLayoutEffect(() => {
46
+ const visibleContainer = localContainerRef.current;
47
+ return () => {
48
+ if (!pathname || mode !== "visible") return;
49
+ dismissTransientUi(visibleContainer, pathname);
50
+ };
51
+ }, [mode, pathname]);
52
+ useLayoutEffect(() => {
53
+ if (typeof globalThis === "undefined" || !pathname) return;
54
+ const saveScrollPosition = () => {
55
+ windowScrollPositions.set(pathname, {
56
+ x: globalThis.scrollX,
57
+ y: globalThis.scrollY
58
+ });
59
+ };
60
+ if (mode === "hidden") return;
61
+ const savedPosition = windowScrollPositions.get(pathname);
62
+ let userRequestedScroll = false;
63
+ let scrollTrackingIntervalId;
64
+ const startScrollTracking = () => {
65
+ scrollTrackingIntervalId ??= globalThis.setInterval(saveScrollPosition, SCROLL_TRACKING_INTERVAL_MS);
66
+ };
67
+ const handleScroll = () => {
68
+ if (!savedPosition || userRequestedScroll) saveScrollPosition();
69
+ };
70
+ const markUserRequestedScroll = () => {
71
+ userRequestedScroll = true;
72
+ };
73
+ const handleKeyDown = (event) => {
74
+ if (SCROLL_KEYS.has(event.key)) markUserRequestedScroll();
75
+ };
76
+ globalThis.addEventListener("scroll", handleScroll, { passive: true });
77
+ globalThis.addEventListener("wheel", markUserRequestedScroll, { passive: true });
78
+ globalThis.addEventListener("touchstart", markUserRequestedScroll, { passive: true });
79
+ globalThis.addEventListener("pointerdown", markUserRequestedScroll, { passive: true });
80
+ globalThis.addEventListener("keydown", handleKeyDown);
81
+ if (!savedPosition) {
82
+ startScrollTracking();
83
+ saveScrollPosition();
84
+ return () => {
85
+ if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
86
+ globalThis.removeEventListener("scroll", handleScroll);
87
+ globalThis.removeEventListener("wheel", markUserRequestedScroll);
88
+ globalThis.removeEventListener("touchstart", markUserRequestedScroll);
89
+ globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
90
+ globalThis.removeEventListener("keydown", handleKeyDown);
91
+ };
92
+ }
93
+ const restoreScrollPosition = () => {
94
+ if (userRequestedScroll) return;
95
+ globalThis.scrollTo(savedPosition.x, savedPosition.y);
96
+ saveScrollPosition();
97
+ startScrollTracking();
98
+ };
99
+ const timeoutIds = [];
100
+ let rafId = globalThis.requestAnimationFrame(() => {
101
+ rafId = globalThis.requestAnimationFrame(() => {
102
+ restoreScrollPosition();
103
+ for (const delay of SCROLL_RESTORE_DELAYS) timeoutIds.push(globalThis.setTimeout(restoreScrollPosition, delay));
104
+ });
105
+ });
106
+ return () => {
107
+ globalThis.cancelAnimationFrame(rafId);
108
+ for (const timeoutId of timeoutIds) globalThis.clearTimeout(timeoutId);
109
+ if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
110
+ globalThis.removeEventListener("scroll", handleScroll);
111
+ globalThis.removeEventListener("wheel", markUserRequestedScroll);
112
+ globalThis.removeEventListener("touchstart", markUserRequestedScroll);
113
+ globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
114
+ globalThis.removeEventListener("keydown", handleKeyDown);
115
+ };
116
+ }, [mode, pathname]);
117
+ return /* @__PURE__ */ jsx(Activity, {
118
+ mode,
119
+ children: /* @__PURE__ */ jsx("div", {
120
+ className: "flex min-h-full w-full flex-1 flex-col",
121
+ "data-router-cache-container": "true",
122
+ "data-router-cache-mode": mode,
123
+ "data-router-cache-pathname": pathname,
124
+ ref: attachContainerRef,
125
+ children
126
+ })
127
+ });
128
+ }
129
+ //#endregion
130
+ export { OffScreenIn as default };
@@ -0,0 +1,8 @@
1
+ const require_off_screen_in = require("./off-screen-in.cjs");
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ //#region src/components/off-screen.tsx
4
+ function OffScreen(props) {
5
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_off_screen_in.default, { ...props });
6
+ }
7
+ //#endregion
8
+ exports.default = OffScreen;
@@ -0,0 +1,8 @@
1
+ import OffScreenIn from "./off-screen-in.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/components/off-screen.tsx
4
+ function OffScreen(props) {
5
+ return /* @__PURE__ */ jsx(OffScreenIn, { ...props });
6
+ }
7
+ //#endregion
8
+ export { OffScreen as default };
@@ -0,0 +1,28 @@
1
+ //#region src/components/restore-cached-href.ts
2
+ const FALLBACK_ORIGIN = "http://tanstack-router-cache.local";
3
+ function parseHref(href) {
4
+ try {
5
+ const url = new URL(href, FALLBACK_ORIGIN);
6
+ return {
7
+ hasHash: url.hash.length > 0,
8
+ hasSearch: url.search.length > 0,
9
+ pathname: url.pathname
10
+ };
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function isBareRouteHref(href, currentPathname) {
16
+ const candidateHref = href ?? currentPathname;
17
+ const parsedHref = parseHref(candidateHref);
18
+ if (!parsedHref) return candidateHref === currentPathname;
19
+ return parsedHref.pathname === currentPathname && !parsedHref.hasSearch && !parsedHref.hasHash;
20
+ }
21
+ function shouldRestoreCachedHref({ cachedHref, currentHref, currentPathname, isRouteCacheEnabled, previousPathname }) {
22
+ if (!(isRouteCacheEnabled && cachedHref)) return false;
23
+ if (!previousPathname || previousPathname === currentPathname) return false;
24
+ if (!isBareRouteHref(currentHref, currentPathname)) return false;
25
+ return cachedHref !== (currentHref ?? currentPathname);
26
+ }
27
+ //#endregion
28
+ exports.shouldRestoreCachedHref = shouldRestoreCachedHref;
@@ -0,0 +1,28 @@
1
+ //#region src/components/restore-cached-href.ts
2
+ const FALLBACK_ORIGIN = "http://tanstack-router-cache.local";
3
+ function parseHref(href) {
4
+ try {
5
+ const url = new URL(href, FALLBACK_ORIGIN);
6
+ return {
7
+ hasHash: url.hash.length > 0,
8
+ hasSearch: url.search.length > 0,
9
+ pathname: url.pathname
10
+ };
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function isBareRouteHref(href, currentPathname) {
16
+ const candidateHref = href ?? currentPathname;
17
+ const parsedHref = parseHref(candidateHref);
18
+ if (!parsedHref) return candidateHref === currentPathname;
19
+ return parsedHref.pathname === currentPathname && !parsedHref.hasSearch && !parsedHref.hasHash;
20
+ }
21
+ function shouldRestoreCachedHref({ cachedHref, currentHref, currentPathname, isRouteCacheEnabled, previousPathname }) {
22
+ if (!(isRouteCacheEnabled && cachedHref)) return false;
23
+ if (!previousPathname || previousPathname === currentPathname) return false;
24
+ if (!isBareRouteHref(currentHref, currentPathname)) return false;
25
+ return cachedHref !== (currentHref ?? currentPathname);
26
+ }
27
+ //#endregion
28
+ export { shouldRestoreCachedHref };