@iyulab/router 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{main.d.ts → index.d.ts} +4 -2
- package/dist/{main.js → index.js} +2 -1
- package/package.json +7 -8
- package/dist/main.cjs.js +0 -882
|
@@ -29,7 +29,7 @@ declare interface BaseRouteConfig {
|
|
|
29
29
|
* }
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
render
|
|
32
|
+
render?: (info: RouteInfo) => Promise<RenderResult> | RenderResult;
|
|
33
33
|
/**
|
|
34
34
|
* 중첩 라우트
|
|
35
35
|
*/
|
|
@@ -150,9 +150,11 @@ declare interface PathRouteConfig extends BaseRouteConfig {
|
|
|
150
150
|
declare interface RenderOption {
|
|
151
151
|
id?: string;
|
|
152
152
|
force?: boolean;
|
|
153
|
-
content:
|
|
153
|
+
content: RenderResult;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
declare type RenderResult = HTMLElement | ReactElement | TemplateResult<1>;
|
|
157
|
+
|
|
156
158
|
/**
|
|
157
159
|
* 라우트 시작 이벤트
|
|
158
160
|
*/
|
|
@@ -835,7 +835,8 @@ class Router {
|
|
|
835
835
|
let title = void 0;
|
|
836
836
|
for (const route of routes) {
|
|
837
837
|
if (this._requestID !== requestID) return;
|
|
838
|
-
|
|
838
|
+
if (!route.render) continue;
|
|
839
|
+
const content = await route.render(routeInfo);
|
|
839
840
|
const element = await outlet.renderContent({ id: route.id, content, force: route.force });
|
|
840
841
|
outlet = findOutlet(element) || outlet;
|
|
841
842
|
title = route.title || title;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A modern client-side router for web applications with support for Lit and React components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lit",
|
|
@@ -24,12 +24,11 @@
|
|
|
24
24
|
"LICENSE"
|
|
25
25
|
],
|
|
26
26
|
"type": "module",
|
|
27
|
-
"types": "dist/
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
28
|
"exports": {
|
|
29
29
|
".": {
|
|
30
|
-
"types": "./dist/
|
|
31
|
-
"import": "./dist/
|
|
32
|
-
"require": "./dist/main.cjs.js"
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
33
32
|
}
|
|
34
33
|
},
|
|
35
34
|
"scripts": {
|
|
@@ -42,9 +41,9 @@
|
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@lit/react": "^1.0.8",
|
|
45
|
-
"@types/node": "^24.10.
|
|
46
|
-
"@types/react": "^19.2.
|
|
47
|
-
"@types/react-dom": "^19.2.
|
|
44
|
+
"@types/node": "^24.10.1",
|
|
45
|
+
"@types/react": "^19.2.3",
|
|
46
|
+
"@types/react-dom": "^19.2.3",
|
|
48
47
|
"tslib": "^2.8.1",
|
|
49
48
|
"typescript": "^5.9.3",
|
|
50
49
|
"vite": "^7.2.2",
|
package/dist/main.cjs.js
DELETED
|
@@ -1,882 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const React = require("react");
|
|
4
|
-
const lit = require("lit");
|
|
5
|
-
const decorators_js = require("lit/decorators.js");
|
|
6
|
-
const client = require("react-dom/client");
|
|
7
|
-
const e = /* @__PURE__ */ new Set(["children", "localName", "ref", "style", "className"]), n = /* @__PURE__ */ new WeakMap(), t = (e2, t2, o2, l, a) => {
|
|
8
|
-
const s = a?.[t2];
|
|
9
|
-
void 0 === s ? (e2[t2] = o2, null == o2 && t2 in HTMLElement.prototype && e2.removeAttribute(t2)) : o2 !== l && ((e3, t3, o3) => {
|
|
10
|
-
let l2 = n.get(e3);
|
|
11
|
-
void 0 === l2 && n.set(e3, l2 = /* @__PURE__ */ new Map());
|
|
12
|
-
let a2 = l2.get(t3);
|
|
13
|
-
void 0 !== o3 ? void 0 === a2 ? (l2.set(t3, a2 = { handleEvent: o3 }), e3.addEventListener(t3, a2)) : a2.handleEvent = o3 : void 0 !== a2 && (l2.delete(t3), e3.removeEventListener(t3, a2));
|
|
14
|
-
})(e2, s, o2);
|
|
15
|
-
}, o = ({ react: n2, tagName: o2, elementClass: l, events: a, displayName: s }) => {
|
|
16
|
-
const c = new Set(Object.keys(a ?? {})), r = n2.forwardRef(((s2, r2) => {
|
|
17
|
-
const i = n2.useRef(/* @__PURE__ */ new Map()), d = n2.useRef(null), f = {}, u = {};
|
|
18
|
-
for (const [n3, t2] of Object.entries(s2)) e.has(n3) ? f["className" === n3 ? "class" : n3] = t2 : c.has(n3) || n3 in l.prototype ? u[n3] = t2 : f[n3] = t2;
|
|
19
|
-
return n2.useLayoutEffect((() => {
|
|
20
|
-
if (null === d.current) return;
|
|
21
|
-
const e2 = /* @__PURE__ */ new Map();
|
|
22
|
-
for (const n3 in u) t(d.current, n3, s2[n3], i.current.get(n3), a), i.current.delete(n3), e2.set(n3, s2[n3]);
|
|
23
|
-
for (const [e3, n3] of i.current) t(d.current, e3, void 0, n3, a);
|
|
24
|
-
i.current = e2;
|
|
25
|
-
})), n2.useLayoutEffect((() => {
|
|
26
|
-
d.current?.removeAttribute("defer-hydration");
|
|
27
|
-
}), []), f.suppressHydrationWarning = true, n2.createElement(o2, { ...f, ref: n2.useCallback(((e2) => {
|
|
28
|
-
d.current = e2, "function" == typeof r2 ? r2(e2) : null !== r2 && (r2.current = e2);
|
|
29
|
-
}), [r2]) });
|
|
30
|
-
}));
|
|
31
|
-
return r.displayName = s ?? l.name, r;
|
|
32
|
-
};
|
|
33
|
-
function isExternalUrl(url) {
|
|
34
|
-
if (!url) return false;
|
|
35
|
-
const s = url.trim();
|
|
36
|
-
if (/^(?:mailto:|tel:|javascript:)/i.test(s)) return true;
|
|
37
|
-
if (s.startsWith("//")) return true;
|
|
38
|
-
try {
|
|
39
|
-
const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
40
|
-
const parsed = new URL(s, base);
|
|
41
|
-
if (/^(?:ftp:|ftps:|data:|ws:|wss:)/i.test(parsed.protocol)) return true;
|
|
42
|
-
return parsed.origin !== new URL(base).origin;
|
|
43
|
-
} catch {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function parseUrl(url, basepath) {
|
|
48
|
-
let urlObj;
|
|
49
|
-
basepath = catchBasePath(basepath);
|
|
50
|
-
if (url.startsWith("http")) {
|
|
51
|
-
urlObj = new URL(url);
|
|
52
|
-
} else if (url.startsWith("/")) {
|
|
53
|
-
urlObj = new URL(url, window.location.origin);
|
|
54
|
-
} else if (url.startsWith("?")) {
|
|
55
|
-
urlObj = new URL(window.location.pathname + url, window.location.origin);
|
|
56
|
-
} else if (url.startsWith("#")) {
|
|
57
|
-
urlObj = new URL(window.location.pathname + window.location.search + url, window.location.origin);
|
|
58
|
-
} else {
|
|
59
|
-
urlObj = new URL(absolutePath(basepath, url), window.location.origin);
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
href: urlObj.href,
|
|
63
|
-
origin: urlObj.origin,
|
|
64
|
-
basepath,
|
|
65
|
-
path: urlObj.href.replace(urlObj.origin, ""),
|
|
66
|
-
pathname: urlObj.pathname,
|
|
67
|
-
query: new URLSearchParams(urlObj.search),
|
|
68
|
-
hash: urlObj.hash,
|
|
69
|
-
params: {}
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function absolutePath(...paths) {
|
|
73
|
-
paths = paths.map((p) => p.replace(/^\/|\/$/g, "")).filter((p) => p.length > 0);
|
|
74
|
-
if (paths.length === 0) return "/";
|
|
75
|
-
return "/" + paths.join("/");
|
|
76
|
-
}
|
|
77
|
-
function catchBasePath(basepath) {
|
|
78
|
-
if (basepath === "/") return basepath;
|
|
79
|
-
let pattern = new URLPattern({ pathname: basepath + "/*" });
|
|
80
|
-
let match = pattern.exec({ pathname: window.location.pathname });
|
|
81
|
-
if (match) {
|
|
82
|
-
const rawPath = match.pathname.input;
|
|
83
|
-
const restPath = match.pathname.groups?.["0"];
|
|
84
|
-
return restPath ? rawPath.replace("/" + restPath, "") : rawPath.slice(0, -1);
|
|
85
|
-
}
|
|
86
|
-
pattern = new URLPattern({ pathname: `${basepath}{/}?` });
|
|
87
|
-
match = pattern.exec({ pathname: window.location.pathname });
|
|
88
|
-
if (match) {
|
|
89
|
-
return match.pathname.input;
|
|
90
|
-
}
|
|
91
|
-
return basepath;
|
|
92
|
-
}
|
|
93
|
-
var __defProp$1 = Object.defineProperty;
|
|
94
|
-
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
95
|
-
var result = void 0;
|
|
96
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
97
|
-
if (decorator = decorators[i])
|
|
98
|
-
result = decorator(target, key, result) || result;
|
|
99
|
-
if (result) __defProp$1(target, key, result);
|
|
100
|
-
return result;
|
|
101
|
-
};
|
|
102
|
-
const _Link = class _Link extends lit.LitElement {
|
|
103
|
-
constructor() {
|
|
104
|
-
super(...arguments);
|
|
105
|
-
this.isExternal = false;
|
|
106
|
-
this.anchorHref = "#";
|
|
107
|
-
this.handleMouseDown = (event) => {
|
|
108
|
-
const isNonNavigationClick = event.button === 2 || event.metaKey || event.shiftKey || event.altKey;
|
|
109
|
-
if (event.defaultPrevented || isNonNavigationClick) return;
|
|
110
|
-
event.preventDefault();
|
|
111
|
-
event.stopPropagation();
|
|
112
|
-
const basepath = window.history.state?.basepath || "";
|
|
113
|
-
if (event.button === 1 || event.ctrlKey) {
|
|
114
|
-
window.open(this.anchorHref, "_blank");
|
|
115
|
-
} else if (!this.href) {
|
|
116
|
-
this.dispatchPopstate(basepath, basepath);
|
|
117
|
-
} else if (this.isExternal || this.href.startsWith("/") && !this.href.startsWith(basepath)) {
|
|
118
|
-
window.location.href = this.href;
|
|
119
|
-
} else if (this.href.startsWith("#")) {
|
|
120
|
-
const url = window.location.pathname + window.location.search + this.href;
|
|
121
|
-
this.dispatchHashchange(basepath, url);
|
|
122
|
-
} else if (this.href.startsWith("?")) {
|
|
123
|
-
const url = window.location.pathname + this.href;
|
|
124
|
-
this.dispatchPopstate(basepath, url);
|
|
125
|
-
} else {
|
|
126
|
-
const url = absolutePath(basepath, this.href);
|
|
127
|
-
this.dispatchPopstate(basepath, url);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
this.preventClickEvent = (event) => {
|
|
131
|
-
event.preventDefault();
|
|
132
|
-
event.stopPropagation();
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
connectedCallback() {
|
|
136
|
-
super.connectedCallback();
|
|
137
|
-
this.addEventListener("mousedown", this.handleMouseDown);
|
|
138
|
-
}
|
|
139
|
-
disconnectedCallback() {
|
|
140
|
-
this.removeEventListener("mousedown", this.handleMouseDown);
|
|
141
|
-
super.disconnectedCallback();
|
|
142
|
-
}
|
|
143
|
-
async updated(changedProperties) {
|
|
144
|
-
super.updated(changedProperties);
|
|
145
|
-
await this.updateComplete;
|
|
146
|
-
if (changedProperties.has("href")) {
|
|
147
|
-
this.isExternal = isExternalUrl(this.href || "");
|
|
148
|
-
this.anchorHref = this.getAnchorHref(this.href);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
render() {
|
|
152
|
-
return lit.html`
|
|
153
|
-
<a href=${this.anchorHref} @click=${this.preventClickEvent}>
|
|
154
|
-
<slot></slot>
|
|
155
|
-
</a>
|
|
156
|
-
`;
|
|
157
|
-
}
|
|
158
|
-
/** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
|
|
159
|
-
dispatchPopstate(basepath, url) {
|
|
160
|
-
window.history.pushState({ basepath }, "", url);
|
|
161
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
162
|
-
}
|
|
163
|
-
/** 클라이언트 라우팅을 위해 hashchange 이벤트를 발생시킵니다. */
|
|
164
|
-
dispatchHashchange(basepath, url) {
|
|
165
|
-
window.history.pushState({ basepath }, "", url);
|
|
166
|
-
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
167
|
-
}
|
|
168
|
-
/** a 태그에 주입할 href 값을 계산합니다. */
|
|
169
|
-
getAnchorHref(href) {
|
|
170
|
-
const basepath = window.history.state?.basepath || "";
|
|
171
|
-
if (!href) {
|
|
172
|
-
return window.location.origin + basepath;
|
|
173
|
-
}
|
|
174
|
-
if (this.isExternal || href.startsWith("/") || href.startsWith("#") || href.startsWith("?")) {
|
|
175
|
-
return href;
|
|
176
|
-
}
|
|
177
|
-
return absolutePath(basepath, href);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
_Link.styles = lit.css`
|
|
181
|
-
:host {
|
|
182
|
-
display: inline-flex;
|
|
183
|
-
cursor: pointer;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
a {
|
|
187
|
-
display: contents;
|
|
188
|
-
text-decoration: none;
|
|
189
|
-
color: inherit;
|
|
190
|
-
}
|
|
191
|
-
`;
|
|
192
|
-
let Link = _Link;
|
|
193
|
-
__decorateClass$1([
|
|
194
|
-
decorators_js.state()
|
|
195
|
-
], Link.prototype, "anchorHref");
|
|
196
|
-
__decorateClass$1([
|
|
197
|
-
decorators_js.property({ type: String })
|
|
198
|
-
], Link.prototype, "href");
|
|
199
|
-
class Outlet extends lit.LitElement {
|
|
200
|
-
/** 쉐도우를 사용하지 않고, 직접 렌더링합니다. */
|
|
201
|
-
createRenderRoot() {
|
|
202
|
-
return this;
|
|
203
|
-
}
|
|
204
|
-
render() {
|
|
205
|
-
return lit.html`${this.container}`;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* render 함수의 결과를 렌더링합니다.
|
|
209
|
-
* - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
|
|
210
|
-
*/
|
|
211
|
-
async renderContent({ id, content, force }) {
|
|
212
|
-
if (this.routeId === id && force === false && this.container) {
|
|
213
|
-
return this.container;
|
|
214
|
-
}
|
|
215
|
-
this.routeId = id;
|
|
216
|
-
this.clear();
|
|
217
|
-
if (!this.container) {
|
|
218
|
-
throw new Error("DOM이 초기화되지 않았습니다.");
|
|
219
|
-
}
|
|
220
|
-
if (content instanceof HTMLElement) {
|
|
221
|
-
this.container.appendChild(content);
|
|
222
|
-
} else if ("_$litType$" in content) {
|
|
223
|
-
this.content = lit.render(content, this.container);
|
|
224
|
-
} else if ("$$typeof" in content) {
|
|
225
|
-
this.content = client.createRoot(this.container);
|
|
226
|
-
this.content.render(content);
|
|
227
|
-
} else {
|
|
228
|
-
throw new Error("not supported content type for Outlet rendering.");
|
|
229
|
-
}
|
|
230
|
-
this.requestUpdate();
|
|
231
|
-
await this.updateComplete;
|
|
232
|
-
return this.container;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* 기존 DOM을 라이프 사이클에 맞게 제거합니다.
|
|
236
|
-
*/
|
|
237
|
-
clear() {
|
|
238
|
-
if (this.content) {
|
|
239
|
-
if ("unmount" in this.content) {
|
|
240
|
-
this.content.unmount();
|
|
241
|
-
}
|
|
242
|
-
if ("setConnected" in this.content) {
|
|
243
|
-
this.content.setConnected(false);
|
|
244
|
-
}
|
|
245
|
-
this.content = void 0;
|
|
246
|
-
}
|
|
247
|
-
this.container = document.createElement("div");
|
|
248
|
-
this.container.style.display = "contents";
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
customElements.define("u-link", Link);
|
|
252
|
-
customElements.define("u-outlet", Outlet);
|
|
253
|
-
const ULink = o({
|
|
254
|
-
react: React,
|
|
255
|
-
tagName: "u-link",
|
|
256
|
-
elementClass: Link
|
|
257
|
-
});
|
|
258
|
-
const UOutlet = o({
|
|
259
|
-
react: React,
|
|
260
|
-
tagName: "u-outlet",
|
|
261
|
-
elementClass: Outlet
|
|
262
|
-
});
|
|
263
|
-
class RouteError extends Error {
|
|
264
|
-
constructor(code, message, original) {
|
|
265
|
-
super(message);
|
|
266
|
-
this.name = "RouteError";
|
|
267
|
-
this.code = code;
|
|
268
|
-
this.original = original;
|
|
269
|
-
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
270
|
-
if (Error.captureStackTrace) {
|
|
271
|
-
Error.captureStackTrace(this, RouteError);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
class NotFoundRouteError extends RouteError {
|
|
276
|
-
constructor(path, original) {
|
|
277
|
-
super(404, `Page not found: ${path}`, original);
|
|
278
|
-
this.name = "NotFoundError";
|
|
279
|
-
if (Error.captureStackTrace) {
|
|
280
|
-
Error.captureStackTrace(this, NotFoundRouteError);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
class RouteEvent extends Event {
|
|
285
|
-
constructor(type, routeInfo, cancelable = false) {
|
|
286
|
-
super(type, { bubbles: true, composed: true, cancelable });
|
|
287
|
-
this.routeInfo = routeInfo;
|
|
288
|
-
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
289
|
-
}
|
|
290
|
-
/** 이벤트가 취소되었는지 확인 */
|
|
291
|
-
get cancelled() {
|
|
292
|
-
return this.defaultPrevented;
|
|
293
|
-
}
|
|
294
|
-
/** 이벤트 취소 */
|
|
295
|
-
cancel() {
|
|
296
|
-
if (this.cancelable) {
|
|
297
|
-
this.preventDefault();
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
class RouteBeginEvent extends RouteEvent {
|
|
302
|
-
constructor(routeInfo) {
|
|
303
|
-
super("route-begin", routeInfo, false);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
class RouteDoneEvent extends RouteEvent {
|
|
307
|
-
constructor(routeInfo) {
|
|
308
|
-
super("route-done", routeInfo, false);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
class RouteErrorEvent extends RouteEvent {
|
|
312
|
-
constructor(error, routeInfo) {
|
|
313
|
-
super("route-error", routeInfo, false);
|
|
314
|
-
this.error = error;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
const styles = lit.css`
|
|
318
|
-
:host {
|
|
319
|
-
display: flex;
|
|
320
|
-
justify-content: center;
|
|
321
|
-
align-items: center;
|
|
322
|
-
min-height: 100vh;
|
|
323
|
-
width: 100%;
|
|
324
|
-
padding: 2rem;
|
|
325
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
326
|
-
background: var(--route-error-background, linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%));
|
|
327
|
-
color: var(--route-error-color, #2d3748);
|
|
328
|
-
line-height: 1.6;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.container {
|
|
332
|
-
max-width: 520px;
|
|
333
|
-
margin: 0 auto;
|
|
334
|
-
text-align: center;
|
|
335
|
-
background: var(--route-error-container-bg, rgba(255, 255, 255, 0.95));
|
|
336
|
-
backdrop-filter: blur(10px);
|
|
337
|
-
border-radius: var(--route-error-border-radius, 24px);
|
|
338
|
-
padding: 3rem 2rem;
|
|
339
|
-
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));
|
|
340
|
-
border: 1px solid var(--route-error-border, rgba(255, 255, 255, 0.2));
|
|
341
|
-
animation: slideUp 0.6s ease-out;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
@keyframes slideUp {
|
|
345
|
-
from {
|
|
346
|
-
opacity: 0;
|
|
347
|
-
transform: translateY(30px);
|
|
348
|
-
}
|
|
349
|
-
to {
|
|
350
|
-
opacity: 1;
|
|
351
|
-
transform: translateY(0);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.icon {
|
|
356
|
-
font-size: 5rem;
|
|
357
|
-
margin-bottom: 1.5rem;
|
|
358
|
-
animation: bounce 0.8s ease-out 0.2s both;
|
|
359
|
-
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
@keyframes bounce {
|
|
363
|
-
0%, 20%, 53%, 80%, 100% {
|
|
364
|
-
transform: translate3d(0, 0, 0);
|
|
365
|
-
}
|
|
366
|
-
40%, 43% {
|
|
367
|
-
transform: translate3d(0, -10px, 0);
|
|
368
|
-
}
|
|
369
|
-
70% {
|
|
370
|
-
transform: translate3d(0, -5px, 0);
|
|
371
|
-
}
|
|
372
|
-
90% {
|
|
373
|
-
transform: translate3d(0, -2px, 0);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
.code {
|
|
378
|
-
font-size: 2rem;
|
|
379
|
-
font-weight: 700;
|
|
380
|
-
margin-bottom: 1rem;
|
|
381
|
-
color: var(--route-error-code-color, #4a5568);
|
|
382
|
-
letter-spacing: -0.025em;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
.message {
|
|
386
|
-
font-size: 1.125rem;
|
|
387
|
-
margin-bottom: 2.5rem;
|
|
388
|
-
color: var(--route-error-message-color, #718096);
|
|
389
|
-
font-weight: 400;
|
|
390
|
-
max-width: 400px;
|
|
391
|
-
margin-left: auto;
|
|
392
|
-
margin-right: auto;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.actions {
|
|
396
|
-
display: flex;
|
|
397
|
-
gap: 1rem;
|
|
398
|
-
justify-content: center;
|
|
399
|
-
flex-wrap: wrap;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.button {
|
|
403
|
-
position: relative;
|
|
404
|
-
padding: 0.875rem 2rem;
|
|
405
|
-
border: none;
|
|
406
|
-
border-radius: 12px;
|
|
407
|
-
font-size: 0.95rem;
|
|
408
|
-
font-weight: 600;
|
|
409
|
-
cursor: pointer;
|
|
410
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
411
|
-
text-decoration: none;
|
|
412
|
-
display: inline-flex;
|
|
413
|
-
align-items: center;
|
|
414
|
-
justify-content: center;
|
|
415
|
-
gap: 0.5rem;
|
|
416
|
-
font-family: inherit;
|
|
417
|
-
min-width: 120px;
|
|
418
|
-
overflow: hidden;
|
|
419
|
-
user-select: none;
|
|
420
|
-
-webkit-tap-highlight-color: transparent;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
.button:first-child {
|
|
424
|
-
background: var(--route-error-primary-button-bg, linear-gradient(135deg, #667eea 0%, #764ba2 100%));
|
|
425
|
-
color: var(--route-error-primary-button-color, white);
|
|
426
|
-
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.4);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.button:first-child:hover {
|
|
430
|
-
transform: translateY(-2px);
|
|
431
|
-
box-shadow: 0 8px 25px 0 rgba(102, 126, 234, 0.5);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.button:first-child:active {
|
|
435
|
-
transform: translateY(0);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
.button:last-child {
|
|
439
|
-
background: var(--route-error-secondary-button-bg, rgba(255, 255, 255, 0.9));
|
|
440
|
-
color: var(--route-error-secondary-button-color, #4a5568);
|
|
441
|
-
border: 2px solid var(--route-error-secondary-button-border, rgba(74, 85, 104, 0.2));
|
|
442
|
-
backdrop-filter: blur(10px);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
.button:last-child:hover {
|
|
446
|
-
background: var(--route-error-secondary-button-hover-bg, rgba(255, 255, 255, 1));
|
|
447
|
-
border-color: var(--route-error-secondary-button-hover-border, rgba(74, 85, 104, 0.4));
|
|
448
|
-
transform: translateY(-1px);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
.button:focus-visible {
|
|
452
|
-
outline: 2px solid var(--route-error-focus-color, #667eea);
|
|
453
|
-
outline-offset: 2px;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
.button::before {
|
|
457
|
-
content: '';
|
|
458
|
-
position: absolute;
|
|
459
|
-
top: 0;
|
|
460
|
-
left: -100%;
|
|
461
|
-
width: 100%;
|
|
462
|
-
height: 100%;
|
|
463
|
-
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
464
|
-
transition: left 0.5s;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
.button:hover::before {
|
|
468
|
-
left: 100%;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
@media (max-width: 640px) {
|
|
472
|
-
:host {
|
|
473
|
-
padding: 1rem;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
.container {
|
|
477
|
-
padding: 2rem 1.5rem;
|
|
478
|
-
border-radius: 20px;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
.icon {
|
|
482
|
-
font-size: 4rem;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
.code {
|
|
486
|
-
font-size: 1.75rem;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
.message {
|
|
490
|
-
font-size: 1rem;
|
|
491
|
-
margin-bottom: 2rem;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.actions {
|
|
495
|
-
flex-direction: column;
|
|
496
|
-
align-items: center;
|
|
497
|
-
gap: 0.75rem;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
.button {
|
|
501
|
-
width: 100%;
|
|
502
|
-
max-width: 280px;
|
|
503
|
-
padding: 1rem 2rem;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
@media (max-width: 480px) {
|
|
508
|
-
.container {
|
|
509
|
-
margin: 1rem;
|
|
510
|
-
padding: 1.5rem 1rem;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
.icon {
|
|
514
|
-
font-size: 3.5rem;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
@media (prefers-color-scheme: dark) {
|
|
519
|
-
:host {
|
|
520
|
-
background: var(--route-error-dark-background, linear-gradient(135deg, #1a202c 0%, #2d3748 100%));
|
|
521
|
-
color: var(--route-error-dark-color, #e2e8f0);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
.container {
|
|
525
|
-
background: var(--route-error-dark-container-bg, rgba(45, 55, 72, 0.95));
|
|
526
|
-
border: 1px solid var(--route-error-dark-border, rgba(255, 255, 255, 0.1));
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
.code {
|
|
530
|
-
color: var(--route-error-dark-code-color, #f7fafc);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
.message {
|
|
534
|
-
color: var(--route-error-dark-message-color, #a0aec0);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
.button:first-child {
|
|
538
|
-
background: var(--route-error-dark-primary-button-bg, linear-gradient(135deg, #553c9a 0%, #764ba2 100%));
|
|
539
|
-
box-shadow: 0 4px 15px 0 rgba(85, 60, 154, 0.4);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
.button:first-child:hover {
|
|
543
|
-
box-shadow: 0 8px 25px 0 rgba(85, 60, 154, 0.5);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
.button:last-child {
|
|
547
|
-
background: var(--route-error-dark-secondary-button-bg, rgba(74, 85, 104, 0.3));
|
|
548
|
-
color: var(--route-error-dark-secondary-button-color, #e2e8f0);
|
|
549
|
-
border: 2px solid var(--route-error-dark-secondary-button-border, rgba(226, 232, 240, 0.2));
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
.button:last-child:hover {
|
|
553
|
-
background: var(--route-error-dark-secondary-button-hover-bg, rgba(74, 85, 104, 0.5));
|
|
554
|
-
border-color: var(--route-error-dark-secondary-button-hover-border, rgba(226, 232, 240, 0.4));
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
@media (prefers-reduced-motion: reduce) {
|
|
559
|
-
.container {
|
|
560
|
-
animation: none;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
.icon {
|
|
564
|
-
animation: none;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
.button::before {
|
|
568
|
-
display: none;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
.button {
|
|
572
|
-
transition: none;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
`;
|
|
576
|
-
var __defProp = Object.defineProperty;
|
|
577
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
578
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
579
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
580
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
581
|
-
if (decorator = decorators[i])
|
|
582
|
-
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
583
|
-
if (kind && result) __defProp(target, key, result);
|
|
584
|
-
return result;
|
|
585
|
-
};
|
|
586
|
-
let ErrorPage = class extends lit.LitElement {
|
|
587
|
-
render() {
|
|
588
|
-
const error = this.error || this.getDefaultError();
|
|
589
|
-
const icon = this.getErrorIcon(error.code);
|
|
590
|
-
return lit.html`
|
|
591
|
-
<div class="container" role="alert" aria-live="polite">
|
|
592
|
-
<div class="icon" aria-hidden="true">${icon}</div>
|
|
593
|
-
<div class="code" aria-label="Error code">${error.code}</div>
|
|
594
|
-
<div class="message">${error.message}</div>
|
|
595
|
-
|
|
596
|
-
<div class="actions">
|
|
597
|
-
<button
|
|
598
|
-
class="button"
|
|
599
|
-
@click=${this.handleGoBack}
|
|
600
|
-
title="Go back to previous page"
|
|
601
|
-
aria-label="Go back to previous page">
|
|
602
|
-
← Go Back
|
|
603
|
-
</button>
|
|
604
|
-
|
|
605
|
-
<button
|
|
606
|
-
class="button"
|
|
607
|
-
@click=${this.handleRefresh}
|
|
608
|
-
title="Refresh the current page"
|
|
609
|
-
aria-label="Refresh the current page">
|
|
610
|
-
🔄 Refresh
|
|
611
|
-
</button>
|
|
612
|
-
</div>
|
|
613
|
-
</div>
|
|
614
|
-
`;
|
|
615
|
-
}
|
|
616
|
-
/** 기본 에러 정보 반환 */
|
|
617
|
-
getDefaultError() {
|
|
618
|
-
return new RouteError(500, "Something went wrong. Please try again or contact support if the problem persists.");
|
|
619
|
-
}
|
|
620
|
-
/** 에러 코드에 따른 기본 아이콘 반환 */
|
|
621
|
-
getErrorIcon(code) {
|
|
622
|
-
const numericCode = typeof code === "string" ? parseInt(code) : code;
|
|
623
|
-
switch (numericCode) {
|
|
624
|
-
case 404:
|
|
625
|
-
return "🔍";
|
|
626
|
-
case 403:
|
|
627
|
-
return "🔒";
|
|
628
|
-
case 401:
|
|
629
|
-
return "🔑";
|
|
630
|
-
case 429:
|
|
631
|
-
return "⏱️";
|
|
632
|
-
case 503:
|
|
633
|
-
return "🛠️";
|
|
634
|
-
case 500:
|
|
635
|
-
default:
|
|
636
|
-
return "⚠️";
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
/** 뒤로가기 */
|
|
640
|
-
handleGoBack() {
|
|
641
|
-
window.history.back();
|
|
642
|
-
}
|
|
643
|
-
/** 새로고침 */
|
|
644
|
-
handleRefresh() {
|
|
645
|
-
window.location.reload();
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
ErrorPage.styles = styles;
|
|
649
|
-
__decorateClass([
|
|
650
|
-
decorators_js.property({ type: Object })
|
|
651
|
-
], ErrorPage.prototype, "error", 2);
|
|
652
|
-
ErrorPage = __decorateClass([
|
|
653
|
-
decorators_js.customElement("u-error-page")
|
|
654
|
-
], ErrorPage);
|
|
655
|
-
function getRandomID() {
|
|
656
|
-
return window.isSecureContext ? window.crypto.randomUUID() : window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
|
|
657
|
-
}
|
|
658
|
-
function findOutlet(element) {
|
|
659
|
-
let outlet = void 0;
|
|
660
|
-
if (element.shadowRoot) {
|
|
661
|
-
outlet = element.shadowRoot.querySelector("u-outlet");
|
|
662
|
-
if (outlet) return outlet;
|
|
663
|
-
for (const child of Array.from(element.shadowRoot.children)) {
|
|
664
|
-
outlet = findOutlet(child);
|
|
665
|
-
if (outlet) return outlet;
|
|
666
|
-
}
|
|
667
|
-
} else {
|
|
668
|
-
outlet = element.querySelector("u-outlet");
|
|
669
|
-
if (outlet) return outlet;
|
|
670
|
-
for (const child of Array.from(element.children)) {
|
|
671
|
-
outlet = findOutlet(child);
|
|
672
|
-
if (outlet) return outlet;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
return void 0;
|
|
676
|
-
}
|
|
677
|
-
function findOutletOrThrow(element) {
|
|
678
|
-
const outlet = findOutlet(element);
|
|
679
|
-
if (!outlet) {
|
|
680
|
-
throw new Error("No Outlet component found in the root element.");
|
|
681
|
-
}
|
|
682
|
-
return outlet;
|
|
683
|
-
}
|
|
684
|
-
function findAnchorFromEvent(e2) {
|
|
685
|
-
const targets = e2.composedPath() || [];
|
|
686
|
-
if (targets && targets.length) {
|
|
687
|
-
for (const node of targets) {
|
|
688
|
-
if (!(node instanceof Element)) continue;
|
|
689
|
-
if (node.tagName === "A") return node;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
const tgt = e2.target;
|
|
693
|
-
if (!tgt) return null;
|
|
694
|
-
const anchor = tgt.closest("a");
|
|
695
|
-
return anchor;
|
|
696
|
-
}
|
|
697
|
-
function setRoutes(routes, basepath) {
|
|
698
|
-
for (const route of routes) {
|
|
699
|
-
route.id ||= getRandomID();
|
|
700
|
-
if ("index" in route && route.index) {
|
|
701
|
-
route.path = new URLPattern({ pathname: `${basepath}{/}?` });
|
|
702
|
-
} else if ("path" in route && route.path) {
|
|
703
|
-
if (typeof route.path === "string") {
|
|
704
|
-
const absolutePathStr = absolutePath(basepath, route.path);
|
|
705
|
-
route.path = new URLPattern({ pathname: `${absolutePathStr}{/}?` });
|
|
706
|
-
}
|
|
707
|
-
} else {
|
|
708
|
-
throw new Error('Route must have either "index" or "path" property defined.');
|
|
709
|
-
}
|
|
710
|
-
if (route.children && route.children.length > 0) {
|
|
711
|
-
let childBasepath;
|
|
712
|
-
if ("index" in route) {
|
|
713
|
-
childBasepath = basepath;
|
|
714
|
-
} else {
|
|
715
|
-
if (typeof route.path === "string") {
|
|
716
|
-
childBasepath = absolutePath(basepath, route.path);
|
|
717
|
-
} else {
|
|
718
|
-
childBasepath = route.path.pathname.replace("{/}?", "");
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
route.children = setRoutes(route.children, childBasepath);
|
|
722
|
-
route.force ||= false;
|
|
723
|
-
} else {
|
|
724
|
-
route.force ||= true;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
return routes;
|
|
728
|
-
}
|
|
729
|
-
function getRoutes(pathname, routes) {
|
|
730
|
-
for (const route of routes) {
|
|
731
|
-
if (route.children) {
|
|
732
|
-
const childRoutes = getRoutes(pathname, route.children);
|
|
733
|
-
if (childRoutes.length > 0) {
|
|
734
|
-
return [route, ...childRoutes];
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
let matches = false;
|
|
738
|
-
if ("index" in route && route.index && route.path) {
|
|
739
|
-
matches = route.path.test({ pathname });
|
|
740
|
-
} else if ("path" in route && route.path instanceof URLPattern) {
|
|
741
|
-
matches = route.path.test({ pathname });
|
|
742
|
-
}
|
|
743
|
-
if (matches) {
|
|
744
|
-
return [route];
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
return [];
|
|
748
|
-
}
|
|
749
|
-
class Router {
|
|
750
|
-
constructor(config) {
|
|
751
|
-
this.handleWindowPopstate = async () => {
|
|
752
|
-
const href = window.location.href;
|
|
753
|
-
await this.go(href);
|
|
754
|
-
};
|
|
755
|
-
this.handleRootClick = (e2) => {
|
|
756
|
-
try {
|
|
757
|
-
if (e2.defaultPrevented) return;
|
|
758
|
-
if (e2.button !== 0 || e2.metaKey || e2.ctrlKey || e2.shiftKey) return;
|
|
759
|
-
const anchor = findAnchorFromEvent(e2);
|
|
760
|
-
if (!anchor) return;
|
|
761
|
-
const href = anchor.getAttribute("href") || anchor.href;
|
|
762
|
-
if (!href) return;
|
|
763
|
-
if (isExternalUrl(href)) return;
|
|
764
|
-
if (anchor.hasAttribute("download")) return;
|
|
765
|
-
if (anchor.getAttribute("rel") === "external") return;
|
|
766
|
-
if (anchor.target && anchor.target !== "") return;
|
|
767
|
-
e2.preventDefault();
|
|
768
|
-
void this.go(anchor.href);
|
|
769
|
-
} catch {
|
|
770
|
-
}
|
|
771
|
-
};
|
|
772
|
-
this._rootElement = config.root;
|
|
773
|
-
this._basepath = absolutePath(config.basepath || "/");
|
|
774
|
-
this._routes = setRoutes(config.routes, this._basepath);
|
|
775
|
-
this.waitConnected();
|
|
776
|
-
window.removeEventListener("popstate", this.handleWindowPopstate);
|
|
777
|
-
window.addEventListener("popstate", this.handleWindowPopstate);
|
|
778
|
-
if (config.useIntercept !== false) {
|
|
779
|
-
this._rootElement.removeEventListener("click", this.handleRootClick);
|
|
780
|
-
this._rootElement.addEventListener("click", this.handleRootClick);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
/** 초기 라우팅 처리, TODO: 제거 */
|
|
784
|
-
async waitConnected() {
|
|
785
|
-
let outlet = findOutlet(this._rootElement);
|
|
786
|
-
let count = 0;
|
|
787
|
-
while (!outlet && count < 20) {
|
|
788
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
789
|
-
outlet = findOutlet(this._rootElement);
|
|
790
|
-
count++;
|
|
791
|
-
}
|
|
792
|
-
this.handleWindowPopstate();
|
|
793
|
-
}
|
|
794
|
-
/** 객체를 정리하고 이벤트 리스너를 제거합니다. */
|
|
795
|
-
destroy() {
|
|
796
|
-
window.removeEventListener("popstate", this.handleWindowPopstate);
|
|
797
|
-
this._rootElement.removeEventListener("click", this.handleRootClick);
|
|
798
|
-
this._routeInfo = void 0;
|
|
799
|
-
this._requestID = void 0;
|
|
800
|
-
}
|
|
801
|
-
/** 라우터의 기본 경로 반환 */
|
|
802
|
-
get basepath() {
|
|
803
|
-
return this._basepath;
|
|
804
|
-
}
|
|
805
|
-
/** 등록된 라우트 반환 */
|
|
806
|
-
get routes() {
|
|
807
|
-
return this._routes;
|
|
808
|
-
}
|
|
809
|
-
/** 현재 라우팅 정보 반환 */
|
|
810
|
-
get routeInfo() {
|
|
811
|
-
return this._routeInfo;
|
|
812
|
-
}
|
|
813
|
-
/**
|
|
814
|
-
* 지정한 경로의 클라이언트 라우팅을 수행합니다. 상대경로일 경우 basepath와 조합되어 이동합니다.
|
|
815
|
-
* @param href 이동할 경로
|
|
816
|
-
*/
|
|
817
|
-
async go(href) {
|
|
818
|
-
const requestID = getRandomID();
|
|
819
|
-
this._requestID = requestID;
|
|
820
|
-
const routeInfo = parseUrl(href, this._basepath);
|
|
821
|
-
if (routeInfo.href === this._routeInfo?.href) return;
|
|
822
|
-
try {
|
|
823
|
-
if (this._requestID !== requestID) return;
|
|
824
|
-
window.dispatchEvent(new RouteBeginEvent(routeInfo));
|
|
825
|
-
const routes = getRoutes(routeInfo.pathname, this._routes);
|
|
826
|
-
const lastRoute = routes[routes.length - 1];
|
|
827
|
-
if (lastRoute && "path" in lastRoute && lastRoute.path instanceof URLPattern) {
|
|
828
|
-
routeInfo.params = lastRoute.path.exec({ pathname: routeInfo.pathname })?.pathname.groups || {};
|
|
829
|
-
}
|
|
830
|
-
this._routeInfo = routeInfo;
|
|
831
|
-
window.route = routeInfo;
|
|
832
|
-
if (this._requestID !== requestID) return;
|
|
833
|
-
if (routes.length === 0) {
|
|
834
|
-
throw new NotFoundRouteError(routeInfo.href);
|
|
835
|
-
}
|
|
836
|
-
let outlet = findOutletOrThrow(this._rootElement);
|
|
837
|
-
let title = void 0;
|
|
838
|
-
for (const route of routes) {
|
|
839
|
-
if (this._requestID !== requestID) return;
|
|
840
|
-
const content = route.render(routeInfo);
|
|
841
|
-
const element = await outlet.renderContent({ id: route.id, content, force: route.force });
|
|
842
|
-
outlet = findOutlet(element) || outlet;
|
|
843
|
-
title = route.title || title;
|
|
844
|
-
}
|
|
845
|
-
document.title = title || document.title;
|
|
846
|
-
if (this._requestID !== requestID) return;
|
|
847
|
-
if (routeInfo.href !== window.location.href) {
|
|
848
|
-
window.history.pushState({ basepath: routeInfo.basepath }, "", routeInfo.href);
|
|
849
|
-
} else {
|
|
850
|
-
window.history.replaceState({ basepath: routeInfo.basepath }, "", routeInfo.href);
|
|
851
|
-
}
|
|
852
|
-
window.dispatchEvent(new RouteDoneEvent(routeInfo));
|
|
853
|
-
} catch (error) {
|
|
854
|
-
const routeError = new RouteError(
|
|
855
|
-
error.status || error.code || "UNKNOWN_ERROR",
|
|
856
|
-
error.message || "An unexpected error occurred",
|
|
857
|
-
error
|
|
858
|
-
);
|
|
859
|
-
window.dispatchEvent(new RouteErrorEvent(routeError, routeInfo));
|
|
860
|
-
console.error("Routing error:", error);
|
|
861
|
-
try {
|
|
862
|
-
const errorEl = new ErrorPage();
|
|
863
|
-
errorEl.error = routeError;
|
|
864
|
-
document.body.innerHTML = "";
|
|
865
|
-
document.body.appendChild(errorEl);
|
|
866
|
-
} catch (pageError) {
|
|
867
|
-
console.error("Failed to render error component:", pageError);
|
|
868
|
-
console.error("Original error:", error);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
exports.Link = Link;
|
|
874
|
-
exports.NotFoundRouteError = NotFoundRouteError;
|
|
875
|
-
exports.Outlet = Outlet;
|
|
876
|
-
exports.RouteBeginEvent = RouteBeginEvent;
|
|
877
|
-
exports.RouteDoneEvent = RouteDoneEvent;
|
|
878
|
-
exports.RouteError = RouteError;
|
|
879
|
-
exports.RouteErrorEvent = RouteErrorEvent;
|
|
880
|
-
exports.Router = Router;
|
|
881
|
-
exports.ULink = ULink;
|
|
882
|
-
exports.UOutlet = UOutlet;
|