@openwebf/react-router 0.22.18 → 0.24.0
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/dist/index.d.ts +89 -4
- package/dist/index.esm.js +367 -67
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +371 -66
- package/dist/index.js.map +1 -1
- package/dist/routes/Route.d.ts +13 -1
- package/dist/routes/Route.d.ts.map +1 -1
- package/dist/routes/Routes.d.ts +34 -0
- package/dist/routes/Routes.d.ts.map +1 -1
- package/dist/routes/utils.d.ts +40 -1
- package/dist/routes/utils.d.ts.map +1 -1
- package/dist/utils/RouterLink.d.ts +1 -0
- package/dist/utils/RouterLink.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var webfEnterpriseTypings = require('@openwebf/webf-enterprise-typings');
|
|
4
3
|
var React = require('react');
|
|
5
4
|
var reactCoreUi = require('@openwebf/react-core-ui');
|
|
6
5
|
|
|
@@ -36,6 +35,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
36
35
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
37
36
|
};
|
|
38
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Router management module
|
|
40
|
+
*
|
|
41
|
+
* Encapsulates routing navigation functionality with route guard mechanism for permission checks
|
|
42
|
+
*/
|
|
43
|
+
function getHybridHistory() {
|
|
44
|
+
var _a;
|
|
45
|
+
return (_a = globalThis === null || globalThis === void 0 ? void 0 : globalThis.webf) === null || _a === void 0 ? void 0 : _a.hybridHistory;
|
|
46
|
+
}
|
|
47
|
+
let ensureRouteMountedCallback = null;
|
|
48
|
+
function __unstable_setEnsureRouteMountedCallback(callback) {
|
|
49
|
+
ensureRouteMountedCallback = callback;
|
|
50
|
+
}
|
|
51
|
+
function ensureRouteMounted(pathname) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
if (!ensureRouteMountedCallback)
|
|
54
|
+
return;
|
|
55
|
+
yield ensureRouteMountedCallback(pathname);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
39
58
|
/**
|
|
40
59
|
* WebF Router object - provides comprehensive navigation APIs
|
|
41
60
|
* Combines web-like history management with Flutter-like navigation patterns
|
|
@@ -45,112 +64,231 @@ const WebFRouter = {
|
|
|
45
64
|
* Get the current state object associated with the history entry
|
|
46
65
|
*/
|
|
47
66
|
get state() {
|
|
48
|
-
|
|
67
|
+
var _a;
|
|
68
|
+
return (_a = getHybridHistory()) === null || _a === void 0 ? void 0 : _a.state;
|
|
49
69
|
},
|
|
50
70
|
/**
|
|
51
71
|
* Get the full hybrid router build context stack.
|
|
52
72
|
* The stack is ordered from root route (index 0) to the current top route (last element).
|
|
53
73
|
*/
|
|
54
74
|
get stack() {
|
|
55
|
-
|
|
75
|
+
var _a, _b;
|
|
76
|
+
return (_b = (_a = getHybridHistory()) === null || _a === void 0 ? void 0 : _a.buildContextStack) !== null && _b !== void 0 ? _b : [];
|
|
56
77
|
},
|
|
57
78
|
/**
|
|
58
79
|
* Get the current route path
|
|
59
80
|
*/
|
|
60
81
|
get path() {
|
|
61
|
-
|
|
82
|
+
var _a, _b;
|
|
83
|
+
return (_b = (_a = getHybridHistory()) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : '/';
|
|
62
84
|
},
|
|
63
85
|
/**
|
|
64
86
|
* Navigate to a specified route
|
|
65
87
|
* Applies route guards for permission checks before navigation
|
|
66
88
|
*/
|
|
67
89
|
push: (path, state) => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
-
|
|
90
|
+
const hybridHistory = getHybridHistory();
|
|
91
|
+
if (!hybridHistory)
|
|
92
|
+
throw new Error('WebF hybridHistory is not available');
|
|
93
|
+
yield ensureRouteMounted(path);
|
|
94
|
+
hybridHistory.pushNamed(path, { arguments: state });
|
|
69
95
|
}),
|
|
70
96
|
/**
|
|
71
97
|
* Replace the current route without adding to history
|
|
72
98
|
* Applies route guards for permission checks before navigation
|
|
73
99
|
*/
|
|
74
100
|
replace: (path, state) => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
-
|
|
101
|
+
const hybridHistory = getHybridHistory();
|
|
102
|
+
if (!hybridHistory)
|
|
103
|
+
throw new Error('WebF hybridHistory is not available');
|
|
104
|
+
yield ensureRouteMounted(path);
|
|
105
|
+
hybridHistory.pushReplacementNamed(path, { arguments: state });
|
|
76
106
|
}),
|
|
77
107
|
/**
|
|
78
108
|
* Navigate back to the previous route
|
|
79
109
|
*/
|
|
80
110
|
back: () => {
|
|
81
|
-
|
|
111
|
+
const hybridHistory = getHybridHistory();
|
|
112
|
+
if (!hybridHistory)
|
|
113
|
+
throw new Error('WebF hybridHistory is not available');
|
|
114
|
+
hybridHistory.back();
|
|
82
115
|
},
|
|
83
116
|
/**
|
|
84
117
|
* Close the current screen and return to the previous one
|
|
85
118
|
* Flutter-style navigation method
|
|
86
119
|
*/
|
|
87
120
|
pop: (result) => {
|
|
88
|
-
|
|
121
|
+
const hybridHistory = getHybridHistory();
|
|
122
|
+
if (!hybridHistory)
|
|
123
|
+
throw new Error('WebF hybridHistory is not available');
|
|
124
|
+
hybridHistory.pop(result);
|
|
89
125
|
},
|
|
90
126
|
/**
|
|
91
127
|
* Pop routes until reaching a specific route
|
|
92
128
|
*/
|
|
93
129
|
popUntil: (path) => {
|
|
94
|
-
|
|
130
|
+
const hybridHistory = getHybridHistory();
|
|
131
|
+
if (!hybridHistory)
|
|
132
|
+
throw new Error('WebF hybridHistory is not available');
|
|
133
|
+
hybridHistory.popUntil(path);
|
|
95
134
|
},
|
|
96
135
|
/**
|
|
97
136
|
* Pop the current route and push a new named route
|
|
98
137
|
*/
|
|
99
138
|
popAndPushNamed: (path, state) => __awaiter(void 0, void 0, void 0, function* () {
|
|
100
|
-
|
|
139
|
+
const hybridHistory = getHybridHistory();
|
|
140
|
+
if (!hybridHistory)
|
|
141
|
+
throw new Error('WebF hybridHistory is not available');
|
|
142
|
+
yield ensureRouteMounted(path);
|
|
143
|
+
hybridHistory.popAndPushNamed(path, { arguments: state });
|
|
101
144
|
}),
|
|
102
145
|
/**
|
|
103
146
|
* Push a new route and remove routes until reaching a specific route
|
|
104
147
|
*/
|
|
105
148
|
pushNamedAndRemoveUntil: (path, state, untilPath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
106
|
-
|
|
149
|
+
const hybridHistory = getHybridHistory();
|
|
150
|
+
if (!hybridHistory)
|
|
151
|
+
throw new Error('WebF hybridHistory is not available');
|
|
152
|
+
yield ensureRouteMounted(path);
|
|
153
|
+
hybridHistory.pushNamedAndRemoveUntil(state, path, untilPath);
|
|
107
154
|
}),
|
|
108
155
|
/**
|
|
109
156
|
* Push a new route and remove all routes until a specific route (Flutter-style)
|
|
110
157
|
*/
|
|
111
158
|
pushNamedAndRemoveUntilRoute: (newPath, untilPath, state) => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
|
-
|
|
159
|
+
const hybridHistory = getHybridHistory();
|
|
160
|
+
if (!hybridHistory)
|
|
161
|
+
throw new Error('WebF hybridHistory is not available');
|
|
162
|
+
yield ensureRouteMounted(newPath);
|
|
163
|
+
hybridHistory.pushNamedAndRemoveUntilRoute(newPath, untilPath, { arguments: state });
|
|
113
164
|
}),
|
|
114
165
|
/**
|
|
115
166
|
* Check if the navigator can go back
|
|
116
167
|
*/
|
|
117
168
|
canPop: () => {
|
|
118
|
-
|
|
169
|
+
const hybridHistory = getHybridHistory();
|
|
170
|
+
if (!hybridHistory)
|
|
171
|
+
return false;
|
|
172
|
+
return hybridHistory.canPop();
|
|
119
173
|
},
|
|
120
174
|
/**
|
|
121
175
|
* Pop the current route if possible
|
|
122
176
|
* Returns true if the pop was successful, false otherwise
|
|
123
177
|
*/
|
|
124
178
|
maybePop: (result) => {
|
|
125
|
-
|
|
179
|
+
const hybridHistory = getHybridHistory();
|
|
180
|
+
if (!hybridHistory)
|
|
181
|
+
return false;
|
|
182
|
+
return hybridHistory.maybePop(result);
|
|
126
183
|
},
|
|
127
184
|
/**
|
|
128
185
|
* Push a new state to the history stack (web-style navigation)
|
|
129
186
|
*/
|
|
130
187
|
pushState: (state, name) => {
|
|
131
|
-
|
|
188
|
+
const hybridHistory = getHybridHistory();
|
|
189
|
+
if (!hybridHistory)
|
|
190
|
+
throw new Error('WebF hybridHistory is not available');
|
|
191
|
+
hybridHistory.pushState(state, name);
|
|
132
192
|
},
|
|
133
193
|
/**
|
|
134
194
|
* Replace the current history entry with a new one (web-style navigation)
|
|
135
195
|
*/
|
|
136
196
|
replaceState: (state, name) => {
|
|
137
|
-
|
|
197
|
+
const hybridHistory = getHybridHistory();
|
|
198
|
+
if (!hybridHistory)
|
|
199
|
+
throw new Error('WebF hybridHistory is not available');
|
|
200
|
+
hybridHistory.replaceState(state, name);
|
|
138
201
|
},
|
|
139
202
|
/**
|
|
140
203
|
* Pop and push with restoration capability
|
|
141
204
|
* Returns a restoration ID string
|
|
142
205
|
*/
|
|
143
206
|
restorablePopAndPushState: (state, name) => {
|
|
144
|
-
|
|
207
|
+
const hybridHistory = getHybridHistory();
|
|
208
|
+
if (!hybridHistory)
|
|
209
|
+
throw new Error('WebF hybridHistory is not available');
|
|
210
|
+
return hybridHistory.restorablePopAndPushState(state, name);
|
|
145
211
|
},
|
|
146
212
|
/**
|
|
147
213
|
* Pop and push named route with restoration capability
|
|
148
214
|
* Returns a restoration ID string
|
|
149
215
|
*/
|
|
150
216
|
restorablePopAndPushNamed: (path, state) => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
-
|
|
217
|
+
const hybridHistory = getHybridHistory();
|
|
218
|
+
if (!hybridHistory)
|
|
219
|
+
throw new Error('WebF hybridHistory is not available');
|
|
220
|
+
yield ensureRouteMounted(path);
|
|
221
|
+
return hybridHistory.restorablePopAndPushNamed(path, { arguments: state });
|
|
152
222
|
})
|
|
153
223
|
};
|
|
224
|
+
/**
|
|
225
|
+
* Convert a route pattern to a regular expression
|
|
226
|
+
* @param pattern Route pattern like "/user/:userId" or "/category/:catId/product/:prodId"
|
|
227
|
+
* @returns Object with regex and parameter names
|
|
228
|
+
*/
|
|
229
|
+
function pathToRegex(pattern) {
|
|
230
|
+
const paramNames = [];
|
|
231
|
+
if (pattern === '*') {
|
|
232
|
+
paramNames.push('*');
|
|
233
|
+
return { regex: /^(.*)$/, paramNames };
|
|
234
|
+
}
|
|
235
|
+
// Escape special regex characters except : and *
|
|
236
|
+
let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
237
|
+
// Replace :param with named capture groups
|
|
238
|
+
regexPattern = regexPattern.replace(/:([^\/]+)/g, (_, paramName) => {
|
|
239
|
+
paramNames.push(paramName);
|
|
240
|
+
return '([^/]+)';
|
|
241
|
+
});
|
|
242
|
+
// Replace * with a splat capture group (matches across segments)
|
|
243
|
+
regexPattern = regexPattern.replace(/\*/g, () => {
|
|
244
|
+
paramNames.push('*');
|
|
245
|
+
return '(.*)';
|
|
246
|
+
});
|
|
247
|
+
// Add anchors for exact matching
|
|
248
|
+
regexPattern = `^${regexPattern}$`;
|
|
249
|
+
return {
|
|
250
|
+
regex: new RegExp(regexPattern),
|
|
251
|
+
paramNames
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Match a pathname against a route pattern and extract parameters
|
|
256
|
+
* @param pattern Route pattern like "/user/:userId"
|
|
257
|
+
* @param pathname Actual pathname like "/user/123"
|
|
258
|
+
* @returns Match result with extracted parameters or null if no match
|
|
259
|
+
*/
|
|
260
|
+
function matchPath(pattern, pathname) {
|
|
261
|
+
const { regex, paramNames } = pathToRegex(pattern);
|
|
262
|
+
const match = pathname.match(regex);
|
|
263
|
+
if (!match) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
// Extract parameters from capture groups
|
|
267
|
+
const params = {};
|
|
268
|
+
paramNames.forEach((paramName, index) => {
|
|
269
|
+
params[paramName] = match[index + 1]; // +1 because match[0] is the full match
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
path: pattern,
|
|
273
|
+
params,
|
|
274
|
+
isExact: true
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Find the best matching route from a list of route patterns
|
|
279
|
+
* @param routes Array of route patterns
|
|
280
|
+
* @param pathname Current pathname
|
|
281
|
+
* @returns Best match or null if no routes match
|
|
282
|
+
*/
|
|
283
|
+
function matchRoutes(routes, pathname) {
|
|
284
|
+
for (const route of routes) {
|
|
285
|
+
const match = matchPath(route, pathname);
|
|
286
|
+
if (match) {
|
|
287
|
+
return match;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
154
292
|
|
|
155
293
|
var isFunction = function (value) {
|
|
156
294
|
return typeof value === 'function';
|
|
@@ -186,7 +324,7 @@ const RawWebFRouterLink = reactCoreUi.createWebFComponent({
|
|
|
186
324
|
tagName: 'webf-router-link',
|
|
187
325
|
displayName: 'WebFRouterLink',
|
|
188
326
|
// Map props to attributes
|
|
189
|
-
attributeProps: ['path', 'title'],
|
|
327
|
+
attributeProps: ['path', 'title', 'theme'],
|
|
190
328
|
// Event handlers
|
|
191
329
|
events: [
|
|
192
330
|
{
|
|
@@ -215,7 +353,7 @@ const WebFRouterLink = function (props) {
|
|
|
215
353
|
props.onScreen(event);
|
|
216
354
|
}
|
|
217
355
|
};
|
|
218
|
-
return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
|
|
356
|
+
return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, theme: props.theme, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
|
|
219
357
|
};
|
|
220
358
|
|
|
221
359
|
/**
|
|
@@ -232,7 +370,7 @@ const WebFRouterLink = function (props) {
|
|
|
232
370
|
*
|
|
233
371
|
* Responsible for managing page rendering, lifecycle and navigation bar
|
|
234
372
|
*/
|
|
235
|
-
function Route({ path, prerender = false, element, title }) {
|
|
373
|
+
function Route({ path, mountedPath, prerender = false, element, title, theme }) {
|
|
236
374
|
// Mark whether the page has been rendered
|
|
237
375
|
const [hasRendered, updateRender] = React.useState(false);
|
|
238
376
|
/**
|
|
@@ -251,7 +389,7 @@ function Route({ path, prerender = false, element, title }) {
|
|
|
251
389
|
*/
|
|
252
390
|
const handleOffScreen = useMemoizedFn(() => {
|
|
253
391
|
});
|
|
254
|
-
return (React.createElement(WebFRouterLink, { path: path, title: title, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
|
|
392
|
+
return (React.createElement(WebFRouterLink, { path: mountedPath !== null && mountedPath !== void 0 ? mountedPath : path, title: title, theme: theme, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
|
|
255
393
|
}
|
|
256
394
|
|
|
257
395
|
/**
|
|
@@ -259,7 +397,9 @@ function Route({ path, prerender = false, element, title }) {
|
|
|
259
397
|
*/
|
|
260
398
|
const RouteContext = React.createContext({
|
|
261
399
|
path: undefined,
|
|
400
|
+
mountedPath: undefined,
|
|
262
401
|
params: undefined,
|
|
402
|
+
routeParams: undefined,
|
|
263
403
|
activePath: undefined,
|
|
264
404
|
routeEventKind: undefined
|
|
265
405
|
});
|
|
@@ -278,9 +418,9 @@ const RouteContext = React.createContext({
|
|
|
278
418
|
*/
|
|
279
419
|
function useRouteContext() {
|
|
280
420
|
const context = React.useContext(RouteContext);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
&& context.
|
|
421
|
+
const isActive = context.activePath !== undefined
|
|
422
|
+
&& context.mountedPath !== undefined
|
|
423
|
+
&& context.activePath === context.mountedPath;
|
|
284
424
|
return Object.assign(Object.assign({}, context), { isActive });
|
|
285
425
|
}
|
|
286
426
|
/**
|
|
@@ -294,6 +434,7 @@ function useRouteContext() {
|
|
|
294
434
|
* const location = useLocation();
|
|
295
435
|
*
|
|
296
436
|
* console.log('Current path:', location.pathname);
|
|
437
|
+
|
|
297
438
|
* console.log('Location state:', location.state);
|
|
298
439
|
* console.log('Is active:', location.isActive);
|
|
299
440
|
*
|
|
@@ -305,25 +446,43 @@ function useLocation() {
|
|
|
305
446
|
const context = useRouteContext();
|
|
306
447
|
// Create location object from context
|
|
307
448
|
const location = React.useMemo(() => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
isActive: true,
|
|
314
|
-
key: `${context.path}-active-${Date.now()}`
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
// For inactive routes, return the global location without state
|
|
449
|
+
const pathname = context.activePath || WebFRouter.path;
|
|
450
|
+
// Get state - prioritize context params, fallback to WebFRouter.state
|
|
451
|
+
const state = context.isActive
|
|
452
|
+
? (context.params || WebFRouter.state)
|
|
453
|
+
: WebFRouter.state;
|
|
318
454
|
return {
|
|
319
|
-
pathname
|
|
320
|
-
state
|
|
321
|
-
isActive:
|
|
322
|
-
key: `${
|
|
455
|
+
pathname,
|
|
456
|
+
state,
|
|
457
|
+
isActive: context.isActive,
|
|
458
|
+
key: `${pathname}-${Date.now()}`
|
|
323
459
|
};
|
|
324
|
-
}, [context.isActive, context.
|
|
460
|
+
}, [context.isActive, context.activePath, context.params]);
|
|
325
461
|
return location;
|
|
326
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Hook to get route parameters from dynamic routes
|
|
465
|
+
*
|
|
466
|
+
* @returns Route parameters object with parameter names as keys and values as strings
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```tsx
|
|
470
|
+
* // For route pattern "/user/:userId" and actual path "/user/123"
|
|
471
|
+
* function UserPage() {
|
|
472
|
+
* const params = useParams();
|
|
473
|
+
*
|
|
474
|
+
* console.log(params.userId); // "123"
|
|
475
|
+
*
|
|
476
|
+
* return <div>User ID: {params.userId}</div>;
|
|
477
|
+
* }
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
function useParams() {
|
|
481
|
+
const context = useRouteContext();
|
|
482
|
+
return React.useMemo(() => {
|
|
483
|
+
return context.routeParams || {};
|
|
484
|
+
}, [context.routeParams]);
|
|
485
|
+
}
|
|
327
486
|
/**
|
|
328
487
|
* Routes component that wraps multiple Route components and provides shared context
|
|
329
488
|
*
|
|
@@ -339,50 +498,113 @@ function useLocation() {
|
|
|
339
498
|
/**
|
|
340
499
|
* Route-specific context provider that only updates when the route is active
|
|
341
500
|
*/
|
|
342
|
-
function RouteContextProvider({
|
|
501
|
+
function RouteContextProvider({ patternPath, mountedPath, children, }) {
|
|
343
502
|
const globalContext = React.useContext(RouteContext);
|
|
344
503
|
// Create a route-specific context that only updates when this route is active
|
|
345
504
|
const routeSpecificContext = React.useMemo(() => {
|
|
346
|
-
|
|
347
|
-
|
|
505
|
+
const isActive = globalContext.activePath !== undefined && globalContext.activePath === mountedPath;
|
|
506
|
+
const match = isActive ? matchPath(patternPath, mountedPath) : null;
|
|
507
|
+
if (isActive && match) {
|
|
508
|
+
const effectiveParams = globalContext.params !== undefined ? globalContext.params : WebFRouter.state;
|
|
348
509
|
return {
|
|
349
|
-
path,
|
|
350
|
-
|
|
510
|
+
path: patternPath,
|
|
511
|
+
mountedPath,
|
|
512
|
+
params: effectiveParams,
|
|
513
|
+
routeParams: match.params,
|
|
351
514
|
activePath: globalContext.activePath,
|
|
352
515
|
routeEventKind: globalContext.routeEventKind
|
|
353
516
|
};
|
|
354
517
|
}
|
|
355
|
-
// Return previous values if not active
|
|
356
518
|
return {
|
|
357
|
-
path,
|
|
519
|
+
path: patternPath,
|
|
520
|
+
mountedPath,
|
|
358
521
|
params: undefined,
|
|
522
|
+
routeParams: undefined,
|
|
359
523
|
activePath: globalContext.activePath,
|
|
360
524
|
routeEventKind: undefined
|
|
361
525
|
};
|
|
362
|
-
}, [
|
|
526
|
+
}, [patternPath, mountedPath, globalContext.activePath, globalContext.params, globalContext.routeEventKind]);
|
|
363
527
|
return (React.createElement(RouteContext.Provider, { value: routeSpecificContext }, children));
|
|
364
528
|
}
|
|
529
|
+
function patternScore(pattern) {
|
|
530
|
+
if (pattern === '*')
|
|
531
|
+
return 0;
|
|
532
|
+
const segments = pattern.split('/').filter(Boolean);
|
|
533
|
+
let score = 0;
|
|
534
|
+
for (const segment of segments) {
|
|
535
|
+
if (segment === '*')
|
|
536
|
+
score += 1;
|
|
537
|
+
else if (segment.startsWith(':'))
|
|
538
|
+
score += 2;
|
|
539
|
+
else
|
|
540
|
+
score += 3;
|
|
541
|
+
}
|
|
542
|
+
return score * 100 + segments.length;
|
|
543
|
+
}
|
|
544
|
+
function findBestMatch(patterns, pathname) {
|
|
545
|
+
var _a;
|
|
546
|
+
let best = null;
|
|
547
|
+
for (const pattern of patterns) {
|
|
548
|
+
const match = matchPath(pattern, pathname);
|
|
549
|
+
if (!match)
|
|
550
|
+
continue;
|
|
551
|
+
const score = patternScore(pattern);
|
|
552
|
+
if (!best || score > best.score)
|
|
553
|
+
best = { match, score };
|
|
554
|
+
}
|
|
555
|
+
return (_a = best === null || best === void 0 ? void 0 : best.match) !== null && _a !== void 0 ? _a : null;
|
|
556
|
+
}
|
|
557
|
+
function escapeAttributeValue(value) {
|
|
558
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
559
|
+
}
|
|
365
560
|
function Routes({ children }) {
|
|
366
561
|
// State to track current route information
|
|
367
562
|
const [routeState, setRouteState] = React.useState({
|
|
368
563
|
path: undefined,
|
|
369
|
-
|
|
564
|
+
mountedPath: undefined,
|
|
565
|
+
activePath: WebFRouter.path, // Initialize with current path
|
|
370
566
|
params: undefined,
|
|
567
|
+
routeParams: undefined,
|
|
371
568
|
routeEventKind: undefined
|
|
372
569
|
});
|
|
570
|
+
const [stack, setStack] = React.useState(() => WebFRouter.stack);
|
|
571
|
+
const [preMountedPaths, setPreMountedPaths] = React.useState([]);
|
|
572
|
+
const routePatternsRef = React.useRef([]);
|
|
573
|
+
const pendingEnsureResolversRef = React.useRef(new Map());
|
|
574
|
+
// Keep a stable view of declared route patterns for event handlers.
|
|
575
|
+
React.useEffect(() => {
|
|
576
|
+
const patterns = [];
|
|
577
|
+
React.Children.forEach(children, (child) => {
|
|
578
|
+
if (!React.isValidElement(child))
|
|
579
|
+
return;
|
|
580
|
+
if (child.type !== Route)
|
|
581
|
+
return;
|
|
582
|
+
patterns.push(child.props.path);
|
|
583
|
+
});
|
|
584
|
+
routePatternsRef.current = patterns;
|
|
585
|
+
}, [children]);
|
|
373
586
|
// Listen to hybridrouterchange event
|
|
374
587
|
React.useEffect(() => {
|
|
375
588
|
const handleRouteChange = (event) => {
|
|
589
|
+
var _a, _b, _c;
|
|
376
590
|
const routeEvent = event;
|
|
377
|
-
//
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
591
|
+
// Check for new event detail structure with params
|
|
592
|
+
const eventDetail = event.detail;
|
|
593
|
+
const newActivePath = WebFRouter.path;
|
|
594
|
+
const newStack = WebFRouter.stack;
|
|
595
|
+
setStack(newStack);
|
|
596
|
+
setPreMountedPaths((prev) => prev.filter((p) => newStack.some((entry) => entry.path === p)));
|
|
597
|
+
const bestMatch = newActivePath ? findBestMatch(routePatternsRef.current, newActivePath) : null;
|
|
598
|
+
const routeParams = (eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.params) || (bestMatch === null || bestMatch === void 0 ? void 0 : bestMatch.params) || undefined;
|
|
599
|
+
const activeEntry = [...newStack].reverse().find((entry) => entry.path === newActivePath);
|
|
600
|
+
const eventState = (_c = (_b = (_a = activeEntry === null || activeEntry === void 0 ? void 0 : activeEntry.state) !== null && _a !== void 0 ? _a : eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.state) !== null && _b !== void 0 ? _b : routeEvent.state) !== null && _c !== void 0 ? _c : WebFRouter.state;
|
|
381
601
|
// Update state based on event kind
|
|
382
602
|
setRouteState({
|
|
383
603
|
path: routeEvent.path,
|
|
604
|
+
mountedPath: routeEvent.path,
|
|
384
605
|
activePath: newActivePath,
|
|
385
|
-
params:
|
|
606
|
+
params: eventState,
|
|
607
|
+
routeParams: routeParams, // Use params from Flutter if available
|
|
386
608
|
routeEventKind: routeEvent.kind
|
|
387
609
|
});
|
|
388
610
|
};
|
|
@@ -392,30 +614,108 @@ function Routes({ children }) {
|
|
|
392
614
|
return () => {
|
|
393
615
|
document.removeEventListener('hybridrouterchange', handleRouteChange);
|
|
394
616
|
};
|
|
395
|
-
}, [
|
|
617
|
+
}, []);
|
|
618
|
+
React.useEffect(() => {
|
|
619
|
+
__unstable_setEnsureRouteMountedCallback((pathname) => {
|
|
620
|
+
var _a;
|
|
621
|
+
if (!pathname)
|
|
622
|
+
return;
|
|
623
|
+
const bestMatch = findBestMatch(routePatternsRef.current, pathname);
|
|
624
|
+
if (!bestMatch)
|
|
625
|
+
return;
|
|
626
|
+
const selector = `webf-router-link[path="${escapeAttributeValue(pathname)}"]`;
|
|
627
|
+
if (document.querySelector(selector))
|
|
628
|
+
return;
|
|
629
|
+
let resolveFn;
|
|
630
|
+
const promise = new Promise((resolve) => {
|
|
631
|
+
resolveFn = resolve;
|
|
632
|
+
});
|
|
633
|
+
pendingEnsureResolversRef.current.set(pathname, [
|
|
634
|
+
...((_a = pendingEnsureResolversRef.current.get(pathname)) !== null && _a !== void 0 ? _a : []),
|
|
635
|
+
resolveFn,
|
|
636
|
+
]);
|
|
637
|
+
setPreMountedPaths((prev) => (prev.includes(pathname) ? prev : [...prev, pathname]));
|
|
638
|
+
return promise;
|
|
639
|
+
});
|
|
640
|
+
return () => {
|
|
641
|
+
__unstable_setEnsureRouteMountedCallback(null);
|
|
642
|
+
};
|
|
643
|
+
}, []);
|
|
644
|
+
React.useEffect(() => {
|
|
645
|
+
const pending = pendingEnsureResolversRef.current;
|
|
646
|
+
for (const [pathname, resolvers] of pending.entries()) {
|
|
647
|
+
const selector = `webf-router-link[path="${escapeAttributeValue(pathname)}"]`;
|
|
648
|
+
if (!document.querySelector(selector))
|
|
649
|
+
continue;
|
|
650
|
+
for (const resolve of resolvers)
|
|
651
|
+
resolve();
|
|
652
|
+
pending.delete(pathname);
|
|
653
|
+
}
|
|
654
|
+
}, [children, stack, preMountedPaths, routeState.activePath]);
|
|
396
655
|
// Global context value
|
|
397
656
|
const globalContextValue = React.useMemo(() => ({
|
|
398
657
|
path: undefined,
|
|
658
|
+
mountedPath: undefined,
|
|
399
659
|
params: routeState.params,
|
|
660
|
+
routeParams: routeState.routeParams, // Pass through route params from Flutter
|
|
400
661
|
activePath: routeState.activePath,
|
|
401
662
|
routeEventKind: routeState.routeEventKind
|
|
402
|
-
}), [routeState.activePath, routeState.params, routeState.routeEventKind]);
|
|
403
|
-
// Wrap each Route component with its own context provider
|
|
663
|
+
}), [routeState.activePath, routeState.params, routeState.routeParams, routeState.routeEventKind]);
|
|
404
664
|
const wrappedChildren = React.useMemo(() => {
|
|
405
|
-
|
|
665
|
+
const declaredRoutes = [];
|
|
666
|
+
const patterns = [];
|
|
667
|
+
const declaredPaths = new Set();
|
|
668
|
+
React.Children.forEach(children, (child) => {
|
|
669
|
+
var _a;
|
|
406
670
|
if (!React.isValidElement(child)) {
|
|
407
|
-
|
|
671
|
+
declaredRoutes.push(child);
|
|
672
|
+
return;
|
|
408
673
|
}
|
|
409
|
-
// Ensure only Route components are direct children
|
|
410
674
|
if (child.type !== Route) {
|
|
411
675
|
console.warn('Routes component should only contain Route components as direct children');
|
|
412
|
-
|
|
676
|
+
declaredRoutes.push(child);
|
|
677
|
+
return;
|
|
413
678
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
679
|
+
const patternPath = child.props.path;
|
|
680
|
+
patterns.push(patternPath);
|
|
681
|
+
declaredPaths.add(patternPath);
|
|
682
|
+
const mountedPath = (_a = child.props.mountedPath) !== null && _a !== void 0 ? _a : patternPath;
|
|
683
|
+
declaredRoutes.push(React.createElement(RouteContextProvider, { key: `declared:${patternPath}`, patternPath: patternPath, mountedPath: mountedPath }, child));
|
|
417
684
|
});
|
|
418
|
-
|
|
685
|
+
const mountedPaths = [];
|
|
686
|
+
for (const entry of stack)
|
|
687
|
+
mountedPaths.push(entry.path);
|
|
688
|
+
for (const path of preMountedPaths)
|
|
689
|
+
mountedPaths.push(path);
|
|
690
|
+
if (routeState.activePath && !mountedPaths.includes(routeState.activePath))
|
|
691
|
+
mountedPaths.push(routeState.activePath);
|
|
692
|
+
const dynamicRoutes = [];
|
|
693
|
+
const seenMountedPaths = new Set();
|
|
694
|
+
for (const mountedPath of mountedPaths) {
|
|
695
|
+
if (seenMountedPaths.has(mountedPath))
|
|
696
|
+
continue;
|
|
697
|
+
seenMountedPaths.add(mountedPath);
|
|
698
|
+
if (declaredPaths.has(mountedPath))
|
|
699
|
+
continue;
|
|
700
|
+
const bestMatch = findBestMatch(patterns, mountedPath);
|
|
701
|
+
if (!bestMatch)
|
|
702
|
+
continue;
|
|
703
|
+
const matchingRouteElement = React.Children.toArray(children).find((node) => {
|
|
704
|
+
if (!React.isValidElement(node))
|
|
705
|
+
return false;
|
|
706
|
+
if (node.type !== Route)
|
|
707
|
+
return false;
|
|
708
|
+
return node.props.path === bestMatch.path;
|
|
709
|
+
});
|
|
710
|
+
if (!matchingRouteElement)
|
|
711
|
+
continue;
|
|
712
|
+
const routeInstance = React.cloneElement(matchingRouteElement, {
|
|
713
|
+
mountedPath,
|
|
714
|
+
});
|
|
715
|
+
dynamicRoutes.push(React.createElement(RouteContextProvider, { key: `dynamic:${mountedPath}`, patternPath: bestMatch.path, mountedPath: mountedPath }, routeInstance));
|
|
716
|
+
}
|
|
717
|
+
return [...declaredRoutes, ...dynamicRoutes];
|
|
718
|
+
}, [children, stack, preMountedPaths, routeState.activePath]);
|
|
419
719
|
return (React.createElement(RouteContext.Provider, { value: globalContextValue }, wrappedChildren));
|
|
420
720
|
}
|
|
421
721
|
/**
|
|
@@ -445,7 +745,7 @@ function useRoutes(routes) {
|
|
|
445
745
|
if (route.children && route.children.length > 0) {
|
|
446
746
|
console.warn('Nested routes are not supported yet');
|
|
447
747
|
}
|
|
448
|
-
return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender }));
|
|
748
|
+
return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender, theme: route.theme }));
|
|
449
749
|
});
|
|
450
750
|
}, [routes]);
|
|
451
751
|
// Return Routes component with Route children
|
|
@@ -519,8 +819,13 @@ exports.Route = Route;
|
|
|
519
819
|
exports.Routes = Routes;
|
|
520
820
|
exports.WebFRouter = WebFRouter;
|
|
521
821
|
exports.WebFRouterLink = WebFRouterLink;
|
|
822
|
+
exports.__unstable_setEnsureRouteMountedCallback = __unstable_setEnsureRouteMountedCallback;
|
|
823
|
+
exports.matchPath = matchPath;
|
|
824
|
+
exports.matchRoutes = matchRoutes;
|
|
825
|
+
exports.pathToRegex = pathToRegex;
|
|
522
826
|
exports.useLocation = useLocation;
|
|
523
827
|
exports.useNavigate = useNavigate;
|
|
828
|
+
exports.useParams = useParams;
|
|
524
829
|
exports.useRouteContext = useRouteContext;
|
|
525
830
|
exports.useRoutes = useRoutes;
|
|
526
831
|
//# sourceMappingURL=index.js.map
|