// autocomplete.component.ts import { Component, Input, OnChanges, OnInit, SimpleChanges, Output, EventEmitter } from '@angular/core'; import { Observable, map, startWith, of } from 'rxjs'; import { Search, CircleAlert } from 'lucide-angular'; @Component({ selector: 'kit-autocomplete', templateUrl: './autocomplete.component.html', }) export class AutocompleteComponent implements OnInit, OnChanges { @Input() options?: any[] = []; @Input() placeholderText: string = ''; @Input() control?: any; @Input() label: string = ''; @Input() minlength?: number; @Input() maxlength?: number; @Input() messageErrorCustom: string = ''; @Input() multiSelectMode: boolean = false; @Input() deselectOption?: any; @Input() allowDeselect: boolean = true; @Input() descriptionTooltip: string = ''; @Input() enableFontPreview: boolean = false; @Output() optionSelected = new EventEmitter(); filteredOptions: Observable | undefined; selectedOptions: Set = new Set(); readonly searchI = Search; readonly infoCircle = CircleAlert; ngOnInit() { this.setupFilteredOptions(); } ngOnChanges(changes: SimpleChanges): void { if (changes['options']) { this.setupFilteredOptions(); } if (changes['multiSelectMode']) { this.selectedOptions.clear(); } if (changes['deselectOption'] && changes['deselectOption'].currentValue && this.multiSelectMode) { const optionToDeselect = changes['deselectOption'].currentValue; this.deselectFromParent(optionToDeselect); } } private setupFilteredOptions(): void { if (!this.control) { this.filteredOptions = of(this.options?.slice() || []); return; } this.filteredOptions = this.control.valueChanges.pipe( startWith(''), map((value: any) => { if (!value || value === '' || this.multiSelectMode) { return this.options?.slice() || []; } const label = typeof value === 'string' ? value : value?.label; return label ? this._filter(label as string) : this.options?.slice() || []; }), ); } getDisplayValue(option: any): string { if (this.multiSelectMode) { return ''; } return option && option.label ? option.label : ''; } isOptionSelected(option: any): boolean { if (!this.multiSelectMode) return false; return Array.from(this.selectedOptions).some(selected => this.compareOptions(selected, option) ); } private compareOptions(option1: any, option2: any): boolean { if (option1.id && option2.id) { return option1.id === option2.id; } if (option1.value && option2.value) { return option1.value === option2.value; } return option1.label === option2.label; } private deselectFromParent(optionToDeselect: any): void { const wasSelected = this.isOptionSelected(optionToDeselect); if (wasSelected) { this.selectedOptions.forEach(selected => { if (this.compareOptions(selected, optionToDeselect)) { this.selectedOptions.delete(selected); } }); this.optionSelected.emit({ option: optionToDeselect, selectedOptions: Array.from(this.selectedOptions), action: 'removed' }); } } onOptionSelected(option: any): void { if (this.multiSelectMode) { const isCurrentlySelected = this.isOptionSelected(option); if (isCurrentlySelected && !this.allowDeselect) { return; } if (isCurrentlySelected) { this.selectedOptions.forEach(selected => { if (this.compareOptions(selected, option)) { this.selectedOptions.delete(selected); } }); this.optionSelected.emit({ option: option, selectedOptions: Array.from(this.selectedOptions), action: 'removed' }); } else { this.selectedOptions.add(option); this.control?.setValue(''); this.optionSelected.emit({ option: option, selectedOptions: Array.from(this.selectedOptions), action: 'added' }); } } else { this.selectedOptions.clear(); this.optionSelected.emit(option); } } private _filter(label: string): any[] { const filterValue = label.toLowerCase(); return this.options?.filter(option => option.label.toLowerCase().includes(filterValue)) || []; } isControlRequired(): boolean { if(this.control && this.control.validator) { const validator = this.control.validator({} as any); return !!(validator && validator.required); } return false } }