diff --git a/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap b/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap
index ff0f3034929..2fbd9f164b9 100644
--- a/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap
+++ b/src/components/context_menu/__snapshots__/context_menu_panel.test.js.snap
@@ -1,5 +1,322 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`EuiContextMenu is rendered 1`] = `
+
+`;
+
+exports[`EuiContextMenu props panels and initialPanelId allows you to click the title button to go back to the previous panel 1`] = `
+
+
+
+
+
+ 2
+
+
+
+
+`;
+
+exports[`EuiContextMenu props panels and initialPanelId allows you to click the title button to go back to the previous panel 2`] = `
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`EuiContextMenu props panels and initialPanelId renders the referenced panel 1`] = `
+
+
+
+
+
+ 2
+
+
+
+
+`;
+
exports[`EuiContextMenuPanel is rendered 1`] = `
new Promise(resolve => {
+export const tick = (ms = 0) => new Promise(resolve => {
setTimeout(resolve, ms);
});
diff --git a/src/components/context_menu/context_menu_panel.js b/src/components/context_menu/context_menu_panel.js
index f68dd942671..9932cfedfad 100644
--- a/src/components/context_menu/context_menu_panel.js
+++ b/src/components/context_menu/context_menu_panel.js
@@ -154,47 +154,50 @@ export class EuiContextMenuPanel extends Component {
};
updateFocus() {
- // If this panel has lost focus, then none of its content should be focused.
- if (!this.props.hasFocus) {
- if (this.panel.contains(document.activeElement)) {
- document.activeElement.blur();
+ // Give positioning time to render before focus is applied. Otherwise page jumps.
+ requestAnimationFrame(() => {
+ // If this panel has lost focus, then none of its content should be focused.
+ if (!this.props.hasFocus) {
+ if (this.panel.contains(document.activeElement)) {
+ document.activeElement.blur();
+ }
+ return;
}
- 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.state.isTransitioning) {
- return;
- }
- // If there aren't any items then this is probably a form or something.
- if (!this.state.menuItems.length) {
- // If we've already focused on something inside the panel, everything's fine.
- if (this.panel.contains(document.activeElement)) {
+ // Setting focus while transitioning causes the animation to glitch, so we have to wait
+ // until it's finished before we focus anything.
+ if (this.state.isTransitioning) {
return;
}
- // Otherwise let's focus the first tabbable item and expedite input from the user.
- if (this.content) {
- const tabbableItems = tabbable(this.content);
- if (tabbableItems.length) {
- tabbableItems[0].focus();
+ // If there aren't any items then this is probably a form or something.
+ if (!this.state.menuItems.length) {
+ // If we've already focused on something inside the panel, everything's fine.
+ if (this.panel.contains(document.activeElement)) {
+ return;
+ }
+
+ // Otherwise let's focus the first tabbable item and expedite input from the user.
+ if (this.content) {
+ const tabbableItems = tabbable(this.content);
+ if (tabbableItems.length) {
+ tabbableItems[0].focus();
+ }
}
+ return;
}
- return;
- }
- // If an item is focused, focus it.
- if (this.state.focusedItemIndex !== undefined) {
- this.state.menuItems[this.state.focusedItemIndex].focus();
- return;
- }
+ // If an item is focused, focus it.
+ if (this.state.focusedItemIndex !== undefined) {
+ this.state.menuItems[this.state.focusedItemIndex].focus();
+ return;
+ }
- // Focus on the panel as a last resort.
- if (!this.panel.contains(document.activeElement)) {
- this.panel.focus();
- }
+ // Focus on the panel as a last resort.
+ if (!this.panel.contains(document.activeElement)) {
+ this.panel.focus();
+ }
+ });
}
onTransitionComplete = () => {
diff --git a/src/components/context_menu/context_menu_panel.test.js b/src/components/context_menu/context_menu_panel.test.js
index 35dab1f7479..04d33fa2287 100644
--- a/src/components/context_menu/context_menu_panel.test.js
+++ b/src/components/context_menu/context_menu_panel.test.js
@@ -11,6 +11,8 @@ import {
EuiContextMenuItem,
} from './context_menu_item';
+import { tick } from './context_menu.test';
+
import { keyCodes } from '../../services';
const items = [
@@ -162,7 +164,7 @@ describe('EuiContextMenuPanel', () => {
});
describe('initialFocusedItemIndex', () => {
- it('sets focus on the item occupying that index', () => {
+ it('sets focus on the item occupying that index', async () => {
const component = mount(
{
/>
);
+ await tick(20);
+
expect(findTestSubject(component, 'itemB').getDOMNode()).toBe(document.activeElement);
});
});
@@ -269,13 +273,15 @@ describe('EuiContextMenuPanel', () => {
describe('behavior', () => {
describe('focus', () => {
- it('is set on the first focusable element by default if there are no items and hasFocus is true', () => {
+ it('is set on the first focusable element by default if there are no items and hasFocus is true', async () => {
const component = mount(
);
+ await tick(20);
+
expect(findTestSubject(component, 'button').getDOMNode()).toBe(document.activeElement);
});
@@ -308,47 +314,55 @@ describe('EuiContextMenuPanel', () => {
);
});
- it(`focuses the panel by default`, () => {
+ it(`focuses the panel by default`, async () => {
+ await tick(20);
+
expect(component.getDOMNode()).toBe(document.activeElement);
});
- it('down arrow key focuses the first menu item', () => {
+ it('down arrow key focuses the first menu item', async () => {
component.simulate('keydown', { keyCode: keyCodes.DOWN });
+ await tick(20);
expect(findTestSubject(component, 'itemA').getDOMNode()).toBe(document.activeElement);
});
- it('subsequently, down arrow key focuses the next menu item', () => {
+ it('subsequently, down arrow key focuses the next menu item', async () => {
component.simulate('keydown', { keyCode: keyCodes.DOWN });
component.simulate('keydown', { keyCode: keyCodes.DOWN });
+ await tick(20);
expect(findTestSubject(component, 'itemB').getDOMNode()).toBe(document.activeElement);
});
- it('down arrow key wraps to first menu item', () => {
+ it('down arrow key wraps to first menu item', async () => {
component.simulate('keydown', { keyCode: keyCodes.UP });
component.simulate('keydown', { keyCode: keyCodes.DOWN });
+ await tick(20);
expect(findTestSubject(component, 'itemA').getDOMNode()).toBe(document.activeElement);
});
- it('up arrow key focuses the last menu item', () => {
+ it('up arrow key focuses the last menu item', async () => {
component.simulate('keydown', { keyCode: keyCodes.UP });
+ await tick(20);
expect(findTestSubject(component, 'itemC').getDOMNode()).toBe(document.activeElement);
});
- it('subsequently, up arrow key focuses the previous menu item', () => {
+ it('subsequently, up arrow key focuses the previous menu item', async () => {
component.simulate('keydown', { keyCode: keyCodes.UP });
component.simulate('keydown', { keyCode: keyCodes.UP });
+ await tick(20);
expect(findTestSubject(component, 'itemB').getDOMNode()).toBe(document.activeElement);
});
- it('up arrow key wraps to last menu item', () => {
+ it('up arrow key wraps to last menu item', async () => {
component.simulate('keydown', { keyCode: keyCodes.DOWN });
component.simulate('keydown', { keyCode: keyCodes.UP });
+ await tick(20);
expect(findTestSubject(component, 'itemC').getDOMNode()).toBe(document.activeElement);
});