@iyulab/router 0.5.1 → 0.5.2

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +162 -108
  2. package/dist/index.js +154 -100
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -18,22 +18,38 @@ declare interface BaseRouteConfig {
18
18
  */
19
19
  title?: string;
20
20
  /**
21
- * 라우트에 대응하는 렌더링 함수
22
- * - 라우트 정보를 받아 HTMLElement, ReactElement, 또는 LitElement TemplateResult를 반환합니다.
23
- * @param info 라우팅 정보
21
+ * 라우터 경로는 string 또는 URLPattern을 사용할 수 있습니다.
22
+ * string일 경우 자동으로 URLPattern으로 변환됩니다.
23
+ * @default '/'
24
+ * @example
25
+ * - "/user/:id/:name"
26
+ * - "/user/:id/:name?"
27
+ * - "/user/:id/:name*"
28
+ * - "/user/:id/:name+"
29
+ * - "/user/:id/:name{1,3}"
30
+ * @link
31
+ * https://developer.mozilla.org/en-US/docs/Web/API/URLPattern
32
+ */
33
+ path?: string | URLPattern;
34
+ /**
35
+ * 라우트 정보를 받아 렌더링 결과를 반환합니다.
36
+ * @param ctx 현재 라우팅 정보 및, 진행 상태 콜백을 포함하는 Context 객체가 인자로 전달됩니다.
24
37
  * @example
25
38
  * ```typescript
26
39
  * const route = {
27
- * path: '/user',
28
- * render: (info) => html`<user-page .routeInfo=${info}></user-page>`,
40
+ * path: '/user:id',
41
+ * render: async (ctx) => {
42
+ * // 사용자 정보를 비동기로 가져오는 예시
43
+ * const userId = ctx.params.id;
44
+ * ctx.progress(30);
45
+ * const userData = await fetchUserData(userId);
46
+ * ctx.progress(70);
47
+ * return html`<user-profile .data=${userData}></user-profile>`;
48
+ * }
29
49
  * }
30
50
  * ```
31
51
  */
32
- render?: (info: RouteInfo) => Promise<RenderResult> | RenderResult;
33
- /**
34
- * 중첩 라우트
35
- */
36
- children?: RouteConfig[];
52
+ render?: (ctx: RouteContext) => Promise<RenderResult> | RenderResult;
37
53
  /**
38
54
  * 라우터 URL 변경시 렌더링을 강제할지 여부
39
55
  * - 기본값으로 children을 가질때 false로 설정되며, children이 없을 경우 true로 설정됩니다.
@@ -56,20 +72,47 @@ export declare class ContentRenderError extends RouteError {
56
72
  constructor(original?: Error | any);
57
73
  }
58
74
 
59
- /**
60
- * 인덱스 라우트 타입
61
- */
62
- declare interface IndexRouteConfig extends BaseRouteConfig {
75
+ export declare type FallbackRenderResult = HTMLElement | ReactElement | TemplateResult<1>;
76
+
77
+ export declare interface FallbackRouteConfig {
63
78
  /**
64
- * true일 경우 인덱스 라우트입니다.
65
- * path는 강제로 빈 문자열로 설정됩니다.
79
+ * 브라우저의 타이틀이 설정에 따라 변경됩니다.
66
80
  */
67
- index: true;
81
+ title?: string;
68
82
  /**
69
- * 인덱스 라우트의 URLPattern (자동 설정됨)
70
- * 부모 라우트의 basepath를 상속받습니다.
83
+ * 라우팅 실패 표시할 렌더링 결과를 반환합니다.
84
+ * - 오류가 발생할 경우 또는 렌더링 결과가 false일 경우 호출됩니다.
85
+ * @param ctx 현재 라우팅 정보 및 오류 정보를 포함하는 Context 객체가 인자로 전달됩니다.
86
+ * @example
87
+ * ```typescript
88
+ * const fallbackRoute = {
89
+ * title: 'Not Found',
90
+ * render: (ctx) => {
91
+ * if (ctx.error) {
92
+ * return html`<error-page .error=${ctx.error}></error-page>`;
93
+ * }
94
+ * return html`<not-found-page></not-found-page>`;
95
+ * }
96
+ * }
97
+ * ```
71
98
  */
72
- path?: URLPattern;
99
+ render?: (ctx: FallbackRouteContext) => Promise<FallbackRenderResult> | FallbackRenderResult;
100
+ }
101
+
102
+ export declare interface FallbackRouteContext extends RouteContext {
103
+ /**
104
+ * 라우팅 에러 정보
105
+ * - 라우팅 중 발생한 에러 정보를 포함합니다.
106
+ */
107
+ error: RouteError;
108
+ }
109
+
110
+ declare interface IndexRouteConfig extends BaseRouteConfig {
111
+ /**
112
+ * 현재 경로의 인덱스 라우트임을 나타냅니다.
113
+ * - 인덱스 라우트는 부모 경로와 동일한 경로를 가지며, path는 자동으로 설정됩니다.
114
+ */
115
+ index: true;
73
116
  }
74
117
 
75
118
  /**
@@ -113,6 +156,18 @@ export declare class Link extends LitElement {
113
156
  static styles: CSSResult;
114
157
  }
115
158
 
159
+ declare interface NonIndexRouteConfig extends BaseRouteConfig {
160
+ /**
161
+ * 인덱스 라우트가 아님을 나타냅니다.
162
+ */
163
+ index?: false;
164
+ /**
165
+ * 하위 라우트 설정, 재귀적으로 RouteConfig 배열을 가질 수 있습니다.
166
+ * - 하위 라우트가 있는 경우, 부모 라우트의 경로를 기준으로 매칭됩니다.
167
+ */
168
+ children?: RouteConfig[];
169
+ }
170
+
116
171
  /**
117
172
  * 페이지를 찾을 수 없을 때 발생하는 에러
118
173
  */
@@ -149,101 +204,27 @@ export declare class OutletMissingError extends RouteError {
149
204
  constructor();
150
205
  }
151
206
 
152
- /**
153
- * 경로 라우트 타입
154
- */
155
- declare interface PathRouteConfig extends BaseRouteConfig {
156
- /**
157
- * 라우터 경로는 string 또는 URLPattern을 사용할 수 있습니다.
158
- * string일 경우 자동으로 URLPattern으로 변환됩니다.
159
- * @example
160
- * - "/user/:id/:name"
161
- * - "/user/:id/:name?"
162
- * - "/user/:id/:name*"
163
- * - "/user/:id/:name+"
164
- * - "/user/:id/:name{1,3}"
165
- * @link
166
- * https://developer.mozilla.org/en-US/docs/Web/API/URLPattern
167
- */
168
- path: string | URLPattern;
169
- }
170
-
171
207
  declare interface RenderOption {
172
208
  id?: string;
173
209
  force?: boolean;
174
210
  content: RenderResult;
175
211
  }
176
212
 
177
- declare type RenderResult = HTMLElement | ReactElement | TemplateResult<1>;
213
+ export declare type RenderResult = HTMLElement | ReactElement | TemplateResult<1> | false;
178
214
 
179
215
  /**
180
216
  * 라우트 시작 이벤트
181
217
  */
182
218
  export declare class RouteBeginEvent extends RouteEvent {
183
- constructor(routeInfo: RouteInfo);
184
- }
185
-
186
- /**
187
- * 라우트 타입 (인덱스 라우트 또는 경로 라우트)
188
- */
189
- export declare type RouteConfig = IndexRouteConfig | PathRouteConfig;
190
-
191
- /**
192
- * 라우트 완료 이벤트
193
- */
194
- export declare class RouteDoneEvent extends RouteEvent {
195
- constructor(routeInfo: RouteInfo);
196
- }
197
-
198
- /**
199
- * 라우팅 에러 정보
200
- */
201
- export declare class RouteError extends Error {
202
- /**
203
- * 에러 코드
204
- * - HTTP 상태 코드 또는 커스텀 에러 코드
205
- * @example 404, 500, 'ROUTE_NOT_FOUND'
206
- */
207
- code: number | string;
208
- /**
209
- * 원본 에러 객체
210
- * - 원본 Error 객체 또는 예외 정보
211
- */
212
- original?: Error | any;
213
- /**
214
- * 에러 발생 시간
215
- * - 에러가 발생한 시간 (ISO 8601 형식)
216
- */
217
- timestamp: string;
218
- constructor(code: number | string, message: string, original?: Error | any);
219
+ constructor(context: RouteContext);
219
220
  }
220
221
 
221
- /**
222
- * 라우트 에러 이벤트
223
- */
224
- export declare class RouteErrorEvent extends RouteEvent {
225
- /** 에러 정보 */
226
- readonly error: RouteError;
227
- constructor(error: RouteError, routeInfo: RouteInfo);
228
- }
229
-
230
- /** 라우터 이벤트 기본 클래스 */
231
- declare abstract class RouteEvent extends Event {
232
- /** 라우팅 정보 */
233
- readonly routeInfo: RouteInfo;
234
- /** 이벤트 발생 시간 */
235
- readonly timestamp: string;
236
- constructor(type: string, routeInfo: RouteInfo, cancelable?: boolean);
237
- /** 이벤트가 취소되었는지 확인 */
238
- get cancelled(): boolean;
239
- /** 이벤트 취소 */
240
- cancel(): void;
241
- }
222
+ export declare type RouteConfig = IndexRouteConfig | NonIndexRouteConfig;
242
223
 
243
224
  /**
244
225
  * 라우터 정보
245
226
  */
246
- export declare interface RouteInfo {
227
+ export declare interface RouteContext {
247
228
  /**
248
229
  * 전체 URL 정보
249
230
  * - 도메인 이름을 포함한 URL의 전체 경로입니다.
@@ -305,6 +286,73 @@ export declare interface RouteInfo {
305
286
  * @example #profile
306
287
  */
307
288
  hash?: string;
289
+ /**
290
+ * 현재 라우팅의 진행 상태를 업데이트하여, window 객체의 'route-progress' 이벤트를 트리거합니다.
291
+ * 여러 라우팅이 호출되는 경우, 가장 최근의 라우팅의 진행 상태만 반영되며, 나머지는 무시됩니다.
292
+ * @param value 진행 상태 값 (0~100)
293
+ */
294
+ progress: (value: number) => void;
295
+ }
296
+
297
+ /**
298
+ * 라우트 완료 이벤트
299
+ */
300
+ export declare class RouteDoneEvent extends RouteEvent {
301
+ constructor(context: RouteContext);
302
+ }
303
+
304
+ /**
305
+ * 라우팅 에러 정보
306
+ */
307
+ export declare class RouteError extends Error {
308
+ /**
309
+ * 에러 코드
310
+ * - HTTP 상태 코드 또는 커스텀 에러 코드
311
+ * @example 404, 500, 'ROUTE_NOT_FOUND'
312
+ */
313
+ code: number | string;
314
+ /**
315
+ * 원본 에러 객체
316
+ * - 원본 Error 객체 또는 예외 정보
317
+ */
318
+ original?: Error | any;
319
+ /**
320
+ * 에러 발생 시간
321
+ * - 에러가 발생한 시간 (ISO 8601 형식)
322
+ */
323
+ timestamp: string;
324
+ constructor(code: number | string, message: string, original?: Error | any);
325
+ }
326
+
327
+ /**
328
+ * 라우트 에러 이벤트
329
+ */
330
+ export declare class RouteErrorEvent extends RouteEvent {
331
+ /** 에러 정보 */
332
+ readonly error: RouteError;
333
+ constructor(context: RouteContext, error: RouteError);
334
+ }
335
+
336
+ /** 라우터 이벤트 기본 클래스 */
337
+ declare abstract class RouteEvent extends Event {
338
+ /** 라우팅 정보 */
339
+ readonly context: RouteContext;
340
+ /** 이벤트 발생 시간 */
341
+ readonly timestamp: string;
342
+ constructor(type: string, context: RouteContext, cancelable?: boolean);
343
+ /** 이벤트가 취소되었는지 확인 */
344
+ get cancelled(): boolean;
345
+ /** 이벤트 취소 */
346
+ cancel(): void;
347
+ }
348
+
349
+ /**
350
+ * 라우트 진행 이벤트
351
+ */
352
+ export declare class RouteProgressEvent extends RouteEvent {
353
+ /** 진행 상태 값 (0~100) */
354
+ readonly progress: number;
355
+ constructor(context: RouteContext, progress: number);
308
356
  }
309
357
 
310
358
  /**
@@ -314,21 +362,20 @@ export declare class Router {
314
362
  private readonly _rootElement;
315
363
  private readonly _basepath;
316
364
  private readonly _routes;
365
+ private readonly _fallback?;
317
366
  /** 현재 라우팅 요청 ID */
318
367
  private _requestID?;
319
368
  /** 현재 라우팅 정보 */
320
- private _routeInfo?;
369
+ private _context?;
321
370
  constructor(config: RouterConfig);
322
- /** 초기 라우팅 처리, TODO: 제거 */
323
- private waitConnected;
324
371
  /** 객체를 정리하고 이벤트 리스너를 제거합니다. */
325
372
  destroy(): void;
326
373
  /** 라우터의 기본 경로 반환 */
327
374
  get basepath(): string;
328
- /** 등록된 라우트 반환 */
375
+ /** 등록된 라우트 정보 반환 */
329
376
  get routes(): RouteConfig[];
330
377
  /** 현재 라우팅 정보 반환 */
331
- get routeInfo(): RouteInfo | undefined;
378
+ get context(): RouteContext | undefined;
332
379
  /**
333
380
  * 지정한 경로의 클라이언트 라우팅을 수행합니다. 상대경로일 경우 basepath와 조합되어 이동합니다.
334
381
  * @param href 이동할 경로
@@ -360,12 +407,22 @@ export declare interface RouterConfig {
360
407
  * - 라우트는 URLPattern을 사용하여 경로를 탐색합니다.
361
408
  * - 라우트는 렌더링할 엘리먼트 또는 컴포넌트를 지정합니다.
362
409
  */
363
- routes: RouteConfig[];
410
+ routes?: RouteConfig[];
411
+ /**
412
+ * 라우트 매칭 실패 또는 오류 발생 시 대체 라우트 설정
413
+ * - 지정된 설정이 없을 경우, 기본 오류 페이지가 렌더링됩니다.
414
+ */
415
+ fallback?: FallbackRouteConfig;
364
416
  /**
365
417
  * `a` 태그 클릭 시 클라이언트 라우팅을 수행할지 여부를 설정합니다.
366
418
  * @default true
367
419
  */
368
420
  useIntercept?: boolean;
421
+ /**
422
+ * 초기 로드 시 현재 URL로 라우팅을 자동으로 수행할지 여부를 설정합니다.
423
+ * @default true
424
+ */
425
+ initialLoad?: boolean;
369
426
  }
370
427
 
371
428
  export declare const ULink: ReactWebComponent<Link, {}>;
@@ -375,12 +432,9 @@ export declare const UOutlet: ReactWebComponent<Outlet, {}>;
375
432
  export { }
376
433
 
377
434
  declare global {
378
- interface Window {
379
- route: RouteInfo;
380
- }
381
-
382
435
  interface WindowEventMap {
383
436
  'route-begin': RouteBeginEvent;
437
+ 'route-progress': RouteProgressEvent;
384
438
  'route-done': RouteDoneEvent;
385
439
  'route-error': RouteErrorEvent;
386
440
  }
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import React from "react";
2
2
  import { LitElement, html, css, render } from "lit";
3
3
  import { state, property, customElement } from "lit/decorators.js";
4
4
  import { createRoot } from "react-dom/client";
5
- import { unsafeSVG } from "lit/directives/unsafe-svg.js";
5
+ import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
6
6
  const e = /* @__PURE__ */ new Set(["children", "localName", "ref", "style", "className"]), n = /* @__PURE__ */ new WeakMap(), t = (e2, t2, o2, l, a) => {
7
7
  const s = a?.[t2];
8
8
  void 0 === s ? (e2[t2] = o2, null == o2 && t2 in HTMLElement.prototype && e2.removeAttribute(t2)) : o2 !== l && ((e3, t3, o3) => {
@@ -65,7 +65,9 @@ function parseUrl(url, basepath) {
65
65
  pathname: urlObj.pathname,
66
66
  query: new URLSearchParams(urlObj.search),
67
67
  hash: urlObj.hash,
68
- params: {}
68
+ params: {},
69
+ progress: () => {
70
+ }
69
71
  };
70
72
  }
71
73
  function absolutePath(...paths) {
@@ -214,7 +216,10 @@ class Outlet extends LitElement {
214
216
  this.routeId = id;
215
217
  this.clear();
216
218
  if (!this.container) {
217
- throw new Error("DOM이 초기화되지 않았습니다.");
219
+ throw new Error("Outlet container is not initialized.");
220
+ }
221
+ if (content === false) {
222
+ throw new Error("Content is false, cannot render.");
218
223
  }
219
224
  if (content instanceof HTMLElement) {
220
225
  this.container.appendChild(content);
@@ -292,9 +297,9 @@ class ContentRenderError extends RouteError {
292
297
  }
293
298
  }
294
299
  class RouteEvent extends Event {
295
- constructor(type, routeInfo, cancelable = false) {
300
+ constructor(type, context, cancelable = false) {
296
301
  super(type, { bubbles: true, composed: true, cancelable });
297
- this.routeInfo = routeInfo;
302
+ this.context = context;
298
303
  this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
299
304
  }
300
305
  /** 이벤트가 취소되었는지 확인 */
@@ -309,30 +314,72 @@ class RouteEvent extends Event {
309
314
  }
310
315
  }
311
316
  class RouteBeginEvent extends RouteEvent {
312
- constructor(routeInfo) {
313
- super("route-begin", routeInfo, false);
317
+ constructor(context) {
318
+ super("route-begin", context, false);
319
+ }
320
+ }
321
+ class RouteProgressEvent extends RouteEvent {
322
+ constructor(context, progress) {
323
+ super("route-progress", context, false);
324
+ this.progress = progress;
314
325
  }
315
326
  }
316
327
  class RouteDoneEvent extends RouteEvent {
317
- constructor(routeInfo) {
318
- super("route-done", routeInfo, false);
328
+ constructor(context) {
329
+ super("route-done", context, false);
319
330
  }
320
331
  }
321
332
  class RouteErrorEvent extends RouteEvent {
322
- constructor(error, routeInfo) {
323
- super("route-error", routeInfo, false);
333
+ constructor(context, error) {
334
+ super("route-error", context, false);
324
335
  this.error = error;
325
336
  }
326
337
  }
327
- const __vite_glob_0_0 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M15 8a6.97 6.97 0 0 0-1.71-4.584l-9.874 9.875A7 7 0 0 0 15 8M2.71 12.584l9.874-9.875a7 7 0 0 0-9.874 9.874ZM16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0"/>\n</svg>';
328
- const __vite_glob_0_1 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2zm3.564 1.426L5.596 5 8 5.961 14.154 3.5zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z"/>\n</svg>';
329
- const __vite_glob_0_2 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.15.15 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.2.2 0 0 1-.054.06.1.1 0 0 1-.066.017H1.146a.1.1 0 0 1-.066-.017.2.2 0 0 1-.054-.06.18.18 0 0 1 .002-.183L7.884 2.073a.15.15 0 0 1 .054-.057m1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767z"/>\n <path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>\n</svg>';
330
- const __vite_glob_0_3 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M8 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3m4 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M5.5 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3"/>\n <path d="M16 8c0 3.15-1.866 2.585-3.567 2.07C11.42 9.763 10.465 9.473 10 10c-.603.683-.475 1.819-.351 2.92C9.826 14.495 9.996 16 8 16a8 8 0 1 1 8-8m-8 7c.611 0 .654-.171.655-.176.078-.146.124-.464.07-1.119-.014-.168-.037-.37-.061-.591-.052-.464-.112-1.005-.118-1.462-.01-.707.083-1.61.704-2.314.369-.417.845-.578 1.272-.618.404-.038.812.026 1.16.104.343.077.702.186 1.025.284l.028.008c.346.105.658.199.953.266.653.148.904.083.991.024C14.717 9.38 15 9.161 15 8a7 7 0 1 0-7 7"/>\n</svg>';
331
- const __vite_glob_0_4 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M11 5a3 3 0 1 1-6 0 3 3 0 0 1 6 0M8 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m0 5.996V14H3s-1 0-1-1 1-4 6-4q.845.002 1.544.107a4.5 4.5 0 0 0-.803.918A11 11 0 0 0 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664zM9 13a1 1 0 0 1 1-1v-1a2 2 0 1 1 4 0v1a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1zm3-3a1 1 0 0 0-1 1v1h2v-1a1 1 0 0 0-1-1"/>\n</svg>';
332
- const __vite_glob_0_5 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>\n</svg>';
333
- const __vite_glob_0_6 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5z"/>\n <path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64l.012-.013.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5M8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3"/>\n</svg>';
334
- const __vite_glob_0_7 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M10.706 3.294A12.6 12.6 0 0 0 8 3C5.259 3 2.723 3.882.663 5.379a.485.485 0 0 0-.048.736.52.52 0 0 0 .668.05A11.45 11.45 0 0 1 8 4q.946 0 1.852.148zM8 6c-1.905 0-3.68.56-5.166 1.526a.48.48 0 0 0-.063.745.525.525 0 0 0 .652.065 8.45 8.45 0 0 1 3.51-1.27zm2.596 1.404.785-.785q.947.362 1.785.907a.482.482 0 0 1 .063.745.525.525 0 0 1-.652.065 8.5 8.5 0 0 0-1.98-.932zM8 10l.933-.933a6.5 6.5 0 0 1 2.013.637c.285.145.326.524.1.75l-.015.015a.53.53 0 0 1-.611.09A5.5 5.5 0 0 0 8 10m4.905-4.905.747-.747q.886.451 1.685 1.03a.485.485 0 0 1 .047.737.52.52 0 0 1-.668.05 11.5 11.5 0 0 0-1.811-1.07M9.02 11.78c.238.14.236.464.04.66l-.707.706a.5.5 0 0 1-.707 0l-.707-.707c-.195-.195-.197-.518.04-.66A2 2 0 0 1 8 11.5c.374 0 .723.102 1.021.28zm4.355-9.905a.53.53 0 0 1 .75.75l-10.75 10.75a.53.53 0 0 1-.75-.75z"/>\n</svg>';
335
- const __vite_glob_0_8 = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M16 4.5a4.5 4.5 0 0 1-1.703 3.526L13 5l2.959-1.11q.04.3.041.61"/>\n <path d="M11.5 9c.653 0 1.273-.139 1.833-.39L12 5.5 11 3l3.826-1.53A4.5 4.5 0 0 0 7.29 6.092l-6.116 5.096a2.583 2.583 0 1 0 3.638 3.638L9.908 8.71A4.5 4.5 0 0 0 11.5 9m-1.292-4.361-.596.893.809-.27a.25.25 0 0 1 .287.377l-.596.893.809-.27.158.475-1.5.5a.25.25 0 0 1-.287-.376l.596-.893-.809.27a.25.25 0 0 1-.287-.377l.596-.893-.809.27-.158-.475 1.5-.5a.25.25 0 0 1 .287.376M3 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2"/>\n</svg>';
338
+ const ban = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M15 8a6.97 6.97 0 0 0-1.71-4.584l-9.874 9.875A7 7 0 0 0 15 8M2.71 12.584l9.874-9.875a7 7 0 0 0-9.874 9.874ZM16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0"/>\n</svg>';
339
+ const __vite_glob_0_0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
340
+ __proto__: null,
341
+ default: ban
342
+ }, Symbol.toStringTag, { value: "Module" }));
343
+ const boxSeam = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5l2.404.961L10.404 2zm3.564 1.426L5.596 5 8 5.961 14.154 3.5zm3.25 1.7-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z"/>\n</svg>';
344
+ const __vite_glob_0_1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
345
+ __proto__: null,
346
+ default: boxSeam
347
+ }, Symbol.toStringTag, { value: "Module" }));
348
+ const exclamationTriangle = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.15.15 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.2.2 0 0 1-.054.06.1.1 0 0 1-.066.017H1.146a.1.1 0 0 1-.066-.017.2.2 0 0 1-.054-.06.18.18 0 0 1 .002-.183L7.884 2.073a.15.15 0 0 1 .054-.057m1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767z"/>\n <path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>\n</svg>';
349
+ const __vite_glob_0_2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
350
+ __proto__: null,
351
+ default: exclamationTriangle
352
+ }, Symbol.toStringTag, { value: "Module" }));
353
+ const palette = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M8 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3m4 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3M5.5 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3"/>\n <path d="M16 8c0 3.15-1.866 2.585-3.567 2.07C11.42 9.763 10.465 9.473 10 10c-.603.683-.475 1.819-.351 2.92C9.826 14.495 9.996 16 8 16a8 8 0 1 1 8-8m-8 7c.611 0 .654-.171.655-.176.078-.146.124-.464.07-1.119-.014-.168-.037-.37-.061-.591-.052-.464-.112-1.005-.118-1.462-.01-.707.083-1.61.704-2.314.369-.417.845-.578 1.272-.618.404-.038.812.026 1.16.104.343.077.702.186 1.025.284l.028.008c.346.105.658.199.953.266.653.148.904.083.991.024C14.717 9.38 15 9.161 15 8a7 7 0 1 0-7 7"/>\n</svg>';
354
+ const __vite_glob_0_3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
355
+ __proto__: null,
356
+ default: palette
357
+ }, Symbol.toStringTag, { value: "Module" }));
358
+ const personLock = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M11 5a3 3 0 1 1-6 0 3 3 0 0 1 6 0M8 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m0 5.996V14H3s-1 0-1-1 1-4 6-4q.845.002 1.544.107a4.5 4.5 0 0 0-.803.918A11 11 0 0 0 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664zM9 13a1 1 0 0 1 1-1v-1a2 2 0 1 1 4 0v1a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1zm3-3a1 1 0 0 0-1 1v1h2v-1a1 1 0 0 0-1-1"/>\n</svg>';
359
+ const __vite_glob_0_4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
360
+ __proto__: null,
361
+ default: personLock
362
+ }, Symbol.toStringTag, { value: "Module" }));
363
+ const search = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001q.044.06.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1 1 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0"/>\n</svg>';
364
+ const __vite_glob_0_5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
365
+ __proto__: null,
366
+ default: search
367
+ }, Symbol.toStringTag, { value: "Module" }));
368
+ const stopwatch = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5z"/>\n <path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64l.012-.013.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5M8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3"/>\n</svg>';
369
+ const __vite_glob_0_6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
370
+ __proto__: null,
371
+ default: stopwatch
372
+ }, Symbol.toStringTag, { value: "Module" }));
373
+ const wifiOff = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M10.706 3.294A12.6 12.6 0 0 0 8 3C5.259 3 2.723 3.882.663 5.379a.485.485 0 0 0-.048.736.52.52 0 0 0 .668.05A11.45 11.45 0 0 1 8 4q.946 0 1.852.148zM8 6c-1.905 0-3.68.56-5.166 1.526a.48.48 0 0 0-.063.745.525.525 0 0 0 .652.065 8.45 8.45 0 0 1 3.51-1.27zm2.596 1.404.785-.785q.947.362 1.785.907a.482.482 0 0 1 .063.745.525.525 0 0 1-.652.065 8.5 8.5 0 0 0-1.98-.932zM8 10l.933-.933a6.5 6.5 0 0 1 2.013.637c.285.145.326.524.1.75l-.015.015a.53.53 0 0 1-.611.09A5.5 5.5 0 0 0 8 10m4.905-4.905.747-.747q.886.451 1.685 1.03a.485.485 0 0 1 .047.737.52.52 0 0 1-.668.05 11.5 11.5 0 0 0-1.811-1.07M9.02 11.78c.238.14.236.464.04.66l-.707.706a.5.5 0 0 1-.707 0l-.707-.707c-.195-.195-.197-.518.04-.66A2 2 0 0 1 8 11.5c.374 0 .723.102 1.021.28zm4.355-9.905a.53.53 0 0 1 .75.75l-10.75 10.75a.53.53 0 0 1-.75-.75z"/>\n</svg>';
374
+ const __vite_glob_0_7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
375
+ __proto__: null,
376
+ default: wifiOff
377
+ }, Symbol.toStringTag, { value: "Module" }));
378
+ const wrenchAdjustable = '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16">\n <path d="M16 4.5a4.5 4.5 0 0 1-1.703 3.526L13 5l2.959-1.11q.04.3.041.61"/>\n <path d="M11.5 9c.653 0 1.273-.139 1.833-.39L12 5.5 11 3l3.826-1.53A4.5 4.5 0 0 0 7.29 6.092l-6.116 5.096a2.583 2.583 0 1 0 3.638 3.638L9.908 8.71A4.5 4.5 0 0 0 11.5 9m-1.292-4.361-.596.893.809-.27a.25.25 0 0 1 .287.377l-.596.893.809-.27.158.475-1.5.5a.25.25 0 0 1-.287-.376l.596-.893-.809.27a.25.25 0 0 1-.287-.377l.596-.893-.809.27-.158-.475 1.5-.5a.25.25 0 0 1 .287.376M3 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2"/>\n</svg>';
379
+ const __vite_glob_0_8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
380
+ __proto__: null,
381
+ default: wrenchAdjustable
382
+ }, Symbol.toStringTag, { value: "Module" }));
336
383
  const styles = css`
337
384
  :host {
338
385
  display: flex;
@@ -392,9 +439,19 @@ var __decorateClass = (decorators, target, key, kind) => {
392
439
  if (kind && result) __defProp(target, key, result);
393
440
  return result;
394
441
  };
395
- const icons = Object.entries(/* @__PURE__ */ Object.assign({ "../assets/ban.svg": __vite_glob_0_0, "../assets/box-seam.svg": __vite_glob_0_1, "../assets/exclamation-triangle.svg": __vite_glob_0_2, "../assets/palette.svg": __vite_glob_0_3, "../assets/person-lock.svg": __vite_glob_0_4, "../assets/search.svg": __vite_glob_0_5, "../assets/stopwatch.svg": __vite_glob_0_6, "../assets/wifi-off.svg": __vite_glob_0_7, "../assets/wrench-adjustable.svg": __vite_glob_0_8 })).reduce((acc, [path, content]) => {
442
+ const icons = Object.entries(/* @__PURE__ */ Object.assign({
443
+ "../assets/ban.svg": __vite_glob_0_0,
444
+ "../assets/box-seam.svg": __vite_glob_0_1,
445
+ "../assets/exclamation-triangle.svg": __vite_glob_0_2,
446
+ "../assets/palette.svg": __vite_glob_0_3,
447
+ "../assets/person-lock.svg": __vite_glob_0_4,
448
+ "../assets/search.svg": __vite_glob_0_5,
449
+ "../assets/stopwatch.svg": __vite_glob_0_6,
450
+ "../assets/wifi-off.svg": __vite_glob_0_7,
451
+ "../assets/wrench-adjustable.svg": __vite_glob_0_8
452
+ })).reduce((acc, [path, content]) => {
396
453
  const name = path.split("/").pop()?.replace(".svg", "") || "";
397
- acc[name] = content;
454
+ acc[name] = content.default;
398
455
  return acc;
399
456
  }, {});
400
457
  let ErrorPage = class extends LitElement {
@@ -402,7 +459,7 @@ let ErrorPage = class extends LitElement {
402
459
  const error = this.error || this.getDefaultError();
403
460
  const icon = this.getErrorIcon(error.code);
404
461
  return html`
