@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.
Files changed (104) hide show
  1. package/README.md +11 -60
  2. package/lib/commonjs/RouteObserver.js +1 -54
  3. package/lib/commonjs/RouteParser.js +1 -314
  4. package/lib/commonjs/RouteTracker.js +1 -112
  5. package/lib/commonjs/RouteTracker.web.js +1 -0
  6. package/lib/commonjs/browserRouteRegistry.js +1 -0
  7. package/lib/commonjs/browserRoutes.js +1 -0
  8. package/lib/commonjs/components/NavigationStack.js +2 -572
  9. package/lib/commonjs/components/ReactNavigationRoutes.js +1 -836
  10. package/lib/commonjs/components/ReactNavigationRoutes.web.js +1 -0
  11. package/lib/commonjs/components/RouteEventDetail.js +1 -97
  12. package/lib/commonjs/components/RouteEventExpandedContent.js +1 -204
  13. package/lib/commonjs/components/RouteEventItemCompact.js +1 -191
  14. package/lib/commonjs/components/RouteEventsModalWithTabs.js +1 -717
  15. package/lib/commonjs/components/RouteEventsTimeline.js +1 -107
  16. package/lib/commonjs/components/RouteFilterViewV2.js +1 -42
  17. package/lib/commonjs/components/RoutesSitemap.js +2 -1035
  18. package/lib/commonjs/expoRouterStore.js +1 -121
  19. package/lib/commonjs/expoRouterStore.web.js +1 -0
  20. package/lib/commonjs/hooks/useRouteEvents.js +1 -57
  21. package/lib/commonjs/index.js +1 -161
  22. package/lib/commonjs/preset.js +1 -69
  23. package/lib/commonjs/stores/navigationStackStore.js +1 -45
  24. package/lib/commonjs/stores/routeEventStore.js +1 -136
  25. package/lib/commonjs/sync/routeEventsSyncAdapter.js +1 -275
  26. package/lib/commonjs/useNavigationStack.js +1 -408
  27. package/lib/commonjs/useNavigationStack.web.js +1 -0
  28. package/lib/commonjs/useRouteObserver.js +1 -73
  29. package/lib/commonjs/useRouteObserverReactNavigation.js +1 -109
  30. package/lib/commonjs/useRouteSitemap.js +1 -255
  31. package/lib/commonjs/utils/routeTemplate.js +1 -28
  32. package/lib/commonjs/utils/safeReactNavigation.js +1 -194
  33. package/lib/commonjs/utils/safeReactNavigation.web.js +1 -0
  34. package/lib/commonjs/utils/testIDs.js +1 -63
  35. package/lib/module/RouteObserver.js +1 -49
  36. package/lib/module/RouteParser.js +1 -309
  37. package/lib/module/RouteTracker.js +1 -109
  38. package/lib/module/RouteTracker.web.js +1 -0
  39. package/lib/module/browserRouteRegistry.js +1 -0
  40. package/lib/module/browserRoutes.js +1 -0
  41. package/lib/module/components/NavigationStack.js +2 -568
  42. package/lib/module/components/ReactNavigationRoutes.js +1 -832
  43. package/lib/module/components/ReactNavigationRoutes.web.js +1 -0
  44. package/lib/module/components/RouteEventDetail.js +1 -93
  45. package/lib/module/components/RouteEventExpandedContent.js +1 -200
  46. package/lib/module/components/RouteEventItemCompact.js +1 -187
  47. package/lib/module/components/RouteEventsModalWithTabs.js +1 -714
  48. package/lib/module/components/RouteEventsTimeline.js +1 -103
  49. package/lib/module/components/RouteFilterViewV2.js +1 -38
  50. package/lib/module/components/RoutesSitemap.js +2 -1033
  51. package/lib/module/expoRouterStore.js +1 -114
  52. package/lib/module/expoRouterStore.web.js +1 -0
  53. package/lib/module/hooks/useRouteEvents.js +1 -54
  54. package/lib/module/index.js +1 -69
  55. package/lib/module/preset.js +1 -66
  56. package/lib/module/stores/navigationStackStore.js +1 -41
  57. package/lib/module/stores/routeEventStore.js +1 -128
  58. package/lib/module/sync/routeEventsSyncAdapter.js +1 -273
  59. package/lib/module/useNavigationStack.js +1 -404
  60. package/lib/module/useNavigationStack.web.js +1 -0
  61. package/lib/module/useRouteObserver.js +1 -70
  62. package/lib/module/useRouteObserverReactNavigation.js +1 -106
  63. package/lib/module/useRouteSitemap.js +1 -250
  64. package/lib/module/utils/routeTemplate.js +1 -24
  65. package/lib/module/utils/safeReactNavigation.js +1 -185
  66. package/lib/module/utils/safeReactNavigation.web.js +1 -0
  67. package/lib/module/utils/testIDs.js +1 -55
  68. package/lib/typescript/RouteTracker.web.d.ts +2 -0
  69. package/lib/typescript/browserRouteRegistry.d.ts +4 -0
  70. package/lib/typescript/browserRoutes.d.ts +2 -0
  71. package/lib/typescript/components/ReactNavigationRoutes.web.d.ts +2 -0
  72. package/lib/typescript/expoRouterStore.web.d.ts +13 -0
  73. package/lib/typescript/index.d.ts +1 -0
  74. package/lib/typescript/useNavigationStack.web.d.ts +4 -0
  75. package/lib/typescript/utils/routeTemplate.d.ts +13 -0
  76. package/lib/typescript/utils/safeReactNavigation.web.d.ts +22 -0
  77. package/lib/web/index.mjs +6 -0
  78. package/package.json +33 -13
  79. package/lib/typescript/RouteObserver.d.ts.map +0 -1
  80. package/lib/typescript/RouteParser.d.ts.map +0 -1
  81. package/lib/typescript/RouteTracker.d.ts.map +0 -1
  82. package/lib/typescript/components/NavigationStack.d.ts.map +0 -1
  83. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +0 -1
  84. package/lib/typescript/components/RouteEventDetail.d.ts.map +0 -1
  85. package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +0 -1
  86. package/lib/typescript/components/RouteEventItemCompact.d.ts.map +0 -1
  87. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +0 -1
  88. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +0 -1
  89. package/lib/typescript/components/RouteFilterViewV2.d.ts.map +0 -1
  90. package/lib/typescript/components/RoutesSitemap.d.ts.map +0 -1
  91. package/lib/typescript/expoRouterStore.d.ts.map +0 -1
  92. package/lib/typescript/hooks/useRouteEvents.d.ts.map +0 -1
  93. package/lib/typescript/index.d.ts.map +0 -1
  94. package/lib/typescript/preset.d.ts.map +0 -1
  95. package/lib/typescript/stores/navigationStackStore.d.ts.map +0 -1
  96. package/lib/typescript/stores/routeEventStore.d.ts.map +0 -1
  97. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +0 -1
  98. package/lib/typescript/useNavigationStack.d.ts.map +0 -1
  99. package/lib/typescript/useRouteObserver.d.ts.map +0 -1
  100. package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +0 -1
  101. package/lib/typescript/useRouteSitemap.d.ts.map +0 -1
  102. package/lib/typescript/utils/routeTemplate.d.ts.map +0 -1
  103. package/lib/typescript/utils/safeReactNavigation.d.ts.map +0 -1
  104. package/lib/typescript/utils/testIDs.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,76 +1,27 @@
