Skip to content

Commit 20802b4

Browse files
authored
feat: add min-size properties and overlay mode to master-detail-layout (#8789)
1 parent 6aa357a commit 20802b4

File tree

6 files changed

+322
-13
lines changed

6 files changed

+322
-13
lines changed

dev/master-detail-layout.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,22 @@
1414
vaadin-master-detail-layout {
1515
border: solid 1px #ccc;
1616
}
17+
18+
vaadin-master-detail-layout[overlay]::part(master) {
19+
background-color: var(--lumo-shade-20pct);
20+
}
21+
22+
vaadin-master-detail-layout[overlay]::part(detail) {
23+
background: #fff;
24+
border-left: solid 1px #ccc;
25+
}
1726
</style>
1827

1928
<p>
2029
<vaadin-checkbox id="detailSize" label="Set detail size"></vaadin-checkbox>
30+
<vaadin-checkbox id="detailMinSize" label="Set detail min-size"></vaadin-checkbox>
2131
<vaadin-checkbox id="masterSize" label="Set master size"></vaadin-checkbox>
32+
<vaadin-checkbox id="masterMinSize" label="Set master min-size"></vaadin-checkbox>
2233
</p>
2334

2435
<vaadin-master-detail-layout>
@@ -38,9 +49,17 @@
3849
layout.detailSize = e.target.checked ? '300px' : null;
3950
});
4051

52+
document.querySelector('#detailMinSize').addEventListener('change', (e) => {
53+
layout.detailMinSize = e.target.checked ? '300px' : null;
54+
});
55+
4156
document.querySelector('#masterSize').addEventListener('change', (e) => {
4257
layout.masterSize = e.target.checked ? '300px' : null;
4358
});
59+
60+
document.querySelector('#masterMinSize').addEventListener('change', (e) => {
61+
layout.masterMinSize = e.target.checked ? '300px' : null;
62+
});
4463
</script>
4564
</body>
4665
</html>

packages/master-detail-layout/src/vaadin-master-detail-layout.d.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,56 @@
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
66
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7+
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
78
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
89

