@@ -409,42 +409,31 @@ export class _MatMenuBase
409
409
* @param origin Action from which the focus originated. Used to set the correct styling.
410
410
*/
411
411
focusFirstItem ( origin : FocusOrigin = 'program' ) : void {
412
- // When the content is rendered lazily, it takes a bit before the items are inside the DOM.
413
- if ( this . lazyContent ) {
414
- this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . _focusFirstItem ( origin ) ) ;
415
- } else {
416
- this . _focusFirstItem ( origin ) ;
417
- }
418
- }
412
+ // Wait for `onStable` to ensure iOS VoiceOver screen reader focuses the first item (#24735).
413
+ this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => {
414
+ let menuPanel : HTMLElement | null = null ;
415
+
416
+ if ( this . _directDescendantItems . length ) {
417
+ // Because the `mat-menuPanel` is at the DOM insertion point, not inside the overlay, we don't
418
+ // have a nice way of getting a hold of the menuPanel panel. We can't use a `ViewChild` either
419
+ // because the panel is inside an `ng-template`. We work around it by starting from one of
420
+ // the items and walking up the DOM.
421
+ menuPanel = this . _directDescendantItems . first ! . _getHostElement ( ) . closest ( '[role="menu"]' ) ;
422
+ }
419
423
420
- /**
421
- * Actual implementation that focuses the first item. Needs to be separated
422
- * out so we don't repeat the same logic in the public `focusFirstItem` method.
423
- */
424
- private _focusFirstItem ( origin : FocusOrigin ) {
425
- const manager = this . _keyManager ;
424
+ // If an item in the menuPanel is already focused, avoid overriding the focus.
425
+ if ( ! menuPanel || ! menuPanel . contains ( document . activeElement ) ) {
426
+ const manager = this . _keyManager ;
427
+ manager . setFocusOrigin ( origin ) . setFirstItemActive ( ) ;
426
428
427
- manager . setFocusOrigin ( origin ) . setFirstItemActive ( ) ;
428
-
429
- // If there's no active item at this point, it means that all the items are disabled.
430
- // Move focus to the menu panel so keyboard events like Escape still work. Also this will
431
- // give _some_ feedback to screen readers.
432
- if ( ! manager . activeItem && this . _directDescendantItems . length ) {
433
- let element = this . _directDescendantItems . first ! . _getHostElement ( ) . parentElement ;
434
-
435
- // Because the `mat-menu` is at the DOM insertion point, not inside the overlay, we don't
436
- // have a nice way of getting a hold of the menu panel. We can't use a `ViewChild` either
437
- // because the panel is inside an `ng-template`. We work around it by starting from one of
438
- // the items and walking up the DOM.
439
- while ( element ) {
440
- if ( element . getAttribute ( 'role' ) === 'menu' ) {
441
- element . focus ( ) ;
442
- break ;
443
- } else {
444
- element = element . parentElement ;
429
+ // If there's no active item at this point, it means that all the items are disabled.
430
+ // Move focus to the menuPanel panel so keyboard events like Escape still work. Also this will
431
+ // give _some_ feedback to screen readers.
432
+ if ( ! manager . activeItem && menuPanel ) {
433
+ menuPanel . focus ( ) ;
445
434
}
446
435
}
447
- }
436
+ } ) ;
448
437
}
449
438
450
439
/**
0 commit comments