@buoy-gg/route-events 7.0.41 → 7.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -64
- package/lib/commonjs/RouteObserver.js +1 -54
- package/lib/commonjs/RouteParser.js +1 -314
- package/lib/commonjs/RouteTracker.js +1 -112
- package/lib/commonjs/RouteTracker.web.js +1 -12
- package/lib/commonjs/browserRouteRegistry.js +1 -30
- package/lib/commonjs/browserRoutes.js +1 -96
- package/lib/commonjs/components/NavigationStack.js +2 -572
- package/lib/commonjs/components/ReactNavigationRoutes.js +1 -836
- package/lib/commonjs/components/ReactNavigationRoutes.web.js +1 -12
- package/lib/commonjs/components/RouteEventDetail.js +1 -97
- package/lib/commonjs/components/RouteEventExpandedContent.js +1 -204
- package/lib/commonjs/components/RouteEventItemCompact.js +1 -191
- package/lib/commonjs/components/RouteEventsModalWithTabs.js +1 -734
- package/lib/commonjs/components/RouteEventsTimeline.js +1 -107
- package/lib/commonjs/components/RouteFilterViewV2.js +1 -42
- package/lib/commonjs/components/RoutesSitemap.js +2 -1035
- package/lib/commonjs/expoRouterStore.js +1 -121
- package/lib/commonjs/expoRouterStore.web.js +1 -44
- package/lib/commonjs/hooks/useRouteEvents.js +1 -57
- package/lib/commonjs/index.js +1 -168
- package/lib/commonjs/preset.js +1 -69
- package/lib/commonjs/stores/navigationStackStore.js +1 -45
- package/lib/commonjs/stores/routeEventStore.js +1 -136
- package/lib/commonjs/sync/routeEventsSyncAdapter.js +1 -275
- package/lib/commonjs/useNavigationStack.js +1 -408
- package/lib/commonjs/useNavigationStack.web.js +1 -32
- package/lib/commonjs/useRouteObserver.js +1 -73
- package/lib/commonjs/useRouteObserverReactNavigation.js +1 -109
- package/lib/commonjs/useRouteSitemap.js +1 -255
- package/lib/commonjs/utils/routeTemplate.js +1 -79
- package/lib/commonjs/utils/safeReactNavigation.js +1 -194
- package/lib/commonjs/utils/safeReactNavigation.web.js +1 -36
- package/lib/commonjs/utils/testIDs.js +1 -63
- package/lib/module/RouteObserver.js +1 -49
- package/lib/module/RouteParser.js +1 -309
- package/lib/module/RouteTracker.js +1 -109
- package/lib/module/RouteTracker.web.js +1 -8
- package/lib/module/browserRouteRegistry.js +1 -24
- package/lib/module/browserRoutes.js +1 -92
- package/lib/module/components/NavigationStack.js +2 -568
- package/lib/module/components/ReactNavigationRoutes.js +1 -832
- package/lib/module/components/ReactNavigationRoutes.web.js +1 -3
- package/lib/module/components/RouteEventDetail.js +1 -93
- package/lib/module/components/RouteEventExpandedContent.js +1 -200
- package/lib/module/components/RouteEventItemCompact.js +1 -187
- package/lib/module/components/RouteEventsModalWithTabs.js +1 -731
- package/lib/module/components/RouteEventsTimeline.js +1 -103
- package/lib/module/components/RouteFilterViewV2.js +1 -38
- package/lib/module/components/RoutesSitemap.js +2 -1033
- package/lib/module/expoRouterStore.js +1 -114
- package/lib/module/expoRouterStore.web.js +1 -37
- package/lib/module/hooks/useRouteEvents.js +1 -54
- package/lib/module/index.js +1 -70
- package/lib/module/preset.js +1 -66
- package/lib/module/stores/navigationStackStore.js +1 -41
- package/lib/module/stores/routeEventStore.js +1 -128
- package/lib/module/sync/routeEventsSyncAdapter.js +1 -273
- package/lib/module/useNavigationStack.js +1 -404
- package/lib/module/useNavigationStack.web.js +1 -28
- package/lib/module/useRouteObserver.js +1 -70
- package/lib/module/useRouteObserverReactNavigation.js +1 -106
- package/lib/module/useRouteSitemap.js +1 -250
- package/lib/module/utils/routeTemplate.js +1 -73
- package/lib/module/utils/safeReactNavigation.js +1 -185
- package/lib/module/utils/safeReactNavigation.web.js +1 -27
- package/lib/module/utils/testIDs.js +1 -55
- package/lib/web/index.mjs +4 -5
- package/package.json +6 -11
- package/lib/typescript/RouteObserver.d.ts.map +0 -1
- package/lib/typescript/RouteParser.d.ts.map +0 -1
- package/lib/typescript/RouteTracker.d.ts.map +0 -1
- package/lib/typescript/RouteTracker.web.d.ts.map +0 -1
- package/lib/typescript/browserRouteRegistry.d.ts.map +0 -1
- package/lib/typescript/browserRoutes.d.ts.map +0 -1
- package/lib/typescript/components/NavigationStack.d.ts.map +0 -1
- package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +0 -1
- package/lib/typescript/components/ReactNavigationRoutes.web.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventDetail.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventItemCompact.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +0 -1
- package/lib/typescript/components/RouteEventsTimeline.d.ts.map +0 -1
- package/lib/typescript/components/RouteFilterViewV2.d.ts.map +0 -1
- package/lib/typescript/components/RoutesSitemap.d.ts.map +0 -1
- package/lib/typescript/expoRouterStore.d.ts.map +0 -1
- package/lib/typescript/expoRouterStore.web.d.ts.map +0 -1
- package/lib/typescript/hooks/useRouteEvents.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/stores/navigationStackStore.d.ts.map +0 -1
- package/lib/typescript/stores/routeEventStore.d.ts.map +0 -1
- package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/useNavigationStack.d.ts.map +0 -1
- package/lib/typescript/useNavigationStack.web.d.ts.map +0 -1
- package/lib/typescript/useRouteObserver.d.ts.map +0 -1
- package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +0 -1
- package/lib/typescript/useRouteSitemap.d.ts.map +0 -1
- package/lib/typescript/utils/routeTemplate.d.ts.map +0 -1
- package/lib/typescript/utils/safeReactNavigation.d.ts.map +0 -1
- package/lib/typescript/utils/safeReactNavigation.web.d.ts.map +0 -1
- package/lib/typescript/utils/testIDs.d.ts.map +0 -1
|
@@ -1,49 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* RouteObserver - Tracks route changes in Expo Router
|
|
5
|
-
*
|
|
6
|
-
* Note: This is a simple event emitter that works with the useRouteObserver hook
|
|
7
|
-
* The actual route tracking happens in the hook using public Expo Router APIs
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export class RouteObserver {
|
|
11
|
-
listeners = new Set();
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Emit a route change event
|
|
15
|
-
* Called by the useRouteObserver hook
|
|
16
|
-
*/
|
|
17
|
-
emit(event) {
|
|
18
|
-
// Notify all listeners
|
|
19
|
-
this.listeners.forEach(listener => {
|
|
20
|
-
try {
|
|
21
|
-
listener(event);
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.error("[RouteObserver] Error in listener:", error);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Add listener for route changes
|
|
30
|
-
* @returns Cleanup function to remove the listener
|
|
31
|
-
*/
|
|
32
|
-
addListener(callback) {
|
|
33
|
-
this.listeners.add(callback);
|
|
34
|
-
return () => this.listeners.delete(callback);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Remove a specific listener
|
|
39
|
-
*/
|
|
40
|
-
removeListener(callback) {
|
|
41
|
-
this.listeners.delete(callback);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Singleton instance of RouteObserver
|
|
47
|
-
* Use this for all route tracking to ensure events are centralized
|
|
48
|
-
*/
|
|
49
|
-
export const routeObserver = new RouteObserver();
|
|
1
|
+
"use strict";export class RouteObserver{listeners=new Set;emit(e){this.listeners.forEach(r=>{try{r(e)}catch(t){console.error("[RouteObserver] Error in listener:",t)}})}addListener(e){this.listeners.add(e);return()=>this.listeners.delete(e)}removeListener(e){this.listeners.delete(e)}}export const routeObserver=new RouteObserver;
|
|
@@ -1,309 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* RouteParser - Extract and organize routes from Expo Router's RouteNode tree
|
|
5
|
-
*
|
|
6
|
-
* Based on research findings in:
|
|
7
|
-
* - docs/routing/expo/ROUTENODE_TYPE_DEFINITION.md
|
|
8
|
-
* - docs/routing/expo/ROUTES_SITEMAP_RESEARCH.md
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Type-only definition to avoid Metro resolution issues
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Types & Interfaces
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Route type classification
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
// 404 fallback
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Parsed route information
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Grouped routes for display
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Route statistics
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// Route Parser Class
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
export class RouteParser {
|
|
40
|
-
/**
|
|
41
|
-
* Parse RouteNode tree and extract all routes
|
|
42
|
-
*/
|
|
43
|
-
static parseRouteTree(rootNode) {
|
|
44
|
-
if (!rootNode) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
const routes = [];
|
|
48
|
-
this.traverseNode(rootNode, '', routes, 0);
|
|
49
|
-
return routes;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Recursively traverse RouteNode tree
|
|
54
|
-
*/
|
|
55
|
-
static traverseNode(node, parentPath, routes, depth) {
|
|
56
|
-
const currentPath = this.buildPath(node, parentPath);
|
|
57
|
-
const routeType = this.detectRouteType(node);
|
|
58
|
-
const shouldInclude = this.shouldIncludeRoute(node);
|
|
59
|
-
const routeInfo = {
|
|
60
|
-
path: currentPath,
|
|
61
|
-
name: node.route,
|
|
62
|
-
type: routeType,
|
|
63
|
-
params: this.extractParams(node),
|
|
64
|
-
nodeType: node.type,
|
|
65
|
-
contextKey: node.contextKey,
|
|
66
|
-
isInternal: node.internal ?? false,
|
|
67
|
-
children: [],
|
|
68
|
-
depth
|
|
69
|
-
};
|
|
70
|
-
if (shouldInclude) {
|
|
71
|
-
routes.push(routeInfo);
|
|
72
|
-
}
|
|
73
|
-
for (const child of node.children) {
|
|
74
|
-
this.traverseNode(child, currentPath, shouldInclude ? routeInfo.children : routes, depth + 1);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Build full path for a route node
|
|
80
|
-
*/
|
|
81
|
-
static buildPath(node, parentPath) {
|
|
82
|
-
if (node.type === 'layout' && node.route === '_layout') {
|
|
83
|
-
return parentPath;
|
|
84
|
-
}
|
|
85
|
-
if (node.route.startsWith('(') && node.route.endsWith(')')) {
|
|
86
|
-
return parentPath;
|
|
87
|
-
}
|
|
88
|
-
if (node.route === 'index') {
|
|
89
|
-
return parentPath || '/';
|
|
90
|
-
}
|
|
91
|
-
const segment = node.route;
|
|
92
|
-
if (!parentPath || parentPath === '/') {
|
|
93
|
-
return `/${segment}`;
|
|
94
|
-
}
|
|
95
|
-
return `${parentPath}/${segment}`;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Detect the route type classification
|
|
100
|
-
*/
|
|
101
|
-
static detectRouteType(node) {
|
|
102
|
-
const routeName = node.route;
|
|
103
|
-
if (node.dynamic?.some(d => d.notFound)) {
|
|
104
|
-
return 'not-found';
|
|
105
|
-
}
|
|
106
|
-
if (node.dynamic?.some(d => d.deep)) {
|
|
107
|
-
return 'catch-all';
|
|
108
|
-
}
|
|
109
|
-
if (node.dynamic && node.dynamic.length > 0) {
|
|
110
|
-
return 'dynamic';
|
|
111
|
-
}
|
|
112
|
-
if (node.type === 'layout') {
|
|
113
|
-
return 'layout';
|
|
114
|
-
}
|
|
115
|
-
if (routeName.startsWith('(') && routeName.endsWith(')')) {
|
|
116
|
-
return 'group';
|
|
117
|
-
}
|
|
118
|
-
if (routeName === 'index') {
|
|
119
|
-
return 'index';
|
|
120
|
-
}
|
|
121
|
-
return 'static';
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Extract dynamic parameter names from route
|
|
126
|
-
*/
|
|
127
|
-
static extractParams(node) {
|
|
128
|
-
if (!node.dynamic || node.dynamic.length === 0) {
|
|
129
|
-
return [];
|
|
130
|
-
}
|
|
131
|
-
return node.dynamic.map(d => d.name);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Determine if route should be included in results
|
|
136
|
-
*/
|
|
137
|
-
static shouldIncludeRoute(node) {
|
|
138
|
-
if (node.internal) return false;
|
|
139
|
-
if (node.generated) return false;
|
|
140
|
-
if (node.type === 'api') return false;
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Organize routes into groups for display
|
|
146
|
-
*/
|
|
147
|
-
static organizeRoutes(routes) {
|
|
148
|
-
const groups = [];
|
|
149
|
-
|
|
150
|
-
// Flatten before grouping, like every other bucket — static/index screens
|
|
151
|
-
// nested under layouts (e.g. (tabs)/about at depth 2) must still be listed,
|
|
152
|
-
// or the stats header counts routes the list never shows.
|
|
153
|
-
const staticRoutes = this.flattenRoutes(routes).filter(r => r.type === 'static' || r.type === 'index');
|
|
154
|
-
const dynamicRoutes = this.flattenRoutes(routes).filter(r => r.type === 'dynamic' || r.type === 'catch-all');
|
|
155
|
-
const layoutRoutes = this.flattenRoutes(routes).filter(r => r.type === 'layout');
|
|
156
|
-
const groupedRoutes = this.flattenRoutes(routes).filter(r => r.type === 'group');
|
|
157
|
-
if (staticRoutes.length > 0) {
|
|
158
|
-
groups.push({
|
|
159
|
-
title: 'Static Routes',
|
|
160
|
-
icon: '',
|
|
161
|
-
routes: staticRoutes
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
if (dynamicRoutes.length > 0) {
|
|
165
|
-
groups.push({
|
|
166
|
-
title: 'Dynamic Routes',
|
|
167
|
-
icon: '',
|
|
168
|
-
description: 'Routes that accept parameters',
|
|
169
|
-
routes: dynamicRoutes
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
if (layoutRoutes.length > 0) {
|
|
173
|
-
groups.push({
|
|
174
|
-
title: 'Layouts',
|
|
175
|
-
icon: '',
|
|
176
|
-
description: 'Shared UI for nested screens',
|
|
177
|
-
routes: layoutRoutes
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
if (groupedRoutes.length > 0) {
|
|
181
|
-
groups.push({
|
|
182
|
-
title: 'Route Groups',
|
|
183
|
-
icon: '',
|
|
184
|
-
routes: groupedRoutes
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
return groups;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Flatten nested routes into a single array
|
|
192
|
-
*/
|
|
193
|
-
static flattenRoutes(routes) {
|
|
194
|
-
const flattened = [];
|
|
195
|
-
function traverse(route) {
|
|
196
|
-
flattened.push(route);
|
|
197
|
-
route.children.forEach(traverse);
|
|
198
|
-
}
|
|
199
|
-
routes.forEach(traverse);
|
|
200
|
-
return flattened;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Get route statistics
|
|
205
|
-
*/
|
|
206
|
-
static getRouteStats(routes) {
|
|
207
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
208
|
-
return {
|
|
209
|
-
total: flatRoutes.length,
|
|
210
|
-
static: flatRoutes.filter(r => r.type === 'static').length,
|
|
211
|
-
dynamic: flatRoutes.filter(r => r.type === 'dynamic').length,
|
|
212
|
-
catchAll: flatRoutes.filter(r => r.type === 'catch-all').length,
|
|
213
|
-
layouts: flatRoutes.filter(r => r.type === 'layout').length,
|
|
214
|
-
groups: flatRoutes.filter(r => r.type === 'group').length
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Search/filter routes by query
|
|
220
|
-
*/
|
|
221
|
-
static filterRoutes(routes, query) {
|
|
222
|
-
if (!query) {
|
|
223
|
-
return routes;
|
|
224
|
-
}
|
|
225
|
-
const lowerQuery = query.toLowerCase();
|
|
226
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
227
|
-
return flatRoutes.filter(route => {
|
|
228
|
-
return route.path.toLowerCase().includes(lowerQuery) || route.name.toLowerCase().includes(lowerQuery) || route.type.toLowerCase().includes(lowerQuery) || route.params.some(p => p.toLowerCase().includes(lowerQuery));
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Build a visual tree string representation
|
|
234
|
-
*/
|
|
235
|
-
static buildTreeString(routes, depth = 0) {
|
|
236
|
-
let output = '';
|
|
237
|
-
routes.forEach((route, index) => {
|
|
238
|
-
const isLast = index === routes.length - 1;
|
|
239
|
-
const prefix = this.buildTreePrefix(depth, isLast);
|
|
240
|
-
let routeLine = `${prefix}${route.path}`;
|
|
241
|
-
if (route.params.length > 0) {
|
|
242
|
-
routeLine += ` { ${route.params.join(', ')} }`;
|
|
243
|
-
}
|
|
244
|
-
output += routeLine + '\n';
|
|
245
|
-
if (route.children.length > 0) {
|
|
246
|
-
output += this.buildTreeString(route.children, depth + 1);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
return output;
|
|
250
|
-
}
|
|
251
|
-
static buildTreePrefix(depth, isLast) {
|
|
252
|
-
if (depth === 0) {
|
|
253
|
-
return '';
|
|
254
|
-
}
|
|
255
|
-
const indent = ' '.repeat(depth - 1);
|
|
256
|
-
const branch = isLast ? '└─ ' : '├─ ';
|
|
257
|
-
return indent + branch;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Sort routes by various criteria
|
|
262
|
-
*/
|
|
263
|
-
static sortRoutes(routes, sortBy = 'path') {
|
|
264
|
-
const sorted = [...routes];
|
|
265
|
-
sorted.sort((a, b) => {
|
|
266
|
-
switch (sortBy) {
|
|
267
|
-
case 'path':
|
|
268
|
-
return a.path.localeCompare(b.path);
|
|
269
|
-
case 'type':
|
|
270
|
-
return a.type.localeCompare(b.type);
|
|
271
|
-
case 'name':
|
|
272
|
-
return a.name.localeCompare(b.name);
|
|
273
|
-
default:
|
|
274
|
-
return 0;
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
return sorted;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Get route by path
|
|
282
|
-
*/
|
|
283
|
-
static findRouteByPath(routes, path) {
|
|
284
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
285
|
-
return flatRoutes.find(r => r.path === path) || null;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Get all parent routes for a given route
|
|
290
|
-
*/
|
|
291
|
-
static getParentRoutes(routes, targetPath) {
|
|
292
|
-
const parents = [];
|
|
293
|
-
const flatRoutes = this.flattenRoutes(routes);
|
|
294
|
-
const target = flatRoutes.find(r => r.path === targetPath);
|
|
295
|
-
if (!target) {
|
|
296
|
-
return parents;
|
|
297
|
-
}
|
|
298
|
-
const pathSegments = targetPath.split('/').filter(Boolean);
|
|
299
|
-
let currentPath = '';
|
|
300
|
-
for (let i = 0; i < pathSegments.length - 1; i++) {
|
|
301
|
-
currentPath += '/' + pathSegments[i];
|
|
302
|
-
const parent = flatRoutes.find(r => r.path === currentPath);
|
|
303
|
-
if (parent) {
|
|
304
|
-
parents.push(parent);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return parents;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
1
|
+
"use strict";export class RouteParser{static parseRouteTree(t){if(!t){return[]}const e=[];this.traverseNode(t,"",e,0);return e}static traverseNode(t,e,r,s){const n=this.buildPath(t,e);const a=this.detectRouteType(t);const i=this.shouldIncludeRoute(t);const u={path:n,name:t.route,type:a,params:this.extractParams(t),nodeType:t.type,contextKey:t.contextKey,isInternal:t.internal??false,children:[],depth:s};if(i){r.push(u)}for(const c of t.children){this.traverseNode(c,n,i?u.children:r,s+1)}}static buildPath(t,e){if(t.type==="layout"&&t.route==="_layout"){return e}if(t.route.startsWith("(")&&t.route.endsWith(")")){return e}if(t.route==="index"){return e||"/"}const r=t.route;if(!e||e==="/"){return`/${r}`}return`${e}/${r}`}static detectRouteType(t){const e=t.route;if(t.dynamic?.some(r=>r.notFound)){return"not-found"}if(t.dynamic?.some(r=>r.deep)){return"catch-all"}if(t.dynamic&&t.dynamic.length>0){return"dynamic"}if(t.type==="layout"){return"layout"}if(e.startsWith("(")&&e.endsWith(")")){return"group"}if(e==="index"){return"index"}return"static"}static extractParams(t){if(!t.dynamic||t.dynamic.length===0){return[]}return t.dynamic.map(e=>e.name)}static shouldIncludeRoute(t){if(t.internal)return false;if(t.generated)return false;if(t.type==="api")return false;return true}static organizeRoutes(t){const e=[];const r=this.flattenRoutes(t).filter(i=>i.type==="static"||i.type==="index");const s=this.flattenRoutes(t).filter(i=>i.type==="dynamic"||i.type==="catch-all");const n=this.flattenRoutes(t).filter(i=>i.type==="layout");const a=this.flattenRoutes(t).filter(i=>i.type==="group");if(r.length>0){e.push({title:"Static Routes",icon:"",routes:r})}if(s.length>0){e.push({title:"Dynamic Routes",icon:"",description:"Routes that accept parameters",routes:s})}if(n.length>0){e.push({title:"Layouts",icon:"",description:"Shared UI for nested screens",routes:n})}if(a.length>0){e.push({title:"Route Groups",icon:"",routes:a})}return e}static flattenRoutes(t){const e=[];function r(s){e.push(s);s.children.forEach(r)}t.forEach(r);return e}static getRouteStats(t){const e=this.flattenRoutes(t);return{total:e.length,static:e.filter(r=>r.type==="static").length,dynamic:e.filter(r=>r.type==="dynamic").length,catchAll:e.filter(r=>r.type==="catch-all").length,layouts:e.filter(r=>r.type==="layout").length,groups:e.filter(r=>r.type==="group").length}}static filterRoutes(t,e){if(!e){return t}const r=e.toLowerCase();const s=this.flattenRoutes(t);return s.filter(n=>{return n.path.toLowerCase().includes(r)||n.name.toLowerCase().includes(r)||n.type.toLowerCase().includes(r)||n.params.some(a=>a.toLowerCase().includes(r))})}static buildTreeString(t,e=0){let r="";t.forEach((s,n)=>{const a=n===t.length-1;const i=this.buildTreePrefix(e,a);let u=`${i}${s.path}`;if(s.params.length>0){u+=` { ${s.params.join(", ")} }`}r+=u+"\n";if(s.children.length>0){r+=this.buildTreeString(s.children,e+1)}});return r}static buildTreePrefix(t,e){if(t===0){return""}const r=" ".repeat(t-1);const s=e?"\u2514\u2500 ":"\u251C\u2500 ";return r+s}static sortRoutes(t,e="path"){const r=[...t];r.sort((s,n)=>{switch(e){case"path":return s.path.localeCompare(n.path);case"type":return s.type.localeCompare(n.type);case"name":return s.name.localeCompare(n.name);default:return 0}});return r}static findRouteByPath(t,e){const r=this.flattenRoutes(t);return r.find(s=>s.path===e)||null}static getParentRoutes(t,e){const r=[];const s=this.flattenRoutes(t);const n=s.find(u=>u.path===e);if(!n){return r}const a=e.split("/").filter(Boolean);let i="";for(let u=0;u<a.length-1;u++){i+="/"+a[u];const c=s.find(o=>o.path===i);if(c){r.push(c)}}return r}}
|
|
@@ -1,109 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* RouteTracker - A component to place inside your navigation tree
|
|
5
|
-
*
|
|
6
|
-
* Automatically detects whether Expo Router or React Navigation is available
|
|
7
|
-
* and uses the appropriate route observation method.
|
|
8
|
-
*
|
|
9
|
-
* IMPORTANT: This component MUST be placed inside your navigation tree
|
|
10
|
-
* (as a child of Stack, Tabs, Slot, or NavigationContainer) for route tracking to work.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```tsx
|
|
14
|
-
* // Expo Router - in your _layout.tsx
|
|
15
|
-
* import { RouteTracker } from '@buoy-gg/route-events';
|
|
16
|
-
*
|
|
17
|
-
* export default function RootLayout() {
|
|
18
|
-
* return (
|
|
19
|
-
* <>
|
|
20
|
-
* <Stack>
|
|
21
|
-
* <Stack.Screen name="(tabs)" />
|
|
22
|
-
* </Stack>
|
|
23
|
-
* <RouteTracker />
|
|
24
|
-
* <FloatingDevTools ... />
|
|
25
|
-
* </>
|
|
26
|
-
* );
|
|
27
|
-
* }
|
|
28
|
-
*
|
|
29
|
-
* // React Navigation (RN CLI) - inside NavigationContainer
|
|
30
|
-
* import { RouteTracker } from '@buoy-gg/route-events';
|
|
31
|
-
*
|
|
32
|
-
* function App() {
|
|
33
|
-
* return (
|
|
34
|
-
* <NavigationContainer>
|
|
35
|
-
* <RootNavigator />
|
|
36
|
-
* <RouteTracker />
|
|
37
|
-
* <FloatingDevTools ... />
|
|
38
|
-
* </NavigationContainer>
|
|
39
|
-
* );
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
import { useEffect } from "react";
|
|
45
|
-
import { isExpoRouterAvailable } from "@buoy-gg/shared-ui";
|
|
46
|
-
import { useRouteObserver } from "./useRouteObserver";
|
|
47
|
-
import { useRouteObserverReactNavigation } from "./useRouteObserverReactNavigation";
|
|
48
|
-
import { useNavigationStack } from "./useNavigationStack";
|
|
49
|
-
import { navigationStackStore } from "./stores/navigationStackStore";
|
|
50
|
-
import { routeEventStore } from "./stores/routeEventStore";
|
|
51
|
-
|
|
52
|
-
// Mounted at boot by FloatingDevTools, so this is where capture starts —
|
|
53
|
-
// routeEventStore.hydrateBootCapture explains why the store needs it.
|
|
54
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
55
|
-
function useBootCapture() {
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
routeEventStore.hydrateBootCapture();
|
|
58
|
-
}, []);
|
|
59
|
-
}
|
|
60
|
-
function ExpoRouteTracker() {
|
|
61
|
-
useBootCapture();
|
|
62
|
-
useRouteObserver();
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
function ReactNavigationRouteTracker() {
|
|
66
|
-
useBootCapture();
|
|
67
|
-
useRouteObserverReactNavigation();
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Mirrors the live navigation stack (and its action functions) into
|
|
73
|
-
* navigationStackStore so the route-events sync adapter can expose the Stack
|
|
74
|
-
* tab to the dashboard. Runs inside the navigation tree where the container ref
|
|
75
|
-
* context is available.
|
|
76
|
-
*/
|
|
77
|
-
function NavigationStackCapture() {
|
|
78
|
-
const {
|
|
79
|
-
stack,
|
|
80
|
-
navigateToIndex,
|
|
81
|
-
navigateToRoute,
|
|
82
|
-
popToIndex,
|
|
83
|
-
goBack,
|
|
84
|
-
popToTop
|
|
85
|
-
} = useNavigationStack();
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
navigationStackStore.setStack(stack);
|
|
88
|
-
}, [stack]);
|
|
89
|
-
|
|
90
|
-
// Keep the action references fresh (they change as the stack changes) so a
|
|
91
|
-
// remote action always operates on the current navigation state.
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
navigationStackStore.setActions({
|
|
94
|
-
navigateToIndex,
|
|
95
|
-
navigateToRoute,
|
|
96
|
-
popToIndex,
|
|
97
|
-
goBack,
|
|
98
|
-
popToTop
|
|
99
|
-
});
|
|
100
|
-
return () => navigationStackStore.setActions(null);
|
|
101
|
-
});
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
export function RouteTracker() {
|
|
105
|
-
const hasExpo = isExpoRouterAvailable();
|
|
106
|
-
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
107
|
-
children: [hasExpo ? /*#__PURE__*/_jsx(ExpoRouteTracker, {}) : /*#__PURE__*/_jsx(ReactNavigationRouteTracker, {}), /*#__PURE__*/_jsx(NavigationStackCapture, {})]
|
|
108
|
-
});
|
|
109
|
-
}
|
|
1
|
+
"use strict";import{useEffect as o}from"react";import{isExpoRouterAvailable as p}from"@buoy-gg/shared-ui";import{useRouteObserver as m}from"./useRouteObserver";import{useRouteObserverReactNavigation as f}from"./useRouteObserverReactNavigation";import{useNavigationStack as l}from"./useNavigationStack";import{navigationStackStore as r}from"./stores/navigationStackStore";import{routeEventStore as v}from"./stores/routeEventStore";import{jsx as e,Fragment as g,jsxs as x}from"react/jsx-runtime";function a(){o(()=>{v.hydrateBootCapture()},[])}function k(){a();m();return null}function R(){a();f();return null}function T(){const{stack:t,navigateToIndex:n,navigateToRoute:i,popToIndex:u,goBack:s,popToTop:c}=l();o(()=>{r.setStack(t)},[t]);o(()=>{r.setActions({navigateToIndex:n,navigateToRoute:i,popToIndex:u,goBack:s,popToTop:c});return()=>r.setActions(null)});return null}export function RouteTracker(){const t=p();return x(g,{children:[t?e(k,{}):e(R,{}),e(T,{})]})}
|
|
@@ -1,8 +1 @@
|
|
|
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
|
-
}
|
|
1
|
+
"use strict";import{useEffect as r}from"react";import{startBrowserRouteCapture as t}from"./browserRoutes";export function RouteTracker(){r(t,[]);return null}
|
|
@@ -1,24 +1 @@
|
|
|
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
|
-
}
|
|
1
|
+
"use strict";const t=new Map;let r=null;export function registerBrowserRoutes(o){for(const e of o)t.set(e,(t.get(e)??0)+1);r=Date.now();let n=true;return()=>{if(!n)return;n=false;for(const e of o){const s=(t.get(e)??1)-1;if(s)t.set(e,s);else t.delete(e)}r=Date.now()}}export function getBrowserRoutePaths(){return[...t.keys()]}export function getBrowserRoutesUpdatedAt(){return r}
|
|
@@ -1,92 +1 @@
|
|
|
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
|
-
}
|
|
1
|
+
"use strict";import{browserRouter as w,getBrowserHref as k,getBrowserRouteURL as b,subscribeBrowserNavigation as T}from"@buoy-gg/shared-ui/utils";import{routeObserver as B}from"./RouteObserver";import{routeEventStore as y}from"./stores/routeEventStore";import{navigationStackStore as u}from"./stores/navigationStackStore";let v=0;let m;export function startBrowserRouteCapture(){if(typeof window==="undefined")return()=>{};if(v++===0){y.hydrateBootCapture();let c;let a;let h="";const r=[];let t=-1;const d=e=>{if(e.href===h&&e.kind==="pop")return;if(e.kind==="replace"&&t>=0)r[t]=e.href;else if(e.kind==="pop"){const i=r.lastIndexOf(e.href,t-1);const o=r.indexOf(e.href,t+1);if(i>=0)t=i;else if(o>=0)t=o;else{r.splice(t+1);r.push(e.href);t++}}else{r.splice(t+1);r.push(e.href);t++}h=e.href;const s=b(e.href);const p=Date.now();B.emit({pathname:s.pathname,params:Object.fromEntries(s.searchParams),segments:s.pathname.split("/").filter(Boolean),previousPathname:c,timestamp:p,timeSincePrevious:a===void 0?void 0:p-a});c=s.pathname;a=p;u.setStack(r.map((i,o)=>{const f=b(i);return{key:`browser:${o}`,name:f.pathname,pathname:f.pathname,params:Object.fromEntries(f.searchParams),index:o,isFocused:o===t,canPop:o>0}}))};const n=e=>{if(!Number.isInteger(e)||e<0||e>=r.length)throw new Error("That browser history entry is unavailable.");window.history.go(e-t)};u.setActions({navigateToIndex:n,popToIndex:n,goBack:w.back,popToTop:()=>n(0),navigateToRoute:(e,s)=>{w.push({pathname:e,params:s});return true}});m=T(d);d({href:k(),kind:"initial"})}let l=false;return()=>{if(l)return;l=true;if(--v===0){m?.();m=void 0;u.setActions(null)}}}
|