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 +