@buoy-gg/route-events 7.0.41 → 7.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +11 -64
  2. package/lib/commonjs/RouteObserver.js +1 -54
  3. package/lib/commonjs/RouteParser.js +1 -314
  4. package/lib/commonjs/RouteTracker.js +1 -112
  5. package/lib/commonjs/RouteTracker.web.js +1 -12
  6. package/lib/commonjs/browserRouteRegistry.js +1 -30
  7. package/lib/commonjs/browserRoutes.js +1 -96
  8. package/lib/commonjs/components/NavigationStack.js +2 -572
  9. package/lib/commonjs/components/ReactNavigationRoutes.js +1 -836
  10. package/lib/commonjs/components/ReactNavigationRoutes.web.js +1 -12
  11. package/lib/commonjs/components/RouteEventDetail.js +1 -97
  12. package/lib/commonjs/components/RouteEventExpandedContent.js +1 -204
  13. package/lib/commonjs/components/RouteEventItemCompact.js +1 -191
  14. package/lib/commonjs/components/RouteEventsModalWithTabs.js +1 -734
  15. package/lib/commonjs/components/RouteEventsTimeline.js +1 -107
  16. package/lib/commonjs/components/RouteFilterViewV2.js +1 -42
  17. package/lib/commonjs/components/RoutesSitemap.js +2 -1035
  18. package/lib/commonjs/expoRouterStore.js +1 -121
  19. package/lib/commonjs/expoRouterStore.web.js +1 -44
  20. package/lib/commonjs/hooks/useRouteEvents.js +1 -57
  21. package/lib/commonjs/index.js +1 -168
  22. package/lib/commonjs/preset.js +1 -69
  23. package/lib/commonjs/stores/navigationStackStore.js +1 -45
  24. package/lib/commonjs/stores/routeEventStore.js +1 -136
  25. package/lib/commonjs/sync/routeEventsSyncAdapter.js +1 -275
  26. package/lib/commonjs/useNavigationStack.js +1 -408
  27. package/lib/commonjs/useNavigationStack.web.js +1 -32
  28. package/lib/commonjs/useRouteObserver.js +1 -73
  29. package/lib/commonjs/useRouteObserverReactNavigation.js +1 -109
  30. package/lib/commonjs/useRouteSitemap.js +1 -255
  31. package/lib/commonjs/utils/routeTemplate.js +1 -79
  32. package/lib/commonjs/utils/safeReactNavigation.js +1 -194
  33. package/lib/commonjs/utils/safeReactNavigation.web.js +1 -36
  34. package/lib/commonjs/utils/testIDs.js +1 -63
  35. package/lib/module/RouteObserver.js +1 -49
  36. package/lib/module/RouteParser.js +1 -309
  37. package/lib/module/RouteTracker.js +1 -109
  38. package/lib/module/RouteTracker.web.js +1 -8
  39. package/lib/module/browserRouteRegistry.js +1 -24
  40. package/lib/module/browserRoutes.js +1 -92
  41. package/lib/module/components/NavigationStack.js +2 -568
  42. package/lib/module/components/ReactNavigationRoutes.js +1 -832
  43. package/lib/module/components/ReactNavigationRoutes.web.js +1 -3
  44. package/lib/module/components/RouteEventDetail.js +1 -93
  45. package/lib/module/components/RouteEventExpandedContent.js +1 -200
  46. package/lib/module/components/RouteEventItemCompact.js +1 -187
  47. package/lib/module/components/RouteEventsModalWithTabs.js +1 -731
  48. package/lib/module/components/RouteEventsTimeline.js +1 -103
  49. package/lib/module/components/RouteFilterViewV2.js +1 -38
  50. package/lib/module/components/RoutesSitemap.js +2 -1033
  51. package/lib/module/expoRouterStore.js +1 -114
  52. package/lib/module/expoRouterStore.web.js +1 -37
  53. package/lib/module/hooks/useRouteEvents.js +1 -54
  54. package/lib/module/index.js +1 -70
  55. package/lib/module/preset.js +1 -66
  56. package/lib/module/stores/navigationStackStore.js +1 -41
  57. package/lib/module/stores/routeEventStore.js +1 -128
  58. package/lib/module/sync/routeEventsSyncAdapter.js +1 -273
  59. package/lib/module/useNavigationStack.js +1 -404
  60. package/lib/module/useNavigationStack.web.js +1 -28
  61. package/lib/module/useRouteObserver.js +1 -70
  62. package/lib/module/useRouteObserverReactNavigation.js +1 -106
  63. package/lib/module/useRouteSitemap.js +1 -250
  64. package/lib/module/utils/routeTemplate.js +1 -73
  65. package/lib/module/utils/safeReactNavigation.js +1 -185
  66. package/lib/module/utils/safeReactNavigation.web.js +1 -27
  67. package/lib/module/utils/testIDs.js +1 -55
  68. package/lib/web/index.mjs +4 -5
  69. package/package.json +6 -11
  70. package/lib/typescript/RouteObserver.d.ts.map +0 -1
  71. package/lib/typescript/RouteParser.d.ts.map +0 -1
  72. package/lib/typescript/RouteTracker.d.ts.map +0 -1
  73. package/lib/typescript/RouteTracker.web.d.ts.map +0 -1
  74. package/lib/typescript/browserRouteRegistry.d.ts.map +0 -1
  75. package/lib/typescript/browserRoutes.d.ts.map +0 -1
  76. package/lib/typescript/components/NavigationStack.d.ts.map +0 -1
  77. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +0 -1
  78. package/lib/typescript/components/ReactNavigationRoutes.web.d.ts.map +0 -1
  79. package/lib/typescript/components/RouteEventDetail.d.ts.map +0 -1
  80. package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +0 -1
  81. package/lib/typescript/components/RouteEventItemCompact.d.ts.map +0 -1
  82. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +0 -1
  83. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +0 -1
  84. package/lib/typescript/components/RouteFilterViewV2.d.ts.map +0 -1
  85. package/lib/typescript/components/RoutesSitemap.d.ts.map +0 -1
  86. package/lib/typescript/expoRouterStore.d.ts.map +0 -1
  87. package/lib/typescript/expoRouterStore.web.d.ts.map +0 -1
  88. package/lib/typescript/hooks/useRouteEvents.d.ts.map +0 -1
  89. package/lib/typescript/index.d.ts.map +0 -1
  90. package/lib/typescript/preset.d.ts.map +0 -1
  91. package/lib/typescript/stores/navigationStackStore.d.ts.map +0 -1
  92. package/lib/typescript/stores/routeEventStore.d.ts.map +0 -1
  93. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +0 -1
  94. package/lib/typescript/useNavigationStack.d.ts.map +0 -1
  95. package/lib/typescript/useNavigationStack.web.d.ts.map +0 -1
  96. package/lib/typescript/useRouteObserver.d.ts.map +0 -1
  97. package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +0 -1
  98. package/lib/typescript/useRouteSitemap.d.ts.map +0 -1
  99. package/lib/typescript/utils/routeTemplate.d.ts.map +0 -1
  100. package/lib/typescript/utils/safeReactNavigation.d.ts.map +0 -1
  101. package/lib/typescript/utils/safeReactNavigation.web.d.ts.map +0 -1
  102. package/lib/typescript/utils/testIDs.d.ts.map +0 -1
