@vaadin/master-detail-layout 24.8.0-alpha10

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.
@@ -0,0 +1,682 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2025 - 2025 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { css, html, LitElement, nothing } from 'lit';
7
+ import { getFocusableElements } from '@vaadin/a11y-base/src/focus-utils.js';
8
+ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
9
+ import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
10
+ import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
11
+ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
12
+ import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
13
+ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
14
+ import { transitionStyles } from './vaadin-master-detail-layout-transition-styles.js';
15
+
16
+ /**
17
+ * `<vaadin-master-detail-layout>` is a web component for building UIs with a master
18
+ * (or primary) area and a detail (or secondary) area that is displayed next to, or
19
+ * overlaid on top of, the master area, depending on configuration and viewport size.
20
+ *
21
+ * ### Styling
22
+ *
23
+ * The following custom CSS properties are available for styling (needed to be set
24
+ * on the `<html>` element since they are used by the global view transitions):
25
+ *
26
+ * Custom CSS property | Description | Default
27
+ * -----------------------------------------------------|---------------------|--------
28
+ * `--vaadin-master-detail-layout-transition-duration` | Transition duration | 300ms
29
+ *
30
+ * The following shadow DOM parts are available for styling:
31
+ *
32
+ * Part name | Description
33
+ * ---------------|----------------------
34
+ * `backdrop` | Backdrop covering the master area in the overlay mode
35
+ * `master` | The master area
36
+ * `detail` | The detail area
37
+ *
38
+ * The following state attributes are available for styling:
39
+ *
40
+ * Attribute | Description
41
+ * ---------------| -----------
42
+ * `containment` | Set to `layout` or `viewport` depending on the containment.
43
+ * `orientation` | Set to `horizontal` or `vertical` depending on the orientation.
44
+ * `has-detail` | Set when the detail content is provided.
45
+ * `overlay` | Set when the layout is using the overlay mode.
46
+ * `stack` | Set when the layout is using the stack mode.
47
+ *
48
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
49
+ *
50
+ * @customElement
51
+ * @extends HTMLElement
52
+ * @mixes ThemableMixin
53
+ * @mixes ElementMixin
54
+ * @mixes ResizeMixin
55
+ * @mixes SlotStylesMixin
56
+ */
57
+ class MasterDetailLayout extends SlotStylesMixin(ResizeMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement))))) {
58
+ static get is() {
59
+ return 'vaadin-master-detail-layout';
60
+ }
61
+
62
+ static get styles() {
63
+ return css`
64
+ :host {
65
+ display: flex;
66
+ box-sizing: border-box;
67
+ height: 100%;
68
+ }
69
+
70
+ :host([hidden]) {
71
+ display: none !important;
72
+ }
73
+
74
+ :host(:not([has-detail])) [part='detail'],
75
+ [part='backdrop'] {
76
+ display: none;
77
+ }
78
+
79
+ /* Overlay mode */
80
+ :host(:is([overlay], [stack])) {
81
+ position: relative;
82
+ }
83
+
84
+ :host(:is([overlay], [stack])[containment='layout']) [part='detail'],
85
+ :host([overlay][containment='layout']) [part='backdrop'] {
86
+ position: absolute;
87
+ }
88
+
89
+ :host(:is([overlay], [stack])[containment='viewport']) [part='detail'],
90
+ :host([overlay][containment='viewport']) [part='backdrop'] {
91
+ position: fixed;
92
+ }
93
+
94
+ :host([overlay][has-detail]) [part='backdrop'] {
95
+ display: block;
96
+ inset: 0;
97
+ z-index: 1;
98
+ }
99
+
100
+ :host([overlay]) [part='detail'] {
101
+ z-index: 1;
102
+ }
103
+
104
+ :host([overlay][orientation='horizontal']) [part='detail'] {
105
+ inset-inline-end: 0;
106
+ height: 100%;
107
+ width: var(--_detail-min-size, min-content);
108
+ max-width: 100%;
109
+ }
110
+
111
+ :host([overlay][orientation='horizontal'][containment='viewport']) [part='detail'] {
112
+ inset-block-start: 0;
113
+ }
114
+
115
+ :host([overlay][orientation='horizontal']) [part='master'] {
116
+ max-width: 100%;
117
+ }
118
+
119
+ /* No fixed size */
120
+ :host(:not([has-master-size])) [part='master'],
121
+ :host(:not([has-detail-size])) [part='detail'] {
122
+ flex-grow: 1;
123
+ flex-basis: 50%;
124
+ }
125
+
126
+ /* Fixed size */
127
+ :host([has-master-size]) [part='master'],
128
+ :host([has-detail-size]) [part='detail'] {
129
+ flex-shrink: 0;
130
+ }
131
+
132
+ :host([has-master-size][orientation='horizontal']) [part='master'] {
133
+ width: var(--_master-size);
134
+ }
135
+
136
+ :host([has-detail-size][orientation='horizontal']:not([stack])) [part='detail'] {
137
+ width: var(--_detail-size);
138
+ }
139
+
140
+ :host([has-master-size][has-detail-size]) [part='master'] {
141
+ flex-grow: 1;
142
+ flex-basis: var(--_master-size);
143
+ }
144
+
145
+ :host([has-master-size][has-detail-size]) [part='detail'] {
146
+ flex-grow: 1;
147
+ flex-basis: var(--_detail-size);
148
+ }
149
+
150
+ /* Min size */
151
+ :host([has-master-min-size][orientation='horizontal']:not([overlay])) [part='master'] {
152
+ min-width: var(--_master-min-size);
153
+ }
154
+
155
+ :host([has-detail-min-size][orientation='horizontal']:not([overlay]):not([stack])) [part='detail'] {
156
+ min-width: var(--_detail-min-size);
157
+ }
158
+
159
+ :host([has-master-min-size]) [part='master'],
160
+ :host([has-detail-min-size]) [part='detail'] {
161
+ flex-shrink: 0;
162
+ }
163
+
164
+ /* Vertical */
165
+ :host([orientation='vertical']) {
166
+ flex-direction: column;
167
+ }
168
+
169
+ :host([orientation='vertical'][overlay]) [part='master'] {
170
+ max-height: 100%;
171
+ }
172
+
173
+ :host([orientation='vertical'][overlay]) [part='detail'] {
174
+ inset-block-end: 0;
175
+ width: 100%;
176
+ height: var(--_detail-min-size, min-content);
177
+ }
178
+
179
+ :host([overlay][orientation='vertical'][containment='viewport']) [part='detail'] {
180
+ inset-inline-start: 0;
181
+ }
182
+
183
+ /* Fixed size */
184
+ :host([has-master-size][orientation='vertical']) [part='master'] {
185
+ height: var(--_master-size);
186
+ }
187
+
188
+ :host([has-detail-size][orientation='vertical']:not([stack])) [part='detail'] {
189
+ height: var(--_detail-size);
190
+ }
191
+
192
+ /* Min size */
193
+ :host([has-master-min-size][orientation='vertical']:not([overlay])) [part='master'],
194
+ :host([has-master-min-size][orientation='vertical'][overlay]) {
195
+ min-height: var(--_master-min-size);
196
+ }
197
+
198
+ :host([has-detail-min-size][orientation='vertical']:not([overlay]):not([stack])) [part='detail'] {
199
+ min-height: var(--_detail-min-size);
200
+ }
201
+
202
+ /* Stack mode */
203
+ :host([stack]) [part='master'] {
204
+ max-height: 100%;
205
+ }
206
+
207
+ :host([stack]) [part='detail'] {
208
+ inset: 0;
209
+ }
210
+
211
+ [part='master']::before {
212
+ background-position-y: var(--_stack-threshold);
213
+ }
214
+ `;
215
+ }
216
+
217
+ static get properties() {
218
+ return {
219
+ /**
220
+ * Fixed size (in CSS length units) to be set on the detail area.
221
+ * When specified, it prevents the detail area from growing or
222
+ * shrinking. If there is not enough space to show master and detail
223
+ * areas next to each other, the layout switches to the overlay mode.
224
+ *
225
+ * @attr {string} detail-size
226
+ */
227
+ detailSize: {
228
+ type: String,
229
+ sync: true,
230
+ observer: '__detailSizeChanged',
231
+ },
232
+
233
+ /**
234
+ * Minimum size (in CSS length units) to be set on the detail area.
235
+ * When specified, it prevents the detail area from shrinking below
236
+ * this size. If there is not enough space to show master and detail
237
+ * areas next to each other, the layout switches to the overlay mode.
238
+ *
239
+ * @attr {string} detail-min-size
240
+ */
241
+ detailMinSize: {
242
+ type: String,
243
+ sync: true,
244
+ observer: '__detailMinSizeChanged',
245
+ },
246
+
247
+ /**
248
+ * Fixed size (in CSS length units) to be set on the master area.
249
+ * When specified, it prevents the master area from growing or
250
+ * shrinking. If there is not enough space to show master and detail
251
+ * areas next to each other, the layout switches to the overlay mode.
252
+ *
253
+ * @attr {string} master-size
254
+ */
255
+ masterSize: {
256
+ type: String,
257
+ sync: true,
258
+ observer: '__masterSizeChanged',
259
+ },
260
+
261
+ /**
262
+ * Minimum size (in CSS length units) to be set on the master area.
263
+ * When specified, it prevents the master area from shrinking below
264
+ * this size. If there is not enough space to show master and detail
265
+ * areas next to each other, the layout switches to the overlay mode.
266
+ *
267
+ * @attr {string} master-min-size
268
+ */
269
+ masterMinSize: {
270
+ type: String,
271
+ sync: true,
272
+ observer: '__masterMinSizeChanged',
273
+ },
274
+
275
+ /**
276
+ * Define how master and detail areas are shown next to each other,
277
+ * and the way how size and min-size properties are applied to them.
278
+ * Possible values are: `horizontal` or `vertical`.
279
+ * Defaults to horizontal.
280
+ */
281
+ orientation: {
282
+ type: String,
283
+ value: 'horizontal',
284
+ reflectToAttribute: true,
285
+ observer: '__orientationChanged',
286
+ sync: true,
287
+ },
288
+
289
+ /**
290
+ * When specified, forces the layout to use overlay mode, even if
291
+ * there is enough space for master and detail to be shown next to
292
+ * each other using the default (split) mode.
293
+ *
294
+ * @attr {boolean} force-overlay
295
+ */
296
+ forceOverlay: {
297
+ type: Boolean,
298
+ value: false,
299
+ observer: '__forceOverlayChanged',
300
+ sync: true,
301
+ },
302
+
303
+ /**
304
+ * Defines the containment of the detail area when the layout is in
305
+ * overlay mode. When set to `layout`, the overlay is confined to the
306
+ * layout. When set to `viewport`, the overlay is confined to the
307
+ * browser's viewport. Defaults to `layout`.
308
+ */
309
+ containment: {
310
+ type: String,
311
+ value: 'layout',
312
+ reflectToAttribute: true,
313
+ sync: true,
314
+ },
315
+
316
+ /**
317
+ * The threshold (in CSS length units) at which the layout switches to
318
+ * the "stack" mode, making detail area fully cover the master area.
319
+ *
320
+ * @attr {string} stack-threshold
321
+ */
322
+ stackThreshold: {
323
+ type: String,
324
+ observer: '__stackThresholdChanged',
325
+ sync: true,
326
+ },
327
+
328
+ /**
329
+ * When true, the layout does not use animated transitions for the detail area.
330
+ *
331
+ * @attr {boolean} no-animation
332
+ */
333
+ noAnimation: {
334
+ type: Boolean,
335
+ value: false,
336
+ },
337
+
338
+ /**
339
+ * When true, the component uses the overlay mode. This property is read-only.
340
+ * In order to enforce the overlay mode, use `forceOverlay` property.
341
+ * @protected
342
+ */
343
+ _overlay: {
344
+ type: Boolean,
345
+ attribute: 'overlay',
346
+ reflectToAttribute: true,
347
+ sync: true,
348
+ },
349
+
350
+ /**
351
+ * When true, the component uses the stack mode. This property is read-only.
352
+ * In order to enforce the stack mode, use `stackThreshold` property.
353
+ * @protected
354
+ */
355
+ _stack: {
356
+ type: Boolean,
357
+ attribute: 'stack',
358
+ reflectToAttribute: true,
359
+ sync: true,
360
+ },
361
+
362
+ /**
363
+ * When true, the component has the detail content provided.
364
+ * @protected
365
+ */
366
+ _hasDetail: {
367
+ type: Boolean,
368
+ attribute: 'has-detail',
369
+ reflectToAttribute: true,
370
+ sync: true,
371
+ },
372
+ };
373
+ }
374
+
375
+ static get experimental() {
376
+ return true;
377
+ }
378
+
379
+ /** @override */
380
+ get slotStyles() {
381
+ return [transitionStyles];
382
+ }
383
+
384
+ /** @protected */
385
+ render() {
386
+ return html`
387
+ <div part="backdrop"></div>
388
+ <div id="master" part="master" ?inert="${this._hasDetail && this._overlay && this.containment === 'layout'}">
389
+ <slot></slot>
390
+ </div>
391
+ <div
392
+ id="detail"
393
+ part="detail"
394
+ role="${this._overlay || this._stack ? 'dialog' : nothing}"
395
+ aria-modal="${this._overlay && this.containment === 'viewport' ? 'true' : nothing}"
396
+ >
397
+ <slot name="detail" @slotchange="${this.__onDetailSlotChange}"></slot>
398
+ </div>
399
+ `;
400
+ }
401
+
402
+ /** @private */
403
+ __onDetailSlotChange(e) {
404
+ const children = e.target.assignedNodes();
405
+
406
+ this._hasDetail = children.length > 0;
407
+ this.__detectLayoutMode();
408
+
409
+ // Move focus to the detail area when it is added to the DOM,
410
+ // in case if the layout is using overlay or stack mode.
411
+ if ((this.hasAttribute('overlay') || this.hasAttribute('stack')) && children.length > 0) {
412
+ const focusables = getFocusableElements(children[0]);
413
+ if (focusables.length) {
414
+ focusables[0].focus();
415
+ }
416
+ }
417
+ }
418
+
419
+ /**
420
+ * @protected
421
+ * @override
422
+ */
423
+ _onResize() {
424
+ this.__detectLayoutMode();
425
+ }
426
+
427
+ /** @private */
428
+ __detailSizeChanged(size, oldSize) {
429
+ this.__updateStyleProperty('detail-size', size, oldSize);
430
+ this.__detectLayoutMode();
431
+ }
432
+
433
+ /** @private */
434
+ __detailMinSizeChanged(size, oldSize) {
435
+ this.__updateStyleProperty('detail-min-size', size, oldSize);
436
+ this.__detectLayoutMode();
437
+ }
438
+
439
+ /** @private */
440
+ __masterSizeChanged(size, oldSize) {
441
+ this.__updateStyleProperty('master-size', size, oldSize);
442
+ this.__detectLayoutMode();
443
+ }
444
+
445
+ /** @private */
446
+ __masterMinSizeChanged(size, oldSize) {
447
+ this.__updateStyleProperty('master-min-size', size, oldSize);
448
+ this.__detectLayoutMode();
449
+ }
450
+
451
+ /** @private */
452
+ __orientationChanged(orientation, oldOrientation) {
453
+ if (orientation || oldOrientation) {
454
+ this.__detectLayoutMode();
455
+ }
456
+ }
457
+
458
+ /** @private */
459
+ __forceOverlayChanged(forceOverlay, oldForceOverlay) {
460
+ if (forceOverlay || oldForceOverlay) {
461
+ this.__detectLayoutMode();
462
+ }
463
+ }
464
+
465
+ /** @private */
466
+ __stackThresholdChanged(threshold, oldThreshold) {
467
+ if (threshold || oldThreshold) {
468
+ if (threshold) {
469
+ this.$.master.style.setProperty('--_stack-threshold', threshold);
470
+ } else {
471
+ this.$.master.style.removeProperty('--_stack-threshold');
472
+ }
473
+
474
+ this.__detectLayoutMode();
475
+ }
476
+ }
477
+
478
+ /** @private */
479
+ __updateStyleProperty(prop, size, oldSize) {
480
+ if (size) {
481
+ this.style.setProperty(`--_${prop}`, size);
482
+ } else if (oldSize) {
483
+ this.style.removeProperty(`--_${prop}`);
484
+ }
485
+
486
+ this.toggleAttribute(`has-${prop}`, !!size);
487
+ }
488
+
489
+ /** @private */
490
+ __detectLayoutMode() {
491
+ this._overlay = false;
492
+ this._stack = false;
493
+
494
+ if (this.forceOverlay) {
495
+ this._overlay = true;
496
+ return;
497
+ }
498
+
499
+ if (this.stackThreshold != null) {
500
+ // Set stack to true to disable masterMinSize and detailMinSize
501
+ // that would affect size measurements below when in split mode
502
+ this._stack = true;
503
+
504
+ const threshold = this.__getStackThresholdInPixels();
505
+ const size = this.orientation === 'vertical' ? this.offsetHeight : this.offsetWidth;
506
+ if (size > threshold) {
507
+ this._stack = false;
508
+ } else {
509
+ return;
510
+ }
511
+ }
512
+
513
+ if (!this._hasDetail) {
514
+ return;
515
+ }
516
+
517
+ if (this.orientation === 'vertical') {
518
+ this.__detectVerticalMode();
519
+ } else {
520
+ this.__detectHorizontalMode();
521
+ }
522
+ }
523
+
524
+ /** @private */
525
+ __detectHorizontalMode() {
526
+ const detailWidth = this.$.detail.offsetWidth;
527
+
528
+ // Detect minimum width needed by master content. Use max-width to ensure
529
+ // the layout can switch back to split mode once there is enough space.
530
+ // If there is master size or min-size set, use that instead to force the
531
+ // overlay mode by setting `masterSize` / `masterMinSize` to 100%/
532
+ this.$.master.style.maxWidth = this.masterSize || this.masterMinSize || 'min-content';
533
+ const masterWidth = this.$.master.offsetWidth;
534
+ this.$.master.style.maxWidth = '';
535
+
536
+ // If the combined minimum size of both the master and the detail content
537
+ // exceeds the size of the layout, the layout changes to the overlay mode.
538
+ this._overlay = this.offsetWidth < masterWidth + detailWidth;
539
+
540
+ // Toggling the overlay resizes master content, which can cause document
541
+ // scroll bar to appear or disappear, and trigger another resize of the
542
+ // layout which can affect previous measurements and end up in horizontal
543
+ // scroll. Check if that is the case and if so, preserve the overlay mode.
544
+ if (this.offsetWidth < this.scrollWidth) {
545
+ this._overlay = true;
546
+ }
547
+ }
548
+
549
+ /** @private */
550
+ __detectVerticalMode() {
551
+ // Remove overlay attribute temporarily to detect if there is enough space
552
+ // for both areas so that layout could switch back to the split mode.
553
+ this._overlay = false;
554
+
555
+ const masterHeight = this.$.master.clientHeight;
556
+
557
+ // If the combined minimum size of both the master and the detail content
558
+ // exceeds the available height, the layout changes to the overlay mode.
559
+ if (this.offsetHeight < masterHeight + this.$.detail.clientHeight) {
560
+ this._overlay = true;
561
+ }
562
+ }
563
+
564
+ /** @private */
565
+ __getStackThresholdInPixels() {
566
+ const { backgroundPositionY } = getComputedStyle(this.$.master, '::before');
567
+ return parseFloat(backgroundPositionY);
568
+ }
569
+
570
+ /**
571
+ * Sets the detail element to be displayed in the detail area and starts a
572
+ * view transition that animates adding, replacing or removing the detail
573
+ * area. During the view transition, the element is added to the DOM and
574
+ * assigned to the `detail` slot. Any previous detail element is removed.
575
+ * When passing null as the element, the current detail element is removed.
576
+ *
577
+ * If the browser does not support view transitions, the respective updates
578
+ * are applied immediately without starting a transition. The transition can
579
+ * also be skipped using the `skipTransition` parameter.
580
+ *
581
+ * @param element the new detail element, or null to remove the current detail
582
+ * @param skipTransition whether to skip the transition
583
+ * @returns {Promise<void>}
584
+ * @protected
585
+ */
586
+ _setDetail(element, skipTransition) {
587
+ // Don't start a transition if detail didn't change
588
+ const currentDetail = this.querySelector('[slot="detail"]');
589
+ if ((element || null) === currentDetail) {
590
+ return Promise.resolve();
591
+ }
592
+
593
+ const updateSlot = () => {
594
+ // Remove old content
595
+ this.querySelectorAll('[slot="detail"]').forEach((oldElement) => oldElement.remove());
596
+ // Add new content
597
+ if (element) {
598
+ element.setAttribute('slot', 'detail');
599
+ this.appendChild(element);
600
+ }
601
+ };
602
+
603
+ if (skipTransition) {
604
+ updateSlot();
605
+ return Promise.resolve();
606
+ }
607
+
608
+ const hasDetail = !!currentDetail;
609
+ const transitionType = hasDetail && element ? 'replace' : hasDetail ? 'remove' : 'add';
610
+ return this._startTransition(transitionType, () => {
611
+ // Update the DOM
612
+ updateSlot();
613
+ // Finish the transition
614
+ this._finishTransition();
615
+ });
616
+ }
617
+
618
+ /**
619
+ * Starts a view transition that animates adding, replacing or removing the
620
+ * detail area. Once the transition is ready and the browser has taken a
621
+ * snapshot of the current layout, the provided update callback is called.
622
+ * The callback should update the DOM, which can happen asynchronously.
623
+ * Once the DOM is updated, the caller must call `_finishTransition`,
624
+ * which results in the browser taking a snapshot of the new layout and
625
+ * animating the transition.
626
+ *
627
+ * If the browser does not support view transitions, or the `noAnimation`
628
+ * property is set, the update callback is called immediately without
629
+ * starting a transition.
630
+ *
631
+ * @param transitionType
632
+ * @param updateCallback
633
+ * @returns {Promise<void>}
634
+ * @protected
635
+ */
636
+ _startTransition(transitionType, updateCallback) {
637
+ const useTransition = typeof document.startViewTransition === 'function' && !this.noAnimation;
638
+ if (!useTransition) {
639
+ updateCallback();
640
+ return Promise.resolve();
641
+ }
642
+
643
+ this.setAttribute('transition', transitionType);
644
+ this.__transition = document.startViewTransition(() => {
645
+ // Return a promise that can be resolved once the DOM is updated
646
+ return new Promise((resolve) => {
647
+ this.__resolveUpdateCallback = resolve;
648
+ // Notify the caller that the transition is ready, so that they can
649
+ // update the DOM
650
+ updateCallback();
651
+ });
652
+ });
653
+ return this.__transition.finished;
654
+ }
655
+
656
+ /**
657
+ * Finishes the current view transition, if any. This method should be called
658
+ * after the DOM has been updated to finish the transition and animate the
659
+ * change in the layout.
660
+ *
661
+ * @returns {Promise<void>}
662
+ * @protected
663
+ */
664
+ async _finishTransition() {
665
+ // Detect new layout mode after DOM has been updated
666
+ this.__detectLayoutMode();
667
+
668
+ if (!this.__transition) {
669
+ return Promise.resolve();
670
+ }
671
+ // Resolve the update callback to finish the transition
672
+ this.__resolveUpdateCallback();
673
+ await this.__transition.finished;
674
+ this.removeAttribute('transition');
675
+ this.__transition = null;
676
+ this.__resolveUpdateCallback = null;
677
+ }
678
+ }
679
+
680
+ defineCustomElement(MasterDetailLayout);
681
+
682
+ export { MasterDetailLayout };
@@ -0,0 +1 @@
1
+ import '@vaadin/vaadin-lumo-styles/color.js';
@@ -0,0 +1,28 @@
1
+ import '@vaadin/vaadin-lumo-styles/color.js';
2
+ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
3
+
4
+ registerStyles(
5
+ 'vaadin-master-detail-layout',
6
+ css`
7
+ :host(:is([overlay], [stack])) [part='detail'] {
8
+ background-color: var(--lumo-base-color);
9
+ }
10
+
11
+ :host([overlay]) [part='detail'] {
12
+ box-shadow: var(--lumo-box-shadow-s);
13
+ }
14
+
15
+ :host([overlay][orientation='horizontal']) [part='detail'] {
16
+ border-inline-start: 1px solid var(--lumo-contrast-10pct);
17
+ }
18
+
19
+ :host([overlay][orientation='vertical']) [part='detail'] {
20
+ border-block-start: 1px solid var(--lumo-contrast-10pct);
21
+ }
22
+
23
+ :host([overlay]) [part='backdrop'] {
24
+ background-color: var(--lumo-shade-20pct);
25
+ }
26
+ `,
27
+ { moduleId: 'lumo-master-detail-layout' },
28
+ );