139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild, PLATFORM_ID, inject } from '@angular/core';
|
|
import { isPlatformBrowser } from '@angular/common';
|
|
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
|
|
|
|
@Component({
|
|
selector: 'app-dropdown',
|
|
template: `
|
|
<div #targetEl [class.hidden]="!isVisible" class="z-50">
|
|
<ng-content></ng-content>
|
|
</div>
|
|
`,
|
|
standalone: true,
|
|
})
|
|
export class DropdownComponent implements AfterViewInit, OnDestroy {
|
|
@ViewChild('targetEl') targetEl!: ElementRef<HTMLElement>;
|
|
@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 platformId = inject(PLATFORM_ID);
|
|
private isBrowser = isPlatformBrowser(this.platformId);
|
|
private popperInstance: PopperInstance | null = null;
|
|
isVisible: boolean = false;
|
|
private clickOutsideListener: any;
|
|
private hoverShowListener: any;
|
|
private hoverHideListener: any;
|
|
|
|
ngAfterViewInit() {
|
|
if (!this.isBrowser) return;
|
|
|
|
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.isBrowser) return;
|
|
|
|
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.isBrowser) return;
|
|
|
|
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 || !this.isBrowser) 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;
|
|
}
|
|
}
|
|
}
|