floating-vue icon indicating copy to clipboard operation
floating-vue copied to clipboard

Trigger event after tooltip/popover is made visible, for ex for .focus()

Open PVince81 opened this issue 3 years ago • 3 comments

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: image

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.

PVince81 avatar Mar 12 '21 14:03 PVince81

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.

PVince81 avatar Mar 12 '21 14:03 PVince81

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 avatar Mar 12 '21 15:03 PVince81

@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.

dword-design avatar Feb 19 '24 19:02 dword-design