@iyulab/modern-app 0.53.1 → 0.55.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 +40 -0
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.154.0}/helpers/esm/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.154.0}/helpers/esm/decorateMetadata.js +1 -1
- package/dist/components/ActionBar.d.ts +8 -3
- package/dist/components/ActionBar.js +9 -4
- package/dist/components/ActionBar.styles.js +14 -2
- package/dist/components/EmptyState.js +2 -2
- package/dist/components/GroupBox.js +2 -2
- package/dist/components/InfoField.d.ts +5 -0
- package/dist/components/InfoField.js +4 -3
- package/dist/components/InfoField.styles.js +8 -0
- package/dist/components/InfoSection.d.ts +9 -0
- package/dist/components/InfoSection.js +7 -4
- package/dist/components/InfoSection.styles.js +18 -0
- package/dist/components/MasterDetailLayout.d.ts +9 -1
- package/dist/components/MasterDetailLayout.js +16 -8
- package/dist/components/MasterDetailLayout.styles.js +17 -0
- package/dist/components/PageHeader.js +2 -2
- package/dist/components/SidebarButton.js +2 -2
- package/dist/components/SidebarGroup.js +2 -2
- package/dist/components/SidebarGroup.styles.js +0 -3
- package/dist/components/SidebarLink.js +2 -2
- package/dist/components/SidebarSection.js +2 -2
- package/dist/components/Timeline.js +1 -1
- package/dist/components/TimelineItem.js +2 -2
- package/dist/components/Wizard.js +2 -2
- package/dist/internals/StyledElement.js +2 -2
- package/dist/layouts/SidebarLayout.js +4 -4
- package/dist/react.d.ts +15 -2
- package/dist/react.js +7 -1
- package/dist/unsaved.d.ts +7 -1
- package/dist/unsaved.js +9 -3
- package/package.json +1 -1
- package/skills/modern-app/SKILL.md +2 -1
- package/skills/modern-app/references/components/action-bar.md +15 -7
- package/skills/modern-app/references/components/info-field.md +2 -0
- package/skills/modern-app/references/components/info-section.md +14 -1
- package/skills/modern-app/references/components/master-detail-layout.md +21 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.55.0] - 2026-10-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `u-master-detail-layout` `detail-size`: the detail pane takes that width and the master fills the rest — a wide
|
|
8
|
+
list with a record summary beside it. `masterSize` keeps the opposite shape (narrow list, wide detail).
|
|
9
|
+
- `u-master-detail-layout` `slot="detail-placeholder"`: shown in the detail place while nothing is chosen (side by side).
|
|
10
|
+
- `useUnsavedChanges(dirty)` returns `{ markClean }`, and `UnsavedChangesController` has `markClean()`: the current state
|
|
11
|
+
is saved, so navigations until the next render (React) or update (Lit) do not ask. A save handler that navigates in
|
|
12
|
+
the same tick — `await post(draft); navigate(...)` — was asked «Discard unsaved changes?», because the check still
|
|
13
|
+
read the pre-save render’s `dirty`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Console diagnostics begin with `[@iyulab/modern-app]`, so a message in a console shared by several packages says which one wrote it (several also say what to do next).
|
|
18
|
+
- **Breaking:** `u-master-detail-layout`: while the `detail` slot is empty the master takes the whole width (it stayed at
|
|
19
|
+
`masterSize` beside an empty area). To keep the narrow master with a message beside it, use `detail-placeholder`.
|
|
20
|
+
|
|
21
|
+
### Removed
|
|
22
|
+
|
|
23
|
+
- An unused custom property on `u-sidebar-group` (`--group-accent`) — nothing read it; group colours come from the
|
|
24
|
+
`--app-sidebar-group-*` tokens.
|
|
25
|
+
|
|
26
|
+
## [0.54.0] - 2026-10-14
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **`u-action-bar` `summary` slot** — what the main action will do («1 change · Order number»). It follows the
|
|
31
|
+
danger actions and fills the free width; below 420px it takes its own line between the main actions (on top) and
|
|
32
|
+
the danger actions. Label-size, weak text.
|
|
33
|
+
- `--action-bar-padding-inline` — inline padding for a bar that runs to a side sheet's edges (default `0`).
|
|
34
|
+
- **`u-info-section layout="strip"` — a stat strip.** The totals of a result as cells on one bordered surface, split
|
|
35
|
+
by 1px dividers: all side by side, or all stacked once the strip is narrower than 640px — never three plus one.
|
|
36
|
+
- `u-info-field` `description` — a supporting line below the value (its basis, or the same amount in another unit).
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- The `u-action-bar` skill reference placed the main actions at the leading edge and the danger actions at the
|
|
41
|
+
trailing edge — the reverse of what the bar does. It now describes the actual layout.
|
|
42
|
+
|
|
3
43
|
## [0.53.1] - 2026-10-13
|
|
4
44
|
|
|
5
45
|
### Fixed
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
1
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorate.js
|
|
2
2
|
function e(e, t, n, r) {
|
|
3
3
|
var i = arguments.length, a = i < 3 ? t : r === null ? r = Object.getOwnPropertyDescriptor(t, n) : r, o;
|
|
4
4
|
if (typeof Reflect == "object" && typeof Reflect.decorate == "function") a = Reflect.decorate(e, t, n, r);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
1
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/decorateMetadata.js
|
|
2
2
|
function e(e, t) {
|
|
3
3
|
if (typeof Reflect == "object" && typeof Reflect.metadata == "function") return Reflect.metadata(e, t);
|
|
4
4
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
|
-
type ElementParts = 'host' | 'danger' | 'main';
|
|
2
|
+
type ElementParts = 'host' | 'danger' | 'summary' | 'main';
|
|
3
3
|
/**
|
|
4
4
|
* 푸터 액션 바 — 상세·편집 화면 맨 아래의 같은 골격.
|
|
5
5
|
*
|
|
@@ -17,10 +17,13 @@ type ElementParts = 'host' | 'danger' | 'main';
|
|
|
17
17
|
* ```
|
|
18
18
|
*
|
|
19
19
|
* 배치 규칙: **위험 액션은 왼쪽 끝, 주 액션은 오른쪽 끝** — 거리가 안전장치다.
|
|
20
|
-
* 좁아지면
|
|
20
|
+
* 좁아지면 무리마다 한 줄을 갖고 **주 액션이 위로** 온다(엄지 위치에 위험 액션을
|
|
21
21
|
* 두지 않기 위해서다).
|
|
22
22
|
*
|
|
23
|
-
*
|
|
23
|
+
* `summary` 슬롯은 주 액션이 무엇을 할지 말하는 글이다(«변경 1건 · 주문 번호») — 위험 액션 뒤에서
|
|
24
|
+
* 남는 폭을 채운다. 시트 가장자리에 붙는 바닥이면 `--action-bar-padding-inline` 으로 안쪽 여백을 준다.
|
|
25
|
+
*
|
|
26
|
+
* 오버라이드: `part`(host·danger·summary·main) + slot 치환.
|
|
24
27
|
*/
|
|
25
28
|
export declare class ActionBar extends StyledElement<ElementParts> {
|
|
26
29
|
static styles: import('lit').CSSResultGroup[];
|
|
@@ -35,6 +38,8 @@ export declare class ActionBar extends StyledElement<ElementParts> {
|
|
|
35
38
|
* (`internals/slotted.ts` 참조). 빈 래퍼가 남으면 주 액션이 가운데로 밀린다.
|
|
36
39
|
*/
|
|
37
40
|
private hasDanger;
|
|
41
|
+
/** 요약 슬롯 배정 상태 — 비면 래퍼를 접는다(위와 같은 이유). */
|
|
42
|
+
private hasSummary;
|
|
38
43
|
render(): import('lit-html').TemplateResult<1>;
|
|
39
44
|
}
|
|
40
45
|
declare global {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { styles as i } from "./ActionBar.styles.js";
|
|
@@ -8,7 +8,7 @@ import { customElement as o, property as s, state as c } from "lit/decorators.js
|
|
|
8
8
|
//#region src/components/ActionBar.ts
|
|
9
9
|
var l = class extends n {
|
|
10
10
|
constructor(...e) {
|
|
11
|
-
super(...e), this.sticky = !1, this.hasDanger = !1;
|
|
11
|
+
super(...e), this.sticky = !1, this.hasDanger = !1, this.hasSummary = !1;
|
|
12
12
|
}
|
|
13
13
|
static {
|
|
14
14
|
this.styles = [super.styles, i];
|
|
@@ -20,6 +20,11 @@ var l = class extends n {
|
|
|
20
20
|
@slotchange=${(e) => this.hasDanger = r(e.target)}
|
|
21
21
|
></slot>
|
|
22
22
|
</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>
|
|
23
28
|
<div class="main" part="main"><slot></slot></div>
|
|
24
29
|
`;
|
|
25
30
|
}
|
|
@@ -27,6 +32,6 @@ var l = class extends n {
|
|
|
27
32
|
t([s({
|
|
28
33
|
type: Boolean,
|
|
29
34
|
reflect: !0
|
|
30
|
-
}), e("design:type", Object)], l.prototype, "sticky", void 0), t([c(), e("design:type", Object)], l.prototype, "hasDanger", void 0), l = t([o("u-action-bar")], l);
|
|
35
|
+
}), e("design:type", Object)], l.prototype, "sticky", void 0), t([c(), e("design:type", Object)], l.prototype, "hasDanger", void 0), t([c(), e("design:type", Object)], l.prototype, "hasSummary", void 0), l = t([o("u-action-bar")], l);
|
|
31
36
|
//#endregion
|
|
32
37
|
export { l as ActionBar };
|
|
@@ -5,7 +5,7 @@ var t = e`
|
|
|
5
5
|
display: flex;
|
|
6
6
|
align-items: center;
|
|
7
7
|
gap: var(--u-space-sm, 8px);
|
|
8
|
-
padding: var(--u-space-md, 12px) 0;
|
|
8
|
+
padding: var(--u-space-md, 12px) var(--action-bar-padding-inline, 0);
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
@@ -36,6 +36,18 @@ var t = e`
|
|
|
36
36
|
display: none;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
.summary {
|
|
42
|
+
flex: 1 1 calc((420px - 100%) * 999);
|
|
43
|
+
min-width: 0;
|
|
44
|
+
font-size: var(--u-text-label-size, 13px);
|
|
45
|
+
color: var(--u-txt-color-weak, #616161);
|
|
46
|
+
}
|
|
47
|
+
.summary.empty {
|
|
48
|
+
display: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
.main {
|
|
40
52
|
display: flex;
|
|
41
53
|
align-items: center;
|
|
@@ -56,7 +68,7 @@ var t = e`
|
|
|
56
68
|
bottom: 0;
|
|
57
69
|
background-color: var(--u-bg-color, #FFFFFF);
|
|
58
70
|
border-top: 1px solid var(--u-border-color-weak, #EEEEEE);
|
|
59
|
-
padding: var(--u-space-md, 12px) 0;
|
|
71
|
+
padding: var(--u-space-md, 12px) var(--action-bar-padding-inline, 0);
|
|
60
72
|
}
|
|
61
73
|
`;
|
|
62
74
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { getLocaleStrings as i } from "../internals/locale.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { styles as i } from "./GroupBox.styles.js";
|
|
@@ -97,6 +97,11 @@ export declare class InfoField extends StyledElement<ElementParts> {
|
|
|
97
97
|
* `--u-success|danger|warning-color-strong`.
|
|
98
98
|
*/
|
|
99
99
|
tone?: InfoFieldTone;
|
|
100
|
+
/**
|
|
101
|
+
* 값 아래의 보조 줄 — 그 값의 근거나 다른 단위의 같은 양(«1,764,211 units» · «Settled ₩743,188,120»).
|
|
102
|
+
* 추세 문구는 `trendLabel` 이다(방향과 색을 갖는다).
|
|
103
|
+
*/
|
|
104
|
+
description?: string;
|
|
100
105
|
/** 라벨을 이름으로 준 슬롯 위젯과 그때의 라벨 — 소비자가 바꾼 이름은 우리 것이 아니다. */
|
|
101
106
|
private readonly namedWidgets;
|
|
102
107
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { styles as r } from "./InfoField.styles.js";
|
|
5
5
|
import { html as i } from "lit";
|
|
@@ -48,12 +48,13 @@ var f = class extends n {
|
|
|
48
48
|
${a ? i`<span aria-hidden="true">${a}</span> ` : ""}${this.trendLabel ?? ""}
|
|
49
49
|
</div>
|
|
50
50
|
` : ""}
|
|
51
|
+
${this.description ? i`<div class="description" part="description">${this.description}</div>` : ""}
|
|
51
52
|
`;
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
55
|
t([o({ type: String }), e("design:type", Object)], f.prototype, "label", void 0), t([o({ type: String }), e("design:type", Object)], f.prototype, "value", void 0), t([o({ type: String }), e("design:type", Object)], f.prototype, "blank", void 0), t([o({ type: Boolean }), e("design:type", Object)], f.prototype, "numeric", void 0), t([o({ type: String }), e("design:type", Object)], f.prototype, "format", void 0), t([o({ type: String }), e("design:type", String)], f.prototype, "currency", void 0), t([o({ type: String }), e("design:type", String)], f.prototype, "unit", void 0), t([o({
|
|
55
56
|
type: String,
|
|
56
57
|
reflect: !0
|
|
57
|
-
}), e("design:type", Object)], f.prototype, "size", void 0), t([o({ type: String }), e("design:type", Object)], f.prototype, "trend", void 0), t([o({ type: String }), e("design:type", String)], f.prototype, "trendLabel", void 0), t([o({ type: String }), e("design:type", Object)], f.prototype, "tone", void 0), f = t([a("u-info-field")], f);
|
|
58
|
+
}), e("design:type", Object)], f.prototype, "size", void 0), t([o({ type: String }), e("design:type", Object)], f.prototype, "trend", void 0), t([o({ type: String }), e("design:type", String)], f.prototype, "trendLabel", void 0), t([o({ type: String }), e("design:type", Object)], f.prototype, "tone", void 0), t([o({ type: String }), e("design:type", String)], f.prototype, "description", void 0), f = t([a("u-info-field")], f);
|
|
58
59
|
//#endregion
|
|
59
60
|
export { f as InfoField, d as isBlank };
|
|
@@ -82,6 +82,14 @@ var t = e`
|
|
|
82
82
|
margin-top: var(--u-space-3xs, 2px);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
|
|
86
|
+
.description {
|
|
87
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
88
|
+
line-height: var(--u-text-caption-leading, 1.5);
|
|
89
|
+
color: var(--u-txt-color-weaker, #757575);
|
|
90
|
+
margin-top: var(--u-space-3xs, 2px);
|
|
91
|
+
}
|
|
92
|
+
|
|
85
93
|
.trend.tone-positive {
|
|
86
94
|
color: var(--u-success-color-strong, #1B5E20);
|
|
87
95
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { StyledElement } from '../internals/StyledElement.js';
|
|
2
2
|
type ElementParts = 'host' | 'grid';
|
|
3
|
+
/** 배치 — `grid` 라벨-값 칸의 반응형 그리드 · `strip` 지표 띠(구분선으로 나뉜 한 면의 칸). */
|
|
4
|
+
export type InfoSectionLayout = 'grid' | 'strip';
|
|
3
5
|
/**
|
|
4
6
|
* 정보 섹션 — `u-info-field` 들의 반응형 그리드.
|
|
5
7
|
*
|
|
@@ -16,11 +18,18 @@ type ElementParts = 'host' | 'grid';
|
|
|
16
18
|
* ```
|
|
17
19
|
*
|
|
18
20
|
* `min` 으로 한 칸의 최소 폭을 조절한다(기본 160px). 라벨이 긴 도메인은 값을 올린다.
|
|
21
|
+
*
|
|
22
|
+
* `layout="strip"` 은 지표 띠다 — 결과의 합계를 문장이 아니라 칸으로(목록 위 «주문 962 · 청구 ₩…»).
|
|
23
|
+
* 한 면 위에 1px 구분선으로 나뉜 칸들이 **전부 나란히, 아니면 전부 쌓인다**(띠 폭 640px 미만 — 들쭉날쭉한 줄을
|
|
24
|
+
* 만들지 않는다). 임계는 크기 containment 가 아니라 칸의 flex-basis 가 건다(«Holy Albatross» — u-action-bar 와 같은
|
|
25
|
+
* 근거). 구분선은 칸 사이 1px 간격으로 비치는 면의 테두리 색이라 가로·세로가 저절로 바뀐다. 이 배치에서 `min` 은 쓰지 않는다.
|
|
19
26
|
*/
|
|
20
27
|
export declare class InfoSection extends StyledElement<ElementParts> {
|
|
21
28
|
static styles: import('lit').CSSResultGroup[];
|
|
22
29
|
/** 한 칸의 최소 폭(px). 이 값보다 좁아지면 열 수가 줄어든다. */
|
|
23
30
|
min: number;
|
|
31
|
+
/** 배치 — `grid`(기본) · `strip`(지표 띠). */
|
|
32
|
+
layout: InfoSectionLayout;
|
|
24
33
|
render(): import('lit-html').TemplateResult<1>;
|
|
25
34
|
}
|
|
26
35
|
declare global {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { styles as r } from "./InfoSection.styles.js";
|
|
5
5
|
import { html as i } from "lit";
|
|
@@ -7,7 +7,7 @@ import { customElement as a, property as o } from "lit/decorators.js";
|
|
|
7
7
|
//#region src/components/InfoSection.ts
|
|
8
8
|
var s = class extends n {
|
|
9
9
|
constructor(...e) {
|
|
10
|
-
super(...e), this.min = 160;
|
|
10
|
+
super(...e), this.min = 160, this.layout = "grid";
|
|
11
11
|
}
|
|
12
12
|
static {
|
|
13
13
|
this.styles = [super.styles, r];
|
|
@@ -18,6 +18,9 @@ var s = class extends n {
|
|
|
18
18
|
`;
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
|
-
t([o({ type: Number }), e("design:type", Object)], s.prototype, "min", void 0),
|
|
21
|
+
t([o({ type: Number }), e("design:type", Object)], s.prototype, "min", void 0), t([o({
|
|
22
|
+
type: String,
|
|
23
|
+
reflect: !0
|
|
24
|
+
}), e("design:type", Object)], s.prototype, "layout", void 0), s = t([a("u-info-section")], s);
|
|
22
25
|
//#endregion
|
|
23
26
|
export { s as InfoSection };
|
|
@@ -13,6 +13,24 @@ var t = e`
|
|
|
13
13
|
grid-template-columns: repeat(auto-fit, minmax(var(--info-min, 160px), 1fr));
|
|
14
14
|
gap: var(--u-space-lg, 16px) var(--u-space-xl, 20px);
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
:host([layout="strip"]) .grid {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-wrap: wrap;
|
|
22
|
+
gap: 1px;
|
|
23
|
+
background: var(--u-border-color, #E0E0E0);
|
|
24
|
+
border: 1px solid var(--u-border-color, #E0E0E0);
|
|
25
|
+
border-radius: var(--u-radius-xl, 8px);
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
}
|
|
28
|
+
:host([layout="strip"]) ::slotted(*) {
|
|
29
|
+
flex: 1 1 calc((640px - 100%) * 999);
|
|
30
|
+
min-width: 0;
|
|
31
|
+
padding: var(--u-space-md, 12px) var(--u-space-lg, 16px);
|
|
32
|
+
background: var(--u-bg-color, #FFFFFF);
|
|
33
|
+
}
|
|
16
34
|
`;
|
|
17
35
|
//#endregion
|
|
18
36
|
export { t as styles };
|
|
@@ -37,8 +37,13 @@ type ElementParts = 'host' | 'master' | 'divider' | 'detail' | 'detail-close';
|
|
|
37
37
|
*/
|
|
38
38
|
export declare class MasterDetailLayout extends StyledElement<ElementParts> {
|
|
39
39
|
static styles: import('lit').CSSResultGroup[];
|
|
40
|
-
/** master 영역의 고정 폭(CSS 길이). detail 은 나머지 공간을 채운다. */
|
|
40
|
+
/** master 영역의 고정 폭(CSS 길이). detail 은 나머지 공간을 채운다. detail 이 비면 master 가 전부 쓴다. */
|
|
41
41
|
masterSize: string;
|
|
42
|
+
/**
|
|
43
|
+
* detail 영역의 고정 폭(CSS 길이) — 주면 detail 이 이 폭이고 master 가 나머지를 채운다(`masterSize` 는 쓰지 않는다).
|
|
44
|
+
* 넓은 목록 곁에 레코드 요약을 붙이는 «곁 패널» 배치. 비우면 종전대로 master 가 고정 폭이다.
|
|
45
|
+
*/
|
|
46
|
+
detailSize?: string;
|
|
42
47
|
/**
|
|
43
48
|
* 이 값(px, 자기 폭 기준) 미만이면 detail 이 전체 오버레이로 전환된다.
|
|
44
49
|
* `masterSize` + 최소한의 읽기 가능한 detail 폭을 더한 값보다 커야 두 패널이
|
|
@@ -52,6 +57,8 @@ export declare class MasterDetailLayout extends StyledElement<ElementParts> {
|
|
|
52
57
|
locale: string;
|
|
53
58
|
/** detail 슬롯 배정 상태 — CSS `:has()` 로는 알 수 없다(`internals/slotted.ts` 참조). */
|
|
54
59
|
private hasDetail;
|
|
60
|
+
/** `detail-placeholder` 슬롯 배정 상태 — detail 이 비었을 때 그 자리에 보인다. */
|
|
61
|
+
private hasPlaceholder;
|
|
55
62
|
/**
|
|
56
63
|
* `:state(detail)` 을 싣는 자리. 인쇄 규칙이 «한 판만 보이는가» 를 호스트에서 갈라야 해서
|
|
57
64
|
* 생겼다(styles 의 인쇄 절 참조).
|
|
@@ -84,6 +91,7 @@ export declare class MasterDetailLayout extends StyledElement<ElementParts> {
|
|
|
84
91
|
*/
|
|
85
92
|
private handleDetailClose;
|
|
86
93
|
private handleDetailSlotChange;
|
|
94
|
+
private handlePlaceholderSlotChange;
|
|
87
95
|
render(): import('lit-html').TemplateResult<1>;
|
|
88
96
|
}
|
|
89
97
|
/** Events `<u-master-detail-layout>` dispatches — bubbling and composed. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { getLocaleStrings as i } from "../internals/locale.js";
|
|
@@ -11,10 +11,12 @@ import "@iyulab/components/dist/components/button/UButton.js";
|
|
|
11
11
|
//#region src/components/MasterDetailLayout.ts
|
|
12
12
|
var u = class extends n {
|
|
13
13
|
constructor(...e) {
|
|
14
|
-
super(...e), this.masterSize = "22rem", this.overlayBreakpoint = 760, this.locale = "", this.hasDetail = !1, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.handleDetailClose = () => {
|
|
14
|
+
super(...e), this.masterSize = "22rem", this.overlayBreakpoint = 760, this.locale = "", this.hasDetail = !1, this.hasPlaceholder = !1, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.handleDetailClose = () => {
|
|
15
15
|
this.fire("detail-close", { cancelable: !1 });
|
|
16
16
|
}, this.handleDetailSlotChange = (e) => {
|
|
17
17
|
this.hasDetail = r(e.target);
|
|
18
|
+
}, this.handlePlaceholderSlotChange = (e) => {
|
|
19
|
+
this.hasPlaceholder = r(e.target);
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
static {
|
|
@@ -32,13 +34,14 @@ var u = class extends n {
|
|
|
32
34
|
super.disconnectedCallback(), this.resizeObserver?.disconnect();
|
|
33
35
|
}
|
|
34
36
|
updated(e) {
|
|
35
|
-
super.updated(e), e.has("masterSize") && this.style.setProperty("--_master-size", this.masterSize), e.has("hasDetail") && this.internals?.states?.[this.hasDetail ? "add" : "delete"]("detail"), e.has("overlayBreakpoint") && this.applyOverlay(this.getBoundingClientRect().width);
|
|
37
|
+
super.updated(e), e.has("masterSize") && this.style.setProperty("--_master-size", this.masterSize), e.has("detailSize") && (this.detailSize ? this.style.setProperty("--_detail-size", this.detailSize) : this.style.removeProperty("--_detail-size")), e.has("hasDetail") && this.internals?.states?.[this.hasDetail ? "add" : "delete"]("detail"), e.has("overlayBreakpoint") && this.applyOverlay(this.getBoundingClientRect().width);
|
|
36
38
|
}
|
|
37
39
|
render() {
|
|
40
|
+
let e = this.hasDetail ? "" : this.hasPlaceholder ? "placeholder" : "empty";
|
|
38
41
|
return o`
|
|
39
|
-
<div class="master ${this.hasDetail ? "covered" : ""}" part="master"><slot></slot></div>
|
|
40
|
-
<div class="divider ${
|
|
41
|
-
<div class="detail ${
|
|
42
|
+
<div class="master ${this.hasDetail ? "covered" : ""} ${e === "empty" ? "alone" : ""}" part="master"><slot></slot></div>
|
|
43
|
+
<div class="divider ${e}" part="divider"></div>
|
|
44
|
+
<div class="detail ${e}" part="detail">
|
|
42
45
|
<u-button class="detail-close" part="detail-close" appearance="plain"
|
|
43
46
|
aria-label=${i(this.locale || void 0).detailClose}
|
|
44
47
|
@click=${this.handleDetailClose}
|
|
@@ -46,6 +49,7 @@ var u = class extends n {
|
|
|
46
49
|
<u-icon lib="internal" name="x"></u-icon>
|
|
47
50
|
</u-button>
|
|
48
51
|
<slot name="detail" @slotchange=${this.handleDetailSlotChange}></slot>
|
|
52
|
+
<slot name="detail-placeholder" ?hidden=${this.hasDetail} @slotchange=${this.handlePlaceholderSlotChange}></slot>
|
|
49
53
|
</div>
|
|
50
54
|
`;
|
|
51
55
|
}
|
|
@@ -54,8 +58,12 @@ t([c({
|
|
|
54
58
|
type: String,
|
|
55
59
|
attribute: "master-size"
|
|
56
60
|
}), e("design:type", Object)], u.prototype, "masterSize", void 0), t([c({
|
|
61
|
+
type: String,
|
|
62
|
+
attribute: "detail-size",
|
|
63
|
+
reflect: !0
|
|
64
|
+
}), e("design:type", String)], u.prototype, "detailSize", void 0), t([c({
|
|
57
65
|
type: Number,
|
|
58
66
|
attribute: "overlay-breakpoint"
|
|
59
|
-
}), e("design:type", Object)], u.prototype, "overlayBreakpoint", void 0), t([c({ type: String }), e("design:type", Object)], u.prototype, "locale", void 0), t([l(), e("design:type", Object)], u.prototype, "hasDetail", void 0), u = t([s("u-master-detail-layout")], u);
|
|
67
|
+
}), e("design:type", Object)], u.prototype, "overlayBreakpoint", void 0), t([c({ type: String }), e("design:type", Object)], u.prototype, "locale", void 0), t([l(), e("design:type", Object)], u.prototype, "hasDetail", void 0), t([l(), e("design:type", Object)], u.prototype, "hasPlaceholder", void 0), u = t([s("u-master-detail-layout")], u);
|
|
60
68
|
//#endregion
|
|
61
69
|
export { u as MasterDetailLayout };
|
|
@@ -17,6 +17,18 @@ var t = e`
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
.master.alone {
|
|
21
|
+
flex: 1 1 auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:host([detail-size]) .master {
|
|
25
|
+
flex: 1 1 auto;
|
|
26
|
+
}
|
|
27
|
+
:host([detail-size]:not([overlay])) .detail {
|
|
28
|
+
flex: 0 0 var(--_detail-size);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
20
32
|
|
|
21
33
|
:host([overlay]) .master {
|
|
22
34
|
flex: 1 1 auto;
|
|
@@ -34,6 +46,10 @@ var t = e`
|
|
|
34
46
|
:host([overlay]) .divider {
|
|
35
47
|
display: none;
|
|
36
48
|
}
|
|
49
|
+
|
|
50
|
+
:host([overlay]) .detail.placeholder {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
37
53
|
|
|
38
54
|
.detail {
|
|
39
55
|
flex: 1 1 auto;
|
|
@@ -102,6 +118,7 @@ var t = e`
|
|
|
102
118
|
overflow: visible;
|
|
103
119
|
}
|
|
104
120
|
.detail-close,
|
|
121
|
+
.detail.placeholder,
|
|
105
122
|
:host([overlay]) .detail-close,
|
|
106
123
|
:host([overlay]) .master.covered {
|
|
107
124
|
display: none;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { getLocaleStrings as i } from "../internals/locale.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
|
|
5
5
|
import { styles as i } from "./SidebarButton.styles.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { DEFAULT_NAV_ICON as r } from "../internals/nav-icon.js";
|
|
5
5
|
import { SidebarLink as i } from "./SidebarLink.js";
|
|
@@ -2,9 +2,6 @@ import { css as e } from "lit";
|
|
|
2
2
|
//#region src/components/SidebarGroup.styles.ts
|
|
3
3
|
var t = e`
|
|
4
4
|
:host {
|
|
5
|
-
|
|
6
|
-
--group-accent: var(--app-sidebar-active-bg, var(--u-primary-color, #1976D2));
|
|
7
|
-
|
|
8
5
|
display: flex;
|
|
9
6
|
flex-direction: column;
|
|
10
7
|
color: var(--app-sidebar-fg, var(--u-txt-color, #212121));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { getLocaleStrings as r } from "../internals/locale.js";
|
|
5
5
|
import { DEFAULT_NAV_ICON as i } from "../internals/nav-icon.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { styles as r } from "./SidebarSection.styles.js";
|
|
5
5
|
import { html as i } from "lit";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
2
2
|
import { StyledElement as t } from "../internals/StyledElement.js";
|
|
3
3
|
import "./TimelineItem.js";
|
|
4
4
|
import { styles as n } from "./Timeline.styles.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { styles as i } from "./TimelineItem.styles.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { getLocaleStrings as r } from "../internals/locale.js";
|
|
5
5
|
import { styles as i } from "./Wizard.styles.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { property as n } from "lit/decorators.js";
|
|
4
4
|
import { UElement as r } from "@iyulab/components/dist/components/UElement.js";
|
|
5
5
|
//#region src/internals/StyledElement.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import e from "../_virtual/_@oxc-project_runtime@0.
|
|
2
|
-
import t from "../_virtual/_@oxc-project_runtime@0.
|
|
1
|
+
import e from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import t from "../_virtual/_@oxc-project_runtime@0.154.0/helpers/esm/decorate.js";
|
|
3
3
|
import { StyledElement as n } from "../internals/StyledElement.js";
|
|
4
4
|
import { slotHasContent as r } from "../internals/slotted.js";
|
|
5
5
|
import { getLocaleStrings as i } from "../internals/locale.js";
|
|
@@ -81,7 +81,7 @@ var M = class extends n {
|
|
|
81
81
|
constructor(...e) {
|
|
82
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.overlayTrigger = null, this.internals = typeof this.attachInternals == "function" ? this.attachInternals() : void 0, this.currentPath = null, this.unsizedWarnKey = `unsized:${++w}`, 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
83
|
let e = this.screenSize ?? a.screen ?? "large";
|
|
84
|
-
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("
|
|
84
|
+
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
85
|
}, this.handleBackdropClick = () => {
|
|
86
86
|
this.state = "slim";
|
|
87
87
|
}, this.handleRouteBegin = (e) => {
|
|
@@ -155,7 +155,7 @@ var M = class extends n {
|
|
|
155
155
|
e.preventDefault();
|
|
156
156
|
}, this.handleScreenResize = (e) => {
|
|
157
157
|
let t = e.detail.size;
|
|
158
|
-
this.screenSize = t, t === "large" ? this.state = "default" : t === "medium" ? this.state = "slim" : t === "small" ? this.state = "mobile" : console.warn("
|
|
158
|
+
this.screenSize = t, t === "large" ? this.state = "default" : t === "medium" ? this.state = "slim" : t === "small" ? this.state = "mobile" : console.warn("[@iyulab/modern-app] unknown screen size:", t);
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
static {
|
package/dist/react.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ import { TimelineItemColor } from './components/TimelineItem.js';
|
|
|
8
8
|
import { InfoFieldFormat, InfoFieldSize, InfoFieldTrend, InfoFieldTone } from './components/InfoField.js';
|
|
9
9
|
export { ScreenObserver } from './internals/ScreenObserver.js';
|
|
10
10
|
export type { ScreenSize, ScreenObserverConfig, ScreenResizeEvent } from './internals/ScreenObserver.js';
|
|
11
|
+
/** `useUnsavedChanges` 가 돌려주는 것. */
|
|
12
|
+
export interface UnsavedChangesHandle {
|
|
13
|
+
/**
|
|
14
|
+
* 지금 값은 저장됐다(또는 버려도 된다) — 다음 렌더 전까지의 이동은 묻지 않는다. 저장 · 삭제 성공 뒤 같은 틱에 떠날 때 부른다:
|
|
15
|
+
* 이동 순간의 판정은 아직 저장 전 렌더의 `dirty` 를 본다. 다음 렌더부터는 다시 그 렌더의 값을 본다.
|
|
16
|
+
*/
|
|
17
|
+
markClean(): void;
|
|
18
|
+
}
|
|
11
19
|
/**
|
|
12
20
|
* React 화면의 «저장 안 한 변경» — 마운트된 동안 판정을 등록한다(`trackUnsavedChanges`). 셸(`app.load()`)이 떠나기 전에
|
|
13
21
|
* «변경을 버릴까요?» 를 묻고, 문서 이탈(새로 고침 · 탭 닫기)에는 브라우저 확인이 걸린다. 값은 매 렌더의 것을 본다.
|
|
@@ -15,11 +23,16 @@ export type { ScreenSize, ScreenObserverConfig, ScreenResizeEvent } from './inte
|
|
|
15
23
|
* ```tsx
|
|
16
24
|
* function OrderForm() {
|
|
17
25
|
* const [draft, setDraft] = useState(initial);
|
|
18
|
-
* useUnsavedChanges(draft !== initial);
|
|
26
|
+
* const unsaved = useUnsavedChanges(draft !== initial);
|
|
27
|
+
* async function save() {
|
|
28
|
+
* const row = await post(draft);
|
|
29
|
+
* unsaved.markClean(); // 저장했다 — 이어지는 이동은 묻지 않는다
|
|
30
|
+
* navigate(`/orders/${row.id}`);
|
|
31
|
+
* }
|
|
19
32
|
* }
|
|
20
33
|
* ```
|
|
21
34
|
*/
|
|
22
|
-
export declare function useUnsavedChanges(dirty: boolean):
|
|
35
|
+
export declare function useUnsavedChanges(dirty: boolean): UnsavedChangesHandle;
|
|
23
36
|
/**
|
|
24
37
|
* React wrapper for `<u-sidebar-layout>`. `config` is an `@property({ type: Object })` —
|
|
25
38
|
* a raw custom element handles that correctly under React 19 (property assignment) but not
|
package/dist/react.js
CHANGED
|
@@ -7,7 +7,13 @@ import { createComponent as a } from "@lit/react";
|
|
|
7
7
|
//#region src/react.ts
|
|
8
8
|
function o(e) {
|
|
9
9
|
let n = i.useRef(e);
|
|
10
|
-
n.current = e
|
|
10
|
+
n.current = e;
|
|
11
|
+
let r = i.useRef(!1);
|
|
12
|
+
return i.useEffect(() => {
|
|
13
|
+
r.current = !1;
|
|
14
|
+
}), i.useEffect(() => t(() => !r.current && n.current), []), i.useMemo(() => ({ markClean: () => {
|
|
15
|
+
r.current = !0;
|
|
16
|
+
} }), []);
|
|
11
17
|
}
|
|
12
18
|
var s = a({
|
|
13
19
|
react: i,
|
package/dist/unsaved.d.ts
CHANGED
|
@@ -32,7 +32,8 @@ export declare function confirmDiscardChanges(options?: DiscardChangesOptions):
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function unsavedChangesGuard(_ctx?: RouteLeaveContext): boolean | Promise<boolean>;
|
|
34
34
|
/**
|
|
35
|
-
* Lit 요소의 «저장 안 한 변경» — 연결된 동안 판정을 등록하고, 떨어지면 해제한다.
|
|
35
|
+
* Lit 요소의 «저장 안 한 변경» — 연결된 동안 판정을 등록하고, 떨어지면 해제한다. 저장 · 삭제 뒤 같은 틱에 떠날 때는
|
|
36
|
+
* `markClean()` — 다음 갱신 전까지의 이동은 묻지 않는다(판정이 폼 상태를 아직 내리지 않았어도).
|
|
36
37
|
* @example
|
|
37
38
|
* ```ts
|
|
38
39
|
* class OrderPage extends LitElement {
|
|
@@ -43,7 +44,12 @@ export declare function unsavedChangesGuard(_ctx?: RouteLeaveContext): boolean |
|
|
|
43
44
|
export declare class UnsavedChangesController implements ReactiveController {
|
|
44
45
|
private readonly isDirty;
|
|
45
46
|
private release?;
|
|
47
|
+
private clean;
|
|
48
|
+
private readonly check;
|
|
46
49
|
constructor(host: ReactiveControllerHost, isDirty: () => boolean);
|
|
50
|
+
/** 지금 상태는 저장됐다 — 호스트의 다음 갱신 전까지의 이동은 묻지 않는다. */
|
|
51
|
+
markClean(): void;
|
|
47
52
|
hostConnected(): void;
|
|
53
|
+
hostUpdated(): void;
|
|
48
54
|
hostDisconnected(): void;
|
|
49
55
|
}
|
package/dist/unsaved.js
CHANGED
|
@@ -12,7 +12,7 @@ function i() {
|
|
|
12
12
|
for (let e of t) try {
|
|
13
13
|
if (e()) return !0;
|
|
14
14
|
} catch (e) {
|
|
15
|
-
return console.error("
|
|
15
|
+
return console.error("[@iyulab/modern-app] the unsaved-changes check threw — treating the screen as unsaved:", e), !0;
|
|
16
16
|
}
|
|
17
17
|
return !1;
|
|
18
18
|
}
|
|
@@ -30,10 +30,16 @@ function o(e) {
|
|
|
30
30
|
}
|
|
31
31
|
var s = class {
|
|
32
32
|
constructor(e, t) {
|
|
33
|
-
this.isDirty = t, e.addController(this);
|
|
33
|
+
this.isDirty = t, this.clean = !1, this.check = () => !this.clean && this.isDirty(), e.addController(this);
|
|
34
|
+
}
|
|
35
|
+
markClean() {
|
|
36
|
+
this.clean = !0;
|
|
34
37
|
}
|
|
35
38
|
hostConnected() {
|
|
36
|
-
this.release ??= r(this.
|
|
39
|
+
this.release ??= r(this.check);
|
|
40
|
+
}
|
|
41
|
+
hostUpdated() {
|
|
42
|
+
this.clean = !1;
|
|
37
43
|
}
|
|
38
44
|
hostDisconnected() {
|
|
39
45
|
this.release?.(), this.release = void 0;
|
package/package.json
CHANGED
|
@@ -245,7 +245,8 @@ Back/Forward included) and the browser asks before the document is left (`before
|
|
|
245
245
|
import { UnsavedChangesController, confirmDiscardChanges } from '@iyulab/modern-app';
|
|
246
246
|
// Lit
|
|
247
247
|
readonly unsaved = new UnsavedChangesController(this, () => this.form.dirty);
|
|
248
|
-
// React: useUnsavedChanges(dirty) from '@iyulab/modern-app/react'
|
|
248
|
+
// React: const unsaved = useUnsavedChanges(dirty) from '@iyulab/modern-app/react'
|
|
249
|
+
// Saved (or deleted) and leaving in the same tick: unsaved.markClean() before navigate() — until the next render
|
|
249
250
|
// Plain: const release = trackUnsavedChanges(() => dirty);
|
|
250
251
|
// A modal drawer's close is not a navigation — ask from its cancellable `hide` event:
|
|
251
252
|
drawer.addEventListener('hide', async (e) => {
|
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
**Tag:** `u-action-bar`
|
|
4
4
|
|
|
5
|
-
The row of actions that closes a form or a detail screen:
|
|
6
|
-
|
|
5
|
+
The row of actions that closes a form or a detail screen: destructive actions at the leading edge, the main
|
|
6
|
+
actions at the trailing edge — the distance between them is the safeguard. Below 420px each group takes its own
|
|
7
|
+
line with the main actions on top, so a destructive action never sits under the thumb.
|
|
7
8
|
|
|
8
9
|
```html
|
|
9
10
|
<u-action-bar sticky>
|
|
10
|
-
<u-button appearance="
|
|
11
|
+
<u-button slot="danger" color="danger" appearance="plain">Delete</u-button>
|
|
12
|
+
<span slot="summary">1 change · Order number</span>
|
|
11
13
|
<u-button appearance="outlined">Cancel</u-button>
|
|
12
|
-
<u-button
|
|
14
|
+
<u-button>Save</u-button>
|
|
13
15
|
</u-action-bar>
|
|
14
16
|
```
|
|
15
17
|
|
|
18
|
+
A side sheet's footer runs to the sheet's edges — give it the sheet's inset with `--action-bar-padding-inline`
|
|
19
|
+
rather than negative margins (the bar cannot know its parent's padding).
|
|
20
|
+
|
|
16
21
|
## Slots
|
|
17
22
|
|
|
18
23
|
| Name | Description |
|
|
19
24
|
|------|-------------|
|
|
20
|
-
| *(default)* |
|
|
21
|
-
| `danger` | Destructive actions,
|
|
25
|
+
| *(default)* | Main actions, at the trailing edge |
|
|
26
|
+
| `danger` | Destructive actions, at the leading edge |
|
|
27
|
+
| `summary` | What the main action will do — «1 change · Order number». Follows the danger actions and fills the free width |
|
|
22
28
|
|
|
23
29
|
## Properties
|
|
24
30
|
|
|
@@ -30,8 +36,9 @@ destructive ones separated on the trailing edge.
|
|
|
30
36
|
|
|
31
37
|
| Part | Description |
|
|
32
38
|
|------|-------------|
|
|
33
|
-
| `main` |
|
|
39
|
+
| `main` | Main action group |
|
|
34
40
|
| `danger` | Destructive action group |
|
|
41
|
+
| `summary` | Summary text wrapper |
|
|
35
42
|
|
|
36
43
|
## CSS Custom Properties
|
|
37
44
|
|
|
@@ -41,6 +48,7 @@ destructive ones separated on the trailing edge.
|
|
|
41
48
|
| `--u-border-color-weak` | Top border when `sticky` |
|
|
42
49
|
| `--u-space-sm` | Gap between actions within a group |
|
|
43
50
|
| `--u-space-md` | Host and sticky-mode vertical padding |
|
|
51
|
+
| `--action-bar-padding-inline` | Inline padding (default `0`) — the sheet inset for a footer that runs edge to edge |
|
|
44
52
|
|
|
45
53
|
⚠ `sticky` adds **vertical** padding only. It deliberately does not stretch to the container
|
|
46
54
|
edges with negative margins — that would assume the parent has exactly that much horizontal
|
|
@@ -40,6 +40,7 @@ and those are different states of the business. So the component owns the rule.
|
|
|
40
40
|
| `size` | `'default'\|'lg'` | `'default'` | ✓ | `'lg'` renders the value at the `title` type-scale step — for dashboard KPI tiles composed inside `u-info-section` |
|
|
41
41
|
| `trend` | `'up'\|'down'\|'flat'` | — | | Trend direction; renders a trend indicator when set, alongside `trendLabel`; pair with trendLabel for an accessible name — trend alone conveys direction by color only |
|
|
42
42
|
| `trendLabel` | `string` | — | | Trend copy, e.g. `"+12% vs last month"` — wording is the consumer's responsibility |
|
|
43
|
+
| `description` | `string` | — | | A supporting line below the value — its basis, or the same amount in another unit (`"1,764,211 units"`). Trend copy belongs in `trendLabel` |
|
|
43
44
|
| `tone` | `'positive'\|'negative'\|'warning'\|'neutral'` | — | | Explicit tone override; resolves from `trend` when unset (`up→positive`, `down→negative`, `flat`/unset→`neutral`) but always wins over inference. Colors the value text itself, independent of `trend` — a static figure (e.g. a balance due) can be toned `negative` with no trend arrow. `warning` means "needs attention, not wrong" (a deadline coming up, a rate under target but within limits) and is never inferred |
|
|
44
45
|
|
|
45
46
|
⚠ `numeric` no longer right-aligns (it did before 0.25.0). A field is a label/value pair that owns
|
|
@@ -83,6 +84,7 @@ as well once it exists.
|
|
|
83
84
|
|------|-------------|
|
|
84
85
|
| `label` · `value` · `trend` | The label, the value, and the (optional) trend indicator |
|
|
85
86
|
| `unit` | The unit, inside `value` (only when `unit` is set and the value is not blank) |
|
|
87
|
+
| `description` | The supporting line (only when `description` is set) |
|
|
86
88
|
|
|
87
89
|
## CSS Custom Properties
|
|
88
90
|
|
|
@@ -16,6 +16,16 @@ markup works inside a side panel.
|
|
|
16
16
|
</u-info-section>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
A stat strip — the totals above a list, as cells rather than a sentence:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<u-info-section layout="strip">
|
|
23
|
+
<u-info-field label="Orders" value="962" unit="orders" description="1,764,211 units" size="lg"></u-info-field>
|
|
24
|
+
<u-info-field label="Due in 7 days" value="14" tone="warning" description="3 not started" size="lg"></u-info-field>
|
|
25
|
+
<u-info-field label="Overdue" value="₩0" tone="negative" description="0 orders" size="lg"></u-info-field>
|
|
26
|
+
</u-info-section>
|
|
27
|
+
```
|
|
28
|
+
|
|
19
29
|
## Slots
|
|
20
30
|
|
|
21
31
|
| Name | Description |
|
|
@@ -26,7 +36,8 @@ markup works inside a side panel.
|
|
|
26
36
|
|
|
27
37
|
| Property | Type | Default | Reflect | Description |
|
|
28
38
|
|----------|------|---------|---------|-------------|
|
|
29
|
-
| `min` | `number` | `160` | | Minimum column width in px. Raise it for domains with long labels |
|
|
39
|
+
| `min` | `number` | `160` | | Minimum column width in px. Raise it for domains with long labels. Not used by `layout="strip"` |
|
|
40
|
+
| `layout` | `'grid'\|'strip'` | `'grid'` | ✓ | `grid` — label-value cells in a responsive grid · `strip` — a stat strip: cells on one bordered surface split by 1px dividers, all side by side or all stacked once the strip is narrower than 640px (never a ragged row) |
|
|
30
41
|
|
|
31
42
|
## CSS Parts
|
|
32
43
|
|
|
@@ -39,6 +50,8 @@ markup works inside a side panel.
|
|
|
39
50
|
| Property | Description |
|
|
40
51
|
|----------|-------------|
|
|
41
52
|
| `--u-space-lg` / `--u-space-xl` | Grid gap (row / column) |
|
|
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) |
|
|
42
55
|
|
|
43
56
|
⚠ Column width is set via the `min` property, not a custom property — it's applied as an inline
|
|
44
57
|
style per instance, so a `--info-min` override in a stylesheet would lose to it.
|
|
@@ -20,6 +20,24 @@ Swap the `detail` slot's content when the selection changes:
|
|
|
20
20
|
</u-master-detail-layout>
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
While nothing is in the `detail` slot the master takes the whole width — a fixed master with an
|
|
24
|
+
empty two-thirds beside it reads as a broken page. To keep the detail place visible with a «pick a
|
|
25
|
+
record» message instead, put it in `slot="detail-placeholder"`; it shows while `detail` is empty
|
|
26
|
+
and steps aside once it is filled (not in overlay mode, where it would cover the master).
|
|
27
|
+
|
|
28
|
+
## A wide list with a side panel
|
|
29
|
+
|
|
30
|
+
`masterSize` fixes the master — the mail / settings shape (narrow list, wide detail). For the
|
|
31
|
+
opposite — a wide table with a record summary beside it — set `detail-size`: the detail takes that
|
|
32
|
+
width and the master fills the rest. With the detail empty the table takes everything.
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<u-master-detail-layout detail-size="24rem">
|
|
36
|
+
<u-rich-table …></u-rich-table>
|
|
37
|
+
<aside slot="detail">…selected record…</aside>
|
|
38
|
+
</u-master-detail-layout>
|
|
39
|
+
```
|
|
40
|
+
|
|
23
41
|
On a narrow container (self width, not viewport — see below) the detail pane switches to a
|
|
24
42
|
full overlay above the master pane, with a small close button that fires `detail-close`.
|
|
25
43
|
Clearing the detail slot yourself (in response to the event) is what actually dismisses it —
|
|
@@ -72,13 +90,15 @@ own margin; that is inherent to printing two columns.
|
|
|
72
90
|
| Name | Description |
|
|
73
91
|
|------|-------------|
|
|
74
92
|
| *(default)* | Master area |
|
|
75
|
-
| `detail` | Detail area — present ⇒ shown, empty ⇒ hidden |
|
|
93
|
+
| `detail` | Detail area — present ⇒ shown, empty ⇒ hidden (the master then takes the whole width) |
|
|
94
|
+
| `detail-placeholder` | Shown in the detail place while `detail` is empty — side by side only |
|
|
76
95
|
|
|
77
96
|
## Properties
|
|
78
97
|
|
|
79
98
|
| Property | Type | Default | Reflect | Description |
|
|
80
99
|
|----------|------|---------|---------|-------------|
|
|
81
100
|
| `masterSize` | `string` | `'22rem'` | | Fixed width of the master pane, a CSS length (`master-size`) |
|
|
101
|
+
| `detailSize` | `string | undefined` | `undefined` | ✓ | Fixed width of the detail pane (`detail-size`) — the master then fills the rest and `masterSize` is not used |
|
|
82
102
|
| `overlayBreakpoint` | `number` | `760` | | Below this self width in px, detail becomes a full overlay (`overlay-breakpoint`) |
|
|
83
103
|
| `locale` | `string` | `''` | | Language tag for the close button's accessible label |
|
|
84
104
|
|