ngx-otp-code
Version:
Customizable OTP input component for Angular with Web OTP support.
191 lines (186 loc) • 8.7 kB
JavaScript
import { NgFor, NgClass } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, forwardRef, ViewChildren, Output, Input, Component } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
class NgxOtpCodeComponent {
get length() {
return this.config.length ?? 6;
}
get inputType() {
return this.config.inputType ?? 'text';
}
get placeholder() {
return this.config.placeholder ?? '';
}
get autoFocus() {
return this.config.autoFocus ?? true;
}
get isAlpha() {
return this.config.isAlpha ?? false;
}
get mask() {
return this.config.mask ?? false;
}
get useWebOtp() {
return this.config.useWebOtp ?? false;
}
get inputClass() {
return this.config.inputClass ?? '';
}
constructor(renderer) {
this.renderer = renderer;
this.config = {};
this.codeFilled = new EventEmitter();
this.error = new EventEmitter();
this.arrayValue = [];
this.value = '';
this.nameItem = 'otp-';
this.onChange = () => { };
this.onTouched = () => { };
}
ngAfterViewInit() {
if (this.autoFocus) {
this.focusInput(0);
}
if (this.useWebOtp && 'OTPCredential' in window) {
this.initWebOtp();
}
}
initWebOtp() {
const ac = new AbortController();
navigator.credentials.get({
otp: { transport: ['sms'] },
signal: ac.signal
}).then((otp) => {
if (otp?.code) {
this.setOtpValue(otp.code);
}
}).catch((err) => {
console.log(err);
});
}
onKey(event, index) {
const input = event.target;
const key = event.key;
if (key === 'Backspace') {
this.arrayValue[index] = '';
input.value = '';
if (index > 0) {
this.focusInput(index - 1);
}
this.updateValue();
event.preventDefault();
return;
}
// Allow only a single char per box
if (key.length === 1) {
// Check validity based on input type
if (this.inputType === 'number' && !/^\d$/.test(key)) {
event.preventDefault();
return;
}
if (this.isAlpha && !/^[A-Za-z0-9]$/.test(key)) {
event.preventDefault();
return;
}
// Assign value
this.arrayValue[index] = key;
input.value = key;
// Move to next input if not last
if (index < this.length - 1) {
this.focusInput(index + 1);
}
else {
input.blur(); // Optionally blur if finished
}
this.updateValue();
event.preventDefault();
}
else {
// Ignore other keys (arrows, etc.)
event.preventDefault();
}
}
onPaste(event) {
event.preventDefault();
const pasted = event.clipboardData?.getData('text') || '';
if (this.inputType === 'number' && /\D/.test(pasted)) {
this.error.emit('El código solo debe contener números.');
return;
}
if (pasted.length < this.length) {
this.error.emit(`La cantidad de caracteres no puede ser menor a ${this.length}.`);
return;
}
this.setOtpValue(pasted);
}
setOtpValue(value) {
this.arrayValue = value.split('').slice(0, this.length);
this.otpInputs?.forEach((ref, i) => {
ref.nativeElement.value = this.arrayValue[i] || '';
});
this.updateValue();
}
focusInput(index) {
const el = this.otpInputs?.get(index);
if (el) {
el.nativeElement.focus();
el.nativeElement.select();
}
}
updateValue() {
this.value = this.arrayValue.join('');
this.onChange(this.value);
if (this.value.length === this.length) {
this.codeFilled.emit(this.value);
}
}
writeValue(value) {
this.value = value || '';
this.arrayValue = this.value.split('');
setTimeout(() => {
this.otpInputs?.forEach((ref, i) => {
ref.nativeElement.value = this.arrayValue[i] || '';
});
});
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxOtpCodeComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: NgxOtpCodeComponent, isStandalone: true, selector: "ngx-otp-code", inputs: { config: "config" }, outputs: { codeFilled: "codeFilled", error: "error" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgxOtpCodeComponent),
multi: true
}
], viewQueries: [{ propertyName: "otpInputs", predicate: ["otpInput"], descendants: true }], ngImport: i0, template: "<div class=\"otp-container\" [ngClass]=\"config?.containerClass\">\n <input\n *ngFor=\"let item of [].constructor(length); let i = index\"\n #otpInput\n [type]=\"config?.mask ? 'password' : config?.inputType || 'text'\"\n [attr.placeholder]=\"config?.placeholder\"\n maxlength=\"1\"\n (keydown)=\"onKey($event, i)\"\n (paste)=\"onPaste($event)\"\n autocomplete=\"one-time-code\"\n class=\"otp-input\"\n [ngClass]=\"inputClass\"\n [attr.aria-label]=\"'C\u00F3digo ' + (i + 1)\"\n />\n</div>\n", styles: [".otp-container{display:flex;gap:.5rem;justify-content:center;align-items:center}.otp-container input::-webkit-outer-spin-button,.otp-container input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.otp-container input[type=number]{-moz-appearance:textfield}.otp-container .otp-input{width:3rem;height:3rem;text-align:center;font-size:1.5rem;border:1px solid #ccc;border-radius:4px}.otp-container .otp-input:focus{border-color:#007bff;outline:none}\n"], dependencies: [{ kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NgxOtpCodeComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-otp-code', providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgxOtpCodeComponent),
multi: true
}
], standalone: true, imports: [NgFor, NgClass], template: "<div class=\"otp-container\" [ngClass]=\"config?.containerClass\">\n <input\n *ngFor=\"let item of [].constructor(length); let i = index\"\n #otpInput\n [type]=\"config?.mask ? 'password' : config?.inputType || 'text'\"\n [attr.placeholder]=\"config?.placeholder\"\n maxlength=\"1\"\n (keydown)=\"onKey($event, i)\"\n (paste)=\"onPaste($event)\"\n autocomplete=\"one-time-code\"\n class=\"otp-input\"\n [ngClass]=\"inputClass\"\n [attr.aria-label]=\"'C\u00F3digo ' + (i + 1)\"\n />\n</div>\n", styles: [".otp-container{display:flex;gap:.5rem;justify-content:center;align-items:center}.otp-container input::-webkit-outer-spin-button,.otp-container input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.otp-container input[type=number]{-moz-appearance:textfield}.otp-container .otp-input{width:3rem;height:3rem;text-align:center;font-size:1.5rem;border:1px solid #ccc;border-radius:4px}.otp-container .otp-input:focus{border-color:#007bff;outline:none}\n"] }]
}], ctorParameters: () => [{ type: i0.Renderer2 }], propDecorators: { config: [{
type: Input
}], codeFilled: [{
type: Output
}], error: [{
type: Output
}], otpInputs: [{
type: ViewChildren,
args: ['otpInput']
}] } });
const OTP_INPUT_TYPES = ['text', 'number'];
/**
* Generated bundle index. Do not edit.
*/
export { NgxOtpCodeComponent, OTP_INPUT_TYPES };
//# sourceMappingURL=ngx-otp-code.mjs.map