@buoy-gg/route-events 3.0.1 → 4.0.1
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.
- package/lib/commonjs/RouteTracker.js +38 -4
- package/lib/commonjs/components/NavigationStack.js +105 -124
- package/lib/commonjs/components/RouteEventsModalWithTabs.js +11 -4
- package/lib/commonjs/components/RoutesSitemap.js +205 -169
- package/lib/commonjs/expoRouterStore.js +17 -0
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/stores/navigationStackStore.js +45 -0
- package/lib/commonjs/sync/routeEventsSyncAdapter.js +148 -0
- package/lib/commonjs/useRouteSitemap.js +28 -7
- package/lib/module/RouteTracker.js +39 -5
- package/lib/module/components/NavigationStack.js +106 -125
- package/lib/module/components/RouteEventsModalWithTabs.js +11 -4
- package/lib/module/components/RoutesSitemap.js +207 -171
- package/lib/module/expoRouterStore.js +16 -0
- package/lib/module/index.js +4 -0
- package/lib/module/stores/navigationStackStore.js +41 -0
- package/lib/module/sync/routeEventsSyncAdapter.js +145 -0
- package/lib/module/useRouteSitemap.js +29 -8
- package/lib/typescript/RouteTracker.d.ts.map +1 -1
- package/lib/typescript/components/NavigationStack.d.ts +24 -1
- package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/components/RoutesSitemap.d.ts +19 -1
- package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -1
- package/lib/typescript/expoRouterStore.d.ts +8 -0
- package/lib/typescript/expoRouterStore.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +3 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/stores/navigationStackStore.d.ts +33 -0
- package/lib/typescript/stores/navigationStackStore.d.ts.map +1 -0
- package/lib/typescript/sync/routeEventsSyncAdapter.d.ts +69 -0
- package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +1 -0
- package/lib/typescript/useRouteSitemap.d.ts +17 -0
- package/lib/typescript/useRouteSitemap.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { useState, useCallback, useMemo, useEffect } from "react";
|
|
15
|
-
import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Alert } from "react-native";
|
|
15
|
+
import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Alert, Platform } from "react-native";
|
|
16
16
|
import { useSafeRouter } from "@buoy-gg/shared-ui";
|
|
17
|
-
import { Search, ChevronDown, ChevronRight, InlineCopyButton,
|
|
17
|
+
import { Search, ChevronDown, ChevronRight, Home, InlineCopyButton, CopyButton, RefreshCw, formatRelativeTime, ProUpgradeModal, buoyColors } from "@buoy-gg/shared-ui";
|
|
18
18
|
import { useIsPro } from "@buoy-gg/license";
|
|
19
19
|
import { useRouteSitemap } from "../useRouteSitemap";
|
|
20
20
|
|
|
@@ -22,12 +22,35 @@ import { useRouteSitemap } from "../useRouteSitemap";
|
|
|
22
22
|
// Types
|
|
23
23
|
// ============================================================================
|
|
24
24
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
25
|
+
// On desktop (web) the toolbar has far more room, so scale the action buttons
|
|
26
|
+
// up for easier clicking and a less cramped header.
|
|
27
|
+
const IS_DESKTOP = Platform.OS === "web";
|
|
28
|
+
const ACTION_ICON_SIZE = IS_DESKTOP ? 22 : 16;
|
|
29
|
+
|
|
30
|
+
// Synthetic route representing the app's home/index ("/"). Used by the
|
|
31
|
+
// "Home" toolbar shortcut so navigation works the same as tapping any route.
|
|
32
|
+
const HOME_ROUTE = {
|
|
33
|
+
path: "/",
|
|
34
|
+
name: "index",
|
|
35
|
+
type: "index",
|
|
36
|
+
params: [],
|
|
37
|
+
nodeType: "route",
|
|
38
|
+
contextKey: "/",
|
|
39
|
+
isInternal: false,
|
|
40
|
+
children: [],
|
|
41
|
+
depth: 0
|
|
42
|
+
};
|
|
43
|
+
|
|
25
44
|
// ============================================================================
|
|
26
45
|
// Main Component
|
|
27
46
|
// ============================================================================
|
|
28
47
|
|
|
29
48
|
export function RoutesSitemap({
|
|
30
|
-
style
|
|
49
|
+
style,
|
|
50
|
+
injectedRoutes,
|
|
51
|
+
injectedSource,
|
|
52
|
+
injectedLastUpdatedAt,
|
|
53
|
+
onNavigateRoute
|
|
31
54
|
}) {
|
|
32
55
|
const [searchQuery, setSearchQuery] = useState("");
|
|
33
56
|
const [isSearching, setIsSearching] = useState(false);
|
|
@@ -49,6 +72,7 @@ export function RoutesSitemap({
|
|
|
49
72
|
groups,
|
|
50
73
|
stats,
|
|
51
74
|
isLoaded,
|
|
75
|
+
isSupported,
|
|
52
76
|
filteredRoutes,
|
|
53
77
|
routes,
|
|
54
78
|
refresh,
|
|
@@ -56,7 +80,10 @@ export function RoutesSitemap({
|
|
|
56
80
|
source
|
|
57
81
|
} = useRouteSitemap({
|
|
58
82
|
searchQuery,
|
|
59
|
-
sortBy: "path"
|
|
83
|
+
sortBy: "path",
|
|
84
|
+
injectedRoutes,
|
|
85
|
+
injectedSource,
|
|
86
|
+
injectedLastUpdatedAt
|
|
60
87
|
});
|
|
61
88
|
|
|
62
89
|
// Prepare copy data - memoized so it only rebuilds when dependencies change
|
|
@@ -143,9 +170,16 @@ export function RoutesSitemap({
|
|
|
143
170
|
}
|
|
144
171
|
}], "plain-text");
|
|
145
172
|
}, [router]);
|
|
146
|
-
const handleNavigate = useCallback(route => {
|
|
147
|
-
//
|
|
148
|
-
|
|
173
|
+
const handleNavigate = useCallback((route, options) => {
|
|
174
|
+
// When a navigation delegate is supplied (dashboard → device), hand off
|
|
175
|
+
// entirely: the delegate owns Pro gating and param resolution.
|
|
176
|
+
if (onNavigateRoute) {
|
|
177
|
+
onNavigateRoute(route);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Gate behind Pro (the Home shortcut bypasses this — it's always free)
|
|
182
|
+
if (!isPro && !options?.bypassPro) {
|
|
149
183
|
setShowUpgradeModal(true);
|
|
150
184
|
return;
|
|
151
185
|
}
|
|
@@ -179,7 +213,12 @@ export function RoutesSitemap({
|
|
|
179
213
|
} catch (error) {
|
|
180
214
|
Alert.alert("Navigation Error", String(error));
|
|
181
215
|
}
|
|
182
|
-
}, [router, promptForParams, isPro]);
|
|
216
|
+
}, [router, promptForParams, isPro, onNavigateRoute]);
|
|
217
|
+
const handleGoHome = useCallback(() => {
|
|
218
|
+
handleNavigate(HOME_ROUTE, {
|
|
219
|
+
bypassPro: true
|
|
220
|
+
});
|
|
221
|
+
}, [handleNavigate]);
|
|
183
222
|
const handleManualRefresh = useCallback(() => {
|
|
184
223
|
if (isRefreshing) return;
|
|
185
224
|
setIsRefreshing(true);
|
|
@@ -214,7 +253,7 @@ export function RoutesSitemap({
|
|
|
214
253
|
style: styles.loadingContainer,
|
|
215
254
|
children: /*#__PURE__*/_jsx(Text, {
|
|
216
255
|
style: styles.loadingText,
|
|
217
|
-
children: "Loading routes..."
|
|
256
|
+
children: isSupported ? "Loading routes..." : "Route sitemap isn't available on the dashboard — the route tree lives on the device. Use the Events tab to see navigation here."
|
|
218
257
|
})
|
|
219
258
|
})
|
|
220
259
|
});
|
|
@@ -226,39 +265,54 @@ export function RoutesSitemap({
|
|
|
226
265
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
227
266
|
style: styles.actionsRow,
|
|
228
267
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
229
|
-
style: styles.actionWrapper,
|
|
230
|
-
children: [/*#__PURE__*/_jsx(
|
|
268
|
+
style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
|
|
269
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
270
|
+
style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
|
|
271
|
+
onPress: handleGoHome,
|
|
272
|
+
accessibilityLabel: "Go to home route",
|
|
273
|
+
children: /*#__PURE__*/_jsx(Home, {
|
|
274
|
+
size: ACTION_ICON_SIZE,
|
|
275
|
+
color: buoyColors.textSecondary
|
|
276
|
+
})
|
|
277
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
278
|
+
style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
|
|
279
|
+
children: "Home"
|
|
280
|
+
})]
|
|
281
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
282
|
+
style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
|
|
283
|
+
children: [/*#__PURE__*/_jsx(CopyButton, {
|
|
231
284
|
value: copyAllData,
|
|
232
|
-
|
|
285
|
+
size: IS_DESKTOP ? ACTION_ICON_SIZE : 14,
|
|
286
|
+
buttonStyle: IS_DESKTOP ? styles.actionButtonHeaderDesktop : styles.actionButtonHeader
|
|
233
287
|
}), /*#__PURE__*/_jsx(Text, {
|
|
234
|
-
style: styles.actionLabel,
|
|
288
|
+
style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
|
|
235
289
|
children: "Copy"
|
|
236
290
|
})]
|
|
237
291
|
}), /*#__PURE__*/_jsxs(View, {
|
|
238
|
-
style: styles.actionWrapper,
|
|
292
|
+
style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
|
|
239
293
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
240
|
-
style: [styles.iconButton, isRefreshing && styles.refreshButtonDisabled],
|
|
294
|
+
style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop, isRefreshing && styles.refreshButtonDisabled],
|
|
241
295
|
onPress: handleManualRefresh,
|
|
242
296
|
disabled: isRefreshing,
|
|
243
297
|
children: /*#__PURE__*/_jsx(RefreshCw, {
|
|
244
|
-
size:
|
|
298
|
+
size: ACTION_ICON_SIZE,
|
|
245
299
|
color: isRefreshing ? buoyColors.textMuted : buoyColors.textSecondary
|
|
246
300
|
})
|
|
247
301
|
}), /*#__PURE__*/_jsx(Text, {
|
|
248
|
-
style: styles.actionLabel,
|
|
302
|
+
style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
|
|
249
303
|
children: "Refresh"
|
|
250
304
|
})]
|
|
251
305
|
}), /*#__PURE__*/_jsxs(View, {
|
|
252
|
-
style: styles.actionWrapper,
|
|
306
|
+
style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
|
|
253
307
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
254
|
-
style: styles.iconButton,
|
|
308
|
+
style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
|
|
255
309
|
onPress: () => setIsSearching(true),
|
|
256
310
|
children: /*#__PURE__*/_jsx(Search, {
|
|
257
|
-
size:
|
|
311
|
+
size: ACTION_ICON_SIZE,
|
|
258
312
|
color: buoyColors.textSecondary
|
|
259
313
|
})
|
|
260
314
|
}), /*#__PURE__*/_jsx(Text, {
|
|
261
|
-
style: styles.actionLabel,
|
|
315
|
+
style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
|
|
262
316
|
children: "Search"
|
|
263
317
|
})]
|
|
264
318
|
})]
|
|
@@ -404,18 +458,18 @@ function RouteGroupView({
|
|
|
404
458
|
children: group.routes.length
|
|
405
459
|
})
|
|
406
460
|
})]
|
|
407
|
-
}),
|
|
461
|
+
}), group.description && /*#__PURE__*/_jsx(View, {
|
|
462
|
+
style: styles.groupDescription,
|
|
463
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
464
|
+
style: styles.groupDescriptionText,
|
|
465
|
+
children: group.description
|
|
466
|
+
})
|
|
467
|
+
}), isExpanded && /*#__PURE__*/_jsx(View, {
|
|
408
468
|
style: styles.routesList,
|
|
409
|
-
children:
|
|
410
|
-
style: styles.groupDescription,
|
|
411
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
412
|
-
style: styles.groupDescriptionText,
|
|
413
|
-
children: group.description
|
|
414
|
-
})
|
|
415
|
-
}), group.routes.map((route, index) => /*#__PURE__*/_jsx(RouteItemView, {
|
|
469
|
+
children: group.routes.map((route, index) => /*#__PURE__*/_jsx(RouteItemView, {
|
|
416
470
|
route: route,
|
|
417
471
|
onNavigate: onNavigate
|
|
418
|
-
}, `${route.path}-${index}`))
|
|
472
|
+
}, `${route.path}-${index}`))
|
|
419
473
|
})]
|
|
420
474
|
});
|
|
421
475
|
}
|
|
@@ -426,85 +480,85 @@ function RouteItemView({
|
|
|
426
480
|
}) {
|
|
427
481
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
428
482
|
const hasChildren = route.children.length > 0;
|
|
429
|
-
const hasParams = route.params.length > 0;
|
|
430
483
|
const typeColor = getRouteTypeColor(route.type);
|
|
431
484
|
const canNavigate = route.type !== "layout" && route.type !== "group";
|
|
432
|
-
|
|
485
|
+
|
|
486
|
+
// Leaf routes (no children) have nothing to drill into — expanding would only
|
|
487
|
+
// reveal the Copy/Go actions, so show those inline instead of behind a toggle.
|
|
488
|
+
const isExpandable = hasChildren;
|
|
489
|
+
const showDetails = isExpandable ? isExpanded : true;
|
|
490
|
+
return /*#__PURE__*/_jsx(View, {
|
|
433
491
|
style: [styles.routeItem, {
|
|
434
492
|
marginLeft: depth * 12
|
|
435
493
|
}],
|
|
436
|
-
children:
|
|
494
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
437
495
|
style: styles.routeCard,
|
|
438
|
-
children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
439
|
-
style: styles.routeHeaderLeft,
|
|
440
|
-
onPress: () => setIsExpanded(!isExpanded),
|
|
441
|
-
activeOpacity: 0.7,
|
|
442
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
443
|
-
style: styles.expandIndicator,
|
|
444
|
-
children: isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
|
|
445
|
-
size: 14,
|
|
446
|
-
color: buoyColors.textSecondary
|
|
447
|
-
}) : /*#__PURE__*/_jsx(ChevronRight, {
|
|
448
|
-
size: 14,
|
|
449
|
-
color: buoyColors.textSecondary
|
|
450
|
-
})
|
|
451
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
452
|
-
style: styles.routePath,
|
|
453
|
-
numberOfLines: 1,
|
|
454
|
-
children: route.path
|
|
455
|
-
})]
|
|
456
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
457
|
-
style: styles.routeHeaderActions,
|
|
458
|
-
children: [hasChildren && /*#__PURE__*/_jsx(View, {
|
|
459
|
-
style: styles.childCountBadge,
|
|
460
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
461
|
-
style: styles.childCountText,
|
|
462
|
-
children: route.children.length
|
|
463
|
-
})
|
|
464
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
465
|
-
style: [styles.typeTag, {
|
|
466
|
-
backgroundColor: `${typeColor}15`,
|
|
467
|
-
borderColor: `${typeColor}40`
|
|
468
|
-
}],
|
|
469
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
470
|
-
style: [styles.typeText, {
|
|
471
|
-
color: typeColor
|
|
472
|
-
}],
|
|
473
|
-
children: route.type.toUpperCase()
|
|
474
|
-
})
|
|
475
|
-
})]
|
|
476
|
-
})]
|
|
477
|
-
}), isExpanded && /*#__PURE__*/_jsxs(View, {
|
|
478
|
-
style: styles.routeDetails,
|
|
479
496
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
480
|
-
style: styles.
|
|
481
|
-
children: [/*#__PURE__*/
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
497
|
+
style: styles.routeHeader,
|
|
498
|
+
children: [isExpandable ? /*#__PURE__*/_jsxs(TouchableOpacity, {
|
|
499
|
+
style: styles.routeHeaderLeft,
|
|
500
|
+
onPress: () => setIsExpanded(!isExpanded),
|
|
501
|
+
activeOpacity: 0.7,
|
|
502
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
503
|
+
style: styles.expandIndicator,
|
|
504
|
+
children: isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
|
|
505
|
+
size: 14,
|
|
506
|
+
color: buoyColors.textSecondary
|
|
507
|
+
}) : /*#__PURE__*/_jsx(ChevronRight, {
|
|
508
|
+
size: 14,
|
|
509
|
+
color: buoyColors.textSecondary
|
|
510
|
+
})
|
|
511
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
512
|
+
style: styles.routePath,
|
|
513
|
+
numberOfLines: 3,
|
|
514
|
+
children: route.path
|
|
515
|
+
})]
|
|
516
|
+
}) :
|
|
517
|
+
/*#__PURE__*/
|
|
518
|
+
// Non-toggling header for leaf routes (keeps the chevron column as a
|
|
519
|
+
// spacer so paths stay aligned with expandable siblings).
|
|
520
|
+
_jsxs(View, {
|
|
521
|
+
style: styles.routeHeaderLeft,
|
|
522
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
523
|
+
style: styles.expandIndicator
|
|
524
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
525
|
+
style: styles.routePath,
|
|
526
|
+
numberOfLines: 3,
|
|
527
|
+
children: route.path
|
|
528
|
+
})]
|
|
529
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
530
|
+
style: styles.routeHeaderActions,
|
|
531
|
+
children: [hasChildren && /*#__PURE__*/_jsx(View, {
|
|
532
|
+
style: styles.childCountBadge,
|
|
501
533
|
children: /*#__PURE__*/_jsx(Text, {
|
|
502
|
-
style: styles.
|
|
503
|
-
children:
|
|
534
|
+
style: styles.childCountText,
|
|
535
|
+
children: route.children.length
|
|
504
536
|
})
|
|
505
|
-
},
|
|
537
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
538
|
+
style: [styles.typeTag, {
|
|
539
|
+
backgroundColor: `${typeColor}15`,
|
|
540
|
+
borderColor: `${typeColor}40`
|
|
541
|
+
}],
|
|
542
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
543
|
+
style: [styles.typeText, {
|
|
544
|
+
color: typeColor
|
|
545
|
+
}],
|
|
546
|
+
children: route.type.toUpperCase()
|
|
547
|
+
})
|
|
548
|
+
}), /*#__PURE__*/_jsx(InlineCopyButton, {
|
|
549
|
+
value: route.path,
|
|
550
|
+
buttonStyle: styles.iconAction
|
|
551
|
+
}), canNavigate && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
552
|
+
style: styles.goButton,
|
|
553
|
+
onPress: () => onNavigate(route),
|
|
554
|
+
activeOpacity: 0.7,
|
|
555
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
556
|
+
style: styles.goButtonText,
|
|
557
|
+
children: "Go"
|
|
558
|
+
})
|
|
559
|
+
})]
|
|
506
560
|
})]
|
|
507
|
-
}), hasChildren && /*#__PURE__*/_jsx(View, {
|
|
561
|
+
}), showDetails && hasChildren && /*#__PURE__*/_jsx(View, {
|
|
508
562
|
style: styles.childrenContainer,
|
|
509
563
|
children: route.children.map((child, index) => /*#__PURE__*/_jsx(RouteItemView, {
|
|
510
564
|
route: child,
|
|
@@ -512,7 +566,7 @@ function RouteItemView({
|
|
|
512
566
|
onNavigate: onNavigate
|
|
513
567
|
}, `${child.path}-${index}`))
|
|
514
568
|
})]
|
|
515
|
-
})
|
|
569
|
+
})
|
|
516
570
|
});
|
|
517
571
|
}
|
|
518
572
|
|
|
@@ -566,7 +620,11 @@ const styles = StyleSheet.create({
|
|
|
566
620
|
loadingText: {
|
|
567
621
|
color: buoyColors.textSecondary,
|
|
568
622
|
fontSize: 14,
|
|
569
|
-
fontFamily: "monospace"
|
|
623
|
+
fontFamily: "monospace",
|
|
624
|
+
textAlign: "center",
|
|
625
|
+
lineHeight: 20,
|
|
626
|
+
paddingHorizontal: 32,
|
|
627
|
+
maxWidth: 420
|
|
570
628
|
},
|
|
571
629
|
header: {
|
|
572
630
|
flexDirection: "column",
|
|
@@ -578,7 +636,7 @@ const styles = StyleSheet.create({
|
|
|
578
636
|
actionsRow: {
|
|
579
637
|
flexDirection: "row",
|
|
580
638
|
alignItems: "center",
|
|
581
|
-
justifyContent: "
|
|
639
|
+
justifyContent: "space-between",
|
|
582
640
|
gap: 12,
|
|
583
641
|
paddingBottom: 4
|
|
584
642
|
},
|
|
@@ -613,22 +671,37 @@ const styles = StyleSheet.create({
|
|
|
613
671
|
letterSpacing: 0.5
|
|
614
672
|
},
|
|
615
673
|
actionWrapper: {
|
|
674
|
+
flex: 1,
|
|
616
675
|
alignItems: "center",
|
|
617
676
|
justifyContent: "center",
|
|
618
677
|
gap: 4,
|
|
619
678
|
minWidth: 48
|
|
620
679
|
},
|
|
680
|
+
actionWrapperDesktop: {
|
|
681
|
+
gap: 8,
|
|
682
|
+
minWidth: 72
|
|
683
|
+
},
|
|
621
684
|
actionButtonHeader: {
|
|
622
685
|
padding: 6,
|
|
623
686
|
borderRadius: 4,
|
|
624
687
|
backgroundColor: buoyColors.input
|
|
625
688
|
},
|
|
689
|
+
actionButtonHeaderDesktop: {
|
|
690
|
+
padding: 12,
|
|
691
|
+
borderRadius: 8,
|
|
692
|
+
backgroundColor: buoyColors.input
|
|
693
|
+
},
|
|
626
694
|
iconButton: {
|
|
627
695
|
padding: 6,
|
|
628
696
|
borderRadius: 4,
|
|
629
697
|
alignItems: "center",
|
|
630
698
|
justifyContent: "center"
|
|
631
699
|
},
|
|
700
|
+
iconButtonDesktop: {
|
|
701
|
+
padding: 12,
|
|
702
|
+
borderRadius: 8,
|
|
703
|
+
backgroundColor: buoyColors.input
|
|
704
|
+
},
|
|
632
705
|
actionLabel: {
|
|
633
706
|
fontSize: 8,
|
|
634
707
|
color: buoyColors.textMuted,
|
|
@@ -636,6 +709,9 @@ const styles = StyleSheet.create({
|
|
|
636
709
|
textTransform: "uppercase",
|
|
637
710
|
letterSpacing: 0.5
|
|
638
711
|
},
|
|
712
|
+
actionLabelDesktop: {
|
|
713
|
+
fontSize: 12
|
|
714
|
+
},
|
|
639
715
|
refreshButtonDisabled: {
|
|
640
716
|
opacity: 0.5
|
|
641
717
|
},
|
|
@@ -780,15 +856,11 @@ const styles = StyleSheet.create({
|
|
|
780
856
|
marginBottom: 6
|
|
781
857
|
},
|
|
782
858
|
routeCard: {
|
|
783
|
-
flexDirection: "row",
|
|
784
|
-
alignItems: "center",
|
|
785
|
-
justifyContent: "space-between",
|
|
786
|
-
paddingVertical: 10,
|
|
787
|
-
paddingHorizontal: 12,
|
|
788
859
|
backgroundColor: buoyColors.card,
|
|
789
860
|
borderRadius: 6,
|
|
790
861
|
borderWidth: 1,
|
|
791
862
|
borderColor: buoyColors.border,
|
|
863
|
+
overflow: "hidden",
|
|
792
864
|
shadowColor: "#000",
|
|
793
865
|
shadowOffset: {
|
|
794
866
|
width: 0,
|
|
@@ -798,8 +870,16 @@ const styles = StyleSheet.create({
|
|
|
798
870
|
shadowRadius: 2,
|
|
799
871
|
elevation: 1
|
|
800
872
|
},
|
|
873
|
+
routeHeader: {
|
|
874
|
+
flexDirection: "row",
|
|
875
|
+
alignItems: "center",
|
|
876
|
+
justifyContent: "space-between",
|
|
877
|
+
paddingVertical: 7,
|
|
878
|
+
paddingHorizontal: 10,
|
|
879
|
+
gap: 8
|
|
880
|
+
},
|
|
801
881
|
expandIndicator: {
|
|
802
|
-
width:
|
|
882
|
+
width: 18,
|
|
803
883
|
alignItems: "center"
|
|
804
884
|
},
|
|
805
885
|
routeHeaderLeft: {
|
|
@@ -848,76 +928,29 @@ const styles = StyleSheet.create({
|
|
|
848
928
|
fontWeight: "600",
|
|
849
929
|
fontFamily: "monospace"
|
|
850
930
|
},
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
borderTopColor: buoyColors.border,
|
|
858
|
-
backgroundColor: buoyColors.card,
|
|
859
|
-
borderRadius: 6,
|
|
931
|
+
// Compact icon-sized Copy button in the header action cluster.
|
|
932
|
+
iconAction: {
|
|
933
|
+
width: 28,
|
|
934
|
+
height: 24,
|
|
935
|
+
borderRadius: 4,
|
|
936
|
+
backgroundColor: buoyColors.input,
|
|
860
937
|
borderWidth: 1,
|
|
861
938
|
borderColor: buoyColors.border,
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
marginTop: -6
|
|
865
|
-
},
|
|
866
|
-
paramsContainer: {
|
|
867
|
-
gap: 6
|
|
868
|
-
},
|
|
869
|
-
paramsLabel: {
|
|
870
|
-
fontSize: 10,
|
|
871
|
-
color: buoyColors.textSecondary,
|
|
872
|
-
fontFamily: "monospace",
|
|
873
|
-
marginBottom: 4
|
|
874
|
-
},
|
|
875
|
-
paramsRow: {
|
|
876
|
-
flexDirection: "row",
|
|
877
|
-
flexWrap: "wrap",
|
|
878
|
-
gap: 6
|
|
939
|
+
alignItems: "center",
|
|
940
|
+
justifyContent: "center"
|
|
879
941
|
},
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
942
|
+
// Small "Go" pill in the header action cluster.
|
|
943
|
+
goButton: {
|
|
944
|
+
height: 24,
|
|
945
|
+
paddingHorizontal: 10,
|
|
884
946
|
borderRadius: 4,
|
|
885
|
-
paddingHorizontal: 6,
|
|
886
|
-
paddingVertical: 2
|
|
887
|
-
},
|
|
888
|
-
paramText: {
|
|
889
|
-
fontSize: 10,
|
|
890
|
-
color: "#F59E0B",
|
|
891
|
-
fontFamily: "monospace",
|
|
892
|
-
fontWeight: "600"
|
|
893
|
-
},
|
|
894
|
-
routeButtons: {
|
|
895
|
-
flexDirection: "row",
|
|
896
|
-
gap: 6,
|
|
897
|
-
marginBottom: 12
|
|
898
|
-
},
|
|
899
|
-
actionButton: {
|
|
900
|
-
flexDirection: "row",
|
|
901
947
|
alignItems: "center",
|
|
902
|
-
|
|
903
|
-
backgroundColor: buoyColors.input,
|
|
904
|
-
borderRadius: 4,
|
|
905
|
-
paddingHorizontal: 12,
|
|
906
|
-
paddingVertical: 6,
|
|
907
|
-
borderWidth: 1,
|
|
908
|
-
borderColor: buoyColors.border
|
|
909
|
-
},
|
|
910
|
-
actionButtonText: {
|
|
911
|
-
fontSize: 12,
|
|
912
|
-
color: buoyColors.textSecondary,
|
|
913
|
-
fontFamily: "monospace",
|
|
914
|
-
fontWeight: "600"
|
|
915
|
-
},
|
|
916
|
-
navigateButton: {
|
|
948
|
+
justifyContent: "center",
|
|
917
949
|
backgroundColor: buoyColors.primary + "15",
|
|
950
|
+
borderWidth: 1,
|
|
918
951
|
borderColor: buoyColors.primary + "40"
|
|
919
952
|
},
|
|
920
|
-
|
|
953
|
+
goButtonText: {
|
|
921
954
|
fontSize: 12,
|
|
922
955
|
color: buoyColors.primary,
|
|
923
956
|
fontFamily: "monospace",
|
|
@@ -926,6 +959,9 @@ const styles = StyleSheet.create({
|
|
|
926
959
|
childrenContainer: {
|
|
927
960
|
borderLeftWidth: 2,
|
|
928
961
|
borderLeftColor: buoyColors.border,
|
|
929
|
-
marginLeft: 16
|
|
962
|
+
marginLeft: 16,
|
|
963
|
+
marginRight: 8,
|
|
964
|
+
marginTop: 2,
|
|
965
|
+
marginBottom: 8
|
|
930
966
|
}
|
|
931
967
|
});
|
|
@@ -24,10 +24,26 @@ function logOnce(message, error) {
|
|
|
24
24
|
* The storeRef gets populated when Expo Router's useStore() hook runs.
|
|
25
25
|
* So we cache the store reference but its property values update over time.
|
|
26
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* Whether the expo-router store can be loaded in this runtime. The store is
|
|
29
|
+
* pulled in via CommonJS `require`, which only exists under Metro/Node. On the
|
|
30
|
+
* web (e.g. the desktop dashboard rendering these RN tools via react-native-web)
|
|
31
|
+
* `require` is undefined, the route tree lives on the device — not here — and
|
|
32
|
+
* there is nothing to load. Callers use this to skip polling/logging.
|
|
33
|
+
*/
|
|
34
|
+
export function isExpoRouterStoreSupported() {
|
|
35
|
+
return typeof require !== "undefined";
|
|
36
|
+
}
|
|
27
37
|
export function getExpoRouterStore() {
|
|
28
38
|
if (cachedStore) {
|
|
29
39
|
return cachedStore;
|
|
30
40
|
}
|
|
41
|
+
|
|
42
|
+
// No `require` (web): bail quietly rather than throwing ReferenceError and
|
|
43
|
+
// logging a misleading "install expo-router" message on the dashboard.
|
|
44
|
+
if (!isExpoRouterStoreSupported()) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
31
47
|
const loadFromBuild = () => {
|
|
32
48
|
try {
|
|
33
49
|
const module = require("expo-router/build/global-state/router-store");
|
package/lib/module/index.js
CHANGED
|
@@ -38,6 +38,10 @@ export { useRouteObserverReactNavigation } from "./useRouteObserverReactNavigati
|
|
|
38
38
|
export { useRouteEvents } from "./hooks/useRouteEvents";
|
|
39
39
|
export { useRouteSitemap, useRoute, useParentRoutes } from "./useRouteSitemap";
|
|
40
40
|
export { useNavigationStack } from "./useNavigationStack";
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
|
|
43
|
+
// =============================================================================
|
|
44
|
+
export { routeEventsSyncAdapter } from "./sync/routeEventsSyncAdapter";
|
|
41
45
|
|
|
42
46
|
// =============================================================================
|
|
43
47
|
// TYPES
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Navigation Stack Store
|
|
5
|
+
*
|
|
6
|
+
* Holds the latest serializable navigation stack plus references to the live
|
|
7
|
+
* navigation action functions. The stack is captured inside the navigation tree
|
|
8
|
+
* by <RouteTracker /> (which runs useNavigationStack), and read by the
|
|
9
|
+
* route-events sync adapter so the dashboard can render the Stack tab and drive
|
|
10
|
+
* navigation on the device remotely.
|
|
11
|
+
*
|
|
12
|
+
* The stack itself (StackDisplayItem[]) is JSON-serializable and synced to the
|
|
13
|
+
* dashboard. The action functions are NOT serialized — they stay on the device
|
|
14
|
+
* and are invoked when the dashboard sends a remote action.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
class NavigationStackStore {
|
|
18
|
+
stack = [];
|
|
19
|
+
actions = null;
|
|
20
|
+
listeners = new Set();
|
|
21
|
+
getStack() {
|
|
22
|
+
return this.stack;
|
|
23
|
+
}
|
|
24
|
+
setStack(stack) {
|
|
25
|
+
this.stack = stack;
|
|
26
|
+
this.listeners.forEach(listener => listener());
|
|
27
|
+
}
|
|
28
|
+
getActions() {
|
|
29
|
+
return this.actions;
|
|
30
|
+
}
|
|
31
|
+
setActions(actions) {
|
|
32
|
+
this.actions = actions;
|
|
33
|
+
}
|
|
34
|
+
subscribe(listener) {
|
|
35
|
+
this.listeners.add(listener);
|
|
36
|
+
return () => {
|
|
37
|
+
this.listeners.delete(listener);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export const navigationStackStore = new NavigationStackStore();
|