Skip to content

Commit

Permalink
feat(dialog): add configurable width, height and position (#1848)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
crisbeto authored and tinayuangao committed Nov 30, 2016
1 parent 5ac29dd commit bc6cf6e
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 23 deletions.
33 changes: 29 additions & 4 deletions src/demo-app/dialog/dialog-demo.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
<h1>Dialog demo</h1>

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

<p>
Last close result: {{lastCloseResult}}
</p>
<md-card class="demo-dialog-card">
<md-card-content>
<h2>Dialog dimensions</h2>

<p>
<md-input [(ngModel)]="config.width" placeholder="Width"></md-input>
<md-input [(ngModel)]="config.height" placeholder="Height"></md-input>
</p>

<h2>Dialog position</h2>

<p>
<md-input [(ngModel)]="config.position.top" (change)="config.position.bottom = ''" placeholder="Top"></md-input>
<md-input [(ngModel)]="config.position.bottom" (change)="config.position.top = ''" placeholder="Bottom"></md-input>
</p>

<p>
<md-input [(ngModel)]="config.position.left" (change)="config.position.right = ''" placeholder="Left"></md-input>
<md-input [(ngModel)]="config.position.right" (change)="config.position.left = ''" placeholder="Right"></md-input>
</p>

<h2>Other options</h2>

<md-checkbox [(ngModel)]="config.disableClose">Disable close</md-checkbox>
</md-card-content>
</md-card>

<p>Last close result: {{lastCloseResult}}</p>
5 changes: 5 additions & 0 deletions src/demo-app/dialog/dialog-demo.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.demo-dialog {
color: rebeccapurple;
}

.demo-dialog-card {
max-width: 350px;
margin: 20px 0;
}
15 changes: 13 additions & 2 deletions src/demo-app/dialog/dialog-demo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {MdDialog, MdDialogRef} from '@angular/material';
import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material';

@Component({
moduleId: module.id,
Expand All @@ -10,11 +10,22 @@ import {MdDialog, MdDialogRef} from '@angular/material';
export class DialogDemo {
dialogRef: MdDialogRef<JazzDialog>;
lastCloseResult: string;
config: MdDialogConfig = {
disableClose: false,
width: '',
height: '',
position: {
top: '',
bottom: '',
left: '',
right: ''
}
};

constructor(public dialog: MdDialog) { }

open() {
this.dialogRef = this.dialog.open(JazzDialog);
this.dialogRef = this.dialog.open(JazzDialog, this.config);

this.dialogRef.afterClosed().subscribe(result => {
this.lastCloseResult = result;
Expand Down
34 changes: 34 additions & 0 deletions src/lib/core/overlay/position/global-position-strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,40 @@ describe('GlobalPositonStrategy', () => {

expect(element.style.position).toBe('fixed');
}));

it('should set the element width', fakeAsync(() => {
strategy.width('100px').apply(element);

flushMicrotasks();

expect(element.style.width).toBe('100px');
}));

it('should set the element height', fakeAsync(() => {
strategy.height('100px').apply(element);

flushMicrotasks();

expect(element.style.height).toBe('100px');
}));

it('should reset the horizontal position and offset when the width is 100%', fakeAsync(() => {
strategy.centerHorizontally().width('100%').apply(element);

flushMicrotasks();

expect(element.style.left).toBe('0px');
expect(element.style.transform).toBe('');
}));

it('should reset the vertical position and offset when the height is 100%', fakeAsync(() => {
strategy.centerVertically().height('100%').apply(element);

flushMicrotasks();

expect(element.style.top).toBe('0px');
expect(element.style.transform).toBe('');
}));
});

function fakeAsyncTest(fn: () => void) {
Expand Down
52 changes: 46 additions & 6 deletions src/lib/core/overlay/position/global-position-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export class GlobalPositionStrategy implements PositionStrategy {
private _bottom: string = '';
private _left: string = '';
private _right: string = '';
private _width: string = '';
private _height: string = '';

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

/** Sets the overlay width and clears any previously set width. */
width(value: string) {
this._width = value;

// When the width is 100%, we should reset the `left` and the offset,
// in order to ensure that the element is flush against the viewport edge.
if (value === '100%') {
this.left('0px');
}

return this;
}

/** Sets the overlay height and clears any previously set height. */
height(value: string) {
this._height = value;

// When the height is 100%, we should reset the `top` and the offset,
// in order to ensure that the element is flush against the viewport edge.
if (value === '100%') {
this.top('0px');
}

return this;
}

/**
* Centers the overlay horizontally with an optional offset.
* Clears any previously set horizontal position.
*/
centerHorizontally(offset = '0px') {
centerHorizontally(offset = '') {
this._left = '50%';
this._right = '';
this._translateX = ['-50%', offset];
this._translateX = ['-50%'];

if (offset) {
this._translateX.push(offset);
}

return this;
}

/**
* Centers the overlay vertically with an optional offset.
* Clears any previously set vertical position.
*/
centerVertically(offset = '0px') {
centerVertically(offset = '') {
this._top = '50%';
this._bottom = '';
this._translateY = ['-50%', offset];
this._translateY = ['-50%'];

if (offset) {
this._translateY.push(offset);
}

return this;
}

Expand All @@ -95,13 +133,15 @@ export class GlobalPositionStrategy implements PositionStrategy {
element.style.left = this._left;
element.style.bottom = this._bottom;
element.style.right = this._right;
element.style.width = this._width;
element.style.height = this._height;

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

applyCssTransform(element, `${tranlateX} ${translateY}`);
applyCssTransform(element, `${translateX} ${translateY}`);

return Promise.resolve(null);
}
Expand Down
7 changes: 5 additions & 2 deletions src/lib/dialog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ MdDialog is a service, which opens dialogs components in the view.

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

## MdDialogRef
Expand Down
20 changes: 18 additions & 2 deletions src/lib/dialog/dialog-config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import {ViewContainerRef} from '@angular/core';

/** Valid ARIA roles for a dialog element. */
export type DialogRole = 'dialog' | 'alertdialog'
export type DialogRole = 'dialog' | 'alertdialog';

/** Possible overrides for a dialog's position. */
export interface DialogPosition {
top?: string;
bottom?: string;
left?: string;
right?: string;
};


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

// TODO(jelbourn): add configuration for size, lifecycle hooks, ARIA labelling.
/** Width of the dialog. */
width?: string = '';

/** Height of the dialog. */
height?: string = '';

/** Position overrides. */
position?: DialogPosition;

// TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling.
}
7 changes: 6 additions & 1 deletion src/lib/dialog/dialog-container.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ md-dialog-container {
@include md-elevation(24);

display: block;
overflow: hidden;
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%;
}
80 changes: 80 additions & 0 deletions src/lib/dialog/dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,86 @@ describe('MdDialog', () => {
expect(overlayContainerElement.querySelector('md-dialog-container')).toBeFalsy();
});

it('should should override the width of the overlay pane', () => {
dialog.open(PizzaMsg, {
width: '500px'
});

viewContainerFixture.detectChanges();

let overlayPane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement;

expect(overlayPane.style.width).toBe('500px');
});

it('should should override the height of the overlay pane', () => {
dialog.open(PizzaMsg, {
height: '100px'
});

viewContainerFixture.detectChanges();

let overlayPane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement;

expect(overlayPane.style.height).toBe('100px');
});

it('should should override the top offset of the overlay pane', () => {
dialog.open(PizzaMsg, {
position: {
top: '100px'
}
});

viewContainerFixture.detectChanges();

let overlayPane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement;

expect(overlayPane.style.top).toBe('100px');
});

it('should should override the bottom offset of the overlay pane', () => {
dialog.open(PizzaMsg, {
position: {
bottom: '200px'
}
});

viewContainerFixture.detectChanges();

let overlayPane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement;

expect(overlayPane.style.bottom).toBe('200px');
});

it('should should override the left offset of the overlay pane', () => {
dialog.open(PizzaMsg, {
position: {
left: '250px'
}
});

viewContainerFixture.detectChanges();

let overlayPane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement;

expect(overlayPane.style.left).toBe('250px');
});

it('should should override the right offset of the overlay pane', () => {
dialog.open(PizzaMsg, {
position: {
right: '125px'
}
});

viewContainerFixture.detectChanges();

let overlayPane = overlayContainerElement.querySelector('.md-overlay-pane') as HTMLElement;

expect(overlayPane.style.right).toBe('125px');
});

describe('disableClose option', () => {
it('should prevent closing via clicks on the backdrop', () => {
dialog.open(PizzaMsg, {
Expand Down
23 changes: 17 additions & 6 deletions src/lib/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export {MdDialogRef} from './dialog-ref';

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

Expand Down Expand Up @@ -119,12 +117,25 @@ export class MdDialog {
*/
private _getOverlayState(dialogConfig: MdDialogConfig): OverlayState {
let state = new OverlayState();
let strategy = this._overlay.position().global();
let position = dialogConfig.position;

state.hasBackdrop = true;
state.positionStrategy = this._overlay.position()
.global()
.centerHorizontally()
.centerVertically();
state.positionStrategy = strategy;

if (position && (position.left || position.right)) {
position.left ? strategy.left(position.left) : strategy.right(position.right);
} else {
strategy.centerHorizontally();
}

if (position && (position.top || position.bottom)) {
position.top ? strategy.top(position.top) : strategy.bottom(position.bottom);
} else {
strategy.centerVertically();
}

strategy.width(dialogConfig.width).height(dialogConfig.height);

return state;
}
Expand Down

0 comments on commit bc6cf6e

Please sign in to comment.