@iyulab/router 0.11.1 → 0.11.3

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.3] - 2026-08-31
4
+
5
+ ### Fixed
6
+
7
+ - **`waitOutlet()` could hang forever in a backgrounded tab.** The wait loop
8
+ only re-checked for the outlet after each `requestAnimationFrame`
9
+ resolved, and a fully suspended tab can stop firing `rAF` entirely (not
10
+ just throttle it), so the loop never exited. It now races each `rAF`
11
+ wait against a `setTimeout` for the remaining budget, and does one more
12
+ outlet check immediately before throwing — the deadline can pass in the
13
+ exact frame the outlet became ready, and without that final check that
14
+ read as a false timeout.
15
+
16
+ ## [0.11.2] - 2026-08-25
17
+
18
+ ### Fixed
19
+
20
+ - **`aria-current`/`aria-label` set on `<u-link>` never reached the accessibility tree.** The
21
+ host attribute was present, but the actual interactive node exposed to assistive technology
22
+ is the native `<a>` rendered inside the shadow root — ARIA content attributes on a shadow
23
+ host do not cross the shadow boundary to label or mark current a descendant. Navigation
24
+ links relying on `aria-current="page"` to announce the active item, or on `aria-label` for
25
+ their accessible name, were exposed with neither. `render()` now forwards both host
26
+ attributes onto the internal `<a>`, and changes made after connection are observed and
27
+ re-rendered.
28
+
3
29
  ## [0.11.1] - 2026-08-20
4
30
 
5
31
  ### Fixed
@@ -3,6 +3,15 @@ import { RouteError } from '../types/RouteError.js';
3
3
  import { TemplateResult } from 'lit-html';
4
4
  /**
5
5
  * 라우팅 중 발생한 에러 정보를 사용자에게 전달하기 위한 기본 컴포넌트 입니다.
6
+ *
7
+ * `Router`가 `fallback`을 지정하지 않았을 때 내부적으로 그리는 기본 화면이라
8
+ * `src/index.ts`에서 공개 export되지 않는다 — 직접 import해 쓰는 컴포넌트가
9
+ * 아니다. 다만 라우팅 실패 시 실제 DOM에 렌더되므로, 기본 화면의 색만 가볍게
10
+ * 맞추고 싶은 소비자를 위해 세 색상 훅을 남겨 둔다(전체 교체는 `fallback.render`).
11
+ *
12
+ * @cssprop --error-icon-color - 아이콘 색. 기본값 없음(미지정 시 상속된 색 사용)
13
+ * @cssprop --error-code-color - 에러 코드 텍스트 색. 기본값 없음(미지정 시 상속된 색 사용)
14
+ * @cssprop --error-message-color - 에러 메시지 텍스트 색. 기본값 없음(미지정 시 상속된 색 사용)
6
15
  */
