@@ -106,6 +106,8 @@ export abstract class _MatAutocompleteTriggerBase
106
106
private _componentDestroyed = false ;
107
107
private _autocompleteDisabled = false ;
108
108
private _scrollStrategy : ( ) => ScrollStrategy ;
109
+ private _keydownSubscription : Subscription | null ;
110
+ private _outsideClickSubscription : Subscription | null ;
109
111
110
112
/** Old value of the native input. Used to work around issues with the `input` event on IE. */
111
113
private _previousValue : string | number | null ;
@@ -286,6 +288,8 @@ export abstract class _MatAutocompleteTriggerBase
286
288
this . _closingActionsSubscription . unsubscribe ( ) ;
287
289
}
288
290
291
+ this . _updatePanelState ( ) ;
292
+
289
293
// Note that in some cases this can end up being called after the component is destroyed.
290
294
// Add a check to ensure that we don't try to run change detection on a destroyed view.
291
295
if ( ! this . _componentDestroyed ) {
@@ -545,7 +549,7 @@ export abstract class _MatAutocompleteTriggerBase
545
549
this . _zone . run ( ( ) => {
546
550
const wasOpen = this . panelOpen ;
547
551
this . _resetActiveItem ( ) ;
548
- this . autocomplete . _setVisibility ( ) ;
552
+ this . _updatePanelState ( ) ;
549
553
this . _changeDetectorRef . detectChanges ( ) ;
550
554
551
555
if ( this . panelOpen ) {
@@ -655,7 +659,6 @@ export abstract class _MatAutocompleteTriggerBase
655
659
} ) ;
656
660
overlayRef = this . _overlay . create ( this . _getOverlayConfig ( ) ) ;
657
661
this . _overlayRef = overlayRef ;
658
- this . _handleOverlayEvents ( overlayRef ) ;
659
662
this . _viewportSubscription = this . _viewportRuler . change ( ) . subscribe ( ( ) => {
660
663
if ( this . panelOpen && overlayRef ) {
661
664
overlayRef . updateSize ( { width : this . _getPanelWidth ( ) } ) ;
@@ -674,9 +677,9 @@ export abstract class _MatAutocompleteTriggerBase
674
677
675
678
const wasOpen = this . panelOpen ;
676
679
677
- this . autocomplete . _setVisibility ( ) ;
678
680
this . autocomplete . _isOpen = this . _overlayAttached = true ;
679
681
this . autocomplete . _setColor ( this . _formField ?. color ) ;
682
+ this . _updatePanelState ( ) ;
680
683
681
684
this . _applyModalPanelOwnership ( ) ;
682
685
@@ -687,6 +690,58 @@ export abstract class _MatAutocompleteTriggerBase
687
690
}
688
691
}
689
692
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
+
690
745
private _getOverlayConfig ( ) : OverlayConfig {
691
746
return new OverlayConfig ( {
692
747
positionStrategy : this . _getOverlayPosition ( ) ,
@@ -835,40 +890,6 @@ export abstract class _MatAutocompleteTriggerBase
835
890
}
836
891
}
837
892
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
-
872
893
/**
873
894
* Track which modal we have modified the `aria-owns` attribute of. When the combobox trigger is
874
895
* inside an aria-modal, we apply aria-owns to the parent modal with the `id` of the options
0 commit comments