Skip to content

Commit 214f719

Browse files
committed
fix(dialog): improved handling of scrollable content
* Improves the handling of scrollable content inside the `mat-dialog-content` by using flexbox, rather than a hardcoded `max-height`, to define the content height. This resolves various issues where the dialog would go out of the screen at certain screen sizes or have multiple scrollbars. * Uses flexbox to ensure that the dialog content elements are always at the appropriate size. Fixes #2481. Fixes #3239. Fixes #6584. Fixes #8493.
1 parent e20d8f0 commit 214f719

7 files changed

+131
-33
lines changed

src/demo-app/dialog/dialog-demo.html

+35-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ <h1>Dialog demo</h1>
66
<button mat-raised-button color="accent" (click)="openContentElement()">
77
Open dialog with content elements
88
</button>
9-
<button mat-raised-button color="accent" (click)="openTemplate()">
9+
<button mat-raised-button color="accent" (click)="openTemplate(templateDialog)">
1010
Open dialog with template content
1111
</button>
12+
<button mat-raised-button color="accent" (click)="openTemplate(templateDialogWithContent)">
13+
Open template dialog with content elements
14+
</button>
15+
1216

1317
<mat-card class="demo-dialog-card">
1418
<mat-card-content>
@@ -98,7 +102,7 @@ <h2>Other options</h2>
98102
<p>Last afterClosed result: {{lastAfterClosedResult}}</p>
99103
<p>Last beforeClose result: {{lastBeforeCloseResult}}</p>
100104

101-
<ng-template let-data let-dialogRef="dialogRef">
105+
<ng-template #templateDialog let-data let-dialogRef="dialogRef">
102106
I'm a template dialog. I've been opened {{numTemplateOpens}} times!
103107

104108
<p>It's Jazz!</p>
@@ -109,5 +113,33 @@ <h2>Other options</h2>
109113

