@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 +18 -0
- package/README.md +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.130.0/helpers → _@oxc-project_runtime@0.138.0/helpers/esm}/decorate.js +2 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.130.0/helpers → _@oxc-project_runtime@0.138.0/helpers/esm}/decorateMetadata.js +2 -2
- package/dist/components/SidebarButton.js +3 -2
- package/dist/components/SidebarGroup.js +3 -2
- package/dist/components/SidebarLink.js +2 -2
- package/dist/components/SidebarSection.js +3 -2
- package/dist/internals/StyledElement.js +2 -2
- package/dist/layouts/SidebarLayout.d.ts +3 -16
- package/dist/layouts/SidebarLayout.js +49 -23
- package/dist/layouts/SidebarLayout.styles.js +5 -0
- package/dist/layouts/SidebarLayout.types.d.ts +12 -2
- package/package.json +3 -3
- package/skills/modern-app/SKILL.md +2 -2
- package/skills/modern-app/references/layout.md +2 -2
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
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
|
|
9
|
+
export { e as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
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
|
|
6
|
+
export { e as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
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
|
|
2
|
-
import
|
|
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
|
|
2
|
-
import
|
|
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
|
|
2
|
-
import
|
|
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
|
|
2
|
-
import
|
|
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
|
|
3
|
-
import
|
|
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
|
-
|
|
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 =
|
|
41
|
-
if (
|
|
42
|
-
let n =
|
|
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
|
-
|
|
54
|
+
n.scrollTop += e.shiftKey ? -r : r;
|
|
47
55
|
break;
|
|
48
56
|
case "PageUp":
|
|
49
|
-
|
|
57
|
+
n.scrollTop -= r;
|
|
50
58
|
break;
|
|
51
59
|
case "End":
|
|
52
|
-
|
|
60
|
+
n.scrollTop = n.scrollHeight;
|
|
53
61
|
break;
|
|
54
62
|
case "Home":
|
|
55
|
-
|
|
63
|
+
n.scrollTop = 0;
|
|
56
64
|
break;
|
|
57
65
|
case "ArrowDown":
|
|
58
|
-
|
|
66
|
+
n.scrollTop += 80;
|
|
59
67
|
break;
|
|
60
68
|
case "ArrowUp":
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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 };
|
|
@@ -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?:
|
|
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.
|
|
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.
|
|
42
|
-
"@iyulab/router": "^0.
|
|
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
|
-
/**
|
|
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;
|