@iyulab/router 0.5.3 → 0.6.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/README.md CHANGED
@@ -126,38 +126,24 @@ export class AppRoot extends LitElement {
126
126
 
127
127
  ```tsx
128
128
  import React from 'react';
129
- import { Outlet, Link } from '@iyulab/router';
129
+ import { UOutlet, ULink } from '@iyulab/router/react';
130
130
 
131
131
  export function AppRoot() {
132
132
  return (
133
133
  <div>
134
134
  <nav>
135
- <Link href="/">Home</Link>
136
- <Link href="/about">About</Link>
137
- <Link href="/user/123">User Profile</Link>
135
+ <ULink href="/">Home</ULink>
136
+ <ULink href="/about">About</ULink>
137
+ <ULink href="/user/123">User Profile</ULink>
138
138
  </nav>
139
139
  <main>
140
- <Outlet />
140
+ <UOutlet />
141
141
  </main>
142
142
  </div>
143
143
  );
144
144
  }
145
145
  ```
146
146
 
147
- ## Browser Support
148
-
149
- - **URLPattern API**: Required for routing functionality
150
- - **Modern browsers**: Chrome 95+, Firefox 106+, Safari 16.4+
151
- - **Polyfill**: Consider using [urlpattern-polyfill](https://www.npmjs.com/package/urlpattern-polyfill) for older browsers
152
-
153
- ## Contributing
154
-
155
- 1. Fork the repository
156
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
157
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
158
- 4. Push to the branch (`git push origin feature/amazing-feature`)
159
- 5. Open a Pull Request
160
-
161
147
  ## License
162
148
 
163
149
  MIT License - see [LICENSE](LICENSE) file for details.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { CSSResult } from 'lit';
2
2
  import { LitElement } from 'lit';
3
+ import { PropertyValues } from 'lit';
3
4
  import { ReactElement } from 'react';
4
- import { ReactWebComponent } from '@lit/react';
5
5
  import { TemplateResult } from 'lit';
6
6
  import { TemplateResult as TemplateResult_2 } from 'lit-html';
7
7
 
@@ -119,47 +119,6 @@ declare interface IndexRouteConfig extends BaseRouteConfig {
119
119
  index: true;
120
120
  }
121
121
 
122
- /**
123
- * - 클라이언트 라우팅을 지원하는 링크 엘리먼트입니다.
124
- * - 내부 링크는 클라이언트 라우팅을 수행하고, 외부 링크는 새로운 페이지로 이동합니다.
125
- * - 마우스 중간 버튼 클릭 또는 Ctrl 키를 누르면 새로운 탭에서 엽니다.
126
- */
127
- export declare class Link extends LitElement {
128
- /** 외부 링크 여부 */
129
- private isExternal;
130
- /** a 태그의 href 속성 및 새로운 페이지로 이동할 때 사용될 링크입니다. */
131
- anchorHref: string;
132
- /**
133
- * - 속성을 정의하지 않으면 basepath로 이동합니다.
134
- * - http 로 시작하면 새로운 페이지를 로드합니다.
135
- * - 절대경로일 경우 basepath로 시작하면 클라이언트 라우팅을 합니다. 그렇지 않으면 새로운 페이지를 로드합니다.
136
- * - 상대경로로 시작하면 (현재의 basepath + 상대경로)로 클라이언트 라우팅을 합니다.
137
- * - ?로 시작하면 현재 경로에 쿼리스트링을 추가하고, 클라이언트 라우팅을 합니다.
138
- * - #으로 시작하면 현재 경로에 해시를 추가하고, hashchange 이벤트를 발생시킵니다.
139
- */
140
- href?: string;
141
- connectedCallback(): void;
142
- disconnectedCallback(): void;
143
- protected updated(changedProperties: any): Promise<void>;
144
- render(): TemplateResult_2<1>;
145
- /**
146
- * 링크 엘리먼트의 마우스 이벤트 핸들러
147
- * - 새로운 탭에서 열기: 마우스 중간 버튼 또는 Ctrl 키를 누릅니다.
148
- * - 새로운 페이지로 이동: http로 시작하거나 절대경로일 경우 새로운 페이지로 이동합니다.
149
- * - 클라이언트 라우팅: 상대경로로 시작하면 클라이언트 라우팅을 합니다.
150
- */
151
- private handleMouseDown;
152
- /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
153
- private dispatchPopstate;
154
- /** 클라이언트 라우팅을 위해 hashchange 이벤트를 발생시킵니다. */
155
- private dispatchHashchange;
156
- /** 기본 a 태그의 클릭 이벤트를 막습니다. */
157
- private preventClickEvent;
158
- /** a 태그에 주입할 href 값을 계산합니다. */
159
- private getAnchorHref;
160
- static styles: CSSResult;
161
- }
162
-
163
122
  declare interface NonIndexRouteConfig extends BaseRouteConfig {
164
123
  /**
165
124
  * 인덱스 라우트가 아님을 나타냅니다.
@@ -179,28 +138,6 @@ export declare class NotFoundError extends RouteError {
179
138
  constructor(path: string, original?: Error | any);
180
139
  }
181
140
 
182
- /**
183
- * 라우트 아웃렛 컴포넌트
184
- * 라우트 설정에 따라 LitElement 또는 React 컴포넌트를 렌더링합니다.
185
- */
186
- export declare class Outlet extends LitElement {
187
- private routeId?;
188
- private container?;
189
- private content?;
190
- /** 쉐도우를 사용하지 않고, 직접 렌더링합니다. */
191
- protected createRenderRoot(): this;
192
- render(): TemplateResult<1>;
193
- /**
194
- * render 함수의 결과를 렌더링합니다.
195
- * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
196
- */
197
- renderContent({ id, content, force }: RenderOption): Promise<HTMLDivElement>;
198
- /**
199
- * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
200
- */
201
- clear(): void;
202
- }
203
-
204
141
  /**
205
142
  * u-outlet 요소를 찾을 수 없을 때 발생하는 에러
206
143
  */
@@ -360,7 +297,7 @@ export declare class RouteProgressEvent extends RouteEvent {
360
297
  }
361
298
 
362
299
  /**
363
- * `lit-element`와 `react-component` 기반의 클라이언트 사이드 라우터
300
+ * `lit-element`, `react`를 지원하는 클라이언트 사이드 라우터
364
301
  */
365
302
  export declare class Router {
366
303
  private readonly _rootElement;
@@ -429,9 +366,67 @@ export declare interface RouterConfig {
429
366
  initialLoad?: boolean;
430
367
  }
431
368
 
432
- export declare const ULink: ReactWebComponent<Link, {}>;
369
+ /**
370
+ * - 클라이언트 라우팅을 지원하는 링크 엘리먼트입니다.
371
+ * - 내부 링크는 클라이언트 라우팅을 수행하고, 외부 링크는 브라우저 기본 네비게이션을 사용합니다.
372
+ * - Ctrl/Meta/Shift/Alt, 중클릭/우클릭 등은 브라우저 기본 동작(새 탭, 컨텍스트 메뉴 등)을 그대로 유지합니다.
373
+ */
374
+ export declare class ULink extends LitElement {
375
+ /** 외부 링크 여부 */
376
+ private isExternal;
377
+ /** a 태그에 주입할 href 값 */
378
+ computedHref: string;
379
+ /**
380
+ * a 태그 target을 지원하고 싶으면 열어두는게 좋습니다.
381
+ * - _blank 등을 쓰면 무조건 브라우저 기본 동작을 따르도록 처리합니다.
382
+ */
383
+ target?: string;
384
+ /**
385
+ * - 속성을 정의하지 않으면 basepath로 이동합니다.
386
+ * - http(s)로 시작하면 외부 링크로 간주하고 브라우저 네비게이션을 사용합니다.
387
+ * - 절대경로(/...)일 경우 basepath로 시작하면 SPA 라우팅 대상이 될 수 있습니다.
388
+ * - 상대경로는 (basepath + 상대경로)로 SPA 라우팅 대상이 될 수 있습니다.
389
+ * - ?로 시작하면 현재 pathname에 쿼리스트링을 붙여 SPA 라우팅합니다.
390
+ * - #으로 시작하면 현재 URL에 해시만 바꾸고(브라우저 기본) 동작합니다.
391
+ */
392
+ href?: string;
393
+ protected willUpdate(changedProperties: PropertyValues): void;
394
+ render(): TemplateResult_2<1>;
395
+ /** basepath를 state에서 꺼내는 헬퍼 */
396
+ private getBasepath;
397
+ /** a 태그에 주입할 href 값을 계산합니다. */
398
+ private computeHref;
399
+ /**
400
+ * 클릭 가로채기 규칙
401
+ * - 좌클릭(0) + 보조키 없음(ctrl/meta/shift/alt 없음) + target이 _self일 때만 SPA 라우팅 고려
402
+ * - 그 외(중클릭/우클릭/보조키/target=_blank 등)는 브라우저 기본 동작 유지
403
+ */
404
+ private handleAnchorClick;
405
+ /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
406
+ private dispatchPopstate;
407
+ static styles: CSSResult;
408
+ }
433
409
 
434
- export declare const UOutlet: ReactWebComponent<Outlet, {}>;
410
+ /**
411
+ * 라우트 설정에 따라 LitElement 또는 React 컴포넌트를 렌더링합니다.
412
+ */
413
+ export declare class UOutlet extends LitElement {
414
+ private routeId?;
415
+ private container?;
416
+ private content?;
417
+ /** 외부 스타일을 적용하기 위해 라이트 돔을사용 합니다. */
418
+ protected createRenderRoot(): this;
419
+ render(): TemplateResult<1>;
420
+ /**
421
+ * render 함수의 결과를 렌더링합니다.
422
+ * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
423
+ */
424
+ renderContent({ id, content, force }: RenderOption): Promise<HTMLDivElement>;
425
+ /**
426
+ * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
427
+ */
428
+ clear(): void;
429
+ }
435
430
 
436
431
  export { }
437
432
 
@@ -442,9 +437,4 @@ declare global {
442
437
  'route-done': RouteDoneEvent;
443
438
  'route-error': RouteErrorEvent;
444
439
  }
445
-
446
- interface HTMLElementTagNameMap {
447
- 'u-link': Link;
448
- 'u-outlet': Outlet;
449
- }
450
440
  }
package/dist/index.js CHANGED
@@ -1,269 +1,8 @@
1
- import React from "react";
2
- import { LitElement, html, css, render } from "lit";
3
- import { state, property, customElement } from "lit/decorators.js";
4
- import { createRoot } from "react-dom/client";
1
+ import { a as absolutePath, i as isExternalUrl, p as parseUrl } from "./share-ZrQFmsur.js";
2
+ import { b, U } from "./share-ZrQFmsur.js";
3
+ import { css, LitElement, html } from "lit";
4
+ import { property, customElement } from "lit/decorators.js";
5
5
  import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
6
- const e = /* @__PURE__ */ new Set(["children", "localName", "ref", "style", "className"]), n = /* @__PURE__ */ new WeakMap(), t = (e2, t2, o2, l, a) => {
7
- const s = a?.[t2];
8
- void 0 === s ? (e2[t2] = o2, null == o2 && t2 in HTMLElement.prototype && e2.removeAttribute(t2)) : o2 !== l && ((e3, t3, o3) => {
9
- let l2 = n.get(e3);
10
- void 0 === l2 && n.set(e3, l2 = /* @__PURE__ */ new Map());
11
- let a2 = l2.get(t3);
12
- 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));
13
- })(e2, s, o2);
14
- }, o = ({ react: n2, tagName: o2, elementClass: l, events: a, displayName: s }) => {
15
- const c = new Set(Object.keys(a ?? {})), r = n2.forwardRef(((s2, r2) => {
16
- const i = n2.useRef(/* @__PURE__ */ new Map()), d = n2.useRef(null), f = {}, u = {};
17
- 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;
18
- return n2.useLayoutEffect((() => {
19
- if (null === d.current) return;
20
- const e2 = /* @__PURE__ */ new Map();
21
- 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]);
22
- for (const [e3, n3] of i.current) t(d.current, e3, void 0, n3, a);
23
- i.current = e2;
24
- })), n2.useLayoutEffect((() => {
25
- d.current?.removeAttribute("defer-hydration");
26
- }), []), f.suppressHydrationWarning = true, n2.createElement(o2, { ...f, ref: n2.useCallback(((e2) => {
27
- d.current = e2, "function" == typeof r2 ? r2(e2) : null !== r2 && (r2.current = e2);
28
- }), [r2]) });
29
- }));
30
- return r.displayName = s ?? l.name, r;
31
- };
32
- function isExternalUrl(url) {
33
- if (!url) return false;
34
- const s = url.trim();
35
- if (/^(?:mailto:|tel:|javascript:)/i.test(s)) return true;
36
- if (s.startsWith("//")) return true;
37
- try {
38
- const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
39
- const parsed = new URL(s, base);
40
- if (/^(?:ftp:|ftps:|data:|ws:|wss:)/i.test(parsed.protocol)) return true;
41
- return parsed.origin !== new URL(base).origin;
42
- } catch {
43
- return false;
44
- }
45
- }
46
- function parseUrl(url, basepath) {
47
- let urlObj;
48
- basepath = catchBasePath(basepath);
49
- if (url.startsWith("http")) {
50
- urlObj = new URL(url);
51
- } else if (url.startsWith("/")) {
52
- urlObj = new URL(url, window.location.origin);
53
- } else if (url.startsWith("?")) {
54
- urlObj = new URL(window.location.pathname + url, window.location.origin);
55
- } else if (url.startsWith("#")) {
56
- urlObj = new URL(window.location.pathname + window.location.search + url, window.location.origin);
57
- } else {
58
- urlObj = new URL(absolutePath(basepath, url), window.location.origin);
59
- }
60
- return {
61
- href: urlObj.href,
62
- origin: urlObj.origin,
63
- basepath,
64
- path: urlObj.href.replace(urlObj.origin, ""),
65
- pathname: urlObj.pathname,
66
- query: new URLSearchParams(urlObj.search),
67
- hash: urlObj.hash,
68
- params: {},
69
- progress: () => {
70
- }
71
- };
72
- }
73
- function absolutePath(...paths) {
74
- paths = paths.map((p) => p.replace(/^\/|\/$/g, "")).filter((p) => p.length > 0);
75
- if (paths.length === 0) return "/";
76
- return "/" + paths.join("/");
77
- }
78
- function catchBasePath(basepath) {
79
- if (basepath === "/") return basepath;
80
- let pattern = new URLPattern({ pathname: basepath + "/*" });
81
- let match = pattern.exec({ pathname: window.location.pathname });
82
- if (match) {
83
- const rawPath = match.pathname.input;
84
- const restPath = match.pathname.groups?.["0"];
85
- return restPath ? rawPath.replace("/" + restPath, "") : rawPath.slice(0, -1);
86
- }
87
- pattern = new URLPattern({ pathname: `${basepath}{/}?` });
88
- match = pattern.exec({ pathname: window.location.pathname });
89
- if (match) {
90
- return match.pathname.input;
91
- }
92
- return basepath;
93
- }
94
- var __defProp$1 = Object.defineProperty;
95
- var __decorateClass$1 = (decorators, target, key, kind) => {
96
- var result = void 0;
97
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
98
- if (decorator = decorators[i])
99
- result = decorator(target, key, result) || result;
100
- if (result) __defProp$1(target, key, result);
101
- return result;
102
- };
103
- const _Link = class _Link extends LitElement {
104
- constructor() {
105
- super(...arguments);
106
- this.isExternal = false;
107
- this.anchorHref = "#";
108
- this.handleMouseDown = (event) => {
109
- const isNonNavigationClick = event.button === 2 || event.metaKey || event.shiftKey || event.altKey;
110
- if (event.defaultPrevented || isNonNavigationClick) return;
111
- event.preventDefault();
112
- event.stopPropagation();
113
- const basepath = window.history.state?.basepath || "";
114
- if (event.button === 1 || event.ctrlKey) {
115
- window.open(this.anchorHref, "_blank");
116
- } else if (!this.href) {
117
- this.dispatchPopstate(basepath, basepath);
118
- } else if (this.isExternal || this.href.startsWith("/") && !this.href.startsWith(basepath)) {
119
- window.location.href = this.href;
120
- } else if (this.href.startsWith("#")) {
121
- const url = window.location.pathname + window.location.search + this.href;
122
- this.dispatchHashchange(basepath, url);
123
- } else if (this.href.startsWith("?")) {
124
- const url = window.location.pathname + this.href;
125
- this.dispatchPopstate(basepath, url);
126
- } else {
127
- const url = absolutePath(basepath, this.href);
128
- this.dispatchPopstate(basepath, url);
129
- }
130
- };
131
- this.preventClickEvent = (event) => {
132
- event.preventDefault();
133
- event.stopPropagation();
134
- };
135
- }
136
- connectedCallback() {
137
- super.connectedCallback();
138
- this.addEventListener("mousedown", this.handleMouseDown);
139
- }
140
- disconnectedCallback() {
141
- this.removeEventListener("mousedown", this.handleMouseDown);
142
- super.disconnectedCallback();
143
- }
144
- async updated(changedProperties) {
145
- super.updated(changedProperties);
146
- await this.updateComplete;
147
- if (changedProperties.has("href")) {
148
- this.isExternal = isExternalUrl(this.href || "");
149
- this.anchorHref = this.getAnchorHref(this.href);
150
- }
151
- }
152
- render() {
153
- return html`
154
- <a href=${this.anchorHref} @click=${this.preventClickEvent}>
155
- <slot></slot>
156
- </a>
157
- `;
158
- }
159
- /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
160
- dispatchPopstate(basepath, url) {
161
- window.history.pushState({ basepath }, "", url);
162
- window.dispatchEvent(new PopStateEvent("popstate"));
163
- }
164
- /** 클라이언트 라우팅을 위해 hashchange 이벤트를 발생시킵니다. */
165
- dispatchHashchange(basepath, url) {
166
- window.history.pushState({ basepath }, "", url);
167
- window.dispatchEvent(new HashChangeEvent("hashchange"));
168
- }
169
- /** a 태그에 주입할 href 값을 계산합니다. */
170
- getAnchorHref(href) {
171
- const basepath = window.history.state?.basepath || "";
172
- if (!href) {
173
- return window.location.origin + basepath;
174
- }
175
- if (this.isExternal || href.startsWith("/") || href.startsWith("#") || href.startsWith("?")) {
176
- return href;
177
- }
178
- return absolutePath(basepath, href);
179
- }
180
- };
181
- _Link.styles = css`
182
- :host {
183
- display: inline-flex;
184
- cursor: pointer;
185
- }
186
-
187
- a {
188
- display: contents;
189
- text-decoration: none;
190
- color: inherit;
191
- }
192
- `;
193
- let Link = _Link;
194
- __decorateClass$1([
195
- state()
196
- ], Link.prototype, "anchorHref");
197
- __decorateClass$1([
198
- property({ type: String })
199
- ], Link.prototype, "href");
200
- class Outlet extends LitElement {
201
- /** 쉐도우를 사용하지 않고, 직접 렌더링합니다. */
202
- createRenderRoot() {
203
- return this;
204
- }
205
- render() {
206
- return html`${this.container}`;
207
- }
208
- /**
209
- * render 함수의 결과를 렌더링합니다.
210
- * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
211
- */
212
- async renderContent({ id, content, force }) {
213
- if (this.routeId === id && force === false && this.container) {
214
- return this.container;
215
- }
216
- this.routeId = id;
217
- this.clear();
218
- if (!this.container) {
219
- throw new Error("Outlet container is not initialized.");
220
- }
221
- if (typeof content !== "object") {
222
- throw new Error("Content is not a valid renderable object.");
223
- }
224
- if (content instanceof HTMLElement) {
225
- this.container.appendChild(content);
226
- } else if ("_$litType$" in content) {
227
- this.content = render(content, this.container);
228
- } else if ("$$typeof" in content) {
229
- this.content = createRoot(this.container);
230
- this.content.render(content);
231
- } else {
232
- throw new Error("not supported content type for Outlet rendering.");
233
- }
234
- this.requestUpdate();
235
- await this.updateComplete;
236
- return this.container;
237
- }
238
- /**
239
- * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
240
- */
241
- clear() {
242
- if (this.content) {
243
- if ("unmount" in this.content) {
244
- this.content.unmount();
245
- }
246
- if ("setConnected" in this.content) {
247
- this.content.setConnected(false);
248
- }
249
- this.content = void 0;
250
- }
251
- this.container = document.createElement("div");
252
- this.container.style.display = "contents";
253
- }
254
- }
255
- customElements.define("u-link", Link);
256
- customElements.define("u-outlet", Outlet);
257
- const ULink = o({
258
- react: React,
259
- tagName: "u-link",
260
- elementClass: Link
261
- });
262
- const UOutlet = o({
263
- react: React,
264
- tagName: "u-outlet",
265
- elementClass: Outlet
266
- });
267
6
  class RouteError extends Error {
268
7
  constructor(code, message, original) {
269
8
  super(message);
@@ -335,100 +74,51 @@ class RouteErrorEvent extends RouteEvent {
335
74
  this.error = error;
336
75
  }
337
76
  }
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>';
77
+ const ban = '<svg xmlns="http://www.w3.org/2000/svg" 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
78
  const __vite_glob_0_0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
340
79
  __proto__: null,
341
80
  default: ban
342
81
  }, 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>';
82
+ const boxSeam = '<svg xmlns="http://www.w3.org/2000/svg" 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
83
  const __vite_glob_0_1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
345
84
  __proto__: null,
346
85
  default: boxSeam
347
86
  }, 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>';
87
+ const exclamationTriangle = '<svg xmlns="http://www.w3.org/2000/svg" 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
88
  const __vite_glob_0_2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
350
89
  __proto__: null,
351
90
  default: exclamationTriangle
352
91
  }, 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>';
92
+ const palette = '<svg xmlns="http://www.w3.org/2000/svg" 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
93
  const __vite_glob_0_3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
355
94
  __proto__: null,
356
95
  default: palette
357
96
  }, 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>';
97
+ const personLock = '<svg xmlns="http://www.w3.org/2000/svg" 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
98
  const __vite_glob_0_4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
360
99
  __proto__: null,
361
100
  default: personLock
362
101
  }, 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>';
102
+ const search = '<svg xmlns="http://www.w3.org/2000/svg" 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
103
  const __vite_glob_0_5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
365
104
  __proto__: null,
366
105
  default: search
367
106
  }, 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>';
107
+ const stopwatch = '<svg xmlns="http://www.w3.org/2000/svg" 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
108
  const __vite_glob_0_6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
370
109
  __proto__: null,
