@iyulab/modern-app 0.57.0 → 0.58.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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.58.0] - 2026-10-23
4
+
5
+ ### Added
6
+
7
+ - **A screen without the shell.** A route with `metadata: { shell: 'hidden' }` takes the whole viewport while it is
8
+ current — no sidebar, mobile header or page padding — for a wall display or a kiosk. The next ordinary route gets
9
+ the chrome back. The layout reflects it as `immersive`.
10
+ - **A screen with its own theme.** `metadata: { theme: 'dark' | 'light' }` applies that theme while the route is
11
+ current without changing or storing the user's choice, and the next route returns to it. Needs `@iyulab/components`
12
+ with `Theme.override` (the next minor).
13
+
14
+ ### Fixed
15
+
16
+ - **A sidebar link's count meets the text contrast floor under every palette.** It was dimmed with `opacity: 0.8`,
17
+ which fell below 4.5:1 where the resting link colour is already a mid grey (about 4.2:1 for `#5B5E63` on white).
18
+ The count now paints in the label's colour; it stays one type step smaller.
19
+
3
20
  ## [0.57.0] - 2026-10-23
4
21
 
5
22
  ### Added
@@ -106,13 +106,13 @@ var t = e`
106
106
 
107
107
 
108
108
 
109
+
109
110
  .count {
110
111
  flex: none;
111
112
  margin-inline-start: auto;
112
113
  font-size: var(--u-text-overline-size, 11px);
113
114
  font-weight: var(--u-text-caption-weight, 400);
114
115
  font-variant-numeric: tabular-nums;
115
- opacity: 0.8;
116
116
  }
117
117
  .count[compact] {
118
118
  position: absolute;
@@ -8,6 +8,14 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
8
8
  state: SidebarState;
9
9
  /** 사이드바 레이아웃 설정 */
10
10
  config?: SidebarLayoutConfig;
11
+ /**
12
+ * The current screen takes the whole viewport: no sidebar, no mobile header, no page padding — a wall
13
+ * display, a kiosk, a full-screen viewer. Set by the route (`metadata: { shell: 'hidden' }`) and cleared when the
14
+ * next screen does not ask for it; the screen itself gives the way back.
15
+ */
16
+ immersive: boolean;
17
+ /** 지금 화면이 건 일시 테마가 있는가 — 다음 화면이 아무것도 묻지 않으면 걷어 사용자 선호로 돌려준다. */
18
+ private routeTheme;
11
19
  /**
12
20
  * Live counts for link items, keyed by the item's `href` — overrides the static `count` of the
13
21
  * item config. Set through `app.setNavCount(href, value)`; assigning a new object re-renders.
@@ -12,28 +12,29 @@ import "../components/SidebarGroup.js";
12
12
  import "../components/SidebarButton.js";
13
13
  import { styles as c } from "./SidebarLayout.styles.js";
14
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";
15
+ import { Theme as u } from "@iyulab/components/dist/utilities/Theme.js";
16
+ import { html as d, nothing as f } from "lit";
17
+ import { customElement as p, property as m, query as h, state as g } from "lit/decorators.js";
17
18
  import "@iyulab/components/dist/components/icon/UIcon.js";
18
19
  import "@iyulab/components/dist/components/button/UButton.js";
19
- import { unsafeHTML as g } from "lit/directives/unsafe-html.js";
20
- import { repeat as _ } from "lit/directives/repeat.js";
20
+ import { unsafeHTML as _ } from "lit/directives/unsafe-html.js";
21
+ import { repeat as v } from "lit/directives/repeat.js";
21
22
  import "@iyulab/components/dist/components/popover/UPopover.js";
22
23
  import "@iyulab/components/dist/components/menu/UMenu.js";
23
24
  import "@iyulab/components/dist/components/menu-item/UMenuItem.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";
25
+ import { createDevWarner as y } from "@iyulab/components/dist/utilities/devWarning.js";
26
+ import { isFocusCandidate as b, querySelectorDeep as x } from "@iyulab/components/dist/utilities/elements.js";
27
+ import { OverlayManager as S } from "@iyulab/components/dist/utilities/OverlayManager.js";
27
28
  import "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
28
29
  // src/layouts/SidebarLayout.ts
29
- var S, C = v("@iyulab/modern-app"), w = 0;
30
- function T(e) {
30
+ var C, w = y("@iyulab/modern-app"), T = 0;
31
+ function E(e) {
31
32
  let t = e.tagName;
32
33
  if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
33
34
  let n = e.getAttribute("role");
34
35
  return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
35
36
  }
36
- var E = /* @__PURE__ */ new Set([
37
+ var D = /* @__PURE__ */ new Set([
37
38
  "button",
38
39
  "link",
39
40
  "checkbox",
@@ -46,27 +47,27 @@ var E = /* @__PURE__ */ new Set([
46
47
  "tab",
47
48
  "treeitem"
48
49
  ]);
49
- function D(e) {
50
+ function O(e) {
50
51
  let t = e.tagName;
51
- return t === "BUTTON" || t === "SUMMARY" || t === "A" && e.hasAttribute("href") ? !0 : E.has(e.getAttribute("role") ?? "");
52
+ return t === "BUTTON" || t === "SUMMARY" || t === "A" && e.hasAttribute("href") ? !0 : D.has(e.getAttribute("role") ?? "");
52
53
  }
53
- function O(e) {
54
+ function k(e) {
54
55
  let t = getComputedStyle(e);
55
56
  return (t.overflowY === "auto" || t.overflowY === "scroll") && e.scrollHeight > e.clientHeight;
56
57
  }
57
- function k(e, t) {
58
+ function A(e, t) {
58
59
  for (let n of e) if (n instanceof HTMLElement) {
59
- if (O(n)) return n === t;
60
+ if (k(n)) return n === t;
60
61
  if (n === t) break;
61
62
  }
62
63
  return !0;
63
64
  }
64
- function A() {
65
+ function j() {
65
66
  let e = document.activeElement;
66
67
  for (; e?.shadowRoot?.activeElement;) e = e.shadowRoot.activeElement;
67
68
  return e;
68
69
  }
69
- function j(e, t) {
70
+ function M(e, t) {
70
71
  let n = t;
71
72
  for (; n;) {
72
73
  if (n === e) return !0;
@@ -74,12 +75,12 @@ function j(e, t) {
74
75
  }
75
76
  return !1;
76
77
  }
77
- var M = class extends n {
78
+ var N = class extends n {
78
79
  static {
79
- S = this;
80
+ C = this;
80
81
  }
81
82
  constructor(...e) {
82
- super(...e), this.state = "default", this.counts = {}, this.locale = "", this.hasOverlay = !1, this.openMenu = null, this.menuIds = /* @__PURE__ */ new WeakMap(), this.hasNotice = !1, this.routeLoading = !1, this.mainTabStop = !1, this.mobileHeaderBlockSize = 0, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.currentPath = null, this.unsizedWarnKey = `unsized:${++w}`, 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 = () => {
83
+ super(...e), this.state = "default", this.immersive = !1, this.routeTheme = !1, this.counts = {}, this.locale = "", this.hasOverlay = !1, this.openMenu = null, this.menuIds = /* @__PURE__ */ new WeakMap(), this.hasNotice = !1, this.routeLoading = !1, this.mainTabStop = !1, this.mobileHeaderBlockSize = 0, 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 = () => {
83
84
  let e = this.screenSize ?? a.screen ?? "large";
84
85
  e === "large" ? this.state = this.state === "default" ? "slim" : "default" : e === "medium" ? this.state = this.state === "slim" ? "modal" : "slim" : e === "small" ? this.state = this.state === "mobile" ? "mobile-open" : "mobile" : console.warn("[@iyulab/modern-app] unknown screen size:", e);
85
86
  }, this.handleBackdropClick = () => {
@@ -89,17 +90,19 @@ var M = class extends n {
89
90
  }, this.handleRouteProgress = (e) => {
90
91
  this.progressBarEl.value = e.progress;
91
92
  }, this.handleRouteDone = (e) => {
92
- this.progressBarEl.value = 100, setTimeout(() => {
93
+ this.immersive = e.context.metadata?.shell === "hidden";
94
+ let t = e.context.metadata?.theme, n = t === "dark" || t === "light" ? t : null;
95
+ (n || this.routeTheme) && u.override(n), this.routeTheme = n !== null, this.progressBarEl.value = 100, setTimeout(() => {
93
96
  this.routeLoading = !1;
94
97
  }, 300);
95
- let t = this.shadowRoot?.querySelector(".main");
96
- t && (this.config?.scrollBehavior?.(e.context, t), this.focusRoute(t), requestAnimationFrame(this.syncMainTabStop));
98
+ let r = this.shadowRoot?.querySelector(".main");
99
+ r && (this.config?.scrollBehavior?.(e.context, r), this.focusRoute(r), requestAnimationFrame(this.syncMainTabStop));
97
100
  }, this.observedContent = [], this.handleMainSlotChange = () => {
98
101
  this.observeContentSize(), this.syncMainTabStop();
99
102
  }, this.syncMainTabStop = () => {
100
103
  let e = this.shadowRoot?.querySelector(".main");
101
104
  if (!e) return;
102
- let t = e.scrollHeight > e.clientHeight + 1 || e.scrollWidth > e.clientWidth + 1, n = t && !!b([...this.noticeContent(), ...this.routeContent()], "*", (e) => y(e) && e.tabIndex >= 0);
105
+ let t = e.scrollHeight > e.clientHeight + 1 || e.scrollWidth > e.clientWidth + 1, n = t && !!x([...this.noticeContent(), ...this.routeContent()], "*", (e) => b(e) && e.tabIndex >= 0);
103
106
  this.mainTabStop = t && !n;
104
107
  }, this.handleSkipLinkClick = (e) => {
105
108
  e.preventDefault();
@@ -112,7 +115,7 @@ var M = class extends n {
112
115
  }, this.handleOverlaySlotChange = (e) => {
113
116
  let t = r(e.target);
114
117
  if (t && !this.hasOverlay) {
115
- let e = A();
118
+ let e = j();
116
119
  this.overlayTrigger = e && e !== document.body ? e : null;
117
120
  }
118
121
  this.hasOverlay = t;
@@ -126,9 +129,9 @@ var M = class extends n {
126
129
  }, this._handleMainKeydown = (e) => {
127
130
  if (e.defaultPrevented) return;
128
131
  let t = e.composedPath()[0];
129
- if (t instanceof HTMLElement && T(t) || e.key === " " && t instanceof HTMLElement && D(t)) return;
132
+ if (t instanceof HTMLElement && E(t) || e.key === " " && t instanceof HTMLElement && O(t)) return;
130
133
  let n = this.shadowRoot?.querySelector(".main");
131
- if (!n || !k(e.composedPath(), n)) return;
134
+ if (!n || !A(e.composedPath(), n)) return;
132
135
  let r = n.clientHeight;
133
136
  switch (e.key) {
134
137
  case " ":
@@ -171,13 +174,13 @@ var M = class extends n {
171
174
  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);
172
175
  }
173
176
  disconnectedCallback() {
174
- 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), this.mainSizeObserver?.disconnect(), this.mainSizeObserver = void 0, this.mobileHeaderObserver?.disconnect(), this.mobileHeaderObserver = void 0, super.disconnectedCallback();
177
+ this.routeTheme &&= (u.override(null), !1), 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), S.closeLayer(this), this.mainSizeObserver?.disconnect(), this.mainSizeObserver = void 0, this.mobileHeaderObserver?.disconnect(), this.mobileHeaderObserver = void 0, super.disconnectedCallback();
175
178
  }
176
179
  willUpdate(e) {
177
180
  super.willUpdate(e), e.has("config") && (this.styles = this.config?.styles);
178
181
  }
179
182
  updated(e) {
180
- super.updated(e), this.observeMainSize(), this.observeMobileHeader(), 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()));
183
+ super.updated(e), this.observeMainSize(), this.observeMobileHeader(), e.has("hasOverlay") && (this.internals?.states?.[this.hasOverlay ? "add" : "delete"]("overlay"), this.hasOverlay ? (S.openLayer(this, this.handleOverlayEscape), this.focusOverlay()) : e.get("hasOverlay") === !0 && (S.closeLayer(this), this.restoreOverlayFocus()));
181
184
  }
182
185
  firstUpdated(e) {
183
186
  super.firstUpdated(e), this.warnIfUnsized();
@@ -186,11 +189,11 @@ var M = class extends n {
186
189
  process.env.NODE_ENV !== "production" && requestAnimationFrame(() => {
187
190
  if (!this.isConnected) return;
188
191
  let e = this.getBoundingClientRect().height;
189
- 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).`);
192
+ 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).`);
190
193
  });
191
194
  }
192
195
  render() {
193
- return this.config ? u`
196
+ return this.config ? d`
194
197
  <!-- 반복 영역 건너뛰기(WCAG 2.4.1) — 셸의 첫 Tab. 포커스될 때만 보인다. -->
195
198
  <a class="skip-link" part="skip-link" href="#" @click=${this.handleSkipLinkClick}>
196
199
  ${i(this.locale || void 0).skipToContent}
@@ -220,7 +223,7 @@ var M = class extends n {
220
223
  ${this.renderLogo()}
221
224
  <span class="title" ?hidden=${this.state === "slim"}>
222
225
  ${this.config.title}
223
- ${this.config.subtitle ? u`<span class="subtitle" part="subtitle">${this.config.subtitle}</span>` : d}
226
+ ${this.config.subtitle ? d`<span class="subtitle" part="subtitle">${this.config.subtitle}</span>` : f}
224
227
  </span>
225
228
  <u-button class="toggler" appearance="plain"
226
229
  aria-label=${i(this.locale || void 0).toggleSidebar}
@@ -234,13 +237,13 @@ var M = class extends n {
234
237
 
235
238
  <!-- Sidebar Navigation Menu -->
236
239
  <nav class="sidebar-main" part="sidebar-main" scrollable
237
- aria-label=${this.config.mainAriaLabel ?? d}>
238
- ${_(o(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
240
+ aria-label=${this.config.mainAriaLabel ?? f}>
241
+ ${v(o(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
239
242
  </nav>
240
243
 
241
244
  <!-- Sidebar Footer -->
242
245
  <div class="sidebar-footer" part="sidebar-footer">
243
- ${_(o(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
246
+ ${v(o(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
244
247
  </div>
245
248
  </aside>
246
249
 
@@ -248,13 +251,13 @@ var M = class extends n {
248
251
  <div class="main-region">
249
252
  <main class="main" part="main" scrollable tabindex=${this.mainTabStop ? "0" : "-1"} @keydown=${this._handleMainKeydown}>
250
253
  <u-progress-bar part="progress" class=${this.routeLoading ? "loading" : ""}
251
- aria-hidden=${this.routeLoading ? d : "true"}
254
+ aria-hidden=${this.routeLoading ? f : "true"}
252
255
  aria-label=${i(this.locale || void 0).pageLoading}></u-progress-bar>
253
256
 
254
257
  <div class="main-content" part="main-content" ?inert=${this.hasOverlay}>
255
258
  <div class="notices ${this.hasNotice ? "" : "empty"}" part="notices"
256
- role=${this.config.noticesAriaLabel ? "region" : d}
257
- aria-label=${this.config.noticesAriaLabel ?? d}>
259
+ role=${this.config.noticesAriaLabel ? "region" : f}
260
+ aria-label=${this.config.noticesAriaLabel ?? f}>
258
261
  <slot name="notice" @slotchange=${this.handleNoticeSlotChange}></slot>
259
262
  </div>
260
263
  <slot @slotchange=${this.handleMainSlotChange}></slot>
@@ -275,13 +278,13 @@ var M = class extends n {
275
278
  <div class="backdrop" ?hidden="${this.state !== "modal"}"
276
279
  @click="${this.handleBackdropClick}"
277
280
  ></div>
278
- ` : d;
281
+ ` : f;
279
282
  }
280
283
  renderMenu(e) {
281
284
  let t = this.menuIds.get(e);
282
- t || (t = `u-sidebar-menu-${++S.menuSeq}`, this.menuIds.set(e, t));
285
+ t || (t = `u-sidebar-menu-${++C.menuSeq}`, this.menuIds.set(e, t));
283
286
  let n = t, r = this.state === "mobile" || this.state === "mobile-open" ? "bottom-start" : "right-start";
284
- return u`
287
+ return d`
285
288
  <u-sidebar-button
286
289
  id=${n}
287
290
  haspopup="menu"
@@ -300,9 +303,9 @@ var M = class extends n {
300
303
  @hide=${(e) => this.handleMenuHide(e, n)}
301
304
  @pick=${(t) => this.handleMenuPick(t, e)}>
302
305
  <u-menu>
303
- ${_(e.items, (e, t) => t, (e, t) => u`
306
+ ${v(e.items, (e, t) => t, (e, t) => d`
304
307
  <u-menu-item value=${String(t)} ?disabled=${e.disabled === !0}>
305
- ${e.icon ? u`<u-icon slot="prefix" .lib=${e.lib} .name=${e.icon}></u-icon>` : d}
308
+ ${e.icon ? d`<u-icon slot="prefix" .lib=${e.lib} .name=${e.icon}></u-icon>` : f}
306
309
  ${e.label}
307
310
  </u-menu-item>
308
311
  `)}
