@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.
Files changed (35) hide show
  1. package/lib/commonjs/RouteTracker.js +38 -4
  2. package/lib/commonjs/components/NavigationStack.js +105 -124
  3. package/lib/commonjs/components/RouteEventsModalWithTabs.js +11 -4
  4. package/lib/commonjs/components/RoutesSitemap.js +205 -169
  5. package/lib/commonjs/expoRouterStore.js +17 -0
  6. package/lib/commonjs/index.js +7 -0
  7. package/lib/commonjs/stores/navigationStackStore.js +45 -0
  8. package/lib/commonjs/sync/routeEventsSyncAdapter.js +148 -0
  9. package/lib/commonjs/useRouteSitemap.js +28 -7
  10. package/lib/module/RouteTracker.js +39 -5
  11. package/lib/module/components/NavigationStack.js +106 -125
  12. package/lib/module/components/RouteEventsModalWithTabs.js +11 -4
  13. package/lib/module/components/RoutesSitemap.js +207 -171
  14. package/lib/module/expoRouterStore.js +16 -0
  15. package/lib/module/index.js +4 -0
  16. package/lib/module/stores/navigationStackStore.js +41 -0
  17. package/lib/module/sync/routeEventsSyncAdapter.js +145 -0
  18. package/lib/module/useRouteSitemap.js +29 -8
  19. package/lib/typescript/RouteTracker.d.ts.map +1 -1
  20. package/lib/typescript/components/NavigationStack.d.ts +24 -1
  21. package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
  22. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
  23. package/lib/typescript/components/RoutesSitemap.d.ts +19 -1
  24. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -1
  25. package/lib/typescript/expoRouterStore.d.ts +8 -0
  26. package/lib/typescript/expoRouterStore.d.ts.map +1 -1
  27. package/lib/typescript/index.d.ts +3 -1
  28. package/lib/typescript/index.d.ts.map +1 -1
  29. package/lib/typescript/stores/navigationStackStore.d.ts +33 -0
  30. package/lib/typescript/stores/navigationStackStore.d.ts.map +1 -0
  31. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts +69 -0
  32. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +1 -0
  33. package/lib/typescript/useRouteSitemap.d.ts +17 -0
  34. package/lib/typescript/useRouteSitemap.d.ts.map +1 -1
  35. package/package.json +6 -6
@@ -4,9 +4,12 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.RouteTracker = RouteTracker;
7
+ var _react = require("react");
7
8
  var _sharedUi = require("@buoy-gg/shared-ui");
8
9
  var _useRouteObserver = require("./useRouteObserver");
9
10
  var _useRouteObserverReactNavigation = require("./useRouteObserverReactNavigation");
11
+ var _useNavigationStack = require("./useNavigationStack");
12
+ var _navigationStackStore = require("./stores/navigationStackStore");
10
13
  var _jsxRuntime = require("react/jsx-runtime");
11
14
  /**
12
15
  * RouteTracker - A component to place inside your navigation tree
@@ -57,10 +60,41 @@ function ReactNavigationRouteTracker() {
57
60
  (0, _useRouteObserverReactNavigation.useRouteObserverReactNavigation)();
58
61
  return null;
59
62
  }
63
+
64
+ /**
65
+ * Mirrors the live navigation stack (and its action functions) into
66
+ * navigationStackStore so the route-events sync adapter can expose the Stack
67
+ * tab to the dashboard. Runs inside the navigation tree where the container ref
68
+ * context is available.
69
+ */
70
+ function NavigationStackCapture() {
71
+ const {
72
+ stack,
73
+ navigateToIndex,
74
+ popToIndex,
75
+ goBack,
76
+ popToTop
77
+ } = (0, _useNavigationStack.useNavigationStack)();
78
+ (0, _react.useEffect)(() => {
79
+ _navigationStackStore.navigationStackStore.setStack(stack);
80
+ }, [stack]);
81
+
82
+ // Keep the action references fresh (they change as the stack changes) so a
83
+ // remote action always operates on the current navigation state.
84
+ (0, _react.useEffect)(() => {
85
+ _navigationStackStore.navigationStackStore.setActions({
86
+ navigateToIndex,
87
+ popToIndex,
88
+ goBack,
89
+ popToTop
90
+ });
91
+ return () => _navigationStackStore.navigationStackStore.setActions(null);
92
+ });
93
+ return null;
94
+ }
60
95
  function RouteTracker() {
61
96
  const hasExpo = (0, _sharedUi.isExpoRouterAvailable)();
62
- if (hasExpo) {
63
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ExpoRouteTracker, {});
64
- }
65
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ReactNavigationRouteTracker, {});
97
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
98
+ children: [hasExpo ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ExpoRouteTracker, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(ReactNavigationRouteTracker, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(NavigationStackCapture, {})]
99
+ });
66
100
  }