1
- # @buoy-gg/route-events
1
+ <div align="center">
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@buoy-gg/route-events?style=flat-square&labelColor=1c1c1c&color=10B981)](https://www.npmjs.com/package/@buoy-gg/route-events) [![npm downloads](https://img.shields.io/npm/dm/@buoy-gg/route-events?style=flat-square&labelColor=1c1c1c&color=10B981)](https://www.npmjs.com/package/@buoy-gg/route-events)
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
- Part of [Buoy](https://github.com/Buoy-gg/buoy) — devtools that live inside your React Native app. Install it and it auto-appears in the floating menu from [`@buoy-gg/core`](https://www.npmjs.com/package/@buoy-gg/core).
8
-
9
- ![Route Inspector demo](https://github.com/user-attachments/assets/90e55dc7-f8ab-423a-9770-84b9ff9c8446)
7
+ <a href="https://buoy.gg/buoy/latest/docs/tools/routes"><b>Routes guide</b></a> &nbsp;·&nbsp; <a href="https://buoy.gg">buoy.gg</a> &nbsp;·&nbsp; <a href="https://github.com/Buoy-gg/buoy">All tools</a>
10
8
 
11
- ## Install
9
+ [![npm version](https://img.shields.io/npm/v/@buoy-gg/route-events?style=flat-square&labelColor=10302a&color=2a9d78)](https://www.npmjs.com/package/@buoy-gg/route-events) [![npm downloads](https://img.shields.io/npm/dm/@buoy-gg/route-events?style=flat-square&labelColor=10302a&color=2a9d78&label=downloads%2Fmonth)](https://www.npmjs.com/package/@buoy-gg/route-events)
12
10
 
13
- ```bash
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
- ## Before you start
13
+ </div>
18
14
 
19
- Use a development build with `@buoy-gg/core` and a Free or Pro Buoy account key. From your app’s directory, sign in:
15
+ ## Install
20
16
 
21
17
  ```bash
22
- npx --package=@buoy-gg/core buoy login
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
- After account setup, open Routes and navigate between two known screens. Check their paths and parameters. Keep your existing navigator and providers; supported data differs between Expo Router and React Navigation. The event duration is time since the previous navigation, not screen render time.
21
+ Keep your existing navigator and providers. Expo Router and React Navigation are both supported, with different data available for each.
53
22
 
54
- ## What you get
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
- 📚 [Full docs](https://buoy.gg/buoy/latest/docs/tools/routes) · [All Buoy tools](https://github.com/Buoy-gg/buoy)
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)}}}