@@ -316,16 +319,16 @@ var M = class extends n {
316
319
  }
317
320
  handleMenuHide(e, t) {
318
321
  this.openMenu === t && (this.openMenu = null);
319
- let n = e.currentTarget, r = A();
320
- r && j(n, r) && this.shadowRoot?.querySelector(`#${t}`)?.focus();
322
+ let n = e.currentTarget, r = j();
323
+ r && M(n, r) && this.shadowRoot?.querySelector(`#${t}`)?.focus();
321
324
  }
322
325
  renderItem(e) {
323
- if (!e) return d;
326
+ if (!e) return f;
324
327
  if (e.type === "html") {
325
328
  let t = e.render(this.state);
326
- return typeof t == "string" ? g(t) : u`${t}`;
329
+ return typeof t == "string" ? _(t) : d`${t}`;
327
330
  }
328
- if (e.type === "button") return u`
331
+ if (e.type === "button") return d`
329
332
  <u-sidebar-button
330
333
  ?compact=${this.state === "slim"}
331
334
  .icon="${e.icon}"
@@ -339,7 +342,7 @@ var M = class extends n {
339
342
  if (e.type === "menu") return this.renderMenu(e);
340
343
  if (e.type === "link") {
341
344
  let t = this.isMatchedLink(e.pattern || e.href);
342
- return u`
345
+ return d`
343
346
  <u-sidebar-link
344
347
  ?compact=${this.state === "slim"}
345
348
  ?selected=${t}
@@ -355,18 +358,18 @@ var M = class extends n {
355
358
  ></u-sidebar-link>
356
359
  `;
357
360
  }
358
- if (e.type === "section") return u`
361
+ if (e.type === "section") return d`
359
362
  <u-sidebar-section
360
363
  ?compact=${this.state === "slim"}
361
364
  .mainTitle="${e.title}"
362
365
  .subTitle="${e.subTitle}"
363
366
  .styles="${e.styles}">
364
- ${_(e.items, (e, t) => t, (e) => this.renderItem(e))}
367
+ ${v(e.items, (e, t) => t, (e) => this.renderItem(e))}
365
368
  </u-sidebar-section>
366
369
  `;
367
370
  if (e.type === "group") {
368
371
  let t = e.items.some((e) => this.isMatchedLink(e.pattern || e.href));
369
- return u`
372
+ return d`
370
373
  <u-sidebar-group
371
374
  ?compact=${this.state === "slim"}
372
375
  ?selected=${t}
@@ -375,11 +378,11 @@ var M = class extends n {
375
378
  .lib="${e.lib}"
376
379
  .label="${e.label}"
377
380
  .styles="${e.styles}">
378
- ${_(e.items, (e, t) => t, (e) => this.renderItem(e))}
381
+ ${v(e.items, (e, t) => t, (e) => this.renderItem(e))}
379
382
  </u-sidebar-group>
380
383
  `;
381
384
  }
382
- return d;
385
+ return f;
383
386
  }
384
387
  handleButtonItemClick(e, t) {
385
388
  let n = e.onClick?.(t);
@@ -395,19 +398,19 @@ var M = class extends n {
395
398
  };
396
399
  }
397
400
  icon(e) {
398
- return this.config?.icons?.[e] ?? S.DEFAULT_ICONS[e];
401
+ return this.config?.icons?.[e] ?? C.DEFAULT_ICONS[e];
399
402
  }
400
403
  renderLogo() {
401
404
  let e = this.config?.logo, t = this.config?.title ?? "Home";
402
- if (!e || typeof e == "string") return e ? u`
405
+ if (!e || typeof e == "string") return e ? d`
403
406
  <u-link class="logo-link" aria-label=${t}>
404
407
  <u-icon class="logo"
405
408
  .name="${e}"
406
409
  .fallback=${s}
407
410
  ></u-icon>
408
411
  </u-link>
409
- ` : d;
410
- if (typeof e == "object" && "icon" in e) return u`
412
+ ` : f;
413
+ if (typeof e == "object" && "icon" in e) return d`
411
414
  <u-link class="logo-link" aria-label=${t}>
412
415
  <u-icon class="logo"
413
416
  .lib=${e.lib}
@@ -418,17 +421,17 @@ var M = class extends n {
418
421
  `;
419
422
  if (typeof e == "function") {
420
423
  let n = e(this.state);
421
- return u`
424
+ return d`
422
425
  <u-link class="logo-link" aria-label=${t}>
423
426
  <span class="logo">
424
427
  <!-- html-sink: documented HTML contract — a logo function returns markup the developer writes -->
425
- ${typeof n == "string" ? g(n) : n}
428
+ ${typeof n == "string" ? _(n) : n}
426
429
  </span>
427
430
  </u-link>
428
431
  `;
429
432
  }
430
- return u`
431
- <u-link class="logo-link" .href=${e.href} aria-label=${e.alt ? d : t}>
433
+ return d`
434
+ <u-link class="logo-link" .href=${e.href} aria-label=${e.alt ? f : t}>
432
435
  <img class="logo"
433
436
  src="${e.src}"
434
437
  alt="${e.alt ?? ""}"
@@ -466,14 +469,14 @@ var M = class extends n {
466
469
  async focusRoute(e) {
467
470
  let t = this.routeContent();
468
471
  if (await Promise.all(t.map((e) => e.updateComplete)), this.hasOverlay || !e.isConnected) return;
469
- let n = A();
470
- n && t.some((e) => j(e, n)) || this.focusContentStart(t, e);
472
+ let n = j();
473
+ n && t.some((e) => M(e, n)) || this.focusContentStart(t, e);
471
474
  }
472
475
  focusContentStart(e, t) {
473
- let n = b(e, "[autofocus]", y);
476
+ let n = x(e, "[autofocus]", b);
474
477
  n?.focus();
475
- let r = A();
476
- n && r && j(n, r) || t.focus({ preventScroll: !0 });
478
+ let r = j();
479
+ n && r && M(n, r) || t.focus({ preventScroll: !0 });
477
480
  }
478
481
  overlayPanels() {
479
482
  return (this.shadowRoot?.querySelector("slot[name=\"overlay\"]"))?.assignedElements({ flatten: !0 }) ?? [];
@@ -481,18 +484,21 @@ var M = class extends n {
481
484
  async focusOverlay() {
482
485
  let e = this.overlayPanels();
483
486
  if (await Promise.all(e.map((e) => e.updateComplete)), !this.hasOverlay) return;
484
- let t = A();
485
- t && e.some((e) => j(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();
487
+ let t = j();
488
+ t && e.some((e) => M(e, t)) || (x(e, "[autofocus]", b) ?? x(e, "input, select, textarea, u-input, u-textarea, u-select, u-checkbox, u-radio, u-switch, u-slider", b) ?? this.shadowRoot?.querySelector(".overlay-close"))?.focus();
486
489
  }
487
490
  restoreOverlayFocus() {
488
491
  let e = this.overlayTrigger;
489
492
  this.overlayTrigger = null;
490
- let t = A();
493
+ let t = j();
491
494
  t && t !== document.body || (e?.isConnected ? e.focus() : this.mainElement?.focus({ preventScroll: !0 }));
492
495
  }
493
496
  };
494
- t([p({
497
+ t([m({
495
498
  type: String,
496
499
  reflect: !0
497
- }), e("design:type", Object)], M.prototype, "state", void 0), t([p({ type: Object }), e("design:type", Object)], M.prototype, "config", void 0), t([p({ type: Object }), e("design:type", typeof Record > "u" ? Object : Record)], M.prototype, "counts", void 0), t([p({ type: String }), e("design:type", Object)], M.prototype, "locale", void 0), t([m("u-progress-bar"), e("design:type", Object)], M.prototype, "progressBarEl", void 0), t([h(), e("design:type", Object)], M.prototype, "hasOverlay", void 0), t([h(), e("design:type", Object)], M.prototype, "openMenu", void 0), t([h(), e("design:type", Object)], M.prototype, "hasNotice", void 0), t([h(), e("design:type", Object)], M.prototype, "routeLoading", void 0), t([h(), e("design:type", Object)], M.prototype, "mainTabStop", void 0), t([h(), e("design:type", Object)], M.prototype, "mobileHeaderBlockSize", void 0), t([h(), e("design:type", Object)], M.prototype, "currentPath", void 0), M = S = t([f("u-sidebar-layout")], M);
498
- export { M as SidebarLayout };
500
+ }), e("design:type", Object)], N.prototype, "state", void 0), t([m({ type: Object }), e("design:type", Object)], N.prototype, "config", void 0), t([m({
501
+ type: Boolean,
502
+ reflect: !0
503
+ }), e("design:type", Object)], N.prototype, "immersive", void 0), t([m({ type: Object }), e("design:type", typeof Record > "u" ? Object : Record)], N.prototype, "counts", void 0), t([m({ type: String }), e("design:type", Object)], N.prototype, "locale", void 0), t([h("u-progress-bar"), e("design:type", Object)], N.prototype, "progressBarEl", void 0), t([g(), e("design:type", Object)], N.prototype, "hasOverlay", void 0), t([g(), e("design:type", Object)], N.prototype, "openMenu", void 0), t([g(), e("design:type", Object)], N.prototype, "hasNotice", void 0), t([g(), e("design:type", Object)], N.prototype, "routeLoading", void 0), t([g(), e("design:type", Object)], N.prototype, "mainTabStop", void 0), t([g(), e("design:type", Object)], N.prototype, "mobileHeaderBlockSize", void 0), t([g(), e("design:type", Object)], N.prototype, "currentPath", void 0), N = C = t([p("u-sidebar-layout")], N);
504
+ export { N as SidebarLayout };
@@ -19,6 +19,16 @@ var t = e`
19
19
  :host([state="mobile-open"]) {
20
20
  flex-direction: column;
21
21
  }
22
+
23
+ :host([immersive]) .sidebar,
24
+ :host([immersive]) .mobile-header,
25
+ :host([immersive]) .backdrop,
26
+ :host([immersive]) .skip-link {
27
+ display: none;
28
+ }
29
+ :host([immersive]) .main {
30
+ padding: 0;
31
+ }
22
32
 
23
33
  .logo {
24
34
  color: var(--u-txt-color, #212121);
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.57.0",
4
+ "version": "0.58.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -64,7 +64,7 @@
64
64
  "lit": "^3.3.3"
65
65
  },
66
66
  "peerDependencies": {
67
- "@iyulab/components": ">=2.8.0",
67
+ "@iyulab/components": ">=2.32.0",
68
68
  "@iyulab/router": "^0.24.0",
69
69
  "@lit/react": "^1.0.8",
70
70
  "react": "^18.0.0 || ^19.0.0"
@@ -78,7 +78,7 @@
78
78
  }
79
79
  },
80
80
  "devDependencies": {
81
- "@iyulab/components": "^2.20.3",
81
+ "@iyulab/components": "^2.32.0",
82
82
  "@iyulab/router": "^0.24.0",
83
83
  "@lit/react": "^1.0.8",
84
84
  "@types/node": "^26.1.1",
@@ -448,6 +448,30 @@ on a component such as `UInput`) — `@iyulab/router` 0.16.1 and later report `r
448
448
  React has committed the screen. While the overlay is open the shell does none of this: the route
449
449
  underneath is inert and focus stays in the panel.
450
450
 
451
+ ## A screen without the shell
452
+
453
+ A screen that owns the whole viewport — a wall display on the production floor, a kiosk, a full-screen viewer —
454
+ asks for it in its route. While that screen is current the shell draws no sidebar, no mobile header and no page
455
+ padding; the next screen that does not ask gets them back.
456
+
457
+ ```ts
458
+ routes: [
459
+ { path: 'board', metadata: { shell: 'hidden' }, render: () => html`<floor-board></floor-board>` },
460
+ ]
461
+ ```
462
+
463
+ The screen gives the way back itself (a link to the menu) — the shell has nothing on screen to return with. The
464
+ state is the reflected `immersive` attribute on `u-sidebar-layout` (`0.58.0~`).
465
+
466
+ A screen can also own its background: `metadata: { theme: 'dark' }` (or `'light'`) applies that theme while the screen
467
+ is current, without changing or storing the user's choice (`Theme.override` from `@iyulab/components`), and the next
468
+ screen that does not ask returns to it. A floor board stays dark on the TV and in a browser where the user picked
469
+ light.
470
+
471
+ ```ts
472
+ { path: 'board', metadata: { shell: 'hidden', theme: 'dark' }, render: () => html`<floor-board></floor-board>` }
473
+ ```
474
+
451
475
  ## Printing
452
476
 
453
477
  On print media the shell drops its app chrome and lets the content flow across pages: the