import { coerceCssPixelValue } from '@angular/cdk/coercion' ; import { AfterViewInit , ChangeDetectionStrategy , ChangeDetectorRef , Component , ContentChild , ElementRef , EventEmitter , forwardRef , Input , OnChanges , OnDestroy , Output , SimpleChanges , TemplateRef , ViewChild } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { Subject } from 'rxjs' ; import { MenuTriggerDirective } from '../menu/menu-trigger/menu-trigger.directive' ; @ Component ( { selector : 'ux-input-dropdown' , templateUrl : './input-dropdown.component.html' , changeDetection : ChangeDetectionStrategy . OnPush , providers : [ { provide : NG_VALUE_ACCESSOR , multi : true , useExisting : forwardRef ( ( ) => InputDropdownComponent ) } ] , host : { '[class.ux-select-disabled]' : 'disabled' } } ) export class InputDropdownComponent < T > implements ControlValueAccessor , AfterViewInit , OnChanges , OnDestroy { /** Define the selected item */ @ Input ( ) selected : T ; /** Filter text */ @ Input ( ) filter : string = '' ; /** Hide the filter input */ @ Input ( ) hideFilter : boolean ; /** Define the max height of the dropdown */ @ Input ( ) set maxHeight ( value ) { this . _maxHeight = coerceCssPixelValue ( value ) ; } /** Controls the disabled state of the input-dropdown. */ @ Input ( ) disabled : boolean = false ; /** Define if null values are allowed */ @ Input ( ) allowNull : boolean ; /** Define the placeholder for the filter input */ @ Input ( ) placeholder : string = 'Type to filter...' ; /** Aria label of the filter field. If not specified, the placeholder will be used. */ @ Input ( 'aria-label' ) ariaLabel : string = '' ; /** Emit when the selected item is changed */ @ Output ( ) selectedChange = new EventEmitter < T > ( ) ; /** Emit when the filter text is changed */ @ Output ( ) filterChange = new EventEmitter < string > ( ) ; /** Emits when `dropdownOpen` changes. */ @ Output ( ) dropdownOpenChange = new EventEmitter < boolean > ( ) ; /** The status of the dropdown. */ @ Input ( ) dropdownOpen : boolean = false ; /** Access the display content template if specified */ @ ContentChild ( 'displayContent' , { static : false } ) displayContentRef : TemplateRef < void > ; /** Access the dropdown menu trigger directive */ @ ViewChild ( MenuTriggerDirective , { static : false } ) menuTrigger : MenuTriggerDirective ; /** Access the filter text input element */ @ ViewChild ( 'filterInput' , { static : false } ) filterInputElement : ElementRef < HTMLInputElement > ; /** Store the max height */ _maxHeight : string ; /** Store the change callback provided by Angular Forms */ onChange : ( _ : T ) => void = ( ) => { } ; /** Store the touched callback provided by Angular Forms */ onTouched : ( ) => void = ( ) => { } ; /** Unsubscribe from all observables on component destroy */ private readonly _onDestroy$ = new Subject < void > ( ) ; constructor ( private readonly _changeDetector ) { } ngOnChanges ( changes ) { if ( changes . dropdownOpen && ! changes . dropdownOpen . firstChange && changes . dropdownOpen . currentValue !== changes . dropdownOpen . previousValue ) { changes . dropdownOpen . currentValue ? this . menuTrigger . openMenu ( ) : this . menuTrigger . closeMenu ( ) ; } if ( changes . selected ) { if ( this . menuTrigger && ! changes . selected . firstChange ) { this . menuTrigger . closeMenu ( ) ; } this . resetFilter ( ) ; this . selectedChange . emit ( changes . selected . currentValue ) ; this . onChange ( changes . selected . currentValue ) ; this . onTouched ( ) ; } } ngAfterViewInit ( ) { if ( this . dropdownOpen ) { Promise . resolve ( ) . then ( ( ) => this . menuTrigger . openMenu ( ) ) ; } this . _changeDetector . detectChanges ( ) ; } ngOnDestroy ( ) { this . _onDestroy$ . next ( ) ; this . _onDestroy$ . complete ( ) ; } resetFilter ( ) { this . filter = '' ; this . filterChange . emit ( this . filter ) ; this . _focusFilter ( ) ; } registerOnChange ( onChange ) { this . onChange = onChange ; } registerOnTouched ( onTouched ) { this . onTouched = onTouched ; } writeValue ( value ) { this . selected = value ; this . _changeDetector . markForCheck ( ) ; } resetValue ( event ) { if ( this . disabled ) { return ; } this . writeValue ( undefined ) ; this . selectedChange . emit ( undefined ) ; event . stopPropagation ( ) ; } setDisabledState ( isDisabled ) { this . disabled = isDisabled ; this . _changeDetector . markForCheck ( ) ; } onMenuOpen ( ) { if ( this . dropdownOpen !== true ) { this . dropdownOpen = true ; this . dropdownOpenChange . emit ( this . dropdownOpen ) ; this . _focusFilter ( ) ; } } onMenuClose ( ) { if ( this . dropdownOpen !== false ) { this . dropdownOpen = false ; this . dropdownOpenChange . emit ( this . dropdownOpen ) ; } } _focusFilter ( ) { if ( this . filterInputElement ) { this . filterInputElement . nativeElement . focus ( ) ; } } inputFocusHandler ( ) { if ( ! this . dropdownOpen ) { this . dropdownOpen = true ; this . dropdownOpenChange . emit ( this . dropdownOpen ) ; } } toggleMenu ( ) { if ( this . disabled ) { return ; } this . dropdownOpen = ! this . dropdownOpen ; this . dropdownOpenChange . emit ( this . dropdownOpen ) ; this . menuTrigger . toggleMenu ( ) ; } }