@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
- function D(e) {
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 O(e, t) {
54
+ function A(e, t) {
38
55
  for (let n of e) if (n instanceof HTMLElement) {
39
- if (D(n)) return n === t;
56
+ if (k(n)) return n === t;
40
57
  if (n === t) break;
41
58
  }
42
59
  return !0;
43
60
  }
44
- function k() {
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 A(e, t) {
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 j = class extends i {
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 = k();
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 || !O(e.composedPath(), n)) return;
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 = k();
354
- n && t.some((e) => A(e, n)) || this.focusContentStart(t, 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 = k();
360
- n && r && A(n, r) || t.focus({ preventScroll: !0 });
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 = k();
369
- t && e.some((e) => A(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();
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 = k();
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)], j.prototype, "state", void 0), r([f({ type: Object }), n("design:type", Object)], j.prototype, "config", void 0), r([f({ type: Object }), n("design:type", typeof Record > "u" ? Object : Record)], j.prototype, "counts", void 0), r([f({ type: String }), n("design:type", Object)], j.prototype, "locale", void 0), r([p("u-progress-bar"), n("design:type", S === void 0 ? Object : S)], j.prototype, "progressBarEl", void 0), r([m(), n("design:type", Object)], j.prototype, "hasOverlay", void 0), r([m(), n("design:type", Object)], j.prototype, "hasNotice", void 0), r([m(), n("design:type", Object)], j.prototype, "context", void 0), j = C = r([d("u-sidebar-layout")], j);
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 { j as SidebarLayout };
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
- height: 100%;
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.0",
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.) Give that container a height — for screen only, so printing is not cut at one page:
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
  }