Script
const amountRef = ref(null);
const amount = ref(0.1);
const insertSuggestion = (e, value) => {
e.preventDefault();
e.stopPropagation();
amount.value = value;
amountRef.value.focus();
};
Template
<Suggestion
class="..."
v-for="suggestion in suggestions"
:key="suggestion"
@click="insertSuggestion($event, suggestion)"
>
{{ suggestion }}
</Suggestion>
<input
class="..."
@keyup.enter="handleBuy"
placeholder="Amount"
ref="amountRef"
v-model="amount"
/>
Script
const amountRef = ref(null);
const amount = ref(0.1);
const insertSuggestion = (e, value) => {
e.preventDefault();
e.stopPropagation();
amount.value = value;
amountRef.value.focus();
};
Template
<Suggestion
class="..."
v-for="suggestion in suggestions"
:key="suggestion"
@click="insertSuggestion($event, suggestion)"
>
{{ suggestion }}
</Suggestion>
<input
class="..."
@keyup.enter="handleBuy"
placeholder="Amount"
ref="amountRef"
v-model="amount"
/>
Share
Improve this question
asked May 12, 2021 at 10:19
oemeraoemera
3,4533 gold badges22 silver badges39 bronze badges
3 Answers
Reset to default 3Stop the de-focusing with @pointerdown.prevent
TLDR: pointerdown
(or mousedown
) occurs before blur
(or click
), so use it to prevent subsequent default events that occur when the click or tap is finished.
NOTE: I believe you can use pointerdown
or mousedown
events interchangeably.
I strongly advise avoiding any focus change at all, even if it's a re-focus, as any state change may invoke a cascade of secondary state changes especially if used with some deeply nested ponents. Even if it works, at the least you risk a visual "hiccup" exposed to the user (yuck).
Instead, you should try to actually stop the de-focusing from happening in the first place.
You had the right idea with @click
and a subsequent e.preventDefault()
, but a click event occurs after blurring.
Instead, make the preventDefault
occur before blur occurs, by triggering it from an earlier event, pointerdown
, which occurs before blur & click.
Simply add Vue's @pointerdown.prevent
on elements where, when clicked, you don't want another element (on the page) to de-focus. With this, the OP can get rid of a bunch of lines:
Script
const insertSuggestion = (value) => {
// NOT NEEDED ANYMORE:
// e.preventDefault();
// e.stopPropagation();
// amountRef.value.focus();
// KEEP:
amount.value = value
}
Template
<Suggestion
class="..."
v-for="suggestion in suggestions"
:key="suggestion"
@pointerdown.prevent
@click="insertSuggestion(suggestion)"
>
{{ suggestion }}
</Suggestion>
You can re-focus the input element on the blur
event:
<input ref="amountRef" @blur="$refs.amountRef.focus()"/>
Edit
You also need to check the relatedTarget
of the blur
event to make sure you only re-focus when one of the buttons is clicked.
Note: this solution does not work on Firefox.
<Suggestion
class="suggestion-btn"
...
>
</Suggestion>
<input
ref="amountRef"
@blur="onBlur"
/>
onBlur(evt) {
if (
evt.relatedTarget &&
evt.relatedTarget.classList.contains("suggestion-btn")
) {
this.$refs.amountRef.focus();
}
},
You could try to capture the blur event and add the classes back if the relatedTarget
is one of those buttons. Super psuedo code below:
<input
class="..."
@keyup.enter="handleBuy"
@blur="maybeMimicFocus"
placeholder="Amount"
ref="amountRef"
v-model="amount"
/>
maybeMimicFocus (event) {
if (event.relatedTarget && event.relatedTarget.tagname === 'btn') {
this.$refs.amountRef.$el.classList.add('the-classes-that-make-it-look-focused')
}
}
the browser won't have to repaint the dom and therefore this transaction should be so quick that the naked eye won't see it a difference. the only caveat I can see is if there are animations attached to transitioning properties in which those will have to fire.