@iyulab/modern-app 0.23.0 → 0.24.0
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,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.24.0] - 2026-09-22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The shell draws its own chrome without reaching the network.** The mobile and sidebar
|
|
8
|
+
toggles were hard-coded to `lib="bootstrap"`, which `@iyulab/components` resolves over a CDN.
|
|
9
|
+
An app that registers its own bundled icons still could not reach those two, because the shell
|
|
10
|
+
never routed through that registration — so on a network-isolated deployment the toggles
|
|
11
|
+
stayed blank and a failing request repeated on every render. Working around it from outside
|
|
12
|
+
meant intercepting a globally registered library name, with an unclear blast radius. Both
|
|
13
|
+
toggles now resolve from the bundled internal set, as the overlay close button already did.
|
|
14
|
+
- That asymmetry was a small defect of its own: one shell drew two different close glyphs, a
|
|
15
|
+
CDN-backed one in the mobile header and a bundled one on the overlay. **The visible change is
|
|
16
|
+
a slightly different stroke on three controls.**
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`icons` config.** Overrides both the source and the individual names, per key. It takes
|
|
21
|
+
names as well as the library because the defaults are bundled-set names and another set is not
|
|
22
|
+
guaranteed to carry them — pointing the shell at your own icons needs both halves. See
|
|
23
|
+
`docs/configuration.md`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- 🔴 **The `@iyulab/components` peer range is now `>=1.43.0`** (was `>=1.42.0`). The shell's
|
|
28
|
+
chrome depends on `menu-2` and `layout-sidebar` existing in that package's built-in set, and
|
|
29
|
+
those names arrive in **1.43.0**. The old range admitted 1.42.x, which would have let
|
|
30
|
+
`components@1.42.4` install alongside this version with no warning at all — and then the shell
|
|
31
|
+
icons would be blank whether or not the deployment had network access, which is a wider
|
|
32
|
+
failure than the one this release fixes. Publishing in the right order does not prevent that:
|
|
33
|
+
ordering is a property of how we release, while the range is what a consumer is allowed to
|
|
34
|
+
install.
|
|
35
|
+
|
|
36
|
+
### Notes
|
|
37
|
+
|
|
38
|
+
- The regression test resolves each default name **against the bundle** rather than only
|
|
39
|
+
asserting which name the shell asks for. Asserting the name alone would let a typo blank the
|
|
40
|
+
icon with every assertion still passing.
|
|
41
|
+
|
|
3
42
|
## [0.23.0] - 2026-09-21
|
|
4
43
|
|
|
5
44
|
### Fixed
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type * from './types/AppConfigs';
|
|
|
3
3
|
export type * from './types/AppOptions';
|
|
4
4
|
export type * from './types/AuthConfig';
|
|
5
5
|
export type { SidebarPermissionGuard } from './layouts/SidebarPermission';
|
|
6
|
+
export type { SidebarIconsConfig } from './layouts/SidebarLayout.types';
|
|
6
7
|
export { filterSidebarItems } from './layouts/filterSidebarItems.js';
|
|
7
8
|
export { PageHeader } from './components/PageHeader.js';
|
|
8
9
|
export { GroupBox } from './components/GroupBox.js';
|
|
@@ -55,6 +55,18 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
55
55
|
* 🔴로고는 홈(또는 `href`)으로 가는 **링크**다 — 종전엔 클릭만 받는 `img`·`span`·`u-icon` 이라 키보드로 닿지 않았고
|
|
56
56
|
* 보조기술에 링크로 드러나지 않았다. `u-link` 는 `href` 가 없으면 basepath 로 SPA 이동하므로 종전 `app.navigate('')` 와 같다.
|
|
57
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* 셸이 **자기 chrome 으로** 그리는 아이콘의 기본값 — 전부 `internal` 번들이라 네트워크를 타지 않는다.
|
|
60
|
+
*
|
|
61
|
+
* 🔴**0.24.0 이전에는 토글러 둘이 `bootstrap`(= jsdelivr CDN 조회)이었다.** 소비앱이 자기
|
|
62
|
+
* 아이콘을 빌드 시점에 전부 구워 등록해도 **셸이 쓰는 것만은 그 등록을 타지 않아**, 폐쇄망
|
|
63
|
+
* 배포에서 사이드바 토글이 빈 채로 남고 매번 실패하는 요청이 쌓였다. ⚠그리고 셸이 스스로
|
|
64
|
+
* 갈려 있었다 — 오버레이 닫기는 `internal`, 모바일 닫기는 `bootstrap` 이라 **한 셸이 서로
|
|
65
|
+
* 다른 X 를 둘 그렸다.**
|
|
66
|
+
*/
|
|
67
|
+
private static readonly DEFAULT_ICONS;
|
|
68
|
+
/** 설정이 준 값이 있으면 그것을, 없으면 기본값을 돌려준다(키 단위 부분 오버라이드). */
|
|
69
|
+
private icon;
|
|
58
70
|
private renderLogo;
|
|
59
71
|
/** 사이드바 토글 핸들러 */
|
|
60
72
|
private handleToggleButtonClick;
|
|
@@ -21,27 +21,30 @@ import { ifDefined as _ } from "lit/directives/if-defined.js";
|
|
|
21
21
|
import { createDevWarner as v } from "@iyulab/components/dist/utilities/devWarning.js";
|
|
22
22
|
import { UProgressBar as y } from "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
|
|
23
23
|
//#region src/layouts/SidebarLayout.ts
|
|
24
|
-
var b = v("@iyulab/modern-app"),
|
|
25
|
-
function
|
|
24
|
+
var b, x = v("@iyulab/modern-app"), S = 0;
|
|
25
|
+
function C(e) {
|
|
26
26
|
let t = e.tagName;
|
|
27
27
|
if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
|
|
28
28
|
let n = e.getAttribute("role");
|
|
29
29
|
return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
|
|
30
30
|
}
|
|
31
|
-
function
|
|
31
|
+
function w(e) {
|
|
32
32
|
let t = getComputedStyle(e);
|
|
33
33
|
return (t.overflowY === "auto" || t.overflowY === "scroll") && e.scrollHeight > e.clientHeight;
|
|
34
34
|
}
|
|
35
|
-
function
|
|
35
|
+
function T(e, t) {
|
|
36
36
|
for (let n of e) if (n instanceof HTMLElement) {
|
|
37
|
-
if (
|
|
37
|
+
if (w(n)) return n === t;
|
|
38
38
|
if (n === t) break;
|
|
39
39
|
}
|
|
40
40
|
return !0;
|
|
41
41
|
}
|
|
42
|
-
var
|
|
42
|
+
var E = class extends i {
|
|
43
|
+
static {
|
|
44
|
+
b = this;
|
|
45
|
+
}
|
|
43
46
|
constructor(...t) {
|
|
44
|
-
super(...t), this.state = "default", this.locale = "", this.hasOverlay = !1, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.context = null, this.unsizedWarnKey = `unsized:${++
|
|
47
|
+
super(...t), this.state = "default", this.locale = "", this.hasOverlay = !1, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.context = null, this.unsizedWarnKey = `unsized:${++S}`, 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 = () => {
|
|
45
48
|
let t = e.screen ?? "large";
|
|
46
49
|
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);
|
|
47
50
|
}, this.handleBackdropClick = () => {
|
|
@@ -66,9 +69,9 @@ var T = class extends i {
|
|
|
66
69
|
this.fire("overlay-close", { cancelable: !1 });
|
|
67
70
|
}, this._handleMainKeydown = (e) => {
|
|
68
71
|
let t = e.composedPath()[0];
|
|
69
|
-
if (t instanceof HTMLElement &&
|
|
72
|
+
if (t instanceof HTMLElement && C(t)) return;
|
|
70
73
|
let n = this.shadowRoot?.querySelector(".main");
|
|
71
|
-
if (!n || !
|
|
74
|
+
if (!n || !T(e.composedPath(), n)) return;
|
|
72
75
|
let r = n.clientHeight;
|
|
73
76
|
switch (e.key) {
|
|
74
77
|
case " ":
|
|
@@ -123,7 +126,7 @@ var T = class extends i {
|
|
|
123
126
|
process.env.NODE_ENV !== "production" && requestAnimationFrame(() => {
|
|
124
127
|
if (!this.isConnected) return;
|
|
125
128
|
let e = this.getBoundingClientRect().height;
|
|
126
|
-
e >= 200 ||
|
|
129
|
+
e >= 200 || x(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).`);
|
|
127
130
|
});
|
|
128
131
|
}
|
|
129
132
|
render() {
|
|
@@ -138,8 +141,8 @@ var T = class extends i {
|
|
|
138
141
|
aria-label=${a(this.locale || void 0).toggleMobileMenu}
|
|
139
142
|
@click=${this.handleToggleButtonClick}>
|
|
140
143
|
<u-icon
|
|
141
|
-
lib
|
|
142
|
-
name=${this.state === "mobile-open" ? "
|
|
144
|
+
lib=${this.icon("lib")}
|
|
145
|
+
name=${this.state === "mobile-open" ? this.icon("close") : this.icon("menu")}
|
|
143
146
|
></u-icon>
|
|
144
147
|
</u-button>
|
|
145
148
|
</div>
|
|
@@ -156,8 +159,8 @@ var T = class extends i {
|
|
|
156
159
|
aria-label=${a(this.locale || void 0).toggleSidebar}
|
|
157
160
|
@click=${this.handleToggleButtonClick}>
|
|
158
161
|
<u-icon
|
|
159
|
-
lib
|
|
160
|
-
name
|
|
162
|
+
lib=${this.icon("lib")}
|
|
163
|
+
name=${this.icon("sidebarToggle")}
|
|
161
164
|
></u-icon>
|
|
162
165
|
</u-button>
|
|
163
166
|
</div>
|
|
@@ -188,7 +191,7 @@ var T = class extends i {
|
|
|
188
191
|
<u-button class="overlay-close" part="overlay-close" variant="ghost"
|
|
189
192
|
aria-label=${a(this.locale || void 0).detailClose}
|
|
190
193
|
@click=${this.handleOverlayClose}>
|
|
191
|
-
<u-icon lib
|
|
194
|
+
<u-icon lib=${this.icon("lib")} name=${this.icon("overlayClose")}></u-icon>
|
|
192
195
|
</u-button>
|
|
193
196
|
<slot name="overlay" @slotchange=${this.handleOverlaySlotChange}></slot>
|
|
194
197
|
</div>
|
|
@@ -260,6 +263,18 @@ var T = class extends i {
|
|
|
260
263
|
}
|
|
261
264
|
return u;
|
|
262
265
|
}
|
|
266
|
+
static {
|
|
267
|
+
this.DEFAULT_ICONS = {
|
|
268
|
+
lib: "internal",
|
|
269
|
+
menu: "menu-2",
|
|
270
|
+
close: "x",
|
|
271
|
+
sidebarToggle: "layout-sidebar",
|
|
272
|
+
overlayClose: "x"
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
icon(e) {
|
|
276
|
+
return this.config?.icons?.[e] ?? b.DEFAULT_ICONS[e];
|
|
277
|
+
}
|
|
263
278
|
renderLogo() {
|
|
264
279
|
let e = this.config?.logo, t = this.config?.title ?? "Home";
|
|
265
280
|
if (!e || typeof e == "string") return e ? l`
|
|
@@ -293,6 +308,6 @@ var T = class extends i {
|
|
|
293
308
|
r([f({
|
|
294
309
|
type: String,
|
|
295
310
|
reflect: !0
|
|
296
|
-
}), n("design:type", Object)],
|
|
311
|
+
}), n("design:type", Object)], E.prototype, "state", void 0), r([f({ type: Object }), n("design:type", Object)], E.prototype, "config", void 0), r([f({ type: String }), n("design:type", Object)], E.prototype, "locale", void 0), r([p("u-progress-bar"), n("design:type", y === void 0 ? Object : y)], E.prototype, "progressBarEl", void 0), r([m(), n("design:type", Object)], E.prototype, "hasOverlay", void 0), r([m(), n("design:type", Object)], E.prototype, "context", void 0), E = b = r([d("u-sidebar-layout")], E);
|
|
297
312
|
//#endregion
|
|
298
|
-
export {
|
|
313
|
+
export { E as SidebarLayout };
|
|
@@ -11,6 +11,34 @@ export type { SidebarLinkConfig } from '../components/SidebarLink';
|
|
|
11
11
|
export type { SidebarSectionConfig } from '../components/SidebarSection';
|
|
12
12
|
export type { SidebarGroupConfig } from '../components/SidebarGroup';
|
|
13
13
|
export type { SidebarButtonConfig } from '../components/SidebarButton';
|
|
14
|
+
/**
|
|
15
|
+
* 셸이 **자기 chrome 으로** 그리는 아이콘의 출처와 이름. 지정한 키만 기본값을 대체한다.
|
|
16
|
+
*
|
|
17
|
+
* 🔴**기본값은 네트워크를 타지 않는다.** `internal` 은 `@iyulab/components` 가 빌드 시점에
|
|
18
|
+
* 굽는 번들이라, 폐쇄망·오프라인 배포에서도 셸 아이콘이 비지 않는다. 0.24.0 이전에는 셸의
|
|
19
|
+
* 토글러 둘이 `bootstrap`(= jsdelivr CDN 조회)을 썼고, 소비자가 자기 아이콘을 전부 번들로
|
|
20
|
+
* 구워 등록해도 **셸이 쓰는 것만은 그 등록을 타지 않았다.**
|
|
21
|
+
*
|
|
22
|
+
* ⚠**`lib` 만 바꿀 때는 이름도 함께 봐야 한다** — 기본 이름들은 `internal` 번들의 이름이고,
|
|
23
|
+
* 다른 lib 이 같은 이름을 갖는다는 보장이 없다. 자기 아이콘 세트로 갈아끼울 때는 보통
|
|
24
|
+
* `lib` 과 이름을 함께 준다:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* icons: { lib: 'app', menu: 'list', close: 'x-lg', sidebarToggle: 'layout-sidebar' }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export interface SidebarIconsConfig {
|
|
31
|
+
/** 아래 이름들을 해석할 아이콘 lib. 기본 `'internal'`(번들 — 네트워크 없음). */
|
|
32
|
+
lib?: string;
|
|
33
|
+
/** 모바일 헤더의 메뉴 «열기». 기본 `'menu-2'`. */
|
|
34
|
+
menu?: string;
|
|
35
|
+
/** 모바일 헤더의 메뉴 «닫기». 기본 `'x'`. */
|
|
36
|
+
close?: string;
|
|
37
|
+
/** 사이드바 접기/펼치기 토글. 기본 `'layout-sidebar'`. */
|
|
38
|
+
sidebarToggle?: string;
|
|
39
|
+
/** 오버레이 닫기 버튼. 기본 `'x'`. */
|
|
40
|
+
overlayClose?: string;
|
|
41
|
+
}
|
|
14
42
|
/** 사이드바 레이아웃 컴포넌트의 요소(part) 타입 */
|
|
15
43
|
export type SidebarParts = 'host' | 'mobile-header' | 'sidebar' | 'sidebar-header' | 'sidebar-main' | 'sidebar-footer' | 'main' | 'main-content' | 'progress' | 'overlay' | 'overlay-close';
|
|
16
44
|
/** 사이드바 상태 타입 */
|
|
@@ -64,6 +92,11 @@ export interface SidebarLayoutConfig {
|
|
|
64
92
|
* "아무것도 안 함" — Vue Router의 `scrollBehavior` 미지정 기본값과 동일하다(breaking 아님).
|
|
65
93
|
*/
|
|
66
94
|
scrollBehavior?: (context: RouteContext, main: HTMLElement) => void;
|
|
95
|
+
/**
|
|
96
|
+
* 셸 chrome 아이콘의 출처·이름 오버라이드. 지정한 키만 기본값을 대체한다.
|
|
97
|
+
* 기본값은 전부 `internal` 번들이라 **네트워크를 타지 않는다** — `SidebarIconsConfig` 참조.
|
|
98
|
+
*/
|
|
99
|
+
icons?: SidebarIconsConfig;
|
|
67
100
|
/** 사이드바 스타일 맵 */
|
|
68
101
|
styles?: StyleMap<SidebarParts>;
|
|
69
102
|
}
|
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.
|
|
4
|
+
"version": "0.24.0",
|
|
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.43.0",
|
|
68
68
|
"@lit/react": ">=1.0.8",
|
|
69
69
|
"react": ">=18.0.0"
|
|
70
70
|
},
|
|
@@ -27,6 +27,9 @@ interface SidebarLayoutConfig {
|
|
|
27
27
|
/** Called on `route-done` (before focus moves to the container) with the new route's `RouteContext` and the scroll container itself — implement reset/save/restore here. Unset does nothing (same default as Vue Router's unset `scrollBehavior`). Also reachable outside the hook via the element's `.mainElement` accessor. */
|
|
28
28
|
scrollBehavior?: (context: RouteContext, main: HTMLElement) => void;
|
|
29
29
|
|
|
30
|
+
/** Shell chrome icon source and names — `{ lib?, menu?, close?, sidebarToggle?, overlayClose? }`, only the keys you set replace the defaults. Defaults come from the bundled `internal` set, so the shell needs no network to draw its own toggles; give `lib` **and** the names to point it at your own set. */
|
|
31
|
+
icons?: SidebarIconsConfig;
|
|
32
|
+
|
|
30
33
|
/** Per-part style overrides (CSS custom properties / inline styles). */
|
|
31
34
|
styles?: StyleMap<SidebarParts>;
|
|
32
35
|
}
|