@buoy-gg/route-events 7.0.40 → 7.0.42
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 +11 -60
- package/lib/commonjs/RouteObserver.js +1 -54
- package/lib/commonjs/RouteParser.js +1 -314
- package/lib/commonjs/RouteTracker.js +1 -112
- package/lib/commonjs/RouteTracker.web.js +1 -0
- package/lib/commonjs/browserRouteRegistry.js +1 -0
- package/lib/commonjs/browserRoutes.js +1 -0
- package/lib/commonjs/components/NavigationStack.js +2 -572
- package/lib/commonjs/components/ReactNavigationRoutes.js +1 -836
- package/lib/commonjs/components/ReactNavigationRoutes.web.js +1 -0
- package/lib/commonjs/components/RouteEventDetail.js +1 -97
- package/lib/commonjs/components/RouteEventExpandedContent.js +1 -204
- package/lib/commonjs/components/RouteEventItemCompact.js +1 -191
- package/lib/commonjs/components/RouteEventsModalWithTabs.js +1 -717
- package/lib/commonjs/components/RouteEventsTimeline.js +1 -107
- package/lib/commonjs/components/RouteFilterViewV2.js +1 -42
- package/lib/commonjs/components/RoutesSitemap.js +2 -1035
- package/lib/commonjs/expoRouterStore.js +1 -121
- package/lib/commonjs/expoRouterStore.web.js +1 -0
- package/lib/commonjs/hooks/useRouteEvents.js +1 -57
- package/lib/commonjs/index.js +1 -161
- package/lib/commonjs/preset.js +1 -69
- package/lib/commonjs/stores/navigationStackStore.js +1 -45
- package/lib/commonjs/stores/routeEventStore.js +1 -136
- package/lib/commonjs/sync/routeEventsSyncAdapter.js +1 -275
- package/lib/commonjs/useNavigationStack.js +1 -408
- package/lib/commonjs/useNavigationStack.web.js +1 -0
- package/lib/commonjs/useRouteObserver.js +1 -73
- package/lib/commonjs/useRouteObserverReactNavigation.js +1 -109
- package/lib/commonjs/useRouteSitemap.js +1 -255
- package/lib/commonjs/utils/routeTemplate.js +1 -28
- package/lib/commonjs/utils/safeReactNavigation.js +1 -194
- package/lib/commonjs/utils/safeReactNavigation.web.js +1 -0
- package/lib/commonjs/utils/testIDs.js +1 -63
- package/lib/module/RouteObserver.js +1 -49
- package/lib/module/RouteParser.js +1 -309
- package/lib/module/RouteTracker.js +1 -109
- package/lib/module/RouteTracker.web.js +1 -0
- package/lib/module/browserRouteRegistry.js +1 -0
- package/lib/module/browserRoutes.js +1 -0
- package/lib/module/components/NavigationStack.js +2 -568
- package/lib/module/components/ReactNavigationRoutes.js +1 -832
- package/lib/module/components/ReactNavigationRoutes.web.js +1 -0
- package/lib/module/components/RouteEventDetail.js +1 -93
- package/lib/module/components/RouteEventExpandedContent.js +1 -200
- package/lib/module/components/RouteEventItemCompact.js +1 -187
- package/lib/module/components/RouteEventsModalWithTabs.js +1 -714
- package/lib/module/components/RouteEventsTimeline.js +1 -103
- package/lib/module/components/RouteFilterViewV2.js +1 -38
- package/lib/module/components/RoutesSitemap.js +2 -1033
- package/lib/module/expoRouterStore.js +1 -114
- package/lib/module/expoRouterStore.web.js +1 -0
- package/lib/module/hooks/useRouteEvents.js +1 -54
- package/lib/module/index.js +1 -69
- package/lib/module/preset.js +1 -66
- package/lib/module/stores/navigationStackStore.js +1 -41
- package/lib/module/stores/routeEventStore.js +1 -128
- package/lib/module/sync/routeEventsSyncAdapter.js +1 -273
- package/lib/module/useNavigationStack.js +1 -404
- package/lib/module/useNavigationStack.web.js +1 -0
- package/lib/module/useRouteObserver.js +1 -70
- package/lib/module/useRouteObserverReactNavigation.js +1 -106
- package/lib/module/useRouteSitemap.js +1 -250
- package/lib/module/utils/routeTemplate.js +1 -24
- package/lib/module/utils/safeReactNavigation.js +1 -185
- package/lib/module/utils/safeReactNavigation.web.js +1 -0
- package/lib/module/utils/testIDs.js +1 -55
- package/lib/typescript/RouteTracker.web.d.ts +2 -0
- package/lib/typescript/browserRouteRegistry.d.ts +4 -0
- package/lib/typescript/browserRoutes.d.ts +2 -0
- package/lib/typescript/components/ReactNavigationRoutes.web.d.ts +2 -0
- package/lib/typescript/expoRouterStore.web.d.ts +13 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/useNavigationStack.web.d.ts +4 -0
- package/lib/typescript/utils/routeTemplate.d.ts +13 -0
- package/lib/typescript/utils/safeReactNavigation.web.d.ts +22 -0
- package/lib/web/index.mjs +6 -0
- package/package.json +33 -13
- package/lib/typescript/RouteObserver.d.ts.map +0 -1
- package/lib/typescript/RouteParser.d.ts.map +0 -1
- package/lib/typescript/RouteTracker.d.ts.map +0 -1
- package/lib/typescript/components/NavigationStack.d.ts.map +0 -1
- package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventDetail.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventItemCompact.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventsTimeline.d.ts.map +0 -1
- package/lib/typescript/components/RouteFilterViewV2.d.ts.map +0 -1
- package/lib/typescript/components/RoutesSitemap.d.ts.map +0 -1
- package/lib/typescript/expoRouterStore.d.ts.map +0 -1
- package/lib/typescript/hooks/useRouteEvents.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/stores/navigationStackStore.d.ts.map +0 -1
- package/lib/typescript/stores/routeEventStore.d.ts.map +0 -1
- package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/useNavigationStack.d.ts.map +0 -1
- package/lib/typescript/useRouteObserver.d.ts.map +0 -1
- package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +0 -1
- package/lib/typescript/useRouteSitemap.d.ts.map +0 -1
- package/lib/typescript/utils/routeTemplate.d.ts.map +0 -1
- package/lib/typescript/utils/safeReactNavigation.d.ts.map +0 -1
- package/lib/typescript/utils/testIDs.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,76 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# @buoy-gg/route-events
|
|
4
4
|
|
|
5
5
|
Inspect observed navigation events, browse supported routes, and navigate using the parameters your app requires.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-

