maskarajs 1.0.0 → 1.0.3

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,94 +1,226 @@
1
- # mask.js
1
+ # maskarajs
2
2
 
3
- Engine de máscaras declarativo para JavaScript — framework-agnostic, zero dependências, ~4kb.
3
+ Declarative input mask engine for JavaScript. Framework-agnostic, zero dependencies, and small enough to live close to your form fields.
4
4
 
5
- ## Sintaxe
5
+ ## Syntax
6
6
 
7
- | Token | Aceita |
7
+ | Token | Accepts |
8
8
  |---|---|
9
- | `#` | qualquer dígito (09) |
10
- | `@` | qualquer letra (az, AZ, acentuados) |
11
- | `*` | qualquer caractere |
12
- | `[texto]` | literal fixo (inserido/removido automaticamente) |
13
- | `{expr}` | slot livre testa um char contra a expressão |
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
- ### Modificador `{expr}`
26
+ ## Install
16
27
 
28
+ ```bash
29
+ npm install maskarajs
17
30
  ```
18
- {4} → só o char '4'
19
- {0-4} → intervalo: ch >= '0' && ch <= '4'
20
- {013} → conjunto: '0', '1' ou '3'
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
- import mask from './mask.js'
30
-
31
- mask(pattern, value) // aplica máscara string formatada
32
- mask.raw(pattern, value) // valor limpo / resultado do transform
33
- mask.is(pattern, value) // padrão completo? boolean
34
- mask.hint(pattern) // placeholder string
35
- mask.format(pattern, value) // alias semântico de mask()
36
- mask.rawLength(pattern, value) // chars de input preenchidos → number
37
- mask.patternLength(pattern) // tamanho mascarado completo → number
38
- mask.define(name, definition) // registra máscara nomeada
39
- mask.undefine(name) // remove do registry
40
- mask.names() // lista nomes registrados → string[]
41
- mask.defineSlot(symbol, definition)// cria/sobrescreve token de input
42
- mask.undefineSlot(symbol) // remove token customizado
43
- mask.slots() // lista tokens de input disponíveis
44
- mask.on(input, pattern, options) // vincula a input DOM → cleanup()
45
- mask.create(presets) // instância isolada com registry próprio
46
- ```
47
-
48
- ## Exemplos
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
54
+ ```
55
+
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
+ ## Vue 3 adapter
98
+
99
+ The Vue adapter is optional and lives in `maskarajs/vue`. Its default export is a Vue 3 directive ready for `v-maskara`. Use it locally in a component or register it once as a plugin.
100
+
101
+ ### Local directive
102
+
103
+ ```vue
104
+ <script setup lang="ts">
105
+ import { maskaraDirective as vMaskara } from 'maskarajs/vue'
106
+
107
+ const pattern = '###[.]###[.]###[-]##'
108
+ </script>
109
+
110
+ <template>
111
+ <input v-maskara="pattern" inputmode="numeric" />
112
+ </template>
113
+ ```
114
+
115
+ ### With `v-model` and raw value callback
116
+
117
+ ```vue
118
+ <script setup lang="ts">
119
+ import { ref } from 'vue'
120
+ import { maskaraDirective as vMaskara } from 'maskarajs/vue'
121
+
122
+ const document = ref('')
123
+ const rawDocument = ref('')
124
+ </script>
125
+
126
+ <template>
127
+ <input
128
+ v-model="document"
129
+ v-maskara="{
130
+ pattern: '###[.]###[.]###[-]##',
131
+ onValue: value => rawDocument = value
132
+ }"
133
+ inputmode="numeric"
134
+ />
135
+ </template>
136
+ ```
137
+
138
+ The directive listens during the capture phase, so Vue's `v-model` receives the masked value during the same input event.
139
+
140
+ ### Register globally
141
+
142
+ ```ts
143
+ import { createApp } from 'vue'
144
+ import { createMaskaraPlugin } from 'maskarajs/vue'
145
+ import App from './App.vue'
146
+
147
+ createApp(App)
148
+ .use(createMaskaraPlugin())
149
+ .mount('#app')
150
+ ```
151
+
152
+ Then use it anywhere:
153
+
154
+ ```vue
155
+ <input v-maskara="'#####[-]###'" />
156
+ ```
157
+
158
+ ### Use an isolated engine
159
+
160
+ ```ts
161
+ import maskara from 'maskarajs'
162
+ import { createMaskaraPlugin } from 'maskarajs/vue'
163
+
164
+ const br = maskara.create({
165
+ cpf: { pattern: '###[.]###[.]###[-]##' },
166
+ phone: { pattern: ['[(]##[)] ####[-]####', '[(]##[)] #####[-]####'] },
167
+ })
168
+
169
+ app.use(createMaskaraPlugin({ engine: br }))
170
+ ```
171
+
172
+ You can also create a directive instance manually:
173
+
174
+ ```ts
175
+ import { createMaskaraDirective } from 'maskarajs/vue'
176
+
177
+ const vMaskara = createMaskaraDirective({ engine: br })
178
+ ```
179
+
180
+ ## Examples
49
181
 
