Skip to content

feat(tooltip): adds animations for tooltip #923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/components/tooltip/tooltip.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
7 changes: 6 additions & 1 deletion src/components/tooltip/tooltip.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
]);
}));
Expand Down
64 changes: 55 additions & 9 deletions src/components/tooltip/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class MdTooltip {
}

private _overlayRef: OverlayRef;
private _tooltipContainer: TooltipContainer;

constructor(private _overlay: Overlay, private _elementRef: ElementRef,
private _viewContainerRef: ViewContainerRef,
Expand Down Expand Up @@ -139,10 +140,11 @@ export class MdTooltip {
show(): Promise<any> {
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<TooltipComponent>) => {
ref.instance.message = this.message;
promise.then((ref: ComponentRef<TooltipContainer>) => {
this._tooltipContainer = ref.instance;
ref.instance.tooltip = this;
this._updatePosition();
});
return promise;
Expand All @@ -153,6 +155,19 @@ export class MdTooltip {
* Hides the tooltip and returns a promise that will resolve when the tooltip is hidden
*/
hide(): Promise<any> {
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<any> {
if (this.visible && this._overlayRef && this._overlayRef.hasAttached()) {
this.visible = false;
return this._overlayRef.detach();
Expand Down Expand Up @@ -184,11 +199,42 @@ export class MdTooltip {
@Component({
moduleId: module.id,
selector: 'md-tooltip-component',
template: `<div class="md-tooltip">{{message}}</div>`,
template: `<div class="md-tooltip {{positionClass}}"
[class.md-tooltip-initialized]="initialized"
[class.md-tooltip-visible]="visible">{{message}}</div>`,
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<any> {
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 */
Expand All @@ -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 { }