@openwebf/react-router 0.22.18 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var webfEnterpriseTypings = require('@openwebf/webf-enterprise-typings');
4
3
  var React = require('react');
5
4
  var reactCoreUi = require('@openwebf/react-core-ui');
6
5
 
@@ -36,6 +35,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
36
35
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
37
36
  };
38
37
 
38
+ /**
39
+ * Router management module
40
+ *
41
+ * Encapsulates routing navigation functionality with route guard mechanism for permission checks
42
+ */
43
+ function getHybridHistory() {
44
+ var _a;
45
+ return (_a = globalThis === null || globalThis === void 0 ? void 0 : globalThis.webf) === null || _a === void 0 ? void 0 : _a.hybridHistory;
46
+ }
47
+ let ensureRouteMountedCallback = null;
48
+ function __unstable_setEnsureRouteMountedCallback(callback) {
49
+ ensureRouteMountedCallback = callback;
50
+ }
51
+ function ensureRouteMounted(pathname) {
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ if (!ensureRouteMountedCallback)
54
+ return;
55
+ yield ensureRouteMountedCallback(pathname);
56
+ });
57
+ }
39
58
  /**
40
59
  * WebF Router object - provides comprehensive navigation APIs
41
60
  * Combines web-like history management with Flutter-like navigation patterns
@@ -45,112 +64,231 @@ const WebFRouter = {
45
64
  * Get the current state object associated with the history entry
46
65
  */
47
66
  get state() {
48
- 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
  };
224
+ /**
225
+ * Convert a route pattern to a regular expression
226
+ * @param pattern Route pattern like "/user/:userId" or "/category/:catId/product/:prodId"
227
+ * @returns Object with regex and parameter names
228
+ */
229
+ function pathToRegex(pattern) {
230
+ const paramNames = [];
231
+ if (pattern === '*') {
232
+ paramNames.push('*');
233
+ return { regex: /^(.*)$/, paramNames };
234
+ }
235
+ // Escape special regex characters except : and *
236
+ let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
237
+ // Replace :param with named capture groups
238
+ regexPattern = regexPattern.replace(/:([^\/]+)/g, (_, paramName) => {
239
+ paramNames.push(paramName);
240
+ return '([^/]+)';
241
+ });
242
+ // Replace * with a splat capture group (matches across segments)
243
+ regexPattern = regexPattern.replace(/\*/g, () => {
244
+ paramNames.push('*');
245
+ return '(.*)';
246
+ });
247
+ // Add anchors for exact matching
248
+ regexPattern = `^${regexPattern}$`;
249
+ return {
250
+ regex: new RegExp(regexPattern),
251
+ paramNames
252
+ };
253
+ }
254
+ /**
255
+ * Match a pathname against a route pattern and extract parameters
256
+ * @param pattern Route pattern like "/user/:userId"
257
+ * @param pathname Actual pathname like "/user/123"
258
+ * @returns Match result with extracted parameters or null if no match
259
+ */
260
+ function matchPath(pattern, pathname) {
261
+ const { regex, paramNames } = pathToRegex(pattern);
262
+ const match = pathname.match(regex);
263
+ if (!match) {
264
+ return null;
265
+ }
266
+ // Extract parameters from capture groups
267
+ const params = {};
268
+ paramNames.forEach((paramName, index) => {
269
+ params[paramName] = match[index + 1]; // +1 because match[0] is the full match
270
+ });
271
+ return {
272
+ path: pattern,
273
+ params,
274
+ isExact: true
275
+ };
276
+ }
277
+ /**
278
+ * Find the best matching route from a list of route patterns
279
+ * @param routes Array of route patterns
280
+ * @param pathname Current pathname
281
+ * @returns Best match or null if no routes match
282
+ */
283
+ function matchRoutes(routes, pathname) {
284
+ for (const route of routes) {
285
+ const match = matchPath(route, pathname);
286
+ if (match) {
287
+ return match;
288
+ }
289
+ }
290
+ return null;
291
+ }
154
292
 
