@openwebf/react-router 0.23.7 → 0.24.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.
- package/dist/index.d.ts +19 -4
- package/dist/index.esm.js +275 -74
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +275 -73
- package/dist/index.js.map +1 -1
- package/dist/routes/Route.d.ts +6 -1
- package/dist/routes/Route.d.ts.map +1 -1
- package/dist/routes/Routes.d.ts +5 -1
- package/dist/routes/Routes.d.ts.map +1 -1
- package/dist/routes/utils.d.ts +3 -1
- package/dist/routes/utils.d.ts.map +1 -1
- package/dist/utils/RouterLink.d.ts +4 -0
- package/dist/utils/RouterLink.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/browser/index.d.ts +0 -88
- package/dist/browser/index.d.ts.map +0 -1
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,110 +64,161 @@ 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
|
};
|
|
154
224
|
/**
|
|
@@ -158,6 +228,10 @@ const WebFRouter = {
|
|
|
158
228
|
*/
|
|
159
229
|
function pathToRegex(pattern) {
|
|
160
230
|
const paramNames = [];
|
|
231
|
+
if (pattern === '*') {
|
|
232
|
+
paramNames.push('*');
|
|
233
|
+
return { regex: /^(.*)$/, paramNames };
|
|
234
|
+
}
|
|
161
235
|
// Escape special regex characters except : and *
|
|
162
236
|
let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
|
|
163
237
|
// Replace :param with named capture groups
|
|
@@ -165,6 +239,11 @@ function pathToRegex(pattern) {
|
|
|
165
239
|
paramNames.push(paramName);
|
|
166
240
|
return '([^/]+)';
|
|
167
241
|
});
|
|
242
|
+
// Replace * with a splat capture group (matches across segments)
|
|
243
|
+
regexPattern = regexPattern.replace(/\*/g, () => {
|
|
244
|
+
paramNames.push('*');
|
|
245
|
+
return '(.*)';
|
|
246
|
+
});
|
|
168
247
|
// Add anchors for exact matching
|
|
169
248
|
regexPattern = `^${regexPattern}$`;
|
|
170
249
|
return {
|
|
@@ -264,6 +343,13 @@ const RawWebFRouterLink = reactCoreUi.createWebFComponent({
|
|
|
264
343
|
callback(event);
|
|
265
344
|
},
|
|
266
345
|
},
|
|
346
|
+
{
|
|
347
|
+
propName: 'onPrerendering',
|
|
348
|
+
eventName: 'prerendering',
|
|
349
|
+
handler: (callback) => (event) => {
|
|
350
|
+
callback(event);
|
|
351
|
+
},
|
|
352
|
+
},
|
|
267
353
|
],
|
|
268
354
|
});
|
|
269
355
|
const WebFRouterLink = function (props) {
|
|
@@ -274,7 +360,12 @@ const WebFRouterLink = function (props) {
|
|
|
274
360
|
props.onScreen(event);
|
|
275
361
|
}
|
|
276
362
|
};
|
|
277
|
-
|
|
363
|
+
const handlePrerendering = (event) => {
|
|
364
|
+
var _a;
|
|
365
|
+
enableRender(true);
|
|
366
|
+
(_a = props.onPrerendering) === null || _a === void 0 ? void 0 : _a.call(props, event);
|
|
367
|
+
};
|
|
368
|
+
return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, theme: props.theme, onScreen: handleOnScreen, offScreen: props.offScreen, onPrerendering: handlePrerendering }, isRender ? props.children : null));
|
|
278
369
|
};
|
|
279
370
|
|
|
280
371
|
/**
|
|
@@ -291,7 +382,7 @@ const WebFRouterLink = function (props) {
|
|
|
291
382
|
*
|
|
292
383
|
* Responsible for managing page rendering, lifecycle and navigation bar
|
|
293
384
|
*/
|
|
294
|
-
function Route({ path, prerender = false, element, title, theme }) {
|
|
385
|
+
function Route({ path, mountedPath, prerender = false, element, title, theme }) {
|
|
295
386
|
// Mark whether the page has been rendered
|
|
296
387
|
const [hasRendered, updateRender] = React.useState(false);
|
|
297
388
|
/**
|
|
@@ -310,7 +401,7 @@ function Route({ path, prerender = false, element, title, theme }) {
|
|
|
310
401
|
*/
|
|
311
402
|
const handleOffScreen = useMemoizedFn(() => {
|
|
312
403
|
});
|
|
313
|
-
return (React.createElement(WebFRouterLink, { path: path, title: title, theme: theme, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
|
|
404
|
+
return (React.createElement(WebFRouterLink, { path: mountedPath !== null && mountedPath !== void 0 ? mountedPath : path, title: title, theme: theme, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
|
|
314
405
|
}
|
|
315
406
|
|
|
316
407
|
/**
|
|
@@ -318,6 +409,7 @@ function Route({ path, prerender = false, element, title, theme }) {
|
|
|
318
409
|
*/
|
|
319
410
|
const RouteContext = React.createContext({
|
|
320
411
|
path: undefined,
|
|
412
|
+
mountedPath: undefined,
|
|
321
413
|
params: undefined,
|
|
322
414
|
routeParams: undefined,
|
|
323
415
|
activePath: undefined,
|
|
@@ -338,9 +430,9 @@ const RouteContext = React.createContext({
|
|
|
338
430
|
*/
|
|
339
431
|
function useRouteContext() {
|
|
340
432
|
const context = React.useContext(RouteContext);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
&& context.
|
|
433
|
+
const isActive = context.activePath !== undefined
|
|
434
|
+
&& context.mountedPath !== undefined
|
|
435
|
+
&& context.activePath === context.mountedPath;
|
|
344
436
|
return Object.assign(Object.assign({}, context), { isActive });
|
|
345
437
|
}
|
|
346
438
|
/**
|
|
@@ -366,12 +458,9 @@ function useLocation() {
|
|
|
366
458
|
const context = useRouteContext();
|
|
367
459
|
// Create location object from context
|
|
368
460
|
const location = React.useMemo(() => {
|
|
369
|
-
const
|
|
370
|
-
let pathname = currentPath;
|
|
371
|
-
// Check if the current component's route matches the active path
|
|
372
|
-
const isCurrentRoute = context.path === context.activePath;
|
|
461
|
+
const pathname = context.activePath || WebFRouter.path;
|
|
373
462
|
// Get state - prioritize context params, fallback to WebFRouter.state
|
|
374
|
-
const state =
|
|
463
|
+
const state = context.isActive
|
|
375
464
|
? (context.params || WebFRouter.state)
|
|
376
465
|
: WebFRouter.state;
|
|
377
466
|
return {
|
|
@@ -380,7 +469,7 @@ function useLocation() {
|
|
|
380
469
|
isActive: context.isActive,
|
|
381
470
|
key: `${pathname}-${Date.now()}`
|
|
382
471
|
};
|
|
383
|
-
}, [context.isActive, context.
|
|
472
|
+
}, [context.isActive, context.activePath, context.params]);
|
|
384
473
|
return location;
|
|
385
474
|
}
|
|
386
475
|
/**
|
|
@@ -421,75 +510,110 @@ function useParams() {
|
|
|
421
510
|
/**
|
|
422
511
|
* Route-specific context provider that only updates when the route is active
|
|
423
512
|
*/
|
|
424
|
-
function RouteContextProvider({
|
|
513
|
+
function RouteContextProvider({ patternPath, mountedPath, children, }) {
|
|
425
514
|
const globalContext = React.useContext(RouteContext);
|
|
426
515
|
// Create a route-specific context that only updates when this route is active
|
|
427
516
|
const routeSpecificContext = React.useMemo(() => {
|
|
428
|
-
|
|
429
|
-
const match =
|
|
430
|
-
if (match) {
|
|
431
|
-
// Use route params from Flutter event if available, otherwise from local matching
|
|
432
|
-
const effectiveRouteParams = globalContext.routeParams || match.params;
|
|
433
|
-
// For matching routes, always try to get state from WebFRouter if params is undefined
|
|
517
|
+
const isActive = globalContext.activePath !== undefined && globalContext.activePath === mountedPath;
|
|
518
|
+
const match = isActive ? matchPath(patternPath, mountedPath) : null;
|
|
519
|
+
if (isActive && match) {
|
|
434
520
|
const effectiveParams = globalContext.params !== undefined ? globalContext.params : WebFRouter.state;
|
|
435
521
|
return {
|
|
436
|
-
path,
|
|
522
|
+
path: patternPath,
|
|
523
|
+
mountedPath,
|
|
437
524
|
params: effectiveParams,
|
|
438
|
-
routeParams:
|
|
525
|
+
routeParams: match.params,
|
|
439
526
|
activePath: globalContext.activePath,
|
|
440
527
|
routeEventKind: globalContext.routeEventKind
|
|
441
528
|
};
|
|
442
529
|
}
|
|
443
|
-
// Return previous values if not active
|
|
444
530
|
return {
|
|
445
|
-
path,
|
|
531
|
+
path: patternPath,
|
|
532
|
+
mountedPath,
|
|
446
533
|
params: undefined,
|
|
447
534
|
routeParams: undefined,
|
|
448
535
|
activePath: globalContext.activePath,
|
|
449
536
|
routeEventKind: undefined
|
|
450
537
|
};
|
|
451
|
-
}, [
|
|
538
|
+
}, [patternPath, mountedPath, globalContext.activePath, globalContext.params, globalContext.routeEventKind]);
|
|
452
539
|
return (React.createElement(RouteContext.Provider, { value: routeSpecificContext }, children));
|
|
453
540
|
}
|
|
541
|
+
function patternScore(pattern) {
|
|
542
|
+
if (pattern === '*')
|
|
543
|
+
return 0;
|
|
544
|
+
const segments = pattern.split('/').filter(Boolean);
|
|
545
|
+
let score = 0;
|
|
546
|
+
for (const segment of segments) {
|
|
547
|
+
if (segment === '*')
|
|
548
|
+
score += 1;
|
|
549
|
+
else if (segment.startsWith(':'))
|
|
550
|
+
score += 2;
|
|
551
|
+
else
|
|
552
|
+
score += 3;
|
|
553
|
+
}
|
|
554
|
+
return score * 100 + segments.length;
|
|
555
|
+
}
|
|
556
|
+
function findBestMatch(patterns, pathname) {
|
|
557
|
+
var _a;
|
|
558
|
+
let best = null;
|
|
559
|
+
for (const pattern of patterns) {
|
|
560
|
+
const match = matchPath(pattern, pathname);
|
|
561
|
+
if (!match)
|
|
562
|
+
continue;
|
|
563
|
+
const score = patternScore(pattern);
|
|
564
|
+
if (!best || score > best.score)
|
|
565
|
+
best = { match, score };
|
|
566
|
+
}
|
|
567
|
+
return (_a = best === null || best === void 0 ? void 0 : best.match) !== null && _a !== void 0 ? _a : null;
|
|
568
|
+
}
|
|
569
|
+
function escapeAttributeValue(value) {
|
|
570
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
571
|
+
}
|
|
454
572
|
function Routes({ children }) {
|
|
455
573
|
// State to track current route information
|
|
456
574
|
const [routeState, setRouteState] = React.useState({
|
|
457
575
|
path: undefined,
|
|
576
|
+
mountedPath: undefined,
|
|
458
577
|
activePath: WebFRouter.path, // Initialize with current path
|
|
459
578
|
params: undefined,
|
|
460
579
|
routeParams: undefined,
|
|
461
580
|
routeEventKind: undefined
|
|
462
581
|
});
|
|
582
|
+
const [stack, setStack] = React.useState(() => WebFRouter.stack);
|
|
583
|
+
const [preMountedPaths, setPreMountedPaths] = React.useState([]);
|
|
584
|
+
const routePatternsRef = React.useRef([]);
|
|
585
|
+
const pendingEnsureResolversRef = React.useRef(new Map());
|
|
586
|
+
// Keep a stable view of declared route patterns for event handlers.
|
|
587
|
+
React.useEffect(() => {
|
|
588
|
+
const patterns = [];
|
|
589
|
+
React.Children.forEach(children, (child) => {
|
|
590
|
+
if (!React.isValidElement(child))
|
|
591
|
+
return;
|
|
592
|
+
if (child.type !== Route)
|
|
593
|
+
return;
|
|
594
|
+
patterns.push(child.props.path);
|
|
595
|
+
});
|
|
596
|
+
routePatternsRef.current = patterns;
|
|
597
|
+
}, [children]);
|
|
463
598
|
// Listen to hybridrouterchange event
|
|
464
599
|
React.useEffect(() => {
|
|
465
600
|
const handleRouteChange = (event) => {
|
|
601
|
+
var _a, _b, _c;
|
|
466
602
|
const routeEvent = event;
|
|
467
603
|
// Check for new event detail structure with params
|
|
468
604
|
const eventDetail = event.detail;
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const registeredRoutes = Array.from(document.querySelectorAll('webf-router-link'));
|
|
478
|
-
for (const routeElement of registeredRoutes) {
|
|
479
|
-
const routePath = routeElement.getAttribute('path');
|
|
480
|
-
if (routePath && routePath.includes(':')) {
|
|
481
|
-
const match = matchPath(routePath, newActivePath);
|
|
482
|
-
if (match) {
|
|
483
|
-
routeParams = match.params;
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
const eventState = (eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.state) || routeEvent.state;
|
|
605
|
+
const newActivePath = WebFRouter.path;
|
|
606
|
+
const newStack = WebFRouter.stack;
|
|
607
|
+
setStack(newStack);
|
|
608
|
+
setPreMountedPaths((prev) => prev.filter((p) => newStack.some((entry) => entry.path === p)));
|
|
609
|
+
const bestMatch = newActivePath ? findBestMatch(routePatternsRef.current, newActivePath) : null;
|
|
610
|
+
const routeParams = (eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.params) || (bestMatch === null || bestMatch === void 0 ? void 0 : bestMatch.params) || undefined;
|
|
611
|
+
const activeEntry = [...newStack].reverse().find((entry) => entry.path === newActivePath);
|
|
612
|
+
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;
|
|
490
613
|
// Update state based on event kind
|
|
491
614
|
setRouteState({
|
|
492
615
|
path: routeEvent.path,
|
|
616
|
+
mountedPath: routeEvent.path,
|
|
493
617
|
activePath: newActivePath,
|
|
494
618
|
params: eventState,
|
|
495
619
|
routeParams: routeParams, // Use params from Flutter if available
|
|
@@ -502,31 +626,108 @@ function Routes({ children }) {
|
|
|
502
626
|
return () => {
|
|
503
627
|
document.removeEventListener('hybridrouterchange', handleRouteChange);
|
|
504
628
|
};
|
|
505
|
-
}, [
|
|
629
|
+
}, []);
|
|
630
|
+
React.useEffect(() => {
|
|
631
|
+
__unstable_setEnsureRouteMountedCallback((pathname) => {
|
|
632
|
+
var _a;
|
|
633
|
+
if (!pathname)
|
|
634
|
+
return;
|
|
635
|
+
const bestMatch = findBestMatch(routePatternsRef.current, pathname);
|
|
636
|
+
if (!bestMatch)
|
|
637
|
+
return;
|
|
638
|
+
const selector = `webf-router-link[path="${escapeAttributeValue(pathname)}"]`;
|
|
639
|
+
if (document.querySelector(selector))
|
|
640
|
+
return;
|
|
641
|
+
let resolveFn;
|
|
642
|
+
const promise = new Promise((resolve) => {
|
|
643
|
+
resolveFn = resolve;
|
|
644
|
+
});
|
|
645
|
+
pendingEnsureResolversRef.current.set(pathname, [
|
|
646
|
+
...((_a = pendingEnsureResolversRef.current.get(pathname)) !== null && _a !== void 0 ? _a : []),
|
|
647
|
+
resolveFn,
|
|
648
|
+
]);
|
|
649
|
+
setPreMountedPaths((prev) => (prev.includes(pathname) ? prev : [...prev, pathname]));
|
|
650
|
+
return promise;
|
|
651
|
+
});
|
|
652
|
+
return () => {
|
|
653
|
+
__unstable_setEnsureRouteMountedCallback(null);
|
|
654
|
+
};
|
|
655
|
+
}, []);
|
|
656
|
+
React.useEffect(() => {
|
|
657
|
+
const pending = pendingEnsureResolversRef.current;
|
|
658
|
+
for (const [pathname, resolvers] of pending.entries()) {
|
|
659
|
+
const selector = `webf-router-link[path="${escapeAttributeValue(pathname)}"]`;
|
|
660
|
+
if (!document.querySelector(selector))
|
|
661
|
+
continue;
|
|
662
|
+
for (const resolve of resolvers)
|
|
663
|
+
resolve();
|
|
664
|
+
pending.delete(pathname);
|
|
665
|
+
}
|
|
666
|
+
}, [children, stack, preMountedPaths, routeState.activePath]);
|
|
506
667
|
// Global context value
|
|
507
668
|
const globalContextValue = React.useMemo(() => ({
|
|
508
669
|
path: undefined,
|
|
670
|
+
mountedPath: undefined,
|
|
509
671
|
params: routeState.params,
|
|
510
672
|
routeParams: routeState.routeParams, // Pass through route params from Flutter
|
|
511
673
|
activePath: routeState.activePath,
|
|
512
674
|
routeEventKind: routeState.routeEventKind
|
|
513
675
|
}), [routeState.activePath, routeState.params, routeState.routeParams, routeState.routeEventKind]);
|
|
514
|
-
// Wrap each Route component with its own context provider
|
|
515
676
|
const wrappedChildren = React.useMemo(() => {
|
|
516
|
-
|
|
677
|
+
const declaredRoutes = [];
|
|
678
|
+
const patterns = [];
|
|
679
|
+
const declaredPaths = new Set();
|
|
680
|
+
React.Children.forEach(children, (child) => {
|
|
681
|
+
var _a;
|
|
517
682
|
if (!React.isValidElement(child)) {
|
|
518
|
-
|
|
683
|
+
declaredRoutes.push(child);
|
|
684
|
+
return;
|
|
519
685
|
}
|
|
520
|
-
// Ensure only Route components are direct children
|
|
521
686
|
if (child.type !== Route) {
|
|
522
687
|
console.warn('Routes component should only contain Route components as direct children');
|
|
523
|
-
|
|
688
|
+
declaredRoutes.push(child);
|
|
689
|
+
return;
|
|
524
690
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
691
|
+
const patternPath = child.props.path;
|
|
692
|
+
patterns.push(patternPath);
|
|
693
|
+
declaredPaths.add(patternPath);
|
|
694
|
+
const mountedPath = (_a = child.props.mountedPath) !== null && _a !== void 0 ? _a : patternPath;
|
|
695
|
+
declaredRoutes.push(React.createElement(RouteContextProvider, { key: `declared:${patternPath}`, patternPath: patternPath, mountedPath: mountedPath }, child));
|
|
528
696
|
});
|
|
529
|
-
|
|
697
|
+
const mountedPaths = [];
|
|
698
|
+
for (const entry of stack)
|
|
699
|
+
mountedPaths.push(entry.path);
|
|
700
|
+
for (const path of preMountedPaths)
|
|
701
|
+
mountedPaths.push(path);
|
|
702
|
+
if (routeState.activePath && !mountedPaths.includes(routeState.activePath))
|
|
703
|
+
mountedPaths.push(routeState.activePath);
|
|
704
|
+
const dynamicRoutes = [];
|
|
705
|
+
const seenMountedPaths = new Set();
|
|
706
|
+
for (const mountedPath of mountedPaths) {
|
|
707
|
+
if (seenMountedPaths.has(mountedPath))
|
|
708
|
+
continue;
|
|
709
|
+
seenMountedPaths.add(mountedPath);
|
|
710
|
+
if (declaredPaths.has(mountedPath))
|
|
711
|
+
continue;
|
|
712
|
+
const bestMatch = findBestMatch(patterns, mountedPath);
|
|
713
|
+
if (!bestMatch)
|
|
714
|
+
continue;
|
|
715
|
+
const matchingRouteElement = React.Children.toArray(children).find((node) => {
|
|
716
|
+
if (!React.isValidElement(node))
|
|
717
|
+
return false;
|
|
718
|
+
if (node.type !== Route)
|
|
719
|
+
return false;
|
|
720
|
+
return node.props.path === bestMatch.path;
|
|
721
|
+
});
|
|
722
|
+
if (!matchingRouteElement)
|
|
723
|
+
continue;
|
|
724
|
+
const routeInstance = React.cloneElement(matchingRouteElement, {
|
|
725
|
+
mountedPath,
|
|
726
|
+
});
|
|
727
|
+
dynamicRoutes.push(React.createElement(RouteContextProvider, { key: `dynamic:${mountedPath}`, patternPath: bestMatch.path, mountedPath: mountedPath }, routeInstance));
|
|
728
|
+
}
|
|
729
|
+
return [...declaredRoutes, ...dynamicRoutes];
|
|
730
|
+
}, [children, stack, preMountedPaths, routeState.activePath]);
|
|
530
731
|
return (React.createElement(RouteContext.Provider, { value: globalContextValue }, wrappedChildren));
|
|
531
732
|
}
|
|
532
733
|
/**
|
|
@@ -630,6 +831,7 @@ exports.Route = Route;
|
|
|
630
831
|
exports.Routes = Routes;
|
|
631
832
|
exports.WebFRouter = WebFRouter;
|
|
632
833
|
exports.WebFRouterLink = WebFRouterLink;
|
|
834
|
+
exports.__unstable_setEnsureRouteMountedCallback = __unstable_setEnsureRouteMountedCallback;
|
|
633
835
|
exports.matchPath = matchPath;
|
|
634
836
|
exports.matchRoutes = matchRoutes;
|
|
635
837
|
exports.pathToRegex = pathToRegex;
|