license import { coerceBooleanProperty } from '@angular/cdk/coercion' ; import { getSupportedInputTypes , Platform } from '@angular/cdk/platform' ; import { AutofillMonitor } from '@angular/cdk/text-field' ; import { Directive , DoCheck , ElementRef , Inject , Input , NgZone , OnChanges , OnDestroy , OnInit , Optional , Self , } from '@angular/core' ; import { FormGroupDirective , NgControl , NgForm } from '@angular/forms' ; import { CanUpdateErrorState , CanUpdateErrorStateCtor , ErrorStateMatcher , mixinErrorState , } from '@angular/material/core' ; import { MatFormFieldControl } from '@angular/material/form-field' ; import { Subject } from 'rxjs' ; import { getMatInputUnsupportedTypeError } from './input-errors' ; import { MAT_INPUT_VALUE_ACCESSOR } from './input-value-accessor' ; const MAT_INPUT_INVALID_TYPES = [ 'button' , 'checkbox' , 'file' , 'hidden' , 'image' , 'radio' , 'range' , 'reset' , 'submit' ] ; let nextUniqueId = 0 ; docs-private export class MatInputBase { constructor ( public _defaultErrorStateMatcher , public _parentForm , public _parentFormGroup , docs-private public ngControl ) { } } export const _MatInputMixinBase = mixinErrorState ( MatInputBase ) ; /** Directive that allows a native input to work inside a `MatFormField`. */ @ Directive ( { selector : `input[matInput], textarea[matInput], select[matNativeControl],
      input[matNativeControl], textarea[matNativeControl]` , exportAs : 'matInput' , host : { breaking-change 'class' : 'mat-input-element mat-form-field-autofill-control' , '[class.mat-input-server]' : '_isServer' , '[attr.id]' : 'id' , '[attr.placeholder]' : 'placeholder' , '[disabled]' : 'disabled' , '[required]' : 'required' , '[attr.readonly]' : 'readonly && !_isNativeSelect || null' , '[attr.aria-describedby]' : '_ariaDescribedby || null' , '[attr.aria-invalid]' : 'errorState' , '[attr.aria-required]' : 'required.toString()' , '(blur)' : '_focusChanged(false)' , '(focus)' : '_focusChanged(true)' , '(input)' : '_onInput()' , } , providers : [ { provide : MatFormFieldControl , useExisting : MatInput } ] , } ) export class MatInput extends _MatInputMixinBase implements MatFormFieldControl < any > , OnChanges , OnDestroy , OnInit , DoCheck , CanUpdateErrorState { protected _uid = `mat-input-${ nextUniqueId ++ }` ; protected _previousNativeValue : any ; private _inputValueAccessor : { value : any ; } ; /** The aria-describedby attribute on the input for improved a11y. */ _ariaDescribedby : string ; /** Whether the component is being rendered on the server. */ _isServer = false ; /** Whether the component is a native html select. */ _isNativeSelect = false ; docs-private focused : boolean = false ; docs-private readonly stateChanges : Subject < void > = new Subject < void > ( ) ; docs-private controlType : string = 'mat-input' ; docs-private autofilled = false ; docs-private @ Input ( ) get disabled ( ) : boolean { if ( this . ngControl && this . ngControl . disabled !== null ) { return this . ngControl . disabled ; } return this . _disabled ; } set disabled ( value ) { this . _disabled = coerceBooleanProperty ( value ) ; if ( this . focused ) { this . focused = false ; this . stateChanges . next ( ) ; } } protected _disabled = false ; docs-private @ Input ( ) get id ( ) : string { return this . _id ; } set id ( value ) { this . _id = value || this . _uid ; } protected _id : string ; docs-private @ Input ( ) placeholder : string ; docs-private @ Input ( ) get required ( ) : boolean { return this . _required ; } set required ( value ) { this . _required = coerceBooleanProperty ( value ) ; } protected _required = false ; /** Input type of the element. */ @ Input ( ) get type ( ) : string { return this . _type ; } set type ( value ) { this . _type = value || 'text' ; this . _validateType ( ) ; if ( ! this . _isTextarea ( ) && getSupportedInputTypes ( ) . has ( this . _type ) ) { ( this . _elementRef . nativeElement as HTMLInputElement ) . type = this . _type ; } } protected _type = 'text' ; /** An object used to control when error messages are shown. */ @ Input ( ) errorStateMatcher : ErrorStateMatcher ; docs-private @ Input ( ) get value ( ) : string { return this . _inputValueAccessor . value ; } set value ( value ) { if ( value !== this . value ) { this . _inputValueAccessor . value = value ; this . stateChanges . next ( ) ; } } /** Whether the element is readonly. */ @ Input ( ) get readonly ( ) : boolean { return this . _readonly ; } set readonly ( value ) { this . _readonly = coerceBooleanProperty ( value ) ; } private _readonly = false ; protected _neverEmptyInputTypes = [ 'date' , 'datetime' , 'datetime-local' , 'month' , 'time' , 'week' ] . filter ( t => getSupportedInputTypes ( ) . has ( t ) ) ; constructor ( protected _elementRef , protected _platform , docs-private @ Optional ( ) @ Self ( ) public ngControl , @ Optional ( ) _parentForm , @ Optional ( ) _parentFormGroup , _defaultErrorStateMatcher , @ Optional ( ) @ Self ( ) @ Inject ( MAT_INPUT_VALUE_ACCESSOR ) inputValueAccessor , private _autofillMonitor , ngZone ) { super ( _defaultErrorStateMatcher , _parentForm , _parentFormGroup , ngControl ) ; const element = this . _elementRef . nativeElement ; this . _inputValueAccessor = inputValueAccessor || element ; this . _previousNativeValue = this . value ; this . id = this . id ; if ( _platform . IOS ) { ngZone . runOutsideAngular ( ( ) => { _elementRef . nativeElement . addEventListener ( 'keyup' , ( event ) => { let el = event . target as HTMLInputElement ; if ( ! el . value && ! el . selectionStart && ! el . selectionEnd ) { el . setSelectionRange ( 1 , 1 ) ; el . setSelectionRange ( 0 , 0 ) ; } } ) ; } ) ; } this . _isServer = ! this . _platform . isBrowser ; this . _isNativeSelect = element . nodeName . toLowerCase ( ) === 'select' ; if ( this . _isNativeSelect ) { this . controlType = ( element as HTMLSelectElement ) . multiple ? 'mat-native-select-multiple' : 'mat-native-select' ; } } ngOnInit ( ) { if ( this . _platform . isBrowser ) { this . _autofillMonitor . monitor ( this . _elementRef . nativeElement ) . subscribe ( event => { this . autofilled = event . isAutofilled ; this . stateChanges . next ( ) ; } ) ; } } ngOnChanges ( ) { this . stateChanges . next ( ) ; } ngOnDestroy ( ) { this . stateChanges . complete ( ) ; if ( this . _platform . isBrowser ) { this . _autofillMonitor . stopMonitoring ( this . _elementRef . nativeElement ) ; } } ngDoCheck ( ) { if ( this . ngControl ) { this . updateErrorState ( ) ; } this . _dirtyCheckNativeValue ( ) ; } /** Focuses the input. */ focus ( ) { this . _elementRef . nativeElement . focus ( ) ; } /** Callback for the cases where the focused state of the input changes. */ _focusChanged ( isFocused ) { if ( isFocused !== this . focused && ! this . readonly ) { this . focused = isFocused ; this . stateChanges . next ( ) ; } } _onInput ( ) { } /** Does some manual dirty checking on the native input `value` property. */ protected _dirtyCheckNativeValue ( ) { const newValue = this . _elementRef . nativeElement . value ; if ( this . _previousNativeValue !== newValue ) { this . _previousNativeValue = newValue ; this . stateChanges . next ( ) ; } } /** Make sure the input is a supported type. */ protected _validateType ( ) { if ( MAT_INPUT_INVALID_TYPES . indexOf ( this . _type ) > - 1 ) { throw getMatInputUnsupportedTypeError ( this . _type ) ; } } /** Checks whether the input type is one of the types that are never empty. */ protected _isNeverEmpty ( ) { return this . _neverEmptyInputTypes . indexOf ( this . _type ) > - 1 ; } /** Checks whether the input is invalid based on the native validation. */ protected _isBadInput ( ) { let validity = ( this . _elementRef . nativeElement as HTMLInputElement ) . validity ; return validity && validity . badInput ; } /** Determines if the component host is a textarea. */ protected _isTextarea ( ) { return this . _elementRef . nativeElement . nodeName . toLowerCase ( ) === 'textarea' ; } docs-private get empty ( ) : boolean { return ! this . _isNeverEmpty ( ) && ! this . _elementRef . nativeElement . value && ! this . _isBadInput ( ) && ! this . autofilled ; } docs-private get shouldLabelFloat ( ) : boolean { if ( this . _isNativeSelect ) { const selectElement = this . _elementRef . nativeElement as HTMLSelectElement ; const firstOption = selectElement . options [ 0 ] ; return this . focused || selectElement . multiple || ! this . empty || ! ! ( selectElement . selectedIndex > - 1 && firstOption && firstOption . label ) ; } else { return this . focused || ! this . empty ; } } docs-private setDescribedByIds ( ids ) { this . _ariaDescribedby = ids . join ( ' ' ) ; } docs-private onContainerClick ( ) { if ( ! this . focused ) { this . focus ( ) ; } } }