Skip to content

Commit da89684

Browse files
fix(menu): preserve scroll position when focusing on open (#25044)
1 parent 3f3a2bc commit da89684

File tree

5 files changed

+57
-7
lines changed

5 files changed

+57
-7
lines changed

core/src/components/menu/menu.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ export class Menu implements ComponentInterface, MenuI {
398398
}
399399

400400
this.beforeAnimation(shouldOpen);
401+
401402
await this.loadAnimation();
402403
await this.startAnimation(shouldOpen, animated);
403404
this.afterAnimation(shouldOpen);
@@ -619,12 +620,17 @@ export class Menu implements ComponentInterface, MenuI {
619620
// emit open event
620621
this.ionDidOpen.emit();
621622

622-
// focus menu content for screen readers
623-
if (this.menuInnerEl) {
624-
this.focusFirstDescendant();
623+
/**
624+
* Move focus to the menu to prepare focus trapping, as long as
625+
* it isn't already focused. Use the host element instead of the
626+
* first descendant to avoid the scroll position jumping around.
627+
*/
628+
const focusedMenu = document.activeElement?.closest('ion-menu');
629+
if (focusedMenu !== this.el) {
630+
this.el.focus();
625631
}
626632

627-
// setup focus trapping
633+
// start focus trapping
628634
document.addEventListener('focus', this.handleFocus, true);
629635
} else {
630636
// remove css classes and unhide content from screen readers

core/src/components/menu/test/basic/e2e.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,42 @@ test('menu: focus trap', async () => {
2828
await menu.waitForVisible();
2929

3030
let activeElID = await getActiveElementID(page);
31+
expect(activeElID).toEqual('start-menu');
32+
33+
await page.keyboard.press('Tab');
34+
activeElID = await getActiveElementID(page);
3135
expect(activeElID).toEqual('start-menu-button');
3236

37+
// do it again to make sure focus stays inside menu
3338
await page.keyboard.press('Tab');
3439
activeElID = await getActiveElementID(page);
3540
expect(activeElID).toEqual('start-menu-button');
3641
});
3742

43+
test('menu: preserve scroll position', async () => {
44+
const page = await newE2EPage({ url: '/src/components/menu/test/basic?ionic:_testing=true' });
45+
46+
await page.click('#open-first');
47+
const menu = await page.find('#start-menu');
48+
await menu.waitForVisible();
49+
50+
await page.$eval('#start-menu ion-content', (menuContentEl: any) => {
51+
return menuContentEl.scrollToPoint(0, 200);
52+
});
53+
54+
await menu.callMethod('close');
55+
56+
await page.click('#open-first');
57+
await menu.waitForVisible();
58+
59+
const scrollTop = await page.$eval('#start-menu ion-content', async (menuContentEl: any) => {
60+
const contentScrollEl = await menuContentEl.getScrollElement();
61+
return contentScrollEl.scrollTop;
62+
});
63+
64+
expect(scrollTop).toEqual(200);
65+
});
66+
3867
/**
3968
* RTL Tests
4069
*/

core/src/components/menu/test/basic/index.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@
4949
<ion-item>Menu Item</ion-item>
5050
<ion-item>Menu Item</ion-item>
5151
<ion-item>Menu Item</ion-item>
52+
<ion-item>Menu Item</ion-item>
53+
<ion-item>Menu Item</ion-item>
54+
<ion-item>Menu Item</ion-item>
55+
<ion-item>Menu Item</ion-item>
56+
<ion-item>Menu Item</ion-item>
57+
<ion-item>Menu Item</ion-item>
58+
<ion-item>Menu Item</ion-item>
59+
<ion-item>Menu Item</ion-item>
60+
<ion-item>Menu Item</ion-item>
61+
<ion-item>Menu Item</ion-item>
62+
<ion-item>Menu Item</ion-item>
63+
<ion-item>Menu Item</ion-item>
64+
<ion-item>Menu Item</ion-item>
65+
<ion-item>Menu Item</ion-item>
66+
<ion-item>Menu Item</ion-item>
5267
</ion-list>
5368
</ion-content>
5469
</ion-menu>

core/src/components/menu/test/focus-trap/e2e.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ test('menu: focus trap with overlays', async () => {
1818
await menu.callMethod('open');
1919
await ionDidOpen.next();
2020

21-
expect(await getActiveElementID(page)).toEqual('open-modal-button');
21+
expect(await getActiveElementID(page)).toEqual('menu');
2222

2323
const openModal = await page.find('#open-modal-button');
2424
await openModal.click();
@@ -45,7 +45,7 @@ test('menu: focus trap with content inside overlays', async () => {
4545
await menu.callMethod('open');
4646
await ionDidOpen.next();
4747

48-
expect(await getActiveElementID(page)).toEqual('open-modal-button');
48+
expect(await getActiveElementID(page)).toEqual('menu');
4949

5050
const openModal = await page.find('#open-modal-button');
5151
await openModal.click();

core/src/components/menu/test/focus-trap/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</head>
1515
<body>
1616
<ion-app>
17-
<ion-menu content-id="main">
17+
<ion-menu content-id="main" id="menu">
1818
<ion-header>
1919
<ion-toolbar>
2020
<ion-title>Menu</ion-title>

0 commit comments

Comments
 (0)