@@ -22,26 +22,39 @@ var _jsxRuntime = require("react/jsx-runtime");
22
22
  // Types
23
23
  // ============================================================================
24
24
 
25
+ /** Stack manipulation a host can be asked to perform on the device. */
26
+
25
27
  // ============================================================================
26
28
  // Main Component
27
29
  // ============================================================================
28
30
 
29
31
  function NavigationStack({
30
- style
32
+ style,
33
+ injectedStack,
34
+ onAction,
35
+ onCopyValueChange
31
36
  }) {
32
- const {
33
- stack,
34
- focusedRoute,
35
- stackDepth,
36
- isAtRoot,
37
- isLoaded,
38
- error,
39
- navigateToIndex,
40
- goBack,
41
- popToTop
42
- } = (0, _useNavigationStack.useNavigationStack)();
37
+ const hookResult = (0, _useNavigationStack.useNavigationStack)();
38
+ const isInjected = injectedStack != null;
39
+ const insets = (0, _sharedUi.useSafeAreaInsets)({
40
+ minBottom: 8
41
+ });
42
+
43
+ // Data source: injected (dashboard) or the local navigation container.
44
+ const stack = isInjected ? injectedStack : hookResult.stack;
45
+ const isLoaded = isInjected ? true : hookResult.isLoaded;
46
+ const error = isInjected ? null : hookResult.error;
47
+ const focusedRoute = (0, _react.useMemo)(() => stack.find(item => item.isFocused) ?? null, [stack]);
48
+ const stackDepth = stack.length;
49
+ const isAtRoot = stackDepth <= 1;
50
+
51
+ // Action wrappers: delegate to the host when provided, else act locally.
52
+ const navigateToIndex = index => onAction ? onAction("navigateToIndex", {
53
+ index
54
+ }) : hookResult.navigateToIndex(index);
55
+ const goBack = () => onAction ? onAction("goBack") : hookResult.goBack();
56
+ const popToTop = () => onAction ? onAction("popToTop") : hookResult.popToTop();
43
57
  const [expandedIndex, setExpandedIndex] = (0, _react.useState)(null);
44
- const [showHelp, setShowHelp] = (0, _react.useState)(false);
45
58
  const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
46
59
 
47
60
  // Check Pro status internally
@@ -66,6 +79,12 @@ function NavigationStack({
66
79
  return JSON.stringify(stackData, null, 2);
67
80
  }, [stack]);
68
81
 
82
+ // Report the copy payload up so the host can render the copy button in the
83
+ // shared navbar.
84
+ (0, _react.useEffect)(() => {
85
+ onCopyValueChange?.(stackDataForCopy);
86
+ }, [stackDataForCopy, onCopyValueChange]);
87
+
69
88
  // Determine which route actions should operate on
70
89
  // If a stack item is expanded, actions target that route
71
90
  // Otherwise, actions target the focused (visible) route
@@ -128,6 +147,26 @@ function NavigationStack({
128
147
  }
129
148
 
130
149
  // Handlers
150
+ // Confirm a destructive action. On device we use the native Alert; when an
151
+ // action delegate is set (dashboard, react-native-web) Alert button presses
152
+ // don't fire, so fall back to window.confirm.
153
+ const confirmDestructive = (title, message, confirmLabel, onConfirm) => {
154
+ if (onAction) {
155
+ const confirmFn = globalThis.window?.confirm;
156
+ if (!confirmFn || confirmFn(`${title}\n\n${message}`)) {
157
+ onConfirm();
158
+ }
159
+ return;
160
+ }
161
+ _reactNative.Alert.alert(title, message, [{
162
+ text: "Cancel",
163
+ style: "cancel"
164
+ }, {
165
+ text: confirmLabel,
166
+ style: "destructive",
167
+ onPress: onConfirm
168
+ }]);
169
+ };
131
170
  const handleGoBack = () => {
132
171
  // Gate behind Pro
133
172
  if (!isPro) {
@@ -150,14 +189,7 @@ function NavigationStack({
150
189
  _reactNative.Alert.alert("Already at Top", "Stack only has one screen");
151
190
  return;
152
191
  }
153
- _reactNative.Alert.alert("Pop to Top", "This will remove all screens except the root screen.", [{
154
- text: "Cancel",
155
- style: "cancel"
156
- }, {
157
- text: "Pop to Top",
158
- style: "destructive",
159
- onPress: popToTop
160
- }]);
192
+ confirmDestructive("Pop to Top", "This will remove all screens except the root screen.", "Pop to Top", popToTop);
161
193
  };
162
194
  const toggleExpand = index => {
163
195
  setExpandedIndex(expandedIndex === index ? null : index);
@@ -199,37 +231,17 @@ function NavigationStack({
199
231
  return;
200
232
  }
201
233
  const screensToRemove = stackDepth - 1 - selectedRoute.index;
202
- _reactNative.Alert.alert("Pop to Route", `Remove ${screensToRemove} screen${screensToRemove !== 1 ? "s" : ""} above ${selectedRoute.pathname}?`, [{
203
- text: "Cancel",
204
- style: "cancel"
205
- }, {
206
- text: "Pop",
207
- style: "destructive",
208
- onPress: () => {
209
- // Navigate to the selected route, which effectively pops everything above it
210
- navigateToIndex(selectedRoute.index);
211
- }
212
- }]);
234
+ const targetIndex = selectedRoute.index;
235
+ confirmDestructive("Pop to Route", `Remove ${screensToRemove} screen${screensToRemove !== 1 ? "s" : ""} above ${selectedRoute.pathname}?`, "Pop",
236
+ // Navigate to the selected route, which effectively pops everything above it
237
+ () => navigateToIndex(targetIndex));
213
238
  };
214
239
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
215
240
  style: [styles.container, style],
216
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
217
- style: styles.header,
218
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
219
- style: [styles.iconButton, showHelp && styles.iconButtonActive],
220
- onPress: () => setShowHelp(!showHelp),
221
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Info, {
222
- size: 16,
223
- color: showHelp ? _sharedUi.buoyColors.primary : _sharedUi.buoyColors.textSecondary
224
- })
225
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton, {
226
- value: stackDataForCopy,
227
- buttonStyle: styles.iconButton
228
- })]
229
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
241
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
230
242
  style: styles.stackScroll,
231
243
  contentContainerStyle: [styles.stackContent, {
232
- paddingBottom: showHelp ? 72 : 56
244
+ paddingBottom: styles.stackContent.padding + insets.bottom
233
245
  }],
234
246
  children: [...stack].reverse().map((item, reverseIndex) => {
235
247
  const actualIndex = stack.length - 1 - reverseIndex;
@@ -298,59 +310,56 @@ function NavigationStack({
298
310
  data: item.params,
299
311
  showTypeFilter: false
300
312
  })
313
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
314
+ style: styles.actionsRow,
315
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
316
+ style: styles.actionWrapper,
317
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
318
+ style: [styles.actionButton, isAtRoot && styles.actionButtonDisabled],
319
+ onPress: handleGoBack,
320
+ disabled: isAtRoot,
321
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
322
+ style: [styles.actionButtonText, isAtRoot && styles.actionButtonTextDisabled],
323
+ children: "Back"
324
+ })
325
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
326
+ style: styles.helpText,
327
+ children: "Go back one screen"
328
+ })]
329
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
330
+ style: styles.actionWrapper,
331
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
332
+ style: [styles.actionButton, item.isFocused && styles.actionButtonDisabled],
333
+ onPress: handleGo,
334
+ disabled: item.isFocused,
335
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
336
+ style: [styles.actionButtonText, item.isFocused && styles.actionButtonTextDisabled],
337
+ children: "Go"
338
+ })
339
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
340
+ style: styles.helpText,
341
+ children: "Navigate to this route"
342
+ })]
343
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
344
+ style: styles.actionWrapper,
345
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
346
+ style: [styles.actionButton, (item.isFocused || actualIndex === stackDepth - 1) && styles.actionButtonDisabled],
347
+ onPress: handlePopTo,
348
+ disabled: item.isFocused || actualIndex === stackDepth - 1,
349
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
350
+ style: [styles.actionButtonText, (item.isFocused || actualIndex === stackDepth - 1) && styles.actionButtonTextDisabled],
351
+ children: "Pop To"
352
+ })
353
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
354
+ style: styles.helpText,
355
+ children: "Remove screens above this"
356
+ })]
357
+ })]
301
358
  })]
