@@ -106,6 +106,8 @@ export abstract class _MatAutocompleteTriggerBase
106106 private _componentDestroyed = false ;
107107 private _autocompleteDisabled = false ;
108108 private _scrollStrategy : ( ) => ScrollStrategy ;
109+ private _keydownSubscription : Subscription | null ;
110+ private _outsideClickSubscription : Subscription | null ;
109111
110112 /** Old value of the native input. Used to work around issues with the `input` event on IE. */
111113 private _previousValue : string | number | null ;
@@ -286,6 +288,8 @@ export abstract class _MatAutocompleteTriggerBase
286288 this . _closingActionsSubscription . unsubscribe ( ) ;
287289 }
288290
291+ this . _updatePanelState ( ) ;
292+
289293 // Note that in some cases this can end up being called after the component is destroyed.
290294 // Add a check to ensure that we don't try to run change detection on a destroyed view.
291295 if ( ! this . _componentDestroyed ) {
@@ -545,7 +549,7 @@ export abstract class _MatAutocompleteTriggerBase
545549 this . _zone . run ( ( ) => {
546550 const wasOpen = this . panelOpen ;
547551 this . _resetActiveItem ( ) ;
548- this . autocomplete . _setVisibility ( ) ;
552+ this . _updatePanelState ( ) ;
549553 this . _changeDetectorRef . detectChanges ( ) ;
550554
551555 if ( this . panelOpen ) {
@@ -655,7 +659,6 @@ export abstract class _MatAutocompleteTriggerBase
655659 } ) ;
656660 overlayRef = this . _overlay . create ( this . _getOverlayConfig ( ) ) ;
657661 this . _overlayRef = overlayRef ;
658- this . _handleOverlayEvents ( overlayRef ) ;
659662 this . _viewportSubscription = this . _viewportRuler . change ( ) . subscribe ( ( ) => {
660663 if ( this . panelOpen && overlayRef ) {
661664 overlayRef . updateSize ( { width : this . _getPanelWidth ( ) } ) ;
@@ -674,9 +677,9 @@ export abstract class _MatAutocompleteTriggerBase
674677
675678 const wasOpen = this . panelOpen ;
676679
677- this . autocomplete . _setVisibility ( ) ;
678680 this . autocomplete . _isOpen = this . _overlayAttached = true ;
679681 this . autocomplete . _setColor ( this . _formField ?. color ) ;
682+ this . _updatePanelState ( ) ;
680683
681684 this . _applyModalPanelOwnership ( ) ;
682685
@@ -687,6 +690,58 @@ export abstract class _MatAutocompleteTriggerBase
687690 }
688691 }
689692
693+ /** Handles keyboard events coming from the overlay panel. */
694+ private _handlePanelKeydown = ( event : KeyboardEvent ) => {
695+ // Close when pressing ESCAPE or ALT + UP_ARROW, based on the a11y guidelines.
696+ // See: https://www.w3.org/TR/wai-aria-practices-1.1/#textbox-keyboard-interaction
697+ if (
698+ ( event . keyCode === ESCAPE && ! hasModifierKey ( event ) ) ||
699+ ( event . keyCode === UP_ARROW && hasModifierKey ( event , 'altKey' ) )
700+ ) {
701+ // If the user had typed something in before we autoselected an option, and they decided
702+ // to cancel the selection, restore the input value to the one they had typed in.
703+ if ( this . _pendingAutoselectedOption ) {
704+ this . _updateNativeInputValue ( this . _valueBeforeAutoSelection ?? '' ) ;
705+ this . _pendingAutoselectedOption = null ;
706+ }
707+ this . _closeKeyEventStream . next ( ) ;
708+ this . _resetActiveItem ( ) ;
709+ // We need to stop propagation, otherwise the event will eventually
710+ // reach the input itself and cause the overlay to be reopened.
711+ event . stopPropagation ( ) ;
712+ event . preventDefault ( ) ;
713+ }
714+ } ;
715+
716+ /** Updates the panel's visibility state and any trigger state tied to id. */
717+ private _updatePanelState ( ) {
718+ this . autocomplete . _setVisibility ( ) ;
719+
720+ // Note that here we subscribe and unsubscribe based on the panel's visiblity state,
721+ // because the act of subscribing will prevent events from reaching other overlays and
722+ // we don't want to block the events if there are no options.
723+ if ( this . panelOpen ) {
724+ const overlayRef = this . _overlayRef ! ;
725+
726+ if ( ! this . _keydownSubscription ) {
727+ // Use the `keydownEvents` in order to take advantage of
728+ // the overlay event targeting provided by the CDK overlay.
729+ this . _keydownSubscription = overlayRef . keydownEvents ( ) . subscribe ( this . _handlePanelKeydown ) ;
730+ }
731+
732+ if ( ! this . _outsideClickSubscription ) {
733+ // Subscribe to the pointer events stream so that it doesn't get picked up by other overlays.
734+ // TODO(crisbeto): we should switch `_getOutsideClickStream` eventually to use this stream,
735+ // but the behvior isn't exactly the same and it ends up breaking some internal tests.
736+ this . _outsideClickSubscription = overlayRef . outsidePointerEvents ( ) . subscribe ( ) ;
737+ }
738+ } else {
739+ this . _keydownSubscription ?. unsubscribe ( ) ;
740+ this . _outsideClickSubscription ?. unsubscribe ( ) ;
741+ this . _keydownSubscription = this . _outsideClickSubscription = null ;
742+ }
743+ }
744+
690745 private _getOverlayConfig ( ) : OverlayConfig {
691746 return new OverlayConfig ( {
692747 positionStrategy : this . _getOverlayPosition ( ) ,
@@ -835,40 +890,6 @@ export abstract class _MatAutocompleteTriggerBase
835890 }
836891 }
837892
838- /** Handles keyboard events coming from the overlay panel. */
839- private _handleOverlayEvents ( overlayRef : OverlayRef ) {
840- // Use the `keydownEvents` in order to take advantage of
841- // the overlay event targeting provided by the CDK overlay.
842- overlayRef . keydownEvents ( ) . subscribe ( event => {
843- // Close when pressing ESCAPE or ALT + UP_ARROW, based on the a11y guidelines.
844- // See: https://www.w3.org/TR/wai-aria-practices-1.1/#textbox-keyboard-interaction
845- if (
846- ( event . keyCode === ESCAPE && ! hasModifierKey ( event ) ) ||
847- ( event . keyCode === UP_ARROW && hasModifierKey ( event , 'altKey' ) )
848- ) {
849- // If the user had typed something in before we autoselected an option, and they decided
850- // to cancel the selection, restore the input value to the one they had typed in.
851- if ( this . _pendingAutoselectedOption ) {
852- this . _updateNativeInputValue ( this . _valueBeforeAutoSelection ?? '' ) ;
853- this . _pendingAutoselectedOption = null ;
854- }
855-
856- this . _closeKeyEventStream . next ( ) ;
857- this . _resetActiveItem ( ) ;
858-
859- // We need to stop propagation, otherwise the event will eventually
860- // reach the input itself and cause the overlay to be reopened.
861- event . stopPropagation ( ) ;
862- event . preventDefault ( ) ;
863- }
864- } ) ;
865-
866- // Subscribe to the pointer events stream so that it doesn't get picked up by other overlays.
867- // TODO(crisbeto): we should switch `_getOutsideClickStream` eventually to use this stream,
868- // but the behvior isn't exactly the same and it ends up breaking some internal tests.
869- overlayRef . outsidePointerEvents ( ) . subscribe ( ) ;
870- }
871-
872893 /**
873894 * Track which modal we have modified the `aria-owns` attribute of. When the combobox trigger is
874895 * inside an aria-modal, we apply aria-owns to the parent modal with the `id` of the options
0 commit comments