@@ -409,42 +409,31 @@ export class _MatMenuBase
409409 * @param origin Action from which the focus originated. Used to set the correct styling.
410410 */
411411 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+ }
419423
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 ( ) ;
426428
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 ( ) ;
445434 }
446435 }
447- }
436+ } ) ;
448437 }
449438
450439 /**
0 commit comments