@iyulab/modern-app 0.42.0 → 0.42.1
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 +12 -0
- package/dist/layouts/SidebarLayout.d.ts +21 -0
- package/dist/layouts/SidebarLayout.js +31 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.42.1] - 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A long screen with no controls can be scrolled from the keyboard.** The sidebar layout's content region carries
|
|
8
|
+
`tabindex="-1"` for programmatic focus (skip link, route change) — and a negative tabindex is the explicit opt-out
|
|
9
|
+
from the browser's own rule that makes a scroller with nothing focusable inside a Tab stop (Chrome 130). So a
|
|
10
|
+
keyboard user who tabbed past a document or notice screen had no way to scroll it (axe
|
|
11
|
+
`scrollable-region-focusable`). The region is now a Tab stop exactly when it overflows and holds nothing reachable
|
|
12
|
+
by Tab (shadow roots included), and re-judges when the content's size or slotted elements change; screens with
|
|
13
|
+
controls keep `-1`.
|
|
14
|
+
|
|
3
15
|
## [0.42.0] - 2026-10-07
|
|
4
16
|
|
|
5
17
|
### Added
|
|
@@ -30,6 +30,16 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
30
30
|
* 숨어 이름 없는 진행 막대가 모든 화면의 접근성 트리에 떠 있었다(axe `aria-progressbar-name`).
|
|
31
31
|
*/
|
|
32
32
|
private routeLoading;
|
|
33
|
+
/**
|
|
34
|
+
* 본문이 탭 정지인가 — 넘쳐서 스크롤되는데 안에 탭으로 닿을 것이 하나도 없을 때만.
|
|
35
|
+
*
|
|
36
|
+
* 브라우저 규칙(«키보드 포커스 가능한 스크롤러», Chrome 130)을 셸이 재현한다: 그런 스크롤러는 브라우저가 스스로
|
|
37
|
+
* 탭 정지로 만들지만, `tabindex="-1"` 이 그 규칙의 명시적 opt-out 이다. 셸은 프로그램 포커스(skip link · 라우트
|
|
38
|
+
* 완료) 때문에 `-1` 을 달았고, 그래서 컨트롤 없는 긴 화면(문서 · 안내)을 Tab 으로 지나치면 스크롤할 길이 없었다
|
|
39
|
+
* (axe `scrollable-region-focusable`). 컨트롤이 있는 화면은 그 컨트롤에서 스크롤 키가 듣는다 — 정지를 더하지 않는다.
|
|
40
|
+
*/
|
|
41
|
+
private mainTabStop;
|
|
42
|
+
private mainSizeObserver?;
|
|
33
43
|
/**
|
|
34
44
|
* 오버레이를 연 컨트롤 — 닫힐 때 포커스를 되돌릴 곳.
|
|
35
45
|
* ★슬롯 배정 «시점» 에 잡는다. 렌더가 본문에 `inert` 를 걸면 그 컨트롤이 쥐던 포커스는
|
|
@@ -129,6 +139,17 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
129
139
|
private handleRouteProgress;
|
|
130
140
|
/** 라우트 변경 완료 핸들러 */
|
|
131
141
|
private handleRouteDone;
|
|
142
|
+
/**
|
|
143
|
+
* 본문과 그 내용의 크기가 바뀌면(넘침 · 내용이 그려짐) 탭 정지를 다시 판정한다. 본문이 처음 그려질 때 한 번 건다.
|
|
144
|
+
* ⚠`.main-content` 는 `display: contents` 라 상자가 없다 — 관찰 대상은 슬롯에 배정된 요소들이고, 배정이 바뀔 때마다 다시 건다.
|
|
145
|
+
*/
|
|
146
|
+
private observeMainSize;
|
|
147
|
+
private observedContent;
|
|
148
|
+
private observeContentSize;
|
|
149
|
+
private handleMainSlotChange;
|
|
150
|
+
private syncMainTabStop;
|
|
151
|
+
/** 공지 슬롯에 배정된 요소들 */
|
|
152
|
+
private noticeContent;
|
|
132
153
|
/** 라우트 본문(기본 슬롯)에 배정된 요소들 */
|
|
133
154
|
private routeContent;
|
|
134
155
|
/**
|
|
@@ -79,7 +79,7 @@ var N = class extends i {
|
|
|
79
79
|
C = this;
|
|
80
80
|
}
|
|
81
81
|
constructor(...t) {
|
|
82
|
-
super(...t), 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.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 = () => {
|
|
82
|
+
super(...t), 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.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
83
|
let t = this.screenSize ?? e.screen ?? "large";
|
|
84
84
|
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);
|
|
85
85
|
}, this.handleBackdropClick = () => {
|
|
@@ -93,7 +93,14 @@ var N = class extends i {
|
|
|
93
93
|
this.routeLoading = !1;
|
|
94
94
|
}, 300);
|
|
95
95
|
let t = this.shadowRoot?.querySelector(".main");
|
|
96
|
-
t && (this.config?.scrollBehavior?.(e.context, t), this.focusRoute(t));
|
|
96
|
+
t && (this.config?.scrollBehavior?.(e.context, t), this.focusRoute(t), requestAnimationFrame(this.syncMainTabStop));
|
|
97
|
+
}, this.observedContent = [], this.handleMainSlotChange = () => {
|
|
98
|
+
this.observeContentSize(), this.syncMainTabStop();
|
|
99
|
+
}, this.syncMainTabStop = () => {
|
|
100
|
+
let e = this.shadowRoot?.querySelector(".main");
|
|
101
|
+
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);
|
|
103
|
+
this.mainTabStop = t && !n;
|
|
97
104
|
}, this.handleSkipLinkClick = (e) => {
|
|
98
105
|
e.preventDefault();
|
|
99
106
|
let t = this.shadowRoot?.querySelector(".main");
|
|
@@ -110,7 +117,7 @@ var N = class extends i {
|
|
|
110
117
|
}
|
|
111
118
|
this.hasOverlay = t;
|
|
112
119
|
}, this.handleNoticeSlotChange = (e) => {
|
|
113
|
-
this.hasNotice = o(e.target);
|
|
120
|
+
this.hasNotice = o(e.target), this.handleMainSlotChange();
|
|
114
121
|
}, this.handleOverlayEscape = (e) => {
|
|
115
122
|
let t = this.shadowRoot?.querySelector(".overlay");
|
|
116
123
|
return !this.hasOverlay || !t || !e.composedPath().includes(t) ? !1 : (this.handleOverlayClose(), !0);
|
|
@@ -164,13 +171,13 @@ var N = class extends i {
|
|
|
164
171
|
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);
|
|
165
172
|
}
|
|
166
173
|
disconnectedCallback() {
|
|
167
|
-
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), super.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, super.disconnectedCallback();
|
|
168
175
|
}
|
|
169
176
|
willUpdate(e) {
|
|
170
177
|
super.willUpdate(e), e.has("config") && (this.styles = this.config?.styles);
|
|
171
178
|
}
|
|
172
179
|
updated(e) {
|
|
173
|
-
super.updated(e), 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()));
|
|
180
|
+
super.updated(e), this.observeMainSize(), 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()));
|
|
174
181
|
}
|
|
175
182
|
firstUpdated(e) {
|
|
176
183
|
super.firstUpdated(e), this.warnIfUnsized();
|
|
@@ -237,7 +244,7 @@ var N = class extends i {
|
|
|
237
244
|
|
|
238
245
|
<!-- Main Content -->
|
|
239
246
|
<div class="main-region">
|
|
240
|
-
<main class="main" part="main" scrollable tabindex
|
|
247
|
+
<main class="main" part="main" scrollable tabindex=${this.mainTabStop ? "0" : "-1"} @keydown=${this._handleMainKeydown}>
|
|
241
248
|
<u-progress-bar part="progress" class=${this.routeLoading ? "loading" : ""}
|
|
242
249
|
aria-hidden=${this.routeLoading ? d : "true"}
|
|
243
250
|
aria-label=${a(this.locale || void 0).pageLoading}></u-progress-bar>
|
|
@@ -248,7 +255,7 @@ var N = class extends i {
|
|
|
248
255
|
aria-label=${this.config.noticesAriaLabel ?? d}>
|
|
249
256
|
<slot name="notice" @slotchange=${this.handleNoticeSlotChange}></slot>
|
|
250
257
|
</div>
|
|
251
|
-
<slot></slot>
|
|
258
|
+
<slot @slotchange=${this.handleMainSlotChange}></slot>
|
|
252
259
|
</div>
|
|
253
260
|
</main>
|
|
254
261
|
|
|
@@ -410,6 +417,22 @@ var N = class extends i {
|
|
|
410
417
|
</u-link>
|
|
411
418
|
`;
|
|
412
419
|
}
|
|
420
|
+
observeMainSize() {
|
|
421
|
+
if (this.mainSizeObserver || typeof ResizeObserver > "u") return;
|
|
422
|
+
let e = this.shadowRoot?.querySelector(".main");
|
|
423
|
+
e && (this.mainSizeObserver = new ResizeObserver(() => this.syncMainTabStop()), this.mainSizeObserver.observe(e), this.observeContentSize());
|
|
424
|
+
}
|
|
425
|
+
observeContentSize() {
|
|
426
|
+
let e = this.mainSizeObserver;
|
|
427
|
+
if (e) {
|
|
428
|
+
for (let t of this.observedContent) e.unobserve(t);
|
|
429
|
+
this.observedContent = [...this.noticeContent(), ...this.routeContent()];
|
|
430
|
+
for (let t of this.observedContent) e.observe(t);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
noticeContent() {
|
|
434
|
+
return (this.shadowRoot?.querySelector("slot[name=\"notice\"]"))?.assignedElements({ flatten: !0 }) ?? [];
|
|
435
|
+
}
|
|
413
436
|
routeContent() {
|
|
414
437
|
return (this.shadowRoot?.querySelector(".main-content > slot:not([name])"))?.assignedElements({ flatten: !0 }) ?? [];
|
|
415
438
|
}
|
|
@@ -444,6 +467,6 @@ var N = class extends i {
|
|
|
444
467
|
r([p({
|
|
445
468
|
type: String,
|
|
446
469
|
reflect: !0
|
|
447
|
-
}), n("design:type", Object)], N.prototype, "state", void 0), r([p({ type: Object }), n("design:type", Object)], N.prototype, "config", void 0), r([p({ type: Object }), n("design:type", typeof Record > "u" ? Object : Record)], N.prototype, "counts", void 0), r([p({ type: String }), n("design:type", Object)], N.prototype, "locale", void 0), r([m("u-progress-bar"), n("design:type", S === void 0 ? Object : S)], N.prototype, "progressBarEl", void 0), r([h(), n("design:type", Object)], N.prototype, "hasOverlay", void 0), r([h(), n("design:type", Object)], N.prototype, "openMenu", void 0), r([h(), n("design:type", Object)], N.prototype, "hasNotice", void 0), r([h(), n("design:type", Object)], N.prototype, "routeLoading", void 0), r([h(), n("design:type", Object)], N.prototype, "currentPath", void 0), N = C = r([f("u-sidebar-layout")], N);
|
|
470
|
+
}), n("design:type", Object)], N.prototype, "state", void 0), r([p({ type: Object }), n("design:type", Object)], N.prototype, "config", void 0), r([p({ type: Object }), n("design:type", typeof Record > "u" ? Object : Record)], N.prototype, "counts", void 0), r([p({ type: String }), n("design:type", Object)], N.prototype, "locale", void 0), r([m("u-progress-bar"), n("design:type", S === void 0 ? Object : S)], N.prototype, "progressBarEl", void 0), r([h(), n("design:type", Object)], N.prototype, "hasOverlay", void 0), r([h(), n("design:type", Object)], N.prototype, "openMenu", void 0), r([h(), n("design:type", Object)], N.prototype, "hasNotice", void 0), r([h(), n("design:type", Object)], N.prototype, "routeLoading", void 0), r([h(), n("design:type", Object)], N.prototype, "mainTabStop", void 0), r([h(), n("design:type", Object)], N.prototype, "currentPath", void 0), N = C = r([f("u-sidebar-layout")], N);
|
|
448
471
|
//#endregion
|
|
449
472
|
export { N as SidebarLayout };
|