floating-ui
floating-ui copied to clipboard
feat: SafePolygon component
Closes #1811
This exports a new SafePolygon component that can be used to block pointer-events as the cursor traverses it, a better solution to the current global "holes" strategy due to side effects
Usage:
const {polygonState, safePolygon} = useSafePolygon();
useHover(context, {
handleClose: safePolygon()
});
return (
<>
{/* Avoids containing blocks messing with the position, not always required */}
<FloatingPortal>
{open && <SafePolygon state={polygonState} />}
</FloatingPortal>
{/* reference / floating stuff here */}
</>
);
Deploy Preview for vibrant-gates-22c214 canceled.
| Name | Link |
|---|---|
| Latest commit | e7f74b2c42b803dd34291cfa96da714ee8053440 |
| Latest deploy log | https://app.netlify.com/sites/vibrant-gates-22c214/deploys/62e13926bd64b00008a5b622 |
@mihkeleidast
A thought on the new component API: as a use, it is not too comfortable to have to use a separate outside state to save the points and pass it on to SafePolygon.
Could it use the context instead? Passing the context to different components seems to be an existing practice:
useHover(context, {
handleClose: safePolygon(),
});
return (
<>
{/* Avoids containing blocks messing with the position, not always required */}
<FloatingPortal>
{open && <SafePolygon context={context} />}
</FloatingPortal>
{/* reference / floating stuff here */}
</>
);
As an alternative, maybe create a hook that saves the state internally:
const { SafePolygon, safePolygon } = useSafePolygon();
useHover(context, {
handleClose: safePolygon(),
});
return (
<>
{/* Avoids containing blocks messing with the position, not always required */}
<FloatingPortal>
{open && <SafePolygon />}
</FloatingPortal>
{/* reference / floating stuff here */}
</>
);
or
const { points, safePolygon } = useSafePolygon();
useHover(context, {
handleClose: safePolygon(),
});
return (
<>
{/* Avoids containing blocks messing with the position, not always required */}
<FloatingPortal>
{open && <SafePolygon points={points} />}
</FloatingPortal>
{/* reference / floating stuff here */}
</>
);
Do I also understand correctly that the SafePolygon is not rendered as a child of the floating element, rather it should be aside it?
useSafePolygon() is indeed much better. This is breaking in React 17 and 18 when using flushSync for some reason due to onScrollMouseLeave(), will debug after and try to refactor to a hook
How's this, you still import SafePolygon as a component, but now the other one is returned from the hook with points state internal within it.
const {polygonState, safePolygon} = useSafePolygon();
useHover(context, {
handleClose: safePolygon(),
});
return open && <SafePolygon state={polygonState} />;
I think this is a good API 👍
Alternative using spreadable props which is slightly more ergonomic imo:
const {polygonProps, safePolygon} = useSafePolygon();
useHover(context, {
handleClose: safePolygon(),
});
return open && <SafePolygon {...polygonProps} />;
Unfortunately this has a problem in Firefox where it won't block pointer-events when the polygon gets generated on the mouseleave for 1 frame ~half the time, doesn't happen in Chrome or Safari 😒 , not sure how to fix it.
How to reproduce that issue? Would investigate a bit, but not sure what exactly to look for.
Add the polygon component to the Menu.tsx component file in the playground. Add another nested submenu right under the "Share..." one. Remove restMs: 25 from the safePolygon() call (non-0 is broken right now)
In Firefox, you can see a 1 frame flicker. This also causes issues seen at the end:
https://user-images.githubusercontent.com/22450188/181524306-82ba4d9f-2e48-45a3-afa6-577fd6bba3bc.mov
This one get stalled? :(
https://github.com/floating-ui/floating-ui/pull/2069