@iyulab/router 0.7.3 → 0.7.5

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/react.d.ts CHANGED
@@ -1,11 +1,92 @@
1
+ import { CSSResult } from 'lit';
2
+ import { LitElement } from 'lit';
3
+ import { PropertyValues } from 'lit';
4
+ import { ReactWebComponent } from '@lit/react';
5
+ import { TemplateResult } from 'lit-html';
6
+
7
+ /** 렌더링 옵션 */
8
+ declare interface RenderOption {
9
+ /** 교차 렌더링 방지 ID */
10
+ id?: string;
11
+ /** 강제 렌더링 여부 */
12
+ force?: boolean;
13
+ /** 렌더링할 값 */
14
+ value: unknown;
15
+ }
16
+
1
17
  /**
2
18
  * `u-link` 웹 컴포넌트를 React에서 사용할 수 있도록 래핑한 컴포넌트입니다.
3
19
  */
4
- export declare const ULink: any;
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
+ /**
31
+ * 링크 대상 target 속성
32
+ *
33
+ * - `_self`: 현재 창에서 링크 열기 (기본값)
34
+ * - `_blank`: 새 탭/창에서 링크 열기
35
+ * - `_parent`: 부모 프레임에서 링크 열기
36
+ * - `_top`: 최상위 프레임에서 링크 열기
37
+ */
38
+ target?: string;
39
+ /**
40
+ * 링크 대상 URL, 다음 사항에 따라 SPA 라우팅 또는 브라우저 네비게이션이 결정됩니다.
41
+ *
42
+ * - 속성을 정의하지 않으면 설정에서 지정한 `basepath`로 SPA 라우팅합니다.
43
+ * - http(s)로 시작하면 외부 링크로 간주하고 브라우저 네비게이션을 사용합니다.
44
+ * - 절대경로(/...)의 경우 `basepath`로 시작하면 SPA 라우팅합니다, 이외 브라우저 네비게이션을 사용합니다.
45
+ * - 상대경로는 (basepath + 상대경로)로 결합하여 SPA 라우팅합니다.
46
+ * - ?로 시작하면 현재 경로에 쿼리스트링을 추가하여 SPA 라우팅합니다.
47
+ * - #으로 시작하면 브라우저 기본 동작을 사용합니다.
48
+ */
49
+ href?: string;
50
+ connectedCallback(): void;
51
+ disconnectedCallback(): void;
52
+ protected willUpdate(changedProperties: PropertyValues): void;
53
+ render(): TemplateResult<1>;
54
+ /** a 태그에 주입할 href 값을 계산합니다. */
55
+ private compute;
56
+ /**
57
+ * 클릭 가로채기 핸들러
58
+ * - 좌클릭(0) + 보조키 없음(ctrl/meta/shift/alt 없음) + target이 _self일 때만 SPA 라우팅 고려
59
+ * - 그 외(중클릭/우클릭/보조키/target=_blank 등)는 브라우저 기본 동작 유지
60
+ */
61
+ private handleClick;
62
+ /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
63
+ private dispatchPopstate;
64
+ /** basepath를 state에서 꺼내는 헬퍼 */
65
+ private getBasepath;
66
+ static styles: CSSResult;
67
+ }
5
68
 
6
69
  /**
7
70
  * `u-outlet` 웹 컴포넌트를 React에서 사용할 수 있도록 래핑한 컴포넌트입니다.
8
71
  */
9
- export declare const UOutlet: any;
72
+ export declare const UOutlet: ReactWebComponent<UOutlet_2, {}>;
73
+
74
+ /**
75
+ * LitElement 또는 React 컴포넌트를 렌더링해주는 웹컴포넌트 입니다.
76
+ */
77
+ declare class UOutlet_2 extends HTMLElement {
78
+ /** 교차 렌더링 방지 id */
79
+ private routeId?;
80
+ /** 실제 렌더링 컨텐츠 */
81
+ private root?;
82
+ /**
83
+ * 주어진 렌더링 옵션에 따라 컨텐츠를 렌더링합니다.
84
+ */
85
+ render({ id, value, force }: RenderOption): Promise<void>;
86
+ /**
87
+ * 기존 DOM을 삭제하여, 초기 상태로 되돌립니다.
88
+ */
89
+ reset(): void;
90
+ }
10
91
 
