@iyulab/modern-app 0.19.2 → 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 +31 -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 +63 -54
- package/dist/layouts/SidebarLayout.styles.js +5 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
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
|
+
|
|
22
|
+
## [0.19.3] - 2026-09-13
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **The "shell has no sized ancestor" dev-mode warning was a complete no-op** — its own guard used
|
|
27
|
+
`import.meta.env.DEV`, which Vite resolves statically at this package's own build time, so the
|
|
28
|
+
published dist always shipped with the check baked to `false` and the warning removed entirely
|
|
29
|
+
by dead-code elimination. Replaced with `process.env.NODE_ENV !== 'production'`. The
|
|
30
|
+
`@iyulab/components` peer floor moves to `>=1.40.3`, which fixes the identical defect in the
|
|
31
|
+
shared `createDevWarner` helper this warning calls into — installing 0.19.3 without that
|
|
32
|
+
components fix would still produce no warning.
|
|
33
|
+
|
|
3
34
|
## [0.19.2] - 2026-09-13
|
|
4
35
|
|
|
5
36
|
### Changed
|
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,33 +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
|
-
|
|
23
|
-
|
|
24
|
-
function y(e) {
|
|
23
|
+
var y = _("@iyulab/modern-app"), b = 0;
|
|
24
|
+
function x(e) {
|
|
25
25
|
let t = e.tagName;
|
|
26
26
|
if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
|
|
27
27
|
let n = e.getAttribute("role");
|
|
28
28
|
return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
|
|
29
29
|
}
|
|
30
|
-
var
|
|
30
|
+
var S = class extends i {
|
|
31
31
|
constructor(...t) {
|
|
32
|
-
super(...t), this.state = "default", this.locale = "", this.context = null, this.unsizedWarnKey = `unsized:${++
|
|
33
|
-
e.navigate(t ?? "");
|
|
34
|
-
}, 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 = () => {
|
|
35
33
|
let t = e.screen ?? "large";
|
|
36
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);
|
|
37
35
|
}, this.handleBackdropClick = () => {
|
|
@@ -52,7 +50,7 @@ var b = class extends i {
|
|
|
52
50
|
}, 300);
|
|
53
51
|
}, this._handleMainKeydown = (e) => {
|
|
54
52
|
let t = e.composedPath()[0];
|
|
55
|
-
if (t instanceof HTMLElement &&
|
|
53
|
+
if (t instanceof HTMLElement && x(t)) return;
|
|
56
54
|
let n = this.shadowRoot?.querySelector(".main");
|
|
57
55
|
if (!n) return;
|
|
58
56
|
let r = n.clientHeight;
|
|
@@ -85,7 +83,7 @@ var b = class extends i {
|
|
|
85
83
|
};
|
|
86
84
|
}
|
|
87
85
|
static {
|
|
88
|
-
this.styles = [super.styles,
|
|
86
|
+
this.styles = [super.styles, s];
|
|
89
87
|
}
|
|
90
88
|
get mainElement() {
|
|
91
89
|
return this.shadowRoot?.querySelector(".main") ?? null;
|
|
@@ -102,9 +100,15 @@ var b = class extends i {
|
|
|
102
100
|
firstUpdated(e) {
|
|
103
101
|
super.firstUpdated(e), this.warnIfUnsized();
|
|
104
102
|
}
|
|
105
|
-
warnIfUnsized() {
|
|
103
|
+
warnIfUnsized() {
|
|
104
|
+
process.env.NODE_ENV !== "production" && requestAnimationFrame(() => {
|
|
105
|
+
if (!this.isConnected) return;
|
|
106
|
+
let e = this.getBoundingClientRect().height;
|
|
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).`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
106
110
|
render() {
|
|
107
|
-
return this.config ?
|
|
111
|
+
return this.config ? c`
|
|
108
112
|
<!-- Mobile Header -->
|
|
109
113
|
<div class="mobile-header" part="mobile-header" ?hidden="${!this.state.startsWith("mobile")}">
|
|
110
114
|
${this.renderLogo()}
|
|
@@ -141,13 +145,13 @@ var b = class extends i {
|
|
|
141
145
|
|
|
142
146
|
<!-- Sidebar Navigation Menu -->
|
|
143
147
|
<nav class="sidebar-main" part="sidebar-main" scrollable
|
|
144
|
-
aria-label=${this.config.mainAriaLabel ??
|
|
145
|
-
${
|
|
148
|
+
aria-label=${this.config.mainAriaLabel ?? l}>
|
|
149
|
+
${h(t(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
|
|
146
150
|
</nav>
|
|
147
151
|
|
|
148
152
|
<!-- Sidebar Footer -->
|
|
149
153
|
<div class="sidebar-footer" part="sidebar-footer">
|
|
150
|
-
${
|
|
154
|
+
${h(t(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
|
|
151
155
|
</div>
|
|
152
156
|
</aside>
|
|
153
157
|
|
|
@@ -162,17 +166,17 @@ var b = class extends i {
|
|
|
162
166
|
<div class="backdrop" ?hidden="${this.state !== "modal"}"
|
|
163
167
|
@click="${this.handleBackdropClick}"
|
|
164
168
|
></div>
|
|
165
|
-
` :
|
|
169
|
+
` : l;
|
|
166
170
|
}
|
|
167
171
|
renderItem(e) {
|
|
168
|
-
if (!e) return
|
|
172
|
+
if (!e) return l;
|
|
169
173
|
if (e.type === "html") {
|
|
170
174
|
let t = e.render(this.state);
|
|
171
|
-
return typeof t == "string" ?
|
|
175
|
+
return typeof t == "string" ? m(t) : c`${t}`;
|
|
172
176
|
}
|
|
173
|
-
if (e.type === "button") return
|
|
177
|
+
if (e.type === "button") return c`
|
|
174
178
|
<u-sidebar-button
|
|
175
|
-
id=${
|
|
179
|
+
id=${g(e.id)}
|
|
176
180
|
?compact=${this.state === "slim"}
|
|
177
181
|
.icon="${e.icon}"
|
|
178
182
|
.lib="${e.lib}"
|
|
@@ -183,7 +187,7 @@ var b = class extends i {
|
|
|
183
187
|
`;
|
|
184
188
|
if (e.type === "link") {
|
|
185
189
|
let t = this.isMatchedLink(e.pattern || e.href);
|
|
186
|
-
return
|
|
190
|
+
return c`
|
|
187
191
|
<u-sidebar-link
|
|
188
192
|
?compact=${this.state === "slim"}
|
|
189
193
|
?selected=${t}
|
|
@@ -198,18 +202,18 @@ var b = class extends i {
|
|
|
198
202
|
></u-sidebar-link>
|
|
199
203
|
`;
|
|
200
204
|
}
|
|
201
|
-
if (e.type === "section") return
|
|
205
|
+
if (e.type === "section") return c`
|
|
202
206
|
<u-sidebar-section
|
|
203
207
|
?compact=${this.state === "slim"}
|
|
204
208
|
.mainTitle="${e.title}"
|
|
205
209
|
.subTitle="${e.subTitle}"
|
|
206
210
|
.styles="${e.styles}">
|
|
207
|
-
${
|
|
211
|
+
${h(e.items, (e, t) => t, (e) => this.renderItem(e))}
|
|
208
212
|
</u-sidebar-section>
|
|
209
213
|
`;
|
|
210
214
|
if (e.type === "group") {
|
|
211
215
|
let t = e.items.some((e) => this.isMatchedLink(e.pattern || e.href));
|
|
212
|
-
return
|
|
216
|
+
return c`
|
|
213
217
|
<u-sidebar-group
|
|
214
218
|
?compact=${this.state === "slim"}
|
|
215
219
|
?selected=${t}
|
|
@@ -218,40 +222,45 @@ var b = class extends i {
|
|
|
218
222
|
.lib="${e.lib}"
|
|
219
223
|
.label="${e.label}"
|
|
220
224
|
.styles="${e.styles}">
|
|
221
|
-
${
|
|
225
|
+
${h(e.items, (e, t) => t, (e) => this.renderItem(e))}
|
|
222
226
|
</u-sidebar-group>
|
|
223
227
|
`;
|
|
224
228
|
}
|
|
225
|
-
return
|
|
229
|
+
return l;
|
|
226
230
|
}
|
|
227
231
|
renderLogo() {
|
|
228
|
-
let e = this.config?.logo;
|
|
229
|
-
if (!e || typeof e == "string") return
|
|
230
|
-
<u-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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>`;
|
|
235
241
|
if (typeof e == "function") {
|
|
236
|
-
let
|
|
237
|
-
return
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
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>
|
|
241
249
|
`;
|
|
242
250
|
}
|
|
243
|
-
return
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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>
|
|
249
258
|
`;
|
|
250
259
|
}
|
|
251
260
|
};
|
|
252
|
-
r([
|
|
261
|
+
r([d({
|
|
253
262
|
type: String,
|
|
254
263
|
reflect: !0
|
|
255
|
-
}), 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);
|
|
256
265
|
//#endregion
|
|
257
|
-
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,12 +60,12 @@
|
|
|
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
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@iyulab/components": ">=1.40.
|
|
68
|
+
"@iyulab/components": ">=1.40.3",
|
|
69
69
|
"@lit/react": ">=1.0.8",
|
|
70
70
|
"react": ">=18.0.0"
|
|
71
71
|
},
|
|
@@ -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
|
}
|