├── .editorconfig
├── .eslintrc.json
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── ci.yaml
│ └── npm.yaml
├── .gitignore
├── .husky
└── pre-commit
├── .prettierignore
├── .prettierrc
├── .vscode
├── extensions.json
├── launch.json
└── tasks.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── projects
└── ngx-otp-input
│ ├── .eslintrc.json
│ ├── README.md
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── lib
│ │ ├── default.config.ts
│ │ ├── directives
│ │ │ ├── ariaLabels.directive.ts
│ │ │ ├── autoBlur.directive.ts
│ │ │ ├── autoFocus.directive.ts
│ │ │ ├── inputNavigations.directive.ts
│ │ │ └── paste.directive.ts
│ │ ├── ngx-otp-input.component.html
│ │ ├── ngx-otp-input.component.scss
│ │ ├── ngx-otp-input.component.spec.ts
│ │ └── ngx-otp-input.component.ts
│ └── public-api.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tsconfig.spec.json
├── src
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.config.ts
│ └── app.routes.ts
├── assets
│ └── .gitkeep
├── components
│ ├── BasicInformation.component.ts
│ └── Header.component.ts
├── favicon.ico
├── index.html
├── main.ts
└── styles.scss
├── tailwind.config.js
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["projects/**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/recommended",
10 | "plugin:@angular-eslint/recommended",
11 | "plugin:@angular-eslint/template/process-inline-templates",
12 | "prettier"
13 | ],
14 | "rules": {}
15 | },
16 | {
17 | "files": ["*.html"],
18 | "extends": [
19 | "plugin:@angular-eslint/template/recommended",
20 | "plugin:@angular-eslint/template/accessibility"
21 | ],
22 | "rules": {}
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 |
33 | - Browser [e.g. stock browser, safari]
34 | - Version [e.g. 22]
35 |
36 | **Additional context**
37 | Add any other context about the problem here.
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: lint-and-test
2 |
3 | run-name: Lint and Test
4 |
5 | on: [pull_request]
6 |
7 | jobs:
8 | lint-and-test:
9 | name: Lint and Test
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: actions/setup-node@v4
14 | with:
15 | node-version: '20'
16 | - run: npm ci
17 | - run: npm run build-lib
18 | - run: npm run lint-lib
19 | - run: npm run test-lib:ci
20 |
--------------------------------------------------------------------------------
/.github/workflows/npm.yaml:
--------------------------------------------------------------------------------
1 | name: Publish Package to npmjs
2 | on:
3 | release:
4 | types: [published]
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | permissions:
9 | contents: read
10 | id-token: write
11 | steps:
12 | - uses: actions/checkout@v4
13 | # Setup .npmrc file to publish to npm
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: '20.x'
17 | registry-url: 'https://registry.npmjs.org'
18 | - run: npm ci
19 | - run: npm run build-lib:prod
20 | - name: '🚀 Publish'
21 | run: npm publish ./dist/ngx-otp-input --access public
22 | env:
23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 | .nx
40 |
41 | # System files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx lint-staged
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 | .nx
40 |
41 | # System files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "semi": true,
4 | "singleQuote": true,
5 | "singleAttributePerLine": true
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/).
7 |
8 | ## 1.1.4
9 |
10 | - [(#32)](https://github.com/pkovzz/ngx-otp-input/issues/35) Fix iOS paste from quickbar
11 |
12 | ## 1.1.3
13 |
14 | - [(#31)](https://github.com/pkovzz/ngx-otp-input/issues/31) Prevent error when pasting more characters than input boxes
15 |
16 | ## 1.1.2
17 |
18 | ### Fixed
19 |
20 | - [(#28)](https://github.com/pkovzz/ngx-otp-input/issues/29) Paste will fire only one _otpChange_ and one _otpComplete_ event
21 |
22 | ## 1.1.1
23 |
24 | ### Fixed
25 |
26 | - [(#27)](https://github.com/pkovzz/ngx-otp-input/issues/27) Android input events
27 |
28 | ## 1.1.0
29 |
30 | ### Added
31 |
32 | - Add `inputMode` configuration option.
33 |
34 | ## 1.0.0
35 |
36 | - Library is now a standalone component and introduced a new API with several breaking changes. Please check the [Documentation](../README.md) for more information.
37 |
38 | ## 0.11.4
39 |
40 | ### Fixed
41 |
42 | - Bugfix of [module import issue](https://github.com/pkovzz/ngx-otp-input/issues/16) and other improvements
43 |
44 | ## 0.11.1
45 |
46 | ### Fixed
47 |
48 | - Bugfix of [Windows copy paste issue](https://github.com/k2peter/ngx-otp-input/issues/11)
49 |
50 | ## 0.11.0
51 |
52 | ### Added
53 |
54 | - Core refactor
55 | - behavior options
56 |
57 | ## 0.9.1
58 |
59 | ### Fixed
60 |
61 | - `[disabled]` input property
62 |
63 | ## 0.9.0
64 |
65 | ### Added
66 |
67 | - `autoblur` configuration option.
68 | For more information, please see the documentation.
69 |
70 | ## 0.8.0
71 |
72 | ### Added
73 |
74 | - `clear` method.
75 | For more information, please see the documentation.
76 |
77 | ## 0.7.0
78 |
79 | ### Added
80 |
81 | - `numericInputMode` config property.
82 | For more information, please see the documentation.
83 |
84 | ## 0.6.6
85 |
86 | ### Fixed
87 |
88 | - missed inputs
89 |
90 | ## 0.6.3
91 |
92 | ### Fixed
93 |
94 | - ngClass error when no custom class is set
95 |
96 | ## 0.6.2
97 |
98 | ### Fixed
99 |
100 | - Applying workaround regard to this: https://github.com/angular/angular/issues/38391
101 |
102 | ## 0.6.1
103 |
104 | ### Fixed
105 |
106 | - Preventing too fast typing, which could cause unregistered inputs
107 | - The custom ["filled"](https://github.com/pkovzz/ngx-otp-input#inputfilled)
108 | style is now applied on every input after _paste_ event
109 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, caste, color, religion, or sexual
10 | identity and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official email address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | [INSERT CONTACT METHOD].
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.1, available at
119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123 |
124 | For answers to common questions about this code of conduct, see the FAQ at
125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126 | [https://www.contributor-covenant.org/translations][translations].
127 |
128 | [homepage]: https://www.contributor-covenant.org
129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130 | [Mozilla CoC]: https://github.com/mozilla/diversity
131 | [FAQ]: https://www.contributor-covenant.org/faq
132 | [translations]: https://www.contributor-covenant.org/translations
133 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ### Contributing
2 |
3 | We welcome contributions to the project. To contribute, please follow these steps:
4 |
5 | 1. [Fork the repository](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo).
6 | 2. Create a new branch for your feature or bug fix.
7 | 3. Make your changes.
8 |
9 | #### To run the project locally:
10 |
11 | First, install the dependencies:
12 |
13 | ```bash
14 | npm install
15 | ```
16 |
17 | Then, run the library in development mode:
18 |
19 | ```bash
20 | npm run build-lib-watch
21 | ```
22 |
23 | Finally, in a new terminal window, run the demo app:
24 |
25 | ```bash
26 | npm run start
27 | ```
28 |
29 | 4. After you have made your changes, run the tests:
30 | ```bash
31 | npm run test-lib
32 | ```
33 | 5. If all tests pass, commit your changes.
34 | 6. Push your changes to your fork.
35 | 7. [Open a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork).
36 | 8. Wait for the maintainers to review your pull request.
37 | 9. If your pull request is approved and merged, it will be included in the next release.
38 |
39 | If you have any questions or need help, please open an issue. If you find a bug or have a feature request, please open an issue. If you would like to contribute but don't know where to start, please check the issues for any open issues that need help.
40 |
41 | Thank you for contributing to the project!
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024, Kovács Péter
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ngx-otp-input
2 |
3 | [](./LICENSE.txt)
4 |
5 | :warning: **Important note:** starting with version 1.0.0, the library has been completely rewritten to use standalone components and introduce breaking changes. As the section "Requirements" states, the library now requires Angular 14 or above.
6 |
7 | ## What is this?
8 |
9 | This is a simple Angular library that allows you to create an OTP (One Time Password) form by providing a set of options. The library is designed to be easy to use and highly customizable, allowing you to configure the form to suit your needs. If you like the library, please consider giving it a star on GitHub.
10 |
11 | ### Demo page
12 |
13 | http://ngx-otp-input.vercel.app
14 |
15 | ## Requirements
16 |
17 | To use this library, your project must be running **Angular 14** or above. This requirement stems from our adoption of standalone components, which are an integral feature in Angular's modern development approach. Standalone components offer a more streamlined and modular way to manage your components and simplify the dependency management, positioning them as the future of Angular development.
18 |
19 | ## Installation
20 |
21 | To install this library, run:
22 |
23 | ```bash
24 | npm install ngx-otp-input --save
25 | ```
26 |
27 | ## Example Usage
28 |
29 | Since the library uses standalone components, you can directly import and use them in your Angular application without needing to declare them in any module. For more configuration options, refer to the [Configuration options](#configuration-options) section.
30 |
31 | ```typescript
32 | import { Component } from '@angular/core';
33 | import { NgxOtpInputComponent, NgxOtpInputComponentOptions } from 'ngx-otp-input';
34 |
35 | @Component({
36 | selector: 'app-root',
37 | standalone: true,
38 | imports: [NgxOtpInputComponent],
39 | template: `
40 |
Welcome to My Angular App
41 |
42 | `,
43 | styleUrls: ['./app.component.scss'],
44 | })
45 | export class AppComponent {
46 | otpOptions: NgxOtpInputComponentOptions = {...};
47 | }
48 | ```
49 |
50 | ## Inputs
51 |
52 | ### `options: NgxOtpInputComponentOptions`
53 |
54 | The `options` input is an object that allows you to configure the OTP form. For a list of available options, refer to the [Configuration options](#configuration-options) section.
55 |
56 | ### `otp: string | null | undefined`
57 |
58 | The `otp` input is a string that allows you to set the OTP value of the form. This input is useful when you want to pre-fill the form with an OTP value. If the `otp` input is set to `null` or `undefined`, the form will be empty. The library will match the length of the OTP value with the `otpLength` option and fill the input fields accordingly, in case the OTP value is shorter than the `otpLength` option, the remaining fields will be empty. If the given value will not match the `regexp` option, the library will throw an error.
59 |
60 | ### `status: NgxOtpStatus | null | undefined`
61 |
62 | The `status` input is a string that allows you to set the status of the OTP form. The status can be one of the following values: `null`, `undefined`, `'success'` or `'failed'`. This status is only used to visually indicate the result of the OTP verification process.
63 |
64 | For type safety, you can use the `NgxOtpStatus` enum:
65 |
66 | ```typescript
67 | import { NgxOtpStatus } from 'ngx-otp-input';
68 |
69 | @Component({
70 | selector: 'app-root',
71 | standalone: true,
72 | imports: [NgxOtpInputComponent],
73 | template: ` `,
74 | })
75 | export class AppComponent {
76 | status = NgxOtpStatus;
77 | }
78 | ```
79 |
80 | ### `disabled: boolean`
81 |
82 | The `disabled` input is a boolean that allows you to disable the OTP form. When set to `true`, the form will be disabled and the user will not be able to interact with it.
83 |
84 | ## Outputs
85 |
86 | ### `otpChange: string[]`
87 |
88 | The `otpChange` output is an event that is emitted whenever the OTP value changes. The event payload is an array of strings, where each string represents a value in the OTP form.
89 |
90 | ### `otpComplete: string`
91 |
92 | The `otpComplete` output is an event that is emitted whenever the OTP form is completed. The event payload is string, which represents the complete OTP value.
93 |
94 | ## Configuration options
95 |
96 | The `NgxOtpInputComponentOptions` interface allows you to configure the OTP form. The following options are available:
97 |
98 | | Option | Type | Default value | Description |
99 | | -------------------- | -------- | ------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100 | | `otpLength` | number | 6 | The number of inputs in the OTP form |
101 | | `autoFocus` | boolean | true | Whether the first input should be focused automatically |
102 | | `autoBlur` | boolean | true | Whether the form should be blurred on complete |
103 | | `hideInputValues` | boolean | false | Whether the input values should be shown as password fields |
104 | | `regexp` | RegExp | /^[0-9]+$/ | The regular expression that the input values should match |
105 | | `showBlinkingCursor` | boolean | true | Whether the input fields should have a blinking cursor |
106 | | `ariaLabels` | string[] | [] | An array of strings that represent the aria-labels for each input field. For more information, please refer to the [More on aria-labels](#more-on-aria-labels) section. |
107 | | `inputMode` | string | 'numeric' | The `inputmode` attribute of the input fields. For more information, please refer to the [HTML `inputmode` attribute](#html-inputmode-attribute) section. |
108 |
109 | ## Styling
110 |
111 | The library provides a set of CSS classes that you can use to style the OTP form. The following classes are available:
112 |
113 | | Class name | Description |
114 | | ------------------------ | -------------------------------------- |
115 | | `ngx-otp-input-form` | The main container of the OTP form |
116 | | `ngx-otp-input-box` | The input field of the OTP form |
117 | | `ngx-blinking-cursor` | The blinking cursor of the input field |
118 | | `ngx-otp-input-disabled` | The disabled state of the form |
119 | | `ngx-otp-input-filled` | The filled state of an input field |
120 | | `ngx-otp-input-success` | The success state of the form |
121 | | `nngx-otp-input-failed` | The failed state of the form |
122 |
123 | ### How to use the classes
124 |
125 | Styling is quite simple, but you have to use the classes directly in **root** style file, otherwise it will not work:
126 |
127 | ```scss
128 | ngx-otp-input {
129 | .ngx-otp-input-form {
130 | ...
131 | }
132 | .ngx-otp-input-box {
133 | ...
134 | }
135 | ...
136 | }
137 | ```
138 |
139 | ## Reset the form
140 |
141 | In order to reset the form, you can use the `reset` method of the `NgxOtpInputComponent`:
142 |
143 | First, get a reference to the component in your template:
144 |
145 | ```html
146 |
150 | ```
151 |
152 | Then, get a reference to the component in your component class:
153 |
154 | ```typescript
155 | import { Component, ViewChild } from '@angular/core';
156 | import { NgxOtpInputComponent } from 'ngx-otp-input';
157 |
158 | @Component({
159 | selector: 'app-root',
160 | standalone: true,
161 | imports: [NgxOtpInputComponent],
162 | template: `
163 |
167 | `,
168 | })
169 | export class AppComponent {
170 | @ViewChild('otpInput') otpInput: NgxOtpInputComponent;
171 |
172 | resetForm() {
173 | this.otpInput.reset();
174 | }
175 | }
176 | ```
177 |
178 | Under the hood, the `reset` method will clear all the input values and reset the form to its initial state. For more information, refer to the [Angular FormArray reset](https://angular.dev/api/forms/FormArray#reset) documentation.
179 |
180 | ## More on aria-labels
181 |
182 | The `ariaLabels` option allows you to provide a set of strings that represent the aria-labels for each input field. This option is useful for making the form more accessible to users who rely on screen readers. The `aria-label` attribute provides a way to specify a string that labels the current element, which can be read by screen readers to provide additional context to the user. The library will automatically assign the `aria-label` attribute to each input with a default value of `One Time Password Input Number` followed by the input index. However, you can override this default value by providing your own set of labels in the `ariaLabels` option.
183 |
184 | If you provide an array of strings in the `ariaLabels` option, the library will use the values in the array to assign the `aria-label` attribute to each input field. The array should contain the same number of strings as the `otpLength` option, with each string representing the label for the corresponding input field. If the array contains fewer strings than the `otpLength` option, the library will use the default value for the remaining input fields.
185 |
186 | ## HTML `inputmode` attribute
187 |
188 | The `inputMode` option allows you to set the `inputmode` attribute of the input fields. The `inputmode` attribute provides a hint to the browser about the type of data that is expected to be entered by the user. This hint can help the browser provide a more appropriate **virtual keyboard** layout for the input field, making it easier for the user to enter the correct data. The `inputMode` option accepts a string value that represents the input mode of the input fields. For more details, check out this [documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode).
189 |
190 | Please note, that `regexp` option should be set to support the `inputmode` attribute. For example, if you set the `inputMode` option to `text` and the `regexp` option to `/^[a-zA-Z]+$/`, the browser will provide a virtual keyboard layout that is optimized for entering text data, but if the `inputMode` option is set to `numeric` and the `regexp` is still `/^[a-zA-Z]+$/`, the browser may provide a numeric keyboard layout, which may not be suitable for entering text data.
191 |
192 | **Default** options for `inputMode` and `regexp` are set to `'numeric'` and `/^[0-9]+$/` respectively, as these are the most common values for one time password inputs.
193 |
194 | ## Side notes
195 |
196 | If `hideInputValues` is set to `true`, the input values will be hidden by default, using the `password` input type. However certain password managers may place their browser extension icon on the input field, which may interfere with the input field's appearance.
197 |
198 | ## Contributing
199 |
200 | If you would like to contribute to this project, please refer to the [CONTRIBUTING](CONTRIBUTING.md) file for more information.
201 |
202 | ## Code of Conduct
203 |
204 | Please read the [CODE OF CONDUCT](CODE_OF_CONDUCT.md) file for more information.
205 |
206 | ## Changelog
207 |
208 | See the [CHANGELOG](CHANGELOG.md) file for details.
209 |
210 | ## License
211 |
212 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
213 |
214 | _This project is tested with BrowserStack_
215 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ngx-otp-input-demo": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | }
12 | },
13 | "root": "",
14 | "sourceRoot": "src",
15 | "prefix": "app",
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:application",
19 | "options": {
20 | "outputPath": "dist/ngx-otp-input-demo",
21 | "index": "src/index.html",
22 | "browser": "src/main.ts",
23 | "polyfills": ["zone.js"],
24 | "tsConfig": "tsconfig.app.json",
25 | "inlineStyleLanguage": "scss",
26 | "assets": ["src/favicon.ico", "src/assets"],
27 | "styles": ["src/styles.scss"],
28 | "scripts": []
29 | },
30 | "configurations": {
31 | "production": {
32 | "budgets": [
33 | {
34 | "type": "initial",
35 | "maximumWarning": "500kb",
36 | "maximumError": "1mb"
37 | },
38 | {
39 | "type": "anyComponentStyle",
40 | "maximumWarning": "2kb",
41 | "maximumError": "4kb"
42 | }
43 | ],
44 | "outputHashing": "all"
45 | },
46 | "development": {
47 | "optimization": false,
48 | "extractLicenses": false,
49 | "sourceMap": true
50 | }
51 | },
52 | "defaultConfiguration": "production"
53 | },
54 | "serve": {
55 | "builder": "@angular-devkit/build-angular:dev-server",
56 | "configurations": {
57 | "production": {
58 | "buildTarget": "ngx-otp-input-demo:build:production"
59 | },
60 | "development": {
61 | "buildTarget": "ngx-otp-input-demo:build:development"
62 | }
63 | },
64 | "defaultConfiguration": "development"
65 | },
66 | "extract-i18n": {
67 | "builder": "@angular-devkit/build-angular:extract-i18n",
68 | "options": {
69 | "buildTarget": "ngx-otp-input-demo:build"
70 | }
71 | },
72 | "test": {
73 | "builder": "@angular-devkit/build-angular:karma",
74 | "options": {
75 | "polyfills": ["zone.js", "zone.js/testing"],
76 | "tsConfig": "tsconfig.spec.json",
77 | "inlineStyleLanguage": "scss",
78 | "assets": ["src/favicon.ico", "src/assets"],
79 | "styles": ["src/styles.scss"],
80 | "scripts": []
81 | }
82 | }
83 | }
84 | },
85 | "ngx-otp-input": {
86 | "projectType": "library",
87 | "root": "projects/ngx-otp-input",
88 | "sourceRoot": "projects/ngx-otp-input/src",
89 | "prefix": "lib",
90 | "architect": {
91 | "build": {
92 | "builder": "@angular-devkit/build-angular:ng-packagr",
93 | "options": {
94 | "project": "projects/ngx-otp-input/ng-package.json"
95 | },
96 | "configurations": {
97 | "production": {
98 | "tsConfig": "projects/ngx-otp-input/tsconfig.lib.prod.json"
99 | },
100 | "development": {
101 | "tsConfig": "projects/ngx-otp-input/tsconfig.lib.json"
102 | }
103 | },
104 | "defaultConfiguration": "production"
105 | },
106 | "test": {
107 | "builder": "@angular-devkit/build-angular:karma",
108 | "options": {
109 | "tsConfig": "projects/ngx-otp-input/tsconfig.spec.json",
110 | "polyfills": ["zone.js", "zone.js/testing"]
111 | }
112 | },
113 | "lint": {
114 | "builder": "@angular-eslint/builder:lint",
115 | "options": {
116 | "lintFilePatterns": [
117 | "projects/ngx-otp-input/**/*.ts",
118 | "projects/ngx-otp-input/**/*.html"
119 | ]
120 | }
121 | }
122 | }
123 | }
124 | },
125 | "cli": {
126 | "analytics": false
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-otp-input-demo",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "build-lib": "ng build ngx-otp-input --configuration development",
9 | "build-lib:prod": "ng build ngx-otp-input",
10 | "build-lib-watch": "ng build ngx-otp-input --watch --configuration development",
11 | "watch": "ng build --watch --configuration development",
12 | "test": "ng test",
13 | "test-lib": "ng test ngx-otp-input",
14 | "test-lib:ci": "ng test ngx-otp-input --no-watch --no-progress --browsers=ChromeHeadless",
15 | "lint": "ng lint",
16 | "lint-lib": "ng lint ngx-otp-input",
17 | "prepare": "husky"
18 | },
19 | "dependencies": {
20 | "@angular/common": "^18.0.1",
21 | "@angular/compiler": "^18.0.1",
22 | "@angular/core": "^18.0.1",
23 | "@angular/forms": "^18.0.1",
24 | "@angular/platform-browser": "^18.0.1",
25 | "@angular/platform-browser-dynamic": "^18.0.1",
26 | "@angular/router": "^18.0.1",
27 | "@vercel/analytics": "^1.3.1",
28 | "rxjs": "~7.8.0",
29 | "tslib": "^2.3.0",
30 | "zone.js": "~0.14.3"
31 | },
32 | "devDependencies": {
33 | "@angular-devkit/build-angular": "^18.0.2",
34 | "@angular-eslint/builder": "^18.0.1",
35 | "@angular-eslint/eslint-plugin": "^18.0.1",
36 | "@angular-eslint/eslint-plugin-template": "^18.0.1",
37 | "@angular-eslint/schematics": "^18.0.1",
38 | "@angular-eslint/template-parser": "^18.0.1",
39 | "@angular/cli": "^18.0.2",
40 | "@angular/compiler-cli": "^18.0.1",
41 | "@types/jasmine": "~5.1.0",
42 | "@typescript-eslint/eslint-plugin": "7.11.0",
43 | "@typescript-eslint/parser": "7.11.0",
44 | "autoprefixer": "^10.4.19",
45 | "eslint": "^8.57.0",
46 | "eslint-config-prettier": "^9.1.0",
47 | "husky": "^9.0.11",
48 | "jasmine-core": "~5.1.0",
49 | "karma": "~6.4.0",
50 | "karma-chrome-launcher": "~3.2.0",
51 | "karma-coverage": "~2.2.0",
52 | "karma-jasmine": "~5.1.0",
53 | "karma-jasmine-html-reporter": "~2.1.0",
54 | "lint-staged": "^15.2.2",
55 | "ng-packagr": "^18.0.0",
56 | "postcss": "^8.4.40",
57 | "prettier": "3.2.5",
58 | "tailwindcss": "^3.4.7",
59 | "typescript": "~5.4.2"
60 | },
61 | "lint-staged": {
62 | "**/*": "prettier --write --ignore-unknown",
63 | "**/*.{ts,js,html}": "eslint --fix"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc.json",
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "rules": {}
8 | },
9 | {
10 | "files": ["*.html"],
11 | "rules": {}
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/README.md:
--------------------------------------------------------------------------------
1 | # ngx-otp-input
2 |
3 | [](/../../LICENSE.txt)
4 |
5 | :warning: **Important note:** starting with version 1.0.0, the library has been completely rewritten to use standalone components and introduce breaking changes. As the section "Requirements" states, the library now requires Angular 14 or above.
6 |
7 | ## What is this?
8 |
9 | This is a simple Angular library that allows you to create an OTP (One Time Password) form by providing a set of options. The library is designed to be easy to use and highly customizable, allowing you to configure the form to suit your needs. If you like the library, please consider giving it a star on GitHub.
10 |
11 | ### Demo page
12 |
13 | http://ngx-otp-input.vercel.app
14 |
15 | ## Requirements
16 |
17 | To use this library, your project must be running **Angular 14** or above. This requirement stems from our adoption of standalone components, which are an integral feature in Angular's modern development approach. Standalone components offer a more streamlined and modular way to manage your components and simplify the dependency management, positioning them as the future of Angular development.
18 |
19 | ## Installation
20 |
21 | To install this library, run:
22 |
23 | ```bash
24 | npm install ngx-otp-input --save
25 | ```
26 |
27 | ## Example Usage
28 |
29 | Since the library uses standalone components, you can directly import and use them in your Angular application without needing to declare them in any module. For more configuration options, refer to the [Configuration options](#configuration-options) section.
30 |
31 | ```typescript
32 | import { Component } from '@angular/core';
33 | import { NgxOtpInputComponent, NgxOtpInputComponentOptions } from 'ngx-otp-input';
34 |
35 | @Component({
36 | selector: 'app-root',
37 | standalone: true,
38 | imports: [NgxOtpInputComponent],
39 | template: `
40 |
Welcome to My Angular App
41 |
42 | `,
43 | styleUrls: ['./app.component.scss'],
44 | })
45 | export class AppComponent {
46 | otpOptions: NgxOtpInputComponentOptions = {...};
47 | }
48 | ```
49 |
50 | ## Inputs
51 |
52 | ### `options: NgxOtpInputComponentOptions`
53 |
54 | The `options` input is an object that allows you to configure the OTP form. For a list of available options, refer to the [Configuration options](#configuration-options) section.
55 |
56 | ### `otp: string | null | undefined`
57 |
58 | The `otp` input is a string that allows you to set the OTP value of the form. This input is useful when you want to pre-fill the form with an OTP value. If the `otp` input is set to `null` or `undefined`, the form will be empty. The library will match the length of the OTP value with the `otpLength` option and fill the input fields accordingly, in case the OTP value is shorter than the `otpLength` option, the remaining fields will be empty. If the given value will not match the `regexp` option, the library will throw an error.
59 |
60 | ### `status: NgxOtpStatus | null | undefined`
61 |
62 | The `status` input is a string that allows you to set the status of the OTP form. The status can be one of the following values: `null`, `undefined`, `'success'` or `'failed'`. This status is only used to visually indicate the result of the OTP verification process.
63 |
64 | For type safety, you can use the `NgxOtpStatus` enum:
65 |
66 | ```typescript
67 | import { NgxOtpStatus } from 'ngx-otp-input';
68 |
69 | @Component({
70 | selector: 'app-root',
71 | standalone: true,
72 | imports: [NgxOtpInputComponent],
73 | template: ` `,
74 | })
75 | export class AppComponent {
76 | status = NgxOtpStatus;
77 | }
78 | ```
79 |
80 | ### `disabled: boolean`
81 |
82 | The `disabled` input is a boolean that allows you to disable the OTP form. When set to `true`, the form will be disabled and the user will not be able to interact with it.
83 |
84 | ## Outputs
85 |
86 | ### `otpChange: string[]`
87 |
88 | The `otpChange` output is an event that is emitted whenever the OTP value changes. The event payload is an array of strings, where each string represents a value in the OTP form.
89 |
90 | ### `otpComplete: string`
91 |
92 | The `otpComplete` output is an event that is emitted whenever the OTP form is completed. The event payload is string, which represents the complete OTP value.
93 |
94 | ## Configuration options
95 |
96 | The `NgxOtpInputComponentOptions` interface allows you to configure the OTP form. The following options are available:
97 |
98 | | Option | Type | Default value | Description |
99 | | -------------------- | -------- | ------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100 | | `otpLength` | number | 6 | The number of inputs in the OTP form |
101 | | `autoFocus` | boolean | true | Whether the first input should be focused automatically |
102 | | `autoBlur` | boolean | true | Whether the form should be blurred on complete |
103 | | `hideInputValues` | boolean | false | Whether the input values should be shown as password fields |
104 | | `regexp` | RegExp | /^[0-9]+$/ | The regular expression that the input values should match |
105 | | `showBlinkingCursor` | boolean | true | Whether the input fields should have a blinking cursor |
106 | | `ariaLabels` | string[] | [] | An array of strings that represent the aria-labels for each input field. For more information, please refer to the [More on aria-labels](#more-on-aria-labels) section. |
107 |
108 | ## Styling
109 |
110 | The library provides a set of CSS classes that you can use to style the OTP form. The following classes are available:
111 |
112 | | Class name | Description |
113 | | ------------------------ | -------------------------------------- |
114 | | `ngx-otp-input-form` | The main container of the OTP form |
115 | | `ngx-otp-input-box` | The input field of the OTP form |
116 | | `ngx-blinking-cursor` | The blinking cursor of the input field |
117 | | `ngx-otp-input-disabled` | The disabled state of the form |
118 | | `ngx-otp-input-filled` | The filled state of an input field |
119 | | `ngx-otp-input-success` | The success state of the form |
120 | | `nngx-otp-input-failed` | The failed state of the form |
121 |
122 | ### How to use the classes
123 |
124 | Styling is quite simple, but you have to use the classes directly in **root** style file, otherwise it will not work:
125 |
126 | ```scss
127 | ngx-otp-input {
128 | .ngx-otp-input-form {
129 | ...
130 | }
131 | .ngx-otp-input-box {
132 | ...
133 | }
134 | ...
135 | }
136 | ```
137 |
138 | ## Reset the form
139 |
140 | In order to reset the form, you can use the `reset` method of the `NgxOtpInputComponent`:
141 |
142 | First, get a reference to the component in your template:
143 |
144 | ```html
145 |
149 | ```
150 |
151 | Then, get a reference to the component in your component class:
152 |
153 | ```typescript
154 | import { Component, ViewChild } from '@angular/core';
155 | import { NgxOtpInputComponent } from 'ngx-otp-input';
156 |
157 | @Component({
158 | selector: 'app-root',
159 | standalone: true,
160 | imports: [NgxOtpInputComponent],
161 | template: `
162 |
166 | `,
167 | })
168 | export class AppComponent {
169 | @ViewChild('otpInput') otpInput: NgxOtpInputComponent;
170 |
171 | resetForm() {
172 | this.otpInput.reset();
173 | }
174 | }
175 | ```
176 |
177 | Under the hood, the `reset` method will clear all the input values and reset the form to its initial state. For more information, refer to the [Angular FormArray reset](https://angular.dev/api/forms/FormArray#reset) documentation.
178 |
179 | ## More on aria-labels
180 |
181 | The `ariaLabels` option allows you to provide a set of strings that represent the aria-labels for each input field. This option is useful for making the form more accessible to users who rely on screen readers. The `aria-label` attribute provides a way to specify a string that labels the current element, which can be read by screen readers to provide additional context to the user. The library will automatically assign the `aria-label` attribute to each input with a default value of `One Time Password Input Number` followed by the input index. However, you can override this default value by providing your own set of labels in the `ariaLabels` option.
182 |
183 | If you provide an array of strings in the `ariaLabels` option, the library will use the values in the array to assign the `aria-label` attribute to each input field. The array should contain the same number of strings as the `otpLength` option, with each string representing the label for the corresponding input field. If the array contains fewer strings than the `otpLength` option, the library will use the default value for the remaining input fields.
184 |
185 | ## HTML `inputmode` attribute
186 |
187 | The `inputMode` option allows you to set the `inputmode` attribute of the input fields. The `inputmode` attribute provides a hint to the browser about the type of data that is expected to be entered by the user. This hint can help the browser provide a more appropriate **virtual keyboard** layout for the input field, making it easier for the user to enter the correct data. The `inputMode` option accepts a string value that represents the input mode of the input fields. For more details, check out this [documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode).
188 |
189 | Please note, that `regexp` option should be set to support the `inputmode` attribute. For example, if you set the `inputMode` option to `text` and the `regexp` option to `/^[a-zA-Z]+$/`, the browser will provide a virtual keyboard layout that is optimized for entering text data, but if the `inputMode` option is set to `numeric` and the `regexp` is still `/^[a-zA-Z]+$/`, the browser may provide a numeric keyboard layout, which may not be suitable for entering text data.
190 |
191 | **Default** options for `inputMode` and `regexp` are set to `'numeric'` and `/^[0-9]+$/` respectively, as these are the most common values for one time password inputs.
192 |
193 | ## Side notes
194 |
195 | If `hideInputValues` is set to `true`, the input values will be hidden by default, using the `password` input type. However certain password managers may place their browser extension icon on the input field, which may interfere with the input field's appearance.
196 |
197 | ## Contributing
198 |
199 | If you would like to contribute to this project, please refer to the [CONTRIBUTING](https://github.com/pkovzz/ngx-otp-input/blob/master/CONTRIBUTING.md) file for more information.
200 |
201 | ## Code of Conduct
202 |
203 | Please read the [CODE OF CONDUCT](https://github.com/pkovzz/ngx-otp-input/blob/master/CODE_OF_CONDUCT.md) file for more information.
204 |
205 | ## Changelog
206 |
207 | See the [CHANGELOG](https://github.com/pkovzz/ngx-otp-input/blob/master/CHANGELOG.md) file for details.
208 |
209 | ## License
210 |
211 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/pkovzz/ngx-otp-input/blob/master/LICENSE) file for details.
212 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/ngx-otp-input",
4 | "lib": {
5 | "entryFile": "src/public-api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/projects/ngx-otp-input/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-otp-input",
3 | "version": "1.1.4",
4 | "peerDependencies": {
5 | "@angular/common": ">=14.0.0",
6 | "@angular/core": ">=14.0.0"
7 | },
8 | "dependencies": {
9 | "tslib": "^2.3.0"
10 | },
11 | "sideEffects": false,
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/pkovzz/ngx-otp-input"
15 | },
16 | "bugs": {
17 | "url": "https://github.com/pkovzz/ngx-otp-input/issues"
18 | },
19 | "homepage": "https://github.com/pkovzz/ngx-otp-input",
20 | "description": "One Time Password input library for Angular (14+)",
21 | "keywords": [
22 | "one time password",
23 | "otp",
24 | "angular",
25 | "ng2",
26 | "ngx",
27 | "ngx-otp-input",
28 | "sms code",
29 | "sms"
30 | ],
31 | "author": "Péter Kovács",
32 | "license": "MIT"
33 | }
34 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/default.config.ts:
--------------------------------------------------------------------------------
1 | export interface NgxOtpInputComponentOptions {
2 | otpLength?: number;
3 | autoFocus?: boolean;
4 | autoBlur?: boolean;
5 | hideInputValues?: boolean;
6 | regexp?: RegExp;
7 | showBlinkingCursor?: boolean;
8 | ariaLabels?: string[];
9 | inputMode?: string;
10 | }
11 |
12 | export const defaultOptions: NgxOtpInputComponentOptions = {
13 | otpLength: 6,
14 | autoFocus: true,
15 | autoBlur: true,
16 | hideInputValues: false,
17 | regexp: /^[0-9]+$/,
18 | showBlinkingCursor: true,
19 | ariaLabels: [],
20 | inputMode: 'numeric',
21 | };
22 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/directives/ariaLabels.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Directive,
3 | Input,
4 | ElementRef,
5 | ContentChildren,
6 | QueryList,
7 | AfterContentInit,
8 | } from '@angular/core';
9 |
10 | @Directive({
11 | standalone: true,
12 | selector: '[ngxOtpAriaLabels]',
13 | })
14 | export class AriaLabelsDirective implements AfterContentInit {
15 | @ContentChildren('otpInputElement', { descendants: true })
16 | inputs!: QueryList>;
17 |
18 | @Input() ngxOtpAriaLabels!: string[];
19 |
20 | ngAfterContentInit(): void {
21 | this.setAriaLabelsAttrs();
22 | }
23 |
24 | private getDefaultAriaLabelText(index: number): string {
25 | return `One Time Password Input Number ${index + 1}`;
26 | }
27 |
28 | private setAriaLabelsAttrs(): void {
29 | this.inputs.forEach((input, index) => {
30 | input.nativeElement.setAttribute(
31 | 'aria-label',
32 | this.ngxOtpAriaLabels[index] ?? this.getDefaultAriaLabelText(index),
33 | );
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/directives/autoBlur.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterContentInit,
3 | ContentChildren,
4 | Directive,
5 | ElementRef,
6 | Input,
7 | OnChanges,
8 | QueryList,
9 | SimpleChanges,
10 | } from '@angular/core';
11 |
12 | @Directive({
13 | standalone: true,
14 | selector: '[ngxAutoBlur]',
15 | })
16 | export class AutoBlurDirective implements OnChanges, AfterContentInit {
17 | private inputHTMLElements: HTMLInputElement[] = [];
18 |
19 | @ContentChildren('otpInputElement', { descendants: true })
20 | inputs!: QueryList>;
21 |
22 | @Input()
23 | ngxAutoBlur!: boolean;
24 |
25 | @Input()
26 | isFormValid!: boolean;
27 |
28 | ngOnChanges(changes: SimpleChanges) {
29 | if (
30 | this.ngxAutoBlur &&
31 | this.inputHTMLElements.length > 0 &&
32 | changes['isFormValid'].currentValue
33 | ) {
34 | this.inputHTMLElements.forEach((input) => {
35 | input.blur();
36 | });
37 | }
38 | }
39 |
40 | ngAfterContentInit() {
41 | this.inputs.forEach((input) => {
42 | this.inputHTMLElements.push(input.nativeElement);
43 | });
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/directives/autoFocus.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterContentInit,
3 | ContentChild,
4 | Directive,
5 | ElementRef,
6 | Input,
7 | } from '@angular/core';
8 |
9 | @Directive({
10 | standalone: true,
11 | selector: '[ngxAutoFocus]',
12 | })
13 | export class AutoFocusDirective implements AfterContentInit {
14 | @ContentChild('otpInputElement', { static: false })
15 | firstInput!: ElementRef;
16 |
17 | @Input() ngxAutoFocus!: boolean;
18 |
19 | ngAfterContentInit(): void {
20 | if (this.ngxAutoFocus && this.firstInput) {
21 | this.firstInput.nativeElement.focus();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/directives/inputNavigations.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterContentInit,
3 | ContentChildren,
4 | Directive,
5 | ElementRef,
6 | EventEmitter,
7 | HostListener,
8 | Input,
9 | Output,
10 | QueryList,
11 | } from '@angular/core';
12 |
13 | export type OtpValueChangeEvent = [number, string];
14 |
15 | @Directive({
16 | standalone: true,
17 | selector: '[ngxInputNavigations]',
18 | })
19 | export class InputNavigationsDirective implements AfterContentInit {
20 | private inputsArray: ElementRef[] = [];
21 |
22 | @ContentChildren('otpInputElement', { descendants: true })
23 | inputs!: QueryList>;
24 |
25 | @Input() regexp!: RegExp;
26 |
27 | @Output() valueChange: EventEmitter =
28 | new EventEmitter();
29 |
30 | ngAfterContentInit() {
31 | this.inputsArray = this.inputs.toArray();
32 | }
33 |
34 | private findInputIndex(target: HTMLElement): number {
35 | return this.inputsArray.findIndex(
36 | (input) => input.nativeElement === target,
37 | );
38 | }
39 |
40 | private setFocus(index: number): void {
41 | if (index >= 0 && index < this.inputs.length) {
42 | this.inputsArray[index].nativeElement.focus();
43 | }
44 | }
45 |
46 | @HostListener('keydown.arrowLeft', ['$event'])
47 | onArrowLeft(event: KeyboardEvent): void {
48 | const index = this.findInputIndex(event.target as HTMLElement);
49 | if (index > 0) {
50 | this.setFocus(index - 1);
51 | }
52 | }
53 |
54 | @HostListener('keydown.arrowRight', ['$event'])
55 | onArrowRight(event: KeyboardEvent): void {
56 | const index = this.findInputIndex(event.target as HTMLElement);
57 | if (index < this.inputs.length - 1) {
58 | this.setFocus(index + 1);
59 | }
60 | }
61 |
62 | @HostListener('keydown.backspace', ['$event'])
63 | onBackspace(event: KeyboardEvent): void {
64 | const index = this.findInputIndex(event.target as HTMLElement);
65 | if (index >= 0) {
66 | this.valueChange.emit([index, '']);
67 | this.setFocus(index - 1);
68 | event.preventDefault();
69 | }
70 | }
71 |
72 | @HostListener('input', ['$event'])
73 | onKeyUp(event: InputEvent): void {
74 | const index = this.findInputIndex(event.target as HTMLElement);
75 | if ((event.target as HTMLInputElement).value?.match(this.regexp)) {
76 | this.valueChange.emit([index, (event.target as HTMLInputElement).value]);
77 | this.setFocus(index + 1);
78 | } else {
79 | this.inputsArray[index].nativeElement.value = '';
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/directives/paste.directive.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ContentChildren,
3 | Directive,
4 | ElementRef,
5 | EventEmitter,
6 | HostListener,
7 | Input,
8 | Output,
9 | QueryList,
10 | } from '@angular/core';
11 |
12 | @Directive({
13 | standalone: true,
14 | selector: '[ngxOtpPaste]',
15 | })
16 | export class PasteDirective {
17 | @ContentChildren('otpInputElement', { descendants: true })
18 | inputs!: QueryList>;
19 |
20 | @Input() regexp!: RegExp;
21 |
22 | @Output() handlePaste: EventEmitter = new EventEmitter();
23 |
24 | @HostListener('paste', ['$event'])
25 | onPaste(event: ClipboardEvent): void {
26 | event.preventDefault();
27 | const clipboardData = event.clipboardData?.getData('text');
28 | if (clipboardData && this.regexp.test(clipboardData)) {
29 | const values = clipboardData.split('');
30 | this.inputs.forEach((input, index) => {
31 | if (values[index]) {
32 | input.nativeElement.value = values[index];
33 | }
34 | });
35 | this.handlePaste.emit(values);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/ngx-otp-input.component.html:
--------------------------------------------------------------------------------
1 |
39 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/ngx-otp-input.component.scss:
--------------------------------------------------------------------------------
1 | .ngx-otp-input-form {
2 | display: inline-flex;
3 | gap: 0.5rem;
4 | caret-color: transparent;
5 | }
6 |
7 | .ngx-blinking-cursor {
8 | caret-color: initial;
9 | }
10 |
11 | .ngx-otp-input-box {
12 | width: 30px;
13 | height: 35px;
14 | padding: 0.5rem;
15 | font-size: 1.5rem;
16 | text-align: center;
17 | border: 1px solid #c4c4c4;
18 | border-radius: 0.5rem;
19 | outline: none;
20 | &:focus {
21 | border-color: #007bff;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/ngx-otp-input.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { NgxOtpInputComponent } from './ngx-otp-input.component';
3 | import { NgxOtpInputComponentOptions } from './default.config';
4 | import { NgxOtpStatus } from 'ngx-otp-input';
5 |
6 | /**
7 | * TODO: add many-many more test cases!
8 | */
9 |
10 | describe('NgxOtpInputComponent with default options', () => {
11 | let component: NgxOtpInputComponent;
12 | let fixture: ComponentFixture;
13 |
14 | beforeEach(async () => {
15 | await TestBed.configureTestingModule({
16 | imports: [NgxOtpInputComponent],
17 | }).compileComponents();
18 |
19 | fixture = TestBed.createComponent(NgxOtpInputComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 |
28 | it('should have the default options', () => {
29 | expect(component.ngxOtpOptionsInUse).toBeDefined();
30 | });
31 |
32 | it('should have as many inputs as the length of the otp', () => {
33 | const inputElements = fixture.nativeElement.querySelectorAll('input');
34 | expect(inputElements.length).toEqual(
35 | component.ngxOtpOptionsInUse.otpLength,
36 | );
37 | });
38 |
39 | it('should have been focused on the first input', () => {
40 | const inputElements = fixture.nativeElement.querySelectorAll('input');
41 | expect(document.activeElement).toEqual(inputElements[0]);
42 | });
43 |
44 | // TODO: Fix the test case
45 | // it('should have been blurred after the otp is completed', () => {
46 | // const inputElements = fixture.nativeElement.querySelectorAll('input');
47 | // inputElements.forEach((inputElement: HTMLInputElement) => {
48 | // inputElement.value = '1';
49 | // inputElement.dispatchEvent(new Event('input'));
50 | // });
51 | // expect(document.activeElement).not.toEqual(inputElements[0]);
52 | // });
53 |
54 | it('should have the input type as text', () => {
55 | expect(component.inputType).toEqual('text');
56 | });
57 |
58 | it('should have default aria labels', () => {
59 | const inputElements = fixture.nativeElement.querySelectorAll('input');
60 | inputElements.forEach((inputElement: HTMLInputElement, index: number) => {
61 | expect(inputElement.getAttribute('aria-label')).toEqual(
62 | `One Time Password Input Number ${index + 1}`,
63 | );
64 | });
65 | });
66 |
67 | it('should have the css class ngx-otp-input-success if the status is success', () => {
68 | component.status = NgxOtpStatus.SUCCESS;
69 | fixture.detectChanges();
70 | const inputElements = fixture.nativeElement.querySelectorAll('input');
71 | inputElements.forEach((inputElement: HTMLInputElement) => {
72 | expect(
73 | inputElement.classList.contains('ngx-otp-input-success'),
74 | ).toBeTrue();
75 | });
76 | });
77 |
78 | it('should have the css class ngx-otp-input-failed if the status is failed', () => {
79 | component.status = NgxOtpStatus.FAILED;
80 | fixture.detectChanges();
81 | const inputElements = fixture.nativeElement.querySelectorAll('input');
82 | inputElements.forEach((inputElement: HTMLInputElement) => {
83 | expect(
84 | inputElement.classList.contains('ngx-otp-input-failed'),
85 | ).toBeTrue();
86 | });
87 | });
88 |
89 | it('should have the css class ngx-otp-input-disabled if the input is disabled', () => {
90 | component.disabled = true;
91 | fixture.detectChanges();
92 | const inputElements = fixture.nativeElement.querySelectorAll('input');
93 | inputElements.forEach((inputElement: HTMLInputElement) => {
94 | expect(
95 | inputElement.classList.contains('ngx-otp-input-disabled'),
96 | ).toBeTrue();
97 | });
98 | });
99 |
100 | it('should have numeric inputmode by default', () => {
101 | const inputElements = fixture.nativeElement.querySelectorAll('input');
102 | inputElements.forEach((inputElement: HTMLInputElement) => {
103 | expect(inputElement.getAttribute('inputmode')).toEqual('numeric');
104 | });
105 | });
106 | });
107 |
108 | describe('NgxOtpInputComponent with custom options', () => {
109 | const options: NgxOtpInputComponentOptions = {
110 | otpLength: 5,
111 | autoFocus: false,
112 | autoBlur: false,
113 | regexp: /^[0-9]+$/,
114 | hideInputValues: true,
115 | showBlinkingCursor: true,
116 | ariaLabels: ['First', 'Second', 'Third', 'Fourth', 'Fifth'],
117 | inputMode: 'text',
118 | };
119 |
120 | let component: NgxOtpInputComponent;
121 |
122 | beforeEach(async () => {
123 | await TestBed.configureTestingModule({
124 | imports: [NgxOtpInputComponent],
125 | }).compileComponents();
126 |
127 | const fixture = TestBed.createComponent(NgxOtpInputComponent);
128 | component = fixture.componentInstance;
129 | component.options = options;
130 | fixture.detectChanges();
131 | });
132 |
133 | it('should have custom options', () => {
134 | expect(component.ngxOtpOptionsInUse).toEqual(options);
135 | });
136 |
137 | it('should have as many inputs as the length of the otp', () => {
138 | const inputElements = document.querySelectorAll('input');
139 | expect(inputElements.length).toEqual(
140 | component.ngxOtpOptionsInUse.otpLength!,
141 | );
142 | });
143 |
144 | it('should not have been focused on the first input', () => {
145 | const inputElements = document.querySelectorAll('input');
146 | expect(document.activeElement).not.toEqual(inputElements[0]);
147 | });
148 |
149 | it('should have input type as password', () => {
150 | const inputElements = document.querySelectorAll('input');
151 | inputElements.forEach((inputElement: HTMLInputElement) => {
152 | expect(inputElement.type).toEqual('password');
153 | });
154 | });
155 |
156 | it('should have custom aria labels', () => {
157 | const inputElements = document.querySelectorAll('input');
158 | inputElements.forEach((inputElement: HTMLInputElement, index: number) => {
159 | if (options.ariaLabels) {
160 | expect(inputElement.getAttribute('aria-label')).toEqual(
161 | options.ariaLabels[index],
162 | );
163 | }
164 | });
165 | });
166 |
167 | it('should have text inputmode', () => {
168 | const inputElements = document.querySelectorAll('input');
169 | inputElements.forEach((inputElement: HTMLInputElement) => {
170 | expect(inputElement.getAttribute('inputmode')).toEqual('text');
171 | });
172 | });
173 |
174 | // TODO: Fix the test case
175 | // it('should have the css class ngx-blinking-cursor if the showBlinkingCursor option is true', () => {
176 | // const inputElements = document.querySelectorAll('input');
177 | // inputElements.forEach((inputElement: HTMLInputElement) => {
178 | // expect(
179 | // inputElement.classList.contains('ngx-blinking-cursor'),
180 | // ).toBeTrue();
181 | // });
182 | // });
183 | });
184 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/lib/ngx-otp-input.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | EventEmitter,
4 | Input,
5 | OnChanges,
6 | OnInit,
7 | Output,
8 | SimpleChanges,
9 | } from '@angular/core';
10 | import { CommonModule } from '@angular/common';
11 | import {
12 | FormArray,
13 | FormControl,
14 | ReactiveFormsModule,
15 | Validators,
16 | } from '@angular/forms';
17 | import { PasteDirective } from './directives/paste.directive';
18 | import { AutoFocusDirective } from './directives/autoFocus.directive';
19 | import {
20 | InputNavigationsDirective,
21 | OtpValueChangeEvent,
22 | } from './directives/inputNavigations.directive';
23 | import { AutoBlurDirective } from './directives/autoBlur.directive';
24 | import { AriaLabelsDirective } from './directives/ariaLabels.directive';
25 | import { NgxOtpInputComponentOptions, defaultOptions } from './default.config';
26 |
27 | export enum NgxOtpStatus {
28 | SUCCESS = 'success',
29 | FAILED = 'failed',
30 | }
31 |
32 | @Component({
33 | standalone: true,
34 | imports: [
35 | CommonModule,
36 | ReactiveFormsModule,
37 | PasteDirective,
38 | AutoFocusDirective,
39 | InputNavigationsDirective,
40 | AutoBlurDirective,
41 | AriaLabelsDirective,
42 | ],
43 | selector: 'ngx-otp-input',
44 | templateUrl: 'ngx-otp-input.component.html',
45 | styleUrls: ['ngx-otp-input.component.scss'],
46 | })
47 | export class NgxOtpInputComponent implements OnInit, OnChanges {
48 | protected ngxOtpInputArray!: FormArray;
49 | protected ngxOtpOptions: NgxOtpInputComponentOptions = defaultOptions;
50 |
51 | @Input() set options(customOptions: NgxOtpInputComponentOptions) {
52 | this.ngxOtpOptions = { ...defaultOptions, ...customOptions };
53 | }
54 |
55 | @Input() status: NgxOtpStatus | null | undefined;
56 | @Input() disabled = false;
57 | @Input() otp: string | null | undefined;
58 | @Output() otpChange = new EventEmitter();
59 | @Output() otpComplete = new EventEmitter();
60 |
61 | // For testing purposes
62 | get ngxOtpOptionsInUse(): NgxOtpInputComponentOptions {
63 | return this.ngxOtpOptions;
64 | }
65 |
66 | get inputType(): string {
67 | return this.ngxOtpOptions.hideInputValues ? 'password' : 'text';
68 | }
69 |
70 | get isOTPSuccess(): boolean {
71 | return this.status === NgxOtpStatus.SUCCESS;
72 | }
73 |
74 | get isOTPFailed(): boolean {
75 | return this.status === NgxOtpStatus.FAILED;
76 | }
77 |
78 | ngOnInit(): void {
79 | this.initOtpInputArray();
80 | }
81 |
82 | ngOnChanges(changes: SimpleChanges): void {
83 | const otpChange = changes['otp'];
84 | if (otpChange?.currentValue) {
85 | if (!otpChange.firstChange) {
86 | this.setInitialOtp(otpChange.currentValue);
87 | } else {
88 | this.ngxOtpOptions.autoFocus = false;
89 | }
90 | }
91 | }
92 |
93 | private initOtpInputArray(): void {
94 | this.ngxOtpInputArray = new FormArray(
95 | Array.from(
96 | { length: this.ngxOtpOptions.otpLength! },
97 | () => new FormControl('', Validators.required),
98 | ),
99 | );
100 | if (this.otp) {
101 | this.setInitialOtp(this.otp);
102 | }
103 | }
104 |
105 | private setInitialOtp(otp: string): void {
106 | if (this.ngxOtpOptions.regexp!.test(otp)) {
107 | const otpValueArray = otp.split('');
108 | otpValueArray.forEach((value, index) => {
109 | this.ngxOtpInputArray.controls[index].setValue(value ?? '');
110 | });
111 | this.emitOtpValueChange();
112 | if (otpValueArray.length !== this.ngxOtpOptions.otpLength) {
113 | console.warn(
114 | 'OTP length does not match the provided otpLength option!',
115 | );
116 | }
117 | } else {
118 | throw new Error('Invalid OTP provided for the component ');
119 | }
120 | }
121 |
122 | protected handleInputChanges($event: OtpValueChangeEvent) {
123 | const [index, value] = $event;
124 | this.ngxOtpInputArray.controls[index].setValue(value);
125 | this.emitOtpValueChange();
126 | }
127 |
128 | protected handlePasteChange($event: string[]): void {
129 | if ($event.length === this.ngxOtpOptions.otpLength) {
130 | this.ngxOtpInputArray.setValue($event);
131 | } else {
132 | $event.map((value, index) => {
133 | this.ngxOtpInputArray.controls[index]?.setValue?.(value);
134 | });
135 | }
136 | this.emitOtpValueChange();
137 | }
138 |
139 | private emitOtpValueChange(): void {
140 | this.otpChange.emit(this.ngxOtpInputArray.value);
141 | if (this.ngxOtpInputArray.valid) {
142 | this.otpComplete.emit(this.ngxOtpInputArray.value.join(''));
143 | }
144 | }
145 |
146 | protected isInputFilled(index: number): boolean {
147 | return !!this.ngxOtpInputArray.controls[index].value;
148 | }
149 |
150 | reset(): void {
151 | this.ngxOtpInputArray.reset();
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/src/public-api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of ngx-otp-input
3 | */
4 |
5 | export * from './lib/ngx-otp-input.component';
6 | export { NgxOtpInputComponentOptions } from './lib/default.config';
7 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/lib",
6 | "declaration": true,
7 | "declarationMap": true,
8 | "inlineSources": true,
9 | "types": []
10 | },
11 | "exclude": [
12 | "**/*.spec.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.lib.json",
4 | "compilerOptions": {
5 | "declarationMap": false
6 | },
7 | "angularCompilerOptions": {
8 | "compilationMode": "partial"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/projects/ngx-otp-input/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "../../tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "../../out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "**/*.spec.ts",
12 | "**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Component Options
7 |
135 |
136 |
137 |
Component Preview
138 |
146 |
147 |
150 |
151 | (otpChange) will be triggered when the form value changes:
152 |
153 |
{{ otpChangeValue }}
154 |
155 |
158 |
159 | (otpComplete) will be triggered when the form value is completed:
160 |
29 | This is just a quick demo showcasing the
30 | ngx-otp-input component. You can configure the component
31 | options below and see the changes in the preview. This demo does not
32 | include every possible configuration option, for more information
33 | check out the
34 | documentation page.
39 |
40 |
To apply the changes, click the Apply changes button.
41 |
42 | If you like the library, please consider giving it a star on GitHub!
43 | 🌟
44 |