diff --git a/src/components/context_menu/context_menu_panel.spec.tsx b/src/components/context_menu/context_menu_panel.spec.tsx
index 97448b5c60c..745fd1eb11b 100644
--- a/src/components/context_menu/context_menu_panel.spec.tsx
+++ b/src/components/context_menu/context_menu_panel.spec.tsx
@@ -208,7 +208,7 @@ describe('EuiContextMenuPanel', () => {
});
});
- it('does not lose focus while using left/right arrow navigation between panels', () => {
+ describe('panels', () => {
const panels = [
{
id: 0,
@@ -245,21 +245,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.tsx b/src/components/context_menu/context_menu_panel.tsx
index 6d1c7d47eaf..4fe3f45d170 100644
--- a/src/components/context_menu/context_menu_panel.tsx
+++ b/src/components/context_menu/context_menu_panel.tsx
@@ -94,6 +94,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);
@@ -269,26 +270,8 @@ export class EuiContextMenuPanel extends Component {
// 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.initialPopoverParent?.addEventListener('focus', () => {
this.updateFocus();
});
}
@@ -417,10 +400,37 @@ export class EuiContextMenuPanel extends Component {
}
}
+ 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.classList.contains(
+ 'euiPopover__panel'
+ );
+ if (!hasPopoverParent) return;
+
+ this.initialPopoverParent = popoverParent;
+ }
+
panelRef = (node: HTMLElement | null) => {
this.panel = node;
this.updateHeight();
+ this.getInitialPopoverParent();
};
render() {