@iyulab/modern-app 0.3.6 → 0.4.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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0] - 2026-07-02
4
+
5
+ ### Added
6
+ - `SidebarLayoutConfig.logo` now accepts an image (`{ src, alt?, href? }`) or a custom render function (`(state) => TemplateResult | HTMLElement | string`), in addition to the existing icon-name string. Logo click navigates to `/` by default, or to `href` when given on the image variant.
7
+
8
+ ### Fixed
9
+ - `README.md`/`docs/`/`skills/` previously documented `logo` as accepting an image URL string (e.g. `'/assets/logo.svg'`), but the implementation only ever rendered it as a `u-icon` name — an image URL string silently failed to render an image. Docs now use the image variant (`{ src, alt }`) for that case.
10
+
11
+ ## [0.3.7] - 2026-06-09
12
+
13
+ ### Fixed
14
+ - `SidebarLayout`: main-content keyboard scroll shortcuts (Space/arrows/Home/End/PageUp/PageDown) were swallowing input inside editable elements (input/textarea/select/contenteditable/ARIA textbox) — now skipped via `composedPath()`-based detection
15
+
16
+ ## [0.3.6] - 2026-05-21
17
+
18
+ ### Added
19
+ - `SidebarLayout`: keyboard scroll support for `.main` content area (`tabindex="-1"` + `@keydown` handler) — WCAG 2.1 SC 2.1.1 accessibility fix, focus moves to `.main` on route change
20
+
3
21
  ## [0.3.5] - 2026-04-07
4
22
 
5
23
  ### Changed
