@buoy-gg/route-events 2.1.3 → 2.1.4-beta.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.
Files changed (36) hide show
  1. package/lib/commonjs/RouteTracker.js +32 -5
  2. package/lib/commonjs/components/NavigationStack.js +24 -33
  3. package/lib/commonjs/components/ReactNavigationRoutes.js +829 -0
  4. package/lib/commonjs/components/RouteEventsModalWithTabs.js +83 -6
  5. package/lib/commonjs/components/RoutesSitemap.js +1 -2
  6. package/lib/commonjs/index.js +7 -0
  7. package/lib/commonjs/useNavigationStack.js +201 -104
  8. package/lib/commonjs/useRouteObserver.js +4 -4
  9. package/lib/commonjs/useRouteObserverReactNavigation.js +109 -0
  10. package/lib/module/RouteTracker.js +32 -5
  11. package/lib/module/components/NavigationStack.js +24 -33
  12. package/lib/module/components/ReactNavigationRoutes.js +825 -0
  13. package/lib/module/components/RouteEventsModalWithTabs.js +85 -7
  14. package/lib/module/components/RoutesSitemap.js +2 -2
  15. package/lib/module/index.js +1 -0
  16. package/lib/module/useNavigationStack.js +203 -106
  17. package/lib/module/useRouteObserver.js +4 -4
  18. package/lib/module/useRouteObserverReactNavigation.js +106 -0
  19. package/lib/typescript/RouteTracker.d.ts +18 -5
  20. package/lib/typescript/RouteTracker.d.ts.map +1 -1
  21. package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
  22. package/lib/typescript/components/ReactNavigationRoutes.d.ts +14 -0
  23. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +1 -0
  24. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
  25. package/lib/typescript/index.d.ts +1 -0
  26. package/lib/typescript/index.d.ts.map +1 -1
  27. package/lib/typescript/useNavigationStack.d.ts +9 -3
  28. package/lib/typescript/useNavigationStack.d.ts.map +1 -1
  29. package/lib/typescript/useRouteObserver.d.ts.map +1 -1
  30. package/lib/typescript/useRouteObserverReactNavigation.d.ts +19 -0
  31. package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +1 -0
  32. package/package.json +6 -6
  33. package/lib/commonjs/utils/safeExpoRouter.js +0 -129
  34. package/lib/module/utils/safeExpoRouter.js +0 -120
  35. package/lib/typescript/utils/safeExpoRouter.d.ts +0 -13
  36. package/lib/typescript/utils/safeExpoRouter.d.ts.map +0 -1
@@ -6,17 +6,67 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.RouteEventsModalWithTabs = RouteEventsModalWithTabs;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
- var _expoRouter = require("expo-router");
9
+ var _native = require("@react-navigation/native");
10
+ var _core = require("@react-navigation/core");
10
11
  var _sharedUi = require("@buoy-gg/shared-ui");
11
12
  var _license = require("@buoy-gg/license");
12
13
  var _useRouteEvents = require("../hooks/useRouteEvents");
13
14
  var _RouteFilterViewV = require("./RouteFilterViewV2");
14
15
  var _RoutesSitemap = require("./RoutesSitemap");
16
+ var _ReactNavigationRoutes = require("./ReactNavigationRoutes");
15
17
  var _NavigationStack = require("./NavigationStack");
16
18
  var _RouteEventsTimeline = require("./RouteEventsTimeline");
17
19
  var _jsxRuntime = require("react/jsx-runtime");
18
20
  // Free tier limit for route events
19
21
  const FREE_TIER_EVENT_LIMIT = 3;
