@iyulab/modern-app 0.18.0 → 0.18.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,46 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.18.3] - 2026-08-22
4
+
5
+ ### Fixed
6
+
7
+ - **`SidebarButtonConfig.id`'s docs no longer claim external light-DOM `u-popover` anchoring
8
+ works.** It doesn't: the rendered `<u-sidebar-button>` lives inside `<u-sidebar-layout>`'s own
9
+ shadow root, and `for="#id"` lookups never cross a shadow boundary — a popover placed outside
10
+ the layout can never find the anchor, no matter how the `id` is passed. Documents the recipe
11
+ that does work instead: assemble the trigger and its `u-popover` together inside a single
12
+ `type: 'html'` item, with `placement` chosen from the sidebar's own state (a sideways flyout
13
+ has no room once the sidebar widens on mobile; a vertical one always does). Not a code change —
14
+ `id` still passes through exactly as before.
15
+
16
+ ## [0.18.2] - 2026-08-19
17
+
18
+ ### Fixed
19
+
20
+ - **`SidebarLayout`'s mobile-header and sidebar-header toggle buttons now have an accessible
21
+ name.** Both are icon-only `<u-button>`s with no `aria-label`, text, or `title` — a
22
+ screen-reader user only ever heard an unnamed "button". Wired through the same locale
23
+ registry `MasterDetailLayout.detailClose` already uses (`toggleMobileMenu` /
24
+ `toggleSidebar`), so translations follow `registerLocale` automatically; default English
25
+ labels preserve current behavior for everyone who hasn't set a `locale`.
26
+
27
+ ### Added
28
+
29
+ - **`SidebarButtonConfig.id?: string`.** Passed straight through to the rendered
30
+ `<u-sidebar-button>` host, so a consumer can anchor a `u-popover` (or any `for="#id"`
31
+ pattern) to a sidebar button using only public API — previously the only way to get an
32
+ anchorable id was to reach into the internal, unexported `<u-sidebar-button>` tag name.
33
+ Unset behaves exactly as before (no `id` attribute) — not a breaking change.
34
+
35
+ ## [0.18.1] - 2026-08-19
36
+
37
+ ### Fixed
38
+
39
+ - **`GroupBox`'s background no longer matches the surrounding page/sidebar in light mode, or
40
+ input fields in dark mode.** It referenced the same token as those surfaces
41
+ (`--u-panel-bg-color`); switched to `--u-bg-color-raised`, which both themes already define
42
+ distinctly.
43
+
3
44
  ## [0.18.0] - 2026-08-17
4
45
 
