import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, ElementRef, inject, } from '@angular/core'; import { CommonModule } from '@angular/common'; import 'mayvio-ui/multiselect/css'; export interface MultiSelectOption { value: string | number; label: string; disabled?: boolean; } @Component({ selector: 'mv-multiselect', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
{{ placeholder }} {{ opt.label }}
{{ emptyText }}
{{ opt.label }}
`, }) export class MultiSelectComponent { @Input() options: MultiSelectOption[] = []; @Input() value: (string | number)[] = []; @Input() disabled = false; @Input() searchable = false; @Input() placeholder = 'Select options...'; @Input() emptyText = 'No options found.'; @Input() className = ''; @Output() selectionChange = new EventEmitter<(string | number)[]>(); isOpen = false; searchQuery = ''; private el = inject(ElementRef); private cdr = inject(ChangeDetectorRef); get selectedOptions(): MultiSelectOption[] { return this.options.filter((opt) => this.value.includes(opt.value)); } get filteredOptions(): MultiSelectOption[] { if (!this.searchable || !this.searchQuery) { return this.options; } const q = this.searchQuery.toLowerCase(); return this.options.filter((opt) => opt.label.toLowerCase().includes(q)); } isSelected(val: string | number): boolean { return this.value.includes(val); } toggleMenu() { if (this.disabled) return; this.isOpen = !this.isOpen; if (!this.isOpen) { this.searchQuery = ''; } this.cdr.markForCheck(); } toggleOption(val: string | number) { const newVal = this.value.includes(val) ? this.value.filter((v) => v !== val) : [...this.value, val]; this.value = newVal; this.selectionChange.emit(this.value); this.cdr.markForCheck(); } onOptionClick(event: Event, opt: MultiSelectOption) { event.stopPropagation(); if (!opt.disabled) { this.toggleOption(opt.value); } } removeTag(event: Event, val: string | number) { event.stopPropagation(); this.toggleOption(val); } onSearch(event: Event) { const input = event.target as HTMLInputElement; this.searchQuery = input.value; this.cdr.markForCheck(); } @HostListener('document:mousedown', ['$event.target']) onClickOutside(target: EventTarget | null) { if (this.isOpen && !this.el.nativeElement.contains(target as Node)) { this.isOpen = false; this.searchQuery = ''; this.cdr.markForCheck(); } } }