50
182
  ```js
51
- // Padrão único
52
- mask('###[.]###[.]###[-]##', '12345678909')
53
- // '123.456.789-09'
183
+ // Single pattern
184
+ maskara('###[.]###[.]###[-]##', '12345678909')
185
+ // -> '123.456.789-09'
54
186
 
55
- // Padrão dinâmico (array) escolhe pelo tamanho do input
56
- mask(['[(]##[)] ####[-]####', '[(]##[)] #####[-]####'], '11987654321')
57
- // '(11) 98765-4321'
187
+ // Dynamic pattern array: chooses by input size
188
+ maskara(['[(]##[)] ####[-]####', '[(]##[)] #####[-]####'], '11987654321')
189
+ // -> '(11) 98765-4321'
58
190
 
59
- // Slot com restrição
60
- mask('{4}### #### #### ####', '4111111111111111')
61
- // '4111 1111 1111 1111' (só aceita Visa — começa com 4)
191
+ // Restricted slot
192
+ maskara('{4}### #### #### ####', '4111111111111111')
193
+ // -> '4111 1111 1111 1111'
62
194
 
63
- // Slot com regex
64
- mask('{[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')
65
- // '1a2b3c'
195
+ // Regex slot
196
+ 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')
197
+ // -> '1a2b3c'
66
198
  ```
67
199
 
68
- ### Mais casos comuns
200
+ ### Common cases
69
201
 
70
202
  ```js
71
- // CEP
72
- mask('#####[-]###', '01310930')
73
- // '01310-930'
203
+ // Brazilian ZIP code
204
+ maskara('#####[-]###', '01310930')
205
+ // -> '01310-930'
74
206
 
75
207
  // CNPJ
76
- mask('##[.]###[.]###[/]####[-]##', '11222333000181')
77
- // '11.222.333/0001-81'
208
+ maskara('##[.]###[.]###[/]####[-]##', '11222333000181')
209
+ // -> '11.222.333/0001-81'
78
210
 
79
- // Cartão Visa
80
- mask('{4}### #### #### ####', '5111111111111111')
81
- // '' (primeiro char não passou)
211
+ // Visa card
212
+ maskara('{4}### #### #### ####', '5111111111111111')
213
+ // -> '' (the first char did not pass)
82
214
 
83
- // Hex color com paste sujo
84
- mask('{[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')
85
- // '12b3c'
215
+ // Hex color with dirty paste
216
+ 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')
217
+ // -> '12b3c'
86
218
  ```
87
219
 
88
- ## mask.define com transform
220
+ ## `maskara.define` with `transform`
89
221
 
