@ktjs/router 0.13.0 → 0.14.1

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/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { KTHTMLElement } from '@ktjs/core';
2
+
1
3
  /**
2
4
  * Guard level that determines which guards to apply during navigation
3
5
  * - there are global and route-level guards
@@ -31,8 +33,12 @@ interface RawRouteConfig {
31
33
  name?: string;
32
34
  /** Optional metadata attached to the route */
33
35
  meta?: Record<string, any>;
34
- /** Route-level guard executed before entering this route */
35
- beforeEnter?: (context: RouteContext) => boolean | void | Promise<boolean | void>;
36
+ /** Optional component to render */
37
+ component: () => HTMLElement | Promise<HTMLElement>;
38
+ /** Route-level guard executed before entering this route. Return false to block, string/object to redirect */
39
+ beforeEnter?: (
40
+ context: RouteContext
41
+ ) => string | NavBaseOptions | boolean | void | Promise<string | NavBaseOptions | boolean | void>;
36
42
  /** Route-level hook executed after navigation */
37
43
  after?: (context: RouteContext) => void | Promise<void>;
38
44
  /** Nested child routes */
@@ -96,8 +102,11 @@ interface RouterConfig {
96
102
  /** Array of route definitions */
97
103
  routes: RawRouteConfig[];
98
104
 
99
- /** Global guard executed before each navigation (except silentPush) */
100
- beforeEach?: (to: RouteContext, from: RouteContext | null) => boolean | void | Promise<boolean | void>;
105
+ /** Global guard executed before each navigation (except silentPush). Return false to block, string/object to redirect */
106
+ beforeEach?: (
107
+ to: RouteContext,
108
+ from: RouteContext | null
109
+ ) => string | NavBaseOptions | boolean | void | Promise<string | NavBaseOptions | boolean | void>;
101
110
 
102
111
  /** Global hook executed after each navigation */
103
112
  afterEach?: (to: RouteContext, from: RouteContext | null) => void | Promise<void>;
@@ -125,6 +134,9 @@ interface Router {
125
134
  /** Navigation history */
126
135
  history: RouteContext[];
127
136
 
137
+ /** Set the router view container */
138
+ setRouterView(view: HTMLElement): void;
139
+
128
140
  /** Navigate with guards */
129
141
  push(location: string | NavOptions): boolean | Promise<boolean>;
130
142
 
@@ -147,10 +159,17 @@ interface RouteMatch {
147
159
  result: RouteConfig[];
148
160
  }
149
161
 
162
+ /**
163
+ * Create a router view container that automatically renders route components
164
+ */
165
+ declare function KTRouter({ router }: {
166
+ router: Router;
167
+ }): KTHTMLElement;
168
+
150
169
  /**
151
170
  * Create a new router instance
152
171
  */
153
172
  declare const createRouter: (config: RouterConfig) => Router;
154
173
 
155
- export { GuardLevel, createRouter };
174
+ export { GuardLevel, KTRouter, createRouter };
156
175
  export type { NavOptions, RawRouteConfig, RouteConfig, RouteContext, RouteMatch, Router, RouterConfig };
@@ -150,6 +150,15 @@ var __ktjs_router__ = (function (exports) {
150
150
  };
151
151
  };
152
152
 
153
+ /**
154
+ * Create a router view container that automatically renders route components
155
+ */
156
+ function KTRouter({ router }) {
157
+ const view = document.createElement('kt-router-view');
158
+ router.setRouterView(view);
159
+ return view;
160
+ }
161
+
153
162
  /**
154
163
  * Create a new router instance
155
164
  */
@@ -162,6 +171,7 @@ var __ktjs_router__ = (function (exports) {
162
171
  const asyncGuards = config.asyncGuards ?? true;
163
172
  // # private values
164
173
  const routes = [];
174
+ let routerView = null;
165
175
  /**
166
176
  * Normalize routes by adding default guards
167
177
  */
@@ -174,6 +184,7 @@ var __ktjs_router__ = (function (exports) {
174
184
  beforeEnter: route.beforeEnter ?? defaultHook,
175
185
  after: route.after ?? defaultHook,
176
186
  children: route.children ? normalize(route.children, path) : [],
187
+ component: route.component,
177
188
  };
178
189
  // directly push the normalized route to the list
179
190
  // avoid flatten them again
@@ -189,49 +200,75 @@ var __ktjs_router__ = (function (exports) {
189
200
  const executeGuardsSync = (to, from, guardLevel) => {
190
201
  try {
191
202
  if (guardLevel === 0 /* GuardLevel.None */) {
192
- return true;
203
+ return { continue: true };
193
204
  }
194
205
  if (guardLevel & 1 /* GuardLevel.Global */) {
195
206
  const result = beforeEach(to, from);
196
207
  if (result === false) {
197
- return false;
208
+ return { continue: false };
209
+ }
210
+ if (typeof result === 'string') {
211
+ return { continue: false, redirectTo: { path: result } };
212
+ }
213
+ if (result && typeof result === 'object' && !('then' in result)) {
214
+ return { continue: false, redirectTo: result };
198
215
  }
199
216
  }
200
217
  if (guardLevel & 2 /* GuardLevel.Route */) {
201
218
  const targetRoute = to.matched[to.matched.length - 1];
202
219
  const result = targetRoute.beforeEnter(to);
203
220
  if (result === false) {
204
- return false;
221
+ return { continue: false };
222
+ }
223
+ if (typeof result === 'string') {
224
+ return { continue: false, redirectTo: { path: result } };
225
+ }
226
+ if (result && typeof result === 'object' && !('then' in result)) {
227
+ return { continue: false, redirectTo: result };
205
228
  }
206
229
  }
207
- return true;
230
+ return { continue: true };
208
231
  }
209
232
  catch (error) {
210
233
  onError(error);
211
- return false;
234
+ return { continue: false };
212
235
  }
213
236
  };
214
237
  const executeGuards = async (to, from, guardLevel) => {
215
238
  try {
216
239
  if (guardLevel === 0 /* GuardLevel.None */) {
217
- return true;
240
+ return { continue: true };
218
241
  }
219
242
  if (guardLevel & 1 /* GuardLevel.Global */) {
220
243
  const result = await beforeEach(to, from);
221
244
  if (result === false) {
222
- return false;
245
+ return { continue: false };
246
+ }
247
+ if (typeof result === 'string') {
248
+ return { continue: false, redirectTo: { path: result } };
249
+ }
250
+ if (result && typeof result === 'object') {
251
+ return { continue: false, redirectTo: result };
223
252
  }
224
253
  }
225
254
  if (guardLevel & 2 /* GuardLevel.Route */) {
226
255
  const targetRoute = to.matched[to.matched.length - 1];
227
- const result = targetRoute.beforeEnter(to);
228
- return result !== false;
256
+ const result = await targetRoute.beforeEnter(to);
257
+ if (result === false) {
258
+ return { continue: false };
259
+ }
260
+ if (typeof result === 'string') {
261
+ return { continue: false, redirectTo: { path: result } };
262
+ }
263
+ if (result && typeof result === 'object') {
264
+ return { continue: false, redirectTo: result };
265
+ }
229
266
  }
230
- return true;
267
+ return { continue: true };
231
268
  }
232
269
  catch (error) {
233
270
  onError(error);
234
- return false;
271
+ return { continue: false };
235
272
  }
236
273
  };
237
274
  const navigatePrepare = (options) => {
@@ -280,15 +317,25 @@ var __ktjs_router__ = (function (exports) {
280
317
  fullPath,
281
318
  };
282
319
  };
283
- const navigateSync = (options) => {
320
+ const navigateSync = (options, redirectCount = 0) => {
284
321
  try {
322
+ // Prevent infinite redirect loop
323
+ if (redirectCount > 10) {
324
+ onError(new Error('Maximum redirect count exceeded'));
325
+ return false;
326
+ }
285
327
  const prep = navigatePrepare(options);
286
328
  if (!prep) {
287
329
  return false;
288
330
  }
289
331
  const { guardLevel, replace, to, fullPath } = prep;
290
332
  // Execute guards
291
- if (!executeGuardsSync(to, current, guardLevel)) {
333
+ const guardResult = executeGuardsSync(to, current, guardLevel);
334
+ if (!guardResult.continue) {
335
+ // Check if there's a redirect
336
+ if (guardResult.redirectTo) {
337
+ return navigateSync(guardResult.redirectTo, redirectCount + 1);
338
+ }
292
339
  return false;
293
340
  }
294
341
  // Update browser history
@@ -302,6 +349,23 @@ var __ktjs_router__ = (function (exports) {
302
349
  // Update current route
303
350
  current = to;
304
351
  history.push(to);
352
+ // Render component if routerView exists
353
+ if (routerView && to.matched.length > 0) {
354
+ const route = to.matched[to.matched.length - 1];
355
+ if (route.component) {
356
+ const element = route.component();
357
+ if (element instanceof Promise) {
358
+ element.then((el) => {
359
+ routerView.innerHTML = '';
360
+ routerView.appendChild(el);
361
+ });
362
+ }
363
+ else {
364
+ routerView.innerHTML = '';
365
+ routerView.appendChild(element);
366
+ }
367
+ }
368
+ }
305
369
  // Execute after hooks
306
370
  executeAfterHooksSync(to, history[history.length - 2] ?? null);
307
371
  return true;
@@ -311,15 +375,24 @@ var __ktjs_router__ = (function (exports) {
311
375
  return false;
312
376
  }
313
377
  };
314
- const navigateAsync = async (options) => {
378
+ const navigateAsync = async (options, redirectCount = 0) => {
315
379
  try {
380
+ // Prevent infinite redirect loop
381
+ if (redirectCount > 10) {
382
+ onError(new Error('Maximum redirect count exceeded'));
383
+ return false;
384
+ }
316
385
  const prep = navigatePrepare(options);
317
386
  if (!prep) {
318
387
  return false;
319
388
  }
320
389
  const { guardLevel, replace, to, fullPath } = prep;
321
- const passed = await executeGuards(to, current, guardLevel);
322
- if (!passed) {
390
+ const guardResult = await executeGuards(to, current, guardLevel);
391
+ if (!guardResult.continue) {
392
+ // Check if there's a redirect
393
+ if (guardResult.redirectTo) {
394
+ return navigateAsync(guardResult.redirectTo, redirectCount + 1);
395
+ }
323
396
  return false;
324
397
  }
325
398
  // ---- Guards passed ----
@@ -330,6 +403,17 @@ var __ktjs_router__ = (function (exports) {
330
403
  else {
331
404
  window.history.pushState({ path: to.path }, '', url);
332
405
  }
406
+ current = to;
407
+ history.push(to);
408
+ // Render component if routerView exists
409
+ if (routerView && to.matched.length > 0) {
410
+ const route = to.matched[to.matched.length - 1];
411
+ if (route.component) {
412
+ const element = await route.component();
413
+ routerView.innerHTML = '';
414
+ routerView.appendChild(element);
415
+ }
416
+ }
333
417
  executeAfterHooks(to, history[history.length - 2] ?? null);
334
418
  return true;
335
419
  }
@@ -378,6 +462,9 @@ var __ktjs_router__ = (function (exports) {
378
462
  get history() {
379
463
  return history.concat();
380
464
  },
465
+ setRouterView(view) {
466
+ routerView = view;
467
+ },
381
468
  push(location) {
382
469
  const options = normalizeLocation(location);
383
470
  return navigate(options);
@@ -399,6 +486,7 @@ var __ktjs_router__ = (function (exports) {
399
486
  };
400
487
  };
401
488
 
489
+ exports.KTRouter = KTRouter;
402
490
  exports.createRouter = createRouter;
403
491
 
404
492
  return exports;
@@ -67,6 +67,16 @@ var __ktjs_router__ = (function (exports) {
67
67
  }
68
68
  }
69
69
 
70
+ function __spreadArray(to, from, pack) {
71
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
72
+ if (ar || !(i in from)) {
73
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
74
+ ar[i] = from[i];
75
+ }
76
+ }
77
+ return to.concat(ar || Array.prototype.slice.call(from));
78
+ }
79
+
70
80
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
71
81
  var e = new Error(message);
72
82
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
@@ -229,6 +239,16 @@ var __ktjs_router__ = (function (exports) {
229
239
  };
230
240
  };
231
241
 
242
+ /**
243
+ * Create a router view container that automatically renders route components
244
+ */
245
+ function KTRouter(_a) {
246
+ var router = _a.router;
247
+ var view = document.createElement('kt-router-view');
248
+ router.setRouterView(view);
249
+ return view;
250
+ }
251
+
232
252
  /**
233
253
  * Create a new router instance
234
254
  */
@@ -242,6 +262,7 @@ var __ktjs_router__ = (function (exports) {
242
262
  var asyncGuards = (_e = config.asyncGuards) !== null && _e !== void 0 ? _e : true;
243
263
  // # private values
244
264
  var routes = [];
265
+ var routerView = null;
245
266
  /**
246
267
  * Normalize routes by adding default guards
247
268
  */
@@ -256,6 +277,7 @@ var __ktjs_router__ = (function (exports) {
256
277
  beforeEnter: (_c = route.beforeEnter) !== null && _c !== void 0 ? _c : defaultHook,
257
278
  after: (_d = route.after) !== null && _d !== void 0 ? _d : defaultHook,
258
279
  children: route.children ? normalize(route.children, path) : [],
280
+ component: route.component,
259
281
  };
260
282
  // directly push the normalized route to the list
261
283
  // avoid flatten them again
@@ -272,26 +294,38 @@ var __ktjs_router__ = (function (exports) {
272
294
  var executeGuardsSync = function (to, from, guardLevel) {
273
295
  try {
274
296
  if (guardLevel === 0 /* GuardLevel.None */) {
275
- return true;
297
+ return { continue: true };
276
298
  }
277
299
  if (guardLevel & 1 /* GuardLevel.Global */) {
278
300
  var result = beforeEach(to, from);
279
301
  if (result === false) {
280
- return false;
302
+ return { continue: false };
303
+ }
304
+ if (typeof result === 'string') {
305
+ return { continue: false, redirectTo: { path: result } };
306
+ }
307
+ if (result && typeof result === 'object' && !('then' in result)) {
308
+ return { continue: false, redirectTo: result };
281
309
  }
282
310
  }
283
311
  if (guardLevel & 2 /* GuardLevel.Route */) {
284
312
  var targetRoute = to.matched[to.matched.length - 1];
285
313
  var result = targetRoute.beforeEnter(to);
286
314
  if (result === false) {
287
- return false;
315
+ return { continue: false };
316
+ }
317
+ if (typeof result === 'string') {
318
+ return { continue: false, redirectTo: { path: result } };
319
+ }
320
+ if (result && typeof result === 'object' && !('then' in result)) {
321
+ return { continue: false, redirectTo: result };
288
322
  }
289
323
  }
290
- return true;
324
+ return { continue: true };
291
325
  }
292
326
  catch (error) {
293
327
  onError(error);
294
- return false;
328
+ return { continue: false };
295
329
  }
296
330
  };
297
331
  var executeGuards = function (to, from, guardLevel) { return __awaiter(void 0, void 0, void 0, function () {
@@ -299,30 +333,46 @@ var __ktjs_router__ = (function (exports) {
299
333
  return __generator(this, function (_a) {
300
334
  switch (_a.label) {
301
335
  case 0:
302
- _a.trys.push([0, 3, , 4]);
336
+ _a.trys.push([0, 5, , 6]);
303
337
  if (guardLevel === 0 /* GuardLevel.None */) {
304
- return [2 /*return*/, true];
338
+ return [2 /*return*/, { continue: true }];
305
339
  }
306
340
  if (!(guardLevel & 1 /* GuardLevel.Global */)) return [3 /*break*/, 2];
307
341
  return [4 /*yield*/, beforeEach(to, from)];
308
342
  case 1:
309
343
  result = _a.sent();
310
344
  if (result === false) {
311
- return [2 /*return*/, false];
345
+ return [2 /*return*/, { continue: false }];
346
+ }
347
+ if (typeof result === 'string') {
348
+ return [2 /*return*/, { continue: false, redirectTo: { path: result } }];
349
+ }
350
+ if (result && typeof result === 'object') {
351
+ return [2 /*return*/, { continue: false, redirectTo: result }];
312
352
  }
313
353
  _a.label = 2;
314
354
  case 2:
315
- if (guardLevel & 2 /* GuardLevel.Route */) {
316
- targetRoute = to.matched[to.matched.length - 1];
317
- result = targetRoute.beforeEnter(to);
318
- return [2 /*return*/, result !== false];
319
- }
320
- return [2 /*return*/, true];
355
+ if (!(guardLevel & 2 /* GuardLevel.Route */)) return [3 /*break*/, 4];
356
+ targetRoute = to.matched[to.matched.length - 1];
357
+ return [4 /*yield*/, targetRoute.beforeEnter(to)];
321
358
  case 3:
359
+ result = _a.sent();
360
+ if (result === false) {
361
+ return [2 /*return*/, { continue: false }];
362
+ }
363
+ if (typeof result === 'string') {
364
+ return [2 /*return*/, { continue: false, redirectTo: { path: result } }];
365
+ }
366
+ if (result && typeof result === 'object') {
367
+ return [2 /*return*/, { continue: false, redirectTo: result }];
368
+ }
369
+ _a.label = 4;
370
+ case 4: return [2 /*return*/, { continue: true }];
371
+ case 5:
322
372
  error_1 = _a.sent();
323
373
  onError(error_1);
324
- return [2 /*return*/, false];
325
- case 4: return [2 /*return*/];
374
+ return [2 /*return*/, { continue: false }];
375
+ case 6: return [2 /*return*/];
326
376
  }
327
377
  });
328
378
  }); };
@@ -373,16 +423,27 @@ var __ktjs_router__ = (function (exports) {
373
423
  fullPath: fullPath,
374
424
  };
375
425
  };
376
- var navigateSync = function (options) {
426
+ var navigateSync = function (options, redirectCount) {
377
427
  var _a;
428
+ if (redirectCount === void 0) { redirectCount = 0; }
378
429
  try {
430
+ // Prevent infinite redirect loop
431
+ if (redirectCount > 10) {
432
+ onError(new Error('Maximum redirect count exceeded'));
433
+ return false;
434
+ }
379
435
  var prep = navigatePrepare(options);
380
436
  if (!prep) {
381
437
  return false;
382
438
  }
383
439
  var guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
384
440
  // Execute guards
385
- if (!executeGuardsSync(to, current, guardLevel)) {
441
+ var guardResult = executeGuardsSync(to, current, guardLevel);
442
+ if (!guardResult.continue) {
443
+ // Check if there's a redirect
444
+ if (guardResult.redirectTo) {
445
+ return navigateSync(guardResult.redirectTo, redirectCount + 1);
446
+ }
386
447
  return false;
387
448
  }
388
449
  // Update browser history
@@ -396,6 +457,23 @@ var __ktjs_router__ = (function (exports) {
396
457
  // Update current route
397
458
  current = to;
398
459
  history.push(to);
460
+ // Render component if routerView exists
461
+ if (routerView && to.matched.length > 0) {
462
+ var route = to.matched[to.matched.length - 1];
463
+ if (route.component) {
464
+ var element = route.component();
465
+ if (element instanceof Promise) {
466
+ element.then(function (el) {
467
+ routerView.innerHTML = '';
468
+ routerView.appendChild(el);
469
+ });
470
+ }
471
+ else {
472
+ routerView.innerHTML = '';
473
+ routerView.appendChild(element);
474
+ }
475
+ }
476
+ }
399
477
  // Execute after hooks
400
478
  executeAfterHooksSync(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
401
479
  return true;
@@ -405,41 +483,69 @@ var __ktjs_router__ = (function (exports) {
405
483
  return false;
406
484
  }
407
485
  };
408
- var navigateAsync = function (options) { return __awaiter(void 0, void 0, void 0, function () {
409
- var prep, guardLevel, replace, to, fullPath, passed, url, error_2;
410
- var _a;
411
- return __generator(this, function (_b) {
412
- switch (_b.label) {
413
- case 0:
414
- _b.trys.push([0, 2, , 3]);
415
- prep = navigatePrepare(options);
416
- if (!prep) {
417
- return [2 /*return*/, false];
418
- }
419
- guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
420
- return [4 /*yield*/, executeGuards(to, current, guardLevel)];
421
- case 1:
422
- passed = _b.sent();
423
- if (!passed) {
486
+ var navigateAsync = function (options_1) {
487
+ var args_1 = [];
488
+ for (var _i = 1; _i < arguments.length; _i++) {
489
+ args_1[_i - 1] = arguments[_i];
490
+ }
491
+ return __awaiter(void 0, __spreadArray([options_1], args_1, true), void 0, function (options, redirectCount) {
492
+ var prep, guardLevel, replace, to, fullPath, guardResult, url, route, element, error_2;
493
+ var _a;
494
+ if (redirectCount === void 0) { redirectCount = 0; }
495
+ return __generator(this, function (_b) {
496
+ switch (_b.label) {
497
+ case 0:
498
+ _b.trys.push([0, 4, , 5]);
499
+ // Prevent infinite redirect loop
500
+ if (redirectCount > 10) {
501
+ onError(new Error('Maximum redirect count exceeded'));
502
+ return [2 /*return*/, false];
503
+ }
504
+ prep = navigatePrepare(options);
505
+ if (!prep) {
506
+ return [2 /*return*/, false];
507
+ }
508
+ guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
509
+ return [4 /*yield*/, executeGuards(to, current, guardLevel)];
510
+ case 1:
511
+ guardResult = _b.sent();
512
+ if (!guardResult.continue) {
513
+ // Check if there's a redirect
514
+ if (guardResult.redirectTo) {
515
+ return [2 /*return*/, navigateAsync(guardResult.redirectTo, redirectCount + 1)];
516
+ }
517
+ return [2 /*return*/, false];
518
+ }
519
+ url = fullPath;
520
+ if (replace) {
521
+ window.history.replaceState({ path: to.path }, '', url);
522
+ }
523
+ else {
524
+ window.history.pushState({ path: to.path }, '', url);
525
+ }
526
+ current = to;
527
+ history.push(to);
528
+ if (!(routerView && to.matched.length > 0)) return [3 /*break*/, 3];
529
+ route = to.matched[to.matched.length - 1];
530
+ if (!route.component) return [3 /*break*/, 3];
531
+ return [4 /*yield*/, route.component()];
532
+ case 2:
533
+ element = _b.sent();
534
+ routerView.innerHTML = '';
535
+ routerView.appendChild(element);
536
+ _b.label = 3;
537
+ case 3:
538
+ executeAfterHooks(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
539
+ return [2 /*return*/, true];
540
+ case 4:
541
+ error_2 = _b.sent();
542
+ onError(error_2);
424
543
  return [2 /*return*/, false];
425
- }
426
- url = fullPath;
427
- if (replace) {
428
- window.history.replaceState({ path: to.path }, '', url);
429
- }
430
- else {
431
- window.history.pushState({ path: to.path }, '', url);
432
- }
433
- executeAfterHooks(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
434
- return [2 /*return*/, true];
435
- case 2:
436
- error_2 = _b.sent();
437
- onError(error_2);
438
- return [2 /*return*/, false];
439
- case 3: return [2 /*return*/];
440
- }
544
+ case 5: return [2 /*return*/];
545
+ }
546
+ });
441
547
  });
442
- }); };
548
+ };
443
549
  var navigate = asyncGuards ? navigateSync : navigateAsync;
444
550
  var executeAfterHooksSync = function (to, from) {
445
551
  var targetRoute = to.matched[to.matched.length - 1];
@@ -492,6 +598,9 @@ var __ktjs_router__ = (function (exports) {
492
598
  get history() {
493
599
  return history.concat();
494
600
  },
601
+ setRouterView: function (view) {
602
+ routerView = view;
603
+ },
495
604
  push: function (location) {
496
605
  var options = normalizeLocation(location);
497
606
  return navigate(options);
@@ -513,6 +622,7 @@ var __ktjs_router__ = (function (exports) {
513
622
  };
514
623
  };
515
624
 
625
+ exports.KTRouter = KTRouter;
516
626
  exports.createRouter = createRouter;
517
627
 
518
628
  return exports;
package/dist/index.mjs CHANGED
@@ -147,6 +147,15 @@ const createMatcher = (routes) => {
147
147
  };
148
148
  };
149
149
 
150
+ /**
151
+ * Create a router view container that automatically renders route components
152
+ */
153
+ function KTRouter({ router }) {
154
+ const view = document.createElement('kt-router-view');
155
+ router.setRouterView(view);
156
+ return view;
157
+ }
158
+
150
159
  /**
151
160
  * Create a new router instance
152
161
  */
@@ -159,6 +168,7 @@ const createRouter = (config) => {
159
168
  const asyncGuards = config.asyncGuards ?? true;
160
169
  // # private values
161
170
  const routes = [];
171
+ let routerView = null;
162
172
  /**
163
173
  * Normalize routes by adding default guards
164
174
  */
@@ -171,6 +181,7 @@ const createRouter = (config) => {
171
181
  beforeEnter: route.beforeEnter ?? defaultHook,
172
182
  after: route.after ?? defaultHook,
173
183
  children: route.children ? normalize(route.children, path) : [],
184
+ component: route.component,
174
185
  };
175
186
  // directly push the normalized route to the list
176
187
  // avoid flatten them again
@@ -186,49 +197,75 @@ const createRouter = (config) => {
186
197
  const executeGuardsSync = (to, from, guardLevel) => {
187
198
  try {
188
199
  if (guardLevel === 0 /* GuardLevel.None */) {
189
- return true;
200
+ return { continue: true };
190
201
  }
191
202
  if (guardLevel & 1 /* GuardLevel.Global */) {
192
203
  const result = beforeEach(to, from);
193
204
  if (result === false) {
194
- return false;
205
+ return { continue: false };
206
+ }
207
+ if (typeof result === 'string') {
208
+ return { continue: false, redirectTo: { path: result } };
209
+ }
210
+ if (result && typeof result === 'object' && !('then' in result)) {
211
+ return { continue: false, redirectTo: result };
195
212
  }
196
213
  }
197
214
  if (guardLevel & 2 /* GuardLevel.Route */) {
198
215
  const targetRoute = to.matched[to.matched.length - 1];
199
216
  const result = targetRoute.beforeEnter(to);
200
217
  if (result === false) {
201
- return false;
218
+ return { continue: false };
219
+ }
220
+ if (typeof result === 'string') {
221
+ return { continue: false, redirectTo: { path: result } };
222
+ }
223
+ if (result && typeof result === 'object' && !('then' in result)) {
224
+ return { continue: false, redirectTo: result };
202
225
  }
203
226
  }
204
- return true;
227
+ return { continue: true };
205
228
  }
206
229
  catch (error) {
207
230
  onError(error);
208
- return false;
231
+ return { continue: false };
209
232
  }
210
233
  };
211
234
  const executeGuards = async (to, from, guardLevel) => {
212
235
  try {
213
236
  if (guardLevel === 0 /* GuardLevel.None */) {
214
- return true;
237
+ return { continue: true };
215
238
  }
216
239
  if (guardLevel & 1 /* GuardLevel.Global */) {
217
240
  const result = await beforeEach(to, from);
218
241
  if (result === false) {
219
- return false;
242
+ return { continue: false };
243
+ }
244
+ if (typeof result === 'string') {
245
+ return { continue: false, redirectTo: { path: result } };
246
+ }
247
+ if (result && typeof result === 'object') {
248
+ return { continue: false, redirectTo: result };
220
249
  }
221
250
  }
222
251
  if (guardLevel & 2 /* GuardLevel.Route */) {
223
252
  const targetRoute = to.matched[to.matched.length - 1];
224
- const result = targetRoute.beforeEnter(to);
225
- return result !== false;
253
+ const result = await targetRoute.beforeEnter(to);
254
+ if (result === false) {
255
+ return { continue: false };
256
+ }
257
+ if (typeof result === 'string') {
258
+ return { continue: false, redirectTo: { path: result } };
259
+ }
260
+ if (result && typeof result === 'object') {
261
+ return { continue: false, redirectTo: result };
262
+ }
226
263
  }
227
- return true;
264
+ return { continue: true };
228
265
  }
229
266
  catch (error) {
230
267
  onError(error);
231
- return false;
268
+ return { continue: false };
232
269
  }
233
270
  };
234
271
  const navigatePrepare = (options) => {
@@ -277,15 +314,25 @@ const createRouter = (config) => {
277
314
  fullPath,
278
315
  };
279
316
  };
280
- const navigateSync = (options) => {
317
+ const navigateSync = (options, redirectCount = 0) => {
281
318
  try {
319
+ // Prevent infinite redirect loop
320
+ if (redirectCount > 10) {
321
+ onError(new Error('Maximum redirect count exceeded'));
322
+ return false;
323
+ }
282
324
  const prep = navigatePrepare(options);
283
325
  if (!prep) {
284
326
  return false;
285
327
  }
286
328
  const { guardLevel, replace, to, fullPath } = prep;
287
329
  // Execute guards
288
- if (!executeGuardsSync(to, current, guardLevel)) {
330
+ const guardResult = executeGuardsSync(to, current, guardLevel);
331
+ if (!guardResult.continue) {
332
+ // Check if there's a redirect
333
+ if (guardResult.redirectTo) {
334
+ return navigateSync(guardResult.redirectTo, redirectCount + 1);
335
+ }
289
336
  return false;
290
337
  }
291
338
  // Update browser history
@@ -299,6 +346,23 @@ const createRouter = (config) => {
299
346
  // Update current route
300
347
  current = to;
301
348
  history.push(to);
349
+ // Render component if routerView exists
350
+ if (routerView && to.matched.length > 0) {
351
+ const route = to.matched[to.matched.length - 1];
352
+ if (route.component) {
353
+ const element = route.component();
354
+ if (element instanceof Promise) {
355
+ element.then((el) => {
356
+ routerView.innerHTML = '';
357
+ routerView.appendChild(el);
358
+ });
359
+ }
360
+ else {
361
+ routerView.innerHTML = '';
362
+ routerView.appendChild(element);
363
+ }
364
+ }
365
+ }
302
366
  // Execute after hooks
303
367
  executeAfterHooksSync(to, history[history.length - 2] ?? null);
304
368
  return true;
@@ -308,15 +372,24 @@ const createRouter = (config) => {
308
372
  return false;
309
373
  }
310
374
  };
311
- const navigateAsync = async (options) => {
375
+ const navigateAsync = async (options, redirectCount = 0) => {
312
376
  try {
377
+ // Prevent infinite redirect loop
378
+ if (redirectCount > 10) {
379
+ onError(new Error('Maximum redirect count exceeded'));
380
+ return false;
381
+ }
313
382
  const prep = navigatePrepare(options);
314
383
  if (!prep) {
315
384
  return false;
316
385
  }
317
386
  const { guardLevel, replace, to, fullPath } = prep;
318
- const passed = await executeGuards(to, current, guardLevel);
319
- if (!passed) {
387
+ const guardResult = await executeGuards(to, current, guardLevel);
388
+ if (!guardResult.continue) {
389
+ // Check if there's a redirect
390
+ if (guardResult.redirectTo) {
391
+ return navigateAsync(guardResult.redirectTo, redirectCount + 1);
392
+ }
320
393
  return false;
321
394
  }
322
395
  // ---- Guards passed ----
@@ -327,6 +400,17 @@ const createRouter = (config) => {
327
400
  else {
328
401
  window.history.pushState({ path: to.path }, '', url);
329
402
  }
403
+ current = to;
404
+ history.push(to);
405
+ // Render component if routerView exists
406
+ if (routerView && to.matched.length > 0) {
407
+ const route = to.matched[to.matched.length - 1];
408
+ if (route.component) {
409
+ const element = await route.component();
410
+ routerView.innerHTML = '';
411
+ routerView.appendChild(element);
412
+ }
413
+ }
330
414
  executeAfterHooks(to, history[history.length - 2] ?? null);
331
415
  return true;
332
416
  }
@@ -375,6 +459,9 @@ const createRouter = (config) => {
375
459
  get history() {
376
460
  return history.concat();
377
461
  },
462
+ setRouterView(view) {
463
+ routerView = view;
464
+ },
378
465
  push(location) {
379
466
  const options = normalizeLocation(location);
380
467
  return navigate(options);
@@ -396,4 +483,4 @@ const createRouter = (config) => {
396
483
  };
397
484
  };
398
485
 
399
- export { createRouter };
486
+ export { KTRouter, createRouter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/router",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Router for kt.js - client-side routing with navigation guards",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -31,7 +31,7 @@
31
31
  "directory": "packages/router"
32
32
  },
33
33
  "dependencies": {
34
- "@ktjs/core": "0.13.0"
34
+ "@ktjs/core": "0.14.3"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "rollup -c rollup.config.mjs",