Skip to content

Commit d8fc95c

Browse files
committed
feat(dialog): add configurable width, height and position
* Adds the ability to set a dialog's `width` and `height`. * Adds the ability to set a dialog's position. If only the position in one axis is overridden, the other axis will stay centered. * Fixes the `GlobalPositionStrategy` adding an unnecessary `0px` transform. * Makes the dialog scrollable. * Adds more options to the dialog demo so it's easier to test them out. Fixes #1698.
1 parent 009046f commit d8fc95c

File tree

9 files changed

+172
-23
lines changed

9 files changed

+172
-23
lines changed

src/demo-app/dialog/dialog-demo.html

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
<h1>Dialog demo</h1>
22

3-
<button (click)="open()" [disabled]="dialogRef">Open dialog</button>
3+
<button md-raised-button color="primary" (click)="open()" [disabled]="dialogRef">Open dialog</button>
44

5-
<p>
6-
Last close result: {{lastCloseResult}}
7-
</p>
5+
<md-card>
6+
<md-card-content>
7+
<h2>Dialog dimensions</h2>
8+
9+
<p>
10+
<md-input [(ngModel)]="config.width" placeholder="Width"></md-input>
11+
<md-input [(ngModel)]="config.height" placeholder="Height"></md-input>
12+
</p>
13+
14+
<h2>Dialog position</h2>
15+
16+
<p>
17+
<md-input [(ngModel)]="config.position.top" (change)="config.position.bottom = ''" placeholder="Top"></md-input>
18+
<md-input [(ngModel)]="config.position.bottom" (change)="config.position.top = ''" placeholder="Bottom"></md-input>
19+
</p>
20+
21+
<p>
22+
<md-input [(ngModel)]="config.position.left" (change)="config.position.right = ''" placeholder="Left"></md-input>
23+
<md-input [(ngModel)]="config.position.right" (change)="config.position.left = ''" placeholder="Right"></md-input>
24+
</p>
25+
26+
<h2>Other options</h2>
27+
28+
<md-checkbox [(ngModel)]="config.disableClose">Disable close</md-checkbox>
29+
</md-card-content>
30+
</md-card>
31+
32+
<p>Last close result: {{lastCloseResult}}</p>

src/demo-app/dialog/dialog-demo.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
.demo-dialog {
22
color: rebeccapurple;
33
}
4+
5+
md-card {
6+
max-width: 350px;
7+
margin: 20px 0;
8+
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Component} from '@angular/core';
2-
import {MdDialog, MdDialogRef} from '@angular/material';
2+
import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material';
33

