react-input-mask-format 2.0.0 → 2.2.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 +164 -1
- package/dist/chunk-CEZXZ7IJ.js +42 -0
- package/dist/chunk-CEZXZ7IJ.js.map +1 -0
- package/dist/index.cjs +276 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -51
- package/dist/index.d.ts +7 -51
- package/dist/index.js +257 -33
- package/dist/index.js.map +1 -1
- package/dist/number.cjs +342 -0
- package/dist/number.cjs.map +1 -0
- package/dist/number.d.cts +46 -0
- package/dist/number.d.ts +46 -0
- package/dist/number.js +280 -0
- package/dist/number.js.map +1 -0
- package/dist/presets.cjs +86 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +18 -0
- package/dist/presets.d.ts +18 -0
- package/dist/presets.js +55 -0
- package/dist/presets.js.map +1 -0
- package/dist/types-BTNqtchO.d.cts +77 -0
- package/dist/types-BTNqtchO.d.ts +77 -0
- package/dist/validators.cjs +99 -0
- package/dist/validators.cjs.map +1 -0
- package/dist/validators.d.cts +6 -0
- package/dist/validators.d.ts +6 -0
- package/dist/validators.js +71 -0
- package/dist/validators.js.map +1 -0
- package/package.json +17 -2
package/README.md
CHANGED
|
@@ -4,10 +4,26 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/react-input-mask-format)
|
|
5
5
|
[](https://github.com/Temirtator/react-input-mask-format/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
-
|
|
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.
|
|
8
10
|
|
|
9
11
|
A maintained fork of [react-input-mask](https://github.com/sanniassin/react-input-mask).
|
|
10
12
|
|
|
13
|
+
## What's new in 2.2
|
|
14
|
+
|
|
15
|
+
- `useMask` hook — mask your own `<input>` with no wrapper component, see [useMask hook](#usemask-hook)
|
|
16
|
+
- `react-input-mask-format/number` — numeric & currency masking, a lightweight `react-number-format` alternative, see [Numeric & currency](#numeric--currency--react-input-mask-formatnumber)
|
|
17
|
+
|
|
18
|
+
### 2.1
|
|
19
|
+
|
|
20
|
+
- `transform` prop (`uppercase` / `lowercase` / custom) — see [transform](#transform)
|
|
21
|
+
- Custom tokens via RegExp `formatChars` + shipped `extendedFormatChars` (`A`, `Я`, `#`)
|
|
22
|
+
- `react-input-mask-format/presets` — `card` + Kazakhstan pack (phone, IIN, BIN, IBAN, plate, postal)
|
|
23
|
+
- `react-input-mask-format/validators` — `isValidIin`, `isValidBin`, `isValidKzIban`, `luhn`
|
|
24
|
+
|
|
25
|
+
All additive — no migration needed.
|
|
26
|
+
|
|
11
27
|
## Why this fork
|
|
12
28
|
|
|
13
29
|
The original `react-input-mask` has been unmaintained since 2021. This fork is:
|
|
@@ -45,6 +61,8 @@ function DateInput(props) {
|
|
|
45
61
|
| Name | Type | Default | Description |
|
|
46
62
|
| :---------------------------------------------------------: | :--------------------------------: | :-----: | :--- |
|
|
47
63
|
| **[`mask`](#mask)** | `{String\|Array<String, RegExp>}` | | Mask format |
|
|
64
|
+
| **[`transform`](#transform)** | `{"uppercase"\|"lowercase"\|Function}` | | Normalize each entered character before it's tested against the mask |
|
|
65
|
+
| **[`formatChars`](#custom-tokens-formatchars-and-extendedformatchars)** | `{Object<String, RegExp>}` | | Custom mask tokens beyond the default `9`, `a`, `*` |
|
|
48
66
|
| **[`maskPlaceholder`](#maskplaceholder)** | `{String}` | `_` | Placeholder to cover unfilled parts of the mask |
|
|
49
67
|
| **[`alwaysShowMask`](#alwaysshowmask)** | `{Boolean}` | `false` | Whether mask prefix and placeholder should be displayed when input is empty and has no focus |
|
|
50
68
|
| **[`beforeMaskedStateChange`](#beforemaskedstatechange)** | `{Function}` | | Function to modify value and selection before applying mask |
|
|
@@ -79,6 +97,151 @@ const mask = [firstLetter, digit, letter, " ", digit, letter, digit];
|
|
|
79
97
|
return <InputMask mask={mask} />;
|
|
80
98
|
```
|
|
81
99
|
|
|
100
|
+
### `transform`
|
|
101
|
+
|
|
102
|
+
Normalize every entered character. Runs before the mask test, so an uppercase-only
|
|
103
|
+
class accepts lowercase typing; runs before `beforeMaskedStateChange`.
|
|
104
|
+
|
|
105
|
+
```jsx
|
|
106
|
+
<InputMask mask="aaaaaa" transform="uppercase" /> // "abc" → "ABC"
|
|
107
|
+
<InputMask mask={[/[A-Z]/, /[A-Z]/]} transform="uppercase" />
|
|
108
|
+
<InputMask mask="9a9a" transform={(char, position) => char} />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Values: `"uppercase"`, `"lowercase"`, or `(char, position) => char` (must be pure).
|
|
112
|
+
|
|
113
|
+
### Custom tokens (`formatChars`) and `extendedFormatChars`
|
|
114
|
+
|
|
115
|
+
Define your own mask tokens by mapping a character to a `RegExp`:
|
|
116
|
+
|
|
117
|
+
```jsx
|
|
118
|
+
import InputMask, { extendedFormatChars } from "react-input-mask-format";
|
|
119
|
+
|
|
120
|
+
// roll your own
|
|
121
|
+
<InputMask mask="ww-ww" formatChars={{ w: /[a-z]/ }} />
|
|
122
|
+
|
|
123
|
+
// or use the shipped extended set: A (uppercase), Я (Cyrillic incl. Kazakh), # (hex)
|
|
124
|
+
<InputMask mask="AAA-###" formatChars={extendedFormatChars} />
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The default tokens (`9`, `a`, `*`) are unchanged. Passing string values (`{ w: "[a-z]" }`)
|
|
128
|
+
still works but is deprecated — pass `RegExp` values.
|
|
129
|
+
|
|
130
|
+
## Presets
|
|
131
|
+
|
|
132
|
+
Ready-made mask configs. Import what you need and spread it — tree-shakeable, and the
|
|
133
|
+
core bundle carries none of it.
|
|
134
|
+
|
|
135
|
+
```jsx
|
|
136
|
+
import InputMask from "react-input-mask-format";
|
|
137
|
+
import { kzPhone, kzIban } from "react-input-mask-format/presets";
|
|
138
|
+
|
|
139
|
+
<InputMask {...kzPhone} value={phone} onChange={onChange} />
|
|
140
|
+
<InputMask {...kzIban} value={iban} onChange={onChange} />
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Presets are a country-agnostic system; Kazakhstan is the first (flagship) country pack —
|
|
144
|
+
PRs adding other countries are welcome.
|
|
145
|
+
|
|
146
|
+
| export | mask | example |
|
|
147
|
+
| --- | --- | --- |
|
|
148
|
+
| `card` | `9999 9999 9999 9999` | `4242 4242 4242 4242` |
|
|
149
|
+
| `kzPhone` | `+7 (799) 999-99-99` | `+7 (701) 234-56-78` |
|
|
150
|
+
| `kzIin` | `999999999999` | `901010123458` |
|
|
151
|
+
| `kzBin` | `999999999999` | `150340004984` |
|
|
152
|
+
| `kzIban` | `KZ99 999* **** **** ****` (uppercase) | `KZ86 125K ZT50 0410 0100` |
|
|
153
|
+
| `kzPlate` | `123 ABC 02` (letters `ABCEHKMNOPTXY`, uppercase) | `123 ABC 02` |
|
|
154
|
+
| `kzPostal` | `999999` | `050000` |
|
|
155
|
+
|
|
156
|
+
## Validators
|
|
157
|
+
|
|
158
|
+
Optional checksum validators, separate entry point, zero-deps. Accept raw or formatted
|
|
159
|
+
input; return `false` on empty/invalid.
|
|
160
|
+
|
|
161
|
+
```jsx
|
|
162
|
+
import { isValidIin, isValidBin, isValidKzIban, luhn } from "react-input-mask-format/validators";
|
|
163
|
+
|
|
164
|
+
isValidIin("901010123458"); // KZ IIN/BIN mod-11 checksum
|
|
165
|
+
isValidKzIban("KZ86 125K ZT50 0410 0100"); // ISO 7064 MOD-97
|
|
166
|
+
luhn("4242 4242 4242 4242"); // card Luhn
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## `useMask` hook
|
|
170
|
+
|
|
171
|
+
Mask your own `<input>` with no wrapper component. `useMask` returns a ref
|
|
172
|
+
callback:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { useMask } from "react-input-mask-format";
|
|
176
|
+
|
|
177
|
+
function Phone() {
|
|
178
|
+
const ref = useMask({ mask: "+7 (999) 999-99-99", maskPlaceholder: "_" });
|
|
179
|
+
return <input ref={ref} name="phone" />;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`useMask` is **uncontrolled**: attach it to an input you don't drive with a
|
|
184
|
+
React `value` prop, and read the masked value through your own `onChange` (it
|
|
185
|
+
fires with the masked value) or on form submit. For a controlled component,
|
|
186
|
+
use `<InputMask>`.
|
|
187
|
+
|
|
188
|
+
Options: `mask`, `maskPlaceholder`, `alwaysShowMask`, `formatChars`,
|
|
189
|
+
`transform`, `beforeMaskedStateChange`.
|
|
190
|
+
|
|
191
|
+
## Numeric & currency — `react-input-mask-format/number`
|
|
192
|
+
|
|
193
|
+
A separate, tree-shakeable entry for formatting numbers, currency, and
|
|
194
|
+
percentages. Prop names match `react-number-format`, so migration is mostly a
|
|
195
|
+
find-and-replace of the import.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
import { NumberFormat } from "react-input-mask-format/number";
|
|
199
|
+
|
|
200
|
+
function Amount() {
|
|
201
|
+
const [value, setValue] = React.useState<number>();
|
|
202
|
+
return (
|
|
203
|
+
<NumberFormat
|
|
204
|
+
value={value ?? ""}
|
|
205
|
+
onValueChange={({ floatValue }) => setValue(floatValue)}
|
|
206
|
+
thousandSeparator="," decimalScale={2} fixedDecimalScale prefix="$ "
|
|
207
|
+
allowNegative
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`onValueChange` receives `{ value, formattedValue, floatValue }`:
|
|
214
|
+
|
|
215
|
+
```txt
|
|
216
|
+
typing "1234.5" → value "1234.5" formattedValue "$ 1,234.50" floatValue 1234.5
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Options: `thousandSeparator` (`true` → `,`, or a custom string),
|
|
220
|
+
`decimalSeparator` (default `.`), `decimalScale` (truncates, no rounding),
|
|
221
|
+
`fixedDecimalScale`, `prefix`, `suffix`, `allowNegative` (default `true`),
|
|
222
|
+
`allowLeadingZeros`, and `isAllowed(values) => boolean` (reject an edit, e.g.
|
|
223
|
+
for min/max).
|
|
224
|
+
|
|
225
|
+
There is also a ref hook and the pure helpers:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { useNumberFormat, formatNumber, parseNumber } from "react-input-mask-format/number";
|
|
229
|
+
|
|
230
|
+
const ref = useNumberFormat({ thousandSeparator: " ", decimalScale: 2, prefix: "$ " });
|
|
231
|
+
// <input ref={ref} />
|
|
232
|
+
|
|
233
|
+
formatNumber(1234.5, { thousandSeparator: ",", decimalScale: 2, fixedDecimalScale: true }); // "1,234.50"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Migrating from react-number-format
|
|
237
|
+
|
|
238
|
+
`NumberFormat`'s props (`thousandSeparator`, `decimalSeparator`, `decimalScale`,
|
|
239
|
+
`fixedDecimalScale`, `prefix`, `suffix`, `allowNegative`, `allowLeadingZeros`,
|
|
240
|
+
`isAllowed`, `onValueChange`) mirror `react-number-format`'s `NumericFormat`.
|
|
241
|
+
The main differences: `decimalScale` **truncates** rather than rounds while
|
|
242
|
+
typing, and this package ships a single `NumberFormat` (no separate
|
|
243
|
+
`PatternFormat` — use the mask engine / `<InputMask>` for pattern masks).
|
|
244
|
+
|
|
82
245
|
### `maskPlaceholder`
|
|
83
246
|
|
|
84
247
|
```jsx
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/utils/input.ts
|
|
2
|
+
function setInputSelection(input, start, end) {
|
|
3
|
+
if (end === void 0) {
|
|
4
|
+
end = start;
|
|
5
|
+
}
|
|
6
|
+
input.setSelectionRange(start, end);
|
|
7
|
+
}
|
|
8
|
+
function getInputSelection(input) {
|
|
9
|
+
const start = input.selectionStart;
|
|
10
|
+
const end = input.selectionEnd;
|
|
11
|
+
return {
|
|
12
|
+
start,
|
|
13
|
+
end,
|
|
14
|
+
length: (end != null ? end : 0) - (start != null ? start : 0)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function isInputFocused(input) {
|
|
18
|
+
const inputDocument = input.ownerDocument;
|
|
19
|
+
return inputDocument.hasFocus() && inputDocument.activeElement === input;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/set-native-value.ts
|
|
23
|
+
function setNativeValue(input, value) {
|
|
24
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
25
|
+
HTMLInputElement.prototype,
|
|
26
|
+
"value"
|
|
27
|
+
);
|
|
28
|
+
const setter = descriptor && descriptor.set;
|
|
29
|
+
if (setter) {
|
|
30
|
+
setter.call(input, value);
|
|
31
|
+
} else {
|
|
32
|
+
input.value = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
setInputSelection,
|
|
38
|
+
getInputSelection,
|
|
39
|
+
isInputFocused,
|
|
40
|
+
setNativeValue
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=chunk-CEZXZ7IJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/input.ts","../src/set-native-value.ts"],"sourcesContent":["import type { Selection } from \"../types\";\n\nexport function setInputSelection(\n input: HTMLInputElement,\n start: number,\n end?: number\n): void {\n if (end === undefined) {\n end = start;\n }\n input.setSelectionRange(start, end);\n}\n\nexport function getInputSelection(input: HTMLInputElement): Required<Selection> {\n const start = input.selectionStart;\n const end = input.selectionEnd;\n\n return {\n start,\n end,\n length: (end ?? 0) - (start ?? 0)\n };\n}\n\nexport function isInputFocused(input: HTMLInputElement): boolean {\n const inputDocument = input.ownerDocument;\n return inputDocument.hasFocus() && inputDocument.activeElement === input;\n}\n","// Write to input.value through the native prototype setter so React's\n// internal value tracker stays stale. The user's keystroke `input` event then\n// bubbles into React's root listener, which sees masked value != stale tracker\n// and fires the consumer's onChange with the masked value. Using the tracked\n// (React-wrapped) setter instead would suppress that onChange.\nexport function setNativeValue(input: HTMLInputElement, value: string): void {\n const descriptor = Object.getOwnPropertyDescriptor(\n HTMLInputElement.prototype,\n \"value\"\n );\n const setter = descriptor && descriptor.set;\n if (setter) {\n setter.call(input, value);\n } else {\n input.value = value;\n }\n}\n"],"mappings":";AAEO,SAAS,kBACd,OACA,OACA,KACM;AACN,MAAI,QAAQ,QAAW;AACrB,UAAM;AAAA,EACR;AACA,QAAM,kBAAkB,OAAO,GAAG;AACpC;AAEO,SAAS,kBAAkB,OAA8C;AAC9E,QAAM,QAAQ,MAAM;AACpB,QAAM,MAAM,MAAM;AAElB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,oBAAO,MAAM,wBAAS;AAAA,EACjC;AACF;AAEO,SAAS,eAAe,OAAkC;AAC/D,QAAM,gBAAgB,MAAM;AAC5B,SAAO,cAAc,SAAS,KAAK,cAAc,kBAAkB;AACrE;;;ACtBO,SAAS,eAAe,OAAyB,OAAqB;AAC3E,QAAM,aAAa,OAAO;AAAA,IACxB,iBAAiB;AAAA,IACjB;AAAA,EACF;AACA,QAAM,SAAS,cAAc,WAAW;AACxC,MAAI,QAAQ;AACV,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B,OAAO;AACL,UAAM,QAAQ;AAAA,EAChB;AACF;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -30,10 +30,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.tsx
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
-
default: () => src_default
|
|
33
|
+
default: () => src_default,
|
|
34
|
+
defaultFormatChars: () => defaultFormatChars,
|
|
35
|
+
extendedFormatChars: () => extendedFormatChars,
|
|
36
|
+
useMask: () => useMask
|
|
34
37
|
});
|
|
35
38
|
module.exports = __toCommonJS(src_exports);
|
|
36
|
-
var
|
|
39
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
37
40
|
|
|
38
41
|
// src/hooks.ts
|
|
39
42
|
var import_react = require("react");
|
|
@@ -265,6 +268,12 @@ var defaultFormatChars = {
|
|
|
265
268
|
a: /[A-Za-z]/,
|
|
266
269
|
"*": /[A-Za-z0-9]/
|
|
267
270
|
};
|
|
271
|
+
var extendedFormatChars = {
|
|
272
|
+
...defaultFormatChars,
|
|
273
|
+
A: /[A-Z]/,
|
|
274
|
+
"\u042F": /[А-Яа-яЁёӘәҒғҚқҢңӨөҰұҮүҺһІі]/,
|
|
275
|
+
"#": /[0-9A-Fa-f]/
|
|
276
|
+
};
|
|
268
277
|
|
|
269
278
|
// src/validate-props.ts
|
|
270
279
|
function warnDev(condition, message) {
|
|
@@ -491,13 +500,14 @@ var MaskUtils = class {
|
|
|
491
500
|
if (position >= mask.length) {
|
|
492
501
|
return value;
|
|
493
502
|
}
|
|
494
|
-
const
|
|
503
|
+
const transformedCharacter = this.transform && this.isPositionEditable(position) ? this.transform(character, position) : character;
|
|
504
|
+
const isAllowed = this.isCharacterAllowedAtPosition(transformedCharacter, position);
|
|
495
505
|
const isEditable = this.isPositionEditable(position);
|
|
496
506
|
const nextEditablePosition = this.getRightEditablePosition(position);
|
|
497
|
-
const isNextPlaceholder = maskPlaceholder && nextEditablePosition ?
|
|
507
|
+
const isNextPlaceholder = maskPlaceholder && nextEditablePosition ? transformedCharacter === maskPlaceholder[nextEditablePosition] : null;
|
|
498
508
|
const valueBefore = value.slice(0, position);
|
|
499
509
|
if (isAllowed || !isEditable) {
|
|
500
|
-
const insertedCharacter = isAllowed ?
|
|
510
|
+
const insertedCharacter = isAllowed ? transformedCharacter : mask[position];
|
|
501
511
|
value = valueBefore + insertedCharacter;
|
|
502
512
|
}
|
|
503
513
|
if (!isAllowed && !isEditable && !isNextPlaceholder) {
|
|
@@ -592,6 +602,7 @@ var MaskUtils = class {
|
|
|
592
602
|
};
|
|
593
603
|
};
|
|
594
604
|
this.maskOptions = parseMask(options);
|
|
605
|
+
this.transform = options.transform;
|
|
595
606
|
}
|
|
596
607
|
};
|
|
597
608
|
|
|
@@ -609,15 +620,22 @@ function resolveMaskPlaceholder(maskPlaceholder, maskChar) {
|
|
|
609
620
|
}
|
|
610
621
|
return maskChar;
|
|
611
622
|
}
|
|
612
|
-
function
|
|
623
|
+
function normalizeFormatChars(formatChars) {
|
|
613
624
|
if (!formatChars) {
|
|
614
|
-
return void 0;
|
|
625
|
+
return { formatChars: void 0, hasLegacyString: false };
|
|
615
626
|
}
|
|
616
627
|
const result = {};
|
|
628
|
+
let hasLegacyString = false;
|
|
617
629
|
for (const key of Object.keys(formatChars)) {
|
|
618
|
-
|
|
630
|
+
const value = formatChars[key];
|
|
631
|
+
if (typeof value === "string") {
|
|
632
|
+
hasLegacyString = true;
|
|
633
|
+
result[key] = new RegExp(value);
|
|
634
|
+
} else {
|
|
635
|
+
result[key] = value;
|
|
636
|
+
}
|
|
619
637
|
}
|
|
620
|
-
return result;
|
|
638
|
+
return { formatChars: result, hasLegacyString };
|
|
621
639
|
}
|
|
622
640
|
function createBeforeMaskedStateChangeAdapter(beforeMaskedValueChange, maskOptions) {
|
|
623
641
|
return ({ previousState, currentState, nextState }) => {
|
|
@@ -631,14 +649,243 @@ function createBeforeMaskedStateChangeAdapter(beforeMaskedValueChange, maskOptio
|
|
|
631
649
|
};
|
|
632
650
|
}
|
|
633
651
|
|
|
652
|
+
// src/utils/transform.ts
|
|
653
|
+
function resolveTransform(transform) {
|
|
654
|
+
if (!transform) {
|
|
655
|
+
return void 0;
|
|
656
|
+
}
|
|
657
|
+
if (transform === "uppercase") {
|
|
658
|
+
return (char) => char.toUpperCase();
|
|
659
|
+
}
|
|
660
|
+
if (transform === "lowercase") {
|
|
661
|
+
return (char) => char.toLowerCase();
|
|
662
|
+
}
|
|
663
|
+
if (typeof transform === "function") {
|
|
664
|
+
return transform;
|
|
665
|
+
}
|
|
666
|
+
if (process.env.NODE_ENV !== "production") {
|
|
667
|
+
console.error(
|
|
668
|
+
`react-input-mask-format: invalid transform "${String(
|
|
669
|
+
transform
|
|
670
|
+
)}", expected "uppercase" | "lowercase" | (char, position) => char`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
return void 0;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/use-mask.ts
|
|
677
|
+
var import_react2 = require("react");
|
|
678
|
+
|
|
679
|
+
// src/set-native-value.ts
|
|
680
|
+
function setNativeValue(input, value) {
|
|
681
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
682
|
+
HTMLInputElement.prototype,
|
|
683
|
+
"value"
|
|
684
|
+
);
|
|
685
|
+
const setter = descriptor && descriptor.set;
|
|
686
|
+
if (setter) {
|
|
687
|
+
setter.call(input, value);
|
|
688
|
+
} else {
|
|
689
|
+
input.value = value;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/bind-mask.ts
|
|
694
|
+
var IS_JSDOM = typeof navigator !== "undefined" && navigator.userAgent.includes("jsdom");
|
|
695
|
+
function createMaskController(maskUtils, options) {
|
|
696
|
+
let input = null;
|
|
697
|
+
let lastValue = "";
|
|
698
|
+
let lastSelection = { start: null, end: null };
|
|
699
|
+
function getInputState() {
|
|
700
|
+
return { value: input.value, selection: getInputSelection(input) };
|
|
701
|
+
}
|
|
702
|
+
function getLastInputState() {
|
|
703
|
+
return { value: lastValue, selection: lastSelection };
|
|
704
|
+
}
|
|
705
|
+
function setInputState({ value, selection }) {
|
|
706
|
+
setNativeValue(input, value);
|
|
707
|
+
lastValue = value;
|
|
708
|
+
if (input && isInputFocused(input) && selection.start !== null && selection.end !== null) {
|
|
709
|
+
setInputSelection(input, selection.start, selection.end);
|
|
710
|
+
lastSelection = getInputSelection(input);
|
|
711
|
+
} else {
|
|
712
|
+
lastSelection = selection;
|
|
713
|
+
}
|
|
714
|
+
if (IS_JSDOM) {
|
|
715
|
+
const resyncValue = value;
|
|
716
|
+
const resyncSelection = selection;
|
|
717
|
+
queueMicrotask(() => {
|
|
718
|
+
if (!input || input.value !== resyncValue) return;
|
|
719
|
+
input.value = resyncValue;
|
|
720
|
+
if (isInputFocused(input) && resyncSelection.start !== null && resyncSelection.end !== null) {
|
|
721
|
+
setInputSelection(input, resyncSelection.start, resyncSelection.end);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function handleInput() {
|
|
727
|
+
const currentState = getInputState();
|
|
728
|
+
const previousState = getLastInputState();
|
|
729
|
+
let nextState = maskUtils.processChange(currentState, previousState);
|
|
730
|
+
if (options.beforeMaskedStateChange) {
|
|
731
|
+
nextState = options.beforeMaskedStateChange({ currentState, previousState, nextState });
|
|
732
|
+
}
|
|
733
|
+
setInputState(nextState);
|
|
734
|
+
}
|
|
735
|
+
function handleFocus() {
|
|
736
|
+
const currentValue = getInputState().value;
|
|
737
|
+
if (!maskUtils.isValueFilled(currentValue)) {
|
|
738
|
+
const newValue = maskUtils.formatValue(currentValue);
|
|
739
|
+
const newSelection = maskUtils.getDefaultSelectionForValue(newValue);
|
|
740
|
+
let nextState = { value: newValue, selection: newSelection };
|
|
741
|
+
if (options.beforeMaskedStateChange) {
|
|
742
|
+
nextState = options.beforeMaskedStateChange({ currentState: getInputState(), nextState });
|
|
743
|
+
}
|
|
744
|
+
setInputState(nextState);
|
|
745
|
+
defer(() => {
|
|
746
|
+
if (input && isInputFocused(input)) {
|
|
747
|
+
setInputSelection(input, lastSelection.start, lastSelection.end);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
function handleBlur() {
|
|
753
|
+
const lastValueOnBlur = getLastInputState().value;
|
|
754
|
+
if (!options.alwaysShowMask && maskUtils.isValueEmpty(lastValueOnBlur)) {
|
|
755
|
+
let nextState = { value: "", selection: { start: null, end: null } };
|
|
756
|
+
if (options.beforeMaskedStateChange) {
|
|
757
|
+
nextState = options.beforeMaskedStateChange({ currentState: getInputState(), nextState });
|
|
758
|
+
}
|
|
759
|
+
setInputState(nextState);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function handleMouseDown(event) {
|
|
763
|
+
if (!input) return;
|
|
764
|
+
const { value } = getInputState();
|
|
765
|
+
const inputDocument = getElementDocument(input);
|
|
766
|
+
if (!isInputFocused(input) && !maskUtils.isValueFilled(value)) {
|
|
767
|
+
const mouseDownX = event.clientX;
|
|
768
|
+
const mouseDownY = event.clientY;
|
|
769
|
+
const mouseDownTime = (/* @__PURE__ */ new Date()).getTime();
|
|
770
|
+
const mouseUpHandler = (mouseUpEvent) => {
|
|
771
|
+
inputDocument.removeEventListener("mouseup", mouseUpHandler);
|
|
772
|
+
if (!input || !isInputFocused(input)) return;
|
|
773
|
+
const deltaX = Math.abs(mouseUpEvent.clientX - mouseDownX);
|
|
774
|
+
const deltaY = Math.abs(mouseUpEvent.clientY - mouseDownY);
|
|
775
|
+
const axisDelta = Math.max(deltaX, deltaY);
|
|
776
|
+
const timeDelta = (/* @__PURE__ */ new Date()).getTime() - mouseDownTime;
|
|
777
|
+
if (axisDelta <= 10 && timeDelta <= 200 || axisDelta <= 5 && timeDelta <= 300) {
|
|
778
|
+
const newSelection = maskUtils.getDefaultSelectionForValue(lastValue);
|
|
779
|
+
if (newSelection.start !== null && newSelection.end !== null) {
|
|
780
|
+
setInputSelection(input, newSelection.start, newSelection.end);
|
|
781
|
+
lastSelection = getInputSelection(input);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
inputDocument.addEventListener("mouseup", mouseUpHandler);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function bind(el) {
|
|
789
|
+
input = el;
|
|
790
|
+
lastValue = el.value;
|
|
791
|
+
lastSelection = getInputSelection(el);
|
|
792
|
+
const formatted = maskUtils.formatValue(el.value);
|
|
793
|
+
if (!maskUtils.isValueEmpty(formatted) || options.alwaysShowMask) {
|
|
794
|
+
setNativeValue(el, formatted);
|
|
795
|
+
lastValue = formatted;
|
|
796
|
+
}
|
|
797
|
+
el.addEventListener("input", handleInput);
|
|
798
|
+
el.addEventListener("focus", handleFocus);
|
|
799
|
+
el.addEventListener("blur", handleBlur);
|
|
800
|
+
el.addEventListener("mousedown", handleMouseDown);
|
|
801
|
+
}
|
|
802
|
+
function unbind() {
|
|
803
|
+
if (input) {
|
|
804
|
+
input.removeEventListener("input", handleInput);
|
|
805
|
+
input.removeEventListener("focus", handleFocus);
|
|
806
|
+
input.removeEventListener("blur", handleBlur);
|
|
807
|
+
input.removeEventListener("mousedown", handleMouseDown);
|
|
808
|
+
}
|
|
809
|
+
input = null;
|
|
810
|
+
}
|
|
811
|
+
function update(nextMaskUtils, nextOptions) {
|
|
812
|
+
maskUtils = nextMaskUtils;
|
|
813
|
+
options = nextOptions;
|
|
814
|
+
}
|
|
815
|
+
return { bind, unbind, update };
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/use-mask.ts
|
|
819
|
+
function useMask(options) {
|
|
820
|
+
const {
|
|
821
|
+
mask,
|
|
822
|
+
maskPlaceholder: maskPlaceholderProp,
|
|
823
|
+
alwaysShowMask = false,
|
|
824
|
+
formatChars,
|
|
825
|
+
transform,
|
|
826
|
+
beforeMaskedStateChange: beforeMaskedStateChangeProp,
|
|
827
|
+
beforeMaskedValueChange
|
|
828
|
+
} = options;
|
|
829
|
+
if (beforeMaskedValueChange !== void 0) {
|
|
830
|
+
warnDeprecatedOnce(
|
|
831
|
+
"beforeMaskedValueChange",
|
|
832
|
+
"beforeMaskedValueChange is deprecated, use beforeMaskedStateChange."
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
const resolvedPlaceholder = resolveMaskPlaceholder(maskPlaceholderProp, void 0);
|
|
836
|
+
const maskPlaceholder = resolvedPlaceholder === void 0 ? "_" : resolvedPlaceholder;
|
|
837
|
+
const { formatChars: normalizedFormatChars, hasLegacyString } = normalizeFormatChars(formatChars);
|
|
838
|
+
if (hasLegacyString) {
|
|
839
|
+
warnDeprecatedOnce(
|
|
840
|
+
"formatChars",
|
|
841
|
+
"string values in formatChars are deprecated, pass RegExp values instead."
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const maskUtils = new MaskUtils({
|
|
845
|
+
mask: mask != null ? mask : null,
|
|
846
|
+
maskPlaceholder,
|
|
847
|
+
formatChars: normalizedFormatChars,
|
|
848
|
+
transform: resolveTransform(transform)
|
|
849
|
+
});
|
|
850
|
+
let beforeMaskedStateChange = beforeMaskedStateChangeProp;
|
|
851
|
+
if (!beforeMaskedStateChange && beforeMaskedValueChange) {
|
|
852
|
+
const v2MaskOptions = {
|
|
853
|
+
mask: mask != null ? mask : void 0,
|
|
854
|
+
maskChar: maskPlaceholder,
|
|
855
|
+
alwaysShowMask,
|
|
856
|
+
formatChars: formatChars != null ? formatChars : { "9": "[0-9]", a: "[A-Za-z]", "*": "[A-Za-z0-9]" },
|
|
857
|
+
permanents: maskUtils.maskOptions.permanents
|
|
858
|
+
};
|
|
859
|
+
beforeMaskedStateChange = createBeforeMaskedStateChangeAdapter(beforeMaskedValueChange, v2MaskOptions);
|
|
860
|
+
}
|
|
861
|
+
const controllerOptions = { alwaysShowMask, beforeMaskedStateChange };
|
|
862
|
+
const controllerRef = (0, import_react2.useRef)(null);
|
|
863
|
+
(0, import_react2.useEffect)(() => {
|
|
864
|
+
var _a;
|
|
865
|
+
(_a = controllerRef.current) == null ? void 0 : _a.update(maskUtils, controllerOptions);
|
|
866
|
+
});
|
|
867
|
+
return (0, import_react2.useCallback)((el) => {
|
|
868
|
+
var _a;
|
|
869
|
+
if (el) {
|
|
870
|
+
const controller = createMaskController(maskUtils, controllerOptions);
|
|
871
|
+
controller.bind(el);
|
|
872
|
+
controllerRef.current = controller;
|
|
873
|
+
} else {
|
|
874
|
+
(_a = controllerRef.current) == null ? void 0 : _a.unbind();
|
|
875
|
+
controllerRef.current = null;
|
|
876
|
+
}
|
|
877
|
+
}, []);
|
|
878
|
+
}
|
|
879
|
+
|
|
634
880
|
// src/index.tsx
|
|
635
|
-
var InputMask = (0,
|
|
881
|
+
var InputMask = (0, import_react3.forwardRef)(function InputMask2(props, forwardedRef) {
|
|
636
882
|
const {
|
|
637
883
|
alwaysShowMask = false,
|
|
638
884
|
children,
|
|
639
885
|
mask,
|
|
640
886
|
maskPlaceholder: maskPlaceholderProp,
|
|
641
887
|
beforeMaskedStateChange: beforeMaskedStateChangeProp,
|
|
888
|
+
transform,
|
|
642
889
|
maskChar,
|
|
643
890
|
formatChars,
|
|
644
891
|
beforeMaskedValueChange,
|
|
@@ -647,9 +894,6 @@ var InputMask = (0, import_react2.forwardRef)(function InputMask2(props, forward
|
|
|
647
894
|
if (maskChar !== void 0) {
|
|
648
895
|
warnDeprecatedOnce("maskChar", "maskChar is deprecated, use maskPlaceholder instead. See migration guide: https://github.com/Temirtator/react-input-mask-format#migrating-from-react-input-mask");
|
|
649
896
|
}
|
|
650
|
-
if (formatChars !== void 0) {
|
|
651
|
-
warnDeprecatedOnce("formatChars", "formatChars is deprecated, prefer an array mask with RegExps. See migration guide: https://github.com/Temirtator/react-input-mask-format#migrating-from-react-input-mask");
|
|
652
|
-
}
|
|
653
897
|
if (beforeMaskedValueChange !== void 0) {
|
|
654
898
|
warnDeprecatedOnce("beforeMaskedValueChange", "beforeMaskedValueChange is deprecated, use beforeMaskedStateChange. See migration guide: https://github.com/Temirtator/react-input-mask-format#migrating-from-react-input-mask");
|
|
655
899
|
}
|
|
@@ -657,10 +901,18 @@ var InputMask = (0, import_react2.forwardRef)(function InputMask2(props, forward
|
|
|
657
901
|
const maskPlaceholder = resolvedPlaceholder === void 0 ? "_" : resolvedPlaceholder;
|
|
658
902
|
validateMaxLength(props);
|
|
659
903
|
validateMaskPlaceholder({ ...props, maskPlaceholder });
|
|
904
|
+
const { formatChars: normalizedFormatChars, hasLegacyString } = normalizeFormatChars(formatChars);
|
|
905
|
+
if (hasLegacyString) {
|
|
906
|
+
warnDeprecatedOnce(
|
|
907
|
+
"formatChars",
|
|
908
|
+
'string values in formatChars are deprecated, pass RegExp values instead (e.g. { "#": /[0-9]/ }). See migration guide: https://github.com/Temirtator/react-input-mask-format#migrating-from-react-input-mask'
|
|
909
|
+
);
|
|
910
|
+
}
|
|
660
911
|
const maskUtils = new MaskUtils({
|
|
661
912
|
mask,
|
|
662
913
|
maskPlaceholder,
|
|
663
|
-
formatChars:
|
|
914
|
+
formatChars: normalizedFormatChars,
|
|
915
|
+
transform: resolveTransform(transform)
|
|
664
916
|
});
|
|
665
917
|
let beforeMaskedStateChange = beforeMaskedStateChangeProp;
|
|
666
918
|
if (!beforeMaskedStateChange && beforeMaskedValueChange) {
|
|
@@ -809,7 +1061,7 @@ var InputMask = (0, import_react2.forwardRef)(function InputMask2(props, forward
|
|
|
809
1061
|
renderValue = newValue;
|
|
810
1062
|
}
|
|
811
1063
|
const lastSelection = getLastInputState().selection;
|
|
812
|
-
(0,
|
|
1064
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
813
1065
|
if (isMasked && isControlled) {
|
|
814
1066
|
setInputState({
|
|
815
1067
|
...getLastInputState(),
|
|
@@ -817,7 +1069,7 @@ var InputMask = (0, import_react2.forwardRef)(function InputMask2(props, forward
|
|
|
817
1069
|
});
|
|
818
1070
|
}
|
|
819
1071
|
});
|
|
820
|
-
(0,
|
|
1072
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
821
1073
|
if (!isMasked) {
|
|
822
1074
|
return;
|
|
823
1075
|
}
|
|
@@ -874,10 +1126,16 @@ var InputMask = (0, import_react2.forwardRef)(function InputMask2(props, forward
|
|
|
874
1126
|
};
|
|
875
1127
|
if (children) {
|
|
876
1128
|
validateChildren(props, children);
|
|
877
|
-
return
|
|
1129
|
+
return import_react3.default.cloneElement(children, inputProps);
|
|
878
1130
|
}
|
|
879
|
-
return /* @__PURE__ */
|
|
1131
|
+
return /* @__PURE__ */ import_react3.default.createElement("input", { ...inputProps });
|
|
880
1132
|
});
|
|
881
1133
|
InputMask.displayName = "InputMask";
|
|
882
1134
|
var src_default = InputMask;
|
|
1135
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1136
|
+
0 && (module.exports = {
|
|
1137
|
+
defaultFormatChars,
|
|
1138
|
+
extendedFormatChars,
|
|
1139
|
+
useMask
|
|
1140
|
+
});
|
|
883
1141
|
//# sourceMappingURL=index.cjs.map
|