import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild } from '@angular/core';
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
@Component({
selector: 'app-dropdown',
template: `
`,
standalone: true,
})
export class DropdownComponent implements AfterViewInit, OnDestroy {
@ViewChild('targetEl') targetEl!: ElementRef;
@Input() triggerEl!: HTMLElement;
@Input() placement: any = 'bottom';
@Input() triggerType: 'click' | 'hover' = 'click';
@Input() offsetSkidding: number = 0;
@Input() offsetDistance: number = 10;
@Input() delay: number = 300;
@Input() ignoreClickOutsideClass: string | false = false;
@HostBinding('class.hidden') isHidden: boolean = true;
private popperInstance: PopperInstance | null = null;
isVisible: boolean = false;
private clickOutsideListener: any;
private hoverShowListener: any;
private hoverHideListener: any;
ngAfterViewInit() {
if (!this.triggerEl) {
console.error('Trigger element is not provided to the dropdown component.');
return;
}
this.initializePopper();
this.setupEventListeners();
}
ngOnDestroy() {
this.destroyPopper();
this.removeEventListeners();
}
private initializePopper() {
this.popperInstance = createPopper(this.triggerEl, this.targetEl.nativeElement, {
placement: this.placement,
modifiers: [
{
name: 'offset',
options: {
offset: [this.offsetSkidding, this.offsetDistance],
},
},
],
});
}
private setupEventListeners() {
if (this.triggerType === 'click') {
this.triggerEl.addEventListener('click', () => this.toggle());
} else if (this.triggerType === 'hover') {
this.hoverShowListener = () => this.show();
this.hoverHideListener = () => this.hide();
this.triggerEl.addEventListener('mouseenter', this.hoverShowListener);
this.triggerEl.addEventListener('mouseleave', this.hoverHideListener);
this.targetEl.nativeElement.addEventListener('mouseenter', this.hoverShowListener);
this.targetEl.nativeElement.addEventListener('mouseleave', this.hoverHideListener);
}
this.clickOutsideListener = (event: MouseEvent) => this.handleClickOutside(event);
document.addEventListener('click', this.clickOutsideListener);
}
private removeEventListeners() {
if (this.triggerType === 'click') {
this.triggerEl.removeEventListener('click', () => this.toggle());
} else if (this.triggerType === 'hover') {
this.triggerEl.removeEventListener('mouseenter', this.hoverShowListener);
this.triggerEl.removeEventListener('mouseleave', this.hoverHideListener);
this.targetEl.nativeElement.removeEventListener('mouseenter', this.hoverShowListener);
this.targetEl.nativeElement.removeEventListener('mouseleave', this.hoverHideListener);
}
document.removeEventListener('click', this.clickOutsideListener);
}
toggle() {
this.isVisible ? this.hide() : this.show();
}
show() {
this.isVisible = true;
this.isHidden = false;
this.targetEl.nativeElement.classList.remove('hidden');
this.popperInstance?.update();
}
hide() {
this.isVisible = false;
this.isHidden = true;
this.targetEl.nativeElement.classList.add('hidden');
}
private handleClickOutside(event: MouseEvent) {
if (!this.isVisible) return;
const clickedElement = event.target as HTMLElement;
if (this.ignoreClickOutsideClass) {
const ignoredElements = document.querySelectorAll(`.${this.ignoreClickOutsideClass}`);
const arr = Array.from(ignoredElements);
for (const el of arr) {
if (el.contains(clickedElement)) return;
}
}
if (!this.targetEl.nativeElement.contains(clickedElement) && !this.triggerEl.contains(clickedElement)) {
this.hide();
}
}
private destroyPopper() {
if (this.popperInstance) {
this.popperInstance.destroy();
this.popperInstance = null;
}
}
}