@iyulab/modern-app 0.19.3 → 0.19.4
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 +19 -0
- package/README.md +14 -0
- package/dist/components/PageHeader.styles.js +5 -0
- package/dist/internals/nav-icon.d.ts +1 -0
- package/dist/layouts/SidebarLayout.d.ts +6 -2
- package/dist/layouts/SidebarLayout.js +57 -53
- package/dist/layouts/SidebarLayout.styles.js +5 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.19.4] - 2026-09-15
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`u-page-header`'s back link was a 41×17 pointer target**, under the 24×24 CSS px minimum of
|
|
8
|
+
WCAG 2.2 SC 2.5.8. It now carries block padding with an equal negative margin, so the target is
|
|
9
|
+
~25px tall while the header's layout does not move (host, title and following content keep their
|
|
10
|
+
exact positions). A new browser gate measures every pointer target this package renders — nav
|
|
11
|
+
items expanded and compact, group headers, the sidebar logo and toggle, wizard steps, and the back
|
|
12
|
+
link — and fails when one is undersized or not actually hit at its own position.
|
|
13
|
+
- **A sidebar `logo` given as an icon name vanished when the name did not resolve** (a 404 or a
|
|
14
|
+
typo) — the icon drew nothing and the home target collapsed to 0×0, which in the slim sidebar left
|
|
15
|
+
only the toggle. It now falls back to the same default icon the navigation items use. Omitting
|
|
16
|
+
`logo` still draws no logo.
|
|
17
|
+
- **The sidebar logo could not be reached from the keyboard.** Every logo form (icon name, image,
|
|
18
|
+
render function) was a click-only element — no focus, not announced as a link. The logo is now a
|
|
19
|
+
`u-link` to `href` (or the base path, as before). An image logo is named by its `alt`; icon and
|
|
20
|
+
custom logos are named by the app `title`.
|
|
21
|
+
|
|
3
22
|
## [0.19.3] - 2026-09-13
|
|
4
23
|
|
|
5
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -205,6 +205,20 @@ export function App() {
|
|
|
205
205
|
}
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
## Accessibility
|
|
209
|
+
|
|
210
|
+
The baseline is **WCAG 2.2**. The table lists what this package **measures in tests** — it is not a
|
|
211
|
+
conformance claim for the success criteria it does not list.
|
|
212
|
+
|
|
213
|
+
| Success criterion | Guarantee | Measured by |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| SC 2.5.8 Target Size (Minimum) | Every pointer target this package renders itself — sidebar links, buttons and group headers (expanded and compact), the sidebar logo and toggle, wizard steps, the page header's back link — is at least 24×24 CSS px or meets the spacing exception (24px between centers), and is actually hit at that position | `tests/browser/target-size.browser.test.ts` (real Chromium) |
|
|
216
|
+
| SC 2.1.1 Keyboard (pointer-cursor check) | Nothing this package renders shows a pointer cursor without being an interactive element — the sidebar logo is a link | `tests/browser/target-size.browser.test.ts` |
|
|
217
|
+
|
|
218
|
+
Buttons placed through slots or rendered as `u-button` without size overrides (wizard back/next, the
|
|
219
|
+
master-detail close button) follow `@iyulab/components`, which measures them in its own gate. Color
|
|
220
|
+
contrast likewise comes from that package's tokens.
|
|
221
|
+
|
|
208
222
|
## Documentation
|
|
209
223
|
|
|
210
224
|
| Guide | Description |
|
|
@@ -47,6 +47,11 @@ var t = e`
|
|
|
47
47
|
font-weight: var(--u-text-label-weight, 600);
|
|
48
48
|
color: var(--u-link-txt-color, #1565C0);
|
|
49
49
|
text-decoration: none;
|
|
50
|
+
/* 타깃 크기(WCAG 2.2 SC 2.5.8) — 글자 줄 상자만으로는 높이가 17px 남짓이라 패딩으로 누를 면을 24px 넘게
|
|
51
|
+
키우고, 같은 크기의 음수 여백으로 그 공간을 돌려준다 — 배치는 움직이지 않는다(접미 아이콘과 같은 기법). */
|
|
52
|
+
display: inline-block;
|
|
53
|
+
padding-block: var(--u-space-2xs, 4px);
|
|
54
|
+
margin-block: calc(-1 * var(--u-space-2xs, 4px));
|
|
50
55
|
}
|
|
51
56
|
.back::before {
|
|
52
57
|
content: '←';
|
|
@@ -40,8 +40,12 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
40
40
|
/** 현재 경로와 패턴 매칭 여부 확인 */
|
|
41
41
|
private isMatchedLink;
|
|
42
42
|
/** 브랜드 로고 클릭 핸들러: `href` 지정 시 해당 경로로, 아니면 홈으로 이동 */
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
/**
|
|
44
|
+
* 로고 렌더링: 아이콘명(문자열, 기존 동작) | 이미지({src,alt,href}) | 커스텀 렌더 함수.
|
|
45
|
+
*
|
|
46
|
+
* 🔴로고는 홈(또는 `href`)으로 가는 **링크**다 — 종전엔 클릭만 받는 `img`·`span`·`u-icon` 이라 키보드로 닿지 않았고
|
|
47
|
+
* 보조기술에 링크로 드러나지 않았다. `u-link` 는 `href` 가 없으면 basepath 로 SPA 이동하므로 종전 `app.navigate('')` 와 같다.
|
|
48
|
+
*/
|
|
45
49
|
private renderLogo;
|
|
46
50
|
/** 사이드바 토글 핸들러 */
|
|
47
51
|
private handleToggleButtonClick;
|
|
@@ -5,32 +5,31 @@ import r from "../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js
|
|
|
5
5
|
import { StyledElement as i } from "../internals/StyledElement.js";
|
|
6
6
|
import { getLocaleStrings as a } from "../internals/locale.js";
|
|
7
7
|
import "../components/SidebarSection.js";
|
|
8
|
+
import { DEFAULT_NAV_ICON as o } from "../internals/nav-icon.js";
|
|
8
9
|
import "../components/SidebarLink.js";
|
|
9
10
|
import "../components/SidebarGroup.js";
|
|
10
11
|
import "../components/SidebarButton.js";
|
|
11
|
-
import { styles as
|
|
12
|
-
import { html as
|
|
13
|
-
import { customElement as
|
|
12
|
+
import { styles as s } from "./SidebarLayout.styles.js";
|
|
13
|
+
import { html as c, nothing as l } from "lit";
|
|
14
|
+
import { customElement as u, property as d, query as f, state as p } from "lit/decorators.js";
|
|
14
15
|
import "@iyulab/components/dist/components/button/UButton.js";
|
|
15
16
|
import "@iyulab/components/dist/components/icon/UIcon.js";
|
|
16
|
-
import { unsafeHTML as
|
|
17
|
-
import { repeat as
|
|
18
|
-
import { ifDefined as
|
|
19
|
-
import { createDevWarner as
|
|
20
|
-
import { UProgressBar as
|
|
17
|
+
import { unsafeHTML as m } from "lit/directives/unsafe-html.js";
|
|
18
|
+
import { repeat as h } from "lit/directives/repeat.js";
|
|
19
|
+
import { ifDefined as g } from "lit/directives/if-defined.js";
|
|
20
|
+
import { createDevWarner as _ } from "@iyulab/components/dist/utilities/devWarning.js";
|
|
21
|
+
import { UProgressBar as v } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
|
|
21
22
|
//#region src/layouts/SidebarLayout.ts
|
|
22
|
-
var
|
|
23
|
-
function
|
|
23
|
+
var y = _("@iyulab/modern-app"), b = 0;
|
|
24
|
+
function x(e) {
|
|
24
25
|
let t = e.tagName;
|
|
25
26
|
if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
|
|
26
27
|
let n = e.getAttribute("role");
|
|
27
28
|
return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
|
|
28
29
|
}
|
|
29
|
-
var
|
|
30
|
+
var S = class extends i {
|
|
30
31
|
constructor(...t) {
|
|
31
|
-
super(...t), this.state = "default", this.locale = "", this.context = null, this.unsizedWarnKey = `unsized:${++
|
|
32
|
-
e.navigate(t ?? "");
|
|
33
|
-
}, this.handleToggleButtonClick = () => {
|
|
32
|
+
super(...t), this.state = "default", this.locale = "", this.context = null, this.unsizedWarnKey = `unsized:${++b}`, 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 = () => {
|
|
34
33
|
let t = e.screen ?? "large";
|
|
35
34
|
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);
|
|
36
35
|
}, this.handleBackdropClick = () => {
|
|
@@ -51,7 +50,7 @@ var x = class extends i {
|
|
|
51
50
|
}, 300);
|
|
52
51
|
}, this._handleMainKeydown = (e) => {
|
|
53
52
|
let t = e.composedPath()[0];
|
|
54
|
-
if (t instanceof HTMLElement &&
|
|
53
|
+
if (t instanceof HTMLElement && x(t)) return;
|
|
55
54
|
let n = this.shadowRoot?.querySelector(".main");
|
|
56
55
|
if (!n) return;
|
|
57
56
|
let r = n.clientHeight;
|
|
@@ -84,7 +83,7 @@ var x = class extends i {
|
|
|
84
83
|
};
|
|
85
84
|
}
|
|
86
85
|
static {
|
|
87
|
-
this.styles = [super.styles,
|
|
86
|
+
this.styles = [super.styles, s];
|
|
88
87
|
}
|
|
89
88
|
get mainElement() {
|
|
90
89
|
return this.shadowRoot?.querySelector(".main") ?? null;
|
|
@@ -105,11 +104,11 @@ var x = class extends i {
|
|
|
105
104
|
process.env.NODE_ENV !== "production" && requestAnimationFrame(() => {
|
|
106
105
|
if (!this.isConnected) return;
|
|
107
106
|
let e = this.getBoundingClientRect().height;
|
|
108
|
-
e >= 200 ||
|
|
107
|
+
e >= 200 || y(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).`);
|
|
109
108
|
});
|
|
110
109
|
}
|
|
111
110
|
render() {
|
|
112
|
-
return this.config ?
|
|
111
|
+
return this.config ? c`
|
|
113
112
|
<!-- Mobile Header -->
|
|
114
113
|
<div class="mobile-header" part="mobile-header" ?hidden="${!this.state.startsWith("mobile")}">
|
|
115
114
|
${this.renderLogo()}
|
|
@@ -146,13 +145,13 @@ var x = class extends i {
|
|
|
146
145
|
|
|
147
146
|
<!-- Sidebar Navigation Menu -->
|
|
148
147
|
<nav class="sidebar-main" part="sidebar-main" scrollable
|
|
149
|
-
aria-label=${this.config.mainAriaLabel ??
|
|
150
|
-
${
|
|
148
|
+
aria-label=${this.config.mainAriaLabel ?? l}>
|
|
149
|
+
${h(t(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
|
|
151
150
|
</nav>
|
|
152
151
|
|
|
153
152
|
<!-- Sidebar Footer -->
|
|
154
153
|
<div class="sidebar-footer" part="sidebar-footer">
|
|
155
|
-
${
|
|
154
|
+
${h(t(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
|
|
156
155
|
</div>
|
|
157
156
|
</aside>
|
|
158
157
|
|
|
@@ -167,17 +166,17 @@ var x = class extends i {
|
|
|
167
166
|
<div class="backdrop" ?hidden="${this.state !== "modal"}"
|
|
168
167
|
@click="${this.handleBackdropClick}"
|
|
169
168
|
></div>
|
|
170
|
-
` :
|
|
169
|
+
` : l;
|
|
171
170
|
}
|
|
172
171
|
renderItem(e) {
|
|
173
|
-
if (!e) return
|
|
172
|
+
if (!e) return l;
|
|
174
173
|
if (e.type === "html") {
|
|
175
174
|
let t = e.render(this.state);
|
|
176
|
-
return typeof t == "string" ?
|
|
175
|
+
return typeof t == "string" ? m(t) : c`${t}`;
|
|
177
176
|
}
|
|
178
|
-
if (e.type === "button") return
|
|
177
|
+
if (e.type === "button") return c`
|
|
179
178
|
<u-sidebar-button
|
|
180
|
-
id=${
|
|
179
|
+
id=${g(e.id)}
|
|
181
180
|
?compact=${this.state === "slim"}
|
|
182
181
|
.icon="${e.icon}"
|
|
183
182
|
.lib="${e.lib}"
|
|
@@ -188,7 +187,7 @@ var x = class extends i {
|
|
|
188
187
|
`;
|
|
189
188
|
if (e.type === "link") {
|
|
190
189
|
let t = this.isMatchedLink(e.pattern || e.href);
|
|
191
|
-
return
|
|
190
|
+
return c`
|
|
192
191
|
<u-sidebar-link
|
|
193
192
|
?compact=${this.state === "slim"}
|
|
194
193
|
?selected=${t}
|
|
@@ -203,18 +202,18 @@ var x = class extends i {
|
|
|
203
202
|
></u-sidebar-link>
|
|
204
203
|
`;
|
|
205
204
|
}
|
|
206
|
-
if (e.type === "section") return
|
|
205
|
+
if (e.type === "section") return c`
|
|
207
206
|
<u-sidebar-section
|
|
208
207
|
?compact=${this.state === "slim"}
|
|
209
208
|
.mainTitle="${e.title}"
|
|
210
209
|
.subTitle="${e.subTitle}"
|
|
211
210
|
.styles="${e.styles}">
|
|
212
|
-
${
|
|
211
|
+
${h(e.items, (e, t) => t, (e) => this.renderItem(e))}
|
|
213
212
|
</u-sidebar-section>
|
|
214
213
|
`;
|
|
215
214
|
if (e.type === "group") {
|
|
216
215
|
let t = e.items.some((e) => this.isMatchedLink(e.pattern || e.href));
|
|
217
|
-
return
|
|
216
|
+
return c`
|
|
218
217
|
<u-sidebar-group
|
|
219
218
|
?compact=${this.state === "slim"}
|
|
220
219
|
?selected=${t}
|
|
@@ -223,40 +222,45 @@ var x = class extends i {
|
|
|
223
222
|
.lib="${e.lib}"
|
|
224
223
|
.label="${e.label}"
|
|
225
224
|
.styles="${e.styles}">
|
|
226
|
-
${
|
|
225
|
+
${h(e.items, (e, t) => t, (e) => this.renderItem(e))}
|
|
227
226
|
</u-sidebar-group>
|
|
228
227
|
`;
|
|
229
228
|
}
|
|
230
|
-
return
|
|
229
|
+
return l;
|
|
231
230
|
}
|
|
232
231
|
renderLogo() {
|
|
233
|
-
let e = this.config?.logo;
|
|
234
|
-
if (!e || typeof e == "string") return
|
|
235
|
-
<u-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
232
|
+
let e = this.config?.logo, t = this.config?.title ?? "Home";
|
|
233
|
+
if (!e || typeof e == "string") return e ? c`
|
|
234
|
+
<u-link class="logo-link" aria-label=${t}>
|
|
235
|
+
<u-icon class="logo"
|
|
236
|
+
.name="${e}"
|
|
237
|
+
.fallback=${o}
|
|
238
|
+
></u-icon>
|
|
239
|
+
</u-link>
|
|
240
|
+
` : c`<u-icon class="logo"></u-icon>`;
|
|
240
241
|
if (typeof e == "function") {
|
|
241
|
-
let
|
|
242
|
-
return
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
let n = e(this.state);
|
|
243
|
+
return c`
|
|
244
|
+
<u-link class="logo-link" aria-label=${t}>
|
|
245
|
+
<span class="logo">
|
|
246
|
+
${typeof n == "string" ? m(n) : n}
|
|
247
|
+
</span>
|
|
248
|
+
</u-link>
|
|
246
249
|
`;
|
|
247
250
|
}
|
|
248
|
-
return
|
|
249
|
-
<
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
return c`
|
|
252
|
+
<u-link class="logo-link" .href=${e.href} aria-label=${e.alt ? l : t}>
|
|
253
|
+
<img class="logo"
|
|
254
|
+
src="${e.src}"
|
|
255
|
+
alt="${e.alt ?? ""}"
|
|
256
|
+
/>
|
|
257
|
+
</u-link>
|
|
254
258
|
`;
|
|
255
259
|
}
|
|
256
260
|
};
|
|
257
|
-
r([
|
|
261
|
+
r([d({
|
|
258
262
|
type: String,
|
|
259
263
|
reflect: !0
|
|
260
|
-
}), n("design:type", Object)],
|
|
264
|
+
}), n("design:type", Object)], S.prototype, "state", void 0), r([d({ type: Object }), n("design:type", Object)], S.prototype, "config", void 0), r([d({ type: String }), n("design:type", Object)], S.prototype, "locale", void 0), r([f("u-progress-bar"), n("design:type", v === void 0 ? Object : v)], S.prototype, "progressBarEl", void 0), r([p(), n("design:type", Object)], S.prototype, "context", void 0), S = r([u("u-sidebar-layout")], S);
|
|
261
265
|
//#endregion
|
|
262
|
-
export {
|
|
266
|
+
export { S 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.19.
|
|
4
|
+
"version": "0.19.4",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-framework",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"typecheck": "tsc --noEmit"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@iyulab/router": "^0.
|
|
63
|
+
"@iyulab/router": "^0.13.0",
|
|
64
64
|
"i18next": "^26.3.6",
|
|
65
65
|
"lit": "^3.3.3"
|
|
66
66
|
},
|
|
@@ -82,13 +82,13 @@
|
|
|
82
82
|
"@types/node": "^26.1.1",
|
|
83
83
|
"@types/react": "^19.2.14",
|
|
84
84
|
"@types/react-dom": "^19.2.3",
|
|
85
|
-
"@vitest/browser-playwright": "^
|
|
85
|
+
"@vitest/browser-playwright": "^5.0.0",
|
|
86
86
|
"happy-dom": "^20.10.6",
|
|
87
87
|
"react": "^19.2.7",
|
|
88
88
|
"react-dom": "^19.2.7",
|
|
89
89
|
"typescript": "^6.0.2",
|
|
90
90
|
"vite": "^8.1.4",
|
|
91
91
|
"vite-plugin-dts": "^5.0.3",
|
|
92
|
-
"vitest": "^
|
|
92
|
+
"vitest": "^5.0.0"
|
|
93
93
|
}
|
|
94
94
|
}
|