react-input-mask-format 1.0.5 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,52 +1,76 @@
1
1
  # react-input-mask-format
2
2
 
3
- [![Build Status](https://img.shields.io/travis/temirtator/react-input-mask-format/master.svg?style=flat)](https://travis-ci.org/temirtator/react-input-mask-format) [![npm version](https://img.shields.io/npm/v/react-input-mask-format.svg?style=flat)](https://www.npmjs.com/package/react-input-mask-format) [![npm downloads](https://img.shields.io/npm/dm/react-input-mask-format.svg?style=flat)](https://www.npmjs.com/package/react-input-mask-format)
3
+ [![npm version](https://img.shields.io/npm/v/react-input-mask-format.svg)](https://www.npmjs.com/package/react-input-mask-format)
4
+ [![npm downloads](https://img.shields.io/npm/dm/react-input-mask-format.svg)](https://www.npmjs.com/package/react-input-mask-format)
5
+ [![CI](https://github.com/Temirtator/react-input-mask-format/actions/workflows/ci.yml/badge.svg)](https://github.com/Temirtator/react-input-mask-format/actions/workflows/ci.yml)
4
6
 
5
- Input masking component for React. Made with attention to UX.
7
+ General-purpose input masking for React — dates, phone numbers, cards, custom
8
+ tokens, and a growing set of ready-made country packs (Kazakhstan ships first).
9
+ Made with attention to UX.
6
10
 
7
- This project is fork from basic library react-input-mask by sanniassin
11
+ A maintained fork of [react-input-mask](https://github.com/sanniassin/react-input-mask).
8
12
 
9
- #### [Demo](http://sanniassin.github.io/react-input-mask/demo.html)
13
+ ## What's new in 2.1
10
14
 
11
- # Table of Contents
12
- * [Installation](#installation)
13
- * [Usage](#usage)
14
- * [Properties](#properties)
15
- * [Known Issues](#known-issues)
15
+ - `transform` prop (`uppercase` / `lowercase` / custom) — see [transform](#transform)
16
+ - Custom tokens via RegExp `formatChars` + shipped `extendedFormatChars` (`A`, `Я`, `#`)
17
+ - `react-input-mask-format/presets` — `card` + Kazakhstan pack (phone, IIN, BIN, IBAN, plate, postal)
18
+ - `react-input-mask-format/validators` — `isValidIin`, `isValidBin`, `isValidKzIban`, `luhn`
16
19
 
17
- # Installation
18
- ```npm install react-input-mask-format@next --save```
20
+ All additive — no migration needed.
19
21
 
20
- react-input-mask-format requires **React 16.8.0 or later.
22
+ ## Why this fork
23
+
24
+ The original `react-input-mask` has been unmaintained since 2021. This fork is:
25
+
26
+ - **React 16.8 – 19 compatible** — no `defaultProps`, no `findDOMNode`, StrictMode-safe
27
+ - **TypeScript-first** — types generated from source
28
+ - **Modern packaging** — ESM + CJS, `exports` map, tree-shakable
29
+ - **Zero runtime dependencies**
30
+ - **Custom `children` support restored** — passing a component as children
31
+ (e.g. to reuse another UI library's input) was broken in every release
32
+ since v1.0.x; it works again here
33
+ - **Drop-in compatible** with both `react-input-mask` v2 (`maskChar`, `formatChars`,
34
+ `beforeMaskedValueChange`) and v3-alpha (`maskPlaceholder`, `beforeMaskedStateChange`) APIs
35
+
36
+ ## Installation
37
+
38
+ ```
39
+ npm install react-input-mask-format
40
+ ```
41
+
42
+ Requires React 16.8.0 or later.
43
+
44
+ ## Usage
21
45
 
22
- # Usage
23
46
  ```jsx
24
- import React from "react"
25
- import InputMask from "react-input-mask";
47
+ import InputMask from "react-input-mask-format";
26
48
 
27
49
  function DateInput(props) {
28
50
  return <InputMask mask="99/99/9999" onChange={props.onChange} value={props.value} />;
29
51
  }
30
52
  ```
31
53
 
32
- # Properties
33
- | Name | Type | Default | Description |
34
- | :-----------------------------------------: | :-------------------------: | :-----: | :--------------------------------------------------------------------- |
35
- | **[`mask`](#mask)** | `{String\|Array<String, RegExp>}` | | Mask format |
36
- | **[`maskPlaceholder`](#maskplaceholder)** | `{String}` | `_` | Placeholder to cover unfilled parts of the mask |
37
- | **[`alwaysShowMask`](#alwaysshowmask)** | `{Boolean}` | `false` | Whether mask prefix and placeholder should be displayed when input is empty and has no focus |
38
- | **[`beforeMaskedStateChange`](#beforemaskedstatechange)** | `{Function}` | | Function to modify value and selection before applying mask |
39
- | **[`children`](#children)** | `{ReactElement}` | | Custom render function for integration with other input components |
54
+ ## Properties
40
55
 
56
+ | Name | Type | Default | Description |
57
+ | :---------------------------------------------------------: | :--------------------------------: | :-----: | :--- |
58
+ | **[`mask`](#mask)** | `{String\|Array<String, RegExp>}` | | Mask format |
59
+ | **[`transform`](#transform)** | `{"uppercase"\|"lowercase"\|Function}` | | Normalize each entered character before it's tested against the mask |
60
+ | **[`formatChars`](#custom-tokens-formatchars-and-extendedformatchars)** | `{Object<String, RegExp>}` | | Custom mask tokens beyond the default `9`, `a`, `*` |
61
+ | **[`maskPlaceholder`](#maskplaceholder)** | `{String}` | `_` | Placeholder to cover unfilled parts of the mask |
62
+ | **[`alwaysShowMask`](#alwaysshowmask)** | `{Boolean}` | `false` | Whether mask prefix and placeholder should be displayed when input is empty and has no focus |
63
+ | **[`beforeMaskedStateChange`](#beforemaskedstatechange)** | `{Function}` | | Function to modify value and selection before applying mask |
64
+ | **[`children`](#children)** | `{ReactElement}` | | Custom render function for integration with other input components |
41
65
 
42
66
  ### `mask`
43
67
 
44
- Mask format. Can be either a string or array of characters and regular expressions.<br /><br />
45
-
68
+ Mask format. Can be either a string or array of characters and regular expressions.
46
69
 
47
70
  ```jsx
48
71
  <InputMask mask="99/99/99" />
49
72
  ```
73
+
50
74
  Simple masks can be defined as strings. The following characters will define mask format:
51
75
 
52
76
  | Character | Allowed input |
@@ -55,10 +79,10 @@ Simple masks can be defined as strings. The following characters will define mas
55
79
  | a | a-z, A-Z |
56
80
  | * | 0-9, a-z, A-Z |
57
81
 
58
- Any format character can be escaped with a backslash.<br /><br />
59
-
82
+ Any format character can be escaped with a backslash.
60
83
 
61
84
  More complex masks can be defined as an array of regular expressions and constant characters.
85
+
62
86
  ```jsx
63
87
  // Canadian postal code mask
64
88
  const firstLetter = /(?!.*[DFIOQU])[A-VXY]/i;
@@ -68,8 +92,77 @@ const mask = [firstLetter, digit, letter, " ", digit, letter, digit];
68
92
  return <InputMask mask={mask} />;
69
93
  ```
70
94
 
95
+ ### `transform`
96
+
97
+ Normalize every entered character. Runs before the mask test, so an uppercase-only
98
+ class accepts lowercase typing; runs before `beforeMaskedStateChange`.
99
+
100
+ ```jsx
101
+ <InputMask mask="aaaaaa" transform="uppercase" /> // "abc" → "ABC"
102
+ <InputMask mask={[/[A-Z]/, /[A-Z]/]} transform="uppercase" />
103
+ <InputMask mask="9a9a" transform={(char, position) => char} />
104
+ ```
105
+
106
+ Values: `"uppercase"`, `"lowercase"`, or `(char, position) => char` (must be pure).
107
+
108
+ ### Custom tokens (`formatChars`) and `extendedFormatChars`
109
+
110
+ Define your own mask tokens by mapping a character to a `RegExp`:
111
+
112
+ ```jsx
113
+ import InputMask, { extendedFormatChars } from "react-input-mask-format";
114
+
115
+ // roll your own
116
+ <InputMask mask="ww-ww" formatChars={{ w: /[a-z]/ }} />
117
+
118
+ // or use the shipped extended set: A (uppercase), Я (Cyrillic incl. Kazakh), # (hex)
119
+ <InputMask mask="AAA-###" formatChars={extendedFormatChars} />
120
+ ```
121
+
122
+ The default tokens (`9`, `a`, `*`) are unchanged. Passing string values (`{ w: "[a-z]" }`)
123
+ still works but is deprecated — pass `RegExp` values.
124
+
125
+ ## Presets
126
+
127
+ Ready-made mask configs. Import what you need and spread it — tree-shakeable, and the
128
+ core bundle carries none of it.
129
+
130
+ ```jsx
131
+ import InputMask from "react-input-mask-format";
132
+ import { kzPhone, kzIban } from "react-input-mask-format/presets";
133
+
134
+ <InputMask {...kzPhone} value={phone} onChange={onChange} />
135
+ <InputMask {...kzIban} value={iban} onChange={onChange} />
136
+ ```
137
+
138
+ Presets are a country-agnostic system; Kazakhstan is the first (flagship) country pack —
139
+ PRs adding other countries are welcome.
140
+
141
+ | export | mask | example |
142
+ | --- | --- | --- |
143
+ | `card` | `9999 9999 9999 9999` | `4242 4242 4242 4242` |
144
+ | `kzPhone` | `+7 (799) 999-99-99` | `+7 (701) 234-56-78` |
145
+ | `kzIin` | `999999999999` | `901010123458` |
146
+ | `kzBin` | `999999999999` | `150340004984` |
147
+ | `kzIban` | `KZ99 999* **** **** ****` (uppercase) | `KZ86 125K ZT50 0410 0100` |
148
+ | `kzPlate` | `123 ABC 02` (letters `ABCEHKMNOPTXY`, uppercase) | `123 ABC 02` |
149
+ | `kzPostal` | `999999` | `050000` |
150
+
151
+ ## Validators
152
+
153
+ Optional checksum validators, separate entry point, zero-deps. Accept raw or formatted
154
+ input; return `false` on empty/invalid.
155
+
156
+ ```jsx
157
+ import { isValidIin, isValidBin, isValidKzIban, luhn } from "react-input-mask-format/validators";
158
+
159
+ isValidIin("901010123458"); // KZ IIN/BIN mod-11 checksum
160
+ isValidKzIban("KZ86 125K ZT50 0410 0100"); // ISO 7064 MOD-97
161
+ luhn("4242 4242 4242 4242"); // card Luhn
162
+ ```
71
163
 
72
164
  ### `maskPlaceholder`
165
+
73
166
  ```jsx
74
167
  // Will be rendered as 12/--/--
75
168
  <InputMask mask="99/99/99" maskPlaceholder="-" value="12" />
@@ -80,18 +173,19 @@ return <InputMask mask={mask} />;
80
173
  // Will be rendered as 12/
81
174
  <InputMask mask="99/99/99" maskPlaceholder={null} value="12" />
82
175
  ```
83
- Character or string to cover unfilled parts of the mask. Default character is "\_". If set to `null` or empty string, unfilled parts will be empty as in a regular input.
84
176
 
177
+ Character or string to cover unfilled parts of the mask. Default character is "\_". If set to `null` or empty string, unfilled parts will be empty as in a regular input.
85
178
 
86
179
  ### `alwaysShowMask`
87
180
 
88
181
  If enabled, mask prefix and placeholder will be displayed even when input is empty and has no focus.
89
182
 
90
-
91
183
  ### `beforeMaskedStateChange`
184
+
92
185
  In case you need to customize masking behavior, you can provide `beforeMaskedStateChange` function to change masked value and cursor position before it's applied to the input.
93
186
 
94
- It receieves an object with `previousState`, `currentState` and `nextState` properties. Each state is an object with `value` and `selection` properites where `value` is a string and selection is an object containing `start` and `end` positions of the selection.
187
+ It receives an object with `previousState`, `currentState` and `nextState` properties. Each state is an object with `value` and `selection` properties where `value` is a string and `selection` is an object containing `start` and `end` positions of the selection.
188
+
95
189
  1. **previousState:** Input state before change. Only defined on `change` event.
96
190
  2. **currentState:** Current raw input state. Not defined during component render.
97
191
  3. **nextState:** Input state with applied mask. Contains `value` and `selection` fields.
@@ -119,19 +213,25 @@ return <InputMask mask="99/99/99" maskPlaceholder={null} beforeMaskedStateChange
119
213
 
120
214
  Please note that `beforeMaskedStateChange` executes more often than `onChange` and must be pure.
121
215
 
122
-
123
216
  ### `children`
124
- To use another component instead of regular `<input />` provide it as children. The following properties, if used, should always be defined on the `InputMask` component itself: `onChange`, `onMouseDown`, `onFocus`, `onBlur`, `value`, `disabled`, `readOnly`.
217
+
218
+ To use another component instead of a regular `<input />`, provide it as children. The following properties, if used, should always be defined on the `InputMask` component itself: `onChange`, `onMouseDown`, `onFocus`, `onBlur`, `value`, `disabled`, `readOnly`.
219
+
220
+ The child component must forward its ref to the underlying `<input>` DOM node (or to a wrapper element that contains one, e.g. a Material-style input with an internal `<input>`) so `InputMask` can read and control its value and selection.
221
+
125
222
  ```jsx
126
- import React from 'react';
127
- import InputMask from 'react-input-mask';
128
- import MaterialInput from '@material-ui/core/Input';
223
+ import React from "react";
224
+ import InputMask from "react-input-mask-format";
225
+
226
+ const CustomInput = React.forwardRef((props, ref) => (
227
+ <input ref={ref} {...props} style={{ borderColor: "rebeccapurple" }} />
228
+ ));
129
229
 
130
230
  // Will work fine
131
231
  function Input(props) {
132
232
  return (
133
233
  <InputMask mask="99/99/9999" value={props.value} onChange={props.onChange}>
134
- <MaterialInput type="tel" disableUnderline />
234
+ <CustomInput />
135
235
  </InputMask>
136
236
  );
137
237
  }
@@ -140,33 +240,46 @@ function Input(props) {
140
240
  function InvalidInput(props) {
141
241
  return (
142
242
  <InputMask mask="99/99/9999" value={props.value}>
143
- <MaterialInput type="tel" disableUnderline onChange={props.onChange} />
243
+ <CustomInput onChange={props.onChange} />
144
244
  </InputMask>
145
245
  );
146
246
  }
147
247
  ```
148
248
 
149
- # Known Issues
249
+ > **Note:** `InputMask` clones the child element and injects its own `ref`
250
+ > callback into it, so a `ref` placed directly on the child element will be
251
+ > replaced. Attach your ref to `<InputMask>` itself instead. The ref you attach
252
+ > to `<InputMask>` receives whatever node the child component forwards its ref to;
253
+ > if the child forwards to a wrapper element rather than the actual `<input>`,
254
+ > you get that wrapper (the library still finds the inner input internally for masking).
255
+
256
+ ## Known Issues
257
+
150
258
  ### Autofill
259
+
151
260
  Browser's autofill requires either empty value in input or value which exactly matches beginning of the autofilled value. I.e. autofilled value "+1 (555) 123-4567" will work with "+1" or "+1 (5", but won't work with "+1 (\_\_\_) \_\_\_-\_\_\_\_" or "1 (555)". There are several possible solutions:
152
- 1. Set `maskChar` to null and trim space after "+1" with `beforeMaskedStateChange` if no more digits are entered.
261
+
262
+ 1. Set `maskPlaceholder` to null and trim space after "+1" with `beforeMaskedStateChange` if no more digits are entered.
153
263
  2. Apply mask only if value is not empty. In general, this is the most reliable solution because we can't be sure about formatting in autofilled value.
154
264
  3. Use less formatting in the mask.
155
265
 
156
266
  Please note that it might lead to worse user experience (should I enter +1 if input is empty?). You should choose what's more important to your users — smooth typing experience or autofill. Phone and ZIP code inputs are very likely to be autofilled and it's a good idea to care about it, while security confirmation code in two-factor authorization shouldn't care about autofill at all.
157
267
 
158
268
  ### Cypress tests
269
+
159
270
  The following sequence could fail
271
+
160
272
  ```js
161
273
  cy.get("input")
162
274
  .focus()
163
275
  .type("12345")
164
276
  .should("have.value", "12/34/5___"); // expected <input> to have value 12/34/5___, but the value was 23/45/____
165
- ````
277
+ ```
166
278
 
167
279
  Since [focus is not an action command](https://docs.cypress.io/api/commands/focus.html#Focus-is-not-an-action-command), it behaves differently than the real user interaction and, therefore, less reliable.
168
280
 
169
281
  There is a few possible workarounds
282
+
170
283
  ```js
171
284
  // Start typing without calling focus() explicitly.
172
285
  // type() is an action command and focuses input anyway
@@ -186,7 +299,26 @@ cy.get("input")
186
299
  .wait(50)
187
300
  .type("12345")
188
301
  .should("have.value", "12/34/5___");
189
- ````
302
+ ```
303
+
304
+ ## Migrating from react-input-mask
305
+
306
+ ### From v2 (2.0.4 and earlier)
307
+
308
+ Your code keeps working as is — `maskChar`, `formatChars` and
309
+ `beforeMaskedValueChange` are supported as deprecated aliases (a one-time
310
+ console warning is emitted in development). Recommended renames:
311
+
312
+ | v2 | v2.x of this package |
313
+ | --- | --- |
314
+ | `maskChar="-"` | `maskPlaceholder="-"` |
315
+ | `maskChar={null}` | `maskPlaceholder={null}` |
316
+ | `formatChars={{ "#": "[0-9]" }}` | array mask: `mask={[/[0-9]/, …]}` (or keep `formatChars`) |
317
+ | `beforeMaskedValueChange={(newState, oldState, userInput, options) => …}` | `beforeMaskedStateChange={({ previousState, currentState, nextState }) => …}` |
318
+ | `inputRef={el => …}` | `ref` (standard forwarded ref) |
319
+ | `alwaysShowMask` | unchanged |
320
+ | custom `children` (e.g. wrapping another input library) | works if the child forwards its ref to the input (or a wrapper containing one) — see [children](#children); upstream v2 accepted any child via findDOMNode |
321
+
322
+ ### From v3-alpha
190
323
 
191
- # Thanks
192
- Thanks to [BrowserStack](https://www.browserstack.com/) for the help with testing on real devices
324
+ API is identical — change the import to `react-input-mask-format` and you are done.