angular-otp-box
Version:
Angular otp input field component for web applications. Easy to integrate and use.
249 lines (240 loc) • 9.24 kB
JavaScript
import { Pipe, EventEmitter, Directive, Input, Output, Component, ViewChildren, ElementRef, Renderer2, NgModule } from '@angular/core';
import { FormGroup, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Subject, Subscription, timer } from 'rxjs';
import { switchMap, take, tap } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
class KeysPipe {
transform(value) {
return Object.keys(value);
}
}
KeysPipe.decorators = [
{ type: Pipe, args: [{
name: 'keys'
},] }
];
class CounterDirective {
constructor() {
this._counterSource$ = new Subject();
this._subscription = Subscription.EMPTY;
this.interval = 1000;
this.value = new EventEmitter();
this._subscription = this._counterSource$.pipe(switchMap(({ interval, count }) => timer(0, interval).pipe(take(count), tap(() => this.value.emit(--count))))).subscribe();
}
ngOnChanges(changes) {
this.startTimer();
}
startTimer() {
this._counterSource$.next({ count: this.counter, interval: this.interval });
}
ngOnDestroy() {
this._subscription.unsubscribe();
}
}
CounterDirective.decorators = [
{ type: Directive, args: [{
selector: '[counter]'
},] }
];
CounterDirective.ctorParameters = () => [];
CounterDirective.propDecorators = {
counter: [{ type: Input }],
value: [{ type: Output }]
};
class OtpInputComponent {
constructor(keysPipe) {
this.keysPipe = keysPipe;
this.setting = {
length: 4,
timer: 0,
timerType: 0
};
this.onValueChange = new EventEmitter();
this.inputControls = new Array(this.setting.length);
this.componentKey = Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
}
ngOnInit() {
console.log(this.setting);
this.otpForm = new FormGroup({});
for (let index = 0; index < this.setting.length; index++) {
this.otpForm.addControl(this.getControlName(index), new FormControl());
}
}
ngAfterViewInit() {
let containerItem = document.getElementById(`c_${this.componentKey}`);
if (containerItem) {
let ele = containerItem.getElementsByClassName('.otp-input')[0];
if (ele && ele.focus) {
ele.focus();
}
}
}
getControlName(idx) {
return `ctrl_${idx}`;
}
isLeftArrow(e) {
return this.isKeyCode(e, 37);
}
isRightArrow(e) {
return this.isKeyCode(e, 39);
}
isBackspaceOrDelete(e) {
return e.key === "Backspace" || e.key === "Delete" || this.isKeyCode(e, 8) || this.isKeyCode(e, 46);
}
isKeyCode(e, targetCode) {
var key = e.keyCode || e.charCode;
if (key == targetCode) {
return true;
}
return false;
}
keyUp(e, inputIdx) {
let nextInputId = this.appendKey(`otp_${inputIdx + 1}`);
let prevInputId = this.appendKey(`otp_${inputIdx - 1}`);
if (this.isRightArrow(e)) {
this.setSelected(nextInputId);
return;
}
if (this.isLeftArrow(e)) {
this.setSelected(prevInputId);
return;
}
let isBackspace = this.isBackspaceOrDelete(e);
if (isBackspace && !e.target.value) {
this.setSelected(prevInputId);
this.rebuildValue();
return;
}
if (!e.target.value) {
return;
}
if (this.isValidEntry(e)) {
this.focusTo(nextInputId);
}
this.rebuildValue();
}
appendKey(id) {
return `${id}_${this.componentKey}`;
}
setSelected(eleId) {
this.focusTo(eleId);
let ele = document.getElementById(eleId);
if (ele && ele.setSelectionRange) {
setTimeout(() => {
ele.setSelectionRange(0, 1);
}, 0);
}
}
isValidEntry(e) {
var inp = String.fromCharCode(e.keyCode);
var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
return isMobile || /[a-zA-Z0-9-_]/.test(inp) || (this.setting.allowKeyCodes && this.setting.allowKeyCodes.includes(e.keyCode)) || (e.keyCode >= 96 && e.keyCode <= 105);
}
focusTo(eleId) {
let ele = document.getElementById(eleId);
if (ele) {
ele.focus();
ele.selectionStart = ele.selectionEnd = 100;
}
}
rebuildValue() {
let val = '';
this.keysPipe.transform(this.otpForm.controls).forEach(k => {
if (this.otpForm.controls[k].value) {
val += this.otpForm.controls[k].value;
}
});
this.onValueChange.emit(val);
}
onCounterChange(e) {
this.counter = e;
if (this.counter == 0) {
this.onValueChange.emit(-1);
}
}
ressendOtp() {
this.CounterDirective.first.startTimer();
this.onValueChange.emit(-2);
}
formatSecsToMins(time) {
// Hours, minutes and seconds
var hrs = ~~(time / 3600);
var mins = ~~((time % 3600) / 60);
var secs = ~~time % 60;
// Output like "1:01" or "4:03:59" or "123:03:59"
var ret = "";
if (hrs > 0) {
ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
}
ret += "" + mins + ":" + (secs < 10 ? "0" : "");
ret += "" + secs;
return ret;
}
}
OtpInputComponent.decorators = [
{ type: Component, args: [{
selector: 'otp',
template: "<div class=\"otp-container {{setting.wrapperClass}}\" id=\"c_{{componentKey}}\" *ngIf=\"otpForm?.controls\"\n [ngStyle]=\"setting.wrapperStyles\">\n <input \n [type]=\"setting.numbersOnly ? 'tel' : 'text'\" \n numberOnly [disabledNumberOnly]=\"!setting.numbersOnly\"\n [ngStyle]=\"setting.inputStyles\" \n maxlength=\"1\" \n class=\"otp-input {{setting.inputClass}}\" \n autocomplete=\"off\"\n *ngFor=\"let item of otpForm?.controls | keys; let i = index\" \n [formControl]=\"otpForm.controls[item]\"\n id=\"otp_{{i}}_{{componentKey}}\" \n (keyup)=\"keyUp($event, i)\"\n >\n <ng-container counter [counter]=\"setting.timer\" (value)=\"onCounterChange($event)\">\n <div>\n <button class=\"btn {{setting.btnClass}}\" [disabled]=\"counter != 0\" (click)=\"ressendOtp()\">\n Resend OTP \n <span *ngIf=\"counter != 0\">\n <ng-container *ngIf=\"!setting.timerType\">\n in {{ counter }} seconds.\n </ng-container>\n <ng-container *ngIf=\"setting.timerType\">\n in {{ formatSecsToMins(counter) }} minutes.\n </ng-container>\n </span>\n </button>\n </div>\n </ng-container>\n</div>",
styles: [".otp-input{width:2em;height:2em;border-radius:4px;border:1px solid #c5c5c5;text-align:center;font-size:28px}.otp-input:focus{outline-offset:0;outline-color:#2b91e2;outline-style:auto;outline-width:5px}.otp-container .otp-input:not(:last-child){margin-right:8px}@media screen and (max-width:767px){.otp-input{font-size:24px}}@media screen and (max-width:420px){.otp-input{font-size:18px}}"]
},] }
];
OtpInputComponent.ctorParameters = () => [
{ type: KeysPipe }
];
OtpInputComponent.propDecorators = {
setting: [{ type: Input }],
onValueChange: [{ type: Output }],
CounterDirective: [{ type: ViewChildren, args: [CounterDirective,] }]
};
class NumberOnly {
constructor(_elRef, _renderer) {
this._elRef = _elRef;
this._renderer = _renderer;
}
ngOnInit() {
if (!this.disabledNumberOnly) {
this._renderer.setAttribute(this._elRef.nativeElement, 'onkeypress', 'return (event.charCode >= 48 && event.charCode <= 57) || event.charCode == 0');
}
}
}
NumberOnly.decorators = [
{ type: Directive, args: [{
selector: '[numberOnly]'
},] }
];
NumberOnly.ctorParameters = () => [
{ type: ElementRef },
{ type: Renderer2 }
];
NumberOnly.propDecorators = {
disabledNumberOnly: [{ type: Input }]
};
class AngularOtpLibModule {
}
AngularOtpLibModule.decorators = [
{ type: NgModule, args: [{
declarations: [
OtpInputComponent,
KeysPipe,
NumberOnly,
CounterDirective
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [
OtpInputComponent
],
providers: [KeysPipe]
},] }
];
/*
* Public API Surface of angular-otp-box
*/
/**
* Generated bundle index. Do not edit.
*/
export { AngularOtpLibModule, OtpInputComponent, CounterDirective as ɵa, KeysPipe as ɵb, NumberOnly as ɵc };
//# sourceMappingURL=angular-otp-box.js.map