Skip to content

Commit

Permalink
feat: currency mask/unmask
Browse files Browse the repository at this point in the history
  • Loading branch information
brunobertolini committed May 6, 2023
1 parent fa5696c commit 8ab42e7
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 97 deletions.
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![.github/workflows/ci.yml](https://github.com/brunobertolini/remask/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/brunobertolini/remask/actions/workflows/ci.yml)

<h1 align="center">
<img src="logo.png" alt="reMask" />
<img src="logo.png" alt="Remask Logo" />
</h1>

> A lightweight, dependency free and framework agnostic multi-mask lib, with Typescript support.
Expand Down Expand Up @@ -30,10 +30,31 @@ Pattern can be a pattern array, so remask choose one pattern based on pattern/va
const patterns = ['999.999.999-99', '99.999.999/9999-99']

mask('12345678901', patterns) // gets firts pattern (999.999.999-99)
// 123.456.789-01
// => 123.456.789-01

mask('12345678000106', patterns) // gets second pattern (99.999.999/9999-99)
// 12.345.678/0001-06
// => 12.345.678/0001-06
```

and you can use `unmask` function to remove any mask pattern:

```js
unmask('12.345.678/0001-06')
// => 12345678000106
```

### Currency

In currency mask, Remask use [Intl.NumberFormater](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) to mask and unmask values, so you need pass [locale](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_negotiation) and currency params.

```js
import { currency } from 'remask'

currency.mask({ locale: 'pt-BR', currency: 'BRL', value: 123456.78 })
// => R$ 123.456,78

currency.unmask({ locale: 'pt-BR', currency: 'BRL', value: 'R$ 123.456,78' })
// => 123456.78
```

## License
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@commitlint/cli": "^17.4.4",
"@commitlint/config-conventional": "^17.4.4",
"@rollup/plugin-typescript": "^11.1.0",
"@types/jest": "^29.5.1",
"babel": "^6.23.0",
"babel-cli": "^6.26.0",
"babel-jest": "^29.5.0",
Expand All @@ -52,5 +53,8 @@
"rollup": "^3.21.4",
"standard-version": "^9.5.0",
"typescript": "^5.0.2"
},
"dependencies": {
"@types/intl": "^1.2.0"
}
}
1 change: 0 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const typescript = require('@rollup/plugin-typescript')
const pkg = require('./package.json')
const tsConfig = require('./tsconfig.json')

module.exports = [
{
Expand Down
185 changes: 185 additions & 0 deletions src/currency/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { mask, unmask } from './'

describe.only('mask', () => {
test('should format 0.01 (number) to R$ 0,01', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: 0.01 })
expect(result).toEqual('R$ 0,01')
})

test('should format 0.1 (number) to R$ 0,10', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: 0.1 })
expect(result).toEqual('R$ 0,10')
})

test('should format 1 (number) to R$ 1,00', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: 1 })
expect(result).toEqual('R$ 1,00')
})

test('should format 1.0 (number) to R$ 1,00', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: 1.0 })
expect(result).toEqual('R$ 1,00')
})

test('should format -0.01 (number) to -R$ 0,01', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: -0.01 })
expect(result).toEqual('-R$ 0,01')
})

test('should format -0.1 (number) to -R$ 0,10', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: -0.1 })
expect(result).toEqual('-R$ 0,10')
})

test('should format -1 (number) to -R$ 1,00', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: -1 })
expect(result).toEqual('-R$ 1,00')
})

test('should format -1.0 (number) to -R$ 1,00', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: -1.0 })
expect(result).toEqual('-R$ 1,00')
})

test('should format 1234567.89 (number) to R$ 1.234.567,89', () => {
const result = mask({ locale: 'pt-BR', currency: 'BRL', value: 1234567.89 })
expect(result).toEqual('R$ 1.234.567,89')
})

test('should format 1234.56 (number) to $1,234.56', () => {
const result = mask({ locale: 'en-US', currency: 'USD', value: 1234.56 })
expect(result).toEqual('$1,234.56')
})

test('should format 1234.56 (number) to 1.234,56 €', () => {
const result = mask({ locale: 'de-DE', currency: 'EUR', value: 1234.56 })
expect(result).toEqual('1.234,56 €')
})

test('should format 1234 (number) to JPY ¥1234', () => {
const result = mask({ locale: 'ja-JP', currency: 'JPY', value: 1234.56 })
expect(result).toEqual('¥1,235')
})
})

describe.only('unmask', () => {
test('should remove format 0.01 (number) to R$ 0,01', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: 'R$ 0,01',
})

expect(result).toEqual(0.01)
})

test('should remove format 0.1 (number) to R$ 0,10', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: 'R$ 0,10',
})

expect(result).toEqual(0.1)
})

test('should remove format 1 (number) to R$ 1,00', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: 'R$ 1,00',
})

expect(result).toEqual(1)
})

test('should remove format 1.0 (number) to R$ 1,00', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: 'R$ 1,00',
})

expect(result).toEqual(1.0)
})

test('should remove format -0.01 (number) to -R$ 0,01', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: '-R$ 0,01',
})

expect(result).toEqual(-0.01)
})

test('should remove format -0.1 (number) to -R$ 0,10', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: '-R$ 0,10',
})

expect(result).toEqual(-0.1)
})

test('should remove format -1 (number) to -R$ 1,00', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: '-R$ 1,00',
})

expect(result).toEqual(-1)
})

test('should remove format -1.0 (number) to -R$ 1,00', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: '-R$ 1,00',
})

expect(result).toEqual(-1.0)
})

test('should remove format 1234567.89 (number) to R$ 1.234.567,89', () => {
const result = unmask({
locale: 'pt-BR',
currency: 'BRL',
value: 'R$ 1.234.567,89',
})

expect(result).toEqual(1234567.89)
})

test('should remove format 1234.56 (number) to $1,234.56', () => {
const result = unmask({
locale: 'en-US',
currency: 'USD',
value: '$1,234.56',
})

expect(result).toEqual(1234.56)
})

test('should remove format 1234.56 (number) to 1.234,56 €', () => {
const result = unmask({
locale: 'de-DE',
currency: 'EUR',
value: '1.234,56 €',
})

expect(result).toEqual(1234.56)
})

test('should remove format 1234 (number) to JPY ¥1234', () => {
const result = unmask({
locale: 'ja-JP',
currency: 'JPY',
value: '¥1,234',
})

expect(result).toEqual(1234)
})
})
37 changes: 37 additions & 0 deletions src/currency/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
interface CurrncyMaskProps {
locale: string | string[]
currency: string
value: number | bigint
}

export const mask = ({ locale, currency, value }: CurrncyMaskProps): string => {
const { format } = new Intl.NumberFormat(`${locale}`, {
style: 'currency',
currency,
})

return format(value)
}

export const unmask = ({
locale,
currency,
value,
}: CurrncyMaskProps): number => {
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency,
})

const decimalPart = formatter
.formatToParts(1.1)
.find((item) => item.type === 'decimal')

const decimalSeparator = decimalPart ? decimalPart.value : false
const regex = new RegExp(`[^\-0-9${decimalSeparator}]`, 'g')
const unformatted = `${value}`.replace(regex, '')

return decimalSeparator && decimalSeparator !== '.'
? parseFloat(unformatted.replace(decimalSeparator, '.'))
: parseFloat(unformatted)
}
92 changes: 3 additions & 89 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,4 @@
interface PatternOptions {
// Placeholder option to represent remaining characters to be entered
placeholder?: string | undefined
}
import * as currency from './currency'

interface RegExpMapProps {
[key: string]: RegExp
}

const regexMap: RegExpMapProps = {
9: new RegExp(/[0-9]/),
A: new RegExp(/[a-zA-Z]/),
S: new RegExp(/[0-9a-zA-Z]/),
}

export const unMask = (value: string) => value.replace(/\W/g, '')

export const masker = (
value: string,
pattern: string,
options?: PatternOptions
) => {
const patternCharList = pattern.split('')
const unmaskedValue = unMask(String(value))
const output = []

let valueIndex = 0

for (let i = 0; i < patternCharList.length; i++) {
const patternChar = pattern[i]
const valueChar = unmaskedValue[valueIndex]
const regex = regexMap[patternChar]

if (valueChar === patternChar) {
output[i] = valueChar
valueIndex++
continue
}

if (!regex) {
output[i] = patternChar
continue
}

if (valueChar && regex.test(valueChar)) {
output[i] = valueChar
valueIndex++
continue
}

if (options?.placeholder) {
output[i] = options.placeholder
continue
}

if (
output.length < patternCharList.length &&
/\W/.test(output[output.length - 1])
) {
output.pop()
}

break
}

return output.join('')
}

const multimasker = (
value: string,
patterns: string[],
options?: PatternOptions
) =>
masker(
value,
patterns.reduce(
(memo, pattern) => (value.length <= unMask(memo).length ? memo : pattern),
patterns[0]
),
options
)

export const mask = (
value: string,
pattern: string | string[],
options?: PatternOptions
): string =>
typeof pattern === 'string'
? masker(value, pattern || '', options)
: multimasker(value, pattern, options)
export * from './masker'
export { currency }
2 changes: 1 addition & 1 deletion src/index.test.ts → src/masker/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mask, unMask } from '.'
import { mask, unMask } from './'

test('should mask correctly', () => {
const result = mask('123', '9.9.9')
Expand Down

0 comments on commit 8ab42e7

Please sign in to comment.