910
/**
1011
* `<vaadin-master-detail-layout>` is a web component for building UIs with a master
1112
* (or primary) area and a detail (or secondary) area that is displayed next to, or
1213
* overlaid on top of, the master area, depending on configuration and viewport size.
1314
*/
14-
declare class MasterDetailLayout extends ThemableMixin(ElementMixin(HTMLElement)) {
15+
declare class MasterDetailLayout extends ResizeMixin(ThemableMixin(ElementMixin(HTMLElement))) {
1516
/**
1617
* Fixed size (in CSS length units) to be set on the detail area.
17-
* When specified, it prevents the detail area from growing.
18+
* When specified, it prevents the detail area from growing or
19+
* shrinking. If there is not enough space to show master and detail
20+
* areas next to each other, the layout switches to the overlay mode.
1821
*
1922
* @attr {string} detail-size
2023
*/
2124
detailSize: string | null | undefined;
2225

26+
/**
27+
* Minimum size (in CSS length units) to be set on the detail area.
28+
* When specified, it prevents the detail area from shrinking below
29+
* this size. If there is not enough space to show master and detail
30+
* areas next to each other, the layout switches to the overlay mode.
31+
*
32+
* @attr {string} detail-min-size
33+
*/
34+
detailMinSize: string | null | undefined;
35+
2336
/**
2437
* Fixed size (in CSS length units) to be set on the master area.
25-
* When specified, it prevents the master area from growing.
38+
* When specified, it prevents the master area from growing or
39+
* shrinking. If there is not enough space to show master and detail
40+
* areas next to each other, the layout switches to the overlay mode.
41+
* Setting `100%` enforces the overlay mode to be used by default.
2642
*
2743
* @attr {string} master-size
2844
*/
2945
masterSize: string | null | undefined;
46+
47+
/**
48+
* Minimum size (in CSS length units) to be set on the master area.
49+
* When specified, it prevents the master area from shrinking below
50+
* this size. If there is not enough space to show master and detail
51+
* areas next to each other, the layout switches to the overlay mode.
52+
* Setting `100%` enforces the overlay mode to be used by default.
53+
*
54+
* @attr {string} master-min-size
55+
*/
56+
masterMinSize: string | null | undefined;
3057
}
3158

3259
declare global {

packages/master-detail-layout/src/vaadin-master-detail-layout.js

Lines changed: 125 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { css, html, LitElement } from 'lit';
77
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
88
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
99
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
10+
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
1011
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
1112

1213
/**
@@ -18,8 +19,9 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
1819
* @extends HTMLElement
1920
* @mixes ThemableMixin
2021
* @mixes ElementMixin
22+
* @mixes ResizeMixin
2123
*/
22-
class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElement))) {
24+
class MasterDetailLayout extends ResizeMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) {
2325
static get is() {
2426
return 'vaadin-master-detail-layout';
2527
}
@@ -40,6 +42,23 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
4042
display: none;
4143
}
4244
45+
/* Overlay mode */
46+
:host([overlay][has-detail]) {
47+
position: relative;
48+
}
49+
50+
:host([overlay]) [part='detail'] {
51+
position: absolute;
52+
inset-inline-end: 0;
53+
height: 100%;
54+
width: var(--_detail-min-size, min-content);
55+
max-width: 100%;
56+
}
57+
58+
:host([overlay]) [part='master'] {
59+
max-width: 100%;
60+
}
61+
4362
/* No fixed size */
4463
:host(:not([has-master-size])) [part='master'],
4564
:host(:not([has-detail-size])) [part='detail'] {
@@ -67,14 +86,30 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
6786
flex-grow: 1;
6887
flex-basis: var(--_detail-size);
6988
}
89+
90+
/* Min size */
91+
:host([has-master-min-size]:not([overlay])) [part='master'] {
92+
min-width: var(--_master-min-size);
93+
}
94+
95+
:host([has-detail-min-size]:not([overlay])) [part='detail'] {
96+
min-width: var(--_detail-min-size);
97+
}
98+
99+
:host([has-master-min-size]) [part='master'],
100+
:host([has-detail-min-size]) [part='detail'] {
101+
flex-shrink: 0;
102+
}
70103
`;
71104
}
72105

73106
static get properties() {
74107
return {
75108
/**
76109
* Fixed size (in CSS length units) to be set on the detail area.
77-
* When specified, it prevents the detail area from growing.
110+
* When specified, it prevents the detail area from growing or
111+
* shrinking. If there is not enough space to show master and detail
112+
* areas next to each other, the layout switches to the overlay mode.
78113
*
79114
* @attr {string} detail-size
80115
*/
@@ -84,9 +119,26 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
84119
observer: '__detailSizeChanged',
85120
},
86121

122+
/**
123+
* Minimum size (in CSS length units) to be set on the detail area.
124+
* When specified, it prevents the detail area from shrinking below
125+
* this size. If there is not enough space to show master and detail
126+
* areas next to each other, the layout switches to the overlay mode.
127+
*
128+
* @attr {string} detail-min-size
129+
*/
130+
detailMinSize: {
131+
type: String,
132+
sync: true,
133+
observer: '__detailMinSizeChanged',
134+
},
135+
87136
/**
88137
* Fixed size (in CSS length units) to be set on the master area.
89-
* When specified, it prevents the master area from growing.
138+
* When specified, it prevents the master area from growing or
139+
* shrinking. If there is not enough space to show master and detail
140+
* areas next to each other, the layout switches to the overlay mode.
141+
* Setting `100%` enforces the overlay mode to be used by default.
90142
*
91143
* @attr {string} master-size
92144
*/
@@ -95,16 +147,31 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
95147
sync: true,
96148
observer: '__masterSizeChanged',
97149
},
150+
151+
/**
152+
* Minimum size (in CSS length units) to be set on the master area.
153+
* When specified, it prevents the master area from shrinking below
154+
* this size. If there is not enough space to show master and detail
155+
* areas next to each other, the layout switches to the overlay mode.
156+
* Setting `100%` enforces the overlay mode to be used by default.
157+
*
158+
* @attr {string} master-min-size
159+
*/
160+
masterMinSize: {
161+
type: String,
162+
sync: true,
163+
observer: '__masterMinSizeChanged',
164+
},
98165
};
99166
}
100167

101168
/** @protected */
102169
render() {
103170
return html`
104-
<div part="master">
171+
<div id="master" part="master">
105172
<slot></slot>
106173
</div>
107-
<div part="detail">
174+
<div id="detail" part="detail">
108175
<slot name="detail" @slotchange="${this.__onDetailSlotChange}"></slot>
109176
</div>
110177
`;
@@ -115,16 +182,36 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
115182
this.toggleAttribute('has-detail', e.target.assignedNodes().length > 0);
116183
}
117184

185+
/**
186+
* @protected
187+
* @override
188+
*/
189+
_onResize() {
190+
this.__detectLayoutMode();
191+
}
192+
118193
/** @private */
119194
__detailSizeChanged(size, oldSize) {
120-
this.toggleAttribute('has-detail-size', !!size);
121195
this.__updateStyleProperty('detail-size', size, oldSize);
196+
this.__detectLayoutMode();
197+
}
198+
199+
/** @private */
200+
__detailMinSizeChanged(size, oldSize) {
201+
this.__updateStyleProperty('detail-min-size', size, oldSize);
202+
this.__detectLayoutMode();
122203
}
123204

124205
/** @private */
125206
__masterSizeChanged(size, oldSize) {
126-
this.toggleAttribute('has-master-size', !!size);
127207
this.__updateStyleProperty('master-size', size, oldSize);
208+
this.__detectLayoutMode();
209+
}
210+
211+
/** @private */
212+
__masterMinSizeChanged(size, oldSize) {
213+
this.__updateStyleProperty('master-min-size', size, oldSize);
214+
this.__detectLayoutMode();
128215
}
129216

130217
/** @private */
@@ -134,6 +221,37 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
134221
} else if (oldSize) {
135222
this.style.removeProperty(`--_${prop}`);
136223
}
224+
225+
this.toggleAttribute(`has-${prop}`, !!size);
226+
}
227+
228+
/** @private */
229+
__detectLayoutMode() {
230+
const detailWidth = this.$.detail.offsetWidth;
231+
232+
// Detect minimum width needed by master content. Use max-width to ensure
233+
// the layout can switch back to split mode once there is enough space.
234+
// If there is master size or min-size set, use that instead to force the
235+
// overlay mode by setting `masterSize` / `masterMinSize` to 100%/
236+
this.$.master.style.maxWidth = this.masterSize || this.masterMinSize || 'min-content';
237+
const masterWidth = this.$.master.offsetWidth;
238+
this.$.master.style.maxWidth = '';
239+
240+
// If the combined minimum size of both the master and the detail content
241+
// exceeds the size of the layout, the layout changes to the overlay mode.
242+
if (this.offsetWidth < masterWidth + detailWidth) {
243+
this.setAttribute('overlay', '');
244+
} else {
245+
this.removeAttribute('overlay');
246+
}
247+
248+
// Toggling the overlay resizes master content, which can cause document
249+
// scroll bar to appear or disappear, and trigger another resize of the
250+
// layout which can affect previous measurements and end up in horizontal
251+
// scroll. Check if that is the case and if so, preserve the overlay mode.
252+
if (this.offsetWidth < this.scrollWidth) {
253+
this.setAttribute('overlay', '');
254+
}
137255
}
138256
}
139257

packages/master-detail-layout/test/dom/__snapshots__/master-detail-layout.test.snap.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
export const snapshots = {};
33

44
snapshots["vaadin-master-detail-layout shadow default"] =
5-
`<div part="master">
5+
`<div
6+
id="master"
7+
part="master"
8+
>
69
<slot>
710
</slot>
811
</div>
9-
<div part="detail">
12+
<div
13+
id="detail"
14+
part="detail"
15+
>
1016
<slot name="detail">
1117
</slot>
1218
</div>

0 commit comments

Comments
 (0)