@iyulab/router 0.5.0 → 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.
- package/dist/index.d.ts +178 -103
- package/dist/index.js +222 -355
- package/package.json +2 -3
package/dist/index.d.ts
CHANGED
|
@@ -18,22 +18,38 @@ declare interface BaseRouteConfig {
|
|
|
18
18
|
*/
|
|
19
19
|
title?: string;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @
|
|
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: (
|
|
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?: (
|
|
33
|
-
/**
|
|
34
|
-
* 중첩 라우트
|
|
35
|
-
*/
|
|
36
|
-
children?: RouteConfig[];
|
|
52
|
+
render?: (ctx: RouteContext) => Promise<RenderResult> | RenderResult;
|
|
37
53
|
/**
|
|
38
54
|
* 라우터 URL 변경시 렌더링을 강제할지 여부
|
|
39
55
|
* - 기본값으로 children을 가질때 false로 설정되며, children이 없을 경우 true로 설정됩니다.
|
|
@@ -43,19 +59,60 @@ declare interface BaseRouteConfig {
|
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
/**
|
|
46
|
-
*
|
|
62
|
+
* 컨텐츠 로드시 나타나는 에러
|
|
47
63
|
*/
|
|
48
|
-
declare
|
|
64
|
+
export declare class ContentLoadError extends RouteError {
|
|
65
|
+
constructor(original?: Error | any);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 컨텐츠 렌더링시 발생하는 에러
|
|
70
|
+
*/
|
|
71
|
+
export declare class ContentRenderError extends RouteError {
|
|
72
|
+
constructor(original?: Error | any);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export declare type FallbackRenderResult = HTMLElement | ReactElement | TemplateResult<1>;
|
|
76
|
+
|
|
77
|
+
export declare interface FallbackRouteConfig {
|
|
49
78
|
/**
|
|
50
|
-
*
|
|
51
|
-
* path는 강제로 빈 문자열로 설정됩니다.
|
|
79
|
+
* 브라우저의 타이틀이 설정에 따라 변경됩니다.
|
|
52
80
|
*/
|
|
53
|
-
|
|
81
|
+
title?: string;
|
|
54
82
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
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
|
+
* ```
|
|
57
98
|
*/
|
|
58
|
-
|
|
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;
|
|
59
116
|
}
|
|
60
117
|
|
|
61
118
|
/**
|
|
@@ -99,10 +156,22 @@ export declare class Link extends LitElement {
|
|
|
99
156
|
static styles: CSSResult;
|
|
100
157
|
}
|
|
101
158
|
|
|
159
|
+
declare interface NonIndexRouteConfig extends BaseRouteConfig {
|
|
160
|
+
/**
|
|
161
|
+
* 인덱스 라우트가 아님을 나타냅니다.
|
|
162
|
+
*/
|
|
163
|
+
index?: false;
|
|
164
|
+
/**
|
|
165
|
+
* 하위 라우트 설정, 재귀적으로 RouteConfig 배열을 가질 수 있습니다.
|
|
166
|
+
* - 하위 라우트가 있는 경우, 부모 라우트의 경로를 기준으로 매칭됩니다.
|
|
167
|
+
*/
|
|
168
|
+
children?: RouteConfig[];
|
|
169
|
+
}
|
|
170
|
+
|
|
102
171
|
/**
|
|
103
172
|
* 페이지를 찾을 수 없을 때 발생하는 에러
|
|
104
173
|
*/
|
|
105
|
-
export declare class
|
|
174
|
+
export declare class NotFoundError extends RouteError {
|
|
106
175
|
constructor(path: string, original?: Error | any);
|
|
107
176
|
}
|
|
108
177
|
|
|
@@ -129,22 +198,10 @@ export declare class Outlet extends LitElement {
|
|
|
129
198
|
}
|
|
130
199
|
|
|
131
200
|
/**
|
|
132
|
-
*
|
|
201
|
+
* u-outlet 요소를 찾을 수 없을 때 발생하는 에러
|
|
133
202
|
*/
|
|
134
|
-
declare
|
|
135
|
-
|
|
136
|
-
* 라우터 경로는 string 또는 URLPattern을 사용할 수 있습니다.
|
|
137
|
-
* string일 경우 자동으로 URLPattern으로 변환됩니다.
|
|
138
|
-
* @example
|
|
139
|
-
* - "/user/:id/:name"
|
|
140
|
-
* - "/user/:id/:name?"
|
|
141
|
-
* - "/user/:id/:name*"
|
|
142
|
-
* - "/user/:id/:name+"
|
|
143
|
-
* - "/user/:id/:name{1,3}"
|
|
144
|
-
* @link
|
|
145
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/URLPattern
|
|
146
|
-
*/
|
|
147
|
-
path: string | URLPattern;
|
|
203
|
+
export declare class OutletMissingError extends RouteError {
|
|
204
|
+
constructor();
|
|
148
205
|
}
|
|
149
206
|
|
|
150
207
|
declare interface RenderOption {
|
|
@@ -153,76 +210,21 @@ declare interface RenderOption {
|
|
|
153
210
|
content: RenderResult;
|
|
154
211
|
}
|
|
155
212
|
|
|
156
|
-
declare type RenderResult = HTMLElement | ReactElement | TemplateResult<1
|
|
213
|
+
export declare type RenderResult = HTMLElement | ReactElement | TemplateResult<1> | false;
|
|
157
214
|
|
|
158
215
|
/**
|
|
159
216
|
* 라우트 시작 이벤트
|
|
160
217
|
*/
|
|
161
218
|
export declare class RouteBeginEvent extends RouteEvent {
|
|
162
|
-
constructor(
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* 라우트 타입 (인덱스 라우트 또는 경로 라우트)
|
|
167
|
-
*/
|
|
168
|
-
export declare type RouteConfig = IndexRouteConfig | PathRouteConfig;
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* 라우트 완료 이벤트
|
|
172
|
-
*/
|
|
173
|
-
export declare class RouteDoneEvent extends RouteEvent {
|
|
174
|
-
constructor(routeInfo: RouteInfo);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* 라우팅 에러 정보
|
|
179
|
-
*/
|
|
180
|
-
export declare class RouteError extends Error {
|
|
181
|
-
/**
|
|
182
|
-
* 에러 코드
|
|
183
|
-
* - HTTP 상태 코드 또는 커스텀 에러 코드
|
|
184
|
-
* @example 404, 500, 'ROUTE_NOT_FOUND'
|
|
185
|
-
*/
|
|
186
|
-
code: number | string;
|
|
187
|
-
/**
|
|
188
|
-
* 원본 에러 객체
|
|
189
|
-
* - 원본 Error 객체 또는 예외 정보
|
|
190
|
-
*/
|
|
191
|
-
original?: Error | any;
|
|
192
|
-
/**
|
|
193
|
-
* 에러 발생 시간
|
|
194
|
-
* - 에러가 발생한 시간 (ISO 8601 형식)
|
|
195
|
-
*/
|
|
196
|
-
timestamp: string;
|
|
197
|
-
constructor(code: number | string, message: string, original?: Error | any);
|
|
219
|
+
constructor(context: RouteContext);
|
|
198
220
|
}
|
|
199
221
|
|
|
200
|
-
|
|
201
|
-
* 라우트 에러 이벤트
|
|
202
|
-
*/
|
|
203
|
-
export declare class RouteErrorEvent extends RouteEvent {
|
|
204
|
-
/** 에러 정보 */
|
|
205
|
-
readonly error: RouteError;
|
|
206
|
-
constructor(error: RouteError, routeInfo: RouteInfo);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/** 라우터 이벤트 기본 클래스 */
|
|
210
|
-
declare abstract class RouteEvent extends Event {
|
|
211
|
-
/** 라우팅 정보 */
|
|
212
|
-
readonly routeInfo: RouteInfo;
|
|
213
|
-
/** 이벤트 발생 시간 */
|
|
214
|
-
readonly timestamp: string;
|
|
215
|
-
constructor(type: string, routeInfo: RouteInfo, cancelable?: boolean);
|
|
216
|
-
/** 이벤트가 취소되었는지 확인 */
|
|
217
|
-
get cancelled(): boolean;
|
|
218
|
-
/** 이벤트 취소 */
|
|
219
|
-
cancel(): void;
|
|
220
|
-
}
|
|
222
|
+
export declare type RouteConfig = IndexRouteConfig | NonIndexRouteConfig;
|
|
221
223
|
|
|
222
224
|
/**
|
|
223
225
|
* 라우터 정보
|
|
224
226
|
*/
|
|
225
|
-
export declare interface
|
|
227
|
+
export declare interface RouteContext {
|
|
226
228
|
/**
|
|
227
229
|
* 전체 URL 정보
|
|
228
230
|
* - 도메인 이름을 포함한 URL의 전체 경로입니다.
|
|
@@ -284,6 +286,73 @@ export declare interface RouteInfo {
|
|
|
284
286
|
* @example #profile
|
|
285
287
|
*/
|
|
286
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);
|
|
287
356
|
}
|
|
288
357
|
|
|
289
358
|
/**
|
|
@@ -293,21 +362,20 @@ export declare class Router {
|
|
|
293
362
|
private readonly _rootElement;
|
|
294
363
|
private readonly _basepath;
|
|
295
364
|
private readonly _routes;
|
|
365
|
+
private readonly _fallback?;
|
|
296
366
|
/** 현재 라우팅 요청 ID */
|
|
297
367
|
private _requestID?;
|
|
298
368
|
/** 현재 라우팅 정보 */
|
|
299
|
-
private
|
|
369
|
+
private _context?;
|
|
300
370
|
constructor(config: RouterConfig);
|
|
301
|
-
/** 초기 라우팅 처리, TODO: 제거 */
|
|
302
|
-
private waitConnected;
|
|
303
371
|
/** 객체를 정리하고 이벤트 리스너를 제거합니다. */
|
|
304
372
|
destroy(): void;
|
|
305
373
|
/** 라우터의 기본 경로 반환 */
|
|
306
374
|
get basepath(): string;
|
|
307
|
-
/** 등록된 라우트 반환 */
|
|
375
|
+
/** 등록된 라우트 정보 반환 */
|
|
308
376
|
get routes(): RouteConfig[];
|
|
309
377
|
/** 현재 라우팅 정보 반환 */
|
|
310
|
-
get
|
|
378
|
+
get context(): RouteContext | undefined;
|
|
311
379
|
/**
|
|
312
380
|
* 지정한 경로의 클라이언트 라우팅을 수행합니다. 상대경로일 경우 basepath와 조합되어 이동합니다.
|
|
313
381
|
* @param href 이동할 경로
|
|
@@ -339,12 +407,22 @@ export declare interface RouterConfig {
|
|
|
339
407
|
* - 라우트는 URLPattern을 사용하여 경로를 탐색합니다.
|
|
340
408
|
* - 라우트는 렌더링할 엘리먼트 또는 컴포넌트를 지정합니다.
|
|
341
409
|
*/
|
|
342
|
-
routes
|
|
410
|
+
routes?: RouteConfig[];
|
|
411
|
+
/**
|
|
412
|
+
* 라우트 매칭 실패 또는 오류 발생 시 대체 라우트 설정
|
|
413
|
+
* - 지정된 설정이 없을 경우, 기본 오류 페이지가 렌더링됩니다.
|
|
414
|
+
*/
|
|
415
|
+
fallback?: FallbackRouteConfig;
|
|
343
416
|
/**
|
|
344
417
|
* `a` 태그 클릭 시 클라이언트 라우팅을 수행할지 여부를 설정합니다.
|
|
345
418
|
* @default true
|
|
346
419
|
*/
|
|
347
420
|
useIntercept?: boolean;
|
|
421
|
+
/**
|
|
422
|
+
* 초기 로드 시 현재 URL로 라우팅을 자동으로 수행할지 여부를 설정합니다.
|
|
423
|
+
* @default true
|
|
424
|
+
*/
|
|
425
|
+
initialLoad?: boolean;
|
|
348
426
|
}
|
|
349
427
|
|
|
350
428
|
export declare const ULink: ReactWebComponent<Link, {}>;
|
|
@@ -354,12 +432,9 @@ export declare const UOutlet: ReactWebComponent<Outlet, {}>;
|
|
|
354
432
|
export { }
|
|
355
433
|
|
|
356
434
|
declare global {
|
|
357
|
-
interface Window {
|
|
358
|
-
route: RouteInfo;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
435
|
interface WindowEventMap {
|
|
362
436
|
'route-begin': RouteBeginEvent;
|
|
437
|
+
'route-progress': RouteProgressEvent;
|
|
363
438
|
'route-done': RouteDoneEvent;
|
|
364
439
|
'route-error': RouteErrorEvent;
|
|
365
440
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +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 { unsafeHTML } from "lit-html/directives/unsafe-html.js";
|
|
5
6
|
const e = /* @__PURE__ */ new Set(["children", "localName", "ref", "style", "className"]), n = /* @__PURE__ */ new WeakMap(), t = (e2, t2, o2, l, a) => {
|
|
6
7
|
const s = a?.[t2];
|
|
7
8
|
void 0 === s ? (e2[t2] = o2, null == o2 && t2 in HTMLElement.prototype && e2.removeAttribute(t2)) : o2 !== l && ((e3, t3, o3) => {
|
|
@@ -64,7 +65,9 @@ function parseUrl(url, basepath) {
|
|
|
64
65
|
pathname: urlObj.pathname,
|
|
65
66
|
query: new URLSearchParams(urlObj.search),
|
|
66
67
|
hash: urlObj.hash,
|
|
67
|
-
params: {}
|
|
68
|
+
params: {},
|
|
69
|
+
progress: () => {
|
|
70
|
+
}
|
|
68
71
|
};
|
|
69
72
|
}
|
|
70
73
|
function absolutePath(...paths) {
|
|
@@ -213,7 +216,10 @@ class Outlet extends LitElement {
|
|
|
213
216
|
this.routeId = id;
|
|
214
217
|
this.clear();
|
|
215
218
|
if (!this.container) {
|
|
216
|
-
throw new Error("
|
|
219
|
+
throw new Error("Outlet container is not initialized.");
|
|
220
|
+
}
|
|
221
|
+
if (content === false) {
|
|
222
|
+
throw new Error("Content is false, cannot render.");
|
|
217
223
|
}
|
|
218
224
|
if (content instanceof HTMLElement) {
|
|
219
225
|
this.container.appendChild(content);
|
|
@@ -270,19 +276,30 @@ class RouteError extends Error {
|
|
|
270
276
|
}
|
|
271
277
|
}
|
|
272
278
|
}
|
|
273
|
-
class
|
|
279
|
+
class NotFoundError extends RouteError {
|
|
274
280
|
constructor(path, original) {
|
|
275
281
|
super(404, `Page not found: ${path}`, original);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
class OutletMissingError extends RouteError {
|
|
285
|
+
constructor() {
|
|
286
|
+
super("OUTLET_MISSING", "Router outlet element not found. Add <u-outlet> to your template.");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
class ContentLoadError extends RouteError {
|
|
290
|
+
constructor(original) {
|
|
291
|
+
super("CONTENT_LOAD_FAILED", "Failed to load route content. Check browser console for details.", original);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
class ContentRenderError extends RouteError {
|
|
295
|
+
constructor(original) {
|
|
296
|
+
super("CONTENT_RENDER_FAILED", "Failed to render route component. Check browser console for details.", original);
|
|
280
297
|
}
|
|
281
298
|
}
|
|
282
299
|
class RouteEvent extends Event {
|
|
283
|
-
constructor(type,
|
|
300
|
+
constructor(type, context, cancelable = false) {
|
|
284
301
|
super(type, { bubbles: true, composed: true, cancelable });
|
|
285
|
-
this.
|
|
302
|
+
this.context = context;
|
|
286
303
|
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
287
304
|
}
|
|
288
305
|
/** 이벤트가 취소되었는지 확인 */
|
|
@@ -297,277 +314,118 @@ class RouteEvent extends Event {
|
|
|
297
314
|
}
|
|
298
315
|
}
|
|
299
316
|
class RouteBeginEvent extends RouteEvent {
|
|
300
|
-
constructor(
|
|
301
|
-
super("route-begin",
|
|
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;
|
|
302
325
|
}
|
|
303
326
|
}
|
|
304
327
|
class RouteDoneEvent extends RouteEvent {
|
|
305
|
-
constructor(
|
|
306
|
-
super("route-done",
|
|
328
|
+
constructor(context) {
|
|
329
|
+
super("route-done", context, false);
|
|
307
330
|
}
|
|
308
331
|
}
|
|
309
332
|
class RouteErrorEvent extends RouteEvent {
|
|
310
|
-
constructor(
|
|
311
|
-
super("route-error",
|
|
333
|
+
constructor(context, error) {
|
|
334
|
+
super("route-error", context, false);
|
|
312
335
|
this.error = error;
|
|
313
336
|
}
|
|
314
337
|
}
|
|
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" }));
|
|
315
383
|
const styles = css`
|
|
316
384
|
:host {
|
|
317
385
|
display: flex;
|
|
386
|
+
flex-direction: column;
|
|
318
387
|
justify-content: center;
|
|
319
388
|
align-items: center;
|
|
320
389
|
min-height: 100vh;
|
|
321
390
|
width: 100%;
|
|
322
|
-
padding: 2rem;
|
|
323
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
324
|
-
background: var(--route-error-background, linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%));
|
|
325
|
-
color: var(--route-error-color, #2d3748);
|
|
326
|
-
line-height: 1.6;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
.container {
|
|
330
|
-
max-width: 520px;
|
|
331
|
-
margin: 0 auto;
|
|
332
391
|
text-align: center;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
padding: 3rem 2rem;
|
|
337
|
-
box-shadow: var(--route-error-box-shadow, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04));
|
|
338
|
-
border: 1px solid var(--route-error-border, rgba(255, 255, 255, 0.2));
|
|
339
|
-
animation: slideUp 0.6s ease-out;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
@keyframes slideUp {
|
|
343
|
-
from {
|
|
344
|
-
opacity: 0;
|
|
345
|
-
transform: translateY(30px);
|
|
346
|
-
}
|
|
347
|
-
to {
|
|
348
|
-
opacity: 1;
|
|
349
|
-
transform: translateY(0);
|
|
350
|
-
}
|
|
392
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
393
|
+
overflow: auto;
|
|
394
|
+
user-select: none;
|
|
351
395
|
}
|
|
352
396
|
|
|
353
397
|
.icon {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
@keyframes bounce {
|
|
361
|
-
0%, 20%, 53%, 80%, 100% {
|
|
362
|
-
transform: translate3d(0, 0, 0);
|
|
363
|
-
}
|
|
364
|
-
40%, 43% {
|
|
365
|
-
transform: translate3d(0, -10px, 0);
|
|
366
|
-
}
|
|
367
|
-
70% {
|
|
368
|
-
transform: translate3d(0, -5px, 0);
|
|
369
|
-
}
|
|
370
|
-
90% {
|
|
371
|
-
transform: translate3d(0, -2px, 0);
|
|
372
|
-
}
|
|
398
|
+
display: contents;
|
|
399
|
+
font-size: 6rem;
|
|
400
|
+
color: var(--route-error-color, #4a5568);
|
|
401
|
+
opacity: 0.85;
|
|
373
402
|
}
|
|
374
403
|
|
|
375
404
|
.code {
|
|
376
405
|
font-size: 2rem;
|
|
377
406
|
font-weight: 700;
|
|
378
|
-
margin
|
|
379
|
-
color: var(--route-error-code-color, #
|
|
380
|
-
letter-spacing: -0.
|
|
407
|
+
margin: 1rem 0;
|
|
408
|
+
color: var(--route-error-code-color, #1a202c);
|
|
409
|
+
letter-spacing: -0.5px;
|
|
381
410
|
}
|
|
382
411
|
|
|
383
412
|
.message {
|
|
384
|
-
font-size:
|
|
385
|
-
margin-bottom: 2.5rem;
|
|
413
|
+
font-size: 1rem;
|
|
386
414
|
color: var(--route-error-message-color, #718096);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
margin-left: auto;
|
|
390
|
-
margin-right: auto;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
.actions {
|
|
394
|
-
display: flex;
|
|
395
|
-
gap: 1rem;
|
|
396
|
-
justify-content: center;
|
|
397
|
-
flex-wrap: wrap;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
.button {
|
|
401
|
-
position: relative;
|
|
402
|
-
padding: 0.875rem 2rem;
|
|
403
|
-
border: none;
|
|
404
|
-
border-radius: 12px;
|
|
405
|
-
font-size: 0.95rem;
|
|
406
|
-
font-weight: 600;
|
|
407
|
-
cursor: pointer;
|
|
408
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
409
|
-
text-decoration: none;
|
|
410
|
-
display: inline-flex;
|
|
411
|
-
align-items: center;
|
|
412
|
-
justify-content: center;
|
|
413
|
-
gap: 0.5rem;
|
|
414
|
-
font-family: inherit;
|
|
415
|
-
min-width: 120px;
|
|
416
|
-
overflow: hidden;
|
|
417
|
-
user-select: none;
|
|
418
|
-
-webkit-tap-highlight-color: transparent;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
.button:first-child {
|
|
422
|
-
background: var(--route-error-primary-button-bg, linear-gradient(135deg, #667eea 0%, #764ba2 100%));
|
|
423
|
-
color: var(--route-error-primary-button-color, white);
|
|
424
|
-
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.4);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
.button:first-child:hover {
|
|
428
|
-
transform: translateY(-2px);
|
|
429
|
-
box-shadow: 0 8px 25px 0 rgba(102, 126, 234, 0.5);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
.button:first-child:active {
|
|
433
|
-
transform: translateY(0);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
.button:last-child {
|
|
437
|
-
background: var(--route-error-secondary-button-bg, rgba(255, 255, 255, 0.9));
|
|
438
|
-
color: var(--route-error-secondary-button-color, #4a5568);
|
|
439
|
-
border: 2px solid var(--route-error-secondary-button-border, rgba(74, 85, 104, 0.2));
|
|
440
|
-
backdrop-filter: blur(10px);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
.button:last-child:hover {
|
|
444
|
-
background: var(--route-error-secondary-button-hover-bg, rgba(255, 255, 255, 1));
|
|
445
|
-
border-color: var(--route-error-secondary-button-hover-border, rgba(74, 85, 104, 0.4));
|
|
446
|
-
transform: translateY(-1px);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
.button:focus-visible {
|
|
450
|
-
outline: 2px solid var(--route-error-focus-color, #667eea);
|
|
451
|
-
outline-offset: 2px;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
.button::before {
|
|
455
|
-
content: '';
|
|
456
|
-
position: absolute;
|
|
457
|
-
top: 0;
|
|
458
|
-
left: -100%;
|
|
459
|
-
width: 100%;
|
|
460
|
-
height: 100%;
|
|
461
|
-
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
462
|
-
transition: left 0.5s;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.button:hover::before {
|
|
466
|
-
left: 100%;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
@media (max-width: 640px) {
|
|
470
|
-
:host {
|
|
471
|
-
padding: 1rem;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
.container {
|
|
475
|
-
padding: 2rem 1.5rem;
|
|
476
|
-
border-radius: 20px;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
.icon {
|
|
480
|
-
font-size: 4rem;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.code {
|
|
484
|
-
font-size: 1.75rem;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
.message {
|
|
488
|
-
font-size: 1rem;
|
|
489
|
-
margin-bottom: 2rem;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
.actions {
|
|
493
|
-
flex-direction: column;
|
|
494
|
-
align-items: center;
|
|
495
|
-
gap: 0.75rem;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
.button {
|
|
499
|
-
width: 100%;
|
|
500
|
-
max-width: 280px;
|
|
501
|
-
padding: 1rem 2rem;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
@media (max-width: 480px) {
|
|
506
|
-
.container {
|
|
507
|
-
margin: 1rem;
|
|
508
|
-
padding: 1.5rem 1rem;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
.icon {
|
|
512
|
-
font-size: 3.5rem;
|
|
513
|
-
}
|
|
415
|
+
max-width: 600px;
|
|
416
|
+
line-height: 1.6;
|
|
514
417
|
}
|
|
515
418
|
|
|
516
419
|
@media (prefers-color-scheme: dark) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
.container {
|
|
523
|
-
background: var(--route-error-dark-container-bg, rgba(45, 55, 72, 0.95));
|
|
524
|
-
border: 1px solid var(--route-error-dark-border, rgba(255, 255, 255, 0.1));
|
|
420
|
+
.icon {
|
|
421
|
+
color: var(--route-error-dark-color, #a0aec0);
|
|
422
|
+
opacity: 0.9;
|
|
525
423
|
}
|
|
526
|
-
|
|
527
424
|
.code {
|
|
528
425
|
color: var(--route-error-dark-code-color, #f7fafc);
|
|
529
426
|
}
|
|
530
|
-
|
|
531
427
|
.message {
|
|
532
|
-
color: var(--route-error-dark-message-color, #
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
.button:first-child {
|
|
536
|
-
background: var(--route-error-dark-primary-button-bg, linear-gradient(135deg, #553c9a 0%, #764ba2 100%));
|
|
537
|
-
box-shadow: 0 4px 15px 0 rgba(85, 60, 154, 0.4);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
.button:first-child:hover {
|
|
541
|
-
box-shadow: 0 8px 25px 0 rgba(85, 60, 154, 0.5);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
.button:last-child {
|
|
545
|
-
background: var(--route-error-dark-secondary-button-bg, rgba(74, 85, 104, 0.3));
|
|
546
|
-
color: var(--route-error-dark-secondary-button-color, #e2e8f0);
|
|
547
|
-
border: 2px solid var(--route-error-dark-secondary-button-border, rgba(226, 232, 240, 0.2));
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
.button:last-child:hover {
|
|
551
|
-
background: var(--route-error-dark-secondary-button-hover-bg, rgba(74, 85, 104, 0.5));
|
|
552
|
-
border-color: var(--route-error-dark-secondary-button-hover-border, rgba(226, 232, 240, 0.4));
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
@media (prefers-reduced-motion: reduce) {
|
|
557
|
-
.container {
|
|
558
|
-
animation: none;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
.icon {
|
|
562
|
-
animation: none;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
.button::before {
|
|
566
|
-
display: none;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
.button {
|
|
570
|
-
transition: none;
|
|
428
|
+
color: var(--route-error-dark-message-color, #cbd5e0);
|
|
571
429
|
}
|
|
572
430
|
}
|
|
573
431
|
`;
|
|
@@ -581,34 +439,29 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
581
439
|
if (kind && result) __defProp(target, key, result);
|
|
582
440
|
return result;
|
|
583
441
|
};
|
|
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]) => {
|
|
453
|
+
const name = path.split("/").pop()?.replace(".svg", "") || "";
|
|
454
|
+
acc[name] = content.default;
|
|
455
|
+
return acc;
|
|
456
|
+
}, {});
|
|
584
457
|
let ErrorPage = class extends LitElement {
|
|
585
458
|
render() {
|
|
586
459
|
const error = this.error || this.getDefaultError();
|
|
587
460
|
const icon = this.getErrorIcon(error.code);
|
|
588
461
|
return html`
|
|
589
|
-
<div class="
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
<div class="message">${error.message}</div>
|
|
593
|
-
|
|
594
|
-
<div class="actions">
|
|
595
|
-
<button
|
|
596
|
-
class="button"
|
|
597
|
-
@click=${this.handleGoBack}
|
|
598
|
-
title="Go back to previous page"
|
|
599
|
-
aria-label="Go back to previous page">
|
|
600
|
-
← Go Back
|
|
601
|
-
</button>
|
|
602
|
-
|
|
603
|
-
<button
|
|
604
|
-
class="button"
|
|
605
|
-
@click=${this.handleRefresh}
|
|
606
|
-
title="Refresh the current page"
|
|
607
|
-
aria-label="Refresh the current page">
|
|
608
|
-
🔄 Refresh
|
|
609
|
-
</button>
|
|
610
|
-
</div>
|
|
611
|
-
</div>
|
|
462
|
+
<div class="icon">${unsafeHTML(icon)}</div>
|
|
463
|
+
<div class="code">${error.code}</div>
|
|
464
|
+
<div class="message">${error.message}</div>
|
|
612
465
|
`;
|
|
613
466
|
}
|
|
614
467
|
/** 기본 에러 정보 반환 */
|
|
@@ -617,31 +470,31 @@ let ErrorPage = class extends LitElement {
|
|
|
617
470
|
}
|
|
618
471
|
/** 에러 코드에 따른 기본 아이콘 반환 */
|
|
619
472
|
getErrorIcon(code) {
|
|
473
|
+
const codeStr = String(code);
|
|
620
474
|
const numericCode = typeof code === "string" ? parseInt(code) : code;
|
|
475
|
+
switch (codeStr) {
|
|
476
|
+
case "OUTLET_NOT_FOUND":
|
|
477
|
+
return icons["box-seam"] || "📦";
|
|
478
|
+
case "CONTENT_LOAD_FAILED":
|
|
479
|
+
return icons["wifi-off"] || "📡";
|
|
480
|
+
case "RENDER_FAILED":
|
|
481
|
+
return icons["palette"] || "🎨";
|
|
482
|
+
}
|
|
621
483
|
switch (numericCode) {
|
|
622
484
|
case 404:
|
|
623
|
-
return "🔍";
|
|
485
|
+
return icons["search"] || "🔍";
|
|
624
486
|
case 403:
|
|
625
|
-
return "
|
|
487
|
+
return icons["ban"] || "🚫";
|
|
626
488
|
case 401:
|
|
627
|
-
return "
|
|
489
|
+
return icons["person-lock"] || "🔐";
|
|
628
490
|
case 429:
|
|
629
|
-
return "⏱️";
|
|
491
|
+
return icons["stopwatch"] || "⏱️";
|
|
630
492
|
case 503:
|
|
631
|
-
return "🛠️";
|
|
632
|
-
case 500:
|
|
493
|
+
return icons["wrench-adjustable"] || "🛠️";
|
|
633
494
|
default:
|
|
634
|
-
return "⚠️";
|
|
495
|
+
return icons["exclamation-triangle"] || "⚠️";
|
|
635
496
|
}
|
|
636
497
|
}
|
|
637
|
-
/** 뒤로가기 */
|
|
638
|
-
handleGoBack() {
|
|
639
|
-
window.history.back();
|
|
640
|
-
}
|
|
641
|
-
/** 새로고침 */
|
|
642
|
-
handleRefresh() {
|
|
643
|
-
window.location.reload();
|
|
644
|
-
}
|
|
645
498
|
};
|
|
646
499
|
ErrorPage.styles = styles;
|
|
647
500
|
__decorateClass([
|
|
@@ -675,7 +528,7 @@ function findOutlet(element) {
|
|
|
675
528
|
function findOutletOrThrow(element) {
|
|
676
529
|
const outlet = findOutlet(element);
|
|
677
530
|
if (!outlet) {
|
|
678
|
-
throw new
|
|
531
|
+
throw new OutletMissingError();
|
|
679
532
|
}
|
|
680
533
|
return outlet;
|
|
681
534
|
}
|
|
@@ -695,51 +548,41 @@ function findAnchorFromEvent(e2) {
|
|
|
695
548
|
function setRoutes(routes, basepath) {
|
|
696
549
|
for (const route of routes) {
|
|
697
550
|
route.id ||= getRandomID();
|
|
698
|
-
if (
|
|
551
|
+
if (route.index === true) {
|
|
699
552
|
route.path = new URLPattern({ pathname: `${basepath}{/}?` });
|
|
700
|
-
|
|
553
|
+
route.force ||= true;
|
|
554
|
+
} else {
|
|
701
555
|
if (typeof route.path === "string") {
|
|
702
556
|
const absolutePathStr = absolutePath(basepath, route.path);
|
|
703
557
|
route.path = new URLPattern({ pathname: `${absolutePathStr}{/}?` });
|
|
558
|
+
} else if (route.path instanceof URLPattern) ;
|
|
559
|
+
else {
|
|
560
|
+
route.path = new URLPattern({ pathname: `${basepath}{/}?` });
|
|
704
561
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
let childBasepath;
|
|
710
|
-
if ("index" in route) {
|
|
711
|
-
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;
|
|
712
566
|
} else {
|
|
713
|
-
|
|
714
|
-
childBasepath = absolutePath(basepath, route.path);
|
|
715
|
-
} else {
|
|
716
|
-
childBasepath = route.path.pathname.replace("{/}?", "");
|
|
717
|
-
}
|
|
567
|
+
route.force ||= true;
|
|
718
568
|
}
|
|
719
|
-
route.children = setRoutes(route.children, childBasepath);
|
|
720
|
-
route.force ||= false;
|
|
721
|
-
} else {
|
|
722
|
-
route.force ||= true;
|
|
723
569
|
}
|
|
724
570
|
}
|
|
725
571
|
return routes;
|
|
726
572
|
}
|
|
727
573
|
function getRoutes(pathname, routes) {
|
|
728
574
|
for (const route of routes) {
|
|
729
|
-
if (route.children) {
|
|
575
|
+
if (route.index !== true && route.children && route.children.length > 0) {
|
|
730
576
|
const childRoutes = getRoutes(pathname, route.children);
|
|
731
577
|
if (childRoutes.length > 0) {
|
|
732
578
|
return [route, ...childRoutes];
|
|
733
579
|
}
|
|
734
580
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
} else
|
|
739
|
-
|
|
740
|
-
}
|
|
741
|
-
if (matches) {
|
|
742
|
-
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.");
|
|
743
586
|
}
|
|
744
587
|
}
|
|
745
588
|
return [];
|
|
@@ -769,44 +612,36 @@ class Router {
|
|
|
769
612
|
};
|
|
770
613
|
this._rootElement = config.root;
|
|
771
614
|
this._basepath = absolutePath(config.basepath || "/");
|
|
772
|
-
this._routes = setRoutes(config.routes, this._basepath);
|
|
773
|
-
this.
|
|
615
|
+
this._routes = setRoutes(config.routes || [], this._basepath);
|
|
616
|
+
this._fallback = config.fallback;
|
|
774
617
|
window.removeEventListener("popstate", this.handleWindowPopstate);
|
|
775
618
|
window.addEventListener("popstate", this.handleWindowPopstate);
|
|
776
619
|
if (config.useIntercept !== false) {
|
|
777
620
|
this._rootElement.removeEventListener("click", this.handleRootClick);
|
|
778
621
|
this._rootElement.addEventListener("click", this.handleRootClick);
|
|
779
622
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
async waitConnected() {
|
|
783
|
-
let outlet = findOutlet(this._rootElement);
|
|
784
|
-
let count = 0;
|
|
785
|
-
while (!outlet && count < 20) {
|
|
786
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
787
|
-
outlet = findOutlet(this._rootElement);
|
|
788
|
-
count++;
|
|
623
|
+
if (config.initialLoad !== false) {
|
|
624
|
+
void this.go(window.location.href);
|
|
789
625
|
}
|
|
790
|
-
this.handleWindowPopstate();
|
|
791
626
|
}
|
|
792
627
|
/** 객체를 정리하고 이벤트 리스너를 제거합니다. */
|
|
793
628
|
destroy() {
|
|
794
629
|
window.removeEventListener("popstate", this.handleWindowPopstate);
|
|
795
630
|
this._rootElement.removeEventListener("click", this.handleRootClick);
|
|
796
|
-
this._routeInfo = void 0;
|
|
797
631
|
this._requestID = void 0;
|
|
632
|
+
this._context = void 0;
|
|
798
633
|
}
|
|
799
634
|
/** 라우터의 기본 경로 반환 */
|
|
800
635
|
get basepath() {
|
|
801
636
|
return this._basepath;
|
|
802
637
|
}
|
|
803
|
-
/** 등록된 라우트 반환 */
|
|
638
|
+
/** 등록된 라우트 정보 반환 */
|
|
804
639
|
get routes() {
|
|
805
640
|
return this._routes;
|
|
806
641
|
}
|
|
807
642
|
/** 현재 라우팅 정보 반환 */
|
|
808
|
-
get
|
|
809
|
-
return this.
|
|
643
|
+
get context() {
|
|
644
|
+
return this._context;
|
|
810
645
|
}
|
|
811
646
|
/**
|
|
812
647
|
* 지정한 경로의 클라이언트 라우팅을 수행합니다. 상대경로일 경우 basepath와 조합되어 이동합니다.
|
|
@@ -815,68 +650,100 @@ class Router {
|
|
|
815
650
|
async go(href) {
|
|
816
651
|
const requestID = getRandomID();
|
|
817
652
|
this._requestID = requestID;
|
|
818
|
-
const
|
|
819
|
-
if (
|
|
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
|
+
}
|
|
666
|
+
let outlet = void 0;
|
|
820
667
|
try {
|
|
821
668
|
if (this._requestID !== requestID) return;
|
|
822
|
-
window.dispatchEvent(new RouteBeginEvent(
|
|
823
|
-
const routes = getRoutes(
|
|
669
|
+
window.dispatchEvent(new RouteBeginEvent(context));
|
|
670
|
+
const routes = getRoutes(context.pathname, this._routes);
|
|
824
671
|
const lastRoute = routes[routes.length - 1];
|
|
825
|
-
if (lastRoute &&
|
|
826
|
-
|
|
672
|
+
if (lastRoute && lastRoute.path instanceof URLPattern) {
|
|
673
|
+
context.params = lastRoute.path.exec({ pathname: context.pathname })?.pathname.groups || {};
|
|
827
674
|
}
|
|
828
|
-
this.
|
|
829
|
-
|
|
830
|
-
|
|
675
|
+
this._context = context;
|
|
676
|
+
outlet = findOutletOrThrow(this._rootElement);
|
|
677
|
+
let title = void 0;
|
|
678
|
+
let content = null;
|
|
679
|
+
let element = null;
|
|
831
680
|
if (routes.length === 0) {
|
|
832
|
-
throw new
|
|
681
|
+
throw new NotFoundError(context.href);
|
|
833
682
|
}
|
|
834
|
-
let outlet = findOutletOrThrow(this._rootElement);
|
|
835
|
-
let title = void 0;
|
|
836
683
|
for (const route of routes) {
|
|
837
684
|
if (this._requestID !== requestID) return;
|
|
838
685
|
if (!route.render) continue;
|
|
839
|
-
|
|
840
|
-
|
|
686
|
+
try {
|
|
687
|
+
content = await route.render(context);
|
|
688
|
+
if (content === false || content === null) {
|
|
689
|
+
throw new Error("Failed to load content for the route.");
|
|
690
|
+
}
|
|
691
|
+
} catch (LoadError) {
|
|
692
|
+
throw new ContentLoadError(LoadError);
|
|
693
|
+
}
|
|
694
|
+
if (this._requestID !== requestID) return;
|
|
695
|
+
try {
|
|
696
|
+
element = await outlet.renderContent({ id: route.id, content, force: route.force });
|
|
697
|
+
} catch (renderError) {
|
|
698
|
+
throw new ContentRenderError(renderError);
|
|
699
|
+
}
|
|
841
700
|
outlet = findOutlet(element) || outlet;
|
|
842
701
|
title = route.title || title;
|
|
843
702
|
}
|
|
844
703
|
document.title = title || document.title;
|
|
845
|
-
|
|
846
|
-
if (routeInfo.href !== window.location.href) {
|
|
847
|
-
window.history.pushState({ basepath: routeInfo.basepath }, "", routeInfo.href);
|
|
848
|
-
} else {
|
|
849
|
-
window.history.replaceState({ basepath: routeInfo.basepath }, "", routeInfo.href);
|
|
850
|
-
}
|
|
851
|
-
window.dispatchEvent(new RouteDoneEvent(routeInfo));
|
|
704
|
+
window.dispatchEvent(new RouteDoneEvent(context));
|
|
852
705
|
} catch (error) {
|
|
853
|
-
const routeError = new RouteError(
|
|
706
|
+
const routeError = error instanceof RouteError ? error : new RouteError(
|
|
854
707
|
error.status || error.code || "UNKNOWN_ERROR",
|
|
855
708
|
error.message || "An unexpected error occurred",
|
|
856
709
|
error
|
|
857
710
|
);
|
|
858
|
-
window.dispatchEvent(new RouteErrorEvent(
|
|
859
|
-
console.error("Routing error:",
|
|
711
|
+
window.dispatchEvent(new RouteErrorEvent(context, routeError));
|
|
712
|
+
console.error("Routing error:", routeError.original);
|
|
860
713
|
try {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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;
|
|
718
|
+
} else {
|
|
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
|
+
}
|
|
727
|
+
}
|
|
865
728
|
} catch (pageError) {
|
|
866
729
|
console.error("Failed to render error component:", pageError);
|
|
867
|
-
console.error("Original error:",
|
|
730
|
+
console.error("Original error:", routeError.original || routeError);
|
|
868
731
|
}
|
|
869
732
|
}
|
|
870
733
|
}
|
|
871
734
|
}
|
|
872
735
|
export {
|
|
736
|
+
ContentLoadError,
|
|
737
|
+
ContentRenderError,
|
|
873
738
|
Link,
|
|
874
|
-
|
|
739
|
+
NotFoundError,
|
|
875
740
|
Outlet,
|
|
741
|
+
OutletMissingError,
|
|
876
742
|
RouteBeginEvent,
|
|
877
743
|
RouteDoneEvent,
|
|
878
744
|
RouteError,
|
|
879
745
|
RouteErrorEvent,
|
|
746
|
+
RouteProgressEvent,
|
|
880
747
|
Router,
|
|
881
748
|
ULink,
|
|
882
749
|
UOutlet
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/router",
|
|
3
|
-
"version": "0.5.
|
|
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,9 +42,8 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@lit/react": "^1.0.8",
|
|
44
44
|
"@types/node": "^24.10.1",
|
|
45
|
-
"@types/react": "^19.2.
|
|
45
|
+
"@types/react": "^19.2.5",
|
|
46
46
|
"@types/react-dom": "^19.2.3",
|
|
47
|
-
"tslib": "^2.8.1",
|
|
48
47
|
"typescript": "^5.9.3",
|
|
49
48
|
"vite": "^7.2.2",
|
|
50
49
|
"vite-plugin-dts": "^4.5.4"
|