UNPKG

ngx-otp-code

Version:

Customizable OTP input component for Angular with Web OTP support.

191 lines (186 loc) 8.7 kB
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