olova-router 1.0.19 → 1.0.21

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Olova Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,8 +1,31 @@
1
1
  # olova-router
2
2
 
3
- Standalone router package for Olova.
3
+ A powerful, feature-rich router for Olova framework with advanced routing capabilities, middleware support, and lazy loading.
4
4
 
5
- ## Main API
5
+ ## Features
6
+
7
+ - 🚀 **Browser History & Hash Modes** - Support for both history and hash-based routing
8
+ - 🛡️ **Route Guards** - Protect routes with beforeEnter guards and middleware
9
+ - 🎯 **Dynamic Routes** - Support for dynamic params like `/blog/:slug` and wildcards
10
+ - 📦 **Lazy Loading** - Code splitting with lazy component loading
11
+ - 🔗 **Named Routes** - Reference routes by name instead of path
12
+ - 🎨 **Active Link Styling** - Automatic active state detection for links
13
+ - 📍 **Breadcrumbs** - Built-in breadcrumb generation utilities
14
+ - 🔄 **Middleware Chain** - Composable middleware for cross-cutting concerns
15
+ - 📜 **Route History** - Track navigation history with utilities
16
+ - ⚡ **Type Safe** - Full TypeScript support with comprehensive types
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install olova-router
22
+ # or
23
+ pnpm add olova-router
24
+ # or
25
+ yarn add olova-router
26
+ ```
27
+
28
+ ## Quick Start
6
29
 
7
30
  ```olova
8
31
  <script>
@@ -23,7 +46,7 @@ Standalone router package for Olova.
23
46
  name: "about"
24
47
  },
25
48
  {
26
- path: "/blog/:blogName",
49
+ path: "/blog/:slug",
27
50
  component: Blog,
28
51
  name: "blog.show"
29
52
  }
@@ -35,70 +58,396 @@ Standalone router package for Olova.
35
58
  <Link name="about">About</Link>
36
59
  <Link
37
60
  name="blog.show"
38
- params={{ blogName: "colors" }}
61
+ params={{ slug: "getting-started" }}
39
62
  activeClass="text-emerald-300"
40
63
  >
41
- Colors
64
+ Blog
42
65
  </Link>
43
66
  </nav>
44
67
 
45
68
  <BrowserRouter routes={routes} />
46
69
  ```
47
70
 
48
- ## Exports
49
-
50
- - `Router`
51
- - `BrowserRouter`
52
- - `HashRouter`
53
- - `Link`
54
- - `Outlet`
55
- - `useRouter()`
56
- - `useRoute()`
57
- - `useParams()`
58
- - `useQuery()`
59
- - `navigate()`
60
- - `matchPath()`
61
-
62
- ## Route Features
63
-
64
- - browser history mode without `#`
65
- - hash mode for static hosting
66
- - dynamic params like `/blog/:slug`
67
- - wildcard routes like `/docs/*`
68
- - named routes
69
- - redirects
70
- - route guards with `beforeEnter`
71
- - nested routes with `children`
72
- - layout routes with `Outlet`
73
- - active link styling
74
- - object targets with `params`, `query`, and `hash`
75
-
76
- ## Nested Routes
71
+ ## Core API
77
72
 
