import { ComponentFactoryResolver, ComponentRef, Directive, HostListener, Inject, Input, OnChanges, OnDestroy, OnInit, ViewContainerRef, } from '@angular/core'; import { DOCUMENT, } from '@angular/common'; import { TooltipComponent, } from './tooltip.component'; import { BrowserDetectorService, } from './../../../helpers/services/index'; import { TooltipVariant } from '../../../models/tooltip.types'; @Directive({ selector: '[tooltip]', }) export class TooltipDirective implements OnInit, OnChanges, OnDestroy { @Input() public tooltip: string; @Input() public tooltipTitle: string; @Input() public tooltipWidth: number; @Input() public onClickVisibleTime = 1500; @Input() public tooltipVariant = TooltipVariant.Default; public tooltipComponent: ComponentRef; public onClickTriggered = false; constructor( private _viewContainerRef: ViewContainerRef, private _componentFactoryResolver: ComponentFactoryResolver, @Inject(DOCUMENT) private _document: Document, private _browserDetectorService: BrowserDetectorService, ) { } public ngOnInit() { const componentFactory = this._componentFactoryResolver.resolveComponentFactory( TooltipComponent, ); this.tooltipComponent = this._viewContainerRef.createComponent( componentFactory, ); this._document.body.appendChild( this.tooltipComponent.location.nativeElement, ); this.initTooltipComponentVariables(); } public ngOnChanges() { this.initTooltipComponentVariables(); } public ngOnDestroy() { this.tooltipComponent.destroy(); } public initTooltipComponentVariables() { if (this.tooltipComponent) { this.tooltipComponent.instance.text = this.tooltip; this.tooltipComponent.instance.tooltipTitle = this.tooltipTitle; this.tooltipComponent.instance.tooltipVariant = this.tooltipVariant; this.tooltipComponent.instance.width = this.tooltipWidth; this.tooltipComponent.instance.changeDetectorRef.markForCheck(); } } public updateTooltipLocation() { const containerBoundingRect = this._viewContainerRef.element .nativeElement.getBoundingClientRect(); const bodyBoundingRect = this._document.body.getBoundingClientRect(); const parentElementWidth = containerBoundingRect.width; let expectedLeft = containerBoundingRect.left + parentElementWidth - bodyBoundingRect.left; const shouldBeRightSide = this.shouldBeRightSide( expectedLeft, ); this.tooltipComponent.instance.isToRight = shouldBeRightSide; if (!shouldBeRightSide) { expectedLeft -= parentElementWidth; } this.tooltipComponent.instance.isBelow = this.shouldBeBelow(); this.tooltipComponent.location.nativeElement .style.display = 'block'; this.tooltipComponent.location.nativeElement .style.position = 'absolute'; this.tooltipComponent.location.nativeElement .style.left = expectedLeft + 'px'; this.tooltipComponent.location.nativeElement .style.top = containerBoundingRect.top + containerBoundingRect.height / 2 - bodyBoundingRect.top + 'px'; } public shouldBeRightSide( startingLeft: number, ) { const bodyWidth = this._document.body.offsetWidth; const tooltipRightEdge = startingLeft + this.tooltipWidth; return tooltipRightEdge < bodyWidth; } public shouldBeBelow() { const parentPositionInView = this._viewContainerRef.element.nativeElement .getBoundingClientRect().top; const viewHeight = this._document.documentElement.clientHeight; return parentPositionInView < viewHeight / 2; } public changeTooltipVisibility(visible: boolean) { if (visible) { this.updateTooltipLocation(); } this.tooltipComponent.instance.visible = visible; this.tooltipComponent.instance.changeDetectorRef.markForCheck(); } @HostListener('mouseover') public onHover() { if (!this.onClickTriggered && !this._browserDetectorService.isIOS()) { this.changeTooltipVisibility(true); } } @HostListener('mouseout') public onMouseOut() { if (!this.onClickTriggered) { this.changeTooltipVisibility(false); } } @HostListener('click') public onClick() { this.changeTooltipVisibility(true); this.onClickTriggered = true; setTimeout(() => { this.changeTooltipVisibility(false); this.onClickTriggered = false; }, this.onClickVisibleTime); } }