diff --git a/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap b/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap index 2feb29e822a..916b2547649 100644 --- a/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap +++ b/src/components/context_menu/__snapshots__/context_menu_panel.test.tsx.snap @@ -130,143 +130,3 @@ exports[`EuiContextMenuPanel props transitionDirection previous with transitionT
`; - -exports[`EuiContextMenuPanel updating items and content updates to items should not re-render if any items's watchedItemProps did not change 1`] = ` -" -
- -
- - - - - - -
-
-
-
" -`; - -exports[`EuiContextMenuPanel updating items and content updates to items should not re-render if any items's watchedItemProps did not change 2`] = ` -" -
- -
- - - - - - -
-
-
-
" -`; - -exports[`EuiContextMenuPanel updating items and content updates to items should re-render at all times when children exists 1`] = ` -" -
- -
- Hello World -
-
-
-
" -`; - -exports[`EuiContextMenuPanel updating items and content updates to items should re-render at all times when children exists 2`] = ` -" -
- -
- More Salutations -
-
-
-
" -`; - -exports[`EuiContextMenuPanel updating items and content updates to items should re-render if any items's watchedItemProps did change 1`] = ` -" -
- -
- - - - - - -
-
-
-
" -`; - -exports[`EuiContextMenuPanel updating items and content updates to items should re-render if any items's watchedItemProps did change 2`] = ` -" -
- -
- - - - - - -
-
-
-
" -`; diff --git a/src/components/context_menu/context_menu_panel.spec.tsx b/src/components/context_menu/context_menu_panel.spec.tsx index 97448b5c60c..1434c86d8fd 100644 --- a/src/components/context_menu/context_menu_panel.spec.tsx +++ b/src/components/context_menu/context_menu_panel.spec.tsx @@ -8,7 +8,7 @@ /// -import React from 'react'; +import React, { useState } from 'react'; import { EuiPopover } from '../popover'; import { EuiContextMenu } from './context_menu'; @@ -42,15 +42,6 @@ describe('EuiContextMenuPanel', () => { cy.focused().should('have.attr', 'class', 'euiContextMenuPanel'); }); - it('sets initial focus from `initialFocusedItemIndex`', () => { - cy.mount( - - {children} - - ); - cy.focused().should('have.attr', 'data-test-subj', 'itemC'); - }); - describe('with `children`', () => { it('ignores arrow key navigation, which only toggles for `items`', () => { cy.mount({children}); @@ -60,6 +51,20 @@ describe('EuiContextMenuPanel', () => { }); describe('with `items`', () => { + it('sets initial focus from `initialFocusedItemIndex`', () => { + cy.mount( + + ); + cy.focused().should('have.attr', 'data-test-subj', 'itemC'); + }); + + it('falls back to the panel if given an invalid `focusedItemIndex`', () => { + cy.mount( + + ); + cy.focused().should('have.attr', 'class', 'euiContextMenuPanel'); + }); + it('focuses and registers any tabbable child as navigable menu items', () => { cy.mount( { cy.realPress('{downarrow}'); cy.focused().should('have.attr', 'data-test-subj', 'itemA'); }); + + it('correctly re-finds navigable menu items if `items` changes', () => { + const DynanicItemsTest = () => { + const [dynamicItems, setDynamicItems] = useState([ + items[0], + items[1], + ]); + const appendItems = () => setDynamicItems(items); + return ( + <> + + + + ); + }; + cy.mount(); + cy.realPress('{downarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemA'); + cy.realPress('{downarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemB'); + cy.realPress('{downarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemA'); + + cy.get('[data-test-subj="appendItems"]').click(); + cy.get('[data-test-subj="itemA"]').click(); + cy.realPress('{uparrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemC'); + }); }); describe('with `panels`', () => { @@ -123,17 +158,66 @@ describe('EuiContextMenuPanel', () => { }); }); - describe('when inside an EuiPopover', () => { - it('reclaims focus from the parent popover panel', () => { - cy.mount( - }> - + describe('within an EuiPopover', () => { + const ContextMenuInPopover: React.FC = ({ children, ...rest }) => { + const [isOpen, setIsOpen] = useState(false); + const closePopover = () => setIsOpen(false); + const openPopover = () => setIsOpen(true); + return ( + + Toggle popover + + } + {...rest} + > + + {children} + + ); - cy.wait(400); // EuiPopover's updateFocus() takes ~350ms to run + }; + + const mountAndOpenPopover = (component = ) => { + cy.realMount(component); + cy.get('[data-test-subj="popoverToggle"]').click(); + cy.wait(350); // EuiPopover's updateFocus() takes ~350ms to run + }; + + it('reclaims focus from the parent popover panel', () => { + mountAndOpenPopover(); cy.focused().should('not.have.attr', 'class', 'euiPopover__panel'); cy.focused().should('have.attr', 'class', 'euiContextMenuPanel'); }); + + it('does not hijack focus from the EuiPopover if `initialFocus` is set', () => { + mountAndOpenPopover( + + + + ); + cy.focused().should('not.have.attr', 'class', 'euiContextMenuPanel'); + cy.focused().should('have.attr', 'id', 'testInitialFocus'); + }); + + it('restores focus to the toggling button on popover close', () => { + mountAndOpenPopover(); + cy.realPress('Tab'); + cy.realPress('Enter'); + cy.focused().should('have.attr', 'data-test-subj', 'popoverToggle'); + }); + + it('restores focus to the toggling button on popover escape key', () => { + mountAndOpenPopover(); + cy.realPress('{esc}'); + cy.focused().should('have.attr', 'data-test-subj', 'popoverToggle'); + }); }); }); @@ -208,7 +292,7 @@ describe('EuiContextMenuPanel', () => { }); }); - it('does not lose focus while using left/right arrow navigation between panels', () => { + describe('panels', () => { const panels = [ { id: 0, @@ -245,21 +329,31 @@ describe('EuiContextMenuPanel', () => { initialFocusedItemIndex: 0, }, ]; - cy.mount(); - cy.realPress('{downarrow}'); - cy.focused().should('have.attr', 'data-test-subj', 'itemA'); - cy.realPress('{rightarrow}'); - cy.focused().should('have.attr', 'data-test-subj', 'itemB'); - cy.realPress('{rightarrow}'); - cy.focused().should('have.attr', 'data-test-subj', 'itemC'); - // Test extremely rapid left/right arrow usage - cy.repeatRealPress('{leftarrow}'); - cy.focused().should('have.attr', 'data-test-subj', 'itemA'); - cy.repeatRealPress('{rightarrow}'); - cy.focused().should('have.attr', 'data-test-subj', 'itemC'); - cy.repeatRealPress('{leftarrow}'); - cy.focused().should('have.attr', 'data-test-subj', 'itemA'); + it('does not lose focus while using left/right arrow navigation between panels', () => { + cy.mount(); + cy.realPress('{downarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemA'); + cy.realPress('{rightarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemB'); + cy.realPress('{rightarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemC'); + }); + + it('does not lose focus when inside an EuiPopover and during rapid left/right arrow usage', () => { + cy.mount( + }> + + + ); + cy.wait(350); // Wait for EuiContextMenuPanel to reclaim focus from popover + cy.realPress('{downarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemA'); + cy.repeatRealPress('{rightarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemC'); + cy.repeatRealPress('{leftarrow}'); + cy.focused().should('have.attr', 'data-test-subj', 'itemA'); + }); }); }); diff --git a/src/components/context_menu/context_menu_panel.test.tsx b/src/components/context_menu/context_menu_panel.test.tsx index 724afe9298e..2b005d8fa7b 100644 --- a/src/components/context_menu/context_menu_panel.test.tsx +++ b/src/components/context_menu/context_menu_panel.test.tsx @@ -255,96 +255,4 @@ describe('EuiContextMenuPanel', () => { }); // @see Cypress context_menu_panel.spec.tsx for focus & keyboard nav testing - - describe('updating items and content', () => { - describe('updates to items', () => { - it("should not re-render if any items's watchedItemProps did not change", () => { - expect.assertions(2); // make sure the assertion in the `setProps` callback is executed - - // by not passing `watchedItemProps` no changes to items should cause a re-render - const component = mount( - - Option A - , - - Option B - , - ]} - /> - ); - - expect(component.debug()).toMatchSnapshot(); - - component.setProps( - { - items: [ - - Option A - , - - Option B - , - ], - }, - () => { - expect(component.debug()).toMatchSnapshot(); - } - ); - }); - - it("should re-render if any items's watchedItemProps did change", () => { - expect.assertions(2); // make sure the assertion in the `setProps` callback is executed - - // by referencing the `data-counter` property in `watchedItemProps` - // changes to the items should be picked up and re-rendered - const component = mount( - - Option A - , - - Option B - , - ]} - /> - ); - - expect(component.debug()).toMatchSnapshot(); - - component.setProps( - { - items: [ - - Option A - , - - Option B - , - ], - }, - () => { - expect(component.debug()).toMatchSnapshot(); - } - ); - }); - - it('should re-render at all times when children exists', () => { - expect.assertions(2); // make sure the assertion in the `setProps` callback is executed - - const component = mount( - Hello World - ); - - expect(component.debug()).toMatchSnapshot(); - - component.setProps({ children: 'More Salutations' }, () => { - expect(component.debug()).toMatchSnapshot(); - }); - }); - }); - }); }); diff --git a/src/components/context_menu/context_menu_panel.tsx b/src/components/context_menu/context_menu_panel.tsx index 8840760618f..8691238fb4d 100644 --- a/src/components/context_menu/context_menu_panel.tsx +++ b/src/components/context_menu/context_menu_panel.tsx @@ -51,7 +51,6 @@ export interface EuiContextMenuPanelProps { title?: ReactNode; transitionDirection?: EuiContextMenuPanelTransitionDirection; transitionType?: EuiContextMenuPanelTransitionType; - watchedItemProps?: string[]; /** * Alters the size of the items and the title */ @@ -84,6 +83,8 @@ interface State { focusedItemIndex?: number; currentHeight?: number; height?: number; + waitingForInitialPopover: boolean; + tookInitialFocus: boolean; } export class EuiContextMenuPanel extends Component { @@ -94,6 +95,7 @@ export class EuiContextMenuPanel extends Component { private _isMounted = false; private backButton?: HTMLElement | null = null; private panel?: HTMLElement | null = null; + private initialPopoverParent?: HTMLElement | null = null; constructor(props: Props) { super(props); @@ -108,18 +110,35 @@ export class EuiContextMenuPanel extends Component { ? props.initialFocusedItemIndex + 1 // Account for panel title back button : props.initialFocusedItemIndex, currentHeight: undefined, + waitingForInitialPopover: false, + tookInitialFocus: false, }; } - incrementFocusedItemIndex = (amount: number) => { + // Find all tabbable menu items on both panel init and + // whenever `menuItems` resets when `props.items` changes + findMenuItems = () => { + if (!this.panel) return; + if (!this.props.items?.length) return; // We only need menu items/arrow key navigation for the `items` API + if (this.state.menuItems.length) return; // If we already have menu items, no need to continue + + const tabbableItems = tabbable(this.panel); + if (tabbableItems.length) { + this.setState({ menuItems: tabbableItems }); + } + }; + + focusMenuItem = (direction: 'up' | 'down') => { + const indexOffset = direction === 'up' ? -1 : 1; let nextFocusedItemIndex; if (this.state.focusedItemIndex === undefined) { // If this is the beginning of the user's keyboard navigation of the menu, then we'll focus // either the first or last item. - nextFocusedItemIndex = amount < 0 ? this.state.menuItems.length - 1 : 0; + nextFocusedItemIndex = + direction === 'up' ? this.state.menuItems.length - 1 : 0; } else { - nextFocusedItemIndex = this.state.focusedItemIndex + amount; + nextFocusedItemIndex = this.state.focusedItemIndex + indexOffset; if (nextFocusedItemIndex < 0) { nextFocusedItemIndex = this.state.menuItems.length - 1; @@ -128,9 +147,8 @@ export class EuiContextMenuPanel extends Component { } } - this.setState({ - focusedItemIndex: nextFocusedItemIndex, - }); + this.setState({ focusedItemIndex: nextFocusedItemIndex }); + this.state.menuItems[nextFocusedItemIndex]?.focus(); }; onKeyDown = (event: React.KeyboardEvent) => { @@ -181,7 +199,7 @@ export class EuiContextMenuPanel extends Component { case cascadingMenuKeys.ARROW_UP: event.preventDefault(); - this.incrementFocusedItemIndex(-1); + this.focusMenuItem('up'); if (this.props.onUseKeyboardToNavigate) { this.props.onUseKeyboardToNavigate(); @@ -190,7 +208,7 @@ export class EuiContextMenuPanel extends Component { case cascadingMenuKeys.ARROW_DOWN: event.preventDefault(); - this.incrementFocusedItemIndex(1); + this.focusMenuItem('down'); if (this.props.onUseKeyboardToNavigate) { this.props.onUseKeyboardToNavigate(); @@ -218,13 +236,19 @@ export class EuiContextMenuPanel extends Component { } }; - updateFocus() { + takeInitialFocus() { // Give positioning time to render before focus is applied. Otherwise page jumps. requestAnimationFrame(() => { if (!this._isMounted) { return; } + // Don't take focus yet if EuiContextMenu is in a popover + // and the popover is initially opening/transitioning in + if (this.initialPopoverParent && this.state.waitingForInitialPopover) { + return; + } + // Setting focus while transitioning causes the animation to glitch, so we have to wait // until it's finished before we focus anything. if (this.props.transitionType) { @@ -235,63 +259,43 @@ export class EuiContextMenuPanel extends Component { return; } - // If menuItems has been cleared, iterate through and set menuItems from tabbableItems - if (!this.state.menuItems.length && this.panel) { - const tabbableItems = tabbable(this.panel); - if (tabbableItems.length) { - this.setState({ menuItems: tabbableItems }); - } + // Initial focus has already been handled, no need to continue and potentially hijack/focus fight + if (this.state.tookInitialFocus) { + return; } - if (this.state.menuItems.length) { - // If an item is focused, focus it - if (this.state.focusedItemIndex != null) { - this.state.menuItems[this.state.focusedItemIndex].focus(); - return; + // If an item should be focused, focus it (if it exists) + if (this.state.focusedItemIndex != null && this.state.menuItems.length) { + const focusedItem = this.state.menuItems[this.state.focusedItemIndex]; + if (focusedItem) { + focusedItem.focus(); + return this.setState({ tookInitialFocus: true }); } - // Otherwise, if the back button panel title is present, focus it - if (this.props.onClose) { + } + + // Otherwise, if the back button panel title is present, focus it + if (this.backButton) { + // Focus the back button for both `items` and `children` APIs + this.backButton.focus(); + // If `items`, ensure our focused item index is correct + if (this.state.menuItems.length) { this.setState({ focusedItemIndex: 0 }); - this.state.menuItems[0].focus(); - return; } + return this.setState({ tookInitialFocus: true }); } // Focus on the panel as a last resort. if (this.panel && !this.panel.contains(document.activeElement)) { this.panel.focus(); + this.setState({ tookInitialFocus: true }); } }); } - // If EuiContextMenu is used within an EuiPopover, EuiPopover's own - // `updateFocus()` method hijacks EuiContextMenuPanel's `updateFocus()` - // 350ms after the popover finishes transitioning in. This workaround - // reclaims focus from parent EuiPopovers that do not set an `initialFocus` - reclaimPopoverFocus() { - if (!this.panel) return; - - const parent = this.panel.parentNode as HTMLElement; - if (!parent) return; - const hasEuiContextMenuParent = parent.classList.contains('euiContextMenu'); - - // It's possible to use an EuiContextMenuPanel directly in a popover without - // an EuiContextMenu, so we need to account for that when searching parent nodes - const popoverParent = hasEuiContextMenuParent - ? (parent?.parentNode?.parentNode as HTMLElement) - : (parent?.parentNode as HTMLElement); - if (!popoverParent) return; - - const hasPopoverParent = popoverParent.classList.contains( - 'euiPopover__panel' - ); - if (!hasPopoverParent) return; - - // If the popover panel gains focus, switch it to the context menu panel instead - popoverParent.addEventListener('focus', () => { - this.updateFocus(); - }); - } + reclaimPopoverFocus = () => { + this.setState({ waitingForInitialPopover: false }); + this.takeInitialFocus(); + }; onTransitionComplete = () => { if (this.props.onTransitionComplete) { @@ -299,13 +303,38 @@ export class EuiContextMenuPanel extends Component { } }; + componentDidUpdate(_: Props, prevState: State) { + if (prevState.menuItems !== this.state.menuItems) { + this.findMenuItems(); + } + // Focus isn't always ready to be taken on mount, so we need to call it + // on update as well just in case + this.takeInitialFocus(); + } + componentDidMount() { - this.updateFocus(); - this.reclaimPopoverFocus(); + // If EuiContextMenu is used within an EuiPopover, we need to wait for EuiPopover to: + // 1. Correctly set its `returnFocus` to the toggling button, + // so focus is correctly restored to the popover toggle on close + // 2. Finish its own `updateFocus()` call 350ms after transitioning in, + // so the panel can handle its own focus without focus fighting + if (this.initialPopoverParent) { + this.initialPopoverParent.addEventListener( + 'focus', + this.reclaimPopoverFocus, + { once: true } + ); + } else { + this.takeInitialFocus(); + } this._isMounted = true; } componentWillUnmount() { + this.initialPopoverParent?.removeEventListener( + 'focus', + this.reclaimPopoverFocus + ); this._isMounted = false; } @@ -329,78 +358,6 @@ export class EuiContextMenuPanel extends Component { return null; } - getWatchedPropsForItems(items: ReactElement[]) { - // This lets us compare prevProps and nextProps among items so we can re-render if our items - // have changed. - const { watchedItemProps } = this.props; - - // Create fingerprint of all item's watched properties - if (items.length && watchedItemProps && watchedItemProps.length) { - return JSON.stringify( - items.map((item) => { - // Create object of item properties and values - const props: any = { - key: item.key, - }; - watchedItemProps.forEach((prop: string) => { - props[prop] = item.props[prop]; - }); - return props; - }) - ); - } - - return null; - } - - didItemsChange(prevItems: ReactElement[], nextItems: ReactElement[]) { - // If the count of items has changed then update - if (prevItems.length !== nextItems.length) { - return true; - } - - // Check if any watched item properties changed by quick string comparison - if ( - this.getWatchedPropsForItems(nextItems) !== - this.getWatchedPropsForItems(prevItems) - ) { - return true; - } - } - - shouldComponentUpdate(nextProps: Props, nextState: State) { - // Prevent calling `this.updateFocus()` below if we don't have to. - if (nextProps.transitionType !== this.props.transitionType) { - return true; - } - - if (nextState.focusedItemIndex !== this.state.focusedItemIndex) { - return true; - } - - // ** - // this component should have either items or children, - // if there are items we can determine via `watchedItemProps` if we should update - // if there are children we can't know if they have changed so return true - // ** - - if ( - (this.props.items && this.props.items.length > 0) || - (nextProps.items && nextProps.items.length > 0) - ) { - if (this.didItemsChange(this.props.items!, nextProps.items!)) { - return true; - } - } - - // it's not possible (in any good way) to know if `children` has changed, assume they might have - if (this.props.children != null) { - return true; - } - - return false; - } - updateHeight() { const currentHeight = this.panel ? this.panel.clientHeight : 0; @@ -413,14 +370,37 @@ export class EuiContextMenuPanel extends Component { } } - componentDidUpdate() { - this.updateFocus(); + getInitialPopoverParent() { + // If `transitionType` exists, that means we're navigating between panels + // and the initial popover has already loaded, so we shouldn't need this logic + if (this.props.transitionType) return; + + if (!this.panel) return; + + const parent = this.panel.parentNode as HTMLElement; + if (!parent) return; + const hasEuiContextMenuParent = parent.classList.contains('euiContextMenu'); + + // It's possible to use an EuiContextMenuPanel directly in a popover without + // an EuiContextMenu, so we need to account for that when searching parent nodes + const popoverParent = hasEuiContextMenuParent + ? (parent?.parentNode?.parentNode as HTMLElement) + : (parent?.parentNode as HTMLElement); + if (!popoverParent) return; + + const hasPopoverParent = !!popoverParent.dataset.popoverPanel; + if (!hasPopoverParent) return; + + this.initialPopoverParent = popoverParent; + this.setState({ waitingForInitialPopover: true }); } panelRef = (node: HTMLElement | null) => { this.panel = node; this.updateHeight(); + this.getInitialPopoverParent(); + this.findMenuItems(); }; render() { @@ -435,7 +415,6 @@ export class EuiContextMenuPanel extends Component { onTransitionComplete, onUseKeyboardToNavigate, items, - watchedItemProps, initialFocusedItemIndex, showNextPanel, showPreviousPanel, diff --git a/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap b/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap index beb97537b57..7bee1dad864 100644 --- a/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap +++ b/src/components/datagrid/controls/__snapshots__/column_selector.test.tsx.snap @@ -117,6 +117,7 @@ exports[`useDataGridColumnSelector columnSelector renders a toolbar button/popov aria-modal="true" class="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiDataGrid__controlPopoverWithDragDrop" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -473,6 +474,7 @@ exports[`useDataGridColumnSelector columnSelector renders a toolbar button/popov aria-live="off" aria-modal="true" className="euiPopover__panel euiPopover__panel--bottom euiDataGrid__controlPopoverWithDragDrop" + data-popover-panel={true} hasShadow={false} paddingSize="s" panelRef={[Function]} @@ -492,6 +494,7 @@ exports[`useDataGridColumnSelector columnSelector renders a toolbar button/popov aria-live="off" aria-modal="true" className="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiDataGrid__controlPopoverWithDragDrop" + data-popover-panel={true} role="dialog" style={ Object { diff --git a/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap b/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap index 703b12fd7c0..b5cf61f5c8f 100644 --- a/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap +++ b/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap @@ -107,6 +107,7 @@ exports[`useDataGridColumnSorting columnSorting renders a toolbar button/popover aria-modal="true" class="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiDataGrid__controlPopoverWithDragDrop" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -484,6 +485,7 @@ exports[`useDataGridColumnSorting columnSorting renders a toolbar button/popover aria-live="off" aria-modal="true" className="euiPopover__panel euiPopover__panel--bottom euiDataGrid__controlPopoverWithDragDrop" + data-popover-panel={true} hasShadow={false} paddingSize="s" panelRef={[Function]} @@ -503,6 +505,7 @@ exports[`useDataGridColumnSorting columnSorting renders a toolbar button/popover aria-live="off" aria-modal="true" className="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiDataGrid__controlPopoverWithDragDrop" + data-popover-panel={true} role="dialog" style={ Object { diff --git a/src/components/form/super_select/__snapshots__/super_select.test.tsx.snap b/src/components/form/super_select/__snapshots__/super_select.test.tsx.snap index f48fb0c5ef3..82484dca42a 100644 --- a/src/components/form/super_select/__snapshots__/super_select.test.tsx.snap +++ b/src/components/form/super_select/__snapshots__/super_select.test.tsx.snap @@ -166,6 +166,7 @@ exports[`EuiSuperSelect props custom display is propagated to dropdown 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-noArrow euiPopover__panel-isAttached" + data-popover-panel="true" role="dialog" style="top: 8px; left: 0px; will-change: transform, opacity; z-index: 2000;" > @@ -441,6 +442,7 @@ exports[`EuiSuperSelect props more props are propogated to each option 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-noArrow euiPopover__panel-isAttached" + data-popover-panel="true" role="dialog" style="top: 8px; left: 0px; will-change: transform, opacity; z-index: 2000;" > @@ -605,6 +607,7 @@ exports[`EuiSuperSelect props options are rendered when select is open 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-noArrow euiPopover__panel-isAttached" + data-popover-panel="true" role="dialog" style="top: 8px; left: 0px; will-change: transform, opacity; z-index: 2000;" > @@ -768,6 +771,7 @@ exports[`EuiSuperSelect props renders popoverProps on the underlying EuiPopover aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-noArrow euiPopover__panel-isAttached goes-on-popover-panel" + data-popover-panel="true" role="dialog" style="top: 8px; left: 0px; will-change: transform, opacity; z-index: 2000;" > diff --git a/src/components/popover/__snapshots__/popover.test.tsx.snap b/src/components/popover/__snapshots__/popover.test.tsx.snap index 81a1cee5ae4..2561fc0df45 100644 --- a/src/components/popover/__snapshots__/popover.test.tsx.snap +++ b/src/components/popover/__snapshots__/popover.test.tsx.snap @@ -109,6 +109,7 @@ exports[`EuiPopover props arrowChildren is rendered 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -167,6 +168,7 @@ exports[`EuiPopover props buffer 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -223,6 +225,7 @@ exports[`EuiPopover props buffer for all sides 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -292,6 +295,7 @@ exports[`EuiPopover props focusTrapProps is rendered 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -361,6 +365,7 @@ exports[`EuiPopover props isOpen renders true 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -418,6 +423,7 @@ exports[`EuiPopover props offset with arrow 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 26px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -475,6 +481,7 @@ exports[`EuiPopover props offset without arrow 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen euiPopover__panel-noArrow" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 18px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -530,6 +537,7 @@ exports[`EuiPopover props ownFocus defaults to true 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -584,6 +592,7 @@ exports[`EuiPopover props ownFocus renders false 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" > @@ -633,6 +642,7 @@ exports[`EuiPopover props panelClassName is rendered 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen test" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -689,6 +699,7 @@ exports[`EuiPopover props panelPaddingSize is rendered 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen" data-autofocus="true" + data-popover-panel="true" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" tabindex="0" @@ -745,6 +756,7 @@ exports[`EuiPopover props panelProps is rendered 1`] = ` aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--bottom euiPopover__panel-isOpen testClass1 testClass2" data-autofocus="true" + data-popover-panel="true" data-test-subj="test subject string" role="dialog" style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;" diff --git a/src/components/popover/popover.tsx b/src/components/popover/popover.tsx index 36a80306406..850e2902c64 100644 --- a/src/components/popover/popover.tsx +++ b/src/components/popover/popover.tsx @@ -801,6 +801,7 @@ export class EuiPopover extends Component { > ); diff --git a/src/components/tour/__snapshots__/tour_step.test.tsx.snap b/src/components/tour/__snapshots__/tour_step.test.tsx.snap index 192630d0e48..2a793994d39 100644 --- a/src/components/tour/__snapshots__/tour_step.test.tsx.snap +++ b/src/components/tour/__snapshots__/tour_step.test.tsx.snap @@ -48,6 +48,7 @@ exports[`EuiTourStep can change the minWidth and maxWidth 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--left euiPopover__panel-isOpen euiTour testClass1 testClass2" + data-popover-panel="true" role="dialog" style="top: -22px; left: -26px; will-change: transform, opacity; max-width: 420px; min-width: 240px; z-index: 2000;" > @@ -145,6 +146,7 @@ exports[`EuiTourStep can have subtitle 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--left euiPopover__panel-isOpen euiTour testClass1 testClass2" + data-popover-panel="true" role="dialog" style="top: -22px; left: -26px; will-change: transform, opacity; max-width: 600px; min-width: 300px; z-index: 2000;" > @@ -247,6 +249,7 @@ exports[`EuiTourStep can override the footer action 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--left euiPopover__panel-isOpen euiTour testClass1 testClass2" + data-popover-panel="true" role="dialog" style="top: -22px; left: -26px; will-change: transform, opacity; max-width: 600px; min-width: 300px; z-index: 2000;" > @@ -333,6 +336,7 @@ exports[`EuiTourStep can turn off the beacon 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--left euiPopover__panel-isOpen euiTour testClass1 testClass2" + data-popover-panel="true" role="dialog" style="top: -22px; left: -16px; will-change: transform, opacity; max-width: 600px; min-width: 300px; z-index: 2000;" > @@ -425,6 +429,7 @@ exports[`EuiTourStep is rendered 1`] = ` aria-live="assertive" aria-modal="true" class="euiPanel euiPanel--paddingMedium euiPanel--borderRadiusMedium euiPanel--plain euiPanel--noShadow euiPopover__panel euiPopover__panel--left euiPopover__panel-isOpen euiTour testClass1 testClass2" + data-popover-panel="true" role="dialog" style="top: -22px; left: -26px; will-change: transform, opacity; max-width: 600px; min-width: 300px; z-index: 2000;" > diff --git a/upcoming_changelogs/5880.md b/upcoming_changelogs/5880.md new file mode 100644 index 00000000000..98a0087364d --- /dev/null +++ b/upcoming_changelogs/5880.md @@ -0,0 +1,7 @@ +**Bug fixes** + +- Fixed `EuiContextMenuPanel` (when used within an `EuiPopover`) to correctly return focus to its popover toggle in all scenarios, not just keyboard Escape press + +**Breaking changes** + +- Removed `watchedItemProps` from `EuiContextMenuPanel`, which now updates like a standard component and no longer needs this logic