I have keydown handler for fast uppercasing/lowercasing with Shift + Up/Down key:
export function onCaseChange(event: KeyboardEvent) {
if (event.shiftKey && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
const textfield = event.target as HTMLInputElement
event.preventDefault()
const processText = (str: string) => {
if (event.key === 'ArrowDown')
return str.toLowerCase()
else
return str.toUpperCase()
}
const selectionStart = textfield.selectionStart
const selectionEnd = textfield.selectionEnd
if (!selectionStart || !selectionEnd)
return
if (textfield.selectionStart == textfield.selectionEnd) {
textfield.value = processText(textfield.value)
} else {
const left = textfield.value.substring(0, selectionStart)
const middle = textfield.value.substring(selectionStart, selectionEnd)
const right = textfield.value.substring(selectionEnd)
textfield.value = left + processText(middle) + right
}
textfield.selectionStart = selectionStart
textfield.selectionEnd = selectionEnd
}
}
Without Vue I could do this before. But with Vue framework it doesn’t works. When I press Shift + Up the value changes and immediately changes to previous state. That’s because my input have binding to model, in my example it is a ref value:
<script setup lang="ts">
import { ref } from 'vue'
import { onCaseChange } from './caseChanger'
const trademarkVerbal = ref("")
const otherValue1 = ref("")
const otherValue2 = ref("")
...
</script>
<template>
<v-text-field v-model="trademarkVerbal" @keydown="onCaseChange" label="Trademark verbal"/>
<v-text-field v-model="otherValue1" @keydown="onCaseChange" label="Other value 1"/>
<v-text-field v-model="otherValue2" @keydown="onCaseChange" label="Other value 2"/>
...
</template>
How to force model change in my onCaseChange method? I want to use this method on many inputs, so I need somehow to pass model name to event handler or something that
v-model
is syntax sugar for a specific event handler that relies on the provided model as a single source of truth, it may be possible to affect how it processes model values with custom modifiers, but not how it processes DOM events. It needs to be desugared, a composable could be used in order to make it reusable:
const useKeyCase = (model = ref()) => {
const keyHandler = (e) => {
...
model.value = ...
},
return [model, keyHandler];
}
const [trademarkVerbal, trademarkVerbalKeyHandler] = useKeyCase()
...
<input :value="trademarkVerbal" @keydown="trademarkVerbalKeyHandler"/>
Directives are good for reusable DOM-related code, but they would need to re-emit input
/change
event to make v-model
handle the updates:
const vKeyCase = {
beforeMount(el) {
el.addEventListener('keydown', el.dataset.keyCaseHandler = e => {
setTimeout(() => {
...
el.value = ...;
el.dispatchEvent(new Event('input'));
})
});
},
unmounted(el) {
el.removeEventListener('keydown', el.dataset.keyCaseHandler);
}
};
...
<input v-model="trademarkVerbal" v-key-case />
This will result in a model containing unprocessed value at some points.
0
In fact, adding textfield.dispatchEvent(new Event('input'))
to end of onCaseChange
method solve my problem. Thanks @Estus Flask for the tip
2