Skip to content

Commit cc3ba6f

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 ae41a0a commit cc3ba6f

File tree

7 files changed

+129
-28
lines changed

7 files changed

+129
-28
lines changed

Diff for: src/dev-app/dialog/dialog-demo.html

+34-2
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>
@@ -125,3 +129,31 @@ <h2>Other options</h2>
125129
<button type="button" (click)="dialogRef.close(howMuch.value)">Close dialog</button>
126130
<button (click)="dialogRef.updateSize('500px', '500px').updatePosition({top: '25px', left: '25px'});">Change dimensions</button>
127131
</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>
159+
</ng-template>

Diff for: src/dev-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, ViewEncapsulation} from '@angular/core';
10+
import {Component, Inject, TemplateRef, ViewEncapsulation} 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

Diff for: 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;

Diff for: 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>

Diff for: 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. */
@@ -197,4 +207,19 @@ export class MatDialogContainer extends BasePortalOutlet {
197207
// view container is using OnPush change detection.
198208
this._changeDetectorRef.markForCheck();
199209
}
210+
211+
/**
212+
* Toggles a class on the host element, depending on whether it has
213+
* scrollable content. Used to activate particular flexbox styling.
214+
*/
215+
private _toggleScrollableContentClass() {
216+
const element: HTMLElement = this._elementRef.nativeElement;
217+
const cssClass = 'mat-dialog-container-scrollable';
218+
219+
if (element.querySelector('.mat-dialog-content')) {
220+
element.classList.add(cssClass);
221+
} else {
222+
element.classList.remove(cssClass);
223+
}
224+
}
200225
}

Diff for: src/lib/dialog/dialog.scss

+42-12
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

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

10+
// TODO(crisbeto): not used anywhere, to be removed next major release.
11+
$mat-dialog-max-height: 65vh !default;
12+
1013
.mat-dialog-container {
1114
display: block;
1215
padding: $mat-dialog-padding;
@@ -29,29 +32,56 @@ $mat-dialog-button-margin: 8px !default;
2932
}
3033
}
3134

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

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

4678
.mat-dialog-actions {
47-
padding: 8px 0;
79+
padding: 8px $mat-dialog-padding;
4880
display: flex;
4981
flex-wrap: wrap;
5082
min-height: 52px;
5183
align-items: center;
52-
53-
// Pull the actions down to avoid their padding stacking with the dialog's padding.
54-
margin-bottom: -$mat-dialog-padding;
84+
flex-shrink: 0;
5585

5686
&[align='end'] {
5787
justify-content: flex-end;

Diff for: src/lib/dialog/dialog.spec.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,13 @@ describe('MatDialog', () => {
11061106
}));
11071107

11081108
runContentElementTests();
1109+
1110+
it('should set the `mat-dialog-component-host` class on the rendered component', () => {
1111+
const container = overlayContainerElement.querySelector('mat-dialog-container')!;
1112+
const host = container.querySelector('content-element-dialog')!;
1113+
1114+
expect(host.classList).toContain('mat-dialog-component-host');
1115+
});
11091116
});
11101117

11111118
describe('inside template portal', () => {
@@ -1192,6 +1199,11 @@ describe('MatDialog', () => {
11921199
expect(container.getAttribute('aria-labelledby'))
11931200
.toBe(title.id, 'Expected the aria-labelledby to match the title id.');
11941201
}));
1202+
1203+
it('should set the `mat-dialog-container-scrollable` class on the container', () => {
1204+
const container = overlayContainerElement.querySelector('mat-dialog-container')!;
1205+
expect(container.classList).toContain('mat-dialog-container-scrollable');
1206+
});
11951207
}
11961208
});
11971209

@@ -1503,6 +1515,7 @@ class PizzaMsg {
15031515
}
15041516

15051517
@Component({
1518+
selector: 'content-element-dialog',
15061519
template: `
15071520
<h1 mat-dialog-title>This is the title</h1>
15081521
<mat-dialog-content>Lorem ipsum dolor sit amet.</mat-dialog-content>
@@ -1522,6 +1535,7 @@ class PizzaMsg {
15221535
class ContentElementDialog {}
15231536

15241537
@Component({
1538+
selector: 'content-element-dialog',
15251539
template: `
15261540
<ng-template>
15271541
<h1 mat-dialog-title>This is the title</h1>

0 commit comments

Comments
 (0)