diff --git a/src/lib/tooltip/tooltip.html b/src/lib/tooltip/tooltip.html
index 1da0d330d3e..8f9981cf2e2 100644
--- a/src/lib/tooltip/tooltip.html
+++ b/src/lib/tooltip/tooltip.html
@@ -2,4 +2,5 @@
[ngClass]="tooltipClass"
[style.transform-origin]="_transformOrigin"
[@state]="_visibility"
- (@state.done)="_afterVisibilityAnimation($event)">{{message}}
+ (@state.start)="_animationStart()"
+ (@state.done)="_animationDone($event)">{{message}}
diff --git a/src/lib/tooltip/tooltip.spec.ts b/src/lib/tooltip/tooltip.spec.ts
index a251597ddc5..d05fd3e983e 100644
--- a/src/lib/tooltip/tooltip.spec.ts
+++ b/src/lib/tooltip/tooltip.spec.ts
@@ -328,14 +328,14 @@ describe('MdTooltip', () => {
fixture.detectChanges();
// At this point the animation should be able to complete itself and trigger the
- // _afterVisibilityAnimation function, but for unknown reasons in the test infrastructure,
+ // _animationDone function, but for unknown reasons in the test infrastructure,
// this does not occur. Manually call this and verify that doing so does not
// throw an error.
- tooltipInstance._afterVisibilityAnimation({
+ tooltipInstance._animationDone({
fromState: 'visible',
toState: 'hidden',
totalTime: 150,
- phaseName: '',
+ phaseName: 'done',
} as AnimationEvent);
}));
@@ -436,6 +436,39 @@ describe('MdTooltip', () => {
expect(tooltipDirective.message).toBe('100');
}));
+
+ it('should hide when clicking away', fakeAsync(() => {
+ tooltipDirective.show();
+ tick(0);
+ fixture.detectChanges();
+ tick(500);
+
+ expect(tooltipDirective._isTooltipVisible()).toBe(true);
+ expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
+
+ document.body.click();
+ tick(0);
+ fixture.detectChanges();
+ tick(500);
+
+ expect(tooltipDirective._isTooltipVisible()).toBe(false);
+ expect(overlayContainerElement.textContent).toBe('');
+ }));
+
+ it('should not hide immediately if a click fires while animating', fakeAsync(() => {
+ tooltipDirective.show();
+ tick(0);
+ fixture.detectChanges();
+
+ document.body.click();
+ fixture.detectChanges();
+
+ tick(500);
+
+ expect(tooltipDirective._isTooltipVisible()).toBe(true);
+ expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
+ }));
+
});
describe('scrollable usage', () => {
diff --git a/src/lib/tooltip/tooltip.ts b/src/lib/tooltip/tooltip.ts
index 3fa8f937ace..9dddf7387c6 100644
--- a/src/lib/tooltip/tooltip.ts
+++ b/src/lib/tooltip/tooltip.ts
@@ -422,10 +422,8 @@ export type TooltipVisibility = 'initial' | 'visible' | 'hidden';
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('state', [
- state('void', style({transform: 'scale(0)'})),
- state('initial', style({transform: 'scale(0)'})),
+ state('initial, void, hidden', style({transform: 'scale(0)'})),
state('visible', style({transform: 'scale(1)'})),
- state('hidden', style({transform: 'scale(0)'})),
transition('* => visible', animate('150ms cubic-bezier(0.0, 0.0, 0.2, 1)')),
transition('* => hidden', animate('150ms cubic-bezier(0.4, 0.0, 1, 1)')),
])
@@ -455,7 +453,7 @@ export class TooltipComponent {
_visibility: TooltipVisibility = 'initial';
/** Whether interactions on the page should close the tooltip */
- _closeOnInteraction: boolean = false;
+ private _closeOnInteraction: boolean = false;
/** The transform origin used in the animation for showing and hiding the tooltip */
_transformOrigin: string = 'bottom';
@@ -477,21 +475,13 @@ export class TooltipComponent {
clearTimeout(this._hideTimeoutId);
}
- // Body interactions should cancel the tooltip if there is a delay in showing.
- this._closeOnInteraction = true;
-
this._setTransformOrigin(position);
this._showTimeoutId = setTimeout(() => {
this._visibility = 'visible';
- // If this was set to true immediately, then a body click that triggers show() would
- // trigger interaction and close the tooltip right after it was displayed.
- this._closeOnInteraction = false;
-
// Mark for check so if any parent component has set the
// ChangeDetectionStrategy to OnPush it will be checked anyways
this._markForCheck();
- setTimeout(() => this._closeOnInteraction = true, 0);
}, delay);
}
@@ -507,7 +497,6 @@ export class TooltipComponent {
this._hideTimeoutId = setTimeout(() => {
this._visibility = 'hidden';
- this._closeOnInteraction = false;
// Mark for check so if any parent component has set the
// ChangeDetectionStrategy to OnPush it will be checked anyways
@@ -543,10 +532,23 @@ export class TooltipComponent {
}
}
- _afterVisibilityAnimation(e: AnimationEvent): void {
- if (e.toState === 'hidden' && !this.isVisible()) {
+ _animationStart() {
+ this._closeOnInteraction = false;
+ }
+
+ _animationDone(event: AnimationEvent): void {
+ const toState = event.toState as TooltipVisibility;
+
+ if (toState === 'hidden' && !this.isVisible()) {
this._onHide.next();
}
+
+ if (toState === 'visible' || toState === 'hidden') {
+ // Note: as of Angular 4.3, the animations module seems to fire the `start` callback before
+ // the end if animations are disabled. Make this call async to ensure that it still fires
+ // at the appropriate time.
+ Promise.resolve().then(() => this._closeOnInteraction = true);
+ }
}
/**