Skip to content

Commit c67dec1

Browse files
authored
feat: add containment mode to master-detail-layout (#8826)
1 parent 603a86b commit c67dec1

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

dev/master-detail-layout.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<vaadin-checkbox id="detailMinSize" label="Set detail min-size"></vaadin-checkbox>
4343
<vaadin-checkbox id="masterSize" label="Set master size"></vaadin-checkbox>
4444
<vaadin-checkbox id="masterMinSize" label="Set master min-size"></vaadin-checkbox>
45+
<vaadin-checkbox id="containmentViewport" label="Use viewport containment"></vaadin-checkbox>
4546
<vaadin-checkbox id="vertical" label="Use vertical orientation"></vaadin-checkbox>
4647
<vaadin-checkbox id="maxWidth" label="Use max-width on the host"></vaadin-checkbox>
4748
<vaadin-checkbox id="maxHeight" label="Use max-height on the host"></vaadin-checkbox>
@@ -86,6 +87,10 @@
8687
layout.masterMinSize = e.target.checked ? '300px' : null;
8788
});
8889

90+
document.querySelector('#containmentViewport').addEventListener('change', (e) => {
91+
layout.containment = e.target.checked ? 'viewport' : 'layout';
92+
});
93+
8994
document.querySelector('#vertical').addEventListener('change', (e) => {
9095
layout.orientation = e.target.checked ? 'vertical' : 'horizontal';
9196
});

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ declare class MasterDetailLayout extends ResizeMixin(ThemableMixin(ElementMixin(
6969
* @attr {boolean} force-overlay
7070
*/
7171
forceOverlay: boolean;
72+
73+
/**
74+
* Defines the containment of the detail area when the layout is in
75+
* overlay mode. When set to `layout`, the overlay is confined to the
76+
* layout. When set to `viewport`, the overlay is confined to the
77+
* browser's viewport. Defaults to `layout`.
78+
*/
79+
containment: 'layout' | 'viewport';
7280
}
7381

7482
declare global {

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,25 @@ class MasterDetailLayout extends ResizeMixin(ElementMixin(ThemableMixin(PolylitM
4747
position: relative;
4848
}
4949
50-
:host([overlay]) [part='detail'] {
50+
:host([overlay][containment='layout']) [part='detail'] {
5151
position: absolute;
5252
}
5353
54+
:host([overlay][containment='viewport']) [part='detail'] {
55+
position: fixed;
56+
}
57+
5458
:host([overlay][orientation='horizontal']) [part='detail'] {
5559
inset-inline-end: 0;
5660
height: 100%;
5761
width: var(--_detail-min-size, min-content);
5862
max-width: 100%;
5963
}
6064
65+
:host([overlay][orientation='horizontal'][containment='viewport']) [part='detail'] {
66+
inset-block-start: 0;
67+
}
68+
6169
:host([overlay][orientation='horizontal']) [part='master'] {
6270
max-width: 100%;
6371
}
@@ -122,6 +130,10 @@ class MasterDetailLayout extends ResizeMixin(ElementMixin(ThemableMixin(PolylitM
122130
height: var(--_detail-min-size, min-content);
123131
}
124132
133+
:host([overlay][orientation='vertical'][containment='viewport']) [part='detail'] {
134+
inset-inline-start: 0;
135+
}
136+
125137
/* Fixed size */
126138
:host([has-master-size][orientation='vertical']) [part='master'] {
127139
height: var(--_master-size);
@@ -228,6 +240,19 @@ class MasterDetailLayout extends ResizeMixin(ElementMixin(ThemableMixin(PolylitM
228240
observer: '__forceOverlayChanged',
229241
sync: true,
230242
},
243+
244+
/**
245+
* Defines the containment of the detail area when the layout is in
246+
* overlay mode. When set to `layout`, the overlay is confined to the
247+
* layout. When set to `viewport`, the overlay is confined to the
248+
* browser's viewport. Defaults to `layout`.
249+
*/
250+
containment: {
251+
type: String,
252+
value: 'layout',
253+
reflectToAttribute: true,
254+
sync: true,
255+
},
231256
};
232257
}
233258

packages/master-detail-layout/test/master-detail-layout.test.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,5 +437,86 @@ describe('vaadin-master-detail-layout', () => {
437437
expect(layout.hasAttribute('overlay')).to.be.false;
438438
});
439439
});
440+
441+
describe('containment', () => {
442+
before(() => {
443+
// Apply padding to body to test viewport containment.
444+
document.body.style.padding = '20px';
445+
});
446+
447+
after(() => {
448+
document.body.style.padding = '';
449+
});
450+
451+
describe('horizontal orientation', () => {
452+
beforeEach(async () => {
453+
// Use the threshold at which the overlay mode is on by default.
454+
await setViewport({ width: 350, height });
455+
await nextResize(layout);
456+
457+
expect(layout.hasAttribute('overlay')).to.be.true;
458+
});
459+
460+
it('should contain overlay to layout by default', () => {
461+
const layoutBounds = layout.getBoundingClientRect();
462+
const detailBounds = detail.getBoundingClientRect();
463+
464+
expect(getComputedStyle(detail).position).to.equal('absolute');
465+
expect(detailBounds.top).to.equal(layoutBounds.top);
466+
expect(detailBounds.bottom).to.equal(layoutBounds.bottom);
467+
expect(detailBounds.right).to.equal(layoutBounds.right);
468+
});
469+
470+
it('should contain overlay to viewport when configured', async () => {
471+
layout.containment = 'viewport';
472+
await nextRender();
473+
474+
const detailBounds = detail.getBoundingClientRect();
475+
const windowBounds = document.documentElement.getBoundingClientRect();
476+
477+
expect(getComputedStyle(detail).position).to.equal('fixed');
478+
expect(detailBounds.top).to.equal(windowBounds.top);
479+
expect(detailBounds.bottom).to.equal(windowBounds.bottom);
480+
expect(detailBounds.right).to.equal(windowBounds.right);
481+
});
482+
});
483+
484+
describe('vertical orientation', () => {
485+
beforeEach(async () => {
486+
layout.orientation = 'vertical';
487+
layout.style.maxHeight = '500px';
488+
layout.parentElement.style.height = '100%';
489+
490+
// Use the threshold at which the overlay mode is on by default.
491+
await setViewport({ width: 500, height: 400 });
492+
await nextResize(layout);
493+
494+
expect(layout.hasAttribute('overlay')).to.be.true;
495+
});
496+
497+
it('should contain overlay to layout by default', () => {
498+
const layoutBounds = layout.getBoundingClientRect();
499+
const detailBounds = detail.getBoundingClientRect();
500+
501+
expect(getComputedStyle(detail).position).to.equal('absolute');
502+
expect(detailBounds.left).to.equal(layoutBounds.left);
503+
expect(detailBounds.right).to.equal(layoutBounds.right);
504+
expect(detailBounds.bottom).to.equal(layoutBounds.bottom);
505+
});
506+
507+
it('should contain overlay to viewport when configured', async () => {
508+
layout.containment = 'viewport';
509+
await nextRender();
510+
511+
const detailBounds = detail.getBoundingClientRect();
512+
const windowBounds = document.documentElement.getBoundingClientRect();
513+
514+
expect(getComputedStyle(detail).position).to.equal('fixed');
515+
expect(detailBounds.left).to.equal(windowBounds.left);
516+
expect(detailBounds.right).to.equal(windowBounds.right);
517+
expect(detailBounds.bottom).to.equal(windowBounds.bottom);
518+
});
519+
});
520+
});
440521
});
441522
});

0 commit comments

Comments
 (0)