@iyulab/modern-app 0.38.0 → 0.40.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/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.40.0] - 2026-10-07
4
+
5
+ ### Changed
6
+
7
+ - **With `i18n` configured, i18next's language drives the whole app** — at load and on every `changeLanguage()` it is
8
+ handed to `@iyulab/components`' `Locale` (shell, primitives and component chrome follow, re-rendering on screen) and
9
+ to `<html lang>` (WCAG 3.1.1). Before, `changeLanguage('ko')` changed the app's own text while button names, empty
10
+ states and validation messages stayed in the old language, and a first load could show two languages when i18next's
11
+ `lng` differed from `<html lang>` or the browser's. Without `i18n` nothing changes — `Locale.set()` chooses.
12
+ - **Requires `@iyulab/components` 2.8.0** (peer `>=2.8.0`) — the version whose components re-render on a locale switch.
13
+
14
+ ## [0.39.0] - 2026-10-06
15
+
16
+ ### Fixed
17
+
18
+ - **`u-sidebar-layout` marks the current menu on arrival.** It learned the current route only from `route-begin`, so
19
+ when the layout is rendered by a route (a React shell as a nested route's parent), the first navigation's event
20
+ had passed before the layout connected: after typing an address or reloading, no link was `selected` and none
21
+ had `aria-current`, until the user navigated once. It now starts from the current location (`currentRoutePath()`
22
+ from `@iyulab/router` 0.20).
23
+ - **Sidebar group icons follow `--app-sidebar-icon-size`**, like links. They used a fixed 20px, so a theme setting
24
+ the token (16px in `@iyulab/house-style`) drew two icon sizes in one rail.
25
+
26
+ ### Added
27
+
28
+ - **`--app-sidebar-group-active-fg`** — text of a group header whose group holds the active menu. It sits on the
29
+ panel, but its default is derived from the active *background* (85% + black), which works for a dark active
30
+ background and leaves a light one (`--hs-brand-soft`) with near-invisible text (about 1.6:1). A theme with a
31
+ light active background sets this to its ink color. Unset, nothing changes.
32
+
33
+ ### Changed
34
+
35
+ - Requires `@iyulab/router` `^0.20.0`.
36
+
3
37
  ## [0.38.0] - 2026-10-06
4
38
 
5
39
  ### Added
package/README.md CHANGED
@@ -148,7 +148,9 @@ html`<p>${translate('common::greeting')}</p>`;
148
148
  The package's own strings — the page header's back link, empty-state copy, the shell's toggle and
149
149
  close labels, the wizard's buttons and step announcement — live in the `modern-app` namespace of
150
150
  `@iyulab/components`' `Locale`. English and Korean are built in, English being the default; pick the
151
- language once, with `Locale.set()` — the primitives and `@iyulab/flex-table` follow the same call.
151
+ language once, with `Locale.set()` — the primitives and `@iyulab/flex-table` follow the same call. With
152
+ `i18n` configured you do not call it: i18next's language (at load and on every `changeLanguage()`) is
153
+ handed to `Locale` and to `<html lang>`.
152
154
  Register other languages, or your own wording, in the same namespace:
153
155
 
154
156
  ```typescript
package/dist/App.d.ts CHANGED
@@ -14,6 +14,8 @@ declare class App {
14
14
  private _screen?;
15
15
  private _user?;
16
16
  private _gateTeardown?;
17
+ /** i18next 언어 → components `Locale` 동기화 해제(`unload`). */
18
+ private _languageSync?;
17
19
  private constructor();
18
20
  /** 싱글톤 인스턴스 반환 */
19
21
  static get instance(): App;
@@ -32,6 +34,13 @@ declare class App {
32
34
  /** 앱 로드 및 초기화 */
33
35
  load(config: AppConfig): Promise<void>;
34
36
  /** 앱 언로드 */
37
+ /**
38
+ * i18next 를 설정한 앱에서는 **i18next 가 언어의 정본**이다 — 그 언어를 셸·컴포넌트 chrome(`@iyulab/components` 의
39
+ * `Locale`, 이 패키지의 `modernAppLocale` 이 그것을 따른다)과 문서 `<html lang>`(WCAG 3.1.1, 낭독기 발음)에 잇는다.
40
+ * 종전에는 둘이 따로였다: `i18next.changeLanguage('ko')` 가 앱 문장만 바꾸고 버튼 이름·빈 상태·검증 메시지는 옛 언어로,
41
+ * 첫 로드에서도 i18next `lng` 와 `<html lang>`/브라우저 언어가 다르면 한 화면에 두 언어가 섰다.
42
+ */
43
+ private syncLanguage;
35
44
  unload(): void;
36
45
  /** 페이지 이동 */
37
46
  navigate(path: string): void;
package/dist/App.js CHANGED
@@ -4,23 +4,24 @@ import { Router as n } from "@iyulab/router";
4
4
  import { setDefaultBaseUrl as r } from "@iyulab/components/dist/utilities/icons.js";
5
5
  import { Theme as i } from "@iyulab/components/dist/utilities/Theme.js";
6
6
  import { Toast as a } from "@iyulab/components/dist/utilities/Toast.js";
7
+ import { Locale as o } from "@iyulab/components/dist/utilities/Locale.js";
7
8
  //#region src/App.ts
8
- var o = "\n:where(body) { margin: 0; }\n@media screen {\n :where(body) { width: 100vw; height: 100vh; }\n}\n", s;
9
- function c() {
10
- if (!s) {
9
+ var s = "\n:where(body) { margin: 0; }\n@media screen {\n :where(body) { width: 100vw; height: 100vh; }\n}\n", c;
10
+ function l() {
11
+ if (!c) {
11
12
  if ("adoptedStyleSheets" in document && typeof CSSStyleSheet < "u" && typeof CSSStyleSheet.prototype.replaceSync == "function") {
12
13
  let e = new CSSStyleSheet();
13
- e.replaceSync(o), document.adoptedStyleSheets = [...document.adoptedStyleSheets, e], s = e;
14
+ e.replaceSync(s), document.adoptedStyleSheets = [...document.adoptedStyleSheets, e], c = e;
14
15
  } else {
15
16
  let e = document.createElement("style");
16
- e.textContent = o, document.head.prepend(e), s = e;
17
+ e.textContent = s, document.head.prepend(e), c = e;
17
18
  }
18
19
  }
19
20
  }
20
- var l = class o {
21
+ var u = class s {
21
22
  constructor() {}
22
23
  static get instance() {
23
- return o._instance ||= new o(), o._instance;
24
+ return s._instance ||= new s(), s._instance;
24
25
  }
25
26
  get config() {
26
27
  return this._config;
@@ -43,7 +44,7 @@ var l = class o {
43
44
  async load(a) {
44
45
  if (this.unload(), this._config = a, await i.init(a.theme), a.iconBasepath && r(a.iconBasepath), a.i18n) {
45
46
  for (let e of a.i18n.plugins || []) t.use(e);
46
- await t.init(a.i18n);
47
+ await t.init(a.i18n), this.syncLanguage();
47
48
  }
48
49
  if (a.auth) {
49
50
  let e = a.root || document.body, t = () => {
@@ -88,8 +89,16 @@ var l = class o {
88
89
  useIntercept: a.useIntercept
89
90
  });
90
91
  }
92
+ syncLanguage() {
93
+ let e = (e) => {
94
+ e && e !== "cimode" && (o.set(e), typeof document < "u" && (document.documentElement.lang = e));
95
+ };
96
+ e(t.resolvedLanguage ?? t.language);
97
+ let n = (n) => e(t.resolvedLanguage ?? n);
98
+ t.on("languageChanged", n), this._languageSync = () => t.off("languageChanged", n);
99
+ }
91
100
  unload() {
92
- this._gateTeardown &&= (this._gateTeardown(), void 0), this._user = void 0, this._screen &&= (this._screen.destroy(), void 0), this._layout &&= (this._layout.remove(), void 0), this._router &&= (this._router.destroy(), void 0), this._config &&= void 0;
101
+ this._languageSync?.(), this._languageSync = void 0, this._gateTeardown &&= (this._gateTeardown(), void 0), this._user = void 0, this._screen &&= (this._screen.destroy(), void 0), this._layout &&= (this._layout.remove(), void 0), this._router &&= (this._router.destroy(), void 0), this._config &&= void 0;
93
102
  }
94
103
  navigate(e) {
95
104
  this._router?.go(e);
@@ -116,7 +125,7 @@ var l = class o {
116
125
  await a.error(e, { ...t });
117
126
  }
118
127
  async createLayout(e, t) {
119
- e === document.body && c();
128
+ e === document.body && l();
120
129
  let n;
121
130
  if (t.type === "sidebar") {
122
131
  let { SidebarLayout: e } = await import("./layouts/SidebarLayout.js"), r = new e();
@@ -126,4 +135,4 @@ var l = class o {
126
135
  }
127
136
  }.instance;
128
137
  //#endregion
129
- export { l as app };
138
+ export { u as app };
@@ -34,8 +34,10 @@ var t = e`
34
34
  }
