From c5e50bc2bddc54527f1b3236fddad7153e255fba Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 12 Jul 2018 14:54:44 +0200 Subject: [PATCH] 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. --- src/demo-app/dialog/dialog-demo.html | 38 ++++++++++++++++++-- src/demo-app/dialog/dialog-demo.ts | 18 +++++----- src/lib/dialog/dialog-config.ts | 2 +- src/lib/dialog/dialog-container.html | 4 ++- src/lib/dialog/dialog-container.ts | 29 ++++++++++++++-- src/lib/dialog/dialog.scss | 52 +++++++++++++++++++++++----- src/lib/dialog/dialog.spec.ts | 14 ++++++++ 7 files changed, 131 insertions(+), 26 deletions(-) diff --git a/src/demo-app/dialog/dialog-demo.html b/src/demo-app/dialog/dialog-demo.html index ebf76c003267..afad8ee075e5 100644 --- a/src/demo-app/dialog/dialog-demo.html +++ b/src/demo-app/dialog/dialog-demo.html @@ -6,9 +6,13 @@

Dialog demo

- + + @@ -111,7 +115,7 @@

Other options

Last afterClosed result: {{lastAfterClosedResult}}

Last beforeClose result: {{lastBeforeCloseResult}}

- + I'm a template dialog. I've been opened {{numTemplateOpens}} times!

It's Jazz!

@@ -123,5 +127,33 @@

Other options

{{ data.message }}

