@buoy-gg/route-events 2.1.3 → 2.1.4-beta.0

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 (33) hide show
  1. package/lib/commonjs/RouteTracker.js +31 -5
  2. package/lib/commonjs/components/NavigationStack.js +24 -33
  3. package/lib/commonjs/components/ReactNavigationRoutes.js +829 -0
  4. package/lib/commonjs/components/RouteEventsModalWithTabs.js +83 -6
  5. package/lib/commonjs/components/RoutesSitemap.js +1 -2
  6. package/lib/commonjs/index.js +7 -0
  7. package/lib/commonjs/useNavigationStack.js +201 -104
  8. package/lib/commonjs/useRouteObserverReactNavigation.js +109 -0
  9. package/lib/module/RouteTracker.js +31 -5
  10. package/lib/module/components/NavigationStack.js +24 -33
  11. package/lib/module/components/ReactNavigationRoutes.js +825 -0
  12. package/lib/module/components/RouteEventsModalWithTabs.js +85 -7
  13. package/lib/module/components/RoutesSitemap.js +2 -2
  14. package/lib/module/index.js +1 -0
  15. package/lib/module/useNavigationStack.js +203 -106
  16. package/lib/module/useRouteObserverReactNavigation.js +106 -0
  17. package/lib/typescript/RouteTracker.d.ts +18 -5
  18. package/lib/typescript/RouteTracker.d.ts.map +1 -1
  19. package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
  20. package/lib/typescript/components/ReactNavigationRoutes.d.ts +14 -0
  21. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +1 -0
  22. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
  23. package/lib/typescript/index.d.ts +1 -0
  24. package/lib/typescript/index.d.ts.map +1 -1
  25. package/lib/typescript/useNavigationStack.d.ts +9 -3
  26. package/lib/typescript/useNavigationStack.d.ts.map +1 -1
  27. package/lib/typescript/useRouteObserverReactNavigation.d.ts +19 -0
  28. package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +1 -0
  29. package/package.json +6 -6
  30. package/lib/commonjs/utils/safeExpoRouter.js +0 -129
  31. package/lib/module/utils/safeExpoRouter.js +0 -120
  32. package/lib/typescript/utils/safeExpoRouter.d.ts +0 -13
  33. package/lib/typescript/utils/safeExpoRouter.d.ts.map +0 -1