110114
<p> {{ data.message }} </p>
111115
<button type="button" (click)="dialogRef.close(lastCloseResult = howMuch.value)">Close dialog</button>
112-
<button (click)="dialogRef.updateSize('500px', '500px').updatePosition({ top: '25px', left: '25px' });">Change dimensions</button>`
116+
<button (click)="dialogRef.updateSize('500px', '500px').updatePosition({ top: '25px', left: '25px' });">Change dimensions</button>
117+
</ng-template>
118+
119+
<ng-template #templateDialogWithContent let-data let-dialogRef="dialogRef">
120+
<h2 mat-dialog-title>Saturn</h2>
121+
122+
<mat-dialog-content>
123+
<img style="max-width: 100%;" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Saturn_during_Equinox.jpg/1920px-Saturn_during_Equinox.jpg" alt="Saturn">
124+
Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after
125+
Jupiter. It is a gas giant with an average radius about nine times that of Earth. It has
126+
only one-eighth the average density of Earth, but with its larger volume Saturn is over
127+
95 times more massive. Saturn is named after the Roman god of agriculture; its astronomical
128+
symbol (♄) represents the god's sickle.
129+
130+
Saturn's interior is probably composed of a core of iron–nickel and rock
131+
(silicon and oxygen compounds). This core is surrounded by a deep layer of metallic hydrogen,
132+
an intermediate layer of liquid hydrogen and liquid helium, and finally a gaseous outer layer.
133+
Saturn has a pale yellow hue due to ammonia crystals in its upper atmosphere. Electrical
134+
current within the metallic hydrogen layer is thought to give rise to Saturn's planetary
135+
magnetic field, which is weaker than Earth's, but has a magnetic moment 580 times that of
136+
Earth due to Saturn's larger size. Saturn's magnetic field strength is around one-twentieth
137+
of Jupiter's. The outer atmosphere is generally bland and lacking in contrast, although
138+
long-lived features can appear. Wind speeds on Saturn can reach 1,800 km/h (1,100 mph),
139+
higher than on Jupiter, but not as high as those on Neptune.
140+
</mat-dialog-content>
141+
142+
<mat-dialog-actions>
143+
<button mat-raised-button color="primary" (click)="dialogRef.close()">Close dialog</button>
144+
</mat-dialog-actions>
113145
</ng-template>

src/demo-app/dialog/dialog-demo.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Component, Inject, ViewChild, TemplateRef} from '@angular/core';
9+
import {Component, Inject, TemplateRef} from '@angular/core';
1010
import {DOCUMENT} from '@angular/common';
1111
import {MatDialog, MatDialogConfig, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';
1212

@@ -28,12 +28,12 @@ export class DialogDemo {
2828
panelClass: 'custom-overlay-pane-class',
2929
hasBackdrop: true,
3030
backdropClass: '',
31-
width: '',
32-
height: '',
33-
minWidth: '',
34-
minHeight: '',
31+
width: defaultDialogConfig.width,
32+
height: defaultDialogConfig.height,
33+
minWidth: defaultDialogConfig.minWidth,
34+
minHeight: defaultDialogConfig.minHeight,
3535
maxWidth: defaultDialogConfig.maxWidth,
36-
maxHeight: '',
36+
maxHeight: defaultDialogConfig.maxHeight,
3737
position: {
3838
top: '',
3939
bottom: '',
@@ -46,8 +46,6 @@ export class DialogDemo {
4646
};
4747
numTemplateOpens = 0;
4848

49-
@ViewChild(TemplateRef) template: TemplateRef<any>;
50-
5149
constructor(public dialog: MatDialog, @Inject(DOCUMENT) doc: any) {
5250
// Possible useful example for the open and closeAll events.
5351
// Adding a class to the body if a dialog opens and
@@ -79,9 +77,9 @@ export class DialogDemo {
7977
dialogRef.componentInstance.actionsAlignment = this.actionsAlignment;
8078
}
8179

82-
openTemplate() {
80+
openTemplate(template: TemplateRef<any>) {
8381
this.numTemplateOpens++;
84-
this.dialog.open(this.template, this.config);
82+
this.dialog.open(template, this.config);
8583
}
8684
}
8785

src/lib/dialog/dialog-config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class MatDialogConfig<D = any> {
7474
maxWidth?: number | string = '80vw';
7575

7676
/** Max-height of the dialog. If a number is provided, pixel units are assumed. */
77-
maxHeight?: number | string;
77+
maxHeight?: number | string = '80vh';
7878

7979
/** Position overrides. */
8080
position?: DialogPosition;

src/lib/dialog/dialog-container.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
<ng-template cdkPortalOutlet></ng-template>
1+
<div class="mat-dialog-component-host">
2+
<ng-template cdkPortalOutlet></ng-template>
3+
</div>

src/lib/dialog/dialog-container.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,13 @@ export class MatDialogContainer extends BasePortalOutlet {
120120
}
121121

122122
this._savePreviouslyFocusedElement();
123-
return this._portalOutlet.attachComponentPortal(portal);
123+
124+
const componentRef = this._portalOutlet.attachComponentPortal(portal);
125+
126+
componentRef.location.nativeElement.classList.add('mat-dialog-component-host');
127+
this._toggleScrollableContentClass();
128+
129+
return componentRef;
124130
}
125131

126132
/**
@@ -133,7 +139,9 @@ export class MatDialogContainer extends BasePortalOutlet {
133139
}
134140

135141
this._savePreviouslyFocusedElement();
136-
return this._portalOutlet.attachTemplatePortal(portal);
142+
const viewRef = this._portalOutlet.attachTemplatePortal(portal);
143+
this._toggleScrollableContentClass();
144+
return viewRef;
137145
}
138146

139147
/** Moves the focus inside the focus trap. */
@@ -200,4 +208,19 @@ export class MatDialogContainer extends BasePortalOutlet {
200208
// view container is using OnPush change detection.
201209
this._changeDetectorRef.markForCheck();
202210
}
211+
212+
/**
213+
* Toggles a class on the host element, depending on whether it has
214+
* scrollable content. Used to activate particular flexbox styling.
215+
*/
216+
private _toggleScrollableContentClass() {
217+
const element: HTMLElement = this._elementRef.nativeElement;
218+
const cssClass = 'mat-dialog-container-scrollable';
219+
220+
if (element.querySelector('.mat-dialog-content')) {
221+
element.classList.add(cssClass);
222+
} else {
223+
element.classList.remove(cssClass);
224+
}
225+
}
203226
}

src/lib/dialog/dialog.scss

+46-16
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55

66
$mat-dialog-padding: 24px !default;
7+
$mat-dialog-title-padding: 24px !default;
78
$mat-dialog-border-radius: 2px !default;
8-
$mat-dialog-max-height: 65vh !default;
99
$mat-dialog-button-margin: 8px !default;
1010

11+
// TODO(crisbeto): not used anywhere, to be removed next major release.
12+
$mat-dialog-max-height: 65vh !default;
13+
1114
.mat-dialog-container {
1215
@include mat-elevation(24);
1316

@@ -27,34 +30,61 @@ $mat-dialog-button-margin: 8px !default;
2730
}
2831
}
2932

33+
.mat-dialog-container-scrollable {
34+
padding: 0;
35+
36+
// Since there are 5-6 levels of elements down before we can reach
37+
// the projected content, we have to use a class that lets us propagate
38+
// the dimensions down to the relevant flexboxes, in order for IE to
39+
// work correctly.
40+
&, .mat-dialog-component-host {
41+
width: inherit;
42+
min-width: inherit;
43+
max-width: inherit;
44+
height: inherit;
45+
min-height: inherit;
46+
max-height: inherit;
47+
display: flex;
48+
flex-direction: column;
49+
overflow: auto;
50+
}
51+
}
52+
53+
.mat-dialog-title {
54+
display: flex;
55+
flex-direction: column;
56+
justify-content: center;
57+
width: 100%;
58+
flex-shrink: 0;
59+
order: 1;
60+
margin: 0;
61+
padding: $mat-dialog-title-padding;
62+
box-sizing: border-box;
63+
}
64+
3065
.mat-dialog-content {
3166
display: block;
32-
margin: 0 $mat-dialog-padding * -1;
33-
padding: 0 $mat-dialog-padding;
34-
max-height: $mat-dialog-max-height;
67+
padding: $mat-dialog-padding $mat-dialog-padding 0;
3568
overflow: auto;
3669
-webkit-overflow-scrolling: touch;
70+
order: 2;
3771

3872
// Promote the content to a new GPU layer to avoid repaints on scroll.
3973
@include backface-visibility(hidden);
40-
}
4174

42-
.mat-dialog-title {
43-
margin: 0 0 20px;
44-
display: block;
75+
// Avoid stacking the padding if there's a title.
76+
.mat-dialog-title ~ & {
77+
padding-top: 0;
78+
}
4579
}
4680

4781
.mat-dialog-actions {
48-
padding: $mat-dialog-padding / 2 0;
82+
padding: $mat-dialog-padding / 2 $mat-dialog-padding;
4983
display: flex;
5084
flex-wrap: wrap;
51-
52-
&:last-child {
53-
// If the actions are the last element in a dialog, we need to pull them down
54-
// over the dialog padding, in order to avoid the action's padding stacking
55-
// with the dialog's.
56-
margin-bottom: -$mat-dialog-padding;
57-
}
85+
order: 3;
86+
align-items: center;
87+
flex-shrink: 0;
5888

5989
&[align='end'] {
6090
justify-content: flex-end;

src/lib/dialog/dialog.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,18 @@ describe('MatDialog', () => {
938938
.toBe(title.id, 'Expected the aria-labelledby to match the title id.');
939939
}));
940940

941+
it('should set the `mat-dialog-container-scrollable` class on the container', () => {
942+
const container = overlayContainerElement.querySelector('mat-dialog-container')!;
943+
expect(container.classList).toContain('mat-dialog-container-scrollable');
944+
});
945+
946+
it('should set the `mat-dialog-component-host` class on the rendered component', () => {
947+
const container = overlayContainerElement.querySelector('mat-dialog-container')!;
948+
const host = container.querySelector('content-element-dialog')!;
949+
950+
expect(host.classList).toContain('mat-dialog-component-host');
951+
});
952+
941953
});
942954

943955
describe('aria-label', () => {
@@ -1097,6 +1109,7 @@ class PizzaMsg {
10971109
}
10981110

10991111
@Component({
1112+
selector: 'content-element-dialog',
11001113
template: `
11011114
<h1 mat-dialog-title>This is the title</h1>
11021115
<mat-dialog-content>Lorem ipsum dolor sit amet.</mat-dialog-content>

0 commit comments

Comments
 (0)