@openwebf/react-router 0.3.2 → 0.3.4

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.
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+ export interface RouteProps {
3
+ title?: string;
4
+ path: string;
5
+ prerender?: boolean;
6
+ element: React.ReactNode;
7
+ }
8
+ export interface RoutesProps {
9
+ children: React.ReactNode;
10
+ }
11
+ export interface Location {
12
+ pathname: string;
13
+ state: any;
14
+ key?: string;
15
+ }
16
+ export interface RouteParams {
17
+ [key: string]: string;
18
+ }
19
+ export interface RouteMatch {
20
+ path: string;
21
+ params: RouteParams;
22
+ isExact: boolean;
23
+ }
24
+ export interface RouteContext<S = any> {
25
+ path: string | undefined;
26
+ params: S | undefined;
27
+ routeParams: RouteParams | undefined;
28
+ activePath: string | undefined;
29
+ routeEventKind?: 'didPushNext' | 'didPush' | 'didPop' | 'didPopNext';
30
+ }
31
+ export interface NavigateOptions {
32
+ replace?: boolean;
33
+ state?: any;
34
+ }
35
+ export interface NavigateFunction {
36
+ (to: string, options?: NavigateOptions): void;
37
+ (delta: number): void;
38
+ }
39
+ export interface NavigationMethods {
40
+ navigate: NavigateFunction;
41
+ pop: (result?: any) => void;
42
+ popUntil: (path: string) => void;
43
+ popAndPush: (path: string, state?: any) => Promise<void>;
44
+ pushAndRemoveUntil: (newPath: string, untilPath: string, state?: any) => Promise<void>;
45
+ canPop: () => boolean;
46
+ maybePop: (result?: any) => boolean;
47
+ }
48
+ export declare function Route({ path, element }: RouteProps): React.JSX.Element;
49
+ export declare function Routes({ children }: RoutesProps): React.JSX.Element;
50
+ export declare function useLocation(): Location & {
51
+ isActive: boolean;
52
+ };
53
+ export declare function useRouteContext(): RouteContext & {
54
+ isActive: boolean;
55
+ };
56
+ export declare function useParams(): RouteParams;
57
+ export declare function useRoutes(routes: Array<{
58
+ path: string;
59
+ element: React.ReactNode;
60
+ prerender?: boolean;
61
+ children?: any[];
62
+ }>): React.ReactElement | null;
63
+ export declare function useNavigate(): NavigationMethods;
64
+ export declare function pathToRegex(pattern: string): {
65
+ regex: RegExp;
66
+ paramNames: string[];
67
+ };
68
+ export declare function matchPath(pattern: string, pathname: string): RouteMatch | null;
69
+ export declare function matchRoutes(routes: string[], pathname: string): RouteMatch | null;
70
+ export declare const WebFRouter: {
71
+ readonly state: any;
72
+ readonly path: string;
73
+ push: (path: string, state?: any) => Promise<void>;
74
+ replace: (path: string, state?: any) => Promise<void>;
75
+ back: () => void;
76
+ pop: (result?: any) => void;
77
+ popUntil: (path: string) => void;
78
+ popAndPushNamed: (path: string, state?: any) => Promise<void>;
79
+ pushNamedAndRemoveUntil: (path: string, _state: any, _untilPath: string) => Promise<void>;
80
+ pushNamedAndRemoveUntilRoute: (newPath: string, _untilPath: string, state?: any) => Promise<void>;
81
+ canPop: () => boolean;
82
+ maybePop: (result?: any) => boolean;
83
+ pushState: (state: any, name: string) => void;
84
+ replaceState: (state: any, name: string) => void;
85
+ restorablePopAndPushState: (state: any, _name: string) => string;
86
+ restorablePopAndPushNamed: (path: string, state?: any) => Promise<string>;
87
+ };
88
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.tsx"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -78,6 +78,43 @@ declare const WebFRouter: {
78
78
  */
79
79
  restorablePopAndPushNamed: <T extends RoutePath>(path: T, state?: any) => Promise<string>;
80
80
  };
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;
81
118
 
82
119
  /**
83
120
  * Route Component
@@ -116,13 +153,20 @@ interface RouteProps {
116
153
  * The actual page component to render
117
154
  */
118
155
  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';
119
163
  }
