First of all, the Vue directive example on cleave.js repository is meant to be used directly on an <input> element, not on an wrapping component:
import Vue from 'vue'
import Cleave from 'cleave.js';
Vue.directive('cleave', {
inserted: (el, binding) => {
el.cleave = new Cleave(el, binding.value || {})
},
update: (el) => {
const event = new Event('input', {bubbles: true});
setTimeout(function () {
el.value = el.cleave.properties.result
el.dispatchEvent(event)
}, 100);
}
})
Reference: https://github.com/nosir/cleave.js/blob/master/doc/vue.md
Let's see your implementation:
// Vue directive as showed in official documentation:
// https://github.com/nosir/cleave.js/blob/master/doc/vue.md
Vue.directive('cleave', {
inserted: (el, binding) => {
let input = el.querySelector('input');
input.cleave = new Cleave(input, binding.value || {})
},
update: (el) => {
const input = el.querySelector('input');
const event = new Event('input', {bubbles: true});
setTimeout(function () {
input.value = input.cleave.properties.result;
input.dispatchEvent(event);
}, 100);
}
});
// Simple vue component
Vue.component('custom-input', {
props: {value:{}},
template: '<div> <input :value="value" @input="$emit(\'input\', $event.target.value)"> {{ value }}</div>'
});
// Root
new Vue({
props: ['foo'],
el: '#app'
});
<div id="app">
<custom-input v-cleave="{
delimiters: ['.', '.', '-'],
blocks: [3, 3, 3, 2],
numericOnly: true
}" v-model="foo"></custom-input>
</div>
Reference: https://jsfiddle.net/87e4hfc0/1/
Your first comment:
// Vue directive as showed in official documentation...
Is not accurate.
You changed the code sample from cleave.js' docs to query for a inner <input> element inside a component, whereas, as already said, the sample in the official docs is meant to be applied directly to an <input> element, not to an wrapping element.
What happens with your variant is as you expect the v-model to be applied in the component's outer <div>, and v-model listens for input events either from a form component, or a bubbling event from within a DOM element.
So when a user presses a key, the v-model catches the native input event. Upon updating the bound value (foo) it then calls the updated hook in your v-cleave directive variant. You directive implementation then dispatches a new input event from the component's inner <input> DOM element, which is captured again by the outer element v-model method again, leading to circular call-chain until the value stabilizes.
As you want to bind to an outer component you can try this:
Vue.directive('cleave', {
inserted: (el, binding) => {
let input = el.querySelector('input');
input.cleave = new Cleave(input, binding.value || {})
},
update: (el, bindings, vnode) => {
const input = el.querySelector('input');
setTimeout(function () {
input.value = input.cleave.properties.result;
if (vnode.data.model) vnode.data.model.callback(input.value);
if (vnode.data.on && vnode.data.on.input) vnode.data.on.input(input.value);
}, 100);
}
});
Instead of dispatching a new input event from the DOM, which would lead to the circular call-chain we already outlined, it checks if the vnode (the outer component) has any model handlers (v-model) or any input listeners bound to it. If there are any handlers it calls them directly.
Note this new modified v-cleave directive variant won't work if applied directly to an <input> element. As it searches for an <input> from the directive's root element on both inserted and updated hooks (el.querySelector('input')).
Hope it is clearer.