90
222
  ```js
91
- mask.define('date', {
223
+ maskara.define('date', {
92
224
  pattern: '##[/]##[/]####',
93
225
  validate: (raw, masked, complete) => {
94
226
  if (raw.length < 4) return true
@@ -102,23 +234,21 @@ mask.define('date', {
102
234
  },
103
235
  })
104
236
 
105
- mask('date', '01012025') // '01/01/2025'
106
- mask.raw('date', '01/01/2025') // Date(2025-01-01)
107
- mask.raw('date', '01/01') // null (incompleto — transform decidiu)
108
- mask.is('date', '01/01/2025') // true
109
- mask.hint('date') // '00/00/0000'
110
- mask.rawLength('date', '01/01') // 4
111
- mask.patternLength('date') // 10
237
+ maskara('date', '01012025') // -> '01/01/2025'
238
+ maskara.raw('date', '01/01/2025') // -> Date(2025-01-01)
239
+ maskara.raw('date', '01/01') // -> null
240
+ maskara.is('date', '01/01/2025') // -> true
241
+ maskara.hint('date') // -> '00/00/0000'
242
+ maskara.rawLength('date', '01/01') // -> 4
243
+ maskara.patternLength('date') // -> 10
112
244
  ```
113
245
 
114
- ## validate validação incremental
246
+ ## `validate`: incremental validation
115
247
 
116
- Use `validate` em máscaras nomeadas quando a regra depende do que foi digitado.
117
- Isso resolve casos onde a sintaxe caractere a caractere não é suficiente, como mês
118
- entre `01` e `12`.
248
+ 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
249
 
120
250
  ```js
121
- mask.define('month', {
251
+ maskara.define('month', {
122
252
  pattern: '{0-1}#',
123
253
  validate: (raw, masked, complete) => {
124
254
  if (!complete) return true
@@ -127,98 +257,129 @@ mask.define('month', {
127
257
  },
128
258
  })
129
259
 
130
- mask('month', '12') // '12'
131
- mask('month', '19') // '1' (o 9 é recusado)
132
- mask.is('month', '12') // true
133
- mask.is('month', '19') // false
260
+ maskara('month', '12') // -> '12'
261
+ maskara('month', '19') // -> '1'
262
+ maskara.is('month', '12') // -> true
263
+ maskara.is('month', '19') // -> false
134
264
  ```
135
265
 
136
- ## Slots customizados — sua própria linguagem de pattern
266
+ ## Conditional masks
137
267
 
138
- Além de `#`, `@`, `*` e `{expr}`, você pode criar símbolos que façam sentido
139
- para o seu time. Isso ajuda quando o projeto quer uma linguagem mais expressiva,
140
- mais curta ou alinhada ao domínio do produto.
268
+ 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.
269
+
270
+ `select(raw, value)` receives the value without literals from all possible patterns and returns a key from `patterns`.
141
271
 
142
272
  ```js
143
- mask.defineSlot('N', {
273
+ const cpf = '###[.]###[.]###[-]##'
274
+ const cnpj = '##[.]###[.]###[/]####[-]##'
275
+
276
+ maskara.define('smartDocument', {
277
+ patterns: { cpf, cnpj },
278
+ select: raw => raw.includes('123') ? 'cnpj' : 'cpf',
279
+ })
280
+
281
+ maskara('smartDocument', '98765432100')
282
+ // -> '987.654.321-00'
283
+
284
+ maskara('smartDocument', '12345678000199')
285
+ // -> '12.345.678/0001-99'
286
+ ```
287
+
288
+ It also works with isolated instances:
289
+
290
+ ```js
291
+ const forms = maskara.create({
292
+ smartDocument: {
293
+ patterns: { cpf, cnpj },
294
+ select: raw => raw.startsWith('9') ? 'cpf' : 'cnpj',
295
+ },
296
+ })
297
+ ```
298
+
299
+ 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.
300
+
301
+ ## Custom slots: create your own pattern language
302
+
303
+ 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.
304
+
305
+ ```js
306
+ maskara.defineSlot('N', {
144
307
  test: ch => /\d/.test(ch),
145
308
  hint: '0',
146
309
  })
147
310
 
148
- mask('NNN[-]NN', '12345') // '123-45'
149
- mask.hint('NNN[-]NN') // '000-00'
150
- mask.slots() // ['#', '@', '*', 'N']
311
+ maskara('NNN[-]NN', '12345') // -> '123-45'
312
+ maskara.hint('NNN[-]NN') // -> '000-00'
313
+ maskara.slots() // -> ['#', '@', '*', 'N']
151
314
  ```