35
35
  button[selected] {
36
36
  /* 패널 위 텍스트라 강조색을 그대로 쓰면 대비가 부족하다(blue-600 = 3.68:1).
37
- 활성 배경과 같은 파생식으로 한 단 어둡게 해 AA 를 넘긴다(4.85:1). */
38
- color: color-mix(in srgb, var(--group-accent) 85%, black);
37
+ 기본은 활성 배경과 같은 파생식으로 한 단 어둡게 해 AA 를 넘긴다(4.85:1) — 활성 면이 «진한» 색이라는 전제다.
38
+ 연한 활성 면(+ 진한 글자)을 쓰는 테마에서는 그 면을 15% 어둡게 한 색이 패널 위에서 1.6:1 로 묻혔다.
39
+ 그런 테마는 --app-sidebar-group-active-fg 를 «패널 위 강조 글자» 로 정한다. */
40
+ color: var(--app-sidebar-group-active-fg, color-mix(in srgb, var(--app-sidebar-active-bg, var(--u-primary-color, #1976D2)) 85%, black));
39
41
  }
40
42
  button:hover {
41
43
  color: var(--u-txt-color-hover, #1565C0);
@@ -52,7 +54,8 @@ var t = e`
52
54
  .icon {
53
55
  flex-shrink: 0;
54
56
  color: inherit;
55
- font-size: 20px;
57
+ /* 링크와 같은 토큰 — 한 레일의 아이콘은 한 크기다(위계는 들여쓰기·캐럿·레일이 진다). */
58
+ font-size: var(--app-sidebar-icon-size, 20px);
56
59
  }
57
60
 
58
61
  /*
@@ -1,6 +1,5 @@
1
1
  import { nothing, PropertyValues } from 'lit';
2
2
  import { UProgressBar } from '@iyulab/components/dist/components/progress-bar/UProgressBar.js';
3
- import { RouteContext } from '@iyulab/router';
4
3
  import { StyledElement } from '../internals/StyledElement.js';
5
4
  import { SidebarLayoutConfig, SidebarState, SidebarParts } from './SidebarLayout.types';
6
5
  export declare class SidebarLayout extends StyledElement<SidebarParts> {
@@ -38,8 +37,12 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
38
37
  * `NotSupportedError`이고, `connectedCallback`은 재연결마다 다시 돈다.
39
38
  */
40
39
  private readonly internals;
41
- /** 현재 라우터 컨텍스트 */
42
- context: RouteContext | null;
40
+ /**
41
+ * 현재 라우트 경로(`RouteContext.path` 형식) — 메뉴의 «현재 항목» 판정 원천.
42
+ * 붙을 때 지금 위치로 시작한다: 셸이 라우트 결과물로 그려지면(React 중첩 라우트의 부모) 첫 `route-begin` 은
43
+ * 셸이 붙기 **전에** 지나가, 주소창 진입·새로 고침 직후 현재 항목이 하나도 없었다.
44
+ */
45
+ private currentPath;
43
46
  /**
44
47
  * 라우트 컨텐츠의 실제 스크롤 컨테이너(섀도 DOM `[part="main"]`). `scrollTop`을 읽어
45
48
  * 위치를 저장하거나, 써서 복원한다 — `part="main"`은 스타일링용 CSS 훅일 뿐 JS 접근
@@ -11,28 +11,29 @@ import "../components/SidebarLink.js";
11
11
  import "../components/SidebarGroup.js";
12
12
  import "../components/SidebarButton.js";
13
13
  import { styles as c } from "./SidebarLayout.styles.js";
14
- import { html as l, nothing as u } from "lit";
15
- import { customElement as d, property as f, query as p, state as m } from "lit/decorators.js";
14
+ import { currentRoutePath as l } from "@iyulab/router";
15
+ import { html as u, nothing as d } from "lit";
16
+ import { customElement as f, property as p, query as m, state as h } from "lit/decorators.js";
16
17
  import "@iyulab/components/dist/components/icon/UIcon.js";
17
18
  import "@iyulab/components/dist/components/button/UButton.js";
18
- import { unsafeHTML as h } from "lit/directives/unsafe-html.js";
19
- import { repeat as g } from "lit/directives/repeat.js";
19
+ import { unsafeHTML as g } from "lit/directives/unsafe-html.js";
20
+ import { repeat as _ } from "lit/directives/repeat.js";
20
21
  import "@iyulab/components/dist/components/popover/UPopover.js";
21
22
  import "@iyulab/components/dist/components/menu/UMenu.js";
22
23
  import "@iyulab/components/dist/components/menu-item/UMenuItem.js";
23
- import { createDevWarner as _ } from "@iyulab/components/dist/utilities/devWarning.js";
24
- import { isFocusCandidate as v, querySelectorDeep as y } from "@iyulab/components/dist/utilities/elements.js";
25
- import { OverlayManager as b } from "@iyulab/components/dist/utilities/OverlayManager.js";
26
- import { UProgressBar as x } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
24
+ import { createDevWarner as v } from "@iyulab/components/dist/utilities/devWarning.js";
25
+ import { isFocusCandidate as y, querySelectorDeep as b } from "@iyulab/components/dist/utilities/elements.js";
26
+ import { OverlayManager as x } from "@iyulab/components/dist/utilities/OverlayManager.js";
27
+ import { UProgressBar as S } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
27
28
  //#region src/layouts/SidebarLayout.ts
28
- var S, C = _("@iyulab/modern-app"), w = 0;
29
- function T(e) {
29
+ var C, w = v("@iyulab/modern-app"), T = 0;
30
+ function E(e) {
30
31
  let t = e.tagName;
31
32
  if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
32
33
  let n = e.getAttribute("role");
33
34
  return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
34
35
  }
35
- var E = /* @__PURE__ */ new Set([
36
+ var D = /* @__PURE__ */ new Set([
36
37
  "button",
37
38
  "link",
38
39
  "checkbox",
@@ -45,27 +46,27 @@ var E = /* @__PURE__ */ new Set([
45
46
  "tab",
46
47
  "treeitem"
47
48
  ]);
48
- function D(e) {
49
+ function O(e) {
49
50
  let t = e.tagName;
50
- return t === "BUTTON" || t === "SUMMARY" || t === "A" && e.hasAttribute("href") ? !0 : E.has(e.getAttribute("role") ?? "");
51
+ return t === "BUTTON" || t === "SUMMARY" || t === "A" && e.hasAttribute("href") ? !0 : D.has(e.getAttribute("role") ?? "");
51
52
  }
52
- function O(e) {
53
+ function k(e) {
53
54
  let t = getComputedStyle(e);
54
55
  return (t.overflowY === "auto" || t.overflowY === "scroll") && e.scrollHeight > e.clientHeight;
55
56
  }
56
- function k(e, t) {
57
+ function A(e, t) {
57
58
  for (let n of e) if (n instanceof HTMLElement) {
58
- if (O(n)) return n === t;
59
+ if (k(n)) return n === t;
59
60
  if (n === t) break;
60
61
  }
61
62
  return !0;
62
63
  }
63
- function A() {
64
+ function j() {
64
65
  let e = document.activeElement;
65
66
  for (; e?.shadowRoot?.activeElement;) e = e.shadowRoot.activeElement;
66
67
  return e;
67
68
  }
68
- function j(e, t) {
69
+ function M(e, t) {
69
70
  let n = t;
70
71
  for (; n;) {
71
72
  if (n === e) return !0;
@@ -73,18 +74,18 @@ function j(e, t) {
73
74
  }
74
75
  return !1;
75
76
  }
76
- var M = class extends i {
77
+ var N = class extends i {
77
78
  static {
78
- S = this;
79
+ C = this;
79
80
  }
80
81
  constructor(...t) {
81
- super(...t), this.state = "default", this.counts = {}, this.locale = "", this.hasOverlay = !1, this.openMenu = null, this.menuIds = /* @__PURE__ */ new WeakMap(), this.hasNotice = !1, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.context = null, this.unsizedWarnKey = `unsized:${++w}`, this.isMatchedLink = (e) => !this.context || !e ? !1 : (e = typeof e == "string" ? new URLPattern(e, window.location.origin) : e, e.test(this.context.path, window.location.origin)), this.handleToggleButtonClick = () => {
82
+ super(...t), this.state = "default", this.counts = {}, this.locale = "", this.hasOverlay = !1, this.openMenu = null, this.menuIds = /* @__PURE__ */ new WeakMap(), this.hasNotice = !1, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.currentPath = null, this.unsizedWarnKey = `unsized:${++T}`, this.isMatchedLink = (e) => this.currentPath === null || !e ? !1 : (e = typeof e == "string" ? new URLPattern(e, window.location.origin) : e, e.test(this.currentPath, window.location.origin)), this.handleToggleButtonClick = () => {
82
83
  let t = this.screenSize ?? e.screen ?? "large";
83
84
  t === "large" ? this.state = this.state === "default" ? "slim" : "default" : t === "medium" ? this.state = this.state === "slim" ? "modal" : "slim" : t === "small" ? this.state = this.state === "mobile" ? "mobile-open" : "mobile" : console.warn("Unknown screen size:", t);
84
85
  }, this.handleBackdropClick = () => {
85
86
  this.state = "slim";
86
87
  }, this.handleRouteBegin = (e) => {
87
- this.progressBarEl.setAttribute("visible", ""), this.progressBarEl.value = 0, this.state === "modal" && (this.state = "slim"), this.state === "mobile-open" && (this.state = "mobile"), this.context = e.context;
88
+ this.progressBarEl.setAttribute("visible", ""), this.progressBarEl.value = 0, this.state === "modal" && (this.state = "slim"), this.state === "mobile-open" && (this.state = "mobile"), this.currentPath = e.context.path;
88
89
  }, this.handleRouteProgress = (e) => {
89
90
  this.progressBarEl.value = e.progress;
90
91
  }, this.handleRouteDone = (e) => {
@@ -104,7 +105,7 @@ var M = class extends i {
104
105
  }, this.handleOverlaySlotChange = (e) => {
105
106
  let t = o(e.target);
106
107
  if (t && !this.hasOverlay) {
107
- let e = A();
108
+ let e = j();
108
109
  this.overlayTrigger = e && e !== document.body ? e : null;
109
110
  }
110
111
  this.hasOverlay = t;
@@ -118,9 +119,9 @@ var M = class extends i {
118
119
  }, this._handleMainKeydown = (e) => {
119
120
  if (e.defaultPrevented) return;
120
121
  let t = e.composedPath()[0];
121
- if (t instanceof HTMLElement && T(t) || e.key === " " && t instanceof HTMLElement && D(t)) return;
122
+ if (t instanceof HTMLElement && E(t) || e.key === " " && t instanceof HTMLElement && O(t)) return;
122
123
  let n = this.shadowRoot?.querySelector(".main");
123
- if (!n || !k(e.composedPath(), n)) return;
124
+ if (!n || !A(e.composedPath(), n)) return;
124
125
  let r = n.clientHeight;
125
126
  switch (e.key) {
126
127
  case " ":
@@ -160,16 +161,16 @@ var M = class extends i {
160
161
  return this.shadowRoot?.querySelector(".main") ?? null;
161
162
  }
162
163
  connectedCallback() {
163
- super.connectedCallback(), window.addEventListener("route-begin", this.handleRouteBegin), window.addEventListener("route-done", this.handleRouteDone), window.addEventListener("route-progress", this.handleRouteProgress), window.addEventListener("route-error", this.handleRouteError), window.addEventListener("screen-resize", this.handleScreenResize);
164
+ super.connectedCallback(), this.currentPath = l(), window.addEventListener("route-begin", this.handleRouteBegin), window.addEventListener("route-done", this.handleRouteDone), window.addEventListener("route-progress", this.handleRouteProgress), window.addEventListener("route-error", this.handleRouteError), window.addEventListener("screen-resize", this.handleScreenResize);
164
165
  }
165
166
  disconnectedCallback() {
166
- window.removeEventListener("route-begin", this.handleRouteBegin), window.removeEventListener("route-done", this.handleRouteDone), window.removeEventListener("route-progress", this.handleRouteProgress), window.removeEventListener("route-error", this.handleRouteError), window.removeEventListener("screen-resize", this.handleScreenResize), b.closeLayer(this), super.disconnectedCallback();
167
+ window.removeEventListener("route-begin", this.handleRouteBegin), window.removeEventListener("route-done", this.handleRouteDone), window.removeEventListener("route-progress", this.handleRouteProgress), window.removeEventListener("route-error", this.handleRouteError), window.removeEventListener("screen-resize", this.handleScreenResize), x.closeLayer(this), super.disconnectedCallback();
167
168
  }
168
169
  willUpdate(e) {
169
170
  super.willUpdate(e), e.has("config") && (this.styles = this.config?.styles);
170
171
  }
171
172
  updated(e) {
172
- super.updated(e), e.has("hasOverlay") && (this.internals?.states?.[this.hasOverlay ? "add" : "delete"]("overlay"), this.hasOverlay ? (b.openLayer(this, this.handleOverlayEscape), this.focusOverlay()) : e.get("hasOverlay") === !0 && (b.closeLayer(this), this.restoreOverlayFocus()));
173
+ super.updated(e), e.has("hasOverlay") && (this.internals?.states?.[this.hasOverlay ? "add" : "delete"]("overlay"), this.hasOverlay ? (x.openLayer(this, this.handleOverlayEscape), this.focusOverlay()) : e.get("hasOverlay") === !0 && (x.closeLayer(this), this.restoreOverlayFocus()));
173
174
  }
174
175
  firstUpdated(e) {
175
176
  super.firstUpdated(e), this.warnIfUnsized();
@@ -178,11 +179,11 @@ var M = class extends i {
178
179
  process.env.NODE_ENV !== "production" && requestAnimationFrame(() => {
179
180
  if (!this.isConnected) return;
180
181
  let e = this.getBoundingClientRect().height;
181
- e >= 200 || C(this.unsizedWarnKey, `u-sidebar-layout is only ${Math.round(e)}px tall — its height: 100% found no sized ancestor, so the shell sits at its own chrome height and the route area has almost no room. Give the root element a height (e.g. #app { height: 100vh } — app.load() does this for document.body).`);
182
+ e >= 200 || w(this.unsizedWarnKey, `u-sidebar-layout is only ${Math.round(e)}px tall — its height: 100% found no sized ancestor, so the shell sits at its own chrome height and the route area has almost no room. Give the root element a height (e.g. #app { height: 100vh } — app.load() does this for document.body).`);
182
183
  });
183
184
  }
184
185
  render() {
185
- return this.config ? l`
186
+ return this.config ? u`
186
187
  <!-- 반복 영역 건너뛰기(WCAG 2.4.1) — 셸의 첫 Tab. 포커스될 때만 보인다. -->
187
188
  <a class="skip-link" part="skip-link" href="#" @click=${this.handleSkipLinkClick}>
188
189
  ${a(this.locale || void 0).skipToContent}
@@ -224,13 +225,13 @@ var M = class extends i {
224
225
 
225
226
  <!-- Sidebar Navigation Menu -->
226
227
  <nav class="sidebar-main" part="sidebar-main" scrollable
227
- aria-label=${this.config.mainAriaLabel ?? u}>
228
- ${g(t(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
228
+ aria-label=${this.config.mainAriaLabel ?? d}>
229
+ ${_(t(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
229
230
  </nav>
230
231
 
231
232
  <!-- Sidebar Footer -->
232
233
  <div class="sidebar-footer" part="sidebar-footer">
233
- ${g(t(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
234
+ ${_(t(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
234
235
  </div>
235
236
  </aside>
236
237
 
@@ -241,8 +242,8 @@ var M = class extends i {
241
242
 
242
243
  <div class="main-content" part="main-content" ?inert=${this.hasOverlay}>
243
244
  <div class="notices ${this.hasNotice ? "" : "empty"}" part="notices"
244
- role=${this.config.noticesAriaLabel ? "region" : u}
245
- aria-label=${this.config.noticesAriaLabel ?? u}>
245
+ role=${this.config.noticesAriaLabel ? "region" : d}
246
+ aria-label=${this.config.noticesAriaLabel ?? d}>
246
247
  <slot name="notice" @slotchange=${this.handleNoticeSlotChange}></slot>
247
248
  </div>
248
249
  <slot></slot>
@@ -263,13 +264,13 @@ var M = class extends i {
263
264
  <div class="backdrop" ?hidden="${this.state !== "modal"}"
264
265
  @click="${this.handleBackdropClick}"
265
266
  ></div>
266
- ` : u;
267
+ ` : d;
267
268
  }
268
269
  renderMenu(e) {
269
270
  let t = this.menuIds.get(e);
270
- t || (t = `u-sidebar-menu-${++S.menuSeq}`, this.menuIds.set(e, t));
271
+ t || (t = `u-sidebar-menu-${++C.menuSeq}`, this.menuIds.set(e, t));
271
272
  let n = t, r = this.state === "mobile" || this.state === "mobile-open" ? "bottom-start" : "right-start";
272
- return l`
273
+ return u`
273
274
  <u-sidebar-button
274
275
  id=${n}
275
276
  haspopup="menu"
@@ -286,9 +287,9 @@ var M = class extends i {
286
287
  @hide=${(e) => this.handleMenuHide(e, n)}
287
288
  @pick=${(t) => this.handleMenuPick(t, e)}>
288
289
  <u-menu>
289
- ${g(e.items, (e, t) => t, (e, t) => l`
290
+ ${_(e.items, (e, t) => t, (e, t) => u`
290
291
  <u-menu-item value=${String(t)} ?disabled=${e.disabled === !0}>
291
- ${e.icon ? l`<u-icon slot="prefix" .lib=${e.lib} .name=${e.icon}></u-icon>` : u}
292
+ ${e.icon ? u`<u-icon slot="prefix" .lib=${e.lib} .name=${e.icon}></u-icon>` : d}
292
293
  ${e.label}
293
294
  </u-menu-item>
294
295
  `)}
@@ -302,16 +303,16 @@ var M = class extends i {
302
303
  }
303
304
  handleMenuHide(e, t) {
304
305
  this.openMenu === t && (this.openMenu = null);
305
- let n = e.currentTarget, r = A();
306
- r && j(n, r) && this.shadowRoot?.querySelector(`#${t}`)?.focus();
306
+ let n = e.currentTarget, r = j();
307
+ r && M(n, r) && this.shadowRoot?.querySelector(`#${t}`)?.focus();
307
308
  }
308
309
  renderItem(e) {
309
- if (!e) return u;
310
+ if (!e) return d;
310
311
  if (e.type === "html") {
311
312
  let t = e.render(this.state);
312
- return typeof t == "string" ? h(t) : l`${t}`;
313
+ return typeof t == "string" ? g(t) : u`${t}`;
313
314
  }
314
- if (e.type === "button") return l`
315
+ if (e.type === "button") return u`
315
316
  <u-sidebar-button
316
317
  ?compact=${this.state === "slim"}
317
318
  .icon="${e.icon}"
@@ -324,7 +325,7 @@ var M = class extends i {
324
325
  if (e.type === "menu") return this.renderMenu(e);
325
326
  if (e.type === "link") {
326
327
  let t = this.isMatchedLink(e.pattern || e.href);
327
- return l`
328
+ return u`
328
329
  <u-sidebar-link
329
330
  ?compact=${this.state === "slim"}
330
331
  ?selected=${t}
@@ -340,18 +341,18 @@ var M = class extends i {
340
341
  ></u-sidebar-link>
341
342
  `;
342
343
  }
343
- if (e.type === "section") return l`
344
+ if (e.type === "section") return u`
344
345
  <u-sidebar-section
345
346
  ?compact=${this.state === "slim"}
346
347
  .mainTitle="${e.title}"
347
348
  .subTitle="${e.subTitle}"
348
349
  .styles="${e.styles}">
349
- ${g(e.items, (e, t) => t, (e) => this.renderItem(e))}
350
+ ${_(e.items, (e, t) => t, (e) => this.renderItem(e))}
350
351
  </u-sidebar-section>
351
352
  `;
352
353
  if (e.type === "group") {
353
354
  let t = e.items.some((e) => this.isMatchedLink(e.pattern || e.href));
354
- return l`
355
+ return u`
355
356
  <u-sidebar-group
356
357
  ?compact=${this.state === "slim"}
357
358
  ?selected=${t}
@@ -360,11 +361,11 @@ var M = class extends i {
360
361
  .lib="${e.lib}"
361
362
  .label="${e.label}"
362
363
  .styles="${e.styles}">
363
- ${g(e.items, (e, t) => t, (e) => this.renderItem(e))}
364
+ ${_(e.items, (e, t) => t, (e) => this.renderItem(e))}
364
365
  </u-sidebar-group>
365
366
  `;
366
367
  }
367
- return u;
368
+ return d;
368
369
  }
369
370
  static {
370
371
  this.DEFAULT_ICONS = {
@@ -376,30 +377,30 @@ var M = class extends i {
376
377
  };
377
378
  }
378
379
  icon(e) {
379
- return this.config?.icons?.[e] ?? S.DEFAULT_ICONS[e];
380
+ return this.config?.icons?.[e] ?? C.DEFAULT_ICONS[e];
380
381
  }
381
382
  renderLogo() {
382
383
  let e = this.config?.logo, t = this.config?.title ?? "Home";
383
- if (!e || typeof e == "string") return e ? l`
384
+ if (!e || typeof e == "string") return e ? u`
384
385
  <u-link class="logo-link" aria-label=${t}>
385
386
  <u-icon class="logo"
386
387
  .name="${e}"
387
388
  .fallback=${s}
388
389
  ></u-icon>
389
390
  </u-link>
390
- ` : u;
391
+ ` : d;
391
392
  if (typeof e == "function") {
392
393
  let n = e(this.state);
393
- return l`
394
+ return u`
394
395
  <u-link class="logo-link" aria-label=${t}>
395
396
  <span class="logo">
396
- ${typeof n == "string" ? h(n) : n}
397
+ ${typeof n == "string" ? g(n) : n}
397
398
  </span>
398
399
  </u-link>
399
400
  `;
400
401
  }
401
- return l`
402
- <u-link class="logo-link" .href=${e.href} aria-label=${e.alt ? u : t}>
402
+ return u`
403
+ <u-link class="logo-link" .href=${e.href} aria-label=${e.alt ? d : t}>
403
404
  <img class="logo"
404
405
  src="${e.src}"
405
406
  alt="${e.alt ?? ""}"
@@ -413,14 +414,14 @@ var M = class extends i {
413
414
  async focusRoute(e) {
414
415
  let t = this.routeContent();
415
416
  if (await Promise.all(t.map((e) => e.updateComplete)), this.hasOverlay || !e.isConnected) return;
416
- let n = A();
417
- n && t.some((e) => j(e, n)) || this.focusContentStart(t, e);
417
+ let n = j();
418
+ n && t.some((e) => M(e, n)) || this.focusContentStart(t, e);
418
419
  }
419
420
  focusContentStart(e, t) {
420
- let n = y(e, "[autofocus]", v);
421
+ let n = b(e, "[autofocus]", y);
421
422
  n?.focus();
422
- let r = A();
423
- n && r && j(n, r) || t.focus({ preventScroll: !0 });
423
+ let r = j();
424
+ n && r && M(n, r) || t.focus({ preventScroll: !0 });
424
425
  }
425
426
  overlayPanels() {
426
427
  return (this.shadowRoot?.querySelector("slot[name=\"overlay\"]"))?.assignedElements({ flatten: !0 }) ?? [];
@@ -428,19 +429,19 @@ var M = class extends i {
428
429
  async focusOverlay() {
429
430
  let e = this.overlayPanels();
430
431
  if (await Promise.all(e.map((e) => e.updateComplete)), !this.hasOverlay) return;
431
- let t = A();
432
- t && e.some((e) => j(e, t)) || (y(e, "[autofocus]", v) ?? y(e, "input, select, textarea, u-input, u-textarea, u-select, u-checkbox, u-radio, u-switch, u-slider", v) ?? this.shadowRoot?.querySelector(".overlay-close"))?.focus();
432
+ let t = j();
433
+ t && e.some((e) => M(e, t)) || (b(e, "[autofocus]", y) ?? b(e, "input, select, textarea, u-input, u-textarea, u-select, u-checkbox, u-radio, u-switch, u-slider", y) ?? this.shadowRoot?.querySelector(".overlay-close"))?.focus();
433
434
  }
434
435
  restoreOverlayFocus() {
435
436
  let e = this.overlayTrigger;
436
437
  this.overlayTrigger = null;
437
- let t = A();
438
+ let t = j();
438
439
  t && t !== document.body || (e?.isConnected ? e.focus() : this.mainElement?.focus({ preventScroll: !0 }));
439
440
  }
440
441
  };
441
- r([f({
442
+ r([p({
442
443
  type: String,
443
444
  reflect: !0
444
- }), n("design:type", Object)], M.prototype, "state", void 0), r([f({ type: Object }), n("design:type", Object)], M.prototype, "config", void 0), r([f({ type: Object }), n("design:type", typeof Record > "u" ? Object : Record)], M.prototype, "counts", void 0), r([f({ type: String }), n("design:type", Object)], M.prototype, "locale", void 0), r([p("u-progress-bar"), n("design:type", x === void 0 ? Object : x)], M.prototype, "progressBarEl", void 0), r([m(), n("design:type", Object)], M.prototype, "hasOverlay", void 0), r([m(), n("design:type", Object)], M.prototype, "openMenu", void 0), r([m(), n("design:type", Object)], M.prototype, "hasNotice", void 0), r([m(), n("design:type", Object)], M.prototype, "context", void 0), M = S = r([d("u-sidebar-layout")], M);
445
+ }), n("design:type", Object)], N.prototype, "state", void 0), r([p({ type: Object }), n("design:type", Object)], N.prototype, "config", void 0), r([p({ type: Object }), n("design:type", typeof Record > "u" ? Object : Record)], N.prototype, "counts", void 0), r([p({ type: String }), n("design:type", Object)], N.prototype, "locale", void 0), r([m("u-progress-bar"), n("design:type", S === void 0 ? Object : S)], N.prototype, "progressBarEl", void 0), r([h(), n("design:type", Object)], N.prototype, "hasOverlay", void 0), r([h(), n("design:type", Object)], N.prototype, "openMenu", void 0), r([h(), n("design:type", Object)], N.prototype, "hasNotice", void 0), r([h(), n("design:type", Object)], N.prototype, "currentPath", void 0), N = C = r([f("u-sidebar-layout")], N);
445
446
  //#endregion
446
- export { M as SidebarLayout };
447
+ export { N as SidebarLayout };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/modern-app",
3
3
  "description": "web-framework by iyulab based on lit-element",
4
- "version": "0.38.0",
4
+ "version": "0.40.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -60,12 +60,12 @@
60
60
  "test:react-types": "tsc -p tsconfig.react-smoke.json"
61
61
  },
62
62
  "dependencies": {
63
- "@iyulab/router": "^0.19.1",
63
+ "@iyulab/router": "^0.20.0",
64
64
  "i18next": "^26.3.6",
65
65
  "lit": "^3.3.3"
66
66
  },
67
67
  "peerDependencies": {
68
- "@iyulab/components": ">=2.0.0",
68
+ "@iyulab/components": ">=2.8.0",
69
69
  "@lit/react": "^1.0.8",
70
70
  "react": "^18.0.0 || ^19.0.0"
71
71
  },
@@ -78,7 +78,7 @@
78
78
  }
79
79
  },
80
80
  "devDependencies": {
81
- "@iyulab/components": ">=2.0.0",
81
+ "@iyulab/components": ">=2.8.0",
82
82
  "@lit/react": "^1.0.8",
83
83
  "@types/node": "^26.1.1",
84
84
  "@types/react": "^19.2.14",