302
359
  })]
303
360
  })
304
361
  }, `stack-${actualIndex}-${item.key}`);
305
362
  })
306
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
307
- style: styles.actionsContainer,
308
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
309
- style: styles.actionsRow,
310
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
311
- style: styles.actionWrapper,
312
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
313
- style: [styles.actionButton, isAtRoot && styles.actionButtonDisabled],
314
- onPress: handleGoBack,
315
- disabled: isAtRoot,
316
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
317
- style: [styles.actionButtonText, isAtRoot && styles.actionButtonTextDisabled],
318
- children: "Back"
319
- })
320
- }), showHelp && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
321
- style: styles.helpText,
322
- children: "Go back one screen"
323
- })]
324
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
325
- style: styles.actionWrapper,
326
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
327
- style: [styles.actionButton, selectedRoute?.isFocused && styles.actionButtonDisabled],
328
- onPress: handleGo,
329
- disabled: selectedRoute?.isFocused,
330
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
331
- style: [styles.actionButtonText, selectedRoute?.isFocused && styles.actionButtonTextDisabled],
332
- children: "Go"
333
- })
334
- }), showHelp && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
335
- style: styles.helpText,
336
- children: "Navigate to selected route"
337
- })]
338
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
339
- style: styles.actionWrapper,
340
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
341
- style: [styles.actionButton, (selectedRoute?.isFocused || selectedRoute?.index === stackDepth - 1) && styles.actionButtonDisabled],
342
- onPress: handlePopTo,
343
- disabled: selectedRoute?.isFocused || selectedRoute?.index === stackDepth - 1,
344
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
345
- style: [styles.actionButtonText, (selectedRoute?.isFocused || selectedRoute?.index === stackDepth - 1) && styles.actionButtonTextDisabled],
346
- children: "Pop To"
347
- })
348
- }), showHelp && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
349
- style: styles.helpText,
350
- children: "Remove screens above selected"
351
- })]
352
- })]
353
- })
354
363
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
355
364
  visible: showUpgradeModal,
