tanstack-router-cache 0.1.0 → 0.1.2

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/dist/index.cjs CHANGED
@@ -1,66 +1,1345 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let src_components_router_cache_outlet_tsx = require("./components/router-cache-outlet.tsx");
3
- let src_contexts_router_cache_tsx = require("./contexts/router-cache.tsx");
4
- let src_hooks_use_route_cache_active_ts = require("./hooks/use-route-cache-active.ts");
5
- let src_hooks_use_route_cache_activity_ts = require("./hooks/use-route-cache-activity.ts");
6
- let src_hooks_use_route_cache_effect_ts = require("./hooks/use-route-cache-effect.ts");
7
- let src_hooks_use_route_cache_error_boundary_ts = require("./hooks/use-route-cache-error-boundary.ts");
8
- let src_hooks_use_route_cache_navigation_ts = require("./hooks/use-route-cache-navigation.ts");
9
- let src_hooks_use_router_cache_ts = require("./hooks/use-router-cache.ts");
10
- Object.defineProperty(exports, "RouterCacheOutlet", {
11
- enumerable: true,
12
- get: function() {
13
- return src_components_router_cache_outlet_tsx.RouterCacheOutlet;
14
- }
15
- });
16
- Object.defineProperty(exports, "RouterCacheProvider", {
17
- enumerable: true,
18
- get: function() {
19
- return src_contexts_router_cache_tsx.RouterCacheProvider;
20
- }
21
- });
22
- Object.defineProperty(exports, "useRouteCacheActive", {
23
- enumerable: true,
24
- get: function() {
25
- return src_hooks_use_route_cache_active_ts.useRouteCacheActive;
26
- }
27
- });
28
- Object.defineProperty(exports, "useRouteCacheActivity", {
29
- enumerable: true,
30
- get: function() {
31
- return src_hooks_use_route_cache_activity_ts.useRouteCacheActivity;
32
- }
33
- });
34
- Object.defineProperty(exports, "useRouteCacheEffect", {
35
- enumerable: true,
36
- get: function() {
37
- return src_hooks_use_route_cache_effect_ts.useRouteCacheEffect;
38
- }
39
- });
40
- Object.defineProperty(exports, "useRouteCacheErrorBoundary", {
41
- enumerable: true,
42
- get: function() {
43
- return src_hooks_use_route_cache_error_boundary_ts.useRouteCacheErrorBoundary;
44
- }
45
- });
46
- Object.defineProperty(exports, "useRouteCacheNavigation", {
47
- enumerable: true,
48
- get: function() {
49
- return src_hooks_use_route_cache_navigation_ts.useRouteCacheNavigation;
50
- }
51
- });
52
- Object.defineProperty(exports, "useRouterCache", {
53
- enumerable: true,
54
- get: function() {
55
- return src_hooks_use_router_cache_ts.useRouterCache;
56
- }
57
- });
58
- var src_types_ts = require("./types.ts");
59
- Object.keys(src_types_ts).forEach(function(k) {
60
- if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
61
- enumerable: true,
62
- get: function() {
63
- return src_types_ts[k];
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;
64
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) {
375
+ lastWarnedCountRef.current = nextSnapshot.totalCachedRouteCount;
376
+ console.warn("[tanstack-router-cache] cached route count exceeded threshold", nextSnapshot);
377
+ }
378
+ });
379
+ return () => {
380
+ globalThis.cancelAnimationFrame(rafId);
381
+ if (debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ === api) debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ = void 0;
382
+ };
383
+ }, [
384
+ cachedRoutes,
385
+ snapshot,
386
+ visiblePathname
387
+ ]);
388
+ }
389
+ //#endregion
390
+ //#region src/components/cached-outlet.tsx
391
+ function CachedOutlet(props) {
392
+ const { matchId, routerSnapshot } = props;
393
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.RouterContextProvider, {
394
+ router: routerSnapshot,
395
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.Match, { matchId })
396
+ });
397
+ }
398
+ //#endregion
399
+ //#region src/dom/dismiss-transient-ui.ts
400
+ const documentStates = /* @__PURE__ */ new WeakMap();
401
+ const PERSISTENT_EXTERNAL_ATTRIBUTE = "data-router-cache-persistent-external";
402
+ function isBlurTarget(element) {
403
+ return element instanceof HTMLElement;
404
+ }
405
+ function getOrCreateDocumentState(documentObject) {
406
+ const existingState = documentStates.get(documentObject);
407
+ if (existingState) return existingState;
408
+ const state = {
409
+ observer: null,
410
+ ownerByElement: /* @__PURE__ */ new WeakMap(),
411
+ routes: /* @__PURE__ */ new Map(),
412
+ visiblePathname: null
413
+ };
414
+ if (typeof MutationObserver !== "undefined") {
415
+ state.observer = new MutationObserver((mutations) => {
416
+ handleDomMutations(mutations, state);
417
+ });
418
+ state.observer.observe(documentObject.body ?? documentObject.documentElement, {
419
+ childList: true,
420
+ subtree: true
421
+ });
422
+ }
423
+ documentStates.set(documentObject, state);
424
+ return state;
425
+ }
426
+ function getOrCreateRouteState(state, pathname) {
427
+ const existingRouteState = state.routes.get(pathname);
428
+ if (existingRouteState) return existingRouteState;
429
+ const routeState = {
430
+ hiddenElements: /* @__PURE__ */ new Map(),
431
+ ownedElements: /* @__PURE__ */ new Set()
432
+ };
433
+ state.routes.set(pathname, routeState);
434
+ return routeState;
435
+ }
436
+ function getHoveredElements(documentObject) {
437
+ try {
438
+ return Array.from(documentObject.querySelectorAll(":hover")).reverse();
439
+ } catch {
440
+ return [];
441
+ }
442
+ }
443
+ function getOwnedExternalElements(documentObject, pathname) {
444
+ const routeState = getOrCreateRouteState(getOrCreateDocumentState(documentObject), pathname);
445
+ return Array.from(routeState.ownedElements).filter((element) => element.isConnected);
446
+ }
447
+ function dispatchEscapeKeyboardEvent(target, documentObject, type) {
448
+ const keyboardEventConstructor = documentObject.defaultView?.KeyboardEvent ?? KeyboardEvent;
449
+ target.dispatchEvent(new keyboardEventConstructor(type, {
450
+ bubbles: true,
451
+ cancelable: true,
452
+ code: "Escape",
453
+ key: "Escape",
454
+ keyCode: 27,
455
+ which: 27
456
+ }));
457
+ }
458
+ function dispatchHoverExitEvent(target, documentObject, type) {
459
+ const eventConstructor = type.startsWith("pointer") ? documentObject.defaultView?.PointerEvent ?? documentObject.defaultView?.MouseEvent ?? MouseEvent : documentObject.defaultView?.MouseEvent ?? MouseEvent;
460
+ target.dispatchEvent(new eventConstructor(type, {
461
+ bubbles: type.endsWith("out"),
462
+ cancelable: true,
463
+ composed: true,
464
+ relatedTarget: documentObject.body
465
+ }));
466
+ }
467
+ function dispatchHoverExitEvents(hoveredElements, documentObject) {
468
+ for (const hoveredElement of hoveredElements) {
469
+ dispatchHoverExitEvent(hoveredElement, documentObject, "pointerout");
470
+ dispatchHoverExitEvent(hoveredElement, documentObject, "pointerleave");
471
+ dispatchHoverExitEvent(hoveredElement, documentObject, "mouseout");
472
+ dispatchHoverExitEvent(hoveredElement, documentObject, "mouseleave");
473
+ }
474
+ }
475
+ function focusDocumentBody(documentObject) {
476
+ const { body } = documentObject;
477
+ const previousTabIndex = body.getAttribute("tabindex");
478
+ if (previousTabIndex === null) body.setAttribute("tabindex", "-1");
479
+ body.focus();
480
+ if (previousTabIndex === null) body.removeAttribute("tabindex");
481
+ }
482
+ function getRouterCacheContainerAncestor(element) {
483
+ const container = element?.closest("[data-router-cache-container=\"true\"]");
484
+ return container instanceof HTMLElement ? container : null;
485
+ }
486
+ function isElementInsideAnyRouterCacheContainer(element) {
487
+ return getRouterCacheContainerAncestor(element) !== null;
488
+ }
489
+ function containsRouterCacheContainer(element) {
490
+ return element.querySelector("[data-router-cache-container=\"true\"]") instanceof HTMLElement;
491
+ }
492
+ function isElementConnectedOutsideRouterCacheContainer(element) {
493
+ return element.isConnected && !isElementInsideAnyRouterCacheContainer(element);
494
+ }
495
+ function isPersistentExternalElement(element) {
496
+ return element.closest(`[${PERSISTENT_EXTERNAL_ATTRIBUTE}="true"]`) instanceof HTMLElement;
497
+ }
498
+ function getTrackableElementsFromNode(node) {
499
+ if (node instanceof HTMLElement) return [node];
500
+ if (node instanceof DocumentFragment) return Array.from(node.children).flatMap((child) => child instanceof HTMLElement ? [child] : []);
501
+ return [];
502
+ }
503
+ function forEachRemovedNode(mutations, callback) {
504
+ for (const mutation of mutations) for (const removedNode of mutation.removedNodes) callback(removedNode);
505
+ }
506
+ function forEachAddedNode(mutations, callback) {
507
+ for (const mutation of mutations) for (const addedNode of mutation.addedNodes) callback(addedNode);
508
+ }
509
+ function handleDomMutations(mutations, state) {
510
+ forEachRemovedNode(mutations, (removedNode) => {
511
+ untrackRemovedNode(removedNode, state);
512
+ });
513
+ if (!state.visiblePathname) return;
514
+ forEachAddedNode(mutations, (addedNode) => {
515
+ trackAddedNode(addedNode, state.visiblePathname, state);
516
+ });
517
+ }
518
+ function hasTrackedAncestor(element, state) {
519
+ let ancestor = element.parentElement;
520
+ while (ancestor) {
521
+ if (state.ownerByElement.get(ancestor)) return true;
522
+ ancestor = ancestor.parentElement;
523
+ }
524
+ return false;
525
+ }
526
+ function restoreOwnedExternalElement(routeState, element) {
527
+ const snapshot = routeState.hiddenElements.get(element);
528
+ if (!snapshot) return;
529
+ element.style.setProperty("display", snapshot.display, snapshot.displayPriority);
530
+ if (snapshot.ariaHidden === null) element.removeAttribute("aria-hidden");
531
+ else element.setAttribute("aria-hidden", snapshot.ariaHidden);
532
+ element.inert = snapshot.inert;
533
+ routeState.hiddenElements.delete(element);
534
+ }
535
+ function trackExternalElement(pathname, element, state) {
536
+ if (!isElementConnectedOutsideRouterCacheContainer(element) || containsRouterCacheContainer(element) || isPersistentExternalElement(element) || hasTrackedAncestor(element, state)) return;
537
+ const currentOwner = state.ownerByElement.get(element);
538
+ if (currentOwner === pathname) return;
539
+ if (currentOwner) {
540
+ const previousRouteState = state.routes.get(currentOwner);
541
+ if (previousRouteState) {
542
+ restoreOwnedExternalElement(previousRouteState, element);
543
+ previousRouteState.ownedElements.delete(element);
544
+ }
545
+ }
546
+ getOrCreateRouteState(state, pathname).ownedElements.add(element);
547
+ state.ownerByElement.set(element, pathname);
548
+ }
549
+ function trackAddedNode(node, pathname, state) {
550
+ const elements = getTrackableElementsFromNode(node);
551
+ for (const element of elements) trackExternalElement(pathname, element, state);
552
+ }
553
+ function untrackElement(routeState, state, element) {
554
+ routeState.hiddenElements.delete(element);
555
+ routeState.ownedElements.delete(element);
556
+ state.ownerByElement.delete(element);
557
+ }
558
+ function untrackRemovedNode(node, state) {
559
+ if (!(node instanceof HTMLElement)) return;
560
+ 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);
561
+ }
562
+ function hideOwnedExternalElements(documentObject, pathname) {
563
+ const routeState = getOrCreateRouteState(getOrCreateDocumentState(documentObject), pathname);
564
+ for (const element of Array.from(routeState.ownedElements)) {
565
+ if (!isElementConnectedOutsideRouterCacheContainer(element)) {
566
+ routeState.ownedElements.delete(element);
567
+ routeState.hiddenElements.delete(element);
568
+ continue;
569
+ }
570
+ if (routeState.hiddenElements.has(element)) continue;
571
+ routeState.hiddenElements.set(element, {
572
+ ariaHidden: element.getAttribute("aria-hidden"),
573
+ display: element.style.getPropertyValue("display"),
574
+ displayPriority: element.style.getPropertyPriority("display"),
575
+ inert: element.inert
576
+ });
577
+ element.style.setProperty("display", "none", "important");
578
+ element.setAttribute("aria-hidden", "true");
579
+ element.inert = true;
580
+ }
581
+ }
582
+ function showOwnedExternalElements(documentObject, pathname) {
583
+ const routeState = getOrCreateRouteState(getOrCreateDocumentState(documentObject), pathname);
584
+ for (const [element] of Array.from(routeState.hiddenElements)) {
585
+ if (!element.isConnected) {
586
+ routeState.hiddenElements.delete(element);
587
+ routeState.ownedElements.delete(element);
588
+ continue;
589
+ }
590
+ restoreOwnedExternalElement(routeState, element);
591
+ }
592
+ }
593
+ function initializeTransientUiTracking(documentObject) {
594
+ getOrCreateDocumentState(documentObject);
595
+ }
596
+ function syncTransientUiRouteActivity(pathname, mode) {
597
+ if (typeof document === "undefined") return;
598
+ const documentState = getOrCreateDocumentState(document);
599
+ getOrCreateRouteState(documentState, pathname);
600
+ if (mode === "visible") {
601
+ documentState.visiblePathname = pathname;
602
+ showOwnedExternalElements(document, pathname);
603
+ return;
604
+ }
605
+ if (documentState.visiblePathname === pathname) documentState.visiblePathname = null;
606
+ dispatchHoverExitEvents(getOwnedExternalElements(document, pathname), document);
607
+ hideOwnedExternalElements(document, pathname);
608
+ }
609
+ function dismissTransientUi(container, pathname) {
610
+ if (typeof document === "undefined" || !container) return;
611
+ initializeTransientUiTracking(document);
612
+ const hoveredElements = getHoveredElements(document);
613
+ const hoverExitTargets = Array.from(new Set([...hoveredElements, ...getOwnedExternalElements(document, pathname)]));
614
+ const activeElement = document.activeElement;
615
+ const activeElementBelongsToHoveredTree = activeElement instanceof Element && hoverExitTargets.some((hoveredElement) => hoveredElement === activeElement || hoveredElement.contains(activeElement));
616
+ if (activeElement instanceof Element && (container.contains(activeElement) || activeElementBelongsToHoveredTree) && isBlurTarget(activeElement)) activeElement.blur();
617
+ dispatchHoverExitEvents(hoverExitTargets, document);
618
+ dispatchEscapeKeyboardEvent(document, document, "keydown");
619
+ dispatchEscapeKeyboardEvent(document, document, "keyup");
620
+ hideOwnedExternalElements(document, pathname);
621
+ const nextActiveElement = document.activeElement;
622
+ const nextActiveElementBelongsToHoveredTree = nextActiveElement instanceof Element && hoverExitTargets.some((hoveredElement) => hoveredElement === nextActiveElement || hoveredElement.contains(nextActiveElement));
623
+ if (nextActiveElement instanceof Element && (container.contains(nextActiveElement) || nextActiveElementBelongsToHoveredTree)) focusDocumentBody(document);
624
+ }
625
+ //#endregion
626
+ //#region src/components/off-screen-in.tsx
627
+ const windowScrollPositions = /* @__PURE__ */ new Map();
628
+ const IMMEDIATE_SCROLL_RESTORE_DELAY = 0;
629
+ const EARLY_SCROLL_RESTORE_DELAY = 80;
630
+ const MIDDLE_SCROLL_RESTORE_DELAY = 240;
631
+ const LATE_SCROLL_RESTORE_DELAY = 600;
632
+ const FINAL_SCROLL_RESTORE_DELAY = 1e3;
633
+ const SCROLL_TRACKING_INTERVAL_MS = 200;
634
+ const SCROLL_KEYS = new Set([
635
+ " ",
636
+ "ArrowDown",
637
+ "ArrowLeft",
638
+ "ArrowRight",
639
+ "ArrowUp",
640
+ "End",
641
+ "Home",
642
+ "PageDown",
643
+ "PageUp"
644
+ ]);
645
+ const SCROLL_RESTORE_DELAYS = [
646
+ IMMEDIATE_SCROLL_RESTORE_DELAY,
647
+ EARLY_SCROLL_RESTORE_DELAY,
648
+ MIDDLE_SCROLL_RESTORE_DELAY,
649
+ LATE_SCROLL_RESTORE_DELAY,
650
+ FINAL_SCROLL_RESTORE_DELAY
651
+ ];
652
+ function OffScreenIn(props) {
653
+ const { mode, children, containerRef, pathname } = props;
654
+ const localContainerRef = (0, react.useRef)(null);
655
+ const attachContainerRef = (node) => {
656
+ localContainerRef.current = node;
657
+ if (containerRef) containerRef.current = node;
658
+ };
659
+ (0, react.useLayoutEffect)(() => {
660
+ if (typeof document === "undefined") return;
661
+ initializeTransientUiTracking(document);
662
+ }, []);
663
+ (0, react.useLayoutEffect)(() => {
664
+ if (!pathname) return;
665
+ syncTransientUiRouteActivity(pathname, mode);
666
+ }, [mode, pathname]);
667
+ (0, react.useLayoutEffect)(() => {
668
+ const visibleContainer = localContainerRef.current;
669
+ return () => {
670
+ if (!pathname || mode !== "visible") return;
671
+ dismissTransientUi(visibleContainer, pathname);
672
+ };
673
+ }, [mode, pathname]);
674
+ (0, react.useLayoutEffect)(() => {
675
+ if (typeof globalThis === "undefined" || !pathname) return;
676
+ const saveScrollPosition = () => {
677
+ windowScrollPositions.set(pathname, {
678
+ x: globalThis.scrollX,
679
+ y: globalThis.scrollY
680
+ });
681
+ };
682
+ if (mode === "hidden") return;
683
+ const savedPosition = windowScrollPositions.get(pathname);
684
+ let userRequestedScroll = false;
685
+ let scrollTrackingIntervalId;
686
+ const startScrollTracking = () => {
687
+ scrollTrackingIntervalId ??= globalThis.setInterval(saveScrollPosition, SCROLL_TRACKING_INTERVAL_MS);
688
+ };
689
+ const handleScroll = () => {
690
+ if (!savedPosition || userRequestedScroll) saveScrollPosition();
691
+ };
692
+ const markUserRequestedScroll = () => {
693
+ userRequestedScroll = true;
694
+ };
695
+ const handleKeyDown = (event) => {
696
+ if (SCROLL_KEYS.has(event.key)) markUserRequestedScroll();
697
+ };
698
+ globalThis.addEventListener("scroll", handleScroll, { passive: true });
699
+ globalThis.addEventListener("wheel", markUserRequestedScroll, { passive: true });
700
+ globalThis.addEventListener("touchstart", markUserRequestedScroll, { passive: true });
701
+ globalThis.addEventListener("pointerdown", markUserRequestedScroll, { passive: true });
702
+ globalThis.addEventListener("keydown", handleKeyDown);
703
+ if (!savedPosition) {
704
+ startScrollTracking();
705
+ saveScrollPosition();
706
+ return () => {
707
+ if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
708
+ globalThis.removeEventListener("scroll", handleScroll);
709
+ globalThis.removeEventListener("wheel", markUserRequestedScroll);
710
+ globalThis.removeEventListener("touchstart", markUserRequestedScroll);
711
+ globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
712
+ globalThis.removeEventListener("keydown", handleKeyDown);
713
+ };
714
+ }
715
+ const restoreScrollPosition = () => {
716
+ if (userRequestedScroll) return;
717
+ globalThis.scrollTo(savedPosition.x, savedPosition.y);
718
+ saveScrollPosition();
719
+ startScrollTracking();
720
+ };
721
+ const timeoutIds = [];
722
+ let rafId = globalThis.requestAnimationFrame(() => {
723
+ rafId = globalThis.requestAnimationFrame(() => {
724
+ restoreScrollPosition();
725
+ for (const delay of SCROLL_RESTORE_DELAYS) timeoutIds.push(globalThis.setTimeout(restoreScrollPosition, delay));
726
+ });
727
+ });
728
+ return () => {
729
+ globalThis.cancelAnimationFrame(rafId);
730
+ for (const timeoutId of timeoutIds) globalThis.clearTimeout(timeoutId);
731
+ if (scrollTrackingIntervalId !== void 0) globalThis.clearInterval(scrollTrackingIntervalId);
732
+ globalThis.removeEventListener("scroll", handleScroll);
733
+ globalThis.removeEventListener("wheel", markUserRequestedScroll);
734
+ globalThis.removeEventListener("touchstart", markUserRequestedScroll);
735
+ globalThis.removeEventListener("pointerdown", markUserRequestedScroll);
736
+ globalThis.removeEventListener("keydown", handleKeyDown);
737
+ };
738
+ }, [mode, pathname]);
739
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Activity, {
740
+ mode,
741
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
742
+ className: "flex min-h-full w-full flex-1 flex-col",
743
+ "data-router-cache-container": "true",
744
+ "data-router-cache-mode": mode,
745
+ "data-router-cache-pathname": pathname,
746
+ ref: attachContainerRef,
747
+ children
748
+ })
749
+ });
750
+ }
751
+ //#endregion
752
+ //#region src/components/off-screen.tsx
753
+ function OffScreen(props) {
754
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OffScreenIn, { ...props });
755
+ }
756
+ //#endregion
757
+ //#region src/components/restore-cached-href.ts
758
+ const FALLBACK_ORIGIN = "http://tanstack-router-cache.local";
759
+ function parseHref(href) {
760
+ try {
761
+ const url = new URL(href, FALLBACK_ORIGIN);
762
+ return {
763
+ hasHash: url.hash.length > 0,
764
+ hasSearch: url.search.length > 0,
765
+ pathname: url.pathname
766
+ };
767
+ } catch {
768
+ return null;
769
+ }
770
+ }
771
+ function isBareRouteHref(href, currentPathname) {
772
+ const candidateHref = href ?? currentPathname;
773
+ const parsedHref = parseHref(candidateHref);
774
+ if (!parsedHref) return candidateHref === currentPathname;
775
+ return parsedHref.pathname === currentPathname && !parsedHref.hasSearch && !parsedHref.hasHash;
776
+ }
777
+ function shouldRestoreCachedHref({ cachedHref, currentHref, currentPathname, isRouteCacheEnabled, previousPathname }) {
778
+ if (!(isRouteCacheEnabled && cachedHref)) return false;
779
+ if (!previousPathname || previousPathname === currentPathname) return false;
780
+ if (!isBareRouteHref(currentHref, currentPathname)) return false;
781
+ return cachedHref !== (currentHref ?? currentPathname);
782
+ }
783
+ //#endregion
784
+ //#region src/components/route-cache-manager.tsx
785
+ const LIVE_ROUTER_METHODS = [
786
+ "buildLocation",
787
+ "commitLocation",
788
+ "invalidate",
789
+ "loadRouteChunk",
790
+ "navigate",
791
+ "preloadRoute"
792
+ ];
793
+ function createStaticStore(value) {
794
+ return {
795
+ get: () => value,
796
+ subscribe: () => ({ unsubscribe: () => void 0 })
797
+ };
798
+ }
799
+ function getLiveRouterMethodDescriptors(router) {
800
+ return Object.fromEntries(LIVE_ROUTER_METHODS.flatMap((methodName) => {
801
+ const method = router[methodName];
802
+ if (typeof method !== "function") return [];
803
+ return [[methodName, { value: method.bind(router) }]];
804
+ }));
805
+ }
806
+ function toRouterLocation(location) {
807
+ return { ...location };
808
+ }
809
+ function isReadyCachedRoute(route) {
810
+ return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.matchId && route.routerSnapshot);
811
+ }
812
+ function isRouteCacheEnabled(staticData) {
813
+ return staticData?.routeCache === true;
814
+ }
815
+ function hasErroredRouteMatch(matches) {
816
+ return matches.some((match) => match?.status === "error");
817
+ }
818
+ function hasRetainedRouteError(erroredRouteCounts, pathname) {
819
+ return (erroredRouteCounts[pathname] ?? 0) > 0;
820
+ }
821
+ function hasCurrentRouterMatchError({ matches, resolvedPathname, routerPathname }) {
822
+ return routerPathname === resolvedPathname && hasErroredRouteMatch(matches);
823
+ }
824
+ function hasCurrentRouteError({ erroredRouteCounts, matches, resolvedPathname, routerPathname }) {
825
+ return hasRetainedRouteError(erroredRouteCounts, routerPathname) || hasCurrentRouterMatchError({
826
+ matches,
827
+ resolvedPathname,
828
+ routerPathname
829
+ });
830
+ }
831
+ function isRouterMatch(match) {
832
+ return Boolean(match?.id && match.routeId);
833
+ }
834
+ function snapshotMatch(match) {
835
+ if (!isRouterMatch(match)) return;
836
+ return {
837
+ ...match,
838
+ _nonReactive: { ...match._nonReactive }
839
+ };
840
+ }
841
+ function shouldRefreshCachedRoute(route, routerHref) {
842
+ return !(isRouteCacheEnabled(route?.staticData) && route.ready) || route.href !== routerHref;
843
+ }
844
+ function syncReadyCachedRoute({ matchId, matches, routeId, route, routerLocation, router, routerResolvedLocation, routerHref, routerPathname, setCachedRoutes, staticData }) {
845
+ if (!shouldRefreshCachedRoute(route, routerHref)) return;
846
+ setCachedRoutes(routerPathname, {
847
+ href: routerHref,
848
+ matchId,
849
+ routeId,
850
+ ready: true,
851
+ routerSnapshot: matchId ? createRouterSnapshot({
852
+ matches,
853
+ router,
854
+ routerLocation,
855
+ routerResolvedLocation
856
+ }) : void 0,
857
+ staticData
858
+ });
859
+ }
860
+ function syncCachedRouteState({ isCurrentMatchReady, isCurrentMatchResolved, deleteCachedRoutes, isCurrentRouteErrored, matchId, matches, route, routeId, router, routerHref, routerLocation, routerPathname, routerResolvedLocation, setCachedRoutes, staticData }) {
861
+ if (isCurrentRouteErrored) {
862
+ if (route) deleteCachedRoutes([routerPathname]);
863
+ return;
864
+ }
865
+ if (!isCurrentMatchResolved) return;
866
+ if (staticData && isRouteCacheEnabled(staticData)) {
867
+ if (!isCurrentMatchReady) return;
868
+ syncReadyCachedRoute({
869
+ matchId,
870
+ matches,
871
+ routeId,
872
+ route,
873
+ routerLocation,
874
+ router,
875
+ routerResolvedLocation,
876
+ routerHref,
877
+ routerPathname,
878
+ setCachedRoutes,
879
+ staticData
880
+ });
881
+ return;
882
+ }
883
+ if (route) deleteCachedRoutes([routerPathname]);
884
+ }
885
+ function createRouterSnapshot({ matches, router, routerLocation, routerResolvedLocation }) {
886
+ const snapshotMatches = matches.reduce((snapshot, match) => {
887
+ const nextMatch = snapshotMatch(match);
888
+ if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
889
+ return snapshot;
890
+ }, []);
891
+ const matchStores = new Map(snapshotMatches.map((match) => {
892
+ const store = Object.assign(createStaticStore(match), { routeId: match.routeId });
893
+ return [match.id, store];
894
+ }));
895
+ const snapshotState = {
896
+ ...router.stores.__store.get(),
897
+ matches: snapshotMatches,
898
+ location: routerLocation,
899
+ resolvedLocation: routerResolvedLocation
900
+ };
901
+ const routeMatchStoreCache = /* @__PURE__ */ new Map();
902
+ const stores = {
903
+ ...router.stores,
904
+ status: createStaticStore(router.stores.status.get()),
905
+ loadedAt: createStaticStore(router.stores.loadedAt.get()),
906
+ isLoading: createStaticStore(router.stores.isLoading.get()),
907
+ isTransitioning: createStaticStore(router.stores.isTransitioning.get()),
908
+ location: createStaticStore(routerLocation),
909
+ resolvedLocation: createStaticStore(routerResolvedLocation),
910
+ statusCode: createStaticStore(router.stores.statusCode.get()),
911
+ redirect: createStaticStore(router.stores.redirect.get()),
912
+ matchesId: createStaticStore(snapshotMatches.map((match) => match.id)),
913
+ pendingIds: createStaticStore([]),
914
+ cachedIds: createStaticStore([]),
915
+ matches: createStaticStore(snapshotMatches),
916
+ pendingMatches: createStaticStore([]),
917
+ cachedMatches: createStaticStore([]),
918
+ firstId: createStaticStore(snapshotMatches[0]?.id),
919
+ hasPending: createStaticStore(snapshotMatches.some((match) => match.status === "pending")),
920
+ matchRouteDeps: createStaticStore({
921
+ locationHref: routerLocation.href,
922
+ resolvedLocationHref: routerResolvedLocation?.href,
923
+ status: snapshotState.status
924
+ }),
925
+ __store: createStaticStore(snapshotState),
926
+ matchStores,
927
+ pendingMatchStores: /* @__PURE__ */ new Map(),
928
+ cachedMatchStores: /* @__PURE__ */ new Map(),
929
+ getRouteMatchStore: (routeId) => {
930
+ let cached = routeMatchStoreCache.get(routeId);
931
+ if (!cached) {
932
+ cached = createStaticStore(snapshotMatches.find((match) => match.routeId === routeId));
933
+ routeMatchStoreCache.set(routeId, cached);
934
+ }
935
+ return cached;
936
+ },
937
+ setMatches: () => void 0,
938
+ setPending: () => void 0,
939
+ setCached: () => void 0
940
+ };
941
+ const routerSnapshot = Object.create(router);
942
+ Object.defineProperties(routerSnapshot, {
943
+ stores: { value: stores },
944
+ latestLocation: { value: routerLocation },
945
+ getMatch: { value: (matchId) => matchStores.get(matchId)?.get() },
946
+ updateMatch: { value: () => void 0 },
947
+ ...getLiveRouterMethodDescriptors(router)
65
948
  });
66
- });
949
+ return routerSnapshot;
950
+ }
951
+ function getRouterCacheStaticData(childMatches, isCurrentMatchResolved) {
952
+ if (!isCurrentMatchResolved) return;
953
+ for (let i = childMatches.length - 1; i >= 0; i--) {
954
+ const match = childMatches[i];
955
+ if (match?.staticData && isRouteCacheEnabled(match.staticData)) return match.staticData;
956
+ }
957
+ }
958
+ function restoreCachedHref(router, href) {
959
+ router.navigate({
960
+ href,
961
+ replace: true,
962
+ resetScroll: false
963
+ }).catch(() => void 0);
964
+ }
965
+ function isAncestorPathname(ancestorPathname, pathname) {
966
+ if (ancestorPathname === pathname) return false;
967
+ if (ancestorPathname === "/") return pathname.startsWith("/");
968
+ return pathname.startsWith(`${ancestorPathname}/`);
969
+ }
970
+ function getShouldRenderLiveOutlet({ cachedRoutes, bypassCachedPathname, visiblePathname }) {
971
+ if (visiblePathname === bypassCachedPathname) return true;
972
+ const visibleRoute = cachedRoutes[visiblePathname];
973
+ return !isReadyCachedRoute(visibleRoute);
974
+ }
975
+ function renderCachedRoute({ bypassCachedPathname, pathname, route, visiblePathname }) {
976
+ if (pathname === bypassCachedPathname) return null;
977
+ const content = route.matchId && route.routerSnapshot ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CachedOutlet, {
978
+ matchId: route.matchId,
979
+ routerSnapshot: route.routerSnapshot
980
+ }) : null;
981
+ if (!content) return null;
982
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OffScreen, {
983
+ mode: visiblePathname === pathname ? "visible" : "hidden",
984
+ pathname,
985
+ children: content
986
+ }, pathname);
987
+ }
988
+ function buildRouteCacheModes(cachedRoutes, visiblePathname) {
989
+ const nextModes = /* @__PURE__ */ new Map();
990
+ for (const pathname of Object.keys(cachedRoutes)) nextModes.set(pathname, visiblePathname === pathname ? "visible" : "hidden");
991
+ return nextModes;
992
+ }
993
+ function syncCachedRouteActivityEvents(params) {
994
+ const nextModes = buildRouteCacheModes(params.cachedRoutes, params.visiblePathname);
995
+ for (const [pathname, mode] of nextModes) {
996
+ const previousMode = params.previousRouteCacheModes.get(pathname);
997
+ if (previousMode === void 0 && mode === "hidden") continue;
998
+ if (previousMode === mode) continue;
999
+ params.eventListener.emit("activeChange", {
1000
+ pathname,
1001
+ mode
1002
+ });
1003
+ }
1004
+ for (const [pathname] of params.previousRouteCacheModes) {
1005
+ if (nextModes.has(pathname)) continue;
1006
+ params.eventListener.emit("activeChange", {
1007
+ pathname,
1008
+ mode: "hidden"
1009
+ });
1010
+ }
1011
+ return nextModes;
1012
+ }
1013
+ function RouteCacheManager() {
1014
+ const { cachedRoutes, deleteCachedRoutes, erroredRouteCounts, setCachedRoutes, touchCachedRoutes } = useRouterCacheContext();
1015
+ const { eventListener } = useEventListener();
1016
+ const pendingCachedNavigationRef = (0, react.useRef)(null);
1017
+ const previousPathnameRef = (0, react.useRef)(void 0);
1018
+ const previousHrefRef = (0, react.useRef)(void 0);
1019
+ const previousRouteCacheModesRef = (0, react.useRef)(null);
1020
+ if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
1021
+ const previousVisiblePathnameRef = (0, react.useRef)(void 0);
1022
+ const routerLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => toRouterLocation(state.location) });
1023
+ const routerHref = routerLocation.href;
1024
+ const routerPathname = normalizeCachedRoutePathname(routerLocation.pathname);
1025
+ const routerResolvedLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => state.resolvedLocation ? toRouterLocation(state.resolvedLocation) : void 0 });
1026
+ const resolvedPathname = normalizeCachedRoutePathname(routerResolvedLocation?.pathname ?? routerPathname);
1027
+ const destinationRoute = cachedRoutes[routerPathname];
1028
+ const visiblePathname = routerPathname !== resolvedPathname && (isReadyCachedRoute(destinationRoute) || isAncestorPathname(routerPathname, resolvedPathname)) ? routerPathname : resolvedPathname;
1029
+ const matches = (0, _tanstack_react_router.useMatches)();
1030
+ const childMatches = (0, _tanstack_react_router.useChildMatches)();
1031
+ const router = (0, _tanstack_react_router.useRouter)();
1032
+ const currentMatch = matches.length ? matches.at(-1) : void 0;
1033
+ const outletRootMatch = childMatches.length ? childMatches[0] : currentMatch;
1034
+ const isCurrentMatchResolved = (currentMatch?.pathname ? normalizeCachedRoutePathname(currentMatch.pathname) : void 0) === routerPathname;
1035
+ const isCurrentMatchReady = isCurrentMatchResolved && currentMatch?.status === "success";
1036
+ const staticData = getRouterCacheStaticData(childMatches, isCurrentMatchResolved);
1037
+ const matchId = isCurrentMatchResolved && outletRootMatch ? outletRootMatch.id : void 0;
1038
+ const routeId = isCurrentMatchResolved && outletRootMatch ? outletRootMatch.routeId : void 0;
1039
+ const currentCachedRoute = cachedRoutes[routerPathname];
1040
+ const isCurrentRouteErrored = hasCurrentRouteError({
1041
+ erroredRouteCounts,
1042
+ matches,
1043
+ resolvedPathname,
1044
+ routerPathname
1045
+ });
1046
+ const bypassCachedPathname = isCurrentRouteErrored ? routerPathname : void 0;
1047
+ const previousPathname = previousPathnameRef.current;
1048
+ previousHrefRef.current;
1049
+ const shouldRestoreDestinationHref = shouldRestoreCachedHref({
1050
+ cachedHref: destinationRoute?.href,
1051
+ currentHref: routerHref,
1052
+ currentPathname: routerPathname,
1053
+ isRouteCacheEnabled: isRouteCacheEnabled(destinationRoute?.staticData),
1054
+ previousPathname
1055
+ });
1056
+ (0, react.useLayoutEffect)(() => {
1057
+ if (shouldRestoreDestinationHref) return;
1058
+ syncCachedRouteState({
1059
+ isCurrentMatchReady,
1060
+ isCurrentMatchResolved,
1061
+ isCurrentRouteErrored,
1062
+ matchId,
1063
+ matches,
1064
+ routeId,
1065
+ route: currentCachedRoute,
1066
+ routerLocation,
1067
+ router,
1068
+ routerResolvedLocation,
1069
+ routerHref,
1070
+ routerPathname,
1071
+ deleteCachedRoutes,
1072
+ setCachedRoutes,
1073
+ staticData
1074
+ });
1075
+ }, [
1076
+ currentCachedRoute,
1077
+ deleteCachedRoutes,
1078
+ isCurrentRouteErrored,
1079
+ isCurrentMatchReady,
1080
+ isCurrentMatchResolved,
1081
+ matchId,
1082
+ matches,
1083
+ routeId,
1084
+ router,
1085
+ routerLocation,
1086
+ routerResolvedLocation,
1087
+ routerHref,
1088
+ routerPathname,
1089
+ staticData,
1090
+ setCachedRoutes,
1091
+ shouldRestoreDestinationHref
1092
+ ]);
1093
+ (0, react.useLayoutEffect)(() => {
1094
+ const lastVisitedPathname = previousPathnameRef.current;
1095
+ const pendingNavigation = pendingCachedNavigationRef.current;
1096
+ if (pendingNavigation && pendingNavigation.pathname !== routerPathname && visiblePathname !== pendingNavigation.pathname) {
1097
+ eventListener.emit("cachedNavigationCancel", pendingNavigation);
1098
+ pendingCachedNavigationRef.current = null;
1099
+ }
1100
+ if (!lastVisitedPathname || lastVisitedPathname === routerPathname) return;
1101
+ if (!isReadyCachedRoute(destinationRoute)) return;
1102
+ if (pendingCachedNavigationRef.current?.pathname === routerPathname) return;
1103
+ const nextNavigation = {
1104
+ pathname: routerPathname,
1105
+ startedAt: performance.now()
1106
+ };
1107
+ pendingCachedNavigationRef.current = nextNavigation;
1108
+ eventListener.emit("cachedNavigationStart", nextNavigation);
1109
+ }, [
1110
+ destinationRoute,
1111
+ eventListener,
1112
+ routerPathname,
1113
+ visiblePathname
1114
+ ]);
1115
+ (0, react.useLayoutEffect)(() => {
1116
+ if (previousRouteCacheModesRef.current === null) previousRouteCacheModesRef.current = /* @__PURE__ */ new Map();
1117
+ previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
1118
+ cachedRoutes,
1119
+ eventListener,
1120
+ previousRouteCacheModes: previousRouteCacheModesRef.current,
1121
+ visiblePathname
1122
+ });
1123
+ }, [
1124
+ cachedRoutes,
1125
+ eventListener,
1126
+ visiblePathname
1127
+ ]);
1128
+ (0, react.useLayoutEffect)(() => {
1129
+ if (previousVisiblePathnameRef.current === visiblePathname || !cachedRoutes[visiblePathname]) {
1130
+ previousVisiblePathnameRef.current = visiblePathname;
1131
+ return;
1132
+ }
1133
+ previousVisiblePathnameRef.current = visiblePathname;
1134
+ touchCachedRoutes([visiblePathname]);
1135
+ }, [
1136
+ cachedRoutes,
1137
+ touchCachedRoutes,
1138
+ visiblePathname
1139
+ ]);
1140
+ (0, react.useEffect)(() => {
1141
+ const pendingNavigation = pendingCachedNavigationRef.current;
1142
+ if (visiblePathname !== pendingNavigation?.pathname) return;
1143
+ let firstFrameId = 0;
1144
+ let secondFrameId = 0;
1145
+ firstFrameId = globalThis.requestAnimationFrame(() => {
1146
+ const visibleAt = performance.now();
1147
+ secondFrameId = globalThis.requestAnimationFrame(() => {
1148
+ const paintedAt = performance.now();
1149
+ if (pendingCachedNavigationRef.current?.pathname !== pendingNavigation.pathname) return;
1150
+ pendingCachedNavigationRef.current = null;
1151
+ eventListener.emit("cachedNavigationComplete", {
1152
+ ...pendingNavigation,
1153
+ duration: paintedAt - pendingNavigation.startedAt,
1154
+ paintedAt,
1155
+ visibleAt
1156
+ });
1157
+ });
1158
+ });
1159
+ return () => {
1160
+ globalThis.cancelAnimationFrame(firstFrameId);
1161
+ globalThis.cancelAnimationFrame(secondFrameId);
1162
+ };
1163
+ }, [eventListener, visiblePathname]);
1164
+ (0, react.useLayoutEffect)(() => {
1165
+ if (!(shouldRestoreDestinationHref && destinationRoute?.href)) return;
1166
+ restoreCachedHref(router, destinationRoute.href);
1167
+ }, [
1168
+ destinationRoute?.href,
1169
+ router,
1170
+ shouldRestoreDestinationHref
1171
+ ]);
1172
+ (0, react.useEffect)(() => {
1173
+ previousPathnameRef.current = routerPathname;
1174
+ previousHrefRef.current = routerHref;
1175
+ }, [routerHref, routerPathname]);
1176
+ const shouldRenderLiveOutlet = getShouldRenderLiveOutlet({
1177
+ cachedRoutes,
1178
+ bypassCachedPathname,
1179
+ visiblePathname
1180
+ });
1181
+ useRouterCacheDebug(cachedRoutes, visiblePathname);
1182
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [Object.entries(cachedRoutes).map(([pathname, route]) => renderCachedRoute({
1183
+ bypassCachedPathname,
1184
+ pathname,
1185
+ route,
1186
+ visiblePathname
1187
+ })), shouldRenderLiveOutlet ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.Outlet, {}, `live-${routerPathname}`) : null] });
1188
+ }
1189
+ //#endregion
1190
+ //#region src/components/router-cache-outlet.tsx
1191
+ function RouterCacheOutlet(props) {
1192
+ const { children } = props;
1193
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCacheManager, {}), children] });
1194
+ }
1195
+ //#endregion
1196
+ //#region src/hooks/use-route-cache-active.ts
1197
+ function useRoutePathname(pathname) {
1198
+ const locationPathname = (0, _tanstack_react_router.useLocation)({ select: (location) => location.pathname });
1199
+ return normalizeCachedRoutePathname(pathname ?? locationPathname);
1200
+ }
1201
+ function useRouteCacheActive(pathname) {
1202
+ const routePathname = useRoutePathname(pathname);
1203
+ const [isActive, setIsActive] = (0, react.useState)(true);
1204
+ useEventListener({ on: { activeChange: ({ pathname: changedPathname, mode }) => {
1205
+ if (changedPathname === routePathname) setIsActive(mode === "visible");
1206
+ } } });
1207
+ return isActive;
1208
+ }
1209
+ //#endregion
1210
+ //#region src/hooks/use-route-cache-activity.ts
1211
+ function useRouteCacheActivity(fn) {
1212
+ const routePathname = (0, _tanstack_react_router.useLocation)({ select: (location) => location.pathname });
1213
+ useEventListener({ on: { activeChange: ({ pathname, mode, callback }) => {
1214
+ if (pathname === routePathname) fn(mode === "visible");
1215
+ callback?.();
1216
+ } } });
1217
+ }
1218
+ //#endregion
1219
+ //#region src/hooks/use-route-cache-effect.ts
1220
+ function areDependenciesEqual(previousDeps, nextDeps) {
1221
+ if (previousDeps?.length !== nextDeps.length) return false;
1222
+ for (let index = 0; index < nextDeps.length; index += 1) if (!Object.is(previousDeps[index], nextDeps[index])) return false;
1223
+ return true;
1224
+ }
1225
+ function useRouteCacheEffect(activeCallback, deps = []) {
1226
+ const returnValue = (0, react.useRef)(void 0);
1227
+ const isActiveRef = (0, react.useRef)(false);
1228
+ const previousCallbackRef = (0, react.useRef)(void 0);
1229
+ const previousDepsRef = (0, react.useRef)(void 0);
1230
+ (0, react.useEffect)(() => {
1231
+ const callbackChanged = previousCallbackRef.current !== activeCallback;
1232
+ const dependenciesChanged = !areDependenciesEqual(previousDepsRef.current, deps);
1233
+ if (!(callbackChanged || dependenciesChanged)) return;
1234
+ previousCallbackRef.current = activeCallback;
1235
+ previousDepsRef.current = deps;
1236
+ if (!isActiveRef.current) return;
1237
+ returnValue.current?.();
1238
+ returnValue.current = activeCallback();
1239
+ });
1240
+ (0, react.useEffect)(() => () => {
1241
+ returnValue.current?.();
1242
+ }, []);
1243
+ useRouteCacheActivity((active) => {
1244
+ if (active) {
1245
+ isActiveRef.current = true;
1246
+ returnValue.current = activeCallback();
1247
+ return;
1248
+ }
1249
+ isActiveRef.current = false;
1250
+ returnValue.current?.();
1251
+ });
1252
+ }
1253
+ //#endregion
1254
+ //#region src/hooks/use-route-cache-error-boundary.ts
1255
+ function useRouteCacheErrorBoundary(pathname) {
1256
+ const context = useOptionalRouterCacheContext();
1257
+ const routePathname = (0, _tanstack_react_router.useLocation)({ select: (location) => location.pathname });
1258
+ (0, react.useEffect)(() => {
1259
+ if (!context) return;
1260
+ const targetPathname = normalizeCachedRoutePathname(pathname ?? routePathname);
1261
+ context.retainErroredRoute(targetPathname);
1262
+ return () => {
1263
+ context.releaseErroredRoute(targetPathname);
1264
+ };
1265
+ }, [
1266
+ context,
1267
+ pathname,
1268
+ routePathname
1269
+ ]);
1270
+ }
1271
+ //#endregion
1272
+ //#region src/hooks/use-route-cache-navigation.ts
1273
+ const INITIAL_STATE = {
1274
+ activeNavigation: null,
1275
+ lastCompletedNavigation: null
1276
+ };
1277
+ function useRouteCacheNavigation() {
1278
+ const [state, setState] = (0, react.useState)(INITIAL_STATE);
1279
+ useEventListener({ on: {
1280
+ cachedNavigationStart: (navigation) => {
1281
+ setState((current) => ({
1282
+ ...current,
1283
+ activeNavigation: navigation
1284
+ }));
1285
+ },
1286
+ cachedNavigationCancel: (navigation) => {
1287
+ setState((current) => {
1288
+ if (current.activeNavigation?.pathname !== navigation.pathname) return current;
1289
+ return {
1290
+ ...current,
1291
+ activeNavigation: null
1292
+ };
1293
+ });
1294
+ },
1295
+ cachedNavigationComplete: (navigation) => {
1296
+ setState((current) => ({
1297
+ activeNavigation: current.activeNavigation?.pathname === navigation.pathname ? null : current.activeNavigation,
1298
+ lastCompletedNavigation: navigation
1299
+ }));
1300
+ }
1301
+ } });
1302
+ return state;
1303
+ }
1304
+ //#endregion
1305
+ //#region src/hooks/use-update.ts
1306
+ function useUpdate() {
1307
+ const [, setState] = (0, react.useState)({});
1308
+ return (0, react.useCallback)(() => setState({}), []);
1309
+ }
1310
+ //#endregion
1311
+ //#region src/hooks/use-router-cache.ts
1312
+ function useRouterCache() {
1313
+ const { cachedRoutes, deleteCachedRoutes } = useRouterCacheContext();
1314
+ const update = useUpdate();
1315
+ useEventListener({ on: { activeChange: () => {
1316
+ update();
1317
+ } } });
1318
+ const destroy = (pathname) => {
1319
+ deleteCachedRoutes(Array.isArray(pathname) ? pathname : [pathname]);
1320
+ };
1321
+ const destroyAll = () => {
1322
+ deleteCachedRoutes(Object.keys(cachedRoutes));
1323
+ };
1324
+ const invalidateWhere = (predicate) => {
1325
+ const pathnames = Object.entries(cachedRoutes).flatMap(([pathname, route]) => predicate(pathname, route) ? [pathname] : []);
1326
+ if (pathnames.length > 0) deleteCachedRoutes(pathnames);
1327
+ return pathnames;
1328
+ };
1329
+ return {
1330
+ cachedRoutes,
1331
+ destroy,
1332
+ destroyAll,
1333
+ invalidateWhere,
1334
+ isCached: (pathname) => Object.hasOwn(cachedRoutes, normalizeCachedRoutePathname(pathname))
1335
+ };
1336
+ }
1337
+ //#endregion
1338
+ exports.RouterCacheOutlet = RouterCacheOutlet;
1339
+ exports.RouterCacheProvider = RouterCacheProvider;
1340
+ exports.useRouteCacheActive = useRouteCacheActive;
1341
+ exports.useRouteCacheActivity = useRouteCacheActivity;
1342
+ exports.useRouteCacheEffect = useRouteCacheEffect;
1343
+ exports.useRouteCacheErrorBoundary = useRouteCacheErrorBoundary;
1344
+ exports.useRouteCacheNavigation = useRouteCacheNavigation;
1345
+ exports.useRouterCache = useRouterCache;