@buoy-gg/route-events 7.0.39 → 7.0.41

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 (46) hide show
  1. package/README.md +4 -0
  2. package/lib/commonjs/RouteTracker.web.js +12 -0
  3. package/lib/commonjs/browserRouteRegistry.js +30 -0
  4. package/lib/commonjs/browserRoutes.js +96 -0
  5. package/lib/commonjs/components/ReactNavigationRoutes.web.js +12 -0
  6. package/lib/commonjs/components/RouteEventDetail.js +1 -1
  7. package/lib/commonjs/components/RouteEventItemCompact.js +1 -1
  8. package/lib/commonjs/components/RouteEventsModalWithTabs.js +24 -7
  9. package/lib/commonjs/expoRouterStore.web.js +44 -0
  10. package/lib/commonjs/index.js +7 -0
  11. package/lib/commonjs/useNavigationStack.web.js +32 -0
  12. package/lib/commonjs/utils/routeTemplate.js +51 -0
  13. package/lib/commonjs/utils/safeReactNavigation.web.js +36 -0
  14. package/lib/module/RouteTracker.web.js +8 -0
  15. package/lib/module/browserRouteRegistry.js +24 -0
  16. package/lib/module/browserRoutes.js +92 -0
  17. package/lib/module/components/ReactNavigationRoutes.web.js +3 -0
  18. package/lib/module/components/RouteEventDetail.js +2 -2
  19. package/lib/module/components/RouteEventItemCompact.js +2 -2
  20. package/lib/module/components/RouteEventsModalWithTabs.js +24 -7
  21. package/lib/module/expoRouterStore.web.js +37 -0
  22. package/lib/module/index.js +1 -0
  23. package/lib/module/useNavigationStack.web.js +28 -0
  24. package/lib/module/utils/routeTemplate.js +49 -0
  25. package/lib/module/utils/safeReactNavigation.web.js +27 -0
  26. package/lib/typescript/RouteTracker.web.d.ts +2 -0
  27. package/lib/typescript/RouteTracker.web.d.ts.map +1 -0
  28. package/lib/typescript/browserRouteRegistry.d.ts +4 -0
  29. package/lib/typescript/browserRouteRegistry.d.ts.map +1 -0
  30. package/lib/typescript/browserRoutes.d.ts +2 -0
  31. package/lib/typescript/browserRoutes.d.ts.map +1 -0
  32. package/lib/typescript/components/ReactNavigationRoutes.web.d.ts +2 -0
  33. package/lib/typescript/components/ReactNavigationRoutes.web.d.ts.map +1 -0
  34. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
  35. package/lib/typescript/expoRouterStore.web.d.ts +13 -0
  36. package/lib/typescript/expoRouterStore.web.d.ts.map +1 -0
  37. package/lib/typescript/index.d.ts +1 -0
  38. package/lib/typescript/index.d.ts.map +1 -1
  39. package/lib/typescript/useNavigationStack.web.d.ts +4 -0
  40. package/lib/typescript/useNavigationStack.web.d.ts.map +1 -0
  41. package/lib/typescript/utils/routeTemplate.d.ts +13 -0
  42. package/lib/typescript/utils/routeTemplate.d.ts.map +1 -1
  43. package/lib/typescript/utils/safeReactNavigation.web.d.ts +22 -0
  44. package/lib/typescript/utils/safeReactNavigation.web.d.ts.map +1 -0
  45. package/lib/web/index.mjs +7 -0
  46. package/package.json +33 -13