78
- ```olova
79
- <script>
80
- import { BrowserRouter, Outlet } from "olova-router";
73
+ ### Router Components
81
74
 
82
- const routes = [
83
- {
84
- path: "/dashboard",
85
- component: DashboardLayout,
86
- children: [
87
- { index: true, component: DashboardHome },
88
- { path: "settings", component: DashboardSettings }
89
- ]
90
- }
75
+ - `Router` - Base router component
76
+ - `BrowserRouter` - History mode router (recommended)
77
+ - `HashRouter` - Hash mode router (for static hosting)
78
+ - `Link` - Navigation link component
79
+ - `Outlet` - Nested route outlet
80
+
81
+ ### Router Hooks
82
+
83
+ ```typescript
84
+ import { useRouter, useRoute, useParams, useQuery } from "olova-router";
85
+
86
+ // Get full router API
87
+ const router = useRouter();
88
+
89
+ // Get current route location
90
+ const route = useRoute();
91
+
92
+ // Get route parameters
93
+ const params = useParams();
94
+
95
+ // Get query parameters
96
+ const query = useQuery();
97
+ ```
98
+
99
+ ### Navigation
100
+
101
+ ```typescript
102
+ import { navigate, link } from "olova-router";
103
+
104
+ // Programmatic navigation
105
+ navigate("/about");
106
+ navigate({ name: "blog.show", params: { slug: "hello" } });
107
+
108
+ // With options
109
+ navigate("/home", { replace: true, scroll: true });
110
+
111
+ // Link handler
112
+ const handleClick = link("/about");
113
+ ```
114
+
115
+ ## Advanced Features
116
+
117
+ ### Route Guards
118
+
119
+ Protect routes with guards and middleware:
120
+
121
+ ```typescript
122
+ import { createConditionalGuard, createParamGuard } from "olova-router/guards";
123
+
124
+ const routes = [
125
+ {
126
+ path: "/admin",
127
+ component: AdminPanel,
128
+ beforeEnter: createConditionalGuard(
129
+ () => isUserAdmin(),
130
+ "/unauthorized"
131
+ )
132
+ },
133
+ {
134
+ path: "/user/:id",
135
+ component: UserProfile,
136
+ beforeEnter: createParamGuard(
137
+ "id",
138
+ (id) => /^\d+$/.test(id),
139
+ "/not-found"
140
+ )
141
+ }
142
+ ];
143
+ ```
144
+
145
+ ### Middleware Chain
146
+
147
+ Compose multiple middleware for route transitions:
148
+
149
+ ```typescript
150
+ import {
151
+ createMiddlewareChain,
152
+ createAuthMiddleware,
153
+ createAnalyticsMiddleware,
154
+ createScrollMiddleware
155
+ } from "olova-router/middleware";
156
+
157
+ const middleware = createMiddlewareChain()
158
+ .use(createAuthMiddleware(() => isAuthenticated(), "/login"))
159
+ .use(createAnalyticsMiddleware((path) => trackPageView(path)))
160
+ .use(createScrollMiddleware(true));
161
+
162
+ // Execute middleware
163
+ const result = await middleware.execute(context);
164
+ ```
165
+
166
+ ### Lazy Loading
167
+
168
+ Code split components with lazy loading:
169
+
170
+ ```typescript
171
+ import { lazyComponent, lazyComponentWithFallback } from "olova-router/lazy";
172
+
173
+ const routes = [
174
+ {
175
+ path: "/dashboard",
176
+ component: lazyComponent(() => import("./Dashboard.olova"))
177
+ },
178
+ {
179
+ path: "/settings",
180
+ component: lazyComponentWithFallback(
181
+ () => import("./Settings.olova"),
182
+ LoadingSpinner,
183
+ ErrorFallback
184
+ )
185
+ }
186
+ ];
187
+ ```
188
+
189
+ ### Breadcrumbs
190
+
191
+ Generate breadcrumbs from route location:
192
+
193
+ ```typescript
194
+ import { generateBreadcrumbs, generateBreadcrumbsFromMatches } from "olova-router/breadcrumbs";
195
+
196
+ const route = useRoute();
197
+ const breadcrumbs = generateBreadcrumbs(route, {
198
+ home: "Home",
199
+ blog: "Blog",
200
+ settings: "Settings"
201
+ });
202
+
203
+ // Or from route matches
204
+ const breadcrumbs = generateBreadcrumbsFromMatches(route.matches);
205
+ ```
206
+
207
+ ### Route History
208
+
209
+ Track navigation history:
210
+
211
+ ```typescript
212
+ import { createRouterHistory } from "olova-router/history";
213
+
214
+ const history = createRouterHistory(50); // max 50 entries
215
+
216
+ history.push("/home");
217
+ history.push("/about");
218
+
219
+ const prev = history.back(); // { path: "/home", timestamp: ... }
220
+ const next = history.forward(); // { path: "/about", timestamp: ... }
221
+
222
+ const entries = history.getHistory();
223
+ ```
224
+
225
+ ## Route Configuration
226
+
227
+ ### Basic Route
228
+
229
+ ```typescript
230
+ {
231
+ path: "/about",
232
+ component: About,
233
+ name: "about"
234
+ }
235
+ ```
236
+
237
+ ### Dynamic Route
238
+
239
+ ```typescript
240
+ {
241
+ path: "/blog/:slug",
242
+ component: BlogPost,
243
+ name: "blog.post"
244
+ }
245
+ ```
246
+
247
+ ### Wildcard Route
248
+
249
+ ```typescript
250
+ {
251
+ path: "/docs/*",
252
+ component: DocsLayout,
253
+ name: "docs"
254
+ }
255
+ ```
256
+
257
+ ### Nested Routes
258
+
259
+ ```typescript
260
+ {
261
+ path: "/dashboard",
262
+ component: DashboardLayout,
263
+ children: [
264
+ { index: true, component: DashboardHome },
265
+ { path: "settings", component: DashboardSettings },
266
+ { path: "profile", component: DashboardProfile }
91
267
  ]
92
- </script>
268
+ }
269
+ ```
93
270
 
94
- <BrowserRouter routes={routes} />
271
+ ### Route with Guards
272
+
273
+ ```typescript
274
+ {
275
+ path: "/admin",
276
+ component: AdminPanel,
277
+ beforeEnter: async (context) => {
278
+ if (!isAdmin()) {
279
+ return "/unauthorized";
280
+ }
281
+ return true;
282
+ }
283
+ }
95
284
  ```
96
285
 
97
- Then inside `DashboardLayout.olova`:
286
+ ### Route with Props
287
+
288
+ ```typescript
289
+ {
290
+ path: "/user/:id",
291
+ component: UserProfile,
292
+ props: (context) => ({
293
+ userId: context.params.id,
294
+ isAdmin: context.query.admin === "true"
295
+ })
296
+ }
297
+ ```
298
+
299
+ ### Route Redirect
300
+
301
+ ```typescript
302
+ {
303
+ path: "/old-path",
304
+ redirect: "/new-path"
305
+ }
306
+ ```
307
+
308
+ ## Link Component
98
309
 
99
310
  ```olova
