wts-otp
Version:
An Angular library that simplifies the integration of OTP (One-Time Password) input fields into your Angular applications. This library offers customizable and secure OTP input components to enhance user authentication experiences.
217 lines (212 loc) • 14 kB
JavaScript
import * as i0 from '@angular/core';
import { input, output, computed, viewChild, afterNextRender, forwardRef, Component } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
class Options {
digits = 4;
placeholder = ''; //'●' // '★', '-'
type = 'number';
visibleAs = 'text';
borderOn = 'all-side';
autofocus = true;
constructor(data) {
if (!data)
return;
Object.keys(data).forEach((key) => {
typeof (data[key]) !== 'undefined' && (this[key] = data[key]);
});
}
}
class WtsOtpComponent {
options = input(new Options());
showError = input(false);
value = input('');
valueChange = output();
getValue = output();
OTP = '';
config = computed(() => new Options(this.options()));
OTPMaster = viewChild('OTPMaster');
OPTARRAY = [];
constructor() {
afterNextRender(() => {
const value = this.value();
const _inputs = this.OTPMaster();
if (value && _inputs) {
const allvalue = value.split("").slice(0, this.config().digits);
const otp = _inputs.nativeElement, inputs = otp.querySelectorAll('input');
for (let i = 0; i < allvalue.length; i++) {
const value = allvalue[i];
setTimeout(() => { inputs.item(i).value = value; }, 0);
}
}
});
}
_keyPress(e, index) {
const config = this.config();
const _inputs = this.OTPMaster();
if (_inputs) {
let otp = _inputs.nativeElement, inputs = otp.querySelectorAll('input');
if (e.key === 'ArrowLeft') {
index > 0 && setTimeout(() => { inputs.item(index - 1).focus(); }, 0);
}
if (e.key === 'ArrowRight') {
inputs.item(index + 1) && setTimeout(() => { inputs.item(index + 1).focus(); }, 0);
}
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
inputs.item(index + 1) && setTimeout(() => { inputs.item(index + 1).focus(); }, 0);
}
if (e.key === 'Backspace') {
inputs.item(index).value = '';
delete this.OPTARRAY[index];
this.emitValue();
index > 0 && setTimeout(() => { inputs.item(index - 1).focus(); }, 0);
}
}
if (config.type === 'number') {
return e.key >= '0' && e.key <= '9';
}
else {
return true;
}
}
reset() {
const _inputs = this.OTPMaster();
if (_inputs) {
const otp = _inputs.nativeElement, inputs = otp.querySelectorAll('input');
inputs.forEach((item) => {
item.value = '';
});
}
}
focus(index = 0) {
if (!this.config().autofocus)
return;
const _inputs = this.OTPMaster();
if (_inputs) {
const otp = _inputs.nativeElement;
const inputs = otp.querySelectorAll('input');
index < inputs.length ? inputs.item(index).focus() : console.error('Invalid index number!');
}
}
onFocus(e, index) {
const value = e.target.value;
value && e.target.select();
}
onInputChange(e, index) {
const config = this.config();
(config.digits - 1 > index) && this.focusItem(index + 1);
this.emitValue();
}
emitValue() {
const _inputs = this.OTPMaster();
if (_inputs) {
const value = [];
const otp = _inputs.nativeElement;
const inputs = otp.querySelectorAll('input');
inputs.forEach(element => {
value.push(element.value);
});
if (value.length) {
setTimeout(() => {
const _value = value.join(',').replaceAll(',', '');
this.valueChange.emit(_value);
this.getValue.emit(_value);
}, this.config().digits * 50);
}
}
}
focusItem(index) {
const _inputs = this.OTPMaster();
if (_inputs) {
const otp = _inputs.nativeElement, inputs = otp.querySelectorAll('input');
inputs.item(index).focus();
}
}
onChange(e, index) {
const config = this.config();
const target = e.target;
target.value = target.value.slice(-1);
if (target.value) {
this.OPTARRAY[index] = target.value;
setTimeout(() => {
index < (config.digits - 1) && this.focusItem(index + 1);
// index >= (config.digits - 1) && this.focusItem(index - 1);
}, 10);
this.emitValue();
// (config.digits - 1 > index) && this.focusItem(index + 1);
// index && (index <= config.digits) && this.focusItem(index - 1);
}
}
onPaste(e, index) {
const _inputs = this.OTPMaster();
if (!e.clipboardData || !_inputs)
return;
let otp = _inputs.nativeElement, inputs = otp.querySelectorAll('input'), value = e.clipboardData.getData('Text');
let isValid = true;
const target = e.target;
if (this.config().type === 'number') {
value = parseFloat(value);
isValid = typeof (value) === 'number';
if (isNaN(value)) {
throw new Error('Invalid OTP');
}
}
value = value.toString();
if (value && isValid) {
const allvalue = value.slice(0, this.config().digits);
console.log(allvalue);
for (let i = 0; i < allvalue.length; i++) {
const _value = allvalue[i];
inputs.item(i).value = _value;
this.OPTARRAY[i] = _value;
setTimeout(() => { inputs.item(i).value = _value; }, 0);
}
}
else {
target.value = '';
}
}
onKeyUp(e, index) {
// const _inputs = this.OTPMaster();
// if (_inputs) {
// let otp = _inputs.nativeElement, inputs = otp.querySelectorAll('input');
// if (e.key === 'ArrowLeft') {
// index > 0 && setTimeout(() => { inputs.item(index - 1).focus() }, 0);
// }
// if (e.key === 'ArrowRight') {
// inputs.item(index + 1) && setTimeout(() => { inputs.item(index + 1).focus() }, 0)
// }
// if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
// inputs.item(index + 1) && setTimeout(() => { inputs.item(index + 1).focus() }, 0)
// }
// if (e.key === 'Backspace') {
// console.log(e.key);
// }
// }
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: WtsOtpComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.3", type: WtsOtpComponent, isStandalone: true, selector: "wts-otp, [wts-otp]", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, showError: { classPropertyName: "showError", publicName: "showError", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", getValue: "getValue" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => WtsOtpComponent), // replace name as appropriate
multi: true
}
], viewQueries: [{ propertyName: "OTPMaster", first: true, predicate: ["OTPMaster"], descendants: true, isSignal: true }], ngImport: i0, template: "@let _config = config();\n@let _isError = showError();\n\n<div #OTPMaster class=\"___wts___OTP\" [class.___invalid]=\"_isError\">\n @for (item of [].constructor(_config.digits); track '__inputs'+$index) {\n\n @let isDisabled = ($index >= 1 && !(OPTARRAY[$index-1]));\n\n <div class=\"__wts__input__box\">\n <input type=\"password\" #inputItem name=\"password\" (input)=\"onChange($event, $index)\" autofocus\n autocomplete=\"new-password\" [attr.inputmode]=\"_config.type === 'number' ? 'numeric' : 'text'\"\n (paste)=\"onPaste($event, $index)\" (focus)=\"onFocus($event, $index)\" (keyup)=\"onKeyUp($event, $index)\"\n [type]=\"_config.visibleAs === 'password'? 'password' : 'text'\" (keydown)=\"_keyPress($event, $index)\"\n [placeholder]=\"_config.placeholder\" [class]=\"'border-'+_config.borderOn\" [disabled]=\"isDisabled\"\n [class.disabled]=\"isDisabled\" [class.hasError]=\"!OPTARRAY[$index]\">\n </div>\n }\n</div>\n", styles: [":host{--border: solid 2px #545353;--borderRadius: 4px;--fontWeight: 600;--fontSize: 16px;--fontColor: #1b1b1b;--placeholderColor: #000;--inputHeight: 46px;--inputWidth: 60px;--gapBetweenInput: 5px;--focusBorderColor: green;--invalidInputBorderColor: #e84545}.___wts___OTP{display:flex;font-family:inherit}.___wts___OTP .__wts__input__box:not(:last-child){padding-right:var(--gapBetweenInput)}.___wts___OTP.___invalid input.hasError.border-all-side,.___wts___OTP.___invalid input.hasError.border-right,.___wts___OTP.___invalid input.hasError.border-left,.___wts___OTP.___invalid input.hasError.border-top,.___wts___OTP.___invalid input.hasError.border-bottom{border-color:var(--invalidInputBorderColor)}.___wts___OTP input{text-align:center;padding:0;border-radius:var(--borderRadius);font-weight:var(--fontWeight);font-size:var(--fontSize);color:var(--fontColor);height:var(--inputHeight);width:var(--inputWidth);outline:none;border:none}@media (max-width: 767px){.___wts___OTP input{appearance:textfield}}.___wts___OTP input::-webkit-outer-spin-button,.___wts___OTP input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.___wts___OTP input.border-none{border:none}.___wts___OTP input.border-all-side{border:var(--border)}.___wts___OTP input.border-right{border-right:var(--border)}.___wts___OTP input.border-left{border-left:var(--border)}.___wts___OTP input.border-top{border-top:var(--border)}.___wts___OTP input.border-bottom{border-bottom:var(--border)}.___wts___OTP input:focus,.___wts___OTP input:active,.___wts___OTP input:focus-visible{border-color:var(--focusBorderColor)!important}.___wts___OTP input::placeholder{color:var(--placeholderColor);opacity:1}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: WtsOtpComponent, decorators: [{
type: Component,
args: [{ selector: 'wts-otp, [wts-otp]', providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => WtsOtpComponent), // replace name as appropriate
multi: true
}
], template: "@let _config = config();\n@let _isError = showError();\n\n<div #OTPMaster class=\"___wts___OTP\" [class.___invalid]=\"_isError\">\n @for (item of [].constructor(_config.digits); track '__inputs'+$index) {\n\n @let isDisabled = ($index >= 1 && !(OPTARRAY[$index-1]));\n\n <div class=\"__wts__input__box\">\n <input type=\"password\" #inputItem name=\"password\" (input)=\"onChange($event, $index)\" autofocus\n autocomplete=\"new-password\" [attr.inputmode]=\"_config.type === 'number' ? 'numeric' : 'text'\"\n (paste)=\"onPaste($event, $index)\" (focus)=\"onFocus($event, $index)\" (keyup)=\"onKeyUp($event, $index)\"\n [type]=\"_config.visibleAs === 'password'? 'password' : 'text'\" (keydown)=\"_keyPress($event, $index)\"\n [placeholder]=\"_config.placeholder\" [class]=\"'border-'+_config.borderOn\" [disabled]=\"isDisabled\"\n [class.disabled]=\"isDisabled\" [class.hasError]=\"!OPTARRAY[$index]\">\n </div>\n }\n</div>\n", styles: [":host{--border: solid 2px #545353;--borderRadius: 4px;--fontWeight: 600;--fontSize: 16px;--fontColor: #1b1b1b;--placeholderColor: #000;--inputHeight: 46px;--inputWidth: 60px;--gapBetweenInput: 5px;--focusBorderColor: green;--invalidInputBorderColor: #e84545}.___wts___OTP{display:flex;font-family:inherit}.___wts___OTP .__wts__input__box:not(:last-child){padding-right:var(--gapBetweenInput)}.___wts___OTP.___invalid input.hasError.border-all-side,.___wts___OTP.___invalid input.hasError.border-right,.___wts___OTP.___invalid input.hasError.border-left,.___wts___OTP.___invalid input.hasError.border-top,.___wts___OTP.___invalid input.hasError.border-bottom{border-color:var(--invalidInputBorderColor)}.___wts___OTP input{text-align:center;padding:0;border-radius:var(--borderRadius);font-weight:var(--fontWeight);font-size:var(--fontSize);color:var(--fontColor);height:var(--inputHeight);width:var(--inputWidth);outline:none;border:none}@media (max-width: 767px){.___wts___OTP input{appearance:textfield}}.___wts___OTP input::-webkit-outer-spin-button,.___wts___OTP input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.___wts___OTP input.border-none{border:none}.___wts___OTP input.border-all-side{border:var(--border)}.___wts___OTP input.border-right{border-right:var(--border)}.___wts___OTP input.border-left{border-left:var(--border)}.___wts___OTP input.border-top{border-top:var(--border)}.___wts___OTP input.border-bottom{border-bottom:var(--border)}.___wts___OTP input:focus,.___wts___OTP input:active,.___wts___OTP input:focus-visible{border-color:var(--focusBorderColor)!important}.___wts___OTP input::placeholder{color:var(--placeholderColor);opacity:1}\n"] }]
}], ctorParameters: () => [] });
/*
* Public API Surface of wts-otp
*/
/**
* Generated bundle index. Do not edit.
*/
export { Options, WtsOtpComponent };
//# sourceMappingURL=wts-otp.mjs.map