diff --git a/src/components/tooltip/tooltip.scss b/src/components/tooltip/tooltip.scss index 8b32906046e4..fff11e279a09 100644 --- a/src/components/tooltip/tooltip.scss +++ b/src/components/tooltip/tooltip.scss @@ -19,4 +19,27 @@ $md-tooltip-padding: 8px; margin: $md-tooltip-margin; height: $md-tooltip-height; line-height: $md-tooltip-height; + opacity: 0; + pointer-events: none; + transform: scale(0); +} +.md-tooltip-initialized { + transition: $swift-ease-out-duration $swift-ease-out-timing-function; + transition-property: opacity, transform; +} +.md-tooltip-below { + transform-origin: 50% 0; +} +.md-tooltip-above { + transform-origin: 50% 100%; +} +.md-tooltip-before { + transform-origin: 100% 50%; +} +.md-tooltip-after { + transform-origin: 0 50%; +} +.md-tooltip-visible { + opacity: 1; + transform: scale(1); } diff --git a/src/components/tooltip/tooltip.spec.ts b/src/components/tooltip/tooltip.spec.ts index 9863f148880a..74ab8d1a5b6e 100644 --- a/src/components/tooltip/tooltip.spec.ts +++ b/src/components/tooltip/tooltip.spec.ts @@ -63,10 +63,15 @@ describe('MdTooltip', () => { whenStable([ () => { expect(overlayContainerElement.textContent).toBe('some message'); + }, + () => { + expect(overlayContainerElement.querySelector('.md-tooltip').classList + .contains('md-tooltip-visible')).toBeTruthy(); tooltipDirective._handleMouseLeave(null); }, () => { - expect(overlayContainerElement.textContent).toBe(''); + // Query for md-tooltip-visible class rather than waiting for CSS transitions to finish + expect(overlayContainerElement.classList.contains('md-tooltip-visible')).toBeFalsy(); } ]); })); diff --git a/src/components/tooltip/tooltip.ts b/src/components/tooltip/tooltip.ts index cf450ca7cec0..31fbe49b43e9 100644 --- a/src/components/tooltip/tooltip.ts +++ b/src/components/tooltip/tooltip.ts @@ -54,6 +54,7 @@ export class MdTooltip { } private _overlayRef: OverlayRef; + private _tooltipContainer: TooltipContainer; constructor(private _overlay: Overlay, private _elementRef: ElementRef, private _viewContainerRef: ViewContainerRef, @@ -139,10 +140,11 @@ export class MdTooltip { show(): Promise { if (!this.visible && this._overlayRef && !this._overlayRef.hasAttached()) { this.visible = true; - let promise = this._overlayRef.attach(new ComponentPortal(TooltipComponent, + let promise = this._overlayRef.attach(new ComponentPortal(TooltipContainer, this._viewContainerRef)); - promise.then((ref: ComponentRef) => { - ref.instance.message = this.message; + promise.then((ref: ComponentRef) => { + this._tooltipContainer = ref.instance; + ref.instance.tooltip = this; this._updatePosition(); }); return promise; @@ -153,6 +155,19 @@ export class MdTooltip { * Hides the tooltip and returns a promise that will resolve when the tooltip is hidden */ hide(): Promise { + if (this._tooltipContainer) { + let tooltipContainer = this._tooltipContainer; + // Reset _tooltipContainer to prevent duplicate calls to `hide()` + this._tooltipContainer = null; + return tooltipContainer.remove().then(() => this.destroy()); + } + } + + /** + * Removes the tooltip from the DOM and returns a promise that resolves once the element is fully + * removed. + */ + destroy(): Promise { if (this.visible && this._overlayRef && this._overlayRef.hasAttached()) { this.visible = false; return this._overlayRef.detach(); @@ -184,11 +199,42 @@ export class MdTooltip { @Component({ moduleId: module.id, selector: 'md-tooltip-component', - template: `
{{message}}
`, + template: `
{{message}}
`, styleUrls: ['tooltip.css'], }) -export class TooltipComponent { - message: string; +class TooltipContainer { + tooltip: MdTooltip; + visible = false; + initialized = false; + + constructor(private _elementRef: ElementRef) {} + + get message() { + return this.tooltip.message; + } + + get positionClass() { + return `md-tooltip-${this.tooltip.position}`; + } + + /** Once the view is rendered, add the `visible` class to trigger enter animation */ + ngAfterViewInit() { + // transitions are only enabled after the view is initialized + this.initialized = true; + // with animations enabled, we can make the tooltip visible + this.visible = true; + } + + /** Trigger the leave animation and returns a promise that resolves when the animation is done */ + remove(): Promise { + this.visible = false; + return new Promise(done => { + // Note: This only works if all transitions have the same duration + this._elementRef.nativeElement.addEventListener('transitionend', () => done()); + }); + } } /** @deprecated */ @@ -197,8 +243,8 @@ export const MD_TOOLTIP_DIRECTIVES = [MdTooltip]; @NgModule({ imports: [OverlayModule], - exports: [MD_TOOLTIP_DIRECTIVES, TooltipComponent], - declarations: [MD_TOOLTIP_DIRECTIVES, TooltipComponent], - entryComponents: [TooltipComponent], + exports: [MD_TOOLTIP_DIRECTIVES, TooltipContainer], + declarations: [MD_TOOLTIP_DIRECTIVES, TooltipContainer], + entryComponents: [TooltipContainer], }) export class MdTooltipModule { }