371
110
  default: stopwatch
372
111
  }, 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>';
112
+ const wifiOff = '<svg xmlns="http://www.w3.org/2000/svg" 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
113
  const __vite_glob_0_7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
375
114
  __proto__: null,
376
115
  default: wifiOff
377
116
  }, 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>';
117
+ const wrenchAdjustable = '<svg xmlns="http://www.w3.org/2000/svg" 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
118
  const __vite_glob_0_8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
380
119
  __proto__: null,
381
120
  default: wrenchAdjustable
382
121
  }, Symbol.toStringTag, { value: "Module" }));
383
- const styles = css`
384
- :host {
385
- display: flex;
386
- flex-direction: column;
387
- justify-content: center;
388
- align-items: center;
389
- min-height: 100vh;
390
- width: 100%;
391
- text-align: center;
392
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
393
- overflow: auto;
394
- user-select: none;
395
- }
396
-
397
- .icon {
398
- display: contents;
399
- font-size: 6rem;
400
- color: var(--route-error-color, #4a5568);
401
- opacity: 0.85;
402
- }
403
-
404
- .code {
405
- font-size: 2rem;
406
- font-weight: 700;
407
- margin: 1rem 0;
408
- color: var(--route-error-code-color, #1a202c);
409
- letter-spacing: -0.5px;
410
- }
411
-
412
- .message {
413
- font-size: 1rem;
414
- color: var(--route-error-message-color, #718096);
415
- max-width: 600px;
416
- line-height: 1.6;
417
- }
418
-
419
- @media (prefers-color-scheme: dark) {
420
- .icon {
421
- color: var(--route-error-dark-color, #a0aec0);
422
- opacity: 0.9;
423
- }
424
- .code {
425
- color: var(--route-error-dark-code-color, #f7fafc);
426
- }
427
- .message {
428
- color: var(--route-error-dark-message-color, #cbd5e0);
429
- }
430
- }
431
- `;
432
122
  var __defProp = Object.defineProperty;