@@ -0,0 +1,829 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ReactNavigationRoutes = ReactNavigationRoutes;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _native = require("@react-navigation/native");
10
+ var _core = require("@react-navigation/core");
11
+ var _sharedUi = require("@buoy-gg/shared-ui");
12
+ var _license = require("@buoy-gg/license");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ /**
15
+ * ReactNavigationRoutes - Visual sitemap of all screens in a React Navigation app
16
+ *
17
+ * Discovers all registered screens by walking the navigation state tree.
18
+ * Each navigator's state includes a `routeNames` array listing ALL screens
19
+ * registered with it — even ones not currently mounted in the stack.
20
+ *
21
+ * Provides navigation to any discovered screen using nested screen/params actions.
22
+ */
23
+
24
+ // ============================================================================
25
+ // Types
26
+ // ============================================================================
27
+
28
+ // ============================================================================
29
+ // State Tree Walking
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Walk the navigation state tree and collect all navigators with their registered screens.
34
+ */
35
+ function collectNavigatorGroups(state, parentPath = "Root") {
36
+ if (!state) return [];
37
+ const groups = [];
38
+
39
+ // routeNames lists ALL screens registered with this navigator
40
+ const routeNames = state.routeNames || [];
41
+ const mountedRouteNames = new Set((state.routes || []).map(r => r.name));
42
+ const focusedIndex = state.index ?? 0;
43
+ const focusedRouteName = state.routes?.[focusedIndex]?.name;
44
+ if (routeNames.length > 0) {
45
+ const screens = routeNames.map(name => ({
46
+ name,
47
+ isMounted: mountedRouteNames.has(name),
48
+ isFocused: name === focusedRouteName
49
+ }));
50
+ groups.push({
51
+ name: parentPath === "Root" ? "Root" : parentPath.split(" > ").pop(),
52
+ path: parentPath,
53
+ screens,
54
+ totalScreens: screens.length
55
+ });
56
+ }
57
+
58
+ // Recurse into mounted routes that have nested navigator state
59
+ for (const route of state.routes || []) {
60
+ if (route.state) {
61
+ const childPath = `${parentPath} > ${route.name}`;
62
+ const nestedGroups = collectNavigatorGroups(route.state, childPath);
63
+ groups.push(...nestedGroups);
64
+ }
65
+ }
66
+ return groups;
67
+ }
68
+
69
+ /**
70
+ * Find the path from root navigation state to a screen by name.
71
+ */
72
+ function findScreenPath(state, screenName) {
73
+ if (!state || !state.routes) return null;
74
+
75
+ // Check routeNames (all registered screens) not just mounted routes
76
+ const routeNames = state.routeNames || [];
77
+ if (routeNames.includes(screenName)) {
78
+ return [screenName];
79
+ }
80
+ for (const route of state.routes) {
81
+ if (route.state) {
82
+ const nested = findScreenPath(route.state, screenName);
83
+ if (nested) {
84
+ return [route.name, ...nested];
85
+ }
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Build a nested CommonActions.navigate() from a path of screen names.
93
+ */
94
+ function buildNestedNavigateAction(path) {
95
+ if (path.length === 0) return null;
96
+ if (path.length === 1) {
97
+ return _native.CommonActions.navigate({
98
+ name: path[0]
99
+ });
100
+ }
101
+ let params = {
102
+ screen: path[path.length - 1]
103
+ };
104
+ for (let i = path.length - 2; i >= 1; i--) {
105
+ params = {
106
+ screen: path[i],
107
+ params
108
+ };
109
+ }
110
+ return _native.CommonActions.navigate({
111
+ name: path[0],
112
+ params
113
+ });
114
+ }
115
+
116
+ // ============================================================================
117
+ // Main Component
118
+ // ============================================================================
119
+
120
+ function ReactNavigationRoutes({
121
+ style
122
+ }) {
123
+ const containerRef = (0, _react.useContext)(_core.NavigationContainerRefContext);
124
+ const [navigationState, setNavigationState] = (0, _react.useState)(() => containerRef?.getRootState?.() ?? null);
125
+ const [searchQuery, setSearchQuery] = (0, _react.useState)("");
126
+ const [isSearching, setIsSearching] = (0, _react.useState)(false);
127
+ const [expandedGroup, setExpandedGroup] = (0, _react.useState)(null);
128
+ const [isRefreshing, setIsRefreshing] = (0, _react.useState)(false);
129
+ const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
130
+ const isPro = (0, _license.useIsPro)();
131
+ (0, _react.useEffect)(() => {
132
+ if (showUpgradeModal && isPro) {
133
+ setShowUpgradeModal(false);
134
+ }
135
+ }, [showUpgradeModal, isPro]);
136
+
137
+ // Subscribe to navigation state changes
138
+ (0, _react.useEffect)(() => {
139
+ if (!containerRef) return;
140
+ const initialState = containerRef.getRootState?.();
141
+ if (initialState) {
142
+ setNavigationState(initialState);
143
+ }
144
+ const unsubscribe = containerRef.addListener?.("state", () => {
145
+ const state = containerRef.getRootState?.();
146
+ if (state) {
147
+ setNavigationState(state);
148
+ }
149
+ });
150
+ return () => {
151
+ unsubscribe?.();
152
+ };
153
+ }, [containerRef]);
154
+
155
+ // Build navigator groups from state
156
+ const groups = (0, _react.useMemo)(() => collectNavigatorGroups(navigationState), [navigationState]);
157
+
158
+ // Auto-expand first group on first load
159
+ (0, _react.useEffect)(() => {
160
+ if (groups.length > 0 && expandedGroup === null) {
161
+ setExpandedGroup(groups[0].path);
162
+ }
163
+ }, [groups.length]);
164
+
165
+ // Flatten all screens for stats and search
166
+ const allScreens = (0, _react.useMemo)(() => {
167
+ const screens = [];
168
+ for (const group of groups) {
169
+ for (const screen of group.screens) {
170
+ screens.push({
171
+ name: screen.name,
172
+ navigatorPath: group.path,
173
+ isMounted: screen.isMounted,
174
+ isFocused: screen.isFocused
175
+ });
176
+ }
177
+ }
178
+ return screens;
179
+ }, [groups]);
180
+
181
+ // Filter groups by search
182
+ const filteredGroups = (0, _react.useMemo)(() => {
183
+ if (!searchQuery.trim()) return groups;
184
+ const query = searchQuery.toLowerCase();
185
+ return groups.map(group => ({
186
+ ...group,
187
+ screens: group.screens.filter(s => s.name.toLowerCase().includes(query))
188
+ })).filter(group => group.screens.length > 0);
189
+ }, [groups, searchQuery]);
190
+
191
+ // Stats
192
+ const stats = (0, _react.useMemo)(() => {
193
+ const total = allScreens.length;
194
+ const mounted = allScreens.filter(s => s.isMounted).length;
195
+ const available = total - mounted;
196
+ const navigators = groups.length;
197
+ return {
198
+ total,
199
+ mounted,
200
+ available,
201
+ navigators
202
+ };
203
+ }, [allScreens, groups]);
204
+
205
+ // Copy data
206
+ const copyAllData = (0, _react.useMemo)(() => {
207
+ return {
208
+ summary: {
209
+ totalScreens: stats.total,
210
+ mounted: stats.mounted,
211
+ available: stats.available,
212
+ navigators: stats.navigators,
213
+ timestamp: new Date().toISOString()
214
+ },
215
+ navigators: groups.map(group => ({
216
+ path: group.path,
217
+ screens: group.screens.map(s => ({
218
+ name: s.name,
219
+ isMounted: s.isMounted,
220
+ isFocused: s.isFocused
221
+ }))
222
+ }))
223
+ };
224
+ }, [groups, stats]);
225
+ const handleToggleGroup = (0, _react.useCallback)(groupPath => {
226
+ setExpandedGroup(prev => prev === groupPath ? null : groupPath);
227
+ }, []);
228
+ const handleNavigate = (0, _react.useCallback)(screenName => {
229
+ if (!isPro) {
230
+ setShowUpgradeModal(true);
231
+ return;
232
+ }
233
+ if (!containerRef || !navigationState) return;
234
+ try {
235
+ const path = findScreenPath(navigationState, screenName);
236
+ if (path) {
237
+ const action = buildNestedNavigateAction(path);
238
+ if (action) {
239
+ containerRef.dispatch(action);
240
+ return;
241
+ }
242
+ }
243
+ // Fallback
244
+ containerRef.dispatch(_native.CommonActions.navigate({
245
+ name: screenName
246
+ }));
247
+ } catch (error) {
248
+ _reactNative.Alert.alert("Navigation Error", String(error));
249
+ }
250
+ }, [containerRef, navigationState, isPro]);
251
+ const handleRefresh = (0, _react.useCallback)(() => {
252
+ if (isRefreshing || !containerRef) return;
253
+ setIsRefreshing(true);
254
+ const state = containerRef.getRootState?.();
255
+ if (state) {
256
+ setNavigationState(state);
257
+ }
258
+ setTimeout(() => setIsRefreshing(false), 400);
259
+ }, [isRefreshing, containerRef]);
260
+ if (!navigationState) {
261
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
262
+ style: [styles.container, style],
263
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
264
+ style: styles.emptyState,
265
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
266
+ style: styles.emptyText,
267
+ children: "No navigation state available"
268
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
269
+ style: styles.emptySubtext,
270
+ children: "Ensure you are inside a NavigationContainer"
271
+ })]
272
+ })
273
+ });
274
+ }
275
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
276
+ style: [styles.container, style],
277
+ children: [!isSearching ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
278
+ style: styles.header,
279
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
280
+ style: styles.actionsRow,
281
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
282
+ style: styles.actionWrapper,
283
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
284
+ value: copyAllData,
285
+ buttonStyle: styles.actionButtonHeader
286
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
287
+ style: styles.actionLabel,
288
+ children: "Copy"
289
+ })]
290
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
291
+ style: styles.actionWrapper,
292
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
293
+ style: [styles.iconButton, isRefreshing && styles.refreshButtonDisabled],
294
+ onPress: handleRefresh,
295
+ disabled: isRefreshing,
296
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
297
+ size: 16,
298
+ color: isRefreshing ? _sharedUi.buoyColors.textMuted : _sharedUi.buoyColors.textSecondary
299
+ })
300
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
301
+ style: styles.actionLabel,
302
+ children: "Refresh"
303
+ })]
304
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
305
+ style: styles.actionWrapper,
306
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
307
+ style: styles.iconButton,
308
+ onPress: () => setIsSearching(true),
309
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
310
+ size: 16,
311
+ color: _sharedUi.buoyColors.textSecondary
312
+ })
313
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
314
+ style: styles.actionLabel,
315
+ children: "Search"
316
+ })]
317
+ })]
318
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
319
+ style: styles.statsGrid,
320
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
321
+ value: stats.total,
322
+ label: "Screens"
323
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
324
+ value: stats.mounted,
325
+ label: "Mounted"
326
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
327
+ value: stats.available,
328
+ label: "Available"
329
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(StatItem, {
330
+ value: stats.navigators,
331
+ label: "Navigators"
332
+ })]
333
+ })]
334
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
335
+ style: styles.searchContainer,
336
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
337
+ size: 16,
338
+ color: _sharedUi.buoyColors.textMuted
339
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
340
+ style: styles.searchInput,
341
+ placeholder: "Search screens...",
342
+ placeholderTextColor: _sharedUi.buoyColors.textMuted,
343
+ value: searchQuery,
344
+ onChangeText: setSearchQuery,
345
+ autoCapitalize: "none",
346
+ autoCorrect: false,
347
+ autoFocus: true
348
+ }), searchQuery.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
349
+ onPress: () => setSearchQuery(""),
350
+ style: styles.clearButton,
351
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
352
+ style: styles.clearButtonText,
353
+ children: "\u2715"
354
+ })
355
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
356
+ style: styles.closeSearchButton,
357
+ onPress: () => {
358
+ setIsSearching(false);
359
+ setSearchQuery("");
360
+ },
361
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
362
+ style: styles.closeSearchText,
363
+ children: "Done"
364
+ })
365
+ })]
366
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
367
+ style: styles.scrollView,
368
+ contentContainerStyle: styles.scrollContent,
369
+ children: [filteredGroups.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
370
+ style: styles.emptyState,
371
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
372
+ style: styles.emptyText,
373
+ children: searchQuery ? "No matching screens" : "No screens found"
374
+ })
375
+ }) : filteredGroups.map(group => /*#__PURE__*/(0, _jsxRuntime.jsx)(NavigatorGroupView, {
376
+ group: group,
377
+ isExpanded: searchQuery.length > 0 || expandedGroup === group.path,
378
+ onToggleExpand: () => handleToggleGroup(group.path),
379
+ onNavigate: handleNavigate
380
+ }, group.path)), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
381
+ style: styles.metaFooter,
382
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
383
+ style: styles.metaText,
384
+ children: "Source: navigation state"
385
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
386
+ style: styles.sourceBadge,
387
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
388
+ style: styles.sourceText,
389
+ children: "React Navigation"
390
+ })
391
+ })]
392
+ })]
393
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
394
+ visible: showUpgradeModal,
395
+ onClose: () => setShowUpgradeModal(false),
396
+ featureName: "Route Navigation"
397
+ })]
398
+ });
399
+ }
400
+
401
+ // ============================================================================
402
+ // Sub-components
403
+ // ============================================================================
404
+
405
+ function StatItem({
406
+ value,
407
+ label
408
+ }) {
409
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
410
+ style: styles.statItem,
411
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
412
+ style: styles.statValue,
413
+ children: value
414
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
415
+ style: styles.statLabel,
416
+ children: label
417
+ })]
418
+ });
419
+ }
420
+ function NavigatorGroupView({
421
+ group,
422
+ isExpanded,
423
+ onToggleExpand,
424
+ onNavigate
425
+ }) {
426
+ const [expandedScreen, setExpandedScreen] = (0, _react.useState)(null);
427
+ const handleToggleScreen = (0, _react.useCallback)(screenName => {
428
+ setExpandedScreen(prev => prev === screenName ? null : screenName);
429
+ }, []);
430
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
431
+ style: styles.groupContainer,
432
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
433
+ style: styles.groupHeader,
434
+ onPress: onToggleExpand,
435
+ activeOpacity: 0.7,
436
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
437
+ style: styles.groupHeaderLeft,
438
+ children: [isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
439
+ size: 14,
440
+ color: _sharedUi.buoyColors.textSecondary
441
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
442
+ size: 14,
443
+ color: _sharedUi.buoyColors.textSecondary
444
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
445
+ style: styles.groupTitle,
446
+ numberOfLines: 1,
447
+ children: group.name
448
+ })]
449
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
450
+ style: styles.groupBadge,
451
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
452
+ style: styles.groupCount,
453
+ children: group.screens.length
454
+ })
455
+ })]
456
+ }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
457
+ style: styles.screensList,
458
+ children: [group.path !== "Root" && group.path.includes(" > ") && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
459
+ style: styles.groupDescription,
460
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
461
+ style: styles.groupDescriptionText,
462
+ children: group.path
463
+ })
464
+ }), group.screens.map(screen => /*#__PURE__*/(0, _jsxRuntime.jsx)(ScreenItemView, {
465
+ screen: screen,
466
+ isExpanded: expandedScreen === screen.name,
467
+ onToggleExpand: () => handleToggleScreen(screen.name),
468
+ onNavigate: onNavigate
469
+ }, screen.name))]
470
+ })]
471
+ });
472
+ }
473
+ function ScreenItemView({
474
+ screen,
475
+ isExpanded,
476
+ onToggleExpand,
477
+ onNavigate
478
+ }) {
479
+ const statusColor = screen.isFocused ? _sharedUi.buoyColors.success : screen.isMounted ? "#3B82F6" : _sharedUi.buoyColors.textMuted;
480
+ const statusLabel = screen.isFocused ? "FOCUSED" : screen.isMounted ? "MOUNTED" : "AVAILABLE";
481
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
482
+ style: styles.screenItem,
483
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
484
+ style: styles.screenCard,
485
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
486
+ style: styles.screenHeaderLeft,
487
+ onPress: onToggleExpand,
488
+ activeOpacity: 0.7,
489
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
490
+ style: styles.expandIndicator,
491
+ children: isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
492
+ size: 14,
493
+ color: _sharedUi.buoyColors.textSecondary
494
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
495
+ size: 14,
496
+ color: _sharedUi.buoyColors.textSecondary
497
+ })
498
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
499
+ style: styles.screenName,
500
+ numberOfLines: 1,
501
+ children: screen.name
502
+ })]
503
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
504
+ style: styles.screenHeaderActions,
505
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton, {
506
+ value: screen.name,
507
+ buttonStyle: styles.headerActionButton
508
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
509
+ style: [styles.headerActionButton, styles.navigateButton],
510
+ onPress: () => onNavigate(screen.name),
511
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
512
+ style: styles.navigateButtonText,
513
+ children: "Go"
514
+ })
515
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
516
+ style: [styles.statusBadge, {
517
+ backgroundColor: `${statusColor}15`,
518
+ borderColor: `${statusColor}40`
519
+ }],
520
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
521
+ style: [styles.statusText, {
522
+ color: statusColor
523
+ }],
524
+ children: statusLabel
525
+ })
526
+ })]
527
+ })]
528
+ })
529
+ });
530
+ }
531
+
532
+ // ============================================================================
533
+ // Styles
534
+ // ============================================================================
535
+
536
+ const styles = _reactNative.StyleSheet.create({
537
+ container: {
538
+ flex: 1,
539
+ backgroundColor: _sharedUi.buoyColors.base
540
+ },
541
+ emptyState: {
542
+ padding: 32,
543
+ alignItems: "center"
544
+ },
545
+ emptyText: {
546
+ color: _sharedUi.buoyColors.textSecondary,
547
+ fontSize: 14,
548
+ fontFamily: "monospace"
549
+ },
550
+ emptySubtext: {
551
+ color: _sharedUi.buoyColors.textMuted,
552
+ fontSize: 12,
553
+ fontFamily: "monospace",
554
+ marginTop: 8
555
+ },
556
+ header: {
557
+ flexDirection: "column",
558
+ padding: 8,
559
+ borderBottomWidth: 1,
560
+ borderBottomColor: _sharedUi.buoyColors.border,
561
+ gap: 12
562
+ },
563
+ actionsRow: {
564
+ flexDirection: "row",
565
+ alignItems: "center",
566
+ justifyContent: "flex-end",
567
+ gap: 12,
568
+ paddingBottom: 4
569
+ },
570
+ actionWrapper: {
571
+ alignItems: "center",
572
+ justifyContent: "center",
573
+ gap: 4,
574
+ minWidth: 48
575
+ },
576
+ actionButtonHeader: {
577
+ padding: 6,
578
+ borderRadius: 4,
579
+ backgroundColor: _sharedUi.buoyColors.input
580
+ },
581
+ iconButton: {
582
+ padding: 6,
583
+ borderRadius: 4,
584
+ alignItems: "center",
585
+ justifyContent: "center"
586
+ },
587
+ actionLabel: {
588
+ fontSize: 8,
589
+ color: _sharedUi.buoyColors.textMuted,
590
+ fontFamily: "monospace",
591
+ textTransform: "uppercase",
592
+ letterSpacing: 0.5
593
+ },
594
+ refreshButtonDisabled: {
595
+ opacity: 0.5
596
+ },
597
+ statsGrid: {
598
+ flexDirection: "row",
599
+ flexWrap: "wrap",
600
+ gap: 8
601
+ },
602
+ statItem: {
603
+ flexGrow: 1,
604
+ flexShrink: 1,
605
+ flexBasis: "20%",
606
+ minWidth: 70,
607
+ backgroundColor: _sharedUi.buoyColors.card,
608
+ borderRadius: 8,
609
+ paddingVertical: 10,
610
+ paddingHorizontal: 12,
611
+ alignItems: "center"
612
+ },
613
+ statValue: {
614
+ fontSize: 18,
615
+ fontWeight: "800",
616
+ color: _sharedUi.buoyColors.text,
617
+ fontFamily: "monospace"
618
+ },
619
+ statLabel: {
620
+ fontSize: 10,
621
+ color: _sharedUi.buoyColors.textMuted,
622
+ marginTop: 4,
623
+ fontFamily: "monospace",
624
+ textTransform: "uppercase",
625
+ letterSpacing: 0.5
626
+ },
627
+ searchContainer: {
628
+ flexDirection: "row",
629
+ alignItems: "center",
630
+ padding: 8,
631
+ gap: 8,
632
+ borderBottomWidth: 1,
633
+ borderBottomColor: _sharedUi.buoyColors.border
634
+ },
635
+ searchInput: {
636
+ flex: 1,
637
+ color: _sharedUi.buoyColors.text,
638
+ fontSize: 14,
639
+ fontFamily: "monospace"
640
+ },
641
+ clearButton: {
642
+ padding: 4
643
+ },
644
+ clearButtonText: {
645
+ color: _sharedUi.buoyColors.textMuted,
646
+ fontSize: 18
647
+ },
648
+ closeSearchButton: {
649
+ paddingHorizontal: 8,
650
+ paddingVertical: 4
651
+ },
652
+ closeSearchText: {
653
+ color: _sharedUi.buoyColors.primary,
654
+ fontSize: 14,
655
+ fontWeight: "600",
656
+ fontFamily: "monospace"
657
+ },
658
+ scrollView: {
659
+ flex: 1
660
+ },
661
+ scrollContent: {
662
+ paddingVertical: 8
663
+ },
664
+ metaFooter: {
665
+ flexDirection: "row",
666
+ alignItems: "center",
667
+ justifyContent: "space-between",
668
+ gap: 8,
669
+ paddingHorizontal: 16,
670
+ paddingVertical: 16,
671
+ marginTop: 16,
672
+ borderTopWidth: 1,
673
+ borderTopColor: _sharedUi.buoyColors.border
674
+ },
675
+ metaText: {
676
+ fontSize: 12,
677
+ color: _sharedUi.buoyColors.textSecondary,
678
+ fontFamily: "monospace"
679
+ },
680
+ sourceBadge: {
681
+ paddingHorizontal: 8,
682
+ paddingVertical: 2,
683
+ borderRadius: 999,
684
+ borderWidth: 1,
685
+ borderColor: _sharedUi.buoyColors.border,
686
+ backgroundColor: _sharedUi.buoyColors.card
687
+ },
688
+ sourceText: {
689
+ fontSize: 11,
690
+ color: _sharedUi.buoyColors.textSecondary,
691
+ textTransform: "capitalize",
692
+ fontFamily: "monospace"
693
+ },
694
+ groupContainer: {
695
+ marginVertical: 4
696
+ },
697
+ groupHeader: {
698
+ flexDirection: "row",
699
+ alignItems: "center",
700
+ justifyContent: "space-between",
701
+ padding: 12,
702
+ paddingHorizontal: 16,
703
+ backgroundColor: _sharedUi.buoyColors.card,
704
+ borderLeftWidth: 3,
705
+ borderLeftColor: _sharedUi.buoyColors.textSecondary
706
+ },
707
+ groupHeaderLeft: {
708
+ flexDirection: "row",
709
+ alignItems: "center",
710
+ gap: 8,
711
+ flex: 1
712
+ },
713
+ groupTitle: {
714
+ fontSize: 12,
715
+ fontWeight: "700",
716
+ color: _sharedUi.buoyColors.text,
717
+ fontFamily: "monospace",
718
+ textTransform: "uppercase",
719
+ letterSpacing: 0.8
720
+ },
721
+ groupBadge: {
722
+ backgroundColor: "#3B82F615",
723
+ borderColor: "#3B82F640",
724
+ borderWidth: 1,
725
+ borderRadius: 12,
726
+ paddingHorizontal: 8,
727
+ paddingVertical: 3,
728
+ minWidth: 24,
729
+ alignItems: "center",
730
+ justifyContent: "center"
731
+ },
732
+ groupCount: {
733
+ fontSize: 11,
734
+ fontWeight: "600",
735
+ color: "#3B82F6",
736
+ fontFamily: "monospace"
737
+ },
738
+ screensList: {
739
+ backgroundColor: _sharedUi.buoyColors.base
740
+ },
741
+ groupDescription: {
742
+ paddingHorizontal: 16,
743
+ paddingVertical: 8,
744
+ marginBottom: 8,
745
+ backgroundColor: _sharedUi.buoyColors.input,
746
+ borderLeftWidth: 3,
747
+ borderLeftColor: _sharedUi.buoyColors.textSecondary
748
+ },
749
+ groupDescriptionText: {
750
+ fontSize: 11,
751
+ color: _sharedUi.buoyColors.textSecondary,
752
+ fontFamily: "monospace",
753
+ lineHeight: 16
754
+ },
755
+ screenItem: {
756
+ marginBottom: 6
757
+ },
758
+ screenCard: {
759
+ flexDirection: "row",
760
+ alignItems: "center",
761
+ justifyContent: "space-between",
762
+ paddingVertical: 10,
763
+ paddingHorizontal: 12,
764
+ backgroundColor: _sharedUi.buoyColors.card,
765
+ borderRadius: 6,
766
+ borderWidth: 1,
767
+ borderColor: _sharedUi.buoyColors.border,
768
+ shadowColor: "#000",
769
+ shadowOffset: {
770
+ width: 0,
771
+ height: 1
772
+ },
773
+ shadowOpacity: 0.05,
774
+ shadowRadius: 2,
775
+ elevation: 1
776
+ },
777
+ screenHeaderLeft: {
778
+ flexDirection: "row",
779
+ alignItems: "center",
780
+ gap: 8,
781
+ flex: 1
782
+ },
783
+ expandIndicator: {
784
+ width: 20,
785
+ alignItems: "center"
786
+ },
787
+ screenName: {
788
+ fontSize: 13,
789
+ fontWeight: "600",
790
+ color: _sharedUi.buoyColors.text,
791
+ fontFamily: "monospace",
792
+ flex: 1
793
+ },
794
+ screenHeaderActions: {
795
+ flexDirection: "row",
796
+ alignItems: "center",
797
+ gap: 6
798
+ },
799
+ statusBadge: {
800
+ borderRadius: 4,
801
+ paddingHorizontal: 6,
802
+ paddingVertical: 2,
803
+ borderWidth: 1
804
+ },
805
+ statusText: {
806
+ fontSize: 10,
807
+ fontWeight: "600",
808
+ fontFamily: "monospace"
809
+ },
810
+ headerActionButton: {
811
+ padding: 6,
812
+ borderRadius: 4,
813
+ backgroundColor: _sharedUi.buoyColors.input,
814
+ borderWidth: 1,
815
+ borderColor: _sharedUi.buoyColors.border,
816
+ alignItems: "center",
817
+ justifyContent: "center"
818
+ },
819
+ navigateButton: {
820
+ backgroundColor: _sharedUi.buoyColors.primary + "15",
821
+ borderColor: _sharedUi.buoyColors.primary + "40"
822
+ },
823
+ navigateButtonText: {
824
+ fontSize: 12,
825
+ color: _sharedUi.buoyColors.primary,
826
+ fontFamily: "monospace",
827
+ fontWeight: "600"
828
+ }
829
+ });