JAVASCRIPT
Vue 3 Custom Directive for Real-time Input Masking
Learn to create a custom Vue 3 directive to automatically format user input in real-time, such as phone numbers or credit card numbers, enhancing user experience and data consistency.
// src/directives/inputMask.js
export default {
mounted(el, binding) {
const mask = binding.value; // e.g., '###-###-####' for phone
let previousValue = '';
const applyMask = () => {
let value = el.value.replace(/\D/g, ''); // Remove non-digits
let maskedValue = '';
let maskIndex = 0;
let valueIndex = 0;
while (maskIndex < mask.length && valueIndex < value.length) {
if (mask[maskIndex] === '#') {
maskedValue += value[valueIndex];
valueIndex++;
} else {
maskedValue += mask[maskIndex];
}
maskIndex++;
}
el.value = maskedValue;
// Dispatch input event to ensure v-model updates
el.dispatchEvent(new Event('input', { bubbles: true }));
};
el.addEventListener('input', applyMask);
// Initial application if element already has a value
if (el.value) {
applyMask();
}
el.__v_inputMask_handler = applyMask; // Store handler for unmounted
},
updated(el, binding) {
// Re-apply if mask changes, though less common for mask directives
if (binding.value !== binding.oldValue) {
// Remove old listener if exists
if (el.__v_inputMask_handler) {
el.removeEventListener('input', el.__v_inputMask_handler);
}
// Re-mount logic
const mask = binding.value;
const applyMask = () => {
let value = el.value.replace(/\D/g, '');
let maskedValue = '';
let maskIndex = 0;
let valueIndex = 0;
while (maskIndex < mask.length && valueIndex < value.length) {
if (mask[maskIndex] === '#') {
maskedValue += value[valueIndex];
valueIndex++;
} else {
maskedValue += mask[maskIndex];
}
maskIndex++;
}
el.value = maskedValue;
el.dispatchEvent(new Event('input', { bubbles: true }));
};
el.addEventListener('input', applyMask);
if (el.value) {
applyMask();
}
el.__v_inputMask_handler = applyMask;
}
},
unmounted(el) {
if (el.__v_inputMask_handler) {
el.removeEventListener('input', el.__v_inputMask_handler);
delete el.__v_inputMask_handler;
}
}
};
// main.js or plugin file
/*
import { createApp } from 'vue';
import App from './App.vue';
import inputMaskDirective from './directives/inputMask';
const app = createApp(App);
app.directive('mask', inputMaskDirective);
app.mount('#app');
*/
// MyComponent.vue template usage:
/*
<template>
<input v-model="phoneNumber" v-mask="'###-###-####'" placeholder="Phone Number" type="tel" />
<input v-model="creditCard" v-mask="'#### #### #### ####'" placeholder="Credit Card" />
</template>
<script setup>
import { ref } from 'vue';
const phoneNumber = ref('');
const creditCard = ref('');
</script>
*/
How it works: This snippet demonstrates creating a Vue 3 custom directive named `v-mask`. It attaches an `input` event listener to the element in the `mounted` hook. The `applyMask` function cleans the input, applies the provided mask pattern (where `#` represents a digit), and updates the input's value. An `input` event is manually dispatched to ensure compatibility with `v-model`. The `unmounted` hook cleans up the event listener to prevent memory leaks. The `updated` hook handles cases where the mask pattern itself might change.