Skip to content

Commit 472e72f

Browse files
committed
feat: support escaped characters
1 parent c018927 commit 472e72f

File tree

2 files changed

+71
-28
lines changed

2 files changed

+71
-28
lines changed

packages/vuetify/src/composables/__tests__/mask.spec.ts

+18-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import { isMaskDelimiter, useMask } from '../mask'
33

44
// Utilities
5-
import { describe, expect, it } from '@jest/globals'
65
import { ref } from 'vue'
76

87
// Types
@@ -26,23 +25,29 @@ describe('mask', () => {
2625
[{ mask: '(###)!!', modelValue: '123' }, '(123)!!'],
2726
[{ mask: '##.##', modelValue: '1234' }, '12.34'],
2827
[{ mask: '#', modelValue: null }, ''],
29-
])('maskText', (props, expected) => {
28+
[{ mask: '\\#(###)', modelValue: '123' }, '#(123)'],
29+
[{ mask: '\\####', modelValue: '1' }, '#1'],
30+
])('maskText %#', (props, expected) => {
3031
const { maskText } = useMask(props as MaskProps, ref(undefined))
3132
expect(maskText(props.modelValue)).toEqual(expected)
3233
})
3334

3435
it.each([
35-
['(5', '5'],
36-
['1111', '1111'],
37-
['(123)4', '1234'],
38-
['(456) 7)', '4567'],
39-
['4444 - 4444 - 4', '444444444'],
40-
['A31 - 4444', 'A314444'],
41-
['555', '555'],
42-
[null, null],
43-
])('unmaskText', (input, expected) => {
44-
const { unmaskText } = useMask({} as MaskProps, ref(undefined))
45-
expect(unmaskText(input)).toEqual(expected)
36+
[{ mask: '(#', modelValue: '(5' }, '5'],
37+
[{ mask: '####', modelValue: '1111' }, '1111'],
38+
[{ mask: '(###)#', modelValue: '(123)4' }, '1234'],
39+
[{ mask: '(###) #)', modelValue: '(456) 7)' }, '4567'],
40+
[{ mask: '#### - #### - #', modelValue: '4444 - 4444 - 4' }, '444444444'],
41+
[{ mask: 'NNN - ####', modelValue: 'A31 - 4444' }, 'A314444'],
42+
[{ mask: '\\#(###)', modelValue: '#(123)' }, '123'],
43+
[{ mask: '\\#(###)', modelValue: '123' }, '123'],
44+
[{ mask: '\\####', modelValue: '#(123)' }, '(123)'],
45+
[{ mask: '\\####', modelValue: '#1' }, '1'],
46+
[{ mask: '#-#', modelValue: '2-23' }, '223'],
47+
[{ mask: '', modelValue: null }, null],
48+
])('unmaskText %#', (props, expected) => {
49+
const { unmaskText } = useMask(props as MaskProps, ref(undefined))
50+
expect(unmaskText(props.modelValue)).toEqual(expected)
4651
})
4752

4853
it.each([

packages/vuetify/src/composables/mask.ts

+53-15
Original file line numberDiff line numberDiff line change
@@ -95,32 +95,70 @@ export function useMask (props: MaskProps, inputRef: Ref<HTMLInputElement | unde
9595
let newText = ''
9696

9797
while (maskIndex < masks.value.length) {
98-
const mask = masks.value[maskIndex]
99-
100-
// Assign the next character
101-
const char = text[textIndex]
98+
const mchar = masks.value[maskIndex]
99+
const tchar = text[textIndex]
100+
101+
// Escaped character in mask, the next mask character is inserted
102+
if (mchar === '\\') {
103+
newText += masks.value[maskIndex + 1]
104+
maskIndex += 2
105+
continue
106+
}
102107

103-
// Check if mask is delimiter
104-
// and current char matches
105-
if (!isMask(mask)) {
106-
newText += mask
107-
if (char === mask) {
108+
if (!isMask(mchar)) {
109+
newText += mchar
110+
if (tchar === mchar) {
108111
textIndex++
109112
}
110-
// Check if is mask and validates
111-
} else if (maskValidates(mask as MaskType, char)) {
112-
newText += convert(mask as MaskType, char)
113+
} else if (maskValidates(mchar as MaskType, tchar)) {
114+
newText += convert(mchar as MaskType, tchar)
113115
textIndex++
114116
} else {
115-
return newText
117+
break
116118
}
117119

118120
maskIndex++
119121
}
120122
return newText
121123
}
122-
function unmaskText (text: string | null): string {
123-
return text ? String(text).replace(new RegExp(defaultDelimiters.source, 'g'), '') : text!
124+
function unmaskText (text: string | null): string | null {
125+
if (text == null) return null
126+
127+
if (!masks.value.length || !text.length) return text
128+
129+
let textIndex = 0
130+
let maskIndex = 0
131+
let newText = ''
132+
133+
while (true) {
134+
const mchar = masks.value[maskIndex]
135+
const tchar = text[textIndex]
136+
137+
if (tchar == null) break
138+
139+
if (mchar == null) {
140+
newText += tchar
141+
textIndex++
142+
continue
143+
}
144+
145+
// Escaped character in mask, skip the next input character
146+
if (mchar === '\\') {
147+
if (tchar === masks.value[maskIndex + 1]) {
148+
textIndex++
149+
}
150+
maskIndex += 2
151+
continue
152+
}
153+
154+
if (mchar !== tchar) {
155+
newText += tchar
156+
}
157+
158+
textIndex++
159+
maskIndex++
160+
}
161+
return newText
124162
}
125163
function setCaretPosition (newSelection: number) {
126164
selection.value = newSelection

0 commit comments

Comments
 (0)