-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(overlay): add basic core of overlay
- Loading branch information
Showing
11 changed files
with
349 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import {PortalHost, Portal} from '../portal/portal'; | ||
|
||
/** | ||
* Reference to an overlay that has been created with the Overlay service. | ||
* Used to manipulate or dispose of said overlay. | ||
*/ | ||
export class OverlayRef implements PortalHost { | ||
constructor(private _portalHost: PortalHost) { } | ||
|
||
attach(portal: Portal<any>): Promise<any> { | ||
return this._portalHost.attach(portal); | ||
} | ||
|
||
detach(): Promise<any> { | ||
return this._portalHost.detach(); | ||
} | ||
|
||
dispose(): void { | ||
this._portalHost.dispose(); | ||
} | ||
|
||
hasAttached(): boolean { | ||
return this._portalHost.hasAttached(); | ||
} | ||
|
||
// TODO(jelbourn): add additional methods for manipulating the overlay. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
* OverlayState is a bag of values for either the initial configuration or current state of an | ||
* overlay. | ||
*/ | ||
export class OverlayState { | ||
// Not yet implemented. | ||
// TODO(jelbourn): add overlay state / configuration. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { | ||
inject, | ||
TestComponentBuilder, | ||
fakeAsync, | ||
flushMicrotasks, | ||
beforeEachProviders, | ||
} from 'angular2/testing'; | ||
import { | ||
it, | ||
describe, | ||
expect, | ||
beforeEach, | ||
} from '../../core/facade/testing'; | ||
import { | ||
Component, | ||
ViewChild, | ||
ElementRef, | ||
provide, | ||
} from 'angular2/core'; | ||
import {BrowserDomAdapter} from '../platform/browser/browser_adapter'; | ||
import {TemplatePortalDirective} from '../portal/portal-directives'; | ||
import {TemplatePortal, ComponentPortal} from '../portal/portal'; | ||
import {Overlay, OVERLAY_CONTAINER_TOKEN} from './overlay'; | ||
import {DOM} from '../platform/dom/dom_adapter'; | ||
import {OverlayRef} from './overlay-ref'; | ||
|
||
|
||
export function main() { | ||
describe('Overlay', () => { | ||
BrowserDomAdapter.makeCurrent(); | ||
|
||
let builder: TestComponentBuilder; | ||
let overlay: Overlay; | ||
let componentPortal: ComponentPortal; | ||
let templatePortal: TemplatePortal; | ||
let overlayContainerElement: Element; | ||
|
||
beforeEachProviders(() => [ | ||
Overlay, | ||
provide(OVERLAY_CONTAINER_TOKEN, {useFactory: () => { | ||
overlayContainerElement = DOM.createElement('div'); | ||
return overlayContainerElement; | ||
}}) | ||
]); | ||
|
||
let deps = [TestComponentBuilder, Overlay]; | ||
beforeEach(inject(deps, fakeAsync((tcb: TestComponentBuilder, o: Overlay) => { | ||
builder = tcb; | ||
overlay = o; | ||
|
||
builder.createAsync(TestComponentWithTemplatePortals).then(fixture => { | ||
fixture.detectChanges(); | ||
templatePortal = fixture.componentInstance.templatePortal; | ||
componentPortal = new ComponentPortal(PizzaMsg, fixture.componentInstance.elementRef); | ||
}); | ||
|
||
flushMicrotasks(); | ||
}))); | ||
|
||
it('should load a component into an overlay', fakeAsyncTest(() => { | ||
let overlayRef: OverlayRef; | ||
|
||
overlay.create().then(ref => { | ||
overlayRef = ref; | ||
overlayRef.attach(componentPortal); | ||
}); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(overlayContainerElement.textContent).toContain('Pizza'); | ||
|
||
overlayRef.dispose(); | ||
expect(overlayContainerElement.childNodes.length).toBe(0); | ||
expect(overlayContainerElement.textContent).toBe(''); | ||
})); | ||
|
||
it('should load a template portal into an overlay', fakeAsyncTest(() => { | ||
let overlayRef: OverlayRef; | ||
|
||
overlay.create().then(ref => { | ||
overlayRef = ref; | ||
overlayRef.attach(templatePortal); | ||
}); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(overlayContainerElement.textContent).toContain('Cake'); | ||
|
||
overlayRef.dispose(); | ||
expect(overlayContainerElement.childNodes.length).toBe(0); | ||
expect(overlayContainerElement.textContent).toBe(''); | ||
})); | ||
|
||
it('should open multiple overlays', fakeAsyncTest(() => { | ||
let pizzaOverlayRef: OverlayRef; | ||
let cakeOverlayRef: OverlayRef; | ||
|
||
overlay.create().then(ref => { | ||
pizzaOverlayRef = ref; | ||
pizzaOverlayRef.attach(componentPortal); | ||
}); | ||
|
||
flushMicrotasks(); | ||
|
||
overlay.create().then(ref => { | ||
cakeOverlayRef = ref; | ||
cakeOverlayRef.attach(templatePortal); | ||
}); | ||
|
||
flushMicrotasks(); | ||
|
||
expect(overlayContainerElement.childNodes.length).toBe(2); | ||
expect(overlayContainerElement.textContent).toContain('Pizza'); | ||
expect(overlayContainerElement.textContent).toContain('Cake'); | ||
|
||
pizzaOverlayRef.dispose(); | ||
expect(overlayContainerElement.childNodes.length).toBe(1); | ||
expect(overlayContainerElement.textContent).toContain('Cake'); | ||
|
||
cakeOverlayRef.dispose(); | ||
expect(overlayContainerElement.childNodes.length).toBe(0); | ||
expect(overlayContainerElement.textContent).toBe(''); | ||
})); | ||
}); | ||
} | ||
|
||
|
||
/** Simple component for testing ComponentPortal. */ | ||
@Component({ | ||
selector: 'pizza-msg', | ||
template: '<p>Pizza</p>', | ||
}) | ||
class PizzaMsg {} | ||
|
||
|
||
/** Test-bed component that contains a TempatePortal and an ElementRef. */ | ||
@Component({ | ||
selector: 'portal-test', | ||
template: `<template portal>Cake</template>`, | ||
directives: [TemplatePortalDirective], | ||
}) | ||
class TestComponentWithTemplatePortals { | ||
@ViewChild(TemplatePortalDirective) templatePortal: TemplatePortalDirective; | ||
constructor(public elementRef: ElementRef) { } | ||
} | ||
|
||
function fakeAsyncTest(fn: () => void) { | ||
return inject([], fakeAsync(fn)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { | ||
DynamicComponentLoader, | ||
AppViewManager, | ||
OpaqueToken, | ||
Inject, | ||
Injectable} from 'angular2/core'; | ||
import {CONST_EXPR} from 'angular2/src/facade/lang'; | ||
import {OverlayState} from './overlay-state'; | ||
import {DomPortalHost} from '../portal/dom-portal-host'; | ||
import {OverlayRef} from './overlay-ref'; | ||
import {DOM} from '../platform/dom/dom_adapter'; | ||
|
||
// Re-export OverlayState and OverlayRef so they can be imported directly from here. | ||
export {OverlayState} from './overlay-state'; | ||
export {OverlayRef} from './overlay-ref'; | ||
|
||
/** Token used to inject the DOM element that serves as the overlay container. */ | ||
export const OVERLAY_CONTAINER_TOKEN = CONST_EXPR(new OpaqueToken('overlayContainer')); | ||
|
||
/** Next overlay unique ID. */ | ||
let nextUniqueId = 0; | ||
|
||
/** The default state for newly created overlays. */ | ||
let defaultState = new OverlayState(); | ||
|
||
|
||
/** | ||
* Service to create Overlays. Overlays are dynamically added pieces of floating UI, meant to be | ||
* used as a low-level building building block for other components. Dialogs, tooltips, menus, | ||
* selects, etc. can all be built using overlays. The service should primarily be used by authors | ||
* of re-usable components rather than developers building end-user applications. | ||
* | ||
* An overlay *is* a PortalHost, so any kind of Portal can be loaded into one. | ||
*/ | ||
@Injectable() | ||
export class Overlay { | ||
constructor( | ||
@Inject(OVERLAY_CONTAINER_TOKEN) private _overlayContainerElement: Element, | ||
private _dynamicComponentLoader: DynamicComponentLoader, | ||
private _appViewManager: AppViewManager) { | ||
} | ||
|
||
/** | ||
* Creates an overlay. | ||
* @param state State to apply to the overlay. | ||
* @returns A reference to the created overlay. | ||
*/ | ||
create(state: OverlayState = defaultState): Promise<OverlayRef> { | ||
return this._createPaneElement(state).then(pane => this._createOverlayRef(pane)); | ||
} | ||
|
||
/** | ||
* Creates the DOM element for an overlay. | ||
* @param state State to apply to the created element. | ||
* @returns Promise resolving to the created element. | ||
*/ | ||
private _createPaneElement(state: OverlayState): Promise<Element> { | ||
var pane = DOM.createElement('div'); | ||
pane.id = `md-overlay-${nextUniqueId++}`; | ||
DOM.addClass(pane, 'md-overlay-pane'); | ||
|
||
this.applyState(pane, state); | ||
this._overlayContainerElement.appendChild(pane); | ||
|
||
return Promise.resolve(pane); | ||
} | ||
|
||
/** | ||
* Applies a given state to the given pane element. | ||
* @param pane The pane to modify. | ||
* @param state The state to apply. | ||
*/ | ||
applyState(pane: Element, state: OverlayState) { | ||
// Not yet implemented. | ||
// TODO(jelbourn): apply state to the pane element. | ||
} | ||
|
||
/** | ||
* Create a DomPortalHost into which the overlay content can be loaded. | ||
* @param pane The DOM element to turn into a portal host. | ||
* @returns A portal host for the given DOM element. | ||
*/ | ||
private _createPortalHost(pane: Element): DomPortalHost { | ||
return new DomPortalHost( | ||
pane, | ||
this._dynamicComponentLoader, | ||
this._appViewManager); | ||
} | ||
|
||
/** | ||
* Creates an OverlayRef for an overlay in the given DOM element. | ||
* @param pane DOM element for the overlay | ||
* @returns {OverlayRef} | ||
*/ | ||
private _createOverlayRef(pane: Element): OverlayRef { | ||
return new OverlayRef(this._createPortalHost(pane)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<button (click)="openRotiniPanel()"> | ||
Pasta 1 | ||
</button> | ||
|
||
<button (click)="openFusilliPanel()"> | ||
Pasta 2 | ||
</button> | ||
|
||
<!-- Template to load into an overlay. --> | ||
<template portal> | ||
<p> Fusilli </p> | ||
</template> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import {Component, provide, ElementRef, ViewChildren, QueryList} from 'angular2/core'; | ||
import {Overlay, OVERLAY_CONTAINER_TOKEN} from '../../core/overlay/overlay'; | ||
import {ComponentPortal, Portal} from '../../core/portal/portal'; | ||
import {BrowserDomAdapter} from '../../core/platform/browser/browser_adapter'; | ||
import {TemplatePortalDirective} from '../../core/portal/portal-directives'; | ||
|
||
|
||
@Component({ | ||
selector: 'overlay-demo', | ||
templateUrl: 'demo-app/overlay/overlay-demo.html', | ||
styleUrls: ['demo-app/overlay/overlay-demo.css'], | ||
directives: [TemplatePortalDirective], | ||
providers: [ | ||
Overlay, | ||
provide(OVERLAY_CONTAINER_TOKEN, {useValue: document.body}) | ||
] | ||
}) | ||
export class OverlayDemo { | ||
@ViewChildren(TemplatePortalDirective) templatePortals: QueryList<Portal<any>>; | ||
|
||
constructor(public overlay: Overlay, public elementRef: ElementRef) { | ||
BrowserDomAdapter.makeCurrent(); | ||
} | ||
|
||
openRotiniPanel() { | ||
this.overlay.create().then(ref => { | ||
ref.attach(new ComponentPortal(PastaPanel, this.elementRef)); | ||
}); | ||
} | ||
|
||
openFusilliPanel() { | ||
this.overlay.create().then(ref => { | ||
ref.attach(this.templatePortals.first); | ||
}); | ||
} | ||
} | ||
|
||
/** Simple component to load into an overlay */ | ||
@Component({ | ||
selector: 'pasta-panel', | ||
template: '<p>Rotini {{value}}</p>' | ||
}) | ||
class PastaPanel { | ||
value: number = 9000; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters