Skip to content

Commit 347252d

Browse files
committed
feat: add support for repeating characters
Simlar to regex, add an asterisk after a mask character to mark it as optionally repeating (0 or more). BREAKING CHANGE: The asterisk is now a special character, if you were using it before in your masks it must be escaped or changed in your token definition.
1 parent 2ee837d commit 347252d

File tree

4 files changed

+46
-17
lines changed

4 files changed

+46
-17
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export default {
6363
+ `A` = alpha characters, transformed to uppercase
6464
+ `a` = alpha characters, transformed to lowercase
6565
+ `\` = escape any of the above characters
66-
+ `?` = mark the preceding character as optional
66+
+ `?` = mark the preceding character as optional [0 or 1]
67+
+ `*` = mark the preceding character as optional & repeating [0 or more]
6768

6869
See the [token source file](https://github.com/RonaldJerez/vue-input-facade/blob/master/src/tokens.js) for definition signature
6970

src/masker.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ let tokenDefinitions = defaultTokens
99
* @param {object} tokens the new token object
1010
*/
1111
export function setTokens(tokens) {
12+
/* istanbul ignore if */
1213
if (!tokens) return
1314
tokenDefinitions = tokens
1415
}
@@ -67,31 +68,38 @@ export function formatter(value, config) {
6768

6869
let output = new FacadeValue()
6970
let escaped = false
70-
let optional = false
7171

7272
let valueIndex = 0
7373
let maskIndex = 0
7474
let accumulator = ''
7575

76+
// gets some information about the mask before formating
77+
function getMetaData(masker) {
78+
const nextMaskChar = mask[maskIndex + 1]
79+
const nextMasker = tokens[nextMaskChar]
80+
81+
return {
82+
escape: !!(masker && masker.escape),
83+
optional: !!(nextMasker && nextMasker.optional),
84+
repeat: !!(nextMasker && nextMasker.repeat)
85+
}
86+
}
87+
7688
while (maskIndex < mask.length) {
7789
const maskChar = mask[maskIndex]
7890
const masker = tokens[maskChar]
79-
const nextMaskChar = mask[maskIndex + 1]
80-
const nextMasker = tokens[nextMaskChar]
8191
let char = value[valueIndex]
8292

8393
if (masker && !escaped) {
94+
const meta = getMetaData(masker)
95+
8496
// when is escape char, do not mask, just continue
85-
if (masker.escape) {
97+
if (meta.escape) {
8698
escaped = true
8799
maskIndex++
88100
continue
89101
}
90102

91-
if (masker.optional || (nextMasker && nextMasker.optional)) {
92-
optional = true
93-
}
94-
95103
// no more input characters and next character is a masked one
96104
if (!char) break
97105

@@ -101,13 +109,12 @@ export function formatter(value, config) {
101109
output.masked += accumulator + char
102110

103111
accumulator = ''
104-
maskIndex++
105-
optional = false
106-
}
107112

108-
if (optional) {
109-
optional = false
110-
maskIndex++
113+
if (!meta.repeat) {
114+
maskIndex += meta.optional ? 2 : 1
115+
}
116+
} else if (meta.optional || meta.repeat) {
117+
maskIndex += 2
111118
continue
112119
}
113120

@@ -124,7 +131,6 @@ export function formatter(value, config) {
124131
}
125132
}
126133

127-
optional = false
128134
escaped = false
129135
maskIndex++
130136
}

src/tokens.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export default {
55
A: { pattern: /[a-z]/i, transform: (v) => v.toLocaleUpperCase() },
66
a: { pattern: /[a-z]/i, transform: (v) => v.toLocaleLowerCase() },
77
'\\': { escape: true },
8-
'?': { optional: true }
8+
'?': { optional: true },
9+
'*': { repeat: true }
910
}

tests/formatter.test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,27 @@ test('12.3456ac -> ##?#?.###.##?a?S', () => {
106106
})
107107
})
108108

109+
test('123456 -> #*', () => {
110+
expect(formatter('123456', { mask: '#*' })).toMatchObject({
111+
masked: '123456',
112+
unmasked: '123456'
113+
})
114+
})
115+
116+
test('1234HH -> #* AA', () => {
117+
expect(formatter('1234HH', { mask: '#* AA' })).toMatchObject({
118+
masked: '1234 HH',
119+
unmasked: '1234HH'
120+
})
121+
})
122+
123+
test('abc1234xyz -> ##* AAA', () => {
124+
expect(formatter('abc1234xyz', { mask: '##* AAA' })).toMatchObject({
125+
masked: '1234 XYZ',
126+
unmasked: '1234XYZ'
127+
})
128+
})
129+
109130
test('escaped -> \\+1 # 5', () => {
110131
expect(formatter('', { mask: '\\+1 # 5', prefill: true })).toMatchObject({ masked: '+1 ', unmasked: '' })
111132
})

0 commit comments

Comments
 (0)