├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── angular-code-input ├── CHANGELOG.md ├── LICENSE ├── README.md ├── karma.conf.js ├── ng-package.json ├── package.json ├── src │ ├── lib │ │ ├── code-input.component.config.ts │ │ ├── code-input.component.html │ │ ├── code-input.component.scss │ │ ├── code-input.component.spec.ts │ │ ├── code-input.component.ts │ │ └── code-input.module.ts │ ├── public-api.ts │ └── test.ts ├── tsconfig.lib.json ├── tsconfig.lib.prod.json ├── tsconfig.spec.json └── tslint.json ├── angular.json ├── package-lock.json ├── package.json ├── preview1.gif ├── preview2.gif ├── star.jpg ├── tsconfig.json └── tslint.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 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | .angular 44 | 45 | # System Files 46 | .DS_Store 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Alexander Dmitrenko 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall 11 | be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code/pincode input component for angular 2 | 3 | ![](https://img.shields.io/npm/dm/angular-code-input?color=55aa33) 4 | ![](https://img.shields.io/github/stars/AlexMiniApps/angular-code-input) 5 | ![](https://badgen.net/badge/icon/typescript?icon=typescript&label&color=99aabb) 6 | ![](https://img.shields.io/github/license/AlexMiniApps/angular-code-input?color=00ccbb) 7 | 8 | Robust and tested code (number/chars) input component for Angular 7 - 16+ projects.
9 | Ionic 4 - 7+ is supported, can be used in iOS and Android.
10 | Clipboard events are supported. 11 | 12 | Star it to inspire us to build the best component! Star 13 | 14 | Preview 15 | 16 | ![](https://github.com/AlexMiniApps/angular-code-input/blob/master/preview1.gif) 17 | 18 | ![](https://github.com/AlexMiniApps/angular-code-input/blob/master/preview2.gif) 19 | 20 | ## Supported platforms 21 | 22 | Angular 7 - 16+
23 | Ionic 4 - 7+
24 | Mobile browsers and WebViews on: Android and iOS
25 | Desktop browsers: Chrome, Firefox, Safari, Edge v.79 +
26 | Other browsers: Edge v.41 - 44 (without code hidden feature) 27 | 28 | ## Installation 29 | 30 | $ npm install --save angular-code-input 31 | 32 | Choose the version corresponding to your Angular version: 33 | 34 | | Angular | angular-code-input | 35 | |------------|--------------------| 36 | | 16+ | 2.x+ | 37 | | 7-15 | 1.x+ | 38 | 39 | ## Usage 40 | 41 | Import `CodeInputModule` in your app module or page module: 42 | 43 | ```ts 44 | import { CodeInputModule } from 'angular-code-input'; 45 | 46 | @NgModule({ 47 | imports: [ 48 | // ... 49 | CodeInputModule 50 | ] 51 | }) 52 | ``` 53 | 54 | It is possible to configure the component across the app using the root config. In such case the import will look as follows: 55 | ```ts 56 | import { CodeInputModule } from 'angular-code-input'; 57 | 58 | @NgModule({ 59 | imports: [ 60 | // ... 61 | CodeInputModule.forRoot({ 62 | codeLength: 6, 63 | isCharsCode: true, 64 | code: 'abcdef' 65 | }), 66 | ] 67 | }) 68 | ``` 69 | 70 | Include the component on page template HTML: 71 | 72 | ```html 73 | 77 | 78 | ``` 79 | 80 | Inside a page script: 81 | 82 | ```ts 83 | // this called every time when user changed the code 84 | onCodeChanged(code: string) { 85 | } 86 | 87 | // this called only if user entered full code 88 | onCodeCompleted(code: string) { 89 | } 90 | ``` 91 | 92 | ## Configuration 93 | 94 | #### View 95 | 96 | It is possible to configure the component via CSS vars 97 |
or using `::ng-deep` (deprecated) angular CSS selector 98 | [::ng-deep](https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep) 99 | 100 | CSS vars: 101 | 102 | | CSS Var | Description | 103 | |--------------------------------------------------------------|--------------------------------------------------------| 104 | | `--text-security-type: disc;` | Text presentation type when the isCodeHidden is enabled | 105 | | `--item-spacing: 4px;` | Horizontal space between input items | 106 | | `--item-height: 4.375em;` | Height of input items | 107 | | `--item-border: 1px solid #dddddd;` | Border of input item for an empty value | 108 | | `--item-border-bottom: 1px solid #dddddd;` | Bottom border of input item for an empty value | 109 | | `--item-border-has-value: 1px solid #dddddd;` | Border of input item with a value | 110 | | `--item-border-bottom-has-value: 1px solid #dddddd;` | Bottom border of input item with a value | 111 | | `--item-border-focused: 1px solid #dddddd;` | Border of input item when focused | 112 | | `--item-border-bottom-focused: 1px solid #dddddd;` | Bottom border of input item when focused | 113 | | `--item-shadow-focused: 0px 1px 5px rgba(221, 221, 221, 1);` | Shadow of input item when focused | 114 | | `--item-border-radius: 5px;` | Border radius of input item | 115 | | `--item-background: transparent;` | Input item background | 116 | | `--item-font-weight: 300;` | Font weight of input item | 117 | | `--color: #171516;` | Text color of input items | 118 | 119 | Example with only bottom borders: 120 | 121 | ```` 122 | /* inside page styles*/ 123 | ... 124 | code-input { 125 | --item-spacing: 10px; 126 | --item-height: 3em; 127 | --item-border: none; 128 | --item-border-bottom: 2px solid #dddddd; 129 | --item-border-has-value: none; 130 | --item-border-bottom-has-value: 2px solid #888888; 131 | --item-border-focused: none; 132 | --item-border-bottom-focused: 2px solid #809070; 133 | --item-shadow-focused: none; 134 | --item-border-radius: 0px; 135 | } 136 | ... 137 | ```` 138 | 139 | #### Component options 140 | 141 | | Property | Type | Default | Description | 142 | |----------|:-------:|:-----:|----------| 143 | | `codeLength` | number | 4 | Length of input code | 144 | | `inputType` | string | tel | Type of the input DOM elements like `` default '`tel'` | 145 | | `inputMode` | string | numeric | inputmode of the input DOM elements like `` default '`numeric'` | 146 | | `isCodeHidden` | boolean | false | When `true` inputted code chars will be shown as asterisks (points) | 147 | | `isCharsCode` | boolean | false | When `true` inputted code can contain any char and not only digits from 0 to 9. If the input parameter `code` contains non digits chars and `isCharsCode` is `false` the value will be ignored | 148 | | `isPrevFocusableAfterClearing` | boolean | true | When `true` after the input value deletion the caret will be moved to the previous input immediately. If `false` then after the input value deletion the caret will stay on the current input and be moved to the previous input only if the current input is empty | 149 | | `isFocusingOnLastByClickIfFilled` | boolean | false | When `true` and the code is filled then the focus will be moved to the last input element when clicked | 150 | | `initialFocusField` | number | - | The index of the input box for initial focusing. When the component will appear the focus will be placed on the input with this index.
Note: If you need to dynamically hide the component it is needed to use *ngIf directive instead of the `[hidden]` attribute | 151 | | `code` | string / number | - | The input code value for the component. If the parameter contains non digits chars and `isCharsCode` is `false` the value will be ignored | 152 | | `disabled` | boolean | false | When `true` then the component will not handle user actions, like in regular html input element with the `disabled` attribute | 153 | | `autocapitalize` | string | - | The autocapitalize attribute is an enumerated attribute that controls whether and how text input is automatically capitalized as it is entered/edited by the user | 154 | 155 | #### Events 156 | 157 | | Event | Description | 158 | |----------|--------------------| 159 | | `codeChanged` | Will be called every time when a user changed the code | 160 | | `codeCompleted` | Will be called only if a user entered full code | 161 | 162 | ## Methods 163 | 164 | For calling the component's methods it is required to access the component inside the template or page script. 165 | It can be reached as follows. 166 | 167 | Inside the page template HTML add a template ref: 168 | 169 | ```html 170 | 175 | 176 | ``` 177 | 178 | Inside a page script attach the component: 179 | 180 | ```ts 181 | ... 182 | // adding to the imports 183 | import {CodeInputComponent} from 'angular-code-input'; 184 | ... 185 | // adding to the page props 186 | @ViewChild('codeInput') codeInput !: CodeInputComponent; 187 | ... 188 | // calling the component's methods somewhere in the page. 189 | // IMPORTANT: it will be accessible only after the view initialization! 190 | this.codeInput.reset(); 191 | ``` 192 | 193 | | Method | Description | 194 | |----------------|--------------------| 195 | | `focusOnField(index: number): void` | Focuses the input caret on the input box with the passed index | 196 | | `reset(isChangesEmitting = false): void` |

Resets the component values in the following way:

if the `code` option is supplied then the value will be reset to the `code` option value. If the `code` option is not supplied then the component will be reset to empty values.

if the `initialFocusField` option is supplied then the caret will be focused in that filed after reset.

if the `isChangesEmitting` param is passed then changes will be emitted

| 197 | -------------------------------------------------------------------------------- /angular-code-input/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.0.0 (15.06.2023) 2 | **Breaking changes**: 3 | - Enabled Ivy in the compiled library 4 | - The minimum supported version of Angular is set to **16** 5 | 6 | **Note:** Resolved the issue: 7 | - Add support for angular 13 [#59](https://github.com/AlexMiniApps/angular-code-input/issues/59) 8 | 9 | # 1.6.0 (11.11.2021) 10 | **Note:** Fixed issues and implemented cool features: 11 | - Always uppercase keyboard [#51](https://github.com/AlexMiniApps/angular-code-input/issues/51) 12 | - Change codeLength in app [#46](https://github.com/AlexMiniApps/angular-code-input/issues/46) 13 | - Delete button is not triggering on (codechanged) [#44](https://github.com/AlexMiniApps/angular-code-input/issues/44) 14 | 15 | # 1.5.0 (27.05.2021) 16 | **Note:** The following changes have been made: 17 | - Have implemented the feature 'Disabled state' [#28](https://github.com/AlexMiniApps/angular-code-input/issues/28) 18 | - Have fixed the issue 'Insert code "from messages" doesn't appear on iOS' [#36](https://github.com/AlexMiniApps/angular-code-input/issues/36) 19 | 20 | # 1.4.0 (09.02.2021) 21 | **Note:** The following changes have been made: 22 | - Have fixed the issue 'initialFocusField not working for 0 as index bug' [#26](https://github.com/AlexMiniApps/angular-code-input/issues/26) 23 | - Have implemented the feature 'Clear the inputs?' [#27](https://github.com/AlexMiniApps/angular-code-input/issues/27) 24 | - Have added the method for an input field focusing 25 | 26 | # 1.3.3 (21.01.2021) 27 | **Note:** Have implemented the feature: 28 | 29 | Insert Code from Messages not working on IOS [#19](https://github.com/AlexMiniApps/angular-code-input/issues/19) 30 | 31 | # 1.3.2 (15.12.2020) 32 | **Note:** Have implemented the feature: 33 | 34 | Trim whitespace before paste in digit code input [#23](https://github.com/AlexMiniApps/angular-code-input/issues/23) 35 | 36 | # 1.3.1 (01.12.2020) 37 | **Note:** The following changes have been made: 38 | - The issue with inline component configuration has been resolved 39 | - Have fixed issues with readonly properties in Angular 11 40 | - Adjusting tslint issues 41 | 42 | # 1.3.0 (30.11.2020) 43 | **Note:** The following changes have been made: 44 | - Added the root config of the component 45 | - The prop `isCharsCode` has been added for using instead of the `isNonDigitsCode` 46 | - Added the ability to configure component at runtime 47 | 48 | # 1.2.2 (04.08.2020) 49 | **Note:** Have added the prop initialFocusField for focusing on the appropriate input when 50 | the component has been appeared. 51 | 52 | # 1.2.1 (20.06.2020) 53 | **Note:** Have added the prop isFocusingOnLastByClickIfFilled for focusing on the last input box 54 | if the code is full, and any input box clicked. 55 | 56 | # 1.2.0 (20.05.2020) 57 | **Note:** Have added the ability to paste code from the clipboard. 58 | 59 | # 1.1.0 (04.03.2020) 60 | **Note:** Have added to the component new input props - code, isPrevFocusableAfterClearing, inputType. 61 | Minor refactoring. 62 | Fixing Android issues. 63 | 64 | # 1.0.1 (30.12.2019) 65 | **Note:** Have added links to the repository and bugs to the package.json. 66 | 67 | # 1.0.0 (28.12.2019) 68 | **Note:** Initial build. 69 | -------------------------------------------------------------------------------- /angular-code-input/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Alexander Dmitrenko 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall 11 | be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 17 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /angular-code-input/README.md: -------------------------------------------------------------------------------- 1 | # Code/pincode input component for angular 2 | 3 | ![](https://img.shields.io/npm/dm/angular-code-input?color=55aa33) 4 | ![](https://img.shields.io/github/stars/AlexMiniApps/angular-code-input) 5 | ![](https://badgen.net/badge/icon/typescript?icon=typescript&label&color=99aabb) 6 | ![](https://img.shields.io/github/license/AlexMiniApps/angular-code-input?color=00ccbb) 7 | 8 | Robust and tested code (number/chars) input component for Angular 7 - 16+ projects.
9 | Ionic 4 - 7+ is supported, can be used in iOS and Android.
10 | Clipboard events are supported. 11 | 12 | Star it to inspire us to build the best component! Star 13 | 14 | Preview 15 | 16 | ![](https://github.com/AlexMiniApps/angular-code-input/blob/master/preview1.gif) 17 | 18 | ![](https://github.com/AlexMiniApps/angular-code-input/blob/master/preview2.gif) 19 | 20 | ## Supported platforms 21 | 22 | Angular 7 - 16+
23 | Ionic 4 - 7+
24 | Mobile browsers and WebViews on: Android and iOS
25 | Desktop browsers: Chrome, Firefox, Safari, Edge v.79 +
26 | Other browsers: Edge v.41 - 44 (without code hidden feature) 27 | 28 | ## Installation 29 | 30 | $ npm install --save angular-code-input 31 | 32 | Choose the version corresponding to your Angular version: 33 | 34 | | Angular | angular-code-input | 35 | |------------|--------------------| 36 | | 16+ | 2.x+ | 37 | | 7-15 | 1.x+ | 38 | 39 | ## Usage 40 | 41 | Import `CodeInputModule` in your app module or page module: 42 | 43 | ```ts 44 | import { CodeInputModule } from 'angular-code-input'; 45 | 46 | @NgModule({ 47 | imports: [ 48 | // ... 49 | CodeInputModule 50 | ] 51 | }) 52 | ``` 53 | 54 | It is possible to configure the component across the app using the root config. In such case the import will look as follows: 55 | ```ts 56 | import { CodeInputModule } from 'angular-code-input'; 57 | 58 | @NgModule({ 59 | imports: [ 60 | // ... 61 | CodeInputModule.forRoot({ 62 | codeLength: 6, 63 | isCharsCode: true, 64 | code: 'abcdef' 65 | }), 66 | ] 67 | }) 68 | ``` 69 | 70 | Include the component on page template HTML: 71 | 72 | ```html 73 | 77 | 78 | ``` 79 | 80 | Inside a page script: 81 | 82 | ```ts 83 | // this called every time when user changed the code 84 | onCodeChanged(code: string) { 85 | } 86 | 87 | // this called only if user entered full code 88 | onCodeCompleted(code: string) { 89 | } 90 | ``` 91 | 92 | ## Configuration 93 | 94 | #### View 95 | 96 | It is possible to configure the component via CSS vars 97 |
or using `::ng-deep` (deprecated) angular CSS selector 98 | [::ng-deep](https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep) 99 | 100 | CSS vars: 101 | 102 | | CSS Var | Description | 103 | |--------------------------------------------------------------|--------------------------------------------------------| 104 | | `--text-security-type: disc;` | Text presentation type when the isCodeHidden is enabled | 105 | | `--item-spacing: 4px;` | Horizontal space between input items | 106 | | `--item-height: 4.375em;` | Height of input items | 107 | | `--item-border: 1px solid #dddddd;` | Border of input item for an empty value | 108 | | `--item-border-bottom: 1px solid #dddddd;` | Bottom border of input item for an empty value | 109 | | `--item-border-has-value: 1px solid #dddddd;` | Border of input item with a value | 110 | | `--item-border-bottom-has-value: 1px solid #dddddd;` | Bottom border of input item with a value | 111 | | `--item-border-focused: 1px solid #dddddd;` | Border of input item when focused | 112 | | `--item-border-bottom-focused: 1px solid #dddddd;` | Bottom border of input item when focused | 113 | | `--item-shadow-focused: 0px 1px 5px rgba(221, 221, 221, 1);` | Shadow of input item when focused | 114 | | `--item-border-radius: 5px;` | Border radius of input item | 115 | | `--item-background: transparent;` | Input item background | 116 | | `--item-font-weight: 300;` | Font weight of input item | 117 | | `--color: #171516;` | Text color of input items | 118 | 119 | Example with only bottom borders: 120 | 121 | ```` 122 | /* inside page styles*/ 123 | ... 124 | code-input { 125 | --item-spacing: 10px; 126 | --item-height: 3em; 127 | --item-border: none; 128 | --item-border-bottom: 2px solid #dddddd; 129 | --item-border-has-value: none; 130 | --item-border-bottom-has-value: 2px solid #888888; 131 | --item-border-focused: none; 132 | --item-border-bottom-focused: 2px solid #809070; 133 | --item-shadow-focused: none; 134 | --item-border-radius: 0px; 135 | } 136 | ... 137 | ```` 138 | 139 | #### Component options 140 | 141 | | Property | Type | Default | Description | 142 | |----------|:-------:|:-----:|----------| 143 | | `codeLength` | number | 4 | Length of input code | 144 | | `inputType` | string | tel | Type of the input DOM elements like `` default '`tel'` | 145 | | `isCodeHidden` | boolean | false | When `true` inputted code chars will be shown as asterisks (points) | 146 | | `isCharsCode` | boolean | false | When `true` inputted code can contain any char and not only digits from 0 to 9. If the input parameter `code` contains non digits chars and `isCharsCode` is `false` the value will be ignored | 147 | | `isPrevFocusableAfterClearing` | boolean | true | When `true` after the input value deletion the caret will be moved to the previous input immediately. If `false` then after the input value deletion the caret will stay on the current input and be moved to the previous input only if the current input is empty | 148 | | `isFocusingOnLastByClickIfFilled` | boolean | false | When `true` and the code is filled then the focus will be moved to the last input element when clicked | 149 | | `initialFocusField` | number | - | The index of the input box for initial focusing. When the component will appear the focus will be placed on the input with this index.
Note: If you need to dynamically hide the component it is needed to use *ngIf directive instead of the `[hidden]` attribute | 150 | | `code` | string / number | - | The input code value for the component. If the parameter contains non digits chars and `isCharsCode` is `false` the value will be ignored | 151 | | `disabled` | boolean | false | When `true` then the component will not handle user actions, like in regular html input element with the `disabled` attribute | 152 | | `autocapitalize` | string | - | The autocapitalize attribute is an enumerated attribute that controls whether and how text input is automatically capitalized as it is entered/edited by the user | 153 | 154 | #### Events 155 | 156 | | Event | Description | 157 | |----------|--------------------| 158 | | `codeChanged` | Will be called every time when a user changed the code | 159 | | `codeCompleted` | Will be called only if a user entered full code | 160 | 161 | ## Methods 162 | 163 | For calling the component's methods it is required to access the component inside the template or page script. 164 | It can be reached as follows. 165 | 166 | Inside the page template HTML add a template ref: 167 | 168 | ```html 169 | 174 | 175 | ``` 176 | 177 | Inside a page script attach the component: 178 | 179 | ```ts 180 | ... 181 | // adding to the imports 182 | import {CodeInputComponent} from 'angular-code-input'; 183 | ... 184 | // adding to the page props 185 | @ViewChild('codeInput') codeInput !: CodeInputComponent; 186 | ... 187 | // calling the component's methods somewhere in the page. 188 | // IMPORTANT: it will be accessible only after the view initialization! 189 | this.codeInput.reset(); 190 | ``` 191 | 192 | | Method | Description | 193 | |----------------|--------------------| 194 | | `focusOnField(index: number): void` | Focuses the input caret on the input box with the passed index | 195 | | `reset(isChangesEmitting = false): void` |

Resets the component values in the following way:

if the `code` option is supplied then the value will be reset to the `code` option value. If the `code` option is not supplied then the component will be reset to empty values.

if the `initialFocusField` option is supplied then the caret will be focused in that filed after reset.

if the `isChangesEmitting` param is passed then changes will be emitted

| 196 | -------------------------------------------------------------------------------- /angular-code-input/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage/angular-code-input'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /angular-code-input/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../dist/angular-code-input", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /angular-code-input/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-code-input", 3 | "version": "2.0.0", 4 | "description": "Code or pin code input for Angular 7 - 16+ / Ionic 4 - 7+ projects", 5 | "keywords": [ 6 | "angular", 7 | "pincode", 8 | "angular-pincode", 9 | "otp", 10 | "code-input", 11 | "angular-otp", 12 | "ionic-otp", 13 | "ionic-code-input", 14 | "ionic-pincode" 15 | ], 16 | "author": "Alexander Dmitrenko", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/AlexMiniApps/angular-code-input.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/AlexMiniApps/angular-code-input/issues" 24 | }, 25 | "peerDependencies": { 26 | "@angular/common": ">=16.0.0", 27 | "@angular/core": ">=16.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /angular-code-input/src/lib/code-input.component.config.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | 3 | export const CodeInputComponentConfigToken = new InjectionToken('CodeInputComponentConfig'); 4 | 5 | export interface CodeInputComponentConfig { 6 | codeLength?: number; 7 | inputType?: string; 8 | inputMode?: string; 9 | initialFocusField?: number; 10 | isCharsCode?: boolean; 11 | isCodeHidden?: boolean; 12 | isPrevFocusableAfterClearing?: boolean; 13 | isFocusingOnLastByClickIfFilled?: boolean; 14 | code?: string | number; 15 | disabled?: boolean; 16 | autocapitalize?: string; 17 | } 18 | 19 | export const defaultComponentConfig: CodeInputComponentConfig = { 20 | codeLength: 4, 21 | inputType: 'tel', 22 | inputMode: 'numeric', 23 | initialFocusField: undefined, 24 | isCharsCode: false, 25 | isCodeHidden: false, 26 | isPrevFocusableAfterClearing: true, 27 | isFocusingOnLastByClickIfFilled: false, 28 | code: undefined, 29 | disabled: false, 30 | autocapitalize: undefined 31 | }; 32 | -------------------------------------------------------------------------------- /angular-code-input/src/lib/code-input.component.html: -------------------------------------------------------------------------------- 1 | 3 | 13 | 14 | -------------------------------------------------------------------------------- /angular-code-input/src/lib/code-input.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | --text-security-type: disc; 3 | --item-spacing: 4px; 4 | --item-height: 4.375em; 5 | --item-border: 1px solid #dddddd; 6 | --item-border-bottom: 1px solid #dddddd; 7 | --item-border-has-value: 1px solid #dddddd; 8 | --item-border-bottom-has-value: 1px solid #dddddd; 9 | --item-border-focused: 1px solid #dddddd; 10 | --item-border-bottom-focused: 1px solid #dddddd; 11 | --item-shadow-focused: 0px 1px 5px rgba(221, 221, 221, 1); 12 | --item-border-radius: 5px; 13 | --item-background: transparent; 14 | --item-font-weight: 300; 15 | --color: #171516; 16 | 17 | display: flex; 18 | transform: translate3d(0, 0, 0); 19 | font-size: inherit; 20 | color: var(--color); 21 | 22 | span { 23 | display: block; 24 | flex: 1; 25 | padding-right: var(--item-spacing); 26 | 27 | &:first-child { 28 | padding-left: var(--item-spacing); 29 | } 30 | 31 | &.code-hidden input { 32 | text-security: var(--text-security-type); 33 | -webkit-text-security: var(--text-security-type); 34 | -moz-text-security: var(--text-security-type); 35 | } 36 | } 37 | 38 | input { 39 | width: 100%; 40 | height: var(--item-height); 41 | color: inherit; 42 | background: var(--item-background); 43 | text-align: center; 44 | font-size: inherit; 45 | font-weight: var(--item-font-weight); 46 | border: var(--item-border); 47 | border-bottom: var(--item-border-bottom); 48 | border-radius: var(--item-border-radius); 49 | -webkit-appearance: none; 50 | transform: translate3d(0, 0, 0); 51 | -webkit-transform: translate3d(0, 0, 0); 52 | outline: none; 53 | 54 | &.has-value { 55 | border: var(--item-border-has-value); 56 | border-bottom: var(--item-border-bottom-has-value); 57 | } 58 | 59 | &:focus { 60 | background: var(--item-background-focused, transparent); 61 | border: var(--item-border-focused); 62 | border-bottom: var(--item-border-bottom-focused); 63 | box-shadow: var(--item-shadow-focused); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /angular-code-input/src/lib/code-input.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CodeInputComponent } from './code-input.component'; 4 | 5 | describe('CodeInputComponent', () => { 6 | let component: CodeInputComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CodeInputComponent ] 12 | }).compileComponents(); 13 | 14 | fixture = TestBed.createComponent(CodeInputComponent); 15 | component = fixture.componentInstance; 16 | fixture.detectChanges(); 17 | })); 18 | 19 | it('should create', () => { 20 | expect(component).toBeTruthy(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /angular-code-input/src/lib/code-input.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterViewChecked, 3 | AfterViewInit, 4 | Component, 5 | ElementRef, 6 | EventEmitter, 7 | Inject, 8 | Input, 9 | OnChanges, 10 | OnDestroy, 11 | OnInit, 12 | Optional, 13 | Output, 14 | QueryList, 15 | SimpleChanges, 16 | ViewChildren 17 | } from '@angular/core'; 18 | import { 19 | CodeInputComponentConfig, 20 | CodeInputComponentConfigToken, 21 | defaultComponentConfig 22 | } from './code-input.component.config'; 23 | import { Subscription } from 'rxjs'; 24 | 25 | enum InputState { 26 | ready = 0, 27 | reset = 1 28 | } 29 | 30 | @Component({ 31 | // tslint:disable-next-line:component-selector 32 | selector: 'code-input', 33 | templateUrl: 'code-input.component.html', 34 | styleUrls: ['./code-input.component.scss'] 35 | }) 36 | export class CodeInputComponent implements AfterViewInit, OnInit, OnChanges, OnDestroy, AfterViewChecked, CodeInputComponentConfig { 37 | 38 | @ViewChildren('input') inputsList !: QueryList; 39 | 40 | @Input() codeLength !: number; 41 | @Input() inputType !: string; 42 | @Input() inputMode !: string; 43 | @Input() initialFocusField?: number; 44 | /** @deprecated Use isCharsCode prop instead. */ 45 | @Input() isNonDigitsCode = false; 46 | @Input() isCharsCode !: boolean; 47 | @Input() isCodeHidden !: boolean; 48 | @Input() isPrevFocusableAfterClearing !: boolean; 49 | @Input() isFocusingOnLastByClickIfFilled !: boolean; 50 | @Input() code ?: string | number; 51 | @Input() disabled !: boolean; 52 | @Input() autocapitalize ?: string; 53 | 54 | @Output() readonly codeChanged = new EventEmitter(); 55 | @Output() readonly codeCompleted = new EventEmitter(); 56 | 57 | public placeholders: number[] = []; 58 | 59 | private inputs: HTMLInputElement[] = []; 60 | private inputsStates: InputState[] = []; 61 | private inputsListSubscription !: Subscription; 62 | 63 | // tslint:disable-next-line:variable-name 64 | private _codeLength !: number; 65 | private state = { 66 | isFocusingAfterAppearingCompleted: false, 67 | isInitialFocusFieldEnabled: false 68 | }; 69 | 70 | constructor(@Optional() @Inject(CodeInputComponentConfigToken) config?: CodeInputComponentConfig) { 71 | Object.assign(this, defaultComponentConfig); 72 | 73 | if (!config) { 74 | return; 75 | } 76 | 77 | // filtering for only valid config props 78 | for (const prop in config) { 79 | if (!config.hasOwnProperty(prop)) { 80 | continue; 81 | } 82 | 83 | if (!defaultComponentConfig.hasOwnProperty(prop)) { 84 | continue; 85 | } 86 | 87 | // @ts-ignore 88 | this[prop] = config[prop]; 89 | } 90 | } 91 | 92 | /** 93 | * Life cycle 94 | */ 95 | 96 | ngOnInit(): void { 97 | // defining the state 98 | this.state.isInitialFocusFieldEnabled = !this.isEmpty(this.initialFocusField); 99 | // initiating the code 100 | this.onCodeLengthChanges(); 101 | } 102 | 103 | ngAfterViewInit(): void { 104 | // initiation of the inputs 105 | this.inputsListSubscription = this.inputsList.changes.subscribe(this.onInputsListChanges.bind(this)); 106 | this.onInputsListChanges(this.inputsList); 107 | } 108 | 109 | ngAfterViewChecked(): void { 110 | this.focusOnInputAfterAppearing(); 111 | } 112 | 113 | ngOnChanges(changes: SimpleChanges): void { 114 | if (changes.code) { 115 | this.onInputCodeChanges(); 116 | } 117 | if (changes.codeLength) { 118 | this.onCodeLengthChanges(); 119 | } 120 | } 121 | 122 | ngOnDestroy(): void { 123 | if (this.inputsListSubscription) { 124 | this.inputsListSubscription.unsubscribe(); 125 | } 126 | } 127 | 128 | /** 129 | * Methods 130 | */ 131 | 132 | reset(isChangesEmitting = false): void { 133 | // resetting the code to its initial value or to an empty value 134 | this.onInputCodeChanges(); 135 | 136 | if (this.state.isInitialFocusFieldEnabled) { 137 | // tslint:disable-next-line:no-non-null-assertion 138 | this.focusOnField(this.initialFocusField!); 139 | } 140 | 141 | if (isChangesEmitting) { 142 | this.emitChanges(); 143 | } 144 | } 145 | 146 | focusOnField(index: number): void { 147 | if (index >= this._codeLength) { 148 | throw new Error('The index of the focusing input box should be less than the codeLength.'); 149 | } 150 | 151 | this.inputs[index].focus(); 152 | } 153 | 154 | onClick(e: any): void { 155 | // handle click events only if the the prop is enabled 156 | if (!this.isFocusingOnLastByClickIfFilled) { 157 | return; 158 | } 159 | 160 | const target = e.target; 161 | const last = this.inputs[this._codeLength - 1]; 162 | // already focused 163 | if (target === last) { 164 | return; 165 | } 166 | 167 | // check filling 168 | const isFilled = this.getCurrentFilledCode().length >= this._codeLength; 169 | if (!isFilled) { 170 | return; 171 | } 172 | 173 | // focusing on the last input if is filled 174 | setTimeout(() => last.focus()); 175 | } 176 | 177 | onInput(e: any, i: number): void { 178 | const target = e.target; 179 | const value = e.data || target.value; 180 | 181 | if (this.isEmpty(value)) { 182 | return; 183 | } 184 | 185 | // only digits are allowed if isCharsCode flag is absent/false 186 | if (!this.canInputValue(value)) { 187 | e.preventDefault(); 188 | e.stopPropagation(); 189 | this.setInputValue(target, null); 190 | this.setStateForInput(target, InputState.reset); 191 | return; 192 | } 193 | 194 | const values = value.toString().trim().split(''); 195 | for (let j = 0; j < values.length; j++) { 196 | const index = j + i; 197 | if (index > this._codeLength - 1) { 198 | break; 199 | } 200 | 201 | this.setInputValue(this.inputs[index], values[j]); 202 | } 203 | this.emitChanges(); 204 | 205 | const next = i + values.length; 206 | if (next > this._codeLength - 1) { 207 | target.blur(); 208 | return; 209 | } 210 | 211 | this.inputs[next].focus(); 212 | } 213 | 214 | onPaste(e: ClipboardEvent, i: number): void { 215 | e.preventDefault(); 216 | e.stopPropagation(); 217 | 218 | const data = e.clipboardData ? e.clipboardData.getData('text').trim() : undefined; 219 | 220 | if (this.isEmpty(data)) { 221 | return; 222 | } 223 | 224 | // Convert paste text into iterable 225 | // tslint:disable-next-line:no-non-null-assertion 226 | const values = data!.split(''); 227 | let valIndex = 0; 228 | 229 | for (let j = i; j < this.inputs.length; j++) { 230 | // The values end is reached. Loop exit 231 | if (valIndex === values.length) { 232 | break; 233 | } 234 | 235 | const input = this.inputs[j]; 236 | const val = values[valIndex]; 237 | 238 | // Cancel the loop when a value cannot be used 239 | if (!this.canInputValue(val)) { 240 | this.setInputValue(input, null); 241 | this.setStateForInput(input, InputState.reset); 242 | return; 243 | } 244 | 245 | this.setInputValue(input, val.toString()); 246 | valIndex++; 247 | } 248 | 249 | this.inputs[i].blur(); 250 | this.emitChanges(); 251 | } 252 | 253 | async onKeydown(e: any, i: number): Promise { 254 | const target = e.target; 255 | const isTargetEmpty = this.isEmpty(target.value); 256 | const prev = i - 1; 257 | 258 | // processing only the backspace and delete key events 259 | const isBackspaceKey = await this.isBackspaceKey(e); 260 | const isDeleteKey = this.isDeleteKey(e); 261 | if (!isBackspaceKey && !isDeleteKey) { 262 | return; 263 | } 264 | 265 | e.preventDefault(); 266 | 267 | this.setInputValue(target, null); 268 | if (!isTargetEmpty) { 269 | this.emitChanges(); 270 | } 271 | 272 | // preventing to focusing on the previous field if it does not exist or the delete key has been pressed 273 | if (prev < 0 || isDeleteKey) { 274 | return; 275 | } 276 | 277 | if (isTargetEmpty || this.isPrevFocusableAfterClearing) { 278 | this.inputs[prev].focus(); 279 | } 280 | } 281 | 282 | private onInputCodeChanges(): void { 283 | if (!this.inputs.length) { 284 | return; 285 | } 286 | 287 | if (this.isEmpty(this.code)) { 288 | this.inputs.forEach((input: HTMLInputElement) => { 289 | this.setInputValue(input, null); 290 | }); 291 | return; 292 | } 293 | 294 | // tslint:disable-next-line:no-non-null-assertion 295 | const chars = this.code!.toString().trim().split(''); 296 | // checking if all the values are correct 297 | let isAllCharsAreAllowed = true; 298 | for (const char of chars) { 299 | if (!this.canInputValue(char)) { 300 | isAllCharsAreAllowed = false; 301 | break; 302 | } 303 | } 304 | 305 | this.inputs.forEach((input: HTMLInputElement, index: number) => { 306 | const value = isAllCharsAreAllowed ? chars[index] : null; 307 | this.setInputValue(input, value); 308 | }); 309 | } 310 | 311 | private onCodeLengthChanges(): void { 312 | if (!this.codeLength) { 313 | return; 314 | } 315 | 316 | this._codeLength = this.codeLength; 317 | if (this._codeLength > this.placeholders.length) { 318 | const numbers = Array(this._codeLength - this.placeholders.length).fill(1); 319 | this.placeholders.splice(this.placeholders.length - 1, 0, ...numbers); 320 | } 321 | else if (this._codeLength < this.placeholders.length) { 322 | this.placeholders.splice(this._codeLength); 323 | } 324 | } 325 | 326 | private onInputsListChanges(list: QueryList): void { 327 | if (list.length > this.inputs.length) { 328 | const inputsToAdd = list.filter((item, index) => index > this.inputs.length - 1); 329 | this.inputs.splice(this.inputs.length, 0, ...inputsToAdd.map(item => item.nativeElement)); 330 | const states = Array(inputsToAdd.length).fill(InputState.ready); 331 | this.inputsStates.splice(this.inputsStates.length, 0, ...states); 332 | } 333 | else if (list.length < this.inputs.length) { 334 | this.inputs.splice(list.length); 335 | this.inputsStates.splice(list.length); 336 | } 337 | 338 | // filling the inputs after changing of their count 339 | this.onInputCodeChanges(); 340 | } 341 | 342 | private focusOnInputAfterAppearing(): void { 343 | if (!this.state.isInitialFocusFieldEnabled) { 344 | return; 345 | } 346 | 347 | if (this.state.isFocusingAfterAppearingCompleted) { 348 | return; 349 | } 350 | 351 | // tslint:disable-next-line:no-non-null-assertion 352 | this.focusOnField(this.initialFocusField!); 353 | // tslint:disable-next-line:no-non-null-assertion 354 | this.state.isFocusingAfterAppearingCompleted = document.activeElement === this.inputs[this.initialFocusField!]; 355 | } 356 | 357 | private emitChanges(): void { 358 | setTimeout(() => this.emitCode(), 50); 359 | } 360 | 361 | private emitCode(): void { 362 | const code = this.getCurrentFilledCode(); 363 | 364 | this.codeChanged.emit(code); 365 | 366 | if (code.length >= this._codeLength) { 367 | this.codeCompleted.emit(code); 368 | } 369 | } 370 | 371 | private getCurrentFilledCode(): string { 372 | let code = ''; 373 | 374 | for (const input of this.inputs) { 375 | if (!this.isEmpty(input.value)) { 376 | code += input.value; 377 | } 378 | } 379 | 380 | return code; 381 | } 382 | 383 | private isBackspaceKey(e: any): Promise { 384 | const isBackspace = (e.key && e.key.toLowerCase() === 'backspace') || (e.keyCode && e.keyCode === 8); 385 | if (isBackspace) { 386 | return Promise.resolve(true); 387 | } 388 | 389 | // process only key with placeholder keycode on android devices 390 | if (!e.keyCode || e.keyCode !== 229) { 391 | return Promise.resolve(false); 392 | } 393 | 394 | return new Promise((resolve) => { 395 | setTimeout(() => { 396 | const input = e.target; 397 | const isReset = this.getStateForInput(input) === InputState.reset; 398 | if (isReset) { 399 | this.setStateForInput(input, InputState.ready); 400 | } 401 | // if backspace key pressed the caret will have position 0 (for single value field) 402 | resolve(input.selectionStart === 0 && !isReset); 403 | }); 404 | }); 405 | } 406 | 407 | private isDeleteKey(e: any): boolean { 408 | return (e.key && e.key.toLowerCase() === 'delete') || (e.keyCode && e.keyCode === 46); 409 | } 410 | 411 | private setInputValue(input: HTMLInputElement, value: any): void { 412 | const isEmpty = this.isEmpty(value); 413 | const valueClassCSS = 'has-value'; 414 | const emptyClassCSS = 'empty'; 415 | if (isEmpty) { 416 | input.value = ''; 417 | input.classList.remove(valueClassCSS); 418 | // tslint:disable-next-line:no-non-null-assertion 419 | input.parentElement!.classList.add(emptyClassCSS); 420 | } 421 | else { 422 | input.value = value; 423 | input.classList.add(valueClassCSS); 424 | // tslint:disable-next-line:no-non-null-assertion 425 | input.parentElement!.classList.remove(emptyClassCSS); 426 | } 427 | } 428 | 429 | private canInputValue(value: any): boolean { 430 | if (this.isEmpty(value)) { 431 | return false; 432 | } 433 | 434 | const isDigitsValue = /^[0-9]+$/.test(value.toString()); 435 | return isDigitsValue || (this.isCharsCode || this.isNonDigitsCode); 436 | } 437 | 438 | private setStateForInput(input: HTMLInputElement, state: InputState): void { 439 | const index = this.inputs.indexOf(input); 440 | if (index < 0) { 441 | return; 442 | } 443 | 444 | this.inputsStates[index] = state; 445 | } 446 | 447 | private getStateForInput(input: HTMLInputElement): InputState | undefined { 448 | const index = this.inputs.indexOf(input); 449 | return this.inputsStates[index]; 450 | } 451 | 452 | private isEmpty(value: any): boolean { 453 | return value === null || value === undefined || !value.toString().length; 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /angular-code-input/src/lib/code-input.module.ts: -------------------------------------------------------------------------------- 1 | import {ModuleWithProviders, NgModule} from '@angular/core'; 2 | import {CodeInputComponent } from './code-input.component'; 3 | import {CommonModule} from '@angular/common'; 4 | import {CodeInputComponentConfig, CodeInputComponentConfigToken} from './code-input.component.config'; 5 | 6 | @NgModule({ 7 | imports: [ 8 | CommonModule 9 | ], 10 | declarations: [ 11 | CodeInputComponent 12 | ], 13 | exports: [ 14 | CodeInputComponent 15 | ] 16 | }) 17 | export class CodeInputModule { 18 | static forRoot(config: CodeInputComponentConfig): ModuleWithProviders { 19 | return { 20 | ngModule: CodeInputModule, 21 | providers: [ 22 | {provide: CodeInputComponentConfigToken, useValue: config } 23 | ] 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /angular-code-input/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of code-input 3 | */ 4 | 5 | export * from './lib/code-input.component'; 6 | export * from './lib/code-input.component.config'; 7 | export * from './lib/code-input.module'; 8 | -------------------------------------------------------------------------------- /angular-code-input/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es/reflect'; 4 | import 'zone.js'; 5 | import 'zone.js/testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | -------------------------------------------------------------------------------- /angular-code-input/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/lib", 5 | "module": "es2015", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "inlineSources": true, 10 | "experimentalDecorators": true, 11 | "importHelpers": true, 12 | "types": [], 13 | "lib": [ 14 | "dom", 15 | "es2018" 16 | ] 17 | }, 18 | "angularCompilerOptions": { 19 | "annotateForClosureCompiler": true, 20 | "skipTemplateCodegen": true, 21 | "strictMetadataEmit": true, 22 | "fullTemplateTypeCheck": true, 23 | "strictInjectionParameters": true, 24 | "enableResourceInlining": true 25 | }, 26 | "exclude": [ 27 | "src/test.ts", 28 | "**/*.spec.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /angular-code-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 | -------------------------------------------------------------------------------- /angular-code-input/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /angular-code-input/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "camelCase" 8 | ], 9 | "component-selector": [ 10 | true, 11 | "element", 12 | "kebab-case" 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-code-input": { 7 | "root": "angular-code-input", 8 | "sourceRoot": "angular-code-input/src", 9 | "projectType": "library", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "tsConfig": "angular-code-input/tsconfig.lib.json", 16 | "project": "angular-code-input/ng-package.json" 17 | }, 18 | "configurations": { 19 | "production": { 20 | "tsConfig": "angular-code-input/tsconfig.lib.prod.json" 21 | } 22 | } 23 | }, 24 | "test": { 25 | "builder": "@angular-devkit/build-angular:karma", 26 | "options": { 27 | "main": "angular-code-input/src/test.ts", 28 | "tsConfig": "angular-code-input/tsconfig.spec.json", 29 | "karmaConfig": "angular-code-input/karma.conf.js" 30 | } 31 | }, 32 | "lint": { 33 | "builder": "@angular-devkit/build-angular:tslint", 34 | "options": { 35 | "tsConfig": [ 36 | "angular-code-input/tsconfig.lib.json", 37 | "angular-code-input/tsconfig.spec.json" 38 | ], 39 | "exclude": [ 40 | "**/node_modules/**" 41 | ] 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-code-input-workspace", 3 | "version": "1.7.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e", 12 | "publish": "ng build angular-code-input --configuration production && npm publish ./dist/angular-code-input" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/AlexMiniApps/angular-code-input.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/AlexMiniApps/angular-code-input/issues" 20 | }, 21 | "private": false, 22 | "dependencies": { 23 | "@angular/animations": "^16.0.2", 24 | "@angular/common": "^16.0.2", 25 | "@angular/compiler": "^16.0.2", 26 | "@angular/core": "^16.0.2", 27 | "@angular/forms": "^16.0.2", 28 | "@angular/platform-browser": "^16.0.2", 29 | "@angular/platform-browser-dynamic": "^16.0.2", 30 | "@angular/router": "^16.0.2", 31 | "rxjs": "^7.8.1", 32 | "tslib": "^2.5.2", 33 | "zone.js": "^0.13.0" 34 | }, 35 | "devDependencies": { 36 | "@angular-devkit/build-angular": "^16.0.2", 37 | "@angular/cli": "^16.0.2", 38 | "@angular/compiler-cli": "^16.0.2", 39 | "@angular/language-service": "^16.0.2", 40 | "@types/jasmine": "^4.3.1", 41 | "@types/jasminewd2": "^2.0.10", 42 | "@types/node": "^20.2.3", 43 | "codelyzer": "^6.0.2", 44 | "core-js": "^3.30.2", 45 | "jasmine-core": "^4.2.0", 46 | "jasmine-spec-reporter": "^7.0.0", 47 | "karma": "^6.4.2", 48 | "karma-chrome-launcher": "^3.2.0", 49 | "karma-coverage-istanbul-reporter": "^3.0.3", 50 | "karma-jasmine": "^5.1.0", 51 | "karma-jasmine-html-reporter": "^2.0.0", 52 | "ng-packagr": "^16.0.1", 53 | "protractor": "^7.0.0", 54 | "ts-node": "^10.9.1", 55 | "tslint": "^6.1.3", 56 | "typescript": "~5.0.4" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /preview1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexMiniApps/angular-code-input/f216f756098f51a8ed7a4bb028485004062951e4/preview1.gif -------------------------------------------------------------------------------- /preview2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexMiniApps/angular-code-input/f216f756098f51a8ed7a4bb028485004062951e4/preview2.gif -------------------------------------------------------------------------------- /star.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexMiniApps/angular-code-input/f216f756098f51a8ed7a4bb028485004062951e4/star.jpg -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "downlevelIteration": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "es2020", 10 | "moduleResolution": "node", 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "ES2022", 14 | "typeRoots": ["node_modules/@types"], 15 | "lib": ["es2018", "dom"], 16 | "paths": { 17 | "code-input": ["dist/code-input"], 18 | "code-input/*": ["dist/code-input/*"] 19 | }, 20 | "useDefineForClassFields": false 21 | }, 22 | "angularCompilerOptions": { 23 | "strictTemplates": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-parens": false, 15 | "arrow-return-shorthand": true, 16 | "curly": true, 17 | "deprecation": { 18 | "severity": "warn" 19 | }, 20 | "eofline": true, 21 | "import-blacklist": [ 22 | true, 23 | "rxjs/Rx" 24 | ], 25 | "import-spacing": true, 26 | "indent": { 27 | "options": [ 28 | "spaces" 29 | ] 30 | }, 31 | "interface-name": false, 32 | "max-classes-per-file": false, 33 | "max-line-length": [ 34 | true, 35 | 140 36 | ], 37 | "member-access": false, 38 | "member-ordering": [ 39 | true, 40 | { 41 | "order": [ 42 | "static-field", 43 | "instance-field", 44 | "static-method", 45 | "instance-method" 46 | ] 47 | } 48 | ], 49 | "no-consecutive-blank-lines": false, 50 | "no-console": [ 51 | true, 52 | "debug", 53 | "info", 54 | "time", 55 | "timeEnd", 56 | "trace" 57 | ], 58 | "no-empty": false, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-non-null-assertion": true, 64 | "no-redundant-jsdoc": true, 65 | "no-switch-case-fall-through": true, 66 | "no-var-requires": false, 67 | "object-literal-key-quotes": [ 68 | true, 69 | "as-needed" 70 | ], 71 | "object-literal-sort-keys": false, 72 | "ordered-imports": false, 73 | "quotemark": [ 74 | true, 75 | "single" 76 | ], 77 | "semicolon": { 78 | "options": [ 79 | "always" 80 | ] 81 | }, 82 | "space-before-function-paren": { 83 | "options": { 84 | "anonymous": "never", 85 | "asyncArrow": "always", 86 | "constructor": "never", 87 | "method": "never", 88 | "named": "never" 89 | } 90 | }, 91 | "trailing-comma": false, 92 | "no-output-on-prefix": true, 93 | "no-inputs-metadata-property": true, 94 | "no-outputs-metadata-property": true, 95 | "no-host-metadata-property": true, 96 | "no-input-rename": true, 97 | "no-output-rename": true, 98 | "typedef-whitespace": { 99 | "options": [ 100 | { 101 | "call-signature": "nospace", 102 | "index-signature": "nospace", 103 | "parameter": "nospace", 104 | "property-declaration": "nospace", 105 | "variable-declaration": "nospace" 106 | }, 107 | { 108 | "call-signature": "onespace", 109 | "index-signature": "onespace", 110 | "parameter": "onespace", 111 | "property-declaration": "onespace", 112 | "variable-declaration": "onespace" 113 | } 114 | ] 115 | }, 116 | "use-lifecycle-interface": true, 117 | "use-pipe-transform-interface": true, 118 | "component-class-suffix": true, 119 | "directive-class-suffix": true 120 | , "variable-name": { 121 | "options": [ 122 | "ban-keywords", 123 | "check-format", 124 | "allow-pascal-case" 125 | ] 126 | }, 127 | "whitespace": { 128 | "options": [ 129 | "check-branch", 130 | "check-decl", 131 | "check-operator", 132 | "check-separator", 133 | "check-type", 134 | "check-typecast" 135 | ] 136 | } 137 | } 138 | } 139 | --------------------------------------------------------------------------------