@buoy-gg/route-events 1.7.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.
Files changed (79) hide show
  1. package/README.md +654 -0
  2. package/lib/commonjs/RouteObserver.js +54 -0
  3. package/lib/commonjs/RouteParser.js +310 -0
  4. package/lib/commonjs/RouteTracker.js +39 -0
  5. package/lib/commonjs/components/NavigationStack.js +584 -0
  6. package/lib/commonjs/components/RouteEventDetailContent.js +492 -0
  7. package/lib/commonjs/components/RouteEventExpandedContent.js +187 -0
  8. package/lib/commonjs/components/RouteEventItemCompact.js +175 -0
  9. package/lib/commonjs/components/RouteEventsModalWithTabs.js +560 -0
  10. package/lib/commonjs/components/RouteEventsTimeline.js +82 -0
  11. package/lib/commonjs/components/RouteFilterViewV2.js +42 -0
  12. package/lib/commonjs/components/RoutesSitemap.js +948 -0
  13. package/lib/commonjs/expoRouterStore.js +104 -0
  14. package/lib/commonjs/index.js +99 -0
  15. package/lib/commonjs/package.json +1 -0
  16. package/lib/commonjs/preset.js +83 -0
  17. package/lib/commonjs/useNavigationStack.js +241 -0
  18. package/lib/commonjs/useRouteObserver.js +73 -0
  19. package/lib/commonjs/useRouteSitemap.js +234 -0
  20. package/lib/commonjs/utils/safeExpoRouter.js +129 -0
  21. package/lib/commonjs/utils/safeReactNavigation.js +104 -0
  22. package/lib/module/RouteObserver.js +49 -0
  23. package/lib/module/RouteParser.js +305 -0
  24. package/lib/module/RouteTracker.js +35 -0
  25. package/lib/module/components/NavigationStack.js +580 -0
  26. package/lib/module/components/RouteEventDetailContent.js +487 -0
  27. package/lib/module/components/RouteEventExpandedContent.js +183 -0
  28. package/lib/module/components/RouteEventItemCompact.js +171 -0
  29. package/lib/module/components/RouteEventsModalWithTabs.js +557 -0
  30. package/lib/module/components/RouteEventsTimeline.js +78 -0
  31. package/lib/module/components/RouteFilterViewV2.js +38 -0
  32. package/lib/module/components/RoutesSitemap.js +944 -0
  33. package/lib/module/expoRouterStore.js +98 -0
  34. package/lib/module/index.js +23 -0
  35. package/lib/module/preset.js +79 -0
  36. package/lib/module/useNavigationStack.js +238 -0
  37. package/lib/module/useRouteObserver.js +70 -0
  38. package/lib/module/useRouteSitemap.js +229 -0
  39. package/lib/module/utils/safeExpoRouter.js +120 -0
  40. package/lib/module/utils/safeReactNavigation.js +98 -0
  41. package/lib/typescript/RouteObserver.d.ts +37 -0
  42. package/lib/typescript/RouteObserver.d.ts.map +1 -0
  43. package/lib/typescript/RouteParser.d.ts +129 -0
  44. package/lib/typescript/RouteParser.d.ts.map +1 -0
  45. package/lib/typescript/RouteTracker.d.ts +29 -0
  46. package/lib/typescript/RouteTracker.d.ts.map +1 -0
  47. package/lib/typescript/components/NavigationStack.d.ts +11 -0
  48. package/lib/typescript/components/NavigationStack.d.ts.map +1 -0
  49. package/lib/typescript/components/RouteEventDetailContent.d.ts +21 -0
  50. package/lib/typescript/components/RouteEventDetailContent.d.ts.map +1 -0
  51. package/lib/typescript/components/RouteEventExpandedContent.d.ts +16 -0
  52. package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +1 -0
  53. package/lib/typescript/components/RouteEventItemCompact.d.ts +15 -0
  54. package/lib/typescript/components/RouteEventItemCompact.d.ts.map +1 -0
  55. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts +15 -0
  56. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -0
  57. package/lib/typescript/components/RouteEventsTimeline.d.ts +17 -0
  58. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +1 -0
  59. package/lib/typescript/components/RouteFilterViewV2.d.ts +9 -0
  60. package/lib/typescript/components/RouteFilterViewV2.d.ts.map +1 -0
  61. package/lib/typescript/components/RoutesSitemap.d.ts +15 -0
  62. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -0
  63. package/lib/typescript/expoRouterStore.d.ts +28 -0
  64. package/lib/typescript/expoRouterStore.d.ts.map +1 -0
  65. package/lib/typescript/index.d.ts +18 -0
  66. package/lib/typescript/index.d.ts.map +1 -0
  67. package/lib/typescript/preset.d.ts +76 -0
  68. package/lib/typescript/preset.d.ts.map +1 -0
  69. package/lib/typescript/useNavigationStack.d.ts +48 -0
  70. package/lib/typescript/useNavigationStack.d.ts.map +1 -0
  71. package/lib/typescript/useRouteObserver.d.ts +27 -0
  72. package/lib/typescript/useRouteObserver.d.ts.map +1 -0
  73. package/lib/typescript/useRouteSitemap.d.ts +102 -0
  74. package/lib/typescript/useRouteSitemap.d.ts.map +1 -0
  75. package/lib/typescript/utils/safeExpoRouter.d.ts +13 -0
  76. package/lib/typescript/utils/safeExpoRouter.d.ts.map +1 -0
  77. package/lib/typescript/utils/safeReactNavigation.d.ts +10 -0
  78. package/lib/typescript/utils/safeReactNavigation.d.ts.map +1 -0
  79. package/package.json +72 -0
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useParentRoutes = useParentRoutes;
7
+ exports.useRoute = useRoute;
8
+ exports.useRouteSitemap = useRouteSitemap;
9
+ var _react = require("react");
10
+ var _RouteParser = require("./RouteParser");
11
+ var _expoRouterStore = require("./expoRouterStore");
12
+ /**
13
+ * useRouteSitemap - React hooks for accessing parsed route information
14
+ *
15
+ * Provides access to the Expo Router route tree with parsing, filtering,
16
+ * and search capabilities.
17
+ */
18
+
19
+ // Type-only definition to avoid Metro resolution issues
20
+
21
+ /**
22
+ * Extract all routes from the navigation state recursively
23
+ * This builds a RouteNode-like structure from React Navigation's state
24
+ */
25
+ function buildRouteNodeFromNavigationState(state) {
26
+ if (!state || !state.routes) return null;
27
+
28
+ // Find the app directory structure from the navigation state
29
+ // The root route usually contains the file-based routing structure
30
+ const rootRoute = state.routes?.[0];
31
+ if (!rootRoute) return null;
32
+
33
+ // Build a simple route node structure
34
+ // This will work with the RouteParser
35
+ const routeNode = {
36
+ type: 'route',
37
+ route: '',
38
+ dynamic: null,
39
+ children: [],
40
+ contextKey: '_app'
41
+ };
42
+
43
+ // Recursively collect all routes from the state
44
+ function collectRoutes(navState, parent, pathPrefix = '') {
45
+ if (!navState || !navState.routes) return;
46
+ navState.routes.forEach(route => {
47
+ const routeName = route.name;
48
+
49
+ // Skip internal routes
50
+ if (routeName.startsWith('__') || routeName.includes('_layout') || routeName.startsWith('+not-found')) {
51
+ return;
52
+ }
53
+
54
+ // Create a route node
55
+ const node = {
56
+ type: routeName === 'index' ? 'route' : 'route',
57
+ route: routeName === 'index' ? '' : routeName,
58
+ dynamic: routeName.includes('[') ? [routeName.match(/\[([^\]]+)\]/)?.[1] || ''] : null,
59
+ children: [],
60
+ contextKey: `app/${routeName}`
61
+ };
62
+ parent.children.push(node);
63
+
64
+ // If this route has nested state, recurse
65
+ if (route.state) {
66
+ collectRoutes(route.state, node, `${pathPrefix}/${routeName}`);
67
+ }
68
+ });
69
+ }
70
+ collectRoutes(state, routeNode);
71
+ return routeNode.children.length > 0 ? routeNode : null;
72
+ }
73
+
74
+ // ============================================================================
75
+ // Hook Options & Return Types
76
+ // ============================================================================
77
+
78
+ // ============================================================================
79
+ // Main Hook
80
+ // ============================================================================
81
+
82
+ /**
83
+ * Hook to access and parse Expo Router's route tree
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * const { routes, groups, stats, filteredRoutes } = useRouteSitemap({
88
+ * searchQuery: 'pokemon',
89
+ * sortBy: 'path',
90
+ * autoRefresh: true
91
+ * });
92
+ * ```
93
+ */
94
+ function useRouteSitemap(options = {}) {
95
+ const {
96
+ searchQuery = "",
97
+ sortBy = "path",
98
+ autoRefresh = false,
99
+ refreshInterval = 1000
100
+ } = options;
101
+ const [routeTreeState, setRouteTreeState] = (0, _react.useState)(() => {
102
+ const node = (0, _expoRouterStore.loadRouteNode)();
103
+ const metadata = (0, _expoRouterStore.getRouteNodeMetadata)();
104
+ return {
105
+ node,
106
+ version: 0,
107
+ lastUpdatedAt: metadata.lastLoadedAt,
108
+ source: metadata.source
109
+ };
110
+ });
111
+ const routeNode = routeTreeState.node;
112
+ const routeNodeVersion = routeTreeState.version;
113
+ const lastUpdatedAt = routeTreeState.lastUpdatedAt;
114
+ const source = routeTreeState.source;
115
+ const refresh = (0, _react.useCallback)(() => {
116
+ setRouteTreeState(previous => {
117
+ const node = (0, _expoRouterStore.loadRouteNode)();
118
+ const metadata = (0, _expoRouterStore.getRouteNodeMetadata)();
119
+ return {
120
+ node,
121
+ version: previous.version + 1,
122
+ lastUpdatedAt: metadata.lastLoadedAt,
123
+ source: metadata.source
124
+ };
125
+ });
126
+ }, []);
127
+
128
+ // When the route tree isn't available yet (e.g., Expo Router still mounting),
129
+ // poll until it becomes ready. Use a more aggressive retry since the store
130
+ // is a singleton that gets populated asynchronously.
131
+ (0, _react.useEffect)(() => {
132
+ if (routeNode) {
133
+ return;
134
+ }
135
+ let retryCount = 0;
136
+ const maxRetries = 100; // 10 seconds max
137
+
138
+ const poll = () => {
139
+ retryCount++;
140
+ refresh();
141
+ if (retryCount < maxRetries) {
142
+ timeoutRef = setTimeout(poll, 100);
143
+ }
144
+ };
145
+ let timeoutRef = setTimeout(poll, 100);
146
+ return () => clearTimeout(timeoutRef);
147
+ }, [routeNode, refresh]);
148
+
149
+ // Optional auto-refresh hook for callers that want periodic updates
150
+ (0, _react.useEffect)(() => {
151
+ if (!autoRefresh) return;
152
+ const interval = setInterval(refresh, refreshInterval);
153
+ return () => clearInterval(interval);
154
+ }, [autoRefresh, refreshInterval, refresh]);
155
+ const isLoaded = !!routeNode;
156
+
157
+ // Parse routes
158
+ const routes = (0, _react.useMemo)(() => {
159
+ if (!routeNode) return [];
160
+ return _RouteParser.RouteParser.parseRouteTree(routeNode);
161
+ }, [routeNode, routeNodeVersion]);
162
+
163
+ // Sort routes
164
+ const sortedRoutes = (0, _react.useMemo)(() => {
165
+ return _RouteParser.RouteParser.sortRoutes(routes, sortBy);
166
+ }, [routes, sortBy]);
167
+
168
+ // Filter routes by search query
169
+ const filteredRoutes = (0, _react.useMemo)(() => {
170
+ if (!searchQuery) return sortedRoutes;
171
+ return _RouteParser.RouteParser.filterRoutes(sortedRoutes, searchQuery);
172
+ }, [sortedRoutes, searchQuery]);
173
+
174
+ // Organize into groups
175
+ const groups = (0, _react.useMemo)(() => {
176
+ return _RouteParser.RouteParser.organizeRoutes(filteredRoutes);
177
+ }, [filteredRoutes]);
178
+
179
+ // Calculate stats
180
+ const stats = (0, _react.useMemo)(() => {
181
+ return _RouteParser.RouteParser.getRouteStats(routes);
182
+ }, [routes]);
183
+
184
+ // Helper functions
185
+ const findRoute = path => _RouteParser.RouteParser.findRouteByPath(routes, path);
186
+ const getParents = path => _RouteParser.RouteParser.getParentRoutes(routes, path);
187
+ return {
188
+ routes: sortedRoutes,
189
+ groups,
190
+ stats,
191
+ filteredRoutes,
192
+ isLoaded,
193
+ refresh,
194
+ findRoute,
195
+ getParents,
196
+ lastUpdatedAt,
197
+ source
198
+ };
199
+ }
200
+
201
+ // ============================================================================
202
+ // Helper Hooks
203
+ // ============================================================================
204
+
205
+ /**
206
+ * Get a specific route by path
207
+ *
208
+ * @example
209
+ * ```tsx
210
+ * const pokemonRoute = useRoute('/pokemon/[id]');
211
+ * ```
212
+ */
213
+ function useRoute(path) {
214
+ const {
215
+ findRoute
216
+ } = useRouteSitemap();
217
+ return (0, _react.useMemo)(() => findRoute(path), [findRoute, path]);
218
+ }
219
+
220
+ /**
221
+ * Get all parent routes for a given path
222
+ *
223
+ * @example
224
+ * ```tsx
225
+ * const parents = useParentRoutes('/pokemon/[id]');
226
+ * // Returns: [{ path: '/pokemon', ... }]
227
+ * ```
228
+ */
229
+ function useParentRoutes(path) {
230
+ const {
231
+ getParents
232
+ } = useRouteSitemap();
233
+ return (0, _react.useMemo)(() => getParents(path), [getParents, path]);
234
+ }
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getSafeRouter = getSafeRouter;
7
+ exports.isExpoRouterAvailable = isExpoRouterAvailable;
8
+ exports.useSafeGlobalSearchParams = useSafeGlobalSearchParams;
9
+ exports.useSafePathname = useSafePathname;
10
+ exports.useSafeRouter = useSafeRouter;
11
+ exports.useSafeSegments = useSafeSegments;
12
+ /**
13
+ * Safe wrapper for expo-router
14
+ *
15
+ * Provides optional imports for expo-router hooks and utilities.
16
+ * Falls back to no-op implementations when expo-router is not installed.
17
+ */
18
+
19
+ let expoRouter = null;
20
+ let isAvailable = false;
21
+ let checkedAvailability = false;
22
+ function checkExpoRouterAvailability() {
23
+ if (checkedAvailability) return isAvailable;
24
+ try {
25
+ expoRouter = require("expo-router");
26
+ isAvailable = expoRouter != null;
27
+ } catch (error) {
28
+ isAvailable = false;
29
+ expoRouter = null;
30
+ }
31
+ checkedAvailability = true;
32
+ return isAvailable;
33
+ }
34
+
35
+ // ============================================================================
36
+ // No-op implementations when expo-router is not available
37
+ // ============================================================================
38
+
39
+ function noOpUseRouter() {
40
+ return {
41
+ push: () => console.warn("[route-events] expo-router not installed: push() unavailable"),
42
+ replace: () => console.warn("[route-events] expo-router not installed: replace() unavailable"),
43
+ back: () => console.warn("[route-events] expo-router not installed: back() unavailable"),
44
+ canGoBack: () => false,
45
+ setParams: () => console.warn("[route-events] expo-router not installed: setParams() unavailable"),
46
+ navigate: () => console.warn("[route-events] expo-router not installed: navigate() unavailable")
47
+ };
48
+ }
49
+ function noOpUsePathname() {
50
+ return "/";
51
+ }
52
+ function noOpUseSegments() {
53
+ return [];
54
+ }
55
+ function noOpUseGlobalSearchParams() {
56
+ return {};
57
+ }
58
+
59
+ // ============================================================================
60
+ // Safe hook exports
61
+ // ============================================================================
62
+
63
+ function useSafeRouter() {
64
+ if (!checkExpoRouterAvailability()) {
65
+ return noOpUseRouter();
66
+ }
67
+ try {
68
+ return expoRouter.useRouter();
69
+ } catch (error) {
70
+ console.warn("[route-events] Failed to use expo-router.useRouter:", error);
71
+ return noOpUseRouter();
72
+ }
73
+ }
74
+ function useSafePathname() {
75
+ if (!checkExpoRouterAvailability()) {
76
+ return noOpUsePathname();
77
+ }
78
+ try {
79
+ return expoRouter.usePathname();
80
+ } catch (error) {
81
+ console.warn("[route-events] Failed to use expo-router.usePathname:", error);
82
+ return noOpUsePathname();
83
+ }
84
+ }
85
+ function useSafeSegments() {
86
+ if (!checkExpoRouterAvailability()) {
87
+ return noOpUseSegments();
88
+ }
89
+ try {
90
+ return expoRouter.useSegments();
91
+ } catch (error) {
92
+ console.warn("[route-events] Failed to use expo-router.useSegments:", error);
93
+ return noOpUseSegments();
94
+ }
95
+ }
96
+ function useSafeGlobalSearchParams() {
97
+ if (!checkExpoRouterAvailability()) {
98
+ return noOpUseGlobalSearchParams();
99
+ }
100
+ try {
101
+ return expoRouter.useGlobalSearchParams();
102
+ } catch (error) {
103
+ console.warn("[route-events] Failed to use expo-router.useGlobalSearchParams:", error);
104
+ return noOpUseGlobalSearchParams();
105
+ }
106
+ }
107
+
108
+ // ============================================================================
109
+ // Router instance getter (for imperative navigation)
110
+ // ============================================================================
111
+
112
+ function getSafeRouter() {
113
+ if (!checkExpoRouterAvailability()) {
114
+ return null;
115
+ }
116
+ try {
117
+ return expoRouter.router || null;
118
+ } catch (error) {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ // ============================================================================
124
+ // Availability check
125
+ // ============================================================================
126
+
127
+ function isExpoRouterAvailable() {
128
+ return checkExpoRouterAvailability();
129
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isReactNavigationAvailable = isReactNavigationAvailable;
7
+ exports.useSafeNavigation = useSafeNavigation;
8
+ exports.useSafeNavigationState = useSafeNavigationState;
9
+ var _react = require("react");
10
+ /**
11
+ * Safe wrapper for @react-navigation/native
12
+ *
13
+ * Provides optional imports for React Navigation hooks.
14
+ * Falls back to no-op implementations when @react-navigation/native is not installed.
15
+ */
16
+
17
+ let reactNavigation = null;
18
+ let isAvailable = false;
19
+ let checkedAvailability = false;
20
+ function checkReactNavigationAvailability() {
21
+ if (checkedAvailability) return isAvailable;
22
+ try {
23
+ reactNavigation = require("@react-navigation/native");
24
+ isAvailable = reactNavigation != null;
25
+ } catch (error) {
26
+ isAvailable = false;
27
+ reactNavigation = null;
28
+ }
29
+ checkedAvailability = true;
30
+ return isAvailable;
31
+ }
32
+
33
+ // ============================================================================
34
+ // No-op implementations when @react-navigation/native is not available
35
+ // ============================================================================
36
+
37
+ function noOpUseNavigation() {
38
+ return (0, _react.useMemo)(() => ({
39
+ navigate: () => console.warn("[route-events] @react-navigation/native not installed: navigate() unavailable"),
40
+ goBack: () => console.warn("[route-events] @react-navigation/native not installed: goBack() unavailable"),
41
+ canGoBack: () => false,
42
+ reset: () => console.warn("[route-events] @react-navigation/native not installed: reset() unavailable"),
43
+ setParams: () => console.warn("[route-events] @react-navigation/native not installed: setParams() unavailable"),
44
+ dispatch: () => console.warn("[route-events] @react-navigation/native not installed: dispatch() unavailable"),
45
+ isFocused: () => true,
46
+ addListener: () => () => {},
47
+ removeListener: () => {},
48
+ getParent: () => undefined,
49
+ getState: () => undefined,
50
+ getId: () => undefined
51
+ }), []);
52
+ }
53
+ function noOpUseNavigationState(selector) {
54
+ return (0, _react.useMemo)(() => {
55
+ // Return a minimal state structure
56
+ const emptyState = {
57
+ key: "default",
58
+ index: 0,
59
+ routeNames: [],
60
+ routes: [],
61
+ type: "stack"
62
+ };
63
+ try {
64
+ return selector ? selector(emptyState) : emptyState;
65
+ } catch {
66
+ return emptyState;
67
+ }
68
+ }, [selector]);
69
+ }
70
+
71
+ // ============================================================================
72
+ // Safe hook exports
73
+ // ============================================================================
74
+
75
+ function useSafeNavigation() {
76
+ if (!checkReactNavigationAvailability()) {
77
+ return noOpUseNavigation();
78
+ }
79
+ try {
80
+ return reactNavigation.useNavigation();
81
+ } catch (error) {
82
+ console.warn("[route-events] Failed to use @react-navigation/native.useNavigation:", error);
83
+ return noOpUseNavigation();
84
+ }
85
+ }
86
+ function useSafeNavigationState(selector) {
87
+ if (!checkReactNavigationAvailability()) {
88
+ return noOpUseNavigationState(selector);
89
+ }
90
+ try {
91
+ return reactNavigation.useNavigationState(selector);
92
+ } catch (error) {
93
+ console.warn("[route-events] Failed to use @react-navigation/native.useNavigationState:", error);
94
+ return noOpUseNavigationState(selector);
95
+ }
96
+ }
97
+
98
+ // ============================================================================
99
+ // Availability check
100
+ // ============================================================================
101
+
102
+ function isReactNavigationAvailable() {
103
+ return checkReactNavigationAvailability();
104
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * RouteObserver - Tracks route changes in Expo Router
5
+ *
6
+ * Note: This is a simple event emitter that works with the useRouteObserver hook
7
+ * The actual route tracking happens in the hook using public Expo Router APIs
8
+ */
9
+
10
+ export class RouteObserver {
11
+ listeners = new Set();
12
+
13
+ /**
14
+ * Emit a route change event
15
+ * Called by the useRouteObserver hook
16
+ */
17
+ emit(event) {
18
+ // Notify all listeners
19
+ this.listeners.forEach(listener => {
20
+ try {
21
+ listener(event);
22
+ } catch (error) {
23
+ console.error("[RouteObserver] Error in listener:", error);
24
+ }
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Add listener for route changes
30
+ * @returns Cleanup function to remove the listener
31
+ */
32
+ addListener(callback) {
33
+ this.listeners.add(callback);
34
+ return () => this.listeners.delete(callback);
35
+ }
36
+
37
+ /**
38
+ * Remove a specific listener
39
+ */
40
+ removeListener(callback) {
41
+ this.listeners.delete(callback);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Singleton instance of RouteObserver
47
+ * Use this for all route tracking to ensure events are centralized
48
+ */
49
+ export const routeObserver = new RouteObserver();