7
16
  export declare class UErrorPage extends LitElement {
8
17
  constructor(error?: RouteError);
@@ -61,6 +61,20 @@ export declare class ULink extends LitElement {
61
61
  navigate?: "router" | "document";
62
62
  connectedCallback(): void;
63
63
  disconnectedCallback(): void;
64
+ /**
65
+ * 호스트에 세팅된 `aria-current`/`aria-label`은 실제 접근 가능한(포커스 대상)
66
+ * 엘리먼트가 아니라 — 그 안쪽 shadow DOM 의 네이티브 `<a>`다. 섀도우 경계를
67
+ * 넘지 않으므로 접근성 트리에 자동 반영되지 않는다(docket #45 실측 — 속성은
68
+ * 붙어 있는데 접근성 트리의 `aria-current`는 계속 비어 있음). `render()`가 이
69
+ * 값을 읽어 내부 `<a>`에 직접 옮긴다.
70
+ *
71
+ * 둘 다 Lit 리액티브 프로퍼티로 선언돼 있지 않아 `observedAttributes`에 없다 —
72
+ * 그 목록에 없는 속성은 `attributeChangedCallback` 자체가 호출되지 않는다
73
+ * (커스텀 엘리먼트 표준 동작). 초기 렌더는 되지만 연결 후 동적 변경은 반영되지
74
+ * 않았다 — 목록에 명시적으로 추가해야 한다.
75
+ */
76
+ static get observedAttributes(): string[];
77
+ attributeChangedCallback(name: string, old: string | null, value: string | null): void;
64
78
  protected willUpdate(changedProperties: PropertyValues): void;
65
79
  render(): TemplateResult<1>;
66
80
  /** a 태그에 주입할 href 값을 계산합니다. */
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-Dl02vMhW.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-iJLbxFbJ.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
@@ -260,14 +260,18 @@ function findOutletOrThrow(element, skip = false) {
260
260
  * @returns 준비된 `u-outlet` 엘리먼트
261
261
  */
262
262
  async function waitOutlet(element, timeout = 1e4, skip = false) {
263
- const start = performance.now();
264
- while (performance.now() - start < timeout) {
263
+ const deadline = performance.now() + timeout;
264
+ while (performance.now() < deadline) {
265
265
  const outlet = findOutlet(element, skip);
266
266
  if (outlet) return outlet;
267
267
  if (element.localName.includes("-")) await customElements.whenDefined(element.localName);
268
268
  if ("updateComplete" in element) await element.updateComplete;
269
- await new Promise((resolve) => requestAnimationFrame(() => resolve()));
269
+ const remaining = deadline - performance.now();
270
+ if (remaining <= 0) break;
271
+ await Promise.race([new Promise((resolve) => requestAnimationFrame(() => resolve())), new Promise((resolve) => setTimeout(resolve, remaining))]);
270
272
  }
273
+ const outlet = findOutlet(element, skip);
274
+ if (outlet) return outlet;
271
275
  throw new Error(`Timed out waiting for <u-outlet> inside <${element.tagName.toLowerCase()}>. Ensure that the router root element contains a <u-outlet> child.`);
272
276
  }
273
277
  /**
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { s as UOutlet$1, t as ULink$1 } from "./share-Dl02vMhW.js";
1
+ import { s as UOutlet$1, t as ULink$1 } from "./share-iJLbxFbJ.js";
2
2
  import React from "react";
3
3
  import { createComponent } from "@lit/react";
4
4
  //#region src/react.ts
@@ -1023,12 +1023,12 @@ function catchBasepath(basepath) {
1023
1023
  return basepath;
1024
1024
  }
1025
1025
  //#endregion
1026
- //#region \0@oxc-project+runtime@0.146.0/helpers/esm/decorateMetadata.js
1026
+ //#region \0@oxc-project+runtime@0.147.0/helpers/esm/decorateMetadata.js
1027
1027
  function __decorateMetadata(k, v) {
1028
1028
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1029
1029
  }
1030
1030
  //#endregion
1031
- //#region \0@oxc-project+runtime@0.146.0/helpers/esm/decorate.js
1031
+ //#region \0@oxc-project+runtime@0.147.0/helpers/esm/decorate.js
1032
1032
  function __decorate(decorators, target, key, desc) {
1033
1033
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1034
1034
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1081,6 +1081,29 @@ var ULink = class ULink extends LitElement {
1081
1081
  this.removeEventListener("click", this.handleClick);
1082
1082
  super.disconnectedCallback();
1083
1083
  }
1084
+ /**
1085
+ * 호스트에 세팅된 `aria-current`/`aria-label`은 실제 접근 가능한(포커스 대상)
1086
+ * 엘리먼트가 아니라 — 그 안쪽 shadow DOM 의 네이티브 `<a>`다. 섀도우 경계를
1087
+ * 넘지 않으므로 접근성 트리에 자동 반영되지 않는다(docket #45 실측 — 속성은
1088
+ * 붙어 있는데 접근성 트리의 `aria-current`는 계속 비어 있음). `render()`가 이
1089
+ * 값을 읽어 내부 `<a>`에 직접 옮긴다.
1090
+ *
1091
+ * 둘 다 Lit 리액티브 프로퍼티로 선언돼 있지 않아 `observedAttributes`에 없다 —
1092
+ * 그 목록에 없는 속성은 `attributeChangedCallback` 자체가 호출되지 않는다
1093
+ * (커스텀 엘리먼트 표준 동작). 초기 렌더는 되지만 연결 후 동적 변경은 반영되지
1094
+ * 않았다 — 목록에 명시적으로 추가해야 한다.
1095
+ */
1096
+ static get observedAttributes() {
1097
+ return [
1098
+ ...super.observedAttributes,
1099
+ "aria-current",
1100
+ "aria-label"
1101
+ ];
1102
+ }
1103
+ attributeChangedCallback(name, old, value) {
1104
+ super.attributeChangedCallback(name, old, value);
1105
+ if (name === "aria-current" || name === "aria-label") this.requestUpdate();
1106
+ }
1084
1107
  willUpdate(changedProperties) {
1085
1108
  super.willUpdate(changedProperties);
1086
1109
  if (changedProperties.has("href")) this.isExternal = isExternalUrl(this.href || "");
@@ -1092,6 +1115,8 @@ var ULink = class ULink extends LitElement {
1092
1115
  target=${ifDefined(this.target)}
1093
1116
  rel=${ifDefined(this.rel)}
1094
1117
  data-navigate=${ifDefined(this.navigate)}
1118
+ aria-current=${ifDefined(this.getAttribute("aria-current") ?? void 0)}
1119
+ aria-label=${ifDefined(this.getAttribute("aria-label") ?? void 0)}
1095
1120
  >
1096
1121
  <slot></slot>
1097
1122
  </a>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/router",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "description": "A modern client-side router for web applications with support for Lit and React components",
5
5
  "keywords": [
6
6
  "lit",
@@ -47,6 +47,7 @@
47
47
  }
48
48
  },
49
49
  "scripts": {
50
+ "preversion": "node ../../scripts/preversion-check.mjs",
50
51
  "test": "vitest run",
51
52
  "build": "npm run typecheck && vite build",
52
53
  "test:watch": "vitest",
@@ -41,3 +41,23 @@ export function AppRoot() {
41
41
  ## Nested Outlet Rule
42
42
 
43
43
  A parent route must render `<u-outlet>` to host child route content.
44
+
45
+ ## Default Error Page Styling
46
+
47
+ `Router` renders `<u-error-page>` internally when a route fails and no custom
48
+ `fallback` was configured. It is not exported for direct import, but three CSS
49
+ custom properties are available to lightly restyle it without replacing
50
+ `fallback.render` entirely:
51
+
52
+ | Custom Property | Description | Default |
53
+ | --- | --- | --- |
54
+ | `--error-icon-color` | Icon color | none — inherits the ambient text color |
55
+ | `--error-code-color` | Error code text color | none — inherits the ambient text color |
56
+ | `--error-message-color` | Error message text color | none — inherits the ambient text color |
57
+
58
+ ```css
59
+ u-error-page {
60
+ --error-icon-color: #b91c1c;
61
+ --error-code-color: #b91c1c;
62
+ }
63
+ ```