152
315
 
153
- Você também pode passar uma `RegExp` direta ou apenas uma função:
316
+ You can also pass a `RegExp` or a function directly:
154
317
 
155
318
  ```js
156
- mask.defineSlot('H', /[0-9a-f]/i)
157
- mask.defineSlot('V', ch => 'AEIOUaeiou'.includes(ch))
319
+ maskara.defineSlot('H', /[0-9a-f]/i)
320
+ maskara.defineSlot('V', ch => 'AEIOUaeiou'.includes(ch))
158
321
 
159
- mask('HHHHHH', '1a2b3c') // '1a2b3c'
160
- mask('VVV', 'mask') // 'a'
322
+ maskara('HHHHHH', '1a2b3c') // -> '1a2b3c'
323
+ maskara('VVV', 'mask') // -> 'a'
161
324
  ```
162
325
 
163
- O registro global afeta o engine global. Para bibliotecas, design systems ou
164
- produtos com regras próprias, prefira uma instância isolada:
326
+ Global slots affect the global engine. For libraries, design systems or products with their own rules, prefer an isolated instance:
165
327
 
166
328
  ```js
167
- const forge = mask.create()
329
+ const forge = maskara.create()
168
330
 
169
331
  forge.defineSlot('N', { test: ch => /\d/.test(ch), hint: '0' })
170
332
  forge.defineSlot('#', { test: ch => /[1-9]/.test(ch), hint: '1' })
171
333
 
172
- forge('NNN[-]NN', '12345') // '123-45'
173
- forge('#', '0') // '' (# foi sobrescrito nesta instância)
174
- mask('#', '0') // '0' (global continua igual)
334
+ forge('NNN[-]NN', '12345') // -> '123-45'
335
+ forge('#', '0') // -> ''
336
+ maskara('#', '0') // -> '0' (global stays unchanged)
175
337
  ```
176
338
 
177
- Se um símbolo registrado precisar aparecer como texto fixo, escape com
178
- colchetes:
339
+ If a registered symbol must appear as fixed text, escape it with brackets:
179
340
 
180
341
  ```js
181
- mask.defineSlot('N', /\d/)
342
+ maskara.defineSlot('N', /\d/)
182
343
 
183
- mask('[N]##', '45') // 'N45'
184
- mask('N##', '145') // '145'
344
+ maskara('[N]##', '45') // -> 'N45'
345
+ maskara('N##', '145') // -> '145'
185
346
  ```
186
347
 
187
- ## mask.on qualquer framework
348
+ ## `maskara.on`: any framework
188
349
 
