Skip to content

Commit 9f5cc59

Browse files
committed
fix(dynamic): changes the way we choose the mask from the dynamics array
Prior we were choosing the mask based on the size of the masked value and exited early if a shorter mask was encountered in the loop. The logic is now updated to choose a mask based on the which one gives us the most information from the input. This also seems to fix the issue faced when using this library for dollar amounts. fixes #1
1 parent abe8122 commit 9f5cc59

File tree

2 files changed

+40
-17
lines changed

2 files changed

+40
-17
lines changed

src/masker.js

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,50 @@ export function setTokens(tokens) {
1515
/**
1616
* Given an array of masks, determines which one is the appropriate one based on the value
1717
*
18-
* @param {String} value the value to mask
18+
* @param {String} inputValue the inputValue value to mask
1919
* @param {{masks: [String]}} config
2020
* @param {Array} config.masks the list of masks to choose from
2121
* @returns {FacadeValue} facade value object
2222
*/
23-
export function dynamic(value, config = {}) {
23+
export function dynamic(inputValue, config = {}) {
2424
const masks = config.masks.slice().sort((a, b) => a.length - b.length)
2525
const withConfig = (overrides) => Object.assign({}, config, overrides)
2626

27-
const nextFacadeIsLarger = (currentMask, nextMask) => {
28-
const nextMaskedVal = formatter(value, withConfig({ mask: nextMask, short: true }))
29-
return nextMaskedVal.masked.length > currentMask.length
27+
// this method will choose a facade based on which one exposes more data from the input
28+
const chooseBestFacade = (currentValue, nextMask) => {
29+
const nextValue = formatter(inputValue, withConfig({ mask: nextMask }))
30+
const currentLength = currentValue.unmasked.length
31+
const nextLength = nextValue.unmasked.length
32+
return nextLength > currentLength ? nextValue : currentValue
3033
}
3134

32-
for (let i = 0; i < masks.length; i++) {
33-
const currentMask = masks[i]
34-
const nextMask = masks[i + 1]
35+
// empty masks array
36+
if (!masks.length) {
37+
return new FacadeValue()
38+
}
3539

36-
if (!nextMask || !nextFacadeIsLarger(currentMask, nextMask)) {
37-
return formatter(value, withConfig({ mask: currentMask }))
38-
}
40+
const firstMask = masks.shift()
41+
let output = formatter(inputValue, withConfig({ mask: firstMask }))
42+
43+
while (masks.length) {
44+
const nextMask = masks.shift()
45+
output = chooseBestFacade(output, nextMask)
3946
}
4047

41-
return new FacadeValue() // empty masks
48+
return output
4249
}
4350

4451
/**
4552
* Formats the value based on the given masking rule
4653
*
4754
* @param {string} value the value to mask
48-
* @param {{mask: String, tokens: Object, short: Boolean, prepend: Boolean}} config
55+
* @param {{mask: String, tokens: Object, prepend: Boolean}} config
4956
* @param {string} config.mask the masking string
5057
* @param {object} config.tokens the tokens to add/override to the global
51-
* @param {boolean} config.short keep the input short as possible by not auto appending masking characters
5258
* @param {boolean} config.prepend whether or not to add masking characters to the input before the user types.
5359
*/
5460
export function formatter(value = '', config = {}) {
55-
let { mask = '', tokens, short = false, prepend = false } = config
61+
let { mask = '', tokens, prepend = false } = config
5662

5763
// append/override global tokens instead of complete override
5864
tokens = tokens ? Object.assign({}, tokenDefinitions, tokens) : tokenDefinitions
@@ -73,7 +79,7 @@ export function formatter(value = '', config = {}) {
7379
let char = value[valueIndex]
7480

7581
// no more input charactors and next charactor is a masked char
76-
if (!char && (short || masker)) break
82+
if (!char && masker) break
7783

7884
if (masker && !escaped) {
7985
// when is escape char, do not mask, just continue
@@ -103,7 +109,7 @@ export function formatter(value = '', config = {}) {
103109

104110
// if there is no unmasked value, set masked to empty to avoid showing masking
105111
// characters in an otherwise empty input, unless prepend is set ot true
106-
if (prepend || (output.unmasked && !short)) {
112+
if (prepend || output.unmasked) {
107113
output.masked += accumulator
108114
}
109115

tests/dynamic.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,20 @@ test('bank account', () => {
4848
expect(dynamic('123456789', { masks })).toMatchObject({ masked: '12345678-9', unmasked: '123456789' })
4949
expect(dynamic('1234567890', { masks })).toMatchObject({ masked: '123456789-0', unmasked: '1234567890' })
5050
})
51+
52+
test('US Currency', () => {
53+
var masks = ['$###', '$#,###', '$##,###', '$###,###']
54+
expect(dynamic('12', { masks })).toMatchObject({ masked: '$12', unmasked: '12' })
55+
expect(dynamic('123', { masks })).toMatchObject({ masked: '$123', unmasked: '123' })
56+
expect(dynamic('1234', { masks })).toMatchObject({ masked: '$1,234', unmasked: '1234' })
57+
expect(dynamic('12345', { masks })).toMatchObject({ masked: '$12,345', unmasked: '12345' })
58+
expect(dynamic('123456', { masks })).toMatchObject({ masked: '$123,456', unmasked: '123456' })
59+
})
60+
61+
test('UK Postal code', () => {
62+
var masks = ['A# #AA', 'AXX #AA', 'AA#X #AA']
63+
expect(dynamic('B11', { masks })).toMatchObject({ masked: 'B1 1', unmasked: 'B11' })
64+
expect(dynamic('B112', { masks })).toMatchObject({ masked: 'B11 2', unmasked: 'B112' })
65+
expect(dynamic('BB99', { masks })).toMatchObject({ masked: 'BB9 9', unmasked: 'BB99' })
66+
expect(dynamic('BB990', { masks })).toMatchObject({ masked: 'BB99 0', unmasked: 'BB990' })
67+
})

0 commit comments

Comments
 (0)