@iyulab/router 0.2.1 → 0.3.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/README.md CHANGED
@@ -5,15 +5,15 @@ A modern, lightweight client-side router for web applications with support for b
5
5
  ## Features
6
6
 
7
7
  - 🚀 **Modern URLPattern-based routing** - Uses native URLPattern API for powerful path matching
8
- - 🔧 **Dual Framework Support** - Works with both Lit and React components
8
+ - 🔧 **Unified Framework Support** - Works with both Lit and React components using render functions
9
9
  - 📱 **Client-side Navigation** - History API integration with browser back/forward support
10
- - 🎯 **Nested Routing** - Support for deeply nested route hierarchies
11
- - ⚡ **Async Data Loading** - Built-in loader functions for data fetching
10
+ - 🎯 **Nested Routing** - Support for deeply nested route hierarchies with index and path routes
12
11
  - 🔗 **Smart Link Component** - Automatic external link detection and handling
13
- - 📊 **Route Events** - Track navigation progress and handle errors
14
- - 🎨 **Flexible Outlet System** - Dynamic component rendering with lifecycle management
12
+ - 📊 **Route Events** - Track navigation progress with route-begin, route-done, and route-error events
13
+ - 🎨 **Flexible Outlet System** - Unified rendering with renderContent method
15
14
  - 🌐 **Global Route Access** - Access current route information anywhere via `window.route`
16
15
  - 🔄 **Force Re-rendering** - Control component re-rendering on route changes
16
+ - ⚠️ **Enhanced Error Handling** - Built-in ErrorPage component with improved styling
17
17
 
18
18
  ## Installation
19
19
 
@@ -27,22 +27,19 @@ npm install @iyulab/router
27
27
 
28
28
  ```typescript
29
29
  import { Router } from '@iyulab/router';
30
+ import { html } from 'lit';
30
31
 
31
32
  const router = new Router({
32
33
  root: document.getElementById('app')!,
33
34
  basepath: '/app',
34
35
  routes: [
35
36
  {
36
- path: '/',
37
- element: 'home-page'
37
+ index: true,
38
+ render: () => html`<home-page></home-page>`
38
39
  },
39
40
  {
40
41
  path: '/user/:id',
41
- component: UserComponent,
42
- loader: async (routeInfo) => {
43
- const response = await fetch(`/api/user/${routeInfo.params.id}`);
44
- return response.json();
45
- }
42
+ render: (routeInfo) => html`<user-page .userId=${routeInfo.params.id}></user-page>`
46
43
  }
47
44
  ]
48
45
  });
@@ -51,411 +48,108 @@ const router = new Router({
51
48
  router.go(window.location.href);
52
49
  ```
53
50
 
54
- ## Usage Examples
55
-
56
- ### Using with Lit Components
57
-
58
- ```typescript
59
- import { LitElement, html } from 'lit';
60
- import { customElement } from 'lit/decorators.js';
61
-
62
- @customElement('app-root')
63
- export class AppRoot extends LitElement {
64
- render() {
65
- return html`
66
- <nav>
67
- <u-link href="/">Home</u-link>
68
- <u-link href="/about">About</u-link>
69
- <u-link href="/user/123">User Profile</u-link>
70
- </nav>
71
- <main>
72
- <u-outlet></u-outlet>
73
- </main>
74
- `;
75
- }
76
- }
77
- ```
78
-
79
- ### Using with React Components
80
-
81
- ```tsx
82
- import React from 'react';
83
- import { Outlet, Link } from '@iyulab/router';
84
-
85
- export function AppRoot() {
86
- return (
87
- <div>
88
- <nav>
89
- <Link href="/">Home</Link>
90
- <Link href="/about">About</Link>
91
- <Link href="/user/123">User Profile</Link>
92
- </nav>
93
- <main>
94
- <Outlet />
95
- </main>
96
- </div>
97
- );
98
- }
99
- ```
100
-
101
- ### Advanced Routing Features
102
-
103
- #### Nested Routes with Layouts
51
+ ### Nested Routes
104
52
 
