@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.
- package/README.md +654 -0
- package/lib/commonjs/RouteObserver.js +54 -0
- package/lib/commonjs/RouteParser.js +310 -0
- package/lib/commonjs/RouteTracker.js +39 -0
- package/lib/commonjs/components/NavigationStack.js +584 -0
- package/lib/commonjs/components/RouteEventDetailContent.js +492 -0
- package/lib/commonjs/components/RouteEventExpandedContent.js +187 -0
- package/lib/commonjs/components/RouteEventItemCompact.js +175 -0
- package/lib/commonjs/components/RouteEventsModalWithTabs.js +560 -0
- package/lib/commonjs/components/RouteEventsTimeline.js +82 -0
- package/lib/commonjs/components/RouteFilterViewV2.js +42 -0
- package/lib/commonjs/components/RoutesSitemap.js +948 -0
- package/lib/commonjs/expoRouterStore.js +104 -0
- package/lib/commonjs/index.js +99 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +83 -0
- package/lib/commonjs/useNavigationStack.js +241 -0
- package/lib/commonjs/useRouteObserver.js +73 -0
- package/lib/commonjs/useRouteSitemap.js +234 -0
- package/lib/commonjs/utils/safeExpoRouter.js +129 -0
- package/lib/commonjs/utils/safeReactNavigation.js +104 -0
- package/lib/module/RouteObserver.js +49 -0
- package/lib/module/RouteParser.js +305 -0
- package/lib/module/RouteTracker.js +35 -0
- package/lib/module/components/NavigationStack.js +580 -0
- package/lib/module/components/RouteEventDetailContent.js +487 -0
- package/lib/module/components/RouteEventExpandedContent.js +183 -0
- package/lib/module/components/RouteEventItemCompact.js +171 -0
- package/lib/module/components/RouteEventsModalWithTabs.js +557 -0
- package/lib/module/components/RouteEventsTimeline.js +78 -0
- package/lib/module/components/RouteFilterViewV2.js +38 -0
- package/lib/module/components/RoutesSitemap.js +944 -0
- package/lib/module/expoRouterStore.js +98 -0
- package/lib/module/index.js +23 -0
- package/lib/module/preset.js +79 -0
- package/lib/module/useNavigationStack.js +238 -0
- package/lib/module/useRouteObserver.js +70 -0
- package/lib/module/useRouteSitemap.js +229 -0
- package/lib/module/utils/safeExpoRouter.js +120 -0
- package/lib/module/utils/safeReactNavigation.js +98 -0
- package/lib/typescript/RouteObserver.d.ts +37 -0
- package/lib/typescript/RouteObserver.d.ts.map +1 -0
- package/lib/typescript/RouteParser.d.ts +129 -0
- package/lib/typescript/RouteParser.d.ts.map +1 -0
- package/lib/typescript/RouteTracker.d.ts +29 -0
- package/lib/typescript/RouteTracker.d.ts.map +1 -0
- package/lib/typescript/components/NavigationStack.d.ts +11 -0
- package/lib/typescript/components/NavigationStack.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventDetailContent.d.ts +21 -0
- package/lib/typescript/components/RouteEventDetailContent.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventExpandedContent.d.ts +16 -0
- package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventItemCompact.d.ts +15 -0
- package/lib/typescript/components/RouteEventItemCompact.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts +15 -0
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventsTimeline.d.ts +17 -0
- package/lib/typescript/components/RouteEventsTimeline.d.ts.map +1 -0
- package/lib/typescript/components/RouteFilterViewV2.d.ts +9 -0
- package/lib/typescript/components/RouteFilterViewV2.d.ts.map +1 -0
- package/lib/typescript/components/RoutesSitemap.d.ts +15 -0
- package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -0
- package/lib/typescript/expoRouterStore.d.ts +28 -0
- package/lib/typescript/expoRouterStore.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +18 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +76 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/lib/typescript/useNavigationStack.d.ts +48 -0
- package/lib/typescript/useNavigationStack.d.ts.map +1 -0
- package/lib/typescript/useRouteObserver.d.ts +27 -0
- package/lib/typescript/useRouteObserver.d.ts.map +1 -0
- package/lib/typescript/useRouteSitemap.d.ts +102 -0
- package/lib/typescript/useRouteSitemap.d.ts.map +1 -0
- package/lib/typescript/utils/safeExpoRouter.d.ts +13 -0
- package/lib/typescript/utils/safeExpoRouter.d.ts.map +1 -0
- package/lib/typescript/utils/safeReactNavigation.d.ts +10 -0
- package/lib/typescript/utils/safeReactNavigation.d.ts.map +1 -0
- 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();
|