@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.
Files changed (79) hide show
  1. package/README.md +654 -0
  2. package/lib/commonjs/RouteObserver.js +54 -0
  3. package/lib/commonjs/RouteParser.js +310 -0
  4. package/lib/commonjs/RouteTracker.js +39 -0
  5. package/lib/commonjs/components/NavigationStack.js +584 -0
  6. package/lib/commonjs/components/RouteEventDetailContent.js +492 -0
  7. package/lib/commonjs/components/RouteEventExpandedContent.js +187 -0
  8. package/lib/commonjs/components/RouteEventItemCompact.js +175 -0
  9. package/lib/commonjs/components/RouteEventsModalWithTabs.js +560 -0
  10. package/lib/commonjs/components/RouteEventsTimeline.js +82 -0
  11. package/lib/commonjs/components/RouteFilterViewV2.js +42 -0
  12. package/lib/commonjs/components/RoutesSitemap.js +948 -0
  13. package/lib/commonjs/expoRouterStore.js +104 -0
  14. package/lib/commonjs/index.js +99 -0
  15. package/lib/commonjs/package.json +1 -0
  16. package/lib/commonjs/preset.js +83 -0
  17. package/lib/commonjs/useNavigationStack.js +241 -0
  18. package/lib/commonjs/useRouteObserver.js +73 -0
  19. package/lib/commonjs/useRouteSitemap.js +234 -0
  20. package/lib/commonjs/utils/safeExpoRouter.js +129 -0
  21. package/lib/commonjs/utils/safeReactNavigation.js +104 -0
  22. package/lib/module/RouteObserver.js +49 -0
  23. package/lib/module/RouteParser.js +305 -0
  24. package/lib/module/RouteTracker.js +35 -0
  25. package/lib/module/components/NavigationStack.js +580 -0
  26. package/lib/module/components/RouteEventDetailContent.js +487 -0
  27. package/lib/module/components/RouteEventExpandedContent.js +183 -0
  28. package/lib/module/components/RouteEventItemCompact.js +171 -0
  29. package/lib/module/components/RouteEventsModalWithTabs.js +557 -0
  30. package/lib/module/components/RouteEventsTimeline.js +78 -0
  31. package/lib/module/components/RouteFilterViewV2.js +38 -0
  32. package/lib/module/components/RoutesSitemap.js +944 -0
  33. package/lib/module/expoRouterStore.js +98 -0
  34. package/lib/module/index.js +23 -0
  35. package/lib/module/preset.js +79 -0
  36. package/lib/module/useNavigationStack.js +238 -0
  37. package/lib/module/useRouteObserver.js +70 -0
  38. package/lib/module/useRouteSitemap.js +229 -0
  39. package/lib/module/utils/safeExpoRouter.js +120 -0
  40. package/lib/module/utils/safeReactNavigation.js +98 -0
  41. package/lib/typescript/RouteObserver.d.ts +37 -0
  42. package/lib/typescript/RouteObserver.d.ts.map +1 -0
  43. package/lib/typescript/RouteParser.d.ts +129 -0
  44. package/lib/typescript/RouteParser.d.ts.map +1 -0
  45. package/lib/typescript/RouteTracker.d.ts +29 -0
  46. package/lib/typescript/RouteTracker.d.ts.map +1 -0
  47. package/lib/typescript/components/NavigationStack.d.ts +11 -0
  48. package/lib/typescript/components/NavigationStack.d.ts.map +1 -0
  49. package/lib/typescript/components/RouteEventDetailContent.d.ts +21 -0
  50. package/lib/typescript/components/RouteEventDetailContent.d.ts.map +1 -0
  51. package/lib/typescript/components/RouteEventExpandedContent.d.ts +16 -0
  52. package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +1 -0
  53. package/lib/typescript/components/RouteEventItemCompact.d.ts +15 -0
  54. package/lib/typescript/components/RouteEventItemCompact.d.ts.map +1 -0
  55. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts +15 -0
  56. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -0
  57. package/lib/typescript/components/RouteEventsTimeline.d.ts +17 -0
  58. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +1 -0
  59. package/lib/typescript/components/RouteFilterViewV2.d.ts +9 -0
  60. package/lib/typescript/components/RouteFilterViewV2.d.ts.map +1 -0
  61. package/lib/typescript/components/RoutesSitemap.d.ts +15 -0
  62. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -0
  63. package/lib/typescript/expoRouterStore.d.ts +28 -0
  64. package/lib/typescript/expoRouterStore.d.ts.map +1 -0
  65. package/lib/typescript/index.d.ts +18 -0
  66. package/lib/typescript/index.d.ts.map +1 -0
  67. package/lib/typescript/preset.d.ts +76 -0
  68. package/lib/typescript/preset.d.ts.map +1 -0
  69. package/lib/typescript/useNavigationStack.d.ts +48 -0
  70. package/lib/typescript/useNavigationStack.d.ts.map +1 -0
  71. package/lib/typescript/useRouteObserver.d.ts +27 -0
  72. package/lib/typescript/useRouteObserver.d.ts.map +1 -0
  73. package/lib/typescript/useRouteSitemap.d.ts +102 -0
  74. package/lib/typescript/useRouteSitemap.d.ts.map +1 -0
  75. package/lib/typescript/utils/safeExpoRouter.d.ts +13 -0
  76. package/lib/typescript/utils/safeExpoRouter.d.ts.map +1 -0
  77. package/lib/typescript/utils/safeReactNavigation.d.ts +10 -0
  78. package/lib/typescript/utils/safeReactNavigation.d.ts.map +1 -0
  79. package/package.json +72 -0
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * RouteParser - Extract and organize routes from Expo Router's RouteNode tree
5
+ *
6
+ * Based on research findings in:
7
+ * - docs/routing/expo/ROUTENODE_TYPE_DEFINITION.md
8
+ * - docs/routing/expo/ROUTES_SITEMAP_RESEARCH.md
9
+ */
10
+
11
+ // Type-only definition to avoid Metro resolution issues
12
+
13
+ // ============================================================================
14
+ // Types & Interfaces
15
+ // ============================================================================
16
+
17
+ /**
18
+ * Route type classification
19
+ */
20
+
21
+ // 404 fallback
22
+
23
+ /**
24
+ * Parsed route information
25
+ */
26
+
27
+ /**
28
+ * Grouped routes for display
29
+ */
30
+
31
+ /**
32
+ * Route statistics
33
+ */
34
+
35
+ // ============================================================================
36
+ // Route Parser Class
37
+ // ============================================================================
38
+
39
+ export class RouteParser {
40
+ /**
41
+ * Parse RouteNode tree and extract all routes
42
+ */
43
+ static parseRouteTree(rootNode) {
44
+ if (!rootNode) {
45
+ return [];
46
+ }
47
+ const routes = [];
48
+ this.traverseNode(rootNode, '', routes, 0);
49
+ return routes;
50
+ }
51
+
52
+ /**
53
+ * Recursively traverse RouteNode tree
54
+ */
55
+ static traverseNode(node, parentPath, routes, depth) {
56
+ const currentPath = this.buildPath(node, parentPath);
57
+ const routeType = this.detectRouteType(node);
58
+ const shouldInclude = this.shouldIncludeRoute(node);
59
+ const routeInfo = {
60
+ path: currentPath,
61
+ name: node.route,
62
+ type: routeType,
63
+ params: this.extractParams(node),
64
+ nodeType: node.type,
65
+ contextKey: node.contextKey,
66
+ isInternal: node.internal ?? false,
67
+ children: [],
68
+ depth
69
+ };
70
+ if (shouldInclude) {
71
+ routes.push(routeInfo);
72
+ }
73
+ for (const child of node.children) {
74
+ this.traverseNode(child, currentPath, shouldInclude ? routeInfo.children : routes, depth + 1);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Build full path for a route node
80
+ */
81
+ static buildPath(node, parentPath) {
82
+ if (node.type === 'layout' && node.route === '_layout') {
83
+ return parentPath;
84
+ }
85
+ if (node.route.startsWith('(') && node.route.endsWith(')')) {
86
+ return parentPath;
87
+ }
88
+ if (node.route === 'index') {
89
+ return parentPath || '/';
90
+ }
91
+ const segment = node.route;
92
+ if (!parentPath || parentPath === '/') {
93
+ return `/${segment}`;
94
+ }
95
+ return `${parentPath}/${segment}`;
96
+ }
97
+
98
+ /**
99
+ * Detect the route type classification
100
+ */
101
+ static detectRouteType(node) {
102
+ const routeName = node.route;
103
+ if (node.dynamic?.some(d => d.notFound)) {
104
+ return 'not-found';
105
+ }
106
+ if (node.dynamic?.some(d => d.deep)) {
107
+ return 'catch-all';
108
+ }
109
+ if (node.dynamic && node.dynamic.length > 0) {
110
+ return 'dynamic';
111
+ }
112
+ if (node.type === 'layout') {
113
+ return 'layout';
114
+ }
115
+ if (routeName.startsWith('(') && routeName.endsWith(')')) {
116
+ return 'group';
117
+ }
118
+ if (routeName === 'index') {
119
+ return 'index';
120
+ }
121
+ return 'static';
122
+ }
123
+
124
+ /**
125
+ * Extract dynamic parameter names from route
126
+ */
127
+ static extractParams(node) {
128
+ if (!node.dynamic || node.dynamic.length === 0) {
129
+ return [];
130
+ }
131
+ return node.dynamic.map(d => d.name);
132
+ }
133
+
134
+ /**
135
+ * Determine if route should be included in results
136
+ */
137
+ static shouldIncludeRoute(node) {
138
+ if (node.internal) return false;
139
+ if (node.generated) return false;
140
+ if (node.type === 'api') return false;
141
+ return true;
142
+ }
143
+
144
+ /**
145
+ * Organize routes into groups for display
146
+ */
147
+ static organizeRoutes(routes) {
148
+ const groups = [];
149
+ const rootRoutes = routes.filter(r => r.depth === 0 && r.type !== 'dynamic' && r.type !== 'layout');
150
+ const dynamicRoutes = this.flattenRoutes(routes).filter(r => r.type === 'dynamic' || r.type === 'catch-all');
151
+ const layoutRoutes = this.flattenRoutes(routes).filter(r => r.type === 'layout');
152
+ const groupedRoutes = this.flattenRoutes(routes).filter(r => r.type === 'group');
153
+ if (rootRoutes.length > 0) {
154
+ groups.push({
155
+ title: 'Root Routes',
156
+ icon: '',
157
+ routes: rootRoutes
158
+ });
159
+ }
160
+ if (dynamicRoutes.length > 0) {
161
+ groups.push({
162
+ title: 'Dynamic Routes',
163
+ icon: '',
164
+ description: 'Routes that accept parameters',
165
+ routes: dynamicRoutes
166
+ });
167
+ }
168
+ if (layoutRoutes.length > 0) {
169
+ groups.push({
170
+ title: 'Layouts',
171
+ icon: '',
172
+ description: 'Shared UI for nested screens',
173
+ routes: layoutRoutes
174
+ });
175
+ }
176
+ if (groupedRoutes.length > 0) {
177
+ groups.push({
178
+ title: 'Route Groups',
179
+ icon: '',
180
+ routes: groupedRoutes
181
+ });
182
+ }
183
+ return groups;
184
+ }
185
+
186
+ /**
187
+ * Flatten nested routes into a single array
188
+ */
189
+ static flattenRoutes(routes) {
190
+ const flattened = [];
191
+ function traverse(route) {
192
+ flattened.push(route);
193
+ route.children.forEach(traverse);
194
+ }
195
+ routes.forEach(traverse);
196
+ return flattened;
197
+ }
198
+
199
+ /**
200
+ * Get route statistics
201
+ */
202
+ static getRouteStats(routes) {
203
+ const flatRoutes = this.flattenRoutes(routes);
204
+ return {
205
+ total: flatRoutes.length,
206
+ static: flatRoutes.filter(r => r.type === 'static').length,
207
+ dynamic: flatRoutes.filter(r => r.type === 'dynamic').length,
208
+ catchAll: flatRoutes.filter(r => r.type === 'catch-all').length,
209
+ layouts: flatRoutes.filter(r => r.type === 'layout').length,
210
+ groups: flatRoutes.filter(r => r.type === 'group').length
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Search/filter routes by query
216
+ */
217
+ static filterRoutes(routes, query) {
218
+ if (!query) {
219
+ return routes;
220
+ }
221
+ const lowerQuery = query.toLowerCase();
222
+ const flatRoutes = this.flattenRoutes(routes);
223
+ return flatRoutes.filter(route => {
224
+ return route.path.toLowerCase().includes(lowerQuery) || route.name.toLowerCase().includes(lowerQuery) || route.type.toLowerCase().includes(lowerQuery) || route.params.some(p => p.toLowerCase().includes(lowerQuery));
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Build a visual tree string representation
230
+ */
231
+ static buildTreeString(routes, depth = 0) {
232
+ let output = '';
233
+ routes.forEach((route, index) => {
234
+ const isLast = index === routes.length - 1;
235
+ const prefix = this.buildTreePrefix(depth, isLast);
236
+ let routeLine = `${prefix}${route.path}`;
237
+ if (route.params.length > 0) {
238
+ routeLine += ` { ${route.params.join(', ')} }`;
239
+ }
240
+ output += routeLine + '\n';
241
+ if (route.children.length > 0) {
242
+ output += this.buildTreeString(route.children, depth + 1);
243
+ }
244
+ });
245
+ return output;
246
+ }
247
+ static buildTreePrefix(depth, isLast) {
248
+ if (depth === 0) {
249
+ return '';
250
+ }
251
+ const indent = ' '.repeat(depth - 1);
252
+ const branch = isLast ? '└─ ' : '├─ ';
253
+ return indent + branch;
254
+ }
255
+
256
+ /**
257
+ * Sort routes by various criteria
258
+ */
259
+ static sortRoutes(routes, sortBy = 'path') {
260
+ const sorted = [...routes];
261
+ sorted.sort((a, b) => {
262
+ switch (sortBy) {
263
+ case 'path':
264
+ return a.path.localeCompare(b.path);
265
+ case 'type':
266
+ return a.type.localeCompare(b.type);
267
+ case 'name':
268
+ return a.name.localeCompare(b.name);
269
+ default:
270
+ return 0;
271
+ }
272
+ });
273
+ return sorted;
274
+ }
275
+
276
+ /**
277
+ * Get route by path
278
+ */
279
+ static findRouteByPath(routes, path) {
280
+ const flatRoutes = this.flattenRoutes(routes);
281
+ return flatRoutes.find(r => r.path === path) || null;
282
+ }
283
+
284
+ /**
285
+ * Get all parent routes for a given route
286
+ */
287
+ static getParentRoutes(routes, targetPath) {
288
+ const parents = [];
289
+ const flatRoutes = this.flattenRoutes(routes);
290
+ const target = flatRoutes.find(r => r.path === targetPath);
291
+ if (!target) {
292
+ return parents;
293
+ }
294
+ const pathSegments = targetPath.split('/').filter(Boolean);
295
+ let currentPath = '';
296
+ for (let i = 0; i < pathSegments.length - 1; i++) {
297
+ currentPath += '/' + pathSegments[i];
298
+ const parent = flatRoutes.find(r => r.path === currentPath);
299
+ if (parent) {
300
+ parents.push(parent);
301
+ }
302
+ }
303
+ return parents;
304
+ }
305
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * RouteTracker - A component to place inside your navigation tree
5
+ *
6
+ * This component calls useRouteObserver() which uses expo-router hooks
7
+ * (usePathname, useSegments, etc.) to track navigation changes.
8
+ *
9
+ * IMPORTANT: This component MUST be placed inside your navigation tree
10
+ * (as a child of Stack, Tabs, or Slot) for route tracking to work.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * // In your _layout.tsx
15
+ * import { RouteTracker } from '@buoy-gg/route-events';
16
+ *
17
+ * export default function RootLayout() {
18
+ * return (
19
+ * <>
20
+ * <Stack>
21
+ * <Stack.Screen name="(tabs)" />
22
+ * </Stack>
23
+ * <RouteTracker />
24
+ * <FloatingDevTools ... />
25
+ * </>
26
+ * );
27
+ * }
28
+ * ```
29
+ */
30
+
31
+ import { useRouteObserver } from "./useRouteObserver";
32
+ export function RouteTracker() {
33
+ useRouteObserver();
34
+ return null;
35
+ }