@@ -3,41 +3,41 @@ import {
3
3
Component ,
4
4
ElementRef ,
5
5
EventEmitter ,
6
- Input ,
6
+ Input , OnChanges ,
7
7
OnInit ,
8
8
Output ,
9
- QueryList ,
9
+ QueryList , SimpleChanges ,
10
10
ViewChildren
11
11
} from '@angular/core' ;
12
12
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
+
19
18
@Component ( {
20
19
selector : 'code-input' ,
21
20
templateUrl : 'code-input.component.html' ,
22
21
styleUrls : [ './code-input.component.scss' ]
23
22
} )
24
- export class CodeInputComponent implements AfterViewInit , OnInit {
23
+ export class CodeInputComponent implements AfterViewInit , OnInit , OnChanges {
25
24
26
25
@ViewChildren ( 'input' ) inputsList : QueryList < ElementRef > ;
27
26
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 ;
31
33
32
34
@Output ( ) codeChanged = new EventEmitter < string > ( ) ;
33
35
@Output ( ) codeCompleted = new EventEmitter < string > ( ) ;
34
36
35
37
public placeHolders : number [ ] ;
36
- public state = {
37
- isEmpty : true
38
- } ;
39
38
40
39
private inputs : HTMLInputElement [ ] = [ ] ;
40
+ private inputsStates : InputState [ ] = [ ] ;
41
41
42
42
constructor ( ) {
43
43
}
@@ -53,7 +53,17 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
53
53
ngAfterViewInit ( ) : void {
54
54
this . inputsList . forEach ( ( item ) => {
55
55
this . inputs . push ( item . nativeElement ) ;
56
+ this . inputsStates . push ( InputState . ready ) ;
56
57
} ) ;
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
+ }
57
67
}
58
68
59
69
/**
@@ -63,21 +73,22 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
63
73
onInput ( e : any , i : number ) : void {
64
74
const next = i + 1 ;
65
75
const target = e . target ;
66
- const data = e . data || target . value ;
76
+ const value = e . data || target . value ;
67
77
68
- if ( data === null || data === undefined || ! data . toString ( ) . length ) {
78
+ if ( this . isEmpty ( value ) ) {
69
79
return ;
70
80
}
71
81
72
82
// only digits are allowed if isNonDigitsCode flag is absent/false
73
- if ( ! this . isNonDigitsCode && ! this . isDigitsData ( data ) ) {
83
+ if ( ! this . canInputValue ( value ) ) {
74
84
e . preventDefault ( ) ;
75
85
e . stopPropagation ( ) ;
76
86
this . setInputValue ( target , null ) ;
87
+ this . setStateForInput ( target , InputState . reset ) ;
77
88
return ;
78
89
}
79
90
80
- this . setInputValue ( target , data . toString ( ) . charAt ( 0 ) ) ;
91
+ this . setInputValue ( target , value . toString ( ) . charAt ( 0 ) ) ;
81
92
this . emitChanges ( ) ;
82
93
83
94
if ( next > this . codeLength - 1 ) {
@@ -89,25 +100,64 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
89
100
}
90
101
91
102
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
+
92
107
// processing only backspace events
93
108
const isBackspaceKey = await this . isBackspaceKey ( e ) ;
94
109
if ( ! isBackspaceKey ) {
95
110
return ;
96
111
}
97
112
98
- const prev = i - 1 ;
99
- const target = e . target ;
100
-
101
113
e . preventDefault ( ) ;
102
114
103
115
this . setInputValue ( target , null ) ;
104
- this . emitChanges ( ) ;
116
+ if ( ! isTargetEmpty ) {
117
+ this . emitChanges ( ) ;
118
+ }
105
119
106
120
if ( prev < 0 ) {
107
121
return ;
108
122
}
109
123
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
+ } ) ;
111
161
}
112
162
113
163
private emitChanges ( ) : void {
@@ -118,20 +168,14 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
118
168
let code = '' ;
119
169
120
170
for ( const input of this . inputs ) {
121
- if ( input . value !== null && input . value !== undefined ) {
171
+ if ( ! this . isEmpty ( input . value ) ) {
122
172
code += input . value ;
123
173
}
124
174
}
125
175
126
176
this . codeChanged . emit ( code ) ;
127
177
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 ) {
135
179
this . codeCompleted . emit ( code ) ;
136
180
}
137
181
}
@@ -149,19 +193,47 @@ export class CodeInputComponent implements AfterViewInit, OnInit {
149
193
150
194
return new Promise < boolean > ( ( resolve ) => {
151
195
setTimeout ( ( ) => {
196
+ const input = e . target ;
197
+ const isReset = this . getStateForInput ( input ) === InputState . reset ;
198
+ if ( isReset ) {
199
+ this . setStateForInput ( input , InputState . ready ) ;
200
+ }
152
201
// 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 ) ;
154
203
} ) ;
155
204
} ) ;
156
205
}
157
206
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 ;
160
220
}
161
221
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 ;
166
238
}
167
239
}
0 commit comments