189
350
  ```js
190
351
  // Vanilla JS
191
- const off = mask.on(inputEl, 'cpf', {
192
- onValue: raw => setState(raw),
193
- onMasked: masked => setLabel(masked),
352
+ const off = maskara.on(inputEl, 'cpf', {
353
+ onValue: raw => setState(raw),
354
+ onMaskara: masked => setLabel(masked),
194
355
  })
195
- off() // remove listeners
356
+ off()
196
357
 
197
358
  // React
198
359
  useEffect(() => {
199
- return mask.on(ref.current, 'date', {
200
- onValue: (date) => setValue(date), // Date | null
360
+ return maskara.on(ref.current, 'date', {
361
+ onValue: date => setValue(date), // Date | null
201
362
  })
202
363
  }, [])
203
364
 
204
365
  // Vue
205
366
  onMounted(() => {
206
- mask.on(inputRef.value, 'phone', {
207
- onValue: v => emit('update:modelValue', v),
367
+ maskara.on(inputRef.value, 'phone', {
368
+ onValue: value => emit('update:modelValue', value),
208
369
  })
209
370
  })
210
371
 
211
372
  // Svelte action
212
373
  function maskAction(node, pattern) {
213
- const off = mask.on(node, pattern)
374
+ const off = maskara.on(node, pattern)
214
375
  return { destroy: off }
215
376
  }
216
377
  ```
217
378
 
218
- ## mask.create instâncias isoladas
379
+ ## `maskara.create`: isolated instances
219
380
 
220
381
  ```js
221
- export const maskBR = mask.create({
382
+ export const maskaraBR = maskara.create({
222
383
  cpf: { pattern: '###[.]###[.]###[-]##' },
223
384
  cnpj: { pattern: '##[.]###[.]###[/]####[-]##' },
224
385
  phone: { pattern: ['[(]##[)] ####[-]####', '[(]##[)] #####[-]####'] },
@@ -231,53 +392,78 @@ export const maskBR = mask.create({
231
392
  return isNaN(dt) ? null : dt
232
393
  },
233
394
  },
234
- money: {
235
- pattern: '########[,]##',
236
- transform: raw => parseInt(raw || '0', 10) / 100,
237
- },
238
395
  })
239
396
 
240
- export const maskUS = mask.create({
397
+ export const maskaraUS = maskara.create({
241
398
  ssn: { pattern: '###[-]##[-]####' },
242
399
  zip: { pattern: '#####[-]####' },
243
400
  phone: { pattern: '[(]###[)] ###[-]####' },
244
401
  })
245
402
 
246
- // Mesma API, registries isolados — alterações em maskBR não afetam maskUS
247
- maskBR('cpf', '12345678909') // '123.456.789-09'
248
- maskBR.raw('date', '01/01/2025') // Date
249
- maskBR.names() // ['cpf', 'cnpj', 'phone', 'cep', 'date', 'money']
250
- maskUS.names() // → ['ssn', 'zip', 'phone']
403
+ maskaraBR('cpf', '12345678909') // -> '123.456.789-09'
404
+ maskaraBR.raw('date', '01/01/2025') // -> Date
405
+ maskaraBR.names() // -> ['cpf', 'cnpj', 'phone', 'cep', 'date']
406
+ maskaraUS.names() // -> ['ssn', 'zip', 'phone']
407
+ ```
408
+
409
+ ## Official Brazilian presets
410
+
411
+ If you want common Brazilian masks ready to use, import `maskarajs/presets/br` and create an isolated engine from it.
412
+
413
+ ```ts
414
+ import maskara from 'maskarajs'
415
+ import { br, type BrazilPresetRegistry } from 'maskarajs/presets/br'
416
+
417
+ const maskaraBR = maskara.create<BrazilPresetRegistry>(br)
418
+
419
+ maskaraBR('cpf', '12345678909') // -> '123.456.789-09'
420
+ maskaraBR('cnpj', '11222333000181') // -> '11.222.333/0001-81'
421
+ maskaraBR('phone', '11987654321') // -> '(11) 98765-4321'
422
+ maskaraBR.raw('cep', '01310-930') // -> '01310930'
423
+ maskaraBR.raw('date', '01/12/2025') // -> Date
424
+ maskaraBR.raw('money', '1299,90') // -> 1299.9
251
425
  ```
252
426
 
