diff --git a/e2e/components/fullscreen/fullscreen.e2e.ts b/e2e/components/fullscreen/fullscreen.e2e.ts new file mode 100644 index 00000000000..63a3c65d06e --- /dev/null +++ b/e2e/components/fullscreen/fullscreen.e2e.ts @@ -0,0 +1,38 @@ +import {browser, by, element, Key, ProtractorBy} from 'protractor'; + +describe('fullscreen', () => { + beforeEach(() => browser.get('/fullscreen')); + + let overlayInBody = () => + browser.isElementPresent(by.css('body > .cdk-overlay-container') as ProtractorBy); + let overlayInFullscreen = () => + browser.isElementPresent(by.css('#fullscreenpane > .cdk-overlay-container') as ProtractorBy); + + it('should open a dialog inside a fullscreen element and move it to the document body', () => { + element(by.id('fullscreen')).click(); + element(by.id('dialog')).click(); + + overlayInFullscreen().then((isPresent: boolean) => { + expect(isPresent).toBe(true); + element(by.id('exitfullscreenindialog')).click(); + overlayInBody().then((isPresent: boolean) => { + expect(isPresent).toBe(true); + }); + }); + }); + + it('should open a dialog inside the document body and move it to a fullscreen element', () => { + element(by.id('dialog')).click(); + overlayInBody().then((isPresent: boolean) => { + expect(isPresent).toBe(true); + element(by.id('fullscreenindialog')).click(); + overlayInFullscreen().then((isPresent: boolean) => { + expect(isPresent).toBe(true); + element(by.id('exitfullscreenindialog')).click(); + overlayInBody().then((isPresent: boolean) => { + expect(isPresent).toBe(true); + }); + }); + }); + }); +}); diff --git a/src/demo-app/demo-app-module.ts b/src/demo-app/demo-app-module.ts index b0cccd84959..5f99a3a5422 100644 --- a/src/demo-app/demo-app-module.ts +++ b/src/demo-app/demo-app-module.ts @@ -4,7 +4,8 @@ import {HttpModule} from '@angular/http'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {DemoApp, Home} from './demo-app/demo-app'; import {RouterModule} from '@angular/router'; -import {MaterialModule} from '@angular/material'; +import {MaterialModule, OverlayContainer, + FullscreenOverlayContainer} from '@angular/material'; import {DEMO_APP_ROUTES} from './demo-app/routes'; import {ProgressBarDemo} from './progress-bar/progress-bar-demo'; import {JazzDialog, ContentElementDialog, DialogDemo} from './dialog/dialog-demo'; @@ -94,6 +95,9 @@ import {InputContainerDemo} from './input/input-container-demo'; FoggyTabContent, PlatformDemo ], + providers: [ + {provide: OverlayContainer, useClass: FullscreenOverlayContainer} + ], entryComponents: [ DemoApp, JazzDialog, diff --git a/src/demo-app/demo-app/demo-app.html b/src/demo-app/demo-app/demo-app.html index b48e4a1efab..e3af8f85b27 100644 --- a/src/demo-app/demo-app/demo-app.html +++ b/src/demo-app/demo-app/demo-app.html @@ -25,6 +25,9 @@

Angular Material 2 Demos

+ diff --git a/src/demo-app/demo-app/demo-app.scss b/src/demo-app/demo-app/demo-app.scss index 17338c88abf..62d5833daec 100644 --- a/src/demo-app/demo-app/demo-app.scss +++ b/src/demo-app/demo-app/demo-app.scss @@ -39,3 +39,9 @@ body { font-size: 20px; } } + +// stretch to screen size in fullscreen mode +.demo-content { + width: 100%; + height: 100%; +} diff --git a/src/demo-app/demo-app/demo-app.ts b/src/demo-app/demo-app/demo-app.ts index 57b8f96049c..b5528b7f4be 100644 --- a/src/demo-app/demo-app/demo-app.ts +++ b/src/demo-app/demo-app/demo-app.ts @@ -1,4 +1,4 @@ -import {Component, ViewEncapsulation} from '@angular/core'; +import {Component, ViewEncapsulation, ElementRef} from '@angular/core'; @Component({ @@ -52,4 +52,21 @@ export class DemoApp { {name: 'Tooltip', route: 'tooltip'}, {name: 'Platform', route: 'platform'} ]; + + constructor(private _element: ElementRef) { + + } + + toggleFullscreen() { + let elem = this._element.nativeElement.querySelector('.demo-content'); + if (elem.requestFullscreen) { + elem.requestFullscreen(); + } else if (elem.webkitRequestFullScreen) { + elem.webkitRequestFullScreen(); + } else if (elem.mozRequestFullScreen) { + elem.mozRequestFullScreen(); + } else if (elem.msRequestFullScreen) { + elem.msRequestFullScreen(); + } + } } diff --git a/src/e2e-app/e2e-app-module.ts b/src/e2e-app/e2e-app-module.ts index a54832b2b83..091698206b8 100644 --- a/src/e2e-app/e2e-app-module.ts +++ b/src/e2e-app/e2e-app-module.ts @@ -13,11 +13,11 @@ import {GridListE2E} from './grid-list/grid-list-e2e'; import {ListE2E} from './list/list-e2e'; import {ProgressBarE2E} from './progress-bar/progress-bar-e2e'; import {ProgressSpinnerE2E} from './progress-spinner/progress-spinner-e2e'; -import {MaterialModule} from '@angular/material'; +import {FullscreenE2E, TestDialog as TestDialogFullScreen} from './fullscreen/fullscreen-e2e'; +import {MaterialModule, OverlayContainer, FullscreenOverlayContainer} from '@angular/material'; import {E2E_APP_ROUTES} from './e2e-app/routes'; import {SlideToggleE2E} from './slide-toggle/slide-toggle-e2e'; - @NgModule({ imports: [ BrowserModule, @@ -39,12 +39,15 @@ import {SlideToggleE2E} from './slide-toggle/slide-toggle-e2e'; ListE2E, ProgressBarE2E, ProgressSpinnerE2E, - SlideToggleE2E + SlideToggleE2E, + FullscreenE2E, + TestDialogFullScreen ], bootstrap: [E2EApp], providers: [ - {provide: AnimationDriver, useValue: AnimationDriver.NOOP} + {provide: AnimationDriver, useValue: AnimationDriver.NOOP}, + {provide: OverlayContainer, useClass: FullscreenOverlayContainer} ], - entryComponents: [TestDialog] + entryComponents: [TestDialog, TestDialogFullScreen] }) export class E2eAppModule { } diff --git a/src/e2e-app/e2e-app/e2e-app.html b/src/e2e-app/e2e-app/e2e-app.html index 43a251d80ba..753f9be0f8b 100644 --- a/src/e2e-app/e2e-app/e2e-app.html +++ b/src/e2e-app/e2e-app/e2e-app.html @@ -13,6 +13,7 @@ Radios Slide Toggle Tabs + Fullscreen
diff --git a/src/e2e-app/e2e-app/routes.ts b/src/e2e-app/e2e-app/routes.ts index 94b69fc33c0..5d9d6e4687a 100644 --- a/src/e2e-app/e2e-app/routes.ts +++ b/src/e2e-app/e2e-app/routes.ts @@ -12,6 +12,7 @@ import {ListE2E} from '../list/list-e2e'; import {ProgressBarE2E} from '../progress-bar/progress-bar-e2e'; import {ProgressSpinnerE2E} from '../progress-spinner/progress-spinner-e2e'; import {SlideToggleE2E} from '../slide-toggle/slide-toggle-e2e'; +import {FullscreenE2E} from '../fullscreen/fullscreen-e2e'; export const E2E_APP_ROUTES: Routes = [ {path: '', component: Home}, @@ -26,5 +27,6 @@ export const E2E_APP_ROUTES: Routes = [ {path: 'list', component: ListE2E}, {path: 'progress-bar', component: ProgressBarE2E}, {path: 'progress-spinner', component: ProgressSpinnerE2E}, - {path: 'slide-toggle', component: SlideToggleE2E} + {path: 'slide-toggle', component: SlideToggleE2E}, + {path: 'fullscreen', component: FullscreenE2E} ]; diff --git a/src/e2e-app/fullscreen/fullscreen-e2e.html b/src/e2e-app/fullscreen/fullscreen-e2e.html new file mode 100644 index 00000000000..2ec7433bf79 --- /dev/null +++ b/src/e2e-app/fullscreen/fullscreen-e2e.html @@ -0,0 +1,5 @@ + +
+ + +
\ No newline at end of file diff --git a/src/e2e-app/fullscreen/fullscreen-e2e.ts b/src/e2e-app/fullscreen/fullscreen-e2e.ts new file mode 100644 index 00000000000..f2569eb6139 --- /dev/null +++ b/src/e2e-app/fullscreen/fullscreen-e2e.ts @@ -0,0 +1,61 @@ +import {Component, ElementRef, Output, EventEmitter} from '@angular/core'; +import {MdDialog, MdDialogRef} from '@angular/material'; + +@Component({ + selector: 'fullscreen-e2e', + moduleId: module.id, + templateUrl: 'fullscreen-e2e.html' +}) +export class FullscreenE2E { + dialogRef: MdDialogRef; + + constructor (private _element: ElementRef, private _dialog: MdDialog) { } + + openDialog() { + this.dialogRef = this._dialog.open(TestDialog); + this.dialogRef.componentInstance.fullscreen.subscribe(() => this.toggleFullScreen()); + this.dialogRef.componentInstance.exitfullscreen.subscribe(() => this.exitFullscreen()); + this.dialogRef.afterClosed().subscribe(() => { + this.dialogRef = null; + }); + } + + toggleFullScreen() { + let element = this._element.nativeElement.querySelector('#fullscreenpane'); + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.webkitRequestFullScreen) { + element.webkitRequestFullScreen(); + } else if ((element as any).mozRequestFullScreen) { + (element as any).mozRequestFullScreen(); + } else if ((element as any).msRequestFullScreen) { + (element as any).msRequestFullScreen(); + } + } + + exitFullscreen() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if ((document as any).mozExitFullScreen) { + (document as any).mozExitFullScreen(); + } else if ((document as any).msExitFullScreen) { + (document as any).msExitFullScreen(); + } + } +} + +@Component({ + selector: 'fullscreen-dialog-e2e-test', + template: ` +

Lorem ipsum dolor sit amet, consectetur adipisicing elit.

+ + + ` +}) +export class TestDialog { + constructor(public dialogRef: MdDialogRef) { } + @Output() fullscreen = new EventEmitter(); + @Output() exitfullscreen = new EventEmitter(); +} diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index 062c430e90f..00ca0edfa45 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -45,6 +45,7 @@ export {Platform as MdPlatform} from './platform/platform'; // Overlay export {Overlay, OVERLAY_PROVIDERS} from './overlay/overlay'; export {OverlayContainer} from './overlay/overlay-container'; +export {FullscreenOverlayContainer} from './overlay/fullscreen-overlay-container'; export {OverlayRef} from './overlay/overlay-ref'; export {OverlayState} from './overlay/overlay-state'; export { diff --git a/src/lib/core/overlay/fullscreen-overlay-container.ts b/src/lib/core/overlay/fullscreen-overlay-container.ts new file mode 100644 index 00000000000..7dfd7893bce --- /dev/null +++ b/src/lib/core/overlay/fullscreen-overlay-container.ts @@ -0,0 +1,53 @@ +import {Injectable} from '@angular/core'; +import {OverlayContainer} from './overlay-container'; + +/** + * The FullscreenOverlayContainer is the alternative to OverlayContainer + * that supports correct displaying of overlay elements in Fullscreen mode + * https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen + * It should be provided in the root component that way: + * providers: [ + * {provide: OverlayContainer, useClass: FullscreenOverlayContainer} + * ], + */ +@Injectable() +export class FullscreenOverlayContainer extends OverlayContainer { + protected _createContainer(): void { + super._createContainer(); + this._adjustParentForFullscreenChange(); + this._addFullscreenChangeListener(() => this._adjustParentForFullscreenChange()); + } + + private _adjustParentForFullscreenChange(): void { + if (!this._containerElement) { + return; + } + let fullscreenElement = this.getFullscreenElement(); + let parent = fullscreenElement || document.body; + parent.appendChild(this._containerElement); + } + + private _addFullscreenChangeListener(fn: () => void) { + if (document.fullscreenEnabled) { + document.addEventListener('fullscreenchange', fn); + } else if (document.webkitFullscreenEnabled) { + document.addEventListener('webkitfullscreenchange', fn); + } else if ((document as any).mozFullScreenEnabled) { + document.addEventListener('mozfullscreenchange', fn); + } else if ((document as any).msFullscreenEnabled) { + document.addEventListener('MSFullscreenChange', fn); + } + } + + /** + * When the page is put into fullscreen mode, a specific element is specified. + * Only that element and its children are visible when in fullscreen mode. + */ + getFullscreenElement(): Element { + return document.fullscreenElement || + document.webkitFullscreenElement || + (document as any).mozFullScreenElement || + (document as any).msFullscreenElement || + null; + } +} diff --git a/src/lib/core/overlay/overlay-container.ts b/src/lib/core/overlay/overlay-container.ts index c05147261ed..c227961a23c 100644 --- a/src/lib/core/overlay/overlay-container.ts +++ b/src/lib/core/overlay/overlay-container.ts @@ -1,9 +1,12 @@ +import {Injectable} from '@angular/core'; + /** * The OverlayContainer is the container in which all overlays will load. * It should be provided in the root component to ensure it is properly shared. */ +@Injectable() export class OverlayContainer { - private _containerElement: HTMLElement; + protected _containerElement: HTMLElement; /** * This method returns the overlay container element. It will lazily @@ -20,7 +23,7 @@ export class OverlayContainer { * Create the overlay container element, which is simply a div * with the 'cdk-overlay-container' class on the document body. */ - private _createContainer(): void { + protected _createContainer(): void { let container = document.createElement('div'); container.classList.add('cdk-overlay-container'); document.body.appendChild(container);