@ktjs/router 0.14.7 → 0.14.9

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
@@ -8,28 +8,25 @@
8
8
 
9
9
  Client-side router with navigation guards for KT.js.
10
10
 
11
- **Current Version:** 0.13.0
11
+ **Current Version:** 0.14.9
12
12
 
13
13
  ## Overview
14
14
 
15
- `@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.
15
+ `@ktjs/router` is a lightweight, hash-based client-side router with powerful async navigation guards. It provides all the essential routing features you need without the bloat. Simplified in v0.14.7+ to focus exclusively on hash-based routing for better performance and maintainability.
16
16
 
17
17
  ## Features
18
18
 
19
- - **Hash-Based Routing**: Uses URL hash for client-side navigation (`#/path`)
19
+ - **Hash-Based Routing**: Uses URL hash for client-side navigation (`#/path`) - **hash-mode only** since v0.14.7
20
20
  - **Path Matching**: Static and dynamic route matching with parameter extraction
21
21
  - Dynamic segments: `/user/:id`
22
22
  - Wildcard matching support
23
23
  - Optimized matching algorithm with pre-flattened routes
24
- - **Navigation Guards**: Control navigation flow with powerful guard system
24
+ - **Async Navigation Guards**: Control navigation flow with Promise-based guard system
25
25
  - `beforeEach`: Global guard before every navigation
26
26
  - `beforeEnter`: Per-route guard for specific routes
27
27
  - `afterEach`: Global hook after successful navigation
28
28
  - Guard-level control with bitwise operations for fine-grained execution
29
- - **Async/Sync Support**: Automatically adapts to environment
30
- - Uses async guards when `Promise` is available
31
- - Falls back to synchronous mode in older browsers
32
- - No configuration needed - it just works
29
+ - All guards are async - return `Promise` or immediate values
33
30
  - **Named Routes**: Navigate using route names instead of paths
34
31
  - **Query Parameters**: Built-in query string parsing and handling
35
32
  - **Route Context**: Access route information in handlers
@@ -87,7 +84,7 @@ const router = createRouter({
87
84
  h1({}, `User Profile`),
88
85
  div({}, `User ID: ${to.params.id}`),
89
86
  div({}, `Query: ${JSON.stringify(to.query)}`),
90
- ])
87
+ ]),
91
88
  );
92
89
  },
93
90
  },
@@ -196,27 +193,61 @@ Creates and returns a router instance.
196
193
  - `path` (string): Route path with optional dynamic segments (`:param`)
197
194
  - `name` (string, optional): Route name for named navigation
198
195
  - `meta` (object, optional): Metadata attached to the route
199
- - `beforeEnter` (function, optional): Route-specific guard, receives `(to: RouteContext) => boolean | void | Promise<boolean | void>`
196
+ - `component` (function): Function that returns HTMLElement or Promise<HTMLElement>
197
+ - `beforeEnter` (function, optional): Route-specific guard, receives `(to: RouteContext) => boolean | string | NavOptions | void | Promise<...>`
200
198
  - `after` (function, optional): Route-specific hook after navigation
201
199
  - `children` (array, optional): Nested child routes
202
- - `beforeEach` (function, optional): Global guard before every navigation, receives `(to: RouteContext, from: RouteContext | null)`
203
- - `afterEach` (function, optional): Global hook after successful navigation, receives `(to: RouteContext, from: RouteContext | null)`
204
- - `onNotFound` (function, optional): Handler for 404 errors, receives `(path: string)`
205
- - `onError` (function, optional): Error handler for navigation failures, receives `(error: Error, route?: RouteConfig)`
206
- - `asyncGuards` (boolean, optional): Enable async guards (default: `true`)
200
+ - `beforeEach` (function, optional): Global guard before every navigation, receives `(to: RouteContext, from: RouteContext | null) => boolean | string | NavOptions | void | Promise<...>`
201
+ - `afterEach` (function, optional): Global hook after successful navigation, receives `(to: RouteContext, from: RouteContext | null) => void | Promise<void>`
202
+ - `onNotFound` (function, optional): Handler for 404 errors, receives `(path: string) => void`
203
+ - `onError` (function, optional): Error handler for navigation failures, receives `(error: Error) => void`
204
+ - `prefix` (string, optional): URL prefix for all routes (default: `''`)
207
205
 
208
206
  **Router Instance Properties:**
209
207
 
210
208
  - `current` (property): Current active route context (or `null`)
211
209
  - `history` (property): Array of navigation history
210
+ - `routes` (property): Normalized route configurations
212
211
 
213
212
  **Router Instance Methods:**
214
213
 
215
- - `push(location)`: Navigate to a new location (string path or route object)
216
- - `silentPush(location)`: Navigate without global guards (`beforeEach` guards)
214
+ - `push(location)`: Navigate to a new location (string path or route object with `name`/`params`)
217
215
  - `replace(location)`: Replace current history entry