356
365
  onClose: () => setShowUpgradeModal(false),
@@ -417,22 +426,6 @@ const styles = _reactNative.StyleSheet.create({
417
426
  fontFamily: "monospace",
418
427
  textAlign: "center"
419
428
  },
420
- header: {
421
- flexDirection: "row",
422
- padding: 8,
423
- gap: 8,
424
- borderBottomWidth: 1,
425
- borderBottomColor: _sharedUi.buoyColors.border,
426
- alignItems: "center",
427
- justifyContent: "flex-end"
428
- },
429
- iconButton: {
430
- padding: 6,
431
- borderRadius: 4
432
- },
433
- iconButtonActive: {
434
- backgroundColor: _sharedUi.buoyColors.input
435
- },
436
429
  stackScroll: {
437
430
  flex: 1
438
431
  },
@@ -512,22 +505,10 @@ const styles = _reactNative.StyleSheet.create({
512
505
  marginHorizontal: -12,
513
506
  marginBottom: 8
514
507
  },
515
- actionsContainer: {
516
- position: "absolute",
517
- left: 0,
518
- right: 0,
519
- bottom: 0,
520
- borderTopWidth: 1,
521
- borderTopColor: _sharedUi.buoyColors.border,
522
- backgroundColor: _sharedUi.buoyColors.base,
523
- paddingHorizontal: 8,
524
- paddingTop: 8,
525
- paddingBottom: 8
526
- },
527
508
  actionsRow: {
528
509
  flexDirection: "row",
529
510
  gap: 6,
530
- marginBottom: 6
511
+ marginTop: 12
531
512
  },
532
513
  actionWrapper: {
533
514
  flex: 1
@@ -79,6 +79,9 @@ function RouteEventsModalWithTabs({
79
79
  const hasExpoRouter = (0, _sharedUi.isExpoRouterAvailable)();
80
80
  const [activeTab, setActiveTab] = (0, _react.useState)("events");
81
81
  const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
82
+ // Serialized stack reported up by NavigationStack so the copy button can live
83
+ // in the shared navbar.
84
+ const [stackCopyValue, setStackCopyValue] = (0, _react.useState)("");
82
85
 
83
86
  // Check Pro status internally
84
87
  const isPro = (0, _license.useIsPro)();
@@ -377,7 +380,8 @@ function RouteEventsModalWithTabs({
377
380
  }
378
381
  if (activeTab === "stack") {
379
382
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NavigationStack.NavigationStack, {
380
- style: styles.contentWrapper
383
+ style: styles.contentWrapper,
384
+ onCopyValueChange: setStackCopyValue
381
385
  });
382
386
  }
383
387
 
@@ -482,8 +486,11 @@ function RouteEventsModalWithTabs({
482
486
  activeTab: activeTab,
483
487
  onTabChange: tab => setActiveTab(tab)
484
488
  })
485
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {
486
- children: activeTab === "events" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
489
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
490
+ children: [activeTab === "stack" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
491
+ value: stackCopyValue,
492
+ buttonStyle: styles.iconButton
493
+ }), activeTab === "events" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
487
494
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
488
495
  value: copyAllEventsData,
489
496
  buttonStyle: styles.iconButton
@@ -506,7 +513,7 @@ function RouteEventsModalWithTabs({
506
513
  color: _sharedUi.buoyColors.error
507
514
  })
508
515
  })]
509
- })
516
+ })]
510
517
  })]
511
518
  })
512
519
  },