@openwebf/react-router 0.23.7 → 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 +14 -3
- package/dist/index.esm.js +262 -73
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +262 -72
- 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/package.json +1 -1
- 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 {
|
|
@@ -291,7 +370,7 @@ const WebFRouterLink = function (props) {
|
|
|
291
370
|
*
|
|
292
371
|
* Responsible for managing page rendering, lifecycle and navigation bar
|
|
293
372
|
*/
|
|
294
|
-
function Route({ path, prerender = false, element, title, theme }) {
|
|
373
|
+
function Route({ path, mountedPath, prerender = false, element, title, theme }) {
|
|
295
374
|
// Mark whether the page has been rendered
|
|
296
375
|
const [hasRendered, updateRender] = React.useState(false);
|
|
297
376
|
/**
|
|
@@ -310,7 +389,7 @@ function Route({ path, prerender = false, element, title, theme }) {
|
|
|
310
389
|
*/
|
|
311
390
|
const handleOffScreen = useMemoizedFn(() => {
|
|
312
391
|
});
|
|
313
|
-
return (React.createElement(WebFRouterLink, { path: path, title: title, theme: theme, 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));
|
|
314
393
|
}
|
|
315
394
|
|
|
316
395
|
/**
|
|
@@ -318,6 +397,7 @@ function Route({ path, prerender = false, element, title, theme }) {
|
|
|
318
397
|
*/
|
|
319
398
|
const RouteContext = React.createContext({
|
|
320
399
|
path: undefined,
|
|
400
|
+
mountedPath: undefined,
|
|
321
401
|
params: undefined,
|
|
322
402
|
routeParams: undefined,
|
|
323
403
|
activePath: undefined,
|
|
@@ -338,9 +418,9 @@ const RouteContext = React.createContext({
|
|
|
338
418
|
*/
|
|
339
419
|
function useRouteContext() {
|
|
340
420
|
const context = React.useContext(RouteContext);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
&& context.
|
|
421
|
+
const isActive = context.activePath !== undefined
|
|
422
|
+
&& context.mountedPath !== undefined
|
|
423
|
+
&& context.activePath === context.mountedPath;
|
|
344
424
|
return Object.assign(Object.assign({}, context), { isActive });
|
|
345
425
|
}
|
|
346
426
|
/**
|
|
@@ -366,12 +446,9 @@ function useLocation() {
|
|
|
366
446
|
const context = useRouteContext();
|
|
367
447
|
// Create location object from context
|
|
368
448
|
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;
|
|
449
|
+
const pathname = context.activePath || WebFRouter.path;
|
|
373
450
|
// Get state - prioritize context params, fallback to WebFRouter.state
|
|
374
|
-
const state =
|
|
451
|
+
const state = context.isActive
|
|
375
452
|
? (context.params || WebFRouter.state)
|
|
376
453
|
: WebFRouter.state;
|
|
377
454
|
return {
|
|
@@ -380,7 +457,7 @@ function useLocation() {
|
|
|
380
457
|
isActive: context.isActive,
|
|
381
458
|
key: `${pathname}-${Date.now()}`
|
|
382
459
|
};
|
|
383
|
-
}, [context.isActive, context.
|
|
460
|
+
}, [context.isActive, context.activePath, context.params]);
|
|
384
461
|
return location;
|
|
385
462
|
}
|
|
386
463
|
/**
|
|
@@ -421,75 +498,110 @@ function useParams() {
|
|
|
421
498
|
/**
|
|
422
499
|
* Route-specific context provider that only updates when the route is active
|
|
423
500
|
*/
|
|
424
|
-
function RouteContextProvider({
|
|
501
|
+
function RouteContextProvider({ patternPath, mountedPath, children, }) {
|
|
425
502
|
const globalContext = React.useContext(RouteContext);
|
|
426
503
|
// Create a route-specific context that only updates when this route is active
|
|
427
504
|
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
|
|
505
|
+
const isActive = globalContext.activePath !== undefined && globalContext.activePath === mountedPath;
|
|
506
|
+
const match = isActive ? matchPath(patternPath, mountedPath) : null;
|
|
507
|
+
if (isActive && match) {
|
|
434
508
|
const effectiveParams = globalContext.params !== undefined ? globalContext.params : WebFRouter.state;
|
|
435
509
|
return {
|
|
436
|
-
path,
|
|
510
|
+
path: patternPath,
|
|
511
|
+
mountedPath,
|
|
437
512
|
params: effectiveParams,
|
|
438
|
-
routeParams:
|
|
513
|
+
routeParams: match.params,
|
|
439
514
|
activePath: globalContext.activePath,
|
|
440
515
|
routeEventKind: globalContext.routeEventKind
|
|
441
516
|
};
|
|
442
517
|
}
|
|
443
|
-
// Return previous values if not active
|
|
444
518
|
return {
|
|
445
|
-
path,
|
|
519
|
+
path: patternPath,
|
|
520
|
+
mountedPath,
|
|
446
521
|
params: undefined,
|
|
447
522
|
routeParams: undefined,
|
|
448
523
|
activePath: globalContext.activePath,
|
|
449
524
|
routeEventKind: undefined
|
|
450
525
|
};
|
|
451
|
-
}, [
|
|
526
|
+
}, [patternPath, mountedPath, globalContext.activePath, globalContext.params, globalContext.routeEventKind]);
|
|
452
527
|
return (React.createElement(RouteContext.Provider, { value: routeSpecificContext }, children));
|
|
453
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
|
+
}
|
|
454
560
|
function Routes({ children }) {
|
|
455
561
|
// State to track current route information
|
|
456
562
|
const [routeState, setRouteState] = React.useState({
|
|
457
563
|
path: undefined,
|
|
564
|
+
mountedPath: undefined,
|
|
458
565
|
activePath: WebFRouter.path, // Initialize with current path
|
|
459
566
|
params: undefined,
|
|
460
567
|
routeParams: undefined,
|
|
461
568
|
routeEventKind: undefined
|
|
462
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]);
|
|
463
586
|
// Listen to hybridrouterchange event
|
|
464
587
|
React.useEffect(() => {
|
|
465
588
|
const handleRouteChange = (event) => {
|
|
589
|
+
var _a, _b, _c;
|
|
466
590
|
const routeEvent = event;
|
|
467
591
|
// Check for new event detail structure with params
|
|
468
592
|
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;
|
|
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;
|
|
490
601
|
// Update state based on event kind
|
|
491
602
|
setRouteState({
|
|
492
603
|
path: routeEvent.path,
|
|
604
|
+
mountedPath: routeEvent.path,
|
|
493
605
|
activePath: newActivePath,
|
|
494
606
|
params: eventState,
|
|
495
607
|
routeParams: routeParams, // Use params from Flutter if available
|
|
@@ -502,31 +614,108 @@ function Routes({ children }) {
|
|
|
502
614
|
return () => {
|
|
503
615
|
document.removeEventListener('hybridrouterchange', handleRouteChange);
|
|
504
616
|
};
|
|
505
|
-
}, [
|
|
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]);
|
|
506
655
|
// Global context value
|
|
507
656
|
const globalContextValue = React.useMemo(() => ({
|
|
508
657
|
path: undefined,
|
|
658
|
+
mountedPath: undefined,
|
|
509
659
|
params: routeState.params,
|
|
510
660
|
routeParams: routeState.routeParams, // Pass through route params from Flutter
|
|
511
661
|
activePath: routeState.activePath,
|
|
512
662
|
routeEventKind: routeState.routeEventKind
|
|
513
663
|
}), [routeState.activePath, routeState.params, routeState.routeParams, routeState.routeEventKind]);
|
|
514
|
-
// Wrap each Route component with its own context provider
|
|
515
664
|
const wrappedChildren = React.useMemo(() => {
|
|
516
|
-
|
|
665
|
+
const declaredRoutes = [];
|
|
666
|
+
const patterns = [];
|
|
667
|
+
const declaredPaths = new Set();
|
|
668
|
+
React.Children.forEach(children, (child) => {
|
|
669
|
+
var _a;
|
|
517
670
|
if (!React.isValidElement(child)) {
|
|
518
|
-
|
|
671
|
+
declaredRoutes.push(child);
|
|
672
|
+
return;
|
|
519
673
|
}
|
|
520
|
-
// Ensure only Route components are direct children
|
|
521
674
|
if (child.type !== Route) {
|
|
522
675
|
console.warn('Routes component should only contain Route components as direct children');
|
|
523
|
-
|
|
676
|
+
declaredRoutes.push(child);
|
|
677
|
+
return;
|
|
524
678
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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));
|
|
528
684
|
});
|
|
529
|
-
|
|
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]);
|
|
530
719
|
return (React.createElement(RouteContext.Provider, { value: globalContextValue }, wrappedChildren));
|
|
531
720
|
}
|
|
532
721
|
/**
|
|
@@ -630,6 +819,7 @@ exports.Route = Route;
|
|
|
630
819
|
exports.Routes = Routes;
|
|
631
820
|
exports.WebFRouter = WebFRouter;
|
|
632
821
|
exports.WebFRouterLink = WebFRouterLink;
|
|
822
|
+
exports.__unstable_setEnsureRouteMountedCallback = __unstable_setEnsureRouteMountedCallback;
|
|
633
823
|
exports.matchPath = matchPath;
|
|
634
824
|
exports.matchRoutes = matchRoutes;
|
|
635
825
|
exports.pathToRegex = pathToRegex;
|