120
164
  /**
121
165
  * Route Component
122
166
  *
123
167
  * Responsible for managing page rendering, lifecycle and navigation bar
124
168
  */
125
- declare function Route({ path, prerender, element, title }: RouteProps): React.JSX.Element;
169
+ declare function Route({ path, prerender, element, title, theme }: RouteProps): React.JSX.Element;
126
170
 
127
171
  /**
128
172
  * Hook to get route context
@@ -149,6 +193,10 @@ declare function useRouteContext(): {
149
193
  * State data passed during route navigation
150
194
  */
151
195
  params: any;
196
+ /**
197
+ * Route parameters extracted from dynamic routes (e.g., :userId in /user/:userId)
198
+ */
199
+ routeParams: RouteParams | undefined;
152
200
  /**
153
201
  * Current active path from router
154
202
  */
@@ -186,6 +234,7 @@ interface Location {
186
234
  * const location = useLocation();
187
235
  *
188
236
  * console.log('Current path:', location.pathname);
237
+
189
238
  * console.log('Location state:', location.state);
190
239
  * console.log('Is active:', location.isActive);
191
240
  *
@@ -196,6 +245,24 @@ interface Location {
196
245
  declare function useLocation(): Location & {
197
246
  isActive: boolean;
198
247
  };
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;
199
266
  /**
200
267
  * Route configuration object
201
268
  */
@@ -212,6 +279,12 @@ interface RouteObject {
212
279
  * Whether to pre-render this route
213
280
  */
214
281
  prerender?: boolean;
282
+ /**
283
+ * Theme for this route
284
+ *
285
+ * @default "material"
286
+ */
287
+ theme?: 'material' | 'cupertino';
215
288
  /**
216
289
  * Child routes (not supported yet)
217
290
  */
@@ -337,6 +410,7 @@ type HybridRouterChangeEventHandler = EventHandler<HybridRouterChangeEvent>;
337
410
  interface WebFHybridRouterProps {
338
411
  path: string;
339
412
  title?: string;
413
+ theme?: 'material' | 'cupertino';
340
414
  onScreen?: HybridRouterChangeEventHandler;
341
415
  offScreen?: HybridRouterChangeEventHandler;
342
416
  children?: ReactNode;
@@ -345,5 +419,5 @@ interface WebFRouterLinkElement extends WebFElementWithMethods<{}> {
345
419
  }
346
420
  declare const WebFRouterLink: FC<WebFHybridRouterProps>;
347
421
 
348
- export { Route, Routes, WebFRouter, WebFRouterLink, useLocation, useNavigate, useRouteContext, useRoutes };
349
- export type { HybridRouterChangeEvent, HybridRouterChangeEventHandler, Location, NavigateFunction, NavigateOptions, NavigationMethods, RouteObject, RouteProps, RoutesProps, WebFHybridRouterProps, WebFRouterLinkElement };
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 };
package/dist/index.esm.js CHANGED
@@ -142,44 +142,101 @@ const WebFRouter = {
142
142
  return webf.hybridHistory.restorablePopAndPushNamed(path, { arguments: state });
143
143
  })
144
144
  };
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
+ }
145
204
 
146
205
  var isFunction = function (value) {
147
- return typeof value === 'function';
206
+ return typeof value === 'function';
148
207
  };
149
208
 
150
209
  var isDev = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
151
210
 
