├── .babelrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── __snapshots__ │ └── text-input-mask.test.tsx.snap ├── cel-phone.mask.test.ts ├── cnpj.mask.test.ts ├── cpf.mask.test.ts ├── credit-card.mask.test.ts ├── custom.mask.test.ts ├── date-parser.test.ts ├── datetime.mask.test.ts ├── money.mask.test.ts ├── only-numbers.mask.test.ts ├── text-input-mask.test.tsx └── zip-code.mask.test.ts ├── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src ├── index.ts ├── masks │ ├── base.mask.ts │ ├── cel-phone.mask.ts │ ├── cnpj.mask.ts │ ├── cpf.mask.ts │ ├── credit-card.mask.ts │ ├── custom.mask.ts │ ├── datetime.mask.ts │ ├── internal-dependencies │ │ ├── date-parser.ts │ │ └── vanilla-masker.ts │ ├── money.mask.ts │ ├── only-numbers.mask.ts │ └── zip-code.mask.ts └── text-input-mask.tsx ├── tiny-mask └── tinyMask.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | "@babel/preset-react", 6 | "@babel/preset-env" 7 | ] 8 | }, 9 | "build": { 10 | "presets": [ 11 | ["@babel/preset-env", { "modules": false }], 12 | "@babel/preset-react" 13 | ], 14 | "plugins": [ 15 | "@babel/plugin-transform-destructuring", 16 | "@babel/plugin-transform-parameters", 17 | "@babel/plugin-proposal-object-rest-spread" 18 | ] 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | .idea/ 4 | npm-debug.log 5 | dist 6 | poc.js 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | .idea/ 4 | npm-debug.log 5 | src/ 6 | tiny-mask 7 | __tests__ 8 | **.tgz 9 | .babelrc 10 | jest.config.js 11 | rollup.config.mjs 12 | tsconfig.json 13 | yarn-error.log 14 | yarn.lock 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5 2 | * Adjust bug in ref to mask components 3 | 4 | ## 1.0.3 5 | * Adjust bug in accent in macOS 6 | 7 | ## 1.0.2 8 | * Remove unecessary eval 9 | 10 | ## 1.0.1 11 | * Update to .npmignore file. 12 | * Minification of the distribution files. 13 | * Adjustment to the tsconfig file. 14 | 15 | ## 1.0.0 16 | * Library update to accept only the "mask" prop, and in this prop, you import the mask you want to use from the library. If the library doesn't have the desired mask, two options are available: the first is "customMask," which is used for less complex custom masks, and you can also import "BaseMask" to create any new mask regardless of its complexity. 17 | * Removal of unnecessary code, making the library easier to understand and update. 18 | 19 | ## 0.5.0 20 | * Added types 21 | * Change lib from js to ts 22 | 23 | ## 0.2.0 24 | * Change the _type_ property to __kind__ 25 | * Fix the tests 26 | 27 | ## 0.1.3 28 | * Nothing changed. Version bump. 29 | 30 | 31 | ## 0.1.2 32 | * Nothing changed. Version bump. 33 | 34 | ## 0.1.1 35 | * Removed unused props. 36 | 37 | ## 0.1.0 38 | * Adapted react-native-masked-text for Web applications. 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Edmar Miyake 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-masked-text 2 | 3 | This is a simple masked text (normal text and input text) component for React. 4 | 5 | Thanks to [vanilla-masker](https://github.com/BankFacil/vanilla-masker) =). 6 | Thanks to [benhurott](https://github.com/benhurott/react-native-masked-text) 7 | 8 | ## Supported Versions 9 | React: 16.0.0 or higher 10 | 11 | ## Install 12 | `npm install react-masked-text --save` 13 | 14 | ## Usage (TextInputMask) 15 | ```tsx 16 | import React from 'react'; 17 | 18 | // import the component and the mask 19 | import { TextInputMask, datetimeMask } from 'react-masked-text'; 20 | 21 | export const MyComponent = () => { 22 | const myDateTextRef = React.createRef(); 23 | 24 | return ( 25 | 29 | ); 30 | } 31 | 32 | ``` 33 | 34 | ## Props 35 | 36 | ### value 37 | If you set this prop, this component becomes a controlled component. 38 | 39 | ### defaultValue 40 | Use this props if you're using this component as an uncontrolled component and you want to set its default value (initial value). 41 | You may notice that it doesn't make sense to set value and defaultValue at the same time. 42 | 43 | ### mask 44 | 45 | **creditCardMask:** use the mask `9999 9999 9999 9999` and `text` keyboard.
46 | **cpfMask:** use the mask `999.999.999-99` and `text` keyboard.
47 | **cnpjMask:** use the mask `99.999.999/9999-99` and `text` keyboard.
48 | **zipCodeMask:** use the mask `99999-999` and `text` keyboard.
49 | **onlyNumbersMask:** accept only numbers on field with `numeric` keyboard.
50 | **moneyMask:** use the mask `R$ 0,00` on the field with `text` keyboard.
51 | **celPhoneMask:** use the mask `(99) 9999-9999` or `(99) 99999-9999` (changing automaticaly by length).
52 | **datetimeMask:** use datetime mask with a similiar [moment](https://momentjs.com/docs/#/parsing/string-format/) format (default DD/MM/YYYY HH:mm:ss).
53 | **customMask:** use your custom mask (see later in this doc).
54 | 55 | 56 | ### customMask 57 | Use `customMask` to basic mask formatting, use letter 'A' to letters and number '9' to numbers and separators like a `space`, `/`, `-`, etc... for example:
58 | 59 | ```tsx 60 | return ( 61 | 65 | ); 66 | ``` 67 | 68 | 69 | ## Methods 70 | 71 | * `getElement()`: return the instance of *TextInput* component. 72 | * `isValid()`: if the value inputed is valid for the mask. 73 | * *credit-card*: return true if the mask is complete. 74 | * *cpf*: return true if the mask is complete and cpf is valid. 75 | * *cnpj*: return true if the mask is complete and cnpj is valid. 76 | * *zip-code*: return true if the mask is complete. 77 | * *only-numbers*: always returns true. 78 | * *money*: always returns true. 79 | * *cel-phone*: return true if the mask is complete. 80 | * *datetime*: return true if the date value is valid for format. 81 | * *custom*: use custom validation, if it not exist, always returns true. 82 | * `getRawValue()`: get the converted value of mask. 83 | * *credit-card*: return the array with the value parts. Ex: `1234 1234 1234 1234` returns `[1234, 1234, 1234, 1234]`. 84 | * *cpf*: return the value without mask. 85 | * *cnpj*: return the value without mask. 86 | * *zip-code*: return the value without mask. 87 | * *only-numbers*: return the value without mask. 88 | * *money*: return the Number value. Ex: `R$ 1.234,56` returns `1234.56`. 89 | * *cel-phone*: return the value without mask. 90 | * *datetime*: return a Date object for the date and format. 91 | * *custom*: use custom method (passed in options). If it not exists, returns the current value. 92 | 93 | 94 | 95 | ## Extra (BaseMask) 96 | If you want, we expose the `BaseMask`. You can use it to create your own mask: 97 | 98 | Ex: 99 | 100 | ``` ts 101 | import { BaseMask } from 'react-masked-text'; 102 | 103 | interface DollarMoneyMaskSettings { 104 | precision: number; 105 | separator: string; 106 | delimiter: string; 107 | unit: string; 108 | suffixUnit: string; 109 | zeroCents: boolean; 110 | } 111 | 112 | const DOLLAR_MONEY_MASK_SETTINGS: DollarMoneyMaskSettings = { 113 | precision: 2, 114 | separator: '.', 115 | delimiter: ',', 116 | unit: 'U$', 117 | suffixUnit: '', 118 | zeroCents: false, 119 | }; 120 | 121 | class DollarMoneyMask extends BaseMask { 122 | static getType(): string { 123 | return 'dollar-money'; 124 | } 125 | 126 | getValue(value: string, settings?: Partial, oldValue?: string): string { 127 | const mergedSettings = super.mergeSettings(DOLLAR_MONEY_MASK_SETTINGS, settings) as MoneyMaskSettings; 128 | 129 | if (mergedSettings.suffixUnit && oldValue && value) { 130 | if (value.length === oldValue.length - 1) { 131 | const cleared = this.removeNotNumbers(value); 132 | value = cleared.substring(0, cleared.length - 1); 133 | } 134 | } 135 | 136 | const masked = this.getVMasker().toMoney(value, mergedSettings); 137 | return masked; 138 | } 139 | 140 | getRawValue(maskedValue: string, settings?: Partial): number { 141 | const mergedSettings = super.mergeSettings(DOLLAR_MONEY_MASK_SETTINGS, settings) as MoneyMaskSettings; 142 | let normalized = super.removeNotNumbers(maskedValue); 143 | 144 | const dotPosition = normalized.length - mergedSettings.precision; 145 | normalized = this._insert(normalized, dotPosition, '.'); 146 | 147 | return Number(normalized); 148 | } 149 | 150 | validate(): boolean { 151 | return true; 152 | } 153 | 154 | private _insert(text: string, index: number, stringToInsert: string): string { 155 | if (index > 0) { 156 | return text.substring(0, index) + stringToInsert + text.substring(index, text.length); 157 | } else { 158 | return stringToInsert + text; 159 | } 160 | } 161 | } 162 | 163 | export const dollarMoneyMask = () => new DollarMoneyMask(); 164 | 165 | // dollar-money -> U$ 9,475.28 166 | ``` 167 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/text-input-mask.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TextInputMask renders controlled component 1`] = ` 4 | 8 | `; 9 | 10 | exports[`TextInputMask renders controlled component 2`] = ` 11 | 15 | `; 16 | 17 | exports[`TextInputMask renders controlled component with initial value 1`] = ` 18 | { 19 | "asFragment": [Function], 20 | "baseElement": 21 |
22 | 26 |
27 | , 28 | "container":
29 | 33 |
, 34 | "debug": [Function], 35 | "findAllByAltText": [Function], 36 | "findAllByDisplayValue": [Function], 37 | "findAllByLabelText": [Function], 38 | "findAllByPlaceholderText": [Function], 39 | "findAllByRole": [Function], 40 | "findAllByTestId": [Function], 41 | "findAllByText": [Function], 42 | "findAllByTitle": [Function], 43 | "findByAltText": [Function], 44 | "findByDisplayValue": [Function], 45 | "findByLabelText": [Function], 46 | "findByPlaceholderText": [Function], 47 | "findByRole": [Function], 48 | "findByTestId": [Function], 49 | "findByText": [Function], 50 | "findByTitle": [Function], 51 | "getAllByAltText": [Function], 52 | "getAllByDisplayValue": [Function], 53 | "getAllByLabelText": [Function], 54 | "getAllByPlaceholderText": [Function], 55 | "getAllByRole": [Function], 56 | "getAllByTestId": [Function], 57 | "getAllByText": [Function], 58 | "getAllByTitle": [Function], 59 | "getByAltText": [Function], 60 | "getByDisplayValue": [Function], 61 | "getByLabelText": [Function], 62 | "getByPlaceholderText": [Function], 63 | "getByRole": [Function], 64 | "getByTestId": [Function], 65 | "getByText": [Function], 66 | "getByTitle": [Function], 67 | "queryAllByAltText": [Function], 68 | "queryAllByDisplayValue": [Function], 69 | "queryAllByLabelText": [Function], 70 | "queryAllByPlaceholderText": [Function], 71 | "queryAllByRole": [Function], 72 | "queryAllByTestId": [Function], 73 | "queryAllByText": [Function], 74 | "queryAllByTitle": [Function], 75 | "queryByAltText": [Function], 76 | "queryByDisplayValue": [Function], 77 | "queryByLabelText": [Function], 78 | "queryByPlaceholderText": [Function], 79 | "queryByRole": [Function], 80 | "queryByTestId": [Function], 81 | "queryByText": [Function], 82 | "queryByTitle": [Function], 83 | "rerender": [Function], 84 | "unmount": [Function], 85 | } 86 | `; 87 | 88 | exports[`TextInputMask renders uncontrolled component 1`] = ` 89 | { 90 | "asFragment": [Function], 91 | "baseElement": 92 |
93 | 97 |
98 | , 99 | "container":
100 | 104 |
, 105 | "debug": [Function], 106 | "findAllByAltText": [Function], 107 | "findAllByDisplayValue": [Function], 108 | "findAllByLabelText": [Function], 109 | "findAllByPlaceholderText": [Function], 110 | "findAllByRole": [Function], 111 | "findAllByTestId": [Function], 112 | "findAllByText": [Function], 113 | "findAllByTitle": [Function], 114 | "findByAltText": [Function], 115 | "findByDisplayValue": [Function], 116 | "findByLabelText": [Function], 117 | "findByPlaceholderText": [Function], 118 | "findByRole": [Function], 119 | "findByTestId": [Function], 120 | "findByText": [Function], 121 | "findByTitle": [Function], 122 | "getAllByAltText": [Function], 123 | "getAllByDisplayValue": [Function], 124 | "getAllByLabelText": [Function], 125 | "getAllByPlaceholderText": [Function], 126 | "getAllByRole": [Function], 127 | "getAllByTestId": [Function], 128 | "getAllByText": [Function], 129 | "getAllByTitle": [Function], 130 | "getByAltText": [Function], 131 | "getByDisplayValue": [Function], 132 | "getByLabelText": [Function], 133 | "getByPlaceholderText": [Function], 134 | "getByRole": [Function], 135 | "getByTestId": [Function], 136 | "getByText": [Function], 137 | "getByTitle": [Function], 138 | "queryAllByAltText": [Function], 139 | "queryAllByDisplayValue": [Function], 140 | "queryAllByLabelText": [Function], 141 | "queryAllByPlaceholderText": [Function], 142 | "queryAllByRole": [Function], 143 | "queryAllByTestId": [Function], 144 | "queryAllByText": [Function], 145 | "queryAllByTitle": [Function], 146 | "queryByAltText": [Function], 147 | "queryByDisplayValue": [Function], 148 | "queryByLabelText": [Function], 149 | "queryByPlaceholderText": [Function], 150 | "queryByRole": [Function], 151 | "queryByTestId": [Function], 152 | "queryByText": [Function], 153 | "queryByTitle": [Function], 154 | "rerender": [Function], 155 | "unmount": [Function], 156 | } 157 | `; 158 | -------------------------------------------------------------------------------- /__tests__/cel-phone.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { CelPhoneMask } from './../src/masks/cel-phone.mask'; 2 | 3 | test('getType results cel-phone', () => { 4 | var expected = 'cel-phone'; 5 | var received = CelPhoneMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('5188888888 results (51) 8888-8888', () => { 11 | var mask = new CelPhoneMask(); 12 | var expected = '(51) 8888-8888'; 13 | var received = mask.getValue('5188888888'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('51888888888 results (51) 88888-8888', () => { 19 | var mask = new CelPhoneMask(); 20 | var expected = '(51) 88888-8888'; 21 | var received = mask.getValue('51888888888'); 22 | 23 | expect(received).toBe(expected); 24 | }); 25 | 26 | test('88888888 withDDD=false results 8888-8888', () => { 27 | var mask = new CelPhoneMask(); 28 | var expected = '8888-8888'; 29 | var received = mask.getValue('88888888', { 30 | withDDD: false 31 | }); 32 | 33 | expect(received).toBe(expected); 34 | }); 35 | 36 | test('888888888 withDDD=false results 88888-8888', () => { 37 | var mask = new CelPhoneMask(); 38 | var expected = '88888-8888'; 39 | var received = mask.getValue('888888888', { 40 | withDDD: false 41 | }); 42 | 43 | expect(received).toBe(expected); 44 | }); 45 | 46 | test('12377777777 dddMask=999 results 123 7777-7777', () => { 47 | var mask = new CelPhoneMask(); 48 | var expected = '123 7777-7777'; 49 | var received = mask.getValue('12377777777', { 50 | dddMask: '999 ' 51 | }); 52 | 53 | expect(received).toBe(expected); 54 | }); 55 | 56 | test('123777777777 dddMask=999 results 123 77777-7777', () => { 57 | var mask = new CelPhoneMask(); 58 | var expected = '123 77777-7777'; 59 | var received = mask.getValue('123777777777', { 60 | dddMask: '999 ' 61 | }); 62 | 63 | expect(received).toBe(expected); 64 | }); 65 | 66 | test('123123 is not valid', () => { 67 | var mask = new CelPhoneMask(); 68 | var expected = false; 69 | var received = mask.validate('123123'); 70 | 71 | expect(received).toBe(expected); 72 | }); 73 | 74 | test('5188888888 is valid', () => { 75 | var mask = new CelPhoneMask(); 76 | var isValid = mask.validate('5188888888'); 77 | 78 | expect(isValid).toBe(true); 79 | }); 80 | 81 | test('123777777777 dddMask=999 is valid', () => { 82 | var mask = new CelPhoneMask(); 83 | var isValid = mask.validate('123777777777', { 84 | dddMask: '999 ' 85 | }); 86 | 87 | expect(isValid).toBe(true); 88 | }); 89 | 90 | test('1237777777 dddMask=999 is not valid', () => { 91 | var mask = new CelPhoneMask(); 92 | var isValid = mask.validate('1237777777', { 93 | dddMask: '999 ' 94 | }); 95 | 96 | expect(isValid).toBe(false); 97 | }); 98 | 99 | test('5188888888 results (51) 8888-8888 and raw value 5188888888', () => { 100 | var mask = new CelPhoneMask(); 101 | var expected = '(51) 8888-8888'; 102 | var received = mask.getValue('5188888888'); 103 | 104 | var expectedRawValue = '5188888888'; 105 | var receivedRawValue = mask.getRawValue(received); 106 | 107 | expect(received).toBe(expected); 108 | expect(receivedRawValue).toBe(expectedRawValue); 109 | }); 110 | 111 | test('123777777777 dddMask=999 results 123 77777-7777 and raw value 123777777777', () => { 112 | var mask = new CelPhoneMask(); 113 | var expected = '123 77777-7777'; 114 | var received = mask.getValue('123777777777', { 115 | dddMask: '999 ' 116 | }); 117 | 118 | var expectedRawValue = '123777777777'; 119 | var receivedRawValue = mask.getRawValue(received); 120 | 121 | expect(received).toBe(expected); 122 | expect(receivedRawValue).toBe(expectedRawValue); 123 | }); -------------------------------------------------------------------------------- /__tests__/cnpj.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { CnpjMask } from './../src/masks/cnpj.mask'; 2 | 3 | test('getType results cnpj', () => { 4 | var expected = 'cnpj'; 5 | var received = CnpjMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('79885262000130 results 79.885.262/0001-30', () => { 11 | var mask = new CnpjMask(); 12 | var expected = '79.885.262/0001-30'; 13 | var received = mask.getValue('79885262000130'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('798852 results 79.885.2', () => { 19 | var mask = new CnpjMask(); 20 | var expected = '79.885.2'; 21 | var received = mask.getValue('798852'); 22 | 23 | expect(received).toBe(expected); 24 | }); 25 | 26 | test('79885262000130 results 79.885.262/0001-30 and is valid', () => { 27 | var mask = new CnpjMask(); 28 | var expected = '79.885.262/0001-30'; 29 | var received = mask.getValue('79885262000130'); 30 | var isValid = mask.validate(received); 31 | 32 | expect(received).toBe(expected); 33 | expect(isValid).toBe(true); 34 | }); 35 | 36 | test('79885262000140 results 79.885.262/0001-40 and is not valid', () => { 37 | var mask = new CnpjMask(); 38 | var expected = '79.885.262/0001-40'; 39 | var received = mask.getValue('79885262000140'); 40 | var isValid = mask.validate(received); 41 | 42 | expect(received).toBe(expected); 43 | expect(isValid).toBe(false); 44 | }); 45 | 46 | test('7988526200013 results 79.885.262/0001-3 and is not valid', () => { 47 | var mask = new CnpjMask(); 48 | var expected = '79.885.262/0001-3'; 49 | var received = mask.getValue('7988526200013'); 50 | var isValid = mask.validate(received); 51 | 52 | expect(received).toBe(expected); 53 | expect(isValid).toBe(false); 54 | }); 55 | 56 | test('79885262000130 results 79.885.262/0001-30 and raw value 79885262000130', () => { 57 | var mask = new CnpjMask(); 58 | var expected = '79.885.262/0001-30'; 59 | var received = mask.getValue('79885262000130'); 60 | 61 | var expectedRawValue = '79885262000130'; 62 | var receivedRawValue = mask.getRawValue(received); 63 | 64 | expect(received).toBe(expected); 65 | expect(receivedRawValue).toBe(expectedRawValue); 66 | }); -------------------------------------------------------------------------------- /__tests__/cpf.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { CpfMask } from './../src/masks/cpf.mask'; 2 | 3 | test('getType results cpf', () => { 4 | var expected = 'cpf'; 5 | var received = CpfMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('12312312356 results 123.123.123-56', () => { 11 | var mask = new CpfMask(); 12 | var expected = '123.123.123-56'; 13 | var received = mask.getValue('12312312356'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('123123 results 123.123', () => { 19 | var mask = new CpfMask(); 20 | var expected = '123.123'; 21 | var received = mask.getValue('123123'); 22 | 23 | expect(received).toBe(expected); 24 | }); 25 | 26 | test('07833823678 results 078.338.236-78 and is valid', () => { 27 | var mask = new CpfMask(); 28 | var expected = '078.338.236-78'; 29 | var received = mask.getValue('07833823678'); 30 | var isValid = mask.validate(received); 31 | 32 | expect(received).toBe(expected); 33 | expect(isValid).toBe(true); 34 | }); 35 | 36 | test('11111111111 results 111.111.111-11 and is not valid', () => { 37 | var mask = new CpfMask(); 38 | var expected = '111.111.111-11'; 39 | var received = mask.getValue('11111111111'); 40 | var isValid = mask.validate(received); 41 | 42 | expect(received).toBe(expected); 43 | expect(isValid).toBe(false); 44 | }); 45 | 46 | test('1234567890 results 123.456.789-0 and is not valid', () => { 47 | var mask = new CpfMask(); 48 | var expected = '123.456.789-0'; 49 | var received = mask.getValue('1234567890'); 50 | var isValid = mask.validate(received); 51 | 52 | expect(received).toBe(expected); 53 | expect(isValid).toBe(false); 54 | }); 55 | 56 | test('12312312356 results 123.123.123-56 and raw value 12312312356', () => { 57 | var mask = new CpfMask(); 58 | var expected = '123.123.123-56'; 59 | var received = mask.getValue('12312312356'); 60 | 61 | var expectedRawValue = '12312312356'; 62 | var receivedRawValue = mask.getRawValue(received); 63 | 64 | expect(received).toBe(expected); 65 | expect(receivedRawValue).toBe(expectedRawValue); 66 | }); -------------------------------------------------------------------------------- /__tests__/credit-card.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { CreditCardMask } from './../src/masks/credit-card.mask'; 2 | 3 | test('getType results credit-card', () => { 4 | var expected = 'credit-card'; 5 | var received = CreditCardMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('1234123412341234 results 1234 1234 1234 1234', () => { 11 | var mask = new CreditCardMask(); 12 | var expected = '1234 1234 1234 1234'; 13 | var received = mask.getValue('1234123412341234'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('1234123412341234 obfuscated true results 1234 **** **** 1234', () => { 19 | var mask = new CreditCardMask(); 20 | var expected = '1234 **** **** 1234'; 21 | var received = mask.getValue('1234123412341234', { 22 | obfuscated: true 23 | }); 24 | 25 | expect(received).toBe(expected); 26 | }); 27 | 28 | test('1234123412341234 obfuscated false results 1234 1234 1234 1234', () => { 29 | var mask = new CreditCardMask(); 30 | var expected = '1234 1234 1234 1234'; 31 | var received = mask.getValue('1234123412341234', { 32 | obfuscated: false 33 | }); 34 | 35 | expect(received).toBe(expected); 36 | }); 37 | 38 | test('1234123412341234 obfuscated false results 1234 1234 1234 1234 and raw value [1234, 1234, 1234, 1234]', () => { 39 | var mask = new CreditCardMask(); 40 | var options = { 41 | obfuscated: false 42 | }; 43 | 44 | var expected = '1234 1234 1234 1234'; 45 | var received = mask.getValue('1234123412341234', options); 46 | 47 | var expectedRawValue = ['1234', '1234', '1234', '1234']; 48 | var receivedRawValue = mask.getRawValue(received); 49 | 50 | expect(received).toBe(expected); 51 | 52 | expectedRawValue.forEach((val, index) => { 53 | expect(val).toBe(receivedRawValue[index]); 54 | }); 55 | }); 56 | 57 | test('1234123412341234 obfuscated true results 1234 **** **** 1234 and raw value [1234, ****, ****, 1234]', () => { 58 | var mask = new CreditCardMask(); 59 | var options = { 60 | obfuscated: true 61 | }; 62 | 63 | var expected = '1234 **** **** 1234'; 64 | var received = mask.getValue('1234123412341234', options); 65 | 66 | var expectedRawValue = ['1234', '****', '****', '1234']; 67 | var receivedRawValue = mask.getRawValue(received); 68 | 69 | expect(received).toBe(expected); 70 | 71 | expectedRawValue.forEach((val, index) => { 72 | expect(val).toBe(receivedRawValue[index]); 73 | }); 74 | }); -------------------------------------------------------------------------------- /__tests__/custom.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { CustomMask } from './../src/masks/custom.mask'; 2 | 3 | test('getType results custom', () => { 4 | var expected = 'custom'; 5 | var received = CustomMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('123 with mask AAA9 results ', () => { 11 | var mask = new CustomMask('AAA9'); 12 | var expected = ''; 13 | var received = mask.getValue('123'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('TA3 with mask AAA9 results TA', () => { 19 | var mask = new CustomMask('AAA9'); 20 | var expected = 'TA'; 21 | var received = mask.getValue('TA3'); 22 | 23 | expect(received).toBe(expected); 24 | }); 25 | 26 | test('TABC with mask AAA9 results TAB', () => { 27 | var mask = new CustomMask('AAA9'); 28 | var expected = 'TAB'; 29 | var received = mask.getValue('TABC'); 30 | 31 | expect(received).toBe(expected); 32 | }); 33 | 34 | test('1111111 with mask 999-9999 results 111-1111', () => { 35 | var mask = new CustomMask('999-9999'); 36 | var expected = '111-1111'; 37 | var received = mask.getValue('1111111'); 38 | 39 | expect(received).toBe(expected); 40 | }); 41 | 42 | test('B45 with mask A#99 results B#45', () => { 43 | var mask = new CustomMask('A#99'); 44 | var expected = 'B#45'; 45 | var received = mask.getValue('B45'); 46 | 47 | expect(received).toBe(expected); 48 | }); 49 | 50 | test('BC45 with mask AS#99 results BC#45', () => { 51 | var mask = new CustomMask('AS#99'); 52 | var expected = 'BC#45'; 53 | var received = mask.getValue('BC45'); 54 | 55 | expect(received).toBe(expected); 56 | }); 57 | 58 | test('B345 with mask AS#99 results B3#45', () => { 59 | var mask = new CustomMask('AS#99'); 60 | var expected = 'B3#45'; 61 | var received = mask.getValue('B345'); 62 | 63 | expect(received).toBe(expected); 64 | }); 65 | 66 | test('DWARF with mask AAAAA and custom validator results DWARF and is valid', () => { 67 | var mask = new CustomMask('AAAAA'); 68 | var input = 'DWARF'; 69 | 70 | var validator = (value) => { 71 | return value === 'DWARF'; 72 | }; 73 | 74 | var expected = 'DWARF'; 75 | var received = mask.getValue(input); 76 | var isValid = mask.validate(input, { validator }); 77 | 78 | expect(expected).toBe(received); 79 | expect(isValid).toBe(true); 80 | }); 81 | 82 | test('ELF with mask AAAAA and custom validator results DWARF and is invalid', () => { 83 | var mask = new CustomMask('AAAAA'); 84 | var input = 'ELF'; 85 | 86 | var validator = (value) => { 87 | return value === 'DWARF'; 88 | }; 89 | 90 | var expected = 'ELF'; 91 | var received = mask.getValue(input); 92 | var isValid = mask.validate(input, { validator }); 93 | 94 | expect(expected).toBe(received); 95 | expect(isValid).toBe(false); 96 | }); 97 | 98 | test('123 with mask 999 results 123 and raw value 123(type Number)', () => { 99 | var mask = new CustomMask('999'); 100 | var options = { 101 | getRawValue: function (maskedValue) { 102 | return Number(maskedValue); 103 | } 104 | }; 105 | 106 | var expected = '123'; 107 | var received = mask.getValue('123'); 108 | 109 | var expectedRawValue = 123; 110 | var receivedRawValue = mask.getRawValue(received, options); 111 | 112 | expect(received).toBe(expected); 113 | expect(receivedRawValue).toBe(expectedRawValue); 114 | }); 115 | 116 | test('mask with custom translation and match', () => { 117 | var mask = new CustomMask('999&AAA'); 118 | var options = { 119 | translation: { 120 | '&': function (val) { 121 | return ['#', '.', ':'].indexOf(val) >= 0 ? val : null; 122 | } 123 | } 124 | } 125 | 126 | var expected = '123#ABC'; 127 | var received = mask.getValue('123#ABC', options); 128 | 129 | expect(received).toBe(expected); 130 | }); 131 | 132 | test('mask with custom translation and not match', () => { 133 | var mask = new CustomMask('999&AAA'); 134 | var options = { 135 | translation: { 136 | '&': function (val) { 137 | return ['#', '.', ':'].indexOf(val) >= 0 ? val : null; 138 | } 139 | } 140 | } 141 | 142 | var expected = '123'; 143 | var received = mask.getValue('123|ABC', options); 144 | 145 | expect(received).toBe(expected); 146 | }); 147 | 148 | test('mask with custom translation and optionals and matching', () => { 149 | var mask = new CustomMask('999***AAA&'); 150 | var options = { 151 | translation: { 152 | '&': function (val) { 153 | return ['#', '.', ':'].indexOf(val) >= 0 ? val : null; 154 | } 155 | } 156 | } 157 | 158 | var expected = '123|% ABC.'; 159 | var received = mask.getValue('123|% ABC.', options); 160 | 161 | expect(received).toBe(expected); 162 | }); 163 | -------------------------------------------------------------------------------- /__tests__/date-parser.test.ts: -------------------------------------------------------------------------------- 1 | import { parseStringDate } from '../src/masks/internal-dependencies/date-parser'; 2 | 3 | var input1 = '01/01/1990 17:40:30'; 4 | const format1 = `DD/MM/YYYY HH:mm:ss`; 5 | test(`format ${format1} parses ${input1}`, () => { 6 | const date = parseStringDate(input1, format1); 7 | 8 | expect(date.getFullYear()).toBe(1990); 9 | expect(date.getMonth()).toBe(0); 10 | expect(date.getDay()).toBe(1); 11 | expect(date.getHours()).toBe(17); 12 | expect(date.getMinutes()).toBe(40); 13 | expect(date.getSeconds()).toBe(30); 14 | }); 15 | 16 | var input2 = '01-01-1990'; 17 | const format2 = `DD-MM-YYYY`; 18 | test(`format ${format2} parses ${input2}`, () => { 19 | const date = parseStringDate(input2, format2); 20 | 21 | expect(date.getFullYear()).toBe(1990); 22 | expect(date.getMonth()).toBe(0); 23 | expect(date.getDay()).toBe(1); 24 | }); 25 | 26 | var input3 = '99.99.9999'; 27 | const format3 = `DD.MM.YYYY`; 28 | test(`format ${format3} doesnt parse ${input3}`, () => { 29 | const date = parseStringDate(input3, format3); 30 | 31 | expect(date).toBe(null); 32 | }); 33 | 34 | var input4 = '14/a1/2018'; 35 | const format4 = `DD/MM/YYYY`; 36 | test(`format ${format4} parses ${input4}`, () => { 37 | const date = parseStringDate(input4, format4); 38 | 39 | expect(date).toBe(null); 40 | }); 41 | 42 | var input5 = '17:40:30'; 43 | const format5 = `HH:mm:ss`; 44 | test(`format ${format5} parses ${input5}`, () => { 45 | const date = parseStringDate(input5, format5); 46 | 47 | expect(date.getHours()).toBe(17); 48 | expect(date.getMinutes()).toBe(40); 49 | expect(date.getSeconds()).toBe(30); 50 | }); 51 | 52 | var input6 = '09:40 PM'; 53 | const format6 = `hh:mm aa`; 54 | test(`format ${format6} parses ${input6}`, () => { 55 | const date = parseStringDate(input6, format6); 56 | 57 | expect(date.getHours()).toBe(21); 58 | expect(date.getMinutes()).toBe(40); 59 | }); 60 | 61 | var input7 = '17'; 62 | const format7 = `HH`; 63 | test(`format ${format7} parses ${input7}`, () => { 64 | const date = parseStringDate(input7, format7); 65 | 66 | expect(date.getHours()).toBe(17); 67 | }); 68 | -------------------------------------------------------------------------------- /__tests__/datetime.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { DatetimeMask } from './../src/masks/datetime.mask'; 2 | var moment = require('moment'); 3 | const { parseStringDate } = require('../src/masks/internal-dependencies/date-parser.ts'); 4 | 5 | function compareMomentObj(dateTimeA, dateTimeB) { 6 | var momentA = moment(dateTimeA, "DD/MM/YYYY"); 7 | var momentB = moment(dateTimeB, "DD/MM/YYYY"); 8 | if (momentA > momentB) return 1; 9 | else if (momentA < momentB) return -1; 10 | else return 0; 11 | } 12 | 13 | test('getType results datetime', () => { 14 | var expected = 'datetime'; 15 | var received = DatetimeMask.getType(); 16 | 17 | expect(received).toBe(expected); 18 | }); 19 | 20 | test('01011990174030 with format DD/MM/YYYY HH:mm:ss results 01/01/1990 17:40:30', () => { 21 | var mask = new DatetimeMask(); 22 | var expected = '01/01/1990 17:40:30'; 23 | var received = mask.getValue('01011990174030'); 24 | 25 | expect(received).toBe(expected); 26 | }); 27 | 28 | test('01011990174030 with format DD-MM-YYYY HH:mm:ss results 01-01-1990 17:40:30', () => { 29 | var mask = new DatetimeMask(); 30 | var expected = '01-01-1990 17:40:30'; 31 | var received = mask.getValue('01011990174030', { 32 | format: 'DD-MM-YYYY HH:mm:ss' 33 | }); 34 | 35 | expect(received).toBe(expected); 36 | }); 37 | 38 | 39 | test('01011990 with format DD-MM-YYYY results 01-01-1990', () => { 40 | var mask = new DatetimeMask(); 41 | var expected = '01-01-1990'; 42 | var received = mask.getValue('01011990', { 43 | format: 'DD-MM-YYYY HH:mm:ss', 44 | }); 45 | 46 | expect(received).toBe(expected); 47 | }); 48 | 49 | test('191050 with format HH:mm:ss results 19:10:50 and is valid', () => { 50 | var mask = new DatetimeMask(); 51 | var input = '191050'; 52 | var settings = { 53 | format: 'HH:mm:ss', 54 | }; 55 | 56 | var expected = '19:10:50'; 57 | var received = mask.getValue(input, settings); 58 | var isValid = mask.validate(input, settings); 59 | 60 | expect(received).toBe(expected); 61 | expect(isValid).toBe(true); 62 | }); 63 | 64 | test('99999999 with format DD/MM/YYYY results 99/99/9999 and is invalid', () => { 65 | var mask = new DatetimeMask(); 66 | var input = '99999999'; 67 | var settings = { 68 | format: 'DD/MM/YYYY', 69 | }; 70 | 71 | var expected = '99/99/9999'; 72 | var received = mask.getValue(input, settings); 73 | var isValid = mask.validate(input, settings); 74 | 75 | expect(received).toBe(expected); 76 | expect(isValid).toBe(false); 77 | }); 78 | 79 | test('01011990174030 with format DD/MM/YYYY HH:mm:ss results 01/01/1990 17:40:30 and is valid', () => { 80 | var mask = new DatetimeMask(); 81 | var input = '01011990174030'; 82 | var settings = { 83 | format: 'DD/MM/YYYY HH:mm:ss', 84 | }; 85 | 86 | var expected = '01/01/1990 17:40:30'; 87 | var received = mask.getValue(input, settings); 88 | var isValid = mask.validate(input, settings); 89 | 90 | expect(received).toBe(expected); 91 | expect(isValid).toBe(true); 92 | }); 93 | 94 | test('01011990174030 with format DD/MM/YYYY HH:mm:ss results 01/01/1990 17:40:30 and raw value Date', () => { 95 | var mask = new DatetimeMask(); 96 | var expected = '01/01/1990 17:40:30'; 97 | var settings = { 98 | format: 'DD/MM/YYYY HH:mm:ss', 99 | }; 100 | var received = mask.getValue('01011990174030'); 101 | 102 | var expectedRawValue = parseStringDate(received, settings.format); 103 | var receivedRawValue = mask.getRawValue(received, settings); 104 | 105 | expect(received).toBe(expected); 106 | expect(compareMomentObj(receivedRawValue, expectedRawValue)).toBe(0); 107 | }); -------------------------------------------------------------------------------- /__tests__/money.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { MoneyMask } from './../src/masks/money.mask'; 2 | 3 | test('getType results money', () => { 4 | var expected = 'money'; 5 | var received = MoneyMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('1 results R$0,01', () => { 11 | var mask = new MoneyMask(); 12 | var expected = 'R$0,01'; 13 | var received = mask.getValue('1'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('111 results R$1,11', () => { 19 | var mask = new MoneyMask(); 20 | var expected = 'R$1,11'; 21 | var received = mask.getValue('111'); 22 | 23 | expect(received).toBe(expected); 24 | }); 25 | 26 | test('1111 results R$11,11', () => { 27 | var mask = new MoneyMask(); 28 | var expected = 'R$11,11'; 29 | var received = mask.getValue('1111'); 30 | 31 | expect(received).toBe(expected); 32 | }); 33 | 34 | test('11111 results R$111,11', () => { 35 | var mask = new MoneyMask(); 36 | var expected = 'R$111,11'; 37 | var received = mask.getValue('11111'); 38 | 39 | expect(received).toBe(expected); 40 | }); 41 | 42 | test('111111 results R$1.111,11', () => { 43 | var mask = new MoneyMask(); 44 | var expected = 'R$1.111,11'; 45 | var received = mask.getValue('111111'); 46 | 47 | expect(received).toBe(expected); 48 | }); 49 | 50 | test('111111111 results R$1.111.111,11', () => { 51 | var mask = new MoneyMask(); 52 | var expected = 'R$1.111.111,11'; 53 | var received = mask.getValue('111111111'); 54 | 55 | expect(received).toBe(expected); 56 | }); 57 | 58 | test(' results R$0,00', () => { 59 | var mask = new MoneyMask(); 60 | var expected = 'R$0,00'; 61 | var received = mask.getValue(''); 62 | 63 | expect(received).toBe(expected); 64 | }); 65 | 66 | test('11111 precision 3 results R$11,111', () => { 67 | var mask = new MoneyMask(); 68 | var expected = 'R$11,111'; 69 | var received = mask.getValue('11111', { 70 | precision: 3 71 | }); 72 | 73 | expect(received).toBe(expected); 74 | }); 75 | 76 | test('111 separator . results R$1.11', () => { 77 | var mask = new MoneyMask(); 78 | var expected = 'R$1.11'; 79 | var received = mask.getValue('111', { 80 | separator: '.' 81 | }); 82 | 83 | expect(received).toBe(expected); 84 | }); 85 | 86 | test('111111 delimiter , results R$1,111,11', () => { 87 | var mask = new MoneyMask(); 88 | var expected = 'R$1,111,11'; 89 | var received = mask.getValue('111111', { 90 | delimiter: ',' 91 | }); 92 | 93 | expect(received).toBe(expected); 94 | }); 95 | 96 | test('1 unit US$ results US$0,01', () => { 97 | var mask = new MoneyMask(); 98 | var expected = 'US$0,01'; 99 | var received = mask.getValue('1', { 100 | unit: 'US$' 101 | }); 102 | 103 | expect(received).toBe(expected); 104 | }); 105 | 106 | test('1 suffixUnit $$$ results R$0,01', () => { 107 | var mask = new MoneyMask(); 108 | var expected = 'R$0,01 $$$'; 109 | var received = mask.getValue('1', { 110 | suffixUnit: '$$$' 111 | }); 112 | 113 | expect(received).toBe(expected); 114 | }); 115 | 116 | test('1 zeroCents results R$1,00', () => { 117 | var mask = new MoneyMask(); 118 | var expected = 'R$1,00'; 119 | var received = mask.getValue('1', { 120 | zeroCents: true 121 | }); 122 | 123 | expect(received).toBe(expected); 124 | }); 125 | 126 | test('US$ config with value 1234567 results US$12,345.67', () => { 127 | var mask = new MoneyMask(); 128 | var expected = 'US$12,345.67'; 129 | var received = mask.getValue('1234567', { 130 | unit: 'US$', 131 | delimiter: ',', 132 | separator: '.' 133 | }); 134 | 135 | expect(received).toBe(expected); 136 | }); 137 | 138 | test('1 results R$0,01 and raw value 0.01', () => { 139 | var mask = new MoneyMask(); 140 | var expected = 'R$0,01'; 141 | var received = mask.getValue('1'); 142 | 143 | var expectedRawValue = 0.01; 144 | var receivedRawValue = mask.getRawValue(received); 145 | 146 | expect(received).toBe(expected); 147 | expect(receivedRawValue).toBe(expectedRawValue); 148 | }); 149 | 150 | test('111111 results R$1.111,11 and raw value 1111.11', () => { 151 | var mask = new MoneyMask(); 152 | var expected = 'R$1.111,11'; 153 | var received = mask.getValue('111111'); 154 | 155 | var expectedRawValue = 1111.11; 156 | var receivedRawValue = mask.getRawValue(received); 157 | 158 | expect(received).toBe(expected); 159 | expect(receivedRawValue).toBe(expectedRawValue); 160 | }); 161 | 162 | test('1 zeroCents results R$1,00 and raw value 1', () => { 163 | var mask = new MoneyMask(); 164 | var expected = 'R$1,00'; 165 | var received = mask.getValue('1', { 166 | zeroCents: true 167 | }); 168 | 169 | var expectedRawValue = 1; 170 | var receivedRawValue = mask.getRawValue(received); 171 | 172 | expect(received).toBe(expected); 173 | expect(receivedRawValue).toBe(expectedRawValue); 174 | }); 175 | 176 | test('111111 delimiter , results R$1,111,11 and raw value 1111.11', () => { 177 | var mask = new MoneyMask(); 178 | var expected = 'R$1,111,11'; 179 | var received = mask.getValue('111111', { 180 | delimiter: ',' 181 | }); 182 | 183 | var expectedRawValue = 1111.11; 184 | var receivedRawValue = mask.getRawValue(received); 185 | 186 | expect(received).toBe(expected); 187 | expect(receivedRawValue).toBe(expectedRawValue); 188 | }); 189 | 190 | test('1 unit US$ results US$ 0,01', () => { 191 | var mask = new MoneyMask(); 192 | var expected = 'US$ 0,01'; 193 | var received = mask.getValue('1', { 194 | unit: 'US$ ' 195 | }); 196 | 197 | expect(received).toBe(expected); 198 | }); -------------------------------------------------------------------------------- /__tests__/only-numbers.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { OnlyNumbersMask } from './../src/masks/only-numbers.mask'; 2 | 3 | test('getType results only-numbers', () => { 4 | var expected = 'only-numbers'; 5 | var received = OnlyNumbersMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('abc123 results 123', () => { 11 | var mask = new OnlyNumbersMask(); 12 | var expected = '123'; 13 | var received = mask.getValue('abc123'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('1 results 1', () => { 19 | var mask = new OnlyNumbersMask(); 20 | var expected = '1'; 21 | var received = mask.getValue('1'); 22 | 23 | expect(received).toBe(expected); 24 | }); 25 | 26 | test('abc results ', () => { 27 | var mask = new OnlyNumbersMask(); 28 | var expected = ''; 29 | var received = mask.getValue('abc'); 30 | 31 | expect(received).toBe(expected); 32 | }); 33 | 34 | test('1 results 1 and raw value 1', () => { 35 | var mask = new OnlyNumbersMask(); 36 | var expected = '1'; 37 | var received = mask.getValue('1'); 38 | 39 | var expectedRawValue = '1'; 40 | var receivedRawValue = mask.getRawValue(received); 41 | 42 | expect(received).toBe(expected); 43 | expect(receivedRawValue).toBe(expectedRawValue); 44 | }); 45 | -------------------------------------------------------------------------------- /__tests__/text-input-mask.test.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { render, screen } from "@testing-library/react"; 3 | import "@testing-library/jest-dom"; 4 | import TextInputMask, { cpfMask } from "../src/text-input-mask"; 5 | import * as React from "react"; 6 | 7 | beforeAll(() => { 8 | // Create a spy on console (console.error in this case) and provide some mocked implementation 9 | jest.spyOn(console, "error").mockImplementation(() => {}); 10 | }); 11 | afterAll(() => { 12 | // Restore mock after all tests are done, so it won't affect other test suites 13 | console.error.mockRestore(); 14 | }); 15 | afterEach(() => { 16 | // Clear mock (all calls etc) after each test. 17 | // It's needed when you're using console somewhere in the tests so you have clean mock each time 18 | console.error.mockClear(); 19 | }); 20 | 21 | test("TextInputMask renders uncontrolled component", () => { 22 | const result = render(); 23 | expect(result).toMatchSnapshot(); 24 | }); 25 | 26 | test("TextInputMask renders controlled component", () => { 27 | const { rerender } = render(); 28 | expect(screen.getByDisplayValue("")).toMatchSnapshot(); 29 | 30 | rerender(); 31 | expect(screen.getByDisplayValue("123.123")).toMatchSnapshot(); 32 | }); 33 | 34 | test("TextInputMask renders hybrid component (controlled and uncontrolled). This is not recommended and will throw an error on the console.", () => { 35 | expect(() => 36 | render() 37 | ).toThrow( 38 | "Use either the defaultValue prop, or the value prop, but not both" 39 | ); 40 | }); 41 | 42 | test("TextInputMask renders controlled component with initial value", () => { 43 | let value = "223"; 44 | const onChange = (val) => (value = val); 45 | const result = render( 46 | 47 | ); 48 | expect(result).toMatchSnapshot(); 49 | }); 50 | -------------------------------------------------------------------------------- /__tests__/zip-code.mask.test.ts: -------------------------------------------------------------------------------- 1 | import { ZipCodeMask } from './../src/masks/zip-code.mask'; 2 | 3 | test('getType results zip-code', () => { 4 | var expected = 'zip-code'; 5 | var received = ZipCodeMask.getType(); 6 | 7 | expect(received).toBe(expected); 8 | }); 9 | 10 | test('11111111 results 11111-111', () => { 11 | var mask = new ZipCodeMask(); 12 | var expected = '11111-111'; 13 | var received = mask.getValue('11111111'); 14 | 15 | expect(received).toBe(expected); 16 | }); 17 | 18 | test('1111111 results 11111-11', () => { 19 | var mask = new ZipCodeMask(); 20 | var expected = '11111-11'; 21 | var received = mask.getValue('1111111'); 22 | 23 | expect(received).toBe(expected); 24 | }); 25 | 26 | test('1111111 results 11111-11 and is not valid', () => { 27 | var mask = new ZipCodeMask(); 28 | var expected = '11111-11'; 29 | var received = mask.getValue('1111111'); 30 | var isValid = mask.validate(received); 31 | 32 | expect(isValid).toBe(false); 33 | }); 34 | 35 | test('11111111 results 11111-111 and is not valid', () => { 36 | var mask = new ZipCodeMask(); 37 | var expected = '11111-111'; 38 | var received = mask.getValue('11111111'); 39 | var isValid = mask.validate(received); 40 | 41 | expect(isValid).toBe(true); 42 | }); 43 | 44 | test('11111111 results 11111-111 and raw value 11111111', () => { 45 | var mask = new ZipCodeMask(); 46 | var expected = '11111-111'; 47 | var received = mask.getValue('11111111'); 48 | 49 | var expectedRawValue = '11111111'; 50 | var receivedRawValue = mask.getRawValue(received); 51 | 52 | expect(received).toBe(expected); 53 | expect(receivedRawValue).toBe(expectedRawValue); 54 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import TextInputMask from "./dist/text-input-mask"; 2 | 3 | module.exports.TextInputMask = TextInputMask; 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'jsdom', 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-masked-text", 3 | "version": "1.0.5", 4 | "description": "Text and TextInput with mask for React applications, based on benhurott/react-native-masked-text.", 5 | "licenses": [ 6 | { 7 | "type": "MIT", 8 | "url": "https://www.opensource.org/licenses/mit-license.php" 9 | } 10 | ], 11 | "main": "dist/index.js", 12 | "types": "dist/types/index.d.ts", 13 | "scripts": { 14 | "test": "BABEL_ENV=test jest", 15 | "build": "BABEL_ENV=build rollup -c --bundleConfigAsCjs" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/emiyake/react-masked-text" 20 | }, 21 | "keywords": [ 22 | "mask", 23 | "text", 24 | "textinput", 25 | "react", 26 | "vanilla-masker" 27 | ], 28 | "author": "Edmar Miyake", 29 | "license": "ISC", 30 | "bugs": { 31 | "url": "https://github.com/benhurott/react-masked-text/issues" 32 | }, 33 | "homepage": "https://github.com/benhurott/react-masked-text#readme", 34 | "resolutions": { 35 | "babel-core": "7.0.0-bridge.0" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "7.0.0", 39 | "@babel/plugin-proposal-object-rest-spread": "7.0.0", 40 | "@babel/plugin-transform-destructuring": "7.0.0", 41 | "@babel/plugin-transform-parameters": "7.0.0", 42 | "@babel/preset-env": "7.0.0", 43 | "@babel/preset-react": "7.0.0", 44 | "@rollup/plugin-terser": "0.4.4", 45 | "@testing-library/jest-dom": "6.1.3", 46 | "@testing-library/react": "12.1.2", 47 | "@types/jest": "29.5.4", 48 | "@types/react": "18.2.22", 49 | "babel-core": "^7.0.0-bridge.0", 50 | "babel-jest": "23.6.0", 51 | "jest": "29.7.0", 52 | "jest-environment-jsdom": "29.7.0", 53 | "moment": "2.22.2", 54 | "react": "18.2.0", 55 | "react-dom": "18.2.0", 56 | "rollup": "3.29.2", 57 | "rollup-plugin-babel": "4.4.0", 58 | "rollup-plugin-copy": "3.5.0", 59 | "rollup-plugin-node-resolve": "5.2.0", 60 | "rollup-plugin-ts": "3.4.5", 61 | "ts-jest": "29.1.1", 62 | "typescript": "5.2.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | // rollup.config.mjs 2 | const ts = require('rollup-plugin-ts'); 3 | const babel = require("rollup-plugin-babel"); 4 | const resolve = require("rollup-plugin-node-resolve"); 5 | const copy = require("rollup-plugin-copy"); 6 | const terser = require("@rollup/plugin-terser"); 7 | 8 | let includePathOptions = { 9 | paths: ['src'], 10 | extensions: ['.js', '.ts', '.tsx'] 11 | }; 12 | 13 | 14 | export default { 15 | input: "src/index.ts", 16 | output: { 17 | file: "dist/index.js", 18 | name: 'My Bundle', 19 | format: "umd", 20 | globals: { 21 | react: 'react', 22 | }, 23 | }, 24 | external: ['react'], 25 | plugins: [ 26 | copy({ 'src/masks/internal-dependencies': 'dist/internal-dependencies' }), 27 | resolve({ jsnext: true }), 28 | babel(), 29 | ts(), 30 | terser(), 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from "./masks/base.mask"; 2 | import { celPhoneMask } from "./masks/cel-phone.mask"; 3 | import { cnpjMask } from "./masks/cnpj.mask"; 4 | import { customMask } from "./masks/custom.mask"; 5 | import { cpfMask } from "./masks/cpf.mask"; 6 | import { creditCardMask } from "./masks/credit-card.mask"; 7 | import { datetimeMask } from "./masks/datetime.mask"; 8 | import { moneyMask } from "./masks/money.mask"; 9 | import { onlyNumbersMask } from "./masks/only-numbers.mask"; 10 | import { zipCodeMask } from "./masks/zip-code.mask"; 11 | import TextInputMask from "./text-input-mask"; 12 | 13 | export { 14 | BaseMask, 15 | celPhoneMask, 16 | customMask, 17 | cnpjMask, 18 | cpfMask, 19 | creditCardMask, 20 | datetimeMask, 21 | moneyMask, 22 | onlyNumbersMask, 23 | zipCodeMask, 24 | TextInputMask, 25 | }; 26 | -------------------------------------------------------------------------------- /src/masks/base.mask.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | import VMasker from './internal-dependencies/vanilla-masker'; 3 | 4 | export default class BaseMask { 5 | getVMasker(): typeof VMasker { 6 | return VMasker; 7 | } 8 | 9 | getValue(value: string): string { 10 | return value; 11 | } 12 | 13 | mergeSettings

>(obj1: P, obj2: P): P { 14 | const obj3: Record = {}; 15 | for (const attrname in obj1) { 16 | if (Object.prototype.hasOwnProperty.call(obj1, attrname)) { 17 | obj3[attrname] = obj1[attrname]; 18 | } 19 | } 20 | for (const attrname in obj2) { 21 | if (Object.prototype.hasOwnProperty.call(obj2, attrname)) { 22 | obj3[attrname] = obj2[attrname]; 23 | } 24 | } 25 | return obj3 as P; 26 | } 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 29 | getRawValue(maskedValue: string, settings?: any): any { 30 | return maskedValue; 31 | } 32 | 33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 34 | getDefaultValue(value: any, settings?: any): string { 35 | if (value === undefined || value === null) { 36 | return ''; 37 | } 38 | 39 | return String(value); 40 | } 41 | 42 | removeNotNumbers(text: string): string { 43 | return text.replace(/[^0-9]+/g, ''); 44 | } 45 | 46 | removeWhiteSpaces(text: string): string { 47 | return (text || '').replace(/\s/g, ''); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/masks/cel-phone.mask.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from './base.mask'; 2 | 3 | const PHONE_8_MASK = '9999-9999'; 4 | const PHONE_9_MASK = '99999-9999'; 5 | 6 | interface CellPhoneSettings { 7 | withDDD?: boolean; 8 | dddMask?: string; 9 | } 10 | 11 | const CEL_PHONE_SETTINGS: CellPhoneSettings = { 12 | withDDD: true, 13 | dddMask: '(99) ', 14 | }; 15 | 16 | class CelPhoneMask extends BaseMask { 17 | static getType(): string { 18 | return 'cel-phone'; 19 | } 20 | 21 | getValue(value: string, settings?: CellPhoneSettings): string { 22 | const mask: string = this._getMask(value, settings); 23 | return this.getVMasker().toPattern(value, mask); 24 | } 25 | 26 | getRawValue(maskedValue: string): string { 27 | return super.removeNotNumbers(maskedValue); 28 | } 29 | 30 | validate(value: string, settings?: CellPhoneSettings): boolean { 31 | let valueToValidate: string = super.getDefaultValue(value); 32 | valueToValidate = this.getValue(value, settings); 33 | 34 | const mask: string = this._getMask(value, settings); 35 | 36 | return valueToValidate.length === mask.length; 37 | } 38 | 39 | _getMask(value: string, settings: CellPhoneSettings): string { 40 | const mergedSettings: any = super.mergeSettings(CEL_PHONE_SETTINGS, settings); 41 | 42 | const numbers: string = super.removeNotNumbers(value); 43 | let mask: string = PHONE_8_MASK; 44 | 45 | const use9DigitMask: boolean = (() => { 46 | if (mergedSettings.withDDD) { 47 | const numbersDDD: string = super.removeNotNumbers(mergedSettings.dddMask); 48 | const remainingValueNumbers: string = numbers.substring(numbersDDD.length); 49 | return remainingValueNumbers.length >= 9; 50 | } else { 51 | return numbers.length >= 9; 52 | } 53 | })(); 54 | 55 | if (use9DigitMask) { 56 | mask = PHONE_9_MASK; 57 | } 58 | 59 | if (mergedSettings.withDDD) { 60 | mask = `${mergedSettings.dddMask}${mask}`; 61 | } 62 | 63 | return mask; 64 | } 65 | } 66 | 67 | export const celPhoneMask = () => new CelPhoneMask(); -------------------------------------------------------------------------------- /src/masks/cnpj.mask.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from './base.mask'; 2 | 3 | const CNPJ_MASK = '99.999.999/9999-99'; 4 | 5 | const validateCnpj = (cnpj: string): boolean => { 6 | const valida: number[] = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; 7 | let dig1 = 0; 8 | let dig2 = 0; 9 | 10 | const exp = /\.|\-|\//g; 11 | cnpj = cnpj.toString().replace(exp, ''); 12 | const digito = Number(cnpj.charAt(12) + cnpj.charAt(13)); 13 | 14 | for (let i = 0; i < valida.length; i++) { 15 | dig1 += i > 0 ? Number(cnpj.charAt(i - 1)) * valida[i] : 0; 16 | dig2 += Number(cnpj.charAt(i)) * valida[i]; 17 | } 18 | dig1 = dig1 % 11 < 2 ? 0 : 11 - (dig1 % 11); 19 | dig2 = dig2 % 11 < 2 ? 0 : 11 - (dig2 % 11); 20 | 21 | return dig1 * 10 + dig2 === digito; 22 | }; 23 | 24 | class CnpjMask extends BaseMask { 25 | static getType(): string { 26 | return 'cnpj'; 27 | } 28 | 29 | getValue(value: string): string { 30 | return this.getVMasker().toPattern(value, CNPJ_MASK); 31 | } 32 | 33 | getRawValue(maskedValue: string): string { 34 | return super.removeNotNumbers(maskedValue); 35 | } 36 | 37 | validate(value: string): boolean { 38 | return validateCnpj(value); 39 | } 40 | } 41 | 42 | export const cnpjMask = () => new CnpjMask(); 43 | -------------------------------------------------------------------------------- /src/masks/cpf.mask.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from './base.mask'; 2 | 3 | const CPF_MASK = '999.999.999-99'; 4 | 5 | const validateCPF = (cpf: string): boolean => { 6 | if (cpf == '') { 7 | return true; 8 | } 9 | 10 | cpf = cpf.replace(/\./gi, '').replace(/-/gi, ''); 11 | let isValid = true; 12 | let sum: number; 13 | let rest: number; 14 | let i: number; 15 | i = 0; 16 | sum = 0; 17 | 18 | if ( 19 | cpf.length != 11 || 20 | cpf == '00000000000' || 21 | cpf == '11111111111' || 22 | cpf == '22222222222' || 23 | cpf == '33333333333' || 24 | cpf == '44444444444' || 25 | cpf == '55555555555' || 26 | cpf == '66666666666' || 27 | cpf == '77777777777' || 28 | cpf == '88888888888' || 29 | cpf == '99999999999' 30 | ) { 31 | isValid = false; 32 | } 33 | 34 | for (i = 1; i <= 9; i++) { 35 | sum = sum + parseInt(cpf.substring(i - 1, i)) * (11 - i); 36 | } 37 | 38 | rest = (sum * 10) % 11; 39 | 40 | if (rest == 10 || rest == 11) { 41 | rest = 0; 42 | } 43 | 44 | if (rest != parseInt(cpf.substring(9, 10))) { 45 | isValid = false; 46 | } 47 | 48 | sum = 0; 49 | 50 | for (i = 1; i <= 10; i++) { 51 | sum = sum + parseInt(cpf.substring(i - 1, i)) * (12 - i); 52 | } 53 | 54 | rest = (sum * 10) % 11; 55 | 56 | if (rest == 10 || rest == 11) { 57 | rest = 0; 58 | } 59 | if (rest != parseInt(cpf.substring(10, 11))) { 60 | isValid = false; 61 | } 62 | 63 | return isValid; 64 | }; 65 | 66 | class CpfMask extends BaseMask { 67 | static getType(): string { 68 | return 'cpf'; 69 | } 70 | 71 | getValue(value: string): string { 72 | return this.getVMasker().toPattern(value, CPF_MASK); 73 | } 74 | 75 | getRawValue(maskedValue: string): string { 76 | return super.removeNotNumbers(maskedValue); 77 | } 78 | 79 | validate(value: string): boolean { 80 | return validateCPF(value); 81 | } 82 | } 83 | 84 | export const cpfMask = () => new CpfMask(); 85 | -------------------------------------------------------------------------------- /src/masks/credit-card.mask.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from './base.mask'; 2 | 3 | interface ICreditCardSettings { 4 | obfuscated?: boolean; 5 | } 6 | 7 | const CREDIT_CARD_MASK = '9999 9999 9999 9999'; 8 | const CREDIT_CARD_OBFUSCATED_MASK = '9999 **** **** 9999'; 9 | 10 | const CREDIT_CARD_SETTINGS: ICreditCardSettings = { 11 | obfuscated: false, 12 | }; 13 | 14 | class CreditCardMask extends BaseMask { 15 | static getType(): string { 16 | return 'credit-card'; 17 | } 18 | 19 | getValue(value: string, settings?: ICreditCardSettings): string { 20 | const selectedMask = this._getMask(settings); 21 | return this.getVMasker().toPattern(value, selectedMask); 22 | } 23 | 24 | validate(value: string, settings: ICreditCardSettings): boolean { 25 | if (!!value) { 26 | const selectedMask = this._getMask(settings); 27 | return value.length === selectedMask.length; 28 | } 29 | 30 | return true; 31 | } 32 | 33 | getRawValue(maskedValue: string): string[] { 34 | if (!maskedValue) return []; 35 | 36 | return maskedValue.split(' ').map((val: string) => { 37 | if (!val) return ''; 38 | 39 | return val.trim(); 40 | }); 41 | } 42 | 43 | private _getMask(settings: ICreditCardSettings): string { 44 | const mergedSettings = super.mergeSettings(CREDIT_CARD_SETTINGS, settings); 45 | const selectedMask = mergedSettings.obfuscated ? CREDIT_CARD_OBFUSCATED_MASK : CREDIT_CARD_MASK; 46 | return selectedMask; 47 | } 48 | } 49 | 50 | export const creditCardMask = () => new CreditCardMask(); 51 | -------------------------------------------------------------------------------- /src/masks/custom.mask.ts: -------------------------------------------------------------------------------- 1 | import TinyMask from '../../tiny-mask/tinyMask'; 2 | 3 | import BaseMask from './base.mask'; 4 | 5 | interface Settings { 6 | mask?: string; 7 | translation?: any; 8 | getRawValue?: (maskedValue: any, settings: Settings) => any; 9 | validator?: (value: any, settings: Settings) => boolean; 10 | } 11 | 12 | const DEFAULT_TRANSLATION: { [key: string]: (val: string) => string } = { 13 | '9': function (val: string) { 14 | return val.replace(/[^0-9]+/g, ''); 15 | }, 16 | A: function (val: string) { 17 | return val.replace(/[^a-zA-Z]+/g, ''); 18 | }, 19 | S: function (val: string) { 20 | return val.replace(/[^a-zA-Z0-9]+/g, ''); 21 | }, 22 | '*': function (val: string) { 23 | return val; 24 | }, 25 | }; 26 | 27 | class CustomMask extends BaseMask { 28 | private _mask: string; 29 | constructor(customMask: string = '') { 30 | super(); 31 | this._mask = customMask; 32 | } 33 | 34 | static getType(): string { 35 | return 'custom'; 36 | } 37 | 38 | getValue(value: string, settings?: Settings): string { 39 | if (value === '') { 40 | return value; 41 | } 42 | const translation = this.mergeSettings(DEFAULT_TRANSLATION, settings?.translation); 43 | 44 | const masked = new TinyMask(this._mask, { translation }).mask(value); 45 | return masked; 46 | } 47 | 48 | getRawValue(maskedValue: string, settings: Settings): string { 49 | if (!!settings && settings?.getRawValue) { 50 | return settings?.getRawValue(maskedValue, settings); 51 | } 52 | 53 | return maskedValue; 54 | } 55 | 56 | validate(value: string, settings: Settings): boolean { 57 | if (!!settings && settings?.validator) { 58 | return settings?.validator(value, settings); 59 | } 60 | 61 | return true; 62 | } 63 | } 64 | 65 | export const customMask = (customMask: string) => new CustomMask(customMask); 66 | -------------------------------------------------------------------------------- /src/masks/datetime.mask.ts: -------------------------------------------------------------------------------- 1 | // TypeScript 2 | import BaseMask from './base.mask'; 3 | import { parseStringDate } from './internal-dependencies/date-parser'; 4 | 5 | interface Settings { 6 | format: string; 7 | } 8 | 9 | const DATETIME_MASK_SETTINGS: Settings = { 10 | format: 'DD/MM/YYYY HH:mm:ss', 11 | }; 12 | 13 | class DatetimeMask extends BaseMask { 14 | static getType(): string { 15 | return 'datetime'; 16 | } 17 | 18 | getValue(value: string, settings?: Settings): string { 19 | const mergedSettings = this._getMergedSettings(settings); 20 | let mask = ''; 21 | 22 | for (let i = 0; i < mergedSettings.format.length; i++) { 23 | mask += mergedSettings.format[i].replace(/[a-zA-Z]+/g, '9'); 24 | } 25 | 26 | return this.getVMasker().toPattern(value, mask); 27 | } 28 | 29 | getRawValue(maskedValue: string, settings: Settings): Date { 30 | const mergedSettings = this._getMergedSettings(settings); 31 | return parseStringDate(maskedValue, mergedSettings.format) as unknown as Date; 32 | } 33 | 34 | validate(value: string, settings: Settings): boolean { 35 | const maskedValue = this.getValue(value, settings); 36 | const mergedSettings = this._getMergedSettings(settings); 37 | const date = parseStringDate(maskedValue, mergedSettings.format); 38 | const isValid = this._isValidDate(date); 39 | return isValid; 40 | } 41 | 42 | _getMergedSettings(settings: Settings): Settings { 43 | return super.mergeSettings(DATETIME_MASK_SETTINGS, settings); 44 | } 45 | 46 | /** https://stackoverflow.com/a/1353711/3670829 */ 47 | _isValidDate(d: Date): boolean { 48 | return d instanceof Date && !isNaN(d.getTime()); 49 | } 50 | } 51 | 52 | export const datetimeMask = () => new DatetimeMask(); 53 | -------------------------------------------------------------------------------- /src/masks/internal-dependencies/date-parser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses a datetime (ex: 01/01/1990 17:40:30) given a format (ex: DD/MM/YYYY HH:mm:ss) 3 | * Nowadays, this parsers supports: 4 | * - MM = 01..12 Month number 5 | * - DD = 01..31 Day of month 6 | * - YYYY = 4 or 2 digit year (ex= 2018) 7 | * - YY = 2 digit year (ex= 18) 8 | * - HH = 0..23 Hours (24 hour time) 9 | * - hh = 1..12 Hours (12 hour time used with a A.) 10 | * - kk = 1..24 Hours (24 hour time from 1 to 24) 11 | * - aa = m pm Post or ante meridiem (Note the one character a p are also considered valid) 12 | * - mm = 0..59 Minutes 13 | * - ss = 0..59 Seconds 14 | */ 15 | type DateKeys = 'Y' | 'M' | 'D' | 'H' | 'h' | 'a' | 'm' | 's'; 16 | 17 | type DateComponents = { 18 | [k in DateKeys]: number; 19 | }; 20 | 21 | interface FormatComponents { 22 | [key: string]: string; 23 | } 24 | 25 | export const parseStringDate = (input: string, format: string): Date => { 26 | if (input.length !== format.length) { 27 | return null; 28 | } 29 | 30 | const componentsOfFormatFromInput: FormatComponents = format 31 | .split('') 32 | .reduce((acc: FormatComponents, curr: string, index: number) => { 33 | acc[curr] = `${acc[curr] || ''}${input[index]}`; 34 | return acc; 35 | }, {}); 36 | 37 | const dateComponents: DateComponents = validChars.reduce((acc: DateComponents, curr: string) => { 38 | acc[curr as DateKeys] = strToDateComponent[curr](componentsOfFormatFromInput[curr]); 39 | return acc; 40 | }, {} as DateComponents); 41 | 42 | if (Object.values(dateComponents).some((component) => component === null)) { 43 | return null; 44 | } 45 | 46 | const year = dateComponents['Y']; 47 | const monthIndex = dateComponents['M']; 48 | const day = dateComponents['D']; 49 | const hours = 50 | dateComponents['h'] && dateComponents['a'] 51 | ? dateComponents['h'] + (dateComponents['a'] === AMPM.PM ? 12 : 0) 52 | : dateComponents['H']; 53 | const minutes = dateComponents['m']; 54 | const seconds = dateComponents['s']; 55 | 56 | const date = new Date(year, monthIndex, day, hours, minutes, seconds); 57 | return date; 58 | }; 59 | 60 | const validChars: string[] = ['Y', 'M', 'D', 'H', 'h', 'a', 'm', 's']; 61 | 62 | const AMPM: { [key: string]: number } = { 63 | AM: 0, 64 | PM: 1, 65 | }; 66 | const now: Date = new Date(); 67 | const strToDateComponent: { [key: string]: (str: string) => number | null | undefined } = { 68 | Y: (str: string): number => { 69 | return str ? Number(str) : now.getFullYear(); 70 | }, 71 | M: (str: string): number | null => { 72 | const month = str ? Number(str) - 1 : now.getMonth(); 73 | return month >= 0 && month <= 11 ? month : null; 74 | }, 75 | D: (str: string): number => { 76 | return str ? Number(str) : now.getDate(); 77 | }, 78 | H: (str: string): number | null => { 79 | const hour = str ? Number(str) : now.getHours(); 80 | return hour >= 0 && hour <= 23 ? hour : null; 81 | }, 82 | h: (str: string): number | null => { 83 | const hour = str ? Number(str) : now.getHours() % 12; 84 | return hour >= 0 && hour <= 12 ? hour : null; 85 | }, 86 | a: (str: string): number | undefined => { 87 | if (!str) return undefined; 88 | if (str.toLowerCase() === 'am') { 89 | return AMPM.AM; 90 | } else if (str.toLowerCase() === 'pm') { 91 | return AMPM.PM; 92 | } else { 93 | return null; 94 | } 95 | }, 96 | m: (str: string): number | null => { 97 | const minute = str ? Number(str) : now.getMinutes(); 98 | return minute >= 1 && minute <= 59 ? minute : null; 99 | }, 100 | s: (str: string): number | null => { 101 | const second = str ? Number(str) : now.getSeconds(); 102 | return second >= 1 && second <= 59 ? second : null; 103 | }, 104 | }; 105 | -------------------------------------------------------------------------------- /src/masks/internal-dependencies/vanilla-masker.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | class VanillaMasker { 3 | constructor(elements) { 4 | this.elements = elements; 5 | } 6 | 7 | unbindElementToMask() { 8 | for (let i = 0, len = this.elements.length; i < len; i++) { 9 | this.elements[i].lastOutput = ''; 10 | this.elements[i].onkeyup = false; 11 | this.elements[i].onkeydown = false; 12 | 13 | if (this.elements[i].value.length) { 14 | this.elements[i].value = this.elements[i].value.replace(/\D/g, ''); 15 | } 16 | } 17 | } 18 | 19 | bindElementToMask(maskFunction) { 20 | const that = this; 21 | const onType = function (e) { 22 | const source = e.target || e.srcElement; 23 | 24 | if (isAllowedKeyCode(e.keyCode)) { 25 | setTimeout(function () { 26 | that.opts.lastOutput = source.lastOutput; 27 | source.value = VMasker[maskFunction](source.value, that.opts); 28 | source.lastOutput = source.value; 29 | if (source.setSelectionRange && that.opts.suffixUnit) { 30 | source.setSelectionRange( 31 | source.value.length, 32 | source.value.length - that.opts.suffixUnit.length 33 | ); 34 | } 35 | }, 0); 36 | } 37 | }; 38 | for (let i = 0, len = this.elements.length; i < len; i++) { 39 | this.elements[i].lastOutput = ''; 40 | this.elements[i].onkeyup = onType; 41 | if (this.elements[i].value.length) { 42 | this.elements[i].value = VMasker[maskFunction](this.elements[i].value, this.opts); 43 | } 44 | } 45 | } 46 | 47 | maskMoney(opts) { 48 | this.opts = mergeMoneyOptions(opts); 49 | this.bindElementToMask('toMoney'); 50 | } 51 | 52 | maskNumber() { 53 | this.opts = {}; 54 | this.bindElementToMask('toNumber'); 55 | } 56 | 57 | maskAlphaNum() { 58 | this.opts = {}; 59 | this.bindElementToMask('toAlphaNumeric'); 60 | } 61 | 62 | maskPattern(pattern) { 63 | this.opts = { pattern: pattern }; 64 | this.bindElementToMask('toPattern'); 65 | } 66 | 67 | unMask() { 68 | this.unbindElementToMask(); 69 | } 70 | } 71 | 72 | const VMasker = function (el) { 73 | if (!el) { 74 | throw new Error('VanillaMasker: There is no element to bind.'); 75 | } 76 | const elements = 'length' in el ? (el.length ? el : []) : [el]; 77 | return new VanillaMasker(elements); 78 | }; 79 | 80 | VMasker.toMoney = function (value, opts) { 81 | opts = mergeMoneyOptions(opts); 82 | if (opts.zeroCents) { 83 | opts.lastOutput = opts.lastOutput || ''; 84 | const zeroMatcher = '(' + opts.separator + '[0]{0,' + opts.precision + '})'; 85 | const zeroRegExp = new RegExp(zeroMatcher, 'g'); 86 | const digitsLength = value.toString().replace(/[\D]/g, '').length || 0; 87 | const lastDigitLength = opts.lastOutput.toString().replace(/[\D]/g, '').length || 0; 88 | value = value.toString().replace(zeroRegExp, ''); 89 | if (digitsLength < lastDigitLength) { 90 | value = value.slice(0, value.length - 1); 91 | } 92 | } 93 | const number = value.toString().replace(/[\D]/g, ''); 94 | const clearDelimiter = new RegExp('^(0|\\' + opts.delimiter + ')'); 95 | const clearSeparator = new RegExp('(\\' + opts.separator + ')$'); 96 | let money = number.substr(0, number.length - opts.moneyPrecision); 97 | let masked = money.substr(0, money.length % 3); 98 | let cents = new Array(opts.precision + 1).join('0'); 99 | money = money.substr(money.length % 3, money.length); 100 | for (let i = 0, len = money.length; i < len; i++) { 101 | if (i % 3 === 0) { 102 | masked += opts.delimiter; 103 | } 104 | masked += money[i]; 105 | } 106 | masked = masked.replace(clearDelimiter, ''); 107 | masked = masked.length ? masked : '0'; 108 | if (!opts.zeroCents) { 109 | const beginCents = number.length - opts.precision; 110 | const centsValue = number.substr(beginCents, opts.precision); 111 | const centsLength = centsValue.length; 112 | const centsSliced = opts.precision > centsLength ? opts.precision : centsLength; 113 | cents = (cents + centsValue).slice(-centsSliced); 114 | } 115 | 116 | const unitToApply = 117 | opts.unit[opts.unit.length - 1] === ' ' ? opts.unit.substring(0, opts.unit.length - 1) : opts.unit; 118 | const output = unitToApply + masked + opts.separator + cents + opts.suffixUnit; 119 | return output.replace(clearSeparator, ''); 120 | }; 121 | 122 | VMasker.toPattern = function (value, opts) { 123 | const pattern = typeof opts === 'object' ? opts.pattern : opts; 124 | const patternChars = pattern.replace(/\W/g, ''); 125 | const output = pattern.split(''); 126 | const values = value.toString().replace(/\W/g, ''); 127 | const charsValues = values.replace(/\W/g, ''); 128 | let index = 0; 129 | let i; 130 | const outputLength = output.length; 131 | const placeholder = typeof opts === 'object' ? opts.placeholder : undefined; 132 | for (i = 0; i < outputLength; i++) { 133 | if (index >= values.length) { 134 | if (patternChars.length == charsValues.length) { 135 | return output.join(''); 136 | } else if (placeholder !== undefined && patternChars.length > charsValues.length) { 137 | return addPlaceholdersToOutput(output, i, placeholder).join(''); 138 | } else { 139 | break; 140 | } 141 | } else { 142 | if ( 143 | (output[i] === DIGIT && values[index].match(/[0-9]/)) || 144 | (output[i] === ALPHA && values[index].match(/[a-zA-Z]/)) || 145 | (output[i] === ALPHANUM && values[index].match(/[0-9a-zA-Z]/)) 146 | ) { 147 | output[i] = values[index++]; 148 | } else if (output[i] === DIGIT || output[i] === ALPHA || output[i] === ALPHANUM) { 149 | if (placeholder !== undefined) { 150 | return addPlaceholdersToOutput(output, i, placeholder).join(''); 151 | } else { 152 | return output.slice(0, i).join(''); 153 | } 154 | } 155 | } 156 | } 157 | return output.join('').substr(0, i); 158 | }; 159 | 160 | VMasker.toNumber = function (value) { 161 | return value.toString().replace(/(?!^-)[^0-9]/g, ''); 162 | }; 163 | 164 | VMasker.toAlphaNumeric = function (value) { 165 | return value.toString().replace(/[^a-z0-9 ]+/i, ''); 166 | }; 167 | 168 | function isAllowedKeyCode(keyCode) { 169 | for (let i = 0, len = BY_PASS_KEYS.length; i < len; i++) { 170 | if (keyCode == BY_PASS_KEYS[i]) { 171 | return false; 172 | } 173 | } 174 | return true; 175 | } 176 | 177 | function mergeMoneyOptions(opts) { 178 | opts = opts || {}; 179 | opts = { 180 | precision: opts.hasOwnProperty('precision') ? opts.precision : 2, 181 | separator: opts.separator || ',', 182 | delimiter: opts.delimiter || '.', 183 | unit: opts.unit ? opts.unit + ' ' : '', 184 | suffixUnit: (opts.suffixUnit && ' ' + opts.suffixUnit.replace(/[\s]/g, '')) || '', 185 | zeroCents: opts.zeroCents, 186 | lastOutput: opts.lastOutput, 187 | }; 188 | opts.moneyPrecision = opts.zeroCents ? 0 : opts.precision; 189 | return opts; 190 | } 191 | 192 | function addPlaceholdersToOutput(output, index, placeholder) { 193 | for (; index < output.length; index++) { 194 | if (output[index] === DIGIT || output[index] === ALPHA || output[index] === ALPHANUM) { 195 | output[index] = placeholder; 196 | } 197 | } 198 | return output; 199 | } 200 | 201 | const DIGIT = '9'; 202 | const ALPHA = 'A'; 203 | const ALPHANUM = 'S'; 204 | const BY_PASS_KEYS = [9, 16, 17, 18, 36, 37, 38, 39, 40, 91, 92, 93]; 205 | 206 | export default VMasker; 207 | -------------------------------------------------------------------------------- /src/masks/money.mask.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from './base.mask'; 2 | 3 | interface MoneyMaskSettings { 4 | precision: number; 5 | separator: string; 6 | delimiter: string; 7 | unit: string; 8 | suffixUnit: string; 9 | zeroCents: boolean; 10 | } 11 | 12 | const MONEY_MASK_SETTINGS: MoneyMaskSettings = { 13 | precision: 2, 14 | separator: ',', 15 | delimiter: '.', 16 | unit: 'R$', 17 | suffixUnit: '', 18 | zeroCents: false, 19 | }; 20 | 21 | class MoneyMask extends BaseMask { 22 | static getType(): string { 23 | return 'money'; 24 | } 25 | 26 | getValue(value: string, settings?: Partial, oldValue?: string): string { 27 | const mergedSettings = super.mergeSettings(MONEY_MASK_SETTINGS, settings) as MoneyMaskSettings; 28 | 29 | if (mergedSettings.suffixUnit && oldValue && value) { 30 | if (value.length === oldValue.length - 1) { 31 | const cleared = this.removeNotNumbers(value); 32 | value = cleared.substring(0, cleared.length - 1); 33 | } 34 | } 35 | 36 | const masked = this.getVMasker().toMoney(value, mergedSettings); 37 | return masked; 38 | } 39 | 40 | getRawValue(maskedValue: string, settings?: Partial): number { 41 | const mergedSettings = super.mergeSettings(MONEY_MASK_SETTINGS, settings) as MoneyMaskSettings; 42 | let normalized = super.removeNotNumbers(maskedValue); 43 | 44 | const dotPosition = normalized.length - mergedSettings.precision; 45 | normalized = this._insert(normalized, dotPosition, '.'); 46 | 47 | return Number(normalized); 48 | } 49 | 50 | validate(): boolean { 51 | return true; 52 | } 53 | 54 | private _insert(text: string, index: number, stringToInsert: string): string { 55 | if (index > 0) { 56 | return text.substring(0, index) + stringToInsert + text.substring(index, text.length); 57 | } else { 58 | return stringToInsert + text; 59 | } 60 | } 61 | } 62 | 63 | export const moneyMask = () => new MoneyMask(); 64 | -------------------------------------------------------------------------------- /src/masks/only-numbers.mask.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from './base.mask'; 2 | 3 | class OnlyNumbersMask extends BaseMask { 4 | static getType(): string { 5 | return 'only-numbers'; 6 | } 7 | 8 | getValue(value: string): string { 9 | return this.getVMasker().toNumber(value); 10 | } 11 | 12 | getRawValue(maskedValue: string): string { 13 | return super.removeNotNumbers(maskedValue); 14 | } 15 | 16 | validate(): boolean { 17 | return true; 18 | } 19 | } 20 | 21 | export const onlyNumbersMask = () => new OnlyNumbersMask(); 22 | -------------------------------------------------------------------------------- /src/masks/zip-code.mask.ts: -------------------------------------------------------------------------------- 1 | import BaseMask from './base.mask'; 2 | 3 | const ZIP_CODE_MASK = '99999-999'; 4 | 5 | class ZipCodeMask extends BaseMask { 6 | static getType(): string { 7 | return 'zip-code'; 8 | } 9 | 10 | getValue(value: string): string { 11 | return this.getVMasker().toPattern(value, ZIP_CODE_MASK); 12 | } 13 | 14 | getRawValue(maskedValue: string): string { 15 | return super.removeNotNumbers(maskedValue); 16 | } 17 | 18 | validate(value: string): boolean { 19 | if (!!value) { 20 | return value.length === ZIP_CODE_MASK.length; 21 | } 22 | 23 | return true; 24 | } 25 | } 26 | 27 | export const zipCodeMask = () => new ZipCodeMask(); 28 | -------------------------------------------------------------------------------- /src/text-input-mask.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | ForwardRefRenderFunction, 3 | InputHTMLAttributes, 4 | useEffect, 5 | useRef, 6 | } from "react"; 7 | 8 | import BaseMask from "./masks/base.mask"; 9 | 10 | export interface BaseTextComponentProps 11 | extends InputHTMLAttributes { 12 | mask?: BaseMask; 13 | } 14 | 15 | const BaseTextComponent: ForwardRefRenderFunction< 16 | HTMLInputElement, 17 | BaseTextComponentProps 18 | > = (props, ref) => { 19 | const { defaultValue, value, mask, type, onChange, ...otherProps } = props; 20 | 21 | const maskHandler = mask as any; // Adjust the type according to MaskResolver 22 | 23 | const inputRef = useRef(); 24 | 25 | const isControlled = React.useCallback((): boolean => { 26 | return value !== undefined; 27 | }, [value]); 28 | 29 | useEffect(() => { 30 | if (defaultValue !== undefined && value !== undefined) { 31 | throw new Error( 32 | "Use either the defaultValue prop, or the value prop, but not both" 33 | ); 34 | } 35 | 36 | let masked = maskHandler?.getValue(defaultValue || ""); 37 | 38 | if (isControlled()) { 39 | masked = maskHandler?.getValue(value || "") || value; 40 | } 41 | 42 | inputRef.current.value = masked; 43 | }, [mask, defaultValue, value, isControlled]); 44 | 45 | const handleChangeText = async (text: string) => { 46 | const maskedText = mask?.getValue(text || "") || text; 47 | onChange?.(maskedText as any); 48 | 49 | if (!isControlled()) { 50 | inputRef.current.value = maskedText; 51 | } 52 | }; 53 | return ( 54 | { 56 | inputRef.current = elementRef; 57 | if (typeof ref === "function") { 58 | ref(elementRef); 59 | } else if (!!ref) { 60 | ref.current = elementRef; 61 | } 62 | }} 63 | type={type ?? "text"} 64 | {...otherProps} 65 | onChange={(event) => handleChangeText(event.currentTarget.value)} 66 | /> 67 | ); 68 | }; 69 | 70 | export default React.forwardRef(BaseTextComponent); 71 | -------------------------------------------------------------------------------- /tiny-mask/tinyMask.ts: -------------------------------------------------------------------------------- 1 | type TranslationOptions = { 2 | [key: string]: (val: string) => string; 3 | }; 4 | 5 | interface TinyMaskOptions { 6 | translation?: TranslationOptions; 7 | invalidValues?: any[]; 8 | } 9 | 10 | class TinyMask { 11 | private _options: { 12 | translation: TranslationOptions; 13 | invalidValues: any[]; 14 | pattern: string; 15 | }; 16 | private _handlers: (string | ((val: string) => string))[]; 17 | 18 | constructor(pattern: string, options?: TinyMaskOptions) { 19 | const defaultOptions = { 20 | translation: { 21 | '9': (val: string) => val.replace(/[^0-9]+/g, ''), 22 | 'A': (val: string) => val.replace(/[^a-zA-Z]+/g, ''), 23 | 'S': (val: string) => val.replace(/[^a-zA-Z0-9]+/g, ''), 24 | '*': (val: string) => val 25 | }, 26 | invalidValues: [null, undefined, ''] 27 | }; 28 | 29 | const opt = options || {}; 30 | this._options = { 31 | translation: { ...defaultOptions.translation, ...opt.translation }, 32 | invalidValues: [...defaultOptions.invalidValues, ...opt.invalidValues || []], 33 | pattern: pattern 34 | }; 35 | 36 | this._handlers = []; 37 | 38 | for (let i = 0; i < pattern.length; i++) { 39 | const element = pattern[i]; 40 | const result = this._options.translation[element] || element; 41 | this._handlers.push(result); 42 | } 43 | } 44 | 45 | private _isString(value: any): value is string { 46 | return typeof value === "string"; 47 | } 48 | 49 | public mask(value: any): string { 50 | let result = ''; 51 | const val = String(value); 52 | 53 | 54 | if (val.length === 0) return ''; 55 | 56 | let maskSize = this._handlers.length; 57 | let maskResolved = 0; 58 | 59 | let valueSize = val.length; 60 | let valueResolved = 0; 61 | 62 | while (maskResolved < maskSize) { 63 | const hand = this._handlers[maskResolved]; 64 | const char = val[valueResolved]; 65 | 66 | if (char === undefined) { 67 | break; 68 | } 69 | 70 | if (char === hand) { 71 | result += char; 72 | maskResolved++; 73 | valueResolved++ 74 | continue; 75 | } 76 | 77 | if (this._isString(hand)) { 78 | result += hand; 79 | maskResolved++; 80 | continue; 81 | } 82 | 83 | const parsed = hand(char); 84 | 85 | 86 | if (this._options.invalidValues.indexOf(parsed) < 0) { 87 | result += parsed; 88 | valueResolved++; 89 | } 90 | else { 91 | break; 92 | } 93 | 94 | maskResolved++; 95 | } 96 | 97 | return result; 98 | } 99 | } 100 | 101 | export default TinyMask; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "module": "ESNext", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "lib": ["dom", "dom.iterable", "ES2020"], 9 | "target": "ES5", 10 | "declaration": true, 11 | "sourceMap": true, 12 | "moduleResolution": "Node", 13 | "allowSyntheticDefaultImports": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "skipLibCheck": true, 16 | "declarationDir": "dist/types" 17 | }, 18 | "include": ["src"], 19 | "exclude": ["node_modules"] 20 | } 21 | --------------------------------------------------------------------------------