Skip to content

Commit 75ee754

Browse files
Have added to the component new input props - code, isPrevFocusableAfterClearing, inputType. Minor refactoring. Fixing Android issues.
1 parent 751c7e2 commit 75ee754

File tree

4 files changed

+119
-51
lines changed

4 files changed

+119
-51
lines changed

angular-code-input/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "angular-code-input",
3-
"version": "1.0.1",
4-
"description": "Code or pin code input for angular 7+ / ionic 4 + projects",
3+
"version": "1.1.0",
4+
"description": "Code or pin code input for Angular 7, 8, 9 + / Ionic 4, 5 + projects",
55
"keywords": ["angular", "pincode", "angular-pincode", "otp", "code-input", "angular-otp", "ionic-otp", "ionic-code-input", "ionic-pincode"],
66
"author": "Alexander Dmitrenko",
77
"license": "MIT",
@@ -13,7 +13,7 @@
1313
"url": "https://github.com/AlexMiniApps/angular-code-input/issues"
1414
},
1515
"peerDependencies": {
16-
"@angular/common": "^7.2.0",
17-
"@angular/core": "^7.2.0"
16+
"@angular/common": ">=7.2.0",
17+
"@angular/core": ">=7.2.0"
1818
}
19-
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
<span *ngFor="let holder of placeHolders; index as i"
2-
class="custom-class"
3-
[class.empty]="state.isEmpty"
2+
[class.empty]="isInputElementEmptyAt(i)"
43
[class.code-hidden]="isCodeHidden">
54
<input #input
65
(input)="onInput($event, i)"
76
(keydown)="onKeydown($event, i)"
8-
type="tel"/>
7+
[type]="inputType"/>
98
</span>
10-
11-
12-

angular-code-input/src/lib/code-input.component.ts

+111-39
Original file line numberDiff line numberDiff line change
@@ -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
}

angular-code-input/src/lib/code-input.module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NgModule } from '@angular/core';
1+
import {NgModule} from '@angular/core';
22
import {CodeInputComponent} from './code-input.component';
33
import {CommonModule} from '@angular/common';
44

0 commit comments

Comments
 (0)