152
- function useMemoizedFn(fn) {
153
- if (isDev) {
154
- if (!isFunction(fn)) {
155
- console.error("useMemoizedFn expected parameter is a function, got ".concat(typeof fn));
211
+ var useMemoizedFn = function (fn) {
212
+ if (isDev) {
213
+ if (!isFunction(fn)) {
214
+ console.error("useMemoizedFn expected parameter is a function, got ".concat(typeof fn));
215
+ }
156
216
  }
157
- }
158
- var fnRef = useRef(fn);
159
- // why not write `fnRef.current = fn`?
160
- // https://github.com/alibaba/hooks/issues/728
161
- fnRef.current = useMemo(function () {
162
- return fn;
163
- }, [fn]);
164
- var memoizedFn = useRef();
165
- if (!memoizedFn.current) {
166
- memoizedFn.current = function () {
167
- var args = [];
168
- for (var _i = 0; _i < arguments.length; _i++) {
169
- args[_i] = arguments[_i];
170
- }
171
- return fnRef.current.apply(this, args);
172
- };
173
- }
174
- return memoizedFn.current;
175
- }
217
+ var fnRef = useRef(fn);
218
+ // why not write `fnRef.current = fn`?
219
+ // https://github.com/alibaba/hooks/issues/728
220
+ fnRef.current = useMemo(function () { return fn; }, [fn]);
221
+ var memoizedFn = useRef(undefined);
222
+ if (!memoizedFn.current) {
223
+ memoizedFn.current = function () {
224
+ var args = [];
225
+ for (var _i = 0; _i < arguments.length; _i++) {
226
+ args[_i] = arguments[_i];
227
+ }
228
+ return fnRef.current.apply(this, args);
229
+ };
230
+ }
231
+ return memoizedFn.current;
232
+ };
176
233
 
177
234
  // Create the raw component using createWebFComponent
178
235
  const RawWebFRouterLink = createWebFComponent({
179
236
  tagName: 'webf-router-link',
180
237
  displayName: 'WebFRouterLink',
181
238
  // Map props to attributes
182
- attributeProps: ['path', 'title'],
239
+ attributeProps: ['path', 'title', 'theme'],
183
240
  // Event handlers
184
241
  events: [
185
242
  {
@@ -208,7 +265,7 @@ const WebFRouterLink = function (props) {
208
265
  props.onScreen(event);
209
266
  }
210
267
  };
211
- return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
268
+ return (React.createElement(RawWebFRouterLink, { title: props.title, path: props.path, theme: props.theme, onScreen: handleOnScreen, offScreen: props.offScreen }, isRender ? props.children : null));
212
269
  };
213
270
 
214
271
  /**
@@ -225,7 +282,7 @@ const WebFRouterLink = function (props) {
225
282
  *
226
283
  * Responsible for managing page rendering, lifecycle and navigation bar
227
284
  */
228
- function Route({ path, prerender = false, element, title }) {
285
+ function Route({ path, prerender = false, element, title, theme = 'material' }) {
229
286
  // Mark whether the page has been rendered
230
287
  const [hasRendered, updateRender] = useState(false);
231
288
  /**
@@ -244,7 +301,7 @@ function Route({ path, prerender = false, element, title }) {
244
301
  */
245
302
  const handleOffScreen = useMemoizedFn(() => {
246
303
  });
247
- return (React.createElement(WebFRouterLink, { path: path, title: title, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
304
+ return (React.createElement(WebFRouterLink, { path: path, title: title, theme: theme, onScreen: handleOnScreen, offScreen: handleOffScreen }, shouldRenderChildren ? element : null));
248
305
  }
249
306
 
250
307
  /**
@@ -253,6 +310,7 @@ function Route({ path, prerender = false, element, title }) {
253
310
  const RouteContext = createContext({
254
311
  path: undefined,
255
312
  params: undefined,
313
+ routeParams: undefined,
256
314
  activePath: undefined,
257
315
  routeEventKind: undefined
258
316
  });
@@ -287,6 +345,7 @@ function useRouteContext() {
287
345
  * const location = useLocation();
288
346
  *
289
347
  * console.log('Current path:', location.pathname);
348
+
290
349
  * console.log('Location state:', location.state);
291
350
  * console.log('Is active:', location.isActive);
292
351
  *
@@ -298,25 +357,46 @@ function useLocation() {
298
357
  const context = useRouteContext();
299
358
  // Create location object from context
300
359
  const location = useMemo(() => {
301
- // For active routes, return the current location with state
302
- if (context.isActive) {
303
- return {
304
- pathname: context.path || context.activePath || WebFRouter.path,
305
- state: context.params,
306
- isActive: true,
307
- key: `${context.path}-active-${Date.now()}`
308
- };
309
- }
310
- // For inactive routes, return the global location without state
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;
311
368
  return {
312
- pathname: context.activePath || WebFRouter.path,
313
- state: undefined,
314
- isActive: false,
315
- key: `${context.activePath}-inactive`
369
+ pathname,
370
+ state,
371
+ isActive: context.isActive,
372
+ key: `${pathname}-${Date.now()}`
316
373
  };
317
374
  }, [context.isActive, context.path, context.activePath, context.params]);
318
375
  return location;
319
376
  }
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
+ }
320
400
  /**
321
401
  * Routes component that wraps multiple Route components and provides shared context
322
402
  *
@@ -336,11 +416,17 @@ function RouteContextProvider({ path, children }) {
336
416
  const globalContext = useContext(RouteContext);
337
417
  // Create a route-specific context that only updates when this route is active
338
418
  const routeSpecificContext = useMemo(() => {
339
- // Only update if this route is the active one
340
- if (globalContext.activePath === path) {
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;
341
426
  return {
342
427
  path,
343
- params: globalContext.params,
428
+ params: effectiveParams,
429
+ routeParams: effectiveRouteParams,
344
430
  activePath: globalContext.activePath,
345
431
  routeEventKind: globalContext.routeEventKind
346
432
  };
@@ -349,33 +435,55 @@ function RouteContextProvider({ path, children }) {
349
435
  return {
350
436
  path,
351
437
  params: undefined,
438
+ routeParams: undefined,
352
439
  activePath: globalContext.activePath,
353
440
  routeEventKind: undefined
354
441
  };
355
- }, [path, globalContext.activePath, globalContext.params, globalContext.routeEventKind]);
442
+ }, [path, globalContext.activePath, globalContext.params, globalContext.routeParams, globalContext.routeEventKind]);
356
443
  return (React.createElement(RouteContext.Provider, { value: routeSpecificContext }, children));
357
444
  }
358
445
  function Routes({ children }) {
359
446
  // State to track current route information
360
447
  const [routeState, setRouteState] = useState({
361
448
  path: undefined,
362
- activePath: undefined,
449
+ activePath: WebFRouter.path, // Initialize with current path
363
450
  params: undefined,
451
+ routeParams: undefined,
364
452
  routeEventKind: undefined
365
453
  });
366
454
  // Listen to hybridrouterchange event
367
455
  useEffect(() => {
368
456
  const handleRouteChange = (event) => {
369
457
  const routeEvent = event;
458
+ // Check for new event detail structure with params
459
+ const eventDetail = event.detail;
370
460
  // Only update activePath for push events
371
461
  const newActivePath = (routeEvent.kind === 'didPushNext' || routeEvent.kind === 'didPush')
372
462
  ? routeEvent.path
373
463
  : 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;
374
481
  // Update state based on event kind
375
482
  setRouteState({
376
483
  path: routeEvent.path,
377
484
  activePath: newActivePath,
378
- params: routeEvent.state,
485
+ params: eventState,
486
+ routeParams: routeParams, // Use params from Flutter if available
379
487
  routeEventKind: routeEvent.kind
380
488
  });
381
489
  };
@@ -390,9 +498,10 @@ function Routes({ children }) {
390
498
  const globalContextValue = useMemo(() => ({
391
499
  path: undefined,
392
500
  params: routeState.params,
501
+ routeParams: routeState.routeParams, // Pass through route params from Flutter
393
502
  activePath: routeState.activePath,
394
503
  routeEventKind: routeState.routeEventKind
395
- }), [routeState.activePath, routeState.params, routeState.routeEventKind]);
504
+ }), [routeState.activePath, routeState.params, routeState.routeParams, routeState.routeEventKind]);
396
505
  // Wrap each Route component with its own context provider
397
506
  const wrappedChildren = useMemo(() => {
398
507
  return Children.map(children, (child) => {
@@ -438,7 +547,7 @@ function useRoutes(routes) {
438
547
  if (route.children && route.children.length > 0) {
439
548
  console.warn('Nested routes are not supported yet');
440
549
  }
441
- return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender }));
550
+ return (React.createElement(Route, { key: route.path, path: route.path, element: route.element, prerender: route.prerender, theme: route.theme }));
442
551
  });
443
552
  }, [routes]);
444
553
  // Return Routes component with Route children
@@ -508,5 +617,5 @@ function useNavigate() {
508
617
  }, []);
509
618
  }
510
619
 
511
- export { Route, Routes, WebFRouter, WebFRouterLink, useLocation, useNavigate, useRouteContext, useRoutes };
620
+ export { Route, Routes, WebFRouter, WebFRouterLink, matchPath, matchRoutes, pathToRegex, useLocation, useNavigate, useParams, useRouteContext, useRoutes };
512
621
  //# sourceMappingURL=index.esm.js.map