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
JavaScript
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