405
- <div class="icon">${icon}</div>
462
+ <div class="icon">${unsafeHTML(icon)}</div>
406
463
  <div class="code">${error.code}</div>
407
464
  <div class="message">${error.message}</div>
408
465
  `;
@@ -417,25 +474,25 @@ let ErrorPage = class extends LitElement {
417
474
  const numericCode = typeof code === "string" ? parseInt(code) : code;
418
475
  switch (codeStr) {
419
476
  case "OUTLET_NOT_FOUND":
420
- return unsafeSVG(icons["box-seam"] || "📦");
477
+ return icons["box-seam"] || "📦";
421
478
  case "CONTENT_LOAD_FAILED":
422
- return unsafeSVG(icons["wifi-off"] || "📡");
479
+ return icons["wifi-off"] || "📡";
423
480
  case "RENDER_FAILED":
424
- return unsafeSVG(icons["palette"] || "🎨");
481
+ return icons["palette"] || "🎨";
425
482
  }
426
483
  switch (numericCode) {
427
484
  case 404:
428
- return unsafeSVG(icons["search"] || "🔍");
485
+ return icons["search"] || "🔍";
429
486
  case 403:
430
- return unsafeSVG(icons["ban"] || "🚫");
487
+ return icons["ban"] || "🚫";
431
488
  case 401:
432
- return unsafeSVG(icons["person-lock"] || "🔐");
489
+ return icons["person-lock"] || "🔐";
433
490
  case 429:
434
- return unsafeSVG(icons["stopwatch"] || "⏱️");
491
+ return icons["stopwatch"] || "⏱️";
435
492
  case 503:
436
- return unsafeSVG(icons["wrench-adjustable"] || "🛠️");
493
+ return icons["wrench-adjustable"] || "🛠️";
437
494
  default:
438
- return unsafeSVG(icons["exclamation-triangle"] || "⚠️");
495
+ return icons["exclamation-triangle"] || "⚠️";
439
496
  }
440
497
  }
441
498
  };
@@ -491,51 +548,41 @@ function findAnchorFromEvent(e2) {
491
548
  function setRoutes(routes, basepath) {
492
549
  for (const route of routes) {
493
550
  route.id ||= getRandomID();
494
- if ("index" in route && route.index) {
551
+ if (route.index === true) {
495
552
  route.path = new URLPattern({ pathname: `${basepath}{/}?` });
496
- } else if ("path" in route && route.path) {
553
+ route.force ||= true;
554
+ } else {
497
555
  if (typeof route.path === "string") {
498
556
  const absolutePathStr = absolutePath(basepath, route.path);
499
557
  route.path = new URLPattern({ pathname: `${absolutePathStr}{/}?` });
558
+ } else if (route.path instanceof URLPattern) ;
559
+ else {
560
+ route.path = new URLPattern({ pathname: `${basepath}{/}?` });
500
561
  }
501
- } else {
502
- throw new Error('Route must have either "index" or "path" property defined.');
503
- }
504
- if (route.children && route.children.length > 0) {
505
- let childBasepath;
506
- if ("index" in route) {
507
- childBasepath = basepath;
562
+ if (route.children && route.children.length > 0) {
563
+ const childBasepath = route.path.pathname.replace("{/}?", "");
564
+ route.children = setRoutes(route.children, childBasepath);
565
+ route.force ||= false;
508
566
  } else {
509
- if (typeof route.path === "string") {
510
- childBasepath = absolutePath(basepath, route.path);
511
- } else {
512
- childBasepath = route.path.pathname.replace("{/}?", "");
513
- }
567
+ route.force ||= true;
514
568
  }
515
- route.children = setRoutes(route.children, childBasepath);
516
- route.force ||= false;
517
- } else {
518
- route.force ||= true;
519
569
  }
520
570
  }
521
571
  return routes;
522
572
  }
523
573
  function getRoutes(pathname, routes) {
524
574
  for (const route of routes) {
525
- if (route.children) {
575
+ if (route.index !== true && route.children && route.children.length > 0) {
526
576
  const childRoutes = getRoutes(pathname, route.children);
527
577
  if (childRoutes.length > 0) {
528
578
  return [route, ...childRoutes];
529
579
  }
530
580
  }
531
- let matches = false;
532
- if ("index" in route && route.index && route.path) {
533
- matches = route.path.test({ pathname });
534
- } else if ("path" in route && route.path instanceof URLPattern) {
535
- matches = route.path.test({ pathname });
536
- }
537
- if (matches) {
538
- return [route];
581
+ if (route.path instanceof URLPattern) {
582
+ const isMatch = route.path.test({ pathname });
583
+ if (isMatch) return [route];
584
+ } else {
585
+ throw new Error("Route path must be an instance of URLPattern, Something wrong in setRoutes function.");
539
586
  }
540
587
  }
541
588
  return [];
@@ -565,44 +612,36 @@ class Router {
565
612
  };
566
613
  this._rootElement = config.root;
567
614
  this._basepath = absolutePath(config.basepath || "/");
568
- this._routes = setRoutes(config.routes, this._basepath);
569
- this.waitConnected();
615
+ this._routes = setRoutes(config.routes || [], this._basepath);
616
+ this._fallback = config.fallback;
570
617
  window.removeEventListener("popstate", this.handleWindowPopstate);
571
618
  window.addEventListener("popstate", this.handleWindowPopstate);
572
619
  if (config.useIntercept !== false) {
573
620
  this._rootElement.removeEventListener("click", this.handleRootClick);
574
621
  this._rootElement.addEventListener("click", this.handleRootClick);
575
622
  }
576
- }
577
- /** 초기 라우팅 처리, TODO: 제거 */
578
- async waitConnected() {
579
- let outlet = findOutlet(this._rootElement);
580
- let count = 0;
581
- while (!outlet && count < 20) {
582
- await new Promise((resolve) => setTimeout(resolve, 50));
583
- outlet = findOutlet(this._rootElement);
584
- count++;
623
+ if (config.initialLoad !== false) {
624
+ void this.go(window.location.href);
585
625
  }
586
- this.handleWindowPopstate();
587
626
  }
588
627
  /** 객체를 정리하고 이벤트 리스너를 제거합니다. */
589
628
  destroy() {
590
629
  window.removeEventListener("popstate", this.handleWindowPopstate);
591
630
  this._rootElement.removeEventListener("click", this.handleRootClick);
592
- this._routeInfo = void 0;
593
631
  this._requestID = void 0;
632
+ this._context = void 0;
594
633
  }
595
634
  /** 라우터의 기본 경로 반환 */
596
635
  get basepath() {
597
636
  return this._basepath;
598
637
  }
599
- /** 등록된 라우트 반환 */
638
+ /** 등록된 라우트 정보 반환 */
600
639
  get routes() {
601
640
  return this._routes;
602
641
  }
603
642
  /** 현재 라우팅 정보 반환 */
604
- get routeInfo() {
605
- return this._routeInfo;
643
+ get context() {
644
+ return this._context;
606
645
  }
607
646
  /**
608
647
  * 지정한 경로의 클라이언트 라우팅을 수행합니다. 상대경로일 경우 basepath와 조합되어 이동합니다.
@@ -611,34 +650,48 @@ class Router {
611
650
  async go(href) {
612
651
  const requestID = getRandomID();
613
652
  this._requestID = requestID;
614
- const routeInfo = parseUrl(href, this._basepath);
615
- if (routeInfo.href === this._routeInfo?.href) return;
653
+ const context = parseUrl(href, this._basepath);
654
+ if (context.href === this._context?.href) return;
655
+ const progressCallback = (value) => {
656
+ if (this._requestID !== requestID) return;
657
+ const progress = Math.max(0, Math.min(100, Math.round(value)));
658
+ window.dispatchEvent(new RouteProgressEvent(context, progress));
659
+ };
660
+ context.progress = progressCallback;
661
+ if (context.href !== window.location.href) {
662
+ window.history.pushState({ basepath: context.basepath }, "", context.href);
663
+ } else {
664
+ window.history.replaceState({ basepath: context.basepath }, "", context.href);
665
+ }
616
666
  let outlet = void 0;
617
667
  try {
618
668
  if (this._requestID !== requestID) return;
619
- window.dispatchEvent(new RouteBeginEvent(routeInfo));
620
- const routes = getRoutes(routeInfo.pathname, this._routes);
669
+ window.dispatchEvent(new RouteBeginEvent(context));
670
+ const routes = getRoutes(context.pathname, this._routes);
621
671
  const lastRoute = routes[routes.length - 1];
622
- if (lastRoute && "path" in lastRoute && lastRoute.path instanceof URLPattern) {
623
- routeInfo.params = lastRoute.path.exec({ pathname: routeInfo.pathname })?.pathname.groups || {};
672
+ if (lastRoute && lastRoute.path instanceof URLPattern) {
673
+ context.params = lastRoute.path.exec({ pathname: context.pathname })?.pathname.groups || {};
624
674
  }
625
- this._routeInfo = routeInfo;
626
- window.route = routeInfo;
675
+ this._context = context;
627
676
  outlet = findOutletOrThrow(this._rootElement);
628
677
  let title = void 0;
629
678
  let content = null;
630
679
  let element = null;
631
680
  if (routes.length === 0) {
632
- throw new NotFoundError(routeInfo.href);
681
+ throw new NotFoundError(context.href);
633
682
  }
634
683
  for (const route of routes) {
635
684
  if (this._requestID !== requestID) return;
636
685
  if (!route.render) continue;
637
686
  try {
638
- content = await route.render(routeInfo);
687
+ content = await route.render(context);
688
+ if (content === false || content === null) {
689
+ throw new Error("Failed to load content for the route.");
690
+ }
639
691
  } catch (LoadError) {
640
692
  throw new ContentLoadError(LoadError);
641
693
  }
694
+ if (this._requestID !== requestID) return;
642
695
  try {
643
696
  element = await outlet.renderContent({ id: route.id, content, force: route.force });
644
697
  } catch (renderError) {
@@ -648,29 +701,29 @@ class Router {
648
701
  title = route.title || title;
649
702
  }
650
703
  document.title = title || document.title;
651
- if (this._requestID !== requestID) return;
652
- if (routeInfo.href !== window.location.href) {
653
- window.history.pushState({ basepath: routeInfo.basepath }, "", routeInfo.href);
654
- } else {
655
- window.history.replaceState({ basepath: routeInfo.basepath }, "", routeInfo.href);
656
- }
657
- window.dispatchEvent(new RouteDoneEvent(routeInfo));
704
+ window.dispatchEvent(new RouteDoneEvent(context));
658
705
  } catch (error) {
659
706
  const routeError = error instanceof RouteError ? error : new RouteError(
660
707
  error.status || error.code || "UNKNOWN_ERROR",
661
708
  error.message || "An unexpected error occurred",
662
709
  error
663
710
  );
664
- window.dispatchEvent(new RouteErrorEvent(routeError, routeInfo));
665
- console.error("Routing error:", error.original || error);
711
+ window.dispatchEvent(new RouteErrorEvent(context, routeError));
712
+ console.error("Routing error:", routeError.original);
666
713
  try {
667
- const errorPage = new ErrorPage();
668
- errorPage.error = error;
669
- if (outlet) {
670
- outlet.renderContent({ id: "#", content: errorPage, force: true });
714
+ if (this._fallback && this._fallback.render && outlet) {
715
+ const fallbackContent = await this._fallback.render({ ...context, error: routeError });
716
+ outlet.renderContent({ id: "#fallback", content: fallbackContent, force: true });
717
+ document.title = this._fallback.title || document.title;
671
718
  } else {
672
- document.body.innerHTML = "";
673
- document.body.appendChild(errorPage);
719
+ const errorContent = new ErrorPage();
720
+ errorContent.error = error;
721
+ if (outlet) {
722
+ outlet.renderContent({ id: "#error", content: errorContent, force: true });
723
+ } else {
724
+ document.body.innerHTML = "";
725
+ document.body.appendChild(errorContent);
726
+ }
674
727
  }
675
728
  } catch (pageError) {
676
729
  console.error("Failed to render error component:", pageError);
@@ -690,6 +743,7 @@ export {
690
743
  RouteDoneEvent,
691
744
  RouteError,
692
745
  RouteErrorEvent,
746
+ RouteProgressEvent,
693
747
  Router,
694
748
  ULink,
695
749
  UOutlet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/router",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "A modern client-side router for web applications with support for Lit and React components",
5
5
  "keywords": [
6
6
  "lit",
@@ -42,7 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@lit/react": "^1.0.8",
44
44
  "@types/node": "^24.10.1",
45
- "@types/react": "^19.2.4",
45
+ "@types/react": "^19.2.5",
46
46
  "@types/react-dom": "^19.2.3",
47
47
  "typescript": "^5.9.3",
48
48
  "vite": "^7.2.2",