import { Component , OnInit , Input , Output , EventEmitter , AfterViewInit } from '@angular/core' ; import { FormGroup , FormControl } from '@angular/forms' ; import { KeysPipe } from '../../pipes/keys.pipe' ; import { Config } from '../../models/config' ; @ Component ( { selector : 'ng-otp-input' , templateUrl : './ng-otp-input.component.html' , styleUrls : [ './ng-otp-input.component.scss' ] } ) export class NgOtpInputComponent implements OnInit , AfterViewInit { @ Input ( ) config : Config = { length : 4 } ; @ Output ( ) onInputChange = new EventEmitter < string > ( ) ; otpForm : FormGroup ; inputControls : FormControl [ ] = new Array ( this . config . length ) ; componentKey = Math . random ( ) . toString ( 36 ) . substring ( 2 ) + new Date ( ) . getTime ( ) . toString ( 36 ) ; inputType : string ; constructor ( private keysPipe ) { } ngOnInit ( ) { this . otpForm = new FormGroup ( { } ) ; for ( let index = 0 ; index < this . config . length ; index ++ ) { this . otpForm . addControl ( this . getControlName ( index ) , new FormControl ( ) ) ; } this . otpForm . valueChanges . subscribe ( ( v ) => { this . keysPipe . transform ( this . otpForm . controls ) . forEach ( ( k ) => { var val = this . otpForm . controls [ k ] . value ; if ( val && val . length > 1 ) { if ( val . length >= this . config . length ) { this . setValue ( val ) ; } else { this . rebuildValue ( ) ; } } } ) ; } ) ; this . inputType = this . getInputType ( ) ; } ngAfterViewInit ( ) { if ( ! this . config . disableAutoFocus ) { const containerItem = document . getElementById ( `c_${ this . componentKey }` ) ; if ( containerItem ) { containerItem . addEventListener ( 'paste' , ( evt ) => this . handlePaste ( evt ) ) ; const ele = containerItem . getElementsByClassName ( 'otp-input' ) [ 0 ] ; if ( ele && ele . focus ) { ele . focus ( ) ; } } } } private getControlName ( idx ) { return `ctrl_${ idx }` ; } ifLeftArrow ( event ) { return this . ifKeyCode ( event , 37 ) ; } ifRightArrow ( event ) { return this . ifKeyCode ( event , 39 ) ; } ifBackspaceOrDelete ( event ) { return ( event . key === 'Backspace' || event . key === 'Delete' || this . ifKeyCode ( event , 8 ) || this . ifKeyCode ( event , 46 ) ) ; } ifKeyCode ( event , targetCode ) { const key = event . keyCode || event . charCode ; return key == targetCode ? true : false ; } onKeyDown ( $event ) { var isSpace = this . ifKeyCode ( $event , 32 ) ; if ( isSpace ) { return false ; } } onKeyUp ( $event , inputIdx ) { const nextInputId = this . appendKey ( `otp_${ inputIdx + 1 }` ) ; const prevInputId = this . appendKey ( `otp_${ inputIdx - 1 }` ) ; if ( this . ifRightArrow ( $event ) ) { this . setSelected ( nextInputId ) ; return ; } if ( this . ifLeftArrow ( $event ) ) { this . setSelected ( prevInputId ) ; return ; } const isBackspace = this . ifBackspaceOrDelete ( $event ) ; if ( isBackspace && ! $event . target . value ) { this . setSelected ( prevInputId ) ; this . rebuildValue ( ) ; return ; } if ( ! $event . target . value ) { return ; } if ( this . ifValidEntry ( $event ) ) { this . setSelected ( nextInputId ) ; } this . rebuildValue ( ) ; } appendKey ( id ) { return `${ id }_${ this . componentKey }` ; } setSelected ( eleId ) { this . focusTo ( eleId ) ; const ele = document . getElementById ( eleId ) ; if ( ele && ele . setSelectionRange ) { setTimeout ( ( ) => { ele . setSelectionRange ( 0 , 1 ) ; } , 0 ) ; } } ifValidEntry ( event ) { const inp = String . fromCharCode ( event . keyCode ) ; const isMobile = /iPhone|iPad|iPod|Android/i . test ( navigator . userAgent ) ; return ( isMobile || /[a-zA-Z0-9-_]/ . test ( inp ) || ( this . config . allowKeyCodes && this . config . allowKeyCodes . includes ( event . keyCode ) ) || ( event . keyCode >= 96 && event . keyCode <= 105 ) ) ; } focusTo ( eleId ) { const ele = document . getElementById ( eleId ) ; if ( ele ) { ele . focus ( ) ; } } setValue ( value ) { if ( this . config . allowNumbersOnly && isNaN ( value ) ) { return ; } this . otpForm . reset ( ) ; if ( ! value ) { this . rebuildValue ( ) ; return ; } value = value . toString ( ) . replace ( /\s/g , '' ) ; Array . from ( value ) . forEach ( ( c , idx ) => { if ( this . otpForm . get ( this . getControlName ( idx ) ) ) { this . otpForm . get ( this . getControlName ( idx ) ) . setValue ( c ) ; } } ) ; if ( ! this . config . disableAutoFocus ) { const containerItem = document . getElementById ( `c_${ this . componentKey }` ) ; var indexOfElementToFocus = value . length < this . config . length ? value . length : ( this . config . length - 1 ) ; let ele = containerItem . getElementsByClassName ( 'otp-input' ) [ indexOfElementToFocus ] ; if ( ele && ele . focus ) { ele . focus ( ) ; } } this . rebuildValue ( ) ; } rebuildValue ( ) { let val = '' ; this . keysPipe . transform ( this . otpForm . controls ) . forEach ( k => { if ( this . otpForm . controls [ k ] . value ) { let ctrlVal = this . otpForm . controls [ k ] . value ; let isLengthExceed = ctrlVal . length > 1 ; let isCaseTransformEnabled = ! this . config . allowNumbersOnly && this . config . letterCase && ( this . config . letterCase . toLocaleLowerCase ( ) == 'upper' || this . config . letterCase . toLocaleLowerCase ( ) == 'lower' ) ; ctrlVal = ctrlVal [ 0 ] ; let transformedVal = isCaseTransformEnabled ? this . config . letterCase . toLocaleLowerCase ( ) == 'upper' ? ctrlVal . toUpperCase ( ) : ctrlVal . toLowerCase ( ) : ctrlVal ; if ( isCaseTransformEnabled && transformedVal == ctrlVal ) { isCaseTransformEnabled = false ; } else { ctrlVal = transformedVal ; } val += ctrlVal ; if ( isLengthExceed || isCaseTransformEnabled ) { this . otpForm . controls [ k ] . setValue ( ctrlVal ) ; } } } ) ; this . onInputChange . emit ( val ) ; } getInputType ( ) { return this . config . isPasswordInput ? 'password' : this . config . allowNumbersOnly ? 'tel' : 'text' ; } handlePaste ( e ) { let clipboardData = e . clipboardData || window [ 'clipboardData' ] ; if ( clipboardData ) { var pastedData = clipboardData . getData ( 'Text' ) ; } e . stopPropagation ( ) ; e . preventDefault ( ) ; if ( ! pastedData ) { return ; } this . setValue ( pastedData ) ; } }