UNPKG

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