105
53
  ```typescript
106
54
  const routes = [
107
55
  {
108
56
  path: '/dashboard',
109
- element: 'dashboard-layout',
57
+ render: () => html`<dashboard-layout><u-outlet></u-outlet></dashboard-layout>`,
110
58
  children: [
111
59
  {
112
60
  index: true, // Matches /dashboard exactly
113
- element: 'dashboard-home'
61
+ render: () => html`<dashboard-home></dashboard-home>`
114
62
  },
115
63
  {
116
64
  path: 'settings',
117
- element: 'dashboard-settings'
118
- },
119
- {
120
- path: 'profile',
121
- component: ProfileComponent
122
- },
123
- {
124
- path: 'users/:id',
125
- component: UserDetailComponent,
126
- loader: async (routeInfo) => {
127
- const userId = routeInfo.params.id;
128
- return await fetchUserData(userId);
129
- }
65
+ render: () => html`<dashboard-settings></dashboard-settings>`
130
66
  }
131
67
  ]
132
68
  }
133
69
  ];
134
70
  ```
135
71
 
136
- #### Route Data Loading
72
+ ### Mixed Framework Support
137
73
 
138
74
  ```typescript
139
- // Routes with async data loading
140
- {
141
- path: '/posts/:slug',
142
- component: PostComponent,
143
- loader: async (routeInfo) => {
144
- const { slug } = routeInfo.params;
145
- const post = await fetch(`/api/posts/${slug}`).then(r => r.json());
146
- return { post };
147
- },
148
- title: 'Blog Post' // Sets document title
149
- }
150
- ```
151
-
152
- #### Force Re-rendering Control
75
+ import React from 'react';
153
76
 
154
- ```typescript
155
77
  const routes = [
78
+ // Lit component
79
+ {
80
+ path: '/lit-page',
81
+ render: (routeInfo) => html`<my-lit-component .routeInfo=${routeInfo}></my-lit-component>`
82
+ },
83
+ // React component
156
84
  {
157
- path: '/live-data',
158
- component: LiveDataComponent,
159
- force: true // Always re-render on navigation
85
+ path: '/react-page',
86
+ render: (routeInfo) => React.createElement(MyReactComponent, { routeInfo })
160
87
  },
88
+ // HTML element
161
89
  {
162
- path: '/cached-content',
163
- component: CachedComponent,
164
- force: false // Reuse existing component instance
90
+ path: '/element-page',
91
+ render: (routeInfo) => {
92
+ const element = document.createElement('my-element');
93
+ element.data = routeInfo.params;
94
+ return element;
95
+ }
165
96
  }
166
97
  ];
167
98
  ```
168
99
 
169
- ### Global Route Information Access
170
-
171
- Access current route information anywhere in your application:
172
-
173
- ```typescript
174
- // Available globally on window.route
175
- console.log('Current path:', window.route.pathname);
176
- console.log('Route params:', window.route.params);
177
- console.log('Query params:', window.route.search);
178
- console.log('Loaded data:', window.route.data);
179
- ```
100
+ ## Usage Examples
180
101
 
181
- #### Lit Component Route Integration
102
+ ### Using with Lit Components
182
103
 
183
104
  ```typescript
184
105
  import { LitElement, html } from 'lit';
185
- import { customElement, state } from 'lit/decorators.js';
186
-
187
- @customElement('route-aware-component')
188
- export class RouteAwareComponent extends LitElement {
189
- @state() private routeInfo = window.route;
190
-
191
- connectedCallback() {
192
- super.connectedCallback();
193
- // Listen for route changes
194
- window.addEventListener('route-end', this.handleRouteChange);
195
- }
196
-
197
- disconnectedCallback() {
198
- window.removeEventListener('route-end', this.handleRouteChange);
199
- super.disconnectedCallback();
200
- }
201
-
202
- private handleRouteChange = (event: CustomEvent) => {
203
- this.routeInfo = event.detail;
204
- this.requestUpdate();
205
- };
106
+ import { customElement } from 'lit/decorators.js';
206
107
 
108
+ @customElement('app-root')
109
+ export class AppRoot extends LitElement {
207
110
  render() {
208
111
  return html`