44
@Component({
55
moduleId: module.id,
@@ -10,11 +10,22 @@ import {MdDialog, MdDialogRef} from '@angular/material';
1010
export class DialogDemo {
1111
dialogRef: MdDialogRef<JazzDialog>;
1212
lastCloseResult: string;
13+
config: MdDialogConfig = {
14+
disableClose: false,
15+
width: '',
16+
height: '',
17+
position: {
18+
top: '',
19+
bottom: '',
20+
left: '',
21+
right: ''
22+
}
23+
};
1324

1425
constructor(public dialog: MdDialog) { }
1526

1627
open() {
17-
this.dialogRef = this.dialog.open(JazzDialog);
28+
this.dialogRef = this.dialog.open(JazzDialog, this.config);
1829

1930
this.dialogRef.afterClosed().subscribe(result => {
2031
this.lastCloseResult = result;

src/lib/core/overlay/position/global-position-strategy.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,40 @@ describe('GlobalPositonStrategy', () => {
100100

101101
expect(element.style.position).toBe('fixed');
102102
}));
103+
104+
it('should set the element width', fakeAsync(() => {
105+
strategy.width('100px').apply(element);
106+
107+
flushMicrotasks();
108+
109+
expect(element.style.width).toBe('100px');
110+
}));
111+
112+
it('should set the element height', fakeAsync(() => {
113+
strategy.height('100px').apply(element);
114+
115+
flushMicrotasks();
116+
117+
expect(element.style.height).toBe('100px');
118+
}));
119+
120+
it('should reset the horizontal position and offset when the width is 100%', fakeAsync(() => {
121+
strategy.centerHorizontally().width('100%').apply(element);
122+
123+
flushMicrotasks();
124+
125+
expect(element.style.left).toBe('0px');
126+
expect(element.style.transform).toBe('');
127+
}));
128+
129+
it('should reset the vertical position and offset when the height is 100%', fakeAsync(() => {
130+
strategy.centerVertically().height('100%').apply(element);
131+
132+
flushMicrotasks();
133+
134+
expect(element.style.top).toBe('0px');
135+
expect(element.style.transform).toBe('');
136+
}));
103137
});
104138

105139
function fakeAsyncTest(fn: () => void) {

src/lib/core/overlay/position/global-position-strategy.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export class GlobalPositionStrategy implements PositionStrategy {
1212
private _bottom: string = '';
1313
private _left: string = '';
1414
private _right: string = '';
15+
private _width: string = '';
16+
private _height: string = '';
1517

1618
/** Array of individual applications of translateX(). Currently only for centering. */
1719
private _translateX: string[] = [];
@@ -63,25 +65,61 @@ export class GlobalPositionStrategy implements PositionStrategy {
6365
return this;
6466
}
6567

68+
/** Sets the overlay width and clears any previously set width. */
69+
width(value: string) {
70+
this._width = value;
71+
72+
if (value === '100%') {
73+
// When the width is 100%, we should reset the `left` and the offset,
74+
// in order to ensure that the element is flush against the viewport edge.
75+
this.left('0px');
76+
}
77+
78+
return this;
79+
}
80+
81+
/** Sets the overlay height and clears any previously set height. */
82+
height(value: string) {
83+
this._height = value;
84+
85+
if (value === '100%') {
86+
// When the height is 100%, we should reset the `top` and the offset,
87+
// in order to ensure that the element is flush against the viewport edge.
88+
this.top('0px');
89+
}
90+
91+
return this;
92+
}
93+
6694
/**
6795
* Centers the overlay horizontally with an optional offset.
6896
* Clears any previously set horizontal position.
6997
*/
70-
centerHorizontally(offset = '0px') {
98+
centerHorizontally(offset?: string) {
7199
this._left = '50%';
72100
this._right = '';
73-
this._translateX = ['-50%', offset];
101+
this._translateX = ['-50%'];
102+
103+
if (offset) {
104+
this._translateX.push(offset);
105+
}
106+
74107
return this;
75108
}
76109

77110
/**
78111
* Centers the overlay vertically with an optional offset.
79112
* Clears any previously set vertical position.
80113
*/
81-
centerVertically(offset = '0px') {
114+
centerVertically(offset?: string) {
82115
this._top = '50%';
83116
this._bottom = '';
84-
this._translateY = ['-50%', offset];
117+
this._translateY = ['-50%'];
118+
119+
if (offset) {
120+
this._translateY.push(offset);
121+
}
122+
85123
return this;
86124
}
87125

@@ -95,13 +133,15 @@ export class GlobalPositionStrategy implements PositionStrategy {
95133
element.style.left = this._left;
96134
element.style.bottom = this._bottom;
97135
element.style.right = this._right;
136+
element.style.width = this._width;
137+
element.style.height = this._height;
98138

99139
// TODO(jelbourn): we don't want to always overwrite the transform property here,
100140
// because it will need to be used for animations.
101-
let tranlateX = this._reduceTranslateValues('translateX', this._translateX);
141+
let translateX = this._reduceTranslateValues('translateX', this._translateX);
102142
let translateY = this._reduceTranslateValues('translateY', this._translateY);
103143

104-
applyCssTransform(element, `${tranlateX} ${translateY}`);
144+
applyCssTransform(element, `${translateX} ${translateY}`);
105145

106146
return Promise.resolve(null);
107147
}

src/lib/dialog/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ MdDialog is a service, which opens dialogs components in the view.
1212

1313
| Key | Description |
1414
| --- | --- |
15-
| `role: DialogRole = 'dialog'` | The ARIA role of the dialog element. Possible values are `dialog` and `alertdialog`. Defaults to `dialog`. |
16-
| `disableClose: boolean = false` | Whether to prevent the user from closing a dialog by clicking on the backdrop or pressing escape. Defaults to `false`. |
15+
| `role: DialogRole = 'dialog'` | The ARIA role of the dialog element. Possible values are `dialog` and `alertdialog`. Optional. |
16+
| `disableClose: boolean = false` | Whether to prevent the user from closing a dialog by clicking on the backdrop or pressing escape. Optional. |
17+
| `width: string = ''` | Width of the dialog. Takes any valid CSS value. Optional. |
18+
| `height: string = ''` | Height of the dialog. Takes any valid CSS value. Optional. |
19+
| `position: { top?: string, bottom?: string, left?: string, right?: string }` | Position of the dialog that overrides the default centering in it's axis. Optional. |
1720
| `viewContainerRef: ViewContainerRef` | The view container ref to attach the dialog to. Optional. |
1821

1922
## MdDialogRef

src/lib/dialog/dialog-config.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import {ViewContainerRef} from '@angular/core';
22

33
/** Valid ARIA roles for a dialog element. */
4-
export type DialogRole = 'dialog' | 'alertdialog'
4+
export type DialogRole = 'dialog' | 'alertdialog';
55

6+
/** Possible overrides for a dialog's position. */
7+
export interface DialogPosition {
8+
top?: string;
9+
bottom?: string;
10+
left?: string;
11+
right?: string;
12+
};
613

714

815
/**
@@ -17,5 +24,14 @@ export class MdDialogConfig {
1724
/** Whether the user can use escape or clicking outside to close a modal. */
1825
disableClose?: boolean = false;
1926

20-
// TODO(jelbourn): add configuration for size, lifecycle hooks, ARIA labelling.
27+
/** Width of the dialog. */
28+
width?: string = '';
29+
30+
/** Height of the dialog. */
31+
height?: string = '';
32+
33+
/** Position overrides. */
34+
position?: DialogPosition;
35+
36+
// TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling.
2137
}

src/lib/dialog/dialog-container.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ md-dialog-container {
77
@include md-elevation(24);
88

99
display: block;
10-
overflow: hidden;
1110
padding: $md-dialog-padding;
11+
width: 100%;
12+
height: 100%;
13+
box-sizing: border-box;
14+
overflow: auto;
1215
}

src/lib/dialog/dialog.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ export {MdDialogRef} from './dialog-ref';
2222

2323
// TODO(jelbourn): add support for opening with a TemplateRef
2424
// TODO(jelbourn): add `closeAll` method
25-
// TODO(jelbourn): default dialog config
26-
// TODO(jelbourn): escape key closes dialog
2725
// TODO(jelbourn): dialog content directives (e.g., md-dialog-header)
2826
// TODO(jelbourn): animations
2927

@@ -119,12 +117,26 @@ export class MdDialog {
119117
*/
120118
private _getOverlayState(dialogConfig: MdDialogConfig): OverlayState {
121119
let state = new OverlayState();
120+
let strategy = this._overlay.position().global();
121+
let position = dialogConfig.position;
122122

123123
state.hasBackdrop = true;
124-
state.positionStrategy = this._overlay.position()
125-
.global()
126-
.centerHorizontally()
127-
.centerVertically();
124+
state.positionStrategy = strategy;
125+
126+
// TODO(crisbeto): replace with `horizontal` to support RTL?
127+
if (position && (position.left || position.right)) {
128+
position.left ? strategy.left(position.left) : strategy.right(position.right);
129+
} else {
130+
strategy.centerHorizontally();
131+
}
132+
133+
if (position && (position.top || position.bottom)) {
134+
position.top ? strategy.top(position.top) : strategy.bottom(position.bottom);
135+
} else {
136+
strategy.centerVertically();
137+
}
138+
139+
strategy.width(dialogConfig.width).height(dialogConfig.height);
128140

129141
return state;
130142
}

0 commit comments

Comments
 (0)