@ruc-lib/input-otp 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,63 +14,74 @@ npm install @uxpractice/ruc-lib
14
14
  ```
15
15
 
16
16
  ### Install Individual Component
17
- If you only need the Input OTP component:
18
- ```bash
19
- npm install @ruc-lib/input-otp`
20
17
 
18
+ If you only need the InputOtp component:
19
+
20
+ **For Angular 15:**
21
+ ```bash
22
+ npm install @ruc-lib/input-otp@3.2.0 @angular/material@^15.0.0 @angular/cdk@^15.0.0
21
23
  ```
22
24
 
23
- After installing the the required package successfully, we have to import the CSS in our style.scss file in following way.
25
+ **For Angular 16:**
26
+ ```bash
27
+ npm install @ruc-lib/input-otp@3.2.0 @angular/material@^16.0.0 @angular/cdk@^16.0.0
28
+ ```
24
29
 
25
- `@import "../../../node_modules/primeng/resources/themes/lara-light-indigo/theme.css";`
26
- `@import "../../../node_modules/primeng/resources/primeng.min.css";`
30
+ **For Angular 17:**
31
+ ```bash
32
+ npm install @ruc-lib/input-otp@4.0.0 @angular/material@^17.0.0 @angular/cdk@^17.0.0
33
+ ```
27
34
 
28
- here path of the scss file is subject to change as per choice of the installation
35
+ **For Angular 18:**
36
+ ```bash
37
+ npm install @ruc-lib/input-otp@4.0.0 @angular/material@^18.0.0 @angular/cdk@^18.0.0
38
+ ```
29
39
 
30
- for library
31
- `@import "../node_modules/primeng/resources/themes/lara-light-indigo/theme.css";`
32
- `@import "../node_modules/primeng/resources/primeng.min.css";`
40
+ **For Angular 19:**
41
+ ```bash
42
+ npm install @ruc-lib/input-otp@4.0.0 @angular/material@^19.0.0 @angular/cdk@^19.0.0
43
+ ```
33
44
 
34
- for seperate package
35
- `@import "../node_modules/primeng/resources/themes/lara-light-indigo/theme.css";`
36
- `@import "../node_modules/primeng/resources/primeng.min.css";`
45
+ **For Angular 20:**
46
+ ```bash
47
+ npm install @ruc-lib/input-otp@4.0.0
48
+ ```
37
49
 
50
+ > **Note:** When installing in Angular 15-19 apps, you must specify the matching `@angular/material` and `@angular/cdk` versions to avoid peer dependency conflicts. Angular 20 will automatically use the correct versions.
38
51
 
39
- # Version Compatibility
52
+ ### 📦 Version Compatibility
40
53
 
41
- Please ensure you install the correct version of `@ruc-lib/input-otp` based on your Angular version.
42
-
43
54
  | Angular Version | Compatible `@ruc-lib/input-otp` Version |
44
55
  |--------------------|---------------------------------------------|
45
56
  | 15.x.x | `npm install @ruc-lib/input-otp@^3.2.0` |
46
57
  | 16.x.x | `npm install @ruc-lib/input-otp@^3.2.0` |
47
-
58
+ | 17.x.x | `npm install @ruc-lib/input-otp@^4.0.0` |
59
+ | 18.x.x | `npm install @ruc-lib/input-otp@^4.0.0` |
60
+ | 19.x.x | `npm install @ruc-lib/input-otp@^4.0.0` |
61
+ | 20.x.x | `npm install @ruc-lib/input-otp@^4.0.0` |
48
62
 
49
63
  ## Usage
50
64
 
51
- ### 1. Import the Module
52
- In your Angular module file (e.g., `app.module.ts`), import the `RuclibInputOtpModule`:
65
+ ### 1. Import the Component
66
+ In your Angular component file (e.g., `app.component.ts`), import the `RuclibInputOtpComponent`:
53
67
 
54
68
  ```typescript
69
+ import { Component } from '@angular/core';
70
+
55
71
  // For Complete Library
56
- import { RuclibInputOtpModule } from '@uxpractice/ruc-lib/input-otp';
72
+ import { RuclibInputOtpComponent } from '@uxpractice/ruc-lib/input-otp';
57
73
 
