@iyulab/router 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -145,10 +145,14 @@ export declare class OutletMissingError extends RouteError {
145
145
  constructor();
146
146
  }
147
147
 
148
+ /** 렌더링 옵션 */
148
149
  declare interface RenderOption {
150
+ /** 교차 렌더링 방지 ID */
149
151
  id?: string;
152
+ /** 강제 렌더링 여부 */
150
153
  force?: boolean;
151
- content: RenderResult;
154
+ /** 렌더링할 값 */
155
+ value: RenderResult;
152
156
  }
153
157
 
154
158
  export declare type RenderResult = HTMLElement | ReactElement | TemplateResult<1> | false;
@@ -297,7 +301,7 @@ export declare class RouteProgressEvent extends RouteEvent {
297
301
  }
298
302
 
299
303
  /**
300
- * `lit-element`, `react`를 지원하는 클라이언트 사이드 라우터
304
+ * `lit-element`, `react`를 지원하는 SPA 클라이언트 라우터 객체입니다.
301
305
  */
302
306
  export declare class Router {
303
307
  private readonly _rootElement;
@@ -375,57 +379,60 @@ export declare class ULink extends LitElement {
375
379
  /** 외부 링크 여부 */
376
380
  private isExternal;
377
381
  /**
378
- * a 태그 target 지원하고 싶으면 열어두는게 좋습니다.
379
- * - _blank 등을 쓰면 무조건 브라우저 기본 동작을 따르도록 처리합니다.
382
+ * 링크 대상 target 속성
383
+ *
384
+ * - `_self`: 현재 창에서 링크 열기 (기본값)
385
+ * - `_blank`: 새 탭/창에서 링크 열기
386
+ * - `_parent`: 부모 프레임에서 링크 열기
387
+ * - `_top`: 최상위 프레임에서 링크 열기
380
388
  */
381
389
  target?: string;
382
390
  /**
383
- * - 속성을 정의하지 않으면 basepath로 이동합니다.
391
+ * 링크 대상 URL, 다음 사항에 따라 SPA 라우팅 또는 브라우저 네비게이션이 결정됩니다.
392
+ *
393
+ * - 속성을 정의하지 않으면 설정에서 지정한 `basepath`로 SPA 라우팅합니다.
384
394
  * - http(s)로 시작하면 외부 링크로 간주하고 브라우저 네비게이션을 사용합니다.
385
- * - 절대경로(/...) 경우 basepath 시작하면 SPA 라우팅 대상이 있습니다.
386
- * - 상대경로는 (basepath + 상대경로)로 SPA 라우팅 대상이 될 수 있습니다.
387
- * - ?로 시작하면 현재 pathname에 쿼리스트링을 붙여 SPA 라우팅합니다.
388
- * - #으로 시작하면 현재 URL에 해시만 바꾸고(브라우저 기본) 동작합니다.
395
+ * - 절대경로(/...) 경우 `basepath`로 시작하면 SPA 라우팅합니다, 이외 브라우저 네비게이션을 사용합니다.
396
+ * - 상대경로는 (basepath + 상대경로)로 결합하여 SPA 라우팅합니다.
397
+ * - ?로 시작하면 현재 경로에 쿼리스트링을 추가하여 SPA 라우팅합니다.
398
+ * - #으로 시작하면 브라우저 기본 동작을 사용합니다.
389
399
  */
390
400
  href?: string;
401
+ connectedCallback(): void;
402
+ disconnectedCallback(): void;
391
403
  protected willUpdate(changedProperties: PropertyValues): void;
392
404
  render(): TemplateResult_2<1>;
393
- /** basepath를 state에서 꺼내는 헬퍼 */
394
- private getBasepath;
395
405
  /** a 태그에 주입할 href 값을 계산합니다. */
396
- private computeHref;
406
+ private compute;
397
407
  /**
398
- * 클릭 가로채기 규칙
408
+ * 클릭 가로채기 핸들러
399
409
  * - 좌클릭(0) + 보조키 없음(ctrl/meta/shift/alt 없음) + target이 _self일 때만 SPA 라우팅 고려
400
410
  * - 그 외(중클릭/우클릭/보조키/target=_blank 등)는 브라우저 기본 동작 유지
401
411
  */
402
- private handleAnchorClick;
412
+ private handleClick;
403
413
  /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
404
414
  private dispatchPopstate;
415
+ /** basepath를 state에서 꺼내는 헬퍼 */
416
+ private getBasepath;
405
417
  static styles: CSSResult;
406
418
  }
407
419
 
408
420
  /**
409
- * 라우트 설정에 따라 LitElement 또는 React 컴포넌트를 렌더링합니다.
421
+ * LitElement 또는 React 컴포넌트를 렌더링해주는 웹컴포넌트 입니다.
410
422
  */
411
- export declare class UOutlet extends LitElement {
423
+ export declare class UOutlet extends HTMLElement {
424
+ /** 교차 렌더링 방지 id */
412
425
  private routeId?;
413
- private container?;
414
- private content?;
415
- /** 외부 스타일을 적용하기 위해 라이트 돔을사용 합니다. */
416
- protected createRenderRoot(): this;
417
- /** 컴포넌트가 DOM에 연결될 때 초기화 작업 수행 */
418
- connectedCallback(): void;
419
- render(): TemplateResult<1>;
426
+ /** 실제 렌더링 컨텐츠 */
427
+ private root?;
420
428
  /**
421
- * render 함수의 결과를 렌더링합니다.
422
- * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
429
+ * 주어진 렌더링 옵션에 따라 컨텐츠를 렌더링합니다.
423
430
  */
424
- renderContent({ id, content, force }: RenderOption): Promise<HTMLDivElement>;
431
+ render({ id, value, force }: RenderOption): void;
425
432
  /**
426
- * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
433
+ * 기존 DOM을 삭제하여, 초기 상태로 되돌립니다.
427
434
  */
428
- clear(): void;
435
+ reset(): void;
429
436
  }
430
437
 
431
438
  export { }
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
- import { a as absolutePath, i as isExternalUrl, p as parseUrl } from "./share-SPj-xl6y.js";
2
- import { b, U } from "./share-SPj-xl6y.js";
1
+ import { a as absolutePath, i as isExternalUrl, p as parseUrl } from "./share-6Zs4TunA.js";
2
+ import { b, U } from "./share-6Zs4TunA.js";
3
3
  import { css, LitElement, html } from "lit";
4
4
  import { property, customElement } from "lit/decorators.js";
5
- import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
6
5
  class RouteError extends Error {
7
6
  constructor(code, message, original) {
8
7
  super(message);
@@ -74,51 +73,6 @@ class RouteErrorEvent extends RouteEvent {
74
73
  this.error = error;
75
74
  }
76
75
  }
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>';
78
- const __vite_glob_0_0 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
79
- __proto__: null,
80
- default: ban
81
- }, Symbol.toStringTag, { value: "Module" }));
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>';
83
- const __vite_glob_0_1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
84
- __proto__: null,
85
- default: boxSeam
86
- }, Symbol.toStringTag, { value: "Module" }));
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>';
88
- const __vite_glob_0_2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
89
- __proto__: null,
90
- default: exclamationTriangle
91
- }, Symbol.toStringTag, { value: "Module" }));
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>';
93
- const __vite_glob_0_3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
94
- __proto__: null,
95
- default: palette
96
- }, Symbol.toStringTag, { value: "Module" }));
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>';
98
- const __vite_glob_0_4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
99
- __proto__: null,
100
- default: personLock
101
- }, Symbol.toStringTag, { value: "Module" }));
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>';
103
- const __vite_glob_0_5 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
104
- __proto__: null,
105
- default: search
106
- }, Symbol.toStringTag, { value: "Module" }));
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>';
108
- const __vite_glob_0_6 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
109
- __proto__: null,
110
- default: stopwatch
111
- }, Symbol.toStringTag, { value: "Module" }));
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>';
113
- const __vite_glob_0_7 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
114
- __proto__: null,
115
- default: wifiOff
116
- }, Symbol.toStringTag, { value: "Module" }));
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>';
118
- const __vite_glob_0_8 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
119
- __proto__: null,
120
- default: wrenchAdjustable
121
- }, Symbol.toStringTag, { value: "Module" }));
122
76
  var __defProp = Object.defineProperty;
