@ktjs/router 0.5.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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.iife.js +1 -0
- package/dist/index.legacy.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 kasukabe tsumugi
|
|
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
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# @ktjs/router
|
|
2
|
+
|
|
3
|
+
<img src="https://raw.githubusercontent.com/baendlorel/kt.js/dev/.assets/ktjs-0.0.1.svg" alt="KT.js Logo" width="150"/>
|
|
4
|
+
|
|
5
|
+
> 📦 Part of [KT.js](https://raw.githubusercontent.com/baendlorel/kt.js/dev/README.md) - A simple and easy-to-use web framework that never re-renders.
|
|
6
|
+
|
|
7
|
+
Client-side router with navigation guards for KT.js.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`@ktjs/router` is a lightweight, hash-based client-side router with powerful navigation guards and async/sync auto-adaptation. It provides all the essential routing features you need without the bloat.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Hash-Based Routing**: Uses URL hash for client-side navigation (`#/path`)
|
|
16
|
+
- **Path Matching**: Static and dynamic route matching with parameter extraction
|
|
17
|
+
- Dynamic segments: `/user/:id`
|
|
18
|
+
- Wildcard matching support
|
|
19
|
+
- Optimized matching algorithm with pre-flattened routes
|
|
20
|
+
- **Navigation Guards**: Control navigation flow with powerful guard system
|
|
21
|
+
- `beforeEach`: Global guard before every navigation
|
|
22
|
+
- `beforeEnter`: Per-route guard for specific routes
|
|
23
|
+
- `afterEach`: Global hook after successful navigation
|
|
24
|
+
- Guard-level control with bitwise operations for fine-grained execution
|
|
25
|
+
- **Async/Sync Support**: Automatically adapts to environment
|
|
26
|
+
- Uses async guards when `Promise` is available
|
|
27
|
+
- Falls back to synchronous mode in older browsers
|
|
28
|
+
- No configuration needed - it just works
|
|
29
|
+
- **Named Routes**: Navigate using route names instead of paths
|
|
30
|
+
- **Query Parameters**: Built-in query string parsing and handling
|
|
31
|
+
- **Route Context**: Access route information in handlers
|
|
32
|
+
- Current route path and name
|
|
33
|
+
- Dynamic parameters (`params`)
|
|
34
|
+
- Query string parameters (`query`)
|
|
35
|
+
- **Error Handling**: Comprehensive error handling with `onError` callback
|
|
36
|
+
- **Type-Safe**: Full TypeScript support with intelligent type inference
|
|
37
|
+
- **Zero Dependencies**: Fully self-contained implementation (does **not** require `@ktjs/core` for runtime, only for TypeScript types)
|
|
38
|
+
- **ES5 Compatible**: Works in IE9+ and all modern browsers
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm add @ktjs/router @ktjs/core
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Basic Usage
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { createRouter } from '@ktjs/router';
|
|
50
|
+
import { div, h1 } from '@ktjs/core';
|
|
51
|
+
|
|
52
|
+
const router = createRouter({
|
|
53
|
+
routes: [
|
|
54
|
+
{
|
|
55
|
+
path: '/',
|
|
56
|
+
name: 'home',
|
|
57
|
+
beforeEnter: (to) => {
|
|
58
|
+
// Render your home page
|
|
59
|
+
const app = document.getElementById('app')!;
|
|
60
|
+
app.innerHTML = '';
|
|
61
|
+
app.appendChild(div({}, [h1({}, 'Home Page')]));
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
path: '/about',
|
|
66
|
+
name: 'about',
|
|
67
|
+
beforeEnter: (to) => {
|
|
68
|
+
// Render your about page
|
|
69
|
+
const app = document.getElementById('app')!;
|
|
70
|
+
app.innerHTML = '';
|
|
71
|
+
app.appendChild(div({}, [h1({}, 'About Page')]));
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
path: '/user/:id',
|
|
76
|
+
name: 'user',
|
|
77
|
+
beforeEnter: (to) => {
|
|
78
|
+
// Render user profile with params
|
|
79
|
+
const app = document.getElementById('app')!;
|
|
80
|
+
app.innerHTML = '';
|
|
81
|
+
app.appendChild(
|
|
82
|
+
div({}, [
|
|
83
|
+
h1({}, `User Profile`),
|
|
84
|
+
div({}, `User ID: ${to.params.id}`),
|
|
85
|
+
div({}, `Query: ${JSON.stringify(to.query)}`),
|
|
86
|
+
])
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Navigate programmatically
|
|
94
|
+
router.push('/user/123?tab=profile');
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Advanced Usage
|
|
98
|
+
|
|
99
|
+
### Navigation Guards
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { createRouter, GuardLevel } from '@ktjs/router';
|
|
103
|
+
|
|
104
|
+
const router = createRouter({
|
|
105
|
+
routes: [
|
|
106
|
+
{
|
|
107
|
+
path: '/admin',
|
|
108
|
+
name: 'admin',
|
|
109
|
+
beforeEnter: (to) => {
|
|
110
|
+
// Route-specific guard and rendering
|
|
111
|
+
if (!isAuthenticated()) {
|
|
112
|
+
console.log('Access denied');
|
|
113
|
+
return false; // Block navigation
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Render admin panel
|
|
117
|
+
const app = document.getElementById('app')!;
|
|
118
|
+
app.innerHTML = '';
|
|
119
|
+
app.appendChild(div({}, 'Admin Panel'));
|
|
120
|
+
return true;
|
|
121
|
+
},
|
|
122
|
+
after: (to) => {
|
|
123
|
+
// Called after this route is successfully entered
|
|
124
|
+
console.log('Admin page rendered');
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
beforeEach: async (to, from) => {
|
|
129
|
+
// Global guard - can be async
|
|
130
|
+
console.log(`Navigating from ${from?.path} to ${to.path}`);
|
|
131
|
+
|
|
132
|
+
// You can return false to block navigation
|
|
133
|
+
if (to.path.includes('forbidden')) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Or return true to allow
|
|
138
|
+
return true;
|
|
139
|
+
},
|
|
140
|
+
afterEach: (to, from) => {
|
|
141
|
+
// Called after successful navigation
|
|
142
|
+
document.title = to.name || to.path;
|
|
143
|
+
|
|
144
|
+
// Track page views
|
|
145
|
+
analytics.track('pageview', { path: to.path });
|
|
146
|
+
},
|
|
147
|
+
onError: (error) => {
|
|
148
|
+
console.error('Navigation error:', error);
|
|
149
|
+
},
|
|
150
|
+
onNotFound: (path) => {
|
|
151
|
+
console.log('404:', path);
|
|
152
|
+
// You can render a 404 page here
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Named Route Navigation
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Navigate by route name with parameters
|
|
161
|
+
router.push({
|
|
162
|
+
name: 'user',
|
|
163
|
+
params: { id: '456' },
|
|
164
|
+
query: { tab: 'settings' },
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Equivalent to: router.push('/user/456?tab=settings');
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Accessing Current Route
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const current = router.current;
|
|
174
|
+
|
|
175
|
+
if (current) {
|
|
176
|
+
console.log('Path:', current.path); // e.g., '/user/123'
|
|
177
|
+
console.log('Name:', current.name); // e.g., 'user'
|
|
178
|
+
console.log('Params:', current.params); // e.g., { id: '123' }
|
|
179
|
+
console.log('Query:', current.query); // e.g., { tab: 'profile' }
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## API Reference
|
|
184
|
+
|
|
185
|
+
### `createRouter(config)`
|
|
186
|
+
|
|
187
|
+
Creates and returns a router instance.
|
|
188
|
+
|
|
189
|
+
**Config Options:**
|
|
190
|
+
|
|
191
|
+
- `routes` (Array): Array of route configurations
|
|
192
|
+
- `path` (string): Route path with optional dynamic segments (`:param`)
|
|
193
|
+
- `name` (string, optional): Route name for named navigation
|
|
194
|
+
- `meta` (object, optional): Metadata attached to the route
|
|
195
|
+
- `beforeEnter` (function, optional): Route-specific guard, receives `(to: RouteContext) => boolean | void | Promise<boolean | void>`
|
|
196
|
+
- `after` (function, optional): Route-specific hook after navigation
|
|
197
|
+
- `children` (array, optional): Nested child routes
|
|
198
|
+
- `beforeEach` (function, optional): Global guard before every navigation, receives `(to: RouteContext, from: RouteContext | null)`
|
|
199
|
+
- `afterEach` (function, optional): Global hook after successful navigation, receives `(to: RouteContext, from: RouteContext | null)`
|
|
200
|
+
- `onNotFound` (function, optional): Handler for 404 errors, receives `(path: string)`
|
|
201
|
+
- `onError` (function, optional): Error handler for navigation failures, receives `(error: Error, route?: RouteConfig)`
|
|
202
|
+
- `asyncGuards` (boolean, optional): Enable async guards (default: `true`)
|
|
203
|
+
|
|
204
|
+
**Router Instance Properties:**
|
|
205
|
+
|
|
206
|
+
- `current` (property): Current active route context (or `null`)
|
|
207
|
+
- `history` (property): Array of navigation history
|
|
208
|
+
|
|
209
|
+
**Router Instance Methods:**
|
|
210
|
+
|
|
211
|
+
- `push(location)`: Navigate to a new location (string path or route object)
|
|
212
|
+
- `silentPush(location)`: Navigate without global guards (`beforeEach` guards)
|
|
213
|
+
- `replace(location)`: Replace current history entry
|
|
214
|
+
- `back()`: Navigate back in history
|
|
215
|
+
- `forward()`: Navigate forward in history
|
|
216
|
+
|
|
217
|
+
### Route Context
|
|
218
|
+
|
|
219
|
+
Guards and hooks receive a `RouteContext` object with:
|
|
220
|
+
|
|
221
|
+
- `params`: Object containing dynamic route parameters
|
|
222
|
+
- `query`: Object containing query string parameters
|
|
223
|
+
- `path`: Current route path
|
|
224
|
+
- `name`: Current route name (if defined)
|
|
225
|
+
|
|
226
|
+
## Performance Optimizations
|
|
227
|
+
|
|
228
|
+
The router includes several performance optimizations:
|
|
229
|
+
|
|
230
|
+
- **Pre-flattened Routes**: Nested routes are flattened during initialization
|
|
231
|
+
- **Efficient Matching**: Optimized regex-based path matching
|
|
232
|
+
- **Cached Methods**: Native DOM methods are cached
|
|
233
|
+
- **Minimal Re-renders**: Only updates DOM when route actually changes
|
|
234
|
+
- **Guard Level Control**: Fine-grained control over guard execution using bitwise operations
|
|
235
|
+
|
|
236
|
+
## Browser Compatibility
|
|
237
|
+
|
|
238
|
+
Works in all modern browsers and IE9+ with ES5 transpilation. In environments without `Promise` support, navigation guards run synchronously.
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard level that determines which guards to apply during navigation
|
|
3
|
+
* - there are global and route-level guards
|
|
4
|
+
*/
|
|
5
|
+
declare const enum GuardLevel {
|
|
6
|
+
/**
|
|
7
|
+
* Ignores all guards
|
|
8
|
+
*/
|
|
9
|
+
None = 0,
|
|
10
|
+
/**
|
|
11
|
+
* Only applies global guards
|
|
12
|
+
*/
|
|
13
|
+
Global = 1,
|
|
14
|
+
/**
|
|
15
|
+
* Only applies guards of routes(`beforeEnter` guards)
|
|
16
|
+
*/
|
|
17
|
+
Route = 2,
|
|
18
|
+
/**
|
|
19
|
+
* Applies all guards
|
|
20
|
+
*/
|
|
21
|
+
Default = 15
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Route configuration for defining application routes
|
|
26
|
+
*/
|
|
27
|
+
interface RawRouteConfig {
|
|
28
|
+
/** Route path pattern (e.g., '/user/:id') */
|
|
29
|
+
path: string;
|
|
30
|
+
/** Optional unique route name for named navigation */
|
|
31
|
+
name?: string;
|
|
32
|
+
/** Optional metadata attached to the route */
|
|
33
|
+
meta?: Record<string, any>;
|
|
34
|
+
/** Route-level guard executed before entering this route */
|
|
35
|
+
beforeEnter?: (context: RouteContext) => boolean | void | Promise<boolean | void>;
|
|
36
|
+
/** Route-level hook executed after navigation */
|
|
37
|
+
after?: (context: RouteContext) => void | Promise<void>;
|
|
38
|
+
/** Nested child routes */
|
|
39
|
+
children?: RawRouteConfig[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type RouteConfig = Required<Omit<RawRouteConfig, 'children'>> & { children: RouteConfig[] };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Current route context information
|
|
46
|
+
*/
|
|
47
|
+
interface RouteContext {
|
|
48
|
+
/** Full matched path */
|
|
49
|
+
path: string;
|
|
50
|
+
|
|
51
|
+
/** Route name if defined */
|
|
52
|
+
name: string;
|
|
53
|
+
|
|
54
|
+
/** Dynamic parameters extracted from path */
|
|
55
|
+
params: Record<string, string>;
|
|
56
|
+
|
|
57
|
+
/** Query string parameters */
|
|
58
|
+
query: Record<string, string>;
|
|
59
|
+
|
|
60
|
+
/** Route metadata */
|
|
61
|
+
meta: Record<string, any>;
|
|
62
|
+
|
|
63
|
+
/** Array of matched route configs (for nested routes) */
|
|
64
|
+
matched: RouteConfig[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Base navigation options containing target information
|
|
69
|
+
*/
|
|
70
|
+
interface NavBaseOptions {
|
|
71
|
+
/** Target path (alternative to name) */
|
|
72
|
+
path?: string;
|
|
73
|
+
|
|
74
|
+
/** Target route name (alternative to path) */
|
|
75
|
+
name?: string;
|
|
76
|
+
|
|
77
|
+
/** Parameters to substitute in path */
|
|
78
|
+
params?: Record<string, string>;
|
|
79
|
+
|
|
80
|
+
/** Query parameters to append */
|
|
81
|
+
query?: Record<string, string>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Complete navigation options including control flags
|
|
86
|
+
*/
|
|
87
|
+
interface NavOptions extends NavBaseOptions {
|
|
88
|
+
guardLevel?: GuardLevel;
|
|
89
|
+
replace?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Router configuration
|
|
94
|
+
*/
|
|
95
|
+
interface RouterConfig {
|
|
96
|
+
/** Array of route definitions */
|
|
97
|
+
routes: RawRouteConfig[];
|
|
98
|
+
|
|
99
|
+
/** Global guard executed before each navigation (except silentPush) */
|
|
100
|
+
beforeEach?: (to: RouteContext, from: RouteContext | null) => boolean | void | Promise<boolean | void>;
|
|
101
|
+
|
|
102
|
+
/** Global hook executed after each navigation */
|
|
103
|
+
afterEach?: (to: RouteContext, from: RouteContext | null) => void | Promise<void>;
|
|
104
|
+
|
|
105
|
+
/** Handler for 404 errors - return false to prevent default behavior */
|
|
106
|
+
onNotFound?: (path: string) => void | false;
|
|
107
|
+
|
|
108
|
+
/** Handler for routing errors */
|
|
109
|
+
onError?: (error: Error, route?: RouteConfig) => void;
|
|
110
|
+
|
|
111
|
+
// # options
|
|
112
|
+
/**
|
|
113
|
+
* Default is `true`
|
|
114
|
+
*/
|
|
115
|
+
asyncGuards?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Router instance
|
|
120
|
+
*/
|
|
121
|
+
interface Router {
|
|
122
|
+
/** Current active route context */
|
|
123
|
+
current: RouteContext | null;
|
|
124
|
+
|
|
125
|
+
/** Navigation history */
|
|
126
|
+
history: RouteContext[];
|
|
127
|
+
|
|
128
|
+
/** Navigate with guards */
|
|
129
|
+
push(location: string | NavOptions): boolean | Promise<boolean>;
|
|
130
|
+
|
|
131
|
+
/** Navigate without global guards(`beforeEach` guards) */
|
|
132
|
+
silentPush(location: string | NavOptions): boolean | Promise<boolean>;
|
|
133
|
+
|
|
134
|
+
/** Replace current history entry */
|
|
135
|
+
replace(location: string | NavOptions): boolean | Promise<boolean>;
|
|
136
|
+
|
|
137
|
+
/** Navigate back in history */
|
|
138
|
+
back(): void;
|
|
139
|
+
|
|
140
|
+
/** Navigate forward in history */
|
|
141
|
+
forward(): void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
interface RouteMatch {
|
|
145
|
+
route: RouteConfig;
|
|
146
|
+
params: Record<string, string>;
|
|
147
|
+
result: RouteConfig[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create a new router instance
|
|
152
|
+
*/
|
|
153
|
+
declare const createRouter: (config: RouterConfig) => Router;
|
|
154
|
+
|
|
155
|
+
export { GuardLevel, createRouter };
|
|
156
|
+
export type { NavOptions, RawRouteConfig, RouteConfig, RouteContext, RouteMatch, Router, RouterConfig };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var __ktjs_router__=function(t){"use strict";const r=()=>!0,e=t=>{throw new Error(`@ktjs/router: ${t}`)},n=(...t)=>"/"+t.map(t=>t.split("/")).flat().filter(Boolean).join("/"),o=t=>{const r={};if(!t||"?"===t)return r;const e=t.replace(/^\?/,"").split("&");for(const t of e){const[e,n]=t.split("=");e&&(r[decodeURIComponent(e)]=n?decodeURIComponent(n):"")}return r},u=(t,r)=>{const e={},n=t.split("/"),o=r.split("/");if(n.length!==o.length)return null;for(let t=0;t<n.length;t++){const r=n[t],u=o[t];if(r.startsWith(":")){e[r.slice(1)]=u}else if(r!==u)return null}return e};return t.createRouter=t=>{const c=t.beforeEach??r,a=t.afterEach??r,s=t.onNotFound??r,i=t.onError??r,l=t.asyncGuards??!0,f=[],d=(t,e)=>t.map(t=>{const o=n(e,t.path),u={path:o,name:t.name??"",meta:t.meta??{},beforeEnter:t.beforeEnter??r,after:t.after??r,children:t.children?d(t.children,o):[]};return f.push(u),u});d(t.routes,"/");const{findByName:p,match:h}=(t=>{const r={};for(let n=0;n<t.length;n++){const o=t[n];void 0!==o.name&&(o.name in r&&e(`Duplicate route name detected: '${o.name}'`),r[o.name]=o)}const o=r=>{const e=[r],n=r.path;for(let o=0;o<t.length;o++){const u=t[o];u!==r&&n.startsWith(u.path)&&n!==u.path&&e.push(u)}return e.reverse()};return{findByName:t=>r[t]??null,match:r=>{const e=n(r);for(const r of t)if(r.path===e)return{route:r,params:{},result:o(r)};for(const r of t)if(r.path.includes(":")){const t=u(r.path,e);if(t)return{route:r,params:t,result:o(r)}}return null}}})(f);let w=null;const m=[],y=t=>{let r,o;t.name?(o=p(t.name),o||e(`Route not found: ${t.name}`),r=o.path):t.path?(r=n(t.path),o=h(r)?.route):e("Either path or name must be provided"),t.params&&(r=((t,r)=>{let e=t;for(const t in r)e=e.replace(`:${t}`,r[t]);return e})(r,t.params));const u=h(r);if(!u)return s(r),null;const c=r+(t.query?(t=>{const r=Object.keys(t);return 0===r.length?"":`?${r.map(r=>`${encodeURIComponent(r)}=${encodeURIComponent(t[r])}`).join("&")}`})(t.query):""),a={path:r,name:u.route.name,params:{...u.params,...t.params||{}},query:t.query||{},meta:u.route.meta||{},matched:u.result};return{guardLevel:t.guardLevel??15,replace:t.replace??!1,to:a,fullPath:c}},v=l?t=>{try{const r=y(t);if(!r)return!1;const{guardLevel:e,replace:n,to:o,fullPath:u}=r;if(!((t,r,e)=>{try{if(0===e)return!0;if(1&e&&!1===c(t,r))return!1;if(2&e){if(!1===t.matched[t.matched.length-1].beforeEnter(t))return!1}return!0}catch(t){return i(t),!1}})(o,w,e))return!1;const a=u;return n?window.history.replaceState({path:o.path},"",a):window.history.pushState({path:o.path},"",a),w=o,m.push(o),g(o,m[m.length-2]||null),!0}catch(t){return i(t),!1}}:async t=>{try{const r=y(t);if(!r)return!1;const{guardLevel:e,replace:n,to:o,fullPath:u}=r,a=await(async(t,r,e)=>{try{if(0===e)return!0;if(1&e&&!1===await c(t,r))return!1;if(2&e){return!1!==t.matched[t.matched.length-1].beforeEnter(t)}return!0}catch(t){return i(t),!1}})(o,w,e);if(!a)return!1;const s=u;return n?window.history.replaceState({path:o.path},"",s):window.history.pushState({path:o.path},"",s),$(o,m[m.length-2]||null),!0}catch(t){return i(t),!1}},g=(t,r)=>{t.matched[t.matched.length-1].after(t),a(t,r)},$=async(t,r)=>{const e=t.matched[t.matched.length-1];await e.after(t),await a(t,r)},L=t=>{if("string"==typeof t){const[r,e]=t.split("?");return{path:r,query:e?o(e):void 0}}return t};return window.addEventListener("popstate",t=>{t.state?.path&&v({path:t.state.path,guardLevel:1,replace:!0})}),{get current(){return w},get history(){return m.concat()},push(t){const r=L(t);return v(r)},silentPush(t){const r=L(t);return v({...r,guardLevel:2})},replace(t){const r=L(t);return v({...r,replace:!0})},back(){window.history.back()},forward(){window.history.forward()}}},t}({});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var __ktjs_router__=function(r){"use strict";var n=function(){return n=Object.assign||function(r){for(var n,t=1,e=arguments.length;t<e;t++)for(var u in n=arguments[t])Object.prototype.hasOwnProperty.call(n,u)&&(r[u]=n[u]);return r},n.apply(this,arguments)};function t(r,n,t,e){return new(t||(t=Promise))(function(u,o){function i(r){try{c(e.next(r))}catch(r){o(r)}}function a(r){try{c(e.throw(r))}catch(r){o(r)}}function c(r){var n;r.done?u(r.value):(n=r.value,n instanceof t?n:new t(function(r){r(n)})).then(i,a)}c((e=e.apply(r,n||[])).next())})}function e(r,n){var t,e,u,o={label:0,sent:function(){if(1&u[0])throw u[1];return u[1]},trys:[],ops:[]},i=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return i.next=a(0),i.throw=a(1),i.return=a(2),"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(a){return function(c){return function(a){if(t)throw new TypeError("Generator is already executing.");for(;i&&(i=0,a[0]&&(o=0)),o;)try{if(t=1,e&&(u=2&a[0]?e.return:a[0]?e.throw||((u=e.return)&&u.call(e),0):e.next)&&!(u=u.call(e,a[1])).done)return u;switch(e=0,u&&(a=[2&a[0],u.value]),a[0]){case 0:case 1:u=a;break;case 4:return o.label++,{value:a[1],done:!1};case 5:o.label++,e=a[1],a=[0];continue;case 7:a=o.ops.pop(),o.trys.pop();continue;default:if(!(u=o.trys,(u=u.length>0&&u[u.length-1])||6!==a[0]&&2!==a[0])){o=0;continue}if(3===a[0]&&(!u||a[1]>u[0]&&a[1]<u[3])){o.label=a[1];break}if(6===a[0]&&o.label<u[1]){o.label=u[1],u=a;break}if(u&&o.label<u[2]){o.label=u[2],o.ops.push(a);break}u[2]&&o.ops.pop(),o.trys.pop();continue}a=n.call(r,o)}catch(r){a=[6,r],e=0}finally{t=u=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,c])}}}"function"==typeof SuppressedError&&SuppressedError;var u=function(){return!0},o=function(r){throw new Error("@ktjs/router: ".concat(r))},i=function(){for(var r=[],n=0;n<arguments.length;n++)r[n]=arguments[n];return"/"+r.map(function(r){return r.split("/")}).flat().filter(Boolean).join("/")},a=function(r){var n={};if(!r||"?"===r)return n;for(var t=0,e=r.replace(/^\?/,"").split("&");t<e.length;t++){var u=e[t].split("="),o=u[0],i=u[1];o&&(n[decodeURIComponent(o)]=i?decodeURIComponent(i):"")}return n},c=function(r,n){var t={},e=r.split("/"),u=n.split("/");if(e.length!==u.length)return null;for(var o=0;o<e.length;o++){var i=e[o],a=u[o];if(i.startsWith(":"))t[i.slice(1)]=a;else if(i!==a)return null}return t};return r.createRouter=function(r){var f,v,l,d,s,h=null!==(f=r.beforeEach)&&void 0!==f?f:u,p=null!==(v=r.afterEach)&&void 0!==v?v:u,w=null!==(l=r.onNotFound)&&void 0!==l?l:u,m=null!==(d=r.onError)&&void 0!==d?d:u,y=null===(s=r.asyncGuards)||void 0===s||s,b=[],g=function(r,n){return r.map(function(r){var t,e,o,a,c=i(n,r.path),f={path:c,name:null!==(t=r.name)&&void 0!==t?t:"",meta:null!==(e=r.meta)&&void 0!==e?e:{},beforeEnter:null!==(o=r.beforeEnter)&&void 0!==o?o:u,after:null!==(a=r.after)&&void 0!==a?a:u,children:r.children?g(r.children,c):[]};return b.push(f),f})};g(r.routes,"/");var j=function(r){for(var n={},t=0;t<r.length;t++){var e=r[t];void 0!==e.name&&(e.name in n&&o("Duplicate route name detected: '".concat(e.name,"'")),n[e.name]=e)}var u=function(n){for(var t=[n],e=n.path,u=0;u<r.length;u++){var o=r[u];o!==n&&e.startsWith(o.path)&&e!==o.path&&t.push(o)}return t.reverse()};return{findByName:function(r){var t;return null!==(t=n[r])&&void 0!==t?t:null},match:function(n){for(var t=i(n),e=0,o=r;e<o.length;e++)if((v=o[e]).path===t)return{route:v,params:{},result:u(v)};for(var a=0,f=r;a<f.length;a++){var v;if((v=f[a]).path.includes(":")){var l=c(v.path,t);if(l)return{route:v,params:l,result:u(v)}}}return null}}}(b),k=j.findByName,E=j.match,I=null,O=[],R=function(r,n,u){return t(void 0,void 0,void 0,function(){var t,o;return e(this,function(e){switch(e.label){case 0:return e.trys.push([0,3,,4]),0===u?[2,!0]:1&u?[4,h(r,n)]:[3,2];case 1:if(!1===e.sent())return[2,!1];e.label=2;case 2:return 2&u?(t=r.matched[r.matched.length-1],[2,!1!==t.beforeEnter(r)]):[2,!0];case 3:return o=e.sent(),m(o),[2,!1];case 4:return[2]}})})},_=function(r){var t,e,u,a,c;r.name?((c=k(r.name))||o("Route not found: ".concat(r.name)),a=c.path):r.path?(a=i(r.path),c=null===(t=E(a))||void 0===t?void 0:t.route):o("Either path or name must be provided"),r.params&&(a=function(r,n){var t=r;for(var e in n)t=t.replace(":".concat(e),n[e]);return t}(a,r.params));var f=E(a);if(!f)return w(a),null;var v=a+(r.query?function(r){var n=Object.keys(r);if(0===n.length)return"";var t=n.map(function(n){return"".concat(encodeURIComponent(n),"=").concat(encodeURIComponent(r[n]))}).join("&");return"?".concat(t)}(r.query):""),l={path:a,name:f.route.name,params:n(n({},f.params),r.params||{}),query:r.query||{},meta:f.route.meta||{},matched:f.result};return{guardLevel:null!==(e=r.guardLevel)&&void 0!==e?e:15,replace:null!==(u=r.replace)&&void 0!==u&&u,to:l,fullPath:v}},C=y?function(r){try{var n=_(r);if(!n)return!1;var t=n.guardLevel,e=n.replace,u=n.to,o=n.fullPath;if(!function(r,n,t){try{return 0===t||!(1&t&&!1===h(r,n))&&!(2&t&&!1===r.matched[r.matched.length-1].beforeEnter(r))}catch(r){return m(r),!1}}(u,I,t))return!1;var i=o;return e?window.history.replaceState({path:u.path},"",i):window.history.pushState({path:u.path},"",i),I=u,O.push(u),S(u,O[O.length-2]||null),!0}catch(r){return m(r),!1}}:function(r){return t(void 0,void 0,void 0,function(){var n,t,u,o,i,a,c;return e(this,function(e){switch(e.label){case 0:return e.trys.push([0,2,,3]),(n=_(r))?(t=n.guardLevel,u=n.replace,o=n.to,i=n.fullPath,[4,R(o,I,t)]):[2,!1];case 1:return e.sent()?(a=i,u?window.history.replaceState({path:o.path},"",a):window.history.pushState({path:o.path},"",a),U(o,O[O.length-2]||null),[2,!0]):[2,!1];case 2:return c=e.sent(),m(c),[2,!1];case 3:return[2]}})})},S=function(r,n){r.matched[r.matched.length-1].after(r),p(r,n)},U=function(r,n){return t(void 0,void 0,void 0,function(){return e(this,function(t){switch(t.label){case 0:return[4,r.matched[r.matched.length-1].after(r)];case 1:return t.sent(),[4,p(r,n)];case 2:return t.sent(),[2]}})})},L=function(r){if("string"==typeof r){var n=r.split("?"),t=n[0],e=n[1];return{path:t,query:e?a(e):void 0}}return r};return window.addEventListener("popstate",function(r){var n;(null===(n=r.state)||void 0===n?void 0:n.path)&&C({path:r.state.path,guardLevel:1,replace:!0})}),{get current(){return I},get history(){return O.concat()},push:function(r){var n=L(r);return C(n)},silentPush:function(r){var t=L(r);return C(n(n({},t),{guardLevel:2}))},replace:function(r){var t=L(r);return C(n(n({},t),{replace:!0}))},back:function(){window.history.back()},forward:function(){window.history.forward()}}},r}({});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=()=>!0,r=t=>{throw new Error(`@ktjs/router: ${t}`)},e=(...t)=>"/"+t.map(t=>t.split("/")).flat().filter(Boolean).join("/"),n=t=>{const r={};if(!t||"?"===t)return r;const e=t.replace(/^\?/,"").split("&");for(const t of e){const[e,n]=t.split("=");e&&(r[decodeURIComponent(e)]=n?decodeURIComponent(n):"")}return r},o=(t,r)=>{const e={},n=t.split("/"),o=r.split("/");if(n.length!==o.length)return null;for(let t=0;t<n.length;t++){const r=n[t],u=o[t];if(r.startsWith(":")){e[r.slice(1)]=u}else if(r!==u)return null}return e},u=u=>{const c=u.beforeEach??t,a=u.afterEach??t,s=u.onNotFound??t,l=u.onError??t,i=u.asyncGuards??!0,f=[],d=(r,n)=>r.map(r=>{const o=e(n,r.path),u={path:o,name:r.name??"",meta:r.meta??{},beforeEnter:r.beforeEnter??t,after:r.after??t,children:r.children?d(r.children,o):[]};return f.push(u),u});d(u.routes,"/");const{findByName:p,match:h}=(t=>{const n={};for(let e=0;e<t.length;e++){const o=t[e];void 0!==o.name&&(o.name in n&&r(`Duplicate route name detected: '${o.name}'`),n[o.name]=o)}const u=r=>{const e=[r],n=r.path;for(let o=0;o<t.length;o++){const u=t[o];u!==r&&n.startsWith(u.path)&&n!==u.path&&e.push(u)}return e.reverse()};return{findByName:t=>n[t]??null,match:r=>{const n=e(r);for(const r of t)if(r.path===n)return{route:r,params:{},result:u(r)};for(const r of t)if(r.path.includes(":")){const t=o(r.path,n);if(t)return{route:r,params:t,result:u(r)}}return null}}})(f);let w=null;const m=[],y=t=>{let n,o;t.name?(o=p(t.name),o||r(`Route not found: ${t.name}`),n=o.path):t.path?(n=e(t.path),o=h(n)?.route):r("Either path or name must be provided"),t.params&&(n=((t,r)=>{let e=t;for(const t in r)e=e.replace(`:${t}`,r[t]);return e})(n,t.params));const u=h(n);if(!u)return s(n),null;const c=n+(t.query?(t=>{const r=Object.keys(t);return 0===r.length?"":`?${r.map(r=>`${encodeURIComponent(r)}=${encodeURIComponent(t[r])}`).join("&")}`})(t.query):""),a={path:n,name:u.route.name,params:{...u.params,...t.params||{}},query:t.query||{},meta:u.route.meta||{},matched:u.result};return{guardLevel:t.guardLevel??15,replace:t.replace??!1,to:a,fullPath:c}},g=i?t=>{try{const r=y(t);if(!r)return!1;const{guardLevel:e,replace:n,to:o,fullPath:u}=r;if(!((t,r,e)=>{try{if(0===e)return!0;if(1&e&&!1===c(t,r))return!1;if(2&e){if(!1===t.matched[t.matched.length-1].beforeEnter(t))return!1}return!0}catch(t){return l(t),!1}})(o,w,e))return!1;const a=u;return n?window.history.replaceState({path:o.path},"",a):window.history.pushState({path:o.path},"",a),w=o,m.push(o),v(o,m[m.length-2]||null),!0}catch(t){return l(t),!1}}:async t=>{try{const r=y(t);if(!r)return!1;const{guardLevel:e,replace:n,to:o,fullPath:u}=r,a=await(async(t,r,e)=>{try{if(0===e)return!0;if(1&e&&!1===await c(t,r))return!1;if(2&e){return!1!==t.matched[t.matched.length-1].beforeEnter(t)}return!0}catch(t){return l(t),!1}})(o,w,e);if(!a)return!1;const s=u;return n?window.history.replaceState({path:o.path},"",s):window.history.pushState({path:o.path},"",s),$(o,m[m.length-2]||null),!0}catch(t){return l(t),!1}},v=(t,r)=>{t.matched[t.matched.length-1].after(t),a(t,r)},$=async(t,r)=>{const e=t.matched[t.matched.length-1];await e.after(t),await a(t,r)},L=t=>{if("string"==typeof t){const[r,e]=t.split("?");return{path:r,query:e?n(e):void 0}}return t};return window.addEventListener("popstate",t=>{t.state?.path&&g({path:t.state.path,guardLevel:1,replace:!0})}),{get current(){return w},get history(){return m.concat()},push(t){const r=L(t);return g(r)},silentPush(t){const r=L(t);return g({...r,guardLevel:2})},replace(t){const r=L(t);return g({...r,replace:!0})},back(){window.history.back()},forward(){window.history.forward()}}};export{u as createRouter};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktjs/router",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Router for kt.js - client-side routing with navigation guards",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"default": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"router",
|
|
18
|
+
"routing",
|
|
19
|
+
"navigation",
|
|
20
|
+
"spa",
|
|
21
|
+
"web"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "Kasukabe Tsumugi",
|
|
26
|
+
"email": "futami16237@gmail.com"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/baendlorel/kt.js",
|
|
31
|
+
"directory": "packages/router"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@ktjs/core": "0.5.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "rollup -c rollup.config.mjs",
|
|
38
|
+
"dev": "rollup -c rollup.config.mjs -w"
|
|
39
|
+
}
|
|
40
|
+
}
|