155
293
  var isFunction = function (value) {
156
294
  return typeof value === 'function';
@@ -186,7 +324,7 @@ const RawWebFRouterLink = reactCoreUi.createWebFComponent({
186
324
  tagName: 'webf-router-link',
187
325
  displayName: 'WebFRouterLink',
188
326
  // Map props to attributes
189
- attributeProps: ['path', 'title'],
327
+ attributeProps: ['path', 'title', 'theme'],
190
328
  // Event handlers
191
329
  events: [
192
330
  {
@@ -215,7 +353,7 @@ const WebFRouterLink = function (props) {
215
353
  props.onScreen(event);
216
354
  }
217
355
  };
218
- return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
356
+ return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, theme: props.theme, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
219
357
  };
220
358
 
221
359
  /**
@@ -232,7 +370,7 @@ const WebFRouterLink = function (props) {
232
370
  *
233
371
  * Responsible for managing page rendering, lifecycle and navigation bar
234
372
  */
235
- function Route({ path, prerender = false, element, title }) {
373
+ function Route({ path, mountedPath, prerender = false, element, title, theme }) {
236
374
  // Mark whether the page has been rendered
237
375
  const [hasRendered, updateRender] = React.useState(false);
238
376
  /**
@@ -251,7 +389,7 @@ function Route({ path, prerender = false, element, title }) {
251
389
  */
252
390
  const handleOffScreen = useMemoizedFn(() => {
253
391
  });
254
- return (React.createElement(WebFRouterLink, { path: path, title: title, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
392
+ return (React.createElement(WebFRouterLink, { path: mountedPath !== null && mountedPath !== void 0 ? mountedPath : path, title: title, theme: theme, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
255
393
  }
256
394
 
257
395
  /**
@@ -259,7 +397,9 @@ function Route({ path, prerender = false, element, title }) {
259
397
  */
260
398
  const RouteContext = React.createContext({
261
399
  path: undefined,
400
+ mountedPath: undefined,
262
401
  params: undefined,
402
+ routeParams: undefined,
263
403
  activePath: undefined,
264
404
  routeEventKind: undefined
265
405
  });
@@ -278,9 +418,9 @@ const RouteContext = React.createContext({
278
418
  */
279
419
  function useRouteContext() {
280
420
  const context = React.useContext(RouteContext);
281
- // isActive is true only for push events with matching path
282
- const isActive = (context.routeEventKind === 'didPush' || context.routeEventKind === 'didPushNext')
283
- && context.path === context.activePath;
421
+ const isActive = context.activePath !== undefined
422
+ && context.mountedPath !== undefined
423
+ && context.activePath === context.mountedPath;
284
424
  return Object.assign(Object.assign({}, context), { isActive });
285
425
  }
286
426
  /**
@@ -294,6 +434,7 @@ function useRouteContext() {
294
434
  * const location = useLocation();
295
435
  *
296
436
  * console.log('Current path:', location.pathname);
437
+
297
438
  * console.log('Location state:', location.state);
298
439
  * console.log('Is active:', location.isActive);
299
440
  *
@@ -305,25 +446,43 @@ function useLocation() {
305
446
  const context = useRouteContext();
306
447
  // Create location object from context
307
448
  const location = React.useMemo(() => {
308
- // For active routes, return the current location with state
309
- if (context.isActive) {
310
- return {
311
- pathname: context.path || context.activePath || WebFRouter.path,
312
- state: context.params,
313
- isActive: true,
314
- key: `${context.path}-active-${Date.now()}`
315
- };
316
- }
317
- // For inactive routes, return the global location without state
449
+ const pathname = context.activePath || WebFRouter.path;
450
+ // Get state - prioritize context params, fallback to WebFRouter.state
451
+ const state = context.isActive
452
+ ? (context.params || WebFRouter.state)
453
+ : WebFRouter.state;
318
454
  return {
319
- pathname: context.activePath || WebFRouter.path,
320
- state: undefined,
321
- isActive: false,
322
- key: `${context.activePath}-inactive`
455
+ pathname,
456
+ state,
457
+ isActive: context.isActive,
458
+ key: `${pathname}-${Date.now()}`
323
459
  };
324
- }, [context.isActive, context.path, context.activePath, context.params]);
460
+ }, [context.isActive, context.activePath, context.params]);
325
461
  return location;
326
462
  }
463
+ /**
464
+ * Hook to get route parameters from dynamic routes
465
+ *
466
+ * @returns Route parameters object with parameter names as keys and values as strings
467
+ *
468
+ * @example
469
+ * ```tsx
470
+ * // For route pattern "/user/:userId" and actual path "/user/123"
471
+ * function UserPage() {
472
+ * const params = useParams();
473
+ *
474
+ * console.log(params.userId); // "123"
475
+ *
476
+ * return <div>User ID: {params.userId}</div>;
477
+ * }
478
+ * ```
479
+ */
480
+ function useParams() {
481
+ const context = useRouteContext();
482
+ return React.useMemo(() => {
483
+ return context.routeParams || {};
484
+ }, [context.routeParams]);
485
+ }
327
486
  /**
328
487
  * Routes component that wraps multiple Route components and provides shared context
329
488
  *
@@ -339,50 +498,113 @@ function useLocation() {
339
498
  /**
340
499
  * Route-specific context provider that only updates when the route is active
341
500
  */
342
- function RouteContextProvider({ path, children }) {
501
+ function RouteContextProvider({ patternPath, mountedPath, children, }) {
343
502
  const globalContext = React.useContext(RouteContext);
344
503
  // Create a route-specific context that only updates when this route is active
345
504
  const routeSpecificContext = React.useMemo(() => {
346
- // Only update if this route is the active one
347
- if (globalContext.activePath === path) {
505
+ const isActive = globalContext.activePath !== undefined && globalContext.activePath === mountedPath;
506
+ const match = isActive ? matchPath(patternPath, mountedPath) : null;
507
+ if (isActive && match) {
508
+ const effectiveParams = globalContext.params !== undefined ? globalContext.params : WebFRouter.state;
348
509
  return {
349
- path,
350
- params: globalContext.params,
510
+ path: patternPath,
511
+ mountedPath,
512
+ params: effectiveParams,
513
+ routeParams: match.params,
351
514
  activePath: globalContext.activePath,
352
515
  routeEventKind: globalContext.routeEventKind
353
516
  };
354
517
  }
355
- // Return previous values if not active
356
518
  return {
357
- path,
519
+ path: patternPath,
520
+ mountedPath,
358
521
  params: undefined,
522
+ routeParams: undefined,
359
523
  activePath: globalContext.activePath,
360
524
  routeEventKind: undefined
361
525
  };
362
- }, [path, globalContext.activePath, globalContext.params, globalContext.routeEventKind]);
526
+ }, [patternPath, mountedPath, globalContext.activePath, globalContext.params, globalContext.routeEventKind]);
363
527
  return (React.createElement(RouteContext.Provider, { value: routeSpecificContext }, children));
364
528
  }
529
+ function patternScore(pattern) {
530
+ if (pattern === '*')
531
+ return 0;
532
+ const segments = pattern.split('/').filter(Boolean);
533
+ let score = 0;
534
+ for (const segment of segments) {
535
+ if (segment === '*')
536
+ score += 1;
537
+ else if (segment.startsWith(':'))
538
+ score += 2;
539
+ else
540
+ score += 3;
541
+ }
542
+ return score * 100 + segments.length;
543
+ }
544
+ function findBestMatch(patterns, pathname) {
545
+ var _a;
546
+ let best = null;
547
+ for (const pattern of patterns) {
548
+ const match = matchPath(pattern, pathname);
549
+ if (!match)
550
+ continue;
551
+ const score = patternScore(pattern);
552
+ if (!best || score > best.score)
553
+ best = { match, score };
554
+ }
555
+ return (_a = best === null || best === void 0 ? void 0 : best.match) !== null && _a !== void 0 ? _a : null;
556
+ }
557
+ function escapeAttributeValue(value) {
558
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
559
+ }
365
560
  function Routes({ children }) {
366
561
  // State to track current route information
367
562
  const [routeState, setRouteState] = React.useState({
368
563
  path: undefined,
369
- activePath: undefined,
564
+ mountedPath: undefined,
565
+ activePath: WebFRouter.path, // Initialize with current path
370
566
  params: undefined,
567
+ routeParams: undefined,
371
568
  routeEventKind: undefined
372
569
  });
570
+ const [stack, setStack] = React.useState(() => WebFRouter.stack);
571
+ const [preMountedPaths, setPreMountedPaths] = React.useState([]);
572
+ const routePatternsRef = React.useRef([]);
573
+ const pendingEnsureResolversRef = React.useRef(new Map());
574
+ // Keep a stable view of declared route patterns for event handlers.
575
+ React.useEffect(() => {
576
+ const patterns = [];
577
+ React.Children.forEach(children, (child) => {
578
+ if (!React.isValidElement(child))
579
+ return;
580
+ if (child.type !== Route)
581
+ return;
582
+ patterns.push(child.props.path);
583
+ });
584
+ routePatternsRef.current = patterns;
585
+ }, [children]);
373
586
  // Listen to hybridrouterchange event
374
587
  React.useEffect(() => {
375
588
  const handleRouteChange = (event) => {
589
+ var _a, _b, _c;
376
590
  const routeEvent = event;
377
- // Only update activePath for push events
378
- const newActivePath = (routeEvent.kind === 'didPushNext' || routeEvent.kind === 'didPush')
379
- ? routeEvent.path
380
- : routeState.activePath;
591
+ // Check for new event detail structure with params
592
+ const eventDetail = event.detail;
593
+ const newActivePath = WebFRouter.path;
594
+ const newStack = WebFRouter.stack;
595
+ setStack(newStack);
596
+ setPreMountedPaths((prev) => prev.filter((p) => newStack.some((entry) => entry.path === p)));
597
+ const bestMatch = newActivePath ? findBestMatch(routePatternsRef.current, newActivePath) : null;
598
+ const routeParams = (eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.params) || (bestMatch === null || bestMatch === void 0 ? void 0 : bestMatch.params) || undefined;
599
+ const activeEntry = [...newStack].reverse().find((entry) => entry.path === newActivePath);
600
+ const eventState = (_c = (_b = (_a = activeEntry === null || activeEntry === void 0 ? void 0 : activeEntry.state) !== null && _a !== void 0 ? _a : eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.state) !== null && _b !== void 0 ? _b : routeEvent.state) !== null && _c !== void 0 ? _c : WebFRouter.state;
381
601
  // Update state based on event kind
382
602
  setRouteState({
383
603
  path: routeEvent.path,
604
+ mountedPath: routeEvent.path,
384
605
  activePath: newActivePath,
385
- params: routeEvent.state,
606
+ params: eventState,
607
+ routeParams: routeParams, // Use params from Flutter if available
386
608
  routeEventKind: routeEvent.kind
387
609
  });
388
610
  };
@@ -392,30 +614,108 @@ function Routes({ children }) {
392
614
  return () => {
393
615
  document.removeEventListener('hybridrouterchange', handleRouteChange);
394
616
  };
395
- }, [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]);
396
655
  // Global context value
397
656
  const globalContextValue = React.useMemo(() => ({
398
657
  path: undefined,
658
+ mountedPath: undefined,
399
659
  params: routeState.params,
660
+ routeParams: routeState.routeParams, // Pass through route params from Flutter
400
661
  activePath: routeState.activePath,
401
662
  routeEventKind: routeState.routeEventKind
402
- }), [routeState.activePath, routeState.params, routeState.routeEventKind]);
403
- // Wrap each Route component with its own context provider
663
+ }), [routeState.activePath, routeState.params, routeState.routeParams, routeState.routeEventKind]);
404
664
  const wrappedChildren = React.useMemo(() => {
405
- 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;
406
670
  if (!React.isValidElement(child)) {
407
- return child;
671
+ declaredRoutes.push(child);
672
+ return;
408
673
  }
409
- // Ensure only Route components are direct children
410
674
  if (child.type !== Route) {
411
675
  console.warn('Routes component should only contain Route components as direct children');
412
- return child;
676
+ declaredRoutes.push(child);
677
+ return;
413
678
  }
414
- // Wrap each Route with its own context provider
415
- const routePath = child.props.path;
416
- 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));
417
684
  });
418
- }, [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]);
419
719
  return (React.createElement(RouteContext.Provider, { value: globalContextValue }, wrappedChildren));
420
720
  }
421
721
  /**
@@ -445,7 +745,7 @@ function useRoutes(routes) {
445
745
  if (route.children && route.children.length > 0) {
446
746
  console.warn('Nested routes are not supported yet');
447
747
  }
448
- return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender }));
748
+ return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender, theme: route.theme }));
449
749
  });
450
750
  }, [routes]);
451
751
  // Return Routes component with Route children
@@ -519,8 +819,13 @@ exports.Route = Route;
519
819
  exports.Routes = Routes;
520
820
  exports.WebFRouter = WebFRouter;
521
821
  exports.WebFRouterLink = WebFRouterLink;
822
+ exports.__unstable_setEnsureRouteMountedCallback = __unstable_setEnsureRouteMountedCallback;
823
+ exports.matchPath = matchPath;
824
+ exports.matchRoutes = matchRoutes;
825
+ exports.pathToRegex = pathToRegex;
522
826
  exports.useLocation = useLocation;
523
827
  exports.useNavigate = useNavigate;
828
+ exports.useParams = useParams;
524
829
  exports.useRouteContext = useRouteContext;
525
830
  exports.useRoutes = useRoutes;
526
831
  //# sourceMappingURL=index.js.map