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