@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,948 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RoutesSitemap = RoutesSitemap;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _expoRouter = require("expo-router");
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
+ var _useRouteSitemap = require("../useRouteSitemap");
12
+ var _jsxRuntime = require("react/jsx-runtime");
13
+ /**
14
+ * RoutesSitemap - Visual sitemap of all app routes
15
+ *
16
+ * Displays parsed route information from Expo Router with:
17
+ * - Search/filter
18
+ * - Organized groups
19
+ * - Route details
20
+ * - Copy to clipboard
21
+ * - Navigation
22
+ */
23
+
24
+ // Lazy load the license hooks to avoid circular dependencies
25
+ let _useIsPro = null;
26
+ let _licenseLoadAttempted = false;
27
+ function loadLicenseModule() {
28
+ if (_licenseLoadAttempted) return;
29
+ _licenseLoadAttempted = true;
30
+ try {
31
+ const mod = require("@buoy-gg/license");
32
+ if (mod) {
33
+ _useIsPro = mod.useIsPro ?? null;
34
+ }
35
+ } catch {
36
+ // License package not available
37
+ }
38
+ }
39
+ function getUseIsPro() {
40
+ loadLicenseModule();
41
+ return _useIsPro ?? (() => false);
42
+ }
43
+
44
+ // ============================================================================
45
+ // Types
46
+ // ============================================================================
47
+
48
+ // ============================================================================
49
+ // Main Component
50
+ // ============================================================================
51
+
52
+ function RoutesSitemap({
53
+ style
54
+ }) {
55
+ const [searchQuery, setSearchQuery] = (0, _react.useState)("");
56
+ const [isSearching, setIsSearching] = (0, _react.useState)(false);
57
+ const [expandedGroups, setExpandedGroups] = (0, _react.useState)(new Set(["Root Routes", "Dynamic Routes"]));
58
+ const [isRefreshing, setIsRefreshing] = (0, _react.useState)(false);
59
+ const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
60
+
61
+ // Check Pro status internally
62
+ const useIsPro = getUseIsPro();
63
+ const isPro = useIsPro();
64
+ const router = (0, _expoRouter.useRouter)();
65
+ const {
66
+ groups,
67
+ stats,
68
+ isLoaded,
69
+ filteredRoutes,
70
+ routes,
71
+ refresh,
72
+ lastUpdatedAt,
73
+ source
74
+ } = (0, _useRouteSitemap.useRouteSitemap)({
75
+ searchQuery,
76
+ sortBy: "path"
77
+ });
78
+
79
+ // Prepare copy data - memoized so it only rebuilds when dependencies change
80
+ const copyAllData = (0, _react.useMemo)(() => {
81
+ return {
82
+ summary: {
83
+ total: stats.total,
84
+ static: stats.static,
85
+ dynamic: stats.dynamic,
86
+ layouts: stats.layouts,
87
+ groups: stats.groups,
88
+ timestamp: new Date().toISOString()
89
+ },
90
+ groups: groups.map(group => ({
91
+ title: group.title,
92
+ description: group.description,
93
+ count: group.routes.length,
94
+ routes: group.routes.map(route => ({
95
+ path: route.path,
96
+ name: route.name,
97
+ type: route.type,
98
+ params: route.params,
99
+ depth: route.depth,
100
+ hasChildren: route.children.length > 0,
101
+ childrenCount: route.children.length
102
+ }))
103
+ })),
104
+ allRoutes: routes.map(route => route.path)
105
+ };
106
+ }, [groups, stats, routes]);
107
+ const handleToggleGroup = (0, _react.useCallback)(groupTitle => {
108
+ setExpandedGroups(prev => {
109
+ const next = new Set(prev);
110
+ if (next.has(groupTitle)) {
111
+ next.delete(groupTitle);
112
+ } else {
113
+ next.add(groupTitle);
114
+ }
115
+ return next;
116
+ });
117
+ }, []);
118
+ const promptForParams = (0, _react.useCallback)((route, paramIndex = 0, collectedParams = {}) => {
119
+ const params = route.params;
120
+ if (paramIndex >= params.length) {
121
+ // All parameters collected, build path and navigate
122
+ let finalPath = route.path;
123
+
124
+ // Replace each parameter in the path
125
+ Object.entries(collectedParams).forEach(([param, value]) => {
126
+ if (route.type === "catch-all") {
127
+ // For catch-all routes, replace [...param] with the value
128
+ finalPath = finalPath.replace(`[...${param}]`, value);
129
+ } else {
130
+ // For regular dynamic routes, replace [param] with the value
131
+ finalPath = finalPath.replace(`[${param}]`, value);
132
+ }
133
+ });
134
+
135
+ // Navigate
136
+ try {
137
+ router.push(finalPath);
138
+ } catch (error) {
139
+ _reactNative.Alert.alert("Navigation Error", String(error));
140
+ }
141
+ return;
142
+ }
143
+ const currentParam = params[paramIndex];
144
+ const paramDisplay = route.type === "catch-all" ? `[...${currentParam}]` : `[${currentParam}]`;
145
+ _reactNative.Alert.prompt("Enter Parameter Value", `Enter value for ${paramDisplay}:\n\nRoute: ${route.path}`, [{
146
+ text: "Cancel",
147
+ style: "cancel"
148
+ }, {
149
+ text: paramIndex < params.length - 1 ? "Next" : "Navigate",
150
+ onPress: value => {
151
+ if (value && value.trim()) {
152
+ const newParams = {
153
+ ...collectedParams,
154
+ [currentParam]: value.trim()
155
+ };
156
+ promptForParams(route, paramIndex + 1, newParams);
157
+ } else {
158
+ _reactNative.Alert.alert("Invalid Value", "Please enter a value for the parameter");
159
+ }
160
+ }
161
+ }], "plain-text");
162
+ }, [router]);
163
+ const handleNavigate = (0, _react.useCallback)(route => {
164
+ // Gate behind Pro
165
+ if (!isPro) {
166
+ setShowUpgradeModal(true);
167
+ return;
168
+ }
169
+
170
+ // Don't navigate to layouts or groups
171
+ if (route.type === "layout" || route.type === "group") {
172
+ _reactNative.Alert.alert("Cannot Navigate", `${route.type === "layout" ? "Layouts" : "Route groups"} are not navigable routes`);
173
+ return;
174
+ }
175
+
176
+ // For dynamic routes, prompt for parameters
177
+ if (route.type === "dynamic" || route.type === "catch-all") {
178
+ if (route.params.length === 0) {
179
+ // No parameters despite being dynamic? Just navigate
180
+ try {
181
+ router.push(route.path);
182
+ } catch (error) {
183
+ _reactNative.Alert.alert("Navigation Error", String(error));
184
+ }
185
+ return;
186
+ }
187
+
188
+ // Start the parameter prompting flow
189
+ promptForParams(route);
190
+ return;
191
+ }
192
+
193
+ // Navigate to static route
194
+ try {
195
+ router.push(route.path);
196
+ } catch (error) {
197
+ _reactNative.Alert.alert("Navigation Error", String(error));
198
+ }
199
+ }, [router, promptForParams, isPro]);
200
+ const handleManualRefresh = (0, _react.useCallback)(() => {
201
+ if (isRefreshing) return;
202
+ setIsRefreshing(true);
203
+ refresh();
204
+ setTimeout(() => setIsRefreshing(false), 400);
205
+ }, [isRefreshing, refresh]);
206
+ const lastRefreshLabel = (0, _react.useMemo)(() => {
207
+ if (!lastUpdatedAt) return "Awaiting route data";
208
+ return `Updated ${(0, _sharedUi.formatRelativeTime)(lastUpdatedAt)}`;
209
+ }, [lastUpdatedAt]);
210
+ const sourceLabel = (0, _react.useMemo)(() => {
211
+ if (!source) return "Source: unknown";
212
+ return `Source: ${source}`;
213
+ }, [source]);
214
+
215
+ // When searching, show filtered routes, otherwise show groups
216
+ const displayGroups = (0, _react.useMemo)(() => {
217
+ if (searchQuery && filteredRoutes.length > 0) {
218
+ // Create a single "Search Results" group
219
+ return [{
220
+ title: "Search Results",
221
+ icon: "",
222
+ routes: filteredRoutes
223
+ }];
224
+ }
225
+ return groups;
226
+ }, [searchQuery, filteredRoutes, groups]);
227
+ if (!isLoaded) {
228
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
229
+ style: [styles.container, style],
230
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
231
+ style: styles.loadingContainer,
232
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
233
+ style: styles.loadingText,
234
+ children: "Loading routes..."
235
+ })
236
+ })
237
+ });
238
+ }
239
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
240
+ style: [styles.container, style],
241
+ children: [!isSearching ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
242
+ style: styles.header,
243
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
244
+ style: styles.actionsRow,
245
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
246
+ style: styles.actionWrapper,
247
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
248
+ value: copyAllData,
249
+ buttonStyle: styles.actionButtonHeader
250
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
251
+ style: styles.actionLabel,
252
+ children: "Copy"
253
+ })]
254
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
255
+ style: styles.actionWrapper,
256
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
257
+ style: [styles.iconButton, isRefreshing && styles.refreshButtonDisabled],
258
+ onPress: handleManualRefresh,
259
+ disabled: isRefreshing,
260
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
261
+ size: 16,
262
+ color: isRefreshing ? _sharedUi.buoyColors.textMuted : _sharedUi.buoyColors.textSecondary
263
+ })
264
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
265
+ style: styles.actionLabel,
266
+ children: "Refresh"
267
+ })]
268
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
269
+ style: styles.actionWrapper,
270
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
271
+ style: styles.iconButton,
272
+ onPress: () => setIsSearching(true),
273
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
274
+ size: 16,
275
+ color: _sharedUi.buoyColors.textSecondary
276
+ })
277
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
278
+ style: styles.actionLabel,
279
+ children: "Search"
280
+ })]
281
+ })]
282
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
283
+ style: styles.statsGrid,
284
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
285
+ value: stats.total,
286
+ label: "Total"
287
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
288
+ value: stats.static,
289
+ label: "Static"
290
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
291
+ value: stats.dynamic,
292
+ label: "Dynamic"
293
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
294
+ value: stats.catchAll,
295
+ label: "Catch-All"
296
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
297
+ value: stats.layouts,
298
+ label: "Layouts"
299
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
300
+ value: stats.groups,
301
+ label: "Groups"
302
+ })]
303
+ })]
304
+ }) :
305
+ /*#__PURE__*/
306
+ /* Search mode - full width search bar */
307
+ (0, _jsxRuntime.jsxs)(_reactNative.View, {
308
+ style: styles.searchContainer,
309
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
310
+ size: 16,
311
+ color: _sharedUi.buoyColors.textMuted
312
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
313
+ style: styles.searchInput,
314
+ placeholder: "Search routes...",
315
+ placeholderTextColor: _sharedUi.buoyColors.textMuted,
316
+ value: searchQuery,
317
+ onChangeText: setSearchQuery,
318
+ autoCapitalize: "none",
319
+ autoCorrect: false,
320
+ autoFocus: true
321
+ }), searchQuery.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
322
+ onPress: () => setSearchQuery(""),
323
+ style: styles.clearButton,
324
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
325
+ style: styles.clearButtonText,
326
+ children: "\u2715"
327
+ })
328
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
329
+ style: styles.closeSearchButton,
330
+ onPress: () => {
331
+ setIsSearching(false);
332
+ setSearchQuery("");
333
+ },
334
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
335
+ style: styles.closeSearchText,
336
+ children: "Done"
337
+ })
338
+ })]
339
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
340
+ style: styles.scrollView,
341
+ contentContainerStyle: styles.scrollContent,
342
+ children: [displayGroups.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
343
+ style: styles.emptyState,
344
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
345
+ style: styles.emptyText,
346
+ children: "No routes found"
347
+ })
348
+ }) : displayGroups.map(group => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteGroupView, {
349
+ group: group,
350
+ isExpanded: searchQuery.length > 0 || expandedGroups.has(group.title),
351
+ onToggleExpand: () => handleToggleGroup(group.title),
352
+ onNavigate: handleNavigate
353
+ }, group.title)), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
354
+ style: styles.metaFooter,
355
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
356
+ style: styles.metaText,
357
+ children: lastRefreshLabel
358
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
359
+ style: styles.sourceBadge,
360
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
361
+ style: styles.sourceText,
362
+ children: sourceLabel
363
+ })
364
+ })]
365
+ })]
366
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
367
+ visible: showUpgradeModal,
368
+ onClose: () => setShowUpgradeModal(false),
369
+ featureName: "Route Navigation"
370
+ })]
371
+ });
372
+ }
373
+
374
+ // ============================================================================
375
+ // Sub-components
376
+ // ============================================================================
377
+
378
+ function StatItem({
379
+ value,
380
+ label
381
+ }) {
382
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
383
+ style: styles.statItem,
384
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
385
+ style: styles.statValue,
386
+ children: value
387
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
388
+ style: styles.statLabel,
389
+ children: label
390
+ })]
391
+ });
392
+ }
393
+ function RouteGroupView({
394
+ group,
395
+ isExpanded,
396
+ onToggleExpand,
397
+ onNavigate
398
+ }) {
399
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
400
+ style: styles.groupContainer,
401
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
402
+ style: styles.groupHeader,
403
+ onPress: onToggleExpand,
404
+ activeOpacity: 0.7,
405
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
406
+ style: styles.groupHeaderLeft,
407
+ children: [isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
408
+ size: 14,
409
+ color: _sharedUi.buoyColors.textSecondary
410
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
411
+ size: 14,
412
+ color: _sharedUi.buoyColors.textSecondary
413
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
414
+ style: styles.groupTitle,
415
+ children: group.title
416
+ })]
417
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
418
+ style: styles.groupBadge,
419
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
420
+ style: styles.groupCount,
421
+ children: group.routes.length
422
+ })
423
+ })]
424
+ }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
425
+ style: styles.routesList,
426
+ children: [group.description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
427
+ style: styles.groupDescription,
428
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
429
+ style: styles.groupDescriptionText,
430
+ children: group.description
431
+ })
432
+ }), group.routes.map((route, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteItemView, {
433
+ route: route,
434
+ onNavigate: onNavigate
435
+ }, `${route.path}-${index}`))]
436
+ })]
437
+ });
438
+ }
439
+ function RouteItemView({
440
+ route,
441
+ depth = 0,
442
+ onNavigate
443
+ }) {
444
+ const [isExpanded, setIsExpanded] = (0, _react.useState)(false);
445
+ const hasChildren = route.children.length > 0;
446
+ const hasParams = route.params.length > 0;
447
+ const typeColor = getRouteTypeColor(route.type);
448
+ const canNavigate = route.type !== "layout" && route.type !== "group";
449
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
450
+ style: [styles.routeItem, {
451
+ marginLeft: depth * 12
452
+ }],
453
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
454
+ style: styles.routeCard,
455
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
456
+ style: styles.routeHeaderLeft,
457
+ onPress: () => setIsExpanded(!isExpanded),
458
+ activeOpacity: 0.7,
459
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
460
+ style: styles.expandIndicator,
461
+ children: isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
462
+ size: 14,
463
+ color: _sharedUi.buoyColors.textSecondary
464
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
465
+ size: 14,
466
+ color: _sharedUi.buoyColors.textSecondary
467
+ })
468
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
469
+ style: styles.routePath,
470
+ numberOfLines: 1,
471
+ children: route.path
472
+ })]
473
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
474
+ style: styles.routeHeaderActions,
475
+ children: [hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
476
+ style: styles.childCountBadge,
477
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
478
+ style: styles.childCountText,
479
+ children: route.children.length
480
+ })
481
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
482
+ style: [styles.typeTag, {
483
+ backgroundColor: `${typeColor}15`,
484
+ borderColor: `${typeColor}40`
485
+ }],
486
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
487
+ style: [styles.typeText, {
488
+ color: typeColor
489
+ }],
490
+ children: route.type.toUpperCase()
491
+ })
492
+ })]
493
+ })]
494
+ }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
495
+ style: styles.routeDetails,
496
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
497
+ style: styles.routeButtons,
498
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton, {
499
+ value: route.path,
500
+ buttonStyle: styles.actionButton
501
+ }), canNavigate && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
502
+ style: [styles.actionButton, styles.navigateButton],
503
+ onPress: () => onNavigate(route),
504
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
505
+ style: styles.navigateButtonText,
506
+ children: "Go"
507
+ })
508
+ })]
509
+ }), hasParams && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
510
+ style: styles.paramsContainer,
511
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
512
+ style: styles.paramsLabel,
513
+ children: "Parameters:"
514
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
515
+ style: styles.paramsRow,
516
+ children: route.params.map(param => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
517
+ style: styles.paramTag,
518
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
519
+ style: styles.paramText,
520
+ children: param
521
+ })
522
+ }, param))
523
+ })]
524
+ }), hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
525
+ style: styles.childrenContainer,
526
+ children: route.children.map((child, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteItemView, {
527
+ route: child,
528
+ depth: depth + 1,
529
+ onNavigate: onNavigate
530
+ }, `${child.path}-${index}`))
531
+ })]
532
+ })]
533
+ });
534
+ }
535
+
536
+ // ============================================================================
537
+ // Helpers
538
+ // ============================================================================
539
+
540
+ function getRouteTypeColor(type) {
541
+ switch (type) {
542
+ case "static":
543
+ return "#3B82F6";
544
+ // Blue
545
+ case "dynamic":
546
+ return "#F59E0B";
547
+ // Orange
548
+ case "catch-all":
549
+ return "#EC4899";
550
+ // Pink
551
+ case "index":
552
+ return "#10B981";
553
+ // Green
554
+ case "layout":
555
+ return "#8B5CF6";
556
+ // Purple
557
+ case "group":
558
+ return "#6366F1";
559
+ // Indigo
560
+ case "not-found":
561
+ return "#EF4444";
562
+ // Red
563
+ default:
564
+ return "#6B7280";
565
+ // Gray
566
+ }
567
+ }
568
+
569
+ // ============================================================================
570
+ // Styles
571
+ // ============================================================================
572
+
573
+ const styles = _reactNative.StyleSheet.create({
574
+ container: {
575
+ flex: 1,
576
+ backgroundColor: _sharedUi.buoyColors.base
577
+ },
578
+ loadingContainer: {
579
+ flex: 1,
580
+ justifyContent: "center",
581
+ alignItems: "center"
582
+ },
583
+ loadingText: {
584
+ color: _sharedUi.buoyColors.textSecondary,
585
+ fontSize: 14,
586
+ fontFamily: "monospace"
587
+ },
588
+ header: {
589
+ flexDirection: "column",
590
+ padding: 8,
591
+ borderBottomWidth: 1,
592
+ borderBottomColor: _sharedUi.buoyColors.border,
593
+ gap: 12
594
+ },
595
+ actionsRow: {
596
+ flexDirection: "row",
597
+ alignItems: "center",
598
+ justifyContent: "flex-end",
599
+ gap: 12,
600
+ paddingBottom: 4
601
+ },
602
+ statsGrid: {
603
+ flexDirection: "row",
604
+ flexWrap: "wrap",
605
+ gap: 8
606
+ },
607
+ statItem: {
608
+ flexGrow: 1,
609
+ flexShrink: 1,
610
+ flexBasis: "30%",
611
+ minWidth: 90,
612
+ backgroundColor: _sharedUi.buoyColors.card,
613
+ borderRadius: 8,
614
+ paddingVertical: 10,
615
+ paddingHorizontal: 12,
616
+ alignItems: "center"
617
+ },
618
+ statValue: {
619
+ fontSize: 18,
620
+ fontWeight: "800",
621
+ color: _sharedUi.buoyColors.text,
622
+ fontFamily: "monospace"
623
+ },
624
+ statLabel: {
625
+ fontSize: 10,
626
+ color: _sharedUi.buoyColors.textMuted,
627
+ marginTop: 4,
628
+ fontFamily: "monospace",
629
+ textTransform: "uppercase",
630
+ letterSpacing: 0.5
631
+ },
632
+ actionWrapper: {
633
+ alignItems: "center",
634
+ justifyContent: "center",
635
+ gap: 4,
636
+ minWidth: 48
637
+ },
638
+ actionButtonHeader: {
639
+ padding: 6,
640
+ borderRadius: 4,
641
+ backgroundColor: _sharedUi.buoyColors.input
642
+ },
643
+ iconButton: {
644
+ padding: 6,
645
+ borderRadius: 4,
646
+ alignItems: "center",
647
+ justifyContent: "center"
648
+ },
649
+ actionLabel: {
650
+ fontSize: 8,
651
+ color: _sharedUi.buoyColors.textMuted,
652
+ fontFamily: "monospace",
653
+ textTransform: "uppercase",
654
+ letterSpacing: 0.5
655
+ },
656
+ refreshButtonDisabled: {
657
+ opacity: 0.5
658
+ },
659
+ metaFooter: {
660
+ flexDirection: "row",
661
+ alignItems: "center",
662
+ justifyContent: "space-between",
663
+ gap: 8,
664
+ paddingHorizontal: 16,
665
+ paddingVertical: 16,
666
+ marginTop: 16,
667
+ borderTopWidth: 1,
668
+ borderTopColor: _sharedUi.buoyColors.border
669
+ },
670
+ metaText: {
671
+ fontSize: 12,
672
+ color: _sharedUi.buoyColors.textSecondary,
673
+ fontFamily: "monospace"
674
+ },
675
+ sourceBadge: {
676
+ paddingHorizontal: 8,
677
+ paddingVertical: 2,
678
+ borderRadius: 999,
679
+ borderWidth: 1,
680
+ borderColor: _sharedUi.buoyColors.border,
681
+ backgroundColor: _sharedUi.buoyColors.card
682
+ },
683
+ sourceText: {
684
+ fontSize: 11,
685
+ color: _sharedUi.buoyColors.textSecondary,
686
+ textTransform: "capitalize",
687
+ fontFamily: "monospace"
688
+ },
689
+ searchContainer: {
690
+ flexDirection: "row",
691
+ alignItems: "center",
692
+ padding: 8,
693
+ gap: 8,
694
+ borderBottomWidth: 1,
695
+ borderBottomColor: _sharedUi.buoyColors.border
696
+ },
697
+ searchInput: {
698
+ flex: 1,
699
+ color: _sharedUi.buoyColors.text,
700
+ fontSize: 14,
701
+ fontFamily: "monospace"
702
+ },
703
+ clearButton: {
704
+ padding: 4
705
+ },
706
+ clearButtonText: {
707
+ color: _sharedUi.buoyColors.textMuted,
708
+ fontSize: 18
709
+ },
710
+ closeSearchButton: {
711
+ paddingHorizontal: 8,
712
+ paddingVertical: 4
713
+ },
714
+ closeSearchText: {
715
+ color: _sharedUi.buoyColors.primary,
716
+ fontSize: 14,
717
+ fontWeight: "600",
718
+ fontFamily: "monospace"
719
+ },
720
+ scrollView: {
721
+ flex: 1
722
+ },
723
+ scrollContent: {
724
+ paddingVertical: 8
725
+ },
726
+ emptyState: {
727
+ padding: 32,
728
+ alignItems: "center"
729
+ },
730
+ emptyText: {
731
+ color: _sharedUi.buoyColors.textSecondary,
732
+ fontSize: 14,
733
+ fontFamily: "monospace"
734
+ },
735
+ groupContainer: {
736
+ marginVertical: 4
737
+ },
738
+ groupHeader: {
739
+ flexDirection: "row",
740
+ alignItems: "center",
741
+ justifyContent: "space-between",
742
+ padding: 12,
743
+ paddingHorizontal: 16,
744
+ backgroundColor: _sharedUi.buoyColors.card,
745
+ borderLeftWidth: 3,
746
+ borderLeftColor: _sharedUi.buoyColors.textSecondary
747
+ },
748
+ groupHeaderLeft: {
749
+ flexDirection: "row",
750
+ alignItems: "center",
751
+ gap: 8,
752
+ flex: 1
753
+ },
754
+ groupTitle: {
755
+ fontSize: 12,
756
+ fontWeight: "700",
757
+ color: _sharedUi.buoyColors.text,
758
+ fontFamily: "monospace",
759
+ textTransform: "uppercase",
760
+ letterSpacing: 0.8
761
+ },
762
+ groupBadge: {
763
+ backgroundColor: "#3B82F615",
764
+ borderColor: "#3B82F640",
765
+ borderWidth: 1,
766
+ borderRadius: 12,
767
+ paddingHorizontal: 8,
768
+ paddingVertical: 3,
769
+ minWidth: 24,
770
+ alignItems: "center",
771
+ justifyContent: "center"
772
+ },
773
+ groupCount: {
774
+ fontSize: 11,
775
+ fontWeight: "600",
776
+ color: "#3B82F6",
777
+ fontFamily: "monospace"
778
+ },
779
+ routesList: {
780
+ backgroundColor: _sharedUi.buoyColors.base
781
+ },
782
+ groupDescription: {
783
+ paddingHorizontal: 16,
784
+ paddingVertical: 8,
785
+ marginBottom: 8,
786
+ backgroundColor: _sharedUi.buoyColors.input,
787
+ borderLeftWidth: 3,
788
+ borderLeftColor: _sharedUi.buoyColors.textSecondary
789
+ },
790
+ groupDescriptionText: {
791
+ fontSize: 11,
792
+ color: _sharedUi.buoyColors.textSecondary,
793
+ fontFamily: "monospace",
794
+ lineHeight: 16
795
+ },
796
+ routeItem: {
797
+ marginBottom: 6
798
+ },
799
+ routeCard: {
800
+ flexDirection: "row",
801
+ alignItems: "center",
802
+ justifyContent: "space-between",
803
+ paddingVertical: 10,
804
+ paddingHorizontal: 12,
805
+ backgroundColor: _sharedUi.buoyColors.card,
806
+ borderRadius: 6,
807
+ borderWidth: 1,
808
+ borderColor: _sharedUi.buoyColors.border,
809
+ shadowColor: "#000",
810
+ shadowOffset: {
811
+ width: 0,
812
+ height: 1
813
+ },
814
+ shadowOpacity: 0.05,
815
+ shadowRadius: 2,
816
+ elevation: 1
817
+ },
818
+ expandIndicator: {
819
+ width: 20,
820
+ alignItems: "center"
821
+ },
822
+ routeHeaderLeft: {
823
+ flexDirection: "row",
824
+ alignItems: "center",
825
+ gap: 8,
826
+ flex: 1
827
+ },
828
+ routePath: {
829
+ fontSize: 13,
830
+ fontWeight: "600",
831
+ color: _sharedUi.buoyColors.text,
832
+ fontFamily: "monospace",
833
+ flex: 1
834
+ },
835
+ routeHeaderActions: {
836
+ flexDirection: "row",
837
+ alignItems: "center",
838
+ gap: 6
839
+ },
840
+ childCountBadge: {
841
+ backgroundColor: "#6B728015",
842
+ borderColor: "#6B728040",
843
+ borderWidth: 1,
844
+ borderRadius: 12,
845
+ paddingHorizontal: 7,
846
+ paddingVertical: 2,
847
+ minWidth: 22,
848
+ alignItems: "center",
849
+ justifyContent: "center"
850
+ },
851
+ childCountText: {
852
+ fontSize: 11,
853
+ fontWeight: "600",
854
+ color: "#6B7280",
855
+ fontFamily: "monospace"
856
+ },
857
+ typeTag: {
858
+ borderRadius: 4,
859
+ paddingHorizontal: 6,
860
+ paddingVertical: 2,
861
+ borderWidth: 1
862
+ },
863
+ typeText: {
864
+ fontSize: 10,
865
+ fontWeight: "600",
866
+ fontFamily: "monospace"
867
+ },
868
+ routeDetails: {
869
+ paddingHorizontal: 12,
870
+ paddingTop: 8,
871
+ paddingBottom: 12,
872
+ gap: 12,
873
+ borderTopWidth: 1,
874
+ borderTopColor: _sharedUi.buoyColors.border,
875
+ backgroundColor: _sharedUi.buoyColors.card,
876
+ borderRadius: 6,
877
+ borderWidth: 1,
878
+ borderColor: _sharedUi.buoyColors.border,
879
+ borderTopLeftRadius: 0,
880
+ borderTopRightRadius: 0,
881
+ marginTop: -6
882
+ },
883
+ paramsContainer: {
884
+ gap: 6
885
+ },
886
+ paramsLabel: {
887
+ fontSize: 10,
888
+ color: _sharedUi.buoyColors.textSecondary,
889
+ fontFamily: "monospace",
890
+ marginBottom: 4
891
+ },
892
+ paramsRow: {
893
+ flexDirection: "row",
894
+ flexWrap: "wrap",
895
+ gap: 6
896
+ },
897
+ paramTag: {
898
+ backgroundColor: "#F59E0B15",
899
+ borderColor: "#F59E0B40",
900
+ borderWidth: 1,
901
+ borderRadius: 4,
902
+ paddingHorizontal: 6,
903
+ paddingVertical: 2
904
+ },
905
+ paramText: {
906
+ fontSize: 10,
907
+ color: "#F59E0B",
908
+ fontFamily: "monospace",
909
+ fontWeight: "600"
910
+ },
911
+ routeButtons: {
912
+ flexDirection: "row",
913
+ gap: 6,
914
+ marginBottom: 12
915
+ },
916
+ actionButton: {
917
+ flexDirection: "row",
918
+ alignItems: "center",
919
+ gap: 4,
920
+ backgroundColor: _sharedUi.buoyColors.input,
921
+ borderRadius: 4,
922
+ paddingHorizontal: 12,
923
+ paddingVertical: 6,
924
+ borderWidth: 1,
925
+ borderColor: _sharedUi.buoyColors.border
926
+ },
927
+ actionButtonText: {
928
+ fontSize: 12,
929
+ color: _sharedUi.buoyColors.textSecondary,
930
+ fontFamily: "monospace",
931
+ fontWeight: "600"
932
+ },
933
+ navigateButton: {
934
+ backgroundColor: _sharedUi.buoyColors.primary + "15",
935
+ borderColor: _sharedUi.buoyColors.primary + "40"
936
+ },
937
+ navigateButtonText: {
938
+ fontSize: 12,
939
+ color: _sharedUi.buoyColors.primary,
940
+ fontFamily: "monospace",
941
+ fontWeight: "600"
942
+ },
943
+ childrenContainer: {
944
+ borderLeftWidth: 2,
945
+ borderLeftColor: _sharedUi.buoyColors.border,
946
+ marginLeft: 16
947
+ }
948
+ });