433
123
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
434
124
  var __decorateClass = (decorators, target, key, kind) => {
@@ -454,7 +144,7 @@ const icons = Object.entries(/* @__PURE__ */ Object.assign({
454
144
  acc[name] = content.default;
455
145
  return acc;
456
146
  }, {});
457
- let ErrorPage = class extends LitElement {
147
+ let UErrorPage = class extends LitElement {
458
148
  render() {
459
149
  const error = this.error || this.getDefaultError();
460
150
  const icon = this.getErrorIcon(error.code);
@@ -496,13 +186,73 @@ let ErrorPage = class extends LitElement {
496
186
  }
497
187
  }
498
188
  };
499
- ErrorPage.styles = styles;
189
+ UErrorPage.styles = css`
190
+ :host {
191
+ --route-icon-color: #4a5568;
192
+ --route-code-color: #1a202c;
193
+ --route-message-color: #718096;
194
+ }
195
+ :host-context([theme="dark"]) {
196
+ --route-icon-color: #a0aec0;
197
+ --route-code-color: #f7fafc;
198
+ --route-message-color: #cbd5e0;
199
+ }
200
+
201
+ @media (prefers-color-scheme: dark) {
202
+ :host {
203
+ --route-icon-color: #a0aec0;
204
+ --route-code-color: #f7fafc;
205
+ --route-message-color: #cbd5e0;
206
+ }
207
+ };
208
+
209
+ :host {
210
+ display: flex;
211
+ flex-direction: column;
212
+ justify-content: center;
213
+ align-items: center;
214
+ min-height: 100vh;
215
+ width: 100%;
216
+ text-align: center;
217
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
218
+ overflow: auto;
219
+ user-select: none;
220
+ }
221
+
222
+ .icon {
223
+ display: contents;
224
+ font-size: 6rem;
225
+ color: var(--route-icon-color);
226
+ opacity: 0.85;
227
+ }
228
+
229
+ svg {
230
+ width: 1em;
231
+ height: 1em;
232
+ fill: currentColor;
233
+ }
234
+
235
+ .code {
236
+ font-size: 2rem;
237
+ font-weight: 700;
238
+ margin: 1rem 0;
239
+ color: var(--route-code-color);
240
+ letter-spacing: -0.5px;
241
+ }
242
+
243
+ .message {
244
+ font-size: 1rem;
245
+ color: var(--route-message-color);
246
+ max-width: 600px;
247
+ line-height: 1.6;
248
+ }
249
+ `;
500
250
  __decorateClass([
501
251
  property({ type: Object })
502
- ], ErrorPage.prototype, "error", 2);
503
- ErrorPage = __decorateClass([
252
+ ], UErrorPage.prototype, "error", 2);
253
+ UErrorPage = __decorateClass([
504
254
  customElement("u-error-page")
505
- ], ErrorPage);
255
+ ], UErrorPage);
506
256
  function getRandomID() {
507
257
  return window.isSecureContext ? window.crypto.randomUUID() : window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
508
258
  }
@@ -532,15 +282,15 @@ function findOutletOrThrow(element) {
532
282
  }
533
283
  return outlet;
534
284
  }