22
+ /**
23
+ * Find the path from root navigation state to a screen by name.
24
+ * Returns array of route names from root to the target, or null if not found.
25
+ */
26
+ function findScreenPath(state, screenName) {
27
+ if (!state || !state.routes) return null;
28
+ for (const route of state.routes) {
29
+ if (route.name === screenName) {
30
+ return [route.name];
31
+ }
32
+ if (route.state) {
33
+ const nested = findScreenPath(route.state, screenName);
34
+ if (nested) {
35
+ return [route.name, ...nested];
36
+ }
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+
42
+ /**
43
+ * Build a nested CommonActions.navigate() from a path of screen names.
44
+ * e.g. ["MainApp", "Settings", "SettingScreen"] becomes:
45
+ * navigate("MainApp", { screen: "Settings", params: { screen: "SettingScreen" } })
46
+ */
47
+ function buildNestedNavigateAction(path) {
48
+ if (path.length === 0) return null;
49
+ if (path.length === 1) {
50
+ return _native.CommonActions.navigate({
51
+ name: path[0]
52
+ });
53
+ }
54
+
55
+ // Build nested params from the end
56
+ let params = {
57
+ screen: path[path.length - 1]
58
+ };
59
+ for (let i = path.length - 2; i >= 1; i--) {
60
+ params = {
61
+ screen: path[i],
62
+ params
63
+ };
64
+ }
65
+ return _native.CommonActions.navigate({
66
+ name: path[0],
67
+ params
68
+ });
69
+ }
20
70
  function RouteEventsModalWithTabs({
21
71
  visible,
22
72
  onClose,
@@ -24,7 +74,9 @@ function RouteEventsModalWithTabs({
24
74
  onMinimize,
25
75
  enableSharedModalDimensions = false
26
76
  }) {
27
- const router = (0, _expoRouter.useRouter)();
77
+ const router = (0, _sharedUi.useSafeRouter)();
78
+ const containerRef = (0, _react.useContext)(_core.NavigationContainerRefContext);
79
+ const hasExpoRouter = (0, _sharedUi.isExpoRouterAvailable)();
28
80
  const [activeTab, setActiveTab] = (0, _react.useState)("events");
29
81
  const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
30
82
 
@@ -69,11 +121,34 @@ function RouteEventsModalWithTabs({
69
121
  }, []);
70
122
  const handleNavigate = (0, _react.useCallback)(pathname => {
71
123
  try {
72
- router.push(pathname);
124
+ if (hasExpoRouter) {
125
+ router.push(pathname);
126
+ } else if (containerRef) {
127
+ // On RN CLI, pathname is /<ScreenName> — extract the screen name
128
+ const screenName = pathname.startsWith("/") ? pathname.slice(1) : pathname;
129
+
130
+ // Walk the state tree to find the nesting path to this screen
131
+ const rootState = containerRef.getRootState?.();
132
+ if (rootState) {
133
+ const path = findScreenPath(rootState, screenName);
134
+ if (path) {
135
+ const action = buildNestedNavigateAction(path);
136
+ if (action) {
137
+ containerRef.dispatch(action);
138
+ return;
139
+ }
140
+ }
141
+ }
142
+
143
+ // Fallback: try direct navigation (works for top-level screens)
144
+ containerRef.dispatch(_native.CommonActions.navigate({
145
+ name: screenName
146
+ }));
147
+ }
73
148
  } catch (error) {
74
149
  _reactNative.Alert.alert("Navigation Error", String(error));
75
150
  }
76
- }, [router]);
151
+ }, [router, containerRef, hasExpoRouter]);
77
152
 
78
153
  // Load persisted tab state on mount
79
154
  (0, _react.useEffect)(() => {
@@ -294,7 +369,9 @@ function RouteEventsModalWithTabs({
294
369
  const footerNode = null;
295
370
  const renderContent = () => {
296
371
  if (activeTab === "routes") {
297
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_RoutesSitemap.RoutesSitemap, {
372
+ return hasExpoRouter ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_RoutesSitemap.RoutesSitemap, {
373
+ style: styles.contentWrapper
374
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_ReactNavigationRoutes.ReactNavigationRoutes, {
298
375
  style: styles.contentWrapper
299
376
  });
300
377
  }
@@ -393,7 +470,7 @@ function RouteEventsModalWithTabs({
393
470
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
394
471
  tabs: [{
395
472
  key: "routes",
396
- label: "Routes"
473
+ label: hasExpoRouter ? "Routes" : "Screens"
397
474
  }, {
398
475
  key: "events",
399
476
  label: `Events${events.length > 0 && activeTab !== "events" ? ` (${events.length})` : ""}`
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.RoutesSitemap = RoutesSitemap;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
- var _expoRouter = require("expo-router");
10
9
  var _sharedUi = require("@buoy-gg/shared-ui");
11
10
  var _license = require("@buoy-gg/license");
12
11
  var _useRouteSitemap = require("../useRouteSitemap");
@@ -48,7 +47,7 @@ function RoutesSitemap({
48
47
  setShowUpgradeModal(false);
49
48
  }
50
49
  }, [showUpgradeModal, isPro]);
51
- const router = (0, _expoRouter.useRouter)();
50
+ const router = (0, _sharedUi.useSafeRouter)();
52
51
  const {
53
52
  groups,
54
53
  stats,
@@ -105,6 +105,12 @@ Object.defineProperty(exports, "useRouteObserver", {
105
105
  return _useRouteObserver.useRouteObserver;
106
106
  }
107
107
  });
108
+ Object.defineProperty(exports, "useRouteObserverReactNavigation", {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _useRouteObserverReactNavigation.useRouteObserverReactNavigation;
112
+ }
113
+ });
108
114
  Object.defineProperty(exports, "useRouteSitemap", {
109
115
  enumerable: true,
110
116
  get: function () {
@@ -119,6 +125,7 @@ var _NavigationStack = require("./components/NavigationStack");
119
125
  var _RouteEventItemCompact = require("./components/RouteEventItemCompact");
120
126
  var _RouteEventExpandedContent = require("./components/RouteEventExpandedContent");
121
127
  var _useRouteObserver = require("./useRouteObserver");
128
+ var _useRouteObserverReactNavigation = require("./useRouteObserverReactNavigation");
122
129
  var _useRouteEvents = require("./hooks/useRouteEvents");
123
130
  var _useRouteSitemap = require("./useRouteSitemap");
124
131
  var _useNavigationStack = require("./useNavigationStack");
@@ -6,20 +6,75 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.useNavigationStack = useNavigationStack;
7
7
  var _react = require("react");
8
8
  var _native = require("@react-navigation/native");
9
- var _expoRouter = require("expo-router");
9
+ var _core = require("@react-navigation/core");
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
11
  /**
11
12
  * useNavigationStack - Hook to access current navigation stack state
12
13
  *
13
- * Provides real-time access to the navigation stack from Expo Router,
14
+ * Provides real-time access to the navigation stack,
14
15
  * showing what screens are currently mounted in memory and which is visible.
15
16
  *
16
- * Data source: @react-navigation/native
17
+ * Supports both Expo Router and React Navigation (RN CLI).
18
+ *
19
+ * - Expo Router: uses useNavigation/useNavigationState (inside navigator context)
20
+ * - RN CLI: uses NavigationContainerRefContext (works as sibling of navigators)
17
21
  */
18
22
 
19
23
  // ============================================================================
20
24
  // Type Definitions
21
25
  // ============================================================================
22
26
 
27
+ // ============================================================================
28
+ // Nested Navigation Helpers
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Find the path from root navigation state to a screen by name.
33
+ * Returns array of route names from root to the target, or null if not found.
34
+ */
35
+ function findScreenPath(state, screenName) {
36
+ if (!state || !state.routes) return null;
37
+ for (const route of state.routes) {
38
+ if (route.name === screenName) {
39
+ return [route.name];
40
+ }
41
+ if (route.state) {
42
+ const nested = findScreenPath(route.state, screenName);
43
+ if (nested) {
44
+ return [route.name, ...nested];
45
+ }
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * Build a nested CommonActions.navigate() from a path of screen names.
53
+ * e.g. ["MainApp", "Settings", "SettingScreen"] becomes:
54
+ * navigate("MainApp", { screen: "Settings", params: { screen: "SettingScreen" } })
55
+ */
56
+ function buildNestedNavigateAction(path) {
57
+ if (path.length === 0) return null;
58
+ if (path.length === 1) {
59
+ return _native.CommonActions.navigate({
60
+ name: path[0]
61
+ });
62
+ }
63
+ let params = {
64
+ screen: path[path.length - 1]
65
+ };
66
+ for (let i = path.length - 2; i >= 1; i--) {
67
+ params = {
68
+ screen: path[i],
69
+ params
70
+ };
71
+ }
72
+ return _native.CommonActions.navigate({
73
+ name: path[0],
74
+ params
75
+ });
76
+ }
77
+
23
78
  // ============================================================================
24
79
  // Main Hook
25
80
  // ============================================================================
@@ -74,7 +129,57 @@ function collectRoutesFromState(state, depth = 0, parentPath = "") {
74
129
  }
75
130
 
76
131
  /**
77
- * Access the current navigation stack from Expo Router
132
+ * Transform raw navigation state into display items
133
+ */
134
+ function buildStack(navigationState, isExpo) {
135
+ if (!navigationState) return [];
136
+ try {
137
+ const routes = collectRoutesFromState(navigationState);
138
+
139
+ // Filter out internal Expo Router routes (layouts, __root, etc.)
140
+ // These only exist in Expo Router; on RN CLI all routes are user-defined
141
+ const filteredRoutes = isExpo ? routes.filter(route => {
142
+ if (route.name.startsWith("__")) return false;
143
+ if (route.name.includes("_layout")) return false;
144
+ if (route.name.startsWith("+not-found")) return false;
145
+ return true;
146
+ }) : routes;
147
+
148
+ // If we filtered everything out, just keep the last route
149
+ const finalRoutes = filteredRoutes.length > 0 ? filteredRoutes : routes.slice(-1);
150
+
151
+ // The last route in the collected list is the focused one
152
+ return finalRoutes.map((route, index) => {
153
+ let cleanPath = route.path || `/${route.name}`;
154
+ // Clean up Expo Router internal /__root prefix
155
+ if (isExpo) {
156
+ cleanPath = cleanPath.replace(/^\/__root/, "");
157
+ }
158
+ // Ensure we always have at least a /
159
+ if (!cleanPath || cleanPath === "") {
160
+ cleanPath = "/";
161
+ }
162
+ return {
163
+ key: route.key,
164
+ name: route.name,
165
+ pathname: cleanPath,
166
+ params: route.params || {},
167
+ isFocused: index === finalRoutes.length - 1,
168
+ index,
169
+ canPop: index > 0
170
+ };
171
+ });
172
+ } catch (err) {
173
+ console.error("Error transforming navigation state:", err);
174
+ return [];
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Access the current navigation stack.
180
+ *
181
+ * Works in both Expo Router and RN CLI apps. On RN CLI, uses the
182
+ * NavigationContainer ref context so it works even as a sibling of navigators.
78
183
  *
79
184
  * @example
80
185
  * ```tsx
@@ -90,60 +195,37 @@ function collectRoutesFromState(state, depth = 0, parentPath = "") {
90
195
  * ```
91
196
  */
92
197
  function useNavigationStack() {
198
+ const isExpo = (0, _sharedUi.isExpoRouterAvailable)();
199
+ const containerRef = (0, _react.useContext)(_core.NavigationContainerRefContext);
200
+ const [navigationState, setNavigationState] = (0, _react.useState)(() => containerRef?.getRootState?.() ?? null);
93
201
  const [isLoaded, setIsLoaded] = (0, _react.useState)(false);
94
202
  const [error, setError] = (0, _react.useState)(null);
95
- const navigation = (0, _native.useNavigation)();
96
203
 
97
- // Subscribe to navigation state changes - this will cause re-renders when state updates
98
- const navigationState = (0, _native.useNavigationState)(state => state);
99
-
100
- // Mark as loaded once we have navigation
204
+ // Subscribe to navigation state changes via the container ref
101
205
  (0, _react.useEffect)(() => {
206
+ if (!containerRef) return;
207
+
208
+ // Get initial state
209
+ const initialState = containerRef.getRootState?.();
210
+ if (initialState) {
211
+ setNavigationState(initialState);
212
+ }
102
213
  setIsLoaded(true);
103
- }, []);
214
+
215
+ // Listen for state changes
216
+ const unsubscribe = containerRef.addListener?.("state", () => {
217
+ const state = containerRef.getRootState?.();
218
+ if (state) {
219
+ setNavigationState(state);
220
+ }
221
+ });
222
+ return () => {
223
+ unsubscribe?.();
224
+ };
225
+ }, [containerRef]);
104
226
 
105
227
  // Transform navigation state into display items
106
- const stack = (0, _react.useMemo)(() => {
107
- if (!navigationState) return [];
108
- try {
109
- const routes = collectRoutesFromState(navigationState);
110
-
111
- // Filter out internal Expo Router routes (layouts, __root, etc.)
112
- const filteredRoutes = routes.filter(route => {
113
- // Remove __root and other internal routes
114
- if (route.name.startsWith("__")) return false;
115
- if (route.name.includes("_layout")) return false;
116
- if (route.name.startsWith("+not-found")) return false;
117
- return true;
118
- });
119
-
120
- // If we filtered everything out, just keep the last route
121
- const finalRoutes = filteredRoutes.length > 0 ? filteredRoutes : routes.slice(-1);
122
-
123
- // The last route in the collected list is the focused one
124
- return finalRoutes.map((route, index) => {
125
- // Clean up pathname by removing /__root prefix
126
- let cleanPath = route.path || `/${route.name}`;
127
- cleanPath = cleanPath.replace(/^\/__root/, "");
128
- // Ensure we always have at least a /
129
- if (!cleanPath || cleanPath === "") {
130
- cleanPath = "/";
131
- }
132
- return {
133
- key: route.key,
134
- name: route.name,
135
- pathname: cleanPath,
136
- params: route.params || {},
137
- isFocused: index === finalRoutes.length - 1,
138
- index,
139
- canPop: index > 0
140
- };
141
- });
142
- } catch (err) {
143
- console.error("Error transforming navigation state:", err);
144
- return [];
145
- }
146
- }, [navigationState]);
228
+ const stack = (0, _react.useMemo)(() => buildStack(navigationState, isExpo), [navigationState, isExpo]);
147
229
 
148
230
  // Get focused route
149
231
  const focusedRoute = (0, _react.useMemo)(() => {
@@ -154,77 +236,92 @@ function useNavigationStack() {
154
236
  const stackDepth = stack.length;
155
237
  const isAtRoot = stackDepth <= 1;
156
238
 
157
- // Manual refresh (no-op since we're using React Navigation's state)
158
- const refresh = () => {
159
- // Navigation state updates automatically via useNavigationState
160
- };
161
-
162
- // Navigation actions
163
- const navigateToIndex = index => {
164
- if (index >= stack.length || index < 0) {
165
- return;
166
- }
167
- const targetRoute = stack[index];
168
- if (!targetRoute) {
169
- return;
239
+ // Manual refresh
240
+ const refresh = (0, _react.useCallback)(() => {
241
+ if (!containerRef) return;
242
+ const state = containerRef.getRootState?.();
243
+ if (state) {
244
+ setNavigationState(state);
170
245
  }
246
+ }, [containerRef]);
171
247
 
172
- // Use router directly from expo-router
173
- if (!_expoRouter.router) {
174
- return;
175
- }
248
+ // Navigation actions - use Expo Router when available, fall back to React Navigation
249
+ const navigateToIndex = (0, _react.useCallback)(index => {
250
+ if (index >= stack.length || index < 0) return;
251
+ const targetRoute = stack[index];
252
+ if (!targetRoute) return;
176
253
  try {
177
- _expoRouter.router.navigate(targetRoute.pathname);
254
+ const expoRouter = (0, _sharedUi.getSafeRouter)();
255
+ if (expoRouter) {
256
+ expoRouter.navigate(targetRoute.pathname);
257
+ } else if (containerRef) {
258
+ // Walk the state tree to build nested screen/params for deep navigation
259
+ const rootState = containerRef.getRootState?.();
260
+ if (rootState) {
261
+ const path = findScreenPath(rootState, targetRoute.name);
262
+ if (path) {
263
+ const action = buildNestedNavigateAction(path);
264
+ if (action) {
265
+ containerRef.dispatch(action);
266
+ return;
267
+ }
268
+ }
269
+ }
270
+ // Fallback: try direct navigation
271
+ containerRef.dispatch(_native.CommonActions.navigate({
272
+ name: targetRoute.name,
273
+ params: targetRoute.params
274
+ }));
275
+ }
178
276
  } catch (err) {
179
277
  console.error("Failed to navigate:", err);
180
278
  }
181
- };
182
- const popToIndex = index => {
183
- if (index >= stack.length || index < 0) {
184
- return;
185
- }
279
+ }, [stack, containerRef]);
280
+ const popToIndex = (0, _react.useCallback)(index => {
281
+ if (index >= stack.length || index < 0) return;
186
282
  const currentIndex = stack.length - 1;
187
283
  const popCount = currentIndex - index;
188
- if (popCount <= 0) {
189
- return;
190
- }
191
-
192
- // Use router directly from expo-router
193
- if (!_expoRouter.router) {
194
- return;
195
- }
284
+ if (popCount <= 0) return;
196
285
  try {
197
- // Pop multiple times
198
- for (let i = 0; i < popCount; i++) {
199
- _expoRouter.router.back();
286
+ const expoRouter = (0, _sharedUi.getSafeRouter)();
287
+ if (expoRouter) {
288
+ for (let i = 0; i < popCount; i++) {
289
+ expoRouter.back();
290
+ }
291
+ } else if (containerRef) {
292
+ containerRef.dispatch(_native.StackActions.pop(popCount));
200
293
  }
201
294
  } catch (err) {
202
295
  console.error("Failed to pop:", err);
203
296
  }
204
- };
205
- const goBack = () => {
206
- if (isAtRoot) {
207
- return;
208
- }
209
-
210
- // Use router directly from expo-router
211
- if (!_expoRouter.router) {
212
- return;
213
- }
297
+ }, [stack, containerRef]);
298
+ const goBack = (0, _react.useCallback)(() => {
299
+ if (isAtRoot) return;
214
300
  try {
215
- _expoRouter.router.back();
301
+ const expoRouter = (0, _sharedUi.getSafeRouter)();
302
+ if (expoRouter) {
303
+ expoRouter.back();
304
+ } else if (containerRef) {
305
+ containerRef.goBack();
306
+ }
216
307
  } catch (err) {
217
308
  console.error("Failed to go back:", err);
218
309
  }
219
- };
220
- const popToTop = () => {
221
- if (isAtRoot) {
222
- return;
310
+ }, [isAtRoot, containerRef]);
311
+ const popToTop = (0, _react.useCallback)(() => {
312
+ if (isAtRoot) return;
313
+ try {
314
+ const expoRouter = (0, _sharedUi.getSafeRouter)();
315
+ if (expoRouter) {
316
+ // Pop to index 0 via expo-router
317
+ popToIndex(0);
318
+ } else if (containerRef) {
319
+ containerRef.dispatch(_native.StackActions.popToTop());
320
+ }
321
+ } catch (err) {
322
+ console.error("Failed to pop to top:", err);
223
323
  }
224
-
225
- // Pop to index 0 (root)
226
- popToIndex(0);
227
- };
324
+ }, [isAtRoot, containerRef, popToIndex]);
228
325
  return {
229
326
  stack,
230
327
  focusedRoute,
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.useRouteObserver = useRouteObserver;
7
7
  var _react = require("react");
8
- var _expoRouter = require("expo-router");
8
+ var _sharedUi = require("@buoy-gg/shared-ui");
9
9
  var _RouteObserver = require("./RouteObserver");
10
10
  /**
11
11
  * useRouteObserver - React hook for observing route changes
@@ -33,9 +33,9 @@ var _RouteObserver = require("./RouteObserver");
33
33
  * ```
34
34
  */
35
35
  function useRouteObserver(callback) {
36
- const pathname = (0, _expoRouter.usePathname)();
37
- const segments = (0, _expoRouter.useSegments)();
38
- const params = (0, _expoRouter.useGlobalSearchParams)();
36
+ const pathname = (0, _sharedUi.useSafePathname)();
37
+ const segments = (0, _sharedUi.useSafeSegments)();
38
+ const params = (0, _sharedUi.useSafeGlobalSearchParams)();
39
39
  const callbackRef = (0, _react.useRef)(callback);
40
40
  const previousPathnameRef = (0, _react.useRef)(undefined);
41
41
  const previousTimestampRef = (0, _react.useRef)(undefined);
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useRouteObserverReactNavigation = useRouteObserverReactNavigation;
7
+ var _react = require("react");
8
+ var _core = require("@react-navigation/core");
9
+ var _RouteObserver = require("./RouteObserver");
10
+ /**
11
+ * useRouteObserverReactNavigation - React hook for observing route changes
12
+ * using @react-navigation/native APIs.
13
+ *
14
+ * This is the React Navigation (RN CLI) counterpart to useRouteObserver.ts
15
+ * which uses Expo Router hooks. Both emit to the same routeObserver singleton.
16
+ *
17
+ * Uses NavigationContainerRefContext so it works anywhere inside
18
+ * a NavigationContainer — does NOT need to be inside a navigator.
19
+ */
20
+
21
+ /**
22
+ * Walk a React Navigation state tree to find the deepest focused route.
23
+ * Returns the route object and the array of route names from root to leaf.
24
+ */
25
+ function getDeepestFocusedRoute(state) {
26
+ if (!state || !state.routes || state.routes.length === 0) {
27
+ return {
28
+ route: null,
29
+ segments: []
30
+ };
31
+ }
32
+ const route = state.routes[state.index ?? 0];
33
+ const segments = [route.name];
34
+ if (route.state) {
35
+ const nested = getDeepestFocusedRoute(route.state);
36
+ if (nested.route) {
37
+ return {
38
+ route: nested.route,
39
+ segments: [...segments, ...nested.segments]
40
+ };
41
+ }
42
+ }
43
+ return {
44
+ route,
45
+ segments
46
+ };
47
+ }
48
+ function emitRouteEvent(state, previousPathnameRef, previousTimestampRef, callbackRef) {
49
+ if (!state) return;
50
+ const {
51
+ route,
52
+ segments
53
+ } = getDeepestFocusedRoute(state);
54
+ if (!route) return;
55
+ const pathname = `/${route.name}`;
56
+
57
+ // Skip if pathname hasn't changed
58
+ if (pathname === previousPathnameRef.current) return;
59
+ const now = Date.now();
60
+ const timeSincePrevious = previousTimestampRef.current ? now - previousTimestampRef.current : undefined;
61
+ const event = {
62
+ pathname,
63
+ params: route.params || {},
64
+ segments,
65
+ timestamp: now,
66
+ previousPathname: previousPathnameRef.current,
67
+ timeSincePrevious
68
+ };
69
+ previousPathnameRef.current = pathname;
70
+ previousTimestampRef.current = now;
71
+ _RouteObserver.routeObserver.emit(event);
72
+ if (callbackRef.current) {
73
+ callbackRef.current(event);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Hook to observe route changes via @react-navigation/native.
79
+ * Automatically emits events to the global RouteObserver.
80
+ *
81
+ * Works anywhere inside a NavigationContainer (does not need to be inside a navigator).
82
+ */
83
+ function useRouteObserverReactNavigation(callback) {
84
+ const containerRef = (0, _react.useContext)(_core.NavigationContainerRefContext);
85
+ const callbackRef = (0, _react.useRef)(callback);
86
+ const previousPathnameRef = (0, _react.useRef)(undefined);
87
+ const previousTimestampRef = (0, _react.useRef)(undefined);
88
+ (0, _react.useEffect)(() => {
89
+ callbackRef.current = callback;
90
+ }, [callback]);
91
+ (0, _react.useEffect)(() => {
92
+ if (!containerRef) return;
93
+
94
+ // Emit for the initial state
95
+ const initialState = containerRef.getRootState?.();
96
+ if (initialState) {
97
+ emitRouteEvent(initialState, previousPathnameRef, previousTimestampRef, callbackRef);
98
+ }
99
+
100
+ // Listen for state changes
101
+ const unsubscribe = containerRef.addListener?.("state", () => {
102
+ const state = containerRef.getRootState?.();
103
+ emitRouteEvent(state, previousPathnameRef, previousTimestampRef, callbackRef);
104
+ });
105
+ return () => {
106
+ unsubscribe?.();
107
+ };
108
+ }, [containerRef]);
109
+ }