maskarajs 1.0.0 → 1.0.2
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 +339 -169
- package/mask.cjs.js +173 -44
- package/mask.d.ts +49 -7
- package/mask.js +2 -874
- package/mask.mjs +2 -874
- package/package.json +39 -3
- package/presets/br.cjs.js +45 -0
- package/presets/br.d.ts +23 -0
- package/presets/br.mjs +40 -0
- package/react.cjs.js +89 -0
- package/react.d.ts +57 -0
- package/react.mjs +2 -0
- package/src/core/mask.d.ts +333 -0
- package/src/core/mask.mjs +1002 -0
- package/src/react/useMask.mjs +80 -0
package/README.md
CHANGED
|
@@ -1,94 +1,143 @@
|
|
|
1
|
-
#
|
|
1
|
+
# maskarajs
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Declarative input mask engine for JavaScript. Framework-agnostic, zero dependencies, and small enough to live close to your form fields.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Syntax
|
|
6
6
|
|
|
7
|
-
| Token |
|
|
7
|
+
| Token | Accepts |
|
|
8
8
|
|---|---|
|
|
9
|
-
| `#` |
|
|
10
|
-
| `@` |
|
|
11
|
-
| `*` |
|
|
12
|
-
| `[
|
|
13
|
-
| `{expr}` | slot
|
|
9
|
+
| `#` | any digit (`0-9`) |
|
|
10
|
+
| `@` | any letter (`a-z`, `A-Z`, accented letters) |
|
|
11
|
+
| `*` | any character |
|
|
12
|
+
| `[text]` | fixed literal text, inserted and removed automatically |
|
|
13
|
+
| `{expr}` | free slot: tests one character against an expression |
|
|
14
|
+
|
|
15
|
+
### `{expr}` modifier
|
|
16
|
+
|
|
17
|
+
```txt
|
|
18
|
+
{4} -> only the char "4"
|
|
19
|
+
{0-4} -> range: ch >= "0" && ch <= "4"
|
|
20
|
+
{013} -> set: "0", "1" or "3"
|
|
21
|
+
{[0-9a-f]} -> regex: any hexadecimal char
|
|
22
|
+
{\d} -> regex: any digit
|
|
23
|
+
{[^aeiou]} -> regex: any consonant
|
|
24
|
+
```
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
## Install
|
|
16
27
|
|
|
28
|
+
```bash
|
|
29
|
+
npm install maskarajs
|
|
17
30
|
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
{[0-9a-f]} → regex: qualquer char hex
|
|
22
|
-
{\d} → regex: qualquer dígito
|
|
23
|
-
{[^aeiou]} → regex: consoante
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import maskara from 'maskarajs'
|
|
24
34
|
```
|
|
25
35
|
|
|
26
36
|
## API
|
|
27
37
|
|
|
28
38
|
```js
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
mask.on(input, pattern, options) // vincula a input DOM → cleanup()
|
|
45
|
-
mask.create(presets) // instância isolada com registry próprio
|
|
39
|
+
maskara(pattern, value) // apply mask -> formatted string
|
|
40
|
+
maskara.raw(pattern, value) // clean value / transform result
|
|
41
|
+
maskara.is(pattern, value) // complete pattern? -> boolean
|
|
42
|
+
maskara.hint(pattern) // readable placeholder -> string
|
|
43
|
+
maskara.format(pattern, value) // semantic alias for maskara()
|
|
44
|
+
maskara.rawLength(pattern, value) // filled input chars -> number
|
|
45
|
+
maskara.patternLength(pattern) // full formatted length -> number
|
|
46
|
+
maskara.define(name, definition) // register a named mask
|
|
47
|
+
maskara.undefine(name) // remove a named mask
|
|
48
|
+
maskara.names() // list registered names -> string[]
|
|
49
|
+
maskara.defineSlot(symbol, definition) // create or override an input token
|
|
50
|
+
maskara.undefineSlot(symbol) // remove a custom token
|
|
51
|
+
maskara.slots() // list available input tokens
|
|
52
|
+
maskara.on(input, pattern, options) // bind to a DOM input -> cleanup()
|
|
53
|
+
maskara.create(presets) // isolated instance with its own registry
|
|
46
54
|
```
|
|
47
55
|
|
|
48
|
-
##
|
|
56
|
+
## React adapter
|
|
57
|
+
|
|
58
|
+
The React adapter is optional and lives in `maskarajs/react`. Use `useMaskara` directly for small forms, or wrap your app with `MaskaraProvider` when you have an isolated engine created with `maskara.create()`.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import maskara from 'maskarajs'
|
|
62
|
+
import { MaskaraProvider, useMaskara } from 'maskarajs/react'
|
|
63
|
+
|
|
64
|
+
const appMaskara = maskara.create({
|
|
65
|
+
cpf: { pattern: '###[.]###[.]###[-]##' },
|
|
66
|
+
phone: { pattern: ['[(]##[)] ####[-]####', '[(]##[)] #####[-]####'] },
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
function CPFInput() {
|
|
70
|
+
const cpf = useMaskara('cpf')
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<input
|
|
74
|
+
{...cpf.inputProps({ inputMode: 'numeric' })}
|
|
75
|
+
aria-label="CPF"
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function App() {
|
|
81
|
+
return (
|
|
82
|
+
<MaskaraProvider engine={appMaskara}>
|
|
83
|
+
<CPFInput />
|
|
84
|
+
</MaskaraProvider>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`useMaskara` still accepts an `engine` option when a single field needs a specific instance:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
const cpf = useMaskara('cpf', { engine: appMaskara })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The hook stores field state. The engine stores mask configuration.
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
49
98
|
|
|
50
99
|
```js
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
//
|
|
100
|
+
// Single pattern
|
|
101
|
+
maskara('###[.]###[.]###[-]##', '12345678909')
|
|
102
|
+
// -> '123.456.789-09'
|
|
54
103
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
//
|
|
104
|
+
// Dynamic pattern array: chooses by input size
|
|
105
|
+
maskara(['[(]##[)] ####[-]####', '[(]##[)] #####[-]####'], '11987654321')
|
|
106
|
+
// -> '(11) 98765-4321'
|
|
58
107
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
//
|
|
108
|
+
// Restricted slot
|
|
109
|
+
maskara('{4}### #### #### ####', '4111111111111111')
|
|
110
|
+
// -> '4111 1111 1111 1111'
|
|
62
111
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
//
|
|
112
|
+
// Regex slot
|
|
113
|
+
maskara('{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}', '1a2b3c')
|
|
114
|
+
// -> '1a2b3c'
|
|
66
115
|
```
|
|
67
116
|
|
|
68
|
-
###
|
|
117
|
+
### Common cases
|
|
69
118
|
|
|
70
119
|
```js
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
//
|
|
120
|
+
// Brazilian ZIP code
|
|
121
|
+
maskara('#####[-]###', '01310930')
|
|
122
|
+
// -> '01310-930'
|
|
74
123
|
|
|
75
124
|
// CNPJ
|
|
76
|
-
|
|
77
|
-
//
|
|
125
|
+
maskara('##[.]###[.]###[/]####[-]##', '11222333000181')
|
|
126
|
+
// -> '11.222.333/0001-81'
|
|
78
127
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
//
|
|
128
|
+
// Visa card
|
|
129
|
+
maskara('{4}### #### #### ####', '5111111111111111')
|
|
130
|
+
// -> '' (the first char did not pass)
|
|
82
131
|
|
|
83
|
-
// Hex color
|
|
84
|
-
|
|
85
|
-
//
|
|
132
|
+
// Hex color with dirty paste
|
|
133
|
+
maskara('{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}{[0-9a-fA-F]}', '1z2b3c')
|
|
134
|
+
// -> '12b3c'
|
|
86
135
|
```
|
|
87
136
|
|
|
88
|
-
##
|
|
137
|
+
## `maskara.define` with `transform`
|
|
89
138
|
|
|
90
139
|
```js
|
|
91
|
-
|
|
140
|
+
maskara.define('date', {
|
|
92
141
|
pattern: '##[/]##[/]####',
|
|
93
142
|
validate: (raw, masked, complete) => {
|
|
94
143
|
if (raw.length < 4) return true
|
|
@@ -102,23 +151,21 @@ mask.define('date', {
|
|
|
102
151
|
},
|
|
103
152
|
})
|
|
104
153
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
154
|
+
maskara('date', '01012025') // -> '01/01/2025'
|
|
155
|
+
maskara.raw('date', '01/01/2025') // -> Date(2025-01-01)
|
|
156
|
+
maskara.raw('date', '01/01') // -> null
|
|
157
|
+
maskara.is('date', '01/01/2025') // -> true
|
|
158
|
+
maskara.hint('date') // -> '00/00/0000'
|
|
159
|
+
maskara.rawLength('date', '01/01') // -> 4
|
|
160
|
+
maskara.patternLength('date') // -> 10
|
|
112
161
|
```
|
|
113
162
|
|
|
114
|
-
## validate
|
|
163
|
+
## `validate`: incremental validation
|
|
115
164
|
|
|
116
|
-
Use `validate`
|
|
117
|
-
Isso resolve casos onde a sintaxe caractere a caractere não é suficiente, como mês
|
|
118
|
-
entre `01` e `12`.
|
|
165
|
+
Use `validate` on named masks when the rule depends on what has already been typed. This covers cases where character-by-character syntax is not enough, such as accepting only months from `01` to `12`.
|
|
119
166
|
|
|
120
167
|
```js
|
|
121
|
-
|
|
168
|
+
maskara.define('month', {
|
|
122
169
|
pattern: '{0-1}#',
|
|
123
170
|
validate: (raw, masked, complete) => {
|
|
124
171
|
if (!complete) return true
|
|
@@ -127,98 +174,129 @@ mask.define('month', {
|
|
|
127
174
|
},
|
|
128
175
|
})
|
|
129
176
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
177
|
+
maskara('month', '12') // -> '12'
|
|
178
|
+
maskara('month', '19') // -> '1'
|
|
179
|
+
maskara.is('month', '12') // -> true
|
|
180
|
+
maskara.is('month', '19') // -> false
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Conditional masks
|
|
184
|
+
|
|
185
|
+
Use `patterns` + `select` on a named mask when the mask must change because of the typed value, not only because of length. Arrays of strings keep the old behavior and still choose the smallest pattern that fits the input. Conditional masks live inside `define` or `create`, where product rules already belong.
|
|
186
|
+
|
|
187
|
+
`select(raw, value)` receives the value without literals from all possible patterns and returns a key from `patterns`.
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
const cpf = '###[.]###[.]###[-]##'
|
|
191
|
+
const cnpj = '##[.]###[.]###[/]####[-]##'
|
|
192
|
+
|
|
193
|
+
maskara.define('smartDocument', {
|
|
194
|
+
patterns: { cpf, cnpj },
|
|
195
|
+
select: raw => raw.includes('123') ? 'cnpj' : 'cpf',
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
maskara('smartDocument', '98765432100')
|
|
199
|
+
// -> '987.654.321-00'
|
|
200
|
+
|
|
201
|
+
maskara('smartDocument', '12345678000199')
|
|
202
|
+
// -> '12.345.678/0001-99'
|
|
134
203
|
```
|
|
135
204
|
|
|
136
|
-
|
|
205
|
+
It also works with isolated instances:
|
|
137
206
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
207
|
+
```js
|
|
208
|
+
const forms = maskara.create({
|
|
209
|
+
smartDocument: {
|
|
210
|
+
patterns: { cpf, cnpj },
|
|
211
|
+
select: raw => raw.startsWith('9') ? 'cpf' : 'cnpj',
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Prefer conditional masks for rules that depend on the value itself: prefixes, country codes, card BINs, product codes or any domain rule. Prefer a simple string array when the only difference is input size, such as phone numbers with 10 or 11 digits.
|
|
217
|
+
|
|
218
|
+
## Custom slots: create your own pattern language
|
|
219
|
+
|
|
220
|
+
Besides `#`, `@`, `*` and `{expr}`, you can create symbols that make sense for your team. This is useful when a project needs a shorter, more expressive language aligned with its product domain.
|
|
141
221
|
|
|
142
222
|
```js
|
|
143
|
-
|
|
223
|
+
maskara.defineSlot('N', {
|
|
144
224
|
test: ch => /\d/.test(ch),
|
|
145
225
|
hint: '0',
|
|
146
226
|
})
|
|
147
227
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
228
|
+
maskara('NNN[-]NN', '12345') // -> '123-45'
|
|
229
|
+
maskara.hint('NNN[-]NN') // -> '000-00'
|
|
230
|
+
maskara.slots() // -> ['#', '@', '*', 'N']
|
|
151
231
|
```
|
|
152
232
|
|
|
153
|
-
|
|
233
|
+
You can also pass a `RegExp` or a function directly:
|
|
154
234
|
|
|
155
235
|
```js
|
|
156
|
-
|
|
157
|
-
|
|
236
|
+
maskara.defineSlot('H', /[0-9a-f]/i)
|
|
237
|
+
maskara.defineSlot('V', ch => 'AEIOUaeiou'.includes(ch))
|
|
158
238
|
|
|
159
|
-
|
|
160
|
-
|
|
239
|
+
maskara('HHHHHH', '1a2b3c') // -> '1a2b3c'
|
|
240
|
+
maskara('VVV', 'mask') // -> 'a'
|
|
161
241
|
```
|
|
162
242
|
|
|
163
|
-
|
|
164
|
-
produtos com regras próprias, prefira uma instância isolada:
|
|
243
|
+
Global slots affect the global engine. For libraries, design systems or products with their own rules, prefer an isolated instance:
|
|
165
244
|
|
|
166
245
|
```js
|
|
167
|
-
const forge =
|
|
246
|
+
const forge = maskara.create()
|
|
168
247
|
|
|
169
248
|
forge.defineSlot('N', { test: ch => /\d/.test(ch), hint: '0' })
|
|
170
249
|
forge.defineSlot('#', { test: ch => /[1-9]/.test(ch), hint: '1' })
|
|
171
250
|
|
|
172
|
-
forge('NNN[-]NN', '12345') //
|
|
173
|
-
forge('#', '0') //
|
|
174
|
-
|
|
251
|
+
forge('NNN[-]NN', '12345') // -> '123-45'
|
|
252
|
+
forge('#', '0') // -> ''
|
|
253
|
+
maskara('#', '0') // -> '0' (global stays unchanged)
|
|
175
254
|
```
|
|
176
255
|
|
|
177
|
-
|
|
178
|
-
colchetes:
|
|
256
|
+
If a registered symbol must appear as fixed text, escape it with brackets:
|
|
179
257
|
|
|
180
258
|
```js
|
|
181
|
-
|
|
259
|
+
maskara.defineSlot('N', /\d/)
|
|
182
260
|
|
|
183
|
-
|
|
184
|
-
|
|
261
|
+
maskara('[N]##', '45') // -> 'N45'
|
|
262
|
+
maskara('N##', '145') // -> '145'
|
|
185
263
|
```
|
|
186
264
|
|
|
187
|
-
##
|
|
265
|
+
## `maskara.on`: any framework
|
|
188
266
|
|
|
189
267
|
```js
|
|
190
268
|
// Vanilla JS
|
|
191
|
-
const off =
|
|
192
|
-
onValue:
|
|
193
|
-
|
|
269
|
+
const off = maskara.on(inputEl, 'cpf', {
|
|
270
|
+
onValue: raw => setState(raw),
|
|
271
|
+
onMaskara: masked => setLabel(masked),
|
|
194
272
|
})
|
|
195
|
-
off()
|
|
273
|
+
off()
|
|
196
274
|
|
|
197
275
|
// React
|
|
198
276
|
useEffect(() => {
|
|
199
|
-
return
|
|
200
|
-
onValue:
|
|
277
|
+
return maskara.on(ref.current, 'date', {
|
|
278
|
+
onValue: date => setValue(date), // Date | null
|
|
201
279
|
})
|
|
202
280
|
}, [])
|
|
203
281
|
|
|
204
282
|
// Vue
|
|
205
283
|
onMounted(() => {
|
|
206
|
-
|
|
207
|
-
onValue:
|
|
284
|
+
maskara.on(inputRef.value, 'phone', {
|
|
285
|
+
onValue: value => emit('update:modelValue', value),
|
|
208
286
|
})
|
|
209
287
|
})
|
|
210
288
|
|
|
211
289
|
// Svelte action
|
|
212
290
|
function maskAction(node, pattern) {
|
|
213
|
-
const off =
|
|
291
|
+
const off = maskara.on(node, pattern)
|
|
214
292
|
return { destroy: off }
|
|
215
293
|
}
|
|
216
294
|
```
|
|
217
295
|
|
|
218
|
-
##
|
|
296
|
+
## `maskara.create`: isolated instances
|
|
219
297
|
|
|
220
298
|
```js
|
|
221
|
-
export const
|
|
299
|
+
export const maskaraBR = maskara.create({
|
|
222
300
|
cpf: { pattern: '###[.]###[.]###[-]##' },
|
|
223
301
|
cnpj: { pattern: '##[.]###[.]###[/]####[-]##' },
|
|
224
302
|
phone: { pattern: ['[(]##[)] ####[-]####', '[(]##[)] #####[-]####'] },
|
|
@@ -231,53 +309,78 @@ export const maskBR = mask.create({
|
|
|
231
309
|
return isNaN(dt) ? null : dt
|
|
232
310
|
},
|
|
233
311
|
},
|
|
234
|
-
money: {
|
|
235
|
-
pattern: '########[,]##',
|
|
236
|
-
transform: raw => parseInt(raw || '0', 10) / 100,
|
|
237
|
-
},
|
|
238
312
|
})
|
|
239
313
|
|
|
240
|
-
export const
|
|
314
|
+
export const maskaraUS = maskara.create({
|
|
241
315
|
ssn: { pattern: '###[-]##[-]####' },
|
|
242
316
|
zip: { pattern: '#####[-]####' },
|
|
243
317
|
phone: { pattern: '[(]###[)] ###[-]####' },
|
|
244
318
|
})
|
|
245
319
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
maskUS.names() // → ['ssn', 'zip', 'phone']
|
|
320
|
+
maskaraBR('cpf', '12345678909') // -> '123.456.789-09'
|
|
321
|
+
maskaraBR.raw('date', '01/01/2025') // -> Date
|
|
322
|
+
maskaraBR.names() // -> ['cpf', 'cnpj', 'phone', 'cep', 'date']
|
|
323
|
+
maskaraUS.names() // -> ['ssn', 'zip', 'phone']
|
|
251
324
|
```
|
|
252
325
|
|
|
253
|
-
##
|
|
326
|
+
## Official Brazilian presets
|
|
327
|
+
|
|
328
|
+
If you want common Brazilian masks ready to use, import `maskarajs/presets/br` and create an isolated engine from it.
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import maskara from 'maskarajs'
|
|
332
|
+
import { br, type BrazilPresetRegistry } from 'maskarajs/presets/br'
|
|
333
|
+
|
|
334
|
+
const maskaraBR = maskara.create<BrazilPresetRegistry>(br)
|
|
335
|
+
|
|
336
|
+
maskaraBR('cpf', '12345678909') // -> '123.456.789-09'
|
|
337
|
+
maskaraBR('cnpj', '11222333000181') // -> '11.222.333/0001-81'
|
|
338
|
+
maskaraBR('phone', '11987654321') // -> '(11) 98765-4321'
|
|
339
|
+
maskaraBR.raw('cep', '01310-930') // -> '01310930'
|
|
340
|
+
maskaraBR.raw('date', '01/12/2025') // -> Date
|
|
341
|
+
maskaraBR.raw('money', '1299,90') // -> 1299.9
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The preset includes:
|
|
345
|
+
|
|
346
|
+
| Name | Pattern / behavior |
|
|
347
|
+
|---|---|
|
|
348
|
+
| `cpf` | `000.000.000-00` |
|
|
349
|
+
| `cnpj` | `00.000.000/0000-00` |
|
|
350
|
+
| `cep` | `00000-000`, returns `null` until complete |
|
|
351
|
+
| `phone` | landline or mobile phone |
|
|
352
|
+
| `date` | `DD/MM/YYYY`, rejects invalid months and returns `Date \| null` |
|
|
353
|
+
| `month` | accepts only `01` to `12` |
|
|
354
|
+
| `money` | decimal money value from cents |
|
|
355
|
+
|
|
356
|
+
## `rawLength` and `patternLength`
|
|
254
357
|
|
|
255
358
|
```js
|
|
256
|
-
const filled =
|
|
257
|
-
const total
|
|
359
|
+
const filled = maskara.rawLength('cpf', value)
|
|
360
|
+
const total = maskara.patternLength('cpf')
|
|
258
361
|
|
|
259
|
-
const pct
|
|
260
|
-
const ready
|
|
261
|
-
const label
|
|
362
|
+
const pct = maskara('cpf', value).length / total * 100
|
|
363
|
+
const ready = maskara.is('cpf', value)
|
|
364
|
+
const label = `${filled} raw chars`
|
|
262
365
|
```
|
|
263
366
|
|
|
264
|
-
`patternLength`
|
|
367
|
+
`patternLength` counts the final formatted length, including literals:
|
|
265
368
|
|
|
266
369
|
```js
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
370
|
+
maskara.patternLength('##[/]##[/]####') // -> 10
|
|
371
|
+
maskara.patternLength('###[.]###[.]###[-]##') // -> 14
|
|
372
|
+
maskara.patternLength('{4}### #### #### ####') // -> 19
|
|
270
373
|
```
|
|
271
374
|
|
|
272
|
-
##
|
|
375
|
+
## Visual showcase
|
|
273
376
|
|
|
274
|
-
|
|
377
|
+
The `maskforge-showcase` project demonstrates the library with:
|
|
275
378
|
|
|
276
|
-
- playground
|
|
277
|
-
-
|
|
278
|
-
-
|
|
279
|
-
-
|
|
280
|
-
-
|
|
379
|
+
- a playground that applies the mask while the user types;
|
|
380
|
+
- a visual pattern map with slot, literal and expression blocks;
|
|
381
|
+
- examples for React, Vue and Vanilla JavaScript;
|
|
382
|
+
- interactive recipes for `validate`, `define`, `defineSlot` and `create`;
|
|
383
|
+
- local benchmark notes.
|
|
281
384
|
|
|
282
385
|
```bash
|
|
283
386
|
cd maskforge-showcase
|
|
@@ -285,54 +388,121 @@ npm install
|
|
|
285
388
|
npm run dev
|
|
286
389
|
```
|
|
287
390
|
|
|
288
|
-
##
|
|
391
|
+
## Local benchmark
|
|
289
392
|
|
|
290
|
-
|
|
291
|
-
Os valores variam por máquina, mas ajudam a orientar expectativas de uso em input.
|
|
393
|
+
Simple benchmark executed in Node/WSL with 200,000 iterations per case after warmup. Numbers vary by machine, but they help set expectations for input-level usage.
|
|
292
394
|
|
|
293
|
-
|
|
|
395
|
+
| Case | Result |
|
|
294
396
|
|---|---:|
|
|
295
|
-
| CPF format | 39
|
|
296
|
-
| Phone dynamic | 32
|
|
297
|
-
| Date validate | 64
|
|
298
|
-
| Raw extraction | 44
|
|
397
|
+
| CPF format | 39,705 ops/s |
|
|
398
|
+
| Phone dynamic | 32,455 ops/s |
|
|
399
|
+
| Date validate | 64,572 ops/s |
|
|
400
|
+
| Raw extraction | 44,729 ops/s |
|
|
299
401
|
|
|
300
402
|
```js
|
|
301
403
|
const iterations = 200000
|
|
302
404
|
for (let i = 0; i < iterations; i++) {
|
|
303
|
-
|
|
405
|
+
maskara('###[.]###[.]###[-]##', '12345678909')
|
|
304
406
|
}
|
|
305
407
|
```
|
|
306
408
|
|
|
307
|
-
## transform
|
|
409
|
+
## `transform` contract
|
|
308
410
|
|
|
309
411
|
```js
|
|
310
412
|
// transform(raw, masked, complete) => T
|
|
311
413
|
// validate(raw, masked, complete) => boolean
|
|
312
414
|
//
|
|
313
|
-
// raw
|
|
314
|
-
// masked
|
|
315
|
-
// complete
|
|
415
|
+
// raw -> input chars without literals
|
|
416
|
+
// masked -> formatted string
|
|
417
|
+
// complete -> true when every slot is filled
|
|
316
418
|
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
mask.define('money', {
|
|
322
|
-
pattern: '########[,]##',
|
|
323
|
-
transform: raw => parseInt(raw || '0', 10) / 100,
|
|
324
|
-
// retorna number sempre — mesmo parcial
|
|
325
|
-
})
|
|
419
|
+
// Without transform -> maskara.raw() always returns the raw string
|
|
420
|
+
// With transform -> maskara.raw() always returns whatever transform returns
|
|
421
|
+
```
|
|
326
422
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
423
|
+
## License
|
|
424
|
+
|
|
425
|
+
MIT
|
|
426
|
+
|
|
427
|
+
## React hook
|
|
428
|
+
|
|
429
|
+
`maskarajs/react` exports a small `useMaskara` hook. It lives in a separate entrypoint, so the core package stays framework-agnostic for users who do not use React.
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { useMaskara } from 'maskarajs/react'
|
|
433
|
+
|
|
434
|
+
export function CPFField() {
|
|
435
|
+
const cpf = useMaskara('###[.]###[.]###[-]##')
|
|
436
|
+
|
|
437
|
+
return <input {...cpf.inputProps({ inputMode: 'numeric' })} />
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### React Hook Form
|
|
442
|
+
|
|
443
|
+
Use `onValue` to send the raw value to React Hook Form while the input keeps the masked value on screen.
|
|
444
|
+
|
|
445
|
+
```tsx
|
|
446
|
+
import { Controller, useForm } from 'react-hook-form'
|
|
447
|
+
import { useMaskara } from 'maskarajs/react'
|
|
448
|
+
|
|
449
|
+
type FormValues = {
|
|
450
|
+
cpf: string
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function CPFController({ field }) {
|
|
454
|
+
const cpf = useMaskara('###[.]###[.]###[-]##', {
|
|
455
|
+
value: field.value,
|
|
456
|
+
onValue: field.onChange,
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
return (
|
|
460
|
+
<input
|
|
461
|
+
{...cpf.inputProps({
|
|
462
|
+
name: field.name,
|
|
463
|
+
onBlur: field.onBlur,
|
|
464
|
+
ref: field.ref,
|
|
465
|
+
inputMode: 'numeric',
|
|
466
|
+
})}
|
|
467
|
+
/>
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function Form() {
|
|
472
|
+
const { control, handleSubmit } = useForm<FormValues>({
|
|
473
|
+
defaultValues: { cpf: '' },
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
return (
|
|
477
|
+
<form onSubmit={handleSubmit(console.log)}>
|
|
478
|
+
<Controller
|
|
479
|
+
name="cpf"
|
|
480
|
+
control={control}
|
|
481
|
+
render={({ field }) => <CPFController field={field} />}
|
|
482
|
+
/>
|
|
483
|
+
</form>
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Zod
|
|
489
|
+
|
|
490
|
+
Keep the form value raw and validate it normally.
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
import { z } from 'zod'
|
|
494
|
+
|
|
495
|
+
export const schema = z.object({
|
|
496
|
+
cpf: z.string().length(11, 'CPF must contain 11 digits'),
|
|
333
497
|
})
|
|
334
498
|
```
|
|
335
499
|
|
|
336
|
-
|
|
500
|
+
### Yup
|
|
337
501
|
|
|
338
|
-
|
|
502
|
+
```ts
|
|
503
|
+
import * as yup from 'yup'
|
|
504
|
+
|
|
505
|
+
export const schema = yup.object({
|
|
506
|
+
cpf: yup.string().length(11, 'CPF must contain 11 digits').required(),
|
|
507
|
+
})
|
|
508
|
+
```
|