Skip to content

Commit 742c226

Browse files
crisbetojosephperrott
authored andcommitted
fix(menu): scrollable menu not scrolled to top when opened for the first time (#11859)
1 parent 1e6acfb commit 742c226

File tree

2 files changed

+29
-0
lines changed

2 files changed

+29
-0
lines changed

src/lib/menu/menu-directive.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,5 +385,15 @@ export class MatMenu implements AfterContentInit, MatMenuPanel<MatMenuItem>, OnI
385385
_onAnimationDone(event: AnimationEvent) {
386386
this._animationDone.next(event);
387387
this._isAnimating = false;
388+
389+
// Scroll the content element to the top once the animation is done. This is necessary, because
390+
// we move focus to the first item while it's still being animated, which can throw the browser
391+
// off when it determines the scroll position. Alternatively we can move focus when the
392+
// animation is done, however moving focus asynchronously will interrupt screen readers
393+
// which are in the process of reading out the menu already. We take the `element` from
394+
// the `event` since we can't use a `ViewChild` to access the pane.
395+
if (event.toState === 'enter' && this._keyManager.activeItemIndex === 0) {
396+
event.element.scrollTop = 0;
397+
}
388398
}
389399
}

src/lib/menu/menu.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,23 @@ describe('MatMenu', () => {
164164
expect(document.activeElement).toBe(triggerEl);
165165
}));
166166

167+
it('should scroll the panel to the top on open, when it is scrollable', fakeAsync(() => {
168+
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
169+
fixture.detectChanges();
170+
171+
// Add 50 items to make the menu scrollable
172+
fixture.componentInstance.extraItems = new Array(50).fill('Hello there');
173+
fixture.detectChanges();
174+
175+
const triggerEl = fixture.componentInstance.triggerEl.nativeElement;
176+
dispatchFakeEvent(triggerEl, 'mousedown');
177+
triggerEl.click();
178+
fixture.detectChanges();
179+
tick();
180+
181+
expect(overlayContainerElement.querySelector('.mat-menu-panel')!.scrollTop).toBe(0);
182+
}));
183+
167184
it('should set the proper focus origin when restoring focus after opening by keyboard',
168185
fakeAsync(() => {
169186
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
@@ -1674,6 +1691,7 @@ describe('MatMenu default overrides', () => {
16741691
<fake-icon>unicorn</fake-icon>
16751692
Item with an icon
16761693
</button>
1694+
<button *ngFor="let item of extraItems" mat-menu-item> {{item}} </button>
16771695
</mat-menu>
16781696
`
16791697
})
@@ -1682,6 +1700,7 @@ class SimpleMenu {
16821700
@ViewChild('triggerEl') triggerEl: ElementRef;
16831701
@ViewChild(MatMenu) menu: MatMenu;
16841702
@ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
1703+
extraItems: string[] = [];
16851704
closeCallback = jasmine.createSpy('menu closed callback');
16861705
backdropClass: string;
16871706
}

0 commit comments

Comments
 (0)