@iyulab/router 0.10.4 → 0.11.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.1] - 2026-08-20
4
+
5
+ ### Fixed
6
+
7
+ - **Route matching threw `ReferenceError: URLPattern is not defined` on browsers that ship
8
+ without the URL Pattern API.** The router constructs routes with the global `URLPattern`
9
+ constructor directly; browsers that have not yet shipped it (older Safari and Firefox
10
+ releases are still common in the field) failed synchronously in `new Router(...)`, before
11
+ any route could render. A guarded polyfill (`urlpattern-polyfill`, self-installs only when
12
+ `globalThis.URLPattern` is absent) is now imported by the router's internals, so browsers
13
+ without native support get a working fallback and browsers with native support are
14
+ unaffected at runtime.
15
+
16
+ ### Internal
17
+
18
+ - Router's own `URLPattern` construction now runs through a small local type shim — the
19
+ polyfill package's bundled types have not yet caught up to the constructor's `options`
20
+ argument (`ignoreCase`), even though its runtime implementation already supports it.
21
+
22
+ ## [0.11.0] - 2026-08-07
23
+
24
+ ### Added
25
+
26
+ - **`<u-link navigate="document">` — link to a same-origin path the router does not own.**
27
+ Whether a link was "external" was decided by an origin comparison alone, so there was no way to
28
+ point at a same-origin path that is not a SPA route: a static docs site, a server-rendered page,
29
+ a download endpoint, an auth redirect. The router intercepted the click and, with no matching
30
+ route, the screen fell through to not-found.
31
+
32
+ The three available escapes all meant something else: a different origin (the requirement is the
33
+ *same* origin — cookies, session, reverse proxy), `target="_blank"` (forces a new tab; same-tab
34
+ navigation was unexpressible), and a `#` fragment (not another document).
35
+
36
+ `navigate` defaults to `router`, so existing behaviour is unchanged. The rendered anchor carries
37
+ `data-navigate="document"`, which the router's global anchor delegation also honours — the
38
+ element handler alone is not enough, since a link pointing at a *registered* route would
39
+ otherwise be intercepted there instead.
40
+
41
+ The name is deliberately not `external`: this module already uses `isExternalUrl`/`isExternal`
42
+ with origin semantics, and the question here is about the document, not the origin.
43
+
44
+ ### Fixed
45
+
46
+ - **`sideEffects` omitted the source-resolved entry barrel.** The allowlist covered the built
47
+ artifacts with a directory-wide glob, but the source-form barrel and the `react` wrapper entry
48
+ were outside it. The published artifacts
49
+ were unaffected — the shipped allowlist already covered them — but a consumer resolving this
50
+ package from source (a workspace sibling) could have the barrel elided, dropping the element
51
+ registrations it pulls in. The failure is silent: no error, and unregistered custom elements
52
+ render nothing. The source-form entry points are now declared alongside the artifact ones.
53
+
3
54
  ## [0.10.4] - 2026-08-01
4
55
 
5
56
  ### Documentation
package/README.md CHANGED
@@ -141,13 +141,32 @@ const routes = [
141
141
  - `<u-link>`: SPA-aware anchor element
142
142
  - `<u-outlet>`: render target for matched route output
143
143
 
144
- `<u-link>` supports `href`, `target`, and `rel`.
144
+ `<u-link>` supports `href`, `target`, `rel`, and `navigate`.
145
145
 
146
146
  ```html
147
147
  <u-link href="/docs">Docs</u-link>
148
148
  <u-link href="https://example.com" target="_blank" rel="noopener noreferrer">External</u-link>
149
149
  ```
150
150
 
151
+ ### Linking to a path the router does not own
152
+
153
+ Not every path on your origin is a SPA route. A static docs site, a server-rendered
154
+ page, a report endpoint, an auth redirect — these live alongside your routes and must
155
+ open as documents, in the same tab. Declare that with `navigate="document"`:
156
+
157
+ ```html
158
+ <u-link href="/help/" navigate="document">Help</u-link>
159
+ ```
160
+
161
+ The router leaves the click alone and the browser navigates normally. The same works
162
+ on a plain anchor via `data-navigate="document"`, which is what the element renders.
163
+
164
+ `navigate` defaults to `router`: same-origin links are handled as SPA navigation, as
165
+ before. Note that `navigate` asks a different question than the automatic origin check
166
+ — not *"is this another origin?"* but *"is this another document?"* Without it the only
167
+ escapes were a different origin, `target="_blank"` (which forces a new tab), or a `#`
168
+ fragment (which is not another document at all).
169
+
151
170
  React wrappers:
152
171
 
153
172
  ```tsx
@@ -38,6 +38,27 @@ export declare class ULink extends LitElement {
38
38
  * - #으로 시작하면 브라우저 기본 동작을 사용합니다.
39
39
  */
40
40
  href?: string;
41
+ /**
42
+ * 이 링크를 라우터가 처리할지, 브라우저의 문서 이동에 맡길지.
43
+ *
44
+ * - `router`(기본): 종전 동작 그대로 — 같은 오리진이면 SPA 이동, 아니면 브라우저에 맡긴다.
45
+ * - `document`: 라우터가 **가로채지 않는다.** 같은 오리진이지만 SPA 라우트가 아닌 경로
46
+ * (정적 문서 사이트, 서버 렌더 페이지, 파일 다운로드 엔드포인트, 인증 리다이렉트)를 가리킬 때 쓴다.
47
+ *
48
+ * ⚠**「외부 오리진」이 아니라 「다른 문서」다.** 종전에는 이 구분이 **오리진 비교 하나**로만
49
+ * 결정돼서, 같은 오리진의 비-SPA 경로를 가리킬 수단이 없었다 — 라우터가 클릭을 가로채고
50
+ * 등록되지 않은 라우트이므로 화면이 not-found 로 떨어졌다. 빠져나갈 길이 셋뿐이었고
51
+ * (다른 오리진 · `target="_blank"` · `#` 프래그먼트) 셋 다 요구와 다르다:
52
+ * 같은 오리진이어야 하고(쿠키·세션·역방향 프록시), **같은 탭**이어야 하며, 다른 문서다.
53
+ *
54
+ * ```html
55
+ * <u-link href="/help/" navigate="document">Help</u-link>
56
+ * ```
57
+ *
58
+ * ⚠**자동 판정을 넓히지 않는다.** 「등록된 라우트와 대조해 미등록이면 문서 이동」도 가능하지만
59
+ * 라우트가 늦게 등록되면 판정이 **시점에 의존**하게 된다. 명시 선언이 예측 가능하다.
60
+ */
61
+ navigate?: "router" | "document";
41
62
  connectedCallback(): void;
42
63
  disconnectedCallback(): void;
43
64
  protected willUpdate(changedProperties: PropertyValues): void;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as isExternalUrl, i as absolutePath, n as __decorate, o as parseUrl, r as __decorateMetadata, s as UOutlet, t as ULink } from "./share-CZzCH0Yf.js";