535
- function findAnchorFromEvent(e2) {
536
- const targets = e2.composedPath() || [];
285
+ function findAnchorFromEvent(e) {
286
+ const targets = e.composedPath() || [];
537
287
  if (targets && targets.length) {
538
288
  for (const node of targets) {
539
289
  if (!(node instanceof Element)) continue;
540
290
  if (node.tagName === "A") return node;
541
291
  }
542
292
  }
543
- const tgt = e2.target;
293
+ const tgt = e.target;
544
294
  if (!tgt) return null;
545
295
  const anchor = tgt.closest("a");
546
296
  return anchor;
@@ -600,11 +350,11 @@ class Router {
600
350
  const href = window.location.href;
601
351
  await this.go(href);
602
352
  };
603
- this.handleRootClick = (e2) => {
353
+ this.handleRootClick = (e) => {
604
354
  try {
605
- if (e2.defaultPrevented) return;
606
- if (e2.button !== 0 || e2.metaKey || e2.ctrlKey || e2.shiftKey) return;
607
- const anchor = findAnchorFromEvent(e2);
355
+ if (e.defaultPrevented) return;
356
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey) return;
357
+ const anchor = findAnchorFromEvent(e);
608
358
  if (!anchor) return;
609
359
  const href = anchor.getAttribute("href") || anchor.href;
610
360
  if (!href) return;
@@ -612,7 +362,7 @@ class Router {
612
362
  if (anchor.hasAttribute("download")) return;
613
363
  if (anchor.getAttribute("rel") === "external") return;
614
364
  if (anchor.target && anchor.target !== "") return;
615
- e2.preventDefault();
365
+ e.preventDefault();
616
366
  void this.go(anchor.href);
617
367
  } catch {
618
368
  }
@@ -721,7 +471,7 @@ class Router {
721
471
  outlet.renderContent({ id: "#fallback", content: fallbackContent, force: true });
722
472
  document.title = this._fallback.title || document.title;
723
473
  } else {
724
- const errorContent = new ErrorPage();
474
+ const errorContent = new UErrorPage();
725
475
  errorContent.error = error;
726
476
  if (outlet) {
727
477
  outlet.renderContent({ id: "#error", content: errorContent, force: true });
@@ -740,9 +490,7 @@ class Router {
740
490
  export {
741
491
  ContentLoadError,
742
492
  ContentRenderError,
743
- Link,
744
493
  NotFoundError,
745
- Outlet,
746
494
  OutletMissingError,
747
495
  RouteBeginEvent,
748
496
  RouteDoneEvent,
@@ -750,6 +498,6 @@ export {
750
498
  RouteErrorEvent,
751
499
  RouteProgressEvent,
752
500
  Router,
753
- ULink,
754
- UOutlet
501
+ b as ULink,
502
+ U as UOutlet
755
503
  };
@@ -0,0 +1,89 @@
1
+ import { CSSResult } from 'lit';
2
+ import { LitElement } from 'lit';
3
+ import { PropertyValues } from 'lit';
4
+ import { ReactElement } from 'react';
5
+ import { ReactWebComponent } from '@lit/react';
6
+ import { TemplateResult } from 'lit-html';
7
+ import { TemplateResult as TemplateResult_2 } from 'lit';
8
+
9
+ declare interface RenderOption {
10
+ id?: string;
11
+ force?: boolean;
12
+ content: RenderResult;
13
+ }
14
+
15
+ declare type RenderResult = HTMLElement | ReactElement | TemplateResult_2<1> | false;
16
+
17
+ /**
18
+ * `u-link` 웹 컴포넌트를 React에서 사용할 수 있도록 래핑한 컴포넌트입니다.
19
+ */
20
+ export declare const ULink: ReactWebComponent<ULink_2, {}>;
21
+
22
+ /**
23
+ * - 클라이언트 라우팅을 지원하는 링크 엘리먼트입니다.
24
+ * - 내부 링크는 클라이언트 라우팅을 수행하고, 외부 링크는 브라우저 기본 네비게이션을 사용합니다.
25
+ * - Ctrl/Meta/Shift/Alt, 중클릭/우클릭 등은 브라우저 기본 동작(새 탭, 컨텍스트 메뉴 등)을 그대로 유지합니다.
26
+ */
27
+ declare class ULink_2 extends LitElement {
28
+ /** 외부 링크 여부 */
29
+ private isExternal;
30
+ /** a 태그에 주입할 href 값 */
31
+ computedHref: string;
32
+ /**
33
+ * a 태그 target을 지원하고 싶으면 열어두는게 좋습니다.
34
+ * - _blank 등을 쓰면 무조건 브라우저 기본 동작을 따르도록 처리합니다.
35
+ */
36
+ target?: string;
37
+ /**
38
+ * - 속성을 정의하지 않으면 basepath로 이동합니다.
39
+ * - http(s)로 시작하면 외부 링크로 간주하고 브라우저 네비게이션을 사용합니다.
40
+ * - 절대경로(/...)일 경우 basepath로 시작하면 SPA 라우팅 대상이 될 수 있습니다.
41
+ * - 상대경로는 (basepath + 상대경로)로 SPA 라우팅 대상이 될 수 있습니다.
42
+ * - ?로 시작하면 현재 pathname에 쿼리스트링을 붙여 SPA 라우팅합니다.
43
+ * - #으로 시작하면 현재 URL에 해시만 바꾸고(브라우저 기본) 동작합니다.
44
+ */
45
+ href?: string;
46
+ protected willUpdate(changedProperties: PropertyValues): void;
47
+ render(): TemplateResult<1>;
48
+ /** basepath를 state에서 꺼내는 헬퍼 */
49
+ private getBasepath;
50
+ /** a 태그에 주입할 href 값을 계산합니다. */
51
+ private computeHref;
52
+ /**
53
+ * 클릭 가로채기 규칙
54
+ * - 좌클릭(0) + 보조키 없음(ctrl/meta/shift/alt 없음) + target이 _self일 때만 SPA 라우팅 고려
55
+ * - 그 외(중클릭/우클릭/보조키/target=_blank 등)는 브라우저 기본 동작 유지
56
+ */
57
+ private handleAnchorClick;
58
+ /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
59
+ private dispatchPopstate;
60
+ static styles: CSSResult;
61
+ }
62
+
63
+ /**
64
+ * `u-outlet` 웹 컴포넌트를 React에서 사용할 수 있도록 래핑한 컴포넌트입니다.
65
+ */
66
+ export declare const UOutlet: ReactWebComponent<UOutlet_2, {}>;
67
+
68
+ /**
69
+ * 라우트 설정에 따라 LitElement 또는 React 컴포넌트를 렌더링합니다.
70
+ */
71
+ declare class UOutlet_2 extends LitElement {
72
+ private routeId?;
73
+ private container?;
74
+ private content?;
75
+ /** 외부 스타일을 적용하기 위해 라이트 돔을사용 합니다. */
76
+ protected createRenderRoot(): this;
77
+ render(): TemplateResult_2<1>;
78
+ /**
79
+ * render 함수의 결과를 렌더링합니다.
80
+ * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
81
+ */
82
+ renderContent({ id, content, force }: RenderOption): Promise<HTMLDivElement>;
83
+ /**
84
+ * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
85
+ */
86
+ clear(): void;
87
+ }
88
+
89
+ export { }
package/dist/react.js ADDED
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { b as ULink$1, U as UOutlet$1 } from "./share-ZrQFmsur.js";
3
+ const e = /* @__PURE__ */ new Set(["children", "localName", "ref", "style", "className"]), n = /* @__PURE__ */ new WeakMap(), t = (e2, t2, o2, l, a) => {
4
+ const s = a?.[t2];
5
+ void 0 === s ? (e2[t2] = o2, null == o2 && t2 in HTMLElement.prototype && e2.removeAttribute(t2)) : o2 !== l && ((e3, t3, o3) => {
6
+ let l2 = n.get(e3);
7
+ void 0 === l2 && n.set(e3, l2 = /* @__PURE__ */ new Map());
8
+ let a2 = l2.get(t3);
9
+ 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));
10
+ })(e2, s, o2);
11
+ }, o = ({ react: n2, tagName: o2, elementClass: l, events: a, displayName: s }) => {
12
+ const c = new Set(Object.keys(a ?? {})), r = n2.forwardRef(((s2, r2) => {
13
+ const i = n2.useRef(/* @__PURE__ */ new Map()), d = n2.useRef(null), f = {}, u = {};
14
+ 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;
15
+ return n2.useLayoutEffect((() => {
16
+ if (null === d.current) return;
17
+ const e2 = /* @__PURE__ */ new Map();
18
+ 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]);
19
+ for (const [e3, n3] of i.current) t(d.current, e3, void 0, n3, a);
20
+ i.current = e2;
21
+ })), n2.useLayoutEffect((() => {
22
+ d.current?.removeAttribute("defer-hydration");
23
+ }), []), f.suppressHydrationWarning = true, n2.createElement(o2, { ...f, ref: n2.useCallback(((e2) => {
24
+ d.current = e2, "function" == typeof r2 ? r2(e2) : null !== r2 && (r2.current = e2);
25
+ }), [r2]) });
26
+ }));
27
+ return r.displayName = s ?? l.name, r;
28
+ };
29
+ const ULink = o({
30
+ react: React,
31
+ tagName: "u-link",
32
+ elementClass: ULink$1,
33
+ events: {}
34
+ });
35
+ const UOutlet = o({
36
+ react: React,
37
+ tagName: "u-outlet",
38
+ elementClass: UOutlet$1,
39
+ events: {}
40
+ });
41
+ export {
42
+ ULink,
43
+ UOutlet
44
+ };
@@ -0,0 +1,253 @@
1
+ import { LitElement, html, render, css } from "lit";
2
+ import { customElement, state, property } from "lit/decorators.js";
3
+ import { createRoot } from "react-dom/client";
4
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
5
+ var __decorateClass$1 = (decorators, target, key, kind) => {
6
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
7
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
8
+ if (decorator = decorators[i])
9
+ result = decorator(result) || result;
10
+ return result;
11
+ };
12
+ let UOutlet = class extends LitElement {
13
+ /** 외부 스타일을 적용하기 위해 라이트 돔을사용 합니다. */
14
+ createRenderRoot() {
15
+ return this;
16
+ }
17
+ render() {
18
+ return html`${this.container}`;
19
+ }
20
+ /**
21
+ * render 함수의 결과를 렌더링합니다.
22
+ * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
23
+ */
24
+ async renderContent({ id, content, force }) {
25
+ if (this.routeId === id && force === false && this.container) {
26
+ return this.container;
27
+ }
28
+ this.routeId = id;
29
+ this.clear();
30
+ if (!this.container) {
31
+ throw new Error("Outlet container is not initialized.");
32
+ }
33
+ if (typeof content !== "object") {
34
+ throw new Error("Content is not a valid renderable object.");
35
+ }
36
+ if (content instanceof HTMLElement) {
37
+ this.container.appendChild(content);
38
+ } else if ("_$litType$" in content) {
39
+ this.content = render(content, this.container);
40
+ } else if ("$$typeof" in content) {
41
+ this.content = createRoot(this.container);
42
+ this.content.render(content);
43
+ } else {
44
+ throw new Error("not supported content type for Outlet rendering.");
45
+ }
46
+ this.requestUpdate();
47
+ await this.updateComplete;
48
+ return this.container;
49
+ }
50
+ /**
51
+ * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
52
+ */
53
+ clear() {
54
+ if (this.content) {
55
+ if ("unmount" in this.content) {
56
+ this.content.unmount();
57
+ }
58
+ if ("setConnected" in this.content) {
59
+ this.content.setConnected(false);
60
+ }
61
+ this.content = void 0;
62
+ }
63
+ this.container = document.createElement("div");
64
+ this.container.style.display = "contents";
65
+ }
66
+ };
67
+ UOutlet = __decorateClass$1([
68
+ customElement("u-outlet")
69
+ ], UOutlet);
70
+ function isExternalUrl(url) {
71
+ if (!url) return false;
72
+ const s = url.trim();
73
+ if (/^(?:mailto:|tel:|javascript:)/i.test(s)) return true;
74
+ if (s.startsWith("//")) return true;
75
+ try {
76
+ const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
77
+ const parsed = new URL(s, base);
78
+ if (/^(?:ftp:|ftps:|data:|ws:|wss:)/i.test(parsed.protocol)) return true;
79
+ return parsed.origin !== new URL(base).origin;
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+ function parseUrl(url, basepath) {
85
+ let urlObj;
86
+ basepath = catchBasePath(basepath);
87
+ if (url.startsWith("http")) {
88
+ urlObj = new URL(url);
89
+ } else if (url.startsWith("/")) {
90
+ urlObj = new URL(url, window.location.origin);
91
+ } else if (url.startsWith("?")) {
92
+ urlObj = new URL(window.location.pathname + url, window.location.origin);
93
+ } else if (url.startsWith("#")) {
94
+ urlObj = new URL(window.location.pathname + window.location.search + url, window.location.origin);
95
+ } else {
96
+ urlObj = new URL(absolutePath(basepath, url), window.location.origin);
97
+ }
98
+ return {
99
+ href: urlObj.href,
100
+ origin: urlObj.origin,
101
+ basepath,
102
+ path: urlObj.href.replace(urlObj.origin, ""),
103
+ pathname: urlObj.pathname,
104
+ query: new URLSearchParams(urlObj.search),
105
+ hash: urlObj.hash,
106
+ params: {},
107
+ progress: () => {
108
+ }
109
+ };
110
+ }
111
+ function absolutePath(...paths) {
112
+ paths = paths.map((p) => p.replace(/^\/|\/$/g, "")).filter((p) => p.length > 0);
113
+ if (paths.length === 0) return "/";
114
+ return "/" + paths.join("/");
115
+ }
116
+ function catchBasePath(basepath) {
117
+ if (basepath === "/") return basepath;
118
+ let pattern = new URLPattern({ pathname: basepath + "/*" });
119
+ let match = pattern.exec({ pathname: window.location.pathname });
120
+ if (match) {
121
+ const rawPath = match.pathname.input;
122
+ const restPath = match.pathname.groups?.["0"];
123
+ return restPath ? rawPath.replace("/" + restPath, "") : rawPath.slice(0, -1);
124
+ }
125
+ pattern = new URLPattern({ pathname: `${basepath}{/}?` });
126
+ match = pattern.exec({ pathname: window.location.pathname });
127
+ if (match) {
128
+ return match.pathname.input;
129
+ }
130
+ return basepath;
131
+ }
132
+ var __defProp = Object.defineProperty;
133
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
134
+ var __decorateClass = (decorators, target, key, kind) => {
135
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
136
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
137
+ if (decorator = decorators[i])
138
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
139
+ if (kind && result) __defProp(target, key, result);
140
+ return result;
141
+ };
142
+ let ULink = class extends LitElement {
143
+ constructor() {
144
+ super(...arguments);
145
+ this.isExternal = false;
146
+ this.computedHref = "#";
147
+ this.handleAnchorClick = (event) => {
148
+ if (event.defaultPrevented) return;
149
+ if (event.button !== 0) return;
150
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
151
+ const target = (this.target ?? "").trim();
152
+ if (target && target.toLowerCase() !== "_self") return;
153
+ const basepath = this.getBasepath();
154
+ if (!this.href) {
155
+ event.preventDefault();
156
+ this.dispatchPopstate(basepath, basepath);
157
+ return;
158
+ }
159
+ if (this.isExternal) return;
160
+ if (this.href.startsWith("#")) return;
161
+ event.preventDefault();
162
+ if (this.href.startsWith("?")) {
163
+ const url2 = window.location.pathname + this.href;
164
+ this.dispatchPopstate(basepath, url2);
165
+ return;
166
+ }
167
+ if (this.href.startsWith("/")) {
168
+ if (!this.href.startsWith(basepath)) {
169
+ window.location.assign(this.href);
170
+ return;
171
+ }
172
+ this.dispatchPopstate(basepath, this.href);
173
+ return;
174
+ }
175
+ const url = absolutePath(basepath, this.href);
176
+ this.dispatchPopstate(basepath, url);
177
+ };
178
+ }
179
+ willUpdate(changedProperties) {
180
+ super.willUpdate(changedProperties);
181
+ if (changedProperties.has("href")) {
182
+ this.isExternal = isExternalUrl(this.href || "");
183
+ this.computedHref = this.computeHref(this.href);
184
+ }
185
+ }
186
+ render() {
187
+ return html`
188
+ <a
189
+ target=${this.target ?? "_self"}
190
+ href=${this.computedHref}
191
+ @click=${this.handleAnchorClick}
192
+ >
193
+ <slot></slot>
194
+ </a>
195
+ `;
196
+ }
197
+ /** basepath를 state에서 꺼내는 헬퍼 */
198
+ getBasepath() {
199
+ return window.history.state?.basepath || "";
200
+ }
201
+ /** a 태그에 주입할 href 값을 계산합니다. */
202
+ computeHref(href) {
203
+ const basepath = this.getBasepath();
204
+ if (!href) {
205
+ return window.location.origin + basepath;
206
+ }
207
+ if (this.isExternal) return href;
208
+ if (href.startsWith("#") || href.startsWith("?")) return href;
209
+ if (href.startsWith("/")) return href;
210
+ return absolutePath(basepath, href);
211
+ }
212
+ /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
213
+ dispatchPopstate(basepath, url) {
214
+ window.history.pushState({ basepath }, "", url);
215
+ window.dispatchEvent(new PopStateEvent("popstate"));
216
+ }
217
+ };
218
+ ULink.styles = css`
219
+ :host {
220
+ display: inline-flex;
221
+ cursor: pointer;
222
+ }
223
+
224
+ a {
225
+ display: contents;
226
+ text-decoration: none;
227
+
228
+ font-size: inherit;
229
+ font-weight: inherit;
230
+ font-family: inherit;
231
+ color: inherit;
232
+ cursor: inherit;
233
+ }
234
+ `;
235
+ __decorateClass([
236
+ state()
237
+ ], ULink.prototype, "computedHref", 2);
238
+ __decorateClass([
239
+ property({ type: String })
240
+ ], ULink.prototype, "target", 2);
241
+ __decorateClass([
242
+ property({ type: String })
243
+ ], ULink.prototype, "href", 2);
244
+ ULink = __decorateClass([
245
+ customElement("u-link")
246
+ ], ULink);
247
+ export {
248
+ UOutlet as U,
249
+ absolutePath as a,
250
+ ULink as b,
251
+ isExternalUrl as i,
252
+ parseUrl as p
253
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/router",
3
- "version": "0.5.3",
3
+ "version": "0.6.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",
@@ -29,23 +29,27 @@
29
29
  ".": {
30
30
  "types": "./dist/index.d.ts",
31
31
  "import": "./dist/index.js"
32
+ },
33
+ "./react": {
34
+ "types": "./dist/react.d.ts",
35
+ "import": "./dist/react.js"
32
36
  }
33
37
  },
34
38
  "scripts": {
35
39
  "build": "vite build"
36
40
  },
37
41
  "dependencies": {
38
- "lit": "^3.3.1",
39
- "react": "^19.2.1",
40
- "react-dom": "^19.2.1"
42
+ "lit": "^3.3.2",
43
+ "react": "^19.2.3",
44
+ "react-dom": "^19.2.3"
41
45
  },
42
46
  "devDependencies": {
43
47
  "@lit/react": "^1.0.8",
44
- "@types/node": "^24.10.1",
45
- "@types/react": "^19.2.7",
48
+ "@types/node": "^25.0.8",
49
+ "@types/react": "^19.2.8",
46
50
  "@types/react-dom": "^19.2.3",
47
51
  "typescript": "^5.9.3",
48
- "vite": "^7.2.6",
52
+ "vite": "^7.3.1",
49
53
  "vite-plugin-dts": "^4.5.4"
50
54
  }
51
- }
55
+ }