import { Directive, Input, ElementRef, HostListener, Renderer2 } from '@angular/core'; import {WindowService} from '../services/window.service'; import {DocumentService} from '../services/document.service'; @Directive({ selector: '[appTooltip]' }) export class TooltipDirective { @Input() appTooltipTitle: string | undefined; @Input() appTooltip: string | undefined; @Input() placement: 'top' | 'bottom' | 'left' | 'right' | 'cursor' = 'bottom'; @Input() tooltipClasses: string | undefined; @Input() delay = 0; tooltip: HTMLElement | undefined; offset = 0; private timeout: number | undefined; private window: Window; private document: Document; private mousePosition: MouseEvent | undefined; constructor(protected el: ElementRef, private renderer: Renderer2, private windowService: WindowService, private documentService: DocumentService) { this.window = windowService.nativeWindow(); this.document = documentService.nativeDocument(); } @HostListener('mouseenter') onMouseEnter(): void { if (!this.tooltip) { this.timeout = this.window.setTimeout(() => { this.show(); }, this.delay); } } @HostListener('mousemove', ['$event']) onMouseMove(event: MouseEvent): void { if (!this.tooltip && this.placement === 'cursor') { this.mousePosition = event; } } @HostListener('mouseleave') onMouseLeave(): void { if (this.timeout) { clearTimeout(this.timeout); } if (this.tooltip) { this.hide(); } } get tooltipTitle(): string | undefined { return this.appTooltipTitle; } get tooltipText(): string | undefined { return this.appTooltip; } public show(): void { this.create(); this.setPosition(); this.renderer.addClass(this.tooltip, 'ng-tooltip-show'); } public hide(): void { if (this.tooltip) { this.renderer.removeClass(this.tooltip, 'ng-tooltip-show'); this.window.setTimeout(() => { if (this.tooltip) { this.renderer.removeChild(this.documentService.nativeDocument().body, this.tooltip); this.tooltip = undefined; } }, 500); } } create(): void { this.tooltip = this.renderer.createElement('div'); const tooltipHeader = this.renderer.createElement('h4'); const tooltipContent = this.renderer.createElement('span'); if (this.tooltipTitle != null) { this.renderer.appendChild( tooltipHeader, this.renderer.createText(this.tooltipTitle) ); } if (this.tooltipText != null) { this.renderer.appendChild( tooltipContent, this.renderer.createText(this.tooltipText) ); } this.renderer.appendChild(this.tooltip, tooltipHeader); this.renderer.appendChild(this.tooltip, tooltipContent); this.renderer.appendChild(this.documentService.nativeDocument().body, this.tooltip); this.renderer.addClass(this.tooltip, 'ng-tooltip'); this.renderer.addClass(this.tooltip, `ng-tooltip-${this.placement}`); this.renderer.setStyle(this.tooltip, '-webkit-transition', `opacity ${this.delay}ms`); this.renderer.setStyle(this.tooltip, '-moz-transition', `opacity ${this.delay}ms`); this.renderer.setStyle(this.tooltip, '-o-transition', `opacity ${this.delay}ms`); this.renderer.setStyle(this.tooltip, 'transition', `opacity ${this.delay}ms`); if (!!this.tooltipClasses) { this.renderer.addClass(this.tooltip, this.tooltipClasses); } } setPosition(): void { if (this.tooltip === undefined) { return; } const hostPos = this.el.nativeElement.getBoundingClientRect(); const tooltipPos = this.tooltip.getBoundingClientRect(); const scrollPos = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; let top; let left; switch (this.placement || 'bottom') { case 'top': top = hostPos.top - tooltipPos.height - this.offset; left = hostPos.left + (hostPos.width - tooltipPos.width) / 2 - (tooltipPos.width / 2); break; case 'left': top = hostPos.top + (hostPos.height - tooltipPos.height) / 2; left = hostPos.left - tooltipPos.width - this.offset; break; case 'right': top = hostPos.top + (hostPos.height - tooltipPos.height) / 2; left = hostPos.right + this.offset; break; case 'cursor': top = (this.mousePosition) ? this.mousePosition.clientY + this.offset : hostPos.bottom + this.offset; left = (this.mousePosition) ? this.mousePosition.clientX + this.offset : hostPos.left + (hostPos.width - tooltipPos.width) / 2 - (tooltipPos.width / 2); break; case 'bottom': top = hostPos.bottom + this.offset; left = hostPos.left + (hostPos.width - tooltipPos.width) / 2 - (tooltipPos.width / 2); break; default: console.error(`TooltipDirective does not know how to handle placement '${this.placement}'`); } this.renderer.setStyle(this.tooltip, 'position', 'absolute'); this.renderer.setStyle(this.tooltip, 'top', `${top + scrollPos}px`); this.renderer.setStyle(this.tooltip, 'left', `${left}px`); } }