1
+ import { a as isExternalUrl, i as absolutePath, n as __decorate, o as parseUrl, r as __decorateMetadata, s as UOutlet, t as ULink } from "./share-Dl02vMhW.js";
2
2
  import { LitElement, css, html } from "lit";
3
3
  import { customElement, property } from "lit/decorators.js";
4
4
  //#region src/types/RouteError.ts
@@ -289,6 +289,12 @@ function findAnchorFrom(event) {
289
289
  //#endregion
290
290
  //#region src/internals/route-helpers.ts
291
291
  /**
292
+ * `urlpattern-polyfill`의 번들 타입 선언이 `options` 인자를 아직 반영하지 않는다(런타임
293
+ * 구현은 WHATWG 스펙대로 `ignoreCase`를 지원한다 — `dist/index.js`의 3항 생성자 실측
294
+ * 확인됨). 그 타입 공백만 좁혀서 캐스트한다.
295
+ */
296
+ var createURLPattern = URLPattern;
297
+ /**
292
298
  * 라우트들을 다음 사항에 따라 재귀적으로 재설정합니다.
293
299
  * - 각 라우트에 고유 `id`를 랜덤하게 부여합니다.
294
300
  * - `path`를 URLPattern 객체로 변환합니다.
@@ -303,13 +309,11 @@ function setRoutes(routes, basepath) {
303
309
  route.id ||= getRandomID();
304
310
  route.ignoreCase ||= false;
305
311
  if (route.index === true) {
306
- route.path = new URLPattern({ pathname: `${basepath}{/}?` }, { ignoreCase: route.ignoreCase });
312
+ route.path = new createURLPattern({ pathname: `${basepath}{/}?` }, { ignoreCase: route.ignoreCase });
307
313
  route.force ||= true;
308
314
  } else {
309
- if (typeof route.path === "string") {
310
- const absolutePathStr = absolutePath(basepath, route.path);
311
- route.path = new URLPattern({ pathname: `${absolutePathStr}{/}?` }, { ignoreCase: route.ignoreCase });
312
- } else if (route.path instanceof URLPattern) {} else route.path = new URLPattern({ pathname: `${basepath}{/}?` }, { ignoreCase: route.ignoreCase });
315
+ if (typeof route.path === "string") route.path = new createURLPattern({ pathname: `${absolutePath(basepath, route.path)}{/}?` }, { ignoreCase: route.ignoreCase });
316
+ else if (route.path instanceof URLPattern) {} else route.path = new createURLPattern({ pathname: `${basepath}{/}?` }, { ignoreCase: route.ignoreCase });
313
317
  if (route.children && route.children.length > 0) {
314
318
  const childBasepath = route.path.pathname.replace("{/}?", "");
315
319
  route.children = setRoutes(route.children, childBasepath);
@@ -401,6 +405,7 @@ var Router = class {
401
405
  if (isExternalUrl(href)) return;
402
406
  if (anchor.hasAttribute("download")) return;
403
407
  if (anchor.getAttribute("rel") === "external") return;
408
+ if (anchor.getAttribute("data-navigate") === "document") return;
404
409
  if (anchor.target && anchor.target !== "") return;
405
410
  const pathname = new URL(anchor.href).pathname;
406
411
  if (this._basepath !== "/" && !pathname.startsWith(this._basepath)) return;
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { s as UOutlet$1, t as ULink$1 } from "./share-CZzCH0Yf.js";
1
+ import { s as UOutlet$1, t as ULink$1 } from "./share-Dl02vMhW.js";
2
2
  import React from "react";
3
3
  import { createComponent } from "@lit/react";
4
4
  //#region src/react.ts