`;
-
-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