ngx-otp-input
Version:
One Time Password input library for Angular (14+)
348 lines (337 loc) • 18.9 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, Directive, ContentChildren, Input, Output, HostListener, ContentChild, Component } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i2 from '@angular/forms';
import { FormArray, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
class PasteDirective {
constructor() {
this.handlePaste = new EventEmitter();
}
onPaste(event) {
event.preventDefault();
const clipboardData = event.clipboardData?.getData('text');
if (clipboardData && this.regexp.test(clipboardData)) {
const values = clipboardData.split('');
this.inputs.forEach((input, index) => {
if (values[index]) {
input.nativeElement.value = values[index];
}
});
this.handlePaste.emit(values);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: PasteDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.1", type: PasteDirective, isStandalone: true, selector: "[ngxOtpPaste]", inputs: { regexp: "regexp" }, outputs: { handlePaste: "handlePaste" }, host: { listeners: { "paste": "onPaste($event)" } }, queries: [{ propertyName: "inputs", predicate: ["otpInputElement"], descendants: true }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: PasteDirective, decorators: [{
type: Directive,
args: [{
standalone: true,
selector: '[ngxOtpPaste]',
}]
}], propDecorators: { inputs: [{
type: ContentChildren,
args: ['otpInputElement', { descendants: true }]
}], regexp: [{
type: Input
}], handlePaste: [{
type: Output
}], onPaste: [{
type: HostListener,
args: ['paste', ['$event']]
}] } });
class AutoFocusDirective {
ngAfterContentInit() {
if (this.ngxAutoFocus && this.firstInput) {
this.firstInput.nativeElement.focus();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: AutoFocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.1", type: AutoFocusDirective, isStandalone: true, selector: "[ngxAutoFocus]", inputs: { ngxAutoFocus: "ngxAutoFocus" }, queries: [{ propertyName: "firstInput", first: true, predicate: ["otpInputElement"], descendants: true }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: AutoFocusDirective, decorators: [{
type: Directive,
args: [{
standalone: true,
selector: '[ngxAutoFocus]',
}]
}], propDecorators: { firstInput: [{
type: ContentChild,
args: ['otpInputElement', { static: false }]
}], ngxAutoFocus: [{
type: Input
}] } });
class InputNavigationsDirective {
constructor() {
this.inputsArray = [];
this.valueChange = new EventEmitter();
}
ngAfterContentInit() {
this.inputsArray = this.inputs.toArray();
}
findInputIndex(target) {
return this.inputsArray.findIndex((input) => input.nativeElement === target);
}
setFocus(index) {
if (index >= 0 && index < this.inputs.length) {
this.inputsArray[index].nativeElement.focus();
}
}
onArrowLeft(event) {
const index = this.findInputIndex(event.target);
if (index > 0) {
this.setFocus(index - 1);
}
}
onArrowRight(event) {
const index = this.findInputIndex(event.target);
if (index < this.inputs.length - 1) {
this.setFocus(index + 1);
}
}
onBackspace(event) {
const index = this.findInputIndex(event.target);
if (index >= 0) {
this.valueChange.emit([index, '']);
this.setFocus(index - 1);
event.preventDefault();
}
}
onKeyUp(event) {
const index = this.findInputIndex(event.target);
if (event.target.value?.match(this.regexp)) {
this.valueChange.emit([index, event.target.value]);
this.setFocus(index + 1);
}
else {
this.inputsArray[index].nativeElement.value = '';
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: InputNavigationsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.1", type: InputNavigationsDirective, isStandalone: true, selector: "[ngxInputNavigations]", inputs: { regexp: "regexp" }, outputs: { valueChange: "valueChange" }, host: { listeners: { "keydown.arrowLeft": "onArrowLeft($event)", "keydown.arrowRight": "onArrowRight($event)", "keydown.backspace": "onBackspace($event)", "input": "onKeyUp($event)" } }, queries: [{ propertyName: "inputs", predicate: ["otpInputElement"], descendants: true }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: InputNavigationsDirective, decorators: [{
type: Directive,
args: [{
standalone: true,
selector: '[ngxInputNavigations]',
}]
}], propDecorators: { inputs: [{
type: ContentChildren,
args: ['otpInputElement', { descendants: true }]
}], regexp: [{
type: Input
}], valueChange: [{
type: Output
}], onArrowLeft: [{
type: HostListener,
args: ['keydown.arrowLeft', ['$event']]
}], onArrowRight: [{
type: HostListener,
args: ['keydown.arrowRight', ['$event']]
}], onBackspace: [{
type: HostListener,
args: ['keydown.backspace', ['$event']]
}], onKeyUp: [{
type: HostListener,
args: ['input', ['$event']]
}] } });
class AutoBlurDirective {
constructor() {
this.inputHTMLElements = [];
}
ngOnChanges(changes) {
if (this.ngxAutoBlur &&
this.inputHTMLElements.length > 0 &&
changes['isFormValid'].currentValue) {
this.inputHTMLElements.forEach((input) => {
input.blur();
});
}
}
ngAfterContentInit() {
this.inputs.forEach((input) => {
this.inputHTMLElements.push(input.nativeElement);
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: AutoBlurDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.1", type: AutoBlurDirective, isStandalone: true, selector: "[ngxAutoBlur]", inputs: { ngxAutoBlur: "ngxAutoBlur", isFormValid: "isFormValid" }, queries: [{ propertyName: "inputs", predicate: ["otpInputElement"], descendants: true }], usesOnChanges: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: AutoBlurDirective, decorators: [{
type: Directive,
args: [{
standalone: true,
selector: '[ngxAutoBlur]',
}]
}], propDecorators: { inputs: [{
type: ContentChildren,
args: ['otpInputElement', { descendants: true }]
}], ngxAutoBlur: [{
type: Input
}], isFormValid: [{
type: Input
}] } });
class AriaLabelsDirective {
ngAfterContentInit() {
this.setAriaLabelsAttrs();
}
getDefaultAriaLabelText(index) {
return `One Time Password Input Number ${index + 1}`;
}
setAriaLabelsAttrs() {
this.inputs.forEach((input, index) => {
input.nativeElement.setAttribute('aria-label', this.ngxOtpAriaLabels[index] ?? this.getDefaultAriaLabelText(index));
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: AriaLabelsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.1", type: AriaLabelsDirective, isStandalone: true, selector: "[ngxOtpAriaLabels]", inputs: { ngxOtpAriaLabels: "ngxOtpAriaLabels" }, queries: [{ propertyName: "inputs", predicate: ["otpInputElement"], descendants: true }], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: AriaLabelsDirective, decorators: [{
type: Directive,
args: [{
standalone: true,
selector: '[ngxOtpAriaLabels]',
}]
}], propDecorators: { inputs: [{
type: ContentChildren,
args: ['otpInputElement', { descendants: true }]
}], ngxOtpAriaLabels: [{
type: Input
}] } });
const defaultOptions = {
otpLength: 6,
autoFocus: true,
autoBlur: true,
hideInputValues: false,
regexp: /^[0-9]+$/,
showBlinkingCursor: true,
ariaLabels: [],
inputMode: 'numeric',
};
var NgxOtpStatus;
(function (NgxOtpStatus) {
NgxOtpStatus["SUCCESS"] = "success";
NgxOtpStatus["FAILED"] = "failed";
})(NgxOtpStatus || (NgxOtpStatus = {}));
class NgxOtpInputComponent {
constructor() {
this.ngxOtpOptions = defaultOptions;
this.disabled = false;
this.otpChange = new EventEmitter();
this.otpComplete = new EventEmitter();
}
set options(customOptions) {
this.ngxOtpOptions = { ...defaultOptions, ...customOptions };
}
// For testing purposes
get ngxOtpOptionsInUse() {
return this.ngxOtpOptions;
}
get inputType() {
return this.ngxOtpOptions.hideInputValues ? 'password' : 'text';
}
get isOTPSuccess() {
return this.status === NgxOtpStatus.SUCCESS;
}
get isOTPFailed() {
return this.status === NgxOtpStatus.FAILED;
}
ngOnInit() {
this.initOtpInputArray();
}
ngOnChanges(changes) {
const otpChange = changes['otp'];
if (otpChange?.currentValue) {
if (!otpChange.firstChange) {
this.setInitialOtp(otpChange.currentValue);
}
else {
this.ngxOtpOptions.autoFocus = false;
}
}
}
initOtpInputArray() {
this.ngxOtpInputArray = new FormArray(Array.from({ length: this.ngxOtpOptions.otpLength }, () => new FormControl('', Validators.required)));
if (this.otp) {
this.setInitialOtp(this.otp);
}
}
setInitialOtp(otp) {
if (this.ngxOtpOptions.regexp.test(otp)) {
const otpValueArray = otp.split('');
otpValueArray.forEach((value, index) => {
this.ngxOtpInputArray.controls[index].setValue(value ?? '');
});
this.emitOtpValueChange();
if (otpValueArray.length !== this.ngxOtpOptions.otpLength) {
console.warn('OTP length does not match the provided otpLength option!');
}
}
else {
throw new Error('Invalid OTP provided for the component <ngx-otp-input>');
}
}
handleInputChanges($event) {
const [index, value] = $event;
this.ngxOtpInputArray.controls[index].setValue(value);
this.emitOtpValueChange();
}
handlePasteChange($event) {
if ($event.length === this.ngxOtpOptions.otpLength) {
this.ngxOtpInputArray.setValue($event);
}
else {
$event.map((value, index) => {
this.ngxOtpInputArray.controls[index]?.setValue?.(value);
});
}
this.emitOtpValueChange();
}
emitOtpValueChange() {
this.otpChange.emit(this.ngxOtpInputArray.value);
if (this.ngxOtpInputArray.valid) {
this.otpComplete.emit(this.ngxOtpInputArray.value.join(''));
}
}
isInputFilled(index) {
return !!this.ngxOtpInputArray.controls[index].value;
}
reset() {
this.ngxOtpInputArray.reset();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: NgxOtpInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.1", type: NgxOtpInputComponent, isStandalone: true, selector: "ngx-otp-input", inputs: { options: "options", status: "status", disabled: "disabled", otp: "otp" }, outputs: { otpChange: "otpChange", otpComplete: "otpComplete" }, usesOnChanges: true, ngImport: i0, template: "<form\n ngxOtpPaste\n ngxInputNavigations\n [regexp]=\"ngxOtpOptions.regexp!\"\n [isFormValid]=\"ngxOtpInputArray.valid\"\n [ngxAutoFocus]=\"ngxOtpOptions.autoFocus!\"\n [ngxAutoBlur]=\"ngxOtpOptions.autoBlur!\"\n [ngxOtpAriaLabels]=\"ngxOtpOptions.ariaLabels!\"\n [ngClass]=\"{\n 'ngx-blinking-cursor': ngxOtpOptions.showBlinkingCursor\n }\"\n (valueChange)=\"handleInputChanges($event)\"\n (handlePaste)=\"handlePasteChange($event)\"\n class=\"ngx-otp-input-form\"\n data-testid=\"ngx-otp-input-form\"\n>\n <input\n *ngFor=\"let control of ngxOtpInputArray.controls; let i = index\"\n #otpInputElement\n [value]=\"control.value\"\n [type]=\"inputType\"\n [inputMode]=\"ngxOtpOptions.inputMode\"\n [disabled]=\"disabled\"\n [ngClass]=\"{\n 'ngx-otp-input-disabled': disabled,\n 'ngx-otp-input-filled': isInputFilled(i),\n 'ngx-otp-input-success': isOTPSuccess,\n 'ngx-otp-input-failed': isOTPFailed\n }\"\n class=\"ngx-otp-input-box\"\n maxlength=\"1\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n data-testid=\"ngx-otp-input-box\"\n />\n</form>\n", styles: [".ngx-otp-input-form{display:inline-flex;gap:.5rem;caret-color:transparent}.ngx-blinking-cursor{caret-color:initial}.ngx-otp-input-box{width:30px;height:35px;padding:.5rem;font-size:1.5rem;text-align:center;border:1px solid #c4c4c4;border-radius:.5rem;outline:none}.ngx-otp-input-box:focus{border-color:#007bff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: PasteDirective, selector: "[ngxOtpPaste]", inputs: ["regexp"], outputs: ["handlePaste"] }, { kind: "directive", type: AutoFocusDirective, selector: "[ngxAutoFocus]", inputs: ["ngxAutoFocus"] }, { kind: "directive", type: InputNavigationsDirective, selector: "[ngxInputNavigations]", inputs: ["regexp"], outputs: ["valueChange"] }, { kind: "directive", type: AutoBlurDirective, selector: "[ngxAutoBlur]", inputs: ["ngxAutoBlur", "isFormValid"] }, { kind: "directive", type: AriaLabelsDirective, selector: "[ngxOtpAriaLabels]", inputs: ["ngxOtpAriaLabels"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: NgxOtpInputComponent, decorators: [{
type: Component,
args: [{ standalone: true, imports: [
CommonModule,
ReactiveFormsModule,
PasteDirective,
AutoFocusDirective,
InputNavigationsDirective,
AutoBlurDirective,
AriaLabelsDirective,
], selector: 'ngx-otp-input', template: "<form\n ngxOtpPaste\n ngxInputNavigations\n [regexp]=\"ngxOtpOptions.regexp!\"\n [isFormValid]=\"ngxOtpInputArray.valid\"\n [ngxAutoFocus]=\"ngxOtpOptions.autoFocus!\"\n [ngxAutoBlur]=\"ngxOtpOptions.autoBlur!\"\n [ngxOtpAriaLabels]=\"ngxOtpOptions.ariaLabels!\"\n [ngClass]=\"{\n 'ngx-blinking-cursor': ngxOtpOptions.showBlinkingCursor\n }\"\n (valueChange)=\"handleInputChanges($event)\"\n (handlePaste)=\"handlePasteChange($event)\"\n class=\"ngx-otp-input-form\"\n data-testid=\"ngx-otp-input-form\"\n>\n <input\n *ngFor=\"let control of ngxOtpInputArray.controls; let i = index\"\n #otpInputElement\n [value]=\"control.value\"\n [type]=\"inputType\"\n [inputMode]=\"ngxOtpOptions.inputMode\"\n [disabled]=\"disabled\"\n [ngClass]=\"{\n 'ngx-otp-input-disabled': disabled,\n 'ngx-otp-input-filled': isInputFilled(i),\n 'ngx-otp-input-success': isOTPSuccess,\n 'ngx-otp-input-failed': isOTPFailed\n }\"\n class=\"ngx-otp-input-box\"\n maxlength=\"1\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n data-testid=\"ngx-otp-input-box\"\n />\n</form>\n", styles: [".ngx-otp-input-form{display:inline-flex;gap:.5rem;caret-color:transparent}.ngx-blinking-cursor{caret-color:initial}.ngx-otp-input-box{width:30px;height:35px;padding:.5rem;font-size:1.5rem;text-align:center;border:1px solid #c4c4c4;border-radius:.5rem;outline:none}.ngx-otp-input-box:focus{border-color:#007bff}\n"] }]
}], propDecorators: { options: [{
type: Input
}], status: [{
type: Input
}], disabled: [{
type: Input
}], otp: [{
type: Input
}], otpChange: [{
type: Output
}], otpComplete: [{
type: Output
}] } });
/*
* Public API Surface of ngx-otp-input
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxOtpInputComponent, NgxOtpStatus };
//# sourceMappingURL=ngx-otp-input.mjs.map