@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 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
@@ -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
+ }