diff --git a/src/material-experimental/mdc-tooltip/testing/tooltip-harness.ts b/src/material-experimental/mdc-tooltip/testing/tooltip-harness.ts
index 62596cba1967..2761b4ffdf5a 100644
--- a/src/material-experimental/mdc-tooltip/testing/tooltip-harness.ts
+++ b/src/material-experimental/mdc-tooltip/testing/tooltip-harness.ts
@@ -14,6 +14,9 @@ export class MatTooltipHarness extends _MatTooltipHarnessBase {
protected _optionalPanel =
this.documentRootLocatorFactory().locatorForOptional('.mat-mdc-tooltip');
static hostSelector = '.mat-mdc-tooltip-trigger';
+ protected _hiddenClass = 'mat-mdc-tooltip-hide';
+ protected _showAnimationName = 'mat-mdc-tooltip-show';
+ protected _hideAnimationName = 'mat-mdc-tooltip-hide';
/**
* Gets a `HarnessPredicate` that can be used to search
diff --git a/src/material-experimental/mdc-tooltip/tooltip.html b/src/material-experimental/mdc-tooltip/tooltip.html
index 6eff2c6af2c9..28b374a88f8a 100644
--- a/src/material-experimental/mdc-tooltip/tooltip.html
+++ b/src/material-experimental/mdc-tooltip/tooltip.html
@@ -1,9 +1,8 @@
diff --git a/src/material-experimental/mdc-tooltip/tooltip.scss b/src/material-experimental/mdc-tooltip/tooltip.scss
index 07c0a9af2dba..5901072294cb 100644
--- a/src/material-experimental/mdc-tooltip/tooltip.scss
+++ b/src/material-experimental/mdc-tooltip/tooltip.scss
@@ -6,6 +6,7 @@
.mat-mdc-tooltip {
// We don't use MDC's positioning so this has to be relative.
position: relative;
+ transform: scale(0);
// Increases the area of the tooltip so the user's pointer can go from the trigger directly to it.
&::before {
@@ -18,8 +19,47 @@
z-index: -1;
position: absolute;
}
+
+ &._mat-animation-noopable {
+ animation: none;
+ transform: scale(1);
+ }
}
.mat-mdc-tooltip-panel-non-interactive {
pointer-events: none;
}
+
+// TODO(crisbeto): we may be able to use MDC directly for these animations.
+
+@keyframes mat-mdc-tooltip-show {
+ 0% {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes mat-mdc-tooltip-hide {
+ 0% {
+ opacity: 1;
+ transform: scale(1);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+}
+
+.mat-mdc-tooltip-show {
+ animation: mat-mdc-tooltip-show 150ms cubic-bezier(0, 0, 0.2, 1) forwards;
+}
+
+.mat-mdc-tooltip-hide {
+ animation: mat-mdc-tooltip-hide 75ms cubic-bezier(0.4, 0, 1, 1) forwards;
+}
diff --git a/src/material-experimental/mdc-tooltip/tooltip.spec.ts b/src/material-experimental/mdc-tooltip/tooltip.spec.ts
index 020a0f7674c0..a07f148c8e50 100644
--- a/src/material-experimental/mdc-tooltip/tooltip.spec.ts
+++ b/src/material-experimental/mdc-tooltip/tooltip.spec.ts
@@ -1,4 +1,3 @@
-import {AnimationEvent} from '@angular/animations';
import {FocusMonitor} from '@angular/cdk/a11y';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {ESCAPE} from '@angular/cdk/keycodes';
@@ -26,14 +25,12 @@ import {
ComponentFixture,
fakeAsync,
flush,
- flushMicrotasks,
inject,
TestBed,
tick,
waitForAsync,
} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
-import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Subject} from 'rxjs';
import {
MAT_TOOLTIP_DEFAULT_OPTIONS,
@@ -43,6 +40,7 @@ import {
TooltipPosition,
TooltipTouchGestures,
} from './index';
+import {NoopAnimationsModule} from '@angular/platform-browser/animations';
const initialTooltipMessage = 'initial tooltip message';
@@ -55,7 +53,7 @@ describe('MDC-based MatTooltip', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
- imports: [MatTooltipModule, OverlayModule, NoopAnimationsModule],
+ imports: [MatTooltipModule, OverlayModule],
declarations: [
BasicTooltipDemo,
ScrollableTooltipDemo,
@@ -111,19 +109,19 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
- // wait till animation has finished
- tick(500);
+ // Wait until animation has finished
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
- // Make sure tooltip is shown to the user and animation has finished
+ // Make sure tooltip is shown to the user and animation has finished.
const tooltipElement = overlayContainerElement.querySelector(
'.mat-mdc-tooltip',
) as HTMLElement;
expect(tooltipElement instanceof HTMLElement).toBe(true);
- expect(tooltipElement.style.transform).toBe('scale(1)');
+ expect(tooltipElement.classList).toContain('mat-mdc-tooltip-show');
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
- // After hide called, a timeout delay is created that will to hide the tooltip.
+ // After hide is called, a timeout delay is created that will to hide the tooltip.
const tooltipDelay = 1000;
tooltipDirective.hide(tooltipDelay);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
@@ -134,7 +132,7 @@ describe('MDC-based MatTooltip', () => {
expect(tooltipDirective._isTooltipVisible()).toBe(false);
// On animation complete, should expect that the tooltip has been detached.
- flushMicrotasks();
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(tooltipDirective, false);
}));
@@ -143,17 +141,17 @@ describe('MDC-based MatTooltip', () => {
tick(0);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
tooltipDirective._overlayRef!.detach();
tick(0);
fixture.detectChanges();
expect(tooltipDirective._isTooltipVisible()).toBe(false);
- flushMicrotasks();
assertTooltipInstance(tooltipDirective, false);
tooltipDirective.show();
tick(0);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
}));
@@ -175,7 +173,7 @@ describe('MDC-based MatTooltip', () => {
it('should be able to override the default show and hide delays', fakeAsync(() => {
TestBed.resetTestingModule()
.configureTestingModule({
- imports: [MatTooltipModule, OverlayModule, NoopAnimationsModule],
+ imports: [MatTooltipModule, OverlayModule],
declarations: [BasicTooltipDemo],
providers: [
{
@@ -212,7 +210,7 @@ describe('MDC-based MatTooltip', () => {
it('should be able to override the default position', fakeAsync(() => {
TestBed.resetTestingModule()
.configureTestingModule({
- imports: [MatTooltipModule, OverlayModule, NoopAnimationsModule],
+ imports: [MatTooltipModule, OverlayModule],
declarations: [TooltipDemoWithoutPositionBinding],
providers: [
{
@@ -421,16 +419,7 @@ describe('MDC-based MatTooltip', () => {
tooltipDirective.hide(0);
fixture.detectChanges();
tick();
-
- // At this point the animation should be able to complete itself and trigger the
- // _animationDone function, but for unknown reasons in the test infrastructure,
- // this does not occur. Manually call the hook so the animation subscriptions get invoked.
- tooltipDirective._tooltipInstance!._animationDone({
- fromState: 'visible',
- toState: 'hidden',
- totalTime: 150,
- phaseName: 'done',
- } as AnimationEvent);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
expect(() => {
tooltipDirective.position = 'right';
@@ -444,7 +433,7 @@ describe('MDC-based MatTooltip', () => {
tooltipDirective.show();
tick(0); // Tick for the show delay (default is 0)
- expect(tooltipDirective._tooltipInstance!._visibility).toBe('visible');
+ expect(tooltipDirective._tooltipInstance!.isVisible()).toBe(true);
fixture.detectChanges();
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
@@ -530,33 +519,21 @@ describe('MDC-based MatTooltip', () => {
it('should not try to dispose the tooltip when destroyed and done hiding', fakeAsync(() => {
tooltipDirective.show();
fixture.detectChanges();
- tick(150);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
const tooltipDelay = 1000;
tooltipDirective.hide();
tick(tooltipDelay); // Change the tooltip state to hidden and trigger animation start
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
- // Store the tooltip instance, which will be set to null after the button is hidden.
- const tooltipInstance = tooltipDirective._tooltipInstance!;
fixture.componentInstance.showButton = false;
fixture.detectChanges();
-
- // At this point the animation should be able to complete itself and trigger the
- // _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._animationDone({
- fromState: 'visible',
- toState: 'hidden',
- totalTime: 150,
- phaseName: 'done',
- } as AnimationEvent);
}));
it('should complete the afterHidden stream when tooltip is destroyed', fakeAsync(() => {
tooltipDirective.show();
fixture.detectChanges();
- tick(150);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
const spy = jasmine.createSpy('complete spy');
const subscription = tooltipDirective
@@ -566,7 +543,7 @@ describe('MDC-based MatTooltip', () => {
tooltipDirective.hide(0);
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
expect(spy).toHaveBeenCalled();
subscription.unsubscribe();
@@ -642,7 +619,7 @@ describe('MDC-based MatTooltip', () => {
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
let tooltipWrapper = overlayContainerElement.querySelector(
'.cdk-overlay-connected-position-bounding-box',
@@ -654,13 +631,13 @@ describe('MDC-based MatTooltip', () => {
tooltipDirective.hide(0);
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
dir.value = 'ltr';
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
tooltipWrapper = overlayContainerElement.querySelector(
'.cdk-overlay-connected-position-bounding-box',
@@ -681,7 +658,7 @@ describe('MDC-based MatTooltip', () => {
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
@@ -689,7 +666,7 @@ describe('MDC-based MatTooltip', () => {
document.body.click();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
fixture.detectChanges();
expect(tooltipDirective._isTooltipVisible()).toBe(false);
@@ -700,7 +677,7 @@ describe('MDC-based MatTooltip', () => {
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
@@ -708,7 +685,7 @@ describe('MDC-based MatTooltip', () => {
dispatchFakeEvent(document.body, 'auxclick');
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
fixture.detectChanges();
expect(tooltipDirective._isTooltipVisible()).toBe(false);
@@ -723,6 +700,7 @@ describe('MDC-based MatTooltip', () => {
document.body.click();
fixture.detectChanges();
tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
}));
@@ -741,6 +719,7 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
tick(500);
fixture.detectChanges();
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
expect(tooltipDirective._isTooltipVisible()).toBe(false);
expect(overlayContainerElement.textContent).toBe('');
@@ -822,7 +801,7 @@ describe('MDC-based MatTooltip', () => {
tick(0);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
const overlayRef = tooltipDirective._overlayRef!;
@@ -832,7 +811,7 @@ describe('MDC-based MatTooltip', () => {
tick(0);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(overlayRef.detach).not.toHaveBeenCalled();
}));
@@ -1176,14 +1155,14 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
// wait until animation has finished
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
// Make sure tooltip is shown to the user and animation has finished
const tooltipElement = overlayContainerElement.querySelector(
'.mat-mdc-tooltip',
) as HTMLElement;
expect(tooltipElement instanceof HTMLElement).toBe(true);
- expect(tooltipElement.style.transform).toBe('scale(1)');
+ expect(tooltipElement.classList).toContain('mat-mdc-tooltip-show');
// After hide called, a timeout delay is created that will to hide the tooltip.
const tooltipDelay = 1000;
@@ -1196,7 +1175,7 @@ describe('MDC-based MatTooltip', () => {
expect(tooltipDirective._isTooltipVisible()).toBe(false);
// On animation complete, should expect that the tooltip has been detached.
- flushMicrotasks();
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(tooltipDirective, false);
}));
@@ -1233,9 +1212,9 @@ describe('MDC-based MatTooltip', () => {
assertTooltipInstance(fixture.componentInstance.tooltip, false);
- tick(250); // Finish the delay.
+ tick(500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true); // Finish the animation.
assertTooltipInstance(fixture.componentInstance.tooltip, true);
}));
@@ -1274,7 +1253,7 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true); // Finish the animation.
assertTooltipInstance(fixture.componentInstance.tooltip, true);
dispatchFakeEvent(button, 'touchend');
@@ -1284,7 +1263,7 @@ describe('MDC-based MatTooltip', () => {
tick(500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false); // Finish the exit animation.
assertTooltipInstance(fixture.componentInstance.tooltip, false);
}));
@@ -1298,7 +1277,7 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true); // Finish the animation.
assertTooltipInstance(fixture.componentInstance.tooltip, true);
dispatchFakeEvent(button, 'touchcancel');
@@ -1308,7 +1287,7 @@ describe('MDC-based MatTooltip', () => {
tick(500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false); // Finish the exit animation.
assertTooltipInstance(fixture.componentInstance.tooltip, false);
}));
@@ -1431,7 +1410,7 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
assertTooltipInstance(fixture.componentInstance.tooltip, true);
// Simulate the pointer at the bottom/right of the page.
@@ -1445,7 +1424,7 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
tick(1500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(fixture.componentInstance.tooltip, false);
}));
@@ -1464,7 +1443,7 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
assertTooltipInstance(fixture.componentInstance.tooltip, true);
// Simulate the pointer over the trigger.
@@ -1479,7 +1458,7 @@ describe('MDC-based MatTooltip', () => {
fixture.detectChanges();
tick(1500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(fixture.componentInstance.tooltip, true);
}));
@@ -1621,3 +1600,12 @@ function assertTooltipInstance(tooltip: MatTooltip, shouldExist: boolean): void
// happens due to the `_tooltipInstance` having a circular structure.
expect(!!tooltip._tooltipInstance).toBe(shouldExist);
}
+
+function finishCurrentTooltipAnimation(overlayContainer: HTMLElement, isVisible: boolean) {
+ const tooltip = overlayContainer.querySelector('.mat-mdc-tooltip')!;
+ const event = createFakeEvent('animationend');
+ Object.defineProperty(event, 'animationName', {
+ get: () => `mat-mdc-tooltip-${isVisible ? 'show' : 'hide'}`,
+ });
+ dispatchEvent(tooltip, event);
+}
diff --git a/src/material-experimental/mdc-tooltip/tooltip.ts b/src/material-experimental/mdc-tooltip/tooltip.ts
index cc0a49ba4d42..c08addbf7f2b 100644
--- a/src/material-experimental/mdc-tooltip/tooltip.ts
+++ b/src/material-experimental/mdc-tooltip/tooltip.ts
@@ -15,11 +15,13 @@ import {
Inject,
NgZone,
Optional,
+ ViewChild,
ViewContainerRef,
ViewEncapsulation,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {Platform} from '@angular/cdk/platform';
+import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {AriaDescriber, FocusMonitor} from '@angular/cdk/a11y';
import {Directionality} from '@angular/cdk/bidi';
import {ConnectedPosition, Overlay, ScrollDispatcher} from '@angular/cdk/overlay';
@@ -31,7 +33,6 @@ import {
_TooltipComponentBase,
} from '@angular/material/tooltip';
import {numbers} from '@material/tooltip';
-import {matTooltipAnimations} from './tooltip-animations';
/**
* CSS class that will be attached to the overlay panel.
@@ -116,11 +117,10 @@ export class MatTooltip extends _MatTooltipBase {
styleUrls: ['tooltip.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
- animations: [matTooltipAnimations.tooltipState],
host: {
// Forces the element to have a layout in IE and Edge. This fixes issues where the element
// won't be rendered if the animations are disabled or there is no web animations polyfill.
- '[style.zoom]': '_visibility === "visible" ? 1 : null',
+ '[style.zoom]': 'isVisible() ? 1 : null',
'(mouseleave)': '_handleMouseLeave($event)',
'aria-hidden': 'true',
},
@@ -129,12 +129,27 @@ export class TooltipComponent extends _TooltipComponentBase {
/* Whether the tooltip text overflows to multiple lines */
_isMultiline = false;
- constructor(changeDetectorRef: ChangeDetectorRef, private _elementRef: ElementRef) {
- super(changeDetectorRef);
+ /** Reference to the internal tooltip element. */
+ @ViewChild('tooltip', {
+ // Use a static query here since we interact directly with
+ // the DOM which can happen before `ngAfterViewInit`.
+ static: true,
+ })
+ _tooltip: ElementRef;
+ _showAnimation = 'mat-mdc-tooltip-show';
+ _hideAnimation = 'mat-mdc-tooltip-hide';
+
+ constructor(
+ changeDetectorRef: ChangeDetectorRef,
+ private _elementRef: ElementRef,
+ @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string,
+ ) {
+ super(changeDetectorRef, animationMode);
}
protected override _onShow(): void {
this._isMultiline = this._isTooltipMultiline();
+ this._markForCheck();
}
/** Whether the tooltip text has overflown to the next line */
diff --git a/src/material/tooltip/BUILD.bazel b/src/material/tooltip/BUILD.bazel
index deb3e9d9fe9b..656cf3342018 100644
--- a/src/material/tooltip/BUILD.bazel
+++ b/src/material/tooltip/BUILD.bazel
@@ -29,7 +29,6 @@ ng_module(
"//src/cdk/portal",
"//src/cdk/scrolling",
"//src/material/core",
- "@npm//@angular/animations",
"@npm//@angular/common",
"@npm//@angular/core",
"@npm//rxjs",
@@ -65,7 +64,6 @@ ng_test_library(
"//src/cdk/overlay",
"//src/cdk/platform",
"//src/cdk/testing/private",
- "@npm//@angular/animations",
"@npm//@angular/platform-browser",
"@npm//rxjs",
],
diff --git a/src/material/tooltip/testing/tooltip-harness.ts b/src/material/tooltip/testing/tooltip-harness.ts
index 6940760d8752..bd1ce13c833f 100644
--- a/src/material/tooltip/testing/tooltip-harness.ts
+++ b/src/material/tooltip/testing/tooltip-harness.ts
@@ -16,6 +16,9 @@ import {TooltipHarnessFilters} from './tooltip-harness-filters';
export abstract class _MatTooltipHarnessBase extends ComponentHarness {
protected abstract _optionalPanel: AsyncFactoryFn;
+ protected abstract _hiddenClass: string;
+ protected abstract _showAnimationName: string;
+ protected abstract _hideAnimationName: string;
/** Shows the tooltip. */
async show(): Promise {
@@ -24,9 +27,10 @@ export abstract class _MatTooltipHarnessBase extends ComponentHarness {
// We need to dispatch both `touchstart` and a hover event, because the tooltip binds
// different events depending on the device. The `changedTouches` is there in case the
// element has ripples.
- // @breaking-change 12.0.0 Remove null assertion from `dispatchEvent`.
- await host.dispatchEvent?.('touchstart', {changedTouches: []});
+ await host.dispatchEvent('touchstart', {changedTouches: []});
await host.hover();
+ const panel = await this._optionalPanel();
+ await panel?.dispatchEvent('animationend', {animationName: this._showAnimationName});
}
/** Hides the tooltip. */
@@ -35,15 +39,16 @@ export abstract class _MatTooltipHarnessBase extends ComponentHarness {
// We need to dispatch both `touchstart` and a hover event, because
// the tooltip binds different events depending on the device.
- // @breaking-change 12.0.0 Remove null assertion from `dispatchEvent`.
- await host.dispatchEvent?.('touchend');
+ await host.dispatchEvent('touchend');
await host.mouseAway();
- await this.forceStabilize(); // Needed in order to flush the `hide` animation.
+ const panel = await this._optionalPanel();
+ await panel?.dispatchEvent('animationend', {animationName: this._hideAnimationName});
}
/** Gets whether the tooltip is open. */
async isOpen(): Promise {
- return !!(await this._optionalPanel());
+ const panel = await this._optionalPanel();
+ return !!panel && !(await panel.hasClass(this._hiddenClass));
}
/** Gets a promise for the tooltip panel's text. */
@@ -56,6 +61,9 @@ export abstract class _MatTooltipHarnessBase extends ComponentHarness {
/** Harness for interacting with a standard mat-tooltip in tests. */
export class MatTooltipHarness extends _MatTooltipHarnessBase {
protected _optionalPanel = this.documentRootLocatorFactory().locatorForOptional('.mat-tooltip');
+ protected _hiddenClass = 'mat-tooltip-hide';
+ protected _showAnimationName = 'mat-tooltip-show';
+ protected _hideAnimationName = 'mat-tooltip-hide';
static hostSelector = '.mat-tooltip-trigger';
/**
diff --git a/src/material/tooltip/tooltip.html b/src/material/tooltip/tooltip.html
index feaaaf53352f..cf6bd74a0592 100644
--- a/src/material/tooltip/tooltip.html
+++ b/src/material/tooltip/tooltip.html
@@ -1,6 +1,5 @@
-{{message}}
+ [class.mat-tooltip-handset]="(_isHandset | async)?.matches">{{message}}
diff --git a/src/material/tooltip/tooltip.scss b/src/material/tooltip/tooltip.scss
index 1d4c9eeb4b64..1fadd1358a97 100644
--- a/src/material/tooltip/tooltip.scss
+++ b/src/material/tooltip/tooltip.scss
@@ -16,6 +16,12 @@ $handset-margin: 24px;
padding-right: $horizontal-padding;
overflow: hidden;
text-overflow: ellipsis;
+ transform: scale(0);
+
+ &._mat-animation-noopable {
+ animation: none;
+ transform: scale(1);
+ }
@include a11y.high-contrast(active, off) {
outline: solid 1px;
@@ -31,3 +37,40 @@ $handset-margin: 24px;
.mat-tooltip-panel-non-interactive {
pointer-events: none;
}
+
+@keyframes mat-tooltip-show {
+ 0% {
+ opacity: 0;
+ transform: scale(0);
+ }
+
+ 50% {
+ opacity: 0.5;
+ transform: scale(0.99);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes mat-tooltip-hide {
+ 0% {
+ opacity: 1;
+ transform: scale(1);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: scale(1);
+ }
+}
+
+.mat-tooltip-show {
+ animation: mat-tooltip-show 200ms cubic-bezier(0, 0, 0.2, 1) forwards;
+}
+
+.mat-tooltip-hide {
+ animation: mat-tooltip-hide 100ms cubic-bezier(0, 0, 0.2, 1) forwards;
+}
diff --git a/src/material/tooltip/tooltip.spec.ts b/src/material/tooltip/tooltip.spec.ts
index 759f642475a3..ec96e22c6c16 100644
--- a/src/material/tooltip/tooltip.spec.ts
+++ b/src/material/tooltip/tooltip.spec.ts
@@ -1,4 +1,3 @@
-import {AnimationEvent} from '@angular/animations';
import {FocusMonitor} from '@angular/cdk/a11y';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {ESCAPE} from '@angular/cdk/keycodes';
@@ -26,14 +25,12 @@ import {
ComponentFixture,
fakeAsync,
flush,
- flushMicrotasks,
inject,
TestBed,
tick,
waitForAsync,
} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
-import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Subject} from 'rxjs';
import {
MAT_TOOLTIP_DEFAULT_OPTIONS,
@@ -43,6 +40,7 @@ import {
TooltipPosition,
TooltipTouchGestures,
} from './index';
+import {NoopAnimationsModule} from '@angular/platform-browser/animations';
const initialTooltipMessage = 'initial tooltip message';
@@ -55,7 +53,7 @@ describe('MatTooltip', () => {
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
- imports: [MatTooltipModule, OverlayModule, NoopAnimationsModule],
+ imports: [MatTooltipModule, OverlayModule],
declarations: [
BasicTooltipDemo,
ScrollableTooltipDemo,
@@ -111,13 +109,13 @@ describe('MatTooltip', () => {
fixture.detectChanges();
- // Wait until the animation has finished.
- tick(500);
+ // Wait until animation has finished
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
// Make sure tooltip is shown to the user and animation has finished.
const tooltipElement = overlayContainerElement.querySelector('.mat-tooltip') as HTMLElement;
expect(tooltipElement instanceof HTMLElement).toBe(true);
- expect(tooltipElement.style.transform).toBe('scale(1)');
+ expect(tooltipElement.classList).toContain('mat-tooltip-show');
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
@@ -132,7 +130,7 @@ describe('MatTooltip', () => {
expect(tooltipDirective._isTooltipVisible()).toBe(false);
// On animation complete, should expect that the tooltip has been detached.
- flushMicrotasks();
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(tooltipDirective, false);
}));
@@ -141,17 +139,17 @@ describe('MatTooltip', () => {
tick(0);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
tooltipDirective._overlayRef!.detach();
tick(0);
fixture.detectChanges();
expect(tooltipDirective._isTooltipVisible()).toBe(false);
- flushMicrotasks();
assertTooltipInstance(tooltipDirective, false);
tooltipDirective.show();
tick(0);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
}));
@@ -173,7 +171,7 @@ describe('MatTooltip', () => {
it('should be able to override the default show and hide delays', fakeAsync(() => {
TestBed.resetTestingModule()
.configureTestingModule({
- imports: [MatTooltipModule, OverlayModule, NoopAnimationsModule],
+ imports: [MatTooltipModule, OverlayModule],
declarations: [BasicTooltipDemo],
providers: [
{
@@ -210,7 +208,7 @@ describe('MatTooltip', () => {
it('should be able to override the default position', fakeAsync(() => {
TestBed.resetTestingModule()
.configureTestingModule({
- imports: [MatTooltipModule, OverlayModule, NoopAnimationsModule],
+ imports: [MatTooltipModule, OverlayModule],
declarations: [TooltipDemoWithoutPositionBinding],
providers: [
{
@@ -419,16 +417,7 @@ describe('MatTooltip', () => {
tooltipDirective.hide(0);
fixture.detectChanges();
tick();
-
- // At this point the animation should be able to complete itself and trigger the
- // _animationDone function, but for unknown reasons in the test infrastructure,
- // this does not occur. Manually call the hook so the animation subscriptions get invoked.
- tooltipDirective._tooltipInstance!._animationDone({
- fromState: 'visible',
- toState: 'hidden',
- totalTime: 150,
- phaseName: 'done',
- } as AnimationEvent);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
expect(() => {
tooltipDirective.position = 'right';
@@ -442,7 +431,7 @@ describe('MatTooltip', () => {
tooltipDirective.show();
tick(0); // Tick for the show delay (default is 0)
- expect(tooltipDirective._tooltipInstance!._visibility).toBe('visible');
+ expect(tooltipDirective._tooltipInstance!.isVisible()).toBe(true);
fixture.detectChanges();
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
@@ -526,33 +515,21 @@ describe('MatTooltip', () => {
it('should not try to dispose the tooltip when destroyed and done hiding', fakeAsync(() => {
tooltipDirective.show();
fixture.detectChanges();
- tick(150);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
const tooltipDelay = 1000;
tooltipDirective.hide();
tick(tooltipDelay); // Change the tooltip state to hidden and trigger animation start
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
- // Store the tooltip instance, which will be set to null after the button is hidden.
- const tooltipInstance = tooltipDirective._tooltipInstance!;
fixture.componentInstance.showButton = false;
fixture.detectChanges();
-
- // At this point the animation should be able to complete itself and trigger the
- // _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._animationDone({
- fromState: 'visible',
- toState: 'hidden',
- totalTime: 150,
- phaseName: 'done',
- } as AnimationEvent);
}));
it('should complete the afterHidden stream when tooltip is destroyed', fakeAsync(() => {
tooltipDirective.show();
fixture.detectChanges();
- tick(150);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
const spy = jasmine.createSpy('complete spy');
const subscription = tooltipDirective
@@ -562,7 +539,7 @@ describe('MatTooltip', () => {
tooltipDirective.hide(0);
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
expect(spy).toHaveBeenCalled();
subscription.unsubscribe();
@@ -638,7 +615,7 @@ describe('MatTooltip', () => {
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
let tooltipWrapper = overlayContainerElement.querySelector(
'.cdk-overlay-connected-position-bounding-box',
@@ -650,13 +627,13 @@ describe('MatTooltip', () => {
tooltipDirective.hide(0);
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
dir.value = 'ltr';
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
tooltipWrapper = overlayContainerElement.querySelector(
'.cdk-overlay-connected-position-bounding-box',
@@ -677,7 +654,7 @@ describe('MatTooltip', () => {
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
@@ -685,7 +662,7 @@ describe('MatTooltip', () => {
document.body.click();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
fixture.detectChanges();
expect(tooltipDirective._isTooltipVisible()).toBe(false);
@@ -696,7 +673,7 @@ describe('MatTooltip', () => {
tooltipDirective.show();
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
@@ -704,7 +681,7 @@ describe('MatTooltip', () => {
dispatchFakeEvent(document.body, 'auxclick');
tick(0);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
fixture.detectChanges();
expect(tooltipDirective._isTooltipVisible()).toBe(false);
@@ -715,10 +692,10 @@ describe('MatTooltip', () => {
tooltipDirective.show();
tick(0);
fixture.detectChanges();
-
document.body.click();
fixture.detectChanges();
tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(overlayContainerElement.textContent).toContain(initialTooltipMessage);
}));
@@ -737,6 +714,7 @@ describe('MatTooltip', () => {
fixture.detectChanges();
tick(500);
fixture.detectChanges();
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
expect(tooltipDirective._isTooltipVisible()).toBe(false);
expect(overlayContainerElement.textContent).toBe('');
@@ -818,7 +796,7 @@ describe('MatTooltip', () => {
tick(0);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
const overlayRef = tooltipDirective._overlayRef!;
@@ -828,7 +806,7 @@ describe('MatTooltip', () => {
tick(0);
expect(tooltipDirective._isTooltipVisible()).toBe(true);
fixture.detectChanges();
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
expect(overlayRef.detach).not.toHaveBeenCalled();
}));
@@ -1147,12 +1125,12 @@ describe('MatTooltip', () => {
fixture.detectChanges();
// wait until animation has finished
- tick(500);
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
// Make sure tooltip is shown to the user and animation has finished
const tooltipElement = overlayContainerElement.querySelector('.mat-tooltip') as HTMLElement;
expect(tooltipElement instanceof HTMLElement).toBe(true);
- expect(tooltipElement.style.transform).toBe('scale(1)');
+ expect(tooltipElement.classList).toContain('mat-tooltip-show');
// After hide called, a timeout delay is created that will to hide the tooltip.
const tooltipDelay = 1000;
@@ -1165,7 +1143,7 @@ describe('MatTooltip', () => {
expect(tooltipDirective._isTooltipVisible()).toBe(false);
// On animation complete, should expect that the tooltip has been detached.
- flushMicrotasks();
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(tooltipDirective, false);
}));
@@ -1200,9 +1178,9 @@ describe('MatTooltip', () => {
assertTooltipInstance(fixture.componentInstance.tooltip, false);
- tick(250); // Finish the delay.
+ tick(500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true); // Finish the animation.
assertTooltipInstance(fixture.componentInstance.tooltip, true);
}));
@@ -1241,7 +1219,7 @@ describe('MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true); // Finish the animation.
assertTooltipInstance(fixture.componentInstance.tooltip, true);
dispatchFakeEvent(button, 'touchend');
@@ -1251,7 +1229,7 @@ describe('MatTooltip', () => {
tick(500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false); // Finish the exit animation.
assertTooltipInstance(fixture.componentInstance.tooltip, false);
}));
@@ -1265,7 +1243,7 @@ describe('MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true); // Finish the animation.
assertTooltipInstance(fixture.componentInstance.tooltip, true);
dispatchFakeEvent(button, 'touchcancel');
@@ -1275,7 +1253,7 @@ describe('MatTooltip', () => {
tick(500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false); // Finish the exit animation.
assertTooltipInstance(fixture.componentInstance.tooltip, false);
}));
@@ -1398,7 +1376,7 @@ describe('MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
assertTooltipInstance(fixture.componentInstance.tooltip, true);
// Simulate the pointer at the bottom/right of the page.
@@ -1412,7 +1390,7 @@ describe('MatTooltip', () => {
fixture.detectChanges();
tick(1500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(fixture.componentInstance.tooltip, false);
}));
@@ -1431,7 +1409,7 @@ describe('MatTooltip', () => {
fixture.detectChanges();
tick(500); // Finish the open delay.
fixture.detectChanges();
- tick(500); // Finish the animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, true);
assertTooltipInstance(fixture.componentInstance.tooltip, true);
// Simulate the pointer over the trigger.
@@ -1446,7 +1424,7 @@ describe('MatTooltip', () => {
fixture.detectChanges();
tick(1500); // Finish the delay.
fixture.detectChanges();
- tick(500); // Finish the exit animation.
+ finishCurrentTooltipAnimation(overlayContainerElement, false);
assertTooltipInstance(fixture.componentInstance.tooltip, true);
}));
@@ -1588,3 +1566,12 @@ function assertTooltipInstance(tooltip: MatTooltip, shouldExist: boolean): void
// happens due to the `_tooltipInstance` having a circular structure.
expect(!!tooltip._tooltipInstance).toBe(shouldExist);
}
+
+function finishCurrentTooltipAnimation(overlayContainer: HTMLElement, isVisible: boolean) {
+ const tooltip = overlayContainer.querySelector('.mat-tooltip')!;
+ const event = createFakeEvent('animationend');
+ Object.defineProperty(event, 'animationName', {
+ get: () => `mat-tooltip-${isVisible ? 'show' : 'hide'}`,
+ });
+ dispatchEvent(tooltip, event);
+}
diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts
index 4299911474c7..77c9a665acb4 100644
--- a/src/material/tooltip/tooltip.ts
+++ b/src/material/tooltip/tooltip.ts
@@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import {AnimationEvent} from '@angular/animations';
import {AriaDescriber, FocusMonitor} from '@angular/cdk/a11y';
import {Directionality} from '@angular/cdk/bidi';
import {
@@ -46,13 +45,13 @@ import {
ViewContainerRef,
ViewEncapsulation,
AfterViewInit,
+ ViewChild,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
+import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
import {Observable, Subject} from 'rxjs';
import {take, takeUntil} from 'rxjs/operators';
-import {matTooltipAnimations} from './tooltip-animations';
-
/** Possible positions for a tooltip. */
export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after';
@@ -860,13 +859,33 @@ export abstract class _TooltipComponentBase implements OnDestroy {
/** Amount of milliseconds to delay the closing sequence. */
_mouseLeaveHideDelay: number;
+ /** Whether animations are currently disabled. */
+ private _animationsDisabled: boolean;
+
+ /** Reference to the internal tooltip element. */
+ abstract _tooltip: ElementRef;
+
/** Whether interactions on the page should close the tooltip */
- private _closeOnInteraction: boolean = false;
+ private _closeOnInteraction = false;
+
+ /** Whether the tooltip is currently visible. */
+ private _isVisible = false;
/** Subject for notifying that the tooltip has been hidden from the view */
private readonly _onHide: Subject = new Subject();
- constructor(private _changeDetectorRef: ChangeDetectorRef) {}
+ /** Name of the show animation and the class that toggles it. */
+ protected abstract readonly _showAnimation: string;
+
+ /** Name of the hide animation and the class that toggles it. */
+ protected abstract readonly _hideAnimation: string;
+
+ constructor(
+ private _changeDetectorRef: ChangeDetectorRef,
+ @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string,
+ ) {
+ this._animationsDisabled = animationMode === 'NoopAnimations';
+ }
/**
* Shows the tooltip with an animation originating from the provided origin
@@ -876,16 +895,9 @@ export abstract class _TooltipComponentBase implements OnDestroy {
// Cancel the delayed hide if it is scheduled
clearTimeout(this._hideTimeoutId);
- // Body interactions should cancel the tooltip if there is a delay in showing.
- this._closeOnInteraction = true;
this._showTimeoutId = setTimeout(() => {
- this._visibility = 'visible';
+ this._toggleVisibility(true);
this._showTimeoutId = undefined;
- this._onShow();
-
- // Mark for check so if any parent component has set the
- // ChangeDetectionStrategy to OnPush it will be checked anyways
- this._markForCheck();
}, delay);
}
@@ -898,12 +910,8 @@ export abstract class _TooltipComponentBase implements OnDestroy {
clearTimeout(this._showTimeoutId);
this._hideTimeoutId = setTimeout(() => {
- this._visibility = 'hidden';
+ this._toggleVisibility(false);
this._hideTimeoutId = undefined;
-
- // Mark for check so if any parent component has set the
- // ChangeDetectionStrategy to OnPush it will be checked anyways
- this._markForCheck();
}, delay);
}
@@ -914,7 +922,7 @@ export abstract class _TooltipComponentBase implements OnDestroy {
/** Whether the tooltip is being displayed. */
isVisible(): boolean {
- return this._visibility === 'visible';
+ return this._isVisible;
}
ngOnDestroy() {
@@ -924,22 +932,6 @@ export abstract class _TooltipComponentBase implements OnDestroy {
this._triggerElement = null!;
}
- _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') {
- this._closeOnInteraction = true;
- }
- }
-
/**
* Interactions on the HTML body should close the tooltip immediately as defined in the
* material design spec.
@@ -972,6 +964,58 @@ export abstract class _TooltipComponentBase implements OnDestroy {
* in the mdc-tooltip, not here.
*/
protected _onShow(): void {}
+
+ /** Event listener dispatched when an animation on the tooltip finishes. */
+ _handleAnimationEnd({animationName}: AnimationEvent) {
+ if (animationName === this._showAnimation || animationName === this._hideAnimation) {
+ this._finalizeAnimation(animationName === this._showAnimation);
+ }
+ }
+
+ /** Handles the cleanup after an animation has finished. */
+ private _finalizeAnimation(toVisible: boolean) {
+ if (toVisible) {
+ this._closeOnInteraction = true;
+ } else if (!this.isVisible()) {
+ this._onHide.next();
+ }
+ }
+
+ /** Toggles the visibility of the tooltip element. */
+ private _toggleVisibility(isVisible: boolean) {
+ // We set the classes directly here ourselves so that toggling the tooltip state
+ // isn't bound by change detection. This allows us to hide it even if the
+ // view ref has been detached from the CD tree.
+ const tooltip = this._tooltip.nativeElement;
+ const showClass = this._showAnimation;
+ const hideClass = this._hideAnimation;
+ tooltip.classList.remove(isVisible ? hideClass : showClass);
+ tooltip.classList.add(isVisible ? showClass : hideClass);
+ this._isVisible = isVisible;
+
+ // It's common for internal apps to disable animations using `* { animation: none !important }`
+ // which can break the opening sequence. Try to detect such cases and work around them.
+ if (isVisible && !this._animationsDisabled && typeof getComputedStyle === 'function') {
+ const styles = getComputedStyle(tooltip);
+
+ // Use `getPropertyValue` to avoid issues with property renaming.
+ if (
+ styles.getPropertyValue('animation-duration') === '0s' ||
+ styles.getPropertyValue('animation-name') === 'none'
+ ) {
+ this._animationsDisabled = true;
+ }
+ }
+
+ if (isVisible) {
+ this._onShow();
+ }
+
+ if (this._animationsDisabled) {
+ tooltip.classList.add('_mat-animation-noopable');
+ this._finalizeAnimation(isVisible);
+ }
+ }
}
/**
@@ -984,11 +1028,10 @@ export abstract class _TooltipComponentBase implements OnDestroy {
styleUrls: ['tooltip.css'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
- animations: [matTooltipAnimations.tooltipState],
host: {
// Forces the element to have a layout in IE and Edge. This fixes issues where the element
// won't be rendered if the animations are disabled or there is no web animations polyfill.
- '[style.zoom]': '_visibility === "visible" ? 1 : null',
+ '[style.zoom]': 'isVisible() ? 1 : null',
'(mouseleave)': '_handleMouseLeave($event)',
'aria-hidden': 'true',
},
@@ -996,11 +1039,21 @@ export abstract class _TooltipComponentBase implements OnDestroy {
export class TooltipComponent extends _TooltipComponentBase {
/** Stream that emits whether the user has a handset-sized display. */
_isHandset: Observable = this._breakpointObserver.observe(Breakpoints.Handset);
+ _showAnimation = 'mat-tooltip-show';
+ _hideAnimation = 'mat-tooltip-hide';
+
+ @ViewChild('tooltip', {
+ // Use a static query here since we interact directly with
+ // the DOM which can happen before `ngAfterViewInit`.
+ static: true,
+ })
+ _tooltip: ElementRef;
constructor(
changeDetectorRef: ChangeDetectorRef,
private _breakpointObserver: BreakpointObserver,
+ @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string,
) {
- super(changeDetectorRef);
+ super(changeDetectorRef, animationMode);
}
}
diff --git a/tools/public_api_guard/material/tooltip-testing.md b/tools/public_api_guard/material/tooltip-testing.md
index 805d4f1efa9b..f40f0eefdca8 100644
--- a/tools/public_api_guard/material/tooltip-testing.md
+++ b/tools/public_api_guard/material/tooltip-testing.md
@@ -12,21 +12,33 @@ import { TestElement } from '@angular/cdk/testing';
// @public
export class MatTooltipHarness extends _MatTooltipHarnessBase {
+ // (undocumented)
+ protected _hiddenClass: string;
+ // (undocumented)
+ protected _hideAnimationName: string;
// (undocumented)
static hostSelector: string;
// (undocumented)
protected _optionalPanel: AsyncFactoryFn;
+ // (undocumented)
+ protected _showAnimationName: string;
static with(options?: TooltipHarnessFilters): HarnessPredicate;
}
// @public (undocumented)
export abstract class _MatTooltipHarnessBase extends ComponentHarness {
getTooltipText(): Promise;
+ // (undocumented)
+ protected abstract _hiddenClass: string;
hide(): Promise;
+ // (undocumented)
+ protected abstract _hideAnimationName: string;
isOpen(): Promise;
// (undocumented)
protected abstract _optionalPanel: AsyncFactoryFn;
show(): Promise;
+ // (undocumented)
+ protected abstract _showAnimationName: string;
}
// @public
diff --git a/tools/public_api_guard/material/tooltip.md b/tools/public_api_guard/material/tooltip.md
index 9b9dbcabe573..82bf91dddb88 100644
--- a/tools/public_api_guard/material/tooltip.md
+++ b/tools/public_api_guard/material/tooltip.md
@@ -5,7 +5,6 @@
```ts
import { AfterViewInit } from '@angular/core';
-import { AnimationEvent as AnimationEvent_2 } from '@angular/animations';
import { AnimationTriggerMetadata } from '@angular/animations';
import { AriaDescriber } from '@angular/cdk/a11y';
import { BooleanInput } from '@angular/cdk/coercion';
@@ -157,26 +156,30 @@ export const TOOLTIP_PANEL_CLASS = "mat-tooltip-panel";
// @public
export class TooltipComponent extends _TooltipComponentBase {
- constructor(changeDetectorRef: ChangeDetectorRef, _breakpointObserver: BreakpointObserver);
+ constructor(changeDetectorRef: ChangeDetectorRef, _breakpointObserver: BreakpointObserver, animationMode?: string);
+ // (undocumented)
+ _hideAnimation: string;
_isHandset: Observable;
// (undocumented)
+ _showAnimation: string;
+ // (undocumented)
+ _tooltip: ElementRef;
+ // (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration;
// (undocumented)
- static ɵfac: i0.ɵɵFactoryDeclaration;
+ static ɵfac: i0.ɵɵFactoryDeclaration;
}
// @public (undocumented)
export abstract class _TooltipComponentBase implements OnDestroy {
- constructor(_changeDetectorRef: ChangeDetectorRef);
+ constructor(_changeDetectorRef: ChangeDetectorRef, animationMode?: string);
afterHidden(): Observable;
- // (undocumented)
- _animationDone(event: AnimationEvent_2): void;
- // (undocumented)
- _animationStart(): void;
+ _handleAnimationEnd({ animationName }: AnimationEvent): void;
_handleBodyInteraction(): void;
// (undocumented)
_handleMouseLeave({ relatedTarget }: MouseEvent): void;
hide(delay: number): void;
+ protected abstract readonly _hideAnimation: string;
_hideTimeoutId: number | undefined;
isVisible(): boolean;
_markForCheck(): void;
@@ -186,7 +189,9 @@ export abstract class _TooltipComponentBase implements OnDestroy {
ngOnDestroy(): void;
protected _onShow(): void;
show(delay: number): void;
+ protected abstract readonly _showAnimation: string;
_showTimeoutId: number | undefined;
+ abstract _tooltip: ElementRef;
tooltipClass: string | string[] | Set | {
[key: string]: any;
};
@@ -195,7 +200,7 @@ export abstract class _TooltipComponentBase implements OnDestroy {
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<_TooltipComponentBase, never, never, {}, {}, never>;
// (undocumented)
- static ɵfac: i0.ɵɵFactoryDeclaration<_TooltipComponentBase, never>;
+ static ɵfac: i0.ɵɵFactoryDeclaration<_TooltipComponentBase, [null, { optional: true; }]>;
}
// @public