@@ -7,6 +7,7 @@ import { css, html, LitElement } from 'lit';
7
7
import { defineCustomElement } from '@vaadin/component-base/src/define.js' ;
8
8
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js' ;
9
9
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js' ;
10
+ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js' ;
10
11
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js' ;
11
12
12
13
/**
@@ -18,8 +19,9 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
18
19
* @extends HTMLElement
19
20
* @mixes ThemableMixin
20
21
* @mixes ElementMixin
22
+ * @mixes ResizeMixin
21
23
*/
22
- class MasterDetailLayout extends ElementMixin ( ThemableMixin ( PolylitMixin ( LitElement ) ) ) {
24
+ class MasterDetailLayout extends ResizeMixin ( ElementMixin ( ThemableMixin ( PolylitMixin ( LitElement ) ) ) ) {
23
25
static get is ( ) {
24
26
return 'vaadin-master-detail-layout' ;
25
27
}
@@ -40,6 +42,23 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
40
42
display: none;
41
43
}
42
44
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
+
43
62
/* No fixed size */
44
63
:host(:not([has-master-size])) [part='master'],
45
64
:host(:not([has-detail-size])) [part='detail'] {
@@ -67,14 +86,30 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
67
86
flex-grow: 1;
68
87
flex-basis: var(--_detail-size);
69
88
}
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
+ }
70
103
` ;
71
104
}
72
105
73
106
static get properties ( ) {
74
107
return {
75
108
/**
76
109
* 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.
78
113
*
79
114
* @attr {string} detail-size
80
115
*/
@@ -84,9 +119,26 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
84
119
observer : '__detailSizeChanged' ,
85
120
} ,
86
121
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
+
87
136
/**
88
137
* 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.
90
142
*
91
143
* @attr {string} master-size
92
144
*/
@@ -95,16 +147,31 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
95
147
sync : true ,
96
148
observer : '__masterSizeChanged' ,
97
149
} ,
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
+ } ,
98
165
} ;
99
166
}
100
167
101
168
/** @protected */
102
169
render ( ) {
103
170
return html `
104
- < div part ="master ">
171
+ < div id =" master " part ="master ">
105
172
< slot > </ slot >
106
173
</ div >
107
- < div part ="detail ">
174
+ < div id =" detail " part ="detail ">
108
175
< slot name ="detail " @slotchange ="${ this . __onDetailSlotChange } "> </ slot >
109
176
</ div >
110
177
` ;
@@ -115,16 +182,36 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
115
182
this . toggleAttribute ( 'has-detail' , e . target . assignedNodes ( ) . length > 0 ) ;
116
183
}
117
184
185
+ /**
186
+ * @protected
187
+ * @override
188
+ */
189
+ _onResize ( ) {
190
+ this . __detectLayoutMode ( ) ;
191
+ }
192
+
118
193
/** @private */
119
194
__detailSizeChanged ( size , oldSize ) {
120
- this . toggleAttribute ( 'has-detail-size' , ! ! size ) ;
121
195
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 ( ) ;
122
203
}
123
204
124
205
/** @private */
125
206
__masterSizeChanged ( size , oldSize ) {
126
- this . toggleAttribute ( 'has-master-size' , ! ! size ) ;
127
207
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 ( ) ;
128
215
}
129
216
130
217
/** @private */
@@ -134,6 +221,37 @@ class MasterDetailLayout extends ElementMixin(ThemableMixin(PolylitMixin(LitElem
134
221
} else if ( oldSize ) {
135
222
this . style . removeProperty ( `--_${ prop } ` ) ;
136
223
}
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
+ }
137
255
}
138
256
}
139
257
0 commit comments