209
- <div>Current path: ${this.routeInfo?.pathname}</div>
210
- <div>User ID: ${this.routeInfo?.params?.id}</div>
211
- <div>Data: ${JSON.stringify(this.routeInfo?.data)}</div>
112
+ <nav>
113
+ <u-link href="/">Home</u-link>
114
+ <u-link href="/about">About</u-link>
115
+ <u-link href="/user/123">User Profile</u-link>
116
+ </nav>
117
+ <main>
118
+ <u-outlet></u-outlet>
119
+ </main>
212
120
  `;
213
121
  }
214
122
  }
215
123
  ```
216
124
 
217
- #### React Component Route Integration
125
+ ### Using with React Components
218
126
 
219
127
  ```tsx
220
- import React, { useState, useEffect } from 'react';
221
-
222
- export function RouteAwareComponent() {
223
- const [routeInfo, setRouteInfo] = useState(window.route);
224
-
225
- useEffect(() => {
226
- const handleRouteChange = (event) => {
227
- setRouteInfo(event.detail);
228
- };
229
-
230
- window.addEventListener('route-end', handleRouteChange);
231
- return () => window.removeEventListener('route-end', handleRouteChange);
232
- }, []);
128
+ import React from 'react';
129
+ import { Outlet, Link } from '@iyulab/router';
233
130
 
131
+ export function AppRoot() {
234
132
  return (
235
133
  <div>
236
- <div>Current path: {routeInfo?.pathname}</div>
237
- <div>User ID: {routeInfo?.params?.id}</div>
238
- <div>Data: {JSON.stringify(routeInfo?.data)}</div>
134
+ <nav>
135
+ <Link href="/">Home</Link>
136
+ <Link href="/about">About</Link>
137
+ <Link href="/user/123">User Profile</Link>
138
+ </nav>
139
+ <main>
140
+ <Outlet />
141
+ </main>
239
142
  </div>
240
143
  );
241
144
  }
242
145
  ```
243
146
 