@@ -1,28 +1 @@
1
- "use strict";
2
-
3
- import { useSyncExternalStore } from 'react';
4
- import { navigationStackStore } from './stores/navigationStackStore';
5
- const empty = [];
6
- export function useNavigationStack() {
7
- const stack = useSyncExternalStore(callback => navigationStackStore.subscribe(callback), () => navigationStackStore.getStack(), () => empty);
8
- const focused = stack.find(item => item.isFocused) ?? null;
9
- const actions = () => {
10
- const current = navigationStackStore.getActions();
11
- if (!current) throw new Error('Mount RouteTracker to capture browser navigation.');
12
- return current;
13
- };
14
- return {
15
- stack,
16
- focusedRoute: focused,
17
- stackDepth: stack.length,
18
- isAtRoot: !focused || focused.index === 0,
19
- isLoaded: stack.length > 0,
20
- error: null,
21
- refresh: () => {},
22
- navigateToIndex: index => actions().navigateToIndex(index),
23
- navigateToRoute: (name, params) => actions().navigateToRoute?.(name, params) ?? false,
24
- popToIndex: index => actions().popToIndex(index),
25
- goBack: () => actions().goBack(),
26
- popToTop: () => actions().popToTop()
27
- };
28
- }
1
+ "use strict";import{useSyncExternalStore as c}from"react";import{navigationStackStore as r}from"./stores/navigationStackStore";const s=[];export function useNavigationStack(){const e=c(o=>r.subscribe(o),()=>r.getStack(),()=>s);const n=e.find(o=>o.isFocused)??null;const t=()=>{const o=r.getActions();if(!o)throw new Error("Mount RouteTracker to capture browser navigation.");return o};return{stack:e,focusedRoute:n,stackDepth:e.length,isAtRoot:!n||n.index===0,isLoaded:e.length>0,error:null,refresh:()=>{},navigateToIndex:o=>t().navigateToIndex(o),navigateToRoute:(o,a)=>t().navigateToRoute?.(o,a)??false,popToIndex:o=>t().popToIndex(o),goBack:()=>t().goBack(),popToTop:()=>t().popToTop()}}
@@ -1,70 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * useRouteObserver - React hook for observing route changes
5
- *
6
- * Uses public Expo Router hooks to track route changes and emits them
7
- * to the global RouteObserver singleton.
8
- */
9
-
10
- import { useEffect, useRef } from "react";
11
- import { useSafePathname, useSafeSegments, useSafeGlobalSearchParams } from "@buoy-gg/shared-ui";
12
- import { routeObserver } from "./RouteObserver";
13
-
14
- /**
15
- * Hook to observe route changes in Expo Router
16
- * Automatically emits events to the global RouteObserver
17
- *
18
- * @param callback - Optional function to call on route changes (in addition to the observer)
19
- *
20
- * @example
21
- * ```tsx
22
- * // Just track routes (no custom callback)
23
- * useRouteObserver();
24
- *
25
- * // Track routes with custom callback
26
- * useRouteObserver((event) => {
27
- * // Handle route change
28
- * analytics.trackPageView(event.pathname);
29
- * });
30
- * ```
31
- */
32
- export function useRouteObserver(callback) {
33
- const pathname = useSafePathname();
34
- const segments = useSafeSegments();
35
- const params = useSafeGlobalSearchParams();
36
- const callbackRef = useRef(callback);
37
- const previousPathnameRef = useRef(undefined);
38
- const previousTimestampRef = useRef(undefined);
39
-
40
- // Update ref when callback changes
41
- useEffect(() => {
42
- callbackRef.current = callback;
43
- }, [callback]);
44
-
45
- // Trigger observer and callback whenever route changes
46
- useEffect(() => {
47
- const now = Date.now();
48
- const timeSincePrevious = previousTimestampRef.current ? now - previousTimestampRef.current : undefined;
49
- const event = {
50
- pathname,
51
- params: params,
52
- segments: segments,
53
- timestamp: now,
54
- previousPathname: previousPathnameRef.current,
55
- timeSincePrevious
56
- };
57
-
58
- // Update refs for next navigation
59
- previousPathnameRef.current = pathname;
60
- previousTimestampRef.current = now;
61
-
62
- // Emit to the global observer (this notifies the modal)
63
- routeObserver.emit(event);
64
-
65
- // Also call the custom callback if provided
66
- if (callbackRef.current) {
67
- callbackRef.current(event);
68
- }
69
- }, [pathname, segments, params]);
70
- }
1
+ "use strict";import{useEffect as i,useRef as o}from"react";import{useSafePathname as p,useSafeSegments as v,useSafeGlobalSearchParams as d}from"@buoy-gg/shared-ui";import{routeObserver as S}from"./RouteObserver";export function useRouteObserver(e){const t=p();const u=v();const m=d();const r=o(e);const a=o(void 0);const n=o(void 0);i(()=>{r.current=e},[e]);i(()=>{const s=Date.now();const f=n.current?s-n.current:void 0;const c={pathname:t,params:m,segments:u,timestamp:s,previousPathname:a.current,timeSincePrevious:f};a.current=t;n.current=s;S.emit(c);if(r.current){r.current(c)}},[t,u,m])}
@@ -1,106 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * useRouteObserverReactNavigation - React hook for observing route changes
5
- * using @react-navigation/native APIs.
6
- *
7
- * This is the React Navigation (RN CLI) counterpart to useRouteObserver.ts
8
- * which uses Expo Router hooks. Both emit to the same routeObserver singleton.
9
- *
10
- * Uses NavigationContainerRefContext so it works anywhere inside
11
- * a NavigationContainer — does NOT need to be inside a navigator.
12
- */
13
-
14
- import { useEffect, useRef } from "react";
15
- import { useSafeNavigationContainerRef } from "./utils/safeReactNavigation";
16
- import { routeObserver } from "./RouteObserver";
17
-
18
- /**
19
- * Walk a React Navigation state tree to find the deepest focused route.
20
- * Returns the route object and the array of route names from root to leaf.
21
- */
22
- function getDeepestFocusedRoute(state) {
23
- if (!state || !state.routes || state.routes.length === 0) {
24
- return {
25
- route: null,
26
- segments: []
27
- };
28
- }
29
- const route = state.routes[state.index ?? 0];
30
- const segments = [route.name];
31
- if (route.state) {
32
- const nested = getDeepestFocusedRoute(route.state);
33
- if (nested.route) {
34
- return {
35
- route: nested.route,
36
- segments: [...segments, ...nested.segments]
37
- };
38
- }
39
- }
40
- return {
41
- route,
42
- segments
43
- };
44
- }
45
- function emitRouteEvent(state, previousPathnameRef, previousTimestampRef, callbackRef) {
46
- if (!state) return;
47
- const {
48
- route,
49
- segments
50
- } = getDeepestFocusedRoute(state);
51
- if (!route) return;
52
- const pathname = `/${route.name}`;
53
-
54
- // Skip if pathname hasn't changed
55
- if (pathname === previousPathnameRef.current) return;
56
- const now = Date.now();
57
- const timeSincePrevious = previousTimestampRef.current ? now - previousTimestampRef.current : undefined;
58
- const event = {
59
- pathname,
60
- params: route.params || {},
61
- segments,
62
- timestamp: now,
63
- previousPathname: previousPathnameRef.current,
64
- timeSincePrevious
65
- };
66
- previousPathnameRef.current = pathname;
67
- previousTimestampRef.current = now;
68
- routeObserver.emit(event);
69
- if (callbackRef.current) {
70
- callbackRef.current(event);
71
- }
72
- }
73
-
74
- /**
75
- * Hook to observe route changes via @react-navigation/native.
76
- * Automatically emits events to the global RouteObserver.
77
- *
78
- * Works anywhere inside a NavigationContainer (does not need to be inside a navigator).
79
- */
80
- export function useRouteObserverReactNavigation(callback) {
81
- const containerRef = useSafeNavigationContainerRef();
82
- const callbackRef = useRef(callback);
83
- const previousPathnameRef = useRef(undefined);
84
- const previousTimestampRef = useRef(undefined);
85
- useEffect(() => {
86
- callbackRef.current = callback;
87
- }, [callback]);
88
- useEffect(() => {
89
- if (!containerRef) return;
90
-
91
- // Emit for the initial state
92
- const initialState = containerRef.getRootState?.();
93
- if (initialState) {
94
- emitRouteEvent(initialState, previousPathnameRef, previousTimestampRef, callbackRef);
95
- }
96
-
97
- // Listen for state changes
98
- const unsubscribe = containerRef.addListener?.("state", () => {
99
- const state = containerRef.getRootState?.();
100
- emitRouteEvent(state, previousPathnameRef, previousTimestampRef, callbackRef);
101
- });
102
- return () => {
103
- unsubscribe?.();
104
- };
105
- }, [containerRef]);
106
- }
1
+ "use strict";import{useEffect as m,useRef as c}from"react";import{useSafeNavigationContainerRef as p}from"./utils/safeReactNavigation";import{routeObserver as R}from"./RouteObserver";function a(t){if(!t||!t.routes||t.routes.length===0){return{route:null,segments:[]}}const e=t.routes[t.index??0];const n=[e.name];if(e.state){const r=a(e.state);if(r.route){return{route:r.route,segments:[...n,...r.segments]}}}return{route:e,segments:n}}function d(t,e,n,r){if(!t)return;const{route:o,segments:i}=a(t);if(!o)return;const u=`/${o.name}`;if(u===e.current)return;const s=Date.now();const g=n.current?s-n.current:void 0;const f={pathname:u,params:o.params||{},segments:i,timestamp:s,previousPathname:e.current,timeSincePrevious:g};e.current=u;n.current=s;R.emit(f);if(r.current){r.current(f)}}export function useRouteObserverReactNavigation(t){const e=p();const n=c(t);const r=c(void 0);const o=c(void 0);m(()=>{n.current=t},[t]);m(()=>{if(!e)return;const i=e.getRootState?.();if(i){d(i,r,o,n)}const u=e.addListener?.("state",()=>{const s=e.getRootState?.();d(s,r,o,n)});return()=>{u?.()}},[e])}
@@ -1,250 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * useRouteSitemap - React hooks for accessing parsed route information
5
- *
6
- * Provides access to the Expo Router route tree with parsing, filtering,
7
- * and search capabilities.
8
- */
9
-
10
- import { useState, useEffect, useMemo, useCallback } from "react";
11
- import { RouteParser } from "./RouteParser";
12
- import { getRouteNodeMetadata, isExpoRouterStoreSupported, loadRouteNode } from "./expoRouterStore";
13
-
14
- // Type-only definition to avoid Metro resolution issues
15
-
16
- /**
17
- * Extract all routes from the navigation state recursively
18
- * This builds a RouteNode-like structure from React Navigation's state
19
- */
20
- function buildRouteNodeFromNavigationState(state) {
21
- if (!state || !state.routes) return null;
22
-
23
- // Find the app directory structure from the navigation state
24
- // The root route usually contains the file-based routing structure
25
- const rootRoute = state.routes?.[0];
26
- if (!rootRoute) return null;
27
-
28
- // Build a simple route node structure
29
- // This will work with the RouteParser
30
- const routeNode = {
31
- type: 'route',
32
- route: '',
33
- dynamic: null,
34
- children: [],
35
- contextKey: '_app'
36
- };
37
-
38
- // Recursively collect all routes from the state
39
- function collectRoutes(navState, parent, pathPrefix = '') {
40
- if (!navState || !navState.routes) return;
41
- navState.routes.forEach(route => {
42
- const routeName = route.name;
43
-
44
- // Skip internal routes
45
- if (routeName.startsWith('__') || routeName.includes('_layout') || routeName.startsWith('+not-found')) {
46
- return;
47
- }
48
-
49
- // Create a route node
50
- const node = {
51
- type: routeName === 'index' ? 'route' : 'route',
52
- route: routeName === 'index' ? '' : routeName,
53
- dynamic: routeName.includes('[') ? [routeName.match(/\[([^\]]+)\]/)?.[1] || ''] : null,
54
- children: [],
55
- contextKey: `app/${routeName}`
56
- };
57
- parent.children.push(node);
58
-
59
- // If this route has nested state, recurse
60
- if (route.state) {
61
- collectRoutes(route.state, node, `${pathPrefix}/${routeName}`);
62
- }
63
- });
64
- }
65
- collectRoutes(state, routeNode);
66
- return routeNode.children.length > 0 ? routeNode : null;
67
- }
68
-
69
- // ============================================================================
70
- // Hook Options & Return Types
71
- // ============================================================================
72
-
73
- // ============================================================================
74
- // Main Hook
75
- // ============================================================================
76
-
77
- /**
78
- * Hook to access and parse Expo Router's route tree
79
- *
80
- * @example
81
- * ```tsx
82
- * const { routes, groups, stats, filteredRoutes } = useRouteSitemap({
83
- * searchQuery: 'pokemon',
84
- * sortBy: 'path',
85
- * autoRefresh: true
86
- * });
87
- * ```
88
- */
89
- export function useRouteSitemap(options = {}) {
90
- const {
91
- searchQuery = "",
92
- sortBy = "path",
93
- autoRefresh = false,
94
- refreshInterval = 1000,
95
- injectedRoutes,
96
- injectedSource = null,
97
- injectedLastUpdatedAt = null
98
- } = options;
99
- const hasInjectedRoutes = injectedRoutes != null;
100
- const [routeTreeState, setRouteTreeState] = useState(() => {
101
- const node = loadRouteNode();
102
- const metadata = getRouteNodeMetadata();
103
- return {
104
- node,
105
- version: 0,
106
- lastUpdatedAt: metadata.lastLoadedAt,
107
- source: metadata.source
108
- };
109
- });
110
- const routeNode = routeTreeState.node;
111
- const routeNodeVersion = routeTreeState.version;
112
- const lastUpdatedAt = routeTreeState.lastUpdatedAt;
113
- const source = routeTreeState.source;
114
- const refresh = useCallback(() => {
115
- setRouteTreeState(previous => {
116
- const node = loadRouteNode();
117
- const metadata = getRouteNodeMetadata();
118
- return {
119
- node,
120
- version: previous.version + 1,
121
- lastUpdatedAt: metadata.lastLoadedAt,
122
- source: metadata.source
123
- };
124
- });
125
- }, []);
126
-
127
- // When the route tree isn't available yet (e.g., Expo Router still mounting),
128
- // poll until it becomes ready. Use a more aggressive retry since the store
129
- // is a singleton that gets populated asynchronously.
130
- useEffect(() => {
131
- if (routeNode) {
132
- return;
133
- }
134
-
135
- // Routes injected from outside (dashboard) don't come from the local store.
136
- if (hasInjectedRoutes) {
137
- return;
138
- }
139
-
140
- // On web (no expo-router) the tree will never load — don't poll forever.
141
- if (!isExpoRouterStoreSupported()) {
142
- return;
143
- }
144
- let retryCount = 0;
145
- const maxRetries = 100; // 10 seconds max
146
-
147
- const poll = () => {
148
- retryCount++;
149
- refresh();
150
- if (retryCount < maxRetries) {
151
- timeoutRef = setTimeout(poll, 100);
152
- }
153
- };
154
- let timeoutRef = setTimeout(poll, 100);
155
- return () => clearTimeout(timeoutRef);
156
- }, [routeNode, refresh, hasInjectedRoutes]);
157
-
158
- // Optional auto-refresh hook for callers that want periodic updates
159
- useEffect(() => {
160
- if (!autoRefresh) return;
161
- const interval = setInterval(refresh, refreshInterval);
162
- return () => clearInterval(interval);
163
- }, [autoRefresh, refreshInterval, refresh]);
164
-
165
- // With injected routes we're "supported" regardless of the local runtime, and
166
- // "loaded" once the device has actually sent a non-empty tree (an empty array
167
- // means the device is still booting expo-router → keep showing "Loading...").
168
- const isLoaded = hasInjectedRoutes ? injectedRoutes.length > 0 : !!routeNode;
169
- const isSupported = hasInjectedRoutes ? true : isExpoRouterStoreSupported();
170
-
171
- // Parse routes (or use the pre-parsed routes injected from the dashboard).
172
- const routes = useMemo(() => {
173
- if (hasInjectedRoutes) return injectedRoutes;
174
- if (!routeNode) return [];
175
- return RouteParser.parseRouteTree(routeNode);
176
- }, [routeNode, routeNodeVersion, hasInjectedRoutes, injectedRoutes]);
177
-
178
- // Sort routes
179
- const sortedRoutes = useMemo(() => {
180
- return RouteParser.sortRoutes(routes, sortBy);
181
- }, [routes, sortBy]);
182
-
183
- // Filter routes by search query
184
- const filteredRoutes = useMemo(() => {
185
- if (!searchQuery) return sortedRoutes;
186
- return RouteParser.filterRoutes(sortedRoutes, searchQuery);
187
- }, [sortedRoutes, searchQuery]);
188
-
189
- // Organize into groups
190
- const groups = useMemo(() => {
191
- return RouteParser.organizeRoutes(filteredRoutes);
192
- }, [filteredRoutes]);
193
-
194
- // Calculate stats
195
- const stats = useMemo(() => {
196
- return RouteParser.getRouteStats(routes);
197
- }, [routes]);
198
-
199
- // Helper functions
200
- const findRoute = path => RouteParser.findRouteByPath(routes, path);
201
- const getParents = path => RouteParser.getParentRoutes(routes, path);
202
- return {
203
- routes: sortedRoutes,
204
- groups,
205
- stats,
206
- filteredRoutes,
207
- isLoaded,
208
- isSupported,
209
- refresh,
210
- findRoute,
211
- getParents,
212
- lastUpdatedAt: hasInjectedRoutes ? injectedLastUpdatedAt : lastUpdatedAt,
213
- source: hasInjectedRoutes ? injectedSource : source
214
- };
215
- }
216
-
217
- // ============================================================================
218
- // Helper Hooks
219
- // ============================================================================
220
-
221
- /**
222
- * Get a specific route by path
223
- *
224
- * @example
225
- * ```tsx
226
- * const pokemonRoute = useRoute('/pokemon/[id]');
227
- * ```
228
- */
229
- export function useRoute(path) {
230
- const {
231
- findRoute
232
- } = useRouteSitemap();
233
- return useMemo(() => findRoute(path), [findRoute, path]);
234
- }
235
-
236
- /**
237
- * Get all parent routes for a given path
238
- *
239
- * @example
240
- * ```tsx
241
- * const parents = useParentRoutes('/pokemon/[id]');
242
- * // Returns: [{ path: '/pokemon', ... }]
243
- * ```
244
- */
245
- export function useParentRoutes(path) {
246
- const {
247
- getParents
248
- } = useRouteSitemap();
249
- return useMemo(() => getParents(path), [getParents, path]);
250
- }
1
+ "use strict";import{useState as C,useEffect as N,useMemo as u,useCallback as K}from"react";import{RouteParser as s}from"./RouteParser";import{getRouteNodeMetadata as S,isExpoRouterStoreSupported as v,loadRouteNode as P}from"./expoRouterStore";function z(e){if(!e||!e.routes)return null;const r=e.routes?.[0];if(!r)return null;const c={type:"route",route:"",dynamic:null,children:[],contextKey:"_app"};function p(a,i,g=""){if(!a||!a.routes)return;a.routes.forEach(R=>{const t=R.name;if(t.startsWith("__")||t.includes("_layout")||t.startsWith("+not-found")){return}const n={type:t==="index"?"route":"route",route:t==="index"?"":t,dynamic:t.includes("[")?[t.match(/\[([^\]]+)\]/)?.[1]||""]:null,children:[],contextKey:`app/${t}`};i.children.push(n);if(R.state){p(R.state,n,`${g}/${t}`)}})}p(e,c);return c.children.length>0?c:null}export function useRouteSitemap(e={}){const{searchQuery:r="",sortBy:c="path",autoRefresh:p=false,refreshInterval:a=1e3,injectedRoutes:i,injectedSource:g=null,injectedLastUpdatedAt:R=null}=e;const t=i!=null;const[n,T]=C(()=>{const o=P();const f=S();return{node:o,version:0,lastUpdatedAt:f.lastLoadedAt,source:f.source}});const d=n.node;const U=n.version;const j=n.lastUpdatedAt;const I=n.source;const m=K(()=>{T(o=>{const f=P();const h=S();return{node:f,version:o.version+1,lastUpdatedAt:h.lastLoadedAt,source:h.source}})},[]);N(()=>{if(d){return}if(t){return}if(!v()){return}let o=0;const f=100;const h=()=>{o++;m();if(o<f){A=setTimeout(h,100)}};let A=setTimeout(h,100);return()=>clearTimeout(A)},[d,m,t]);N(()=>{if(!p)return;const o=setInterval(m,a);return()=>clearInterval(o)},[p,a,m]);const L=t?i.length>0:!!d;const _=t?true:v();const l=u(()=>{if(t)return i;if(!d)return[];return s.parseRouteTree(d)},[d,U,t,i]);const y=u(()=>{return s.sortRoutes(l,c)},[l,c]);const x=u(()=>{if(!r)return y;return s.filterRoutes(y,r)},[y,r]);const E=u(()=>{return s.organizeRoutes(x)},[x]);const $=u(()=>{return s.getRouteStats(l)},[l]);const b=o=>s.findRouteByPath(l,o);const B=o=>s.getParentRoutes(l,o);return{routes:y,groups:E,stats:$,filteredRoutes:x,isLoaded:L,isSupported:_,refresh:m,findRoute:b,getParents:B,lastUpdatedAt:t?R:j,source:t?g:I}}export function useRoute(e){const{findRoute:r}=useRouteSitemap();return u(()=>r(e),[r,e])}export function useParentRoutes(e){const{getParents:r}=useRouteSitemap();return u(()=>r(e),[r,e])}
@@ -1,73 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Infer a route template from a concrete pathname and its segments.
5
- * Dynamic segments (numeric ids, UUIDs) collapse to `[id]`, so
6
- * `/products/42` becomes `/products/[id]`. Returns null when the template
7
- * matches the pathname (i.e. the route is fully static).
8
- */
9
- export function getRouteTemplate(pathname, segments) {
10
- if (!segments || segments.length === 0) return null;
11
- const templateParts = segments.map(segment => {
12
- // Numeric ids
13
- if (/^\d+$/.test(segment)) {
14
- return "[id]";
15
- }
16
- // UUIDs
17
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) {
18
- return "[id]";
19
- }
20
- return segment;
21
- });
22
- const template = "/" + templateParts.join("/");
23
- return template !== pathname ? template : null;
24
- }
25
-
26
- /**
27
- * The href that reopens a recorded route: its pathname plus the params that are
28
- * not already in the path, as a query string. Expo Router reports dynamic
29
- * segment values (`/order/42` → `{ id: "42" }`) and search params (`?menu=1`)
30
- * in one `params` object; only the second kind needs to be carried, because the
31
- * first is already in the pathname.
32
- */
33
- export function routeHref(pathname, params) {
34
- if (!params) return pathname;
35
- const inPath = new Set(pathname.split("/").filter(Boolean).map(seg => {
36
- try {
37
- return decodeURIComponent(seg);
38
- } catch {
39
- return seg;
40
- }
41
- }));
42
- const query = [];
43
- for (const [key, raw] of Object.entries(params)) {
44
- const values = Array.isArray(raw) ? raw : [raw];
45
- // A catch-all segment's values, or one dynamic segment's value.
46
- if (values.length > 0 && values.every(v => inPath.has(String(v)))) continue;
47
- for (const v of values) query.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`);
48
- }
49
- return query.length ? `${pathname}?${query.join("&")}` : pathname;
50
- }
51
-
52
- /** Split an href from {@link routeHref} back into its path and params. */
53
- export function splitRouteHref(href) {
54
- const q = href.indexOf("?");
55
- if (q < 0) return {
56
- pathname: href,
57
- params: {}
58
- };
59
- const params = {};
60
- for (const pair of href.slice(q + 1).split("&")) {
61
- if (!pair) continue;
62
- const [k, v = ""] = pair.split("=");
63
- try {
64
- params[decodeURIComponent(k)] = decodeURIComponent(v);
65
- } catch {
66
- params[k] = v;
67
- }
68
- }
69
- return {
70
- pathname: href.slice(0, q),
71
- params
72
- };
73
- }
1
+ "use strict";export function getRouteTemplate(t,e){if(!e||e.length===0)return null;const r=e.map(n=>{if(/^\d+$/.test(n)){return"[id]"}if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(n)){return"[id]"}return n});const o="/"+r.join("/");return o!==t?o:null}export function routeHref(t,e){if(!e)return t;const r=new Set(t.split("/").filter(Boolean).map(n=>{try{return decodeURIComponent(n)}catch{return n}}));const o=[];for(const[n,i]of Object.entries(e)){const c=Array.isArray(i)?i:[i];if(c.length>0&&c.every(u=>r.has(String(u))))continue;for(const u of c)o.push(`${encodeURIComponent(n)}=${encodeURIComponent(String(u))}`)}return o.length?`${t}?${o.join("&")}`:t}export function splitRouteHref(t){const e=t.indexOf("?");if(e<0)return{pathname:t,params:{}};const r={};for(const o of t.slice(e+1).split("&")){if(!o)continue;const[n,i=""]=o.split("=");try{r[decodeURIComponent(n)]=decodeURIComponent(i)}catch{r[n]=i}}return{pathname:t.slice(0,e),params:r}}