package/README.md CHANGED
@@ -26,7 +26,7 @@ await app.load({
26
26
  basepath: '/',
27
27
  layout: {
28
28
  type: 'sidebar',
29
- logo: '/assets/logo.svg',
29
+ logo: { src: '/assets/logo.svg', alt: 'My App' },
30
30
  title: 'My App',
31
31
  main: [
32
32
  { type: 'link', icon: 'home', label: 'Home', href: '/' },
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.130.0/helpers/decorate.js
1
+ //#region \0@oxc-project+runtime@0.138.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);
@@ -6,4 +6,4 @@ function e(e, t, n, r) {
6
6
  return i > 3 && a && Object.defineProperty(t, n, a), a;
7
7
  }
8
8
  //#endregion
9
- export { e as __decorate };
9
+ export { e as default };
@@ -1,6 +1,6 @@
1
- //#region \0@oxc-project+runtime@0.130.0/helpers/decorateMetadata.js
1
+ //#region \0@oxc-project+runtime@0.138.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
  }
5
5
  //#endregion
6
- export { e as __decorateMetadata };
6
+ export { e as default };
@@ -1,5 +1,5 @@
1
- import { __decorateMetadata as e } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js";
2
- import { __decorate as t } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./SidebarButton.styles.js";
5
5
  import { html as i } from "lit";
@@ -31,3 +31,4 @@ t([o({
31
31
  reflect: !0
32
32
  }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", String)], s.prototype, "icon", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "label", void 0), s = t([a("u-sidebar-button")], s);
33
33
  //#endregion
34
+ export { s as SidebarButton };
@@ -1,5 +1,5 @@
1
- import { __decorateMetadata as e } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js";
2
- import { __decorate as t } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { SidebarLink as r } from "./SidebarLink.js";
5
5
  import { styles as i } from "./SidebarGroup.styles.js";
@@ -53,3 +53,4 @@ t([s({
53
53
  reflect: !0
54
54
  }), e("design:type", Boolean)], c.prototype, "collapsed", void 0), t([s({ type: String }), e("design:type", String)], c.prototype, "icon", void 0), t([s({ type: String }), e("design:type", Object)], c.prototype, "label", void 0), c = t([o("u-sidebar-group")], c);
55
55
  //#endregion
56
+ export { c as SidebarGroup };
@@ -1,5 +1,5 @@
1
- import { __decorateMetadata as e } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js";
2
- import { __decorate as t } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorate.js";
3
3
  import { StyledElement as n } from "../internals/StyledElement.js";
4
4
  import { styles as r } from "./SidebarLink.styles.js";
5
5
  import { html as i } from "lit";
@@ -1,5 +1,5 @@
1
- import { __decorateMetadata as e } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js";
2
- import { __decorate as t } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.138.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";
@@ -31,3 +31,4 @@ var s = class extends n {
31
31
  };
32
32
  t([o({ type: Boolean }), e("design:type", Object)], s.prototype, "compact", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "mainTitle", void 0), t([o({ type: String }), e("design:type", Object)], s.prototype, "subTitle", void 0), s = t([a("u-sidebar-section")], s);
33
33
  //#endregion
34
+ export { s as SidebarSection };
@@ -1,5 +1,5 @@
1
- import { __decorateMetadata as e } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js";
2
- import { __decorate as t } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js";
1
+ import e from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorateMetadata.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.138.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
@@ -3,21 +3,6 @@ import { UProgressBar } from '@iyulab/components/dist/components/progress-bar/UP
3
3
  import { RouteContext } from '@iyulab/router';
4
4
  import { StyledElement } from '../internals/StyledElement.js';
5
5
  import { SidebarLayoutConfig, SidebarState, SidebarParts } from './SidebarLayout.types';
6
- /**
7
- * 반응형 사이드바 레이아웃 컴포넌트
8
- *
9
- * 화면 크기에 따른 사이드바 모드 전환:
10
- * - large: default / slim
11
- * - medium: slim / modal
12
- * - small: mobile / mobile-open
13
- *
14
- * 사이드바 모드:
15
- * - default: 사이드바가 펼침 상태로 표시됨
16
- * - slim: 아이콘만 표시되는 슬림 모드
17
- * - modal: 오버레이로 사이드바가 펼침 상태로 표시되는 모달 모드, 백드롭 클릭 시 닫힘
18
- * - mobile: 모바일에서 헤더만 표시되는 상태, 사이드바는 숨겨짐
19
- * - mobile-open: 모바일에서 사이드바가 펼침 상태로 표시되는 모드
20
- */
21
6
  export declare class SidebarLayout extends StyledElement<SidebarParts> {
22
7
  static styles: import('lit').CSSResultGroup[];
23
8
  /** 사이드바 상태 */
@@ -35,8 +20,10 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
35
20
  private renderItem;
36
21
  /** 현재 경로와 패턴 매칭 여부 확인 */
37
22
  private isMatchedLink;
38
- /** 브랜드 로고 클릭 핸들러 */
23
+ /** 브랜드 로고 클릭 핸들러: `href` 지정 시 해당 경로로, 아니면 홈으로 이동 */
39
24
  private handleBrandLogoClick;
25
+ /** 로고 렌더링: 아이콘명(문자열, 기존 동작) | 이미지({src,alt,href}) | 커스텀 렌더 함수 */
26
+ private renderLogo;
40
27
  /** 사이드바 토글 핸들러 */
41
28
  private handleToggleButtonClick;
42
29
  /**
@@ -1,6 +1,6 @@
1
1
  import { app as e } from "../App.js";
2
- import { __decorateMetadata as t } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js";
3
- import { __decorate as n } from "../_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js";
2
+ import t from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorateMetadata.js";
3
+ import n from "../_virtual/_@oxc-project_runtime@0.138.0/helpers/esm/decorate.js";
4
4
  import { StyledElement as r } from "../internals/StyledElement.js";
5
5
  import "../components/SidebarSection.js";
6
6
  import "../components/SidebarLink.js";
@@ -15,10 +15,16 @@ import "@iyulab/components/dist/components/icon/UIcon.js";
15
15
  import "@iyulab/components/dist/components/button/UButton.js";
16
16
  import { UProgressBar as p } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
17
17
  //#region src/layouts/SidebarLayout.ts
18
- var m, h = class extends r {
18
+ function m(e) {
19
+ let t = e.tagName;
20
+ if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
21
+ let n = e.getAttribute("role");
22
+ return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
23
+ }
24
+ var h = class extends r {
19
25
  constructor(...t) {
20
- 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 = () => {
21
- e.navigate("");
26
+ 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) => () => {
27
+ e.navigate(t ?? "");
22
28
  }, this.handleToggleButtonClick = () => {
23
29
  let t = e.screen ?? "large";
24
30
  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);
@@ -37,28 +43,30 @@ var m, h = class extends r {
37
43
  this.progressBarEl.removeAttribute("visible"), this.progressBarEl.removeAttribute("error");
38
44
  }, 300);
39
45
  }, this._handleMainKeydown = (e) => {
40
- let t = this.shadowRoot?.querySelector(".main");
41
- if (!t) return;
42
- let n = t.clientHeight;
46
+ let t = e.composedPath()[0];
47
+ if (t instanceof HTMLElement && m(t)) return;
48
+ let n = this.shadowRoot?.querySelector(".main");
49
+ if (!n) return;
50
+ let r = n.clientHeight;
43
51
  switch (e.key) {
44
52
  case " ":
45
53
  case "PageDown":
46
- t.scrollTop += e.shiftKey ? -n : n;
54
+ n.scrollTop += e.shiftKey ? -r : r;
47
55
  break;
48
56
  case "PageUp":
49
- t.scrollTop -= n;
57
+ n.scrollTop -= r;
50
58
  break;
51
59
  case "End":
52
- t.scrollTop = t.scrollHeight;
60
+ n.scrollTop = n.scrollHeight;
53
61
  break;
54
62
  case "Home":
55
- t.scrollTop = 0;
63
+ n.scrollTop = 0;
56
64
  break;
57
65
  case "ArrowDown":
58
- t.scrollTop += 80;
66
+ n.scrollTop += 80;
59
67
  break;
60
68
  case "ArrowUp":
61
- t.scrollTop -= 80;
69
+ n.scrollTop -= 80;
62
70
  break;
63
71
  default: return;
64
72
  }
@@ -84,10 +92,7 @@ var m, h = class extends r {
84
92
  return this.config ? a`
85
93
  <!-- Mobile Header -->
86
94
  <div class="mobile-header" part="mobile-header" ?hidden="${!this.state.startsWith("mobile")}">
87
- <u-icon class="logo"
88
- .name="${this.config.logo}"
89
- @click=${this.handleBrandLogoClick}
90
- ></u-icon>
95
+ ${this.renderLogo()}
91
96
  <span class="title">
92
97
  ${this.config.title}
93
98
  </span>
@@ -104,10 +109,7 @@ var m, h = class extends r {
104
109
  <aside class="sidebar" part="sidebar" state="${this.state}">
105
110
  <!-- Sidebar Header -->
106
111
  <div class="sidebar-header" part="sidebar-header">
107
- <u-icon class="logo"
108
- .name="${this.config.logo}"
109
- @click=${this.handleBrandLogoClick}
110
- ></u-icon>
112
+ ${this.renderLogo()}
111
113
  <span class="title" ?hidden=${this.state === "slim"}>
112
114
  ${this.config.title}
113
115
  </span>
@@ -195,10 +197,34 @@ var m, h = class extends r {
195
197
  `;
196
198
  } else return o;
197
199
  }
200
+ renderLogo() {
201
+ let e = this.config?.logo;
202
+ if (!e || typeof e == "string") return a`
203
+ <u-icon class="logo"
204
+ .name="${e}"
205
+ @click=${this.handleBrandLogoClick()}
206
+ ></u-icon>
207
+ `;
208
+ if (typeof e == "function") {
209
+ let t = e(this.state);
210
+ return a`
211
+ <span class="logo" @click=${this.handleBrandLogoClick()}>
212
+ ${typeof t == "string" ? d(t) : t}
213
+ </span>
214
+ `;
215
+ }
216
+ return a`
217
+ <img class="logo"
218
+ src="${e.src}"
219
+ alt="${e.alt ?? ""}"
220
+ @click=${this.handleBrandLogoClick(e.href)}
221
+ />
222
+ `;
223
+ }
198
224
  };
199
225
  n([c({
200
226
  type: String,
201
227
  reflect: !0
202
- }), t("design:type", Object)], h.prototype, "state", void 0), n([c({ type: Object }), t("design:type", Object)], h.prototype, "config", void 0), n([l("u-progress-bar"), t("design:type", typeof (m = p !== void 0 && p) == "function" ? m : Object)], h.prototype, "progressBarEl", void 0), n([u(), t("design:type", Object)], h.prototype, "context", void 0), h = n([s("u-sidebar-layout")], h);
228
+ }), t("design:type", Object)], h.prototype, "state", void 0), n([c({ type: Object }), t("design:type", Object)], h.prototype, "config", void 0), n([l("u-progress-bar"), t("design:type", p === void 0 ? Object : p)], h.prototype, "progressBarEl", void 0), n([u(), t("design:type", Object)], h.prototype, "context", void 0), h = n([s("u-sidebar-layout")], h);
203
229
  //#endregion
204
230
  export { h as SidebarLayout };
@@ -23,6 +23,11 @@ var t = e`
23
23
  .logo:hover {
24
24
  color: var(--u-txt-color-hover);
25
25
  }
26
+ img.logo {
27
+ height: 24px;
28
+ width: auto;
29
+ object-fit: contain;
30
+ }
26
31
 
27
32
  .title {
28
33
  flex: 1;
@@ -13,13 +13,23 @@ export interface SidebarHtmlConfig {
13
13
  type: 'html';
14
14
  render: (state: SidebarState) => TemplateResult<1> | HTMLElement | string;
15
15
  }
16
+ /** 이미지 로고 설정. `href` 지정 시 클릭 시 기본 홈 이동 대신 해당 경로로 이동 */
17
+ export interface SidebarLogoImage {
18
+ src: string;
19
+ alt?: string;
20
+ href?: string;
21
+ }
22
+ /** 커스텀 로고 렌더 함수. `SidebarHtmlConfig.render`와 동일한 반환 타입 */
23
+ export type SidebarLogoRenderer = (state: SidebarState) => TemplateResult<1> | HTMLElement | string;
24
+ /** 최상단 앱 로고 설정: 아이콘명(문자열, 기존 동작) | 이미지 | 커스텀 렌더 함수 */
25
+ export type SidebarLogoConfig = string | SidebarLogoImage | SidebarLogoRenderer;
16
26
  /** union: section | group | link | button */
17
27
  export type SidebarItem = (SidebarLinkConfig | SidebarSectionConfig | SidebarGroupConfig | SidebarButtonConfig | SidebarHtmlConfig);
18
28
  /** 사이드바 레이아웃 전체 설정 */
19
29
  export interface SidebarLayoutConfig {
20
30
  type: 'sidebar';
21
- /** 최상단 앱 로고 */
22
- logo?: string;
31
+ /** 최상단 앱 로고: 아이콘명(문자열) | 이미지({src,alt,href}) | 커스텀 렌더 함수 */
32
+ logo?: SidebarLogoConfig;
23
33
  /** 앱 제목 */
24
34
  title?: string;
25
35
  /** 상단/메인 메뉴 아이템 항목들 */
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.3.6",
4
+ "version": "0.4.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -38,8 +38,8 @@
38
38
  "build": "vite build"
39
39
  },
40
40
  "dependencies": {
41
- "@iyulab/components": "^1.0.6",
42
- "@iyulab/router": "^0.9.3",
41
+ "@iyulab/components": "^1.1.1",
42
+ "@iyulab/router": "^0.10.0",
43
43
  "i18next": "^26.2.0",
44
44
  "lit": "^3.3.3"
45
45
  },
@@ -35,7 +35,7 @@ await app.load({
35
35
  basepath: '/',
36
36
  layout: {
37
37
  type: 'sidebar',
38
- logo: '/assets/logo.svg',
38
+ logo: { src: '/assets/logo.svg', alt: 'My App' },
39
39
  title: 'My App',
40
40
  main: [
41
41
  { type: 'link', icon: 'home', label: 'Home', href: '/' },
@@ -157,7 +157,7 @@ Full configuration reference: [references/layout.md](./references/layout.md)
157
157
  ```typescript
158
158
  layout: {
159
159
  type: 'sidebar',
160
- logo: '/logo.svg',
160
+ logo: { src: '/logo.svg', alt: 'App Name' },
161
161
  title: 'App Name',
162
162
  main: [
163
163
  {
@@ -6,8 +6,8 @@
6
6
  interface SidebarLayoutConfig {
7
7
  type: 'sidebar';
8
8
 
9
- /** URL or path to the logo image. */
10
- logo?: string;
9
+ /** Icon name (string) | image ({ src, alt?, href? }) | custom render function. Click navigates to `/` by default, or `href` if given (image variant). */
10
+ logo?: string | { src: string; alt?: string; href?: string } | ((state: SidebarState) => TemplateResult<1> | HTMLElement | string);
11
11
 
12
12
  /** Application title displayed beside the logo. */
13
13
  title?: string;