@vaadin/master-detail-layout 25.2.0-alpha1 → 25.2.0-alpha11

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.
@@ -7,125 +7,286 @@ import '@vaadin/component-base/src/styles/style-props.js';
7
7
  import { css } from 'lit';
8
8
 
9
9
  export const masterDetailLayoutStyles = css`
10
+ /* stylelint-disable no-duplicate-selectors */
10
11
  :host {
11
- --_master-size: 30em;
12
- --_detail-size: 15em;
13
- --_master-column: var(--_master-size) 0;
14
- --_detail-column: var(--_detail-size) 0;
12
+ --_rtl-multiplier: 1;
13
+ --_transition-duration: 0s;
14
+ --_transition-easing: cubic-bezier(0.78, 0, 0.22, 1);
15
15
 
16
16
  display: grid;
17
17
  box-sizing: border-box;
18
18
  height: 100%;
19
19
  position: relative;
20
+ overflow: clip;
21
+ }
22
+
23
+ :host:not([overlay-containment='page']) {
20
24
  z-index: 0;
21
- overflow: hidden;
22
- grid-template-columns: [master-start] var(--_master-column) [detail-start] var(--_detail-column) [detail-end];
23
- grid-template-rows: 100%;
24
25
  }
25
26
 
26
- :host([hidden]) {
27
+ :host([dir='rtl']) {
28
+ --_rtl-multiplier: -1;
29
+ }
30
+
31
+ :host([orientation='horizontal']) {
32
+ --_transition-offset: calc(30px * var(--_rtl-multiplier));
33
+ }
34
+
35
+ :host([orientation='vertical']) {
36
+ --_transition-offset: 0 30px;
37
+ }
38
+
39
+ :host([hidden]),
40
+ ::slotted([hidden]) {
27
41
  display: none !important;
28
42
  }
29
43
 
44
+ /* CSS grid template */
45
+
46
+ :host {
47
+ --_master-size: min(100%, 30rem);
48
+ --_master-extra: 0px;
49
+ --_detail-size: var(--_detail-cached-size);
50
+ --_detail-extra: 0px;
51
+ --_detail-cached-size: min-content;
52
+
53
+ /* prettier-ignore */
54
+ --_grid-template:
55
+ [master-start] var(--_master-size) [master-extra] var(--_master-extra)
56
+ [detail-start] var(--_detail-size) [detail-extra] var(--_detail-extra)
57
+ [detail-end];
58
+ }
59
+
60
+ :host([force-overlay]) {
61
+ /* prettier-ignore */
62
+ --_grid-template:
63
+ [master-start] var(--_master-size) [master-extra] var(--_master-extra)
64
+ [detail-start] 0px [detail-extra] 0px
65
+ [detail-end];
66
+ }
67
+
68
+ :host([orientation='horizontal']) {
69
+ grid-template-columns: var(--_grid-template);
70
+ grid-template-rows: 100%;
71
+ }
72
+
30
73
  :host([orientation='vertical']) {
31
74
  grid-template-columns: 100%;
32
- grid-template-rows: [master-start] var(--_master-column) [detail-start] var(--_detail-column) [detail-end];
75
+ grid-template-rows: var(--_grid-template);
33
76
  }
34
77
 
35
- [part~='master'],
36
- [part~='detail'] {
37
- box-sizing: border-box;
78
+ /* CSS grid placement */
79
+
80
+ :host {
81
+ --_master-area: master-start / detail-start;
82
+
83
+ /*
84
+ When the detail size isn't explicitly defined and the detail is set to expand,
85
+ the detail column template is 'min-content 1fr'. In this case, the detail area
86
+ should not span both columns initially (and when recalculating the detail size)
87
+ as spanning both would effectively collapse them into a single '1fr' column where
88
+ min-content resolves to 0, making it impossible to measure the detail's intrinsic
89
+ minimum width from JavaScript.
90
+ */
91
+ --_detail-area: detail-start / detail-extra;
38
92
  }
39
93
 
40
- [part~='master'] {
41
- grid-column: master-start / detail-start;
94
+ :host(:is([has-detail], [has-detail-placeholder]):not([recalculating-detail-size])) {
95
+ --_detail-area: detail-start / detail-end;
42
96
  }
43
97
 
44
- [part~='detail'] {
45
- grid-column: detail-start / detail-end;
98
+ :host([orientation='horizontal']) #master {
99
+ grid-column: var(--_master-area);
100
+ grid-row: 1;
46
101
  }
47
102
 
48
- :host([orientation='vertical']) [part~='master'] {
49
- grid-column: auto;
50
- grid-row: master-start / detail-start;
103
+ :host([orientation='vertical']) #master {
104
+ grid-column: 1;
105
+ grid-row: var(--_master-area);
51
106
  }
52
107
 
53
- :host([orientation='vertical']) [part~='detail'] {
54
- grid-column: auto;
55
- grid-row: detail-start / detail-end;
108
+ :host([orientation='horizontal']) :is(#detail, #detailPlaceholder, #detailOutgoing) {
109
+ grid-column: var(--_detail-area);
110
+ grid-row: 1;
56
111
  }
57
112
 
58
- [part~='backdrop'] {
113
+ :host([orientation='vertical']) :is(#detail, #detailPlaceholder, #detailOutgoing) {
114
+ grid-column: 1;
115
+ grid-row: var(--_detail-area);
116
+ }
117
+
118
+ /* Expand */
119
+
120
+ :host([expand-master]) {
121
+ --_master-extra: 1fr;
122
+ }
123
+
124
+ :host([expand-detail]) {
125
+ --_detail-extra: 1fr;
126
+ }
127
+
128
+ :host([keep-detail-column-offscreen]),
129
+ :host([has-detail-placeholder][overlay]:not([has-detail])),
130
+ :host(:not([has-detail-placeholder], [has-detail])) {
131
+ --_master-extra: calc(100% - var(--_master-size));
132
+ }
133
+
134
+ /* Backdrop base styles */
135
+
136
+ #backdrop {
137
+ --_transition-easing: linear;
138
+
59
139
  position: absolute;
60
140
  inset: 0;
61
- z-index: 1;
62
- display: none;
141
+ z-index: 2;
142
+ opacity: 0;
143
+ pointer-events: none;
63
144
  background: var(--vaadin-overlay-backdrop-background, rgba(0, 0, 0, 0.2));
64
145
  forced-color-adjust: none;
65
146
  }
66
147
 
67
- :host([expand='both']),
68
- :host([expand='master']) {
69
- --_master-column: var(--_master-size) 1fr;
148
+ /* Master base styles */
149
+
150
+ #master {
151
+ opacity: 0;
152
+ pointer-events: none;
153
+ box-sizing: border-box;
70
154
  }
71
155
 
72
- :host(:not([has-detail])),
73
- :host([keep-detail-column-offscreen]) {
74
- --_master-column: var(--_master-size) calc(100% - var(--_master-size));
156
+ :host([has-master]) #master {
157
+ opacity: 1;
158
+ pointer-events: auto;
75
159
  }
76
160
 
77
- :host([expand='both']),
78
- :host([expand='detail']) {
79
- --_detail-column: var(--_detail-size) 1fr;
161
+ /* Detail base styles */
162
+
163
+ #detail {
164
+ translate: var(--_transition-offset);
165
+ opacity: 0;
166
+ z-index: 4;
80
167
  }
81
168
 
82
- :host([orientation='horizontal'][has-detail]:not([overflow])) [part~='detail'] {
83
- border-inline-start: var(--vaadin-master-detail-layout-border-width, 1px) solid
84
- var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
169
+ :host([has-detail]) #detail {
170
+ translate: none;
171
+ opacity: 1;
85
172
  }
86
173
 
87
- :host([orientation='vertical'][has-detail]:not([overflow])) [part~='detail'] {
88
- border-top: var(--vaadin-master-detail-layout-border-width, 1px) solid
89
- var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
174
+ #detailOutgoing {
175
+ position: absolute;
176
+ z-index: 3;
177
+ display: none;
178
+ }
179
+
180
+ :host([transition='replace']) #detailOutgoing {
181
+ display: block;
90
182
  }
91
183
 
92
- :host([overflow]) [part~='detail'] {
184
+ #detailPlaceholder {
185
+ z-index: 1;
186
+ visibility: hidden;
187
+ }
188
+
189
+ :host([has-detail-placeholder]:not([has-detail], [overlay])) #detailPlaceholder {
190
+ visibility: visible;
191
+ }
192
+
193
+ :is(#detail, #detailPlaceholder, #detailOutgoing) {
194
+ box-sizing: border-box;
195
+ }
196
+
197
+ /* Detail borders */
198
+
199
+ #detail,
200
+ #detailPlaceholder {
201
+ border-color: var(--vaadin-master-detail-layout-border-color, var(--vaadin-border-color-secondary));
202
+ border-width: var(--vaadin-master-detail-layout-border-width, 1px);
203
+ }
204
+
205
+ :host([orientation='horizontal']) #detailPlaceholder,
206
+ :host([orientation='horizontal']:not([overlay])) #detail {
207
+ border-inline-start-style: solid;
208
+ }
209
+
210
+ :host([orientation='vertical']) #detailPlaceholder,
211
+ :host([orientation='vertical']:not([overlay])) #detail {
212
+ border-block-start-style: solid;
213
+ }
214
+
215
+ /* Overlay */
216
+
217
+ :host([overlay][orientation='horizontal']) {
218
+ --_transition-offset: calc((100% + 30px) * var(--_rtl-multiplier));
219
+ }
220
+
221
+ :host([overlay][orientation='vertical']) {
222
+ --_transition-offset: 0 calc(100% + 30px);
223
+ }
224
+
225
+ :host([has-detail][overlay]) #backdrop {
226
+ opacity: 1;
227
+ pointer-events: auto;
228
+ }
229
+
230
+ :host([has-detail][overlay]) :is(#detail, #detailOutgoing) {
93
231
  position: absolute;
94
- z-index: 2;
95
232
  background: var(--vaadin-master-detail-layout-detail-background, var(--vaadin-background-color));
96
233
  box-shadow: var(--vaadin-master-detail-layout-detail-shadow, 0 0 20px 0 rgba(0, 0, 0, 0.3));
97
234
  grid-column: none;
235
+ grid-row: none;
98
236
  }
99
237
 
100
- :host([overflow]) [part~='backdrop'] {
101
- display: block;
102
- }
103
-
104
- :host([overflow]:not([orientation='vertical'])) [part~='detail'] {
238
+ :host([has-detail][overlay][orientation='horizontal']) :is(#detail, #detailOutgoing) {
105
239
  inset-block: 0;
106
- width: var(--_overlay-size, var(--_detail-size, min-content));
107
240
  inset-inline-end: 0;
241
+ width: var(--_overlay-size, var(--_detail-size));
242
+ max-width: 100%;
108
243
  }
109
244
 
110
- :host([overflow][orientation='vertical']) [part~='detail'] {
111
- grid-column: auto;
112
- grid-row: none;
245
+ :host([has-detail][overlay][orientation='vertical']) :is(#detail, #detailOutgoing) {
113
246
  inset-inline: 0;
114
- height: var(--_overlay-size, var(--_detail-size, min-content));
115
247
  inset-block-end: 0;
248
+ height: var(--_overlay-size, var(--_detail-size));
249
+ max-height: 100%;
116
250
  }
117
251
 
118
- :host([overflow][overlay-containment='viewport']) [part~='detail'],
119
- :host([overflow][overlay-containment='viewport']) [part~='backdrop'] {
252
+ :host([has-detail][overlay][overlay-containment='page']) :is(#detail, #detailOutgoing, #backdrop) {
120
253
  position: fixed;
254
+ padding-top: env(safe-area-inset-top);
255
+ padding-bottom: env(safe-area-inset-bottom);
256
+ padding-right: env(safe-area-inset-right);
257
+ --safe-area-inset-top: 0px;
258
+ --safe-area-inset-bottom: 0px;
259
+ --safe-area-inset-left: 0px;
260
+ --safe-area-inset-right: 0px;
261
+ --safe-area-inset-inline-start: 0px;
262
+ --safe-area-inset-inline-end: 0px;
263
+ }
264
+
265
+ :host([dir='rtl'][has-detail][overlay][overlay-containment='page']) :is(#detail, #detailOutgoing, #backdrop) {
266
+ padding-right: 0;
267
+ padding-left: env(safe-area-inset-left);
268
+ }
269
+
270
+ /* Transitions */
271
+
272
+ @media (prefers-reduced-motion: no-preference) {
273
+ :host(:not([no-animation], [transition='replace'])) {
274
+ --_transition-duration: 200ms;
275
+ }
276
+
277
+ :host([overlay]:not([no-animation])) {
278
+ --_transition-duration: 300ms;
279
+ }
121
280
  }
122
281
 
282
+ /* Forced colors */
283
+
123
284
  @media (forced-colors: active) {
124
- :host([overflow]) [part~='detail'] {
285
+ :host([has-detail][overlay]) :is(#detail, #detailOutgoing) {
125
286
  outline: 3px solid !important;
126
287
  }
127
288
 
128
- [part~='detail'] {
289
+ :is(#detail, #detailPlaceholder, #detailOutgoing) {
129
290
  background: Canvas !important;
130
291
  }
131
292
  }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2025 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+
7
+ const ANIMATION_ID = 'vaadin-master-detail-layout';
8
+
9
+ /**
10
+ * Reads CSS custom properties from the element that control
11
+ * animation keyframes and timing.
12
+ *
13
+ * @param {HTMLElement} element
14
+ * @return {{ offset: string, easing: string, duration: number }}
15
+ */
16
+ function getAnimationParams(element) {
17
+ const computedStyle = getComputedStyle(element);
18
+ const offset = computedStyle.getPropertyValue('--_transition-offset');
19
+ const easing = computedStyle.getPropertyValue('--_transition-easing');
20
+ const durationStr = computedStyle.getPropertyValue('--_transition-duration');
21
+ const duration = durationStr.endsWith('ms') ? parseFloat(durationStr) : parseFloat(durationStr) * 1000;
22
+ return { offset, easing, duration };
23
+ }
24
+
25
+ /**
26
+ * Returns the currently running master-detail-layout animation on the
27
+ * element, if any. Matches by the shared animation ID and `'running'`
28
+ * play state.
29
+ *
30
+ * @param {HTMLElement} element
31
+ * @return {Animation | undefined}
32
+ */
33
+ export function getCurrentAnimation(element) {
34
+ return element
35
+ .getAnimations()
36
+ .find((animation) => animation.id === ANIMATION_ID && animation.playState !== 'finished');
37
+ }
38
+
39
+ /**
40
+ * Returns the overall progress (0–1) of the current animation on the
41
+ * element, computed as `currentTime / duration`. Returns 0 when no
42
+ * animation is running.
43
+ *
44
+ * @param {HTMLElement} element
45
+ * @return {number}
46
+ */
47
+ export function getCurrentAnimationProgress(element) {
48
+ const animation = getCurrentAnimation(element);
49
+ if (!animation) {
50
+ return 0;
51
+ }
52
+ const currentTime = animation.currentTime;
53
+ if (currentTime == null) {
54
+ return 0;
55
+ }
56
+ return currentTime / animation.effect.getTiming().duration;
57
+ }
58
+
59
+ /**
60
+ * Animates the element using the Web Animations API. Cancels any
61
+ * previous animation and resumes from the given progress for a
62
+ * smooth handoff. No-op when CSS params are missing or progress is 1.
63
+ *
64
+ * @param {HTMLElement} element
65
+ * @param {'in' | 'out'} direction
66
+ * @param {Array<'fade' | 'slide'>} effects
67
+ * @param {number} progress starting progress (0–1) for interrupted resumption
68
+ * @return {Promise<void>} resolves when the animation finishes
69
+ */
70
+ function animate(element, direction, effects, progress) {
71
+ const { offset, easing, duration } = getAnimationParams(element);
72
+ if (!offset || !duration || progress === 1) {
73
+ return Promise.resolve();
74
+ }
75
+
76
+ const oldAnimation = getCurrentAnimation(element);
77
+ if (oldAnimation) {
78
+ oldAnimation.cancel();
79
+ }
80
+
81
+ const keyframes = {};
82
+ if (effects.includes('fade')) {
83
+ keyframes.opacity = [0, 1];
84
+ }
85
+ if (effects.includes('slide')) {
86
+ keyframes.translate = [offset, 0];
87
+ }
88
+
89
+ const newAnimation = element.animate(keyframes, { id: ANIMATION_ID, easing, duration });
90
+ newAnimation.pause();
91
+ newAnimation.currentTime = duration * progress;
92
+ newAnimation.playbackRate = direction === 'in' ? 1 : -1;
93
+ newAnimation.play();
94
+ return newAnimation.finished;
95
+ }
96
+
97
+ /**
98
+ * Runs an enter animation on the element.
99
+ *
100
+ * @param {HTMLElement} element
101
+ * @param {Array<'fade' | 'slide'>} effects
102
+ * @param {number} progress starting progress (0–1) for interrupted resumption
103
+ * @return {Promise<void>} resolves when the animation finishes
104
+ */
105
+ export function animateIn(element, effects, progress) {
106
+ return animate(element, 'in', effects, progress);
107
+ }
108
+
109
+ /**
110
+ * Runs an exit animation on the element.
111
+ *
112
+ * @param {HTMLElement} element
113
+ * @param {Array<'fade' | 'slide'>} effects
114
+ * @param {number} progress starting progress (0–1) for interrupted resumption
115
+ * @return {Promise<void>} resolves when the animation finishes
116
+ */
117
+ export function animateOut(element, effects, progress) {
118
+ return animate(element, 'out', effects, progress);
119
+ }
120
+
121
+ /**
122
+ * Cancels all running animations on the element that match the shared animation ID.
123
+ *
124
+ * @param {HTMLElement} element
125
+ */
126
+ export function cancelAnimations(element) {
127
+ element.getAnimations({ subtree: true }).forEach((animation) => {
128
+ if (animation.id === ANIMATION_ID) {
129
+ animation.cancel();
130
+ }
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Parses a computed `gridTemplateColumns` / `gridTemplateRows` value
136
+ * into an array of track sizes in pixels. Line names (e.g. `[name]`)
137
+ * are stripped before parsing.
138
+ *
139
+ * @param {string} gridTemplate computed grid template string (e.g. `"200px [gap] 10px 400px"`)
140
+ * @return {number[]} track sizes in pixels
141
+ */
142
+ export function parseTrackSizes(gridTemplate) {
143
+ return gridTemplate
144
+ .replace(/\[[^\]]+\]/gu, '')
145
+ .replace(/\s+/gu, ' ')
146
+ .trim()
147
+ .split(' ')
148
+ .map(parseFloat);
149
+ }
150
+
151
+ /**
152
+ * Determines whether the detail area overflows the host element,
153
+ * meaning it should be shown as an overlay instead of side-by-side.
154
+ *
155
+ * Returns `false` when all tracks fit within the host, or when the
156
+ * master's extra space (flexible portion) is large enough to absorb
157
+ * the detail column.
158
+ *
159
+ * @param {number} hostSize the host element's width or height in pixels
160
+ * @param {number[]} trackSizes [masterSize, masterExtra, detailSize] in pixels
161
+ * @return {boolean} `true` if the detail overflows and should be overlaid
162
+ */
163
+ export function detectOverflow(hostSize, trackSizes) {
164
+ const [masterSize, masterExtra, detailSize] = trackSizes;
165
+
166
+ if (Math.floor(masterSize + masterExtra + detailSize) <= Math.floor(hostSize)) {
167
+ return false;
168
+ }
169
+ if (Math.floor(masterExtra) >= Math.floor(detailSize)) {
170
+ return false;
171
+ }
172
+ return true;
173
+ }
@@ -4,7 +4,6 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7
- import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
8
7
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
8
 
10
9
  export interface MasterDetailLayoutCustomEventMap {
@@ -20,25 +19,53 @@ export interface MasterDetailLayoutEventMap extends HTMLElementEventMap, MasterD
20
19
  * (or primary) area and a detail (or secondary) area that is displayed next to, or
21
20
  * overlaid on top of, the master area, depending on configuration and viewport size.
22
21
  *
22
+ * ### Slots
23
+ *
24
+ * The component has two main content areas: the master area (default slot)
25
+ * and the detail area (`detail` slot). When the detail doesn't fit next to
26
+ * the master, it is shown as an overlay on top of the master area:
27
+ *
28
+ * ```html
29
+ * <vaadin-master-detail-layout>
30
+ * <div>Master content</div>
31
+ * <div slot="detail">Detail content</div>
32
+ * </vaadin-master-detail-layout>
33
+ * ```
34
+ *
35
+ * The component also supports a `detail-placeholder` slot for content shown
36
+ * in the detail area when no detail is selected. Unlike the `detail` slot,
37
+ * the placeholder is simply hidden when it doesn't fit next to the master area,
38
+ * rather than shown as an overlay:
39
+ *
40
+ * ```html
41
+ * <vaadin-master-detail-layout>
42
+ * <div>Master content</div>
43
+ * <div slot="detail-placeholder">Select an item</div>
44
+ * </vaadin-master-detail-layout>
45
+ * ```
46
+ *
23
47
  * ### Styling
24
48
  *
25
49
  * The following shadow DOM parts are available for styling:
26
50
  *
27
- * Part name | Description
28
- * ---------------|----------------------
29
- * `backdrop` | Backdrop covering the master area in the overlay mode
30
- * `master` | The master area
31
- * `detail` | The detail area
51
+ * Part name | Description
52
+ * ----------------------|----------------------
53
+ * `backdrop` | Backdrop covering the master area in the overlay mode
54
+ * `master` | The master area
55
+ * `detail` | The detail area
56
+ * `detail-placeholder` | The detail placeholder area
32
57
  *
33
58
  * The following state attributes are available for styling:
34
59
  *
35
- * Attribute | Description
36
- * ----------------------|----------------------
37
- * `expand` | Set to `master`, `detail`, or `both`.
38
- * `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
39
- * `has-detail` | Set when the detail content is provided and visible.
40
- * `overflow` | Set when columns don't fit and the detail is shown as an overlay.
41
- * `overlay-containment` | Set to `layout` or `viewport`.
60
+ * Attribute | Description
61
+ * --------------------------|----------------------
62
+ * `expand-master` | Set when the master area expands to fill available space.
63
+ * `expand-detail` | Set when the detail area expands to fill available space.
64
+ * `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
65
+ * `has-detail` | Set when the detail content is provided and visible.
66
+ * `has-detail-placeholder` | Set when the detail placeholder content is provided.
67
+ * `overlay` | Set when columns don't fit and the detail is shown as an overlay.
68
+ * `overlay-containment` | Set to `layout` or `page`.
42
69
  *
43
70
  * The following custom CSS properties are available for styling:
44
71
  *
@@ -55,12 +82,18 @@ export interface MasterDetailLayoutEventMap extends HTMLElementEventMap, MasterD
55
82
  * @fires {CustomEvent} backdrop-click - Fired when the user clicks the backdrop in the overlay mode.
56
83
  * @fires {CustomEvent} detail-escape-press - Fired when the user presses Escape in the detail area.
57
84
  */
58
- declare class MasterDetailLayout extends SlotStylesMixin(ThemableMixin(ElementMixin(HTMLElement))) {
85
+ declare class MasterDetailLayout extends ThemableMixin(ElementMixin(HTMLElement)) {
59
86
  /**
60
87
  * Size (in CSS length units) to be set on the detail area in
61
- * the CSS grid layout. If there is not enough space to show
88
+ * the CSS grid layout. When there is not enough space to show
62
89
  * master and detail areas next to each other, the detail area
63
- * is shown as an overlay. Defaults to 15em.
90
+ * is shown as an overlay.
91
+ * <p>
92
+ * If not specified, the size is determined automatically by measuring
93
+ * the detail content in a `min-content` CSS grid column when it first
94
+ * becomes visible, and then caching the resulting intrinsic size. To
95
+ * recalculate the cached intrinsic size, use the `recalculateLayout`
96
+ * method.
64
97
  *
65
98
  * @attr {string} detail-size
66
99
  */
@@ -96,19 +129,30 @@ declare class MasterDetailLayout extends SlotStylesMixin(ThemableMixin(ElementMi
96
129
  /**
97
130
  * Defines the containment of the detail area when the layout is in
98
131
  * overlay mode. When set to `layout`, the overlay is confined to the
99
- * layout. When set to `viewport`, the overlay is confined to the
132
+ * layout. When set to `page`, the overlay is confined to the
100
133
  * browser's viewport. Defaults to `layout`.
101
134
  *
102
135
  * @attr {string} overlay-containment
103
136
  */
104
- overlayContainment: 'layout' | 'viewport';
137
+ overlayContainment: 'layout' | 'page';
138
+
139
+ /**
140
+ * When true, the master area grows to fill the available space.
141
+ * If `expandDetail` is also true, both areas share the available
142
+ * space equally.
143
+ *
144
+ * @attr {boolean} expand-master
145
+ */
146
+ expandMaster: boolean;
105
147
 
106
148
  /**
107
- * Controls which column(s) expand to fill available space.
108
- * Possible values: `'master'`, `'detail'`, `'both'`.
109
- * Defaults to `'both'`.
149
+ * When true, the detail area grows to fill the available space.
150
+ * If `expandMaster` is also true, both areas share the available
151
+ * space equally.
152
+ *
153
+ * @attr {boolean} expand-detail
110
154
  */
111
- expand: 'master' | 'detail' | 'both';
155
+ expandDetail: boolean;
112
156
 
113
157
  /**
114
158
  * When true, the layout does not use animated transitions for the detail area.
@@ -117,6 +161,30 @@ declare class MasterDetailLayout extends SlotStylesMixin(ThemableMixin(ElementMi
117
161
  */
118
162
  noAnimation: boolean;
119
163
 
164
+ /**
165
+ * When true, the layout forces the detail area to be shown as an overlay,
166
+ * even if there is enough space for master and detail to be shown next to
167
+ * each other using the default (split) mode.
168
+ *
169
+ * @attr {boolean} force-overlay
170
+ */
171
+ forceOverlay: boolean;
172
+
173
+ /**
174
+ * When `detailSize` is not explicitly set, re-measures the cached intrinsic size of
175
+ * the detail content by placing it in a min-content CSS grid column, then repeats
176
+ * this process for ancestor master-detail layouts without an explicit `detailSize`,
177
+ * if any, so that their detail areas also adapt.
178
+ *
179
+ * Call this method after changing the detail content in a way that affects its intrinsic
180
+ * size — for example, when opening a detail in a nested master-detail layout that was
181
+ * not previously visible.
182
+ *
183
+ * NOTE: This method can be expensive in large layouts as it triggers consecutive
184
+ * synchronous DOM reads and writes.
185
+ */
186
+ recalculateLayout(): void;
187
+
120
188
  addEventListener<K extends keyof MasterDetailLayoutEventMap>(
121
189
  type: K,
122
190
  listener: (this: MasterDetailLayout, ev: MasterDetailLayoutEventMap[K]) => void,