@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,560 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RouteEventsModalWithTabs = RouteEventsModalWithTabs;
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 _RouteObserver = require("../RouteObserver");
12
+ var _RouteFilterViewV = require("./RouteFilterViewV2");
13
+ var _RoutesSitemap = require("./RoutesSitemap");
14
+ var _NavigationStack = require("./NavigationStack");
15
+ var _RouteEventsTimeline = require("./RouteEventsTimeline");
16
+ var _jsxRuntime = require("react/jsx-runtime");
17
+ // Lazy load the license hooks to avoid circular dependencies
18
+ let _useIsPro = null;
19
+ let _licenseLoadAttempted = false;
20
+ function loadLicenseModule() {
21
+ if (_licenseLoadAttempted) return;
22
+ _licenseLoadAttempted = true;
23
+ try {
24
+ const mod = require("@buoy-gg/license");
25
+ if (mod) {
26
+ _useIsPro = mod.useIsPro ?? null;
27
+ }
28
+ } catch {
29
+ // License package not available
30
+ }
31
+ }
32
+ function getUseIsPro() {
33
+ loadLicenseModule();
34
+ return _useIsPro ?? (() => false);
35
+ }
36
+
37
+ // Free tier limit for route events
38
+ const FREE_TIER_EVENT_LIMIT = 3;
39
+ function RouteEventsModalWithTabs({
40
+ visible,
41
+ onClose,
42
+ onBack,
43
+ onMinimize,
44
+ enableSharedModalDimensions = false,
45
+ routeObserver = _RouteObserver.routeObserver
46
+ }) {
47
+ const router = (0, _expoRouter.useRouter)();
48
+ const [activeTab, setActiveTab] = (0, _react.useState)("events");
49
+ const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
50
+
51
+ // Check Pro status internally
52
+ const useIsPro = getUseIsPro();
53
+ const isPro = useIsPro();
54
+
55
+ // NOTE: Route tracking requires <RouteTracker /> to be placed inside the navigation tree.
56
+ // The useRouteObserver hook uses expo-router hooks that only work inside Stack/Tabs/Slot.
57
+ // See the RouteTracker component export for easy setup.
58
+
59
+ // Event Listener state
60
+ const [events, setEvents] = (0, _react.useState)([]);
61
+ const [isListening, setIsListening] = (0, _react.useState)(false);
62
+ const [showFilters, setShowFilters] = (0, _react.useState)(false);
63
+ const [searchQuery, setSearchQuery] = (0, _react.useState)("");
64
+ const [ignoredPatterns, setIgnoredPatterns] = (0, _react.useState)(new Set(["/_sitemap", "/api", "/__dev"]));
65
+ const lastEventRef = (0, _react.useRef)(null);
66
+ const hasLoadedFilters = (0, _react.useRef)(false);
67
+ const hasLoadedTabState = (0, _react.useRef)(false);
68
+ const hasLoadedMonitoringState = (0, _react.useRef)(false);
69
+ const handleModeChange = (0, _react.useCallback)(_mode => {
70
+ // Mode changes handled by JsModal
71
+ }, []);
72
+ const handleNavigate = (0, _react.useCallback)(pathname => {
73
+ try {
74
+ router.push(pathname);
75
+ } catch (error) {
76
+ _reactNative.Alert.alert("Navigation Error", String(error));
77
+ }
78
+ }, [router]);
79
+
80
+ // Load persisted tab state on mount
81
+ (0, _react.useEffect)(() => {
82
+ if (!visible || hasLoadedTabState.current) return;
83
+ const loadTabState = async () => {
84
+ try {
85
+ const storedTab = await (0, _sharedUi.safeGetItem)(_sharedUi.devToolsStorageKeys.routeEvents.activeTab());
86
+ if (storedTab && (storedTab === "routes" || storedTab === "events")) {
87
+ setActiveTab(storedTab);
88
+ }
89
+ hasLoadedTabState.current = true;
90
+ } catch (error) {
91
+ // Failed to load tab state
92
+ }
93
+ };
94
+ loadTabState();
95
+ }, [visible]);
96
+
97
+ // Load persisted monitoring state on mount
98
+ (0, _react.useEffect)(() => {
99
+ if (!visible || hasLoadedMonitoringState.current) return;
100
+ const loadMonitoringState = async () => {
101
+ try {
102
+ const storedMonitoring = await (0, _sharedUi.safeGetItem)(_sharedUi.devToolsStorageKeys.routeEvents.isMonitoring());
103
+ if (storedMonitoring !== null) {
104
+ const shouldMonitor = storedMonitoring === "true";
105
+ setIsListening(shouldMonitor);
106
+ }
107
+ hasLoadedMonitoringState.current = true;
108
+ } catch (error) {
109
+ // Failed to load monitoring state
110
+ }
111
+ };
112
+ loadMonitoringState();
113
+ }, [visible]);
114
+
115
+ // Save tab state when it changes
116
+ (0, _react.useEffect)(() => {
117
+ if (!hasLoadedTabState.current) return;
118
+ const saveTabState = async () => {
119
+ try {
120
+ await (0, _sharedUi.safeSetItem)(_sharedUi.devToolsStorageKeys.routeEvents.activeTab(), activeTab);
121
+ } catch (error) {
122
+ // Failed to save tab state
123
+ }
124
+ };
125
+ saveTabState();
126
+ }, [activeTab]);
127
+
128
+ // Save monitoring state when it changes
129
+ (0, _react.useEffect)(() => {
130
+ if (!hasLoadedMonitoringState.current) return;
131
+ const saveMonitoringState = async () => {
132
+ try {
133
+ await (0, _sharedUi.safeSetItem)(_sharedUi.devToolsStorageKeys.routeEvents.isMonitoring(), isListening.toString());
134
+ } catch (error) {
135
+ // Failed to save monitoring state
136
+ }
137
+ };
138
+ saveMonitoringState();
139
+ }, [isListening]);
140
+
141
+ // Load persisted filters on mount
142
+ (0, _react.useEffect)(() => {
143
+ if (!visible || hasLoadedFilters.current) return;
144
+ const loadFilters = async () => {
145
+ try {
146
+ const storedFilters = await (0, _sharedUi.safeGetItem)(_sharedUi.devToolsStorageKeys.routeEvents.eventFilters());
147
+ if (storedFilters) {
148
+ const filters = JSON.parse(storedFilters);
149
+ setIgnoredPatterns(new Set(filters));
150
+ }
151
+ hasLoadedFilters.current = true;
152
+ } catch (error) {
153
+ // Failed to load filters
154
+ }
155
+ };
156
+ loadFilters();
157
+ }, [visible]);
158
+
159
+ // Save filters when they change
160
+ (0, _react.useEffect)(() => {
161
+ if (!hasLoadedFilters.current) return;
162
+ const saveFilters = async () => {
163
+ try {
164
+ const filters = Array.from(ignoredPatterns);
165
+ await (0, _sharedUi.safeSetItem)(_sharedUi.devToolsStorageKeys.routeEvents.eventFilters(), JSON.stringify(filters));
166
+ } catch (error) {
167
+ // Failed to save filters
168
+ }
169
+ };
170
+ saveFilters();
171
+ }, [ignoredPatterns]);
172
+
173
+ // Event listener setup - keeps capturing even when minimized
174
+ (0, _react.useEffect)(() => {
175
+ if (!isListening) return;
176
+
177
+ // Set up event listener
178
+ const unsubscribe = routeObserver.addListener(event => {
179
+ lastEventRef.current = event;
180
+ setEvents(prev => {
181
+ const updated = [event, ...prev];
182
+ return updated.slice(0, 500);
183
+ });
184
+ });
185
+ return () => {
186
+ unsubscribe();
187
+ };
188
+ }, [isListening, routeObserver]);
189
+ const handleToggleListening = (0, _react.useCallback)(() => {
190
+ setIsListening(prev => !prev);
191
+ }, []);
192
+ const handleClearEvents = (0, _react.useCallback)(() => {
193
+ if (events.length === 0) return;
194
+
195
+ // Gate clear behind Pro
196
+ if (!isPro) {
197
+ setShowUpgradeModal(true);
198
+ return;
199
+ }
200
+ _reactNative.Alert.alert("Clear Events", `Clear ${events.length} event${events.length !== 1 ? "s" : ""}?`, [{
201
+ text: "Cancel",
202
+ style: "cancel"
203
+ }, {
204
+ text: "Clear",
205
+ style: "destructive",
206
+ onPress: () => setEvents([])
207
+ }]);
208
+ }, [events.length, isPro]);
209
+ const handleTogglePattern = (0, _react.useCallback)(pattern => {
210
+ setIgnoredPatterns(prev => {
211
+ const next = new Set(prev);
212
+ if (next.has(pattern)) {
213
+ next.delete(pattern);
214
+ } else {
215
+ next.add(pattern);
216
+ }
217
+ return next;
218
+ });
219
+ }, []);
220
+ const handleAddPattern = (0, _react.useCallback)(pattern => {
221
+ setIgnoredPatterns(prev => new Set([...prev, pattern]));
222
+ }, []);
223
+ const handleToggleFilters = (0, _react.useCallback)(() => {
224
+ setShowFilters(!showFilters);
225
+ }, [showFilters]);
226
+
227
+ // Get all unique pathnames from events
228
+ const allEventPathnames = (0, _react.useMemo)(() => {
229
+ const pathnames = new Set();
230
+ events.forEach(event => {
231
+ if (event.pathname) {
232
+ pathnames.add(event.pathname);
233
+ }
234
+ });
235
+ return Array.from(pathnames).sort();
236
+ }, [events]);
237
+
238
+ // Filter events based on ignored patterns and search query
239
+ const filteredEvents = (0, _react.useMemo)(() => {
240
+ return events.filter(event => {
241
+ if (!event.pathname) return false;
242
+
243
+ // Filter out pathnames that match ignored patterns
244
+ const shouldIgnore = Array.from(ignoredPatterns).some(pattern => event.pathname.includes(pattern));
245
+ if (shouldIgnore) return false;
246
+
247
+ // Filter by search query (search in pathname and param values)
248
+ if (searchQuery.trim()) {
249
+ const query = searchQuery.toLowerCase();
250
+ const pathnameMatch = event.pathname.toLowerCase().includes(query);
251
+ const paramsMatch = Object.entries(event.params || {}).some(([key, value]) => {
252
+ const valueStr = Array.isArray(value) ? value.join(" ") : value;
253
+ return key.toLowerCase().includes(query) || valueStr.toLowerCase().includes(query);
254
+ });
255
+ return pathnameMatch || paramsMatch;
256
+ }
257
+ return true;
258
+ });
259
+ }, [events, ignoredPatterns, searchQuery]);
260
+
261
+ // Limit visible events for free tier
262
+ const visibleEvents = (0, _react.useMemo)(() => {
263
+ if (isPro) return filteredEvents;
264
+ return filteredEvents.slice(0, FREE_TIER_EVENT_LIMIT);
265
+ }, [filteredEvents, isPro]);
266
+
267
+ // Calculate locked event count
268
+ const lockedEventCount = (0, _react.useMemo)(() => {
269
+ if (isPro) return 0;
270
+ return Math.max(0, filteredEvents.length - FREE_TIER_EVENT_LIMIT);
271
+ }, [filteredEvents.length, isPro]);
272
+
273
+ // Calculate visit counts for each route (for duplicate detection)
274
+ const visitCounts = (0, _react.useMemo)(() => {
275
+ const counts = new Map();
276
+ const eventVisitNumbers = new Map();
277
+
278
+ // Iterate from oldest to newest to assign visit numbers correctly
279
+ for (let i = events.length - 1; i >= 0; i--) {
280
+ const event = events[i];
281
+ const currentCount = counts.get(event.pathname) || 0;
282
+ const visitNumber = currentCount + 1;
283
+ counts.set(event.pathname, visitNumber);
284
+ eventVisitNumbers.set(i, visitNumber);
285
+ }
286
+ return eventVisitNumbers;
287
+ }, [events]);
288
+
289
+ // Prepare copy data - memoized so it only rebuilds when dependencies change
290
+ const copyAllEventsData = (0, _react.useMemo)(() => {
291
+ return {
292
+ summary: {
293
+ total: events.length,
294
+ filtered: filteredEvents.length,
295
+ listening: isListening,
296
+ ignoredPatterns: Array.from(ignoredPatterns),
297
+ timestamp: new Date().toISOString()
298
+ },
299
+ events: filteredEvents.map((event, index) => ({
300
+ index,
301
+ visitNumber: visitCounts.get(index) || 1,
302
+ pathname: event.pathname,
303
+ timestamp: event.timestamp,
304
+ timestampRelative: (0, _sharedUi.formatRelativeTime)(event.timestamp),
305
+ params: event.params,
306
+ segments: event.segments,
307
+ previousPathname: event.previousPathname,
308
+ timeSincePrevious: event.timeSincePrevious
309
+ }))
310
+ };
311
+ }, [events.length, filteredEvents, isListening, ignoredPatterns, visitCounts]);
312
+ if (!visible) return null;
313
+ const persistenceKey = enableSharedModalDimensions ? _sharedUi.devToolsStorageKeys.modal.root() : _sharedUi.devToolsStorageKeys.routeEvents.modal();
314
+
315
+ // No footer needed since we're showing a timeline
316
+ const footerNode = null;
317
+ const renderContent = () => {
318
+ if (activeTab === "routes") {
319
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_RoutesSitemap.RoutesSitemap, {
320
+ style: styles.contentWrapper
321
+ });
322
+ }
323
+ if (activeTab === "stack") {
324
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavigationStack.NavigationStack, {
325
+ style: styles.contentWrapper
326
+ });
327
+ }
328
+
329
+ // Events tab content
330
+ if (showFilters) {
331
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_RouteFilterViewV.RouteFilterViewV2, {
332
+ ignoredPatterns: ignoredPatterns,
333
+ onTogglePattern: handleTogglePattern,
334
+ onAddPattern: handleAddPattern,
335
+ availablePathnames: allEventPathnames
336
+ });
337
+ }
338
+
339
+ // Show search bar if there are events (even if filtered to 0)
340
+ const showSearchBar = events.length > 0;
341
+ if (filteredEvents.length === 0) {
342
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
343
+ style: styles.contentWrapper,
344
+ children: [showSearchBar && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
345
+ style: styles.searchContainer,
346
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SearchBar, {
347
+ value: searchQuery,
348
+ onChange: setSearchQuery,
349
+ placeholder: "Search pathname or params..."
350
+ })
351
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
352
+ style: styles.emptyState,
353
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Navigation, {
354
+ size: 48,
355
+ color: _sharedUi.buoyColors.textMuted
356
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
357
+ style: styles.emptyTitle,
358
+ children: searchQuery.trim() ? "No matching events" : isListening ? "No route events yet" : "Event listener is paused"
359
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
360
+ style: styles.emptySubtitle,
361
+ children: searchQuery.trim() ? "Try a different search term" : isListening ? "Navigation events will appear here" : "Press play to start monitoring"
362
+ })]
363
+ })]
364
+ });
365
+ }
366
+
367
+ // Show chronological timeline of events
368
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
369
+ style: styles.contentWrapper,
370
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
371
+ style: styles.searchContainer,
372
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SearchBar, {
373
+ value: searchQuery,
374
+ onChange: setSearchQuery,
375
+ placeholder: "Search pathname or params..."
376
+ })
377
+ }), lockedEventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
378
+ style: styles.lockedEventsBanner,
379
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
380
+ size: 14,
381
+ color: _sharedUi.buoyColors.warning
382
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
383
+ style: styles.lockedEventsBannerText,
384
+ children: [lockedEventCount, " older ", lockedEventCount === 1 ? 'event' : 'events', " locked"]
385
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
386
+ style: styles.lockedEventsBannerSubtext,
387
+ children: "Upgrade to Pro for full history"
388
+ })]
389
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_RouteEventsTimeline.RouteEventsTimeline, {
390
+ events: visibleEvents,
391
+ visitCounts: visitCounts,
392
+ onNavigate: handleNavigate
393
+ })]
394
+ });
395
+ };
396
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.JsModal, {
397
+ visible: visible,
398
+ onClose: onClose,
399
+ onMinimize: onMinimize,
400
+ persistenceKey: persistenceKey,
401
+ header: {
402
+ showToggleButton: true,
403
+ customContent: showFilters ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
404
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
405
+ onBack: () => setShowFilters(false)
406
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
407
+ title: "Filters"
408
+ })]
409
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
410
+ children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
411
+ onBack: onBack
412
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
413
+ title: "",
414
+ noMargin: true,
415
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
416
+ tabs: [{
417
+ key: "routes",
418
+ label: "Routes"
419
+ }, {
420
+ key: "events",
421
+ label: `Events${events.length > 0 && activeTab !== "events" ? ` (${events.length})` : ""}`
422
+ }, {
423
+ key: "stack",
424
+ label: "Stack"
425
+ }],
426
+ activeTab: activeTab,
427
+ onTabChange: tab => setActiveTab(tab)
428
+ })
429
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {
430
+ children: activeTab === "events" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
431
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
432
+ value: copyAllEventsData,
433
+ buttonStyle: styles.iconButton
434
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
435
+ onPress: handleToggleFilters,
436
+ style: [styles.iconButton, ignoredPatterns.size > 0 && styles.activeFilterButton],
437
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
438
+ size: 14,
439
+ color: ignoredPatterns.size > 0 ? _sharedUi.buoyColors.primary : _sharedUi.buoyColors.textSecondary
440
+ })
441
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
442
+ onPress: handleToggleListening,
443
+ style: [styles.iconButton, isListening && styles.activeButton],
444
+ children: isListening ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pause, {
445
+ size: 14,
446
+ color: _sharedUi.buoyColors.success
447
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Play, {
448
+ size: 14,
449
+ color: _sharedUi.buoyColors.success
450
+ })
451
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
452
+ onPress: handleClearEvents,
453
+ style: styles.iconButton,
454
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
455
+ size: 14,
456
+ color: _sharedUi.buoyColors.error
457
+ })
458
+ })]
459
+ })
460
+ })]
461
+ })
462
+ },
463
+ onModeChange: handleModeChange,
464
+ enablePersistence: true,
465
+ initialMode: "bottomSheet",
466
+ enableGlitchEffects: true,
467
+ styles: {},
468
+ footer: footerNode,
469
+ footerHeight: footerNode ? 68 : 0,
470
+ children: [renderContent(), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
471
+ visible: showUpgradeModal,
472
+ onClose: () => setShowUpgradeModal(false),
473
+ featureName: "Route Event Management"
474
+ })]
475
+ });
476
+ }
477
+ const styles = _reactNative.StyleSheet.create({
478
+ iconButton: {
479
+ padding: 6,
480
+ borderRadius: 6,
481
+ backgroundColor: _sharedUi.buoyColors.input
482
+ },
483
+ activeButton: {
484
+ backgroundColor: _sharedUi.buoyColors.success + "1A"
485
+ },
486
+ activeFilterButton: {
487
+ backgroundColor: _sharedUi.buoyColors.primary + "1A"
488
+ },
489
+ emptyState: {
490
+ flex: 1,
491
+ justifyContent: "center",
492
+ alignItems: "center",
493
+ padding: 32
494
+ },
495
+ emptyTitle: {
496
+ color: _sharedUi.buoyColors.text,
497
+ fontSize: 16,
498
+ fontWeight: "600",
499
+ marginTop: 16,
500
+ marginBottom: 8,
501
+ fontFamily: "monospace",
502
+ letterSpacing: 0.5,
503
+ textTransform: "uppercase"
504
+ },
505
+ emptySubtitle: {
506
+ color: _sharedUi.buoyColors.textSecondary,
507
+ fontSize: 14,
508
+ textAlign: "center",
509
+ fontFamily: "monospace"
510
+ },
511
+ contentWrapper: {
512
+ flex: 1
513
+ },
514
+ statsContainer: {
515
+ paddingHorizontal: 16,
516
+ paddingVertical: 8,
517
+ backgroundColor: _sharedUi.buoyColors.card,
518
+ borderBottomWidth: 1,
519
+ borderBottomColor: _sharedUi.buoyColors.border
520
+ },
521
+ statsText: {
522
+ fontSize: 11,
523
+ color: _sharedUi.buoyColors.textSecondary,
524
+ fontFamily: "monospace",
525
+ textAlign: "center"
526
+ },
527
+ statsWarning: {
528
+ color: _sharedUi.buoyColors.warning,
529
+ fontWeight: "600"
530
+ },
531
+ searchContainer: {
532
+ paddingHorizontal: 16,
533
+ paddingVertical: 8,
534
+ backgroundColor: _sharedUi.buoyColors.base
535
+ },
536
+ lockedEventsBanner: {
537
+ flexDirection: "row",
538
+ alignItems: "center",
539
+ gap: 8,
540
+ backgroundColor: _sharedUi.buoyColors.warning + "15",
541
+ borderWidth: 1,
542
+ borderColor: _sharedUi.buoyColors.warning + "30",
543
+ borderRadius: 8,
544
+ paddingVertical: 10,
545
+ paddingHorizontal: 14,
546
+ marginHorizontal: 16,
547
+ marginBottom: 8
548
+ },
549
+ lockedEventsBannerText: {
550
+ fontSize: 12,
551
+ fontWeight: "600",
552
+ color: _sharedUi.buoyColors.warning,
553
+ fontFamily: "monospace"
554
+ },
555
+ lockedEventsBannerSubtext: {
556
+ fontSize: 11,
557
+ color: _sharedUi.buoyColors.textSecondary,
558
+ marginLeft: "auto"
559
+ }
560
+ });
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RouteEventsTimeline = RouteEventsTimeline;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _RouteEventItemCompact = require("./RouteEventItemCompact");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ /**
13
+ * RouteEventsTimeline - Chronological timeline of route navigation events
14
+ *
15
+ * Shows events in the order they happened (most recent first),
16
+ * providing a clear history of navigation actions.
17
+ *
18
+ * Uses two-level expansion pattern matching network/storage components.
19
+ */
20
+
21
+ function RouteEventsTimeline({
22
+ events,
23
+ visitCounts,
24
+ onNavigate
25
+ }) {
26
+ const [expandedIndex, setExpandedIndex] = (0, _react.useState)(null);
27
+ const handleItemPress = (0, _react.useCallback)(index => {
28
+ setExpandedIndex(prev => prev === index ? null : index);
29
+ }, []);
30
+ if (events.length === 0) {
31
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
32
+ style: styles.emptyState,
33
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Navigation, {
34
+ size: 48,
35
+ color: _sharedUi.buoyColors.textMuted
36
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
37
+ style: styles.emptyTitle,
38
+ children: "No events yet"
39
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
40
+ style: styles.emptySubtitle,
41
+ children: "Navigation events will appear here"
42
+ })]
43
+ });
44
+ }
45
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
46
+ contentContainerStyle: styles.listContent,
47
+ children: events.map((event, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_RouteEventItemCompact.RouteEventItemCompact, {
48
+ event: event,
49
+ visitNumber: visitCounts.get(index) || 1,
50
+ isExpanded: expandedIndex === index,
51
+ onPress: () => handleItemPress(index),
52
+ onNavigate: onNavigate
53
+ }, `event-${index}-${event.timestamp}`))
54
+ });
55
+ }
56
+ const styles = _reactNative.StyleSheet.create({
57
+ listContent: {
58
+ paddingVertical: 8
59
+ },
60
+ emptyState: {
61
+ flex: 1,
62
+ justifyContent: "center",
63
+ alignItems: "center",
64
+ padding: 32
65
+ },
66
+ emptyTitle: {
67
+ color: _sharedUi.buoyColors.text,
68
+ fontSize: 16,
69
+ fontWeight: "600",
70
+ marginTop: 16,
71
+ marginBottom: 8,
72
+ fontFamily: "monospace",
73
+ letterSpacing: 0.5,
74
+ textTransform: "uppercase"
75
+ },
76
+ emptySubtitle: {
77
+ color: _sharedUi.buoyColors.textSecondary,
78
+ fontSize: 14,
79
+ textAlign: "center",
80
+ fontFamily: "monospace"
81
+ }
82
+ });
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RouteFilterViewV2 = RouteFilterViewV2;
7
+ var _sharedUi = require("@buoy-gg/shared-ui");
8
+ var _jsxRuntime = require("react/jsx-runtime");
9
+ function RouteFilterViewV2({
10
+ ignoredPatterns,
11
+ onTogglePattern,
12
+ onAddPattern,
13
+ availablePathnames = []
14
+ }) {
15
+ const filterConfig = {
16
+ addFilterSection: {
17
+ enabled: true,
18
+ placeholder: "Enter pattern (e.g., /_sitemap)",
19
+ title: "ACTIVE FILTERS",
20
+ icon: _sharedUi.Filter
21
+ },
22
+ availableItemsSection: {
23
+ enabled: true,
24
+ title: "AVAILABLE ROUTES FROM EVENTS",
25
+ emptyMessage: "No routes available. Routes from navigation events will appear here.",
26
+ items: availablePathnames
27
+ },
28
+ howItWorksSection: {
29
+ enabled: true,
30
+ title: "HOW FILTERS WORK",
31
+ description: "Filtered routes will not appear in the route events list. Patterns match if the route contains the specified text.",
32
+ examples: ["• /_sitemap → filters /_sitemap routes", "• /api → filters /api/users, /api/posts", "• [id] → filters all routes with [id] param"],
33
+ icon: _sharedUi.Filter
34
+ },
35
+ onPatternToggle: onTogglePattern,
36
+ onPatternAdd: onAddPattern,
37
+ activePatterns: ignoredPatterns
38
+ };
39
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.DynamicFilterView, {
40
+ ...filterConfig
41
+ });
42
+ }