UNPKG

ng-otp-input

Version:

A fully customizable, one-time password input component for the web built with Angular.

442 lines (433 loc) 13.8 kB
import { Pipe, EventEmitter, Component, Input, Output, Directive, ElementRef, Renderer2, NgModule } from '@angular/core'; import { FormGroup, FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; /** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class KeysPipe { /** * @param {?} value * @return {?} */ transform(value) { return Object.keys(value); } } KeysPipe.decorators = [ { type: Pipe, args: [{ name: 'keys' },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class Config { } if (false) { /** @type {?} */ Config.prototype.inputStyles; /** @type {?} */ Config.prototype.containerStyles; /** @type {?} */ Config.prototype.allowKeyCodes; /** @type {?} */ Config.prototype.length; /** @type {?} */ Config.prototype.allowNumbersOnly; /** @type {?} */ Config.prototype.inputClass; /** @type {?} */ Config.prototype.containerClass; /** @type {?} */ Config.prototype.isPasswordInput; /** @type {?} */ Config.prototype.disableAutoFocus; /** @type {?} */ Config.prototype.placeholder; } /** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class NgOtpInputComponent { /** * @param {?} keysPipe */ constructor(keysPipe) { this.keysPipe = keysPipe; this.config = { length: 4 }; // tslint:disable-next-line: no-output-on-prefix this.onInputChange = new EventEmitter(); this.inputControls = new Array(this.config.length); this.componentKey = Math.random() .toString(36) .substring(2) + new Date().getTime().toString(36); } /** * @return {?} */ ngOnInit() { this.otpForm = new FormGroup({}); for (let index = 0; index < this.config.length; index++) { this.otpForm.addControl(this.getControlName(index), new FormControl()); } this.inputType = this.getInputType(); } /** * @return {?} */ ngAfterViewInit() { if (!this.config.disableAutoFocus) { /** @type {?} */ const containerItem = document.getElementById(`c_${this.componentKey}`); if (containerItem) { containerItem.addEventListener('paste', (/** * @param {?} evt * @return {?} */ (evt) => this.handlePaste(evt))); /** @type {?} */ const ele = containerItem.getElementsByClassName('otp-input')[0]; if (ele && ele.focus) { ele.focus(); } } } } /** * @private * @param {?} idx * @return {?} */ getControlName(idx) { return `ctrl_${idx}`; } /** * @param {?} event * @return {?} */ ifLeftArrow(event) { return this.ifKeyCode(event, 37); } /** * @param {?} event * @return {?} */ ifRightArrow(event) { return this.ifKeyCode(event, 39); } /** * @param {?} event * @return {?} */ ifBackspaceOrDelete(event) { return (event.key === 'Backspace' || event.key === 'Delete' || this.ifKeyCode(event, 8) || this.ifKeyCode(event, 46)); } /** * @param {?} event * @param {?} targetCode * @return {?} */ ifKeyCode(event, targetCode) { /** @type {?} */ const key = event.keyCode || event.charCode; // tslint:disable-next-line: triple-equals return key == targetCode ? true : false; } /** * @param {?} $event * @return {?} */ onKeyDown($event) { /** @type {?} */ var isSpace = this.ifKeyCode($event, 32); if (isSpace) { // prevent space return false; } } /** * @param {?} $event * @param {?} inputIdx * @return {?} */ onKeyUp($event, inputIdx) { /** @type {?} */ const nextInputId = this.appendKey(`otp_${inputIdx + 1}`); /** @type {?} */ const prevInputId = this.appendKey(`otp_${inputIdx - 1}`); if (this.ifRightArrow($event)) { this.setSelected(nextInputId); return; } if (this.ifLeftArrow($event)) { this.setSelected(prevInputId); return; } /** @type {?} */ const isBackspace = this.ifBackspaceOrDelete($event); if (isBackspace && !$event.target.value) { this.setSelected(prevInputId); this.rebuildValue(); return; } if (!$event.target.value) { return; } if (this.ifValidEntry($event)) { this.setSelected(nextInputId); } this.rebuildValue(); } /** * @param {?} id * @return {?} */ appendKey(id) { return `${id}_${this.componentKey}`; } /** * @param {?} eleId * @return {?} */ setSelected(eleId) { this.focusTo(eleId); /** @type {?} */ const ele = document.getElementById(eleId); if (ele && ele.setSelectionRange) { setTimeout((/** * @return {?} */ () => { ele.setSelectionRange(0, 1); }), 0); } } /** * @param {?} event * @return {?} */ ifValidEntry(event) { /** @type {?} */ const inp = String.fromCharCode(event.keyCode); /** @type {?} */ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); return (isMobile || /[a-zA-Z0-9-_]/.test(inp) || (this.config.allowKeyCodes && this.config.allowKeyCodes.includes(event.keyCode)) || (event.keyCode >= 96 && event.keyCode <= 105)); } /** * @param {?} eleId * @return {?} */ focusTo(eleId) { /** @type {?} */ const ele = document.getElementById(eleId); if (ele) { ele.focus(); } } // method to set component value /** * @param {?} value * @return {?} */ setValue(value) { if (this.config.allowNumbersOnly && isNaN(value)) { return; } this.otpForm.reset(); if (!value) { this.rebuildValue(); return; } value = value.toString().replace(/\s/g, ''); // remove whitespace Array.from(value).forEach((/** * @param {?} c * @param {?} idx * @return {?} */ (c, idx) => { if (this.otpForm.get(this.getControlName(idx))) { this.otpForm.get(this.getControlName(idx)).setValue(c); } })); if (!this.config.disableAutoFocus) { /** @type {?} */ const containerItem = document.getElementById(`c_${this.componentKey}`); /** @type {?} */ var indexOfElementToFocus = value.length < this.config.length ? value.length : (this.config.length - 1); /** @type {?} */ let ele = containerItem.getElementsByClassName('otp-input')[indexOfElementToFocus]; if (ele && ele.focus) { ele.focus(); } } this.rebuildValue(); } /** * @return {?} */ rebuildValue() { /** @type {?} */ let val = ''; this.keysPipe.transform(this.otpForm.controls).forEach((/** * @param {?} k * @return {?} */ k => { if (this.otpForm.controls[k].value) { val += this.otpForm.controls[k].value; } })); this.onInputChange.emit(val); } /** * @return {?} */ getInputType() { return this.config.isPasswordInput ? 'password' : this.config.allowNumbersOnly ? 'tel' : 'text'; } /** * @param {?} e * @return {?} */ handlePaste(e) { // Get pasted data via clipboard API /** @type {?} */ let clipboardData = e.clipboardData || window['clipboardData']; if (clipboardData) { /** @type {?} */ var pastedData = clipboardData.getData('Text'); } // Stop data actually being pasted into div e.stopPropagation(); e.preventDefault(); if (!pastedData) { return; } this.setValue(pastedData); } } NgOtpInputComponent.decorators = [ { type: Component, args: [{ // tslint:disable-next-line: component-selector selector: 'ng-otp-input', template: "<div class=\"wrapper {{config.containerClass}}\" id=\"c_{{componentKey}}\" *ngIf=\"otpForm?.controls\"\r\n [ngStyle]=\"config.containerStyles\">\r\n <input [pattern]=\"config.allowNumbersOnly ? '\\\\d*' : ''\" [type]=\"inputType\" numberOnly [placeholder]=\"config?.placeholder || ''\"\r\n [disabledNumberOnly]=\"!config.allowNumbersOnly\" [ngStyle]=\"config.inputStyles\" maxlength=\"1\"\r\n class=\"otp-input {{config.inputClass}}\" autocomplete=\"off\" *ngFor=\"let item of otpForm?.controls | keys;let i=index\"\r\n [formControl]=\"otpForm.controls[item]\" id=\"otp_{{i}}_{{componentKey}}\" (keydown)=\"onKeyDown($event)\"\r\n (keyup)=\"onKeyUp($event,i)\">\r\n</div>", styles: [".otp-input{width:50px;height:50px;border-radius:4px;border:1px solid #c5c5c5;text-align:center;font-size:32px}.wrapper .otp-input:not(:last-child){margin-right:8px}@media screen and (max-width:767px){.otp-input{width:40px;font-size:24px;height:40px}}@media screen and (max-width:420px){.otp-input{width:30px;font-size:18px;height:30px}}"] }] } ]; /** @nocollapse */ NgOtpInputComponent.ctorParameters = () => [ { type: KeysPipe } ]; NgOtpInputComponent.propDecorators = { config: [{ type: Input }], onInputChange: [{ type: Output }] }; if (false) { /** @type {?} */ NgOtpInputComponent.prototype.config; /** @type {?} */ NgOtpInputComponent.prototype.onInputChange; /** @type {?} */ NgOtpInputComponent.prototype.otpForm; /** @type {?} */ NgOtpInputComponent.prototype.inputControls; /** @type {?} */ NgOtpInputComponent.prototype.componentKey; /** @type {?} */ NgOtpInputComponent.prototype.inputType; /** * @type {?} * @private */ NgOtpInputComponent.prototype.keysPipe; } /** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class NumberOnlyDirective { /** * @param {?} _elRef * @param {?} _renderer */ constructor(_elRef, _renderer) { this._elRef = _elRef; this._renderer = _renderer; } /** * @return {?} */ ngOnInit() { if (!this.disabledNumberOnly) { this._renderer.setAttribute(this._elRef.nativeElement, 'onkeypress', 'return (event.charCode >= 48 && event.charCode <= 57) || event.charCode == 0'); } } } NumberOnlyDirective.decorators = [ { type: Directive, args: [{ selector: '[numberOnly]' },] } ]; /** @nocollapse */ NumberOnlyDirective.ctorParameters = () => [ { type: ElementRef }, { type: Renderer2 } ]; NumberOnlyDirective.propDecorators = { disabledNumberOnly: [{ type: Input }] }; if (false) { /** @type {?} */ NumberOnlyDirective.prototype.disabledNumberOnly; /** * @type {?} * @private */ NumberOnlyDirective.prototype._elRef; /** * @type {?} * @private */ NumberOnlyDirective.prototype._renderer; } /** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class NgOtpInputModule { } NgOtpInputModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule, FormsModule, ReactiveFormsModule ], declarations: [NgOtpInputComponent, KeysPipe, NumberOnlyDirective], exports: [NgOtpInputComponent], providers: [KeysPipe] },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ export { NgOtpInputModule, NgOtpInputComponent as ɵa, KeysPipe as ɵb, NumberOnlyDirective as ɵc }; //# sourceMappingURL=ng-otp-input.js.map