@openwebf/react-router 0.3.4 → 0.22.18

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 CHANGED
@@ -2,6 +2,14 @@ import React, { SyntheticEvent, EventHandler, ReactNode, FC } from 'react';
2
2
  import { WebFElementWithMethods } from '@openwebf/react-core-ui';
3
3
 
4
4
  type RoutePath = string;
5
+ /**
6
+ * Single entry in the hybrid router stack.
7
+ * Mirrors the data returned from webf.hybridHistory.buildContextStack.
8
+ */
9
+ interface HybridRouteStackEntry {
10
+ path: RoutePath;
11
+ state: any;
12
+ }
5
13
  /**
6
14
  * WebF Router object - provides comprehensive navigation APIs
7
15
  * Combines web-like history management with Flutter-like navigation patterns
@@ -11,6 +19,11 @@ declare const WebFRouter: {
11
19
  * Get the current state object associated with the history entry
12
20
  */
13
21
  readonly state: any;
22
+ /**
23
+ * Get the full hybrid router build context stack.
24
+ * The stack is ordered from root route (index 0) to the current top route (last element).
25
+ */
26
+ readonly stack: HybridRouteStackEntry[];
14
27
  /**
15
28
  * Get the current route path
16
29
  */
@@ -78,43 +91,6 @@ declare const WebFRouter: {
78
91
  */
79
92
  restorablePopAndPushNamed: <T extends RoutePath>(path: T, state?: any) => Promise<string>;
80
93
  };
81
- /**
82
- * Route parameters extracted from dynamic routes
83
- */
84
- interface RouteParams {
85
- [key: string]: string;
86
- }
87
- /**
88
- * Route match result
89
- */
90
- interface RouteMatch {
91
- path: string;
92
- params: RouteParams;
93
- isExact: boolean;
94
- }
95
- /**
96
- * Convert a route pattern to a regular expression
97
- * @param pattern Route pattern like "/user/:userId" or "/category/:catId/product/:prodId"
98
- * @returns Object with regex and parameter names
99
- */
100
- declare function pathToRegex(pattern: string): {
101
- regex: RegExp;
102
- paramNames: string[];
103
- };
104
- /**
105
- * Match a pathname against a route pattern and extract parameters
106
- * @param pattern Route pattern like "/user/:userId"
107
- * @param pathname Actual pathname like "/user/123"
108
- * @returns Match result with extracted parameters or null if no match
109
- */
110
- declare function matchPath(pattern: string, pathname: string): RouteMatch | null;
111
- /**
112
- * Find the best matching route from a list of route patterns
113
- * @param routes Array of route patterns
114
- * @param pathname Current pathname
115
- * @returns Best match or null if no routes match
116
- */
117
- declare function matchRoutes(routes: string[], pathname: string): RouteMatch | null;
118
94
 
119
95
  /**
120
96
  * Route Component
@@ -153,20 +129,13 @@ interface RouteProps {
153
129
  * The actual page component to render
154
130
  */
155
131
  element: React.ReactNode;
156
- /**
157
- * Theme for this route
158
- * Controls the visual style of the navigation bar and page
159
- *
160
- * @default "material"
161
- */
162
- theme?: 'material' | 'cupertino';
163
132
  }
164
133
  /**
165
134
  * Route Component
166
135
  *
167
136
  * Responsible for managing page rendering, lifecycle and navigation bar
168
137
  */
169
- declare function Route({ path, prerender, element, title, theme }: RouteProps): React.JSX.Element;
138
+ declare function Route({ path, prerender, element, title }: RouteProps): React.JSX.Element;
170
139
 
171
140
  /**
172
141
  * Hook to get route context
@@ -193,10 +162,6 @@ declare function useRouteContext(): {
193
162
  * State data passed during route navigation
194
163
  */
195
164
  params: any;
196
- /**
197
- * Route parameters extracted from dynamic routes (e.g., :userId in /user/:userId)
198
- */
199
- routeParams: RouteParams | undefined;
200
165
  /**
201
166
  * Current active path from router
202
167
  */