|
|
7
|
+
<a href="https://buoy.gg/buoy/latest/docs/tools/routes"><b>Routes guide</b></a> · <a href="https://buoy.gg">buoy.gg</a> · <a href="https://github.com/Buoy-gg/buoy">All tools</a>
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/@buoy-gg/route-events) [](https://www.npmjs.com/package/@buoy-gg/route-events)
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
npm install @buoy-gg/core @buoy-gg/route-events
|
|
15
|
-
```
|
|
11
|
+
<a href="https://buoy.gg/buoy/latest/docs/tools/routes"><img src="https://raw.githubusercontent.com/Buoy-gg/buoy/main/.github/readme/tools/routes.png" alt="Watch the Routes film on buoy.gg" width="640" /></a>
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
</div>
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
## Install
|
|
20
16
|
|
|
21
17
|
```bash
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
For Expo, initialize Buoy before rendering the menu:
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
import { Buoy } from "@buoy-gg/core";
|
|
29
|
-
|
|
30
|
-
Buoy.init({ licenseKey: process.env.EXPO_PUBLIC_BUOY_KEY });
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The login command writes the Expo key to `.env.local`. For React Native CLI, pass the key from your app’s environment configuration; React Native does not load `.env.local` automatically. Mount `FloatingDevTools` inside the same providers as your screens and restart the development server after installation. The [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) shows the complete root component setup.
|
|
34
|
-
|
|
35
|
-
## Quick start
|
|
36
|
-
|
|
37
|
-
```tsx
|
|
38
|
-
// app/_layout.tsx (Expo Router)
|
|
39
|
-
import { Slot } from "expo-router";
|
|
40
|
-
import { FloatingDevTools } from "@buoy-gg/core";
|
|
41
|
-
|
|
42
|
-
export default function RootLayout() {
|
|
43
|
-
return (
|
|
44
|
-
<>
|
|
45
|
-
<Slot />
|
|
46
|
-
<FloatingDevTools />
|
|
47
|
-
</>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
18
|
+
npm install @buoy-gg/core @buoy-gg/route-events
|
|
50
19
|
```
|
|
51
20
|
|
|
52
|
-
|
|
21
|
+
Keep your existing navigator and providers. Expo Router and React Navigation are both supported, with different data available for each.
|
|
53
22
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- **Sitemap** — browse discovered routes, searchable, with route types (static, dynamic, layout) labeled
|
|
57
|
-
- **Navigate** — select a supported route and supply its required parameters. App navigation guards still apply
|
|
58
|
-
- **Live navigation stack** — watch the stack push, pop, and replace as you move through the app
|
|
59
|
-
- **Navigation event timeline** — every navigation logged with path, params, timestamp, and time since the previous one
|
|
60
|
-
- **Filtering & search** — filter events by pathname pattern, search across routes
|
|
61
|
-
- **Tracker integration** — the core menu mounts the package’s route tracker
|
|
62
|
-
- **Hooks for custom UIs** — `useRouteEvents`, `useRouteSitemap`, and `useNavigationStack` if you want the data without the modal
|
|
63
|
-
|
|
64
|
-
## Desktop & AI
|
|
65
|
-
|
|
66
|
-
To connect a React Native app to [Buoy Desktop](https://github.com/Buoy-gg/Buoy-Desktop) or MCP, install `@buoy-gg/external-sync` and follow the [Desktop connection guide](https://buoy.gg/buoy/latest/docs/desktop). Sign in to Desktop separately. [MCP setup](https://buoy.gg/buoy/latest/docs/mcp) also requires a process account and Pro access. Available remote actions depend on the tool and app integration.
|
|
67
|
-
|
|
68
|
-
## Account and plan requirements
|
|
69
|
-
|
|
70
|
-
Use a Free or Pro Buoy account. History limits and paid features vary by tool; see [pricing](https://buoy.gg/pricing). Production access requires Pro where supported. Development-only hooks and actions remain unavailable in release builds.
|
|
23
|
+
The [Routes guide](https://buoy.gg/buoy/latest/docs/tools/routes) covers setup, what it captures and its limits. New to Buoy? Start with the [Quick start](https://buoy.gg/buoy/latest/docs/quick-start).
|
|
71
24
|
|
|
72
25
|
---
|
|
73
26
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Proprietary software. © Buoy LLC. [Terms](https://buoy.gg/terms)
|
|
27
|
+
<sub>Part of <a href="https://buoy.gg">Buoy</a>, developer tools that live inside your app. Proprietary software. © Buoy LLC. <a href="https://buoy.gg/terms">Terms</a></sub>
|
|
@@ -1,54 +1 @@
|
|
|
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();
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.routeObserver=exports.RouteObserver=void 0;class RouteObserver{listeners=new Set;emit(e){this.listeners.forEach(r=>{try{r(e)}catch(t){console.error("[RouteObserver] Error in listener:",t)}})}addListener(e){this.listeners.add(e);return()=>this.listeners.delete(e)}removeListener(e){this.listeners.delete(e)}}exports.RouteObserver=RouteObserver;const routeObserver=exports.routeObserver=new RouteObserver;
|
|
@@ -1,314 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.RouteParser = void 0;
|
|
7
|
-
/**
|
|
8
|
-
* RouteParser - Extract and organize routes from Expo Router's RouteNode tree
|
|
9
|
-
*
|
|
10
|
-
* Based on research findings in:
|
|
11
|
-
* - docs/routing/expo/ROUTENODE_TYPE_DEFINITION.md
|
|
12
|
-
* - docs/routing/expo/ROUTES_SITEMAP_RESEARCH.md
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// Type-only definition to avoid Metro resolution issues
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Types & Interfaces
|
|
19
|
-
// ============================================================================
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Route type classification
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
// 404 fallback
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Parsed route information
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Grouped routes for display
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Route statistics
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
// ============================================================================
|
|
40
|
-
// Route Parser Class
|
|
41
|
-
// ============================================================================
|
|
42
|
-
|
|
43
|
-
class RouteParser {
|
|
44
|
-
/**
|
|
45
|
-
* Parse RouteNode tree and extract all routes
|
|
46
|
-
*/
|
|
47
|
-
static parseRouteTree(rootNode) {
|
|
48
|
-
if (!rootNode) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
const routes = [];
|
|
52
|
-
this.traverseNode(rootNode, '', routes, 0);
|
|
53
|
-
return routes;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Recursively traverse RouteNode tree
|
|
58
|
-
*/
|
|
59
|
-
static traverseNode(node, parentPath, routes, depth) {
|
|
60
|
-
const currentPath = this.buildPath(node, parentPath);
|
|
61
|
-
const routeType = this.detectRouteType(node);
|
|
62
|
-
const shouldInclude = this.shouldIncludeRoute(node);
|
|
63
|
-
const routeInfo = {
|
|
64
|
-
path: currentPath,
|
|
65
|
-
name: node.route,
|
|
66
|
-
type: routeType,
|
|
67
|
-
params: this.extractParams(node),
|
|
68
|
-
nodeType: node.type,
|
|
69
|
-
contextKey: node.contextKey,
|
|
70
|
-
isInternal: node.internal ?? false,
|
|
71
|
-
children: [],
|
|
72
|
-
depth
|
|
73
|
-
};
|
|
74
|
-
if (shouldInclude) {
|
|
75
|
-
routes.push(routeInfo);
|
|
76
|
-
}
|
|
77
|
-
for (const child of node.children) {
|
|
78
|
-
this.traverseNode(child, currentPath, shouldInclude ? routeInfo.children : routes, depth + 1);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Build full path for a route node
|
|
84
|
-
*/
|
|
85
|
-
static buildPath(node, parentPath) {
|
|
86
|
-
if (node.type === 'layout' && node.route === '_layout') {
|
|
87
|
-
return parentPath;
|
|
88
|
-
}
|
|
89
|
-
if (node.route.startsWith('(') && node.route.endsWith(')')) {
|
|
90
|
-
return parentPath;
|
|
91
|
-
}
|
|
92
|
-
if (node.route === 'index') {
|
|
93
|
-
return parentPath || '/';
|
|
94
|
-
}
|
|
95
|
-
const segment = node.route;
|
|
96
|
-
if (!parentPath || parentPath === '/') {
|
|
97
|
-
return `/${segment}`;
|
|
98
|
-
}
|
|
99
|
-
return `${parentPath}/${segment}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Detect the route type classification
|
|
104
|
-
*/
|
|
105
|
-
static detectRouteType(node) {
|
|
106
|
-
const routeName = node.route;
|
|
107
|
-
if (node.dynamic?.some(d => d.notFound)) {
|
|
108
|
-
return 'not-found';
|
|
109
|
-
}
|
|
110
|
-
if (node.dynamic?.some(d => d.deep)) {
|
|
111
|
-
return 'catch-all';
|
|
112
|
-
}
|
|
113
|
-
if (node.dynamic && node.dynamic.length > 0) {
|
|
114
|
-
return 'dynamic';
|
|
115
|
-
}
|
|
116
|
-
if (node.type === 'layout') {
|
|
117
|
-
return 'layout';
|
|
118
|
-
}
|
|
119
|
-
if (routeName.startsWith('(') && routeName.endsWith(')')) {
|
|
120
|
-
return 'group';
|
|
121
|
-
}
|
|
122
|
-
if (routeName === 'index') {
|
|
123
|
-
return 'index';
|
|
124
|
-
}
|
|
125
|
-
return 'static';
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Extract dynamic parameter names from route
|
|
130
|
-
*/
|
|
131
|
-
static extractParams(node) {
|
|
132
|
-
if (!node.dynamic || node.dynamic.length === 0) {
|
|
133
|
-
return [];
|
|
134
|
-
}
|
|
135
|
-
return node.dynamic.map(d => d.name);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Determine if route should be included in results
|
|
140
|
-
*/
|
|
141
|
-
static shouldIncludeRoute(node) {
|
|
142
|
-
if (node.internal) return false;
|
|
143
|
-
if (node.generated) return false;
|
|
144
|
-
if (node.type === 'api') return false;
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Organize routes into groups for display
|
|
150
|
-
*/
|
|
151
|
-
static organizeRoutes(routes) {
|
|
152
|
-
const groups = [];
|
|
153
|
-
|
|
154
|
-
// Flatten before grouping, like every other bucket — static/index screens
|
|
155
|
-
// nested under layouts (e.g. (tabs)/about at depth 2) must still be listed,
|
|
156
|
-
// or the stats header counts routes the list never shows.
|
|
157
|
-
const staticRoutes = this.flattenRoutes(routes).filter(r => r.type === 'static' || r.type === 'index');
|
|
158
|
-
const dynamicRoutes = this.flattenRoutes(routes).filter(r => r.type === 'dynamic' || r.type === 'catch-all');
|
|
159
|
-
const layoutRoutes = this.flattenRoutes(routes).filter(r => r.type === 'layout');
|
|
160
|
-
const groupedRoutes = this.flattenRoutes(routes).filter(r => r.type === 'group');
|
|
161
|
-
if (staticRoutes.length > 0) {
|
|
162
|
-
groups.push({
|
|
163
|
-
title: 'Static Routes',
|
|
164
|
-
icon: '',
|
|
165
|
-
routes: staticRoutes
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
if (dynamicRoutes.length > 0) {
|
|
169
|
-
groups.push({
|
|
170
|
-
title: 'Dynamic Routes',
|
|
171
|
-
icon: '',
|
|
172
|
-
description: 'Routes that accept parameters',
|
|
173
|
-
routes: dynamicRoutes
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
if (layoutRoutes.length > 0) {
|
|
177
|
-
groups.push({
|
|
178
|
-
title: 'Layouts',
|
|
179
|
-
icon: '',
|
|
180
|
-
description: 'Shared UI for nested screens',
|
|
181
|
-
routes: layoutRoutes
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
if (groupedRoutes.length > 0) {
|
|
185
|
-
groups.push({
|
|
186
|
-
title: 'Route Groups',
|
|
187
|
-
icon: '',
|
|
188
|
-
routes: groupedRoutes
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
return groups;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Flatten nested routes into a single array
|
|
196
|
-
*/
|
|
197
|
-
static flattenRoutes(routes) {
|
|
198
|
-
const flattened = [];
|
|
199
|
-
function traverse(route) {
|
|
200
|
-
flattened.push(route);
|
|
201
|
-
route.children.forEach(traverse);
|
|
202
|
-
}
|
|
203
|
-
routes.forEach(traverse);
|
|
204
|
-
return flattened;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Get route statistics
|
|
209
|
-
*/
|
|
210
|
-
static getRouteStats(routes) {
|
|
211
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
212
|
-
return {
|
|
213
|
-
total: flatRoutes.length,
|
|
214
|
-
static: flatRoutes.filter(r => r.type === 'static').length,
|
|
215
|
-
dynamic: flatRoutes.filter(r => r.type === 'dynamic').length,
|
|
216
|
-
catchAll: flatRoutes.filter(r => r.type === 'catch-all').length,
|
|
217
|
-
layouts: flatRoutes.filter(r => r.type === 'layout').length,
|
|
218
|
-
groups: flatRoutes.filter(r => r.type === 'group').length
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Search/filter routes by query
|
|
224
|
-
*/
|
|
225
|
-
static filterRoutes(routes, query) {
|
|
226
|
-
if (!query) {
|
|
227
|
-
return routes;
|
|
228
|
-
}
|
|
229
|
-
const lowerQuery = query.toLowerCase();
|
|
230
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
231
|
-
return flatRoutes.filter(route => {
|
|
232
|
-
return route.path.toLowerCase().includes(lowerQuery) || route.name.toLowerCase().includes(lowerQuery) || route.type.toLowerCase().includes(lowerQuery) || route.params.some(p => p.toLowerCase().includes(lowerQuery));
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Build a visual tree string representation
|
|
238
|
-
*/
|
|
239
|
-
static buildTreeString(routes, depth = 0) {
|
|
240
|
-
let output = '';
|
|
241
|
-
routes.forEach((route, index) => {
|
|
242
|
-
const isLast = index === routes.length - 1;
|
|
243
|
-
const prefix = this.buildTreePrefix(depth, isLast);
|
|
244
|
-
let routeLine = `${prefix}${route.path}`;
|
|
245
|
-
if (route.params.length > 0) {
|
|
246
|
-
routeLine += ` { ${route.params.join(', ')} }`;
|
|
247
|
-
}
|
|
248
|
-
output += routeLine + '\n';
|
|
249
|
-
if (route.children.length > 0) {
|
|
250
|
-
output += this.buildTreeString(route.children, depth + 1);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
return output;
|
|
254
|
-
}
|
|
255
|
-
static buildTreePrefix(depth, isLast) {
|
|
256
|
-
if (depth === 0) {
|
|
257
|
-
return '';
|
|
258
|
-
}
|
|
259
|
-
const indent = ' '.repeat(depth - 1);
|
|
260
|
-
const branch = isLast ? '└─ ' : '├─ ';
|
|
261
|
-
return indent + branch;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Sort routes by various criteria
|
|
266
|
-
*/
|
|
267
|
-
static sortRoutes(routes, sortBy = 'path') {
|
|
268
|
-
const sorted = [...routes];
|
|
269
|
-
sorted.sort((a, b) => {
|
|
270
|
-
switch (sortBy) {
|
|
271
|
-
case 'path':
|
|
272
|
-
return a.path.localeCompare(b.path);
|
|
273
|
-
case 'type':
|
|
274
|
-
return a.type.localeCompare(b.type);
|
|
275
|
-
case 'name':
|
|
276
|
-
return a.name.localeCompare(b.name);
|
|
277
|
-
default:
|
|
278
|
-
return 0;
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
return sorted;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Get route by path
|
|
286
|
-
*/
|
|
287
|
-
static findRouteByPath(routes, path) {
|
|
288
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
289
|
-
return flatRoutes.find(r => r.path === path) || null;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Get all parent routes for a given route
|
|
294
|
-
*/
|
|
295
|
-
static getParentRoutes(routes, targetPath) {
|
|
296
|
-
const parents = [];
|
|
297
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
298
|
-
const target = flatRoutes.find(r => r.path === targetPath);
|
|
299
|
-
if (!target) {
|
|
300
|
-
return parents;
|
|
301
|
-
}
|
|
302
|
-
const pathSegments = targetPath.split('/').filter(Boolean);
|
|
303
|
-
let currentPath = '';
|
|
304
|
-
for (let i = 0; i < pathSegments.length - 1; i++) {
|
|
305
|
-
currentPath += '/' + pathSegments[i];
|
|
306
|
-
const parent = flatRoutes.find(r => r.path === currentPath);
|
|
307
|
-
if (parent) {
|
|
308
|
-
parents.push(parent);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
return parents;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
exports.RouteParser = RouteParser;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.RouteParser=void 0;class RouteParser{static parseRouteTree(t){if(!t){return[]}const e=[];this.traverseNode(t,"",e,0);return e}static traverseNode(t,e,r,s){const i=this.buildPath(t,e);const a=this.detectRouteType(t);const n=this.shouldIncludeRoute(t);const u={path:i,name:t.route,type:a,params:this.extractParams(t),nodeType:t.type,contextKey:t.contextKey,isInternal:t.internal??false,children:[],depth:s};if(n){r.push(u)}for(const o of t.children){this.traverseNode(o,i,n?u.children:r,s+1)}}static buildPath(t,e){if(t.type==="layout"&&t.route==="_layout"){return e}if(t.route.startsWith("(")&&t.route.endsWith(")")){return e}if(t.route==="index"){return e||"/"}const r=t.route;if(!e||e==="/"){return`/${r}`}return`${e}/${r}`}static detectRouteType(t){const e=t.route;if(t.dynamic?.some(r=>r.notFound)){return"not-found"}if(t.dynamic?.some(r=>r.deep)){return"catch-all"}if(t.dynamic&&t.dynamic.length>0){return"dynamic"}if(t.type==="layout"){return"layout"}if(e.startsWith("(")&&e.endsWith(")")){return"group"}if(e==="index"){return"index"}return"static"}static extractParams(t){if(!t.dynamic||t.dynamic.length===0){return[]}return t.dynamic.map(e=>e.name)}static shouldIncludeRoute(t){if(t.internal)return false;if(t.generated)return false;if(t.type==="api")return false;return true}static organizeRoutes(t){const e=[];const r=this.flattenRoutes(t).filter(n=>n.type==="static"||n.type==="index");const s=this.flattenRoutes(t).filter(n=>n.type==="dynamic"||n.type==="catch-all");const i=this.flattenRoutes(t).filter(n=>n.type==="layout");const a=this.flattenRoutes(t).filter(n=>n.type==="group");if(r.length>0){e.push({title:"Static Routes",icon:"",routes:r})}if(s.length>0){e.push({title:"Dynamic Routes",icon:"",description:"Routes that accept parameters",routes:s})}if(i.length>0){e.push({title:"Layouts",icon:"",description:"Shared UI for nested screens",routes:i})}if(a.length>0){e.push({title:"Route Groups",icon:"",routes:a})}return e}static flattenRoutes(t){const e=[];function r(s){e.push(s);s.children.forEach(r)}t.forEach(r);return e}static getRouteStats(t){const e=this.flattenRoutes(t);return{total:e.length,static:e.filter(r=>r.type==="static").length,dynamic:e.filter(r=>r.type==="dynamic").length,catchAll:e.filter(r=>r.type==="catch-all").length,layouts:e.filter(r=>r.type==="layout").length,groups:e.filter(r=>r.type==="group").length}}static filterRoutes(t,e){if(!e){return t}const r=e.toLowerCase();const s=this.flattenRoutes(t);return s.filter(i=>{return i.path.toLowerCase().includes(r)||i.name.toLowerCase().includes(r)||i.type.toLowerCase().includes(r)||i.params.some(a=>a.toLowerCase().includes(r))})}static buildTreeString(t,e=0){let r="";t.forEach((s,i)=>{const a=i===t.length-1;const n=this.buildTreePrefix(e,a);let u=`${n}${s.path}`;if(s.params.length>0){u+=` { ${s.params.join(", ")} }`}r+=u+"\n";if(s.children.length>0){r+=this.buildTreeString(s.children,e+1)}});return r}static buildTreePrefix(t,e){if(t===0){return""}const r=" ".repeat(t-1);const s=e?"\u2514\u2500 ":"\u251C\u2500 ";return r+s}static sortRoutes(t,e="path"){const r=[...t];r.sort((s,i)=>{switch(e){case"path":return s.path.localeCompare(i.path);case"type":return s.type.localeCompare(i.type);case"name":return s.name.localeCompare(i.name);default:return 0}});return r}static findRouteByPath(t,e){const r=this.flattenRoutes(t);return r.find(s=>s.path===e)||null}static getParentRoutes(t,e){const r=[];const s=this.flattenRoutes(t);const i=s.find(u=>u.path===e);if(!i){return r}const a=e.split("/").filter(Boolean);let n="";for(let u=0;u<a.length-1;u++){n+="/"+a[u];const o=s.find(c=>c.path===n);if(o){r.push(o)}}return r}}exports.RouteParser=RouteParser;
|
|
@@ -1,112 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.RouteTracker = RouteTracker;
|
|
7
|
-
var _react = require("react");
|
|
8
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
|
-
var _useRouteObserver = require("./useRouteObserver");
|
|
10
|
-
var _useRouteObserverReactNavigation = require("./useRouteObserverReactNavigation");
|
|
11
|
-
var _useNavigationStack = require("./useNavigationStack");
|
|
12
|
-
var _navigationStackStore = require("./stores/navigationStackStore");
|
|
13
|
-
var _routeEventStore = require("./stores/routeEventStore");
|
|
14
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
15
|
-
/**
|
|
16
|
-
* RouteTracker - A component to place inside your navigation tree
|
|
17
|
-
*
|
|
18
|
-
* Automatically detects whether Expo Router or React Navigation is available
|
|
19
|
-
* and uses the appropriate route observation method.
|
|
20
|
-
*
|
|
21
|
-
* IMPORTANT: This component MUST be placed inside your navigation tree
|
|
22
|
-
* (as a child of Stack, Tabs, Slot, or NavigationContainer) for route tracking to work.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```tsx
|
|
26
|
-
* // Expo Router - in your _layout.tsx
|
|
27
|
-
* import { RouteTracker } from '@buoy-gg/route-events';
|
|
28
|
-
*
|
|
29
|
-
* export default function RootLayout() {
|
|
30
|
-
* return (
|
|
31
|
-
* <>
|
|
32
|
-
* <Stack>
|
|
33
|
-
* <Stack.Screen name="(tabs)" />
|
|
34
|
-
* </Stack>
|
|
35
|
-
* <RouteTracker />
|
|
36
|
-
* <FloatingDevTools ... />
|
|
37
|
-
* </>
|
|
38
|
-
* );
|
|
39
|
-
* }
|
|
40
|
-
*
|
|
41
|
-
* // React Navigation (RN CLI) - inside NavigationContainer
|
|
42
|
-
* import { RouteTracker } from '@buoy-gg/route-events';
|
|
43
|
-
*
|
|
44
|
-
* function App() {
|
|
45
|
-
* return (
|
|
46
|
-
* <NavigationContainer>
|
|
47
|
-
* <RootNavigator />
|
|
48
|
-
* <RouteTracker />
|
|
49
|
-
* <FloatingDevTools ... />
|
|
50
|
-
* </NavigationContainer>
|
|
51
|
-
* );
|
|
52
|
-
* }
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
// Mounted at boot by FloatingDevTools, so this is where capture starts —
|
|
57
|
-
// routeEventStore.hydrateBootCapture explains why the store needs it.
|
|
58
|
-
function useBootCapture() {
|
|
59
|
-
(0, _react.useEffect)(() => {
|
|
60
|
-
_routeEventStore.routeEventStore.hydrateBootCapture();
|
|
61
|
-
}, []);
|
|
62
|
-
}
|
|
63
|
-
function ExpoRouteTracker() {
|
|
64
|
-
useBootCapture();
|
|
65
|
-
(0, _useRouteObserver.useRouteObserver)();
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
function ReactNavigationRouteTracker() {
|
|
69
|
-
useBootCapture();
|
|
70
|
-
(0, _useRouteObserverReactNavigation.useRouteObserverReactNavigation)();
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Mirrors the live navigation stack (and its action functions) into
|
|
76
|
-
* navigationStackStore so the route-events sync adapter can expose the Stack
|
|
77
|
-
* tab to the dashboard. Runs inside the navigation tree where the container ref
|
|
78
|
-
* context is available.
|
|
79
|
-
*/
|
|
80
|
-
function NavigationStackCapture() {
|
|
81
|
-
const {
|
|
82
|
-
stack,
|
|
83
|
-
navigateToIndex,
|
|
84
|
-
navigateToRoute,
|
|
85
|
-
popToIndex,
|
|
86
|
-
goBack,
|
|
87
|
-
popToTop
|
|
88
|
-
} = (0, _useNavigationStack.useNavigationStack)();
|
|
89
|
-
(0, _react.useEffect)(() => {
|
|
90
|
-
_navigationStackStore.navigationStackStore.setStack(stack);
|
|
91
|
-
}, [stack]);
|
|
92
|
-
|
|
93
|
-
// Keep the action references fresh (they change as the stack changes) so a
|
|
94
|
-
// remote action always operates on the current navigation state.
|
|
95
|
-
(0, _react.useEffect)(() => {
|
|
96
|
-
_navigationStackStore.navigationStackStore.setActions({
|
|
97
|
-
navigateToIndex,
|
|
98
|
-
navigateToRoute,
|
|
99
|
-
popToIndex,
|
|
100
|
-
goBack,
|
|
101
|
-
popToTop
|
|
102
|
-
});
|
|
103
|
-
return () => _navigationStackStore.navigationStackStore.setActions(null);
|
|
104
|
-
});
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
function RouteTracker() {
|
|
108
|
-
const hasExpo = (0, _sharedUi.isExpoRouterAvailable)();
|
|
109
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
110
|
-
children: [hasExpo ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ExpoRouteTracker, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(ReactNavigationRouteTracker, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(NavigationStackCapture, {})]
|
|
111
|
-
});
|
|
112
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.RouteTracker=RouteTracker;var _react=require("react");var _sharedUi=require("@buoy-gg/shared-ui");var _useRouteObserver=require("./useRouteObserver");var _useRouteObserverReactNavigation=require("./useRouteObserverReactNavigation");var _useNavigationStack=require("./useNavigationStack");var _navigationStackStore=require("./stores/navigationStackStore");var _routeEventStore=require("./stores/routeEventStore");var _jsxRuntime=require("react/jsx-runtime");function useBootCapture(){(0,_react.useEffect)(()=>{_routeEventStore.routeEventStore.hydrateBootCapture()},[])}function ExpoRouteTracker(){useBootCapture();(0,_useRouteObserver.useRouteObserver)();return null}function ReactNavigationRouteTracker(){useBootCapture();(0,_useRouteObserverReactNavigation.useRouteObserverReactNavigation)();return null}function NavigationStackCapture(){const{stack:e,navigateToIndex:t,navigateToRoute:r,popToIndex:a,goBack:o,popToTop:u}=(0,_useNavigationStack.useNavigationStack)();(0,_react.useEffect)(()=>{_navigationStackStore.navigationStackStore.setStack(e)},[e]);(0,_react.useEffect)(()=>{_navigationStackStore.navigationStackStore.setActions({navigateToIndex:t,navigateToRoute:r,popToIndex:a,goBack:o,popToTop:u});return()=>_navigationStackStore.navigationStackStore.setActions(null)});return null}function RouteTracker(){const e=(0,_sharedUi.isExpoRouterAvailable)();return(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[e?(0,_jsxRuntime.jsx)(ExpoRouteTracker,{}):(0,_jsxRuntime.jsx)(ReactNavigationRouteTracker,{}),(0,_jsxRuntime.jsx)(NavigationStackCapture,{})]})}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.RouteTracker=RouteTracker;var _react=require("react");var _browserRoutes=require("./browserRoutes");function RouteTracker(){(0,_react.useEffect)(_browserRoutes.startBrowserRouteCapture,[]);return null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getBrowserRoutePaths=getBrowserRoutePaths;exports.getBrowserRoutesUpdatedAt=getBrowserRoutesUpdatedAt;exports.registerBrowserRoutes=registerBrowserRoutes;const declared=new Map;let updatedAt=null;function registerBrowserRoutes(t){for(const e of t)declared.set(e,(declared.get(e)??0)+1);updatedAt=Date.now();let r=true;return()=>{if(!r)return;r=false;for(const e of t){const o=(declared.get(e)??1)-1;if(o)declared.set(e,o);else declared.delete(e)}updatedAt=Date.now()}}function getBrowserRoutePaths(){return[...declared.keys()]}function getBrowserRoutesUpdatedAt(){return updatedAt}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.startBrowserRouteCapture=startBrowserRouteCapture;var _utils=require("@buoy-gg/shared-ui/utils");var _RouteObserver=require("./RouteObserver");var _routeEventStore=require("./stores/routeEventStore");var _navigationStackStore=require("./stores/navigationStackStore");let leases=0;let stop;function startBrowserRouteCapture(){if(typeof window==="undefined")return()=>{};if(leases++===0){_routeEventStore.routeEventStore.hydrateBootCapture();let l;let i;let m="";const r=[];let t=-1;const c=e=>{if(e.href===m&&e.kind==="pop")return;if(e.kind==="replace"&&t>=0)r[t]=e.href;else if(e.kind==="pop"){const a=r.lastIndexOf(e.href,t-1);const o=r.indexOf(e.href,t+1);if(a>=0)t=a;else if(o>=0)t=o;else{r.splice(t+1);r.push(e.href);t++}}else{r.splice(t+1);r.push(e.href);t++}m=e.href;const s=(0,_utils.getBrowserRouteURL)(e.href);const u=Date.now();_RouteObserver.routeObserver.emit({pathname:s.pathname,params:Object.fromEntries(s.searchParams),segments:s.pathname.split("/").filter(Boolean),previousPathname:l,timestamp:u,timeSincePrevious:i===void 0?void 0:u-i});l=s.pathname;i=u;_navigationStackStore.navigationStackStore.setStack(r.map((a,o)=>{const p=(0,_utils.getBrowserRouteURL)(a);return{key:`browser:${o}`,name:p.pathname,pathname:p.pathname,params:Object.fromEntries(p.searchParams),index:o,isFocused:o===t,canPop:o>0}}))};const n=e=>{if(!Number.isInteger(e)||e<0||e>=r.length)throw new Error("That browser history entry is unavailable.");window.history.go(e-t)};_navigationStackStore.navigationStackStore.setActions({navigateToIndex:n,popToIndex:n,goBack:_utils.browserRouter.back,popToTop:()=>n(0),navigateToRoute:(e,s)=>{_utils.browserRouter.push({pathname:e,params:s});return true}});stop=(0,_utils.subscribeBrowserNavigation)(c);c({href:(0,_utils.getBrowserHref)(),kind:"initial"})}let f=false;return()=>{if(f)return;f=true;if(--leases===0){stop?.();stop=void 0;_navigationStackStore.navigationStackStore.setActions(null)}}}
|