mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(dialog): add dialog content elements (#2090)
* feat(dialog): add dialog content elements Adds the following dialog-specific directives: * `md-dialog-close` - Closes the current dialog. * `md-dialog-title` - Title of a dialog. * `md-dialog-content` - Scrollable content for a dialog. * `md-dialog-actions` - Container for the bottom buttons in a dialog. Fixes #1624. Fixes #2042. * Rename the dialog directives file. * Add the selectors for Material 1 compatibility. * Remove the closeTop method and use the dialogRef instead. * Add an aria-label to the close button and simplify the testing setup. * Remove redundant element roles. * Use the computed value on the dialog title font size. * Remove the letter spacing override on the dialog title. * Add a comment regarding the negative bottom margin on the dialog actions.
This commit is contained in:
committed by
Jeremy Elbourn
parent
20ee360912
commit
cac72aa30c
@@ -7,7 +7,7 @@ import {RouterModule} from '@angular/router';
|
||||
import {MaterialModule} from '@angular/material';
|
||||
import {DEMO_APP_ROUTES} from './demo-app/routes';
|
||||
import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
|
||||
import {JazzDialog, DialogDemo} from './dialog/dialog-demo';
|
||||
import {JazzDialog, ContentElementDialog, DialogDemo} from './dialog/dialog-demo';
|
||||
import {RippleDemo} from './ripple/ripple-demo';
|
||||
import {IconDemo} from './icon/icon-demo';
|
||||
import {GesturesDemo} from './gestures/gestures-demo';
|
||||
@@ -65,6 +65,7 @@ import {InputContainerDemo} from './input/input-container-demo';
|
||||
InputDemo,
|
||||
InputContainerDemo,
|
||||
JazzDialog,
|
||||
ContentElementDialog,
|
||||
ListDemo,
|
||||
LiveAnnouncerDemo,
|
||||
MdCheckboxDemoNestedChecklist,
|
||||
@@ -96,6 +97,7 @@ import {InputContainerDemo} from './input/input-container-demo';
|
||||
entryComponents: [
|
||||
DemoApp,
|
||||
JazzDialog,
|
||||
ContentElementDialog,
|
||||
RotiniPanel,
|
||||
ScienceJoke,
|
||||
SpagettiPanel,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<h1>Dialog demo</h1>
|
||||
|
||||
<button md-raised-button color="primary" (click)="open()" [disabled]="dialogRef">Open dialog</button>
|
||||
<button md-raised-button color="primary" (click)="openJazz()" [disabled]="dialogRef">Open dialog</button>
|
||||
<button md-raised-button color="accent" (click)="openContentElement()">Open dialog with content elements</button>
|
||||
|
||||
<md-card class="demo-dialog-card">
|
||||
<md-card-content>
|
||||
|
||||
@@ -24,7 +24,7 @@ export class DialogDemo {
|
||||
|
||||
constructor(public dialog: MdDialog) { }
|
||||
|
||||
open() {
|
||||
openJazz() {
|
||||
this.dialogRef = this.dialog.open(JazzDialog, this.config);
|
||||
|
||||
this.dialogRef.afterClosed().subscribe(result => {
|
||||
@@ -32,6 +32,10 @@ export class DialogDemo {
|
||||
this.dialogRef = null;
|
||||
});
|
||||
}
|
||||
|
||||
openContentElement() {
|
||||
this.dialog.open(ContentElementDialog, this.config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,3 +52,44 @@ export class JazzDialog {
|
||||
|
||||
constructor(public dialogRef: MdDialogRef<JazzDialog>) { }
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'demo-content-element-dialog',
|
||||
styles: [
|
||||
`img {
|
||||
max-width: 100%;
|
||||
}`
|
||||
],
|
||||
template: `
|
||||
<h2 md-dialog-title>Neptune</h2>
|
||||
|
||||
<md-dialog-content>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/5/56/Neptune_Full.jpg"/>
|
||||
|
||||
<p>
|
||||
Neptune is the eighth and farthest known planet from the Sun in the Solar System. In the
|
||||
Solar System, it is the fourth-largest planet by diameter, the third-most-massive planet,
|
||||
and the densest giant planet. Neptune is 17 times the mass of Earth and is slightly more
|
||||
massive than its near-twin Uranus, which is 15 times the mass of Earth and slightly larger
|
||||
than Neptune. Neptune orbits the Sun once every 164.8 years at an average distance of 30.1
|
||||
astronomical units (4.50×109 km). It is named after the Roman god of the sea and has the
|
||||
astronomical symbol ♆, a stylised version of the god Neptune's trident.
|
||||
</p>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions>
|
||||
<button
|
||||
md-raised-button
|
||||
color="primary"
|
||||
md-dialog-close>Close</button>
|
||||
|
||||
<a
|
||||
md-button
|
||||
color="primary"
|
||||
href="https://en.wikipedia.org/wiki/Neptune"
|
||||
target="_blank">Read more on Wikipedia</a>
|
||||
</md-dialog-actions>
|
||||
`
|
||||
})
|
||||
export class ContentElementDialog { }
|
||||
|
||||
@@ -4,15 +4,16 @@ MdDialog is a service, which opens dialogs components in the view.
|
||||
|
||||
### Methods
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| Name | Description |
|
||||
| ---- | ----------- |
|
||||
| `open(component: ComponentType<T>, config: MdDialogConfig): MdDialogRef<T>` | Creates and opens a dialog matching material spec. |
|
||||
| `closeAll(): void` | Closes all of the dialogs that are currently open. |
|
||||
| `closeTop(): void` | Closes the topmost of the open dialogs. |
|
||||
|
||||
### Config
|
||||
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| Key | Description |
|
||||
| --- | ------------ |
|
||||
| `role: DialogRole = 'dialog'` | The ARIA role of the dialog element. Possible values are `dialog` and `alertdialog`. Optional. |
|
||||
| `disableClose: boolean = false` | Whether to prevent the user from closing a dialog by clicking on the backdrop or pressing escape. Optional. |
|
||||
| `width: string = ''` | Width of the dialog. Takes any valid CSS value. Optional. |
|
||||
@@ -26,11 +27,19 @@ A reference to the dialog created by the MdDialog `open` method.
|
||||
|
||||
### Methods
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| Name | Description |
|
||||
| ---- | ----------- |
|
||||
| `close(dialogResult?: any)` | Closes the dialog, pushing a value to the afterClosed observable. |
|
||||
| `afterClosed(): Observable<any>` | Returns an observable which will emit the dialog result, passed to the `close` method above. |
|
||||
|
||||
### Directives
|
||||
| Name | Description |
|
||||
| --- | ------------ |
|
||||
| `md-dialog-title` | Marks the title of the dialog.
|
||||
| `md-dialog-content` | Scrollable content of the dialog.
|
||||
| `md-dialog-close` | When added to a `button`, makes the element act as a close button for the dialog.
|
||||
| `md-dialog-actions` | Wrapper for the set of actions at the bottom of a dialog. Typically contains buttons.
|
||||
|
||||
## Example
|
||||
The service can be injected in a component.
|
||||
|
||||
@@ -62,8 +71,12 @@ export class PizzaComponent {
|
||||
@Component({
|
||||
selector: 'pizza-dialog',
|
||||
template: `
|
||||
<button type="button" (click)="dialogRef.close('yes')">Yes</button>
|
||||
<button type="button" (click)="dialogRef.close('no')">No</button>
|
||||
<h1 md-dialog-title>Would you like to order pizza?</h1>
|
||||
|
||||
<md-dialog-actions>
|
||||
<button (click)="dialogRef.close('yes')">Yes</button>
|
||||
<button md-dialog-close>No</button>
|
||||
</md-dialog-actions>
|
||||
`
|
||||
})
|
||||
export class PizzaDialog {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
@import '../core/style/elevation';
|
||||
@import '../core/a11y/a11y';
|
||||
|
||||
|
||||
$md-dialog-padding: 24px !default;
|
||||
$md-dialog-border-radius: 2px !default;
|
||||
|
||||
md-dialog-container {
|
||||
@include md-elevation(24);
|
||||
|
||||
display: block;
|
||||
padding: $md-dialog-padding;
|
||||
border-radius: $md-dialog-border-radius;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
|
||||
// The dialog container should completely fill its parent overlay element.
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@include md-high-contrast {
|
||||
outline: solid 1px;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import 'rxjs/add/operator/first';
|
||||
moduleId: module.id,
|
||||
selector: 'md-dialog-container, mat-dialog-container',
|
||||
templateUrl: 'dialog-container.html',
|
||||
styleUrls: ['dialog-container.css'],
|
||||
styleUrls: ['dialog.css'],
|
||||
host: {
|
||||
'class': 'md-dialog-container',
|
||||
'[attr.role]': 'dialogConfig?.role',
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import {Directive, Input} from '@angular/core';
|
||||
import {MdDialogRef} from './dialog-ref';
|
||||
|
||||
|
||||
/**
|
||||
* Button that will close the current dialog.
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'button[md-dialog-close], button[mat-dialog-close]',
|
||||
host: {
|
||||
'(click)': 'dialogRef.close()',
|
||||
'[attr.aria-label]': 'ariaLabel'
|
||||
}
|
||||
})
|
||||
export class MdDialogClose {
|
||||
/** Screenreader label for the button. */
|
||||
@Input('aria-label') ariaLabel: string = 'Close dialog';
|
||||
|
||||
constructor(public dialogRef: MdDialogRef<any>) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of a dialog element. Stays fixed to the top of the dialog when scrolling.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[md-dialog-title], [mat-dialog-title]'
|
||||
})
|
||||
export class MdDialogTitle { }
|
||||
|
||||
|
||||
/**
|
||||
* Scrollable content container of a dialog.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[md-dialog-content], md-dialog-content, [mat-dialog-content], mat-dialog-content'
|
||||
})
|
||||
export class MdDialogContent { }
|
||||
|
||||
|
||||
/**
|
||||
* Container for the bottom action buttons in a dialog.
|
||||
* Stays fixed to the bottom when scrolling.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[md-dialog-actions], md-dialog-actions, [mat-dialog-actions], mat-dialog-actions'
|
||||
})
|
||||
export class MdDialogActions { }
|
||||
@@ -0,0 +1,54 @@
|
||||
@import '../core/style/elevation';
|
||||
@import '../core/a11y/a11y';
|
||||
|
||||
|
||||
$md-dialog-padding: 24px !default;
|
||||
$md-dialog-border-radius: 2px !default;
|
||||
$md-dialog-max-width: 80vw !default;
|
||||
$md-dialog-max-height: 65vh !default;
|
||||
|
||||
md-dialog-container {
|
||||
@include md-elevation(24);
|
||||
|
||||
display: block;
|
||||
padding: $md-dialog-padding;
|
||||
border-radius: $md-dialog-border-radius;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
max-width: $md-dialog-max-width;
|
||||
|
||||
// The dialog container should completely fill its parent overlay element.
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@include md-high-contrast {
|
||||
outline: solid 1px;
|
||||
}
|
||||
}
|
||||
|
||||
md-dialog-content, [md-dialog-content], mat-dialog-content, [mat-dialog-content] {
|
||||
display: block;
|
||||
margin: 0 $md-dialog-padding * -1;
|
||||
padding: 0 $md-dialog-padding;
|
||||
max-height: $md-dialog-max-height;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[md-dialog-title], [mat-dialog-title] {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
md-dialog-actions, [md-dialog-actions], mat-dialog-actions, [mat-dialog-actions] {
|
||||
padding: $md-dialog-padding / 2 0;
|
||||
display: block;
|
||||
|
||||
&:last-child {
|
||||
// If the actions are the last element in a dialog, we need to pull them down
|
||||
// over the dialog padding, in order to avoid the action's padding stacking
|
||||
// with the dialog's.
|
||||
margin-bottom: -$md-dialog-padding;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {NgModule, Component, Directive, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {MdDialog, MdDialogModule} from './dialog';
|
||||
import {MdDialogModule} from './index';
|
||||
import {MdDialog} from './dialog';
|
||||
import {OverlayContainer} from '../core';
|
||||
import {MdDialogRef} from './dialog-ref';
|
||||
import {MdDialogContainer} from './dialog-container';
|
||||
@@ -308,6 +309,41 @@ describe('MdDialog', () => {
|
||||
.toBe('dialog-trigger', 'Expected that the trigger was refocused after dialog close');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('dialog content elements', () => {
|
||||
let dialogRef: MdDialogRef<ContentElementDialog>;
|
||||
|
||||
beforeEach(() => {
|
||||
dialogRef = dialog.open(ContentElementDialog);
|
||||
viewContainerFixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should close the dialog when clicking on the close button', () => {
|
||||
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);
|
||||
|
||||
(overlayContainerElement.querySelector('button[md-dialog-close]') as HTMLElement).click();
|
||||
|
||||
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not close the dialog if [md-dialog-close] is applied on a non-button node', () => {
|
||||
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);
|
||||
|
||||
(overlayContainerElement.querySelector('div[md-dialog-close]') as HTMLElement).click();
|
||||
|
||||
expect(overlayContainerElement.querySelectorAll('.md-dialog-container').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow for a user-specified aria-label on the close button', () => {
|
||||
let button = overlayContainerElement.querySelector('button[md-dialog-close]');
|
||||
|
||||
dialogRef.componentInstance.closeButtonAriaLabel = 'Best close button ever';
|
||||
viewContainerFixture.detectChanges();
|
||||
|
||||
expect(button.getAttribute('aria-label')).toBe('Best close button ever');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -334,13 +370,33 @@ class PizzaMsg {
|
||||
constructor(public dialogRef: MdDialogRef<PizzaMsg>) { }
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h1 md-dialog-title>This is the title</h1>
|
||||
<md-dialog-content>Lorem ipsum dolor sit amet.</md-dialog-content>
|
||||
<md-dialog-actions>
|
||||
<button md-dialog-close [aria-label]="closeButtonAriaLabel">Close</button>
|
||||
<div md-dialog-close>Should not close</div>
|
||||
</md-dialog-actions>
|
||||
`
|
||||
})
|
||||
class ContentElementDialog {
|
||||
closeButtonAriaLabel: string;
|
||||
}
|
||||
|
||||
// Create a real (non-test) NgModule as a workaround for
|
||||
// https://github.com/angular/angular/issues/10760
|
||||
const TEST_DIRECTIVES = [ComponentWithChildViewContainer, PizzaMsg, DirectiveWithViewContainer];
|
||||
const TEST_DIRECTIVES = [
|
||||
ComponentWithChildViewContainer,
|
||||
PizzaMsg,
|
||||
DirectiveWithViewContainer,
|
||||
ContentElementDialog
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [MdDialogModule],
|
||||
exports: TEST_DIRECTIVES,
|
||||
declarations: TEST_DIRECTIVES,
|
||||
entryComponents: [ComponentWithChildViewContainer, PizzaMsg],
|
||||
entryComponents: [ComponentWithChildViewContainer, PizzaMsg, ContentElementDialog],
|
||||
})
|
||||
class DialogTestModule { }
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
import {NgModule, ModuleWithProviders, Injector, ComponentRef, Injectable} from '@angular/core';
|
||||
import {
|
||||
Overlay,
|
||||
OverlayModule,
|
||||
PortalModule,
|
||||
OverlayRef,
|
||||
OverlayState,
|
||||
ComponentPortal,
|
||||
OVERLAY_PROVIDERS,
|
||||
ComponentType,
|
||||
A11yModule,
|
||||
InteractivityChecker,
|
||||
MdPlatform,
|
||||
DefaultStyleCompatibilityModeModule,
|
||||
} from '../core';
|
||||
import {Injector, ComponentRef, Injectable} from '@angular/core';
|
||||
|
||||
import {Overlay, OverlayRef, ComponentType, OverlayState, ComponentPortal} from '../core';
|
||||
import {extendObject} from '../core/util/object-extend';
|
||||
|
||||
import {DialogInjector} from './dialog-injector';
|
||||
import {MdDialogConfig} from './dialog-config';
|
||||
import {MdDialogRef} from './dialog-ref';
|
||||
import {DialogInjector} from './dialog-injector';
|
||||
import {MdDialogContainer} from './dialog-container';
|
||||
import {extendObject} from '../core/util/object-extend';
|
||||
export {MdDialogConfig} from './dialog-config';
|
||||
export {MdDialogRef} from './dialog-ref';
|
||||
|
||||
|
||||
// TODO(jelbourn): add support for opening with a TemplateRef
|
||||
// TODO(jelbourn): dialog content directives (e.g., md-dialog-header)
|
||||
// TODO(jelbourn): animations
|
||||
|
||||
|
||||
@@ -184,18 +170,3 @@ function _applyConfigDefaults(dialogConfig: MdDialogConfig): MdDialogConfig {
|
||||
return extendObject(new MdDialogConfig(), dialogConfig);
|
||||
}
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [OverlayModule, PortalModule, A11yModule, DefaultStyleCompatibilityModeModule],
|
||||
exports: [MdDialogContainer, DefaultStyleCompatibilityModeModule],
|
||||
declarations: [MdDialogContainer],
|
||||
entryComponents: [MdDialogContainer],
|
||||
})
|
||||
export class MdDialogModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: MdDialogModule,
|
||||
providers: [MdDialog, OVERLAY_PROVIDERS, InteractivityChecker, MdPlatform],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,59 @@
|
||||
import {NgModule, ModuleWithProviders} from '@angular/core';
|
||||
import {
|
||||
OverlayModule,
|
||||
PortalModule,
|
||||
OVERLAY_PROVIDERS,
|
||||
A11yModule,
|
||||
InteractivityChecker,
|
||||
MdPlatform,
|
||||
DefaultStyleCompatibilityModeModule,
|
||||
} from '../core';
|
||||
|
||||
import {MdDialog} from './dialog';
|
||||
import {MdDialogContainer} from './dialog-container';
|
||||
import {
|
||||
MdDialogClose,
|
||||
MdDialogContent,
|
||||
MdDialogTitle,
|
||||
MdDialogActions
|
||||
} from './dialog-content-directives';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
OverlayModule,
|
||||
PortalModule,
|
||||
A11yModule,
|
||||
DefaultStyleCompatibilityModeModule
|
||||
],
|
||||
exports: [
|
||||
MdDialogContainer,
|
||||
MdDialogClose,
|
||||
MdDialogTitle,
|
||||
MdDialogContent,
|
||||
MdDialogActions,
|
||||
DefaultStyleCompatibilityModeModule
|
||||
],
|
||||
declarations: [
|
||||
MdDialogContainer,
|
||||
MdDialogClose,
|
||||
MdDialogTitle,
|
||||
MdDialogActions,
|
||||
MdDialogContent
|
||||
],
|
||||
entryComponents: [MdDialogContainer],
|
||||
})
|
||||
export class MdDialogModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: MdDialogModule,
|
||||
providers: [MdDialog, OVERLAY_PROVIDERS, InteractivityChecker, MdPlatform],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export * from './dialog';
|
||||
export * from './dialog-container';
|
||||
export * from './dialog-content-directives';
|
||||
export * from './dialog-config';
|
||||
export * from './dialog-ref';
|
||||
|
||||
Reference in New Issue
Block a user