100
- <section>
101
- <h1>Dashboard</h1>
102
- <Outlet />
103
- </section>
311
+ <script>
312
+ import { Link } from "olova-router";
313
+ </script>
314
+
315
+ <!-- Simple href -->
316
+ <Link href="/about">About</Link>
317
+
318
+ <!-- Named route -->
319
+ <Link name="blog.post" params={{ slug: "hello" }}>Read Post</Link>
320
+
321
+ <!-- With query and hash -->
322
+ <Link
323
+ name="search"
324
+ query={{ q: "olova" }}
325
+ hash="results"
326
+ >
327
+ Search
328
+ </Link>
329
+
330
+ <!-- Active styling -->
331
+ <Link
332
+ href="/about"
333
+ class="nav-link"
334
+ activeClass="active"
335
+ inactiveClass="inactive"
336
+ exact
337
+ >
338
+ About
339
+ </Link>
340
+
341
+ <!-- Custom attributes -->
342
+ <Link
343
+ href="/external"
344
+ target="_blank"
345
+ rel="noopener noreferrer"
346
+ title="External Link"
347
+ >
348
+ External
349
+ </Link>
350
+ ```
351
+
352
+ ## Router Props
353
+
354
+ ```typescript
355
+ type RouterProps = {
356
+ routes: RouterRoutes;
357
+ mode?: "auto" | "history" | "hash"; // default: "auto"
358
+ base?: string; // default: "/"
359
+ scroll?: boolean; // default: true
360
+ };
104
361
  ```
362
+
363
+ ## TypeScript Support
364
+
365
+ Full type safety with comprehensive types:
366
+
367
+ ```typescript
368
+ import type {
369
+ RouteLocation,
370
+ RouteParams,
371
+ RouteQuery,
372
+ RouteTarget,
373
+ RouterApi,
374
+ RouteGuardContext
375
+ } from "olova-router";
376
+ ```
377
+
378
+ ## Vite Plugin
379
+
380
+ Auto-generate routes from file structure:
381
+
382
+ ```typescript
383
+ // vite.config.ts
384
+ import { olovaRouter } from "olova-router/vite";
385
+
386
+ export default {
387
+ plugins: [olovaRouter()]
388
+ };
389
+ ```
390
+
391
+ ## Best Practices
392
+
393
+ 1. **Use Named Routes** - More maintainable than hardcoded paths
394
+ 2. **Lazy Load Heavy Components** - Improve initial load time
395
+ 3. **Protect Sensitive Routes** - Use guards for auth/permissions
396
+ 4. **Handle 404s** - Always include a wildcard route
397
+ 5. **Scroll Management** - Enable scroll reset on navigation
398
+ 6. **Type Your Routes** - Leverage TypeScript for safety
399
+
400
+ ## Examples
401
+
402
+ ### Authentication Guard
403
+
404
+ ```typescript
405
+ import { createConditionalGuard } from "olova-router/guards";
406
+
407
+ const authGuard = createConditionalGuard(
408
+ async (context) => {
409
+ const token = localStorage.getItem("auth_token");
410
+ if (!token) return false;
411
+
412
+ const valid = await validateToken(token);
413
+ return valid;
414
+ },
415
+ "/login"
416
+ );
417
+
418
+ const routes = [
419
+ {
420
+ path: "/dashboard",
421
+ component: Dashboard,
422
+ beforeEnter: authGuard
423
+ }
424
+ ];
425
+ ```
426
+
427
+ ### Analytics Tracking
428
+
429
+ ```typescript
430
+ import { createAnalyticsMiddleware } from "olova-router/middleware";
431
+
432
+ const analyticsMiddleware = createAnalyticsMiddleware((path) => {
433
+ gtag.pageview({
434
+ page_path: path,
435
+ page_title: document.title
436
+ });
437
+ });
438
+ ```
439
+
440
+ ### Composite Guards
441
+
442
+ ```typescript
443
+ import { createCompositeGuard } from "olova-router/guards";
444
+
445
+ const protectedGuard = createCompositeGuard([
446
+ createAuthMiddleware(() => isAuthenticated()),
447
+ createRoleMiddleware(() => userRole(), ["admin", "moderator"])
448
+ ]);
449
+ ```
450
+
451
+ ## License
452
+
453
+ MIT