181 lines
4.7 KiB
JavaScript
181 lines
4.7 KiB
JavaScript
// Utilities
|
|
import { computed } from 'vue';
|
|
import { isObject, propsFactory } from "../../util/index.js"; // Types
|
|
export const makeMaskProps = propsFactory({
|
|
mask: [String, Object]
|
|
}, 'mask');
|
|
export const defaultDelimiters = /[-!$%^&*()_+|~=`{}[\]:";'<>?,./\\ ]/;
|
|
const presets = {
|
|
'credit-card': '#### - #### - #### - ####',
|
|
date: '##/##/####',
|
|
'date-time': '##/##/#### ##:##',
|
|
'iso-date': '####-##-##',
|
|
'iso-date-time': '####-##-## ##:##',
|
|
phone: '(###) ### - ####',
|
|
social: '###-##-####',
|
|
time: '##:##',
|
|
'time-with-seconds': '##:##:##'
|
|
};
|
|
const defaultTokens = {
|
|
'#': {
|
|
pattern: /[0-9]/
|
|
},
|
|
A: {
|
|
pattern: /[A-Z]/i,
|
|
convert: v => v.toUpperCase()
|
|
},
|
|
a: {
|
|
pattern: /[a-z]/i,
|
|
convert: v => v.toLowerCase()
|
|
},
|
|
N: {
|
|
pattern: /[0-9A-Z]/i,
|
|
convert: v => v.toUpperCase()
|
|
},
|
|
n: {
|
|
pattern: /[0-9a-z]/i,
|
|
convert: v => v.toLowerCase()
|
|
},
|
|
X: {
|
|
pattern: defaultDelimiters
|
|
}
|
|
};
|
|
export function useMask(props) {
|
|
const mask = computed(() => {
|
|
if (typeof props.mask === 'string') {
|
|
if (props.mask in presets) return presets[props.mask];
|
|
return props.mask;
|
|
}
|
|
return props.mask?.mask ?? '';
|
|
});
|
|
const tokens = computed(() => {
|
|
return {
|
|
...defaultTokens,
|
|
...(isObject(props.mask) ? props.mask.tokens : null)
|
|
};
|
|
});
|
|
function isMask(char) {
|
|
return char in tokens.value;
|
|
}
|
|
function maskValidates(mask, char) {
|
|
if (char == null || !isMask(mask)) return false;
|
|
const item = tokens.value[mask];
|
|
if (item.pattern) return item.pattern.test(char);
|
|
return item.test(char);
|
|
}
|
|
function convert(mask, char) {
|
|
const item = tokens.value[mask];
|
|
return item.convert ? item.convert(char) : char;
|
|
}
|
|
function maskText(text) {
|
|
const trimmedText = text?.trim().replace(/\s+/g, ' ');
|
|
if (trimmedText == null) return '';
|
|
if (!mask.value.length || !trimmedText.length) return trimmedText;
|
|
let textIndex = 0;
|
|
let maskIndex = 0;
|
|
let newText = '';
|
|
while (maskIndex < mask.value.length) {
|
|
const mchar = mask.value[maskIndex];
|
|
const tchar = trimmedText[textIndex];
|
|
|
|
// Escaped character in mask, the next mask character is inserted
|
|
if (mchar === '\\') {
|
|
newText += mask.value[maskIndex + 1];
|
|
maskIndex += 2;
|
|
continue;
|
|
}
|
|
if (!isMask(mchar)) {
|
|
newText += mchar;
|
|
if (tchar === mchar) {
|
|
textIndex++;
|
|
}
|
|
} else if (maskValidates(mchar, tchar)) {
|
|
newText += convert(mchar, tchar);
|
|
textIndex++;
|
|
} else if (textIndex < trimmedText.length) {
|
|
// No match, try the next input character
|
|
textIndex++;
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
maskIndex++;
|
|
}
|
|
return newText;
|
|
}
|
|
function unmaskText(text) {
|
|
if (text == null) return null;
|
|
if (!mask.value.length || !text.length) return text;
|
|
let result = '';
|
|
const unmaskMap = getUnmaskMap(text);
|
|
for (let i = 0; i < text.length; i++) {
|
|
if (!unmaskMap[i]) result += text[i];
|
|
}
|
|
return result;
|
|
}
|
|
function isDelimiter(text, index) {
|
|
if (!mask.value.length || !text.length) return false;
|
|
return !!getUnmaskMap(text)[index];
|
|
}
|
|
function getUnmaskMap(text) {
|
|
if (text == null || !mask.value.length || !text.length) return [];
|
|
let textIndex = 0;
|
|
let maskIndex = 0;
|
|
const result = Array.from({
|
|
length: text.length
|
|
}, () => true);
|
|
while (true) {
|
|
const mchar = mask.value[maskIndex];
|
|
const tchar = text[textIndex];
|
|
if (tchar == null) break;
|
|
if (mchar == null) {
|
|
result[textIndex] = false;
|
|
textIndex++;
|
|
continue;
|
|
}
|
|
|
|
// Escaped character in mask, skip the next input character
|
|
if (mchar === '\\') {
|
|
if (tchar === mask.value[maskIndex + 1]) {
|
|
textIndex++;
|
|
}
|
|
maskIndex += 2;
|
|
continue;
|
|
}
|
|
if (maskValidates(mchar, tchar)) {
|
|
// masked char
|
|
result[textIndex] = false;
|
|
textIndex++;
|
|
maskIndex++;
|
|
continue;
|
|
} else if (mchar !== tchar) {
|
|
// input doesn't match mask, skip forward until it does
|
|
while (true) {
|
|
const mchar = mask.value[maskIndex++];
|
|
if (mchar == null || maskValidates(mchar, tchar)) break;
|
|
}
|
|
continue;
|
|
}
|
|
textIndex++;
|
|
maskIndex++;
|
|
}
|
|
return result;
|
|
}
|
|
function isValid(text) {
|
|
if (!text) return false;
|
|
return unmaskText(text) === unmaskText(maskText(text));
|
|
}
|
|
function isComplete(text) {
|
|
if (!text) return false;
|
|
const maskedText = maskText(text);
|
|
return maskedText.length === mask.value.length && isValid(text);
|
|
}
|
|
return {
|
|
isDelimiter,
|
|
isValid,
|
|
isComplete,
|
|
mask: maskText,
|
|
unmask: unmaskText
|
|
};
|
|
}
|
|
//# sourceMappingURL=mask.js.map
|