package/README.md CHANGED
@@ -74,3 +74,7 @@ Use a Free or Pro Buoy account. History limits and paid features vary by tool; s
74
74
  📚 [Full docs](https://buoy.gg/buoy/latest/docs/tools/routes) · [All Buoy tools](https://github.com/Buoy-gg/buoy)
75
75
 
76
76
  Proprietary software. © Buoy LLC. [Terms](https://buoy.gg/terms)
77
+
78
+ ## Web support (unreleased)
79
+
80
+ Browser History API and hash changes feed the shared route history. Register known paths and the framework router adapter for route selection and navigation. The browser build is available in this checkout and has not been published yet. See the [web setup guide](https://buoy.gg/buoy/latest/docs/web-preview) for registration, dependencies, and browser boundaries.
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RouteTracker = RouteTracker;
7
+ var _react = require("react");
8
+ var _browserRoutes = require("./browserRoutes");
9
+ function RouteTracker() {
10
+ (0, _react.useEffect)(_browserRoutes.startBrowserRouteCapture, []);
11
+ return null;
12
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getBrowserRoutePaths = getBrowserRoutePaths;
7
+ exports.getBrowserRoutesUpdatedAt = getBrowserRoutesUpdatedAt;
8
+ exports.registerBrowserRoutes = registerBrowserRoutes;
9
+ const declared = new Map();
10
+ let updatedAt = null;
11
+ function registerBrowserRoutes(paths) {
12
+ for (const path of paths) declared.set(path, (declared.get(path) ?? 0) + 1);
13
+ updatedAt = Date.now();
14
+ let active = true;
15
+ return () => {
16
+ if (!active) return;
17
+ active = false;
18
+ for (const path of paths) {
19
+ const count = (declared.get(path) ?? 1) - 1;
20
+ if (count) declared.set(path, count);else declared.delete(path);
21
+ }
22
+ updatedAt = Date.now();
23
+ };
24
+ }
25
+ function getBrowserRoutePaths() {
26
+ return [...declared.keys()];
27
+ }
28
+ function getBrowserRoutesUpdatedAt() {
29
+ return updatedAt;
30
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.startBrowserRouteCapture = startBrowserRouteCapture;
7
+ var _utils = require("@buoy-gg/shared-ui/utils");
8
+ var _RouteObserver = require("./RouteObserver");
9
+ var _routeEventStore = require("./stores/routeEventStore");
10
+ var _navigationStackStore = require("./stores/navigationStackStore");
11
+ let leases = 0;
12
+ let stop;
13
+ function startBrowserRouteCapture() {
14
+ if (typeof window === 'undefined') return () => {};
15
+ if (leases++ === 0) {
16
+ _routeEventStore.routeEventStore.hydrateBootCapture();
17
+ let previousPathname;
18
+ let previousTime;
19
+ let lastHref = '';
20
+ const visits = [];
21
+ let cursor = -1;
22
+ const update = change => {
23
+ if (change.href === lastHref && change.kind === 'pop') return;
24
+ if (change.kind === 'replace' && cursor >= 0) visits[cursor] = change.href;else if (change.kind === 'pop') {
25
+ const index = visits.lastIndexOf(change.href, cursor - 1);
26
+ const forward = visits.indexOf(change.href, cursor + 1);
27
+ if (index >= 0) cursor = index;else if (forward >= 0) cursor = forward;else {
28
+ visits.splice(cursor + 1);
29
+ visits.push(change.href);
30
+ cursor++;
31
+ }
32
+ } else {
33
+ visits.splice(cursor + 1);
34
+ visits.push(change.href);
35
+ cursor++;
36
+ }
37
+ lastHref = change.href;
38
+ const url = (0, _utils.getBrowserRouteURL)(change.href);
39
+ const timestamp = Date.now();
40
+ _RouteObserver.routeObserver.emit({
41
+ pathname: url.pathname,
42
+ params: Object.fromEntries(url.searchParams),
43
+ segments: url.pathname.split('/').filter(Boolean),
44
+ previousPathname,
45
+ timestamp,
46
+ timeSincePrevious: previousTime === undefined ? undefined : timestamp - previousTime
47
+ });
48
+ previousPathname = url.pathname;
49
+ previousTime = timestamp;
50
+ _navigationStackStore.navigationStackStore.setStack(visits.map((href, index) => {
51
+ const location = (0, _utils.getBrowserRouteURL)(href);
52
+ return {
53
+ key: `browser:${index}`,
54
+ name: location.pathname,
55
+ pathname: location.pathname,
56
+ params: Object.fromEntries(location.searchParams),
57
+ index,
58
+ isFocused: index === cursor,
59
+ canPop: index > 0
60
+ };
61
+ }));
62
+ };
63
+ const goTo = index => {
64
+ if (!Number.isInteger(index) || index < 0 || index >= visits.length) throw new Error('That browser history entry is unavailable.');
65
+ window.history.go(index - cursor);
66
+ };
67
+ _navigationStackStore.navigationStackStore.setActions({
68
+ navigateToIndex: goTo,
69
+ popToIndex: goTo,
70
+ goBack: _utils.browserRouter.back,
71
+ popToTop: () => goTo(0),
72
+ navigateToRoute: (name, params) => {
73
+ _utils.browserRouter.push({
74
+ pathname: name,
75
+ params: params
76
+ });
77
+ return true;
78
+ }
79
+ });
80
+ stop = (0, _utils.subscribeBrowserNavigation)(update);
81
+ update({
82
+ href: (0, _utils.getBrowserHref)(),
83
+ kind: 'initial'
84
+ });
85
+ }
86
+ let released = false;
87
+ return () => {
88
+ if (released) return;
89
+ released = true;
90
+ if (--leases === 0) {
91
+ stop?.();
92
+ stop = undefined;
93
+ _navigationStackStore.navigationStackStore.setActions(null);
94
+ }
95
+ };
96
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "ReactNavigationRoutes", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _RoutesSitemap.RoutesSitemap;
10
+ }
11
+ });
12
+ var _RoutesSitemap = require("./RoutesSitemap");
@@ -40,7 +40,7 @@ function RouteEventDetail({
40
40
  style: styles.footer,
41
41
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
42
42
  style: styles.goButton,
43
- onPress: () => onNavigate(event.pathname),
43
+ onPress: () => onNavigate((0, _routeTemplate.routeHref)(event.pathname, event.params)),
44
44
  activeOpacity: 0.8,
45
45
  testID: "buoy-routes-detail-go",
46
46
  accessibilityRole: "button",
@@ -137,7 +137,7 @@ const RouteEventItemCompact = exports.RouteEventItemCompact = /*#__PURE__*/(0, _
137
137
  if (!onNavigate) return undefined;
138
138
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
139
139
  style: styles.goButton,
140
- onPress: () => onNavigate(event.pathname),
140
+ onPress: () => onNavigate((0, _routeTemplate.routeHref)(event.pathname, event.params)),
141
141
  testID: `${testID}-go`,
142
142
  accessibilityRole: "button",
143
143
  accessibilityLabel: `Go to ${event.pathname}`,
@@ -18,6 +18,7 @@ var _RoutesSitemap = require("./RoutesSitemap");
18
18
  var _ReactNavigationRoutes = require("./ReactNavigationRoutes");
19
19
  var _NavigationStack = require("./NavigationStack");
20
20
  var _RouteEventsTimeline = require("./RouteEventsTimeline");
21
+ var _routeTemplate = require("../utils/routeTemplate");
21
22
  var _jsxRuntime = require("react/jsx-runtime");
22
23
  // Resolved once at module load; null when React Navigation is absent
23
24
  // (useSafeNavigationContainerRef then yields undefined, so the dispatch
@@ -68,16 +69,23 @@ function findScreenPath(state, screenName) {
68
69
  * e.g. ["MainApp", "Settings", "SettingScreen"] becomes:
69
70
  * navigate("MainApp", { screen: "Settings", params: { screen: "SettingScreen" } })
70
71
  */
71
- function buildNestedNavigateAction(path) {
72
+ function buildNestedNavigateAction(path, leafParams) {
72
73
  if (path.length === 0) return null;
74
+ const leaf = leafParams && Object.keys(leafParams).length > 0 ? leafParams : undefined;
73
75
  if (path.length === 1) {
74
- return CommonActions.navigate({
76
+ return CommonActions.navigate(leaf ? {
77
+ name: path[0],
78
+ params: leaf
79
+ } : {
75
80
  name: path[0]
76
81
  });
77
82
  }
78
83
 
79
84
  // Build nested params from the end
80
- let params = {
85
+ let params = leaf ? {
86
+ screen: path[path.length - 1],
87
+ params: leaf
88
+ } : {
81
89
  screen: path[path.length - 1]
82
90
  };
83
91
  for (let i = path.length - 2; i >= 1; i--) {
@@ -175,15 +183,21 @@ function RouteEventsModalWithTabs({
175
183
  if (hasExpoRouter) {
176
184
  router.push(pathname);
177
185
  } else if (containerRef) {
178
- // On RN CLI, pathname is /<ScreenName> — extract the screen name
179
- const screenName = pathname.startsWith("/") ? pathname.slice(1) : pathname;
186
+ // On RN CLI, pathname is /<ScreenName> — extract the screen name.
187
+ // A recorded event's href may carry its params as a query string.
188
+ const {
189
+ pathname: path,
190
+ params: routeParams
191
+ } = (0, _routeTemplate.splitRouteHref)(pathname);
192
+ const screenName = path.startsWith("/") ? path.slice(1) : path;
193
+ const hasParams = Object.keys(routeParams).length > 0;
180
194
 
181
195
  // Walk the state tree to find the nesting path to this screen
182
196
  const rootState = containerRef.getRootState?.();
183
197
  if (rootState) {
184
198
  const path = findScreenPath(rootState, screenName);
185
199
  if (path) {
186
- const action = buildNestedNavigateAction(path);
200
+ const action = buildNestedNavigateAction(path, routeParams);
187
201
  if (action) {
188
202
  containerRef.dispatch(action);
189
203
  return;
@@ -192,7 +206,10 @@ function RouteEventsModalWithTabs({
192
206
  }
193
207
 
194
208
  // Fallback: try direct navigation (works for top-level screens)
195
- containerRef.dispatch(CommonActions.navigate({
209
+ containerRef.dispatch(CommonActions.navigate(hasParams ? {
210
+ name: screenName,
211
+ params: routeParams
212
+ } : {
196
213
  name: screenName
197
214
  }));
198
215
  } else {
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getExpoRouterStore = getExpoRouterStore;
7
+ exports.getRouteNodeMetadata = getRouteNodeMetadata;
8
+ exports.isExpoRouterStoreSupported = isExpoRouterStoreSupported;
9
+ exports.loadRouteNode = loadRouteNode;
10
+ var _utils = require("@buoy-gg/shared-ui/utils");
11
+ var _browserRouteRegistry = require("./browserRouteRegistry");
12
+ function isExpoRouterStoreSupported() {
13
+ return typeof window !== 'undefined';
14
+ }
15
+ function loadRouteNode() {
16
+ if (!isExpoRouterStoreSupported()) return null;
17
+ const paths = new Set([...(0, _browserRouteRegistry.getBrowserRoutePaths)(), new URL((0, _utils.getBrowserHref)()).pathname]);
18
+ return {
19
+ route: '',
20
+ type: 'layout',
21
+ contextKey: 'browser',
22
+ children: [...paths].map(path => ({
23
+ route: path.replace(/^\//, '') || 'index',
24
+ type: 'route',
25
+ contextKey: path,
26
+ children: [],
27
+ dynamic: null
28
+ }))
29
+ };
30
+ }
31
+ function getExpoRouterStore() {
32
+ return {
33
+ routeNode: loadRouteNode(),
34
+ navigationRef: {
35
+ isReady: () => typeof window !== 'undefined'
36
+ }
37
+ };
38
+ }
39
+ function getRouteNodeMetadata() {
40
+ return {
41
+ source: null,
42
+ lastLoadedAt: (0, _browserRouteRegistry.getBrowserRoutesUpdatedAt)()
43
+ };
44
+ }
@@ -75,6 +75,12 @@ Object.defineProperty(exports, "navigationStackStore", {
75
75
  return _navigationStackStore.navigationStackStore;
76
76
  }
77
77
  });
78
+ Object.defineProperty(exports, "registerBrowserRoutes", {
79
+ enumerable: true,
80
+ get: function () {
81
+ return _browserRouteRegistry.registerBrowserRoutes;
82
+ }
83
+ });
78
84
  Object.defineProperty(exports, "routeEventStore", {
79
85
  enumerable: true,
80
86
  get: function () {
@@ -144,6 +150,7 @@ Object.defineProperty(exports, "useRouteSitemap", {
144
150
  var _preset = require("./preset");
145
151
  var _RouteEventsModalWithTabs = require("./components/RouteEventsModalWithTabs");
146
152
  var _RouteTracker = require("./RouteTracker");
153
+ var _browserRouteRegistry = require("./browserRouteRegistry");
147
154
  var _RoutesSitemap = require("./components/RoutesSitemap");
148
155
  var _NavigationStack = require("./components/NavigationStack");
149
156
  var _RouteEventItemCompact = require("./components/RouteEventItemCompact");
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useNavigationStack = useNavigationStack;
7
+ var _react = require("react");
8
+ var _navigationStackStore = require("./stores/navigationStackStore");
9
+ const empty = [];
10
+ function useNavigationStack() {
11
+ const stack = (0, _react.useSyncExternalStore)(callback => _navigationStackStore.navigationStackStore.subscribe(callback), () => _navigationStackStore.navigationStackStore.getStack(), () => empty);
12
+ const focused = stack.find(item => item.isFocused) ?? null;
13
+ const actions = () => {
14
+ const current = _navigationStackStore.navigationStackStore.getActions();
15
+ if (!current) throw new Error('Mount RouteTracker to capture browser navigation.');
16
+ return current;
17
+ };
18
+ return {
19
+ stack,
20
+ focusedRoute: focused,
21
+ stackDepth: stack.length,
22
+ isAtRoot: !focused || focused.index === 0,
23
+ isLoaded: stack.length > 0,
24
+ error: null,
25
+ refresh: () => {},
26
+ navigateToIndex: index => actions().navigateToIndex(index),
27
+ navigateToRoute: (name, params) => actions().navigateToRoute?.(name, params) ?? false,
28
+ popToIndex: index => actions().popToIndex(index),
29
+ goBack: () => actions().goBack(),
30
+ popToTop: () => actions().popToTop()
31
+ };
32
+ }
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.getRouteTemplate = getRouteTemplate;
7
+ exports.routeHref = routeHref;
8
+ exports.splitRouteHref = splitRouteHref;
7
9
  /**
8
10
  * Infer a route template from a concrete pathname and its segments.
9
11
  * Dynamic segments (numeric ids, UUIDs) collapse to `[id]`, so
@@ -25,4 +27,53 @@ function getRouteTemplate(pathname, segments) {
25
27
  });
26
28
  const template = "/" + templateParts.join("/");
27
29
  return template !== pathname ? template : null;
30
+ }
31
+
32
+ /**
33
+ * The href that reopens a recorded route: its pathname plus the params that are
34
+ * not already in the path, as a query string. Expo Router reports dynamic
35
+ * segment values (`/order/42` → `{ id: "42" }`) and search params (`?menu=1`)
36
+ * in one `params` object; only the second kind needs to be carried, because the
37
+ * first is already in the pathname.
38
+ */
39
+ function routeHref(pathname, params) {
40
+ if (!params) return pathname;
41
+ const inPath = new Set(pathname.split("/").filter(Boolean).map(seg => {
42
+ try {
43
+ return decodeURIComponent(seg);
44
+ } catch {
45
+ return seg;
46
+ }
47
+ }));
48
+ const query = [];
49
+ for (const [key, raw] of Object.entries(params)) {
50
+ const values = Array.isArray(raw) ? raw : [raw];
51
+ // A catch-all segment's values, or one dynamic segment's value.
52
+ if (values.length > 0 && values.every(v => inPath.has(String(v)))) continue;
53
+ for (const v of values) query.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`);
54
+ }
55
+ return query.length ? `${pathname}?${query.join("&")}` : pathname;
56
+ }
57
+
58
+ /** Split an href from {@link routeHref} back into its path and params. */
59
+ function splitRouteHref(href) {
60
+ const q = href.indexOf("?");
61
+ if (q < 0) return {
62
+ pathname: href,
63
+ params: {}
64
+ };
65
+ const params = {};
66
+ for (const pair of href.slice(q + 1).split("&")) {
67
+ if (!pair) continue;
68
+ const [k, v = ""] = pair.split("=");
69
+ try {
70
+ params[decodeURIComponent(k)] = decodeURIComponent(v);
71
+ } catch {
72
+ params[k] = v;
73
+ }
74
+ }
75
+ return {
76
+ pathname: href.slice(0, q),
77
+ params
78
+ };
28
79
  }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getCommonActions = getCommonActions;
7
+ exports.getStackActions = getStackActions;
8
+ exports.isReactNavigationAvailable = isReactNavigationAvailable;
9
+ exports.useSafeNavigation = useSafeNavigation;
10
+ exports.useSafeNavigationContainerRef = useSafeNavigationContainerRef;
11
+ exports.useSafeNavigationState = useSafeNavigationState;
12
+ var _utils = require("@buoy-gg/shared-ui/utils");
13
+ function useSafeNavigation() {
14
+ return _utils.browserRouter;
15
+ }
16
+ function useSafeNavigationState(selector) {
17
+ return selector({
18
+ routes: [],
19
+ routeNames: [],
20
+ index: 0,
21
+ key: 'browser',
22
+ type: 'stack'
23
+ });
24
+ }
25
+ function useSafeNavigationContainerRef() {
26
+ return undefined;
27
+ }
28
+ function getCommonActions() {
29
+ return null;
30
+ }
31
+ function getStackActions() {
32
+ return null;
33
+ }
34
+ function isReactNavigationAvailable() {
35
+ return false;
36
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ import { useEffect } from 'react';
4
+ import { startBrowserRouteCapture } from './browserRoutes';
5
+ export function RouteTracker() {
6
+ useEffect(startBrowserRouteCapture, []);
7
+ return null;
8
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+
3
+ const declared = new Map();
4
+ let updatedAt = null;
5
+ export function registerBrowserRoutes(paths) {
6
+ for (const path of paths) declared.set(path, (declared.get(path) ?? 0) + 1);
7
+ updatedAt = Date.now();
8
+ let active = true;
9
+ return () => {
10
+ if (!active) return;
11
+ active = false;
12
+ for (const path of paths) {
13
+ const count = (declared.get(path) ?? 1) - 1;
14
+ if (count) declared.set(path, count);else declared.delete(path);
15
+ }
16
+ updatedAt = Date.now();
17
+ };
18
+ }
19
+ export function getBrowserRoutePaths() {
20
+ return [...declared.keys()];
21
+ }
22
+ export function getBrowserRoutesUpdatedAt() {
23
+ return updatedAt;
24
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+
3
+ import { browserRouter, getBrowserHref, getBrowserRouteURL, subscribeBrowserNavigation } from '@buoy-gg/shared-ui/utils';
4
+ import { routeObserver } from './RouteObserver';
5
+ import { routeEventStore } from './stores/routeEventStore';
6
+ import { navigationStackStore } from './stores/navigationStackStore';
7
+ let leases = 0;
8
+ let stop;
9
+ export function startBrowserRouteCapture() {
10
+ if (typeof window === 'undefined') return () => {};
11
+ if (leases++ === 0) {
12
+ routeEventStore.hydrateBootCapture();
13
+ let previousPathname;
14
+ let previousTime;
15
+ let lastHref = '';
16
+ const visits = [];
17
+ let cursor = -1;
18
+ const update = change => {
19
+ if (change.href === lastHref && change.kind === 'pop') return;
20
+ if (change.kind === 'replace' && cursor >= 0) visits[cursor] = change.href;else if (change.kind === 'pop') {
21
+ const index = visits.lastIndexOf(change.href, cursor - 1);
22
+ const forward = visits.indexOf(change.href, cursor + 1);
23
+ if (index >= 0) cursor = index;else if (forward >= 0) cursor = forward;else {
24
+ visits.splice(cursor + 1);
25
+ visits.push(change.href);
26
+ cursor++;
27
+ }
28
+ } else {
29
+ visits.splice(cursor + 1);
30
+ visits.push(change.href);
31
+ cursor++;
32
+ }
33
+ lastHref = change.href;
34
+ const url = getBrowserRouteURL(change.href);
35
+ const timestamp = Date.now();
36
+ routeObserver.emit({
37
+ pathname: url.pathname,
38
+ params: Object.fromEntries(url.searchParams),
39
+ segments: url.pathname.split('/').filter(Boolean),
40
+ previousPathname,
41
+ timestamp,
42
+ timeSincePrevious: previousTime === undefined ? undefined : timestamp - previousTime
43
+ });
44
+ previousPathname = url.pathname;
45
+ previousTime = timestamp;
46
+ navigationStackStore.setStack(visits.map((href, index) => {
47
+ const location = getBrowserRouteURL(href);
48
+ return {
49
+ key: `browser:${index}`,
50
+ name: location.pathname,
51
+ pathname: location.pathname,
52
+ params: Object.fromEntries(location.searchParams),
53
+ index,
54
+ isFocused: index === cursor,
55
+ canPop: index > 0
56
+ };
57
+ }));
58
+ };
59
+ const goTo = index => {
60
+ if (!Number.isInteger(index) || index < 0 || index >= visits.length) throw new Error('That browser history entry is unavailable.');
61
+ window.history.go(index - cursor);
62
+ };
63
+ navigationStackStore.setActions({
64
+ navigateToIndex: goTo,
65
+ popToIndex: goTo,
66
+ goBack: browserRouter.back,
67
+ popToTop: () => goTo(0),
68
+ navigateToRoute: (name, params) => {
69
+ browserRouter.push({
70
+ pathname: name,
71
+ params: params
72
+ });
73
+ return true;
74
+ }
75
+ });
76
+ stop = subscribeBrowserNavigation(update);
77
+ update({
78
+ href: getBrowserHref(),
79
+ kind: 'initial'
80
+ });
81
+ }
82
+ let released = false;
83
+ return () => {
84
+ if (released) return;
85
+ released = true;
86
+ if (--leases === 0) {
87
+ stop?.();
88
+ stop = undefined;
89
+ navigationStackStore.setActions(null);
90
+ }
91
+ };
92
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export { RoutesSitemap as ReactNavigationRoutes } from './RoutesSitemap';
@@ -12,7 +12,7 @@
12
12
  import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from "react-native";
13
13
  import { Navigation, night, monoFont } from "@buoy-gg/shared-ui";
14
14
  import { RouteEventExpandedContent } from "./RouteEventExpandedContent";
15
- import { getRouteTemplate } from "../utils/routeTemplate";
15
+ import { getRouteTemplate, routeHref } from "../utils/routeTemplate";
16
16
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
17
  export function RouteEventDetail({
18
18
  event,
@@ -36,7 +36,7 @@ export function RouteEventDetail({
36
36
  style: styles.footer,
37
37
  children: /*#__PURE__*/_jsxs(TouchableOpacity, {
38
38
  style: styles.goButton,
39
- onPress: () => onNavigate(event.pathname),
39
+ onPress: () => onNavigate(routeHref(event.pathname, event.params)),
40
40
  activeOpacity: 0.8,
41
41
  testID: "buoy-routes-detail-go",
42
42
  accessibilityRole: "button",
@@ -10,7 +10,7 @@ import { memo, useMemo } from "react";
10
10
  import { Text, TouchableOpacity, StyleSheet } from "react-native";
11
11
  import { CompactRow, RelativeTime, buoyColors, night, monoFont } from "@buoy-gg/shared-ui";
12
12
  import { RouteEventExpandedContent } from "./RouteEventExpandedContent";
13
- import { getRouteTemplate } from "../utils/routeTemplate";
13
+ import { getRouteTemplate, routeHref } from "../utils/routeTemplate";
14
14
  import { eventRowTestID } from "../utils/testIDs";
15
15
 
16
16
  // Route type for color coding
@@ -133,7 +133,7 @@ export const RouteEventItemCompact = /*#__PURE__*/memo(function RouteEventItemCo
133
133
  if (!onNavigate) return undefined;
134
134
  return /*#__PURE__*/_jsx(TouchableOpacity, {
135
135
  style: styles.goButton,
136
- onPress: () => onNavigate(event.pathname),
136
+ onPress: () => onNavigate(routeHref(event.pathname, event.params)),
137
137
  testID: `${testID}-go`,
138
138
  accessibilityRole: "button",
139
139
  accessibilityLabel: `Go to ${event.pathname}`,