218
216
  - `back()`: Navigate back in history
219
- - `forward()`: Navigate forward in history
217
+ - `listen()`: Start listening to hash changes (auto-called on router creation since v0.14.7)
218
+ - `init(routes)`: Initialize router with route configurations
219
+
220
+ ### Navigation Guards (v0.14.7+)
221
+
222
+ All guards are **async** and can return:
223
+
224
+ - `false`: Cancel navigation
225
+ - `string`: Redirect to the given path
226
+ - `NavOptions` object: Redirect with options `{ name, params, query }`
227
+ - `void` or `undefined`: Continue navigation
228
+
229
+ ```typescript
230
+ // Global guard
231
+ beforeEach: (async (to, from) => {
232
+ // Check authentication
233
+ const isAuthenticated = await checkAuth();
234
+ if (!isAuthenticated && to.path !== '/login') {
235
+ return '/login'; // Redirect to login
236
+ }
237
+ // Return nothing to continue
238
+ },
239
+ // Route guard
240
+ {
241
+ path: '/admin',
242
+ component: () => AdminPage(),
243
+ beforeEnter: async (to) => {
244
+ const hasPermission = await checkAdminPermission();
245
+ if (!hasPermission) {
246
+ return false; // Cancel navigation
247
+ }
248
+ },
249
+ });
250
+ ```
220
251
 
221
252
  ### Route Context
222
253
 
@@ -231,15 +262,22 @@ Guards and hooks receive a `RouteContext` object with:
231
262
 
232
263
  The router includes several performance optimizations:
233
264
 
234
- - **Pre-flattened Routes**: Nested routes are flattened during initialization
235
- - **Efficient Matching**: Optimized regex-based path matching
236
- - **Cached Methods**: Native DOM methods are cached
237
- - **Minimal Re-renders**: Only updates DOM when route actually changes
265
+ - **Hash-Mode Only**: Simplified implementation focusing on hash-based routing (v0.14.7+)
266
+ - **Pre-flattened Routes**: Nested routes are flattened during initialization for faster lookups
267
+ - **Efficient Matching**: Optimized regex-based path matching with caching
268
+ - **Async Guards**: All guards use Promise-based architecture for consistent async handling
238
269
  - **Guard Level Control**: Fine-grained control over guard execution using bitwise operations
270
+ - **Automatic Initialization**: Router auto-initializes on creation, no manual setup needed
239
271
 
240
272
  ## Browser Compatibility
241
273
 
242
- Works in all modern browsers and IE9+ with ES5 transpilation. In environments without `Promise` support, navigation guards run synchronously.
274
+ Works in all modern browsers that support:
275
+
276
+ - Hash-based navigation
277
+ - ES5 (with transpilation)
278
+ - Promise API (required for async guards)
279
+
280
+ For older browsers without Promise support, include a Promise polyfill.
243
281
 
244
282
  ## License
245
283
 
