-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Input mask on form inputs #1385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I've done some experiments and find it's not easy to integrate it whether inside or outside the library. The main problems are that they don't offer a DOM independant API and the handling of selection range issues of the input element. However this is a somehow useful feature. I'll try to make it work later. |
Might not be the optimal solution, but vue-the-mask offers a directive, that can easily be ported to Vue3 and is indeed working (at least for my purposes) with the existing NInput element. With a wrapper component and some tweaking, it's also possible to change between masked/unmasked value (while the displayed value is masked) and also get selection persistence. ./directives/mask.js function event(name, detail) {
const evt = new Event(name, { bubbles: true, cancelable: true })
return evt
}
function dynamicMask(maskit, masks, tokens) {
masks = masks.sort((a, b) => a.length - b.length)
return function (value, mask, masked = true) {
let i = 0
while (i < masks.length) {
const currentMask = masks[i]
i++
const nextMask = masks[i]
if (!(nextMask && maskit(value, nextMask, true, tokens).length > currentMask.length)) {
return maskit(value, currentMask, masked, tokens)
}
}
return '' // empty masks
}
}
function maskit(value, mask, masked = true, tokens) {
value = value || ''
mask = mask || ''
let iMask = 0
let iValue = 0
let output = ''
while (iMask < mask.length && iValue < value.length) {
let cMask = mask[iMask]
const masker = tokens[cMask]
const cValue = value[iValue]
if (masker && !masker.escape) {
if (masker.pattern.test(cValue)) {
output += masker.transform ? masker.transform(cValue) : cValue
iMask++
}
iValue++
} else {
if (masker && masker.escape) {
iMask++ // take the next mask char and treat it as char
cMask = mask[iMask]
}
if (masked) output += cMask
if (cValue === cMask) iValue++ // user typed the same char
iMask++
}
}
// fix mask that ends with a char: (#)
let restOutput = ''
while (iMask < mask.length && masked) {
const cMask = mask[iMask]
if (tokens[cMask]) {
restOutput = ''
break
}
restOutput += cMask
iMask++
}
return output + restOutput
}
export function masker(value, mask, masked = true, tokens) {
return Array.isArray(mask)
? dynamicMask(maskit, mask, tokens)(value, mask, masked, tokens)
: maskit(value, mask, masked, tokens)
}
export const tokens = {
'#': { pattern: /\d/ },
X: { pattern: /[0-9a-zA-Z]/ },
S: { pattern: /[a-zA-Z]/ },
A: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleUpperCase() },
H: { pattern: /[0-9a-fA-F]/, transform: v => v.toLocaleUpperCase() },
a: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleLowerCase() },
'!': { escape: true }
}
export default function (el, binding, vnode) {
let config = binding.value
if (Array.isArray(config) || typeof config === 'string') {
config = {
mask: config,
tokens: tokens
}
} else return
if (el.tagName.toLocaleUpperCase() !== 'INPUT') {
const els = el.getElementsByTagName('input')
if (els.length !== 1) {
throw new Error("v-mask directive requires 1 input, found " + els.length)
} else {
el = els[0]
}
}
let position = el.selectionEnd
const digit = el.value[position - 1]
const newDisplay = masker(el.value, config.mask, true, config.tokens)
if (newDisplay !== el.value) {
el.value = newDisplay
el.dispatchEvent(event('input'))
while (position < el.value.length && el.value.charAt(position - 1) !== digit) {
position++
}
if (el === document.activeElement) {
el.setSelectionRange(position, position)
setTimeout(function () {
el.setSelectionRange(position, position)
}, 0)
}
}
} ./main.js import mask from './directives/mask'
app.directive('mask', mask) ./components/MaskedInput.vue <template>
<n-input :value="display" v-mask="mask" @input="refresh" />
</template>
<script setup>
import { masker, tokens } from "@/directives/mask";
const emit = defineEmits(["update:value"]);
const props = defineProps({
value: [String, Number],
mask: {
type: [String, Array],
required: true,
},
masked: {
type: Boolean,
default: false,
},
});
const { mask, masked, value } = toRefs(props);
const display = ref(value.value);
const lastValue = ref(null);
watch(
() => value.value,
(newValue) => {
if (newValue !== lastValue.value) {
display.value = newValue;
}
}
);
watch(
() => masked.value,
() => refresh(display.value)
);
const refresh = (value) => {
display.value = value;
const val = masker(value, mask.value, masked.value, tokens);
if (val !== lastValue.value) {
lastValue.value = val;
emit("update:value", val);
}
};
</script> Use somewhere <masked-input v-model:value="value" :mask="'##/##/####'" /> |
Hey @B3nsten did you get it to work somehow? I'm in a project that demands masks, including date and currency. |
The posted code was working. What's erroring for you? |
I'll give it a try, now! Have to adapt a bit because Im working with Nuxt 3! |
It worked ❤️! Now I'm missing currency mask. Any chance you have it already? |
Well, depending on your needs you might get away with something like: <masked-input v-model:value="value" :mask="['#.##', '##.##', '###.##']" /> You'd have to change a lot of things if you need negative numbers, want to handle the money prefix inside the input (I'd use a naive input group, tho), etc. This approach is not the best for handling variable input lengths and in this state is not suitable to handle anything but strings. |
Thanks a bunch! I've been working on this feature for a week! You saved me! |
Hello. There is an easier way, it is not necessary to create a new MaskedInput component, you can use the package https://github.com/beholdr/maska , and to set the mask, use as |
@rom-rzic does it work? Tried but nothing happened |
Hello! Could you show how register vMaska in component? I have tried your example but in doesn't work. I register maska in script tag |
I found the solution!
The reason why @rom-rzic example didn't work because Welcome 🎉 |
Solution #1385 (comment) |
someone got it working for n-input pair? |
This function solves the problem (这个功能解决的问题)
Avoids users to enter wrong data from the get go
Expected API (期望的 API)
This will allow us to mask inputs to specific format e.g (##)-(####)
Means a user will enter the data in that format.
Find below pure JavaScript example
https://imask.js.org/
The text was updated successfully, but these errors were encountered: