@iyulab/modern-app 0.55.0 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.55.1] - 2026-10-17
4
+
5
+ ### Fixed
6
+
7
+ - `u-master-detail-layout`: in a container with no height of its own (the page scrolls a long list), the detail pane
8
+ sticks to the top of the scroll container instead of stretching to the list's height with the record at its top —
9
+ a row picked far down the list showed its detail above the visible area. The pane is capped at the visible height of
10
+ that container and scrolls inside past it. In overlay mode the covering pane is placed over the visible part of the
11
+ list the same way (it covered the whole list from its top). With a height from the parent the panes look as before.
12
+ A `top` set through `::part(detail)` moves the pane below a sticky bar.
13
+ - React: the `u-master-detail-layout` JSX attribute map includes `detail-size`.
14
+
15
+ - Skill reference `u-master-detail-layout`: the `detailSize` row of the properties table is no longer split by the `|` in its type.
16
+
3
17
  ## [0.55.0] - 2026-10-16
4
18
 
5
19
  ### Added
@@ -67,6 +67,9 @@ export declare class MasterDetailLayout extends StyledElement<ElementParts> {
67
67
  */
68
68
  private readonly internals;
69
69
  private resizeObserver?;
70
+ /** 상세 칸이 붙는 스크롤 영역 — 없으면(문서가 스크롤한다) 뷰포트. */
71
+ private scroller;
72
+ private detailFrame;
70
73
  connectedCallback(): void;
71
74
  /**
72
75
  * 오버레이 판정 한 곳. **두 입력이 있고 둘 다 바뀔 수 있다** — 자기 폭과
@@ -83,6 +86,13 @@ export declare class MasterDetailLayout extends StyledElement<ElementParts> {
83
86
  */
84
87
  private applyOverlay;
85
88
  disconnectedCallback(): void;
89
+ /**
90
+ * 상세 칸의 높이 상한 — min(그릇 높이, 스크롤 영역 높이 − 칸의 top). 칸은 스크롤 영역 위에
91
+ * 붙으므로(sticky) 그 영역의 보이는 높이가 칸이 보일 수 있는 전부다. 그릇 높이가 그보다 낮으면
92
+ * 그릇 안에 머문다.
93
+ * 다음 프레임에 쓴다 — 크기 관찰 콜백 안에서 크기를 바꾸면 관찰 루프 경고가 난다.
94
+ */
95
+ private scheduleDetailMax;
86
96
  protected updated(changed: PropertyValues): void;
87
97
  /**
88
98
  * 오버레이 모드의 닫기 버튼이 눌렸을 때. detail 내용을 지우는 것은 소비자 몫이라
@@ -11,7 +11,14 @@ 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.hasPlaceholder = !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.scroller = null, this.detailFrame = 0, this.scheduleDetailMax = () => {
15
+ cancelAnimationFrame(this.detailFrame), this.detailFrame = requestAnimationFrame(() => {
16
+ let e = this.shadowRoot?.querySelector(".detail");
17
+ if (!e || !this.isConnected) return;
18
+ let t = this.scroller ? this.scroller.clientHeight : document.documentElement.clientHeight, n = parseFloat(getComputedStyle(e).top) || 0, r = Math.max(0, Math.min(this.getBoundingClientRect().height, t - n));
19
+ this.style.setProperty("--_detail-max", `${r}px`);
20
+ });
21
+ }, this.handleDetailClose = () => {
15
22
  this.fire("detail-close", { cancelable: !1 });
16
23
  }, this.handleDetailSlotChange = (e) => {
17
24
  this.hasDetail = r(e.target);
@@ -24,17 +31,18 @@ var u = class extends n {
24
31
  }
25
32
  connectedCallback() {
26
33
  super.connectedCallback(), this.resizeObserver = new ResizeObserver((e) => {
27
- this.applyOverlay(e[0]?.contentRect.width ?? 0);
28
- }), this.resizeObserver.observe(this);
34
+ for (let t of e) t.target === this && this.applyOverlay(t.contentRect.width);
35
+ this.scheduleDetailMax();
36
+ }), this.resizeObserver.observe(this), this.scroller = d(this), this.scroller ? this.resizeObserver.observe(this.scroller) : window.addEventListener("resize", this.scheduleDetailMax);
29
37
  }
30
38
  applyOverlay(e) {
31
39
  e !== 0 && this.toggleAttribute("overlay", e < this.overlayBreakpoint);
32
40
  }
33
41
  disconnectedCallback() {
34
- super.disconnectedCallback(), this.resizeObserver?.disconnect();
42
+ super.disconnectedCallback(), this.resizeObserver?.disconnect(), window.removeEventListener("resize", this.scheduleDetailMax), cancelAnimationFrame(this.detailFrame), this.scroller = null;
35
43
  }
36
44
  updated(e) {
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);
45
+ 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("hasDetail") || e.has("hasPlaceholder")) && this.scheduleDetailMax(), e.has("overlayBreakpoint") && this.applyOverlay(this.getBoundingClientRect().width);
38
46
  }
39
47
  render() {
40
48
  let e = this.hasDetail ? "" : this.hasPlaceholder ? "placeholder" : "empty";
@@ -65,5 +73,13 @@ t([c({
65
73
  type: Number,
66
74
  attribute: "overlay-breakpoint"
67
75
  }), 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);
76
+ function d(e) {
77
+ let t = e;
78
+ for (;;) {
79
+ let e = t;
80
+ if (t = e instanceof Element && e.assignedSlot ? e.assignedSlot : e.parentNode instanceof ShadowRoot ? e.parentNode.host : e.parentNode, !(t instanceof Element) || t === document.documentElement || t === document.body) return null;
81
+ if (/(auto|scroll|hidden|overlay)/.test(getComputedStyle(t).overflowY)) return t;
82
+ }
83
+ }
68
84
  //#endregion
69
85
  export { u as MasterDetailLayout };
@@ -51,10 +51,27 @@ var t = e`
51
51
  display: none;
52
52
  }
53
53
 
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
54
66
  .detail {
55
67
  flex: 1 1 auto;
56
68
  min-width: 0;
57
- position: relative;
69
+ position: sticky;
70
+ top: 0;
71
+ align-self: flex-start;
72
+ min-height: min(100%, var(--_detail-max, 100%));
73
+ max-height: var(--_detail-max, none);
74
+ box-sizing: border-box;
58
75
  overflow: auto;
59
76
  }
60
77
  .detail.empty {
@@ -63,9 +80,14 @@ var t = e`
63
80
 
64
81
 
65
82
 
83
+
84
+
66
85
  :host([overlay]) .detail {
67
- position: absolute;
68
- inset: 0;
86
+ flex: 0 0 100%;
87
+ margin-left: -100%;
88
+ height: var(--_detail-max, 100%);
89
+ min-height: 0;
90
+ max-height: none;
69
91
  z-index: 1;
70
92
  background-color: var(--u-panel-bg-color, #FFFFFF);
71
93
  box-shadow: var(--u-shadow-lg, 0 4px 12px rgba(0, 0, 0, 0.16), 0 2px 4px rgba(0, 0, 0, 0.06));
@@ -124,9 +146,17 @@ var t = e`
124
146
  display: none;
125
147
  }
126
148
  :host([overlay]) .detail {
127
- position: static;
149
+ margin-left: 0;
150
+ height: auto;
128
151
  box-shadow: none;
129
152
  }
153
+
154
+ .detail {
155
+ position: static;
156
+ align-self: stretch;
157
+ min-height: 0;
158
+ max-height: none;
159
+ }
130
160
  }
131
161
  `;
132
162
  //#endregion
package/dist/react.d.ts CHANGED
@@ -107,6 +107,7 @@ declare module 'react' {
107
107
  };
108
108
  'u-master-detail-layout': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
109
109
  'master-size'?: string;
110
+ 'detail-size'?: string;
110
111
  'overlay-breakpoint'?: number | string;
111
112
  locale?: string;
112
113
  };
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.55.0",
4
+ "version": "0.55.1",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-framework",
@@ -69,6 +69,20 @@ constrained.
69
69
  .page { height: 100%; } /* or any constrained height on the parent */
70
70
  ```
71
71
 
72
+ A long list usually does the opposite on purpose — the page scrolls, and the layout is as tall as
73
+ the list. The detail pane is built for that too: it sticks to the top of the scroll container it
74
+ sits in, so a record picked far down the list is shown beside it, in view. Its height is capped at
75
+ what that container shows (a longer record scrolls inside the pane), and below it there is nothing
76
+ but the list. In overlay mode the covering pane takes the visible height of that container the
77
+ same way, rather than the whole list's height.
78
+
79
+ With a height from the parent the pane fills it, as before. If a sticky bar sits at the top of the
80
+ scroll container, move the pane below it:
81
+
82
+ ```css
83
+ u-master-detail-layout::part(detail) { top: 3rem; } /* the cap leaves room for the offset */
84
+ ```
85
+
72
86
  ## Printing
73
87
 
74
88
  On print media the panes follow what was on screen. Side by side, both panes print and grow
@@ -98,7 +112,7 @@ own margin; that is inherent to printing two columns.
98
112
  | Property | Type | Default | Reflect | Description |
99
113
  |----------|------|---------|---------|-------------|
100
114
  | `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 |
115
+ | `detailSize` | `string \| undefined` | `undefined` | ✓ | Fixed width of the detail pane (`detail-size`) — the master then fills the rest and `masterSize` is not used |
102
116
  | `overlayBreakpoint` | `number` | `760` | | Below this self width in px, detail becomes a full overlay (`overlay-breakpoint`) |
103
117
  | `locale` | `string` | `''` | | Language tag for the close button's accessible label |
104
118