floating-vue
floating-vue copied to clipboard
Trigger event after tooltip/popover is made visible, for ex for .focus()
Use case
We have create a popover menu component and would like to set the focus on the first element inside the popover right after it has appeared.
Problem
To be able to call .focus()
on the first element, we need to make sure that at this moment the Popover is mounted in the DOM and it has the class .open
set, as it has to be actually visible.
This is currently not possible to detect reliably as there is no event triggered at the right moment.
In our current implementation we had used $nextTick()
but since Popover uses requestAnimationFrame
, there is no guarantee how many milliseconds must elapse before the open
class gets set here: https://github.com/Akryum/v-tooltip/blob/master/src/components/Popover.vue#L360
To test the problem have a component listen to then @open
event of the Popover, and use the following handler:
handleShow() {
console.log('Popover: handleShow className=', document.querySelector('.popover')?.className)
this.$nextTick(() => {
console.log('Popover after nextTick: handleShow className=', document.querySelector('.popover')?.className)
})
window.setTimeout(() => {
console.log('Popover after 1ms delay: handleShow className=', document.querySelector('.popover')?.className)
}, 1)
window.setTimeout(() => {
console.log('Popover after 5ms delay: handleShow className=', document.querySelector('.popover')?.className)
}, 5)
window.setTimeout(() => {
console.log('Popover after 20ms delay: handleShow className=', document.querySelector('.popover')?.className)
}, 20)
window.setTimeout(() => {
console.log('Popover after 50ms delay: handleShow className=', document.querySelector('.popover')?.className)
}, 50)
window.setTimeout(() => {
console.log('Popover after 100ms delay: handleShow className=', document.querySelector('.popover')?.className)
}, 100)
You can see my result here:
The messages in-between is where I tried to re-try the focus after a delay after noticing that the focus was not actually set.
As you can see the $nextTick
did not get access to the DOM element and the "open" class only starts to appear after 50ms or so.
Note: this only happens the first time the popover opens. The second time it will directly set this.isOpen=true
here https://github.com/Akryum/v-tooltip/blob/master/src/components/Popover.vue#L343
Solution
Instead of having to add arbitrary delays, it would be nice to trigger the "show" event only once the popover is actually visible. Or, if there are concerns with backwards compatibility, emit a new event "afterShow" there.
This way in our use case we could simply listen to that event and apply the focus there.
This was observed in v-tooltip version 2.0.3.
I didn't try master yet but saw that the logic is anyway the same there.
I just found that there's also an apply-show
event, but still it would fire before the "requestAnimationFrame" initially: https://github.com/Akryum/v-tooltip/blob/master/src/components/Popover.vue#L443
@PVince81 Setting no-auto-focus
and @apply-show="$refs.input.focus()"
fixes the issue for me. The lib seems to reset the focus somehow and no-auto-focus
disables this.