UNPKG

angular-otp-box

Version:

Angular otp input field component for web applications. Easy to integrate and use.

249 lines (240 loc) 9.24 kB
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