Skip to content

Commit 0640302

Browse files
quanteriontinayuangao
authored andcommitted
feat(overlay): add fullscreen-enabled overlay class (#1949)
* feat(overlay): add fullscreen overlay class * fix lint error * move fs container to a new file and rename it * add e2e tests * fix tests * rebased and fix comments * fix typings * fix e2e tests * address comments * fix tests
1 parent f9dd34f commit 0640302

File tree

13 files changed

+207
-10
lines changed

13 files changed

+207
-10
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {browser, by, element, Key, ProtractorBy} from 'protractor';
2+
3+
describe('fullscreen', () => {
4+
beforeEach(() => browser.get('/fullscreen'));
5+
6+
let overlayInBody = () =>
7+
browser.isElementPresent(by.css('body > .cdk-overlay-container') as ProtractorBy);
8+
let overlayInFullscreen = () =>
9+
browser.isElementPresent(by.css('#fullscreenpane > .cdk-overlay-container') as ProtractorBy);
10+
11+
it('should open a dialog inside a fullscreen element and move it to the document body', () => {
12+
element(by.id('fullscreen')).click();
13+
element(by.id('dialog')).click();
14+
15+
overlayInFullscreen().then((isPresent: boolean) => {
16+
expect(isPresent).toBe(true);
17+
element(by.id('exitfullscreenindialog')).click();
18+
overlayInBody().then((isPresent: boolean) => {
19+
expect(isPresent).toBe(true);
20+
});
21+
});
22+
});
23+
24+
it('should open a dialog inside the document body and move it to a fullscreen element', () => {
25+
element(by.id('dialog')).click();
26+
overlayInBody().then((isPresent: boolean) => {
27+
expect(isPresent).toBe(true);
28+
element(by.id('fullscreenindialog')).click();
29+
overlayInFullscreen().then((isPresent: boolean) => {
30+
expect(isPresent).toBe(true);
31+
element(by.id('exitfullscreenindialog')).click();
32+
overlayInBody().then((isPresent: boolean) => {
33+
expect(isPresent).toBe(true);
34+
});
35+
});
36+
});
37+
});
38+
});

src/demo-app/demo-app-module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {HttpModule} from '@angular/http';
44
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
55
import {DemoApp, Home} from './demo-app/demo-app';
66
import {RouterModule} from '@angular/router';
7-
import {MaterialModule} from '@angular/material';
7+
import {MaterialModule, OverlayContainer,
8+
FullscreenOverlayContainer} from '@angular/material';
89
import {DEMO_APP_ROUTES} from './demo-app/routes';
910
import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
1011
import {JazzDialog, ContentElementDialog, DialogDemo} from './dialog/dialog-demo';
@@ -94,6 +95,9 @@ import {InputContainerDemo} from './input/input-container-demo';
9495
FoggyTabContent,
9596
PlatformDemo
9697
],
98+
providers: [
99+
{provide: OverlayContainer, useClass: FullscreenOverlayContainer}
100+
],
97101
entryComponents: [
98102
DemoApp,
99103
JazzDialog,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
</button>
2626
<div class="demo-toolbar">
2727
<h1>Angular Material 2 Demos</h1>
28+
<button md-button (click)="toggleFullscreen()" title="Toggle fullscreen">
29+
Fullscreen
30+
</button>
2831
<button md-button (click)="root.dir = (root.dir == 'rtl' ? 'ltr' : 'rtl')" title="Toggle between RTL and LTR">
2932
{{root.dir.toUpperCase()}}
3033
</button>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,9 @@ body {
3939
font-size: 20px;
4040
}
4141
}
42+
43+
// stretch to screen size in fullscreen mode
44+
.demo-content {
45+
width: 100%;
46+
height: 100%;
47+
}

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, ViewEncapsulation} from '@angular/core';
1+
import {Component, ViewEncapsulation, ElementRef} from '@angular/core';
22

33

