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
package/dist/index.cjs CHANGED
@@ -1,1403 +1,19 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let _tanstack_react_router = require("@tanstack/react-router");
3
- let react = require("react");
4
- let react_jsx_runtime = require("react/jsx-runtime");
5
- //#region src/pathname.ts
6
- const TRAILING_SLASHES_REGEX = /\/+$/u;
7
- function normalizeCachedRoutePathname(pathname) {
8
- if (pathname === "/") return pathname;
9
- return pathname.replace(TRAILING_SLASHES_REGEX, "");
10
- }
11
- //#endregion
12
- //#region src/contexts/router-cache.tsx
13
- const DEFAULT_MAX_ENTRIES = Number.POSITIVE_INFINITY;
14
- const EMPTY_CACHED_ROUTES = {};
15
- const RouterCacheContext = (0, react.createContext)(null);
16
- function shallowEqualRouteStaticData(current, next) {
17
- if (current === next) return true;
18
- const currentEntries = Object.entries({ ...current });
19
- const nextEntries = Object.entries({ ...next });
20
- if (currentEntries.length !== nextEntries.length) return false;
21
- const nextValuesByKey = new Map(nextEntries);
22
- for (const [key, value] of currentEntries) if (!Object.is(value, nextValuesByKey.get(key))) return false;
23
- return true;
24
- }
25
- function isCacheEnabledRouteData(route) {
26
- return route?.staticData.routeCache === true;
27
- }
28
- function filterRouterCacheRoutes(routes) {
29
- return Object.fromEntries(Object.entries(routes).flatMap(([pathname, route]) => isCacheEnabledRouteData(route) ? [[normalizeCachedRoutePathname(pathname), route]] : []));
30
- }
31
- function isSameCachedRouteData(current, next) {
32
- if (!current) return false;
33
- return current.routeId === next.routeId && current.href === next.href && current.matchId === next.matchId && current.ready === next.ready && current.routerSnapshot === next.routerSnapshot && shallowEqualRouteStaticData(current.staticData, next.staticData);
34
- }
35
- function normalizeLimit(limit) {
36
- if (typeof limit !== "number" || Number.isNaN(limit)) return DEFAULT_MAX_ENTRIES;
37
- if (!Number.isFinite(limit)) return DEFAULT_MAX_ENTRIES;
38
- return Math.max(Math.trunc(limit), 0);
39
- }
40
- function getCachedRouteTimestamp(route) {
41
- return route.lastVisibleAt ?? route.createdAt ?? 0;
42
- }
43
- function sortCachedRouteEntries([leftPathname, leftRoute], [rightPathname, rightRoute]) {
44
- const timestampDifference = getCachedRouteTimestamp(leftRoute) - getCachedRouteTimestamp(rightRoute);
45
- if (timestampDifference !== 0) return timestampDifference;
46
- return leftPathname.localeCompare(rightPathname);
47
- }
48
- function getUnmarkedCachedRouteEntries(routes, keysToDelete) {
49
- return Object.entries(routes).filter(([pathname]) => !keysToDelete.has(pathname));
50
- }
51
- function getEntriesByRouteId(entries) {
52
- const entriesByRouteId = /* @__PURE__ */ new Map();
53
- for (const entry of entries) {
54
- const routeId = entry[1].routeId;
55
- if (!routeId) continue;
56
- const existingEntries = entriesByRouteId.get(routeId);
57
- if (existingEntries) {
58
- existingEntries.push(entry);
59
- continue;
60
- }
61
- entriesByRouteId.set(routeId, [entry]);
62
- }
63
- return entriesByRouteId;
64
- }
65
- function markEntriesForDeletion(entries, keysToDelete, protectedKeys, excessEntryCount) {
66
- if (excessEntryCount <= 0) return;
67
- const evictableEntries = entries.filter(([pathname]) => !protectedKeys.has(pathname)).sort(sortCachedRouteEntries);
68
- let remainingExcess = excessEntryCount;
69
- for (const [pathname] of evictableEntries) {
70
- if (remainingExcess <= 0) break;
71
- if (!keysToDelete.has(pathname)) {
72
- keysToDelete.add(pathname);
73
- remainingExcess -= 1;
74
- }
75
- }
76
- }
77
- function applyPerRouteEntryLimit(routes, maxEntriesPerRouteId, keysToDelete, protectedKeys) {
78
- if (maxEntriesPerRouteId === DEFAULT_MAX_ENTRIES) return;
79
- const entriesByRouteId = getEntriesByRouteId(getUnmarkedCachedRouteEntries(routes, keysToDelete));
80
- for (const entries of entriesByRouteId.values()) markEntriesForDeletion(entries, keysToDelete, protectedKeys, entries.length - maxEntriesPerRouteId);
81
- }
82
- function applyGlobalEntryLimit(routes, maxEntries, keysToDelete, protectedKeys) {
83
- if (maxEntries === DEFAULT_MAX_ENTRIES) return;
84
- const remainingEntries = getUnmarkedCachedRouteEntries(routes, keysToDelete);
85
- markEntriesForDeletion(remainingEntries, keysToDelete, protectedKeys, remainingEntries.length - maxEntries);
86
- }
87
- function applyCachedRouteLimits(routes, config, protectedKeys) {
88
- if (config.maxEntries === DEFAULT_MAX_ENTRIES && config.maxEntriesPerRouteId === DEFAULT_MAX_ENTRIES) return routes;
89
- const keysToDelete = /* @__PURE__ */ new Set();
90
- applyPerRouteEntryLimit(routes, config.maxEntriesPerRouteId, keysToDelete, protectedKeys);
91
- applyGlobalEntryLimit(routes, config.maxEntries, keysToDelete, protectedKeys);
92
- if (keysToDelete.size === 0) return routes;
93
- const nextRoutes = { ...routes };
94
- for (const pathname of keysToDelete) delete nextRoutes[pathname];
95
- return nextRoutes;
96
- }
97
- function createCachedRouteData(current, next) {
98
- const now = Date.now();
99
- return {
100
- ...next,
101
- createdAt: current?.createdAt ?? next.createdAt ?? now,
102
- lastVisibleAt: next.lastVisibleAt ?? now
103
- };
104
- }
105
- function getNextCachedRoutesState(params) {
106
- const normalizedKey = normalizeCachedRoutePathname(params.key);
107
- if (params.cacheConfig.maxEntries === 0) return params.state === EMPTY_CACHED_ROUTES ? params.state : EMPTY_CACHED_ROUTES;
108
- if (!isCacheEnabledRouteData(params.value)) {
109
- if (!Object.hasOwn(params.state, normalizedKey)) return params.state;
110
- const nextState = { ...params.state };
111
- delete nextState[normalizedKey];
112
- return nextState;
113
- }
114
- const nextRouteData = createCachedRouteData(params.state[normalizedKey], params.value);
115
- if (isSameCachedRouteData(params.state[normalizedKey], nextRouteData)) return params.state;
116
- return applyCachedRouteLimits({
117
- ...params.state,
118
- [normalizedKey]: nextRouteData
119
- }, params.cacheConfig, new Set([normalizedKey]));
120
- }
121
- function RouterCacheProvider({ cacheScopeKey = "__default__", children, defaultCachedRoutes = EMPTY_CACHED_ROUTES, maxEntries = DEFAULT_MAX_ENTRIES, maxEntriesPerRouteId = DEFAULT_MAX_ENTRIES }) {
122
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouterCacheProviderScope, {
123
- defaultCachedRoutes,
124
- maxEntries,
125
- maxEntriesPerRouteId,
126
- children
127
- }, cacheScopeKey ?? "__default__");
128
- }
129
- function RouterCacheProviderScope({ children, defaultCachedRoutes = EMPTY_CACHED_ROUTES, maxEntries, maxEntriesPerRouteId }) {
130
- const cacheConfigRef = (0, react.useRef)({
131
- maxEntries: normalizeLimit(maxEntries),
132
- maxEntriesPerRouteId: normalizeLimit(maxEntriesPerRouteId)
133
- });
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);
137
- const [erroredRouteCounts, setErroredRouteCounts] = (0, react.useState)({});
138
- const updateCachedRoutes = (key, value) => {
139
- setCachedRoutes((state) => getNextCachedRoutesState({
140
- cacheConfig: cacheConfigRef.current,
141
- key,
142
- state,
143
- value
144
- }));
145
- };
146
- const deleteCachedRoutes = (keys) => {
147
- setCachedRoutes((state) => {
148
- let changed = false;
149
- const newState = { ...state };
150
- for (const key of keys) {
151
- const normalizedKey = normalizeCachedRoutePathname(key);
152
- if (Object.hasOwn(newState, normalizedKey)) {
153
- delete newState[normalizedKey];
154
- changed = true;
155
- }
156
- }
157
- return changed ? newState : state;
158
- });
159
- };
160
- const touchCachedRoutes = (keys) => {
161
- setCachedRoutes((state) => {
162
- let changed = false;
163
- const touchedAt = Date.now();
164
- const nextState = { ...state };
165
- for (const key of keys) {
166
- const normalizedKey = normalizeCachedRoutePathname(key);
167
- const route = nextState[normalizedKey];
168
- if (!route || route.lastVisibleAt === touchedAt) continue;
169
- nextState[normalizedKey] = {
170
- ...route,
171
- lastVisibleAt: touchedAt
172
- };
173
- changed = true;
174
- }
175
- return changed ? nextState : state;
176
- });
177
- };
178
- const retainErroredRoute = (pathname) => {
179
- const normalizedPathname = normalizeCachedRoutePathname(pathname);
180
- setErroredRouteCounts((state) => ({
181
- ...state,
182
- [normalizedPathname]: (state[normalizedPathname] ?? 0) + 1
183
- }));
184
- setCachedRoutes((state) => {
185
- if (!Object.hasOwn(state, normalizedPathname)) return state;
186
- const nextState = { ...state };
187
- delete nextState[normalizedPathname];
188
- return nextState;
189
- });
190
- };
191
- const releaseErroredRoute = (pathname) => {
192
- const normalizedPathname = normalizeCachedRoutePathname(pathname);
193
- setErroredRouteCounts((state) => {
194
- const currentCount = state[normalizedPathname] ?? 0;
195
- if (currentCount <= 1) {
196
- if (!Object.hasOwn(state, normalizedPathname)) return state;
197
- const nextState = { ...state };
198
- delete nextState[normalizedPathname];
199
- return nextState;
200
- }
201
- return {
202
- ...state,
203
- [normalizedPathname]: currentCount - 1
204
- };
205
- });
206
- };
207
- const contextValue = {
208
- cachedRoutes,
209
- erroredRouteCounts,
210
- setCachedRoutes: updateCachedRoutes,
211
- deleteCachedRoutes,
212
- touchCachedRoutes,
213
- retainErroredRoute,
214
- releaseErroredRoute
215
- };
216
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouterCacheContext.Provider, {
217
- value: contextValue,
218
- children
219
- });
220
- }
221
- function useRouterCacheContext() {
222
- const context = (0, react.use)(RouterCacheContext);
223
- if (!context) throw new Error("RouterCacheContext is not available");
224
- return context;
225
- }
226
- function useOptionalRouterCacheContext() {
227
- return (0, react.use)(RouterCacheContext);
228
- }
229
- //#endregion
230
- //#region src/hooks/use-event-listener.ts
231
- const EVENT_BUCKET_KEYS = ["on", "once"];
232
- const instance = class RouterCacheEvent {
233
- static instance;
234
- listeners = {
235
- activeChange: /* @__PURE__ */ new Set(),
236
- cachedNavigationCancel: /* @__PURE__ */ new Set(),
237
- cachedNavigationComplete: /* @__PURE__ */ new Set(),
238
- cachedNavigationStart: /* @__PURE__ */ new Set()
239
- };
240
- constructor() {}
241
- static getInstance() {
242
- if (!RouterCacheEvent.instance) RouterCacheEvent.instance = new RouterCacheEvent();
243
- return RouterCacheEvent.instance;
244
- }
245
- on(eventName, handler) {
246
- this.listeners[eventName].add(handler);
247
- return this;
248
- }
249
- once(eventName, handler) {
250
- const onceHandler = (...args) => {
251
- this.off(eventName, onceHandler);
252
- handler(...args);
253
- };
254
- this.listeners[eventName].add(onceHandler);
255
- return this;
256
- }
257
- off(eventName, handler) {
258
- this.listeners[eventName].delete(handler);
259
- return this;
260
- }
261
- emit(eventName, ...args) {
262
- const handlers = this.listeners[eventName];
263
- for (const handler of handlers) handler(...args);
264
- return handlers.size > 0;
265
- }
266
- }.getInstance();
267
- function syncEventHandler(type, eventName, handler) {
268
- if (type === "on") {
269
- instance.on(eventName, handler);
270
- return;
271
- }
272
- instance.off(eventName, handler);
273
- }
274
- function syncEventBucket(type, bucket) {
275
- if (bucket.activeChange) syncEventHandler(type, "activeChange", bucket.activeChange);
276
- if (bucket.cachedNavigationCancel) syncEventHandler(type, "cachedNavigationCancel", bucket.cachedNavigationCancel);
277
- if (bucket.cachedNavigationComplete) syncEventHandler(type, "cachedNavigationComplete", bucket.cachedNavigationComplete);
278
- if (bucket.cachedNavigationStart) syncEventHandler(type, "cachedNavigationStart", bucket.cachedNavigationStart);
279
- }
280
- function syncEventHandlers(type, events) {
281
- if (!events) return;
282
- for (const key of EVENT_BUCKET_KEYS) {
283
- const bucket = events[key];
284
- if (!bucket) continue;
285
- syncEventBucket(type, bucket);
286
- }
287
- }
288
- function useEventListener(events) {
289
- (0, react.useEffect)(() => {
290
- const registerEvent = () => {
291
- syncEventHandlers("on", events);
292
- return () => {
293
- syncEventHandlers("off", events);
294
- };
295
- };
296
- const unregister = registerEvent();
297
- return () => {
298
- unregister?.();
299
- };
300
- }, [events]);
301
- return { eventListener: instance };
302
- }
303
- //#endregion
304
- //#region src/hooks/use-router-cache-debug.ts
305
- const DYNAMIC_SEGMENT_PATTERNS = [
306
- /^\d{4,}$/u,
307
- /^[0-9a-f]{8,}$/iu,
308
- /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu
309
- ];
310
- function isDynamicLookingSegment(segment) {
311
- for (const pattern of DYNAMIC_SEGMENT_PATTERNS) if (pattern.test(segment)) return true;
312
- return false;
313
- }
314
- function isDynamicLookingPathname(pathname) {
315
- return pathname.split("/").filter(Boolean).some(isDynamicLookingSegment);
316
- }
317
- function countHiddenContainers() {
318
- if (typeof document === "undefined") return 0;
319
- return document.querySelectorAll("[data-router-cache-container=\"true\"][data-router-cache-mode=\"hidden\"]").length;
320
- }
321
- function buildSnapshot(cachedRoutes, visiblePathname) {
322
- const cachedRoutePathnames = Object.keys(cachedRoutes).sort((a, b) => a.localeCompare(b));
323
- const dynamicLookingRoutePathnames = cachedRoutePathnames.filter(isDynamicLookingPathname);
324
- const hiddenCachedRoutePathnames = cachedRoutePathnames.filter((pathname) => pathname !== visiblePathname);
325
- return {
326
- cachedRoutePathnames,
327
- dynamicLookingRouteCount: dynamicLookingRoutePathnames.length,
328
- dynamicLookingRoutePathnames,
329
- hiddenCachedRouteCount: hiddenCachedRoutePathnames.length,
330
- hiddenContainerCount: countHiddenContainers(),
331
- totalCachedRouteCount: cachedRoutePathnames.length,
332
- visiblePathname
333
- };
334
- }
335
- function getDebugWindow() {
336
- return globalThis.window;
337
- }
338
- function isProduction() {
339
- return globalThis.process?.env?.NODE_ENV === "production";
340
- }
341
- function useRouterCacheDebug(cachedRoutes, visiblePathname) {
342
- const warningThresholdRef = (0, react.useRef)(null);
343
- const lastWarnedCountRef = (0, react.useRef)(null);
344
- const lastSnapshotRef = (0, react.useRef)(null);
345
- if (lastSnapshotRef.current === null) lastSnapshotRef.current = buildSnapshot(cachedRoutes, visiblePathname);
346
- const snapshot = (0, react.useMemo)(() => buildSnapshot(cachedRoutes, visiblePathname), [cachedRoutes, visiblePathname]);
347
- (0, react.useEffect)(() => {
348
- if (isProduction()) return;
349
- const debugWindow = getDebugWindow();
350
- if (!debugWindow) return;
351
- const refresh = () => {
352
- lastSnapshotRef.current = buildSnapshot(cachedRoutes, visiblePathname);
353
- return lastSnapshotRef.current;
354
- };
355
- const api = {
356
- getSnapshot: () => lastSnapshotRef.current ?? snapshot,
357
- lastSnapshot: snapshot,
358
- refresh,
359
- setWarningThreshold: (nextThreshold) => {
360
- warningThresholdRef.current = typeof nextThreshold === "number" ? nextThreshold : null;
361
- lastWarnedCountRef.current = null;
362
- },
363
- warningThreshold: warningThresholdRef.current
364
- };
365
- debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ = api;
366
- const rafId = globalThis.requestAnimationFrame(() => {
367
- const nextSnapshot = refresh();
368
- debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ = {
369
- ...api,
370
- lastSnapshot: nextSnapshot,
371
- warningThreshold: warningThresholdRef.current
372
- };
373
- const warningThreshold = warningThresholdRef.current;
374
- if (typeof warningThreshold === "number" && nextSnapshot.totalCachedRouteCount > warningThreshold && lastWarnedCountRef.current !== nextSnapshot.totalCachedRouteCount) lastWarnedCountRef.current = nextSnapshot.totalCachedRouteCount;
375
- });
376
- return () => {
377
- globalThis.cancelAnimationFrame(rafId);
378
- if (debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ === api) debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ = void 0;
379
- };
380
- }, [
381
- cachedRoutes,
382
- snapshot,
383
- visiblePathname
384
- ]);
385
- }
386
- //#endregion
387
- //#region src/components/cached-outlet.tsx
388
- function CachedOutlet(props) {
389
- const { matchId, routerSnapshot } = props;
390
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.RouterContextProvider, {
391
- router: routerSnapshot,
392
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.Match, { matchId })
393
- });
394
- }
395
- //#endregion
396
- //#region src/dom/dismiss-transient-ui.ts
397
- const documentStates = /* @__PURE__ */ new WeakMap();
398
- const PERSISTENT_EXTERNAL_ATTRIBUTE = "data-router-cache-persistent-external";
399
- function isBlurTarget(element) {
400
- return element instanceof HTMLElement;
401
- }
402
- function getOrCreateDocumentState(documentObject) {
403
- const existingState = documentStates.get(documentObject);
404
- if (existingState) return existingState;
405
- const state = {
406
- observer: null,
407
- ownerByElement: /* @__PURE__ */ new WeakMap(),
408
- routes: /* @__PURE__ */ new Map(),
409
- visiblePathname: null
410
- };
411
- if (typeof MutationObserver !== "undefined") {
412
- state.observer = new MutationObserver((mutations) => {
413
- handleDomMutations(mutations, state);
414
- });
415
- state.observer.observe(documentObject.body ?? documentObject.documentElement, {
416
- childList: true,
417
- subtree: true
418
- });
419
- }
420
- documentStates.set(documentObject, state);
421
- return state;
422
- }
423
- function getOrCreateRouteState(state, pathname) {
424
- const existingRouteState = state.routes.get(pathname);
425
- if (existingRouteState) return existingRouteState;
426
- const routeState = {
427
- hiddenElements: /* @__PURE__ */ new Map(),
428
- ownedElements: /* @__PURE__ */ new Set()
429
- };
430
- state.routes.set(pathname, routeState);
431
- return routeState;
432
- }
433
- function getHoveredElements(documentObject) {
434
- try {
435
- return Array.from(documentObject.querySelectorAll(":hover")).reverse();
436
- } catch {
437
- return [];
438
- }
439
- }
440
- function getOwnedExternalElements(documentObject, pathname) {
441
- const routeState = getOrCreateRouteState(getOrCreateDocumentState(documentObject), pathname);
442
- return Array.from(routeState.ownedElements).filter((element) => element.isConnected);
443
- }
444
- function dispatchEscapeKeyboardEvent(target, documentObject, type) {
445
- const keyboardEventConstructor = documentObject.defaultView?.KeyboardEvent ?? KeyboardEvent;
446
- target.dispatchEvent(new keyboardEventConstructor(type, {
447
- bubbles: true,
448
- cancelable: true,
449
- code: "Escape",
450
- key: "Escape",
451
- keyCode: 27,
452
- which: 27
453
- }));
454
- }
455
- function dispatchHoverExitEvent(target, documentObject, type) {
456
- const eventConstructor = type.startsWith("pointer") ? documentObject.defaultView?.PointerEvent ?? documentObject.defaultView?.MouseEvent ?? MouseEvent : documentObject.defaultView?.MouseEvent ?? MouseEvent;
457
- target.dispatchEvent(new eventConstructor(type, {
458
- bubbles: type.endsWith("out"),
459
- cancelable: true,
460
- composed: true,
461
- relatedTarget: documentObject.body
462
- }));
463
- }
464
- function dispatchHoverExitEvents(hoveredElements, documentObject) {
465
- for (const hoveredElement of hoveredElements) {
466
- dispatchHoverExitEvent(hoveredElement, documentObject, "pointerout");
467
- dispatchHoverExitEvent(hoveredElement, documentObject, "pointerleave");
468
- dispatchHoverExitEvent(hoveredElement, documentObject, "mouseout");
469
- dispatchHoverExitEvent(hoveredElement, documentObject, "mouseleave");
470
- }
471
- }
472
- function focusDocumentBody(documentObject) {
473
- const { body } = documentObject;
474
- const previousTabIndex = body.getAttribute("tabindex");
475
- if (previousTabIndex === null) body.setAttribute("tabindex", "-1");
476
- body.focus();
477
- if (previousTabIndex === null) body.removeAttribute("tabindex");
478
- }
479
- function getRouterCacheContainerAncestor(element) {
480
- const container = element?.closest("[data-router-cache-container=\"true\"]");
481
- return container instanceof HTMLElement ? container : null;
482
- }
483
- function isElementInsideAnyRouterCacheContainer(element) {
484
- return getRouterCacheContainerAncestor(element) !== null;
485
- }
486
- function containsRouterCacheContainer(element) {
487
- return element.querySelector("[data-router-cache-container=\"true\"]") instanceof HTMLElement;
488
- }
489
- function isElementConnectedOutsideRouterCacheContainer(element) {
490
- return element.isConnected && !isElementInsideAnyRouterCacheContainer(element);
491
- }
492
- function isPersistentExternalElement(element) {
493
- return element.closest(`[${PERSISTENT_EXTERNAL_ATTRIBUTE}="true"]`) instanceof HTMLElement;
494
- }
495
- function getTrackableElementsFromNode(node) {
496
- if (node instanceof HTMLElement) return [node];
497
- if (node instanceof DocumentFragment) return Array.from(node.children).flatMap((child) => child instanceof HTMLElement ? [child] : []);
498
- return [];
499
- }
500
- function forEachRemovedNode(mutations, callback) {
501
- for (const mutation of mutations) for (const removedNode of mutation.removedNodes) callback(removedNode);
502
- }
503
- function forEachAddedNode(mutations, callback) {
504
- for (const mutation of mutations) for (const addedNode of mutation.addedNodes) callback(addedNode);
505
- }
506
- function handleDomMutations(mutations, state) {
507
- forEachRemovedNode(mutations, (removedNode) => {
508
- untrackRemovedNode(removedNode, state);
509
- });
510
- if (!state.visiblePathname) return;
511
- forEachAddedNode(mutations, (addedNode) => {
512
- trackAddedNode(addedNode, state.visiblePathname, state);
513
- });
514
- }
515
- function hasTrackedAncestor(element, state) {
516
- let ancestor = element.parentElement;
517
- while (ancestor) {
518
- if (state.ownerByElement.get(ancestor)) return true;
519
- ancestor = ancestor.parentElement;
520
- }
521
- return false;
522
- }
523
- function restoreOwnedExternalElement(routeState, element) {
524
- const snapshot = routeState.hiddenElements.get(element);
525
- if (!snapshot) return;
526
- element.style.setProperty("display", snapshot.display, snapshot.displayPriority);
527
- if (snapshot.ariaHidden === null) element.removeAttribute("aria-hidden");
528
- else element.setAttribute("aria-hidden", snapshot.ariaHidden);
529
- element.inert = snapshot.inert;
530
- routeState.hiddenElements.delete(element);
531
- }
532
- function trackExternalElement(pathname, element, state) {
533
- if (!isElementConnectedOutsideRouterCacheContainer(element) || containsRouterCacheContainer(element) || isPersistentExternalElement(element) || hasTrackedAncestor(element, state)) return;
534
- const currentOwner = state.ownerByElement.get(element);
535
- if (currentOwner === pathname) return;
536
- if (currentOwner) {
537
- const previousRouteState = state.routes.get(currentOwner);
538
- if (previousRouteState) {
539
- restoreOwnedExternalElement(previousRouteState, element);
540
- previousRouteState.ownedElements.delete(element);
541
- }
542
- }
543
- getOrCreateRouteState(state, pathname).ownedElements.add(element);
544
- state.ownerByElement.set(element, pathname);
545
- }
546
- function trackAddedNode(node, pathname, state) {
547
- const elements = getTrackableElementsFromNode(node);
548
- for (const element of elements) trackExternalElement(pathname, element, state);
549
- }
550
- function untrackElement(routeState, state, element) {
551
- routeState.hiddenElements.delete(element);
552
- routeState.ownedElements.delete(element);
553
- state.ownerByElement.delete(element);
554
- }
555
- function untrackRemovedNode(node, state) {
556
- if (!(node instanceof HTMLElement)) return;
557
- for (const routeState of state.routes.values()) for (const element of Array.from(routeState.ownedElements)) if (node === element || node.contains(element)) untrackElement(routeState, state, element);
558
- }
559
- function hideOwnedExternalElements(documentObject, pathname) {
560
- const routeState = getOrCreateRouteState(getOrCreateDocumentState(documentObject), pathname);
561
- for (const element of Array.from(routeState.ownedElements)) {
562
- if (!isElementConnectedOutsideRouterCacheContainer(element)) {
563
- routeState.ownedElements.delete(element);
564
- routeState.hiddenElements.delete(element);
565
- continue;
566
- }
567
- if (routeState.hiddenElements.has(element)) continue;
568
- routeState.hiddenElements.set(element, {
569
- ariaHidden: element.getAttribute("aria-hidden"),
570
- display: element.style.getPropertyValue("display"),
571
- displayPriority: element.style.getPropertyPriority("display"),
572
- inert: element.inert
573
- });
574
- element.style.setProperty("display", "none", "important");
575
- element.setAttribute("aria-hidden", "true");
576
- element.inert = true;
577
- }
578
- }
579
- function showOwnedExternalElements(documentObject, pathname) {
580
- const routeState = getOrCreateRouteState(getOrCreateDocumentState(documentObject), pathname);
581
- for (const [element] of Array.from(routeState.hiddenElements)) {
582
- if (!element.isConnected) {
583
- routeState.hiddenElements.delete(element);
584
- routeState.ownedElements.delete(element);
585
- continue;
586
- }
587
- restoreOwnedExternalElement(routeState, element);
588
- }
589
- }
590
- function initializeTransientUiTracking(documentObject) {
591
- getOrCreateDocumentState(documentObject);
592
- }
593
- function syncTransientUiRouteActivity(pathname, mode) {
594
- if (typeof document === "undefined") return;
595
- const documentState = getOrCreateDocumentState(document);
596
- getOrCreateRouteState(documentState, pathname);
597
- if (mode === "visible") {
598
- documentState.visiblePathname = pathname;
599
- showOwnedExternalElements(document, pathname);
600
- return;
601
- }
602
- if (documentState.visiblePathname === pathname) documentState.visiblePathname = null;
603
- dispatchHoverExitEvents(getOwnedExternalElements(document, pathname), document);
604
- hideOwnedExternalElements(document, pathname);
605
- }
606
- function dismissTransientUi(container, pathname) {
607
- if (typeof document === "undefined" || !container) return;
608
- initializeTransientUiTracking(document);
609
- const hoveredElements = getHoveredElements(document);
610
- const hoverExitTargets = Array.from(new Set([...hoveredElements, ...getOwnedExternalElements(document, pathname)]));
611
- const activeElement = document.activeElement;
612
- const activeElementBelongsToHoveredTree = activeElement instanceof Element && hoverExitTargets.some((hoveredElement) => hoveredElement === activeElement || hoveredElement.contains(activeElement));
613
- if (activeElement instanceof Element && (container.contains(activeElement) || activeElementBelongsToHoveredTree) && isBlurTarget(activeElement)) activeElement.blur();
614
- dispatchHoverExitEvents(hoverExitTargets, document);
615
- dispatchEscapeKeyboardEvent(document, document, "keydown");
616
- dispatchEscapeKeyboardEvent(document, document, "keyup");
617
- hideOwnedExternalElements(document, pathname);
618
- const nextActiveElement = document.activeElement;
619
- const nextActiveElementBelongsToHoveredTree = nextActiveElement instanceof Element && hoverExitTargets.some((hoveredElement) => hoveredElement === nextActiveElement || hoveredElement.contains(nextActiveElement));
620
- if (nextActiveElement instanceof Element && (container.contains(nextActiveElement) || nextActiveElementBelongsToHoveredTree)) focusDocumentBody(document);
621
- }
622
- //#endregion
623
- //#region src/components/off-screen-in.tsx
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
- ];
649
- function OffScreenIn(props) {
650
- const { mode, children, containerRef, pathname } = props;
651
- const localContainerRef = (0, react.useRef)(null);
652
- const attachContainerRef = (node) => {
653
- localContainerRef.current = node;
654
- if (containerRef) containerRef.current = node;
655
- };
656
- (0, react.useLayoutEffect)(() => {
657
- if (typeof document === "undefined") return;
658
- initializeTransientUiTracking(document);
659
- }, []);
660
- (0, react.useLayoutEffect)(() => {
661
- if (!pathname) return;
662
- syncTransientUiRouteActivity(pathname, mode);
663
- }, [mode, pathname]);
664
- (0, react.useLayoutEffect)(() => {
665
- const visibleContainer = localContainerRef.current;
666
- return () => {
667
- if (!pathname || mode !== "visible") return;
668
- dismissTransientUi(visibleContainer, pathname);
669
- };
670
- }, [mode, pathname]);
671
- (0, react.useLayoutEffect)(() => {
672
- if (typeof globalThis === "undefined" || !pathname) return;
673
- const saveScrollPosition = () => {
674
- windowScrollPositions.set(pathname, {
675
- x: globalThis.scrollX,
676
- y: globalThis.scrollY
677
- });
678
- };
679
- if (mode === "hidden") return;
680
- const savedPosition = windowScrollPositions.get(pathname);
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
- };
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);
700
- if (!savedPosition) {
701
- startScrollTracking();
702
- saveScrollPosition();
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
- };
711
- }
712
- const restoreScrollPosition = () => {
713
- if (userRequestedScroll) return;
714
- globalThis.scrollTo(savedPosition.x, savedPosition.y);
715
- saveScrollPosition();
716
- startScrollTracking();
717
- };
718
- const timeoutIds = [];
719
- let rafId = globalThis.requestAnimationFrame(() => {
720
- rafId = globalThis.requestAnimationFrame(() => {
721
- restoreScrollPosition();
722
- for (const delay of SCROLL_RESTORE_DELAYS) timeoutIds.push(globalThis.setTimeout(restoreScrollPosition, delay));
723
- });
724
- });
725
- return () => {
726
- globalThis.cancelAnimationFrame(rafId);
727
- for (const timeoutId of timeoutIds) globalThis.clearTimeout(timeoutId);
728
- if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
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);
734
- };
735
- }, [mode, pathname]);
736
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Activity, {
737
- mode,
738
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
739
- className: "flex min-h-full w-full flex-1 flex-col",
740
- "data-router-cache-container": "true",
741
- "data-router-cache-mode": mode,
742
- "data-router-cache-pathname": pathname,
743
- ref: attachContainerRef,
744
- children
745
- })
746
- });
747
- }
748
- //#endregion
749
- //#region src/components/off-screen.tsx
750
- function OffScreen(props) {
751
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OffScreenIn, { ...props });
752
- }
753
- //#endregion
754
- //#region src/components/restore-cached-href.ts
755
- const FALLBACK_ORIGIN = "http://tanstack-router-cache.local";
756
- function parseHref(href) {
757
- try {
758
- const url = new URL(href, FALLBACK_ORIGIN);
759
- return {
760
- hasHash: url.hash.length > 0,
761
- hasSearch: url.search.length > 0,
762
- pathname: url.pathname
763
- };
764
- } catch {
765
- return null;
766
- }
767
- }
768
- function isBareRouteHref(href, currentPathname) {
769
- const candidateHref = href ?? currentPathname;
770
- const parsedHref = parseHref(candidateHref);
771
- if (!parsedHref) return candidateHref === currentPathname;
772
- return parsedHref.pathname === currentPathname && !parsedHref.hasSearch && !parsedHref.hasHash;
773
- }
774
- function shouldRestoreCachedHref({ cachedHref, currentHref, currentPathname, isRouteCacheEnabled, previousPathname }) {
775
- if (!(isRouteCacheEnabled && cachedHref)) return false;
776
- if (!previousPathname || previousPathname === currentPathname) return false;
777
- if (!isBareRouteHref(currentHref, currentPathname)) return false;
778
- return cachedHref !== (currentHref ?? currentPathname);
779
- }
780
- //#endregion
781
- //#region src/components/route-cache-manager.tsx
782
- const LIVE_ROUTER_METHODS = [
783
- "buildLocation",
784
- "commitLocation",
785
- "invalidate",
786
- "loadRouteChunk",
787
- "navigate",
788
- "preloadRoute"
789
- ];
790
- const routerSnapshotUpdaters = /* @__PURE__ */ new WeakMap();
791
- function createSnapshotStore(value) {
792
- let currentValue = value;
793
- const listeners = /* @__PURE__ */ new Set();
794
- return {
795
- get: () => currentValue,
796
- set: (nextValue) => {
797
- if (Object.is(currentValue, nextValue)) return;
798
- currentValue = nextValue;
799
- for (const listener of listeners) listener(currentValue);
800
- },
801
- subscribe: (listener) => {
802
- if (listener) listeners.add(listener);
803
- return { unsubscribe: listener ? () => {
804
- listeners.delete(listener);
805
- } : () => void 0 };
806
- }
807
- };
808
- }
809
- function createRouterSnapshotData({ matches, router, routerLocation, routerResolvedLocation }) {
810
- const snapshotMatches = matches.reduce((snapshot, match) => {
811
- const nextMatch = snapshotMatch(match, routerLocation);
812
- if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
813
- return snapshot;
814
- }, []);
815
- return {
816
- matches: snapshotMatches,
817
- state: {
818
- ...router.stores.__store.get(),
819
- matches: snapshotMatches,
820
- location: routerLocation,
821
- resolvedLocation: routerResolvedLocation
822
- }
823
- };
824
- }
825
- function createMatchStore(match) {
826
- return Object.assign(createSnapshotStore(match), { routeId: match.routeId });
827
- }
828
- function syncRouterSnapshot(routerSnapshot, input) {
829
- const update = routerSnapshotUpdaters.get(routerSnapshot);
830
- if (!update) return false;
831
- update(input);
832
- return true;
833
- }
834
- function getLiveRouterMethodDescriptors(router) {
835
- return Object.fromEntries(LIVE_ROUTER_METHODS.flatMap((methodName) => {
836
- const method = router[methodName];
837
- if (typeof method !== "function") return [];
838
- return [[methodName, { value: method.bind(router) }]];
839
- }));
840
- }
841
- function toRouterLocation(location) {
842
- return { ...location };
843
- }
844
- function isReadyCachedRoute(route) {
845
- return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.matchId && route.routerSnapshot);
846
- }
847
- function isRouteCacheEnabled(staticData) {
848
- return staticData?.routeCache === true;
849
- }
850
- function hasErroredRouteMatch(matches) {
851
- return matches.some((match) => match?.status === "error");
852
- }
853
- function hasRetainedRouteError(erroredRouteCounts, pathname) {
854
- return (erroredRouteCounts[pathname] ?? 0) > 0;
855
- }
856
- function hasCurrentRouterMatchError({ matches, resolvedPathname, routerPathname }) {
857
- return routerPathname === resolvedPathname && hasErroredRouteMatch(matches);
858
- }
859
- function hasCurrentRouteError({ erroredRouteCounts, matches, resolvedPathname, routerPathname }) {
860
- return hasRetainedRouteError(erroredRouteCounts, routerPathname) || hasCurrentRouterMatchError({
861
- matches,
862
- resolvedPathname,
863
- routerPathname
864
- });
865
- }
866
- function isRouterMatch(match) {
867
- return Boolean(match?.id && match.routeId);
868
- }
869
- function snapshotMatch(match, routerLocation) {
870
- if (!isRouterMatch(match)) return;
871
- const isLocationMatch = match.pathname ? normalizeCachedRoutePathname(match.pathname) === normalizeCachedRoutePathname(routerLocation.pathname) : false;
872
- return {
873
- ...match,
874
- ...isLocationMatch ? {
875
- _strictSearch: routerLocation.search,
876
- search: routerLocation.search
877
- } : {},
878
- _nonReactive: { ...match._nonReactive }
879
- };
880
- }
881
- function isCurrentUnmanagedCachedRoute(route, routerHref) {
882
- return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.routerSnapshot && route.href === routerHref);
883
- }
884
- function syncReadyCachedRoute({ matchId, matches, routeId, route, routerLocation, router, routerResolvedLocation, routerHref, routerPathname, setCachedRoutes, staticData }) {
885
- const routerSnapshotInput = {
886
- matches,
887
- router,
888
- routerLocation,
889
- routerResolvedLocation
890
- };
891
- let routerSnapshot = matchId ? route?.routerSnapshot : void 0;
892
- if (routerSnapshot && !syncRouterSnapshot(routerSnapshot, routerSnapshotInput)) {
893
- if (isCurrentUnmanagedCachedRoute(route, routerHref)) return;
894
- routerSnapshot = void 0;
895
- }
896
- if (matchId && !routerSnapshot) routerSnapshot = createRouterSnapshot(routerSnapshotInput);
897
- setCachedRoutes(routerPathname, {
898
- href: routerHref,
899
- matchId,
900
- routeId,
901
- ready: true,
902
- routerSnapshot,
903
- staticData
904
- });
905
- }
906
- function syncCachedRouteState({ isCurrentMatchReady, isCurrentMatchResolved, deleteCachedRoutes, isCurrentRouteErrored, matchId, matches, route, routeId, router, routerHref, routerLocation, routerPathname, routerResolvedLocation, setCachedRoutes, staticData }) {
907
- if (isCurrentRouteErrored) {
908
- if (route) deleteCachedRoutes([routerPathname]);
909
- return;
910
- }
911
- if (!isCurrentMatchResolved) return;
912
- if (staticData && isRouteCacheEnabled(staticData)) {
913
- if (!isCurrentMatchReady) return;
914
- syncReadyCachedRoute({
915
- matchId,
916
- matches,
917
- routeId,
918
- route,
919
- routerLocation,
920
- router,
921
- routerResolvedLocation,
922
- routerHref,
923
- routerPathname,
924
- setCachedRoutes,
925
- staticData
926
- });
927
- return;
928
- }
929
- if (route) deleteCachedRoutes([routerPathname]);
930
- }
931
- function createRouterSnapshot(input) {
932
- const { router, routerLocation, routerResolvedLocation } = input;
933
- const snapshotData = createRouterSnapshotData(input);
934
- const snapshotMatches = snapshotData.matches;
935
- const matchStores = new Map(snapshotMatches.map((match) => [match.id, createMatchStore(match)]));
936
- const routeMatchStoreCache = /* @__PURE__ */ new Map();
937
- const stores = {
938
- ...router.stores,
939
- status: createSnapshotStore(router.stores.status.get()),
940
- loadedAt: createSnapshotStore(router.stores.loadedAt.get()),
941
- isLoading: createSnapshotStore(router.stores.isLoading.get()),
942
- isTransitioning: createSnapshotStore(router.stores.isTransitioning.get()),
943
- location: createSnapshotStore(routerLocation),
944
- resolvedLocation: createSnapshotStore(routerResolvedLocation),
945
- statusCode: createSnapshotStore(router.stores.statusCode.get()),
946
- redirect: createSnapshotStore(router.stores.redirect.get()),
947
- matchesId: createSnapshotStore(snapshotMatches.map((match) => match.id)),
948
- pendingIds: createSnapshotStore([]),
949
- cachedIds: createSnapshotStore([]),
950
- matches: createSnapshotStore(snapshotMatches),
951
- pendingMatches: createSnapshotStore([]),
952
- cachedMatches: createSnapshotStore([]),
953
- firstId: createSnapshotStore(snapshotMatches[0]?.id),
954
- hasPending: createSnapshotStore(snapshotMatches.some((match) => match.status === "pending")),
955
- matchRouteDeps: createSnapshotStore({
956
- locationHref: routerLocation.href,
957
- resolvedLocationHref: routerResolvedLocation?.href,
958
- status: snapshotData.state.status
959
- }),
960
- __store: createSnapshotStore(snapshotData.state),
961
- matchStores,
962
- pendingMatchStores: /* @__PURE__ */ new Map(),
963
- cachedMatchStores: /* @__PURE__ */ new Map(),
964
- getRouteMatchStore: (routeId) => {
965
- let cached = routeMatchStoreCache.get(routeId);
966
- if (!cached) {
967
- cached = createSnapshotStore(snapshotMatches.find((match) => match.routeId === routeId));
968
- routeMatchStoreCache.set(routeId, cached);
969
- }
970
- return cached;
971
- },
972
- setMatches: () => void 0,
973
- setPending: () => void 0,
974
- setCached: () => void 0
975
- };
976
- const updateSnapshot = (nextInput) => {
977
- const nextData = createRouterSnapshotData(nextInput);
978
- const nextMatches = nextData.matches;
979
- const nextMatchIds = new Set(nextMatches.map((match) => match.id));
980
- const nextMatchesByRouteId = new Map(nextMatches.map((match) => [match.routeId, match]));
981
- for (const match of nextMatches) {
982
- const store = matchStores.get(match.id);
983
- if (store) {
984
- store.set(match);
985
- continue;
986
- }
987
- matchStores.set(match.id, createMatchStore(match));
988
- }
989
- for (const [matchId] of matchStores) if (!nextMatchIds.has(matchId)) matchStores.delete(matchId);
990
- stores.status.set(nextInput.router.stores.status.get());
991
- stores.loadedAt.set(nextInput.router.stores.loadedAt.get());
992
- stores.isLoading.set(nextInput.router.stores.isLoading.get());
993
- stores.isTransitioning.set(nextInput.router.stores.isTransitioning.get());
994
- stores.location.set(nextInput.routerLocation);
995
- stores.resolvedLocation.set(nextInput.routerResolvedLocation);
996
- stores.statusCode.set(nextInput.router.stores.statusCode.get());
997
- stores.redirect.set(nextInput.router.stores.redirect.get());
998
- stores.matchesId.set(nextMatches.map((match) => match.id));
999
- stores.pendingIds.set([]);
1000
- stores.cachedIds.set([]);
1001
- stores.matches.set(nextMatches);
1002
- stores.pendingMatches.set([]);
1003
- stores.cachedMatches.set([]);
1004
- stores.firstId.set(nextMatches[0]?.id);
1005
- stores.hasPending.set(nextMatches.some((match) => match.status === "pending"));
1006
- stores.matchRouteDeps.set({
1007
- locationHref: nextInput.routerLocation.href,
1008
- resolvedLocationHref: nextInput.routerResolvedLocation?.href,
1009
- status: nextData.state.status
1010
- });
1011
- stores.__store.set(nextData.state);
1012
- for (const [routeId, store] of routeMatchStoreCache) store.set(nextMatchesByRouteId.get(routeId));
1013
- };
1014
- const routerSnapshot = Object.create(router);
1015
- Object.defineProperties(routerSnapshot, {
1016
- stores: { value: stores },
1017
- latestLocation: { get: () => stores.location.get() },
1018
- getMatch: { value: (matchId) => matchStores.get(matchId)?.get() },
1019
- updateMatch: { value: () => void 0 },
1020
- ...getLiveRouterMethodDescriptors(router)
1021
- });
1022
- routerSnapshotUpdaters.set(routerSnapshot, updateSnapshot);
1023
- return routerSnapshot;
1024
- }
1025
- function getRouterCacheStaticData(childMatches, isCurrentMatchResolved) {
1026
- if (!isCurrentMatchResolved) return;
1027
- for (let i = childMatches.length - 1; i >= 0; i--) {
1028
- const match = childMatches[i];
1029
- if (match?.staticData && isRouteCacheEnabled(match.staticData)) return match.staticData;
1030
- }
1031
- }
1032
- function restoreCachedHref(router, href) {
1033
- router.navigate({
1034
- href,
1035
- replace: true,
1036
- resetScroll: false
1037
- }).catch(() => void 0);
1038
- }
1039
- function renderCachedRoute({ bypassCachedPathname, pathname, route, routerPathname }) {
1040
- if (pathname === bypassCachedPathname) return null;
1041
- const content = route.matchId && route.routerSnapshot ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CachedOutlet, {
1042
- matchId: route.matchId,
1043
- routerSnapshot: route.routerSnapshot
1044
- }) : null;
1045
- if (!content) return null;
1046
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OffScreen, {
1047
- mode: routerPathname === pathname ? "visible" : "hidden",
1048
- pathname,
1049
- children: content
1050
- }, pathname);
1051
- }
1052
- function buildRouteCacheModes(cachedRoutes, routerPathname) {
1053
- const nextModes = /* @__PURE__ */ new Map();
1054
- for (const pathname of Object.keys(cachedRoutes)) nextModes.set(pathname, routerPathname === pathname ? "visible" : "hidden");
1055
- return nextModes;
1056
- }
1057
- function syncCachedRouteActivityEvents(params) {
1058
- const nextModes = buildRouteCacheModes(params.cachedRoutes, params.routerPathname);
1059
- for (const [pathname, mode] of nextModes) {
1060
- const previousMode = params.previousRouteCacheModes.get(pathname);
1061
- if (previousMode === void 0 && mode === "hidden") continue;
1062
- if (previousMode === mode) continue;
1063
- params.eventListener.emit("activeChange", {
1064
- pathname,
1065
- mode
1066
- });
1067
- }
1068
- for (const [pathname] of params.previousRouteCacheModes) {
1069
- if (nextModes.has(pathname)) continue;
1070
- params.eventListener.emit("activeChange", {
1071
- pathname,
1072
- mode: "hidden"
1073
- });
1074
- }
1075
- return nextModes;
1076
- }
1077
- function RouteCacheManager() {
1078
- const { cachedRoutes, deleteCachedRoutes, erroredRouteCounts, setCachedRoutes, touchCachedRoutes } = useRouterCacheContext();
1079
- const { eventListener } = useEventListener();
1080
- const pendingCachedNavigationRef = (0, react.useRef)(null);
1081
- const previousPathnameRef = (0, react.useRef)(void 0);
1082
- const previousHrefRef = (0, react.useRef)(void 0);
1083
- const previousRouteCacheModesRef = (0, react.useRef)(null);
1084
- previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
1085
- const previousVisiblePathnameRef = (0, react.useRef)(void 0);
1086
- const routerLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => toRouterLocation(state.location) });
1087
- const routerHref = routerLocation.href;
1088
- const routerPathname = normalizeCachedRoutePathname(routerLocation.pathname);
1089
- const routerResolvedLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => state.resolvedLocation ? toRouterLocation(state.resolvedLocation) : void 0 });
1090
- const resolvedPathname = normalizeCachedRoutePathname(routerResolvedLocation?.pathname ?? routerPathname);
1091
- const destinationRoute = cachedRoutes[routerPathname];
1092
- const matches = (0, _tanstack_react_router.useMatches)();
1093
- const childMatches = (0, _tanstack_react_router.useChildMatches)();
1094
- const router = (0, _tanstack_react_router.useRouter)();
1095
- const currentMatch = matches.length ? matches.at(-1) : void 0;
1096
- const outletRootMatch = childMatches.length ? childMatches[0] : currentMatch;
1097
- const isCurrentMatchResolved = (currentMatch?.pathname ? normalizeCachedRoutePathname(currentMatch.pathname) : void 0) === routerPathname;
1098
- const isCurrentMatchReady = isCurrentMatchResolved && currentMatch?.status === "success";
1099
- const staticData = getRouterCacheStaticData(childMatches, isCurrentMatchResolved);
1100
- const matchId = isCurrentMatchResolved && outletRootMatch ? outletRootMatch.id : void 0;
1101
- const routeId = isCurrentMatchResolved && outletRootMatch ? outletRootMatch.routeId : void 0;
1102
- const currentCachedRoute = cachedRoutes[routerPathname];
1103
- const isCurrentRouteErrored = hasCurrentRouteError({
1104
- erroredRouteCounts,
1105
- matches,
1106
- resolvedPathname,
1107
- routerPathname
1108
- });
1109
- const bypassCachedPathname = isCurrentRouteErrored ? routerPathname : void 0;
1110
- const previousPathname = previousPathnameRef.current;
1111
- previousHrefRef.current;
1112
- const shouldRestoreDestinationHref = shouldRestoreCachedHref({
1113
- cachedHref: destinationRoute?.href,
1114
- currentHref: routerHref,
1115
- currentPathname: routerPathname,
1116
- isRouteCacheEnabled: isRouteCacheEnabled(destinationRoute?.staticData),
1117
- previousPathname
1118
- });
1119
- (0, react.useLayoutEffect)(() => {
1120
- if (shouldRestoreDestinationHref) return;
1121
- syncCachedRouteState({
1122
- isCurrentMatchReady,
1123
- isCurrentMatchResolved,
1124
- isCurrentRouteErrored,
1125
- matchId,
1126
- matches,
1127
- routeId,
1128
- route: currentCachedRoute,
1129
- routerLocation,
1130
- router,
1131
- routerResolvedLocation,
1132
- routerHref,
1133
- routerPathname,
1134
- deleteCachedRoutes,
1135
- setCachedRoutes,
1136
- staticData
1137
- });
1138
- }, [
1139
- currentCachedRoute,
1140
- deleteCachedRoutes,
1141
- isCurrentRouteErrored,
1142
- isCurrentMatchReady,
1143
- isCurrentMatchResolved,
1144
- matchId,
1145
- matches,
1146
- routeId,
1147
- router,
1148
- routerLocation,
1149
- routerResolvedLocation,
1150
- routerHref,
1151
- routerPathname,
1152
- staticData,
1153
- setCachedRoutes,
1154
- shouldRestoreDestinationHref
1155
- ]);
1156
- (0, react.useLayoutEffect)(() => {
1157
- const lastVisitedPathname = previousPathnameRef.current;
1158
- const pendingNavigation = pendingCachedNavigationRef.current;
1159
- if (pendingNavigation && pendingNavigation.pathname !== routerPathname) {
1160
- eventListener.emit("cachedNavigationCancel", pendingNavigation);
1161
- pendingCachedNavigationRef.current = null;
1162
- }
1163
- if (!lastVisitedPathname || lastVisitedPathname === routerPathname) return;
1164
- if (!isReadyCachedRoute(destinationRoute)) return;
1165
- if (pendingCachedNavigationRef.current?.pathname === routerPathname) return;
1166
- const nextNavigation = {
1167
- pathname: routerPathname,
1168
- startedAt: performance.now()
1169
- };
1170
- pendingCachedNavigationRef.current = nextNavigation;
1171
- eventListener.emit("cachedNavigationStart", nextNavigation);
1172
- }, [
1173
- destinationRoute,
1174
- eventListener,
1175
- routerPathname
1176
- ]);
1177
- (0, react.useLayoutEffect)(() => {
1178
- previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
1179
- previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
1180
- cachedRoutes,
1181
- eventListener,
1182
- previousRouteCacheModes: previousRouteCacheModesRef.current,
1183
- routerPathname
1184
- });
1185
- }, [
1186
- cachedRoutes,
1187
- eventListener,
1188
- routerPathname
1189
- ]);
1190
- (0, react.useLayoutEffect)(() => {
1191
- if (previousVisiblePathnameRef.current === routerPathname || !cachedRoutes[routerPathname]) {
1192
- previousVisiblePathnameRef.current = routerPathname;
1193
- return;
1194
- }
1195
- previousVisiblePathnameRef.current = routerPathname;
1196
- touchCachedRoutes([routerPathname]);
1197
- }, [
1198
- cachedRoutes,
1199
- touchCachedRoutes,
1200
- routerPathname
1201
- ]);
1202
- (0, react.useEffect)(() => {
1203
- const pendingNavigation = pendingCachedNavigationRef.current;
1204
- if (routerPathname !== pendingNavigation?.pathname) return;
1205
- let firstFrameId = 0;
1206
- let secondFrameId = 0;
1207
- firstFrameId = globalThis.requestAnimationFrame(() => {
1208
- const visibleAt = performance.now();
1209
- secondFrameId = globalThis.requestAnimationFrame(() => {
1210
- const paintedAt = performance.now();
1211
- if (pendingCachedNavigationRef.current?.pathname !== pendingNavigation.pathname) return;
1212
- pendingCachedNavigationRef.current = null;
1213
- eventListener.emit("cachedNavigationComplete", {
1214
- ...pendingNavigation,
1215
- duration: paintedAt - pendingNavigation.startedAt,
1216
- paintedAt,
1217
- visibleAt
1218
- });
1219
- });
1220
- });
1221
- return () => {
1222
- globalThis.cancelAnimationFrame(firstFrameId);
1223
- globalThis.cancelAnimationFrame(secondFrameId);
1224
- };
1225
- }, [eventListener, routerPathname]);
1226
- (0, react.useLayoutEffect)(() => {
1227
- if (!(shouldRestoreDestinationHref && destinationRoute?.href)) return;
1228
- restoreCachedHref(router, destinationRoute.href);
1229
- }, [
1230
- destinationRoute?.href,
1231
- router,
1232
- shouldRestoreDestinationHref
1233
- ]);
1234
- (0, react.useEffect)(() => {
1235
- previousPathnameRef.current = routerPathname;
1236
- previousHrefRef.current = routerHref;
1237
- }, [routerHref, routerPathname]);
1238
- const shouldRenderLiveOutlet = routerPathname === bypassCachedPathname || !isReadyCachedRoute(destinationRoute);
1239
- useRouterCacheDebug(cachedRoutes, routerPathname);
1240
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [Object.entries(cachedRoutes).map(([pathname, route]) => renderCachedRoute({
1241
- bypassCachedPathname,
1242
- pathname,
1243
- route,
1244
- routerPathname
1245
- })), shouldRenderLiveOutlet ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.Outlet, {}, `live-${routerPathname}`) : null] });
1246
- }
1247
- //#endregion
1248
- //#region src/components/router-cache-outlet.tsx
1249
- function RouterCacheOutlet(props) {
1250
- const { children } = props;
1251
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCacheManager, {}), children] });
1252
- }
1253
- //#endregion
1254
- //#region src/hooks/use-route-cache-active.ts
1255
- function useRoutePathname(pathname) {
1256
- const locationPathname = (0, _tanstack_react_router.useLocation)({ select: (location) => location.pathname });
1257
- return normalizeCachedRoutePathname(pathname ?? locationPathname);
1258
- }
1259
- function useRouteCacheActive(pathname) {
1260
- const routePathname = useRoutePathname(pathname);
1261
- const [isActive, setIsActive] = (0, react.useState)(true);
1262
- useEventListener({ on: { activeChange: ({ pathname: changedPathname, mode }) => {
1263
- if (changedPathname === routePathname) setIsActive(mode === "visible");
1264
- } } });
1265
- return isActive;
1266
- }
1267
- //#endregion
1268
- //#region src/hooks/use-route-cache-activity.ts
1269
- function useRouteCacheActivity(fn) {
1270
- const routePathname = (0, _tanstack_react_router.useLocation)({ select: (location) => location.pathname });
1271
- useEventListener({ on: { activeChange: ({ pathname, mode, callback }) => {
1272
- if (pathname === routePathname) fn(mode === "visible");
1273
- callback?.();
1274
- } } });
1275
- }
1276
- //#endregion
1277
- //#region src/hooks/use-route-cache-effect.ts
1278
- function areDependenciesEqual(previousDeps, nextDeps) {
1279
- if (previousDeps?.length !== nextDeps.length) return false;
1280
- for (let index = 0; index < nextDeps.length; index += 1) if (!Object.is(previousDeps[index], nextDeps[index])) return false;
1281
- return true;
1282
- }
1283
- function useRouteCacheEffect(activeCallback, deps = []) {
1284
- const returnValue = (0, react.useRef)(void 0);
1285
- const isActiveRef = (0, react.useRef)(false);
1286
- const previousCallbackRef = (0, react.useRef)(void 0);
1287
- const previousDepsRef = (0, react.useRef)(void 0);
1288
- (0, react.useEffect)(() => {
1289
- const callbackChanged = previousCallbackRef.current !== activeCallback;
1290
- const dependenciesChanged = !areDependenciesEqual(previousDepsRef.current, deps);
1291
- if (!(callbackChanged || dependenciesChanged)) return;
1292
- previousCallbackRef.current = activeCallback;
1293
- previousDepsRef.current = deps;
1294
- if (!isActiveRef.current) return;
1295
- returnValue.current?.();
1296
- returnValue.current = activeCallback();
1297
- });
1298
- (0, react.useEffect)(() => () => {
1299
- returnValue.current?.();
1300
- }, []);
1301
- useRouteCacheActivity((active) => {
1302
- if (active) {
1303
- isActiveRef.current = true;
1304
- returnValue.current = activeCallback();
1305
- return;
1306
- }
1307
- isActiveRef.current = false;
1308
- returnValue.current?.();
1309
- });
1310
- }
1311
- //#endregion
1312
- //#region src/hooks/use-route-cache-error-boundary.ts
1313
- function useRouteCacheErrorBoundary(pathname) {
1314
- const context = useOptionalRouterCacheContext();
1315
- const routePathname = (0, _tanstack_react_router.useLocation)({ select: (location) => location.pathname });
1316
- (0, react.useEffect)(() => {
1317
- if (!context) return;
1318
- const targetPathname = normalizeCachedRoutePathname(pathname ?? routePathname);
1319
- context.retainErroredRoute(targetPathname);
1320
- return () => {
1321
- context.releaseErroredRoute(targetPathname);
1322
- };
1323
- }, [
1324
- context,
1325
- pathname,
1326
- routePathname
1327
- ]);
1328
- }
1329
- //#endregion
1330
- //#region src/hooks/use-route-cache-navigation.ts
1331
- const INITIAL_STATE = {
1332
- activeNavigation: null,
1333
- lastCompletedNavigation: null
1334
- };
1335
- function useRouteCacheNavigation() {
1336
- const [state, setState] = (0, react.useState)(INITIAL_STATE);
1337
- useEventListener({ on: {
1338
- cachedNavigationStart: (navigation) => {
1339
- setState((current) => ({
1340
- ...current,
1341
- activeNavigation: navigation
1342
- }));
1343
- },
1344
- cachedNavigationCancel: (navigation) => {
1345
- setState((current) => {
1346
- if (current.activeNavigation?.pathname !== navigation.pathname) return current;
1347
- return {
1348
- ...current,
1349
- activeNavigation: null
1350
- };
1351
- });
1352
- },
1353
- cachedNavigationComplete: (navigation) => {
1354
- setState((current) => ({
1355
- activeNavigation: current.activeNavigation?.pathname === navigation.pathname ? null : current.activeNavigation,
1356
- lastCompletedNavigation: navigation
1357
- }));
1358
- }
1359
- } });
1360
- return state;
1361
- }
1362
- //#endregion
1363
- //#region src/hooks/use-update.ts
1364
- function useUpdate() {
1365
- const [, setState] = (0, react.useState)({});
1366
- return (0, react.useCallback)(() => setState({}), []);
1367
- }
1368
- //#endregion
1369
- //#region src/hooks/use-router-cache.ts
1370
- function useRouterCache() {
1371
- const { cachedRoutes, deleteCachedRoutes } = useRouterCacheContext();
1372
- const update = useUpdate();
1373
- useEventListener({ on: { activeChange: () => {
1374
- update();
1375
- } } });
1376
- const destroy = (pathname) => {
1377
- deleteCachedRoutes(Array.isArray(pathname) ? pathname : [pathname]);
1378
- };
1379
- const destroyAll = () => {
1380
- deleteCachedRoutes(Object.keys(cachedRoutes));
1381
- };
1382
- const invalidateWhere = (predicate) => {
1383
- const pathnames = Object.entries(cachedRoutes).flatMap(([pathname, route]) => predicate(pathname, route) ? [pathname] : []);
1384
- if (pathnames.length > 0) deleteCachedRoutes(pathnames);
1385
- return pathnames;
1386
- };
1387
- return {
1388
- cachedRoutes,
1389
- destroy,
1390
- destroyAll,
1391
- invalidateWhere,
1392
- isCached: (pathname) => Object.hasOwn(cachedRoutes, normalizeCachedRoutePathname(pathname))
1393
- };
1394
- }
1395
- //#endregion
1396
- exports.RouterCacheOutlet = RouterCacheOutlet;
1397
- exports.RouterCacheProvider = RouterCacheProvider;
1398
- exports.useRouteCacheActive = useRouteCacheActive;
1399
- exports.useRouteCacheActivity = useRouteCacheActivity;
1400
- exports.useRouteCacheEffect = useRouteCacheEffect;
1401
- exports.useRouteCacheErrorBoundary = useRouteCacheErrorBoundary;
1402
- exports.useRouteCacheNavigation = useRouteCacheNavigation;
1403
- exports.useRouterCache = useRouterCache;
2
+ const require_route_cache_static_data = require("./route-cache-static-data.cjs");
3
+ const require_router_cache = require("./contexts/router-cache.cjs");
4
+ const require_router_cache_outlet = require("./components/router-cache-outlet.cjs");
5
+ const require_use_route_cache_active = require("./hooks/use-route-cache-active.cjs");
6
+ const require_use_route_cache_activity = require("./hooks/use-route-cache-activity.cjs");
7
+ const require_use_route_cache_effect = require("./hooks/use-route-cache-effect.cjs");
8
+ const require_use_route_cache_error_boundary = require("./hooks/use-route-cache-error-boundary.cjs");
9
+ const require_use_route_cache_navigation = require("./hooks/use-route-cache-navigation.cjs");
10
+ const require_use_router_cache = require("./hooks/use-router-cache.cjs");
11
+ exports.RouterCacheOutlet = require_router_cache_outlet.RouterCacheOutlet;
12
+ exports.RouterCacheProvider = require_router_cache.RouterCacheProvider;
13
+ exports.defineRouteCache = require_route_cache_static_data.defineRouteCache;
14
+ exports.useRouteCacheActive = require_use_route_cache_active.useRouteCacheActive;
15
+ exports.useRouteCacheActivity = require_use_route_cache_activity.useRouteCacheActivity;
16
+ exports.useRouteCacheEffect = require_use_route_cache_effect.useRouteCacheEffect;
17
+ exports.useRouteCacheErrorBoundary = require_use_route_cache_error_boundary.useRouteCacheErrorBoundary;
18
+ exports.useRouteCacheNavigation = require_use_route_cache_navigation.useRouteCacheNavigation;
19
+ exports.useRouterCache = require_use_router_cache.useRouterCache;