@buoy-gg/route-events 2.1.2 → 2.1.3

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.
@@ -9,7 +9,7 @@ var _reactNative = require("react-native");
9
9
  var _expoRouter = require("expo-router");
10
10
  var _sharedUi = require("@buoy-gg/shared-ui");
11
11
  var _license = require("@buoy-gg/license");
12
- var _RouteObserver = require("../RouteObserver");
12
+ var _useRouteEvents = require("../hooks/useRouteEvents");
13
13
  var _RouteFilterViewV = require("./RouteFilterViewV2");
14
14
  var _RoutesSitemap = require("./RoutesSitemap");
15
15
  var _NavigationStack = require("./NavigationStack");
@@ -22,8 +22,7 @@ function RouteEventsModalWithTabs({
22
22
  onClose,
23
23
  onBack,
24
24
  onMinimize,
25
- enableSharedModalDimensions = false,
26
- routeObserver = _RouteObserver.routeObserver
25
+ enableSharedModalDimensions = false
27
26
  }) {
28
27
  const router = (0, _expoRouter.useRouter)();
29
28
  const [activeTab, setActiveTab] = (0, _react.useState)("events");
@@ -43,9 +42,21 @@ function RouteEventsModalWithTabs({
43
42
  // The useRouteObserver hook uses expo-router hooks that only work inside Stack/Tabs/Slot.
44
43
  // See the RouteTracker component export for easy setup.
45
44
 
46
- // Event Listener state
47
- const [events, setEvents] = (0, _react.useState)([]);
48
- const [isListening, setIsListening] = (0, _react.useState)(false);
45
+ // Local state to control subscription (true = subscribed, false = unsubscribed)
46
+ const [isListeningEnabled, setIsListeningEnabled] = (0, _react.useState)(true);
47
+
48
+ // Event Listener state - using centralized store via hook
49
+ // Pass enabled option to control subscription
50
+ const {
51
+ events,
52
+ clearEvents: clearRouteEvents,
53
+ isListening: isStoreListening
54
+ } = (0, _useRouteEvents.useRouteEvents)({
55
+ enabled: isListeningEnabled
56
+ });
57
+
58
+ // isListening = enabled and store is listening
59
+ const isListening = isListeningEnabled && isStoreListening;
49
60
  const [showFilters, setShowFilters] = (0, _react.useState)(false);
50
61
  const [searchQuery, setSearchQuery] = (0, _react.useState)("");
51
62
  const [ignoredPatterns, setIgnoredPatterns] = (0, _react.useState)(new Set(["/_sitemap", "/api", "/__dev"]));
@@ -89,7 +100,7 @@ function RouteEventsModalWithTabs({
89
100
  const storedMonitoring = await _sharedUi.persistentStorage.getItem(_sharedUi.devToolsStorageKeys.routeEvents.isMonitoring());
90
101
  if (storedMonitoring !== null) {
91
102
  const shouldMonitor = storedMonitoring === "true";
92
- setIsListening(shouldMonitor);
103
+ setIsListeningEnabled(shouldMonitor);
93
104
  }
94
105
  hasLoadedMonitoringState.current = true;
95
106
  } catch (error) {
@@ -157,24 +168,11 @@ function RouteEventsModalWithTabs({
157
168
  saveFilters();
158
169
  }, [ignoredPatterns]);
159
170
 
160
- // Event listener setup - keeps capturing even when minimized
161
- (0, _react.useEffect)(() => {
162
- if (!isListening) return;
171
+ // Event listener is now managed by useRouteEvents hook
163
172
 
164
- // Set up event listener
165
- const unsubscribe = routeObserver.addListener(event => {
166
- lastEventRef.current = event;
167
- setEvents(prev => {
168
- const updated = [event, ...prev];
169
- return updated.slice(0, 500);
170
- });
171
- });
172
- return () => {
173
- unsubscribe();
174
- };
175
- }, [isListening, routeObserver]);
176
173
  const handleToggleListening = (0, _react.useCallback)(() => {
177
- setIsListening(prev => !prev);
174
+ // Toggle subscription - this actually subscribes/unsubscribes from the store
175
+ setIsListeningEnabled(prev => !prev);
178
176
  }, []);
179
177
  const handleClearEvents = (0, _react.useCallback)(() => {
180
178
  if (events.length === 0) return;
@@ -184,15 +182,8 @@ function RouteEventsModalWithTabs({
184
182
  setShowUpgradeModal(true);
185
183
  return;
186
184
  }
187
- _reactNative.Alert.alert("Clear Events", `Clear ${events.length} event${events.length !== 1 ? "s" : ""}?`, [{
188
- text: "Cancel",
189
- style: "cancel"
190
- }, {
191
- text: "Clear",
192
- style: "destructive",
193
- onPress: () => setEvents([])
194
- }]);
195
- }, [events.length, isPro]);
185
+ clearRouteEvents();
186
+ }, [events.length, isPro, clearRouteEvents]);
196
187
  const handleTogglePattern = (0, _react.useCallback)(pattern => {
197
188
  setIgnoredPatterns(prev => {
198
189
  const next = new Set(prev);
@@ -425,16 +416,10 @@ function RouteEventsModalWithTabs({
425
416
  size: 14,
426
417
  color: ignoredPatterns.size > 0 ? _sharedUi.buoyColors.primary : _sharedUi.buoyColors.textSecondary
427
418
  })
428
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
429
- onPress: handleToggleListening,
430
- style: [styles.iconButton, isListening && styles.activeButton],
431
- children: isListening ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pause, {
432
- size: 14,
433
- color: _sharedUi.buoyColors.success
434
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Play, {
435
- size: 14,
436
- color: _sharedUi.buoyColors.success
437
- })
419
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
420
+ isEnabled: isListening,
421
+ onToggle: handleToggleListening,
422
+ accessibilityLabel: "Toggle route event monitoring"
438
423
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
439
424
  onPress: handleClearEvents,
440
425
  style: styles.iconButton,
@@ -467,9 +452,6 @@ const styles = _reactNative.StyleSheet.create({
467
452
  borderRadius: 6,
468
453
  backgroundColor: _sharedUi.buoyColors.input
469
454
  },
470
- activeButton: {
471
- backgroundColor: _sharedUi.buoyColors.success + "1A"
472
- },
473
455
  activeFilterButton: {
474
456
  backgroundColor: _sharedUi.buoyColors.primary + "1A"
475
457
  },
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useRouteEvents = useRouteEvents;
7
+ var _react = require("react");
8
+ var _routeEventStore = require("../stores/routeEventStore");
9
+ /**
10
+ * useRouteEvents Hook
11
+ *
12
+ * React hook for subscribing to route events from the centralized store.
13
+ * Uses self-managing Subscribable pattern - the store automatically
14
+ * subscribes to routeObserver when this hook mounts and unsubscribes
15
+ * when all subscribers unmount.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * function MyComponent() {
20
+ * const { events, clearEvents, isListening } = useRouteEvents();
21
+ *
22
+ * return (
23
+ * <View>
24
+ * {events.map((event) => (
25
+ * <Text key={event.timestamp.toString()}>
26
+ * {event.pathname}
27
+ * </Text>
28
+ * ))}
29
+ * </View>
30
+ * );
31
+ * }
32
+ * ```
33
+ */
34
+
35
+ // Re-export RouteChangeEvent for convenience
36
+
37
+ function useRouteEvents(options = {}) {
38
+ const {
39
+ maxEvents = 500,
40
+ enabled = true
41
+ } = options;
42
+ const [events, setEvents] = (0, _react.useState)([]);
43
+ // Track listening state locally so it updates reactively with the subscription
44
+ const [isListening, setIsListening] = (0, _react.useState)(enabled);
45
+
46
+ // Subscribe to store - this automatically starts/stops listening
47
+ // based on subscriber count (Subscribable pattern from TanStack Query)
48
+ // Only subscribe when enabled is true
49
+ (0, _react.useEffect)(() => {
50
+ if (!enabled) {
51
+ setIsListening(false);
52
+ return;
53
+ }
54
+ const unsubscribe = _routeEventStore.routeEventStore.subscribeToEvents(allEvents => {
55
+ // Limit events
56
+ if (allEvents.length > maxEvents) {
57
+ setEvents(allEvents.slice(0, maxEvents));
58
+ } else {
59
+ setEvents(allEvents);
60
+ }
61
+ });
62
+
63
+ // Mark as listening after successful subscription
64
+ setIsListening(true);
65
+ return () => {
66
+ unsubscribe();
67
+ // Note: Don't set isListening=false here because we might be
68
+ // re-subscribing due to dependency change, not actually stopping
69
+ };
70
+ }, [maxEvents, enabled]);
71
+ const clearEvents = (0, _react.useCallback)(() => {
72
+ _routeEventStore.routeEventStore.clearEvents();
73
+ }, []);
74
+ return {
75
+ events,
76
+ clearEvents,
77
+ isListening
78
+ };
79
+ }
@@ -57,6 +57,12 @@ Object.defineProperty(exports, "createRouteEventsTool", {
57
57
  return _preset.createRouteEventsTool;
58
58
  }
59
59
  });
60
+ Object.defineProperty(exports, "routeEventStore", {
61
+ enumerable: true,
62
+ get: function () {
63
+ return _routeEventStore.routeEventStore;
64
+ }
65
+ });
60
66
  Object.defineProperty(exports, "routeEventsToolPreset", {
61
67
  enumerable: true,
62
68
  get: function () {
@@ -87,6 +93,12 @@ Object.defineProperty(exports, "useRoute", {
87
93
  return _useRouteSitemap.useRoute;
88
94
  }
89
95
  });
96
+ Object.defineProperty(exports, "useRouteEvents", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _useRouteEvents.useRouteEvents;
100
+ }
101
+ });
90
102
  Object.defineProperty(exports, "useRouteObserver", {
91
103
  enumerable: true,
92
104
  get: function () {
@@ -107,7 +119,9 @@ var _NavigationStack = require("./components/NavigationStack");
107
119
  var _RouteEventItemCompact = require("./components/RouteEventItemCompact");
108
120
  var _RouteEventExpandedContent = require("./components/RouteEventExpandedContent");
109
121
  var _useRouteObserver = require("./useRouteObserver");
122
+ var _useRouteEvents = require("./hooks/useRouteEvents");
110
123
  var _useRouteSitemap = require("./useRouteSitemap");
111
124
  var _useNavigationStack = require("./useNavigationStack");
112
125
  var _RouteParser = require("./RouteParser");
113
- var _RouteObserver = require("./RouteObserver");
126
+ var _RouteObserver = require("./RouteObserver");
127
+ var _routeEventStore = require("./stores/routeEventStore");
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.subscribeToRouteEvents = exports.routeEventStore = exports.onRouteEvent = exports.isRouteListening = exports.getRouteEvents = exports.clearRouteEvents = void 0;
7
+ var _sharedUi = require("@buoy-gg/shared-ui");
8
+ var _RouteObserver = require("../RouteObserver");
9
+ /**
10
+ * Route Event Store
11
+ *
12
+ * Centralized store that aggregates route change events from the RouteObserver.
13
+ * Uses BaseEventStore pattern - the store automatically subscribes to routeObserver
14
+ * when the first subscriber joins and unsubscribes when the last subscriber leaves.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import { routeEventStore } from '@buoy-gg/route-events';
19
+ *
20
+ * // Subscribe to route events - automatically starts listening!
21
+ * const unsubscribe = routeEventStore.subscribeToEvents((events) => {
22
+ * console.log('Route events:', events);
23
+ * });
24
+ *
25
+ * // Later, clean up - automatically stops if no other subscribers
26
+ * unsubscribe();
27
+ * ```
28
+ */
29
+
30
+ // Re-export types for convenience
31
+
32
+ /**
33
+ * Listener callback type for route events array
34
+ */
35
+
36
+ /**
37
+ * Listener callback for individual new events
38
+ */
39
+
40
+ class RouteEventStore extends _sharedUi.BaseEventStore {
41
+ // Unsubscribe function for routeObserver
42
+ routeObserverUnsubscribe = null;
43
+ constructor() {
44
+ super({
45
+ storeName: "route-events",
46
+ maxEvents: 500
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Start listening to route changes from routeObserver
52
+ */
53
+ startCapturing() {
54
+ if (!this.routeObserverUnsubscribe) {
55
+ this.routeObserverUnsubscribe = _RouteObserver.routeObserver.addListener(event => {
56
+ this.addEvent(event);
57
+ });
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Stop listening to route changes
63
+ */
64
+ stopCapturing() {
65
+ if (this.routeObserverUnsubscribe) {
66
+ this.routeObserverUnsubscribe();
67
+ this.routeObserverUnsubscribe = null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Check if currently listening (has subscribers)
73
+ */
74
+ isCapturing() {
75
+ return this.routeObserverUnsubscribe !== null;
76
+ }
77
+
78
+ // Alias for backwards compatibility
79
+ isListening() {
80
+ return this.isCapturing();
81
+ }
82
+ }
83
+
84
+ // Singleton instance
85
+ const routeEventStore = exports.routeEventStore = new RouteEventStore();
86
+
87
+ // Convenience exports
88
+ const subscribeToRouteEvents = listener => routeEventStore.subscribeToEvents(listener);
89
+ exports.subscribeToRouteEvents = subscribeToRouteEvents;
90
+ const onRouteEvent = callback => routeEventStore.onEvent(callback);
91
+ exports.onRouteEvent = onRouteEvent;
92
+ const getRouteEvents = () => routeEventStore.getEvents();
93
+ exports.getRouteEvents = getRouteEvents;
94
+ const clearRouteEvents = () => routeEventStore.clearEvents();
95
+ exports.clearRouteEvents = clearRouteEvents;
96
+ const isRouteListening = () => routeEventStore.isListening();
97
+ exports.isRouteListening = isRouteListening;
@@ -3,12 +3,12 @@
3
3
  import { useState, useCallback, useEffect, useRef, useMemo } from "react";
4
4
  import { Text, View, TouchableOpacity, StyleSheet, Alert } from "react-native";
5
5
  import { useRouter } from "expo-router";
6
- import { JsModal, ModalHeader, TabSelector, formatRelativeTime, devToolsStorageKeys, Navigation, Pause, Play, Trash2, Filter, SearchBar, persistentStorage, ToolbarCopyButton, Lock, ProUpgradeModal, buoyColors } from "@buoy-gg/shared-ui";
6
+ import { JsModal, ModalHeader, TabSelector, formatRelativeTime, devToolsStorageKeys, Navigation, Trash2, Filter, SearchBar, persistentStorage, ToolbarCopyButton, Lock, ProUpgradeModal, PowerToggleButton, buoyColors } from "@buoy-gg/shared-ui";
7
7
  import { useIsPro } from "@buoy-gg/license";
8
8
 
9
9
  // Free tier limit for route events
10
10
  const FREE_TIER_EVENT_LIMIT = 3;
11
- import { routeObserver as defaultRouteObserver } from "../RouteObserver";
11
+ import { useRouteEvents } from "../hooks/useRouteEvents";
12
12
  import { RouteFilterViewV2 } from "./RouteFilterViewV2";
13
13
  import { RoutesSitemap } from "./RoutesSitemap";
14
14
  import { NavigationStack } from "./NavigationStack";
@@ -19,8 +19,7 @@ export function RouteEventsModalWithTabs({
19
19
  onClose,
20
20
  onBack,
21
21
  onMinimize,
22
- enableSharedModalDimensions = false,
23
- routeObserver = defaultRouteObserver
22
+ enableSharedModalDimensions = false
24
23
  }) {
25
24
  const router = useRouter();
26
25
  const [activeTab, setActiveTab] = useState("events");
@@ -40,9 +39,21 @@ export function RouteEventsModalWithTabs({
40
39
  // The useRouteObserver hook uses expo-router hooks that only work inside Stack/Tabs/Slot.
41
40
  // See the RouteTracker component export for easy setup.
42
41
 
43
- // Event Listener state
44
- const [events, setEvents] = useState([]);
45
- const [isListening, setIsListening] = useState(false);
42
+ // Local state to control subscription (true = subscribed, false = unsubscribed)
43
+ const [isListeningEnabled, setIsListeningEnabled] = useState(true);
44
+
45
+ // Event Listener state - using centralized store via hook
46
+ // Pass enabled option to control subscription
47
+ const {
48
+ events,
49
+ clearEvents: clearRouteEvents,
50
+ isListening: isStoreListening
51
+ } = useRouteEvents({
52
+ enabled: isListeningEnabled
53
+ });
54
+
55
+ // isListening = enabled and store is listening
56
+ const isListening = isListeningEnabled && isStoreListening;
46
57
  const [showFilters, setShowFilters] = useState(false);
47
58
  const [searchQuery, setSearchQuery] = useState("");
48
59
  const [ignoredPatterns, setIgnoredPatterns] = useState(new Set(["/_sitemap", "/api", "/__dev"]));
@@ -86,7 +97,7 @@ export function RouteEventsModalWithTabs({
86
97
  const storedMonitoring = await persistentStorage.getItem(devToolsStorageKeys.routeEvents.isMonitoring());
87
98
  if (storedMonitoring !== null) {
88
99
  const shouldMonitor = storedMonitoring === "true";
89
- setIsListening(shouldMonitor);
100
+ setIsListeningEnabled(shouldMonitor);
90
101
  }
91
102
  hasLoadedMonitoringState.current = true;
92
103
  } catch (error) {
@@ -154,24 +165,11 @@ export function RouteEventsModalWithTabs({
154
165
  saveFilters();
155
166
  }, [ignoredPatterns]);
156
167
 
157
- // Event listener setup - keeps capturing even when minimized
158
- useEffect(() => {
159
- if (!isListening) return;
168
+ // Event listener is now managed by useRouteEvents hook
160
169
 
161
- // Set up event listener
162
- const unsubscribe = routeObserver.addListener(event => {
163
- lastEventRef.current = event;
164
- setEvents(prev => {
165
- const updated = [event, ...prev];
166
- return updated.slice(0, 500);
167
- });
168
- });
169
- return () => {
170
- unsubscribe();
171
- };
172
- }, [isListening, routeObserver]);
173
170
  const handleToggleListening = useCallback(() => {
174
- setIsListening(prev => !prev);
171
+ // Toggle subscription - this actually subscribes/unsubscribes from the store
172
+ setIsListeningEnabled(prev => !prev);
175
173
  }, []);
176
174
  const handleClearEvents = useCallback(() => {
177
175
  if (events.length === 0) return;
@@ -181,15 +179,8 @@ export function RouteEventsModalWithTabs({
181
179
  setShowUpgradeModal(true);
182
180
  return;
183
181
  }
184
- Alert.alert("Clear Events", `Clear ${events.length} event${events.length !== 1 ? "s" : ""}?`, [{
185
- text: "Cancel",
186
- style: "cancel"
187
- }, {
188
- text: "Clear",
189
- style: "destructive",
190
- onPress: () => setEvents([])
191
- }]);
192
- }, [events.length, isPro]);
182
+ clearRouteEvents();
183
+ }, [events.length, isPro, clearRouteEvents]);
193
184
  const handleTogglePattern = useCallback(pattern => {
194
185
  setIgnoredPatterns(prev => {
195
186
  const next = new Set(prev);
@@ -422,16 +413,10 @@ export function RouteEventsModalWithTabs({
422
413
  size: 14,
423
414
  color: ignoredPatterns.size > 0 ? buoyColors.primary : buoyColors.textSecondary
424
415
  })
425
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
426
- onPress: handleToggleListening,
427
- style: [styles.iconButton, isListening && styles.activeButton],
428
- children: isListening ? /*#__PURE__*/_jsx(Pause, {
429
- size: 14,
430
- color: buoyColors.success
431
- }) : /*#__PURE__*/_jsx(Play, {
432
- size: 14,
433
- color: buoyColors.success
434
- })
416
+ }), /*#__PURE__*/_jsx(PowerToggleButton, {
417
+ isEnabled: isListening,
418
+ onToggle: handleToggleListening,
419
+ accessibilityLabel: "Toggle route event monitoring"
435
420
  }), /*#__PURE__*/_jsx(TouchableOpacity, {
436
421
  onPress: handleClearEvents,
437
422
  style: styles.iconButton,
@@ -464,9 +449,6 @@ const styles = StyleSheet.create({
464
449
  borderRadius: 6,
465
450
  backgroundColor: buoyColors.input
466
451
  },
467
- activeButton: {
468
- backgroundColor: buoyColors.success + "1A"
469
- },
470
452
  activeFilterButton: {
471
453
  backgroundColor: buoyColors.primary + "1A"
472
454
  },
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * useRouteEvents Hook
5
+ *
6
+ * React hook for subscribing to route events from the centralized store.
7
+ * Uses self-managing Subscribable pattern - the store automatically
8
+ * subscribes to routeObserver when this hook mounts and unsubscribes
9
+ * when all subscribers unmount.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * function MyComponent() {
14
+ * const { events, clearEvents, isListening } = useRouteEvents();
15
+ *
16
+ * return (
17
+ * <View>
18
+ * {events.map((event) => (
19
+ * <Text key={event.timestamp.toString()}>
20
+ * {event.pathname}
21
+ * </Text>
22
+ * ))}
23
+ * </View>
24
+ * );
25
+ * }
26
+ * ```
27
+ */
28
+
29
+ import { useState, useEffect, useCallback } from "react";
30
+ import { routeEventStore } from "../stores/routeEventStore";
31
+
32
+ // Re-export RouteChangeEvent for convenience
33
+
34
+ export function useRouteEvents(options = {}) {
35
+ const {
36
+ maxEvents = 500,
37
+ enabled = true
38
+ } = options;
39
+ const [events, setEvents] = useState([]);
40
+ // Track listening state locally so it updates reactively with the subscription
41
+ const [isListening, setIsListening] = useState(enabled);
42
+
43
+ // Subscribe to store - this automatically starts/stops listening
44
+ // based on subscriber count (Subscribable pattern from TanStack Query)
45
+ // Only subscribe when enabled is true
46
+ useEffect(() => {
47
+ if (!enabled) {
48
+ setIsListening(false);
49
+ return;
50
+ }
51
+ const unsubscribe = routeEventStore.subscribeToEvents(allEvents => {
52
+ // Limit events
53
+ if (allEvents.length > maxEvents) {
54
+ setEvents(allEvents.slice(0, maxEvents));
55
+ } else {
56
+ setEvents(allEvents);
57
+ }
58
+ });
59
+
60
+ // Mark as listening after successful subscription
61
+ setIsListening(true);
62
+ return () => {
63
+ unsubscribe();
64
+ // Note: Don't set isListening=false here because we might be
65
+ // re-subscribing due to dependency change, not actually stopping
66
+ };
67
+ }, [maxEvents, enabled]);
68
+ const clearEvents = useCallback(() => {
69
+ routeEventStore.clearEvents();
70
+ }, []);
71
+ return {
72
+ events,
73
+ clearEvents,
74
+ isListening
75
+ };
76
+ }
@@ -34,6 +34,7 @@ export { RouteEventExpandedContent } from "./components/RouteEventExpandedConten
34
34
  // HOOKS (For consuming route data)
35
35
  // =============================================================================
36
36
  export { useRouteObserver } from "./useRouteObserver";
37
+ export { useRouteEvents } from "./hooks/useRouteEvents";
37
38
  export { useRouteSitemap, useRoute, useParentRoutes } from "./useRouteSitemap";
38
39
  export { useNavigationStack } from "./useNavigationStack";
39
40
 
@@ -51,4 +52,6 @@ export { RouteObserver } from "./RouteObserver";
51
52
  // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
52
53
  // =============================================================================
53
54
  /** @internal - Singleton instance for cross-package communication */
54
- export { routeObserver } from "./RouteObserver";
55
+ export { routeObserver } from "./RouteObserver";
56
+ /** @internal - Event store for centralized route event management */
57
+ export { routeEventStore } from "./stores/routeEventStore";
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Route Event Store
5
+ *
6
+ * Centralized store that aggregates route change events from the RouteObserver.
7
+ * Uses BaseEventStore pattern - the store automatically subscribes to routeObserver
8
+ * when the first subscriber joins and unsubscribes when the last subscriber leaves.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { routeEventStore } from '@buoy-gg/route-events';
13
+ *
14
+ * // Subscribe to route events - automatically starts listening!
15
+ * const unsubscribe = routeEventStore.subscribeToEvents((events) => {
16
+ * console.log('Route events:', events);
17
+ * });
18
+ *
19
+ * // Later, clean up - automatically stops if no other subscribers
20
+ * unsubscribe();
21
+ * ```
22
+ */
23
+
24
+ import { BaseEventStore } from "@buoy-gg/shared-ui";
25
+ import { routeObserver } from "../RouteObserver";
26
+
27
+ // Re-export types for convenience
28
+
29
+ /**
30
+ * Listener callback type for route events array
31
+ */
32
+
33
+ /**
34
+ * Listener callback for individual new events
35
+ */
36
+
37
+ class RouteEventStore extends BaseEventStore {
38
+ // Unsubscribe function for routeObserver
39
+ routeObserverUnsubscribe = null;
40
+ constructor() {
41
+ super({
42
+ storeName: "route-events",
43
+ maxEvents: 500
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Start listening to route changes from routeObserver
49
+ */
50
+ startCapturing() {
51
+ if (!this.routeObserverUnsubscribe) {
52
+ this.routeObserverUnsubscribe = routeObserver.addListener(event => {
53
+ this.addEvent(event);
54
+ });
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Stop listening to route changes
60
+ */
61
+ stopCapturing() {
62
+ if (this.routeObserverUnsubscribe) {
63
+ this.routeObserverUnsubscribe();
64
+ this.routeObserverUnsubscribe = null;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Check if currently listening (has subscribers)
70
+ */
71
+ isCapturing() {
72
+ return this.routeObserverUnsubscribe !== null;
73
+ }
74
+
75
+ // Alias for backwards compatibility
76
+ isListening() {
77
+ return this.isCapturing();
78
+ }
79
+ }
80
+
81
+ // Singleton instance
82
+ export const routeEventStore = new RouteEventStore();
83
+
84
+ // Convenience exports
85
+ export const subscribeToRouteEvents = listener => routeEventStore.subscribeToEvents(listener);
86
+ export const onRouteEvent = callback => routeEventStore.onEvent(callback);
87
+ export const getRouteEvents = () => routeEventStore.getEvents();
88
+ export const clearRouteEvents = () => routeEventStore.clearEvents();
89
+ export const isRouteListening = () => routeEventStore.isListening();
@@ -1,15 +1,9 @@
1
- import { routeObserver as defaultRouteObserver } from "../RouteObserver";
2
1
  export interface RouteEventsModalWithTabsProps {
3
2
  visible: boolean;
4
3
  onClose: () => void;
5
4
  onBack?: () => void;
6
5
  onMinimize?: (modalState: any) => void;
7
6
  enableSharedModalDimensions?: boolean;
8
- /**
9
- * Optional route observer instance. If not provided, uses the default singleton.
10
- * Route tracking will start automatically when the modal is opened.
11
- */
12
- routeObserver?: typeof defaultRouteObserver;
13
7
  }
14
- export declare function RouteEventsModalWithTabs({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, routeObserver, }: RouteEventsModalWithTabsProps): import("react").JSX.Element | null;
8
+ export declare function RouteEventsModalWithTabs({ visible, onClose, onBack, onMinimize, enableSharedModalDimensions, }: RouteEventsModalWithTabsProps): import("react").JSX.Element | null;
15
9
  //# sourceMappingURL=RouteEventsModalWithTabs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RouteEventsModalWithTabs.d.ts","sourceRoot":"","sources":["../../../src/components/RouteEventsModalWithTabs.tsx"],"names":[],"mappings":"AAmCA,OAAO,EACL,aAAa,IAAI,oBAAoB,EAEtC,MAAM,kBAAkB,CAAC;AAU1B,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,oBAAoB,CAAC;CAC7C;AAID,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,aAAoC,GACrC,EAAE,6BAA6B,sCA4hB/B"}
1
+ {"version":3,"file":"RouteEventsModalWithTabs.d.ts","sourceRoot":"","sources":["../../../src/components/RouteEventsModalWithTabs.tsx"],"names":[],"mappings":"AA8CA,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAID,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,GACpC,EAAE,6BAA6B,sCAigB/B"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * useRouteEvents Hook
3
+ *
4
+ * React hook for subscribing to route events from the centralized store.
5
+ * Uses self-managing Subscribable pattern - the store automatically
6
+ * subscribes to routeObserver when this hook mounts and unsubscribes
7
+ * when all subscribers unmount.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * function MyComponent() {
12
+ * const { events, clearEvents, isListening } = useRouteEvents();
13
+ *
14
+ * return (
15
+ * <View>
16
+ * {events.map((event) => (
17
+ * <Text key={event.timestamp.toString()}>
18
+ * {event.pathname}
19
+ * </Text>
20
+ * ))}
21
+ * </View>
22
+ * );
23
+ * }
24
+ * ```
25
+ */
26
+ import { type RouteChangeEvent } from "../stores/routeEventStore";
27
+ export type { RouteChangeEvent } from "../stores/routeEventStore";
28
+ export interface UseRouteEventsOptions {
29
+ /** Maximum number of events to keep in state */
30
+ maxEvents?: number;
31
+ /** Whether to subscribe to events (default: true). Set to false to unsubscribe. */
32
+ enabled?: boolean;
33
+ }
34
+ export interface UseRouteEventsResult {
35
+ /** All route events (newest first) */
36
+ events: RouteChangeEvent[];
37
+ /** Clear all events */
38
+ clearEvents: () => void;
39
+ /** Whether events are being listened to (has subscribers) */
40
+ isListening: boolean;
41
+ }
42
+ export declare function useRouteEvents(options?: UseRouteEventsOptions): UseRouteEventsResult;
43
+ //# sourceMappingURL=useRouteEvents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRouteEvents.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouteEvents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,MAAM,WAAW,qBAAqB;IACpC,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,sCAAsC;IACtC,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,uBAAuB;IACvB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,cAAc,CAC5B,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CA4CtB"}
@@ -19,6 +19,8 @@ export type { RouteEventItemCompactProps } from "./components/RouteEventItemComp
19
19
  export { RouteEventExpandedContent } from "./components/RouteEventExpandedContent";
20
20
  export type { RouteEventExpandedContentProps } from "./components/RouteEventExpandedContent";
21
21
  export { useRouteObserver } from "./useRouteObserver";
22
+ export { useRouteEvents } from "./hooks/useRouteEvents";
23
+ export type { UseRouteEventsOptions, UseRouteEventsResult, } from "./hooks/useRouteEvents";
22
24
  export { useRouteSitemap, useRoute, useParentRoutes } from "./useRouteSitemap";
23
25
  export type { UseRouteSitemapOptions, UseRouteSitemapResult, } from "./useRouteSitemap";
24
26
  export { useNavigationStack } from "./useNavigationStack";
@@ -29,4 +31,7 @@ export { RouteParser } from "./RouteParser";
29
31
  export { RouteObserver } from "./RouteObserver";
30
32
  /** @internal - Singleton instance for cross-package communication */
31
33
  export { routeObserver } from "./RouteObserver";
34
+ /** @internal - Event store for centralized route event management */
35
+ export { routeEventStore } from "./stores/routeEventStore";
36
+ export type { RouteEventListener, RouteEventCallback, } from "./stores/routeEventStore";
32
37
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAKxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,YAAY,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AAK3F,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAK7F,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAK9B,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,eAAe,CAAC;AAKvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKhD,qEAAqE;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAKxE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjF,YAAY,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AAK3F,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AACrF,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AAK7F,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAK9B,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,eAAe,CAAC;AAKvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKhD,qEAAqE;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,qEAAqE;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,YAAY,EACV,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Route Event Store
3
+ *
4
+ * Centralized store that aggregates route change events from the RouteObserver.
5
+ * Uses BaseEventStore pattern - the store automatically subscribes to routeObserver
6
+ * when the first subscriber joins and unsubscribes when the last subscriber leaves.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { routeEventStore } from '@buoy-gg/route-events';
11
+ *
12
+ * // Subscribe to route events - automatically starts listening!
13
+ * const unsubscribe = routeEventStore.subscribeToEvents((events) => {
14
+ * console.log('Route events:', events);
15
+ * });
16
+ *
17
+ * // Later, clean up - automatically stops if no other subscribers
18
+ * unsubscribe();
19
+ * ```
20
+ */
21
+ import { BaseEventStore } from "@buoy-gg/shared-ui";
22
+ import { type RouteChangeEvent } from "../RouteObserver";
23
+ export type { RouteChangeEvent } from "../RouteObserver";
24
+ /**
25
+ * Listener callback type for route events array
26
+ */
27
+ export type RouteEventListener = (events: RouteChangeEvent[]) => void;
28
+ /**
29
+ * Listener callback for individual new events
30
+ */
31
+ export type RouteEventCallback = (event: RouteChangeEvent) => void;
32
+ declare class RouteEventStore extends BaseEventStore<RouteChangeEvent> {
33
+ private routeObserverUnsubscribe;
34
+ constructor();
35
+ /**
36
+ * Start listening to route changes from routeObserver
37
+ */
38
+ protected startCapturing(): void;
39
+ /**
40
+ * Stop listening to route changes
41
+ */
42
+ protected stopCapturing(): void;
43
+ /**
44
+ * Check if currently listening (has subscribers)
45
+ */
46
+ isCapturing(): boolean;
47
+ isListening(): boolean;
48
+ }
49
+ export declare const routeEventStore: RouteEventStore;
50
+ export declare const subscribeToRouteEvents: (listener: RouteEventListener) => () => void;
51
+ export declare const onRouteEvent: (callback: RouteEventCallback) => () => void;
52
+ export declare const getRouteEvents: () => RouteChangeEvent[];
53
+ export declare const clearRouteEvents: () => void;
54
+ export declare const isRouteListening: () => boolean;
55
+ //# sourceMappingURL=routeEventStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeEventStore.d.ts","sourceRoot":"","sources":["../../../src/stores/routeEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGxE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;AAEtE;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEnE,cAAM,eAAgB,SAAQ,cAAc,CAAC,gBAAgB,CAAC;IAE5D,OAAO,CAAC,wBAAwB,CAA6B;;IAS7D;;OAEG;IACH,SAAS,CAAC,cAAc,IAAI,IAAI;IAQhC;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI;IAO/B;;OAEG;IACH,WAAW,IAAI,OAAO;IAKtB,WAAW,IAAI,OAAO;CAGvB;AAGD,eAAO,MAAM,eAAe,iBAAwB,CAAC;AAGrD,eAAO,MAAM,sBAAsB,GAAI,UAAU,kBAAkB,eACtB,CAAC;AAC9C,eAAO,MAAM,YAAY,GAAI,UAAU,kBAAkB,eACtB,CAAC;AACpC,eAAO,MAAM,cAAc,0BAAoC,CAAC;AAChE,eAAO,MAAM,gBAAgB,YAAsC,CAAC;AACpE,eAAO,MAAM,gBAAgB,eAAsC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/route-events",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "route-events package",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -26,8 +26,8 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "@buoy-gg/floating-tools-core": "2.1.2",
30
- "@buoy-gg/shared-ui": "2.1.2"
29
+ "@buoy-gg/floating-tools-core": "2.1.3",
30
+ "@buoy-gg/shared-ui": "2.1.3"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@buoy-gg/license": "*",
@@ -49,7 +49,7 @@
49
49
  "@types/react-native": "^0.73.0",
50
50
  "expo-router": "~5.0.7",
51
51
  "typescript": "~5.8.3",
52
- "@buoy-gg/license": "2.1.2"
52
+ "@buoy-gg/license": "2.1.3"
53
53
  },
54
54
  "react-native-builder-bob": {
55
55
  "source": "src",