44
@Component({
@@ -52,4 +52,21 @@ export class DemoApp {
5252
{name: 'Tooltip', route: 'tooltip'},
5353
{name: 'Platform', route: 'platform'}
5454
];
55+
56+
constructor(private _element: ElementRef) {
57+
58+
}
59+
60+
toggleFullscreen() {
61+
let elem = this._element.nativeElement.querySelector('.demo-content');
62+
if (elem.requestFullscreen) {
63+
elem.requestFullscreen();
64+
} else if (elem.webkitRequestFullScreen) {
65+
elem.webkitRequestFullScreen();
66+
} else if (elem.mozRequestFullScreen) {
67+
elem.mozRequestFullScreen();
68+
} else if (elem.msRequestFullScreen) {
69+
elem.msRequestFullScreen();
70+
}
71+
}
5572
}

src/e2e-app/e2e-app-module.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import {GridListE2E} from './grid-list/grid-list-e2e';
1313
import {ListE2E} from './list/list-e2e';
1414
import {ProgressBarE2E} from './progress-bar/progress-bar-e2e';
1515
import {ProgressSpinnerE2E} from './progress-spinner/progress-spinner-e2e';
16-
import {MaterialModule} from '@angular/material';
16+
import {FullscreenE2E, TestDialog as TestDialogFullScreen} from './fullscreen/fullscreen-e2e';
17+
import {MaterialModule, OverlayContainer, FullscreenOverlayContainer} from '@angular/material';
1718
import {E2E_APP_ROUTES} from './e2e-app/routes';
1819
import {SlideToggleE2E} from './slide-toggle/slide-toggle-e2e';
1920

20-
2121
@NgModule({
2222
imports: [
2323
BrowserModule,
@@ -39,12 +39,15 @@ import {SlideToggleE2E} from './slide-toggle/slide-toggle-e2e';
3939
ListE2E,
4040
ProgressBarE2E,
4141
ProgressSpinnerE2E,
42-
SlideToggleE2E
42+
SlideToggleE2E,
43+
FullscreenE2E,
44+
TestDialogFullScreen
4345
],
4446
bootstrap: [E2EApp],
4547
providers: [
46-
{provide: AnimationDriver, useValue: AnimationDriver.NOOP}
48+
{provide: AnimationDriver, useValue: AnimationDriver.NOOP},
49+
{provide: OverlayContainer, useClass: FullscreenOverlayContainer}
4750
],
48-
entryComponents: [TestDialog]
51+
entryComponents: [TestDialog, TestDialogFullScreen]
4952
})
5053
export class E2eAppModule { }

src/e2e-app/e2e-app/e2e-app.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<a md-list-item [routerLink]="['radio']">Radios</a>
1414
<a md-list-item [routerLink]="['slide-toggle']">Slide Toggle</a>
1515
<a md-list-item [routerLink]="['tabs']">Tabs</a>
16+
<a md-list-item [routerLink]="['fullscreen']">Fullscreen</a>
1617
</md-nav-list>
1718

1819
<main>

src/e2e-app/e2e-app/routes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {ListE2E} from '../list/list-e2e';
1212
import {ProgressBarE2E} from '../progress-bar/progress-bar-e2e';
1313
import {ProgressSpinnerE2E} from '../progress-spinner/progress-spinner-e2e';
1414
import {SlideToggleE2E} from '../slide-toggle/slide-toggle-e2e';
15+
import {FullscreenE2E} from '../fullscreen/fullscreen-e2e';
1516

1617
export const E2E_APP_ROUTES: Routes = [
1718
{path: '', component: Home},
@@ -26,5 +27,6 @@ export const E2E_APP_ROUTES: Routes = [
2627
{path: 'list', component: ListE2E},
2728
{path: 'progress-bar', component: ProgressBarE2E},
2829
{path: 'progress-spinner', component: ProgressSpinnerE2E},
29-
{path: 'slide-toggle', component: SlideToggleE2E}
30+
{path: 'slide-toggle', component: SlideToggleE2E},
31+
{path: 'fullscreen', component: FullscreenE2E}
3032
];
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<button id="fullscreen" (click)="toggleFullScreen()">FULLSCREEN</button>
2+
<div id="fullscreenpane" style="width: 100%; height: 100%">
3+
<button id="dialog" (click)="openDialog()">DIALOG</button>
4+
<button id="exitfullscreen" (click)="exitFullscreen()">EXIT FULLSCREEN</button>
5+
</div>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {Component, ElementRef, Output, EventEmitter} from '@angular/core';
2+
import {MdDialog, MdDialogRef} from '@angular/material';
3+
4+
@Component({
5+
selector: 'fullscreen-e2e',
6+
moduleId: module.id,
7+
templateUrl: 'fullscreen-e2e.html'
8+
})
9+
export class FullscreenE2E {
10+
dialogRef: MdDialogRef<TestDialog>;
11+
12+
constructor (private _element: ElementRef, private _dialog: MdDialog) { }
13+
14+
openDialog() {
15+
this.dialogRef = this._dialog.open(TestDialog);
16+
this.dialogRef.componentInstance.fullscreen.subscribe(() => this.toggleFullScreen());
17+
this.dialogRef.componentInstance.exitfullscreen.subscribe(() => this.exitFullscreen());
18+
this.dialogRef.afterClosed().subscribe(() => {
19+
this.dialogRef = null;
20+
});
21+
}
22+
23+
toggleFullScreen() {
24+
let element = this._element.nativeElement.querySelector('#fullscreenpane');
25+
if (element.requestFullscreen) {
26+
element.requestFullscreen();
27+
} else if (element.webkitRequestFullScreen) {
28+
element.webkitRequestFullScreen();
29+
} else if ((element as any).mozRequestFullScreen) {
30+
(element as any).mozRequestFullScreen();
31+
} else if ((element as any).msRequestFullScreen) {
32+
(element as any).msRequestFullScreen();
33+
}
34+
}
35+
36+
exitFullscreen() {
37+
if (document.exitFullscreen) {
38+
document.exitFullscreen();
39+
} else if (document.webkitExitFullscreen) {
40+
document.webkitExitFullscreen();
41+
} else if ((document as any).mozExitFullScreen) {
42+
(document as any).mozExitFullScreen();
43+
} else if ((document as any).msExitFullScreen) {
44+
(document as any).msExitFullScreen();
45+
}
46+
}
47+
}
48+
49+
@Component({
50+
selector: 'fullscreen-dialog-e2e-test',
51+
template: `
52+
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
53+
<button id="fullscreenindialog" (click)="fullscreen.emit()">FULLSCREEN</button>
54+
<button id="exitfullscreenindialog" (click)="exitfullscreen.emit()">EXIT FULLSCREEN</button>
55+
<button type="button" (click)="dialogRef.close()" id="close">CLOSE</button>`
56+
})
57+
export class TestDialog {
58+
constructor(public dialogRef: MdDialogRef<TestDialog>) { }
59+
@Output() fullscreen = new EventEmitter<void>();
60+
@Output() exitfullscreen = new EventEmitter<void>();
61+
}

src/lib/core/core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export {Platform as MdPlatform} from './platform/platform';
4545
// Overlay
4646
export {Overlay, OVERLAY_PROVIDERS} from './overlay/overlay';
4747
export {OverlayContainer} from './overlay/overlay-container';
48+
export {FullscreenOverlayContainer} from './overlay/fullscreen-overlay-container';
4849
export {OverlayRef} from './overlay/overlay-ref';
4950
export {OverlayState} from './overlay/overlay-state';
5051
export {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {Injectable} from '@angular/core';
2+
import {OverlayContainer} from './overlay-container';
3+
4+
/**
5+
* The FullscreenOverlayContainer is the alternative to OverlayContainer
6+
* that supports correct displaying of overlay elements in Fullscreen mode
7+
* https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullScreen
8+
* It should be provided in the root component that way:
9+
* providers: [
10+
* {provide: OverlayContainer, useClass: FullscreenOverlayContainer}
11+
* ],
12+
*/
13+
@Injectable()
14+
export class FullscreenOverlayContainer extends OverlayContainer {
15+
protected _createContainer(): void {
16+
super._createContainer();
17+
this._adjustParentForFullscreenChange();
18+
this._addFullscreenChangeListener(() => this._adjustParentForFullscreenChange());
19+
}
20+
21+
private _adjustParentForFullscreenChange(): void {
22+
if (!this._containerElement) {
23+
return;
24+
}
25+
let fullscreenElement = this.getFullscreenElement();
26+
let parent = fullscreenElement || document.body;
27+
parent.appendChild(this._containerElement);
28+
}
29+
30+
private _addFullscreenChangeListener(fn: () => void) {
31+
if (document.fullscreenEnabled) {
32+
document.addEventListener('fullscreenchange', fn);
33+
} else if (document.webkitFullscreenEnabled) {
34+
document.addEventListener('webkitfullscreenchange', fn);
35+
} else if ((document as any).mozFullScreenEnabled) {
36+
document.addEventListener('mozfullscreenchange', fn);
37+
} else if ((document as any).msFullscreenEnabled) {
38+
document.addEventListener('MSFullscreenChange', fn);
39+
}
40+
}
41+
42+
/**
43+
* When the page is put into fullscreen mode, a specific element is specified.
44+
* Only that element and its children are visible when in fullscreen mode.
45+
*/
46+
getFullscreenElement(): Element {
47+
return document.fullscreenElement ||
48+
document.webkitFullscreenElement ||
49+
(document as any).mozFullScreenElement ||
50+
(document as any).msFullscreenElement ||
51+
null;
52+
}
53+
}

src/lib/core/overlay/overlay-container.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import {Injectable} from '@angular/core';
2+
13
/**
24
* The OverlayContainer is the container in which all overlays will load.
35
* It should be provided in the root component to ensure it is properly shared.
46
*/
7+
@Injectable()
58
export class OverlayContainer {
6-
private _containerElement: HTMLElement;
9+
protected _containerElement: HTMLElement;
710

811
/**
912
* This method returns the overlay container element. It will lazily
@@ -20,7 +23,7 @@ export class OverlayContainer {
2023
* Create the overlay container element, which is simply a div
2124
* with the 'cdk-overlay-container' class on the document body.
2225
*/
23-
private _createContainer(): void {
26+
protected _createContainer(): void {
2427
let container = document.createElement('div');
2528
container.classList.add('cdk-overlay-container');
2629
document.body.appendChild(container);

0 commit comments

Comments
 (0)