tanstack-router-cache 0.1.0 → 0.1.1

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.js CHANGED
@@ -1,10 +1,1281 @@
1
- import { RouterCacheOutlet } from "./components/router-cache-outlet.tsx";
2
- import { RouterCacheProvider } from "./contexts/router-cache.tsx";
3
- import { useRouteCacheActive } from "./hooks/use-route-cache-active.ts";
4
- import { useRouteCacheActivity } from "./hooks/use-route-cache-activity.ts";
5
- import { useRouteCacheEffect } from "./hooks/use-route-cache-effect.ts";
6
- import { useRouteCacheErrorBoundary } from "./hooks/use-route-cache-error-boundary.ts";
7
- import { useRouteCacheNavigation } from "./hooks/use-route-cache-navigation.ts";
8
- import { useRouterCache } from "./hooks/use-router-cache.ts";
9
- export * from "./types.ts";
1
+ import { Match, Outlet, RouterContextProvider, useChildMatches, useLocation, useMatches, useRouter, useRouterState } from "@tanstack/react-router";
2
+ import { Activity, createContext, use, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/pathname.ts
5
+ const TRAILING_SLASHES_REGEX = /\/+$/u;
6
+ function normalizeCachedRoutePathname(pathname) {
7
+ if (pathname === "/") return pathname;
8
+ return pathname.replace(TRAILING_SLASHES_REGEX, "");
9
+ }
10
+ //#endregion
11
+ //#region src/contexts/router-cache.tsx
12
+ const DEFAULT_MAX_ENTRIES = Number.POSITIVE_INFINITY;
13
+ const EMPTY_CACHED_ROUTES = {};
14
+ const RouterCacheContext = createContext(null);
15
+ function shallowEqualRouteStaticData(current, next) {
16
+ if (current === next) return true;
17
+ const currentEntries = Object.entries({ ...current });
18
+ const nextEntries = Object.entries({ ...next });
19
+ if (currentEntries.length !== nextEntries.length) return false;
20
+ const nextValuesByKey = new Map(nextEntries);
21
+ for (const [key, value] of currentEntries) if (!Object.is(value, nextValuesByKey.get(key))) return false;
22
+ return true;
23
+ }
24
+ function isCacheEnabledRouteData(route) {
25
+ return route?.staticData.routeCache === true;
26
+ }
27
+ function filterRouterCacheRoutes(routes) {
28
+ return Object.fromEntries(Object.entries(routes).flatMap(([pathname, route]) => isCacheEnabledRouteData(route) ? [[normalizeCachedRoutePathname(pathname), route]] : []));
29
+ }
30
+ function isSameCachedRouteData(current, next) {
31
+ if (!current) return false;
32
+ 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);
33
+ }
34
+ function normalizeLimit(limit) {
35
+ if (typeof limit !== "number" || Number.isNaN(limit)) return DEFAULT_MAX_ENTRIES;
36
+ if (!Number.isFinite(limit)) return DEFAULT_MAX_ENTRIES;
37
+ return Math.max(Math.trunc(limit), 0);
38
+ }
39
+ function getCachedRouteTimestamp(route) {
40
+ return route.lastVisibleAt ?? route.createdAt ?? 0;
41
+ }
42
+ function sortCachedRouteEntries([leftPathname, leftRoute], [rightPathname, rightRoute]) {
43
+ const timestampDifference = getCachedRouteTimestamp(leftRoute) - getCachedRouteTimestamp(rightRoute);
44
+ if (timestampDifference !== 0) return timestampDifference;
45
+ return leftPathname.localeCompare(rightPathname);
46
+ }
47
+ function getUnmarkedCachedRouteEntries(routes, keysToDelete) {
48
+ return Object.entries(routes).filter(([pathname]) => !keysToDelete.has(pathname));
49
+ }
50
+ function getEntriesByRouteId(entries) {
51
+ const entriesByRouteId = /* @__PURE__ */ new Map();
52
+ for (const entry of entries) {
53
+ const routeId = entry[1].routeId;
54
+ if (!routeId) continue;
55
+ const existingEntries = entriesByRouteId.get(routeId);
56
+ if (existingEntries) {
57
+ existingEntries.push(entry);
58
+ continue;
59
+ }
60
+ entriesByRouteId.set(routeId, [entry]);
61
+ }
62
+ return entriesByRouteId;
63
+ }
64
+ function markEntriesForDeletion(entries, keysToDelete, protectedKeys, excessEntryCount) {
65
+ if (excessEntryCount <= 0) return;
66
+ const evictableEntries = entries.filter(([pathname]) => !protectedKeys.has(pathname)).sort(sortCachedRouteEntries);
67
+ let remainingExcess = excessEntryCount;
68
+ for (const [pathname] of evictableEntries) {
69
+ if (remainingExcess <= 0) break;
70
+ if (!keysToDelete.has(pathname)) {
71
+ keysToDelete.add(pathname);
72
+ remainingExcess -= 1;
73
+ }
74
+ }
75
+ }
76
+ function applyPerRouteEntryLimit(routes, maxEntriesPerRouteId, keysToDelete, protectedKeys) {
77
+ if (maxEntriesPerRouteId === DEFAULT_MAX_ENTRIES) return;
78
+ const entriesByRouteId = getEntriesByRouteId(getUnmarkedCachedRouteEntries(routes, keysToDelete));
79
+ for (const entries of entriesByRouteId.values()) markEntriesForDeletion(entries, keysToDelete, protectedKeys, entries.length - maxEntriesPerRouteId);
80
+ }
81
+ function applyGlobalEntryLimit(routes, maxEntries, keysToDelete, protectedKeys) {
82
+ if (maxEntries === DEFAULT_MAX_ENTRIES) return;
83
+ const remainingEntries = getUnmarkedCachedRouteEntries(routes, keysToDelete);
84
+ markEntriesForDeletion(remainingEntries, keysToDelete, protectedKeys, remainingEntries.length - maxEntries);
85
+ }
86
+ function applyCachedRouteLimits(routes, config, protectedKeys) {
87
+ if (config.maxEntries === DEFAULT_MAX_ENTRIES && config.maxEntriesPerRouteId === DEFAULT_MAX_ENTRIES) return routes;
88
+ const keysToDelete = /* @__PURE__ */ new Set();
89
+ applyPerRouteEntryLimit(routes, config.maxEntriesPerRouteId, keysToDelete, protectedKeys);
90
+ applyGlobalEntryLimit(routes, config.maxEntries, keysToDelete, protectedKeys);
91
+ if (keysToDelete.size === 0) return routes;
92
+ const nextRoutes = { ...routes };
93
+ for (const pathname of keysToDelete) delete nextRoutes[pathname];
94
+ return nextRoutes;
95
+ }
96
+ function createCachedRouteData(current, next) {
97
+ const now = Date.now();
98
+ return {
99
+ ...next,
100
+ createdAt: current?.createdAt ?? next.createdAt ?? now,
101
+ lastVisibleAt: next.lastVisibleAt ?? now
102
+ };
103
+ }
104
+ function getNextCachedRoutesState(params) {
105
+ const normalizedKey = normalizeCachedRoutePathname(params.key);
106
+ if (params.cacheConfig.maxEntries === 0) return params.state === EMPTY_CACHED_ROUTES ? params.state : EMPTY_CACHED_ROUTES;
107
+ if (!isCacheEnabledRouteData(params.value)) {
108
+ if (!Object.hasOwn(params.state, normalizedKey)) return params.state;
109
+ const nextState = { ...params.state };
110
+ delete nextState[normalizedKey];
111
+ return nextState;
112
+ }
113
+ const nextRouteData = createCachedRouteData(params.state[normalizedKey], params.value);
114
+ if (isSameCachedRouteData(params.state[normalizedKey], nextRouteData)) return params.state;
115
+ return applyCachedRouteLimits({
116
+ ...params.state,
117
+ [normalizedKey]: nextRouteData
118
+ }, params.cacheConfig, new Set([normalizedKey]));
119
+ }
120
+ function RouterCacheProvider({ cacheScopeKey = "__default__", children, defaultCachedRoutes = EMPTY_CACHED_ROUTES, maxEntries = DEFAULT_MAX_ENTRIES, maxEntriesPerRouteId = DEFAULT_MAX_ENTRIES }) {
121
+ return /* @__PURE__ */ jsx(RouterCacheProviderScope, {
122
+ defaultCachedRoutes,
123
+ maxEntries,
124
+ maxEntriesPerRouteId,
125
+ children
126
+ }, cacheScopeKey ?? "__default__");
127
+ }
128
+ function RouterCacheProviderScope({ children, defaultCachedRoutes = EMPTY_CACHED_ROUTES, maxEntries, maxEntriesPerRouteId }) {
129
+ const cacheConfigRef = useRef({
130
+ maxEntries: normalizeLimit(maxEntries),
131
+ maxEntriesPerRouteId: normalizeLimit(maxEntriesPerRouteId)
132
+ });
133
+ const initialCachedRoutes = useRef(applyCachedRouteLimits(filterRouterCacheRoutes(defaultCachedRoutes), cacheConfigRef.current, /* @__PURE__ */ new Set()));
134
+ const [cachedRoutes, setCachedRoutes] = useState(() => initialCachedRoutes.current);
135
+ const [erroredRouteCounts, setErroredRouteCounts] = useState({});
136
+ const updateCachedRoutes = (key, value) => {
137
+ setCachedRoutes((state) => getNextCachedRoutesState({
138
+ cacheConfig: cacheConfigRef.current,
139
+ key,
140
+ state,
141
+ value
142
+ }));
143
+ };
144
+ const deleteCachedRoutes = (keys) => {
145
+ setCachedRoutes((state) => {
146
+ let changed = false;
147
+ const newState = { ...state };
148
+ for (const key of keys) {
149
+ const normalizedKey = normalizeCachedRoutePathname(key);
150
+ if (Object.hasOwn(newState, normalizedKey)) {
151
+ delete newState[normalizedKey];
152
+ changed = true;
153
+ }
154
+ }
155
+ return changed ? newState : state;
156
+ });
157
+ };
158
+ const touchCachedRoutes = (keys) => {
159
+ setCachedRoutes((state) => {
160
+ let changed = false;
161
+ const touchedAt = Date.now();
162
+ const nextState = { ...state };
163
+ for (const key of keys) {
164
+ const normalizedKey = normalizeCachedRoutePathname(key);
165
+ const route = nextState[normalizedKey];
166
+ if (!route || route.lastVisibleAt === touchedAt) continue;
167
+ nextState[normalizedKey] = {
168
+ ...route,
169
+ lastVisibleAt: touchedAt
170
+ };
171
+ changed = true;
172
+ }
173
+ return changed ? nextState : state;
174
+ });
175
+ };
176
+ const retainErroredRoute = (pathname) => {
177
+ const normalizedPathname = normalizeCachedRoutePathname(pathname);
178
+ setErroredRouteCounts((state) => ({
179
+ ...state,
180
+ [normalizedPathname]: (state[normalizedPathname] ?? 0) + 1
181
+ }));
182
+ setCachedRoutes((state) => {
183
+ if (!Object.hasOwn(state, normalizedPathname)) return state;
184
+ const nextState = { ...state };
185
+ delete nextState[normalizedPathname];
186
+ return nextState;
187
+ });
188
+ };
189
+ const releaseErroredRoute = (pathname) => {
190
+ const normalizedPathname = normalizeCachedRoutePathname(pathname);
191
+ setErroredRouteCounts((state) => {
192
+ const currentCount = state[normalizedPathname] ?? 0;
193
+ if (currentCount <= 1) {
194
+ if (!Object.hasOwn(state, normalizedPathname)) return state;
195
+ const nextState = { ...state };
196
+ delete nextState[normalizedPathname];
197
+ return nextState;
198
+ }
199
+ return {
200
+ ...state,
201
+ [normalizedPathname]: currentCount - 1
202
+ };
203
+ });
204
+ };
205
+ const contextValue = {
206
+ cachedRoutes,
207
+ erroredRouteCounts,
208
+ setCachedRoutes: updateCachedRoutes,
209
+ deleteCachedRoutes,
210
+ touchCachedRoutes,
211
+ retainErroredRoute,
212
+ releaseErroredRoute
213
+ };
214
+ return /* @__PURE__ */ jsx(RouterCacheContext.Provider, {
215
+ value: contextValue,
216
+ children
217
+ });
218
+ }
219
+ function useRouterCacheContext() {
220
+ const context = use(RouterCacheContext);
221
+ if (!context) throw new Error("RouterCacheContext is not available");
222
+ return context;
223
+ }
224
+ function useOptionalRouterCacheContext() {
225
+ return use(RouterCacheContext);
226
+ }
227
+ //#endregion
228
+ //#region src/hooks/use-event-listener.ts
229
+ const EVENT_BUCKET_KEYS = ["on", "once"];
230
+ const instance = class RouterCacheEvent {
231
+ static instance;
232
+ listeners = {
233
+ activeChange: /* @__PURE__ */ new Set(),
234
+ cachedNavigationCancel: /* @__PURE__ */ new Set(),
235
+ cachedNavigationComplete: /* @__PURE__ */ new Set(),
236
+ cachedNavigationStart: /* @__PURE__ */ new Set()
237
+ };
238
+ constructor() {}
239
+ static getInstance() {
240
+ if (!RouterCacheEvent.instance) RouterCacheEvent.instance = new RouterCacheEvent();
241
+ return RouterCacheEvent.instance;
242
+ }
243
+ on(eventName, handler) {
244
+ this.listeners[eventName].add(handler);
245
+ return this;
246
+ }
247
+ once(eventName, handler) {
248
+ const onceHandler = (...args) => {
249
+ this.off(eventName, onceHandler);
250
+ handler(...args);
251
+ };
252
+ this.listeners[eventName].add(onceHandler);
253
+ return this;
254
+ }
255
+ off(eventName, handler) {
256
+ this.listeners[eventName].delete(handler);
257
+ return this;
258
+ }
259
+ emit(eventName, ...args) {
260
+ const handlers = this.listeners[eventName];
261
+ for (const handler of handlers) handler(...args);
262
+ return handlers.size > 0;
263
+ }
264
+ }.getInstance();
265
+ function syncEventHandler(type, eventName, handler) {
266
+ if (type === "on") {
267
+ instance.on(eventName, handler);
268
+ return;
269
+ }
270
+ instance.off(eventName, handler);
271
+ }
272
+ function syncEventBucket(type, bucket) {
273
+ if (bucket.activeChange) syncEventHandler(type, "activeChange", bucket.activeChange);
274
+ if (bucket.cachedNavigationCancel) syncEventHandler(type, "cachedNavigationCancel", bucket.cachedNavigationCancel);
275
+ if (bucket.cachedNavigationComplete) syncEventHandler(type, "cachedNavigationComplete", bucket.cachedNavigationComplete);
276
+ if (bucket.cachedNavigationStart) syncEventHandler(type, "cachedNavigationStart", bucket.cachedNavigationStart);
277
+ }
278
+ function syncEventHandlers(type, events) {
279
+ if (!events) return;
280
+ for (const key of EVENT_BUCKET_KEYS) {
281
+ const bucket = events[key];
282
+ if (!bucket) continue;
283
+ syncEventBucket(type, bucket);
284
+ }
285
+ }
286
+ function useEventListener(events) {
287
+ useEffect(() => {
288
+ const registerEvent = () => {
289
+ syncEventHandlers("on", events);
290
+ return () => {
291
+ syncEventHandlers("off", events);
292
+ };
293
+ };
294
+ const unregister = registerEvent();
295
+ return () => {
296
+ unregister?.();
297
+ };
298
+ }, [events]);
299
+ return { eventListener: instance };
300
+ }
301
+ //#endregion
302
+ //#region src/hooks/use-router-cache-debug.ts
303
+ const DYNAMIC_SEGMENT_PATTERNS = [
304
+ /^\d{4,}$/u,
305
+ /^[0-9a-f]{8,}$/iu,
306
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu
307
+ ];
308
+ function isDynamicLookingSegment(segment) {
309
+ for (const pattern of DYNAMIC_SEGMENT_PATTERNS) if (pattern.test(segment)) return true;
310
+ return false;
311
+ }
312
+ function isDynamicLookingPathname(pathname) {
313
+ return pathname.split("/").filter(Boolean).some(isDynamicLookingSegment);
314
+ }
315
+ function countHiddenContainers() {
316
+ if (typeof document === "undefined") return 0;
317
+ return document.querySelectorAll("[data-router-cache-container=\"true\"][data-router-cache-mode=\"hidden\"]").length;
318
+ }
319
+ function buildSnapshot(cachedRoutes, visiblePathname) {
320
+ const cachedRoutePathnames = Object.keys(cachedRoutes).sort((a, b) => a.localeCompare(b));
321
+ const dynamicLookingRoutePathnames = cachedRoutePathnames.filter(isDynamicLookingPathname);
322
+ const hiddenCachedRoutePathnames = cachedRoutePathnames.filter((pathname) => pathname !== visiblePathname);
323
+ return {
324
+ cachedRoutePathnames,
325
+ dynamicLookingRouteCount: dynamicLookingRoutePathnames.length,
326
+ dynamicLookingRoutePathnames,
327
+ hiddenCachedRouteCount: hiddenCachedRoutePathnames.length,
328
+ hiddenContainerCount: countHiddenContainers(),
329
+ totalCachedRouteCount: cachedRoutePathnames.length,
330
+ visiblePathname
331
+ };
332
+ }
333
+ function getDebugWindow() {
334
+ return globalThis.window;
335
+ }
336
+ function isProduction() {
337
+ return globalThis.process?.env?.NODE_ENV === "production";
338
+ }
339
+ function useRouterCacheDebug(cachedRoutes, visiblePathname) {
340
+ const warningThresholdRef = useRef(null);
341
+ const lastWarnedCountRef = useRef(null);
342
+ const lastSnapshotRef = useRef(buildSnapshot(cachedRoutes, visiblePathname));
343
+ const snapshot = useMemo(() => buildSnapshot(cachedRoutes, visiblePathname), [cachedRoutes, visiblePathname]);
344
+ useEffect(() => {
345
+ if (isProduction()) return;
346
+ const debugWindow = getDebugWindow();
347
+ if (!debugWindow) return;
348
+ const refresh = () => {
349
+ lastSnapshotRef.current = buildSnapshot(cachedRoutes, visiblePathname);
350
+ return lastSnapshotRef.current;
351
+ };
352
+ const api = {
353
+ getSnapshot: () => lastSnapshotRef.current,
354
+ lastSnapshot: snapshot,
355
+ refresh,
356
+ setWarningThreshold: (nextThreshold) => {
357
+ warningThresholdRef.current = typeof nextThreshold === "number" ? nextThreshold : null;
358
+ lastWarnedCountRef.current = null;
359
+ },
360
+ warningThreshold: warningThresholdRef.current
361
+ };
362
+ debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ = api;
363
+ const rafId = globalThis.requestAnimationFrame(() => {
364
+ const nextSnapshot = refresh();
365
+ debugWindow.__TANSTACK_ROUTER_CACHE_DEBUG__ = {
366
+ ...api,
367
+ lastSnapshot: nextSnapshot,
368
+ warningThreshold: warningThresholdRef.current
369
+ };
370
+ const warningThreshold = warningThresholdRef.current;
371
+ if (typeof warningThreshold === "number" && nextSnapshot.totalCachedRouteCount > warningThreshold && lastWarnedCountRef.current !== nextSnapshot.totalCachedRouteCount) {
372
+ lastWarnedCountRef.current = nextSnapshot.totalCachedRouteCount;
373
+ console.warn("[tanstack-router-cache] cached route count exceeded threshold", nextSnapshot);
374
+ }
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__ */ jsx(RouterContextProvider, {
391
+ router: routerSnapshot,
392
+ children: /* @__PURE__ */ jsx(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
+ function OffScreenIn(props) {
626
+ const { mode, children, containerRef, pathname } = props;
627
+ const localContainerRef = useRef(null);
628
+ const attachContainerRef = (node) => {
629
+ localContainerRef.current = node;
630
+ if (containerRef) containerRef.current = node;
631
+ };
632
+ useLayoutEffect(() => {
633
+ if (typeof document === "undefined") return;
634
+ initializeTransientUiTracking(document);
635
+ }, []);
636
+ useLayoutEffect(() => {
637
+ if (!pathname) return;
638
+ syncTransientUiRouteActivity(pathname, mode);
639
+ }, [mode, pathname]);
640
+ useLayoutEffect(() => {
641
+ const visibleContainer = localContainerRef.current;
642
+ return () => {
643
+ if (!pathname || mode !== "visible") return;
644
+ dismissTransientUi(visibleContainer, pathname);
645
+ };
646
+ }, [mode, pathname]);
647
+ useLayoutEffect(() => {
648
+ if (typeof globalThis === "undefined" || !pathname) return;
649
+ const saveScrollPosition = () => {
650
+ windowScrollPositions.set(pathname, {
651
+ x: globalThis.scrollX,
652
+ y: globalThis.scrollY
653
+ });
654
+ };
655
+ if (mode === "hidden") {
656
+ saveScrollPosition();
657
+ return;
658
+ }
659
+ const savedPosition = windowScrollPositions.get(pathname);
660
+ const handleScroll = () => saveScrollPosition();
661
+ globalThis.addEventListener("scroll", handleScroll, { passive: true });
662
+ if (!savedPosition) {
663
+ saveScrollPosition();
664
+ return () => globalThis.removeEventListener("scroll", handleScroll);
665
+ }
666
+ let rafId = globalThis.requestAnimationFrame(() => {
667
+ rafId = globalThis.requestAnimationFrame(() => {
668
+ globalThis.scrollTo(savedPosition.x, savedPosition.y);
669
+ saveScrollPosition();
670
+ });
671
+ });
672
+ return () => {
673
+ globalThis.cancelAnimationFrame(rafId);
674
+ globalThis.removeEventListener("scroll", handleScroll);
675
+ };
676
+ }, [mode, pathname]);
677
+ return /* @__PURE__ */ jsx(Activity, {
678
+ mode,
679
+ children: /* @__PURE__ */ jsx("div", {
680
+ className: "flex min-h-full w-full flex-1 flex-col",
681
+ "data-router-cache-container": "true",
682
+ "data-router-cache-mode": mode,
683
+ "data-router-cache-pathname": pathname,
684
+ ref: attachContainerRef,
685
+ children
686
+ })
687
+ });
688
+ }
689
+ //#endregion
690
+ //#region src/components/off-screen.tsx
691
+ function OffScreen(props) {
692
+ return /* @__PURE__ */ jsx(OffScreenIn, { ...props });
693
+ }
694
+ //#endregion
695
+ //#region src/components/restore-cached-href.ts
696
+ const FALLBACK_ORIGIN = "http://tanstack-router-cache.local";
697
+ function parseHref(href) {
698
+ try {
699
+ const url = new URL(href, FALLBACK_ORIGIN);
700
+ return {
701
+ hasHash: url.hash.length > 0,
702
+ hasSearch: url.search.length > 0,
703
+ pathname: url.pathname
704
+ };
705
+ } catch {
706
+ return null;
707
+ }
708
+ }
709
+ function isBareRouteHref(href, currentPathname) {
710
+ const candidateHref = href ?? currentPathname;
711
+ const parsedHref = parseHref(candidateHref);
712
+ if (!parsedHref) return candidateHref === currentPathname;
713
+ return parsedHref.pathname === currentPathname && !parsedHref.hasSearch && !parsedHref.hasHash;
714
+ }
715
+ function shouldRestoreCachedHref({ cachedHref, currentHref, currentPathname, isRouteCacheEnabled, previousPathname }) {
716
+ if (!(isRouteCacheEnabled && cachedHref)) return false;
717
+ if (!previousPathname || previousPathname === currentPathname) return false;
718
+ if (!isBareRouteHref(currentHref, currentPathname)) return false;
719
+ return cachedHref !== (currentHref ?? currentPathname);
720
+ }
721
+ //#endregion
722
+ //#region src/components/route-cache-manager.tsx
723
+ const LIVE_ROUTER_METHODS = [
724
+ "buildLocation",
725
+ "commitLocation",
726
+ "invalidate",
727
+ "loadRouteChunk",
728
+ "navigate",
729
+ "preloadRoute"
730
+ ];
731
+ function createStaticStore(value) {
732
+ return {
733
+ get: () => value,
734
+ subscribe: () => ({ unsubscribe: () => void 0 })
735
+ };
736
+ }
737
+ function getLiveRouterMethodDescriptors(router) {
738
+ return Object.fromEntries(LIVE_ROUTER_METHODS.flatMap((methodName) => {
739
+ const method = router[methodName];
740
+ if (typeof method !== "function") return [];
741
+ return [[methodName, { value: method.bind(router) }]];
742
+ }));
743
+ }
744
+ function isRouterSearch(value) {
745
+ return typeof value === "object" && value !== null;
746
+ }
747
+ function toRouterLocation(location) {
748
+ return {
749
+ href: location.href,
750
+ pathname: location.pathname,
751
+ search: isRouterSearch(location.search) ? location.search : void 0
752
+ };
753
+ }
754
+ function isReadyCachedRoute(route) {
755
+ return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.matchId && route.routerSnapshot);
756
+ }
757
+ function isRouteCacheEnabled(staticData) {
758
+ return staticData?.routeCache === true;
759
+ }
760
+ function hasErroredRouteMatch(matches) {
761
+ return matches.some((match) => match?.status === "error");
762
+ }
763
+ function hasRetainedRouteError(erroredRouteCounts, pathname) {
764
+ return (erroredRouteCounts[pathname] ?? 0) > 0;
765
+ }
766
+ function hasCurrentRouterMatchError({ matches, resolvedPathname, routerPathname }) {
767
+ return routerPathname === resolvedPathname && hasErroredRouteMatch(matches);
768
+ }
769
+ function hasCurrentRouteError({ erroredRouteCounts, matches, resolvedPathname, routerPathname }) {
770
+ return hasRetainedRouteError(erroredRouteCounts, routerPathname) || hasCurrentRouterMatchError({
771
+ matches,
772
+ resolvedPathname,
773
+ routerPathname
774
+ });
775
+ }
776
+ function isRouterMatch(match) {
777
+ return Boolean(match?.id && match.routeId);
778
+ }
779
+ function snapshotMatch(match) {
780
+ if (!isRouterMatch(match)) return;
781
+ return {
782
+ ...match,
783
+ _nonReactive: { ...match._nonReactive }
784
+ };
785
+ }
786
+ function shouldRefreshCachedRoute(route, routerHref) {
787
+ return !(isRouteCacheEnabled(route?.staticData) && route.ready) || route.href !== routerHref;
788
+ }
789
+ function syncReadyCachedRoute({ matchId, matches, routeId, route, routerLocation, router, routerResolvedLocation, routerHref, routerPathname, setCachedRoutes, staticData }) {
790
+ if (!shouldRefreshCachedRoute(route, routerHref)) return;
791
+ setCachedRoutes(routerPathname, {
792
+ href: routerHref,
793
+ matchId,
794
+ routeId,
795
+ ready: true,
796
+ routerSnapshot: matchId ? createRouterSnapshot({
797
+ matches,
798
+ router,
799
+ routerLocation,
800
+ routerResolvedLocation
801
+ }) : void 0,
802
+ staticData
803
+ });
804
+ }
805
+ function syncCachedRouteState({ isCurrentMatchReady, isCurrentMatchResolved, deleteCachedRoutes, isCurrentRouteErrored, matchId, matches, route, routeId, router, routerHref, routerLocation, routerPathname, routerResolvedLocation, setCachedRoutes, staticData }) {
806
+ if (isCurrentRouteErrored) {
807
+ if (route) deleteCachedRoutes([routerPathname]);
808
+ return;
809
+ }
810
+ if (!isCurrentMatchResolved) return;
811
+ if (staticData && isRouteCacheEnabled(staticData)) {
812
+ if (!isCurrentMatchReady) return;
813
+ syncReadyCachedRoute({
814
+ matchId,
815
+ matches,
816
+ routeId,
817
+ route,
818
+ routerLocation,
819
+ router,
820
+ routerResolvedLocation,
821
+ routerHref,
822
+ routerPathname,
823
+ setCachedRoutes,
824
+ staticData
825
+ });
826
+ return;
827
+ }
828
+ if (route) deleteCachedRoutes([routerPathname]);
829
+ }
830
+ function createRouterSnapshot({ matches, router, routerLocation, routerResolvedLocation }) {
831
+ const snapshotMatches = matches.reduce((snapshot, match) => {
832
+ const nextMatch = snapshotMatch(match);
833
+ if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
834
+ return snapshot;
835
+ }, []);
836
+ const matchStores = new Map(snapshotMatches.map((match) => {
837
+ const store = Object.assign(createStaticStore(match), { routeId: match.routeId });
838
+ return [match.id, store];
839
+ }));
840
+ const snapshotState = {
841
+ ...router.stores.__store.get(),
842
+ matches: snapshotMatches,
843
+ location: routerLocation,
844
+ resolvedLocation: routerResolvedLocation
845
+ };
846
+ const routeMatchStoreCache = /* @__PURE__ */ new Map();
847
+ const stores = {
848
+ ...router.stores,
849
+ status: createStaticStore(router.stores.status.get()),
850
+ loadedAt: createStaticStore(router.stores.loadedAt.get()),
851
+ isLoading: createStaticStore(router.stores.isLoading.get()),
852
+ isTransitioning: createStaticStore(router.stores.isTransitioning.get()),
853
+ location: createStaticStore(routerLocation),
854
+ resolvedLocation: createStaticStore(routerResolvedLocation),
855
+ statusCode: createStaticStore(router.stores.statusCode.get()),
856
+ redirect: createStaticStore(router.stores.redirect.get()),
857
+ matchesId: createStaticStore(snapshotMatches.map((match) => match.id)),
858
+ pendingIds: createStaticStore([]),
859
+ cachedIds: createStaticStore([]),
860
+ matches: createStaticStore(snapshotMatches),
861
+ pendingMatches: createStaticStore([]),
862
+ cachedMatches: createStaticStore([]),
863
+ firstId: createStaticStore(snapshotMatches[0]?.id),
864
+ hasPending: createStaticStore(snapshotMatches.some((match) => match.status === "pending")),
865
+ matchRouteDeps: createStaticStore({
866
+ locationHref: routerLocation.href,
867
+ resolvedLocationHref: routerResolvedLocation?.href,
868
+ status: snapshotState.status
869
+ }),
870
+ __store: createStaticStore(snapshotState),
871
+ matchStores,
872
+ pendingMatchStores: /* @__PURE__ */ new Map(),
873
+ cachedMatchStores: /* @__PURE__ */ new Map(),
874
+ getRouteMatchStore: (routeId) => {
875
+ let cached = routeMatchStoreCache.get(routeId);
876
+ if (!cached) {
877
+ cached = createStaticStore(snapshotMatches.find((match) => match.routeId === routeId));
878
+ routeMatchStoreCache.set(routeId, cached);
879
+ }
880
+ return cached;
881
+ },
882
+ setMatches: () => void 0,
883
+ setPending: () => void 0,
884
+ setCached: () => void 0
885
+ };
886
+ const routerSnapshot = Object.create(router);
887
+ Object.defineProperties(routerSnapshot, {
888
+ stores: { value: stores },
889
+ latestLocation: { value: routerLocation },
890
+ getMatch: { value: (matchId) => matchStores.get(matchId)?.get() },
891
+ updateMatch: { value: () => void 0 },
892
+ ...getLiveRouterMethodDescriptors(router)
893
+ });
894
+ return routerSnapshot;
895
+ }
896
+ function getRouterCacheStaticData(childMatches, isCurrentMatchResolved) {
897
+ if (!isCurrentMatchResolved) return;
898
+ for (let i = childMatches.length - 1; i >= 0; i--) {
899
+ const match = childMatches[i];
900
+ if (match?.staticData && isRouteCacheEnabled(match.staticData)) return match.staticData;
901
+ }
902
+ }
903
+ function restoreCachedHref(router, href) {
904
+ router.navigate({
905
+ href,
906
+ replace: true,
907
+ resetScroll: false
908
+ }).catch(() => void 0);
909
+ }
910
+ function isAncestorPathname(ancestorPathname, pathname) {
911
+ if (ancestorPathname === pathname) return false;
912
+ if (ancestorPathname === "/") return pathname.startsWith("/");
913
+ return pathname.startsWith(`${ancestorPathname}/`);
914
+ }
915
+ function getShouldRenderLiveOutlet({ cachedRoutes, bypassCachedPathname, visiblePathname }) {
916
+ if (visiblePathname === bypassCachedPathname) return true;
917
+ const visibleRoute = cachedRoutes[visiblePathname];
918
+ return !isReadyCachedRoute(visibleRoute);
919
+ }
920
+ function renderCachedRoute({ bypassCachedPathname, pathname, route, visiblePathname }) {
921
+ if (pathname === bypassCachedPathname) return null;
922
+ const content = route.matchId && route.routerSnapshot ? /* @__PURE__ */ jsx(CachedOutlet, {
923
+ matchId: route.matchId,
924
+ routerSnapshot: route.routerSnapshot
925
+ }) : null;
926
+ if (!content) return null;
927
+ return /* @__PURE__ */ jsx(OffScreen, {
928
+ mode: visiblePathname === pathname ? "visible" : "hidden",
929
+ pathname,
930
+ children: content
931
+ }, pathname);
932
+ }
933
+ function buildRouteCacheModes(cachedRoutes, visiblePathname) {
934
+ const nextModes = /* @__PURE__ */ new Map();
935
+ for (const pathname of Object.keys(cachedRoutes)) nextModes.set(pathname, visiblePathname === pathname ? "visible" : "hidden");
936
+ return nextModes;
937
+ }
938
+ function syncCachedRouteActivityEvents(params) {
939
+ const nextModes = buildRouteCacheModes(params.cachedRoutes, params.visiblePathname);
940
+ for (const [pathname, mode] of nextModes) {
941
+ const previousMode = params.previousRouteCacheModes.get(pathname);
942
+ if (previousMode === void 0 && mode === "hidden") continue;
943
+ if (previousMode === mode) continue;
944
+ params.eventListener.emit("activeChange", {
945
+ pathname,
946
+ mode
947
+ });
948
+ }
949
+ for (const [pathname] of params.previousRouteCacheModes) {
950
+ if (nextModes.has(pathname)) continue;
951
+ params.eventListener.emit("activeChange", {
952
+ pathname,
953
+ mode: "hidden"
954
+ });
955
+ }
956
+ return nextModes;
957
+ }
958
+ function RouteCacheManager() {
959
+ const { cachedRoutes, deleteCachedRoutes, erroredRouteCounts, setCachedRoutes, touchCachedRoutes } = useRouterCacheContext();
960
+ const { eventListener } = useEventListener();
961
+ const pendingCachedNavigationRef = useRef(null);
962
+ const previousPathnameRef = useRef(void 0);
963
+ const previousHrefRef = useRef(void 0);
964
+ const previousRouteCacheModesRef = useRef(/* @__PURE__ */ new Map());
965
+ const previousVisiblePathnameRef = useRef(void 0);
966
+ const routerLocation = useRouterState({ select: (state) => toRouterLocation(state.location) });
967
+ const routerHref = routerLocation.href;
968
+ const routerPathname = normalizeCachedRoutePathname(routerLocation.pathname);
969
+ const routerResolvedLocation = useRouterState({ select: (state) => state.resolvedLocation ? toRouterLocation(state.resolvedLocation) : void 0 });
970
+ const resolvedPathname = normalizeCachedRoutePathname(routerResolvedLocation?.pathname ?? routerPathname);
971
+ const destinationRoute = cachedRoutes[routerPathname];
972
+ const visiblePathname = routerPathname !== resolvedPathname && (isReadyCachedRoute(destinationRoute) || isAncestorPathname(routerPathname, resolvedPathname)) ? routerPathname : resolvedPathname;
973
+ const matches = useMatches();
974
+ const childMatches = useChildMatches();
975
+ const router = useRouter();
976
+ const currentMatch = matches.length ? matches.at(-1) : void 0;
977
+ const outletRootMatch = childMatches.length ? childMatches[0] : currentMatch;
978
+ const isCurrentMatchResolved = (currentMatch?.pathname ? normalizeCachedRoutePathname(currentMatch.pathname) : void 0) === routerPathname;
979
+ const isCurrentMatchReady = isCurrentMatchResolved && currentMatch?.status === "success";
980
+ const staticData = getRouterCacheStaticData(childMatches, isCurrentMatchResolved);
981
+ const matchId = isCurrentMatchResolved && outletRootMatch ? outletRootMatch.id : void 0;
982
+ const routeId = isCurrentMatchResolved && outletRootMatch ? outletRootMatch.routeId : void 0;
983
+ const currentCachedRoute = cachedRoutes[routerPathname];
984
+ const isCurrentRouteErrored = hasCurrentRouteError({
985
+ erroredRouteCounts,
986
+ matches,
987
+ resolvedPathname,
988
+ routerPathname
989
+ });
990
+ const bypassCachedPathname = isCurrentRouteErrored ? routerPathname : void 0;
991
+ const previousPathname = previousPathnameRef.current;
992
+ previousHrefRef.current;
993
+ const shouldRestoreDestinationHref = shouldRestoreCachedHref({
994
+ cachedHref: destinationRoute?.href,
995
+ currentHref: routerHref,
996
+ currentPathname: routerPathname,
997
+ isRouteCacheEnabled: isRouteCacheEnabled(destinationRoute?.staticData),
998
+ previousPathname
999
+ });
1000
+ useLayoutEffect(() => {
1001
+ if (shouldRestoreDestinationHref) return;
1002
+ syncCachedRouteState({
1003
+ isCurrentMatchReady,
1004
+ isCurrentMatchResolved,
1005
+ isCurrentRouteErrored,
1006
+ matchId,
1007
+ matches,
1008
+ routeId,
1009
+ route: currentCachedRoute,
1010
+ routerLocation,
1011
+ router,
1012
+ routerResolvedLocation,
1013
+ routerHref,
1014
+ routerPathname,
1015
+ deleteCachedRoutes,
1016
+ setCachedRoutes,
1017
+ staticData
1018
+ });
1019
+ }, [
1020
+ currentCachedRoute,
1021
+ deleteCachedRoutes,
1022
+ isCurrentRouteErrored,
1023
+ isCurrentMatchReady,
1024
+ isCurrentMatchResolved,
1025
+ matchId,
1026
+ matches,
1027
+ routeId,
1028
+ router,
1029
+ routerLocation,
1030
+ routerResolvedLocation,
1031
+ routerHref,
1032
+ routerPathname,
1033
+ staticData,
1034
+ setCachedRoutes,
1035
+ shouldRestoreDestinationHref
1036
+ ]);
1037
+ useLayoutEffect(() => {
1038
+ const lastVisitedPathname = previousPathnameRef.current;
1039
+ const pendingNavigation = pendingCachedNavigationRef.current;
1040
+ if (pendingNavigation && pendingNavigation.pathname !== routerPathname && visiblePathname !== pendingNavigation.pathname) {
1041
+ eventListener.emit("cachedNavigationCancel", pendingNavigation);
1042
+ pendingCachedNavigationRef.current = null;
1043
+ }
1044
+ if (!lastVisitedPathname || lastVisitedPathname === routerPathname) return;
1045
+ if (!isReadyCachedRoute(destinationRoute)) return;
1046
+ if (pendingCachedNavigationRef.current?.pathname === routerPathname) return;
1047
+ const nextNavigation = {
1048
+ pathname: routerPathname,
1049
+ startedAt: performance.now()
1050
+ };
1051
+ pendingCachedNavigationRef.current = nextNavigation;
1052
+ eventListener.emit("cachedNavigationStart", nextNavigation);
1053
+ }, [
1054
+ destinationRoute,
1055
+ eventListener,
1056
+ routerPathname,
1057
+ visiblePathname
1058
+ ]);
1059
+ useLayoutEffect(() => {
1060
+ previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
1061
+ cachedRoutes,
1062
+ eventListener,
1063
+ previousRouteCacheModes: previousRouteCacheModesRef.current,
1064
+ visiblePathname
1065
+ });
1066
+ }, [
1067
+ cachedRoutes,
1068
+ eventListener,
1069
+ visiblePathname
1070
+ ]);
1071
+ useLayoutEffect(() => {
1072
+ if (previousVisiblePathnameRef.current === visiblePathname || !cachedRoutes[visiblePathname]) {
1073
+ previousVisiblePathnameRef.current = visiblePathname;
1074
+ return;
1075
+ }
1076
+ previousVisiblePathnameRef.current = visiblePathname;
1077
+ touchCachedRoutes([visiblePathname]);
1078
+ }, [
1079
+ cachedRoutes,
1080
+ touchCachedRoutes,
1081
+ visiblePathname
1082
+ ]);
1083
+ useEffect(() => {
1084
+ const pendingNavigation = pendingCachedNavigationRef.current;
1085
+ if (visiblePathname !== pendingNavigation?.pathname) return;
1086
+ let firstFrameId = 0;
1087
+ let secondFrameId = 0;
1088
+ firstFrameId = globalThis.requestAnimationFrame(() => {
1089
+ const visibleAt = performance.now();
1090
+ secondFrameId = globalThis.requestAnimationFrame(() => {
1091
+ const paintedAt = performance.now();
1092
+ if (pendingCachedNavigationRef.current?.pathname !== pendingNavigation.pathname) return;
1093
+ pendingCachedNavigationRef.current = null;
1094
+ eventListener.emit("cachedNavigationComplete", {
1095
+ ...pendingNavigation,
1096
+ duration: paintedAt - pendingNavigation.startedAt,
1097
+ paintedAt,
1098
+ visibleAt
1099
+ });
1100
+ });
1101
+ });
1102
+ return () => {
1103
+ globalThis.cancelAnimationFrame(firstFrameId);
1104
+ globalThis.cancelAnimationFrame(secondFrameId);
1105
+ };
1106
+ }, [eventListener, visiblePathname]);
1107
+ useLayoutEffect(() => {
1108
+ if (!(shouldRestoreDestinationHref && destinationRoute?.href)) return;
1109
+ restoreCachedHref(router, destinationRoute.href);
1110
+ }, [
1111
+ destinationRoute?.href,
1112
+ router,
1113
+ shouldRestoreDestinationHref
1114
+ ]);
1115
+ useEffect(() => {
1116
+ previousPathnameRef.current = routerPathname;
1117
+ previousHrefRef.current = routerHref;
1118
+ }, [routerHref, routerPathname]);
1119
+ const shouldRenderLiveOutlet = getShouldRenderLiveOutlet({
1120
+ cachedRoutes,
1121
+ bypassCachedPathname,
1122
+ visiblePathname
1123
+ });
1124
+ useRouterCacheDebug(cachedRoutes, visiblePathname);
1125
+ return /* @__PURE__ */ jsxs(Fragment, { children: [Object.entries(cachedRoutes).map(([pathname, route]) => renderCachedRoute({
1126
+ bypassCachedPathname,
1127
+ pathname,
1128
+ route,
1129
+ visiblePathname
1130
+ })), shouldRenderLiveOutlet ? /* @__PURE__ */ jsx(Outlet, {}, `live-${routerPathname}`) : null] });
1131
+ }
1132
+ //#endregion
1133
+ //#region src/components/router-cache-outlet.tsx
1134
+ function RouterCacheOutlet(props) {
1135
+ const { children } = props;
1136
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(RouteCacheManager, {}), children] });
1137
+ }
1138
+ //#endregion
1139
+ //#region src/hooks/use-route-cache-active.ts
1140
+ function useRoutePathname(pathname) {
1141
+ const locationPathname = useLocation({ select: (location) => location.pathname });
1142
+ return normalizeCachedRoutePathname(pathname ?? locationPathname);
1143
+ }
1144
+ function useRouteCacheActive(pathname) {
1145
+ const routePathname = useRoutePathname(pathname);
1146
+ const [isActive, setIsActive] = useState(true);
1147
+ useEventListener({ on: { activeChange: ({ pathname: changedPathname, mode }) => {
1148
+ if (changedPathname === routePathname) setIsActive(mode === "visible");
1149
+ } } });
1150
+ return isActive;
1151
+ }
1152
+ //#endregion
1153
+ //#region src/hooks/use-route-cache-activity.ts
1154
+ function useRouteCacheActivity(fn) {
1155
+ const routePathname = useLocation({ select: (location) => location.pathname });
1156
+ useEventListener({ on: { activeChange: ({ pathname, mode, callback }) => {
1157
+ if (pathname === routePathname) fn(mode === "visible");
1158
+ callback?.();
1159
+ } } });
1160
+ }
1161
+ //#endregion
1162
+ //#region src/hooks/use-route-cache-effect.ts
1163
+ function areDependenciesEqual(previousDeps, nextDeps) {
1164
+ if (previousDeps?.length !== nextDeps.length) return false;
1165
+ for (let index = 0; index < nextDeps.length; index += 1) if (!Object.is(previousDeps[index], nextDeps[index])) return false;
1166
+ return true;
1167
+ }
1168
+ function useRouteCacheEffect(activeCallback, deps = []) {
1169
+ const returnValue = useRef(void 0);
1170
+ const isActiveRef = useRef(false);
1171
+ const previousCallbackRef = useRef(void 0);
1172
+ const previousDepsRef = useRef(void 0);
1173
+ useEffect(() => {
1174
+ const callbackChanged = previousCallbackRef.current !== activeCallback;
1175
+ const dependenciesChanged = !areDependenciesEqual(previousDepsRef.current, deps);
1176
+ if (!(callbackChanged || dependenciesChanged)) return;
1177
+ previousCallbackRef.current = activeCallback;
1178
+ previousDepsRef.current = deps;
1179
+ if (!isActiveRef.current) return;
1180
+ returnValue.current?.();
1181
+ returnValue.current = activeCallback();
1182
+ });
1183
+ useEffect(() => () => {
1184
+ returnValue.current?.();
1185
+ }, []);
1186
+ useRouteCacheActivity((active) => {
1187
+ if (active) {
1188
+ isActiveRef.current = true;
1189
+ returnValue.current = activeCallback();
1190
+ return;
1191
+ }
1192
+ isActiveRef.current = false;
1193
+ returnValue.current?.();
1194
+ });
1195
+ }
1196
+ //#endregion
1197
+ //#region src/hooks/use-route-cache-error-boundary.ts
1198
+ function useRouteCacheErrorBoundary(pathname) {
1199
+ const context = useOptionalRouterCacheContext();
1200
+ const routePathname = useLocation({ select: (location) => location.pathname });
1201
+ useEffect(() => {
1202
+ if (!context) return;
1203
+ const targetPathname = normalizeCachedRoutePathname(pathname ?? routePathname);
1204
+ context.retainErroredRoute(targetPathname);
1205
+ return () => {
1206
+ context.releaseErroredRoute(targetPathname);
1207
+ };
1208
+ }, [
1209
+ context,
1210
+ pathname,
1211
+ routePathname
1212
+ ]);
1213
+ }
1214
+ //#endregion
1215
+ //#region src/hooks/use-route-cache-navigation.ts
1216
+ const INITIAL_STATE = {
1217
+ activeNavigation: null,
1218
+ lastCompletedNavigation: null
1219
+ };
1220
+ function useRouteCacheNavigation() {
1221
+ const [state, setState] = useState(INITIAL_STATE);
1222
+ useEventListener({ on: {
1223
+ cachedNavigationStart: (navigation) => {
1224
+ setState((current) => ({
1225
+ ...current,
1226
+ activeNavigation: navigation
1227
+ }));
1228
+ },
1229
+ cachedNavigationCancel: (navigation) => {
1230
+ setState((current) => {
1231
+ if (current.activeNavigation?.pathname !== navigation.pathname) return current;
1232
+ return {
1233
+ ...current,
1234
+ activeNavigation: null
1235
+ };
1236
+ });
1237
+ },
1238
+ cachedNavigationComplete: (navigation) => {
1239
+ setState((current) => ({
1240
+ activeNavigation: current.activeNavigation?.pathname === navigation.pathname ? null : current.activeNavigation,
1241
+ lastCompletedNavigation: navigation
1242
+ }));
1243
+ }
1244
+ } });
1245
+ return state;
1246
+ }
1247
+ //#endregion
1248
+ //#region src/hooks/use-update.ts
1249
+ function useUpdate() {
1250
+ const [, setState] = useState({});
1251
+ return useCallback(() => setState({}), []);
1252
+ }
1253
+ //#endregion
1254
+ //#region src/hooks/use-router-cache.ts
1255
+ function useRouterCache() {
1256
+ const { cachedRoutes, deleteCachedRoutes } = useRouterCacheContext();
1257
+ const update = useUpdate();
1258
+ useEventListener({ on: { activeChange: () => {
1259
+ update();
1260
+ } } });
1261
+ const destroy = (pathname) => {
1262
+ deleteCachedRoutes(Array.isArray(pathname) ? pathname : [pathname]);
1263
+ };
1264
+ const destroyAll = () => {
1265
+ deleteCachedRoutes(Object.keys(cachedRoutes));
1266
+ };
1267
+ const invalidateWhere = (predicate) => {
1268
+ const pathnames = Object.entries(cachedRoutes).flatMap(([pathname, route]) => predicate(pathname, route) ? [pathname] : []);
1269
+ if (pathnames.length > 0) deleteCachedRoutes(pathnames);
1270
+ return pathnames;
1271
+ };
1272
+ return {
1273
+ cachedRoutes,
1274
+ destroy,
1275
+ destroyAll,
1276
+ invalidateWhere,
1277
+ isCached: (pathname) => Object.hasOwn(cachedRoutes, normalizeCachedRoutePathname(pathname))
1278
+ };
1279
+ }
1280
+ //#endregion
10
1281
  export { RouterCacheOutlet, RouterCacheProvider, useRouteCacheActive, useRouteCacheActivity, useRouteCacheEffect, useRouteCacheErrorBoundary, useRouteCacheNavigation, useRouterCache };