@@ -3,41 +3,41 @@ import {
33 Component ,
44 ElementRef ,
55 EventEmitter ,
6- Input ,
6+ Input , OnChanges ,
77 OnInit ,
88 Output ,
9- QueryList ,
9+ QueryList , SimpleChanges ,
1010 ViewChildren
1111} from '@angular/core' ;
1212
13- /**
14- * Generated class for the CodeInputComponent component.
15- *
16- * See https://angular.io/api/core/Component for more info on Angular
17- * Components.
18- */
13+ enum InputState {
14+ ready = 0 ,
15+ reset = 1
16+ }
17+
1918@Component ( {
2019 selector : 'code-input' ,
2120 templateUrl : 'code-input.component.html' ,
2221 styleUrls : [ './code-input.component.scss' ]
2322} )
24- export class CodeInputComponent implements AfterViewInit , OnInit {
23+ export class CodeInputComponent implements AfterViewInit , OnInit , OnChanges {
2524
2625 @ViewChildren ( 'input' ) inputsList : QueryList < ElementRef > ;
2726
28- @Input ( ) readonly codeLength : number ;
29- @Input ( ) readonly isCodeHidden : boolean ;
30- @Input ( ) readonly isNonDigitsCode : boolean ;
27+ @Input ( ) readonly codeLength = 4 ;
28+ @Input ( ) readonly isNonDigitsCode = false ;
29+ @Input ( ) readonly isCodeHidden = false ;
30+ @Input ( ) readonly isPrevFocusableAfterClearing = true ;
31+ @Input ( ) readonly inputType = 'tel' ;
32+ @Input ( ) readonly code ?: string | number ;
3133
3234 @Output ( ) codeChanged = new EventEmitter < string > ( ) ;
3335 @Output ( ) codeCompleted = new EventEmitter < string > ( ) ;
3436
3537 public placeHolders : number [ ] ;
36- public state = {
37- isEmpty : true
38- } ;
3938
4039 private inputs : HTMLInputElement [ ] = [ ] ;
40+ private inputsStates : InputState [ ] = [ ] ;
4141
4242 constructor ( ) {
4343 }
@@ -53,7 +53,17 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
5353 ngAfterViewInit ( ) : void {
5454 this . inputsList . forEach ( ( item ) => {
5555 this . inputs . push ( item . nativeElement ) ;
56+ this . inputsStates . push ( InputState . ready ) ;
5657 } ) ;
58+
59+ // the @Input code might have value. Checking
60+ this . onInputCodeChanges ( ) ;
61+ }
62+
63+ ngOnChanges ( changes : SimpleChanges ) : void {
64+ if ( changes . code ) {
65+ this . onInputCodeChanges ( ) ;
66+ }
5767 }
5868
5969 /**
@@ -63,21 +73,22 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
6373 onInput ( e : any , i : number ) : void {
6474 const next = i + 1 ;
6575 const target = e . target ;
66- const data = e . data || target . value ;
76+ const value = e . data || target . value ;
6777
68- if ( data === null || data === undefined || ! data . toString ( ) . length ) {
78+ if ( this . isEmpty ( value ) ) {
6979 return ;
7080 }
7181
7282 // only digits are allowed if isNonDigitsCode flag is absent/false
73- if ( ! this . isNonDigitsCode && ! this . isDigitsData ( data ) ) {
83+ if ( ! this . canInputValue ( value ) ) {
7484 e . preventDefault ( ) ;
7585 e . stopPropagation ( ) ;
7686 this . setInputValue ( target , null ) ;
87+ this . setStateForInput ( target , InputState . reset ) ;
7788 return ;
7889 }
7990
80- this . setInputValue ( target , data . toString ( ) . charAt ( 0 ) ) ;
91+ this . setInputValue ( target , value . toString ( ) . charAt ( 0 ) ) ;
8192 this . emitChanges ( ) ;
8293
8394 if ( next > this . codeLength - 1 ) {
@@ -89,25 +100,64 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
89100 }
90101
91102 async onKeydown ( e : any , i : number ) : Promise < void > {
103+ const target = e . target ;
104+ const isTargetEmpty = this . isEmpty ( target . value ) ;
105+ const prev = i - 1 ;
106+
92107 // processing only backspace events
93108 const isBackspaceKey = await this . isBackspaceKey ( e ) ;
94109 if ( ! isBackspaceKey ) {
95110 return ;
96111 }
97112
98- const prev = i - 1 ;
99- const target = e . target ;
100-
101113 e . preventDefault ( ) ;
102114
103115 this . setInputValue ( target , null ) ;
104- this . emitChanges ( ) ;
116+ if ( ! isTargetEmpty ) {
117+ this . emitChanges ( ) ;
118+ }
105119
106120 if ( prev < 0 ) {
107121 return ;
108122 }
109123
110- this . inputs [ prev ] . focus ( ) ;
124+ if ( isTargetEmpty || this . isPrevFocusableAfterClearing ) {
125+ this . inputs [ prev ] . focus ( ) ;
126+ }
127+ }
128+
129+ isInputElementEmptyAt ( index : number ) : boolean {
130+ const input = this . inputs [ index ] ;
131+ if ( ! input ) {
132+ return true ;
133+ }
134+
135+ return this . isEmpty ( input . value ) ;
136+ }
137+
138+ private onInputCodeChanges ( ) : void {
139+ if ( ! this . inputs . length ) {
140+ return ;
141+ }
142+
143+ if ( this . isEmpty ( this . code ) ) {
144+ this . inputs . forEach ( ( input : HTMLInputElement ) => {
145+ this . setInputValue ( input , null ) ;
146+ } ) ;
147+ return ;
148+ }
149+
150+ const chars = this . code . toString ( ) . trim ( ) . split ( '' ) ;
151+ // checking if all the values are correct
152+ for ( const char of chars ) {
153+ if ( ! this . canInputValue ( char ) ) {
154+ return ;
155+ }
156+ }
157+
158+ this . inputs . forEach ( ( input : HTMLInputElement , index : number ) => {
159+ this . setInputValue ( input , chars [ index ] ) ;
160+ } ) ;
111161 }
112162
113163 private emitChanges ( ) : void {
@@ -118,20 +168,14 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
118168 let code = '' ;
119169
120170 for ( const input of this . inputs ) {
121- if ( input . value !== null && input . value !== undefined ) {
171+ if ( ! this . isEmpty ( input . value ) ) {
122172 code += input . value ;
123173 }
124174 }
125175
126176 this . codeChanged . emit ( code ) ;
127177
128- if ( code . length < this . codeLength ) {
129- code = null ;
130- }
131-
132- this . state . isEmpty = ( code === null ) ;
133-
134- if ( code !== null ) {
178+ if ( code . length >= this . codeLength ) {
135179 this . codeCompleted . emit ( code ) ;
136180 }
137181 }
@@ -149,19 +193,47 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
149193
150194 return new Promise < boolean > ( ( resolve ) => {
151195 setTimeout ( ( ) => {
196+ const input = e . target ;
197+ const isReset = this . getStateForInput ( input ) === InputState . reset ;
198+ if ( isReset ) {
199+ this . setStateForInput ( input , InputState . ready ) ;
200+ }
152201 // if backspace key pressed the caret will have position 0 (for single value field)
153- resolve ( e . target . selectionStart === 0 ) ;
202+ resolve ( input . selectionStart === 0 && ! isReset ) ;
154203 } ) ;
155204 } ) ;
156205 }
157206
158- private isDigitsData ( data : any ) : boolean {
159- return / ^ [ 0 - 9 ] + $ / . test ( data . toString ( ) ) ;
207+ private setInputValue ( input : HTMLInputElement , value : any ) : void {
208+ const isEmpty = this . isEmpty ( value ) ;
209+ input . value = isEmpty ? null : value ;
210+ input . className = isEmpty ? '' : 'has-value' ;
211+ }
212+
213+ private canInputValue ( value : any ) : boolean {
214+ if ( this . isEmpty ( value ) ) {
215+ return false ;
216+ }
217+
218+ const isDigitsValue = / ^ [ 0 - 9 ] + $ / . test ( value . toString ( ) ) ;
219+ return isDigitsValue || this . isNonDigitsCode ;
160220 }
161221
162- private setInputValue ( input : HTMLInputElement , value : any ) : void {
163- const isNonEmpty = value !== null && value !== undefined && value . toString ( ) . length ;
164- input . value = value ;
165- input . className = isNonEmpty ? 'has-value' : '' ;
222+ private setStateForInput ( input : HTMLInputElement , state : InputState ) : void {
223+ const index = this . inputs . indexOf ( input ) ;
224+ if ( index < 0 ) {
225+ return ;
226+ }
227+
228+ this . inputsStates [ index ] = state ;
229+ }
230+
231+ private getStateForInput ( input : HTMLInputElement ) : InputState | undefined {
232+ const index = this . inputs . indexOf ( input ) ;
233+ return this . inputsStates [ index ] ;
234+ }
235+
236+ private isEmpty ( value : any ) : boolean {
237+ return value === null || value === undefined || ! value . toString ( ) . length ;
166238 }
167239}
0 commit comments