183 lines
4.9 KiB
JavaScript
183 lines
4.9 KiB
JavaScript
// Utilities
|
|
import { computed, shallowRef } from 'vue';
|
|
import { isObject, propsFactory } from "../util/index.js"; // Types
|
|
export const makeMaskProps = propsFactory({
|
|
mask: [String, Object],
|
|
returnMaskedValue: Boolean
|
|
}, 'mask');
|
|
export const defaultDelimiters = /[-!$%^&*()_+|~=`{}[\]:";'<>?,./\\ ]/;
|
|
const presets = {
|
|
'credit-card': '#### - #### - #### - ####',
|
|
date: '##/##/####',
|
|
'date-time': '##/##/#### ##:##',
|
|
'iso-date': '####-##-##',
|
|
'iso-date-time': '####-##-## ##:##',
|
|
phone: '(###) ### - ####',
|
|
social: '###-##-####',
|
|
time: '##:##',
|
|
'time-with-seconds': '##:##:##'
|
|
};
|
|
export function isMaskDelimiter(char) {
|
|
return char ? defaultDelimiters.test(char) : false;
|
|
}
|
|
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, inputRef) {
|
|
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)
|
|
};
|
|
});
|
|
const selection = shallowRef(0);
|
|
const lazySelection = shallowRef(0);
|
|
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 {
|
|
break;
|
|
}
|
|
maskIndex++;
|
|
}
|
|
return newText;
|
|
}
|
|
function unmaskText(text) {
|
|
if (text == null) return null;
|
|
if (!mask.value.length || !text.length) return text;
|
|
let textIndex = 0;
|
|
let maskIndex = 0;
|
|
let newText = '';
|
|
while (true) {
|
|
const mchar = mask.value[maskIndex];
|
|
const tchar = text[textIndex];
|
|
if (tchar == null) break;
|
|
if (mchar == null) {
|
|
newText += tchar;
|
|
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
|
|
newText += tchar;
|
|
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 newText;
|
|
}
|
|
function setCaretPosition(newSelection) {
|
|
selection.value = newSelection;
|
|
inputRef.value && inputRef.value.setSelectionRange(selection.value, selection.value);
|
|
}
|
|
function resetSelections() {
|
|
if (!inputRef.value?.selectionEnd) return;
|
|
selection.value = inputRef.value.selectionEnd;
|
|
lazySelection.value = 0;
|
|
for (let index = 0; index < selection.value; index++) {
|
|
isMaskDelimiter(inputRef.value.value[index]) || lazySelection.value++;
|
|
}
|
|
}
|
|
function updateRange() {
|
|
if (!inputRef.value) return;
|
|
resetSelections();
|
|
let selection = 0;
|
|
const newValue = inputRef.value.value;
|
|
if (newValue) {
|
|
for (let index = 0; index < newValue.length; index++) {
|
|
if (lazySelection.value <= 0) break;
|
|
isMaskDelimiter(newValue[index]) || lazySelection.value--;
|
|
selection++;
|
|
}
|
|
}
|
|
setCaretPosition(selection);
|
|
}
|
|
return {
|
|
updateRange,
|
|
maskText,
|
|
unmaskText
|
|
};
|
|
}
|
|
//# sourceMappingURL=mask.js.map
|