5
46
  ### Added
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.144.0/helpers/esm/decorate.js
1
+ //#region \0@oxc-project+runtime@0.146.0/helpers/esm/decorate.js
2
2
  function e(e, t, n, r) {
3
3
  var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
4
4
  if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.144.0/helpers/esm/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.146.0/helpers/esm/decorateMetadata.js
2
2
  function e(e, t) {
3
3
  if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
4
4
  }
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./ActionBar.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { slotHasContent as r } from "../internals/slotted.js";
5
5
  import { styles as i } from "./GroupBox.styles.js";
@@ -7,7 +7,7 @@ var t = e`
7
7
  사이드바가 열린 1280px 화면의 좁은 본문에서도, 넓은 본문을 가진 태블릿에서도
8
8
  같은 판단을 하려면 기준이 자기 폭이어야 한다. */
9
9
  container-type: inline-size;
10
- background-color: var(--u-panel-bg-color, #FFFFFF);
10
+ background-color: var(--u-bg-color-raised, #FAFAFA);
11
11
  border: 1px solid var(--u-border-color-weak, #EEEEEE);
12
12
  /* 면(surface) 단 — 컨트롤과 같은 반경을 쓰면 큰 사각형이 각져 보인다. */
13
13
  border-radius: var(--u-radius-2xl, 12px);
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoField.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./InfoSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { slotHasContent as i } from "../internals/slotted.js";
@@ -6,6 +6,9 @@ type ElementParts = 'host' | 'base' | 'icon' | 'label';
6
6
  /** 버튼 항목 구성 */
7
7
  export interface SidebarButtonConfig extends SidebarPermissionGuard {
8
8
  type: 'button';
9
+ /** 렌더된 `<u-sidebar-button>` 호스트에 그대로 전달된다 — `u-popover[for="#id"]` 같은
10
+ * 외부 앵커링에 쓴다. */
11
+ id?: string;
9
12
  icon?: string;
10
13
  /** `icon`을 어느 등록 라이브러리에서 찾을지. 미지정 시 `u-icon`의 기본 URL 경로로
11
14
  * 해석된다(`IconRegistry.register()`로 등록한 이름 있는 세트를 쓰려면 지정해야 함). */
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarButton.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { SidebarLink as i } from "./SidebarLink.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
5
5
  import { styles as i } from "./SidebarLink.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./SidebarSection.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { getLocaleStrings as r } from "../internals/locale.js";
5
5
  import { styles as i } from "./Wizard.styles.js";
@@ -1,5 +1,5 @@
1
- import e from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
2
- import t from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
3
3
  import { property as n } from "lit/decorators.js";
4
4
  import { UElement as r } from "@iyulab/components/dist/components/UElement.js";
5
5
  //#region src/internals/StyledElement.ts
@@ -28,6 +28,10 @@ export interface ModernAppLocaleStrings {
28
28
  noResultsDescription: string;
29
29
  /** Master-detail layout — accessible label for the overlay-mode close button. */
30
30
  detailClose: string;
31
+ /** Sidebar layout — accessible label for the mobile-header menu toggle button. */
32
+ toggleMobileMenu: string;
33
+ /** Sidebar layout — accessible label for the sidebar-header collapse/expand toggle button. */
34
+ toggleSidebar: string;
31
35
  /** Wizard — default Back/Next action labels ("Submit" wording is the consumer's call). */
32
36
  wizardBack: string;
33
37
  wizardNext: string;
@@ -6,6 +6,8 @@ var e = {
6
6
  noResultsTitle: "No matching results",
7
7
  noResultsDescription: "Try changing your search terms or filters.",
8
8
  detailClose: "Close",
9
+ toggleMobileMenu: "Toggle menu",
10
+ toggleSidebar: "Toggle sidebar",
9
11
  wizardBack: "Back",
10
12
  wizardNext: "Next",
11
13
  wizardStepAnnouncement: (e, t, n) => `Step ${e} of ${t}: ${n}`
@@ -9,6 +9,8 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
9
9
  state: SidebarState;
10
10
  /** 사이드바 레이아웃 설정 */
11
11
  config?: SidebarLayoutConfig;
12
+ /** 크롬 문자열(토글 버튼 접근성 라벨 등) 로케일 — `registerLocale`로 등록한 언어 태그 */
13
+ locale: string;
12
14
  progressBarEl: UProgressBar;
13
15
  /** 현재 라우터 컨텍스트 */
14
16
  context: RouteContext | null;
@@ -1,30 +1,32 @@
1
1
  import { app as e } from "../App.js";
2
2
  import { filterSidebarItems as t } from "./filterSidebarItems.js";
3
- import n from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorateMetadata.js";
4
- import r from "../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorateMetadata.js";
4
+ import r from "../_virtual/_@oxc-project_runtime@0.146.0/helpers/esm/decorate.js";
5
5
  import { StyledElement as i } from "../internals/StyledElement.js";
6
+ import { getLocaleStrings as a } from "../internals/locale.js";
6
7
  import "../components/SidebarSection.js";
7
8
  import "../components/SidebarLink.js";
8
9
  import "../components/SidebarGroup.js";
9
10
  import "../components/SidebarButton.js";
10
- import { styles as a } from "./SidebarLayout.styles.js";
11
- import { html as o, nothing as s } from "lit";
12
- import { customElement as c, property as l, query as u, state as d } from "lit/decorators.js";
11
+ import { styles as o } from "./SidebarLayout.styles.js";
12
+ import { html as s, nothing as c } from "lit";
13
+ import { customElement as l, property as u, query as d, state as f } from "lit/decorators.js";
13
14
  import "@iyulab/components/dist/components/button/UButton.js";
14
15
  import "@iyulab/components/dist/components/icon/UIcon.js";
15
- import { unsafeHTML as f } from "lit/directives/unsafe-html.js";
16
- import { repeat as p } from "lit/directives/repeat.js";
17
- import { UProgressBar as m } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
16
+ import { unsafeHTML as p } from "lit/directives/unsafe-html.js";
17
+ import { repeat as m } from "lit/directives/repeat.js";
18
+ import { ifDefined as h } from "lit/directives/if-defined.js";
19
+ import { UProgressBar as g } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
18
20
  //#region src/layouts/SidebarLayout.ts
19
- function h(e) {
21
+ function _(e) {
20
22
  let t = e.tagName;
21
23
  if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
22
24
  let n = e.getAttribute("role");
23
25
  return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
24
26
  }
25
- var g = class extends i {
27
+ var v = class extends i {
26
28
  constructor(...t) {
27
- super(...t), this.state = "default", this.context = null, 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.handleBrandLogoClick = (t) => () => {
29
+ super(...t), this.state = "default", this.locale = "", this.context = null, 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.handleBrandLogoClick = (t) => () => {
28
30
  e.navigate(t ?? "");
29
31
  }, this.handleToggleButtonClick = () => {
30
32
  let t = e.screen ?? "large";
@@ -45,7 +47,7 @@ var g = class extends i {
45
47
  }, 300);
46
48
  }, this._handleMainKeydown = (e) => {
47
49
  let t = e.composedPath()[0];
48
- if (t instanceof HTMLElement && h(t)) return;
50
+ if (t instanceof HTMLElement && _(t)) return;
49
51
  let n = this.shadowRoot?.querySelector(".main");
50
52
  if (!n) return;
51
53
  let r = n.clientHeight;
@@ -78,7 +80,7 @@ var g = class extends i {
78
80
  };
79
81
  }
80
82
  static {
81
- this.styles = [super.styles, a];
83
+ this.styles = [super.styles, o];
82
84
  }
83
85
  connectedCallback() {
84
86
  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);
@@ -90,7 +92,7 @@ var g = class extends i {
90
92
  super.willUpdate(e), e.has("config") && (this.styles = this.config?.styles);
91
93
  }
92
94
  render() {
93
- return this.config ? o`
95
+ return this.config ? s`
94
96
  <!-- Mobile Header -->
95
97
  <div class="mobile-header" part="mobile-header" ?hidden="${!this.state.startsWith("mobile")}">
96
98
  ${this.renderLogo()}
@@ -98,6 +100,7 @@ var g = class extends i {
98
100
  ${this.config.title}
99
101
  </span>
100
102
  <u-button class="toggler" variant="ghost"
103
+ aria-label=${a(this.locale || void 0).toggleMobileMenu}
101
104
  @click=${this.handleToggleButtonClick}>
102
105
  <u-icon
103
106
  lib="bootstrap"
@@ -115,6 +118,7 @@ var g = class extends i {
115
118
  ${this.config.title}
116
119
  </span>
117
120
  <u-button class="toggler" variant="ghost"
121
+ aria-label=${a(this.locale || void 0).toggleSidebar}
118
122
  @click=${this.handleToggleButtonClick}>
119
123
  <u-icon
120
124
  lib="bootstrap"
@@ -125,13 +129,13 @@ var g = class extends i {
125
129
 
126
130
  <!-- Sidebar Navigation Menu -->
127
131
  <nav class="sidebar-main" part="sidebar-main" scrollable
128
- aria-label=${this.config.mainAriaLabel ?? s}>
129
- ${p(t(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
132
+ aria-label=${this.config.mainAriaLabel ?? c}>
133
+ ${m(t(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
130
134
  </nav>
131
135
 
132
136
  <!-- Sidebar Footer -->
133
137
  <div class="sidebar-footer" part="sidebar-footer">
134
- ${p(t(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
138
+ ${m(t(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
135
139
  </div>
136
140
  </aside>
137
141
 
@@ -146,16 +150,17 @@ var g = class extends i {
146
150
  <div class="backdrop" ?hidden="${this.state !== "modal"}"
147
151
  @click="${this.handleBackdropClick}"
148
152
  ></div>
149
- ` : s;
153
+ ` : c;
150
154
  }
151
155
  renderItem(e) {
152
- if (!e) return s;
156
+ if (!e) return c;
153
157
  if (e.type === "html") {
154
158
  let t = e.render(this.state);
155
- return typeof t == "string" ? f(t) : o`${t}`;
159
+ return typeof t == "string" ? p(t) : s`${t}`;
156
160
  }
157
- if (e.type === "button") return o`
161
+ if (e.type === "button") return s`
158
162
  <u-sidebar-button
163
+ id=${h(e.id)}
159
164
  ?compact=${this.state === "slim"}
160
165
  .icon="${e.icon}"
161
166
  .lib="${e.lib}"
@@ -166,7 +171,7 @@ var g = class extends i {
166
171
  `;
167
172
  if (e.type === "link") {
168
173
  let t = this.isMatchedLink(e.pattern || e.href);
169
- return o`
174
+ return s`
170
175
  <u-sidebar-link
171
176
  ?compact=${this.state === "slim"}
172
177
  ?selected=${t}
@@ -181,18 +186,18 @@ var g = class extends i {
181
186
  ></u-sidebar-link>
182
187
  `;
183
188
  }
184
- if (e.type === "section") return o`
189
+ if (e.type === "section") return s`
185
190
  <u-sidebar-section
186
191
  ?compact=${this.state === "slim"}
187
192
  .mainTitle="${e.title}"
188
193
  .subTitle="${e.subTitle}"
189
194
  .styles="${e.styles}">
190
- ${p(e.items, (e, t) => t, (e) => this.renderItem(e))}
195
+ ${m(e.items, (e, t) => t, (e) => this.renderItem(e))}
191
196
  </u-sidebar-section>
192
197
  `;
193
198
  if (e.type === "group") {
194
199
  let t = e.items.some((e) => this.isMatchedLink(e.pattern || e.href));
195
- return o`
200
+ return s`
196
201
  <u-sidebar-group
197
202
  ?compact=${this.state === "slim"}
198
203
  ?selected=${t}
@@ -201,15 +206,15 @@ var g = class extends i {
201
206
  .lib="${e.lib}"
202
207
  .label="${e.label}"
203
208
  .styles="${e.styles}">
204
- ${p(e.items, (e, t) => t, (e) => this.renderItem(e))}
209
+ ${m(e.items, (e, t) => t, (e) => this.renderItem(e))}
205
210
  </u-sidebar-group>
206
211
  `;
207
212
  }
208
- return s;
213
+ return c;
209
214
  }
210
215
  renderLogo() {
211
216
  let e = this.config?.logo;
212
- if (!e || typeof e == "string") return o`
217
+ if (!e || typeof e == "string") return s`
213
218
  <u-icon class="logo"
214
219
  .name="${e}"
215
220
  @click=${this.handleBrandLogoClick()}
@@ -217,13 +222,13 @@ var g = class extends i {
217
222
  `;
218
223
  if (typeof e == "function") {
219
224
  let t = e(this.state);
220
- return o`
225
+ return s`
221
226
  <span class="logo" @click=${this.handleBrandLogoClick()}>
222
- ${typeof t == "string" ? f(t) : t}
227
+ ${typeof t == "string" ? p(t) : t}
223
228
  </span>
224
229
  `;
225
230
  }
226
- return o`
231
+ return s`
227
232
  <img class="logo"
228
233
  src="${e.src}"
229
234
  alt="${e.alt ?? ""}"
@@ -232,9 +237,9 @@ var g = class extends i {
232
237
  `;
233
238
  }
234
239
  };
235
- r([l({
240
+ r([u({
236
241
  type: String,
237
242
  reflect: !0
238
- }), n("design:type", Object)], g.prototype, "state", void 0), r([l({ type: Object }), n("design:type", Object)], g.prototype, "config", void 0), r([u("u-progress-bar"), n("design:type", m === void 0 ? Object : m)], g.prototype, "progressBarEl", void 0), r([d(), n("design:type", Object)], g.prototype, "context", void 0), g = r([c("u-sidebar-layout")], g);
243
+ }), n("design:type", Object)], v.prototype, "state", void 0), r([u({ type: Object }), n("design:type", Object)], v.prototype, "config", void 0), r([u({ type: String }), n("design:type", Object)], v.prototype, "locale", void 0), r([d("u-progress-bar"), n("design:type", g === void 0 ? Object : g)], v.prototype, "progressBarEl", void 0), r([f(), n("design:type", Object)], v.prototype, "context", void 0), v = r([l("u-sidebar-layout")], v);
239
244
  //#endregion
240
- export { g as SidebarLayout };
245
+ export { v 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.18.0",
4
+ "version": "0.18.3",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -144,6 +144,7 @@ Action button — triggers a callback instead of navigating.
144
144
  ```typescript
145
145
  interface SidebarButtonConfig {
146
146
  type: 'button';
147
+ id?: string;
147
148
  icon?: string;
148
149
  lib?: string;
149
150
  label: string | DirectiveResult;
@@ -158,6 +159,12 @@ Example:
158
159
  { type: 'button', icon: 'logout', label: 'Sign Out', onClick: () => auth.signOut() }
159
160
  ```
160
161
 
162
+ `id` is passed straight through to the rendered `<u-sidebar-button>` host. **It does not enable
163
+ anchoring a `u-popover` you place outside the layout** — the button lives inside
164
+ `<u-sidebar-layout>`'s own shadow root, and `querySelector`/`for="#id"` never crosses a shadow
165
+ boundary. If you need a popover anchored to a sidebar item, use `type: 'html'` and assemble both
166
+ inside the same template — see [popup-style submenus](#popup-style-submenus-u-popover) below.
167
+
161
168
  ---
162
169
 
163
170
  ### `SidebarHtmlConfig` — `type: 'html'`
@@ -190,6 +197,38 @@ Example:
190
197
 
191
198
  ---
192
199
 
200
+ ### Popup-style submenus (`u-popover`)
201
+
202
+ For a submenu that flies out from a sidebar item (rather than expanding in place like
203
+ `SidebarGroupConfig`), assemble a `u-popover` and its trigger together inside a single
204
+ `type: 'html'` item — both then live in the sidebar layout's own shadow root, which is required
205
+ for `for="#id"` anchoring to resolve (see the `id` note above).
206
+
207
+ ```typescript
208
+ {
209
+ type: 'html',
210
+ render: (state) => html`
211
+ <u-sidebar-button id="more-trigger" icon="three-dots" label="More"></u-sidebar-button>
212
+ <u-popover for="#more-trigger" placement=${state.startsWith('mobile') ? 'bottom-start' : 'right-start'}>
213
+ <u-menu>
214
+ <u-menu-item @click=${doA}>Action A</u-menu-item>
215
+ <u-menu-item @click=${doB}>Action B</u-menu-item>
216
+ </u-menu>
217
+ </u-popover>
218
+ `,
219
+ }
220
+ ```
221
+
222
+ ⚠**Pick `placement` based on `state`, not a fixed value.** On `mobile`/`mobile-open`, the sidebar
223
+ itself widens to occupy nearly the full screen — a sideways placement (`right-start`, the natural
224
+ choice for a desktop flyout) then has no room on either side, and `flip()` correctly declines to
225
+ flip when the opposite side has none either. The popover renders off-screen and is invisible. A
226
+ vertical placement (`bottom-start`) has room regardless of sidebar width and works at every state.
227
+ Confirmed empirically in `tests/browser/sidebar-popover-submenu.browser.test.ts` — this is not a
228
+ `strategy="absolute"` vs `"fixed"` distinction, switching strategy does not change the outcome.
229
+
230
+ ---
231
+
193
232
  ## Sidebar parts
194
233
 
195
234
  Parts available for `styles` overrides on the root layout: