@iyulab/modern-app 0.57.0 → 0.59.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 +35 -0
- package/dist/components/ActionBar.js +12 -10
- package/dist/components/ActionBar.styles.js +8 -1
- package/dist/components/EmptyState.js +15 -13
- package/dist/components/EmptyState.styles.js +6 -0
- package/dist/components/GroupBox.js +13 -10
- package/dist/components/GroupBox.styles.js +6 -0
- package/dist/components/InfoField.d.ts +3 -0
- package/dist/components/InfoField.js +17 -13
- package/dist/components/InfoField.styles.js +26 -8
- package/dist/components/InfoSection.styles.js +8 -1
- package/dist/components/PageHeader.js +21 -19
- package/dist/components/PageHeader.styles.js +6 -0
- package/dist/components/SidebarLink.styles.js +1 -1
- package/dist/components/SidebarSection.js +12 -10
- package/dist/components/SidebarSection.styles.js +6 -0
- package/dist/layouts/SidebarLayout.d.ts +8 -0
- package/dist/layouts/SidebarLayout.js +79 -73
- package/dist/layouts/SidebarLayout.styles.js +10 -0
- package/dist/react.d.ts +2 -0
- package/package.json +3 -3
- package/skills/modern-app/references/components/action-bar.md +3 -0
- package/skills/modern-app/references/components/empty-state.md +3 -0
- package/skills/modern-app/references/components/group-box.md +3 -0
- package/skills/modern-app/references/components/info-field.md +3 -0
- package/skills/modern-app/references/components/info-section.md +1 -1
- package/skills/modern-app/references/components/page-header.md +3 -0
- package/skills/modern-app/references/layout.md +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.59.0] - 2026-10-23
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`<u-info-section layout="strip">` type-checks in JSX.** The `@iyulab/modern-app/react` augmentation of
|
|
8
|
+
`JSX.IntrinsicElements` declared only `min` for `u-info-section`, so the documented strip layout was a `TS2322`
|
|
9
|
+
for raw-tag JSX consumers. It now takes `layout?: InfoSectionLayout`. Types only — runtime is unchanged. A test now
|
|
10
|
+
compares every augmented tag with the attributes its element declares, so the two cannot drift apart again.
|
|
11
|
+
- **Padding, borders and spacing survive a document-wide reset.** Under Tailwind's preflight
|
|
12
|
+
(`* { padding: 0; border: 0 solid; margin: 0 }`) a `u-info-section layout="strip"` cell lost its padding, a
|
|
13
|
+
`u-info-field appearance="tile"` lost its border and padding, a `u-group-box` lost its border, and `u-empty-state`,
|
|
14
|
+
`u-action-bar` and `u-page-header` lost their padding or the space under them, and a `u-info-field` trend icon
|
|
15
|
+
touched its caption. Document rules beat `:host` and
|
|
16
|
+
`::slotted()` rules whatever their specificity, so a box drawn on the host is a box any reset removes. These elements
|
|
17
|
+
now draw their box on a new `base` part inside the shadow root; the host only takes its place in the layout.
|
|
18
|
+
Restyle the box with `::part(base)` — padding or a border you set on the host now adds outside it. A strip cell that
|
|
19
|
+
is not a `u-info-field` still gets its padding as host padding.
|
|
20
|
+
|
|
21
|
+
## [0.58.0] - 2026-10-23
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **A screen without the shell.** A route with `metadata: { shell: 'hidden' }` takes the whole viewport while it is
|
|
26
|
+
current — no sidebar, mobile header or page padding — for a wall display or a kiosk. The next ordinary route gets
|
|
27
|
+
the chrome back. The layout reflects it as `immersive`.
|
|
28
|
+
- **A screen with its own theme.** `metadata: { theme: 'dark' | 'light' }` applies that theme while the route is
|
|
29
|
+
current without changing or storing the user's choice, and the next route returns to it. Needs `@iyulab/components`
|
|
30
|
+
with `Theme.override` (the next minor).
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **A sidebar link's count meets the text contrast floor under every palette.** It was dimmed with `opacity: 0.8`,
|
|
35
|
+
which fell below 4.5:1 where the resting link colour is already a mid grey (about 4.2:1 for `#5B5E63` on white).
|
|
36
|
+
The count now paints in the label's colour; it stays one type step smaller.
|
|
37
|
+
|
|
3
38
|
## [0.57.0] - 2026-10-23
|
|
4
39
|
|
|
5
40
|
### Added
|
|
@@ -15,17 +15,19 @@ var l = class extends n {
|
|
|
15
15
|
}
|
|
16
16
|
render() {
|
|
17
17
|
return a`
|
|
18
|
-
<div class="
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
<div class="base" part="base">
|
|
19
|
+
<div class="danger ${this.hasDanger ? "" : "empty"}" part="danger">
|
|
20
|
+
<slot name="danger"
|
|
21
|
+
@slotchange=${(e) => this.hasDanger = r(e.target)}
|
|
22
|
+
></slot>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="summary ${this.hasSummary ? "" : "empty"}" part="summary">
|
|
25
|
+
<slot name="summary"
|
|
26
|
+
@slotchange=${(e) => this.hasSummary = r(e.target)}
|
|
27
|
+
></slot>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="main" part="main"><slot></slot></div>
|
|
22
30
|
</div>
|
|
23
|
-
<div class="summary ${this.hasSummary ? "" : "empty"}" part="summary">
|
|
24
|
-
<slot name="summary"
|
|
25
|
-
@slotchange=${(e) => this.hasSummary = r(e.target)}
|
|
26
|
-
></slot>
|
|
27
|
-
</div>
|
|
28
|
-
<div class="main" part="main"><slot></slot></div>
|
|
29
31
|
`;
|
|
30
32
|
}
|
|
31
33
|
};
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { css as e } from "lit";
|
|
2
2
|
// src/components/ActionBar.styles.ts
|
|
3
3
|
var t = e`
|
|
4
|
+
|
|
5
|
+
|
|
4
6
|
:host {
|
|
7
|
+
display: block;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.base {
|
|
5
11
|
display: flex;
|
|
6
12
|
align-items: center;
|
|
7
13
|
gap: var(--u-space-sm, 8px);
|
|
@@ -67,8 +73,9 @@ var t = e`
|
|
|
67
73
|
position: sticky;
|
|
68
74
|
bottom: 0;
|
|
69
75
|
background-color: var(--u-bg-color, #FFFFFF);
|
|
76
|
+
}
|
|
77
|
+
:host([sticky]) .base {
|
|
70
78
|
border-top: 1px solid var(--u-border-color-weak, #EEEEEE);
|
|
71
|
-
padding: var(--u-space-md, 12px) var(--action-bar-padding-inline, 0);
|
|
72
79
|
}
|
|
73
80
|
`;
|
|
74
81
|
export { t as styles };
|
|
@@ -47,19 +47,21 @@ var d = class extends n {
|
|
|
47
47
|
render() {
|
|
48
48
|
let e = this.defaults, t = this.description || e.description;
|
|
49
49
|
return o`
|
|
50
|
-
<div class="
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
<div class="base" part="base">
|
|
51
|
+
<div class="icon" part="icon" aria-hidden="true">
|
|
52
|
+
<slot name="icon">${e.icon}</slot>
|
|
53
|
+
</div>
|
|
54
|
+
<!-- 오류는 경보다 — 그 범위는 이 상태가 전하는 사실 전체(제목 + 사유)다. 사유가 다음 행동(다시 시도 · 권한 요청 ·
|
|
55
|
+
조건 고치기)을 가르므로 제목만 읽히면 듣는 사람이 다른 사실을 받는다. 행동 슬롯은 밖. -->
|
|
56
|
+
<div class="message" part="message" role=${this.variant === "error" ? "alert" : s}>
|
|
57
|
+
<p class="title" part="title">${this.title || e.title}</p>
|
|
58
|
+
${t ? o`<p class="description" part="description">${t}</p>` : s}
|
|
59
|
+
</div>
|
|
60
|
+
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
61
|
+
<slot name="actions"
|
|
62
|
+
@slotchange=${(e) => this.hasActions = r(e.target)}
|
|
63
|
+
></slot>
|
|
64
|
+
</div>
|
|
63
65
|
</div>
|
|
64
66
|
`;
|
|
65
67
|
}
|
|
@@ -34,18 +34,21 @@ var f, p = {
|
|
|
34
34
|
render() {
|
|
35
35
|
let e = !!this.title || !!this.description || this.hasActions;
|
|
36
36
|
return a`
|
|
37
|
-
<div class="
|
|
38
|
-
<div class="
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
<div class="base" part="base">
|
|
38
|
+
<div class="header ${this.divider ? "divider" : ""} ${e ? "" : "empty"}" part="header">
|
|
39
|
+
<div class="titles">
|
|
40
|
+
${u`<${p[this.headingLevel]} class="title" part="title">${this.title}${this.title && this.meta ? a` <span class="meta" part="meta">${this.meta}</span>` : ""}</${p[this.headingLevel]}>`}
|
|
41
|
+
${this.description ? a`<p class="description" part="description">${this.description}</p>` : o}
|
|
42
|
+
</div>
|
|
43
|
+
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
44
|
+
<slot name="actions"
|
|
45
|
+
@slotchange=${(e) => this.hasActions = r(e.target)}
|
|
46
|
+
></slot>
|
|
47
|
+
</div>
|
|
46
48
|
</div>
|
|
49
|
+
<div class="body ${this.flush ? "flush" : ""}" part="body"><slot></slot></div>
|
|
47
50
|
</div>
|
|
48
|
-
|
|
51
|
+
`;
|
|
49
52
|
}
|
|
50
53
|
};
|
|
51
54
|
t([c({ type: String }), e("design:type", Object)], m.prototype, "title", void 0), t([c({ type: String }), e("design:type", String)], m.prototype, "meta", void 0), t([c({ type: String }), e("design:type", String)], m.prototype, "description", void 0), t([c({ type: Boolean }), e("design:type", Object)], m.prototype, "divider", void 0), t([c({ type: Boolean }), e("design:type", Object)], m.prototype, "flush", void 0), t([c({
|
|
@@ -31,6 +31,7 @@ export declare function isBlank(v: unknown): boolean;
|
|
|
31
31
|
* @slot trend-icon - 추세 줄 앞의 상태 표시(예: 주의 아이콘). 추세 줄이 있을 때(`trend` · `trendLabel`) 그린다.
|
|
32
32
|
* @slot visual - 값과 같은 줄 끝의 작은 그림(추세 미니 차트 · 진행 고리 등). 값을 대신하지 않는다 — 장식이면 `aria-hidden="true"`,
|
|
33
33
|
* 그 자체로 정보면 자기 이름(`role="img"` + `aria-label`)을 준다. 비면 자리를 차지하지 않는다.
|
|
34
|
+
* @csspart base - 칸의 상자 — 타일의 면 · 테두리 · 여백과 지표 띠 칸의 여백을 호스트가 아니라 여기서 그린다(문서 전역 리셋에 지지 않게)
|
|
34
35
|
* @csspart label - 라벨
|
|
35
36
|
* @csspart value - 값
|
|
36
37
|
* @csspart unit - 단위
|
|
@@ -141,6 +142,8 @@ export declare class InfoField extends StyledElement<ElementParts> {
|
|
|
141
142
|
private nameSlottedWidgets;
|
|
142
143
|
protected updated(changed: PropertyValues): void;
|
|
143
144
|
/** `visual` 슬롯에 든 것이 있는가 — 비면 그 상자를 그리지 않는다(값 옆에 빈 간격을 남기지 않게). */
|
|
145
|
+
/** 추세 아이콘 슬롯이 찼는가 — 그 옆 간격은 섀도 안 상자가 갖는다(`::slotted` 여백은 문서의 전역 리셋에 진다). */
|
|
146
|
+
private hasTrendIcon;
|
|
144
147
|
private hasVisual;
|
|
145
148
|
private get hasSlotted();
|
|
146
149
|
private formatValue;
|
|
@@ -14,7 +14,7 @@ function f(e) {
|
|
|
14
14
|
}
|
|
15
15
|
var p = class extends n {
|
|
16
16
|
constructor(...e) {
|
|
17
|
-
super(...e), this.label = "", this.blank = "—", this.numeric = !1, this.size = "default", this.appearance = "plain", this.namedWidgets = /* @__PURE__ */ new WeakMap(), this.hasVisual = !1;
|
|
17
|
+
super(...e), this.label = "", this.blank = "—", this.numeric = !1, this.size = "default", this.appearance = "plain", this.namedWidgets = /* @__PURE__ */ new WeakMap(), this.hasTrendIcon = !1, this.hasVisual = !1;
|
|
18
18
|
}
|
|
19
19
|
static {
|
|
20
20
|
this.styles = [super.styles, r];
|
|
@@ -41,21 +41,25 @@ var p = class extends n {
|
|
|
41
41
|
render() {
|
|
42
42
|
let e = !this.hasSlotted && f(this.value), t = e && this.blankReason ? this.blankReason : void 0, n = this.numeric || this.format === "number" || this.format === "currency", r = this.trend !== void 0 || this.trendLabel !== void 0, a = this.tone ?? d(this.trend), o = this.trend === "up" ? "▲" : this.trend === "down" ? "▼" : "", s = !e && this.unit ? i`<span class="unit" part="unit">${this.unit}</span>` : "";
|
|
43
43
|
return i`
|
|
44
|
-
<div class="
|
|
45
|
-
|
|
46
|
-
<div class="value
|
|
47
|
-
|
|
48
|
-
<
|
|
44
|
+
<div class="base" part="base">
|
|
45
|
+
<div class="label" part="label">${this.label}</div>
|
|
46
|
+
<div class="value-row">
|
|
47
|
+
<div class="value ${n ? "numeric" : ""} ${e ? "blank" : ""} ${t ? "reasoned" : ""} tone-${a}" part="value">${this.hasSlotted ? i`<slot @slotchange=${() => void this.nameSlottedWidgets()}></slot>` : e ? this.blank : this.formatValue()}${s}</div>
|
|
48
|
+
<div class="visual" part="visual" ?hidden=${!this.hasVisual}>
|
|
49
|
+
<slot name="visual" @slotchange=${(e) => {
|
|
49
50
|
this.hasVisual = e.target.assignedElements().length > 0;
|
|
50
51
|
}}></slot>
|
|
52
|
+
</div>
|
|
51
53
|
</div>
|
|
54
|
+
${r ? i`
|
|
55
|
+
<div class="trend tone-${a}" part="trend">
|
|
56
|
+
<span class="trend-icon" ?hidden=${!this.hasTrendIcon}><slot name="trend-icon" @slotchange=${(e) => {
|
|
57
|
+
this.hasTrendIcon = e.target.assignedElements().length > 0;
|
|
58
|
+
}}></slot></span>${o ? i`<span aria-hidden="true">${o}</span> ` : ""}${this.trendLabel ?? ""}
|
|
59
|
+
</div>
|
|
60
|
+
` : ""}
|
|
61
|
+
${t ?? this.description ? i`<div class="description" part="description">${t ?? this.description}</div>` : ""}
|
|
52
62
|
</div>
|
|
53
|
-
${r ? i`
|
|
54
|
-
<div class="trend tone-${a}" part="trend">
|
|
55
|
-
<slot name="trend-icon"></slot>${o ? i`<span aria-hidden="true">${o}</span> ` : ""}${this.trendLabel ?? ""}
|
|
56
|
-
</div>
|
|
57
|
-
` : ""}
|
|
58
|
-
${t ?? this.description ? i`<div class="description" part="description">${t ?? this.description}</div>` : ""}
|
|
59
63
|
`;
|
|
60
64
|
}
|
|
61
65
|
};
|
|
@@ -71,5 +75,5 @@ t([o({ type: String }), e("design:type", Object)], p.prototype, "label", void 0)
|
|
|
71
75
|
}), e("design:type", Object)], p.prototype, "appearance", void 0), t([o({
|
|
72
76
|
type: String,
|
|
73
77
|
attribute: "blank-reason"
|
|
74
|
-
}), e("design:type", String)], p.prototype, "blankReason", void 0), t([o({ type: String }), e("design:type", String)], p.prototype, "description", void 0), t([s(), e("design:type", Object)], p.prototype, "hasVisual", void 0), p = t([a("u-info-field")], p);
|
|
78
|
+
}), e("design:type", String)], p.prototype, "blankReason", void 0), t([o({ type: String }), e("design:type", String)], p.prototype, "description", void 0), t([s(), e("design:type", Object)], p.prototype, "hasTrendIcon", void 0), t([s(), e("design:type", Object)], p.prototype, "hasVisual", void 0), p = t([a("u-info-field")], p);
|
|
75
79
|
export { p as InfoField, f as isBlank };
|
|
@@ -6,6 +6,17 @@ var t = e`
|
|
|
6
6
|
min-width: 0;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
.base {
|
|
14
|
+
padding: var(--_info-field-padding, 0);
|
|
15
|
+
}
|
|
16
|
+
.base > * {
|
|
17
|
+
--_info-field-padding: initial;
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
.label {
|
|
10
21
|
font-size: var(--u-text-caption-size, 12px);
|
|
11
22
|
font-weight: var(--u-text-caption-weight, 400);
|
|
@@ -63,28 +74,29 @@ var t = e`
|
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
|
|
66
|
-
:host([appearance="tile"]) {
|
|
67
|
-
|
|
77
|
+
:host([appearance="tile"]) .base {
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
height: 100%;
|
|
80
|
+
padding: var(--_info-field-padding, var(--u-space-md, 12px));
|
|
68
81
|
border: 1px solid var(--u-border-color, #E0E0E0);
|
|
69
82
|
border-radius: var(--u-radius-xl, 8px);
|
|
70
83
|
background-color: var(--u-bg-color, #FFFFFF);
|
|
71
|
-
box-sizing: border-box;
|
|
72
84
|
}
|
|
73
85
|
|
|
74
|
-
:host([appearance="tile"][tone="warning"]) {
|
|
86
|
+
:host([appearance="tile"][tone="warning"]) .base {
|
|
75
87
|
background-color: var(--u-warning-bg-color, #FFF59D);
|
|
76
88
|
border-color: var(--u-warning-color-weak, #FFEB3B);
|
|
77
89
|
}
|
|
78
|
-
:host([appearance="tile"][tone="negative"]) {
|
|
90
|
+
:host([appearance="tile"][tone="negative"]) .base {
|
|
79
91
|
background-color: var(--u-danger-bg-color, #FFEBEE);
|
|
80
92
|
border-color: var(--u-danger-color-weak, #F44336);
|
|
81
93
|
}
|
|
82
|
-
:host([appearance="tile"][tone="positive"]) {
|
|
94
|
+
:host([appearance="tile"][tone="positive"]) .base {
|
|
83
95
|
background-color: var(--u-success-bg-color, #E8F5E9);
|
|
84
96
|
border-color: var(--u-success-color-weak, #4CAF50);
|
|
85
97
|
}
|
|
86
98
|
@media (forced-colors: active) {
|
|
87
|
-
:host([appearance="tile"]) { border-color: CanvasText; }
|
|
99
|
+
:host([appearance="tile"]) .base { border-color: CanvasText; }
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
|
|
@@ -146,8 +158,14 @@ var t = e`
|
|
|
146
158
|
margin-top: var(--u-space-3xs, 2px);
|
|
147
159
|
}
|
|
148
160
|
|
|
149
|
-
|
|
161
|
+
|
|
162
|
+
.trend-icon {
|
|
150
163
|
margin-inline-end: 0.3em;
|
|
164
|
+
}
|
|
165
|
+
.trend-icon[hidden] {
|
|
166
|
+
display: none;
|
|
167
|
+
}
|
|
168
|
+
::slotted([slot="trend-icon"]) {
|
|
151
169
|
vertical-align: -0.125em;
|
|
152
170
|
}
|
|
153
171
|
|
|
@@ -25,11 +25,18 @@ var t = e`
|
|
|
25
25
|
border-radius: var(--u-radius-xl, 8px);
|
|
26
26
|
overflow: hidden;
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
28
31
|
:host([layout="strip"]) ::slotted(*) {
|
|
29
32
|
flex: 1 1 calc((640px - 100%) * 999);
|
|
30
33
|
min-width: 0;
|
|
31
|
-
padding: var(--u-space-md, 12px) var(--u-space-lg, 16px);
|
|
32
34
|
background: var(--u-bg-color, #FFFFFF);
|
|
35
|
+
--_info-field-padding: var(--u-space-md, 12px) var(--u-space-lg, 16px);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
:host([layout="strip"]) ::slotted(:not(u-info-field)) {
|
|
39
|
+
padding: var(--u-space-md, 12px) var(--u-space-lg, 16px);
|
|
33
40
|
}
|
|
34
41
|
`;
|
|
35
42
|
export { t as styles };
|
|
@@ -16,27 +16,29 @@ var d = class extends n {
|
|
|
16
16
|
}
|
|
17
17
|
render() {
|
|
18
18
|
return o`
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<div class="
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
<div class="base" part="base">
|
|
20
|
+
${this.back ? o`<a class="back" part="back" href=${this.back}>${this.backLabel || i(this.locale || void 0).back}</a>` : s}
|
|
21
|
+
<div class="heading" part="heading">
|
|
22
|
+
<div class="eyebrow ${this.eyebrow || this.hasEyebrow ? "" : "empty"}" part="eyebrow">
|
|
23
|
+
<slot name="eyebrow"
|
|
24
|
+
@slotchange=${(e) => this.hasEyebrow = r(e.target)}
|
|
25
|
+
>${this.eyebrow ?? s}</slot>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="title-row">
|
|
28
|
+
<h1 class="title" part="title">${this.title}</h1>
|
|
29
|
+
<span class="status ${this.hasStatus ? "" : "empty"}" part="status">
|
|
30
|
+
<slot name="status"
|
|
31
|
+
@slotchange=${(e) => this.hasStatus = r(e.target)}
|
|
32
|
+
></slot>
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
${this.subtitle ? o`<p class="subtitle" part="subtitle">${this.subtitle}</p>` : s}
|
|
25
36
|
</div>
|
|
26
|
-
<div class="
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@slotchange=${(e) => this.hasStatus = r(e.target)}
|
|
31
|
-
></slot>
|
|
32
|
-
</span>
|
|
37
|
+
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
38
|
+
<slot name="actions"
|
|
39
|
+
@slotchange=${(e) => this.hasActions = r(e.target)}
|
|
40
|
+
></slot>
|
|
33
41
|
</div>
|
|
34
|
-
${this.subtitle ? o`<p class="subtitle" part="subtitle">${this.subtitle}</p>` : s}
|
|
35
|
-
</div>
|
|
36
|
-
<div class="actions ${this.hasActions ? "" : "empty"}" part="actions">
|
|
37
|
-
<slot name="actions"
|
|
38
|
-
@slotchange=${(e) => this.hasActions = r(e.target)}
|
|
39
|
-
></slot>
|
|
40
42
|
</div>
|
|
41
43
|
`;
|
|
42
44
|
}
|
|
@@ -106,13 +106,13 @@ var t = e`
|
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
|
|
109
110
|
.count {
|
|
110
111
|
flex: none;
|
|
111
112
|
margin-inline-start: auto;
|
|
112
113
|
font-size: var(--u-text-overline-size, 11px);
|
|
113
114
|
font-weight: var(--u-text-caption-weight, 400);
|
|
114
115
|
font-variant-numeric: tabular-nums;
|
|
115
|
-
opacity: 0.8;
|
|
116
116
|
}
|
|
117
117
|
.count[compact] {
|
|
118
118
|
position: absolute;
|
|
@@ -14,17 +14,19 @@ var s = class extends n {
|
|
|
14
14
|
}
|
|
15
15
|
render() {
|
|
16
16
|
return i`
|
|
17
|
-
<div class="
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
<div class="base" part="base">
|
|
18
|
+
<div class="header" part="header" ?hidden=${this.compact}>
|
|
19
|
+
<span class="title" part="title">
|
|
20
|
+
${this.mainTitle}
|
|
21
|
+
</span>
|
|
22
|
+
<span class="subtitle" part="subtitle" ?hidden=${!this.subTitle}>
|
|
23
|
+
${this.subTitle}
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
<div class="items" part="items">
|
|
28
|
+
<slot></slot>
|
|
29
|
+
</div>
|
|
28
30
|
</div>
|
|
29
31
|
`;
|
|
30
32
|
}
|
|
@@ -8,6 +8,14 @@ export declare class SidebarLayout extends StyledElement<SidebarParts> {
|
|
|
8
8
|
state: SidebarState;
|
|
9
9
|
/** 사이드바 레이아웃 설정 */
|
|
10
10
|
config?: SidebarLayoutConfig;
|
|
11
|
+
/**
|
|
12
|
+
* The current screen takes the whole viewport: no sidebar, no mobile header, no page padding — a wall
|
|
13
|
+
* display, a kiosk, a full-screen viewer. Set by the route (`metadata: { shell: 'hidden' }`) and cleared when the
|
|
14
|
+
* next screen does not ask for it; the screen itself gives the way back.
|
|
15
|
+
*/
|
|
16
|
+
immersive: boolean;
|
|
17
|
+
/** 지금 화면이 건 일시 테마가 있는가 — 다음 화면이 아무것도 묻지 않으면 걷어 사용자 선호로 돌려준다. */
|
|
18
|
+
private routeTheme;
|
|
11
19
|
/**
|
|
12
20
|
* Live counts for link items, keyed by the item's `href` — overrides the static `count` of the
|
|
13
21
|
* item config. Set through `app.setNavCount(href, value)`; assigning a new object re-renders.
|
|
@@ -12,28 +12,29 @@ import "../components/SidebarGroup.js";
|
|
|
12
12
|
import "../components/SidebarButton.js";
|
|
13
13
|
import { styles as c } from "./SidebarLayout.styles.js";
|
|
14
14
|
import { currentRoutePath as l } from "@iyulab/router";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { Theme as u } from "@iyulab/components/dist/utilities/Theme.js";
|
|
16
|
+
import { html as d, nothing as f } from "lit";
|
|
17
|
+
import { customElement as p, property as m, query as h, state as g } from "lit/decorators.js";
|
|
17
18
|
import "@iyulab/components/dist/components/icon/UIcon.js";
|
|
18
19
|
import "@iyulab/components/dist/components/button/UButton.js";
|
|
19
|
-
import { unsafeHTML as
|
|
20
|
-
import { repeat as
|
|
20
|
+
import { unsafeHTML as _ } from "lit/directives/unsafe-html.js";
|
|
21
|
+
import { repeat as v } from "lit/directives/repeat.js";
|
|
21
22
|
import "@iyulab/components/dist/components/popover/UPopover.js";
|
|
22
23
|
import "@iyulab/components/dist/components/menu/UMenu.js";
|
|
23
24
|
import "@iyulab/components/dist/components/menu-item/UMenuItem.js";
|
|
24
|
-
import { createDevWarner as
|
|
25
|
-
import { isFocusCandidate as
|
|
26
|
-
import { OverlayManager as
|
|
25
|
+
import { createDevWarner as y } from "@iyulab/components/dist/utilities/devWarning.js";
|
|
26
|
+
import { isFocusCandidate as b, querySelectorDeep as x } from "@iyulab/components/dist/utilities/elements.js";
|
|
27
|
+
import { OverlayManager as S } from "@iyulab/components/dist/utilities/OverlayManager.js";
|
|
27
28
|
import "@iyulab/components/dist/components/progress-bar/UProgressBar.js";
|
|
28
29
|
// src/layouts/SidebarLayout.ts
|
|
29
|
-
var
|
|
30
|
-
function
|
|
30
|
+
var C, w = y("@iyulab/modern-app"), T = 0;
|
|
31
|
+
function E(e) {
|
|
31
32
|
let t = e.tagName;
|
|
32
33
|
if (t === "INPUT" || t === "TEXTAREA" || t === "SELECT" || e.isContentEditable) return !0;
|
|
33
34
|
let n = e.getAttribute("role");
|
|
34
35
|
return n === "textbox" || n === "searchbox" || n === "combobox" || n === "spinbutton";
|
|
35
36
|
}
|
|
36
|
-
var
|
|
37
|
+
var D = /* @__PURE__ */ new Set([
|
|
37
38
|
"button",
|
|
38
39
|
"link",
|
|
39
40
|
"checkbox",
|
|
@@ -46,27 +47,27 @@ var E = /* @__PURE__ */ new Set([
|
|
|
46
47
|
"tab",
|
|
47
48
|
"treeitem"
|
|
48
49
|
]);
|
|
49
|
-
function
|
|
50
|
+
function O(e) {
|
|
50
51
|
let t = e.tagName;
|
|
51
|
-
return t === "BUTTON" || t === "SUMMARY" || t === "A" && e.hasAttribute("href") ? !0 :
|
|
52
|
+
return t === "BUTTON" || t === "SUMMARY" || t === "A" && e.hasAttribute("href") ? !0 : D.has(e.getAttribute("role") ?? "");
|
|
52
53
|
}
|
|
53
|
-
function
|
|
54
|
+
function k(e) {
|
|
54
55
|
let t = getComputedStyle(e);
|
|
55
56
|
return (t.overflowY === "auto" || t.overflowY === "scroll") && e.scrollHeight > e.clientHeight;
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
+
function A(e, t) {
|
|
58
59
|
for (let n of e) if (n instanceof HTMLElement) {
|
|
59
|
-
if (
|
|
60
|
+
if (k(n)) return n === t;
|
|
60
61
|
if (n === t) break;
|
|
61
62
|
}
|
|
62
63
|
return !0;
|
|
63
64
|
}
|
|
64
|
-
function
|
|
65
|
+
function j() {
|
|
65
66
|
let e = document.activeElement;
|
|
66
67
|
for (; e?.shadowRoot?.activeElement;) e = e.shadowRoot.activeElement;
|
|
67
68
|
return e;
|
|
68
69
|
}
|
|
69
|
-
function
|
|
70
|
+
function M(e, t) {
|
|
70
71
|
let n = t;
|
|
71
72
|
for (; n;) {
|
|
72
73
|
if (n === e) return !0;
|
|
@@ -74,12 +75,12 @@ function j(e, t) {
|
|
|
74
75
|
}
|
|
75
76
|
return !1;
|
|
76
77
|
}
|
|
77
|
-
var
|
|
78
|
+
var N = class extends n {
|
|
78
79
|
static {
|
|
79
|
-
|
|
80
|
+
C = this;
|
|
80
81
|
}
|
|
81
82
|
constructor(...e) {
|
|
82
|
-
super(...e), 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.mobileHeaderBlockSize = 0, this.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.currentPath = null, this.unsizedWarnKey = `unsized:${++
|
|
83
|
+
super(...e), this.state = "default", this.immersive = !1, this.routeTheme = !1, 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.mobileHeaderBlockSize = 0, 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
84
|
let e = this.screenSize ?? a.screen ?? "large";
|
|
84
85
|
e === "large" ? this.state = this.state === "default" ? "slim" : "default" : e === "medium" ? this.state = this.state === "slim" ? "modal" : "slim" : e === "small" ? this.state = this.state === "mobile" ? "mobile-open" : "mobile" : console.warn("[@iyulab/modern-app] unknown screen size:", e);
|
|
85
86
|
}, this.handleBackdropClick = () => {
|
|
@@ -89,17 +90,19 @@ var M = class extends n {
|
|
|
89
90
|
}, this.handleRouteProgress = (e) => {
|
|
90
91
|
this.progressBarEl.value = e.progress;
|
|
91
92
|
}, this.handleRouteDone = (e) => {
|
|
92
|
-
this.
|
|
93
|
+
this.immersive = e.context.metadata?.shell === "hidden";
|
|
94
|
+
let t = e.context.metadata?.theme, n = t === "dark" || t === "light" ? t : null;
|
|
95
|
+
(n || this.routeTheme) && u.override(n), this.routeTheme = n !== null, this.progressBarEl.value = 100, setTimeout(() => {
|
|
93
96
|
this.routeLoading = !1;
|
|
94
97
|
}, 300);
|
|
95
|
-
let
|
|
96
|
-
|
|
98
|
+
let r = this.shadowRoot?.querySelector(".main");
|
|
99
|
+
r && (this.config?.scrollBehavior?.(e.context, r), this.focusRoute(r), requestAnimationFrame(this.syncMainTabStop));
|
|
97
100
|
}, this.observedContent = [], this.handleMainSlotChange = () => {
|
|
98
101
|
this.observeContentSize(), this.syncMainTabStop();
|
|
99
102
|
}, this.syncMainTabStop = () => {
|
|
100
103
|
let e = this.shadowRoot?.querySelector(".main");
|
|
101
104
|
if (!e) return;
|
|
102
|
-
let t = e.scrollHeight > e.clientHeight + 1 || e.scrollWidth > e.clientWidth + 1, n = t && !!
|
|
105
|
+
let t = e.scrollHeight > e.clientHeight + 1 || e.scrollWidth > e.clientWidth + 1, n = t && !!x([...this.noticeContent(), ...this.routeContent()], "*", (e) => b(e) && e.tabIndex >= 0);
|
|
103
106
|
this.mainTabStop = t && !n;
|
|
104
107
|
}, this.handleSkipLinkClick = (e) => {
|
|
105
108
|
e.preventDefault();
|
|
@@ -112,7 +115,7 @@ var M = class extends n {
|
|
|
112
115
|
}, this.handleOverlaySlotChange = (e) => {
|
|
113
116
|
let t = r(e.target);
|
|
114
117
|
if (t && !this.hasOverlay) {
|
|
115
|
-
let e =
|
|
118
|
+
let e = j();
|
|
116
119
|
this.overlayTrigger = e && e !== document.body ? e : null;
|
|
117
120
|
}
|
|
118
121
|
this.hasOverlay = t;
|
|
@@ -126,9 +129,9 @@ var M = class extends n {
|
|
|
126
129
|
}, this._handleMainKeydown = (e) => {
|
|
127
130
|
if (e.defaultPrevented) return;
|
|
128
131
|
let t = e.composedPath()[0];
|
|
129
|
-
if (t instanceof HTMLElement &&
|
|
132
|
+
if (t instanceof HTMLElement && E(t) || e.key === " " && t instanceof HTMLElement && O(t)) return;
|
|
130
133
|
let n = this.shadowRoot?.querySelector(".main");
|
|
131
|
-
if (!n || !
|
|
134
|
+
if (!n || !A(e.composedPath(), n)) return;
|
|
132
135
|
let r = n.clientHeight;
|
|
133
136
|
switch (e.key) {
|
|
134
137
|
case " ":
|
|
@@ -171,13 +174,13 @@ var M = class extends n {
|
|
|
171
174
|
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);
|
|
172
175
|
}
|
|
173
176
|
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),
|
|
177
|
+
this.routeTheme &&= (u.override(null), !1), 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), S.closeLayer(this), this.mainSizeObserver?.disconnect(), this.mainSizeObserver = void 0, this.mobileHeaderObserver?.disconnect(), this.mobileHeaderObserver = void 0, super.disconnectedCallback();
|
|
175
178
|
}
|
|
176
179
|
willUpdate(e) {
|
|
177
180
|
super.willUpdate(e), e.has("config") && (this.styles = this.config?.styles);
|
|
178
181
|
}
|
|
179
182
|
updated(e) {
|
|
180
|
-
super.updated(e), this.observeMainSize(), this.observeMobileHeader(), e.has("hasOverlay") && (this.internals?.states?.[this.hasOverlay ? "add" : "delete"]("overlay"), this.hasOverlay ? (
|
|
183
|
+
super.updated(e), this.observeMainSize(), this.observeMobileHeader(), e.has("hasOverlay") && (this.internals?.states?.[this.hasOverlay ? "add" : "delete"]("overlay"), this.hasOverlay ? (S.openLayer(this, this.handleOverlayEscape), this.focusOverlay()) : e.get("hasOverlay") === !0 && (S.closeLayer(this), this.restoreOverlayFocus()));
|
|
181
184
|
}
|
|
182
185
|
firstUpdated(e) {
|
|
183
186
|
super.firstUpdated(e), this.warnIfUnsized();
|
|
@@ -186,11 +189,11 @@ var M = class extends n {
|
|
|
186
189
|
process.env.NODE_ENV !== "production" && requestAnimationFrame(() => {
|
|
187
190
|
if (!this.isConnected) return;
|
|
188
191
|
let e = this.getBoundingClientRect().height;
|
|
189
|
-
e >= 200 ||
|
|
192
|
+
e >= 200 || w(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).`);
|
|
190
193
|
});
|
|
191
194
|
}
|
|
192
195
|
render() {
|
|
193
|
-
return this.config ?
|
|
196
|
+
return this.config ? d`
|
|
194
197
|
<!-- 반복 영역 건너뛰기(WCAG 2.4.1) — 셸의 첫 Tab. 포커스될 때만 보인다. -->
|
|
195
198
|
<a class="skip-link" part="skip-link" href="#" @click=${this.handleSkipLinkClick}>
|
|
196
199
|
${i(this.locale || void 0).skipToContent}
|
|
@@ -220,7 +223,7 @@ var M = class extends n {
|
|
|
220
223
|
${this.renderLogo()}
|
|
221
224
|
<span class="title" ?hidden=${this.state === "slim"}>
|
|
222
225
|
${this.config.title}
|
|
223
|
-
${this.config.subtitle ?
|
|
226
|
+
${this.config.subtitle ? d`<span class="subtitle" part="subtitle">${this.config.subtitle}</span>` : f}
|
|
224
227
|
</span>
|
|
225
228
|
<u-button class="toggler" appearance="plain"
|
|
226
229
|
aria-label=${i(this.locale || void 0).toggleSidebar}
|
|
@@ -234,13 +237,13 @@ var M = class extends n {
|
|
|
234
237
|
|
|
235
238
|
<!-- Sidebar Navigation Menu -->
|
|
236
239
|
<nav class="sidebar-main" part="sidebar-main" scrollable
|
|
237
|
-
aria-label=${this.config.mainAriaLabel ??
|
|
238
|
-
${
|
|
240
|
+
aria-label=${this.config.mainAriaLabel ?? f}>
|
|
241
|
+
${v(o(this.config.main ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
|
|
239
242
|
</nav>
|
|
240
243
|
|
|
241
244
|
<!-- Sidebar Footer -->
|
|
242
245
|
<div class="sidebar-footer" part="sidebar-footer">
|
|
243
|
-
${
|
|
246
|
+
${v(o(this.config.footer ?? [], this.config.hasPermission), (e, t) => t, (e) => this.renderItem(e))}
|
|
244
247
|
</div>
|
|
245
248
|
</aside>
|
|
246
249
|
|
|
@@ -248,13 +251,13 @@ var M = class extends n {
|
|
|
248
251
|
<div class="main-region">
|
|
249
252
|
<main class="main" part="main" scrollable tabindex=${this.mainTabStop ? "0" : "-1"} @keydown=${this._handleMainKeydown}>
|
|
250
253
|
<u-progress-bar part="progress" class=${this.routeLoading ? "loading" : ""}
|
|
251
|
-
aria-hidden=${this.routeLoading ?
|
|
254
|
+
aria-hidden=${this.routeLoading ? f : "true"}
|
|
252
255
|
aria-label=${i(this.locale || void 0).pageLoading}></u-progress-bar>
|
|
253
256
|
|
|
254
257
|
<div class="main-content" part="main-content" ?inert=${this.hasOverlay}>
|
|
255
258
|
<div class="notices ${this.hasNotice ? "" : "empty"}" part="notices"
|
|
256
|
-
role=${this.config.noticesAriaLabel ? "region" :
|
|
257
|
-
aria-label=${this.config.noticesAriaLabel ??
|
|
259
|
+
role=${this.config.noticesAriaLabel ? "region" : f}
|
|
260
|
+
aria-label=${this.config.noticesAriaLabel ?? f}>
|
|
258
261
|
<slot name="notice" @slotchange=${this.handleNoticeSlotChange}></slot>
|
|
259
262
|
</div>
|
|
260
263
|
<slot @slotchange=${this.handleMainSlotChange}></slot>
|
|
@@ -275,13 +278,13 @@ var M = class extends n {
|
|
|
275
278
|
<div class="backdrop" ?hidden="${this.state !== "modal"}"
|
|
276
279
|
@click="${this.handleBackdropClick}"
|
|
277
280
|
></div>
|
|
278
|
-
` :
|
|
281
|
+
` : f;
|
|
279
282
|
}
|
|
280
283
|
renderMenu(e) {
|
|
281
284
|
let t = this.menuIds.get(e);
|
|
282
|
-
t || (t = `u-sidebar-menu-${++
|
|
285
|
+
t || (t = `u-sidebar-menu-${++C.menuSeq}`, this.menuIds.set(e, t));
|
|
283
286
|
let n = t, r = this.state === "mobile" || this.state === "mobile-open" ? "bottom-start" : "right-start";
|
|
284
|
-
return
|
|
287
|
+
return d`
|
|
285
288
|
<u-sidebar-button
|
|
286
289
|
id=${n}
|
|
287
290
|
haspopup="menu"
|
|
@@ -300,9 +303,9 @@ var M = class extends n {
|
|
|
300
303
|
@hide=${(e) => this.handleMenuHide(e, n)}
|
|
301
304
|
@pick=${(t) => this.handleMenuPick(t, e)}>
|
|
302
305
|
<u-menu>
|
|
303
|
-
${
|
|
306
|
+
${v(e.items, (e, t) => t, (e, t) => d`
|
|
304
307
|
<u-menu-item value=${String(t)} ?disabled=${e.disabled === !0}>
|
|
305
|
-
${e.icon ?
|
|
308
|
+
${e.icon ? d`<u-icon slot="prefix" .lib=${e.lib} .name=${e.icon}></u-icon>` : f}
|
|
306
309
|
${e.label}
|
|
307
310
|
</u-menu-item>
|
|
308
311
|
`)}
|
|
@@ -316,16 +319,16 @@ var M = class extends n {
|
|
|
316
319
|
}
|
|
317
320
|
handleMenuHide(e, t) {
|
|
318
321
|
this.openMenu === t && (this.openMenu = null);
|
|
319
|
-
let n = e.currentTarget, r =
|
|
320
|
-
r &&
|
|
322
|
+
let n = e.currentTarget, r = j();
|
|
323
|
+
r && M(n, r) && this.shadowRoot?.querySelector(`#${t}`)?.focus();
|
|
321
324
|
}
|
|
322
325
|
renderItem(e) {
|
|
323
|
-
if (!e) return
|
|
326
|
+
if (!e) return f;
|
|
324
327
|
if (e.type === "html") {
|
|
325
328
|
let t = e.render(this.state);
|
|
326
|
-
return typeof t == "string" ?
|
|
329
|
+
return typeof t == "string" ? _(t) : d`${t}`;
|
|
327
330
|
}
|
|
328
|
-
if (e.type === "button") return
|
|
331
|
+
if (e.type === "button") return d`
|
|
329
332
|
<u-sidebar-button
|
|
330
333
|
?compact=${this.state === "slim"}
|
|
331
334
|
.icon="${e.icon}"
|
|
@@ -339,7 +342,7 @@ var M = class extends n {
|
|
|
339
342
|
if (e.type === "menu") return this.renderMenu(e);
|
|
340
343
|
if (e.type === "link") {
|
|
341
344
|
let t = this.isMatchedLink(e.pattern || e.href);
|
|
342
|
-
return
|
|
345
|
+
return d`
|
|
343
346
|
<u-sidebar-link
|
|
344
347
|
?compact=${this.state === "slim"}
|
|
345
348
|
?selected=${t}
|
|
@@ -355,18 +358,18 @@ var M = class extends n {
|
|
|
355
358
|
></u-sidebar-link>
|
|
356
359
|
`;
|
|
357
360
|
}
|
|
358
|
-
if (e.type === "section") return
|
|
361
|
+
if (e.type === "section") return d`
|
|
359
362
|
<u-sidebar-section
|
|
360
363
|
?compact=${this.state === "slim"}
|
|
361
364
|
.mainTitle="${e.title}"
|
|
362
365
|
.subTitle="${e.subTitle}"
|
|
363
366
|
.styles="${e.styles}">
|
|
364
|
-
${
|
|
367
|
+
${v(e.items, (e, t) => t, (e) => this.renderItem(e))}
|
|
365
368
|
</u-sidebar-section>
|
|
366
369
|
`;
|
|
367
370
|
if (e.type === "group") {
|
|
368
371
|
let t = e.items.some((e) => this.isMatchedLink(e.pattern || e.href));
|
|
369
|
-
return
|
|
372
|
+
return d`
|
|
370
373
|
<u-sidebar-group
|
|
371
374
|
?compact=${this.state === "slim"}
|
|
372
375
|
?selected=${t}
|
|
@@ -375,11 +378,11 @@ var M = class extends n {
|
|
|
375
378
|
.lib="${e.lib}"
|
|
376
379
|
.label="${e.label}"
|
|
377
380
|
.styles="${e.styles}">
|
|
378
|
-
${
|
|
381
|
+
${v(e.items, (e, t) => t, (e) => this.renderItem(e))}
|
|
379
382
|
</u-sidebar-group>
|
|
380
383
|
`;
|
|
381
384
|
}
|
|
382
|
-
return
|
|
385
|
+
return f;
|
|
383
386
|
}
|
|
384
387
|
handleButtonItemClick(e, t) {
|
|
385
388
|
let n = e.onClick?.(t);
|
|
@@ -395,19 +398,19 @@ var M = class extends n {
|
|
|
395
398
|
};
|
|
396
399
|
}
|
|
397
400
|
icon(e) {
|
|
398
|
-
return this.config?.icons?.[e] ??
|
|
401
|
+
return this.config?.icons?.[e] ?? C.DEFAULT_ICONS[e];
|
|
399
402
|
}
|
|
400
403
|
renderLogo() {
|
|
401
404
|
let e = this.config?.logo, t = this.config?.title ?? "Home";
|
|
402
|
-
if (!e || typeof e == "string") return e ?
|
|
405
|
+
if (!e || typeof e == "string") return e ? d`
|
|
403
406
|
<u-link class="logo-link" aria-label=${t}>
|
|
404
407
|
<u-icon class="logo"
|
|
405
408
|
.name="${e}"
|
|
406
409
|
.fallback=${s}
|
|
407
410
|
></u-icon>
|
|
408
411
|
</u-link>
|
|
409
|
-
` :
|
|
410
|
-
if (typeof e == "object" && "icon" in e) return
|
|
412
|
+
` : f;
|
|
413
|
+
if (typeof e == "object" && "icon" in e) return d`
|
|
411
414
|
<u-link class="logo-link" aria-label=${t}>
|
|
412
415
|
<u-icon class="logo"
|
|
413
416
|
.lib=${e.lib}
|
|
@@ -418,17 +421,17 @@ var M = class extends n {
|
|
|
418
421
|
`;
|
|
419
422
|
if (typeof e == "function") {
|
|
420
423
|
let n = e(this.state);
|
|
421
|
-
return
|
|
424
|
+
return d`
|
|
422
425
|
<u-link class="logo-link" aria-label=${t}>
|
|
423
426
|
<span class="logo">
|
|
424
427
|
<!-- html-sink: documented HTML contract — a logo function returns markup the developer writes -->
|
|
425
|
-
${typeof n == "string" ?
|
|
428
|
+
${typeof n == "string" ? _(n) : n}
|
|
426
429
|
</span>
|
|
427
430
|
</u-link>
|
|
428
431
|
`;
|
|
429
432
|
}
|
|
430
|
-
return
|
|
431
|
-
<u-link class="logo-link" .href=${e.href} aria-label=${e.alt ?
|
|
433
|
+
return d`
|
|
434
|
+
<u-link class="logo-link" .href=${e.href} aria-label=${e.alt ? f : t}>
|
|
432
435
|
<img class="logo"
|
|
433
436
|
src="${e.src}"
|
|
434
437
|
alt="${e.alt ?? ""}"
|
|
@@ -466,14 +469,14 @@ var M = class extends n {
|
|
|
466
469
|
async focusRoute(e) {
|
|
467
470
|
let t = this.routeContent();
|
|
468
471
|
if (await Promise.all(t.map((e) => e.updateComplete)), this.hasOverlay || !e.isConnected) return;
|
|
469
|
-
let n =
|
|
470
|
-
n && t.some((e) =>
|
|
472
|
+
let n = j();
|
|
473
|
+
n && t.some((e) => M(e, n)) || this.focusContentStart(t, e);
|
|
471
474
|
}
|
|
472
475
|
focusContentStart(e, t) {
|
|
473
|
-
let n =
|
|
476
|
+
let n = x(e, "[autofocus]", b);
|
|
474
477
|
n?.focus();
|
|
475
|
-
let r =
|
|
476
|
-
n && r &&
|
|
478
|
+
let r = j();
|
|
479
|
+
n && r && M(n, r) || t.focus({ preventScroll: !0 });
|
|
477
480
|
}
|
|
478
481
|
overlayPanels() {
|
|
479
482
|
return (this.shadowRoot?.querySelector("slot[name=\"overlay\"]"))?.assignedElements({ flatten: !0 }) ?? [];
|
|
@@ -481,18 +484,21 @@ var M = class extends n {
|
|
|
481
484
|
async focusOverlay() {
|
|
482
485
|
let e = this.overlayPanels();
|
|
483
486
|
if (await Promise.all(e.map((e) => e.updateComplete)), !this.hasOverlay) return;
|
|
484
|
-
let t =
|
|
485
|
-
t && e.some((e) =>
|
|
487
|
+
let t = j();
|
|
488
|
+
t && e.some((e) => M(e, t)) || (x(e, "[autofocus]", b) ?? x(e, "input, select, textarea, u-input, u-textarea, u-select, u-checkbox, u-radio, u-switch, u-slider", b) ?? this.shadowRoot?.querySelector(".overlay-close"))?.focus();
|
|
486
489
|
}
|
|
487
490
|
restoreOverlayFocus() {
|
|
488
491
|
let e = this.overlayTrigger;
|
|
489
492
|
this.overlayTrigger = null;
|
|
490
|
-
let t =
|
|
493
|
+
let t = j();
|
|
491
494
|
t && t !== document.body || (e?.isConnected ? e.focus() : this.mainElement?.focus({ preventScroll: !0 }));
|
|
492
495
|
}
|
|
493
496
|
};
|
|
494
|
-
t([
|
|
497
|
+
t([m({
|
|
495
498
|
type: String,
|
|
496
499
|
reflect: !0
|
|
497
|
-
}), e("design:type", Object)],
|
|
498
|
-
|
|
500
|
+
}), e("design:type", Object)], N.prototype, "state", void 0), t([m({ type: Object }), e("design:type", Object)], N.prototype, "config", void 0), t([m({
|
|
501
|
+
type: Boolean,
|
|
502
|
+
reflect: !0
|
|
503
|
+
}), e("design:type", Object)], N.prototype, "immersive", void 0), t([m({ type: Object }), e("design:type", typeof Record > "u" ? Object : Record)], N.prototype, "counts", void 0), t([m({ type: String }), e("design:type", Object)], N.prototype, "locale", void 0), t([h("u-progress-bar"), e("design:type", Object)], N.prototype, "progressBarEl", void 0), t([g(), e("design:type", Object)], N.prototype, "hasOverlay", void 0), t([g(), e("design:type", Object)], N.prototype, "openMenu", void 0), t([g(), e("design:type", Object)], N.prototype, "hasNotice", void 0), t([g(), e("design:type", Object)], N.prototype, "routeLoading", void 0), t([g(), e("design:type", Object)], N.prototype, "mainTabStop", void 0), t([g(), e("design:type", Object)], N.prototype, "mobileHeaderBlockSize", void 0), t([g(), e("design:type", Object)], N.prototype, "currentPath", void 0), N = C = t([p("u-sidebar-layout")], N);
|
|
504
|
+
export { N as SidebarLayout };
|
|
@@ -19,6 +19,16 @@ var t = e`
|
|
|
19
19
|
:host([state="mobile-open"]) {
|
|
20
20
|
flex-direction: column;
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
:host([immersive]) .sidebar,
|
|
24
|
+
:host([immersive]) .mobile-header,
|
|
25
|
+
:host([immersive]) .backdrop,
|
|
26
|
+
:host([immersive]) .skip-link {
|
|
27
|
+
display: none;
|
|
28
|
+
}
|
|
29
|
+
:host([immersive]) .main {
|
|
30
|
+
padding: 0;
|
|
31
|
+
}
|
|
22
32
|
|
|
23
33
|
.logo {
|
|
24
34
|
color: var(--u-txt-color, #212121);
|
package/dist/react.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Wizard as WizardElement, WizardEventMap } from './components/Wizard.js'
|
|
|
5
5
|
import { GroupBoxLevel } from './components/GroupBox.js';
|
|
6
6
|
import { EmptyStateVariant } from './components/EmptyState.js';
|
|
7
7
|
import { TimelineItemColor } from './components/TimelineItem.js';
|
|
8
|
+
import { InfoSectionLayout } from './components/InfoSection.js';
|
|
8
9
|
import { InfoFieldFormat, InfoFieldSize, InfoFieldTrend, InfoFieldTone, InfoFieldAppearance } from './components/InfoField.js';
|
|
9
10
|
export { ScreenObserver } from './internals/ScreenObserver.js';
|
|
10
11
|
export type { ScreenSize, ScreenObserverConfig, ScreenResizeEvent } from './internals/ScreenObserver.js';
|
|
@@ -100,6 +101,7 @@ declare module 'react' {
|
|
|
100
101
|
};
|
|
101
102
|
'u-info-section': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
|
|
102
103
|
min?: number | string;
|
|
104
|
+
layout?: InfoSectionLayout;
|
|
103
105
|
};
|
|
104
106
|
'u-timeline': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
105
107
|
'u-timeline-item': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
|
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.59.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": ">=2.
|
|
67
|
+
"@iyulab/components": ">=2.32.0",
|
|
68
68
|
"@iyulab/router": "^0.24.0",
|
|
69
69
|
"@lit/react": "^1.0.8",
|
|
70
70
|
"react": "^18.0.0 || ^19.0.0"
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@iyulab/components": "^2.
|
|
81
|
+
"@iyulab/components": "^2.32.0",
|
|
82
82
|
"@iyulab/router": "^0.24.0",
|
|
83
83
|
"@lit/react": "^1.0.8",
|
|
84
84
|
"@types/node": "^26.1.1",
|
|
@@ -36,10 +36,13 @@ rather than negative margins (the bar cannot know its parent's padding).
|
|
|
36
36
|
|
|
37
37
|
| Part | Description |
|
|
38
38
|
|------|-------------|
|
|
39
|
+
| `base` | The bar's box — layout, padding and the sticky top border are drawn here rather than on the host |
|
|
39
40
|
| `main` | Main action group |
|
|
40
41
|
| `danger` | Destructive action group |
|
|
41
42
|
| `summary` | Summary text wrapper |
|
|
42
43
|
|
|
44
|
+
The host only takes its place in the layout. Padding, borders and spacing live on `base` inside the shadow root, so a document-wide reset such as Tailwind's preflight (`* { padding: 0; border: 0 }`) cannot strip them — document rules beat `:host` rules whatever their specificity. Restyle the box with `::part(base)`.
|
|
45
|
+
|
|
43
46
|
## CSS Custom Properties
|
|
44
47
|
|
|
45
48
|
| Property | Description |
|
|
@@ -50,9 +50,12 @@ action is **asking for access**.
|
|
|
50
50
|
|
|
51
51
|
| Part | Description |
|
|
52
52
|
|------|-------------|
|
|
53
|
+
| `base` | The block's box — layout and padding are drawn here rather than on the host |
|
|
53
54
|
| `icon` · `title` · `description` · `actions` | The four regions |
|
|
54
55
|
| `message` | Wraps `title` and `description` — the alert of `variant="error"` |
|
|
55
56
|
|
|
57
|
+
The host only takes its place in the layout. Padding, borders and spacing live on `base` inside the shadow root, so a document-wide reset such as Tailwind's preflight (`* { padding: 0; border: 0 }`) cannot strip them — document rules beat `:host` rules whatever their specificity. Restyle the box with `::part(base)`.
|
|
58
|
+
|
|
56
59
|
## CSS Custom Properties
|
|
57
60
|
|
|
58
61
|
| Property | Description |
|
|
@@ -46,11 +46,14 @@ do).
|
|
|
46
46
|
|
|
47
47
|
| Part | Description |
|
|
48
48
|
|------|-------------|
|
|
49
|
+
| `base` | The card's box — surface, border, corner and shadow are drawn here rather than on the host |
|
|
49
50
|
| `header` · `title` · `actions` | Header row |
|
|
50
51
|
| `meta` | Secondary text inside the title |
|
|
51
52
|
| `description` | One-line description under the title |
|
|
52
53
|
| `body` | Body wrapper |
|
|
53
54
|
|
|
55
|
+
The host only takes its place in the layout. Padding, borders and spacing live on `base` inside the shadow root, so a document-wide reset such as Tailwind's preflight (`* { padding: 0; border: 0 }`) cannot strip them — document rules beat `:host` rules whatever their specificity. Restyle the box with `::part(base)`.
|
|
56
|
+
|
|
54
57
|
## CSS Custom Properties
|
|
55
58
|
|
|
56
59
|
| Property | Description |
|
|
@@ -130,11 +130,14 @@ name (`role="img"` and `aria-label`) (`0.57.0~`):
|
|
|
130
130
|
|
|
131
131
|
| Part | Description |
|
|
132
132
|
|------|-------------|
|
|
133
|
+
| `base` | The field's box — a tile's surface, border and padding (and a strip cell's padding) are drawn here rather than on the host |
|
|
133
134
|
| `label` · `value` · `trend` | The label, the value, and the (optional) trend indicator |
|
|
134
135
|
| `unit` | The unit, inside `value` (only when `unit` is set and the value is not blank) |
|
|
135
136
|
| `description` | The supporting line (when `description` is set, or the value is blank and `blankReason` is set) |
|
|
136
137
|
| `visual` | The box holding the `visual` slot (not drawn when the slot is empty) |
|
|
137
138
|
|
|
139
|
+
The host only takes its place in the layout. Padding, borders and spacing live on `base` inside the shadow root, so a document-wide reset such as Tailwind's preflight (`* { padding: 0; border: 0 }`) cannot strip them — document rules beat `:host` rules whatever their specificity. Restyle the box with `::part(base)`.
|
|
140
|
+
|
|
138
141
|
## CSS Custom Properties
|
|
139
142
|
|
|
140
143
|
| Property | Description |
|
|
@@ -51,7 +51,7 @@ A stat strip — the totals above a list, as cells rather than a sentence:
|
|
|
51
51
|
|----------|-------------|
|
|
52
52
|
| `--u-space-lg` / `--u-space-xl` | Grid gap (row / column) |
|
|
53
53
|
| `--u-border-color` · `--u-radius-xl` · `--u-bg-color` | Strip surface, dividers and corner; cell background |
|
|
54
|
-
| `--u-space-md` / `--u-space-lg` | Strip cell padding (block / inline) |
|
|
54
|
+
| `--u-space-md` / `--u-space-lg` | Strip cell padding (block / inline) — a `u-info-field` cell draws it inside its own `base` part, so a document-wide reset cannot strip it; any other child element gets it as host padding |
|
|
55
55
|
|
|
56
56
|
⚠ Column width is set via the `min` property, not a custom property — it's applied as an inline
|
|
57
57
|
style per instance, so a `--info-min` override in a stylesheet would lose to it.
|
|
@@ -39,12 +39,15 @@ screens nobody opens.
|
|
|
39
39
|
|
|
40
40
|
| Part | Description |
|
|
41
41
|
|------|-------------|
|
|
42
|
+
| `base` | The header's box — layout and the space under it are drawn here rather than on the host |
|
|
42
43
|
| `back` | Back link |
|
|
43
44
|
| `heading` | Eyebrow + title + subtitle wrapper |
|
|
44
45
|
| `eyebrow` | Eyebrow row (hidden when empty) |
|
|
45
46
|
| `title` · `subtitle` | Text elements |
|
|
46
47
|
| `status` · `actions` | Slot wrappers |
|
|
47
48
|
|
|
49
|
+
The host only takes its place in the layout. Padding, borders and spacing live on `base` inside the shadow root, so a document-wide reset such as Tailwind's preflight (`* { padding: 0; border: 0 }`) cannot strip them — document rules beat `:host` rules whatever their specificity. Restyle the box with `::part(base)`.
|
|
50
|
+
|
|
48
51
|
## CSS Custom Properties
|
|
49
52
|
|
|
50
53
|
| Property | Description |
|
|
@@ -448,6 +448,30 @@ on a component such as `UInput`) — `@iyulab/router` 0.16.1 and later report `r
|
|
|
448
448
|
React has committed the screen. While the overlay is open the shell does none of this: the route
|
|
449
449
|
underneath is inert and focus stays in the panel.
|
|
450
450
|
|
|
451
|
+
## A screen without the shell
|
|
452
|
+
|
|
453
|
+
A screen that owns the whole viewport — a wall display on the production floor, a kiosk, a full-screen viewer —
|
|
454
|
+
asks for it in its route. While that screen is current the shell draws no sidebar, no mobile header and no page
|
|
455
|
+
padding; the next screen that does not ask gets them back.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
routes: [
|
|
459
|
+
{ path: 'board', metadata: { shell: 'hidden' }, render: () => html`<floor-board></floor-board>` },
|
|
460
|
+
]
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The screen gives the way back itself (a link to the menu) — the shell has nothing on screen to return with. The
|
|
464
|
+
state is the reflected `immersive` attribute on `u-sidebar-layout` (`0.58.0~`).
|
|
465
|
+
|
|
466
|
+
A screen can also own its background: `metadata: { theme: 'dark' }` (or `'light'`) applies that theme while the screen
|
|
467
|
+
is current, without changing or storing the user's choice (`Theme.override` from `@iyulab/components`), and the next
|
|
468
|
+
screen that does not ask returns to it. A floor board stays dark on the TV and in a browser where the user picked
|
|
469
|
+
light.
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
{ path: 'board', metadata: { shell: 'hidden', theme: 'dark' }, render: () => html`<floor-board></floor-board>` }
|
|
473
|
+
```
|
|
474
|
+
|
|
451
475
|
## Printing
|
|
452
476
|
|
|
453
477
|
On print media the shell drops its app chrome and lets the content flow across pages: the
|