@iyulab/modern-app 0.34.0 → 0.34.2
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,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.34.2] - 2026-10-06
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The content area's scroll keys yield to the control that owns them** (`SidebarLayout`). With focus
|
|
8
|
+
on a button in the content, Space scrolled the page by a screen and the button never fired; with
|
|
9
|
+
focus in a widget that moves with the arrows itself (a tree, a menu, a radio group, a grid), each
|
|
10
|
+
arrow moved inside the widget and also scrolled the content. A key a widget already handled is left
|
|
11
|
+
alone, and Space on a button or link presses it.
|
|
12
|
+
|
|
13
|
+
## [0.34.1] - 2026-10-05
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **The sidebar footer was cut off when the menu was longer than the screen** and the shell's parent
|
|
18
|
+
had no height of its own (a custom `root`, or `SidebarLayout` mounted from React). The shell is
|
|
19
|
+
capped at the viewport, but the sidebar took `height: 100%`, which cannot resolve against a cap, so
|
|
20
|
+
it grew to the menu's length and the shell clipped the footer out of reach. The sidebar now takes
|
|
21
|
+
the shell's height, the menu scrolls inside `part="sidebar-main"`, and the footer stays at the
|
|
22
|
+
bottom — in the default, slim, drawer and mobile states.
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
- The layout guide now says that a custom `root` also keeps the browser's default body margin, and
|
|
27
|
+
shows `:where(body) { margin: 0; }` next to the height rule.
|
|
28
|
+
|
|
3
29
|
## [0.34.0] - 2026-10-05
|
|
4
30
|
|
|
5
31
|
### Added
|
|
@@ -30,23 +30,40 @@ function E(e) {
|
|
|
30
30
|
let n = e.getAttribute("role");
|
|
31
31
|
return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
var D = /* @__PURE__ */ new Set([
|
|
34
|
+
"button",
|
|
35
|
+
"link",
|
|
36
|
+
"checkbox",
|
|
37
|
+
"switch",
|
|
38
|
+
"radio",
|
|
39
|
+
"menuitem",
|
|
40
|
+
"menuitemcheckbox",
|
|
41
|
+
"menuitemradio",
|
|
42
|
+
"option",
|
|
43
|
+
"tab",
|
|
44
|
+
"treeitem"
|
|
45
|
+
]);
|
|
46
|
+
function O(e) {
|
|
47
|
+
let t = e.tagName;
|
|
48
|
+
return t === "BUTTON" || t === "SUMMARY" || t === "A" && e.hasAttribute("href") ? !0 : D.has(e.getAttribute("role") ?? "");
|
|
49
|
+
}
|
|
50
|
+
function k(e) {
|
|
34
51
|
let t = getComputedStyle(e);
|
|
35
52
|
return (t.overflowY === "auto" || t.overflowY === "scroll") && e.scrollHeight > e.clientHeight;
|
|
36
53
|
}
|
|
37
|
-
function
|
|
54
|
+
function A(e, t) {
|
|
38
55
|
for (let n of e) if (n instanceof HTMLElement) {
|
|
39
|
-
if (
|
|
56
|
+
if (k(n)) return n === t;
|
|
40
57
|
if (n === t) break;
|
|
41
58
|
}
|
|
42
59
|
return !0;
|
|
43
60
|
}
|
|
44
|
-
function
|
|
61
|
+
function j() {
|
|
45
62
|
let e = document.activeElement;
|
|
46
63
|
for (; e?.shadowRoot?.activeElement;) e = e.shadowRoot.activeElement;
|
|
47
64
|
return e;
|
|
48
65
|
}
|
|
49
|
-
function
|
|
66
|
+
function M(e, t) {
|
|
50
67
|
let n = t;
|
|
51
68
|
for (; n;) {
|
|
52
69
|
if (n === e) return !0;
|
|
@@ -54,7 +71,7 @@ function A(e, t) {
|
|
|
54
71
|
}
|
|
55
72
|
return !1;
|
|
56
73
|
}
|
|
57
|
-
var
|
|
74
|
+
var N = class extends i {
|
|
58
75
|
static {
|
|
59
76
|
C = this;
|
|
60
77
|
}
|
|
@@ -85,7 +102,7 @@ var j = class extends i {
|
|
|
85
102
|
}, this.handleOverlaySlotChange = (e) => {
|
|
86
103
|
let t = o(e.target);
|
|
87
104
|
if (t && !this.hasOverlay) {
|
|
88
|
-
let e =
|
|
105
|
+
let e = j();
|
|
89
106
|
this.overlayTrigger = e && e !== document.body ? e : null;
|
|
90
107
|
}
|
|
91
108
|
this.hasOverlay = t;
|
|
@@ -97,10 +114,11 @@ var j = class extends i {
|
|
|
97
114
|
}, this.handleOverlayClose = () => {
|
|
98
115
|
this.fire("overlay-close", { cancelable: !1 });
|
|
99
116
|
}, this._handleMainKeydown = (e) => {
|
|
117
|
+
if (e.defaultPrevented) return;
|
|
100
118
|
let t = e.composedPath()[0];
|
|
101
|
-
if (t instanceof HTMLElement && E(t)) return;
|
|
119
|
+
if (t instanceof HTMLElement && E(t) || e.key === " " && t instanceof HTMLElement && O(t)) return;
|
|
102
120
|
let n = this.shadowRoot?.querySelector(".main");
|
|
103
|
-
if (!n || !
|
|
121
|
+
if (!n || !A(e.composedPath(), n)) return;
|
|
104
122
|
let r = n.clientHeight;
|
|
105
123
|
switch (e.key) {
|
|
106
124
|
case " ":
|
|
@@ -350,14 +368,14 @@ var j = class extends i {
|
|
|
350
368
|
async focusRoute(e) {
|
|
351
369
|
let t = this.routeContent();
|
|
352
370
|
if (await Promise.all(t.map((e) => e.updateComplete)), this.hasOverlay || !e.isConnected) return;
|
|
353
|
-
let n =
|
|
354
|
-
n && t.some((e) =>
|
|
371
|
+
let n = j();
|
|
372
|
+
n && t.some((e) => M(e, n)) || this.focusContentStart(t, e);
|
|
355
373
|
}
|
|
356
374
|
focusContentStart(e, t) {
|
|
357
375
|
let n = b(e, "[autofocus]", y);
|
|
358
376
|
n?.focus();
|
|
359
|
-
let r =
|
|
360
|
-
n && r &&
|
|
377
|
+
let r = j();
|
|
378
|
+
n && r && M(n, r) || t.focus({ preventScroll: !0 });
|
|
361
379
|
}
|
|
362
380
|
overlayPanels() {
|
|
363
381
|
return (this.shadowRoot?.querySelector("slot[name=\"overlay\"]"))?.assignedElements({ flatten: !0 }) ?? [];
|
|
@@ -365,19 +383,19 @@ var j = class extends i {
|
|
|
365
383
|
async focusOverlay() {
|
|
366
384
|
let e = this.overlayPanels();
|
|
367
385
|
if (await Promise.all(e.map((e) => e.updateComplete)), !this.hasOverlay) return;
|
|
368
|
-
let t =
|
|
369
|
-
t && e.some((e) =>
|
|
386
|
+
let t = j();
|
|
387
|
+
t && e.some((e) => M(e, t)) || (b(e, "[autofocus]", y) ?? b(e, "input, select, textarea, u-input, u-textarea, u-select, u-checkbox, u-radio, u-switch, u-slider", y) ?? this.shadowRoot?.querySelector(".overlay-close"))?.focus();
|
|
370
388
|
}
|
|
371
389
|
restoreOverlayFocus() {
|
|
372
390
|
let e = this.overlayTrigger;
|
|
373
391
|
this.overlayTrigger = null;
|
|
374
|
-
let t =
|
|
392
|
+
let t = j();
|
|
375
393
|
t && t !== document.body || (e?.isConnected ? e.focus() : this.mainElement?.focus({ preventScroll: !0 }));
|
|
376
394
|
}
|
|
377
395
|
};
|
|
378
396
|
r([f({
|
|
379
397
|
type: String,
|
|
380
398
|
reflect: !0
|
|
381
|
-
}), n("design:type", Object)],
|
|
399
|
+
}), n("design:type", Object)], N.prototype, "state", void 0), r([f({ type: Object }), n("design:type", Object)], N.prototype, "config", void 0), r([f({ type: Object }), n("design:type", typeof Record > "u" ? Object : Record)], N.prototype, "counts", void 0), r([f({ type: String }), n("design:type", Object)], N.prototype, "locale", void 0), r([p("u-progress-bar"), n("design:type", S === void 0 ? Object : S)], N.prototype, "progressBarEl", void 0), r([m(), n("design:type", Object)], N.prototype, "hasOverlay", void 0), r([m(), n("design:type", Object)], N.prototype, "hasNotice", void 0), r([m(), n("design:type", Object)], N.prototype, "context", void 0), N = C = r([d("u-sidebar-layout")], N);
|
|
382
400
|
//#endregion
|
|
383
|
-
export {
|
|
401
|
+
export { N as SidebarLayout };
|
|
@@ -120,7 +120,12 @@ var t = e`
|
|
|
120
120
|
flex-direction: column;
|
|
121
121
|
justify-content: space-between;
|
|
122
122
|
gap: 8px;
|
|
123
|
-
|
|
123
|
+
/* 높이는 백분율이 아니라 flex 의 stretch 로 받는다 — 셸의 높이가 «부모의 확정 높이» 가 아니라
|
|
124
|
+
max-height 상한에서 나오면 height: 100% 는 풀리지 않아(auto) 사이드바가 메뉴 길이로 커지고,
|
|
125
|
+
셸의 overflow: hidden 이 아래쪽 계정 영역을 잘라 닿을 수 없게 된다. 늘어난 항목의 높이는
|
|
126
|
+
그 경우에도 셸의 (상한으로 잘린) 높이다. .main-region 과 같은 해법이다. 떠 있는 상태
|
|
127
|
+
(modal · mobile*)는 절대배치라 top/bottom 으로 채운다. */
|
|
128
|
+
min-height: 0;
|
|
124
129
|
/* --u-panel-bg-color 가 아니라 --u-bg-color-raised — 사이드바는 카드·메뉴류의
|
|
125
130
|
"떠 있는 패널"이 아니라 상시 크롬이고, 그 구분이 정확히 두 토큰이 갈리는 지점이다
|
|
126
131
|
(light.css 293~298행 참조). 미설정 시 페이지(--u-bg-color)와 같은 흰색으로 앉아
|
|
@@ -146,11 +151,13 @@ var t = e`
|
|
|
146
151
|
width: var(--app-sidebar-width, 260px);
|
|
147
152
|
position: absolute;
|
|
148
153
|
top: 0;
|
|
154
|
+
bottom: 0;
|
|
149
155
|
left: 0;
|
|
150
156
|
}
|
|
151
157
|
.sidebar[state="mobile"] {
|
|
152
158
|
position: absolute;
|
|
153
159
|
top: 0;
|
|
160
|
+
bottom: 0;
|
|
154
161
|
left: 0;
|
|
155
162
|
right: 0;
|
|
156
163
|
transform: translateY(-100%);
|
|
@@ -158,6 +165,7 @@ var t = e`
|
|
|
158
165
|
.sidebar[state="mobile-open"] {
|
|
159
166
|
position: absolute;
|
|
160
167
|
top: 0;
|
|
168
|
+
bottom: 0;
|
|
161
169
|
left: 0;
|
|
162
170
|
right: 0;
|
|
163
171
|
transform: translateY(0);
|
|
@@ -194,6 +202,8 @@ var t = e`
|
|
|
194
202
|
/* Sidebar Main Menu */
|
|
195
203
|
.sidebar-main {
|
|
196
204
|
flex: 1;
|
|
205
|
+
/* 열 방향 flex 항목의 자동 최소 높이는 내용 길이다 — 0 으로 풀어야 남는 높이 안에서 스크롤한다. */
|
|
206
|
+
min-height: 0;
|
|
197
207
|
display: flex;
|
|
198
208
|
flex-direction: column;
|
|
199
209
|
gap: 4px;
|
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.34.
|
|
4
|
+
"version": "0.34.2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-framework",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"preversion": "node -e \"if(require('fs').existsSync('../../scripts/preversion-check.mjs'))require('child_process').execFileSync('node',['../../scripts/preversion-check.mjs'],{stdio:'inherit'})\"",
|
|
53
|
+
"pretest": "node -e \"if(require('fs').existsSync('../../scripts/sibling-dist-check.js'))require('child_process').execFileSync('node',['../../scripts/sibling-dist-check.js'],{stdio:'inherit'})\"",
|
|
53
54
|
"test": "vitest run",
|
|
54
55
|
"test:watch": "vitest",
|
|
55
56
|
"start": "vite",
|
|
@@ -304,9 +304,12 @@ rule of your own wins.
|
|
|
304
304
|
`height: 100%` has nothing to resolve against: the shell renders at whatever its own chrome
|
|
305
305
|
resolves to (measured: about 133px) instead of filling the screen — no error, only a
|
|
306
306
|
development-mode console warning when it is shorter than 200px. (With a long screen the shell grows
|
|
307
|
-
to the viewport cap instead, so the symptom only shows on short screens.)
|
|
307
|
+
to the viewport cap instead, so the symptom only shows on short screens.) The body margin is not
|
|
308
|
+
removed either, so the browser's default 8px stays around the shell. Give that container a height —
|
|
309
|
+
for screen only, so printing is not cut at one page — and clear the margin:
|
|
308
310
|
|
|
309
311
|
```css
|
|
312
|
+
:where(body) { margin: 0; }
|
|
310
313
|
@media screen {
|
|
311
314
|
#app { height: 100vh; } /* or 100%, inside an already-constrained ancestor */
|
|
312
315
|
}
|