@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.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 {
@@ -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
- return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, theme: props.theme, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
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
- // isActive is true only for push events with matching path
342
- const isActive = (context.routeEventKind === 'didPush' || context.routeEventKind === 'didPushNext')
343
- && context.path === context.activePath;
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 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;
461
+ const pathname = context.activePath || WebFRouter.path;
373
462
  // Get state - prioritize context params, fallback to WebFRouter.state
374
- const state = (context.isActive || isCurrentRoute)
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.path, context.activePath, context.params]);
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({ path, children }) {
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
- // 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
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: effectiveRouteParams,
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
- }, [path, globalContext.activePath, globalContext.params, globalContext.routeParams, globalContext.routeEventKind]);
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
- // 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;
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
- }, [routeState.activePath]);
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
- return React.Children.map(children, (child) => {
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
- return child;
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
- return child;
688
+ declaredRoutes.push(child);
689
+ return;
524
690
  }
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));
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
- }, [children]);
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;