diff --git a/src/components/flyout/flyout.spec.tsx b/src/components/flyout/flyout.spec.tsx
new file mode 100644
index 00000000000..930dc65f706
--- /dev/null
+++ b/src/components/flyout/flyout.spec.tsx
@@ -0,0 +1,99 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+///
+
+import React, { useState } from 'react';
+
+import { EuiFlyout } from './flyout';
+
+const childrenDefault = (
+ <>
+
+
+
+
+ >
+);
+
+const Flyout = ({ children = childrenDefault }) => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ return (
+ <>
+ {isOpen ? (
+ setIsOpen(false)}>
+ {children}
+
+ ) : null}
+ >
+ );
+};
+
+describe('EuiFlyout', () => {
+ describe('Focus behavior', () => {
+ it('focuses the close button by default', () => {
+ cy.mount();
+ cy.focused().should(
+ 'have.attr',
+ 'data-test-subj',
+ 'euiFlyoutCloseButton'
+ );
+ });
+
+ it('traps focus and cycles tabbable items', () => {
+ cy.mount();
+ cy.realPress('Tab');
+ cy.realPress('Tab');
+ cy.realPress('Tab');
+ cy.focused().should('have.attr', 'data-test-subj', 'itemC');
+ cy.realPress('Tab');
+ cy.realPress('Tab');
+ cy.focused().should(
+ 'have.attr',
+ 'data-test-subj',
+ 'euiFlyoutCloseButton'
+ );
+ });
+ });
+
+ describe('Close behavior', () => {
+ it('closes the flyout when the close button is clicked', () => {
+ cy.mount();
+ cy.realPress('Enter').then(() => {
+ expect(cy.get('[data-test-subj="flyoutSpec"]').should('not.exist'));
+ });
+ });
+
+ it('closes the flyout with `escape` key', () => {
+ cy.mount();
+ cy.realPress('Escape').then(() => {
+ expect(cy.get('[data-test-subj="flyoutSpec"]').should('not.exist'));
+ });
+ });
+
+ it('closes the flyout when the overlay mask is clicked', () => {
+ cy.mount();
+ cy.get('.euiOverlayMask')
+ .realClick()
+ .then(() => {
+ expect(cy.get('[data-test-subj="flyoutSpec"]').should('not.exist'));
+ });
+ });
+
+ it('does not close the flyout when the overlay mask is only the target of mouseup', () => {
+ cy.mount();
+ cy.get('[data-test-subj="itemD"]').realMouseDown().realMouseMove(-100, 0);
+ cy.get('.euiOverlayMask')
+ .realMouseUp()
+ .then(() => {
+ expect(cy.get('[data-test-subj="flyoutSpec"]').should('exist'));
+ });
+ });
+ });
+});
diff --git a/src/components/flyout/flyout.tsx b/src/components/flyout/flyout.tsx
index ce775acd930..79aaa35ad0d 100644
--- a/src/components/flyout/flyout.tsx
+++ b/src/components/flyout/flyout.tsx
@@ -35,7 +35,6 @@ import { EuiOverlayMask, EuiOverlayMaskProps } from '../overlay_mask';
import { EuiButtonIcon, EuiButtonIconPropsForButton } from '../button';
import { EuiI18n } from '../i18n';
import { useResizeObserver } from '../observer/resize_observer';
-import { EuiOutsideClickDetector } from '../outside_click_detector';
import { EuiPortal } from '../portal';
const typeToClassNameMap = {
@@ -340,20 +339,12 @@ export const EuiFlyout = forwardRef(
);
}
- const flyoutContent = (
- )}
- role={role}
- className={classes}
- tabIndex={-1}
- style={newStyle || style}
- ref={setRef}
- >
- {closeButton}
- {children}
-
- );
-
+ const isDefaultConfiguration = ownFocus && !isPushed;
+ const onClickOutside =
+ (isDefaultConfiguration && outsideClickCloses !== false) ||
+ outsideClickCloses === true
+ ? onClose
+ : undefined;
/*
* Trap focus even when `ownFocus={false}`, otherwise closing
* the flyout won't return focus to the originating button.
@@ -361,36 +352,35 @@ export const EuiFlyout = forwardRef(
* Set `clickOutsideDisables={true}` when `ownFocus={false}`
* to allow non-keyboard users the ability to interact with
* elements outside the flyout.
+ *
+ * Set `onClickOutside={onClose}` when `ownFocus` and `type` are the defaults,
+ * or if `outsideClickCloses={true}` to close on clicks that target
+ * (both mousedown and mouseup) the overlay mask.
*/
let flyout = (
-
- {flyoutContent}
+
+ )}
+ role={role}
+ className={classes}
+ tabIndex={-1}
+ style={newStyle || style}
+ ref={setRef}
+ >
+ {closeButton}
+ {children}
+
);
- /**
- * Unless outsideClickCloses = true, then add the outside click detector
- */
- if (ownFocus === false && outsideClickCloses === true) {
- flyout = (
-
- {/* Outside click detector is needed if theres no overlay mask to auto-close when clicking on elements outside */}
- onClose()}
- >
- {flyoutContent}
-
-
- );
- }
+
// If ownFocus is set, wrap with an overlay and allow the user to click it to close it.
- if (ownFocus && !isPushed) {
+ if (isDefaultConfiguration) {
flyout = (
-
+
{flyout}
);
diff --git a/upcoming_changelogs/5810.md b/upcoming_changelogs/5810.md
new file mode 100644
index 00000000000..e15e1353e96
--- /dev/null
+++ b/upcoming_changelogs/5810.md
@@ -0,0 +1,4 @@
+**Bug fixes**
+
+- Fixed `EuiFlyout` so that it no longer closes when a click starts inside the flyout but completes outside
+