Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HoverPopover does not close with fast mouse movements #67

Closed
Stromij opened this issue Jun 28, 2021 · 5 comments
Closed

HoverPopover does not close with fast mouse movements #67

Stromij opened this issue Jun 28, 2021 · 5 comments

Comments

@Stromij
Copy link

Stromij commented Jun 28, 2021

Hello everyone,

I'm trying to use the HoverPopover on several components. To do so I used the Popover-Example from the docs and adapted it to render more than one button:

import * as React from 'react'
import Button from '@material-ui/core/Button'
import { usePopupState, bindHover, bindPopover } from 'material-ui-popup-state/hooks'
import { Typography } from '@material-ui/core'
import Popover from 'material-ui-popup-state/HoverPopover'

const PopoverText = () => {
    const popupState = usePopupState({
        variant: 'popover',
        popupId: 'demoPopover',
    })

    return (
        <div>
            <Button variant="contained" {...bindHover(popupState)}>
                Open Popover
            </Button>
            <Popover
                {...bindPopover(popupState)}
                anchorOrigin={{
                    vertical: 'bottom',
                    horizontal: 'center',
                }}
                transformOrigin={{
                    vertical: 'top',
                    horizontal: 'center',
                }}
            >
                <Typography>
                    The content of the Popover.
                </Typography>
            </Popover>
        </div>
    )
}

export const TwoTablesNoCollapse = ({ ...args }) => (
    <>
        <PopoverText />
        <PopoverText />
        <PopoverText />
        <PopoverText />
        <PopoverText />
        <PopoverText />
        <PopoverText />
        <PopoverText />
        <PopoverText />
        <PopoverText />
    </>

);

Everything works fine, except with fast mouse movements. The showing popover does not disappear anymore. To get an idea of what I mean I add a small video:

Peek 2021-06-28 13-01

This is a problem because the Popover itself blocks any scroll events as mentioned in another issue (#37). This way, the page is not scrollable until the user closes the Popover by revisiting the corresponding element with the mouse.

I found the following as a workaround in this particular case

    return (
        <div onMouseLeave={popupState.close}>
            <Button variant="contained" {...bindHover(popupState)}>
                Open Popover
            </Button>

but since this is just a toy-example and I need to use this in a table on each table cell, I cannot wrap every table cell in a div (or something similar) without effecting the layout.

@jedwards1211
Copy link
Member

jedwards1211 commented Jun 30, 2021

@Stromij sorry for the trouble. I think the reason this is happening is you're reusing the same popupId. Like any element id, it should be globally unique. Try using a different popupId for each one and let me know if that solves the issue.

For background on why this happens:

bindHover injects an onMouseLeave handler to the button. But menus have to stay open when the mouse moves from the button to the menu (and I provide the same behavior for popovers in case they contain interactive elements), so I can't just close when the mouse leaves the button. Instead I have to wait until the mouse is over neither the button nor the popover.

So bindPopover also injects the same onMouseLeave handler, and that handler has to get the element with popupId to determine whether the mouse is over the popover.

function getPopup({ popupId }: PopupState): ?HTMLElement {
  return popupId && typeof document !== 'undefined'
    ? document.getElementById(popupId) // eslint-disable-line no-undef
    : null
}

function isElementInPopup(
  element: HTMLElement,
  popupState: PopupState
): boolean {
  const { anchorEl, _childPopupState } = popupState
  return (
    isAncestor(anchorEl, element) ||
    isAncestor(getPopup(popupState), element) ||
    (_childPopupState != null && isElementInPopup(element, _childPopupState))
  )
}

@jedwards1211
Copy link
Member

I'm testing with unique popupIds right now to see if that fixes the issue...

@jedwards1211
Copy link
Member

Okay, yes, using unique popupIds seems to prevent the issue for me.

Given some of the issues I've been having with touch devices, I may end up making bindPopover etc inject a ref; if so that will alleviate this issue since I would no longer have to get the popover element by id. But, you should make sure to use unique popupId in any case; much as I hate the reality of global ids, we have to live with it since ARIA props heavily depend on ids. I'm not even sure if duplicate ids are safe with all Material-UI component logic.

@jedwards1211
Copy link
Member

I'm going to close this, but we can revisit it if using unique popupIds doesn't solve the problem for you.

@Stromij
Copy link
Author

Stromij commented Jul 2, 2021

@jedwards1211 Thank you very much, somehow I did not saw that. Now I use unique popupId and it works like a charm. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants