From e097334217ae93414f701e107b09b80b7d2e66ce Mon Sep 17 00:00:00 2001 From: jitender Date: Wed, 21 May 2025 12:51:53 +0530 Subject: [PATCH 1/2] fix #132 --- .../ng-otp-input/ng-otp-input.component.html | 2 +- .../ng-otp-input/ng-otp-input.component.ts | 237 +++++++++--------- src/app/app.component.html | 5 + src/app/app.component.ts | 4 +- 4 files changed, 129 insertions(+), 119 deletions(-) diff --git a/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.html b/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.html index 3b935e4..7cf4cda 100644 --- a/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.html +++ b/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.html @@ -8,7 +8,7 @@ [ngStyle]="config.inputStyles" class="otp-input {{config.inputClass}}" autocomplete="one-time-code" [formControl]="otpForm.controls[item]" #inp [id]="getBoxId(i)" - (keyup)="onKeyUp($event,i)" (input)="onInput($event,i)" (keydown)="onKeyDown($event,i)" [ngClass]="{'error-input': (config?.showError && formCtrl?.invalid && (formCtrl?.dirty || formCtrl?.touched))}"> + (keyup)="onKeyUp($event,i)" (input)="onInput($event,i)" (keydown)="onKeyDown($event,i)" [ngClass]="{'error-input': (config?.showError && formControl?.invalid && (formControl?.dirty || formControl?.touched))}"> {{config.separator}} diff --git a/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.ts b/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.ts index c833807..8338478 100644 --- a/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.ts +++ b/projects/ng-otp-input/src/lib/components/ng-otp-input/ng-otp-input.component.ts @@ -5,8 +5,10 @@ import { Output, AfterViewInit, Inject, - forwardRef} from '@angular/core'; -import { FormGroup, FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; + forwardRef, + Injector +} from '@angular/core'; +import { FormGroup, FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor, NgControl } from '@angular/forms'; import { Config } from '../../models/config'; import { KeyboardUtil } from '../../utils/keyboard-util'; import { DOCUMENT, NgClass, NgFor, NgIf, NgStyle } from '@angular/common'; @@ -15,64 +17,69 @@ import { takeUntil } from 'rxjs/operators'; import { ObjectUtil } from '../../utils/object-util'; import { OnDestroy } from '@angular/core'; @Component({ - // tslint:disable-next-line: component-selector - selector: 'ng-otp-input, ngx-otp-input', - templateUrl: './ng-otp-input.component.html', - styleUrls: ['./ng-otp-input.component.scss'], - imports: [ReactiveFormsModule, NgIf, NgFor, NgStyle, NgClass], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => NgOtpInputComponent), - multi: true, - }, - ] + // tslint:disable-next-line: component-selector + selector: 'ng-otp-input, ngx-otp-input', + templateUrl: './ng-otp-input.component.html', + styleUrls: ['./ng-otp-input.component.scss'], + imports: [ReactiveFormsModule, NgIf, NgFor, NgStyle, NgClass], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => NgOtpInputComponent), + multi: true, + }, + ] }) -export class NgOtpInputComponent implements OnInit, AfterViewInit,OnDestroy,ControlValueAccessor { +export class NgOtpInputComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor { @Input() config: Config = { length: 4 }; /** * @deprecated formCtrl is deprecated and will be removed soon. Use `FormControl` instead. */ - @Input() formCtrl:FormControl; - @Input() set disabled(isDisabled:boolean){ + @Input() formCtrl: FormControl; + @Input() set disabled(isDisabled: boolean) { this.setDisabledState(isDisabled); } @Output() onBlur = new Subject(); @Output() onInputChange = new Subject(); otpForm: FormGroup; - currentVal:string; + currentVal: string; inputControls: FormControl[] = new Array(this.config.length); - componentKey = Math.random() - .toString(36) - .substring(2) + new Date().getTime().toString(36); - get inputType(){ - return this.config?.isPasswordInput - ? 'password' - : this.config?.allowNumbersOnly - ? 'tel' - : 'text'; + componentKey = Math.random() + .toString(36) + .substring(2) + new Date().getTime().toString(36); + get inputType() { + return this.config?.isPasswordInput + ? 'password' + : this.config?.allowNumbersOnly + ? 'tel' + : 'text'; } - get controlKeys(){ return ObjectUtil.keys(this.otpForm?.controls)}; + get controlKeys() { return ObjectUtil.keys(this.otpForm?.controls) }; private destroy$ = new Subject(); private activeFocusCount = 0; - private onChange: (value: any) => void = () => {}; - private onTouched: () => void = () => {}; - private _isDisabled: boolean = false; + private onChange: (value: any) => void = () => { }; + private onTouched: () => void = () => { }; + private _isDisabled: boolean = false; + get formControl() { + return this.formCtrl ?? this.inj?.get(NgControl); + } + constructor(@Inject(DOCUMENT) private document: Document, private inj: Injector) { + + } - constructor(@Inject(DOCUMENT) private document: Document) {} ngOnInit() { this.otpForm = new FormGroup({}); for (let index = 0; index < this.config.length; index++) { this.otpForm.addControl(this.getControlName(index), new FormControl()); } - this.otpForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((v:object)=>{ + this.otpForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((v: object) => { ObjectUtil.keys(this.otpForm.controls).forEach((k) => { var val = this.otpForm.controls[k].value; - if(val && val.length>1){ + if (val && val.length > 1) { if (val.length >= this.config.length) { this.setValue(val); - }else{ + } else { this.rebuildValue(); } } @@ -103,7 +110,7 @@ export class NgOtpInputComponent implements OnInit, AfterViewInit,OnDestroy,Cont } onFocusIn() { - this.onTouched(); + this.onTouched(); this.activeFocusCount++; } @@ -111,7 +118,7 @@ export class NgOtpInputComponent implements OnInit, AfterViewInit,OnDestroy,Cont setTimeout(() => { this.activeFocusCount--; if (this.activeFocusCount === 0) { - this.onTouched(); + this.onTouched(); this.onBlur.next(); } }, 0); @@ -132,64 +139,64 @@ export class NgOtpInputComponent implements OnInit, AfterViewInit,OnDestroy,Cont return `ctrl_${idx}`; } - onKeyDown($event, inputIdx){ + onKeyDown($event, inputIdx) { const prevInputId = this.getBoxId(inputIdx - 1); const currentInputId = this.getBoxId(inputIdx); const nextInputId = this.getBoxId(inputIdx + 1); if (KeyboardUtil.ifSpacebar($event)) { $event.preventDefault(); return false; - } - if (KeyboardUtil.ifBackspace($event)) { - if(!$event.target.value){ - this.clearInput(prevInputId,inputIdx-1); + } + if (KeyboardUtil.ifBackspace($event)) { + if (!$event.target.value) { + this.clearInput(prevInputId, inputIdx - 1); this.setSelected(prevInputId); - }else{ - this.clearInput(currentInputId,inputIdx); + } else { + this.clearInput(currentInputId, inputIdx); } this.rebuildValue(); return; } if (KeyboardUtil.ifDelete($event)) { - if(!$event.target.value){ - this.clearInput(prevInputId,inputIdx-1); + if (!$event.target.value) { + this.clearInput(prevInputId, inputIdx - 1); this.setSelected(prevInputId); - }else{ - this.clearInput(currentInputId,inputIdx); + } else { + this.clearInput(currentInputId, inputIdx); } this.rebuildValue(); return; } - + } - hasVal(val){ + hasVal(val) { return val != null && val != undefined && (!val?.trim || val.trim() != ''); } - onInput($event,inputIdx){ - let newVal=this.hasVal(this.currentVal) ? `${this.currentVal}${$event.target.value}` : $event.target.value; - if(this.config.allowNumbersOnly && !this.validateNumber(newVal)){ - $event.target.value=null; + onInput($event, inputIdx) { + let newVal = this.hasVal(this.currentVal) ? `${this.currentVal}${$event.target.value}` : $event.target.value; + if (this.config.allowNumbersOnly && !this.validateNumber(newVal)) { + $event.target.value = null; $event.stopPropagation(); $event.preventDefault(); - this.clearInput(null,inputIdx); + this.clearInput(null, inputIdx); return; } - if (this.ifValidKeyCode(null,$event.target.value)) { + if (this.ifValidKeyCode(null, $event.target.value)) { const nextInputId = this.getBoxId(inputIdx + 1); this.setSelected(nextInputId); this.rebuildValue(); - }else{ - $event.target.value=null; - let ctrlName=this.getControlName(inputIdx); + } else { + $event.target.value = null; + let ctrlName = this.getControlName(inputIdx); this.otpForm.controls[ctrlName]?.setValue(null); this.rebuildValue(); } } - + onKeyUp($event, inputIdx) { - if(KeyboardUtil.ifTab($event)){ - inputIdx-=1; + if (KeyboardUtil.ifTab($event)) { + inputIdx -= 1; } const nextInputId = this.getBoxId(inputIdx + 1); const prevInputId = this.getBoxId(inputIdx - 1); @@ -205,26 +212,26 @@ export class NgOtpInputComponent implements OnInit, AfterViewInit,OnDestroy,Cont } } - validateNumber(val){ + validateNumber(val) { return val && /^[0-9]+$/.test(val); } - getBoxId(idx:string | number){ + getBoxId(idx: string | number) { return `otp_${idx}_${this.componentKey}`; } - private clearInput(eleId:string,inputIdx){ - let ctrlName=this.getControlName(inputIdx); + private clearInput(eleId: string, inputIdx) { + let ctrlName = this.getControlName(inputIdx); this.otpForm.controls[ctrlName]?.setValue(null); - if(eleId){ - const ele=this.document.getElementById(eleId); - if(ele && ele instanceof HTMLInputElement){ - ele.value=null; + if (eleId) { + const ele = this.document.getElementById(eleId); + if (ele && ele instanceof HTMLInputElement) { + ele.value = null; } } } - private setSelected(eleId) { + private setSelected(eleId) { this.focusTo(eleId); const ele: any = this.document.getElementById(eleId); if (ele && ele.setSelectionRange) { @@ -234,15 +241,15 @@ export class NgOtpInputComponent implements OnInit, AfterViewInit,OnDestroy,Cont } } - private ifValidKeyCode(event,val?) { - const inp =val??event.key; - if(this.config?.allowNumbersOnly){ + private ifValidKeyCode(event, val?) { + const inp = val ?? event.key; + if (this.config?.allowNumbersOnly) { return this.validateNumber(inp); } const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); return ( isMobile || - (/^[a-zA-Z0-9%*_\-@#$!]$/.test(inp) && inp.length==1) + (/^[a-zA-Z0-9%*_\-@#$!]$/.test(inp) && inp.length == 1) ); } @@ -256,77 +263,75 @@ export class NgOtpInputComponent implements OnInit, AfterViewInit,OnDestroy,Cont // method to set component value setValue(value: any) { if (this.config.allowNumbersOnly && isNaN(value)) { - return; + return; } this.otpForm?.reset(); - if (!this.hasVal(value)) { - this.rebuildValue(); - return; - } - value = value.toString().replace(/\s/g, ''); // remove whitespace - Array.from(value).forEach((c, idx) => { - if (this.otpForm.get(this.getControlName(idx))) { - this.otpForm.get(this.getControlName(idx)).setValue(c); - } - }); - if (!this.config.disableAutoFocus) { + if (!this.hasVal(value)) { + this.rebuildValue(); + return; + } + value = value.toString().replace(/\s/g, ''); // remove whitespace + Array.from(value).forEach((c, idx) => { + if (this.otpForm.get(this.getControlName(idx))) { + this.otpForm.get(this.getControlName(idx)).setValue(c); + } + }); + if (!this.config.disableAutoFocus) { setTimeout(() => { const containerItem = this.document.getElementById(`c_${this.componentKey}`); var indexOfElementToFocus = value.length < this.config.length ? value.length : (this.config.length - 1); - let ele : any = containerItem.getElementsByClassName('otp-input')[indexOfElementToFocus]; + let ele: any = containerItem.getElementsByClassName('otp-input')[indexOfElementToFocus]; if (ele && ele.focus) { setTimeout(() => { ele.focus(); }, 1); } }, 0); - - } - this.rebuildValue(); + + } + this.rebuildValue(); } -private rebuildValue() { + private rebuildValue() { let val = null; ObjectUtil.keys(this.otpForm.controls).forEach(k => { - let ctrlVal=this.otpForm.controls[k].value; + let ctrlVal = this.otpForm.controls[k].value; if (ctrlVal) { - let isLengthExceed=ctrlVal.length>1; - let isCaseTransformEnabled= !this.config.allowNumbersOnly && this.config.letterCase && (this.config.letterCase.toLocaleLowerCase() == 'upper' || this.config.letterCase.toLocaleLowerCase()== 'lower'); - ctrlVal=ctrlVal[0]; - let transformedVal=isCaseTransformEnabled ? this.config.letterCase.toLocaleLowerCase() == 'upper' ? ctrlVal.toUpperCase() : ctrlVal.toLowerCase() : ctrlVal; - if(isCaseTransformEnabled && transformedVal == ctrlVal){ - isCaseTransformEnabled=false; - }else{ - ctrlVal=transformedVal; + let isLengthExceed = ctrlVal.length > 1; + let isCaseTransformEnabled = !this.config.allowNumbersOnly && this.config.letterCase && (this.config.letterCase.toLocaleLowerCase() == 'upper' || this.config.letterCase.toLocaleLowerCase() == 'lower'); + ctrlVal = ctrlVal[0]; + let transformedVal = isCaseTransformEnabled ? this.config.letterCase.toLocaleLowerCase() == 'upper' ? ctrlVal.toUpperCase() : ctrlVal.toLowerCase() : ctrlVal; + if (isCaseTransformEnabled && transformedVal == ctrlVal) { + isCaseTransformEnabled = false; + } else { + ctrlVal = transformedVal; } - if(val == null) - { - val=ctrlVal; - }else{ + if (val == null) { + val = ctrlVal; + } else { val += ctrlVal; } - if(isLengthExceed || isCaseTransformEnabled) - { - this.otpForm.controls[k].setValue(ctrlVal); + if (isLengthExceed || isCaseTransformEnabled) { + this.otpForm.controls[k].setValue(ctrlVal); } } }); - if(this.currentVal != val){ - this.currentVal=val; - this.onChange(val); - if(this.formCtrl?.setValue){ + if (this.currentVal != val) { + this.currentVal = val; + this.onChange(val); + if (this.formCtrl?.setValue) { this.formCtrl.setValue(val); } this.onInputChange.next(val); } } - - + + handlePaste(e) { // Get pasted data via clipboard API let clipboardData = e.clipboardData || window['clipboardData']; - if(clipboardData){ - var pastedData =clipboardData.getData('Text'); + if (clipboardData) { + var pastedData = clipboardData.getData('Text'); } e.stopPropagation(); e.preventDefault(); diff --git a/src/app/app.component.html b/src/app/app.component.html index c1be5ea..eb6a087 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -22,6 +22,11 @@ Disabled +
  • + + Show Error +
  • Number of inputs }>=new FormGroup({ - otp:new FormControl(null) + otp:new FormControl(null,[Validators.required]) }); otp: string; showOtpComponent = true; From 8024df70d1401184ba96ff1b49daf0f6a8104bd5 Mon Sep 17 00:00:00 2001 From: jitender Date: Wed, 21 May 2025 13:28:26 +0530 Subject: [PATCH 2/2] package version update --- projects/ng-otp-input/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/ng-otp-input/package.json b/projects/ng-otp-input/package.json index 3e66c18..7488e95 100644 --- a/projects/ng-otp-input/package.json +++ b/projects/ng-otp-input/package.json @@ -1,6 +1,6 @@ { "name": "ng-otp-input", - "version": "2.0.7", + "version": "2.0.8", "description": "A fully customizable, one-time password input component for the web built with Angular.", "author": {"name": "jitender"}, "homepage": "https://github.com/code-farmz/ng-otp-input#readme",