@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.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
- return webfEnterpriseTypings.webf.hybridHistory.state;
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
- return webfEnterpriseTypings.webf.hybridHistory.buildContextStack;
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
- return webfEnterpriseTypings.webf.hybridHistory.path;
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
- webfEnterpriseTypings.webf.hybridHistory.pushNamed(path, { arguments: state });
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
- webfEnterpriseTypings.webf.hybridHistory.pushReplacementNamed(path, { arguments: state });
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
- webfEnterpriseTypings.webf.hybridHistory.back();
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
- webfEnterpriseTypings.webf.hybridHistory.pop(result);
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
- webfEnterpriseTypings.webf.hybridHistory.popUntil(path);
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
- webfEnterpriseTypings.webf.hybridHistory.popAndPushNamed(path, { arguments: state });
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
- webfEnterpriseTypings.webf.hybridHistory.pushNamedAndRemoveUntil(state, path, untilPath);
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
- webfEnterpriseTypings.webf.hybridHistory.pushNamedAndRemoveUntilRoute(newPath, untilPath, { arguments: state });
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
- return webfEnterpriseTypings.webf.hybridHistory.canPop();
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
- return webfEnterpriseTypings.webf.hybridHistory.maybePop(result);
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
- webfEnterpriseTypings.webf.hybridHistory.pushState(state, name);
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
- webfEnterpriseTypings.webf.hybridHistory.replaceState(state, name);
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
- return webfEnterpriseTypings.webf.hybridHistory.restorablePopAndPushState(state, name);
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
- return webfEnterpriseTypings.webf.hybridHistory.restorablePopAndPushNamed(path, { arguments: state });
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
- // isActive is true only for push events with matching path
342
- const isActive = (context.routeEventKind === 'didPush' || context.routeEventKind === 'didPushNext')
343
- && context.path === context.activePath;
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 currentPath = context.path || context.activePath || WebFRouter.path;
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 = (context.isActive || isCurrentRoute)
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.path, context.activePath, context.params]);
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({ path, children }) {
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
- // Check if this route pattern matches the active path
429
- const match = globalContext.activePath ? matchPath(path, globalContext.activePath) : null;
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: effectiveRouteParams,
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
- }, [path, globalContext.activePath, globalContext.params, globalContext.routeParams, globalContext.routeEventKind]);
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
- // Only update activePath for push events
470
- const newActivePath = (routeEvent.kind === 'didPushNext' || routeEvent.kind === 'didPush')
471
- ? routeEvent.path
472
- : routeState.activePath;
473
- // For dynamic routes, extract parameters from the path using registered route patterns
474
- let routeParams = (eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.params) || undefined;
475
- if (!routeParams && newActivePath) {
476
- // Try to extract parameters from registered route patterns
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
- }, [routeState.activePath]);
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
- return React.Children.map(children, (child) => {
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
- return child;
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
- return child;
676
+ declaredRoutes.push(child);
677
+ return;
524
678
  }
525
- // Wrap each Route with its own context provider
526
- const routePath = child.props.path;
527
- return (React.createElement(RouteContextProvider, { key: routePath, path: routePath }, child));
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
- }, [children]);
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;