253
- ## rawLength e patternLength
427
+ The preset includes:
428
+
429
+ | Name | Pattern / behavior |
430
+ |---|---|
431
+ | `cpf` | `000.000.000-00` |
432
+ | `cnpj` | `00.000.000/0000-00` |
433
+ | `cep` | `00000-000`, returns `null` until complete |
434
+ | `phone` | landline or mobile phone |
435
+ | `date` | `DD/MM/YYYY`, rejects invalid months and returns `Date \| null` |
436
+ | `month` | accepts only `01` to `12` |
437
+ | `money` | decimal money value from cents |
438
+
439
+ ## `rawLength` and `patternLength`
254
440
 
255
441
  ```js
256
- const filled = mask.rawLength('cpf', value) // chars preenchidos (sem literais)
257
- const total = mask.patternLength('cpf') // tamanho total mascarado
442
+ const filled = maskara.rawLength('cpf', value)
443
+ const total = maskara.patternLength('cpf')
258
444
 
259
- const pct = mask('cpf', value).length / total * 100
260
- const ready = mask.is('cpf', value) // habilita submit
261
- const label = `${filled} raw chars` // "7 raw chars"
445
+ const pct = maskara('cpf', value).length / total * 100
446
+ const ready = maskara.is('cpf', value)
447
+ const label = `${filled} raw chars`
262
448
  ```
263
449
 
264
- `patternLength` conta o tamanho final mascarado, incluindo literais. Exemplos:
450
+ `patternLength` counts the final formatted length, including literals:
265
451
 
266
452
  ```js
267
- mask.patternLength('##[/]##[/]####') // 10
268
- mask.patternLength('###[.]###[.]###[-]##') // 14
269
- mask.patternLength('{4}### #### #### ####') // 19
453
+ maskara.patternLength('##[/]##[/]####') // -> 10
454
+ maskara.patternLength('###[.]###[.]###[-]##') // -> 14
455
+ maskara.patternLength('{4}### #### #### ####') // -> 19
270
456
  ```
271
457
 
272
- ## Showcase visual
458
+ ## Visual showcase
273
459
 
274
- O projeto `maskforge-showcase` demonstra a lib com:
460
+ The `maskforge-showcase` project demonstrates the library with:
275
461
 
276
- - playground destacado com máscara aplicada enquanto o usuário digita;
277
- - visualização do pattern em blocos: slot, literal e expressão;
278
- - exemplos para React, Vue e Vanilla;
279
- - receitas interativas para `validate`, `define` e `create`;
280
- - análise de benchmark local.
462
+ - a playground that applies the mask while the user types;
463
+ - a visual pattern map with slot, literal and expression blocks;
464
+ - examples for React, Vue and Vanilla JavaScript;
465
+ - interactive recipes for `validate`, `define`, `defineSlot` and `create`;
466
+ - local benchmark notes.
281
467
 
282
468
  ```bash
283
469
  cd maskforge-showcase
@@ -285,54 +471,121 @@ npm install
285
471
  npm run dev
286
472
  ```
287
473
 
288
- ## Benchmark local
474
+ ## Local benchmark
289
475
 
290
- Benchmark simples rodado em Node/WSL com 200.000 iterações por caso após warmup.
291
- Os valores variam por máquina, mas ajudam a orientar expectativas de uso em input.
476
+ 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
477
 
293
- | Caso | Resultado |
478
+ | Case | Result |
294
479
  |---|---:|
295
- | CPF format | 39.705 ops/s |
296
- | Phone dynamic | 32.455 ops/s |
297
- | Date validate | 64.572 ops/s |
298
- | Raw extraction | 44.729 ops/s |
480
+ | CPF format | 39,705 ops/s |
481
+ | Phone dynamic | 32,455 ops/s |
482
+ | Date validate | 64,572 ops/s |
483
+ | Raw extraction | 44,729 ops/s |
299
484
 
300
485
  ```js
301
486
  const iterations = 200000
302
487
  for (let i = 0; i < iterations; i++) {
303
- mask('###[.]###[.]###[-]##', '12345678909')
488
+ maskara('###[.]###[.]###[-]##', '12345678909')
304
489
  }
305
490
  ```
