@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.
- package/lib/commonjs/RouteTracker.js +32 -5
- package/lib/commonjs/components/NavigationStack.js +24 -33
- package/lib/commonjs/components/ReactNavigationRoutes.js +829 -0
- package/lib/commonjs/components/RouteEventsModalWithTabs.js +83 -6
- package/lib/commonjs/components/RoutesSitemap.js +1 -2
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/useNavigationStack.js +201 -104
- package/lib/commonjs/useRouteObserver.js +4 -4
- package/lib/commonjs/useRouteObserverReactNavigation.js +109 -0
- package/lib/module/RouteTracker.js +32 -5
- package/lib/module/components/NavigationStack.js +24 -33
- package/lib/module/components/ReactNavigationRoutes.js +825 -0
- package/lib/module/components/RouteEventsModalWithTabs.js +85 -7
- package/lib/module/components/RoutesSitemap.js +2 -2
- package/lib/module/index.js +1 -0
- package/lib/module/useNavigationStack.js +203 -106
- package/lib/module/useRouteObserver.js +4 -4
- package/lib/module/useRouteObserverReactNavigation.js +106 -0
- package/lib/typescript/RouteTracker.d.ts +18 -5
- package/lib/typescript/RouteTracker.d.ts.map +1 -1
- package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
- package/lib/typescript/components/ReactNavigationRoutes.d.ts +14 -0
- package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +1 -0
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/useNavigationStack.d.ts +9 -3
- package/lib/typescript/useNavigationStack.d.ts.map +1 -1
- package/lib/typescript/useRouteObserver.d.ts.map +1 -1
- package/lib/typescript/useRouteObserverReactNavigation.d.ts +19 -0
- package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +1 -0
- package/package.json +6 -6
- package/lib/commonjs/utils/safeExpoRouter.js +0 -129
- package/lib/module/utils/safeExpoRouter.js +0 -120
- package/lib/typescript/utils/safeExpoRouter.d.ts +0 -13
- 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
|
|
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,
|
|
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
|
-
|
|
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,
|
|
50
|
+
const router = (0, _sharedUi.useSafeRouter)();
|
|
52
51
|
const {
|
|
53
52
|
groups,
|
|
54
53
|
stats,
|
package/lib/commonjs/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
|
158
|
-
const refresh = () => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
37
|
-
const segments = (0,
|
|
38
|
-
const params = (0,
|
|
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
|
+
}
|