import { AfterViewInit , ChangeDetectionStrategy , Component , ElementRef , EventEmitter , Input , OnChanges , Output , SimpleChanges , TemplateRef , ViewChild } from '@angular/core' ; import { KeyboardKeys } from '../../enums/keyboard-keys.enum' ; import { SelectDropdownOption } from './select-dropdown-option.interface' ; import { CoerceBooleanProperty } from '../../utils/coerce/coerce-boolean' ; @ Component ( { exportAs : 'ngxSelectInput' , selector : 'ngx-select-input' , templateUrl : './select-input.component.html' , host : { class : 'ngx-select-input' } , changeDetection : ChangeDetectionStrategy . OnPush } ) export class SelectInputComponent implements AfterViewInit , OnChanges { @ Input ( ) selectId : string ; @ Input ( ) placeholder : string ; @ Input ( ) identifier : string ; @ Input ( ) options : SelectDropdownOption [ ] ; @ Input ( ) label : string ; @ Input ( ) hint : string ; @ Input ( ) selectCaret : string | TemplateRef < any > ; @ Input ( ) requiredIndicator : string | boolean ; @ Input ( ) tabindex = 0 ; @ Input ( ) @ CoerceBooleanProperty ( ) autofocus : boolean ; @ Input ( ) @ CoerceBooleanProperty ( ) allowClear : boolean ; @ Input ( ) @ CoerceBooleanProperty ( ) multiple : boolean ; @ Input ( ) @ CoerceBooleanProperty ( ) tagging : boolean ; @ Input ( ) @ CoerceBooleanProperty ( ) allowAdditions : boolean ; @ Input ( ) @ CoerceBooleanProperty ( ) disableDropdown : boolean ; @ Input ( ) @ CoerceBooleanProperty ( ) disabled : boolean ; @ Input ( ) get selected ( ) { return this . _selected ; } set selected ( val ) { this . _selected = val ; this . selectedOptions = this . calcSelectedOptions ( val ) ; } @ Output ( ) toggle = new EventEmitter < void > ( ) ; @ Output ( ) close = new EventEmitter < void > ( ) ; @ Output ( ) selection = new EventEmitter < any [ ] > ( ) ; @ Output ( ) activate = new EventEmitter < void > ( ) ; @ Output ( ) activateLast = new EventEmitter < void > ( ) ; @ Output ( ) keyup = new EventEmitter < { event : KeyboardEvent ; value ? : string ; } > ( ) ; @ ViewChild ( 'inputContainer' ) readonly inputContainer ? : ElementRef < HTMLElement > ; @ ViewChild ( 'tagInput' ) readonly inputElement ? : ElementRef < HTMLInputElement > ; get caretVisible ( ) : boolean { if ( this . disableDropdown ) return false ; return ! ( this . tagging && ( ! this . options || ! this . options . length ) ) ; } get isNotTemplate ( ) { return ! ( typeof this . selectCaret === 'object' && this . selectCaret instanceof TemplateRef ) ; } selectedOptions : SelectDropdownOption [ ] = [ ] ; private _selected : any [ ] ; ngOnChanges ( changes ) { if ( 'options' in changes && ! changes . options . firstChange ) { this . selectedOptions = this . calcSelectedOptions ( this . selected ) ; } } ngAfterViewInit ( ) { if ( this . tagging && this . autofocus ) { setTimeout ( ( ) => { this . inputElement . nativeElement . focus ( ) ; } , 5 ) ; } } onInputKeyDown ( event ) { if ( event . code === KeyboardKeys . BACKSPACE ) { const value = ( event . target as any ) . value ; if ( value === '' ) { event . stopPropagation ( ) ; event . preventDefault ( ) ; const newSelections = this . selected . slice ( 0 , this . selected . length - 1 ) ; this . selection . emit ( newSelections ) ; } } } onInputKeyUp ( event ) { event . stopPropagation ( ) ; const value = ( event . target as any ) . value ; switch ( event . code ) { case KeyboardKeys . ENTER : event . preventDefault ( ) ; if ( value !== '' ) { const hasSelection = this . selected . find ( selection => { return value === selection ; } ) ; if ( ! hasSelection ) { const newSelections = [ ... this . selected , value ] ; this . selection . emit ( newSelections ) ; this . clearInput ( ) ; } } return ; case KeyboardKeys . ESCAPE : event . preventDefault ( ) ; this . toggle . emit ( ) ; return ; } this . keyup . emit ( { event , value } ) ; } clearInput ( ) { if ( this . inputElement && this . inputElement . nativeElement ) { this . inputElement . nativeElement . value = '' ; } this . keyup . emit ( { event : undefined , value : '' } ) ; } onGlobalKeyUp ( event ) { event . stopPropagation ( ) ; switch ( event . code ) { case KeyboardKeys . SPACE : case KeyboardKeys . ARROW_DOWN : event . preventDefault ( ) ; this . activate . emit ( ) ; break ; case KeyboardKeys . ARROW_UP : event . preventDefault ( ) ; this . activateLast . emit ( ) ; break ; case KeyboardKeys . ESCAPE : event . preventDefault ( ) ; this . close . emit ( ) ; break ; } } onKeyDown ( event ) { if ( event . code === KeyboardKeys . TAB ) return ; if ( this . disableDropdown ) return ; event . stopPropagation ( ) ; if ( ! this . tagging ) { event . preventDefault ( ) ; this . keyup . emit ( { event } ) ; } } onClick ( ) { if ( this . disableDropdown ) return ; this . activate . emit ( ) ; if ( this . tagging ) { setTimeout ( ( ) => { this . inputElement . nativeElement . focus ( ) ; } , 30 ) ; } } onFocus ( ) { if ( ! this . disabled && this . tagging ) { this . onClick ( ) ; } } onToggle ( ) { this . toggle . emit ( ) ; } onOptionRemove ( event , option ) { event . stopPropagation ( ) ; const newSelections = this . selected . filter ( selection => { if ( this . identifier !== undefined ) { return option . value [ this . identifier ] !== selection [ this . identifier ] ; } return option . value !== selection ; } ) ; this . selection . emit ( newSelections ) ; } focus ( ) { this . inputContainer . nativeElement . focus ( ) ; } private calcSelectedOptions ( selected ) { const results = [ ] ; if ( ! selected ) return results ; for ( const selection of selected ) { let match ; if ( this . options ) { match = this . options . find ( option => { if ( this . identifier ) { return selection [ this . identifier ] === option . value [ this . identifier ] ; } return selection === option . value ; } ) ; } if ( ( this . tagging || this . allowAdditions ) && ! match ) { match = { value : selection , name : selection } ; } if ( match ) results . push ( match ) ; } return results ; } }