- ` + +
+ + +

Saturn

+ + + Saturn + Saturn is the sixth planet from the Sun and the second-largest in the Solar System, after + Jupiter. It is a gas giant with an average radius about nine times that of Earth. It has + only one-eighth the average density of Earth, but with its larger volume Saturn is over + 95 times more massive. Saturn is named after the Roman god of agriculture; its astronomical + symbol (♄) represents the god's sickle. + + Saturn's interior is probably composed of a core of iron–nickel and rock + (silicon and oxygen compounds). This core is surrounded by a deep layer of metallic hydrogen, + an intermediate layer of liquid hydrogen and liquid helium, and finally a gaseous outer layer. + Saturn has a pale yellow hue due to ammonia crystals in its upper atmosphere. Electrical + current within the metallic hydrogen layer is thought to give rise to Saturn's planetary + magnetic field, which is weaker than Earth's, but has a magnetic moment 580 times that of + Earth due to Saturn's larger size. Saturn's magnetic field strength is around one-twentieth + of Jupiter's. The outer atmosphere is generally bland and lacking in contrast, although + long-lived features can appear. Wind speeds on Saturn can reach 1,800 km/h (1,100 mph), + higher than on Jupiter, but not as high as those on Neptune. + + + + +
diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index d9e54bd22885..c4ce51167c11 100644 --- a/src/demo-app/dialog/dialog-demo.ts +++ b/src/demo-app/dialog/dialog-demo.ts @@ -7,7 +7,7 @@ */ import {DOCUMENT} from '@angular/common'; -import {Component, Inject, TemplateRef, ViewChild} from '@angular/core'; +import {Component, Inject, TemplateRef} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialog, MatDialogConfig, MatDialogRef} from '@angular/material'; @@ -29,12 +29,12 @@ export class DialogDemo { panelClass: 'custom-overlay-pane-class', hasBackdrop: true, backdropClass: '', - width: '', - height: '', - minWidth: '', - minHeight: '', + width: defaultDialogConfig.width, + height: defaultDialogConfig.height, + minWidth: defaultDialogConfig.minWidth, + minHeight: defaultDialogConfig.minHeight, maxWidth: defaultDialogConfig.maxWidth, - maxHeight: '', + maxHeight: defaultDialogConfig.maxHeight, position: { top: '', bottom: '', @@ -47,8 +47,6 @@ export class DialogDemo { }; numTemplateOpens = 0; - @ViewChild(TemplateRef) template: TemplateRef; - constructor(public dialog: MatDialog, @Inject(DOCUMENT) doc: any) { // Possible useful example for the open and closeAll events. // Adding a class to the body if a dialog opens and @@ -80,9 +78,9 @@ export class DialogDemo { dialogRef.componentInstance.actionsAlignment = this.actionsAlignment; } - openTemplate() { + openTemplate(template: TemplateRef) { this.numTemplateOpens++; - this.dialog.open(this.template, this.config); + this.dialog.open(template, this.config); } } diff --git a/src/lib/dialog/dialog-config.ts b/src/lib/dialog/dialog-config.ts index 770a26a7d5bd..74042c803c01 100644 --- a/src/lib/dialog/dialog-config.ts +++ b/src/lib/dialog/dialog-config.ts @@ -75,7 +75,7 @@ export class MatDialogConfig { maxWidth?: number | string = '80vw'; /** Max-height of the dialog. If a number is provided, pixel units are assumed. */ - maxHeight?: number | string; + maxHeight?: number | string = '80vh'; /** Position overrides. */ position?: DialogPosition; diff --git a/src/lib/dialog/dialog-container.html b/src/lib/dialog/dialog-container.html index 215f0d9c55b3..6122e6b4b583 100644 --- a/src/lib/dialog/dialog-container.html +++ b/src/lib/dialog/dialog-container.html @@ -1 +1,3 @@ - +
+ +
diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index d03f34e0cb6d..4da5ad627a3e 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -113,7 +113,15 @@ export class MatDialogContainer extends BasePortalOutlet { } this._savePreviouslyFocusedElement(); - return this._portalOutlet.attachComponentPortal(portal); + + const componentRef = this._portalOutlet.attachComponentPortal(portal); + + // We need to add an extra class to the root of the instantiated component, which + // allows us to propagate some width/height overrides down from the overlay pane. + componentRef.location.nativeElement.classList.add('mat-dialog-component-host'); + this._toggleScrollableContentClass(); + + return componentRef; } /** @@ -126,7 +134,9 @@ export class MatDialogContainer extends BasePortalOutlet { } this._savePreviouslyFocusedElement(); - return this._portalOutlet.attachTemplatePortal(portal); + const viewRef = this._portalOutlet.attachTemplatePortal(portal); + this._toggleScrollableContentClass(); + return viewRef; } /** Moves the focus inside the focus trap. */ @@ -196,4 +206,19 @@ export class MatDialogContainer extends BasePortalOutlet { // view container is using OnPush change detection. this._changeDetectorRef.markForCheck(); } + + /** + * Toggles a class on the host element, depending on whether it has + * scrollable content. Used to activate particular flexbox styling. + */ + private _toggleScrollableContentClass() { + const element: HTMLElement = this._elementRef.nativeElement; + const cssClass = 'mat-dialog-container-scrollable'; + + if (element.querySelector('.mat-dialog-content')) { + element.classList.add(cssClass); + } else { + element.classList.remove(cssClass); + } + } } diff --git a/src/lib/dialog/dialog.scss b/src/lib/dialog/dialog.scss index 8e7f2f20f13f..035743a71165 100644 --- a/src/lib/dialog/dialog.scss +++ b/src/lib/dialog/dialog.scss @@ -4,10 +4,13 @@ $mat-dialog-padding: 24px !default; +$mat-dialog-title-padding: 24px !default; $mat-dialog-border-radius: 2px !default; -$mat-dialog-max-height: 65vh !default; $mat-dialog-button-margin: 8px !default; +// TODO(crisbeto): not used anywhere, to be removed next major release. +$mat-dialog-max-height: 65vh !default; + .mat-dialog-container { @include mat-elevation(24); @@ -32,27 +35,58 @@ $mat-dialog-button-margin: 8px !default; } } +.mat-dialog-container-scrollable { + padding: 0; + + // Since there are 5-6 levels of elements down before we can reach + // the projected content, we have to use a class that lets us propagate + // the dimensions down to the relevant flexboxes, in order for IE to + // work correctly. + &, .mat-dialog-component-host { + width: inherit; + min-width: inherit; + max-width: inherit; + height: inherit; + min-height: inherit; + max-height: inherit; + display: flex; + flex-direction: column; + overflow: auto; + } +} + +.mat-dialog-title { + display: flex; + flex-direction: column; + justify-content: center; + width: 100%; + flex-shrink: 0; + margin: 0; + padding: $mat-dialog-title-padding; + box-sizing: border-box; +} + .mat-dialog-content { display: block; - margin: 0 $mat-dialog-padding * -1; - padding: 0 $mat-dialog-padding; - max-height: $mat-dialog-max-height; + padding: $mat-dialog-padding $mat-dialog-padding 0; overflow: auto; -webkit-overflow-scrolling: touch; -} -.mat-dialog-title { - margin: 0 0 20px; - display: block; + // Avoid stacking the padding if there's a title. + .mat-dialog-title ~ & { + padding-top: 0; + } } .mat-dialog-actions { - padding: $mat-dialog-padding / 2 0; + padding: $mat-dialog-padding / 2 $mat-dialog-padding; display: flex; flex-wrap: wrap; // Pull the actions down to avoid their padding stacking with the dialog's padding. margin-bottom: -$mat-dialog-padding; + align-items: center; + flex-shrink: 0; &[align='end'] { justify-content: flex-end; diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index b382ab78e96c..5a4c7c8cc021 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -1053,6 +1053,13 @@ describe('MatDialog', () => { })); runContentElementTests(); + + it('should set the `mat-dialog-component-host` class on the rendered component', () => { + const container = overlayContainerElement.querySelector('mat-dialog-container')!; + const host = container.querySelector('content-element-dialog')!; + + expect(host.classList).toContain('mat-dialog-component-host'); + }); }); describe('inside template portal', () => { @@ -1124,6 +1131,11 @@ describe('MatDialog', () => { expect(container.getAttribute('aria-labelledby')) .toBe(title.id, 'Expected the aria-labelledby to match the title id.'); })); + + it('should set the `mat-dialog-container-scrollable` class on the container', () => { + const container = overlayContainerElement.querySelector('mat-dialog-container')!; + expect(container.classList).toContain('mat-dialog-container-scrollable'); + }); } }); @@ -1374,6 +1386,7 @@ class PizzaMsg { } @Component({ + selector: 'content-element-dialog', template: `

This is the title

Lorem ipsum dolor sit amet. @@ -1391,6 +1404,7 @@ class PizzaMsg { class ContentElementDialog {} @Component({ + selector: 'content-element-dialog', template: `

This is the title