import { Component, ElementRef, HostListener, Input, forwardRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'app-select', standalone: true, imports: [CommonModule], templateUrl: './custom-select.html', styleUrls: ['./custom-select.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CustomSelectComponent), multi: true, }, ], }) export class CustomSelectComponent implements ControlValueAccessor { @Input() options: any[] = []; @Input() placeholder = 'Selecione uma opção'; @Input() labelKey = 'label'; @Input() valueKey = 'value'; @Input() size: 'sm' | 'md' = 'md'; @Input() disabled = false; isOpen = false; value: any = null; private onChange: (value: any) => void = () => {}; private onTouched: () => void = () => {}; constructor(private host: ElementRef) {} writeValue(value: any): void { this.value = value; } registerOnChange(fn: (value: any) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) this.isOpen = false; } get displayLabel(): string { const selected = this.findOption(this.value); if (selected !== undefined) return this.getOptionLabel(selected); if (this.value === null || this.value === undefined || this.value === '') return this.placeholder; return String(this.value); } get hasValue(): boolean { return !(this.value === null || this.value === undefined || this.value === ''); } toggle(): void { if (this.disabled) return; this.isOpen = !this.isOpen; } close(): void { this.isOpen = false; } selectOption(option: any): void { if (this.disabled) return; const value = this.getOptionValue(option); this.value = value; this.onChange(value); this.onTouched(); this.close(); } isSelected(option: any): boolean { return this.getOptionValue(option) === this.value; } trackByValue = (_: number, option: any) => this.getOptionValue(option); private getOptionValue(option: any): any { if (option && typeof option === 'object') { return option[this.valueKey]; } return option; } getOptionLabel(option: any): string { if (option && typeof option === 'object') { const v = option[this.labelKey]; return v === undefined || v === null ? '' : String(v); } return option === undefined || option === null ? '' : String(option); } private findOption(value: any): any { return (this.options || []).find((o) => this.getOptionValue(o) === value); } @HostListener('document:click', ['$event']) onDocumentClick(event: MouseEvent): void { if (!this.isOpen) return; const target = event.target as Node | null; if (target && this.host.nativeElement.contains(target)) return; this.close(); } @HostListener('document:keydown.escape') onEsc(): void { if (this.isOpen) this.close(); } }