@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
package/README.md
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
# @buoy/route-events
|
|
2
|
+
|
|
3
|
+
A comprehensive route tracking and visualization toolkit for Expo Router applications. Monitor route changes, inspect navigation state, explore your route sitemap, and visualize the navigation stack.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Route Event Tracking**: Automatically track all route changes with detailed event information
|
|
8
|
+
- **Route Sitemap**: Browse all available routes in your app with search and navigation
|
|
9
|
+
- **Navigation Stack Viewer**: Inspect the current navigation stack in real-time
|
|
10
|
+
- **Event Timeline**: View a chronological timeline of all route changes
|
|
11
|
+
- **Filtering & Search**: Filter events by pathname patterns and search through routes
|
|
12
|
+
- **Persistent State**: Remembers your monitoring preferences and filters
|
|
13
|
+
- **Full Modal UI**: Beautiful, production-ready modal interface for all features
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
This package is part of the React Buoy monorepo and is automatically available to other packages and the example app.
|
|
18
|
+
|
|
19
|
+
For external projects:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @buoy/route-events
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @buoy/route-events
|
|
25
|
+
# or
|
|
26
|
+
yarn add @buoy/route-events
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Simplest Setup - Just 1 Line!
|
|
32
|
+
|
|
33
|
+
**Import the preset and add it to your tools array. Done!**
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { routeEventsToolPreset } from '@buoy/route-events';
|
|
37
|
+
import { FloatingDevTools } from '@buoy/core';
|
|
38
|
+
|
|
39
|
+
const installedApps = [
|
|
40
|
+
routeEventsToolPreset, // That's it! One line.
|
|
41
|
+
// ...your other tools
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function App() {
|
|
45
|
+
return (
|
|
46
|
+
<FloatingDevTools
|
|
47
|
+
apps={installedApps}
|
|
48
|
+
environment="local"
|
|
49
|
+
userRole="admin"
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Done!** The preset automatically:
|
|
56
|
+
- ✅ Starts tracking routes when opened
|
|
57
|
+
- ✅ Uses the built-in route observer singleton
|
|
58
|
+
- ✅ Provides all three tabs (Routes, Events, Stack)
|
|
59
|
+
- ✅ No configuration, no props, no hooks to call
|
|
60
|
+
|
|
61
|
+
## Requirements
|
|
62
|
+
|
|
63
|
+
`@buoy/route-events` relies on the same core libraries as Expo Router itself:
|
|
64
|
+
|
|
65
|
+
- `expo-router` ≥ 2.0.0 (the Routes tab reads `store.routeNode` directly)
|
|
66
|
+
- `@react-navigation/native` ≥ 7.x (Stack tab + Expo Router routing hooks)
|
|
67
|
+
- `@react-native-async-storage/async-storage` (state persistence)
|
|
68
|
+
- `react` / `react-native`
|
|
69
|
+
|
|
70
|
+
These packages are declared as `peerDependencies`, so your app decides the exact versions. When a dependency is missing or not initialized, the devtool logs a clear `[RouteEvents] ...` console message (in dev builds) and gracefully disables the affected feature instead of crashing. See `packages/route-events/docs/ROUTES_SITEMAP_ACCESS_GUIDE.md` for the full data-flow and troubleshooting guide.
|
|
71
|
+
|
|
72
|
+
### Alternative: Manual Setup
|
|
73
|
+
|
|
74
|
+
If you're not using FloatingDevTools or want more control:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { RouteEventsModalWithTabs } from '@buoy/route-events';
|
|
78
|
+
|
|
79
|
+
function App() {
|
|
80
|
+
const [showRoutes, setShowRoutes] = useState(false);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
<Button onPress={() => setShowRoutes(true)}>
|
|
85
|
+
Open Route Inspector
|
|
86
|
+
</Button>
|
|
87
|
+
|
|
88
|
+
<RouteEventsModalWithTabs
|
|
89
|
+
visible={showRoutes}
|
|
90
|
+
onClose={() => setShowRoutes(false)}
|
|
91
|
+
/>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API Reference
|
|
98
|
+
|
|
99
|
+
### Core Components
|
|
100
|
+
|
|
101
|
+
#### `RouteEventsModalWithTabs`
|
|
102
|
+
|
|
103
|
+
The main modal interface with three tabs: Routes, Events, and Stack.
|
|
104
|
+
|
|
105
|
+
**Props:**
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
interface RouteEventsModalWithTabsProps {
|
|
109
|
+
/** Whether the modal is visible */
|
|
110
|
+
visible: boolean;
|
|
111
|
+
/** Callback when modal is closed */
|
|
112
|
+
onClose: () => void;
|
|
113
|
+
/** Optional back button handler */
|
|
114
|
+
onBack?: () => void;
|
|
115
|
+
/** Whether to use shared modal dimensions */
|
|
116
|
+
enableSharedModalDimensions?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Optional route observer instance.
|
|
119
|
+
* If not provided, uses the default singleton (recommended).
|
|
120
|
+
* Route tracking starts automatically when the modal is opened.
|
|
121
|
+
*/
|
|
122
|
+
routeObserver?: RouteObserver;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Example:**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
<RouteEventsModalWithTabs
|
|
130
|
+
visible={isVisible}
|
|
131
|
+
onClose={handleClose}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Advanced Example (with custom observer):**
|
|
136
|
+
|
|
137
|
+
Only needed if you want to use a custom route observer instance:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { RouteEventsModalWithTabs, routeObserver } from '@buoy/route-events';
|
|
141
|
+
|
|
142
|
+
<RouteEventsModalWithTabs
|
|
143
|
+
visible={isVisible}
|
|
144
|
+
onClose={handleClose}
|
|
145
|
+
routeObserver={routeObserver} // Optional - uses default if not provided
|
|
146
|
+
/>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `RoutesSitemap`
|
|
150
|
+
|
|
151
|
+
Standalone component for browsing all routes in your application.
|
|
152
|
+
|
|
153
|
+
**Props:**
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
interface RoutesSitemapProps {
|
|
157
|
+
style?: StyleProp<ViewStyle>;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Example:**
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { RoutesSitemap } from '@buoy/route-events';
|
|
165
|
+
|
|
166
|
+
function RoutesScreen() {
|
|
167
|
+
return <RoutesSitemap />;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### `NavigationStack`
|
|
172
|
+
|
|
173
|
+
Standalone component for visualizing the current navigation stack.
|
|
174
|
+
|
|
175
|
+
**Props:**
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
interface NavigationStackProps {
|
|
179
|
+
style?: StyleProp<ViewStyle>;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Example:**
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { NavigationStack } from '@buoy/route-events';
|
|
187
|
+
|
|
188
|
+
function DebugScreen() {
|
|
189
|
+
return <NavigationStack />;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Hooks
|
|
194
|
+
|
|
195
|
+
#### `useRouteObserver`
|
|
196
|
+
|
|
197
|
+
Hook to track route changes and emit events to the global observer.
|
|
198
|
+
|
|
199
|
+
**Note:** You typically don't need to use this hook directly - the `RouteEventsModalWithTabs` component starts tracking automatically. This hook is only needed for advanced use cases like custom analytics.
|
|
200
|
+
|
|
201
|
+
**Signature:**
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
function useRouteObserver(
|
|
205
|
+
callback?: (event: RouteChangeEvent) => void
|
|
206
|
+
): void
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Examples:**
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// Only needed for custom analytics - modal handles tracking automatically!
|
|
213
|
+
import { useRouteObserver } from '@buoy/route-events';
|
|
214
|
+
|
|
215
|
+
export default function RootLayout() {
|
|
216
|
+
useRouteObserver((event) => {
|
|
217
|
+
analytics.trackPageView(event.pathname);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return <Stack />;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### `useRouteSitemap`
|
|
225
|
+
|
|
226
|
+
Access route information from your app's file-based routing structure.
|
|
227
|
+
|
|
228
|
+
**Signature:**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
function useRouteSitemap(
|
|
232
|
+
options?: UseRouteSitemapOptions
|
|
233
|
+
): UseRouteSitemapResult
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Options:**
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
interface UseRouteSitemapOptions {
|
|
240
|
+
/** Whether to include layout routes */
|
|
241
|
+
includeLayouts?: boolean;
|
|
242
|
+
/** Whether to include route groups */
|
|
243
|
+
includeGroups?: boolean;
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Result:**
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface UseRouteSitemapResult {
|
|
251
|
+
/** All routes in the application */
|
|
252
|
+
routes: RouteInfo[];
|
|
253
|
+
/** Routes grouped by directory */
|
|
254
|
+
groups: RouteGroup[];
|
|
255
|
+
/** Statistics about the routes */
|
|
256
|
+
stats: RouteStats;
|
|
257
|
+
/** Get a specific route by path */
|
|
258
|
+
getRoute: (path: string) => RouteInfo | undefined;
|
|
259
|
+
/** Get all parent routes of a path */
|
|
260
|
+
getParents: (path: string) => RouteInfo[];
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Example:**
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { useRouteSitemap } from '@buoy/route-events';
|
|
268
|
+
|
|
269
|
+
function RouteDebugger() {
|
|
270
|
+
const { routes, stats, getRoute } = useRouteSitemap();
|
|
271
|
+
|
|
272
|
+
console.log(`Total routes: ${stats.total}`);
|
|
273
|
+
console.log(`Dynamic routes: ${stats.byType.dynamic}`);
|
|
274
|
+
|
|
275
|
+
const homeRoute = getRoute('/');
|
|
276
|
+
// { path: '/', type: 'index', ... }
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<View>
|
|
280
|
+
{routes.map(route => (
|
|
281
|
+
<Text key={route.path}>{route.path}</Text>
|
|
282
|
+
))}
|
|
283
|
+
</View>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### `useNavigationStack`
|
|
289
|
+
|
|
290
|
+
Access and control the current navigation stack.
|
|
291
|
+
|
|
292
|
+
**Signature:**
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
function useNavigationStack(): UseNavigationStackResult
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Result:**
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
interface UseNavigationStackResult {
|
|
302
|
+
/** The current navigation stack */
|
|
303
|
+
stack: StackDisplayItem[];
|
|
304
|
+
/** Whether the data is loading */
|
|
305
|
+
loading: boolean;
|
|
306
|
+
/** Navigate to a specific stack index */
|
|
307
|
+
navigateToIndex: (index: number) => void;
|
|
308
|
+
/** Pop back to a specific index */
|
|
309
|
+
popToIndex: (index: number) => void;
|
|
310
|
+
/** Go back one screen */
|
|
311
|
+
goBack: () => void;
|
|
312
|
+
/** Go to the root of the stack */
|
|
313
|
+
popToTop: () => void;
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Example:**
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { useNavigationStack } from '@buoy/route-events';
|
|
321
|
+
|
|
322
|
+
function NavigationDebugger() {
|
|
323
|
+
const { stack, goBack, popToTop } = useNavigationStack();
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<View>
|
|
327
|
+
<Text>Stack Depth: {stack.length}</Text>
|
|
328
|
+
<Button onPress={goBack}>Go Back</Button>
|
|
329
|
+
<Button onPress={popToTop}>Go to Root</Button>
|
|
330
|
+
|
|
331
|
+
{stack.map((item, index) => (
|
|
332
|
+
<Text key={item.key}>
|
|
333
|
+
{index}: {item.displayPath}
|
|
334
|
+
</Text>
|
|
335
|
+
))}
|
|
336
|
+
</View>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Utilities
|
|
342
|
+
|
|
343
|
+
#### `RouteObserver`
|
|
344
|
+
|
|
345
|
+
Singleton instance for observing route changes.
|
|
346
|
+
|
|
347
|
+
**Methods:**
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
class RouteObserver {
|
|
351
|
+
/** Emit a route change event */
|
|
352
|
+
emit(event: RouteChangeEvent): void;
|
|
353
|
+
|
|
354
|
+
/** Add a listener for route changes */
|
|
355
|
+
addListener(callback: (event: RouteChangeEvent) => void): () => void;
|
|
356
|
+
|
|
357
|
+
/** Remove a listener */
|
|
358
|
+
removeListener(callback: (event: RouteChangeEvent) => void): void;
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Example:**
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { routeObserver } from '@buoy/route-events';
|
|
366
|
+
|
|
367
|
+
// Add listener
|
|
368
|
+
const unsubscribe = routeObserver.addListener((event) => {
|
|
369
|
+
console.log('Route changed:', event.pathname);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Later, remove listener
|
|
373
|
+
unsubscribe();
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### `RouteParser`
|
|
377
|
+
|
|
378
|
+
Utility for parsing Expo Router's route structure.
|
|
379
|
+
|
|
380
|
+
**Methods:**
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
class RouteParser {
|
|
384
|
+
/** Get all routes from the router */
|
|
385
|
+
static getRoutes(options?: {
|
|
386
|
+
includeLayouts?: boolean;
|
|
387
|
+
includeGroups?: boolean;
|
|
388
|
+
}): RouteInfo[];
|
|
389
|
+
|
|
390
|
+
/** Group routes by directory */
|
|
391
|
+
static groupRoutes(routes: RouteInfo[]): RouteGroup[];
|
|
392
|
+
|
|
393
|
+
/** Get statistics about routes */
|
|
394
|
+
static getStats(routes: RouteInfo[]): RouteStats;
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Types
|
|
399
|
+
|
|
400
|
+
#### `RouteChangeEvent`
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
interface RouteChangeEvent {
|
|
404
|
+
/** Current pathname */
|
|
405
|
+
pathname: string;
|
|
406
|
+
/** Route parameters */
|
|
407
|
+
params: Record<string, string | string[]>;
|
|
408
|
+
/** Route segments */
|
|
409
|
+
segments: string[];
|
|
410
|
+
/** Timestamp of the change */
|
|
411
|
+
timestamp: number;
|
|
412
|
+
/** Previous pathname (if available) */
|
|
413
|
+
previousPathname?: string;
|
|
414
|
+
/** Time since previous navigation in ms */
|
|
415
|
+
timeSincePrevious?: number;
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### `RouteInfo`
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
interface RouteInfo {
|
|
423
|
+
/** Full path of the route */
|
|
424
|
+
path: string;
|
|
425
|
+
/** Route type */
|
|
426
|
+
type: RouteType;
|
|
427
|
+
/** Route name/file */
|
|
428
|
+
name: string;
|
|
429
|
+
/** Dynamic parameters */
|
|
430
|
+
params: string[];
|
|
431
|
+
/** Whether it's a layout */
|
|
432
|
+
isLayout: boolean;
|
|
433
|
+
/** Whether it's a route group */
|
|
434
|
+
isGroup: boolean;
|
|
435
|
+
/** Parent path */
|
|
436
|
+
parent?: string;
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### `RouteType`
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
type RouteType =
|
|
444
|
+
| 'index' // index routes (/)
|
|
445
|
+
| 'static' // static routes (/about)
|
|
446
|
+
| 'dynamic' // dynamic routes (/user/[id])
|
|
447
|
+
| 'catch-all' // catch-all routes (/docs/[...path])
|
|
448
|
+
| 'layout' // layout routes (_layout.tsx)
|
|
449
|
+
| 'group'; // route groups ((tabs))
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Use Cases
|
|
453
|
+
|
|
454
|
+
### Analytics Integration
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import { useRouteObserver } from '@buoy/route-events';
|
|
458
|
+
|
|
459
|
+
export default function RootLayout() {
|
|
460
|
+
useRouteObserver((event) => {
|
|
461
|
+
// Track page views
|
|
462
|
+
analytics.trackPageView({
|
|
463
|
+
path: event.pathname,
|
|
464
|
+
params: event.params,
|
|
465
|
+
previousPath: event.previousPathname,
|
|
466
|
+
timeSpent: event.timeSincePrevious,
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return <Stack />;
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Performance Monitoring
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
import { useRouteObserver } from '@buoy/route-events';
|
|
478
|
+
|
|
479
|
+
export default function RootLayout() {
|
|
480
|
+
useRouteObserver((event) => {
|
|
481
|
+
if (event.timeSincePrevious && event.timeSincePrevious > 1000) {
|
|
482
|
+
// Log slow navigations
|
|
483
|
+
console.warn('Slow navigation:', {
|
|
484
|
+
from: event.previousPathname,
|
|
485
|
+
to: event.pathname,
|
|
486
|
+
duration: event.timeSincePrevious,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return <Stack />;
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Development Tools
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import { RouteEventsModalWithTabs, routeObserver } from '@buoy/route-events';
|
|
499
|
+
|
|
500
|
+
function DevTools() {
|
|
501
|
+
const [visible, setVisible] = useState(false);
|
|
502
|
+
|
|
503
|
+
// Only show in development
|
|
504
|
+
if (__DEV__) {
|
|
505
|
+
return (
|
|
506
|
+
<>
|
|
507
|
+
<TouchableOpacity
|
|
508
|
+
style={styles.devButton}
|
|
509
|
+
onPress={() => setVisible(true)}
|
|
510
|
+
>
|
|
511
|
+
<Text>🧭 Routes</Text>
|
|
512
|
+
</TouchableOpacity>
|
|
513
|
+
|
|
514
|
+
<RouteEventsModalWithTabs
|
|
515
|
+
visible={visible}
|
|
516
|
+
onClose={() => setVisible(false)}
|
|
517
|
+
routeObserver={routeObserver}
|
|
518
|
+
/>
|
|
519
|
+
</>
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Route Documentation
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import { useRouteSitemap } from '@buoy/route-events';
|
|
531
|
+
|
|
532
|
+
function RouteDocumentation() {
|
|
533
|
+
const { routes, groups, stats } = useRouteSitemap({
|
|
534
|
+
includeLayouts: false,
|
|
535
|
+
includeGroups: false,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
return (
|
|
539
|
+
<ScrollView>
|
|
540
|
+
<Text>Total Routes: {stats.total}</Text>
|
|
541
|
+
|
|
542
|
+
{groups.map(group => (
|
|
543
|
+
<View key={group.name}>
|
|
544
|
+
<Text>{group.name} ({group.routes.length})</Text>
|
|
545
|
+
{group.routes.map(route => (
|
|
546
|
+
<Text key={route.path}>
|
|
547
|
+
{route.path} - {route.type}
|
|
548
|
+
</Text>
|
|
549
|
+
))}
|
|
550
|
+
</View>
|
|
551
|
+
))}
|
|
552
|
+
</ScrollView>
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
## Features in Detail
|
|
558
|
+
|
|
559
|
+
### Routes Tab
|
|
560
|
+
|
|
561
|
+
- Browse all routes in your application
|
|
562
|
+
- Search routes by path or name
|
|
563
|
+
- View route types (static, dynamic, catch-all)
|
|
564
|
+
- Navigate to any route directly
|
|
565
|
+
- Handles dynamic route parameters with prompts
|
|
566
|
+
- Group and layout route identification
|
|
567
|
+
|
|
568
|
+
### Events Tab
|
|
569
|
+
|
|
570
|
+
- Real-time route change monitoring
|
|
571
|
+
- Toggle monitoring on/off
|
|
572
|
+
- Event timeline with timestamps
|
|
573
|
+
- Filter events by pathname patterns
|
|
574
|
+
- Search through event history
|
|
575
|
+
- View detailed event information
|
|
576
|
+
- Compare route changes (diff view)
|
|
577
|
+
- Clear event history
|
|
578
|
+
- Persistent filter preferences
|
|
579
|
+
|
|
580
|
+
### Stack Tab
|
|
581
|
+
|
|
582
|
+
- Real-time navigation stack visualization
|
|
583
|
+
- Stack depth indicator
|
|
584
|
+
- Navigate to any stack level
|
|
585
|
+
- Pop to specific stack index
|
|
586
|
+
- Quick navigation controls
|
|
587
|
+
- Route parameter display
|
|
588
|
+
|
|
589
|
+
## Dependencies
|
|
590
|
+
|
|
591
|
+
- `@buoy/shared-ui` - Common UI components and utilities
|
|
592
|
+
- `expo-router` - Expo Router integration (peer dependency)
|
|
593
|
+
- `@react-navigation/native` - React Navigation integration (peer dependency)
|
|
594
|
+
- `@react-native-async-storage/async-storage` - For persistent preferences (peer dependency)
|
|
595
|
+
- React and React Native (peer dependencies)
|
|
596
|
+
|
|
597
|
+
## Development
|
|
598
|
+
|
|
599
|
+
### Building
|
|
600
|
+
|
|
601
|
+
```bash
|
|
602
|
+
pnpm build
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Type Checking
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
pnpm typecheck
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Clean Build
|
|
612
|
+
|
|
613
|
+
```bash
|
|
614
|
+
pnpm clean
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
## Package Structure
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
route-events/
|
|
621
|
+
├── src/
|
|
622
|
+
│ ├── components/ # UI components
|
|
623
|
+
│ │ ├── RouteEventsModalWithTabs.tsx
|
|
624
|
+
│ │ ├── RoutesSitemap.tsx
|
|
625
|
+
│ │ ├── NavigationStack.tsx
|
|
626
|
+
│ │ ├── RouteEventsTimeline.tsx
|
|
627
|
+
│ │ ├── RouteEventItemCompact.tsx
|
|
628
|
+
│ │ ├── RouteEventDetailContent.tsx
|
|
629
|
+
│ │ ├── RouteEventExpandedContent.tsx
|
|
630
|
+
│ │ └── RouteFilterViewV2.tsx
|
|
631
|
+
│ ├── RouteObserver.ts # Event system
|
|
632
|
+
│ ├── RouteParser.ts # Route parsing utilities
|
|
633
|
+
│ ├── useRouteObserver.ts # Route tracking hook
|
|
634
|
+
│ ├── useRouteSitemap.ts # Sitemap access hook
|
|
635
|
+
│ ├── useNavigationStack.ts # Stack access hook
|
|
636
|
+
│ └── index.tsx # Main exports
|
|
637
|
+
├── lib/ # Built output (git ignored)
|
|
638
|
+
├── package.json
|
|
639
|
+
├── tsconfig.json
|
|
640
|
+
├── tsconfig.build.json
|
|
641
|
+
└── README.md
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
## License
|
|
645
|
+
|
|
646
|
+
MIT
|
|
647
|
+
|
|
648
|
+
## Contributing
|
|
649
|
+
|
|
650
|
+
See the main repository [CONTRIBUTING.md](../../CONTRIBUTING.md) for contribution guidelines.
|
|
651
|
+
|
|
652
|
+
## Support
|
|
653
|
+
|
|
654
|
+
For issues and feature requests, please visit the [GitHub repository](https://github.com/LovesWorking/react-native-buoy/issues).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.routeObserver = exports.RouteObserver = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* RouteObserver - Tracks route changes in Expo Router
|
|
9
|
+
*
|
|
10
|
+
* Note: This is a simple event emitter that works with the useRouteObserver hook
|
|
11
|
+
* The actual route tracking happens in the hook using public Expo Router APIs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class RouteObserver {
|
|
15
|
+
listeners = new Set();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Emit a route change event
|
|
19
|
+
* Called by the useRouteObserver hook
|
|
20
|
+
*/
|
|
21
|
+
emit(event) {
|
|
22
|
+
// Notify all listeners
|
|
23
|
+
this.listeners.forEach(listener => {
|
|
24
|
+
try {
|
|
25
|
+
listener(event);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("[RouteObserver] Error in listener:", error);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Add listener for route changes
|
|
34
|
+
* @returns Cleanup function to remove the listener
|
|
35
|
+
*/
|
|
36
|
+
addListener(callback) {
|
|
37
|
+
this.listeners.add(callback);
|
|
38
|
+
return () => this.listeners.delete(callback);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Remove a specific listener
|
|
43
|
+
*/
|
|
44
|
+
removeListener(callback) {
|
|
45
|
+
this.listeners.delete(callback);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Singleton instance of RouteObserver
|
|
51
|
+
* Use this for all route tracking to ensure events are centralized
|
|
52
|
+
*/
|
|
53
|
+
exports.RouteObserver = RouteObserver;
|
|
54
|
+
const routeObserver = exports.routeObserver = new RouteObserver();
|