@vaadin/master-detail-layout 25.1.0-beta4 → 25.2.0-alpha1
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/custom-elements.json +45 -80
- package/package.json +8 -8
- package/src/styles/vaadin-master-detail-layout-base-styles.js +69 -133
- package/src/styles/vaadin-master-detail-layout-transition-base-styles.js +4 -44
- package/src/vaadin-master-detail-layout.d.ts +30 -62
- package/src/vaadin-master-detail-layout.js +154 -244
- package/web-types.json +31 -75
- package/web-types.lit.json +15 -29
|
@@ -8,12 +8,20 @@ import { getFocusableElements } from '@vaadin/a11y-base/src/focus-utils.js';
|
|
|
8
8
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
9
9
|
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
|
|
10
10
|
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
|
|
11
|
-
import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
|
|
12
11
|
import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
|
|
13
12
|
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
|
|
14
13
|
import { masterDetailLayoutStyles } from './styles/vaadin-master-detail-layout-base-styles.js';
|
|
15
14
|
import { masterDetailLayoutTransitionStyles } from './styles/vaadin-master-detail-layout-transition-base-styles.js';
|
|
16
15
|
|
|
16
|
+
function parseTrackSizes(gridTemplate) {
|
|
17
|
+
return gridTemplate
|
|
18
|
+
.replace(/\[[^\]]+\]/gu, '')
|
|
19
|
+
.replace(/\s+/gu, ' ')
|
|
20
|
+
.trim()
|
|
21
|
+
.split(' ')
|
|
22
|
+
.map(parseFloat);
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* `<vaadin-master-detail-layout>` is a web component for building UIs with a master
|
|
19
27
|
* (or primary) area and a detail (or secondary) area that is displayed next to, or
|
|
@@ -25,19 +33,19 @@ import { masterDetailLayoutTransitionStyles } from './styles/vaadin-master-detai
|
|
|
25
33
|
*
|
|
26
34
|
* Part name | Description
|
|
27
35
|
* ---------------|----------------------
|
|
28
|
-
* `backdrop` | Backdrop covering the master area in the
|
|
36
|
+
* `backdrop` | Backdrop covering the master area in the overlay mode
|
|
29
37
|
* `master` | The master area
|
|
30
38
|
* `detail` | The detail area
|
|
31
39
|
*
|
|
32
40
|
* The following state attributes are available for styling:
|
|
33
41
|
*
|
|
34
|
-
* Attribute
|
|
35
|
-
*
|
|
36
|
-
* `
|
|
37
|
-
* `orientation`
|
|
38
|
-
* `has-detail`
|
|
39
|
-
* `
|
|
40
|
-
* `
|
|
42
|
+
* Attribute | Description
|
|
43
|
+
* ----------------------|----------------------
|
|
44
|
+
* `expand` | Set to `master`, `detail`, or `both`.
|
|
45
|
+
* `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
|
|
46
|
+
* `has-detail` | Set when the detail content is provided and visible.
|
|
47
|
+
* `overflow` | Set when columns don't fit and the detail is shown as an overlay.
|
|
48
|
+
* `overlay-containment` | Set to `layout` or `viewport`.
|
|
41
49
|
*
|
|
42
50
|
* The following custom CSS properties are available for styling:
|
|
43
51
|
*
|
|
@@ -51,17 +59,16 @@ import { masterDetailLayoutTransitionStyles } from './styles/vaadin-master-detai
|
|
|
51
59
|
*
|
|
52
60
|
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
53
61
|
*
|
|
54
|
-
* @fires {CustomEvent} backdrop-click - Fired when the user clicks the backdrop in the
|
|
62
|
+
* @fires {CustomEvent} backdrop-click - Fired when the user clicks the backdrop in the overlay mode.
|
|
55
63
|
* @fires {CustomEvent} detail-escape-press - Fired when the user presses Escape in the detail area.
|
|
56
64
|
*
|
|
57
65
|
* @customElement vaadin-master-detail-layout
|
|
58
66
|
* @extends HTMLElement
|
|
59
67
|
* @mixes ThemableMixin
|
|
60
68
|
* @mixes ElementMixin
|
|
61
|
-
* @mixes ResizeMixin
|
|
62
69
|
* @mixes SlotStylesMixin
|
|
63
70
|
*/
|
|
64
|
-
class MasterDetailLayout extends SlotStylesMixin(
|
|
71
|
+
class MasterDetailLayout extends SlotStylesMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) {
|
|
65
72
|
static get is() {
|
|
66
73
|
return 'vaadin-master-detail-layout';
|
|
67
74
|
}
|
|
@@ -73,11 +80,10 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
73
80
|
static get properties() {
|
|
74
81
|
return {
|
|
75
82
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* either as drawer or stack, depending on the `stackOverlay` property.
|
|
83
|
+
* Size (in CSS length units) to be set on the detail area in
|
|
84
|
+
* the CSS grid layout. If there is not enough space to show
|
|
85
|
+
* master and detail areas next to each other, the detail area
|
|
86
|
+
* is shown as an overlay. Defaults to 15em.
|
|
81
87
|
*
|
|
82
88
|
* @attr {string} detail-size
|
|
83
89
|
*/
|
|
@@ -88,26 +94,10 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
88
94
|
},
|
|
89
95
|
|
|
90
96
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* either as drawer or stack, depending on the `stackOverlay` property.
|
|
96
|
-
*
|
|
97
|
-
* @attr {string} detail-min-size
|
|
98
|
-
*/
|
|
99
|
-
detailMinSize: {
|
|
100
|
-
type: String,
|
|
101
|
-
sync: true,
|
|
102
|
-
observer: '__detailMinSizeChanged',
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Fixed size (in CSS length units) to be set on the master area.
|
|
107
|
-
* When specified, it prevents the master area from growing or
|
|
108
|
-
* shrinking. If there is not enough space to show master and detail
|
|
109
|
-
* areas next to each other, the details are shown as an overlay:
|
|
110
|
-
* either as drawer or stack, depending on the `stackOverlay` property.
|
|
97
|
+
* Size (in CSS length units) to be set on the master area in
|
|
98
|
+
* the CSS grid layout. If there is not enough space to show
|
|
99
|
+
* master and detail areas next to each other, the detail area
|
|
100
|
+
* is shown as an overlay. Defaults to 30em.
|
|
111
101
|
*
|
|
112
102
|
* @attr {string} master-size
|
|
113
103
|
*/
|
|
@@ -118,18 +108,16 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
118
108
|
},
|
|
119
109
|
|
|
120
110
|
/**
|
|
121
|
-
*
|
|
122
|
-
* When
|
|
123
|
-
*
|
|
124
|
-
* areas next to each other, the details are shown as an overlay:
|
|
125
|
-
* either as drawer or stack, depending on the `stackOverlay` property.
|
|
111
|
+
* Size (in CSS length units) for the detail area when shown as an
|
|
112
|
+
* overlay. When not set, falls back to `detailSize`. Set to `100%`
|
|
113
|
+
* to make the detail cover the full layout.
|
|
126
114
|
*
|
|
127
|
-
* @attr {string}
|
|
115
|
+
* @attr {string} overlay-size
|
|
128
116
|
*/
|
|
129
|
-
|
|
117
|
+
overlaySize: {
|
|
130
118
|
type: String,
|
|
131
119
|
sync: true,
|
|
132
|
-
observer: '
|
|
120
|
+
observer: '__overlaySizeChanged',
|
|
133
121
|
},
|
|
134
122
|
|
|
135
123
|
/**
|
|
@@ -142,25 +130,6 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
142
130
|
type: String,
|
|
143
131
|
value: 'horizontal',
|
|
144
132
|
reflectToAttribute: true,
|
|
145
|
-
observer: '__orientationChanged',
|
|
146
|
-
sync: true,
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* When specified, forces the details to be shown as an overlay
|
|
151
|
-
* (either as drawer or stack), even if there is enough space for
|
|
152
|
-
* master and detail to be shown next to each other using the default
|
|
153
|
-
* (split) mode.
|
|
154
|
-
*
|
|
155
|
-
* In order to enforce the stack mode, use this property together with
|
|
156
|
-
* `stackOverlay` property and set both to `true`.
|
|
157
|
-
*
|
|
158
|
-
* @attr {boolean} force-overlay
|
|
159
|
-
*/
|
|
160
|
-
forceOverlay: {
|
|
161
|
-
type: Boolean,
|
|
162
|
-
value: false,
|
|
163
|
-
observer: '__forceOverlayChanged',
|
|
164
133
|
sync: true,
|
|
165
134
|
},
|
|
166
135
|
|
|
@@ -169,8 +138,10 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
169
138
|
* overlay mode. When set to `layout`, the overlay is confined to the
|
|
170
139
|
* layout. When set to `viewport`, the overlay is confined to the
|
|
171
140
|
* browser's viewport. Defaults to `layout`.
|
|
141
|
+
*
|
|
142
|
+
* @attr {string} overlay-containment
|
|
172
143
|
*/
|
|
173
|
-
|
|
144
|
+
overlayContainment: {
|
|
174
145
|
type: String,
|
|
175
146
|
value: 'layout',
|
|
176
147
|
reflectToAttribute: true,
|
|
@@ -178,19 +149,14 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
178
149
|
},
|
|
179
150
|
|
|
180
151
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* In order to enforce the stack mode, use this property together with
|
|
186
|
-
* `forceOverlay` property and set both to `true`.
|
|
187
|
-
*
|
|
188
|
-
* @attr {string} stack-threshold
|
|
152
|
+
* Controls which column(s) expand to fill available space.
|
|
153
|
+
* Possible values: `'master'`, `'detail'`, `'both'`.
|
|
154
|
+
* Defaults to `'both'`.
|
|
189
155
|
*/
|
|
190
|
-
|
|
191
|
-
type:
|
|
192
|
-
value:
|
|
193
|
-
|
|
156
|
+
expand: {
|
|
157
|
+
type: String,
|
|
158
|
+
value: 'both',
|
|
159
|
+
reflectToAttribute: true,
|
|
194
160
|
sync: true,
|
|
195
161
|
},
|
|
196
162
|
|
|
@@ -203,39 +169,6 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
203
169
|
type: Boolean,
|
|
204
170
|
value: false,
|
|
205
171
|
},
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* When true, the component uses the drawer mode. This property is read-only.
|
|
209
|
-
* @protected
|
|
210
|
-
*/
|
|
211
|
-
_drawer: {
|
|
212
|
-
type: Boolean,
|
|
213
|
-
attribute: 'drawer',
|
|
214
|
-
reflectToAttribute: true,
|
|
215
|
-
sync: true,
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* When true, the component uses the stack mode. This property is read-only.
|
|
220
|
-
* @protected
|
|
221
|
-
*/
|
|
222
|
-
_stack: {
|
|
223
|
-
type: Boolean,
|
|
224
|
-
attribute: 'stack',
|
|
225
|
-
reflectToAttribute: true,
|
|
226
|
-
sync: true,
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* When true, the component has the detail content provided.
|
|
231
|
-
* @protected
|
|
232
|
-
*/
|
|
233
|
-
_hasDetail: {
|
|
234
|
-
type: Boolean,
|
|
235
|
-
attribute: 'has-detail',
|
|
236
|
-
reflectToAttribute: true,
|
|
237
|
-
sync: true,
|
|
238
|
-
},
|
|
239
172
|
};
|
|
240
173
|
}
|
|
241
174
|
|
|
@@ -243,194 +176,168 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
243
176
|
return true;
|
|
244
177
|
}
|
|
245
178
|
|
|
246
|
-
/** @
|
|
179
|
+
/** @return {!Array<!CSSResult>} */
|
|
247
180
|
get slotStyles() {
|
|
248
181
|
return [masterDetailLayoutTransitionStyles];
|
|
249
182
|
}
|
|
250
183
|
|
|
251
184
|
/** @protected */
|
|
252
185
|
render() {
|
|
186
|
+
const isOverlay = this.hasAttribute('has-detail') && this.hasAttribute('overflow');
|
|
187
|
+
const isViewport = isOverlay && this.overlayContainment === 'viewport';
|
|
188
|
+
const isLayoutContained = isOverlay && !isViewport;
|
|
189
|
+
|
|
253
190
|
return html`
|
|
254
191
|
<div part="backdrop" @click="${this.__onBackdropClick}"></div>
|
|
192
|
+
<div id="master" part="master" ?inert="${isLayoutContained}">
|
|
193
|
+
<slot @slotchange="${this.__onSlotChange}"></slot>
|
|
194
|
+
</div>
|
|
255
195
|
<div
|
|
256
|
-
id="
|
|
257
|
-
part="
|
|
258
|
-
|
|
196
|
+
id="detail"
|
|
197
|
+
part="detail"
|
|
198
|
+
role="${isOverlay ? 'dialog' : nothing}"
|
|
199
|
+
aria-modal="${isViewport ? 'true' : nothing}"
|
|
200
|
+
@keydown="${this.__onDetailKeydown}"
|
|
259
201
|
>
|
|
260
|
-
<slot></slot>
|
|
261
|
-
</div>
|
|
262
|
-
<div part="_detail-internal">
|
|
263
|
-
<div
|
|
264
|
-
id="detail"
|
|
265
|
-
part="detail"
|
|
266
|
-
role="${this._drawer || this._stack ? 'dialog' : nothing}"
|
|
267
|
-
aria-modal="${this._drawer && this.containment === 'viewport' ? 'true' : nothing}"
|
|
268
|
-
@keydown="${this.__onDetailKeydown}"
|
|
269
|
-
>
|
|
270
|
-
<slot name="detail" @slotchange="${this.__onDetailSlotChange}"></slot>
|
|
271
|
-
</div>
|
|
202
|
+
<slot name="detail" @slotchange="${this.__onSlotChange}"></slot>
|
|
272
203
|
</div>
|
|
273
204
|
`;
|
|
274
205
|
}
|
|
275
206
|
|
|
276
|
-
/** @
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this._hasDetail = children.length > 0;
|
|
281
|
-
this.__detectLayoutMode();
|
|
282
|
-
|
|
283
|
-
// Move focus to the detail area when it is added to the DOM,
|
|
284
|
-
// in case if the layout is using drawer or stack mode.
|
|
285
|
-
if ((this._drawer || this._stack) && children.length > 0) {
|
|
286
|
-
const focusables = getFocusableElements(children[0]);
|
|
287
|
-
if (focusables.length) {
|
|
288
|
-
focusables[0].focus();
|
|
289
|
-
}
|
|
290
|
-
}
|
|
207
|
+
/** @protected */
|
|
208
|
+
connectedCallback() {
|
|
209
|
+
super.connectedCallback();
|
|
210
|
+
this.__initResizeObserver();
|
|
291
211
|
}
|
|
292
212
|
|
|
293
|
-
/** @
|
|
294
|
-
|
|
295
|
-
|
|
213
|
+
/** @protected */
|
|
214
|
+
disconnectedCallback() {
|
|
215
|
+
super.disconnectedCallback();
|
|
216
|
+
this.__resizeObserver.disconnect();
|
|
217
|
+
cancelAnimationFrame(this.__resizeRaf);
|
|
296
218
|
}
|
|
297
219
|
|
|
298
220
|
/** @private */
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// Prevent firing on parent layout when using nested layouts
|
|
302
|
-
event.preventDefault();
|
|
303
|
-
this.dispatchEvent(new CustomEvent('detail-escape-press'));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* @protected
|
|
309
|
-
* @override
|
|
310
|
-
*/
|
|
311
|
-
_onResize() {
|
|
312
|
-
this.__detectLayoutMode();
|
|
221
|
+
__masterSizeChanged(size, oldSize) {
|
|
222
|
+
this.__updateStyleProperty('master-size', size, oldSize);
|
|
313
223
|
}
|
|
314
224
|
|
|
315
225
|
/** @private */
|
|
316
226
|
__detailSizeChanged(size, oldSize) {
|
|
317
227
|
this.__updateStyleProperty('detail-size', size, oldSize);
|
|
318
|
-
this.__detectLayoutMode();
|
|
319
228
|
}
|
|
320
229
|
|
|
321
230
|
/** @private */
|
|
322
|
-
|
|
323
|
-
this.__updateStyleProperty('
|
|
324
|
-
this.__detectLayoutMode();
|
|
231
|
+
__overlaySizeChanged(size, oldSize) {
|
|
232
|
+
this.__updateStyleProperty('overlay-size', size, oldSize);
|
|
325
233
|
}
|
|
326
234
|
|
|
327
235
|
/** @private */
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
236
|
+
__updateStyleProperty(prop, size, oldSize) {
|
|
237
|
+
if (size) {
|
|
238
|
+
this.style.setProperty(`--_${prop}`, size);
|
|
239
|
+
} else if (oldSize) {
|
|
240
|
+
this.style.removeProperty(`--_${prop}`);
|
|
241
|
+
}
|
|
331
242
|
}
|
|
332
243
|
|
|
333
244
|
/** @private */
|
|
334
|
-
|
|
335
|
-
this.
|
|
336
|
-
this.__detectLayoutMode();
|
|
245
|
+
__onSlotChange() {
|
|
246
|
+
this.__initResizeObserver();
|
|
337
247
|
}
|
|
338
248
|
|
|
339
249
|
/** @private */
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
250
|
+
__initResizeObserver() {
|
|
251
|
+
this.__resizeObserver = this.__resizeObserver || new ResizeObserver(() => this.__onResize());
|
|
252
|
+
this.__resizeObserver.disconnect();
|
|
253
|
+
|
|
254
|
+
const children = this.querySelectorAll(':scope > [slot="detail"], :scope >:not([slot])');
|
|
255
|
+
[this, this.$.master, this.$.detail, ...children].forEach((node) => {
|
|
256
|
+
this.__resizeObserver.observe(node);
|
|
257
|
+
});
|
|
344
258
|
}
|
|
345
259
|
|
|
346
|
-
/**
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Called by the ResizeObserver. Reads layout state synchronously (no forced
|
|
262
|
+
* reflow since layout is already computed), then defers writes to rAF.
|
|
263
|
+
* Cancels any pending rAF so the write phase always uses the latest state.
|
|
264
|
+
* @private
|
|
265
|
+
*/
|
|
266
|
+
__onResize() {
|
|
267
|
+
const state = this.__computeLayoutState();
|
|
268
|
+
cancelAnimationFrame(this.__resizeRaf);
|
|
269
|
+
this.__resizeRaf = requestAnimationFrame(() => this.__applyLayoutState(state));
|
|
351
270
|
}
|
|
352
271
|
|
|
353
|
-
/**
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Reads DOM/style state needed for layout detection. Safe to call in
|
|
274
|
+
* ResizeObserver callback where layout is already computed (no forced reflow).
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
__computeLayoutState() {
|
|
278
|
+
const detailContent = this.querySelector('[slot="detail"]');
|
|
279
|
+
const hadDetail = this.hasAttribute('has-detail');
|
|
280
|
+
const hasDetail = detailContent != null && detailContent.checkVisibility();
|
|
281
|
+
const hasOverflow = hasDetail && this.__checkOverflow();
|
|
282
|
+
const focusTarget = !hadDetail && hasDetail && hasOverflow ? getFocusableElements(detailContent)[0] : null;
|
|
283
|
+
return { hadDetail, hasDetail, hasOverflow, focusTarget };
|
|
358
284
|
}
|
|
359
285
|
|
|
360
|
-
/**
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Applies layout state to DOM attributes. Pure writes, no reads.
|
|
288
|
+
* @private
|
|
289
|
+
*/
|
|
290
|
+
__applyLayoutState({ hadDetail, hasDetail, hasOverflow, focusTarget }) {
|
|
291
|
+
// Set keep-detail-column-offscreen when detail first appears with overflow
|
|
292
|
+
// to prevent master width from jumping.
|
|
293
|
+
if (!hadDetail && hasDetail && hasOverflow) {
|
|
294
|
+
this.setAttribute('keep-detail-column-offscreen', '');
|
|
295
|
+
} else if (!hasDetail || !hasOverflow) {
|
|
296
|
+
this.removeAttribute('keep-detail-column-offscreen');
|
|
366
297
|
}
|
|
367
298
|
|
|
368
|
-
this.toggleAttribute(
|
|
369
|
-
|
|
299
|
+
this.toggleAttribute('has-detail', hasDetail);
|
|
300
|
+
this.toggleAttribute('overflow', hasOverflow);
|
|
370
301
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
302
|
+
// Re-render to update ARIA attributes (role, aria-modal, inert)
|
|
303
|
+
// which depend on has-detail and overflow state.
|
|
304
|
+
this.requestUpdate();
|
|
305
|
+
|
|
306
|
+
if (focusTarget) {
|
|
307
|
+
focusTarget.focus();
|
|
377
308
|
}
|
|
378
309
|
}
|
|
379
310
|
|
|
380
311
|
/** @private */
|
|
381
|
-
|
|
382
|
-
this.
|
|
383
|
-
|
|
312
|
+
__checkOverflow() {
|
|
313
|
+
const isVertical = this.orientation === 'vertical';
|
|
314
|
+
const computedStyle = getComputedStyle(this);
|
|
384
315
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
316
|
+
const hostSize = parseFloat(computedStyle[isVertical ? 'height' : 'width']);
|
|
317
|
+
const [masterSize, masterExtra, detailSize] = parseTrackSizes(
|
|
318
|
+
computedStyle[isVertical ? 'gridTemplateRows' : 'gridTemplateColumns'],
|
|
319
|
+
);
|
|
389
320
|
|
|
390
|
-
if (
|
|
391
|
-
return;
|
|
321
|
+
if (Math.floor(masterSize + masterExtra + detailSize) <= Math.floor(hostSize)) {
|
|
322
|
+
return false;
|
|
392
323
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
this.__detectVerticalMode();
|
|
396
|
-
} else {
|
|
397
|
-
this.__detectHorizontalMode();
|
|
324
|
+
if (Math.floor(masterExtra) >= Math.floor(detailSize)) {
|
|
325
|
+
return false;
|
|
398
326
|
}
|
|
327
|
+
return true;
|
|
399
328
|
}
|
|
400
329
|
|
|
401
330
|
/** @private */
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
// Detect minimum width needed by master content. Use max-width to ensure
|
|
406
|
-
// the layout can switch back to split mode once there is enough space.
|
|
407
|
-
// If there is master size or min-size set, use that instead to force the
|
|
408
|
-
// overlay mode by setting `masterSize` / `masterMinSize` to 100%/
|
|
409
|
-
this.$.master.style.maxWidth = this.masterSize || this.masterMinSize || 'min-content';
|
|
410
|
-
const masterWidth = this.$.master.offsetWidth;
|
|
411
|
-
this.$.master.style.maxWidth = '';
|
|
412
|
-
|
|
413
|
-
// If the combined minimum size of both the master and the detail content
|
|
414
|
-
// exceeds the size of the layout, the layout changes to the overlay mode.
|
|
415
|
-
this.__setOverlayMode(this.offsetWidth < masterWidth + detailWidth);
|
|
416
|
-
|
|
417
|
-
// Toggling the overlay resizes master content, which can cause document
|
|
418
|
-
// scroll bar to appear or disappear, and trigger another resize of the
|
|
419
|
-
// layout which can affect previous measurements and end up in horizontal
|
|
420
|
-
// scroll. Check if that is the case and if so, preserve the overlay mode.
|
|
421
|
-
if (this.offsetWidth < this.scrollWidth) {
|
|
422
|
-
this.__setOverlayMode(true);
|
|
423
|
-
}
|
|
331
|
+
__onBackdropClick() {
|
|
332
|
+
this.dispatchEvent(new CustomEvent('backdrop-click'));
|
|
424
333
|
}
|
|
425
334
|
|
|
426
335
|
/** @private */
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (this.offsetHeight < masterHeight + this.$.detail.clientHeight) {
|
|
433
|
-
this.__setOverlayMode(true);
|
|
336
|
+
__onDetailKeydown(event) {
|
|
337
|
+
if (event.key === 'Escape' && !event.defaultPrevented) {
|
|
338
|
+
// Prevent firing on parent layout when using nested layouts
|
|
339
|
+
event.preventDefault();
|
|
340
|
+
this.dispatchEvent(new CustomEvent('detail-escape-press'));
|
|
434
341
|
}
|
|
435
342
|
}
|
|
436
343
|
|
|
@@ -529,10 +436,13 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
529
436
|
* @protected
|
|
530
437
|
*/
|
|
531
438
|
async _finishTransition() {
|
|
532
|
-
// Detect
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
queueMicrotask(() =>
|
|
439
|
+
// Detect layout mode before resolving the transition, so the browser's
|
|
440
|
+
// "new" snapshot includes the correct overlay state. The microtask runs
|
|
441
|
+
// before the Promise resolution propagates to startViewTransition.
|
|
442
|
+
queueMicrotask(() => {
|
|
443
|
+
const state = this.__computeLayoutState();
|
|
444
|
+
this.__applyLayoutState(state);
|
|
445
|
+
});
|
|
536
446
|
|
|
537
447
|
if (!this.__transition) {
|
|
538
448
|
return Promise.resolve();
|
|
@@ -547,7 +457,7 @@ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(Themab
|
|
|
547
457
|
|
|
548
458
|
/**
|
|
549
459
|
* @event backdrop-click
|
|
550
|
-
* Fired when the user clicks the backdrop in the
|
|
460
|
+
* Fired when the user clicks the backdrop in the overlay mode.
|
|
551
461
|
*/
|
|
552
462
|
|
|
553
463
|
/**
|