@iyulab/modern-app 0.30.1 → 0.30.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,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.30.2] - 2026-09-28
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **An overlay panel that is a component now gets focus inside it.** The shell looked for the
|
|
8
|
+
panel's `[autofocus]` element and first input control with `querySelector`, which stops at shadow
|
|
9
|
+
roots — so when the panel was a custom element rendering its controls in its own template, the
|
|
10
|
+
shell found neither and fell back to the close button. The search now goes through shadow roots
|
|
11
|
+
and waits for the panel's first render, as route focus does since 0.30.1.
|
|
12
|
+
- **Route and overlay focus skip a `u-select`'s closed popover.** Searching through shadow roots
|
|
13
|
+
(0.30.1) also reached the popover every `u-select` keeps inside, which carries an `autofocus`
|
|
14
|
+
attribute of its own — a screen with a `u-select` before its `autofocus` field got the content
|
|
15
|
+
area instead. Only elements that can actually take focus are candidates now.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Requires `@iyulab/components` 1.47.1 or later** (peer) — the shell now uses its
|
|
20
|
+
`querySelectorDeep` / `isFocusCandidate`, the same lookup `u-dialog` and `u-drawer` use.
|
|
21
|
+
|
|
3
22
|
## [0.30.1] - 2026-09-28
|
|
4
23
|
|
|
5
24
|
### Fixed
|
|
@@ -122,6 +122,7 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
122
122
|
* `[autofocus]` → 첫 입력 컨트롤 → (없으면) 셸의 닫기 버튼.
|
|
123
123
|
* 닫기 버튼으로 떨어지는 것은 의도다 — 포커스가 `<body>` 에 남는 것보다 항상 낫고,
|
|
124
124
|
* 셸이 늘 가진 유일한 컨트롤이다. 소비자가 이미 패널 안으로 옮겨 두었으면 건드리지 않는다.
|
|
125
|
+
* 탐색은 섀도 경계 안쪽까지 한다 — 패널이 컴포넌트면 컨트롤은 그 섀도 루트에 있다.
|
|
125
126
|
*/
|
|
126
127
|
private focusOverlay;
|
|
127
128
|
/**
|
|
@@ -19,32 +19,33 @@ import { unsafeHTML as h } from "lit/directives/unsafe-html.js";
|
|
|
19
19
|
import { repeat as g } from "lit/directives/repeat.js";
|
|
20
20
|
import { ifDefined as _ } from "lit/directives/if-defined.js";
|
|
21
21
|
import { createDevWarner as v } from "@iyulab/components/dist/utilities/devWarning.js";
|
|
22
|
-
import {
|
|
22
|
+
import { isFocusCandidate as y, querySelectorDeep as b } from "@iyulab/components/dist/utilities/elements.js";
|
|
23
|
+
import { UProgressBar as x } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
|
|
23
24
|
//#region src/layouts/SidebarLayout.ts
|
|
24
|
-
var
|
|
25
|
-
function
|
|
25
|
+
var S, C = v("@iyulab/modern-app"), w = 0;
|
|
26
|
+
function T(e) {
|
|
26
27
|
let t = e.tagName;
|
|
27
28
|
if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
|
|
28
29
|
let n = e.getAttribute("role");
|
|
29
30
|
return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
|
|
30
31
|
}
|
|
31
|
-
function
|
|
32
|
+
function E(e) {
|
|
32
33
|
let t = getComputedStyle(e);
|
|
33
34
|
return (t.overflowY === "auto" || t.overflowY === "scroll") && e.scrollHeight > e.clientHeight;
|
|
34
35
|
}
|
|
35
|
-
function
|
|
36
|
+
function D(e, t) {
|
|
36
37
|
for (let n of e) if (n instanceof HTMLElement) {
|
|
37
|
-
if (
|
|
38
|
+
if (E(n)) return n === t;
|
|
38
39
|
if (n === t) break;
|
|
39
40
|
}
|
|
40
41
|
return !0;
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
+
function O() {
|
|
43
44
|
let e = document.activeElement;
|
|
44
45
|
for (; e?.shadowRoot?.activeElement;) e = e.shadowRoot.activeElement;
|
|
45
46
|
return e;
|
|
46
47
|
}
|
|
47
|
-
function
|
|
48
|
+
function k(e, t) {
|
|
48
49
|
let n = t;
|
|
49
50
|
for (; n;) {
|
|
50
51
|
if (n === e) return !0;
|
|
@@ -52,33 +53,12 @@ function D(e, t) {
|
|
|
52
53
|
}
|
|
53
54
|
return !1;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
let n = (e) => {
|
|
57
|
-
let r = e instanceof Element ? [e] : Array.from(e.children);
|
|
58
|
-
for (let e of r) {
|
|
59
|
-
let r = document.createTreeWalker(e, NodeFilter.SHOW_ELEMENT);
|
|
60
|
-
for (let e = r.currentNode; e; e = r.nextNode()) {
|
|
61
|
-
if (e.matches(t)) return e;
|
|
62
|
-
if (e.shadowRoot) {
|
|
63
|
-
let t = n(e.shadowRoot);
|
|
64
|
-
if (t) return t;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
};
|
|
70
|
-
for (let t of e) {
|
|
71
|
-
let e = n(t);
|
|
72
|
-
if (e) return e;
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
var k = class extends i {
|
|
56
|
+
var A = class extends i {
|
|
77
57
|
static {
|
|
78
|
-
|
|
58
|
+
S = this;
|
|
79
59
|
}
|
|
80
60
|
constructor(...t) {
|
|
81
|
-
super(...t), this.state = "default", this.locale = "", this.hasOverlay = !1, this.hasNotice = !1, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.context = null, this.unsizedWarnKey = `unsized:${++
|
|
61
|
+
super(...t), this.state = "default", this.locale = "", this.hasOverlay = !1, this.hasNotice = !1, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.context = null, this.unsizedWarnKey = `unsized:${++w}`, 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 = () => {
|
|
82
62
|
let t = this.screenSize ?? e.screen ?? "large";
|
|
83
63
|
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);
|
|
84
64
|
}, this.handleBackdropClick = () => {
|
|
@@ -100,7 +80,7 @@ var k = class extends i {
|
|
|
100
80
|
}, this.handleOverlaySlotChange = (e) => {
|
|
101
81
|
let t = o(e.target);
|
|
102
82
|
if (t && !this.hasOverlay) {
|
|
103
|
-
let e =
|
|
83
|
+
let e = O();
|
|
104
84
|
this.overlayTrigger = e && e !== document.body ? e : null;
|
|
105
85
|
}
|
|
106
86
|
this.hasOverlay = t;
|
|
@@ -116,9 +96,9 @@ var k = class extends i {
|
|
|
116
96
|
this.fire("overlay-close", { cancelable: !1 });
|
|
117
97
|
}, this._handleMainKeydown = (e) => {
|
|
118
98
|
let t = e.composedPath()[0];
|
|
119
|
-
if (t instanceof HTMLElement &&
|
|
99
|
+
if (t instanceof HTMLElement && T(t)) return;
|
|
120
100
|
let n = this.shadowRoot?.querySelector(".main");
|
|
121
|
-
if (!n || !
|
|
101
|
+
if (!n || !D(e.composedPath(), n)) return;
|
|
122
102
|
let r = n.clientHeight;
|
|
123
103
|
switch (e.key) {
|
|
124
104
|
case " ":
|
|
@@ -173,7 +153,7 @@ var k = class extends i {
|
|
|
173
153
|
process.env.NODE_ENV !== "production" && requestAnimationFrame(() => {
|
|
174
154
|
if (!this.isConnected) return;
|
|
175
155
|
let e = this.getBoundingClientRect().height;
|
|
176
|
-
e >= 200 ||
|
|
156
|
+
e >= 200 || C(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).`);
|
|
177
157
|
});
|
|
178
158
|
}
|
|
179
159
|
render() {
|
|
@@ -325,7 +305,7 @@ var k = class extends i {
|
|
|
325
305
|
};
|
|
326
306
|
}
|
|
327
307
|
icon(e) {
|
|
328
|
-
return this.config?.icons?.[e] ??
|
|
308
|
+
return this.config?.icons?.[e] ?? S.DEFAULT_ICONS[e];
|
|
329
309
|
}
|
|
330
310
|
renderLogo() {
|
|
331
311
|
let e = this.config?.logo, t = this.config?.title ?? "Home";
|
|
@@ -362,39 +342,32 @@ var k = class extends i {
|
|
|
362
342
|
async focusRoute(e) {
|
|
363
343
|
let t = this.routeContent();
|
|
364
344
|
if (await Promise.all(t.map((e) => e.updateComplete)), this.hasOverlay || !e.isConnected) return;
|
|
365
|
-
let n =
|
|
366
|
-
if (n && t.some((e) =>
|
|
367
|
-
let r =
|
|
345
|
+
let n = O();
|
|
346
|
+
if (n && t.some((e) => k(e, n))) return;
|
|
347
|
+
let r = b(t, "[autofocus]", y);
|
|
368
348
|
r?.focus();
|
|
369
|
-
let i =
|
|
370
|
-
r && i &&
|
|
349
|
+
let i = O();
|
|
350
|
+
r && i && k(r, i) || e.focus({ preventScroll: !0 });
|
|
371
351
|
}
|
|
372
352
|
overlayPanels() {
|
|
373
353
|
return (this.shadowRoot?.querySelector("slot[name=\"overlay\"]"))?.assignedElements({ flatten: !0 }) ?? [];
|
|
374
354
|
}
|
|
375
|
-
focusOverlay() {
|
|
376
|
-
let e = this.overlayPanels()
|
|
377
|
-
if (
|
|
378
|
-
let
|
|
379
|
-
|
|
380
|
-
if (n.matches(t)) return n;
|
|
381
|
-
let e = n.querySelector(t);
|
|
382
|
-
if (e) return e;
|
|
383
|
-
}
|
|
384
|
-
return null;
|
|
385
|
-
};
|
|
386
|
-
(n("[autofocus]") ?? n("input, select, textarea, u-input, u-textarea, u-select, u-checkbox, u-radio, u-switch, u-slider") ?? this.shadowRoot?.querySelector(".overlay-close"))?.focus();
|
|
355
|
+
async focusOverlay() {
|
|
356
|
+
let e = this.overlayPanels();
|
|
357
|
+
if (await Promise.all(e.map((e) => e.updateComplete)), !this.hasOverlay) return;
|
|
358
|
+
let t = O();
|
|
359
|
+
t && e.some((e) => k(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();
|
|
387
360
|
}
|
|
388
361
|
restoreOverlayFocus() {
|
|
389
362
|
let e = this.overlayTrigger;
|
|
390
363
|
this.overlayTrigger = null;
|
|
391
|
-
let t =
|
|
364
|
+
let t = O();
|
|
392
365
|
t && t !== document.body || (e?.isConnected ? e.focus() : this.mainElement?.focus({ preventScroll: !0 }));
|
|
393
366
|
}
|
|
394
367
|
};
|
|
395
368
|
r([f({
|
|
396
369
|
type: String,
|
|
397
370
|
reflect: !0
|
|
398
|
-
}), n("design:type", Object)],
|
|
371
|
+
}), n("design:type", Object)], A.prototype, "state", void 0), r([f({ type: Object }), n("design:type", Object)], A.prototype, "config", void 0), r([f({ type: String }), n("design:type", Object)], A.prototype, "locale", void 0), r([p("u-progress-bar"), n("design:type", x === void 0 ? Object : x)], A.prototype, "progressBarEl", void 0), r([m(), n("design:type", Object)], A.prototype, "hasOverlay", void 0), r([m(), n("design:type", Object)], A.prototype, "hasNotice", void 0), r([m(), n("design:type", Object)], A.prototype, "context", void 0), A = S = r([d("u-sidebar-layout")], A);
|
|
399
372
|
//#endregion
|
|
400
|
-
export {
|
|
373
|
+
export { A 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.30.
|
|
4
|
+
"version": "0.30.2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iyulab",
|
|
7
7
|
"web-framework",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"lit": "^3.3.3"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"@iyulab/components": ">=1.
|
|
67
|
+
"@iyulab/components": ">=1.47.1",
|
|
68
68
|
"@lit/react": ">=1.0.8",
|
|
69
69
|
"react": ">=18.0.0"
|
|
70
70
|
},
|
|
@@ -89,6 +89,6 @@
|
|
|
89
89
|
"vite": "^8.1.4",
|
|
90
90
|
"vite-plugin-dts": "^5.0.3",
|
|
91
91
|
"vitest": "^5.0.0",
|
|
92
|
-
"@iyulab/components": ">=1.
|
|
92
|
+
"@iyulab/components": ">=1.47.1"
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -414,7 +414,7 @@ from either re-implementing what the shell already does, or dropping what it doe
|
|
|
414
414
|
| Making route content non-interactive | shell | `inert` on `part="main-content"`, which propagates through the slot into your route content |
|
|
415
415
|
| A close affordance | shell | `part="overlay-close"`, firing the non-cancelable `overlay-close` event |
|
|
416
416
|
| Keeping the route mounted underneath | shell | the overlay is independent of routing |
|
|
417
|
-
| Moving focus into the panel when it opens | shell | an `[autofocus]` element in your panel, else its first input control, else `part="overlay-close"
|
|
417
|
+
| Moving focus into the panel when it opens | shell | an `[autofocus]` element in your panel, else its first input control, else `part="overlay-close"` — searched through shadow roots, after the panel's first render, so a panel that is itself a component works the same. If you already moved focus into the panel, the shell leaves it there |
|
|
418
418
|
| Restoring focus when it closes | shell | back to the control that held focus when the panel opened — only if focus fell to `<body>`; if you moved it somewhere on purpose, that stands. With no such control (opened from code), focus goes to `part="main"` |
|
|
419
419
|
| Escape to close | shell | Escape inside the panel fires the same `overlay-close` as the button — you still empty the slot. The shell decides **after** every layer inside the panel has: an Escape consumed there (`preventDefault()` — an open list, popover, drawer or dialog, whenever it was opened) closes only that layer. The shell does not mark the Escape consumed itself |
|
|
420
420
|
| **A backdrop / dimmed scrim** | **consumer** | the panel is opaque and full-bleed by design; add a scrim inside your panel if you want one |
|