package/dist/index.d.ts CHANGED
@@ -99,7 +99,10 @@ interface NavOptions extends NavBaseOptions {
99
99
  * Router configuration
100
100
  */
101
101
  interface RouterConfig {
102
- baseUrl?: string;
102
+ /**
103
+ * Might not be needed while using hash routing
104
+ */
105
+ prefix?: string;
103
106
 
104
107
  /** Array of route definitions */
105
108
  routes: RawRouteConfig[];
@@ -118,12 +121,6 @@ interface RouterConfig {
118
121
 
119
122
  /** Handler for routing errors */
120
123
  onError?: (error: Error, route?: RouteConfig) => void;
121
-
122
- // # options
123
- /**
124
- * Default is `true`
125
- */
126
- asyncGuards?: boolean;
127
124
  }
128
125
 
129
126
  /**
@@ -171,7 +168,7 @@ declare function KTRouter({ router }: {
171
168
  /**
172
169
  * Create a new router instance
173
170
  */
174
- declare const createRouter: (config: RouterConfig) => Router;
171
+ declare const createRouter: ({ beforeEach, afterEach, onNotFound, onError, prefix, }: RouterConfig) => Router;
175
172
 
176
173
  export { GuardLevel, KTRouter, createRouter };
177
174
  export type { NavOptions, RawRouteConfig, RouteConfig, RouteContext, RouteMatch, Router, RouterConfig };
@@ -4,7 +4,7 @@ var __ktjs_router__ = (function (exports) {
4
4
  /**
5
5
  * Default guard that always returns true
6
6
  */
7
- const defaultHook = () => true;
7
+ const fn = (() => true);
8
8
  const throws = (m) => {
9
9
  throw new Error(`@ktjs/router: ${m}`);
10
10
  };
@@ -162,14 +162,7 @@ var __ktjs_router__ = (function (exports) {
162
162
  /**
163
163
  * Create a new router instance
164
164
  */
165
- const createRouter = (config) => {
166
- // # default configs
167
- const beforeEach = config.beforeEach ?? defaultHook;
168
- const afterEach = config.afterEach ?? defaultHook;
169
- const onNotFound = config.onNotFound ?? defaultHook;
170
- const onError = config.onError ?? defaultHook;
171
- const asyncGuards = config.asyncGuards ?? true;
172
- const baseUrl = config.baseUrl ?? '';
165
+ const createRouter = ({ beforeEach = fn, afterEach = fn, onNotFound = fn, onError = fn, prefix = '', }) => {
173
166
  // # private values
174
167
  const routes = [];
175
168
  const history = [];
@@ -179,11 +172,11 @@ var __ktjs_router__ = (function (exports) {
179
172
  const normalize = (rawRoutes, parentPath) => rawRoutes.map((route) => {
180
173
  const path = normalizePath(parentPath, route.path);
181
174
  const normalized = {
182
- path: baseUrl + path,
175
+ path: prefix + path,
183
176
  name: route.name ?? '',
184
177
  meta: route.meta ?? {},
185
- beforeEnter: route.beforeEnter ?? defaultHook,
186
- after: route.after ?? defaultHook,
178
+ beforeEnter: route.beforeEnter ?? fn,
179
+ after: route.after ?? fn,
187
180
  children: route.children ? normalize(route.children, path) : [],
188
181
  component: route.component,
189
182
  };
@@ -192,44 +185,7 @@ var __ktjs_router__ = (function (exports) {
192
185
  routes.push(normalized);
193
186
  return normalized;
194
187
  });
195
- const executeGuardsSync = (to, from, guardLevel) => {
196
- try {
197
- if (guardLevel === 0 /* GuardLevel.None */) {
198
- return { continue: true };
199
- }
200
- if (guardLevel & 1 /* GuardLevel.Global */) {
201
- const result = beforeEach(to, from);
202
- if (result === false) {
203
- return { continue: false };
204
- }
205
- if (typeof result === 'string') {
206
- return { continue: false, redirectTo: { path: result } };
207
- }
208
- if (result && typeof result === 'object' && !('then' in result)) {
209
- return { continue: false, redirectTo: result };
210
- }
211
- }
212
- if (guardLevel & 2 /* GuardLevel.Route */) {
213
- const targetRoute = to.matched[to.matched.length - 1];
214
- const result = targetRoute.beforeEnter(to);
215
- if (result === false) {
216
- return { continue: false };
217
- }
218
- if (typeof result === 'string') {
219
- return { continue: false, redirectTo: { path: result } };
220
- }
221
- if (result && typeof result === 'object' && !('then' in result)) {
222
- return { continue: false, redirectTo: result };
223
- }
224
- }
225
- return { continue: true };
226
- }
227
- catch (error) {
228
- onError(error);
229
- return { continue: false };
230
- }
231
- };
232
- const executeGuards = async (to, from, guardLevel) => {
188
+ const guard = async (to, from, guardLevel) => {
233
189
  try {
234
190
  if (guardLevel === 0 /* GuardLevel.None */) {
235
191
  return { continue: true };
@@ -312,76 +268,7 @@ var __ktjs_router__ = (function (exports) {
312
268
  fullPath,
313
269
  };
314
270
  };
315
- const navigateSync = (options, redirectCount = 0) => {
316
- try {
317
- // Prevent infinite redirect loop
318
- if (redirectCount > 10) {
319
- onError(new Error('Maximum redirect count exceeded'));
320
- return false;
321
- }
322
- const prep = navigatePrepare(options);
323
- if (!prep) {
324
- return false;
325
- }
326
- const { guardLevel, replace, to, fullPath } = prep;
327
- // Execute guards
328
- const guardResult = executeGuardsSync(to, current, guardLevel);
329
- if (!guardResult.continue) {
330
- // Check if there's a redirect
331
- if (guardResult.redirectTo) {
332
- return navigateSync(guardResult.redirectTo, redirectCount + 1);
333
- }
334
- return false;
335
- }
336
- // Update browser history depending on mode
337
- const url = fullPath;
338
- const hashUrl = '#' + fullPath;
339
- if (replace) {
340
- window.location.replace(hashUrl);
341
- }
342
- else {
343
- window.location.hash = fullPath;
344
- }
345
- // Update current route in memory
346
- current = to;
347
- if (replace) {
348
- if (history.length > 0) {
349
- history[history.length - 1] = to;
350
- }
351
- else {
352
- history.push(to);
353
- }
354
- }
355
- else {
356
- history.push(to);
357
- }
358
- // Render component if routerView exists
359
- if (routerView && to.matched.length > 0) {
360
- const route = to.matched[to.matched.length - 1];
361
- if (route.component) {
362
- const element = route.component();
363
- if (element instanceof Promise) {
364
- element.then((el) => {
365
- routerView.innerHTML = '';
366
- routerView.appendChild(el);
367
- });
368
- }
369
- else {
370
- routerView.innerHTML = '';
371
- routerView.appendChild(element);
372
- }
373
- }
374
- }
375
- // Execute after hooks
376
- executeAfterHooksSync(to, history[history.length - 2] ?? null);
377
- return true;
378
- }
379
- catch (error) {
380
- onError(error);
381
- return false;
382
- }
383
- };
384
- const navigateAsync = async (options, redirectCount = 0) => {
271
+ const navigate = async (options, redirectCount = 0) => {
385
272
  try {
386
273
  // Prevent infinite redirect loop
387
274
  if (redirectCount > 10) {
@@ -393,11 +280,11 @@ var __ktjs_router__ = (function (exports) {
393
280
  return false;
394
281
  }
395
282
  const { guardLevel, replace, to, fullPath } = prep;
396
- const guardResult = await executeGuards(to, current, guardLevel);
283
+ const guardResult = await guard(to, current, guardLevel);
397
284
  if (!guardResult.continue) {
398
285
  // Check if there's a redirect
399
286
  if (guardResult.redirectTo) {
400
- return navigateAsync(guardResult.redirectTo, redirectCount + 1);
287
+ return await navigate(guardResult.redirectTo, redirectCount + 1);
401
288
  }
402
289
  return false;
403
290
  }
@@ -438,12 +325,6 @@ var __ktjs_router__ = (function (exports) {
438
325
  return false;
439
326
  }
440
327
  };
441
- const navigate = asyncGuards ? navigateSync : navigateAsync;
442
- const executeAfterHooksSync = (to, from) => {
443
- const targetRoute = to.matched[to.matched.length - 1];
444
- targetRoute.after(to);
445
- afterEach(to, from);
446
- };
447
328
  const executeAfterHooks = async (to, from) => {
448
329
  const targetRoute = to.matched[to.matched.length - 1];
449
330
  await targetRoute.after(to);
@@ -463,7 +344,6 @@ var __ktjs_router__ = (function (exports) {
463
344
  };
464
345
  };
465
346
  // # register events
466
- // hash mode: listen to hashchange
467
347
  window.addEventListener('hashchange', () => {
468
348
  const hash = window.location.hash.slice(1);
469
349
  const [path] = hash.split('?');
@@ -505,13 +385,10 @@ var __ktjs_router__ = (function (exports) {
505
385
  }
506
386
  }
507
387
  }
508
- executeAfterHooksSync(to, history[history.length - 2] ?? null);
388
+ executeAfterHooks(to, history[history.length - 2] ?? null);
509
389
  });
510
390
  // # initialize
511
- normalize(config.routes, '/');
512
- const { findByName, match } = createMatcher(routes);
513
- // Router instance
514
- return {
391
+ const instance = {
515
392
  get current() {
516
393
  return current;
517
394
  },
@@ -540,6 +417,13 @@ var __ktjs_router__ = (function (exports) {
540
417
  window.history.forward();
541
418
  },
542
419
  };
420
+ normalize(routes, '/');
421
+ const { findByName, match } = createMatcher(routes);
422
+ const currentHash = window.location.hash.slice(1);
423
+ if (currentHash) {
424
+ instance.push(currentHash);
425
+ }
426
+ return instance;
543
427
  };
544
428
 
545
429
  exports.KTRouter = KTRouter;
@@ -85,7 +85,7 @@ var __ktjs_router__ = (function (exports) {
85
85
  /**
86
86
  * Default guard that always returns true
87
87
  */
88
- var defaultHook = function () { return true; };
88
+ var fn = (function () { return true; });
89
89
  var throws = function (m) {
90
90
  throw new Error("@ktjs/router: ".concat(m));
91
91
  };
@@ -252,15 +252,8 @@ var __ktjs_router__ = (function (exports) {
252
252
  /**
253
253
  * Create a new router instance
254
254
  */
255
- var createRouter = function (config) {
256
- var _a, _b, _c, _d, _e, _f;
257
- // # default configs
258
- var beforeEach = (_a = config.beforeEach) !== null && _a !== void 0 ? _a : defaultHook;
259
- var afterEach = (_b = config.afterEach) !== null && _b !== void 0 ? _b : defaultHook;
260
- var onNotFound = (_c = config.onNotFound) !== null && _c !== void 0 ? _c : defaultHook;
261
- var onError = (_d = config.onError) !== null && _d !== void 0 ? _d : defaultHook;
262
- var asyncGuards = (_e = config.asyncGuards) !== null && _e !== void 0 ? _e : true;
263
- var baseUrl = (_f = config.baseUrl) !== null && _f !== void 0 ? _f : '';
255
+ var createRouter = function (_a) {
256
+ var _b = _a.beforeEach, beforeEach = _b === void 0 ? fn : _b, _c = _a.afterEach, afterEach = _c === void 0 ? fn : _c, _d = _a.onNotFound, onNotFound = _d === void 0 ? fn : _d, _e = _a.onError, onError = _e === void 0 ? fn : _e, _f = _a.prefix, prefix = _f === void 0 ? '' : _f;
264
257
  // # private values
265
258
  var routes = [];
266
259
  var history = [];
@@ -272,11 +265,11 @@ var __ktjs_router__ = (function (exports) {
272
265
  var _a, _b, _c, _d;
273
266
  var path = normalizePath(parentPath, route.path);
274
267
  var normalized = {
275
- path: baseUrl + path,
268
+ path: prefix + path,
276
269
  name: (_a = route.name) !== null && _a !== void 0 ? _a : '',
277
270
  meta: (_b = route.meta) !== null && _b !== void 0 ? _b : {},
278
- beforeEnter: (_c = route.beforeEnter) !== null && _c !== void 0 ? _c : defaultHook,
279
- after: (_d = route.after) !== null && _d !== void 0 ? _d : defaultHook,
271
+ beforeEnter: (_c = route.beforeEnter) !== null && _c !== void 0 ? _c : fn,
272
+ after: (_d = route.after) !== null && _d !== void 0 ? _d : fn,
280
273
  children: route.children ? normalize(route.children, path) : [],
281
274
  component: route.component,
282
275
  };
@@ -286,44 +279,7 @@ var __ktjs_router__ = (function (exports) {
286
279
  return normalized;
287
280
  });
288
281
  };
289
- var executeGuardsSync = function (to, from, guardLevel) {
290
- try {
291
- if (guardLevel === 0 /* GuardLevel.None */) {
292
- return { continue: true };
293
- }
294
- if (guardLevel & 1 /* GuardLevel.Global */) {
295
- var result = beforeEach(to, from);
296
- if (result === false) {
297
- return { continue: false };
298
- }
299
- if (typeof result === 'string') {
300
- return { continue: false, redirectTo: { path: result } };
301
- }
302
- if (result && typeof result === 'object' && !('then' in result)) {
303
- return { continue: false, redirectTo: result };
304
- }
305
- }
306
- if (guardLevel & 2 /* GuardLevel.Route */) {
307
- var targetRoute = to.matched[to.matched.length - 1];
308
- var result = targetRoute.beforeEnter(to);
309
- if (result === false) {
310
- return { continue: false };
311
- }
312
- if (typeof result === 'string') {
313
- return { continue: false, redirectTo: { path: result } };
314
- }
315
- if (result && typeof result === 'object' && !('then' in result)) {
316
- return { continue: false, redirectTo: result };
317
- }
318
- }
319
- return { continue: true };
320
- }
321
- catch (error) {
322
- onError(error);
323
- return { continue: false };
324
- }
325
- };
326
- var executeGuards = function (to, from, guardLevel) { return __awaiter(void 0, void 0, void 0, function () {
282
+ var guard = function (to, from, guardLevel) { return __awaiter(void 0, void 0, void 0, function () {
327
283
  var result, targetRoute, result, error_1;
328
284
  return __generator(this, function (_a) {
329
285
  switch (_a.label) {
@@ -418,78 +374,7 @@ var __ktjs_router__ = (function (exports) {
418
374
  fullPath: fullPath,
419
375
  };
420
376
  };
421
- var navigateSync = function (options, redirectCount) {
422
- var _a;
423
- if (redirectCount === void 0) { redirectCount = 0; }
424
- try {
425
- // Prevent infinite redirect loop
426
- if (redirectCount > 10) {
427
- onError(new Error('Maximum redirect count exceeded'));
428
- return false;
429
- }
430
- var prep = navigatePrepare(options);
431
- if (!prep) {
432
- return false;
433
- }
434
- var guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
435
- // Execute guards
436
- var guardResult = executeGuardsSync(to, current, guardLevel);
437
- if (!guardResult.continue) {
438
- // Check if there's a redirect
439
- if (guardResult.redirectTo) {
440
- return navigateSync(guardResult.redirectTo, redirectCount + 1);
441
- }
442
- return false;
443
- }
444
- // Update browser history depending on mode
445
- var url = fullPath;
446
- var hashUrl = '#' + fullPath;
447
- if (replace) {
448
- window.location.replace(hashUrl);
449
- }
450
- else {
451
- window.location.hash = fullPath;
452
- }
453
- // Update current route in memory
454
- current = to;
455
- if (replace) {
456
- if (history.length > 0) {
457
- history[history.length - 1] = to;
458
- }
459
- else {
460
- history.push(to);
461
- }
462
- }
463
- else {
464
- history.push(to);
465
- }
466
- // Render component if routerView exists
467
- if (routerView && to.matched.length > 0) {
468
- var route = to.matched[to.matched.length - 1];
469
- if (route.component) {
470
- var element = route.component();
471
- if (element instanceof Promise) {
472
- element.then(function (el) {
473
- routerView.innerHTML = '';
474
- routerView.appendChild(el);
475
- });
476
- }
477
- else {
478
- routerView.innerHTML = '';
479
- routerView.appendChild(element);
480
- }
481
- }
482
- }
483
- // Execute after hooks
484
- executeAfterHooksSync(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
485
- return true;
486
- }
487
- catch (error) {
488
- onError(error);
489
- return false;
490
- }
491
- };
492
- var navigateAsync = function (options_1) {
377
+ var navigate = function (options_1) {
493
378
  var args_1 = [];
494
379
  for (var _i = 1; _i < arguments.length; _i++) {
495
380
  args_1[_i - 1] = arguments[_i];
@@ -501,7 +386,7 @@ var __ktjs_router__ = (function (exports) {
501
386
  return __generator(this, function (_b) {
502
387
  switch (_b.label) {
503
388
  case 0:
504
- _b.trys.push([0, 4, , 5]);
389
+ _b.trys.push([0, 7, , 8]);
505
390
  // Prevent infinite redirect loop
506
391
  if (redirectCount > 10) {
507
392
  onError(new Error('Maximum redirect count exceeded'));
@@ -512,16 +397,15 @@ var __ktjs_router__ = (function (exports) {
512
397
  return [2 /*return*/, false];
513
398
  }
514
399
  guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
515
- return [4 /*yield*/, executeGuards(to, current, guardLevel)];
400
+ return [4 /*yield*/, guard(to, current, guardLevel)];
516
401
  case 1:
517
402
  guardResult = _b.sent();
518
- if (!guardResult.continue) {
519
- // Check if there's a redirect
520
- if (guardResult.redirectTo) {
521
- return [2 /*return*/, navigateAsync(guardResult.redirectTo, redirectCount + 1)];
522
- }
523
- return [2 /*return*/, false];
524
- }
403
+ if (!!guardResult.continue) return [3 /*break*/, 4];
404
+ if (!guardResult.redirectTo) return [3 /*break*/, 3];
405
+ return [4 /*yield*/, navigate(guardResult.redirectTo, redirectCount + 1)];
406
+ case 2: return [2 /*return*/, _b.sent()];
407
+ case 3: return [2 /*return*/, false];
408
+ case 4:
525
409
  hashUrl = '#' + fullPath;
526
410
  if (replace) {
527
411
  window.location.replace(hashUrl);
@@ -541,33 +425,27 @@ var __ktjs_router__ = (function (exports) {
541
425
  else {
542
426
  history.push(to);
543
427
  }
544
- if (!(routerView && to.matched.length > 0)) return [3 /*break*/, 3];
428
+ if (!(routerView && to.matched.length > 0)) return [3 /*break*/, 6];
545
429
  route = to.matched[to.matched.length - 1];
546
- if (!route.component) return [3 /*break*/, 3];
430
+ if (!route.component) return [3 /*break*/, 6];
547
431
  return [4 /*yield*/, route.component()];
548
- case 2:
432
+ case 5:
549
433
  element = _b.sent();
550
434
  routerView.innerHTML = '';
551
435
  routerView.appendChild(element);
552
- _b.label = 3;
553
- case 3:
436
+ _b.label = 6;
437
+ case 6:
554
438
  executeAfterHooks(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
555
439
  return [2 /*return*/, true];
556
- case 4:
440
+ case 7:
557
441
  error_2 = _b.sent();
558
442
  onError(error_2);
559
443
  return [2 /*return*/, false];
560
- case 5: return [2 /*return*/];
444
+ case 8: return [2 /*return*/];
561
445
  }
562
446
  });
563
447
  });
564
448
  };
565
- var navigate = asyncGuards ? navigateSync : navigateAsync;
566
- var executeAfterHooksSync = function (to, from) {
567
- var targetRoute = to.matched[to.matched.length - 1];
568
- targetRoute.after(to);
569
- afterEach(to, from);
570
- };
571
449
  var executeAfterHooks = function (to, from) { return __awaiter(void 0, void 0, void 0, function () {
572
450
  var targetRoute;
573
451
  return __generator(this, function (_a) {
@@ -598,7 +476,6 @@ var __ktjs_router__ = (function (exports) {
598
476
  };
599
477
  };
600
478
  // # register events
601
- // hash mode: listen to hashchange
602
479
  window.addEventListener('hashchange', function () {
603
480
  var _a, _b;
604
481
  var hash = window.location.hash.slice(1);
@@ -641,13 +518,10 @@ var __ktjs_router__ = (function (exports) {
641
518
  }
642
519
  }
643
520
  }
644
- executeAfterHooksSync(to, (_b = history[history.length - 2]) !== null && _b !== void 0 ? _b : null);
521
+ executeAfterHooks(to, (_b = history[history.length - 2]) !== null && _b !== void 0 ? _b : null);
645
522
  });
646
523
  // # initialize
647
- normalize(config.routes, '/');
648
- var _g = createMatcher(routes), findByName = _g.findByName, match = _g.match;
649
- // Router instance
650
- return {
524
+ var instance = {
651
525
  get current() {
652
526
  return current;
653
527
  },
@@ -676,6 +550,13 @@ var __ktjs_router__ = (function (exports) {
676
550
  window.history.forward();
677
551
  },
678
552
  };
553
+ normalize(routes, '/');
554
+ var _g = createMatcher(routes), findByName = _g.findByName, match = _g.match;
555
+ var currentHash = window.location.hash.slice(1);
556
+ if (currentHash) {
557
+ instance.push(currentHash);
558
+ }
559
+ return instance;
679
560
  };
680
561
 
681
562
  exports.KTRouter = KTRouter;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Default guard that always returns true
3
3
  */
4
- const defaultHook = () => true;
4
+ const fn = (() => true);
5
5
  const throws = (m) => {
6
6
  throw new Error(`@ktjs/router: ${m}`);
7
7
  };
@@ -159,14 +159,7 @@ function KTRouter({ router }) {
159
159
  /**
160
160
  * Create a new router instance
161
161
  */
162
- const createRouter = (config) => {
163
- // # default configs
164
- const beforeEach = config.beforeEach ?? defaultHook;
165
- const afterEach = config.afterEach ?? defaultHook;
166
- const onNotFound = config.onNotFound ?? defaultHook;
167
- const onError = config.onError ?? defaultHook;
168
- const asyncGuards = config.asyncGuards ?? true;
169
- const baseUrl = config.baseUrl ?? '';
162
+ const createRouter = ({ beforeEach = fn, afterEach = fn, onNotFound = fn, onError = fn, prefix = '', }) => {
170
163
  // # private values
171
164
  const routes = [];
172
165
  const history = [];
@@ -176,11 +169,11 @@ const createRouter = (config) => {
176
169
  const normalize = (rawRoutes, parentPath) => rawRoutes.map((route) => {
177
170
  const path = normalizePath(parentPath, route.path);
178
171
  const normalized = {
179
- path: baseUrl + path,
172
+ path: prefix + path,
180
173
  name: route.name ?? '',
181
174
  meta: route.meta ?? {},
182
- beforeEnter: route.beforeEnter ?? defaultHook,
183
- after: route.after ?? defaultHook,
175
+ beforeEnter: route.beforeEnter ?? fn,
176
+ after: route.after ?? fn,
184
177
  children: route.children ? normalize(route.children, path) : [],
185
178
  component: route.component,
186
179
  };
@@ -189,44 +182,7 @@ const createRouter = (config) => {
189
182
  routes.push(normalized);
190
183
  return normalized;
191
184
  });
192
- const executeGuardsSync = (to, from, guardLevel) => {
193
- try {
194
- if (guardLevel === 0 /* GuardLevel.None */) {
195
- return { continue: true };
196
- }
197
- if (guardLevel & 1 /* GuardLevel.Global */) {
198
- const result = beforeEach(to, from);
199
- if (result === false) {
200
- return { continue: false };
201
- }
202
- if (typeof result === 'string') {
203
- return { continue: false, redirectTo: { path: result } };
204
- }
205
- if (result && typeof result === 'object' && !('then' in result)) {
206
- return { continue: false, redirectTo: result };
207
- }
208
- }
209
- if (guardLevel & 2 /* GuardLevel.Route */) {
210
- const targetRoute = to.matched[to.matched.length - 1];
211
- const result = targetRoute.beforeEnter(to);
212
- if (result === false) {
213
- return { continue: false };
214
- }
215
- if (typeof result === 'string') {
216
- return { continue: false, redirectTo: { path: result } };
217
- }
218
- if (result && typeof result === 'object' && !('then' in result)) {
219
- return { continue: false, redirectTo: result };
220
- }
221
- }
222
- return { continue: true };
223
- }
224
- catch (error) {
225
- onError(error);
226
- return { continue: false };
227
- }
228
- };
229
- const executeGuards = async (to, from, guardLevel) => {
185
+ const guard = async (to, from, guardLevel) => {
230
186
  try {
231
187
  if (guardLevel === 0 /* GuardLevel.None */) {
232
188
  return { continue: true };
@@ -309,76 +265,7 @@ const createRouter = (config) => {
309
265
  fullPath,
310
266
  };
311
267
  };
312
- const navigateSync = (options, redirectCount = 0) => {
313
- try {
314
- // Prevent infinite redirect loop
315
- if (redirectCount > 10) {
316
- onError(new Error('Maximum redirect count exceeded'));
317
- return false;
318
- }
319
- const prep = navigatePrepare(options);
320
- if (!prep) {
321
- return false;
322
- }
323
- const { guardLevel, replace, to, fullPath } = prep;
324
- // Execute guards
325
- const guardResult = executeGuardsSync(to, current, guardLevel);
326
- if (!guardResult.continue) {
327
- // Check if there's a redirect
328
- if (guardResult.redirectTo) {
329
- return navigateSync(guardResult.redirectTo, redirectCount + 1);
330
- }
331
- return false;
332
- }
333
- // Update browser history depending on mode
334
- const url = fullPath;
335
- const hashUrl = '#' + fullPath;
336
- if (replace) {
337
- window.location.replace(hashUrl);
338
- }
339
- else {
340
- window.location.hash = fullPath;
341
- }
342
- // Update current route in memory
343
- current = to;
344
- if (replace) {
345
- if (history.length > 0) {
346
- history[history.length - 1] = to;
347
- }
348
- else {
349
- history.push(to);
350
- }
351
- }
352
- else {
353
- history.push(to);
354
- }
355
- // Render component if routerView exists
356
- if (routerView && to.matched.length > 0) {
357
- const route = to.matched[to.matched.length - 1];
358
- if (route.component) {
359
- const element = route.component();
360
- if (element instanceof Promise) {
361
- element.then((el) => {
362
- routerView.innerHTML = '';
363
- routerView.appendChild(el);
364
- });
365
- }
366
- else {
367
- routerView.innerHTML = '';
368
- routerView.appendChild(element);
369
- }
370
- }
371
- }
372
- // Execute after hooks
373
- executeAfterHooksSync(to, history[history.length - 2] ?? null);
374
- return true;
375
- }
376
- catch (error) {
377
- onError(error);
378
- return false;
379
- }
380
- };
381
- const navigateAsync = async (options, redirectCount = 0) => {
268
+ const navigate = async (options, redirectCount = 0) => {
382
269
  try {
383
270
  // Prevent infinite redirect loop
384
271
  if (redirectCount > 10) {
@@ -390,11 +277,11 @@ const createRouter = (config) => {
390
277
  return false;
391
278
  }
392
279
  const { guardLevel, replace, to, fullPath } = prep;
393
- const guardResult = await executeGuards(to, current, guardLevel);
280
+ const guardResult = await guard(to, current, guardLevel);
394
281
  if (!guardResult.continue) {
395
282
  // Check if there's a redirect
396
283
  if (guardResult.redirectTo) {
397
- return navigateAsync(guardResult.redirectTo, redirectCount + 1);
284
+ return await navigate(guardResult.redirectTo, redirectCount + 1);
398
285
  }
399
286
  return false;
400
287
  }
@@ -435,12 +322,6 @@ const createRouter = (config) => {
435
322
  return false;
436
323
  }
437
324
  };
438
- const navigate = asyncGuards ? navigateSync : navigateAsync;
439
- const executeAfterHooksSync = (to, from) => {
440
- const targetRoute = to.matched[to.matched.length - 1];
441
- targetRoute.after(to);
442
- afterEach(to, from);
443
- };
444
325
  const executeAfterHooks = async (to, from) => {
445
326
  const targetRoute = to.matched[to.matched.length - 1];
446
327
  await targetRoute.after(to);
@@ -460,7 +341,6 @@ const createRouter = (config) => {
460
341
  };
461
342
  };
462
343
  // # register events
463
- // hash mode: listen to hashchange
464
344
  window.addEventListener('hashchange', () => {
465
345
  const hash = window.location.hash.slice(1);
466
346
  const [path] = hash.split('?');
@@ -502,13 +382,10 @@ const createRouter = (config) => {
502
382
  }
503
383
  }
504
384
  }
505
- executeAfterHooksSync(to, history[history.length - 2] ?? null);
385
+ executeAfterHooks(to, history[history.length - 2] ?? null);
506
386
  });
507
387
  // # initialize
508
- normalize(config.routes, '/');
509
- const { findByName, match } = createMatcher(routes);
510
- // Router instance
511
- return {
388
+ const instance = {
512
389
  get current() {
513
390
  return current;
514
391
  },
@@ -537,6 +414,13 @@ const createRouter = (config) => {
537
414
  window.history.forward();
538
415
  },
539
416
  };
417
+ normalize(routes, '/');
418
+ const { findByName, match } = createMatcher(routes);
419
+ const currentHash = window.location.hash.slice(1);
420
+ if (currentHash) {
421
+ instance.push(currentHash);
422
+ }
423
+ return instance;
540
424
  };
541
425
 
542
426
  export { KTRouter, createRouter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/router",
3
- "version": "0.14.7",
3
+ "version": "0.14.9",
4
4
  "description": "Router for kt.js - client-side routing with navigation guards",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",