306
491
 
307
- ## transform — contrato
492
+ ## `transform` contract
308
493
 
309
494
  ```js
310
495
  // transform(raw, masked, complete) => T
311
496
  // validate(raw, masked, complete) => boolean
312
497
  //
313
- // raw string com os chars de input sem literais
314
- // masked string formatada com a máscara
315
- // complete true quando todos os slots estão preenchidos
498
+ // raw -> input chars without literals
499
+ // masked -> formatted string
500
+ // complete -> true when every slot is filled
316
501
  //
317
- // Sem transform mask.raw() retorna string crua, sempre
318
- // Com transform → mask.raw() retorna o que transform devolver, sempre
319
- // O transform decide o que fazer com input parcial
320
-
321
- mask.define('money', {
322
- pattern: '########[,]##',
323
- transform: raw => parseInt(raw || '0', 10) / 100,
324
- // retorna number sempre — mesmo parcial
325
- })
502
+ // Without transform -> maskara.raw() always returns the raw string
503
+ // With transform -> maskara.raw() always returns whatever transform returns
504
+ ```
326
505
 
327
- mask.define('date', {
328
- pattern: '##[/]##[/]####',
329
- transform: (raw, masked, complete) => {
330
- if (!complete) return null // null enquanto incompleto
331
- return new Date(...) // Date quando completo
332
- },
506
+ ## License
507
+
508
+ MIT
509
+
510
+ ## React hook
511
+
512
+ `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.
513
+
514
+ ```tsx
515
+ import { useMaskara } from 'maskarajs/react'
516
+
517
+ export function CPFField() {
518
+ const cpf = useMaskara('###[.]###[.]###[-]##')
519
+
520
+ return <input {...cpf.inputProps({ inputMode: 'numeric' })} />
521
+ }
522
+ ```
523
+
524
+ ### React Hook Form
525
+
526
+ Use `onValue` to send the raw value to React Hook Form while the input keeps the masked value on screen.
527
+
528
+ ```tsx
529
+ import { Controller, useForm } from 'react-hook-form'
530
+ import { useMaskara } from 'maskarajs/react'
531
+
532
+ type FormValues = {
533
+ cpf: string
534
+ }
535
+
536
+ function CPFController({ field }) {
537
+ const cpf = useMaskara('###[.]###[.]###[-]##', {
538
+ value: field.value,
539
+ onValue: field.onChange,
540
+ })
541
+
542
+ return (
543
+ <input
544
+ {...cpf.inputProps({
545
+ name: field.name,
546
+ onBlur: field.onBlur,
547
+ ref: field.ref,
548
+ inputMode: 'numeric',
549
+ })}
550
+ />
551
+ )
552
+ }
553
+
554
+ export function Form() {
555
+ const { control, handleSubmit } = useForm<FormValues>({
556
+ defaultValues: { cpf: '' },
557
+ })
558
+
559
+ return (
560
+ <form onSubmit={handleSubmit(console.log)}>
561
+ <Controller
562
+ name="cpf"
563
+ control={control}
564
+ render={({ field }) => <CPFController field={field} />}
565
+ />
566
+ </form>
567
+ )
568
+ }
569
+ ```
570
+
571
+ ### Zod
572
+
573
+ Keep the form value raw and validate it normally.
574
+
575
+ ```ts
576
+ import { z } from 'zod'
577
+
578
+ export const schema = z.object({
579
+ cpf: z.string().length(11, 'CPF must contain 11 digits'),
333
580
  })
334
581
  ```
335
582
 
336
- ## Licença
583
+ ### Yup
337
584
 
338
- MIT
585
+ ```ts
586
+ import * as yup from 'yup'
587
+
588
+ export const schema = yup.object({
589
+ cpf: yup.string().length(11, 'CPF must contain 11 digits').required(),
590
+ })
591
+ ```