@@ -234,7 +199,6 @@ interface Location {
234
199
  * const location = useLocation();
235
200
  *
236
201
  * console.log('Current path:', location.pathname);
237
-
238
202
  * console.log('Location state:', location.state);
239
203
  * console.log('Is active:', location.isActive);
240
204
  *
@@ -245,24 +209,6 @@ interface Location {
245
209
  declare function useLocation(): Location & {
246
210
  isActive: boolean;
247
211
  };
248
- /**
249
- * Hook to get route parameters from dynamic routes
250
- *
251
- * @returns Route parameters object with parameter names as keys and values as strings
252
- *
253
- * @example
254
- * ```tsx
255
- * // For route pattern "/user/:userId" and actual path "/user/123"
256
- * function UserPage() {
257
- * const params = useParams();
258
- *
259
- * console.log(params.userId); // "123"
260
- *
261
- * return <div>User ID: {params.userId}</div>;
262
- * }
263
- * ```
264
- */
265
- declare function useParams(): RouteParams;
266
212
  /**
267
213
  * Route configuration object
268
214
  */
@@ -279,12 +225,6 @@ interface RouteObject {
279
225
  * Whether to pre-render this route
280
226
  */
281
227
  prerender?: boolean;
282
- /**
283
- * Theme for this route
284
- *
285
- * @default "material"
286
- */
287
- theme?: 'material' | 'cupertino';
288
228
  /**
289
229
  * Child routes (not supported yet)
290
230
  */
@@ -410,7 +350,6 @@ type HybridRouterChangeEventHandler = EventHandler<HybridRouterChangeEvent>;
410
350
  interface WebFHybridRouterProps {
411
351
  path: string;
412
352
  title?: string;
413
- theme?: 'material' | 'cupertino';
414
353
  onScreen?: HybridRouterChangeEventHandler;
415
354
  offScreen?: HybridRouterChangeEventHandler;
416
355
  children?: ReactNode;
@@ -419,5 +358,5 @@ interface WebFRouterLinkElement extends WebFElementWithMethods<{}> {
419
358
  }
420
359
  declare const WebFRouterLink: FC<WebFHybridRouterProps>;
421
360
 
422
- export { Route, Routes, WebFRouter, WebFRouterLink, matchPath, matchRoutes, pathToRegex, useLocation, useNavigate, useParams, useRouteContext, useRoutes };
423
- export type { HybridRouterChangeEvent, HybridRouterChangeEventHandler, Location, NavigateFunction, NavigateOptions, NavigationMethods, RouteMatch, RouteObject, RouteParams, RouteProps, RoutesProps, WebFHybridRouterProps, WebFRouterLinkElement };
361
+ export { Route, Routes, WebFRouter, WebFRouterLink, useLocation, useNavigate, useRouteContext, useRoutes };
362
+ export type { HybridRouteStackEntry, HybridRouterChangeEvent, HybridRouterChangeEventHandler, Location, NavigateFunction, NavigateOptions, NavigationMethods, RouteObject, RouteProps, RoutesProps, WebFHybridRouterProps, WebFRouterLinkElement };
package/dist/index.esm.js CHANGED
@@ -45,6 +45,13 @@ const WebFRouter = {
45
45
  get state() {
46
46
  return webf.hybridHistory.state;
47
47
  },
48
+ /**
49
+ * Get the full hybrid router build context stack.
50
+ * The stack is ordered from root route (index 0) to the current top route (last element).
51
+ */
52
+ get stack() {
53
+ return webf.hybridHistory.buildContextStack;
54
+ },
48
55
  /**
49
56
  * Get the current route path
50
57
  */
@@ -142,65 +149,6 @@ const WebFRouter = {
142
149
  return webf.hybridHistory.restorablePopAndPushNamed(path, { arguments: state });
143
150
  })
144
151
  };
145
- /**
146
- * Convert a route pattern to a regular expression
147
- * @param pattern Route pattern like "/user/:userId" or "/category/:catId/product/:prodId"
148
- * @returns Object with regex and parameter names
149
- */
150
- function pathToRegex(pattern) {
151
- const paramNames = [];
152
- // Escape special regex characters except : and *
153
- let regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
154
- // Replace :param with named capture groups
155
- regexPattern = regexPattern.replace(/:([^\/]+)/g, (_, paramName) => {
156
- paramNames.push(paramName);
157
- return '([^/]+)';
158
- });
159
- // Add anchors for exact matching
160
- regexPattern = `^${regexPattern}$`;
161
- return {
162
- regex: new RegExp(regexPattern),
163
- paramNames
164
- };
165
- }
166
- /**
167
- * Match a pathname against a route pattern and extract parameters
168
- * @param pattern Route pattern like "/user/:userId"
169
- * @param pathname Actual pathname like "/user/123"
170
- * @returns Match result with extracted parameters or null if no match
171
- */
172
- function matchPath(pattern, pathname) {
173
- const { regex, paramNames } = pathToRegex(pattern);
174
- const match = pathname.match(regex);
175
- if (!match) {
176
- return null;
177
- }
178
- // Extract parameters from capture groups
179
- const params = {};
180
- paramNames.forEach((paramName, index) => {
181
- params[paramName] = match[index + 1]; // +1 because match[0] is the full match
182
- });
183
- return {
184
- path: pattern,
185
- params,
186
- isExact: true
187
- };
188
- }
189
- /**
190
- * Find the best matching route from a list of route patterns
191
- * @param routes Array of route patterns
192
- * @param pathname Current pathname
193
- * @returns Best match or null if no routes match
194
- */
195
- function matchRoutes(routes, pathname) {
196
- for (const route of routes) {
197
- const match = matchPath(route, pathname);
198
- if (match) {
199
- return match;
200
- }
201
- }
202
- return null;
203
- }
204
152
 
205
153
  var isFunction = function (value) {
206
154
  return typeof value === 'function';
@@ -236,7 +184,7 @@ const RawWebFRouterLink = createWebFComponent({
236
184
  tagName: 'webf-router-link',
237
185
  displayName: 'WebFRouterLink',
238
186
  // Map props to attributes
239
- attributeProps: ['path', 'title', 'theme'],
187
+ attributeProps: ['path', 'title'],
240
188
  // Event handlers
241
189
  events: [
242
190
  {
@@ -265,7 +213,7 @@ const WebFRouterLink = function (props) {
265
213
  props.onScreen(event);
266
214
  }
267
215
  };
268
- return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, theme: props.theme, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
216
+ return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
269
217
  };
270
218
 
271
219
  /**
@@ -282,7 +230,7 @@ const WebFRouterLink = function (props) {
282
230
  *
283
231
  * Responsible for managing page rendering, lifecycle and navigation bar
284
232
  */
285
- function Route({ path, prerender = false, element, title, theme = 'material' }) {
233
+ function Route({ path, prerender = false, element, title }) {
286
234
  // Mark whether the page has been rendered
287
235
  const [hasRendered, updateRender] = useState(false);
288
236
  /**
@@ -301,7 +249,7 @@ function Route({ path, prerender = false, element, title, theme = 'material' })
301
249
  */
302
250
  const handleOffScreen = useMemoizedFn(() => {
303
251
  });
304
- return (React.createElement(WebFRouterLink, { path: path, title: title, theme: theme, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
252
+ return (React.createElement(WebFRouterLink, { path: path, title: title, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
305
253
  }
306
254
 
307
255
  /**
@@ -310,7 +258,6 @@ function Route({ path, prerender = false, element, title, theme = 'material' })
310
258
  const RouteContext = createContext({
311
259
  path: undefined,
312
260
  params: undefined,
313
- routeParams: undefined,
314
261
  activePath: undefined,
315
262
  routeEventKind: undefined
316
263
  });
@@ -345,7 +292,6 @@ function useRouteContext() {
345
292
  * const location = useLocation();
346
293
  *
347
294
  * console.log('Current path:', location.pathname);
348
-
349
295
  * console.log('Location state:', location.state);
350
296
  * console.log('Is active:', location.isActive);
351
297
  *
@@ -357,46 +303,25 @@ function useLocation() {
357
303
  const context = useRouteContext();
358
304
  // Create location object from context
359
305
  const location = useMemo(() => {
360
- const currentPath = context.path || context.activePath || WebFRouter.path;
361
- let pathname = currentPath;
362
- // Check if the current component's route matches the active path
363
- const isCurrentRoute = context.path === context.activePath;
364
- // Get state - prioritize context params, fallback to WebFRouter.state
365
- const state = (context.isActive || isCurrentRoute)
366
- ? (context.params || WebFRouter.state)
367
- : WebFRouter.state;
306
+ // For active routes, return the current location with state
307
+ if (context.isActive) {
308
+ return {
309
+ pathname: context.path || context.activePath || WebFRouter.path,
310
+ state: context.params,
311
+ isActive: true,
312
+ key: `${context.path}-active-${Date.now()}`
313
+ };
314
+ }
315
+ // For inactive routes, return the global location without state
368
316
  return {
369
- pathname,
370
- state,
371
- isActive: context.isActive,
372
- key: `${pathname}-${Date.now()}`
317
+ pathname: context.activePath || WebFRouter.path,
318
+ state: undefined,
319
+ isActive: false,
320
+ key: `${context.activePath}-inactive`
373
321
  };
374
322
  }, [context.isActive, context.path, context.activePath, context.params]);
375
323
  return location;
376
324
  }
377
- /**
378
- * Hook to get route parameters from dynamic routes
379
- *
380
- * @returns Route parameters object with parameter names as keys and values as strings
381
- *
382
- * @example
383
- * ```tsx
384
- * // For route pattern "/user/:userId" and actual path "/user/123"
385
- * function UserPage() {
386
- * const params = useParams();
387
- *
388
- * console.log(params.userId); // "123"
389
- *
390
- * return <div>User ID: {params.userId}</div>;
391
- * }
392
- * ```
393
- */
394
- function useParams() {
395
- const context = useRouteContext();
396
- return useMemo(() => {
397
- return context.routeParams || {};
398
- }, [context.routeParams]);
399
- }
400
325
  /**
401
326
  * Routes component that wraps multiple Route components and provides shared context
402
327
  *
@@ -416,17 +341,11 @@ function RouteContextProvider({ path, children }) {
416
341
  const globalContext = useContext(RouteContext);
417
342
  // Create a route-specific context that only updates when this route is active
418
343
  const routeSpecificContext = useMemo(() => {
419
- // Check if this route pattern matches the active path
420
- const match = globalContext.activePath ? matchPath(path, globalContext.activePath) : null;
421
- if (match) {
422
- // Use route params from Flutter event if available, otherwise from local matching
423
- const effectiveRouteParams = globalContext.routeParams || match.params;
424
- // For matching routes, always try to get state from WebFRouter if params is undefined
425
- const effectiveParams = globalContext.params !== undefined ? globalContext.params : WebFRouter.state;
344
+ // Only update if this route is the active one
345
+ if (globalContext.activePath === path) {
426
346
  return {
427
347
  path,
428
- params: effectiveParams,
429
- routeParams: effectiveRouteParams,
348
+ params: globalContext.params,
430
349
  activePath: globalContext.activePath,
431
350
  routeEventKind: globalContext.routeEventKind
432
351
  };
@@ -435,55 +354,33 @@ function RouteContextProvider({ path, children }) {
435
354
  return {
436
355
  path,
437
356
  params: undefined,
438
- routeParams: undefined,
439
357
  activePath: globalContext.activePath,
440
358
  routeEventKind: undefined
441
359
  };
442
- }, [path, globalContext.activePath, globalContext.params, globalContext.routeParams, globalContext.routeEventKind]);
360
+ }, [path, globalContext.activePath, globalContext.params, globalContext.routeEventKind]);
443
361
  return (React.createElement(RouteContext.Provider, { value: routeSpecificContext }, children));
444
362
  }
445
363
  function Routes({ children }) {
446
364
  // State to track current route information
447
365
  const [routeState, setRouteState] = useState({
448
366
  path: undefined,
449
- activePath: WebFRouter.path, // Initialize with current path
367
+ activePath: undefined,
450
368
  params: undefined,
451
- routeParams: undefined,
452
369
  routeEventKind: undefined
453
370
  });
454
371
  // Listen to hybridrouterchange event
455
372
  useEffect(() => {
456
373
  const handleRouteChange = (event) => {
457
374
  const routeEvent = event;
458
- // Check for new event detail structure with params
459
- const eventDetail = event.detail;
460
375
  // Only update activePath for push events
461
376
  const newActivePath = (routeEvent.kind === 'didPushNext' || routeEvent.kind === 'didPush')
462
377
  ? routeEvent.path
463
378
  : routeState.activePath;
464
- // For dynamic routes, extract parameters from the path using registered route patterns
465
- let routeParams = (eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.params) || undefined;
466
- if (!routeParams && newActivePath) {
467
- // Try to extract parameters from registered route patterns
468
- const registeredRoutes = Array.from(document.querySelectorAll('webf-router-link'));
469
- for (const routeElement of registeredRoutes) {
470
- const routePath = routeElement.getAttribute('path');
471
- if (routePath && routePath.includes(':')) {
472
- const match = matchPath(routePath, newActivePath);
473
- if (match) {
474
- routeParams = match.params;
475
- break;
476
- }
477
- }
478
- }
479
- }
480
- const eventState = (eventDetail === null || eventDetail === void 0 ? void 0 : eventDetail.state) || routeEvent.state;
481
379
  // Update state based on event kind
482
380
  setRouteState({
483
381
  path: routeEvent.path,
484
382
  activePath: newActivePath,
485
- params: eventState,
486
- routeParams: routeParams, // Use params from Flutter if available
383
+ params: routeEvent.state,
487
384
  routeEventKind: routeEvent.kind
488
385
  });
489
386
  };
@@ -498,10 +395,9 @@ function Routes({ children }) {
498
395
  const globalContextValue = useMemo(() => ({
499
396
  path: undefined,
500
397
  params: routeState.params,
501
- routeParams: routeState.routeParams, // Pass through route params from Flutter
502
398
  activePath: routeState.activePath,
503
399
  routeEventKind: routeState.routeEventKind
504
- }), [routeState.activePath, routeState.params, routeState.routeParams, routeState.routeEventKind]);
400
+ }), [routeState.activePath, routeState.params, routeState.routeEventKind]);
505
401
  // Wrap each Route component with its own context provider
506
402
  const wrappedChildren = useMemo(() => {
507
403
  return Children.map(children, (child) => {
@@ -547,7 +443,7 @@ function useRoutes(routes) {
547
443
  if (route.children && route.children.length > 0) {
548
444
  console.warn('Nested routes are not supported yet');
549
445
  }
550
- return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender, theme: route.theme }));
446
+ return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender }));
551
447
  });
552
448
  }, [routes]);
553
449
  // Return Routes component with Route children
@@ -617,5 +513,5 @@ function useNavigate() {
617
513
  }, []);
618
514
  }
619
515
 
620
- export { Route, Routes, WebFRouter, WebFRouterLink, matchPath, matchRoutes, pathToRegex, useLocation, useNavigate, useParams, useRouteContext, useRoutes };
516
+ export { Route, Routes, WebFRouter, WebFRouterLink, useLocation, useNavigate, useRouteContext, useRoutes };
621
517
  //# sourceMappingURL=index.esm.js.map