244
- ## API Reference
245
-
246
- ### Router Class
247
-
248
- #### Constructor
249
-
250
- ```typescript
251
- new Router(config: RouterConfig)
252
- ```
253
-
254
- **RouterConfig:**
255
- - `root: HTMLElement` - Root element where router renders components
256
- - `basepath?: string` - Base path for all routes (default: '/')
257
- - `routes: RouteConfig[]` - Array of route configurations
258
-
259
- #### Methods
260
-
261
- - `go(href: string): Promise<void>` - Navigate to specified path
262
- - `get basepath(): string` - Get current base path
263
- - `get routeInfo(): RouteInfo | undefined` - Get current route information
264
-
265
- ### Route Configuration
266
-
267
- ```typescript
268
- interface RouteConfig {
269
- id?: string; // Unique route identifier (auto-generated)
270
- path?: string; // URL pattern (URLPattern syntax)
271
- index?: boolean; // Index route (matches parent exactly)
272
- title?: string; // Document title
273
- element?: typeof LitElement | string; // Lit element class or tag name
274
- component?: ComponentType; // React component
275
- loader?: (info: RouteInfo) => Promise<any>; // Async data loader
276
- children?: RouteConfig[]; // Nested routes
277
- force?: boolean; // Force re-render on navigation
278
- }
279
- ```
280
-
281
- #### URLPattern Examples
282
-
283
- ```typescript
284
- // Static paths
285
- { path: '/about' }
286
- { path: '/contact' }
287
-
288
- // Dynamic parameters
289
- { path: '/user/:id' }
290
- { path: '/blog/:year/:month/:slug' }
291
-
292
- // Optional parameters
293
- { path: '/posts/:id?' }
294
-
295
- // Wildcard matching
296
- { path: '/files/*' }
297
- { path: '/api/**' }
298
- ```
299
-
300
- ### Components
301
-
302
- #### u-outlet / Outlet
303
-
304
- Renders the matched route component.
305
-
306
- ```html
307
- <!-- Lit usage -->
308
- <u-outlet></u-outlet>
309
- ```
310
-
311
- ```jsx
312
- // React usage
313
- <Outlet />
314
- ```
315
-
316
- #### u-link / Link
317
-
318
- Smart navigation link with automatic external link detection.
319
-
320
- ```html
321
- <!-- Lit usage -->
322
- <u-link href="/about">About</u-link>
323
- <u-link href="https://example.com">External</u-link>
324
- ```
325
-
326
- ```jsx
327
- // React usage
328
- <Link href="/about">About</Link>
329
- <Link href="https://example.com">External</Link>
330
- ```
331
-
332
- ### Route Information
333
-
334
- ```typescript
335
- interface RouteInfo {
336
- pathname: string; // Current pathname
337
- search: string; // Query string
338
- hash: string; // URL hash
339
- href: string; // Full URL
340
- basepath: string; // Router base path
341
- params: Record<string, string>; // Route parameters
342
- data?: any; // Data from loader function
343
- }
344
- ```
345
-
346
- ### Events
347
-
348
- The router dispatches these events on the `window` object:
349
-
350
- - **`route-start`** - Navigation begins
351
- ```typescript
352
- window.addEventListener('route-start', (event) => {
353
- console.log('Navigating to:', event.detail.pathname);
354
- });
355
- ```
356
-
357
- - **`route-end`** - Navigation completes successfully
358
- ```typescript
359
- window.addEventListener('route-end', (event) => {
360
- console.log('Navigation complete:', event.detail);
361
- });
362
- ```
363
-
364
- - **`route-error`** - Navigation fails
365
- ```typescript
366
- window.addEventListener('route-error', (event) => {
367
- console.error('Route error:', event.detail);
368
- });
369
- ```
370
-
371
- ## Advanced Examples
372
-
373
- ### Error Handling
374
-
375
- ```typescript
376
- // Custom error handling
377
- window.addEventListener('route-error', (event) => {
378
- const error = event.detail;
379
- console.error(`Route error (${error.code}):`, error.message);
380
-
381
- // Show user-friendly error message
382
- if (error.code === '404') {
383
- showNotification('Page not found');
384
- } else {
385
- showNotification('Navigation failed');
386
- }
387
- });
388
- ```
389
-
390
- ### Dynamic Route Registration
391
-
392
- ```typescript
393
- const router = new Router({
394
- root: document.getElementById('app')!,
395
- routes: [
396
- {
397
- path: '/',
398
- element: 'home-page'
399
- }
400
- ]
401
- });
402
-
403
- // Add routes dynamically by reconstructing the router
404
- function addRoute(newRoute: RouteConfig) {
405
- const newRoutes = [...router.routes, newRoute];
406
- return new Router({
407
- root: router.root,
408
- basepath: router.basepath,
409
- routes: newRoutes
410
- });
411
- }
412
- ```
413
-
414
- ### Progressive Enhancement
415
-
416
- ```typescript
417
- // Check if URLPattern is supported
418
- if ('URLPattern' in window) {
419
- // Use the router
420
- const router = new Router({ /* config */ });
421
- router.go(window.location.href);
422
- } else {
423
- // Fallback to traditional navigation
424
- console.warn('URLPattern not supported, using traditional navigation');
425
- }
426
- ```
427
-
428
- ### Performance Optimization
429
-
430
- ```typescript
431
- // Lazy load route components
432
- const routes = [
433
- {
434
- path: '/dashboard',
435
- loader: async () => {
436
- // Dynamically import the component
437
- const module = await import('./pages/DashboardPage.js');
438
- return { component: module.DashboardPage };
439
- }
440
- }
441
- ];
442
- ```
443
-
444
147
  ## Browser Support
445
148
 
446
149
  - **URLPattern API**: Required for routing functionality
447
150
  - **Modern browsers**: Chrome 95+, Firefox 106+, Safari 16.4+
448
151
  - **Polyfill**: Consider using [urlpattern-polyfill](https://www.npmjs.com/package/urlpattern-polyfill) for older browsers
449
152
 
450
- ## Migration Guide
451
-
452
- ### From v0.1.x to v0.2.x
453
-
454
- - Removed `connect()` and `disconnect()` methods
455
- - Navigation now starts with `router.go()` instead of `router.connect()`
456
- - Event names changed from `route-change` to `route-start`/`route-end`
457
- - Simplified component exports - use `UOutlet`/`Outlet` and `ULink`/`Link`
458
-
459
153
  ## Contributing
460
154
 
461
155
  1. Fork the repository