Files
bizmatch-project/bizmatch-client/src/app/components/tooltip/tooltip.component.ts

168 lines
5.6 KiB
TypeScript

import { CommonModule } from '@angular/common';
import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-tooltip',
standalone: true,
imports: [CommonModule],
templateUrl: './tooltip.component.html',
})
export class TooltipComponent implements AfterViewInit, OnDestroy, OnChanges {
@Input() id: string;
@Input() text: string;
@Input() isVisible: boolean = false;
@Input() position: 'top' | 'right' | 'bottom' | 'left' = 'top';
private tooltipElement: HTMLElement | null = null;
private arrowElement: HTMLElement | null = null;
private resizeObserver: ResizeObserver | null = null;
private parentElement: HTMLElement | null = null;
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit() {
this.tooltipElement = document.getElementById(this.id);
this.parentElement = this.elementRef.nativeElement.parentElement;
if (this.tooltipElement && this.parentElement) {
// Create arrow element
this.arrowElement = this.renderer.createElement('div');
this.renderer.addClass(this.arrowElement, 'tooltip-arrow');
this.renderer.appendChild(this.tooltipElement, this.arrowElement);
// Setup resize observer
this.setupResizeObserver();
// Initial positioning
this.updatePosition();
// Initial visibility
this.updateTooltipVisibility();
}
}
ngOnChanges(changes: SimpleChanges) {
if (changes['isVisible'] && this.tooltipElement) {
this.updateTooltipVisibility();
}
if ((changes['position'] || changes['isVisible']) && this.isVisible) {
setTimeout(() => this.updatePosition(), 0);
}
}
ngOnDestroy() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
}
private setupResizeObserver() {
if (window.ResizeObserver && this.parentElement) {
this.resizeObserver = new ResizeObserver(() => {
if (this.isVisible) {
this.updatePosition();
}
});
this.resizeObserver.observe(this.parentElement);
if (this.tooltipElement) {
this.resizeObserver.observe(this.tooltipElement);
}
}
}
private updateTooltipVisibility() {
if (!this.tooltipElement) return;
if (this.isVisible) {
this.renderer.removeClass(this.tooltipElement, 'invisible');
this.renderer.removeClass(this.tooltipElement, 'opacity-0');
this.renderer.addClass(this.tooltipElement, 'visible');
this.renderer.addClass(this.tooltipElement, 'opacity-100');
this.updatePosition();
} else {
this.renderer.removeClass(this.tooltipElement, 'visible');
this.renderer.removeClass(this.tooltipElement, 'opacity-100');
this.renderer.addClass(this.tooltipElement, 'invisible');
this.renderer.addClass(this.tooltipElement, 'opacity-0');
}
}
private updatePosition() {
if (!this.tooltipElement || !this.parentElement || !this.arrowElement) return;
const parentRect = this.parentElement.getBoundingClientRect();
const tooltipRect = this.tooltipElement.getBoundingClientRect();
// Reset any previous positioning
this.renderer.removeStyle(this.tooltipElement, 'top');
this.renderer.removeStyle(this.tooltipElement, 'right');
this.renderer.removeStyle(this.tooltipElement, 'bottom');
this.renderer.removeStyle(this.tooltipElement, 'left');
// Reset arrow classes
this.renderer.removeClass(this.arrowElement, 'arrow-top');
this.renderer.removeClass(this.arrowElement, 'arrow-right');
this.renderer.removeClass(this.arrowElement, 'arrow-bottom');
this.renderer.removeClass(this.arrowElement, 'arrow-left');
let top: number = 0;
let left: number = 0;
switch (this.position) {
case 'top':
top = -tooltipRect.height - 8;
left = (parentRect.width - tooltipRect.width) / 2;
this.renderer.addClass(this.arrowElement, 'arrow-bottom');
break;
case 'right':
top = (parentRect.height - tooltipRect.height) / 2;
left = parentRect.width + 8;
this.renderer.addClass(this.arrowElement, 'arrow-left');
break;
case 'bottom':
top = parentRect.height + 8;
left = (parentRect.width - tooltipRect.width) / 2;
this.renderer.addClass(this.arrowElement, 'arrow-top');
break;
case 'left':
top = (parentRect.height - tooltipRect.height) / 2;
left = -tooltipRect.width - 8;
this.renderer.addClass(this.arrowElement, 'arrow-right');
break;
}
// Apply positioning
this.renderer.setStyle(this.tooltipElement, 'top', `${top}px`);
this.renderer.setStyle(this.tooltipElement, 'left', `${left}px`);
// Make sure the tooltip stays within viewport
this.adjustToViewport();
}
private adjustToViewport() {
if (!this.tooltipElement) return;
const tooltipRect = this.tooltipElement.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Adjust horizontal position
if (tooltipRect.left < 0) {
this.renderer.setStyle(this.tooltipElement, 'left', '0px');
} else if (tooltipRect.right > viewportWidth) {
const newLeft = Math.max(0, viewportWidth - tooltipRect.width);
this.renderer.setStyle(this.tooltipElement, 'left', `${newLeft}px`);
}
// Adjust vertical position
if (tooltipRect.top < 0) {
this.renderer.setStyle(this.tooltipElement, 'top', '0px');
} else if (tooltipRect.bottom > viewportHeight) {
const newTop = Math.max(0, viewportHeight - tooltipRect.height);
this.renderer.setStyle(this.tooltipElement, 'top', `${newTop}px`);
}
}
}