Skip to content

Commit 3c45bee

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 1a60a7a commit 3c45bee

7 files changed

+131
-26
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>
@@ -111,7 +115,7 @@ <h2>Other options</h2>
111115
<p>Last afterClosed result: {{lastAfterClosedResult}}</p>
112116
<p>Last beforeClose result: {{lastBeforeCloseResult}}</p>
113117

114-
<ng-template let-data let-dialogRef="dialogRef">
118+
<ng-template #templateDialog let-data let-dialogRef="dialogRef">
115119
I'm a template dialog. I've been opened {{numTemplateOpens}} times!
116120

117121
<p>It's Jazz!</p>
@@ -123,5 +127,33 @@ <h2>Other options</h2>
123127

124128
<p> {{ data.message }} </p>
125129
<button type="button" (click)="dialogRef.close(lastCloseResult = howMuch.value)">Close dialog</button>
126-
<button (click)="dialogRef.updateSize('500px', '500px').updatePosition({ top: '25px', left: '25px' });">Change dimensions</button>`
130+
<button (click)="dialogRef.updateSize('500px', '500px').updatePosition({ top: '25px', left: '25px' });">Change dimensions</button>
131+
</ng-template>
132+
133+
<ng-template #templateDialogWithContent let-data let-dialogRef="dialogRef">
134+
<h2 mat-dialog-title>Saturn</h2>
135+
136+
<mat-dialog-content>
137+
<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">
138+
Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after
139+
Jupiter. It is a gas giant with an average radius about nine times that of Earth. It has
140+
only one-eighth the average density of Earth, but with its larger volume Saturn is over
141+
95 times more massive. Saturn is named after the Roman god of agriculture; its astronomical
142+
symbol (♄) represents the god's sickle.
143+
144+
Saturn's interior is probably composed of a core of iron–nickel and rock
145+
(silicon and oxygen compounds). This core is surrounded by a deep layer of metallic hydrogen,
146+
an intermediate layer of liquid hydrogen and liquid helium, and finally a gaseous outer layer.
147+
Saturn has a pale yellow hue due to ammonia crystals in its upper atmosphere. Electrical
148+
current within the metallic hydrogen layer is thought to give rise to Saturn's planetary
149+
magnetic field, which is weaker than Earth's, but has a magnetic moment 580 times that of
150+
Earth due to Saturn's larger size. Saturn's magnetic field strength is around one-twentieth
151+
of Jupiter's. The outer atmosphere is generally bland and lacking in contrast, although
152+
long-lived features can appear. Wind speeds on Saturn can reach 1,800 km/h (1,100 mph),
153+
higher than on Jupiter, but not as high as those on Neptune.
154+
</mat-dialog-content>
155+
156+
<mat-dialog-actions>
157+
<button mat-raised-button color="primary" (click)="dialogRef.close()">Close dialog</button>
158+
</mat-dialog-actions>
127159
</ng-template>

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

+8-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

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

1313

@@ -29,12 +29,12 @@ export class DialogDemo {
2929
panelClass: 'custom-overlay-pane-class',
3030
hasBackdrop: true,
3131
backdropClass: '',
32-
width: '',
33-
height: '',
34-
minWidth: '',
35-
minHeight: '',
32+
width: defaultDialogConfig.width,
33+
height: defaultDialogConfig.height,
34+
minWidth: defaultDialogConfig.minWidth,
35+
minHeight: defaultDialogConfig.minHeight,
3636
maxWidth: defaultDialogConfig.maxWidth,
37-
maxHeight: '',
37+
maxHeight: defaultDialogConfig.maxHeight,
3838
position: {
3939
top: '',
4040
bottom: '',
@@ -47,8 +47,6 @@ export class DialogDemo {
4747
};
4848
numTemplateOpens = 0;
4949

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

83-
openTemplate() {
81+
openTemplate(template: TemplateRef<any>) {
8482
this.numTemplateOpens++;
85-
this.dialog.open(this.template, this.config);
83+
this.dialog.open(template, this.config);
8684
}
8785
}
8886

src/lib/dialog/dialog-config.ts

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

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

8080
/** Position overrides. */
8181
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

+27-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,15 @@ export class MatDialogContainer extends BasePortalOutlet {
113113
}
114114

115115
this._savePreviouslyFocusedElement();
116-
return this._portalOutlet.attachComponentPortal(portal);
116+
117+
const componentRef = this._portalOutlet.attachComponentPortal(portal);
118+
119+
// We need to add an extra class to the root of the instantiated component, which
120+
// allows us to propagate some width/height overrides down from the overlay pane.
121+
componentRef.location.nativeElement.classList.add('mat-dialog-component-host');
122+
this._toggleScrollableContentClass();
123+
124+
return componentRef;
117125
}
118126

119127
/**
@@ -126,7 +134,9 @@ export class MatDialogContainer extends BasePortalOutlet {
126134
}
127135

128136
this._savePreviouslyFocusedElement();
129-
return this._portalOutlet.attachTemplatePortal(portal);
137+
const viewRef = this._portalOutlet.attachTemplatePortal(portal);
138+
this._toggleScrollableContentClass();
139+
return viewRef;
130140
}
131141

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

src/lib/dialog/dialog.scss

+43-9
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,27 +30,58 @@ $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+
margin: 0;
60+
padding: $mat-dialog-title-padding;
61+
box-sizing: border-box;
62+
}
63+
3064
.mat-dialog-content {
3165
display: block;
32-
margin: 0 $mat-dialog-padding * -1;
33-
padding: 0 $mat-dialog-padding;
34-
max-height: $mat-dialog-max-height;
66+
padding: $mat-dialog-padding $mat-dialog-padding 0;
3567
overflow: auto;
3668
-webkit-overflow-scrolling: touch;
37-
}
3869

39-
.mat-dialog-title {
40-
margin: 0 0 20px;
41-
display: block;
70+
// Avoid stacking the padding if there's a title.
71+
.mat-dialog-title ~ & {
72+
padding-top: 0;
73+
}
4274
}
4375

4476
.mat-dialog-actions {
45-
padding: $mat-dialog-padding / 2 0;
77+
padding: $mat-dialog-padding / 2 $mat-dialog-padding;
4678
display: flex;
4779
flex-wrap: wrap;
4880

4981
// Pull the actions down to avoid their padding stacking with the dialog's padding.
5082
margin-bottom: -$mat-dialog-padding;
83+
align-items: center;
84+
flex-shrink: 0;
5185

5286
&[align='end'] {
5387
justify-content: flex-end;

src/lib/dialog/dialog.spec.ts

+14
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,13 @@ describe('MatDialog', () => {
968968
}));
969969

970970
runContentElementTests();
971+
972+
it('should set the `mat-dialog-component-host` class on the rendered component', () => {
973+
const container = overlayContainerElement.querySelector('mat-dialog-container')!;
974+
const host = container.querySelector('content-element-dialog')!;
975+
976+
expect(host.classList).toContain('mat-dialog-component-host');
977+
});
971978
});
972979

973980
describe('inside template portal', () => {
@@ -1039,6 +1046,11 @@ describe('MatDialog', () => {
10391046
expect(container.getAttribute('aria-labelledby'))
10401047
.toBe(title.id, 'Expected the aria-labelledby to match the title id.');
10411048
}));
1049+
1050+
it('should set the `mat-dialog-container-scrollable` class on the container', () => {
1051+
const container = overlayContainerElement.querySelector('mat-dialog-container')!;
1052+
expect(container.classList).toContain('mat-dialog-container-scrollable');
1053+
});
10421054
}
10431055
});
10441056

@@ -1289,6 +1301,7 @@ class PizzaMsg {
12891301
}
12901302

12911303
@Component({
1304+
selector: 'content-element-dialog',
12921305
template: `
12931306
<h1 mat-dialog-title>This is the title</h1>
12941307
<mat-dialog-content>Lorem ipsum dolor sit amet.</mat-dialog-content>
@@ -1306,6 +1319,7 @@ class PizzaMsg {
13061319
class ContentElementDialog {}
13071320

13081321
@Component({
1322+
selector: 'content-element-dialog',
13091323
template: `
13101324
<ng-template>
13111325
<h1 mat-dialog-title>This is the title</h1>

0 commit comments

Comments
 (0)