// Based on ng2-tooltip // https://www.npmjs.com/package/ng2-tooltip import { Component, Input, AfterViewInit, ElementRef, ChangeDetectorRef } from '@angular/core'; /** * En komponent för att visa content i tooltip. */ // Todo importera styles enligt wiki /vile @Component({ selector: 'fb-tooltip-content', templateUrl: './fbTooltip.component.html' }) export class FbTooltipComponent implements AfterViewInit { @Input() hostElement: HTMLElement; @Input() fbTooltip: string; @Input() placement: 'top' | 'bottom' | 'left' | 'right' = 'top'; @Input() animation: boolean = true; top: number; left: number; isIn: boolean = false; isFade: boolean = false; private readonly minimumNum: number = -100000; constructor( public element: ElementRef, private readonly cdr: ChangeDetectorRef ) { if (this.top === null || this.top === undefined) { this.top = this.minimumNum; } if (this.left === null || this.left === undefined) { this.left = this.minimumNum; } } ngAfterViewInit(): void { this.show(); this.cdr.detectChanges(); } show(): void { if (!this.hostElement) { return; } const p: {top: number, left: number} = this.positionElements(this.hostElement, this.element.nativeElement.children[0], this.placement); this.top = p.top; this.left = p.left; this.isIn = true; if (this.animation) { this.isFade = true; } } hide(): void { this.top = this.minimumNum; this.left = this.minimumNum; this.isIn = true; if (this.animation) { this.isFade = false; } } private positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } { const positionStrParts: string[] = positionStr.split('-'); const pos0: string = positionStrParts[0]; const pos1: string = positionStrParts[1] || 'center'; const hostElPos: {width: number, height: number, top: number, left: number} = appendToBody ? this.offset(hostEl) : this.position(hostEl); const targetElWidth: number = targetEl.offsetWidth; const targetElHeight: number = targetEl.offsetHeight; const two: number = 2; const shiftWidth: any = { center: (): number => hostElPos.left + hostElPos.width / two - targetElWidth / two, left: (): number => hostElPos.left, right: (): number => hostElPos.left + hostElPos.width }; const shiftHeight: any = { center: (): number => hostElPos.top + hostElPos.height / two - targetElHeight / two, top: (): number => hostElPos.top, bottom: (): number => hostElPos.top + hostElPos.height }; let targetElPos: { top: number, left: number }; switch (pos0) { case 'right': targetElPos = { top: shiftHeight[pos1](), left: shiftWidth[pos0]() }; break; case 'left': targetElPos = { top: shiftHeight[pos1](), left: hostElPos.left - targetElWidth }; break; case 'bottom': targetElPos = { top: shiftHeight[pos0](), left: shiftWidth[pos1]() }; break; default: targetElPos = { top: hostElPos.top - targetElHeight, left: shiftWidth[pos1]() }; break; } return targetElPos; } private position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } { let offsetParentBcr: {top: number, left: number} = { top: 0, left: 0 }; const elBcr: {width: number, height: number, top: number, left: number} = this.offset(nativeEl); const offsetParentEl: any = this.parentOffsetEl(nativeEl); if (offsetParentEl !== window.document) { offsetParentBcr = this.offset(offsetParentEl); offsetParentBcr.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; offsetParentBcr.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; } const boundingClientRect: ClientRect = nativeEl.getBoundingClientRect(); return { width: boundingClientRect.width || nativeEl.offsetWidth, height: boundingClientRect.height || nativeEl.offsetHeight, top: elBcr.top - offsetParentBcr.top, left: elBcr.left - offsetParentBcr.left }; } private offset(nativeEl: any): { width: number, height: number, top: number, left: number } { const boundingClientRect: any = nativeEl.getBoundingClientRect(); return { width: boundingClientRect.width || nativeEl.offsetWidth, height: boundingClientRect.height || nativeEl.offsetHeight, top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) }; } private getStyle(nativeEl: HTMLElement, cssProp: string): string { if ((nativeEl as any).currentStyle) { // IE return (nativeEl as any).currentStyle[cssProp]; } if (window.getComputedStyle) { return (window.getComputedStyle(nativeEl) as any)[cssProp]; } // finally try and get inline style return (nativeEl.style as any)[cssProp]; } private isStaticPositioned(nativeEl: HTMLElement): boolean { return (this.getStyle(nativeEl, 'position') || 'static') === 'static'; } private parentOffsetEl(nativeEl: HTMLElement): any { let offsetParent: any = nativeEl.offsetParent || window.document; while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { offsetParent = offsetParent.offsetParent; } return offsetParent || window.document; } }