diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx
index e0d40910825..c30108dcf57 100644
--- a/core/src/components/menu/menu.tsx
+++ b/core/src/components/menu/menu.tsx
@@ -398,6 +398,7 @@ export class Menu implements ComponentInterface, MenuI {
}
this.beforeAnimation(shouldOpen);
+
await this.loadAnimation();
await this.startAnimation(shouldOpen, animated);
this.afterAnimation(shouldOpen);
@@ -619,12 +620,17 @@ export class Menu implements ComponentInterface, MenuI {
// emit open event
this.ionDidOpen.emit();
- // focus menu content for screen readers
- if (this.menuInnerEl) {
- this.focusFirstDescendant();
+ /**
+ * Move focus to the menu to prepare focus trapping, as long as
+ * it isn't already focused. Use the host element instead of the
+ * first descendant to avoid the scroll position jumping around.
+ */
+ const focusedMenu = document.activeElement?.closest('ion-menu');
+ if (focusedMenu !== this.el) {
+ this.el.focus();
}
- // setup focus trapping
+ // start focus trapping
document.addEventListener('focus', this.handleFocus, true);
} else {
// remove css classes and unhide content from screen readers
diff --git a/core/src/components/menu/test/basic/e2e.ts b/core/src/components/menu/test/basic/e2e.ts
index 171d4cb5909..dd83dfeacff 100644
--- a/core/src/components/menu/test/basic/e2e.ts
+++ b/core/src/components/menu/test/basic/e2e.ts
@@ -28,13 +28,42 @@ test('menu: focus trap', async () => {
await menu.waitForVisible();
let activeElID = await getActiveElementID(page);
+ expect(activeElID).toEqual('start-menu');
+
+ await page.keyboard.press('Tab');
+ activeElID = await getActiveElementID(page);
expect(activeElID).toEqual('start-menu-button');
+ // do it again to make sure focus stays inside menu
await page.keyboard.press('Tab');
activeElID = await getActiveElementID(page);
expect(activeElID).toEqual('start-menu-button');
});
+test('menu: preserve scroll position', async () => {
+ const page = await newE2EPage({ url: '/src/components/menu/test/basic?ionic:_testing=true' });
+
+ await page.click('#open-first');
+ const menu = await page.find('#start-menu');
+ await menu.waitForVisible();
+
+ await page.$eval('#start-menu ion-content', (menuContentEl: any) => {
+ return menuContentEl.scrollToPoint(0, 200);
+ });
+
+ await menu.callMethod('close');
+
+ await page.click('#open-first');
+ await menu.waitForVisible();
+
+ const scrollTop = await page.$eval('#start-menu ion-content', async (menuContentEl: any) => {
+ const contentScrollEl = await menuContentEl.getScrollElement();
+ return contentScrollEl.scrollTop;
+ });
+
+ expect(scrollTop).toEqual(200);
+});
+
/**
* RTL Tests
*/
diff --git a/core/src/components/menu/test/basic/index.html b/core/src/components/menu/test/basic/index.html
index 6012e3a1197..722be746148 100644
--- a/core/src/components/menu/test/basic/index.html
+++ b/core/src/components/menu/test/basic/index.html
@@ -49,6 +49,21 @@