58
74
  // For Individual Package
59
- import { RuclibInputOtpModule } from '@ruc-lib/input-otp';
60
- import { AppComponent } from './app.component';
61
- import { NgModule } from '@angular/core';
62
- import { BrowserModule } from '@angular/platform-browser';
63
-
64
- @NgModule({
65
- declarations: [AppComponent],
66
- imports: [
67
- BrowserModule,
68
- RuclibInputOtpModule
69
- ],
70
- providers: [],
71
- bootstrap: [AppComponent]
75
+ import { RuclibInputOtpComponent } from '@ruc-lib/input-otp';
76
+
77
+ @Component({
78
+ selector: 'app-root',
79
+ imports: [RuclibInputOtpComponent],
80
+ templateUrl: './app.component.html',
72
81
  })
73
- export class AppModule {}
82
+ export class AppComponent {
83
+ // Component code here
84
+ }
74
85
  ```
75
86
  ### 2. Use the Component
76
87
  In your component's template, use the `<uxp-ruclib-input-otp>` selector and pass the configuration object to the `rucInputData` input.
@@ -175,5 +186,4 @@ When implementing **custom themes**, such as:
175
186
  Contributions are welcome! Feel free to open issues or pull requests for any enhancements or fixes.
176
187
 
177
188
  # Acknowledgements
178
- Thank you for choosing the Input OTP Component Library. If you have any feedback or suggestions, please let us know!
179
-
189
+ Thank you for choosing the Input OTP Component Library. If you have any feedback or suggestions, please let us know!
@@ -0,0 +1,153 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, ViewChildren, Input, Output, Component } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import { MatIconModule } from '@angular/material/icon';
6
+ import * as i2 from '@angular/forms';
7
+ import { FormsModule } from '@angular/forms';
8
+
9
+ const BACKSPACE_KEY = "Backspace";
10
+
11
+ class RuclibInputOtpComponent {
12
+ constructor() {
13
+ this.DEFAULT_OTP_LENGTH = 6;
14
+ this.TIME_LEFT = "Time left";
15
+ this.EXPIRED_MSG = "OTP expired. Please request a new one.";
16
+ this.rucEvent = new EventEmitter();
17
+ this.rucInputData = {};
18
+ this.timeLeft = 0;
19
+ this.timerId = null;
20
+ this.otpValues = [];
21
+ }
22
+ ngOnInit() {
23
+ this.otpValues = Array(this.rucInputData.length || 6).fill('');
24
+ if (this.rucInputData.timeLimit) {
25
+ this.timeLeft = this.rucInputData.timeLimit;
26
+ }
27
+ // Set default for copyProtection
28
+ if (this.rucInputData.copyProtection === undefined) {
29
+ this.rucInputData.copyProtection = true;
30
+ }
31
+ }
32
+ ngAfterViewInit() {
33
+ // Merge input config with defaults
34
+ this.otpValues = Array(this.rucInputData.length || 6).fill('');
35
+ if (this.rucInputData.autoFocus) {
36
+ setTimeout(() => this.inputs.first?.nativeElement.focus(), 0); // Auto-focus first input
37
+ }
38
+ if (this.rucInputData.timeLimit) {
39
+ this.timeLeft = this.rucInputData.timeLimit;
40
+ this.timerId = setInterval(() => {
41
+ this.timeLeft--;
42
+ if (this.timeLeft <= 0) {
43
+ clearInterval(this.timerId);
44
+ this.disableAllInputs();
45
+ this.rucEvent.emit({ eventName: 'timeout', eventOutput: null });
46
+ }
47
+ }, 1000);
48
+ }
49
+ }
50
+ // Handle OTP input
51
+ handleInput(event, index) {
52
+ const input = event.target;
53
+ const value = input.value;
54
+ if (this.rucInputData.integerOnly && !/^\d*$/.test(value)) {
55
+ input.value = ''; // Allow integers only
56
+ this.otpValues[index] = '';
57
+ return;
58
+ }
59
+ this.otpValues[index] = value.slice(-1); // Take last digit
60
+ input.value = this.otpValues[index];
61
+ // Auto-jump to next input
62
+ if (value && index < (this.rucInputData.length || 6) - 1 && this.rucInputData.autoFocus) {
63
+ this.inputs.toArray()[index + 1].nativeElement.focus();
64
+ }
65
+ // Auto-submit on complete
66
+ if (this.rucInputData.autoSubmit && this.isOtpComplete(this.otpValues)) {
67
+ if (this.timerId) {
68
+ clearInterval(this.timerId);
69
+ this.timerId = null;
70
+ }
71
+ this.rucEvent.emit({ eventName: 'completed', eventOutput: this.otpValues.join('') });
72
+ }
73
+ }
74
+ // Handle Keyboard navigation
75
+ handleKeyDown(event, index) {
76
+ const input = event.target;
77
+ if (event.key === BACKSPACE_KEY && !input.value && index > 0 && this.rucInputData.autoFocus) {
78
+ this.inputs.toArray()[index - 1].nativeElement.focus();
79
+ }
80
+ else if (event.key === 'ArrowLeft' && index > 0) {
81
+ event.preventDefault();
82
+ this.inputs.toArray()[index - 1].nativeElement.focus();
83
+ }
84
+ else if (event.key === 'ArrowRight' && index < (this.rucInputData.length || 6) - 1) {
85
+ event.preventDefault();
86
+ this.inputs.toArray()[index + 1].nativeElement.focus();
87
+ }
88
+ }
89
+ //Handle Paste Event
90
+ handlePaste(event) {
91
+ event.preventDefault();
92
+ let pasteData = event.clipboardData?.getData('text');
93
+ if (this.rucInputData.integerOnly) {
94
+ pasteData = pasteData?.replace(/\D/g, ''); // Extract digits only
95
+ }
96
+ if (!pasteData)
97
+ return;
98
+ const pasteLength = Math.min(pasteData.length, this.rucInputData.length || 6);
99
+ for (let i = 0; i < pasteLength; i++) {
100
+ this.otpValues[i] = pasteData[i];
101
+ this.inputs.toArray()[i].nativeElement.value = pasteData[i];
102
+ }
103
+ // Focus last filled input or last input
104
+ const focusIndex = pasteLength < (this.rucInputData.length || 6) ? pasteLength : (this.rucInputData.length || 6) - 1;
105
+ this.inputs.toArray()[focusIndex].nativeElement.focus();
106
+ // Auto-submit if complete
107
+ if (this.rucInputData.autoSubmit && this.isOtpComplete(this.otpValues)) {
108
+ if (this.timerId) {
109
+ clearInterval(this.timerId);
110
+ this.timerId = null;
111
+ }
112
+ this.rucEvent.emit({ eventName: 'completed', eventOutput: this.otpValues.join('') });
113
+ }
114
+ }
115
+ //Check if OTP is completed
116
+ isOtpComplete(otpArray) {
117
+ return otpArray.every(val => val.trim() !== '');
118
+ }
119
+ // Disable inputs
120
+ disableAllInputs() {
121
+ this.inputs.forEach((inputRef) => {
122
+ inputRef.nativeElement.disabled = true;
123
+ });
124
+ }
125
+ //Clear Interval
126
+ ngOnDestroy() {
127
+ if (this.timerId) {
128
+ clearInterval(this.timerId);
129
+ }
130
+ }
131
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RuclibInputOtpComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
132
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.9", type: RuclibInputOtpComponent, isStandalone: true, selector: "uxp-ruclib-input-otp", inputs: { customTheme: "customTheme", rucInputData: "rucInputData" }, outputs: { rucEvent: "rucEvent" }, viewQueries: [{ propertyName: "inputs", predicate: ["otpInput"], descendants: true }], ngImport: i0, template: "<div class=\"{{customTheme}} otp-container\" [ngClass]=\"rucInputData.size || 'medium'\" (paste)=\"handlePaste($event)\">\r\n @for (_ of [].constructor(rucInputData.length || DEFAULT_OTP_LENGTH); track _; let i = $index) {\r\n <input\r\n #otpInput\r\n [type]=\"rucInputData.mask ? 'password' : 'text'\"\r\n [ngClass]=\"[\r\n 'otp-input',\r\n rucInputData.templateType || 'rectangle',\r\n rucInputData.validationState === 'valid' ? 'is-valid' : '',\r\n rucInputData.validationState === 'invalid' ? 'is-invalid' : ''\r\n ]\"\r\n maxlength=\"1\"\r\n [(ngModel)]=\"otpValues[i]\"\r\n (input)=\"handleInput($event, i)\"\r\n (copy)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (paste)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (contextmenu)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (keydown)=\"handleKeyDown($event, i)\"\r\n [attr.inputmode]=\"rucInputData.integerOnly ? 'numeric' : 'text'\"\r\n [pattern]=\"rucInputData.integerOnly ? '[0-9]*' : '.*'\"\r\n autocomplete=\"one-time-code\">\r\n }\r\n</div>\r\n<!-- Timer -->\r\n@if (rucInputData.timeLimit) {\r\n <div class=\"otp-timer\">\r\n {{TIME_LEFT}}: {{ timeLeft }}s\r\n </div>\r\n}\r\n<!-- Timeout message -->\r\n@if (rucInputData.timeLimit && timeLeft === 0) {\r\n <div class=\"otp-timeout-message\">\r\n {{EXPIRED_MSG}}\r\n </div>\r\n}\r\n\r\n", styles: [".otp-container{display:flex;gap:10px;justify-content:center}.otp-input{text-align:center;font-size:18px;border:1px solid #ccc;border-radius:4px;outline:none;transition:border-color .2s}.otp-input:focus{border-color:#007bff;box-shadow:0 0 5px #007bff4d}.otp-input::-webkit-outer-spin-button,.otp-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.otp-input[type=number]{-moz-appearance:textfield}.small .otp-input{width:30px;height:30px;font-size:14px}.medium .otp-input{width:40px;height:40px;font-size:18px}.large .otp-input{width:50px;height:50px;font-size:22px}.otp-timer{text-align:center;font-size:.9rem;color:#d9534f;font-weight:700}.otp-timeout-message{text-align:center;color:#d9534f;font-size:.9rem;margin-top:.5rem}.otp-input.rectangle{border-radius:8px}.otp-input.circle{border-radius:50%}.otp-input.underscore{border:none;border-bottom:2px solid #ccc;border-radius:0;background-color:transparent}.otp-input.is-valid{border-color:#28a745;background-color:#e6ffed}.otp-input.is-invalid{border-color:#dc3545;background-color:#ffe6e6}.dark-theme .otp-input:not(.is-invalid){color:#fff;background-color:#333}.light-theme .otp-input:not(.is-invalid){color:#000}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); }
133
+ }
134
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: RuclibInputOtpComponent, decorators: [{
135
+ type: Component,
136
+ args: [{ selector: 'uxp-ruclib-input-otp', imports: [CommonModule, MatIconModule, FormsModule], template: "<div class=\"{{customTheme}} otp-container\" [ngClass]=\"rucInputData.size || 'medium'\" (paste)=\"handlePaste($event)\">\r\n @for (_ of [].constructor(rucInputData.length || DEFAULT_OTP_LENGTH); track _; let i = $index) {\r\n <input\r\n #otpInput\r\n [type]=\"rucInputData.mask ? 'password' : 'text'\"\r\n [ngClass]=\"[\r\n 'otp-input',\r\n rucInputData.templateType || 'rectangle',\r\n rucInputData.validationState === 'valid' ? 'is-valid' : '',\r\n rucInputData.validationState === 'invalid' ? 'is-invalid' : ''\r\n ]\"\r\n maxlength=\"1\"\r\n [(ngModel)]=\"otpValues[i]\"\r\n (input)=\"handleInput($event, i)\"\r\n (copy)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (paste)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (contextmenu)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (keydown)=\"handleKeyDown($event, i)\"\r\n [attr.inputmode]=\"rucInputData.integerOnly ? 'numeric' : 'text'\"\r\n [pattern]=\"rucInputData.integerOnly ? '[0-9]*' : '.*'\"\r\n autocomplete=\"one-time-code\">\r\n }\r\n</div>\r\n<!-- Timer -->\r\n@if (rucInputData.timeLimit) {\r\n <div class=\"otp-timer\">\r\n {{TIME_LEFT}}: {{ timeLeft }}s\r\n </div>\r\n}\r\n<!-- Timeout message -->\r\n@if (rucInputData.timeLimit && timeLeft === 0) {\r\n <div class=\"otp-timeout-message\">\r\n {{EXPIRED_MSG}}\r\n </div>\r\n}\r\n\r\n", styles: [".otp-container{display:flex;gap:10px;justify-content:center}.otp-input{text-align:center;font-size:18px;border:1px solid #ccc;border-radius:4px;outline:none;transition:border-color .2s}.otp-input:focus{border-color:#007bff;box-shadow:0 0 5px #007bff4d}.otp-input::-webkit-outer-spin-button,.otp-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.otp-input[type=number]{-moz-appearance:textfield}.small .otp-input{width:30px;height:30px;font-size:14px}.medium .otp-input{width:40px;height:40px;font-size:18px}.large .otp-input{width:50px;height:50px;font-size:22px}.otp-timer{text-align:center;font-size:.9rem;color:#d9534f;font-weight:700}.otp-timeout-message{text-align:center;color:#d9534f;font-size:.9rem;margin-top:.5rem}.otp-input.rectangle{border-radius:8px}.otp-input.circle{border-radius:50%}.otp-input.underscore{border:none;border-bottom:2px solid #ccc;border-radius:0;background-color:transparent}.otp-input.is-valid{border-color:#28a745;background-color:#e6ffed}.otp-input.is-invalid{border-color:#dc3545;background-color:#ffe6e6}.dark-theme .otp-input:not(.is-invalid){color:#fff;background-color:#333}.light-theme .otp-input:not(.is-invalid){color:#000}\n"] }]
137
+ }], propDecorators: { rucEvent: [{
138
+ type: Output
139
+ }], customTheme: [{
140
+ type: Input
141
+ }], rucInputData: [{
142
+ type: Input
143
+ }], inputs: [{
144
+ type: ViewChildren,
145
+ args: ['otpInput']
146
+ }] } });
147
+
148
+ /**
149
+ * Generated bundle index. Do not edit.
150
+ */
151
+
152
+ export { RuclibInputOtpComponent };
153
+ //# sourceMappingURL=ruc-lib-input-otp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruc-lib-input-otp.mjs","sources":["../../src/modal/constants.ts","../../src/lib/ruclib-input-otp/ruclib-input-otp.component.ts","../../src/lib/ruclib-input-otp/ruclib-input-otp.component.html","../../src/ruc-lib-input-otp.ts"],"sourcesContent":["export const BACKSPACE_KEY = \"Backspace\"","import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, QueryList, ViewChildren } from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { MatIconModule } from '@angular/material/icon';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { InputOtpConfig } from '../../modal/input-otp-config';\r\nimport { BACKSPACE_KEY } from '../../modal/constants';\r\n\r\n@Component({\r\n selector: 'uxp-ruclib-input-otp',\r\n imports: [CommonModule, MatIconModule, FormsModule],\r\n templateUrl: './ruclib-input-otp.component.html',\r\n styleUrl: './ruclib-input-otp.component.scss'\r\n})\r\nexport class RuclibInputOtpComponent implements OnInit, AfterViewInit, OnDestroy {\r\n readonly DEFAULT_OTP_LENGTH = 6;\r\n readonly TIME_LEFT = \"Time left\";\r\n readonly EXPIRED_MSG = \"OTP expired. Please request a new one.\"\r\n\r\n @Output() rucEvent = new EventEmitter<any>();\r\n @Input() customTheme?: string;\r\n\r\n @Input() rucInputData: InputOtpConfig = {};\r\n timeLeft = 0;\r\n timerId: any = null;\r\n\r\n @ViewChildren('otpInput') inputs!: QueryList<ElementRef<HTMLInputElement>>;\r\n\r\n otpValues: string[] = [];\r\n\r\n ngOnInit(): void {\r\n this.otpValues = Array(this.rucInputData.length || 6).fill('');\r\n if (this.rucInputData.timeLimit) {\r\n this.timeLeft = this.rucInputData.timeLimit;\r\n }\r\n\r\n // Set default for copyProtection\r\n if (this.rucInputData.copyProtection === undefined) {\r\n this.rucInputData.copyProtection = true;\r\n }\r\n }\r\n\r\n ngAfterViewInit(): void {\r\n // Merge input config with defaults\r\n this.otpValues = Array(this.rucInputData.length || 6).fill('');\r\n if (this.rucInputData.autoFocus) {\r\n setTimeout(() => this.inputs.first?.nativeElement.focus(), 0); // Auto-focus first input\r\n }\r\n\r\n if (this.rucInputData.timeLimit) {\r\n this.timeLeft = this.rucInputData.timeLimit;\r\n this.timerId = setInterval(() => {\r\n this.timeLeft--;\r\n if (this.timeLeft <= 0) {\r\n clearInterval(this.timerId);\r\n this.disableAllInputs();\r\n this.rucEvent.emit({ eventName: 'timeout', eventOutput: null });\r\n }\r\n }, 1000);\r\n }\r\n }\r\n// Handle OTP input\r\n handleInput(event: Event, index: number): void {\r\n const input = event.target as HTMLInputElement;\r\n const value = input.value;\r\n\r\n if (this.rucInputData.integerOnly && !/^\\d*$/.test(value)) {\r\n input.value = ''; // Allow integers only\r\n this.otpValues[index] = '';\r\n return;\r\n }\r\n\r\n this.otpValues[index] = value.slice(-1); // Take last digit\r\n input.value = this.otpValues[index];\r\n\r\n // Auto-jump to next input\r\n if (value && index < (this.rucInputData.length || 6) - 1 && this.rucInputData.autoFocus) {\r\n this.inputs.toArray()[index + 1].nativeElement.focus();\r\n }\r\n\r\n // Auto-submit on complete\r\n if (this.rucInputData.autoSubmit && this.isOtpComplete(this.otpValues)) {\r\n if (this.timerId) {\r\n clearInterval(this.timerId);\r\n this.timerId = null;\r\n }\r\n this.rucEvent.emit({ eventName: 'completed', eventOutput: this.otpValues.join('') });\r\n }\r\n }\r\n\r\n// Handle Keyboard navigation\r\n handleKeyDown(event: KeyboardEvent, index: number): void {\r\n const input = event.target as HTMLInputElement;\r\n\r\n if (event.key === BACKSPACE_KEY && !input.value && index > 0 && this.rucInputData.autoFocus) {\r\n this.inputs.toArray()[index - 1].nativeElement.focus();\r\n } else if (event.key === 'ArrowLeft' && index > 0) {\r\n event.preventDefault();\r\n this.inputs.toArray()[index - 1].nativeElement.focus();\r\n } else if (event.key === 'ArrowRight' && index < (this.rucInputData.length || 6) - 1) {\r\n event.preventDefault();\r\n this.inputs.toArray()[index + 1].nativeElement.focus();\r\n }\r\n }\r\n\r\n//Handle Paste Event\r\n handlePaste(event: ClipboardEvent): void {\r\n event.preventDefault();\r\n let pasteData = event.clipboardData?.getData('text');\r\n if (this.rucInputData.integerOnly) {\r\n pasteData = pasteData?.replace(/\\D/g, ''); // Extract digits only\r\n }\r\n if (!pasteData) return;\r\n\r\n const pasteLength = Math.min(pasteData.length, this.rucInputData.length || 6);\r\n for (let i = 0; i < pasteLength; i++) {\r\n this.otpValues[i] = pasteData[i];\r\n this.inputs.toArray()[i].nativeElement.value = pasteData[i];\r\n }\r\n\r\n // Focus last filled input or last input\r\n const focusIndex = pasteLength < (this.rucInputData.length || 6) ? pasteLength : (this.rucInputData.length || 6) - 1;\r\n this.inputs.toArray()[focusIndex].nativeElement.focus();\r\n\r\n // Auto-submit if complete\r\n if (this.rucInputData.autoSubmit && this.isOtpComplete(this.otpValues)) {\r\n if (this.timerId) {\r\n clearInterval(this.timerId);\r\n this.timerId = null;\r\n }\r\n this.rucEvent.emit({ eventName: 'completed', eventOutput: this.otpValues.join('') });\r\n }\r\n }\r\n\r\n //Check if OTP is completed\r\n private isOtpComplete(otpArray: string[]): boolean {\r\n return otpArray.every(val => val.trim() !== '');\r\n }\r\n\r\n// Disable inputs\r\n private disableAllInputs(): void {\r\n this.inputs.forEach((inputRef) => {\r\n inputRef.nativeElement.disabled = true;\r\n });\r\n }\r\n\r\n //Clear Interval\r\n ngOnDestroy(): void {\r\n if (this.timerId) {\r\n clearInterval(this.timerId);\r\n }\r\n }\r\n}\r\n\r\n","<div class=\"{{customTheme}} otp-container\" [ngClass]=\"rucInputData.size || 'medium'\" (paste)=\"handlePaste($event)\">\r\n @for (_ of [].constructor(rucInputData.length || DEFAULT_OTP_LENGTH); track _; let i = $index) {\r\n <input\r\n #otpInput\r\n [type]=\"rucInputData.mask ? 'password' : 'text'\"\r\n [ngClass]=\"[\r\n 'otp-input',\r\n rucInputData.templateType || 'rectangle',\r\n rucInputData.validationState === 'valid' ? 'is-valid' : '',\r\n rucInputData.validationState === 'invalid' ? 'is-invalid' : ''\r\n ]\"\r\n maxlength=\"1\"\r\n [(ngModel)]=\"otpValues[i]\"\r\n (input)=\"handleInput($event, i)\"\r\n (copy)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (paste)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (contextmenu)=\"rucInputData.copyProtection && $event.preventDefault()\"\r\n (keydown)=\"handleKeyDown($event, i)\"\r\n [attr.inputmode]=\"rucInputData.integerOnly ? 'numeric' : 'text'\"\r\n [pattern]=\"rucInputData.integerOnly ? '[0-9]*' : '.*'\"\r\n autocomplete=\"one-time-code\">\r\n }\r\n</div>\r\n<!-- Timer -->\r\n@if (rucInputData.timeLimit) {\r\n <div class=\"otp-timer\">\r\n {{TIME_LEFT}}: {{ timeLeft }}s\r\n </div>\r\n}\r\n<!-- Timeout message -->\r\n@if (rucInputData.timeLimit && timeLeft === 0) {\r\n <div class=\"otp-timeout-message\">\r\n {{EXPIRED_MSG}}\r\n </div>\r\n}\r\n\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -1,3 +1,47 @@
1
- export * from './lib/ruclib-input-otp.module';
2
- export * from './lib/input-otp/input-otp.component';
3
- export * from './modal/input-otp-config';
1
+ import * as i0 from '@angular/core';
2
+ import { OnInit, AfterViewInit, OnDestroy, EventEmitter, QueryList, ElementRef } from '@angular/core';
3
+
4
+ interface InputOtpConfig {
5
+ /** Number of OTP digits */
6
+ length?: number;
7
+ /** Mask input as password */
8
+ mask?: boolean;
9
+ /** Sizes: small, medium, large */
10
+ size?: 'small' | 'medium' | 'large';
11
+ /** Only allow integers 0-9 */
12
+ integerOnly?: boolean;
13
+ /** Autofocus and auto-jump */
14
+ autoFocus?: boolean;
15
+ autoSubmit?: boolean;
16
+ timeLimit?: number;
17
+ copyProtection?: boolean;
18
+ templateType?: 'rectangle' | 'circle' | 'underscore';
19
+ /** Validation state for visual feedback */
20
+ validationState?: 'valid' | 'invalid' | undefined;
21
+ }
22
+
23
+ declare class RuclibInputOtpComponent implements OnInit, AfterViewInit, OnDestroy {
24
+ readonly DEFAULT_OTP_LENGTH = 6;
25
+ readonly TIME_LEFT = "Time left";
26
+ readonly EXPIRED_MSG = "OTP expired. Please request a new one.";
27
+ rucEvent: EventEmitter<any>;
28
+ customTheme?: string;
29
+ rucInputData: InputOtpConfig;
30
+ timeLeft: number;
31
+ timerId: any;
32
+ inputs: QueryList<ElementRef<HTMLInputElement>>;
33
+ otpValues: string[];
34
+ ngOnInit(): void;
35
+ ngAfterViewInit(): void;
36
+ handleInput(event: Event, index: number): void;
37
+ handleKeyDown(event: KeyboardEvent, index: number): void;
38
+ handlePaste(event: ClipboardEvent): void;
39
+ private isOtpComplete;
40
+ private disableAllInputs;
41
+ ngOnDestroy(): void;
42
+ static ɵfac: i0.ɵɵFactoryDeclaration<RuclibInputOtpComponent, never>;
43
+ static ɵcmp: i0.ɵɵComponentDeclaration<RuclibInputOtpComponent, "uxp-ruclib-input-otp", never, { "customTheme": { "alias": "customTheme"; "required": false; }; "rucInputData": { "alias": "rucInputData"; "required": false; }; }, { "rucEvent": "rucEvent"; }, never, never, true, never>;
44
+ }
45
+
46
+ export { RuclibInputOtpComponent };
47
+ export type { InputOtpConfig };
package/package.json CHANGED
@@ -1,24 +1,16 @@
1
1
  {
2
2
  "name": "@ruc-lib/input-otp",
3
- "version": "3.2.0",
4
- "license": "MIT",
3
+ "version": "4.0.0",
5
4
  "peerDependencies": {
6
- "@angular/common": "^17.0.0 || ^16.0.0 || ^15.0.0",
7
- "@angular/core": "^17.0.0 || ^16.0.0 || ^15.0.0",
8
- "@angular/material": "^15.2.9 || ^14.0.0 || ^13.0.0"
5
+ "@angular/common": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
6
+ "@angular/core": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
7
+ "@angular/material": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0"
9
8
  },
10
9
  "dependencies": {
11
10
  "tslib": "^2.3.0"
12
11
  },
13
- "publishConfig": {
14
- "access": "public"
15
- },
16
12
  "sideEffects": false,
17
- "module": "fesm2015/ruc-lib-input-otp.mjs",
18
- "es2020": "fesm2020/ruc-lib-input-otp.mjs",
19
- "esm2020": "esm2020/ruc-lib-input-otp.mjs",
20
- "fesm2020": "fesm2020/ruc-lib-input-otp.mjs",
21
- "fesm2015": "fesm2015/ruc-lib-input-otp.mjs",
13
+ "module": "fesm2022/ruc-lib-input-otp.mjs",
22
14
  "typings": "index.d.ts",
23
15
  "exports": {
24
16
  "./package.json": {
@@ -26,11 +18,7 @@
26
18
  },
27
19
  ".": {
28
20
  "types": "./index.d.ts",
29
- "esm2020": "./esm2020/ruc-lib-input-otp.mjs",
30
- "es2020": "./fesm2020/ruc-lib-input-otp.mjs",
31
- "es2015": "./fesm2015/ruc-lib-input-otp.mjs",
32
- "node": "./fesm2015/ruc-lib-input-otp.mjs",
33
- "default": "./fesm2020/ruc-lib-input-otp.mjs"
21
+ "default": "./fesm2022/ruc-lib-input-otp.mjs"
34
22
  }
35
23
  }
36
24
  }
package/esm2020/index.mjs DELETED
@@ -1,4 +0,0 @@
1
- export * from './lib/ruclib-input-otp.module';
2
- export * from './lib/input-otp/input-otp.component';
3
- export * from './modal/input-otp-config';
4
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYywrQkFBK0IsQ0FBQztBQUM5QyxjQUFjLHFDQUFxQyxDQUFDO0FBQ3BELGNBQWMsMEJBQTBCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgKiBmcm9tICcuL2xpYi9ydWNsaWItaW5wdXQtb3RwLm1vZHVsZSc7XHJcbmV4cG9ydCAqIGZyb20gJy4vbGliL2lucHV0LW90cC9pbnB1dC1vdHAuY29tcG9uZW50JztcclxuZXhwb3J0ICogZnJvbSAnLi9tb2RhbC9pbnB1dC1vdHAtY29uZmlnJztcclxuIl19