Skip to content

Commit 67ab3e9

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 3bc4cd3 commit 67ab3e9

File tree

7 files changed

+131
-33
lines changed

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
@@ -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
@@ -114,7 +114,15 @@ export class MatDialogContainer extends BasePortalOutlet {
114114
}
115115

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

120128
/**
@@ -127,7 +135,9 @@ export class MatDialogContainer extends BasePortalOutlet {
127135
}
128136

129137
this._savePreviouslyFocusedElement();
130-
return this._portalOutlet.attachTemplatePortal(portal);
138+
const viewRef = this._portalOutlet.attachTemplatePortal(portal);
139+
this._toggleScrollableContentClass();
140+
return viewRef;
131141
}
132142

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

src/lib/dialog/dialog.scss

+43-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,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;
3769

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

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

4779
.mat-dialog-actions {
48-
padding: $mat-dialog-padding / 2 0;
80+
padding: $mat-dialog-padding / 2 $mat-dialog-padding;
4981
display: flex;
5082
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-
}
83+
align-items: center;
84+
flex-shrink: 0;
5885

5986
&[align='end'] {
6087
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)