11
92
  export { }
package/dist/react.js CHANGED
@@ -1,19 +1,24 @@
1
+ import { s as UOutlet$1, t as ULink$1 } from "./share-sbAElOI7.js";
1
2
  import React from "react";
2
3
  import { createComponent } from "@lit/react";
3
- import { U as ULink$1, b as UOutlet$1 } from "./share-CG-3Tbuy.js";
4
- const ULink = createComponent({
5
- react: React,
6
- tagName: "u-link",
7
- elementClass: ULink$1,
8
- events: {}
4
+ //#region src/react.ts
5
+ /**
6
+ * `u-link` 웹 컴포넌트를 React에서 사용할 수 있도록 래핑한 컴포넌트입니다.
7
+ */
8
+ var ULink = createComponent({
9
+ react: React,
10
+ tagName: "u-link",
11
+ elementClass: ULink$1,
12
+ events: {}
9
13
  });
10
- const UOutlet = createComponent({
11
- react: React,
12
- tagName: "u-outlet",
13
- elementClass: UOutlet$1,
14
- events: {}
14
+ /**
15
+ * `u-outlet` 웹 컴포넌트를 React에서 사용할 수 있도록 래핑한 컴포넌트입니다.
16
+ */
17
+ var UOutlet = createComponent({
18
+ react: React,
19
+ tagName: "u-outlet",
20
+ elementClass: UOutlet$1,
21
+ events: {}
15
22
  });
16
- export {
17
- ULink,
18
- UOutlet
19
- };
23
+ //#endregion
24
+ export { ULink, UOutlet };
@@ -0,0 +1,237 @@
1
+ import { LitElement, css, html, render } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import { ifDefined } from "lit/directives/if-defined.js";
4
+ //#region src/components/UOutlet.ts
5
+ /**
6
+ * LitElement 또는 React 컴포넌트를 렌더링해주는 웹컴포넌트 입니다.
7
+ */
8
+ var UOutlet = class extends HTMLElement {
9
+ /**
10
+ * 주어진 렌더링 옵션에 따라 컨텐츠를 렌더링합니다.
11
+ */
12
+ async render({ id, value, force }) {
13
+ if (this.routeId === id && force === false) return;
14
+ this.routeId = id;
15
+ this.reset();
16
+ if (value === null) throw new Error("Content is null and cannot be rendered.");
17
+ if (typeof value !== "object") throw new Error("Content is not a valid renderable object.");
18
+ if (value instanceof HTMLElement) {
19
+ this.replaceChildren(value);
20
+ this.root = void 0;
21
+ } else if ("_$litType$" in value) this.root = render(value, this);
22
+ else if ("$$typeof" in value) {
23
+ const { createRoot } = await import("react-dom/client");
24
+ this.root = createRoot(this);
25
+ this.root.render(value);
26
+ } else throw new Error("not supported content type for Outlet rendering.");
27
+ }
28
+ /**
29
+ * 기존 DOM을 삭제하여, 초기 상태로 되돌립니다.
30
+ */
31
+ reset() {
32
+ if (this.root && "_$litPart$" in this) delete this._$litPart$;
33
+ if (this.root && "unmount" in this.root) this.root.unmount();
34
+ this.root = void 0;
35
+ this.innerHTML = "";
36
+ }
37
+ };
38
+ customElements.define("u-outlet", UOutlet);
39
+ //#endregion
40
+ //#region src/internals/url-helpers.ts
41
+ /**
42
+ * 주어진 URL이 외부 링크인지 확인합니다.
43
+ *
44
+ * @param url 확인할 URL 문자열
45
+ * @return 외부 링크인 경우 true, 내부 링크인 경우 false
46
+ */
47
+ function isExternalUrl(url) {
48
+ if (!url) return false;
49
+ url = url.trim();
50
+ if (/^(?:mailto:|tel:|javascript:)/i.test(url)) return true;
51
+ if (url.startsWith("//")) return true;
52
+ try {
53
+ const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
54
+ const parsed = new URL(url, base);
55
+ if (/^(?:ftp:|ftps:|ws:|wss:)/i.test(parsed.protocol)) return true;
56
+ return parsed.origin !== new URL(base).origin;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ /**
62
+ * URL 문자열을 파싱하여 RouteContext 객체로 반환합니다.
63
+ * - http(s)로 시작하는 절대 URL은 외부 링크로 간주됩니다.
64
+ * - 절대경로(/...)는 그대로 사용됩니다.
65
+ * - 상대경로는 basepath를 기준으로 절대경로로 변환됩니다.
66
+ * - 쿼리스트링(?)로 시작하는 쿼리는 현재 경로와 추가됩니다.
67
+ * - 해시(#)로 시작하는 해시는 현재 경로와 추가됩니다.
68
+ *
69
+ * @param url 파싱할 URL 문자열
70
+ * @param basepath 기준이 되는 basepath 문자열
71
+ * @returns 파싱된 RouteContext 객체
72
+ */
73
+ function parseUrl(url, basepath) {
74
+ let urlObj;
75
+ basepath = catchBasepath(basepath);
76
+ if (url.startsWith("http")) urlObj = new URL(url);
77
+ else if (url.startsWith("/")) urlObj = new URL(url, window.location.origin);
78
+ else if (url.startsWith("?")) urlObj = new URL(window.location.pathname + url, window.location.origin);
79
+ else if (url.startsWith("#")) urlObj = new URL(window.location.pathname + window.location.search + url, window.location.origin);
80
+ else urlObj = new URL(absolutePath(basepath, url), window.location.origin);
81
+ return {
82
+ href: urlObj.href,
83
+ origin: urlObj.origin,
84
+ basepath,
85
+ path: urlObj.href.replace(urlObj.origin, ""),
86
+ pathname: urlObj.pathname,
87
+ query: new URLSearchParams(urlObj.search),
88
+ hash: urlObj.hash,
89
+ params: {},
90
+ progress: () => {},
91
+ meta: {}
92
+ };
93
+ }
94
+ /**
95
+ * pathname 경로를 조합하여 절대경로를 반환합니다.
96
+ *
97
+ * @param paths 조합할 경로 문자열들
98
+ * @returns 조합된 절대경로 문자열
99
+ */
100
+ function absolutePath(...paths) {
101
+ paths = paths.map((p) => p.replace(/^\/|\/$/g, "")).filter((p) => p.length > 0);
102
+ if (paths.length === 0) return "/";
103
+ return "/" + paths.join("/");
104
+ }
105
+ /**
106
+ * basepath가 동적 패턴일 경우(RouteConfig에서 basepath가 :id 등으로 정의된 경우),
107
+ * 현재 경로에서 해당되는 패턴의 basepath를 추출하여 반환합니다.
108
+ *
109
+ * @param basepath 동적 패턴이 포함된 basepath 문자열
110
+ * @return 현재 경로에 매칭되는 basepath 문자열
111
+ * @example
112
+ * catchBasePath('/app/:id') => '/app/123'
113
+ */
114
+ function catchBasepath(basepath) {
115
+ if (basepath === "/") return basepath;
116
+ let pattern = new URLPattern({ pathname: basepath + "/*" });
117
+ let match = pattern.exec({ pathname: window.location.pathname });
118
+ if (match) {
119
+ const rawPath = match.pathname.input;
120
+ const restPath = match.pathname.groups?.["0"];
121
+ return restPath !== void 0 && restPath !== "" ? rawPath.replace("/" + restPath, "") : rawPath.replace(/\/$/, "");
122
+ }
123
+ pattern = new URLPattern({ pathname: `${basepath}{/}?` });
124
+ match = pattern.exec({ pathname: window.location.pathname });
125
+ if (match) return match.pathname.input;
126
+ return basepath;
127
+ }
128
+ //#endregion
129
+ //#region \0@oxc-project+runtime@0.122.0/helpers/decorateMetadata.js
130
+ function __decorateMetadata(k, v) {
131
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
132
+ }
133
+ //#endregion
134
+ //#region \0@oxc-project+runtime@0.122.0/helpers/decorate.js
135
+ function __decorate(decorators, target, key, desc) {
136
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
137
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
138
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
139
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
140
+ }
141
+ //#endregion
142
+ //#region src/components/ULink.ts
143
+ var ULink = class ULink extends LitElement {
144
+ constructor(..._args) {
145
+ super(..._args);
146
+ this.isExternal = false;
147
+ this.handleClick = (event) => {
148
+ if (event.defaultPrevented) return;
149
+ if (event.button !== 0) return;
150
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
151
+ if (this.target && this.target.toLowerCase() !== "_self") return;
152
+ const basepath = this.getBasepath();
153
+ if (!this.href) {
154
+ event.preventDefault();
155
+ this.dispatchPopstate(basepath, basepath);
156
+ return;
157
+ }
158
+ if (this.isExternal) return;
159
+ if (this.href.startsWith("#")) return;
160
+ event.preventDefault();
161
+ if (this.href.startsWith("?")) {
162
+ const url = window.location.pathname + this.href;
163
+ this.dispatchPopstate(basepath, url);
164
+ return;
165
+ }
166
+ if (this.href.startsWith("/")) {
167
+ if (!this.href.startsWith(basepath)) {
168
+ window.location.assign(this.href);
169
+ return;
170
+ }
171
+ this.dispatchPopstate(basepath, this.href);
172
+ return;
173
+ }
174
+ const url = absolutePath(basepath, this.href);
175
+ this.dispatchPopstate(basepath, url);
176
+ };
177
+ }
178
+ connectedCallback() {
179
+ super.connectedCallback();
180
+ this.addEventListener("click", this.handleClick);
181
+ }
182
+ disconnectedCallback() {
183
+ this.removeEventListener("click", this.handleClick);
184
+ super.disconnectedCallback();
185
+ }
186
+ willUpdate(changedProperties) {
187
+ super.willUpdate(changedProperties);
188
+ if (changedProperties.has("href")) this.isExternal = isExternalUrl(this.href || "");
189
+ }
190
+ render() {
191
+ return html`
192
+ <a target=${ifDefined(this.target)} href=${this.compute(this.href)}>
193
+ <slot></slot>
194
+ </a>
195
+ `;
196
+ }
197
+ /** a 태그에 주입할 href 값을 계산합니다. */
198
+ compute(href) {
199
+ const basepath = this.getBasepath();
200
+ if (!href) return window.location.origin + basepath;
201
+ if (this.isExternal) return href;
202
+ if (href.startsWith("/")) return href;
203
+ if (href.startsWith("#") || href.startsWith("?")) return href;
204
+ return absolutePath(basepath, href);
205
+ }
206
+ /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
207
+ dispatchPopstate(basepath, url) {
208
+ window.history.pushState({ basepath }, "", url);
209
+ window.dispatchEvent(new PopStateEvent("popstate"));
210
+ }
211
+ /** basepath를 state에서 꺼내는 헬퍼 */
212
+ getBasepath() {
213
+ return window.history.state?.basepath || "/";
214
+ }
215
+ static {
216
+ this.styles = css`
217
+ :host {
218
+ cursor: pointer;
219
+ }
220
+
221
+ a {
222
+ text-decoration: none;
223
+
224
+ font-size: inherit;
225
+ font-weight: inherit;
226
+ font-family: inherit;
227
+ color: inherit;
228
+ cursor: inherit;
229
+ }
230
+ `;
231
+ }
232
+ };
233
+ __decorate([property({ type: String }), __decorateMetadata("design:type", String)], ULink.prototype, "target", void 0);
234
+ __decorate([property({ type: String }), __decorateMetadata("design:type", String)], ULink.prototype, "href", void 0);
235
+ ULink = __decorate([customElement("u-link")], ULink);
236
+ //#endregion
237
+ export { isExternalUrl as a, absolutePath as i, __decorate as n, parseUrl as o, __decorateMetadata as r, UOutlet as s, ULink as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/router",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "A modern client-side router for web applications with support for Lit and React components",
5
5
  "keywords": [
6
6
  "lit",
@@ -40,10 +40,10 @@
40
40
  "build": "vite build"
41
41
  },
42
42
  "dependencies": {
43
- "lit": "^3.3.2"
43
+ "lit": "^3.3.2",
44
+ "@lit/react": "^1.0.8"
44
45
  },
45
46
  "peerDependencies": {
46
- "@lit/react": ">=1.0.0",
47
47
  "react": ">=18.0.0",
48
48
  "react-dom": ">=18.0.0"
49
49
  },
@@ -53,17 +53,14 @@
53
53
  },
54
54
  "react-dom": {
55
55
  "optional": true
56
- },
57
- "@lit/react": {
58
- "optional": true
59
56
  }
60
57
  },
61
58
  "devDependencies": {
62
- "@types/node": "^25.3.2",
59
+ "@types/node": "^25.5.0",
63
60
  "@types/react": "^19.2.14",
64
61
  "@types/react-dom": "^19.2.3",
65
62
  "typescript": "^5.9.3",
66
- "vite": "^7.3.1",
63
+ "vite": "^8.0.3",
67
64
  "vite-plugin-dts": "^4.5.4"
68
65
  }
69
66
  }
@@ -1,224 +0,0 @@
1
- import { render, css, LitElement, html } from "lit";
2
- import { property, customElement } from "lit/decorators.js";
3
- import { ifDefined } from "lit/directives/if-defined.js";
4
- class UOutlet extends HTMLElement {
5
- /**
6
- * 주어진 렌더링 옵션에 따라 컨텐츠를 렌더링합니다.
7
- */
8
- async render({ id, value, force }) {
9
- if (this.routeId === id && force === false) return;
10
- this.routeId = id;
11
- this.reset();
12
- if (value === null) {
13
- throw new Error("Content is null and cannot be rendered.");
14
- }
15
- if (typeof value !== "object") {
16
- throw new Error("Content is not a valid renderable object.");
17
- }
18
- if (value instanceof HTMLElement) {
19
- this.replaceChildren(value);
20
- this.root = void 0;
21
- } else if ("_$litType$" in value) {
22
- this.root = render(value, this);
23
- } else if ("$$typeof" in value) {
24
- const { createRoot } = await import("react-dom/client");
25
- this.root = createRoot(this);
26
- this.root.render(value);
27
- } else {
28
- throw new Error("not supported content type for Outlet rendering.");
29
- }
30
- }
31
- /**
32
- * 기존 DOM을 삭제하여, 초기 상태로 되돌립니다.
33
- */
34
- reset() {
35
- if (this.root && "_$litPart$" in this) {
36
- delete this._$litPart$;
37
- }
38
- if (this.root && "unmount" in this.root) {
39
- this.root.unmount();
40
- }
41
- this.root = void 0;
42
- this.innerHTML = "";
43
- }
44
- }
45
- customElements.define("u-outlet", UOutlet);
46
- function isExternalUrl(url) {
47
- if (!url) return false;
48
- url = url.trim();
49
- if (/^(?:mailto:|tel:|javascript:)/i.test(url)) return true;
50
- if (url.startsWith("//")) return true;
51
- try {
52
- const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
53
- const parsed = new URL(url, base);
54
- if (/^(?:ftp:|ftps:|ws:|wss:)/i.test(parsed.protocol)) return true;
55
- return parsed.origin !== new URL(base).origin;
56
- } catch {
57
- return false;
58
- }
59
- }
60
- function parseUrl(url, basepath) {
61
- let urlObj;
62
- basepath = catchBasepath(basepath);
63
- if (url.startsWith("http")) {
64
- urlObj = new URL(url);
65
- } else if (url.startsWith("/")) {
66
- urlObj = new URL(url, window.location.origin);
67
- } else if (url.startsWith("?")) {
68
- urlObj = new URL(window.location.pathname + url, window.location.origin);
69
- } else if (url.startsWith("#")) {
70
- urlObj = new URL(window.location.pathname + window.location.search + url, window.location.origin);
71
- } else {
72
- urlObj = new URL(absolutePath(basepath, url), window.location.origin);
73
- }
74
- return {
75
- href: urlObj.href,
76
- origin: urlObj.origin,
77
- basepath,
78
- path: urlObj.href.replace(urlObj.origin, ""),
79
- pathname: urlObj.pathname,
80
- query: new URLSearchParams(urlObj.search),
81
- hash: urlObj.hash,
82
- params: {},
83
- progress: () => {
84
- },
85
- meta: {}
86
- };
87
- }
88
- function absolutePath(...paths) {
89
- paths = paths.map((p) => p.replace(/^\/|\/$/g, "")).filter((p) => p.length > 0);
90
- if (paths.length === 0) return "/";
91
- return "/" + paths.join("/");
92
- }
93
- function catchBasepath(basepath) {
94
- if (basepath === "/") return basepath;
95
- let pattern = new URLPattern({ pathname: basepath + "/*" });
96
- let match = pattern.exec({ pathname: window.location.pathname });
97
- if (match) {
98
- const rawPath = match.pathname.input;
99
- const restPath = match.pathname.groups?.["0"];
100
- return restPath !== void 0 && restPath !== "" ? rawPath.replace("/" + restPath, "") : rawPath.replace(/\/$/, "");
101
- }
102
- pattern = new URLPattern({ pathname: `${basepath}{/}?` });
103
- match = pattern.exec({ pathname: window.location.pathname });
104
- if (match) {
105
- return match.pathname.input;
106
- }
107
- return basepath;
108
- }
109
- var __defProp = Object.defineProperty;
110
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
111
- var __decorateClass = (decorators, target, key, kind) => {
112
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
113
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
114
- if (decorator = decorators[i])
115
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
116
- if (kind && result) __defProp(target, key, result);
117
- return result;
118
- };
119
- let ULink = class extends LitElement {
120
- constructor() {
121
- super(...arguments);
122
- this.isExternal = false;
123
- this.handleClick = (event) => {
124
- if (event.defaultPrevented) return;
125
- if (event.button !== 0) return;
126
- if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
127
- if (this.target && this.target.toLowerCase() !== "_self") return;
128
- const basepath = this.getBasepath();
129
- if (!this.href) {
130
- event.preventDefault();
131
- this.dispatchPopstate(basepath, basepath);
132
- return;
133
- }
134
- if (this.isExternal) return;
135
- if (this.href.startsWith("#")) return;
136
- event.preventDefault();
137
- if (this.href.startsWith("?")) {
138
- const url2 = window.location.pathname + this.href;
139
- this.dispatchPopstate(basepath, url2);
140
- return;
141
- }
142
- if (this.href.startsWith("/")) {
143
- if (!this.href.startsWith(basepath)) {
144
- window.location.assign(this.href);
145
- return;
146
- }
147
- this.dispatchPopstate(basepath, this.href);
148
- return;
149
- }
150
- const url = absolutePath(basepath, this.href);
151
- this.dispatchPopstate(basepath, url);
152
- };
153
- }
154
- connectedCallback() {
155
- super.connectedCallback();
156
- this.addEventListener("click", this.handleClick);
157
- }
158
- disconnectedCallback() {
159
- this.removeEventListener("click", this.handleClick);
160
- super.disconnectedCallback();
161
- }
162
- willUpdate(changedProperties) {
163
- super.willUpdate(changedProperties);
164
- if (changedProperties.has("href")) {
165
- this.isExternal = isExternalUrl(this.href || "");
166
- }
167
- }
168
- render() {
169
- return html`
170
- <a target=${ifDefined(this.target)} href=${this.compute(this.href)}>
171
- <slot></slot>
172
- </a>
173
- `;
174
- }
175
- /** a 태그에 주입할 href 값을 계산합니다. */
176
- compute(href) {
177
- const basepath = this.getBasepath();
178
- if (!href) return window.location.origin + basepath;
179
- if (this.isExternal) return href;
180
- if (href.startsWith("/")) return href;
181
- if (href.startsWith("#") || href.startsWith("?")) return href;
182
- return absolutePath(basepath, href);
183
- }
184
- /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
185
- dispatchPopstate(basepath, url) {
186
- window.history.pushState({ basepath }, "", url);
187
- window.dispatchEvent(new PopStateEvent("popstate"));
188
- }
189
- /** basepath를 state에서 꺼내는 헬퍼 */
190
- getBasepath() {
191
- return window.history.state?.basepath || "/";
192
- }
193
- };
194
- ULink.styles = css`
195
- :host {
196
- cursor: pointer;
197
- }
198
-
199
- a {
200
- text-decoration: none;
201
-
202
- font-size: inherit;
203
- font-weight: inherit;
204
- font-family: inherit;
205
- color: inherit;
206
- cursor: inherit;
207
- }
208
- `;
209
- __decorateClass([
210
- property({ type: String })
211
- ], ULink.prototype, "target", 2);
212
- __decorateClass([
213
- property({ type: String })
214
- ], ULink.prototype, "href", 2);
215
- ULink = __decorateClass([
216
- customElement("u-link")
217
- ], ULink);
218
- export {
219
- ULink as U,
220
- absolutePath as a,
221
- UOutlet as b,
222
- isExternalUrl as i,
223
- parseUrl as p
224
- };