[EuiFlyout] Scope close events to mask when ownFocus=true#5876
[EuiFlyout] Scope close events to mask when ownFocus=true#5876thompsongl merged 10 commits intoelastic:mainfrom
ownFocus=true#5876Conversation
|
Preview documentation changes for this PR: https://eui.elastic.co/pr_5876/ |
|
Preview documentation changes for this PR: https://eui.elastic.co/pr_5876/ |
|
Preview documentation changes for this PR: https://eui.elastic.co/pr_5876/ |
1Copenut
left a comment
There was a problem hiding this comment.
This should get us exactly what we're needing for the specific cases in Kibana. I appreciate the Cypress specs (with real events!) to test the overlay and EuiToast cases. I'll defer to Constance for the code changes, but wanted to chime in the experience matches a mockup I put together to test UX quickly.
cee-chen
left a comment
There was a problem hiding this comment.
Mostly looks great to me! My comments are mainly around the onClickOutside logic and if we're in a hurry can be skipped, but I think it's worth clarifying for developer experience/understanding
src/components/flyout/flyout.tsx
Outdated
| const onClickOutside = (event: MouseEvent | TouchEvent) => | ||
| (isDefaultConfiguration && | ||
| outsideClickCloses !== false && | ||
| event.target === maskRef.current) || | ||
| outsideClickCloses === true | ||
| ? onClose | ||
| ? onClose(event) | ||
| : undefined; |
There was a problem hiding this comment.
This still feels pretty hard for me to read personally 😅 Since this is a function now, can we prefer early returns?
const onClickOutside = (event: MouseEvent | TouchEvent) => {
if (outsideClickCloses === true) return onClose(event);
if (outsideClickCloses === false) return undefined;
// If outsideClickCloses is not specified, only close on clicks to the overlay mask
if (isDefaultConfiguration && event.target === maskRef) return onClose(event);
// Otherwise if ownFocus is false, outside clicks should not close the flyout
return undefined;
}There was a problem hiding this comment.
I'll refactor for readability, but here's the logic:
- If
ownFocus=true(default), only clicks on the mask should close the flyout, even ifoutsideClickCloses=true - If
ownFocus=false, the flyout will only close ifoutsideClickCloses=true, and in that case clicking any external element will close the flyout
There was a problem hiding this comment.
If ownFocus=true (default), only clicks on the mask should close the flyout, even if outsideClickCloses=true
That's just not what the current branching is doing though?? So you'd need to change this logic if that's the behavior you want. outsideClickCloses === true will always evaluate to returning onClose(). Am I reading this the same way as you?
There was a problem hiding this comment.
const onClickOutside = (event: MouseEvent | TouchEvent) => {
// Do not close the flyout for any external click
if (outsideClickCloses === false) return undefined;
if (isDefaultConfiguration) {
// The overlay mask is present, so only clicks targeting the mask should close the flyout
if (event.target === maskRef.current) return onClose(event);
} else {
// No overlay mask is present, so any outside clicks should close the flyout
if (outsideClickCloses === true) return onClose(event);
}
// Otherwise if ownFocus is false, outside clicks should not close the flyout
return undefined;
};There was a problem hiding this comment.
That's just not what the current branching is doing though??
Yep I had the conditional wrong. #5876 (comment) is correct; will push the change once we get confirmation from @cchaos
There was a problem hiding this comment.
@thompsongl Nice! Definitely much easier to grok now, especially w/ comments. For the last comment/return, I might make it a bit more specific to help follow the possible branch paths:
// Otherwise if ownFocus is false and outsideClickCloses is undefined, outside clicks should not close the flyout
There was a problem hiding this comment.
I also think we should be more specific about the isDefaultConfiguration mask behavior:
// The overlay mask is present, so only clicks on the mask should close the flyout, regardless of outsideClickCloses
It's not that the business logic/UX doesn't make sense, it's just that it's a bit tortuous so more comments should help with that (I think?)
There was a problem hiding this comment.
Oh man, I remember how tricky this logic was. I'm going to repeat the logic just to ensure I understand.
- Default
ownFocus = true: The overlay mask renders and the flyout will only be closed when clicking mask (or close button) ownFocus = false && outsideClickCloses = false: No overlay mask and can only be closed via close button (or some other in-page toggle)ownFocus = false && outsideClickCloses = true: Clicking anywhere outside will close flyout (does this include dragging with mouse up or does this now mean a special extra configuration?)
I feel like that's how it was before, but maybe I'm remembering another config wrong?
There was a problem hiding this comment.
ownFocus = false && outsideClickCloses = true: Clicking anywhere outside will close flyout (does this include dragging with mouse up or does this now mean a special extra configuration?)
By default the close event will happen if mousedown happens outside the flyout. Consumers can change this to occur on mouseup if they have some reason to do so.
By default the close event will not happen if mousedown happens inside the flyout, even if mouseup happens outside the flyout (e.g., mousedown inside, drag outside, release. The flyout remains open)
There was a problem hiding this comment.
43ce709 incorporates all suggestions in this thread
|
Preview documentation changes for this PR: https://eui.elastic.co/pr_5876/ |
| outsideClickCloses === true | ||
| ? onClose | ||
| : undefined; | ||
| const hasOverlayMask = ownFocus && !isPushed; |
There was a problem hiding this comment.
I think this name change will be super helpful for understanding
|
Thanks as always for the thorough review, @constancecchen! |
|
Preview documentation changes for this PR: https://eui.elastic.co/pr_5876/ |

Summary
Restores previous decision to only consider an click event as "outside" when
EuiOverlayMaskis the target andownFocus=true.EuiOverlayMaskaccepts a React ref viamaskRefEuiFlyouttakes advantage of ^ to scope outside click events in the case ofownFocus=true(the default case)EuiFlyoutprovides event data toonClosecallback (derived in most cases fromEuiFocusTrap)When
ownFocus=falseandoutsideClickCloses=true, we will not scope the close event to any particular element. Use theshardsprop to prevent closing when clicking certain external elements.Checklist