@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,310 @@
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
+ const rootRoutes = routes.filter(r => r.depth === 0 && r.type !== 'dynamic' && r.type !== 'layout');
154
+ const dynamicRoutes = this.flattenRoutes(routes).filter(r => r.type === 'dynamic' || r.type === 'catch-all');
155
+ const layoutRoutes = this.flattenRoutes(routes).filter(r => r.type === 'layout');
156
+ const groupedRoutes = this.flattenRoutes(routes).filter(r => r.type === 'group');
157
+ if (rootRoutes.length > 0) {
158
+ groups.push({
159
+ title: 'Root Routes',
160
+ icon: '',
161
+ routes: rootRoutes
162
+ });
163
+ }
164
+ if (dynamicRoutes.length > 0) {
165
+ groups.push({
166
+ title: 'Dynamic Routes',
167
+ icon: '',
168
+ description: 'Routes that accept parameters',
169
+ routes: dynamicRoutes
170
+ });
171
+ }
172
+ if (layoutRoutes.length > 0) {
173
+ groups.push({
174
+ title: 'Layouts',
175
+ icon: '',
176
+ description: 'Shared UI for nested screens',
177
+ routes: layoutRoutes
178
+ });
179
+ }
180
+ if (groupedRoutes.length > 0) {
181
+ groups.push({
182
+ title: 'Route Groups',
183
+ icon: '',
184
+ routes: groupedRoutes
185
+ });
186
+ }
187
+ return groups;
188
+ }
189
+
190
+ /**
191
+ * Flatten nested routes into a single array
192
+ */
193
+ static flattenRoutes(routes) {
194
+ const flattened = [];
195
+ function traverse(route) {
196
+ flattened.push(route);
197
+ route.children.forEach(traverse);
198
+ }
199
+ routes.forEach(traverse);
200
+ return flattened;
201
+ }
202
+
203
+ /**
204
+ * Get route statistics
205
+ */
206
+ static getRouteStats(routes) {
207
+ const flatRoutes = this.flattenRoutes(routes);
208
+ return {
209
+ total: flatRoutes.length,
210
+ static: flatRoutes.filter(r => r.type === 'static').length,
211
+ dynamic: flatRoutes.filter(r => r.type === 'dynamic').length,
212
+ catchAll: flatRoutes.filter(r => r.type === 'catch-all').length,
213
+ layouts: flatRoutes.filter(r => r.type === 'layout').length,
214
+ groups: flatRoutes.filter(r => r.type === 'group').length
215
+ };
216
+ }
217
+
218
+ /**
219
+ * Search/filter routes by query
220
+ */
221
+ static filterRoutes(routes, query) {
222
+ if (!query) {
223
+ return routes;
224
+ }
225
+ const lowerQuery = query.toLowerCase();
226
+ const flatRoutes = this.flattenRoutes(routes);
227
+ return flatRoutes.filter(route => {
228
+ return route.path.toLowerCase().includes(lowerQuery) || route.name.toLowerCase().includes(lowerQuery) || route.type.toLowerCase().includes(lowerQuery) || route.params.some(p => p.toLowerCase().includes(lowerQuery));
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Build a visual tree string representation
234
+ */
235
+ static buildTreeString(routes, depth = 0) {
236
+ let output = '';
237
+ routes.forEach((route, index) => {
238
+ const isLast = index === routes.length - 1;
239
+ const prefix = this.buildTreePrefix(depth, isLast);
240
+ let routeLine = `${prefix}${route.path}`;
241
+ if (route.params.length > 0) {
242
+ routeLine += ` { ${route.params.join(', ')} }`;
243
+ }
244
+ output += routeLine + '\n';
245
+ if (route.children.length > 0) {
246
+ output += this.buildTreeString(route.children, depth + 1);
247
+ }
248
+ });
249
+ return output;
250
+ }
251
+ static buildTreePrefix(depth, isLast) {
252
+ if (depth === 0) {
253
+ return '';
254
+ }
255
+ const indent = ' '.repeat(depth - 1);
256
+ const branch = isLast ? '└─ ' : '├─ ';
257
+ return indent + branch;
258
+ }
259
+
260
+ /**
261
+ * Sort routes by various criteria
262
+ */
263
+ static sortRoutes(routes, sortBy = 'path') {
264
+ const sorted = [...routes];
265
+ sorted.sort((a, b) => {
266
+ switch (sortBy) {
267
+ case 'path':
268
+ return a.path.localeCompare(b.path);
269
+ case 'type':
270
+ return a.type.localeCompare(b.type);
271
+ case 'name':
272
+ return a.name.localeCompare(b.name);
273
+ default:
274
+ return 0;
275
+ }
276
+ });
277
+ return sorted;
278
+ }
279
+
280
+ /**
281
+ * Get route by path
282
+ */
283
+ static findRouteByPath(routes, path) {
284
+ const flatRoutes = this.flattenRoutes(routes);
285
+ return flatRoutes.find(r => r.path === path) || null;
286
+ }
287
+
288
+ /**
289
+ * Get all parent routes for a given route
290
+ */
291
+ static getParentRoutes(routes, targetPath) {
292
+ const parents = [];
293
+ const flatRoutes = this.flattenRoutes(routes);
294
+ const target = flatRoutes.find(r => r.path === targetPath);
295
+ if (!target) {
296
+ return parents;
297
+ }
298
+ const pathSegments = targetPath.split('/').filter(Boolean);
299
+ let currentPath = '';
300
+ for (let i = 0; i < pathSegments.length - 1; i++) {
301
+ currentPath += '/' + pathSegments[i];
302
+ const parent = flatRoutes.find(r => r.path === currentPath);
303
+ if (parent) {
304
+ parents.push(parent);
305
+ }
306
+ }
307
+ return parents;
308
+ }
309
+ }
310
+ exports.RouteParser = RouteParser;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RouteTracker = RouteTracker;
7
+ var _useRouteObserver = require("./useRouteObserver");
8
+ /**
9
+ * RouteTracker - A component to place inside your navigation tree
10
+ *
11
+ * This component calls useRouteObserver() which uses expo-router hooks
12
+ * (usePathname, useSegments, etc.) to track navigation changes.
13
+ *
14
+ * IMPORTANT: This component MUST be placed inside your navigation tree
15
+ * (as a child of Stack, Tabs, or Slot) for route tracking to work.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * // In your _layout.tsx
20
+ * import { RouteTracker } from '@buoy-gg/route-events';
21
+ *
22
+ * export default function RootLayout() {
23
+ * return (
24
+ * <>
25
+ * <Stack>
26
+ * <Stack.Screen name="(tabs)" />
27
+ * </Stack>
28
+ * <RouteTracker />
29
+ * <FloatingDevTools ... />
30
+ * </>
31
+ * );
32
+ * }
33
+ * ```
34
+ */
35
+
36
+ function RouteTracker() {
37
+ (0, _useRouteObserver.useRouteObserver)();
38
+ return null;
39
+ }