@@ -146,9 +146,6 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
146146 /** Emits when the option is clicked. */
147147 readonly _clicked = new Subject < MouseEvent > ( ) ;
148148
149- /** Whether the option is currently active. */
150- private _active = false ;
151-
152149 ngOnDestroy ( ) {
153150 this . destroyed . next ( ) ;
154151 this . destroyed . complete ( ) ;
@@ -161,7 +158,7 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
161158
162159 /** Whether this option is active. */
163160 isActive ( ) {
164- return this . _active ;
161+ return this . listbox . isActive ( this ) ;
165162 }
166163
167164 /** Toggle the selected state of this option. */
@@ -190,20 +187,16 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
190187 }
191188
192189 /**
193- * Set the option as active .
190+ * No-op implemented as a part of `Highlightable` .
194191 * @docs -private
195192 */
196- setActiveStyles ( ) {
197- this . _active = true ;
198- }
193+ setActiveStyles ( ) { }
199194
200195 /**
201- * Set the option as inactive .
196+ * No-op implemented as a part of `Highlightable` .
202197 * @docs -private
203198 */
204- setInactiveStyles ( ) {
205- this . _active = false ;
206- }
199+ setInactiveStyles ( ) { }
207200
208201 /** Handle focus events on the option. */
209202 protected _handleFocus ( ) {
@@ -240,6 +233,7 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
240233 '(focus)' : '_handleFocus()' ,
241234 '(keydown)' : '_handleKeydown($event)' ,
242235 '(focusout)' : '_handleFocusOut($event)' ,
236+ '(focusin)' : '_handleFocusIn()' ,
243237 } ,
244238 providers : [
245239 {
@@ -419,6 +413,9 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
419413 /** A predicate that does not skip any options. */
420414 private readonly _skipNonePredicate = ( ) => false ;
421415
416+ /** Whether the listbox currently has focus. */
417+ private _hasFocus = false ;
418+
422419 ngAfterContentInit ( ) {
423420 if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
424421 this . _verifyNoOptionValueCollisions ( ) ;
@@ -526,6 +523,14 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
526523 return this . isValueSelected ( option . value ) ;
527524 }
528525
526+ /**
527+ * Get whether the given option is active.
528+ * @param option The option to get the active state of
529+ */
530+ isActive ( option : CdkOption < T > ) : boolean {
531+ return ! ! ( this . listKeyManager ?. activeItem === option ) ;
532+ }
533+
529534 /**
530535 * Get whether the given value is selected.
531536 * @param value The value to get the selected state of
@@ -653,7 +658,12 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
653658 /** Called when the listbox receives focus. */
654659 protected _handleFocus ( ) {
655660 if ( ! this . useActiveDescendant ) {
656- this . listKeyManager . setNextItemActive ( ) ;
661+ if ( this . selectionModel . selected . length > 0 ) {
662+ this . _setNextFocusToSelectedOption ( ) ;
663+ } else {
664+ this . listKeyManager . setNextItemActive ( ) ;
665+ }
666+
657667 this . _focusActiveOption ( ) ;
658668 }
659669 }
@@ -759,6 +769,13 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
759769 }
760770 }
761771
772+ /** Called when a focus moves into the listbox. */
773+ protected _handleFocusIn ( ) {
774+ // Note that we use a `focusin` handler for this instead of the existing `focus` handler,
775+ // because focus won't land on the listbox if `useActiveDescendant` is enabled.
776+ this . _hasFocus = true ;
777+ }
778+
762779 /**
763780 * Called when the focus leaves an element in the listbox.
764781 * @param event The focusout event
@@ -767,6 +784,8 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
767784 const otherElement = event . relatedTarget as Element ;
768785 if ( this . element !== otherElement && ! this . element . contains ( otherElement ) ) {
769786 this . _onTouched ( ) ;
787+ this . _hasFocus = false ;
788+ this . _setNextFocusToSelectedOption ( ) ;
770789 }
771790 }
772791
@@ -800,6 +819,10 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
800819 this . listKeyManager . withHorizontalOrientation ( this . _dir ?. value || 'ltr' ) ;
801820 }
802821
822+ if ( this . selectionModel . selected . length ) {
823+ Promise . resolve ( ) . then ( ( ) => this . _setNextFocusToSelectedOption ( ) ) ;
824+ }
825+
803826 this . listKeyManager . change . subscribe ( ( ) => this . _focusActiveOption ( ) ) ;
804827 }
805828
@@ -820,6 +843,20 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
820843 this . selectionModel . clear ( false ) ;
821844 }
822845 this . selectionModel . setSelection ( ...this . _coerceValue ( value ) ) ;
846+
847+ if ( ! this . _hasFocus ) {
848+ this . _setNextFocusToSelectedOption ( ) ;
849+ }
850+ }
851+
852+ /** Sets the first selected option as first in the keyboard focus order. */
853+ private _setNextFocusToSelectedOption ( ) {
854+ // Null check the options since they only get defined after `ngAfterContentInit`.
855+ const selected = this . options ?. find ( option => option . isSelected ( ) ) ;
856+
857+ if ( selected ) {
858+ this . listKeyManager . updateActiveItem ( selected ) ;
859+ }
823860 }
824861
825862 /** Update the internal value of the listbox based on the selection model. */
0 commit comments