123
77
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
124
78
  var __decorateClass = (decorators, target, key, kind) => {
@@ -129,27 +83,12 @@ var __decorateClass = (decorators, target, key, kind) => {
129
83
  if (kind && result) __defProp(target, key, result);
130
84
  return result;
131
85
  };
132
- const icons = Object.entries(/* @__PURE__ */ Object.assign({
133
- "../assets/ban.svg": __vite_glob_0_0,
134
- "../assets/box-seam.svg": __vite_glob_0_1,
135
- "../assets/exclamation-triangle.svg": __vite_glob_0_2,
136
- "../assets/palette.svg": __vite_glob_0_3,
137
- "../assets/person-lock.svg": __vite_glob_0_4,
138
- "../assets/search.svg": __vite_glob_0_5,
139
- "../assets/stopwatch.svg": __vite_glob_0_6,
140
- "../assets/wifi-off.svg": __vite_glob_0_7,
141
- "../assets/wrench-adjustable.svg": __vite_glob_0_8
142
- })).reduce((acc, [path, content]) => {
143
- const name = path.split("/").pop()?.replace(".svg", "") || "";
144
- acc[name] = content.default;
145
- return acc;
146
- }, {});
147
86
  let UErrorPage = class extends LitElement {
148
87
  render() {
149
88
  const error = this.error || this.getDefaultError();
150
89
  const icon = this.getErrorIcon(error.code);
151
90
  return html`
152
- <div class="icon">${unsafeHTML(icon)}</div>
91
+ <div class="icon">${icon}</div>
153
92
  <div class="code">${error.code}</div>
154
93
  <div class="message">${error.message}</div>
155
94
  `;
@@ -164,45 +103,45 @@ let UErrorPage = class extends LitElement {
164
103
  const numericCode = typeof code === "string" ? parseInt(code) : code;
165
104
  switch (codeStr) {
166
105
  case "OUTLET_NOT_FOUND":
167
- return icons["box-seam"] || "📦";
106
+ return "📦";
168
107
  case "CONTENT_LOAD_FAILED":
169
- return icons["wifi-off"] || "📡";
108
+ return "📡";
170
109
  case "RENDER_FAILED":
171
- return icons["palette"] || "🎨";
110
+ return "🎨";
172
111
  }
173
112
  switch (numericCode) {
174
113
  case 404:
175
- return icons["search"] || "🔍";
114
+ return "🔍";
176
115
  case 403:
177
- return icons["ban"] || "🚫";
116
+ return "🚫";
178
117
  case 401:
179
- return icons["person-lock"] || "🔐";
118
+ return "🔐";
180
119
  case 429:
181
- return icons["stopwatch"] || "⏱️";
120
+ return "⏱️";
182
121
  case 503:
183
- return icons["wrench-adjustable"] || "🛠️";
122
+ return "🛠️";
184
123
  default:
185
- return icons["exclamation-triangle"] || "⚠️";
124
+ return "⚠️";
186
125
  }
187
126
  }
188
127
  };
189
128
  UErrorPage.styles = css`
190
129
  :host {
191
- --route-icon-color: #4a5568;
192
- --route-code-color: #1a202c;
193
- --route-message-color: #718096;
130
+ --error-icon-color: #4a5568;
131
+ --error-code-color: #1a202c;
132
+ --error-message-color: #718096;
194
133
  }
195
134
  :host-context([theme="dark"]) {
196
- --route-icon-color: #a0aec0;
197
- --route-code-color: #f7fafc;
198
- --route-message-color: #cbd5e0;
135
+ --error-icon-color: #a0aec0;
136
+ --error-code-color: #f7fafc;
137
+ --error-message-color: #cbd5e0;
199
138
  }
200
139
 
201
140
  @media (prefers-color-scheme: dark) {
202
141
  :host {
203
- --route-icon-color: #a0aec0;
204
- --route-code-color: #f7fafc;
205
- --route-message-color: #cbd5e0;
142
+ --error-icon-color: #a0aec0;
143
+ --error-code-color: #f7fafc;
144
+ --error-message-color: #cbd5e0;
206
145
  }
207
146
  }
208
147
 
@@ -211,8 +150,8 @@ UErrorPage.styles = css`
211
150
  flex-direction: column;
212
151
  justify-content: center;
213
152
  align-items: center;
214
- min-height: 100vh;
215
153
  width: 100%;
154
+ height: 100%;
216
155
  text-align: center;
217
156
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
218
157
  overflow: auto;
@@ -220,31 +159,24 @@ UErrorPage.styles = css`
220
159
  }
221
160
 
222
161
  .icon {
223
- display: contents;
162
+ color: var(--error-icon-color);
224
163
  font-size: 6rem;
225
- color: var(--route-icon-color);
226
164
  opacity: 0.85;
227
165
  }
228
166
 
229
- svg {
230
- width: 1em;
231
- height: 1em;
232
- fill: currentColor;
233
- }
234
-
235
167
  .code {
168
+ color: var(--error-code-color);
236
169
  font-size: 2rem;
237
170
  font-weight: 700;
238
- margin: 1rem 0;
239
- color: var(--route-code-color);
240
171
  letter-spacing: -0.5px;
172
+ margin: 1rem 0;
241
173
  }
242
174
 
243
175
  .message {
176
+ color: var(--error-message-color);
244
177
  font-size: 1rem;
245
- color: var(--route-message-color);
246
- max-width: 600px;
247
178
  line-height: 1.6;
179
+ max-width: 600px;
248
180
  }
249
181
  `;
250
182
  __decorateClass([
@@ -257,6 +189,7 @@ function getRandomID() {
257
189
  return window.isSecureContext ? window.crypto.randomUUID() : window.crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
258
190
  }
259
191
  function findOutlet(element) {
192
+ if (!element) return void 0;
260
193
  let outlet = void 0;
261
194
  if (element.shadowRoot) {
262
195
  outlet = element.shadowRoot.querySelector("u-outlet");
@@ -301,8 +234,7 @@ function findAnchorFrom(event) {
301
234
  }
302
235
  const tgt = event.target;
303
236
  if (!tgt) return null;
304
- const anchor = tgt.closest("a");
305
- return anchor;
237
+ return tgt.closest("a");
306
238
  }
307
239
  function setRoutes(routes, basepath) {
308
240
  for (const route of routes) {
@@ -336,10 +268,10 @@ function setRoutes(routes, basepath) {
336
268
  }
337
269
  return routes;
338
270
  }
339
- function getRoutes(pathname, routes) {
271
+ function getRoutes(routes, pathname) {
340
272
  for (const route of routes) {
341
273
  if (route.index !== true && route.children && route.children.length > 0) {
342
- const childRoutes = getRoutes(pathname, route.children);
274
+ const childRoutes = getRoutes(route.children, pathname);
343
275
  if (childRoutes.length > 0) {
344
276
  return [route, ...childRoutes];
345
277
  }
@@ -355,11 +287,11 @@ function getRoutes(pathname, routes) {
355
287
  }
356
288
  class Router {
357
289
  constructor(config) {
358
- this.handleWindowPopstate = async () => {
290
+ this.handleWindowPopstate = async (_) => {
359
291
  const href = window.location.href;
360
292
  await this.go(href);
361
293
  };
362
- this.handleDocumentClick = (e) => {
294
+ this.handleDocumentClick = async (e) => {
363
295
  try {
364
296
  if (e.defaultPrevented) return;
365
297
  if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey) return;
@@ -372,18 +304,17 @@ class Router {
372
304
  if (anchor.getAttribute("rel") === "external") return;
373
305
  if (anchor.target && anchor.target !== "") return;
374
306
  e.preventDefault();
375
- void this.go(anchor.href);
307
+ await this.go(anchor.href);
376
308
  } catch {
377
309
  }
378
310
  };
311
+ this.destroy();
379
312
  this._rootElement = config.root;
380
313
  this._basepath = absolutePath(config.basepath || "/");
381
314
  this._routes = setRoutes(config.routes || [], this._basepath);
382
315
  this._fallback = config.fallback;
383
- window.removeEventListener("popstate", this.handleWindowPopstate);
384
316
  window.addEventListener("popstate", this.handleWindowPopstate);
385
317
  if (config.useIntercept !== false) {
386
- document.removeEventListener("click", this.handleDocumentClick);
387
318
  document.addEventListener("click", this.handleDocumentClick);
388
319
  }
389
320
  if (config.initialLoad !== false) {
@@ -434,7 +365,7 @@ class Router {
434
365
  try {
435
366
  if (this._requestID !== requestID) return;
436
367
  window.dispatchEvent(new RouteBeginEvent(context));
437
- const routes = getRoutes(context.pathname, this._routes);
368
+ const routes = getRoutes(this._routes, context.pathname);
438
369
  const lastRoute = routes[routes.length - 1];
439
370
  if (lastRoute && lastRoute.path instanceof URLPattern) {
440
371
  context.params = lastRoute.path.exec({ pathname: context.pathname })?.pathname.groups || {};
@@ -443,7 +374,6 @@ class Router {
443
374
  outlet = findOutletOrThrow(this._rootElement);
444
375
  let title = void 0;
445
376
  let content = null;
446
- let element = null;
447
377
  if (routes.length === 0) {
448
378
  throw new NotFoundError(context.href);
449
379
  }
@@ -459,11 +389,11 @@ class Router {
459
389
  throw new ContentLoadError(LoadError);
460
390
  }
461
391
  try {
462
- element = await outlet.renderContent({ id: route.id, content, force: route.force });
392
+ outlet.render({ id: route.id, value: content, force: route.force });
463
393
  } catch (renderError) {
464
394
  throw new ContentRenderError(renderError);
465
395
  }
466
- outlet = findOutlet(element) || outlet;
396
+ outlet = findOutlet(outlet) || outlet;
467
397
  title = route.title || title;
468
398
  }
469
399
  document.title = title || document.title;
@@ -479,13 +409,13 @@ class Router {
479
409
  try {
480
410
  if (this._fallback && this._fallback.render && outlet) {
481
411
  const fallbackContent = await this._fallback.render({ ...context, error: routeError });
482
- outlet.renderContent({ id: "#fallback", content: fallbackContent, force: true });
412
+ outlet.render({ id: "#fallback", value: fallbackContent, force: true });
483
413
  document.title = this._fallback.title || document.title;
484
414
  } else {
485
415
  const errorContent = new UErrorPage();
486
416
  errorContent.error = error;
487
417
  if (outlet) {
488
- outlet.renderContent({ id: "#error", content: errorContent, force: true });
418
+ outlet.render({ id: "#error", value: errorContent, force: true });
489
419
  } else {
490
420
  document.body.innerHTML = "";
491
421
  document.body.appendChild(errorContent);
package/dist/react.d.ts CHANGED
@@ -6,10 +6,14 @@ import { ReactWebComponent } from '@lit/react';
6
6
  import { TemplateResult } from 'lit-html';
7
7
  import { TemplateResult as TemplateResult_2 } from 'lit';
8
8
 
9
+ /** 렌더링 옵션 */
9
10
  declare interface RenderOption {
11
+ /** 교차 렌더링 방지 ID */
10
12
  id?: string;
13
+ /** 강제 렌더링 여부 */
11
14
  force?: boolean;
12
- content: RenderResult;
15
+ /** 렌더링할 값 */
16
+ value: RenderResult;
13
17
  }
14
18
 
15
19
  declare type RenderResult = HTMLElement | ReactElement | TemplateResult_2<1> | false;
@@ -28,33 +32,41 @@ declare class ULink_2 extends LitElement {
28
32
  /** 외부 링크 여부 */
29
33
  private isExternal;
30
34
  /**
31
- * a 태그 target 지원하고 싶으면 열어두는게 좋습니다.
32
- * - _blank 등을 쓰면 무조건 브라우저 기본 동작을 따르도록 처리합니다.
35
+ * 링크 대상 target 속성
36
+ *
37
+ * - `_self`: 현재 창에서 링크 열기 (기본값)
38
+ * - `_blank`: 새 탭/창에서 링크 열기
39
+ * - `_parent`: 부모 프레임에서 링크 열기
40
+ * - `_top`: 최상위 프레임에서 링크 열기
33
41
  */
34
42
  target?: string;
35
43
  /**
36
- * - 속성을 정의하지 않으면 basepath로 이동합니다.
44
+ * 링크 대상 URL, 다음 사항에 따라 SPA 라우팅 또는 브라우저 네비게이션이 결정됩니다.
45
+ *
46
+ * - 속성을 정의하지 않으면 설정에서 지정한 `basepath`로 SPA 라우팅합니다.
37
47
  * - http(s)로 시작하면 외부 링크로 간주하고 브라우저 네비게이션을 사용합니다.
38
- * - 절대경로(/...) 경우 basepath 시작하면 SPA 라우팅 대상이 있습니다.
39
- * - 상대경로는 (basepath + 상대경로)로 SPA 라우팅 대상이 될 수 있습니다.
40
- * - ?로 시작하면 현재 pathname에 쿼리스트링을 붙여 SPA 라우팅합니다.
41
- * - #으로 시작하면 현재 URL에 해시만 바꾸고(브라우저 기본) 동작합니다.
48
+ * - 절대경로(/...) 경우 `basepath`로 시작하면 SPA 라우팅합니다, 이외 브라우저 네비게이션을 사용합니다.
49
+ * - 상대경로는 (basepath + 상대경로)로 결합하여 SPA 라우팅합니다.
50
+ * - ?로 시작하면 현재 경로에 쿼리스트링을 추가하여 SPA 라우팅합니다.
51
+ * - #으로 시작하면 브라우저 기본 동작을 사용합니다.
42
52
  */
43
53
  href?: string;
54
+ connectedCallback(): void;
55
+ disconnectedCallback(): void;
44
56
  protected willUpdate(changedProperties: PropertyValues): void;
45
57
  render(): TemplateResult<1>;
46
- /** basepath를 state에서 꺼내는 헬퍼 */
47
- private getBasepath;
48
58
  /** a 태그에 주입할 href 값을 계산합니다. */
49
- private computeHref;
59
+ private compute;
50
60
  /**
51
- * 클릭 가로채기 규칙
61
+ * 클릭 가로채기 핸들러
52
62
  * - 좌클릭(0) + 보조키 없음(ctrl/meta/shift/alt 없음) + target이 _self일 때만 SPA 라우팅 고려
53
63
  * - 그 외(중클릭/우클릭/보조키/target=_blank 등)는 브라우저 기본 동작 유지
54
64
  */
55
- private handleAnchorClick;
65
+ private handleClick;
56
66
  /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
57
67
  private dispatchPopstate;
68
+ /** basepath를 state에서 꺼내는 헬퍼 */
69
+ private getBasepath;
58
70
  static styles: CSSResult;
59
71
  }
60
72
 
@@ -64,26 +76,21 @@ declare class ULink_2 extends LitElement {
64
76
  export declare const UOutlet: ReactWebComponent<UOutlet_2, {}>;
65
77
 
66
78
  /**
67
- * 라우트 설정에 따라 LitElement 또는 React 컴포넌트를 렌더링합니다.
79
+ * LitElement 또는 React 컴포넌트를 렌더링해주는 웹컴포넌트 입니다.
68
80
  */
69
- declare class UOutlet_2 extends LitElement {
81
+ declare class UOutlet_2 extends HTMLElement {
82
+ /** 교차 렌더링 방지 id */
70
83
  private routeId?;
71
- private container?;
72
- private content?;
73
- /** 외부 스타일을 적용하기 위해 라이트 돔을사용 합니다. */
74
- protected createRenderRoot(): this;
75
- /** 컴포넌트가 DOM에 연결될 때 초기화 작업 수행 */
76
- connectedCallback(): void;
77
- render(): TemplateResult_2<1>;
84
+ /** 실제 렌더링 컨텐츠 */
85
+ private root?;
78
86
  /**
79
- * render 함수의 결과를 렌더링합니다.
80
- * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
87
+ * 주어진 렌더링 옵션에 따라 컨텐츠를 렌더링합니다.
81
88
  */
82
- renderContent({ id, content, force }: RenderOption): Promise<HTMLDivElement>;
89
+ render({ id, value, force }: RenderOption): void;
83
90
  /**
84
- * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
91
+ * 기존 DOM을 삭제하여, 초기 상태로 되돌립니다.
85
92
  */
86
- clear(): void;
93
+ reset(): void;
87
94
  }
88
95
 
89
96
  export { }
package/dist/react.js CHANGED
@@ -1,38 +1,13 @@
1
1
  import React from "react";
2
- import { b as ULink$1, U as UOutlet$1 } from "./share-SPj-xl6y.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({
2
+ import { createComponent } from "@lit/react";
3
+ import { b as ULink$1, U as UOutlet$1 } from "./share-6Zs4TunA.js";
4
+ const ULink = createComponent({
30
5
  react: React,
31
6
  tagName: "u-link",
32
7
  elementClass: ULink$1,
33
8
  events: {}
34
9
  });
35
- const UOutlet = o({
10
+ const UOutlet = createComponent({
36
11
  react: React,
37
12
  tagName: "u-outlet",
38
13
  elementClass: UOutlet$1,
@@ -1,89 +1,54 @@
1
- import { LitElement, html, render, css } from "lit";
2
- import { customElement, property } from "lit/decorators.js";
1
+ import { render, css, LitElement, html } from "lit";
3
2
  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
- /** 컴포넌트가 DOM에 연결될 때 초기화 작업 수행 */
18
- connectedCallback() {
19
- super.connectedCallback();
20
- this.dispatchEvent(new CustomEvent("outlet-load", {
21
- bubbles: true,
22
- composed: true
23
- }));
24
- }
25
- render() {
26
- return html`${this.container}`;
27
- }
3
+ import { property, customElement } from "lit/decorators.js";
4
+ import { ifDefined } from "lit/directives/if-defined.js";
5
+ class UOutlet extends HTMLElement {
28
6
  /**
29
- * render 함수의 결과를 렌더링합니다.
30
- * - HTMLElement, ReactElement, TemplateResult를 모두 처리할 수 있습니다.
7
+ * 주어진 렌더링 옵션에 따라 컨텐츠를 렌더링합니다.
31
8
  */
32
- async renderContent({ id, content, force }) {
33
- if (this.routeId === id && force === false && this.container) {
34
- return this.container;
35
- }
9
+ render({ id, value, force }) {
10
+ if (this.routeId === id && force === false) return;
36
11
  this.routeId = id;
37
- this.clear();
38
- if (!this.container) {
39
- throw new Error("Outlet container is not initialized.");
40
- }
41
- if (typeof content !== "object") {
12
+ this.reset();
13
+ if (typeof value !== "object") {
42
14
  throw new Error("Content is not a valid renderable object.");
43
15
  }
44
- if (content instanceof HTMLElement) {
45
- this.container.appendChild(content);
46
- } else if ("_$litType$" in content) {
47
- this.content = render(content, this.container);
48
- } else if ("$$typeof" in content) {
49
- this.content = createRoot(this.container);
50
- this.content.render(content);
16
+ if (value instanceof HTMLElement) {
17
+ this.replaceChildren(value);
18
+ this.root = void 0;
19
+ } else if ("_$litType$" in value) {
20
+ this.root = render(value, this);
21
+ } else if ("$$typeof" in value) {
22
+ this.root = createRoot(this);
23
+ this.root.render(value);
51
24
  } else {
52
25
  throw new Error("not supported content type for Outlet rendering.");
53
26
  }
54
- this.requestUpdate();
55
- await this.updateComplete;
56
- return this.container;
57
27
  }
58
28
  /**
59
- * 기존 DOM을 라이프 사이클에 맞게 제거합니다.
29
+ * 기존 DOM을 삭제하여, 초기 상태로 되돌립니다.
60
30
  */
61
- clear() {
62
- if (this.content) {
63
- if ("unmount" in this.content) {
64
- this.content.unmount();
65
- }
66
- if ("setConnected" in this.content) {
67
- this.content.setConnected(false);
68
- }
69
- this.content = void 0;
31
+ reset() {
32
+ if (this.root && "_$litPart$" in this) {
33
+ delete this._$litPart$;
70
34
  }
71
- this.container = document.createElement("div");
72
- this.container.style.display = "contents";
35
+ if (this.root && "unmount" in this.root) {
36
+ this.root.unmount();
37
+ }
38
+ this.root = void 0;
39
+ this.innerHTML = "";
73
40
  }
74
- };
75
- UOutlet = __decorateClass$1([
76
- customElement("u-outlet")
77
- ], UOutlet);
41
+ }
42
+ customElements.define("u-outlet", UOutlet);
78
43
  function isExternalUrl(url) {
79
44
  if (!url) return false;
80
- const s = url.trim();
81
- if (/^(?:mailto:|tel:|javascript:)/i.test(s)) return true;
82
- if (s.startsWith("//")) return true;
45
+ url = url.trim();
46
+ if (/^(?:mailto:|tel:|javascript:)/i.test(url)) return true;
47
+ if (url.startsWith("//")) return true;
83
48
  try {
84
49
  const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
85
- const parsed = new URL(s, base);
86
- if (/^(?:ftp:|ftps:|data:|ws:|wss:)/i.test(parsed.protocol)) return true;
50
+ const parsed = new URL(url, base);
51
+ if (/^(?:ftp:|ftps:|ws:|wss:)/i.test(parsed.protocol)) return true;
87
52
  return parsed.origin !== new URL(base).origin;
88
53
  } catch {
89
54
  return false;
@@ -91,7 +56,7 @@ function isExternalUrl(url) {
91
56
  }
92
57
  function parseUrl(url, basepath) {
93
58
  let urlObj;
94
- basepath = catchBasePath(basepath);
59
+ basepath = catchBasepath(basepath);
95
60
  if (url.startsWith("http")) {
96
61
  urlObj = new URL(url);
97
62
  } else if (url.startsWith("/")) {
@@ -121,7 +86,7 @@ function absolutePath(...paths) {
121
86
  if (paths.length === 0) return "/";
122
87
  return "/" + paths.join("/");
123
88
  }
124
- function catchBasePath(basepath) {
89
+ function catchBasepath(basepath) {
125
90
  if (basepath === "/") return basepath;
126
91
  let pattern = new URLPattern({ pathname: basepath + "/*" });
127
92
  let match = pattern.exec({ pathname: window.location.pathname });
@@ -151,12 +116,11 @@ let ULink = class extends LitElement {
151
116
  constructor() {
152
117
  super(...arguments);
153
118
  this.isExternal = false;
154
- this.handleAnchorClick = (event) => {
119
+ this.handleClick = (event) => {
155
120
  if (event.defaultPrevented) return;
156
121
  if (event.button !== 0) return;
157
122
  if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
158
- const target = (this.target ?? "").trim();
159
- if (target && target.toLowerCase() !== "_self") return;
123
+ if (this.target && this.target.toLowerCase() !== "_self") return;
160
124
  const basepath = this.getBasepath();
161
125
  if (!this.href) {
162
126
  event.preventDefault();
@@ -183,6 +147,14 @@ let ULink = class extends LitElement {
183
147
  this.dispatchPopstate(basepath, url);
184
148
  };
185
149
  }
150
+ connectedCallback() {
151
+ super.connectedCallback();
152
+ this.addEventListener("click", this.handleClick);
153
+ }
154
+ disconnectedCallback() {
155
+ this.removeEventListener("click", this.handleClick);
156
+ super.disconnectedCallback();
157
+ }
186
158
  willUpdate(changedProperties) {
187
159
  super.willUpdate(changedProperties);
188
160
  if (changedProperties.has("href")) {
@@ -191,27 +163,18 @@ let ULink = class extends LitElement {
191
163
  }
192
164
  render() {
193
165
  return html`
194
- <a
195
- target=${this.target ?? "_self"}
196
- href=${this.computeHref(this.href)}
197
- @click=${this.handleAnchorClick}>
166
+ <a target=${ifDefined(this.target)} href=${this.compute(this.href)}>
198
167
  <slot></slot>
199
168
  </a>
200
169
  `;
201
170
  }
202
- /** basepath를 state에서 꺼내는 헬퍼 */
203
- getBasepath() {
204
- return window.history.state?.basepath || "";
205
- }
206
171
  /** a 태그에 주입할 href 값을 계산합니다. */
207
- computeHref(href) {
172
+ compute(href) {
208
173
  const basepath = this.getBasepath();
209
- if (!href) {
210
- return window.location.origin + basepath;
211
- }
174
+ if (!href) return window.location.origin + basepath;
212
175
  if (this.isExternal) return href;
213
- if (href.startsWith("#") || href.startsWith("?")) return href;
214
176
  if (href.startsWith("/")) return href;
177
+ if (href.startsWith("#") || href.startsWith("?")) return href;
215
178
  return absolutePath(basepath, href);
216
179
  }
217
180
  /** 클라이언트 라우팅을 위해 popstate 이벤트를 발생시킵니다. */
@@ -219,15 +182,17 @@ let ULink = class extends LitElement {
219
182
  window.history.pushState({ basepath }, "", url);
220
183
  window.dispatchEvent(new PopStateEvent("popstate"));
221
184
  }
185
+ /** basepath를 state에서 꺼내는 헬퍼 */
186
+ getBasepath() {
187
+ return window.history.state?.basepath || "";
188
+ }
222
189
  };
223
190
  ULink.styles = css`
224
191
  :host {
225
- display: inline-flex;
226
192
  cursor: pointer;
227
193
  }
228
194
 
229
195
  a {
230
- display: contents;
231
196
  text-decoration: none;
232
197
 
233
198
  font-size: inherit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/router",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "A modern client-side router for web applications with support for Lit and React components",
5
5
  "keywords": [
6
6
  "lit",
@@ -40,14 +40,14 @@
40
40
  "build": "vite build"
41
41
  },
42
42
  "dependencies": {
43
+ "@lit/react": "^1.0.8",
43
44
  "lit": "^3.3.2",
44
45
  "react": "^19.2.3",
45
46
  "react-dom": "^19.2.3"
46
47
  },
47
48
  "devDependencies": {
48
- "@lit/react": "^1.0.8",
49
49
  "@types/node": "^25.0.9",
50
- "@types/react": "^19.2.8",
50
+ "@types/react": "^19.2.9",
51
51
  "@types/react-dom": "^19.2.3",
52
52
  "typescript": "^5.9.3",
53
53
  "vite": "^7.3.1",