@@ -146,9 +146,6 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
146
146
/** Emits when the option is clicked. */
147
147
readonly _clicked = new Subject < MouseEvent > ( ) ;
148
148
149
- /** Whether the option is currently active. */
150
- private _active = false ;
151
-
152
149
ngOnDestroy ( ) {
153
150
this . destroyed . next ( ) ;
154
151
this . destroyed . complete ( ) ;
@@ -161,7 +158,7 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
161
158
162
159
/** Whether this option is active. */
163
160
isActive ( ) {
164
- return this . _active ;
161
+ return this . listbox . isActive ( this ) ;
165
162
}
166
163
167
164
/** Toggle the selected state of this option. */
@@ -190,20 +187,16 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
190
187
}
191
188
192
189
/**
193
- * Set the option as active .
190
+ * No-op implemented as a part of `Highlightable` .
194
191
* @docs -private
195
192
*/
196
- setActiveStyles ( ) {
197
- this . _active = true ;
198
- }
193
+ setActiveStyles ( ) { }
199
194
200
195
/**
201
- * Set the option as inactive .
196
+ * No-op implemented as a part of `Highlightable` .
202
197
* @docs -private
203
198
*/
204
- setInactiveStyles ( ) {
205
- this . _active = false ;
206
- }
199
+ setInactiveStyles ( ) { }
207
200
208
201
/** Handle focus events on the option. */
209
202
protected _handleFocus ( ) {
@@ -240,6 +233,7 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
240
233
'(focus)' : '_handleFocus()' ,
241
234
'(keydown)' : '_handleKeydown($event)' ,
242
235
'(focusout)' : '_handleFocusOut($event)' ,
236
+ '(focusin)' : '_handleFocusIn()' ,
243
237
} ,
244
238
providers : [
245
239
{
@@ -419,6 +413,9 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
419
413
/** A predicate that does not skip any options. */
420
414
private readonly _skipNonePredicate = ( ) => false ;
421
415
416
+ /** Whether the listbox currently has focus. */
417
+ private _hasFocus = false ;
418
+
422
419
ngAfterContentInit ( ) {
423
420
if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
424
421
this . _verifyNoOptionValueCollisions ( ) ;
@@ -526,6 +523,14 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
526
523
return this . isValueSelected ( option . value ) ;
527
524
}
528
525
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
+
529
534
/**
530
535
* Get whether the given value is selected.
531
536
* @param value The value to get the selected state of
@@ -653,7 +658,12 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
653
658
/** Called when the listbox receives focus. */
654
659
protected _handleFocus ( ) {
655
660
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
+
657
667
this . _focusActiveOption ( ) ;
658
668
}
659
669
}
@@ -759,6 +769,13 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
759
769
}
760
770
}
761
771
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
+
762
779
/**
763
780
* Called when the focus leaves an element in the listbox.
764
781
* @param event The focusout event
@@ -767,6 +784,8 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
767
784
const otherElement = event . relatedTarget as Element ;
768
785
if ( this . element !== otherElement && ! this . element . contains ( otherElement ) ) {
769
786
this . _onTouched ( ) ;
787
+ this . _hasFocus = false ;
788
+ this . _setNextFocusToSelectedOption ( ) ;
770
789
}
771
790
}
772
791
@@ -800,6 +819,10 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
800
819
this . listKeyManager . withHorizontalOrientation ( this . _dir ?. value || 'ltr' ) ;
801
820
}
802
821
822
+ if ( this . selectionModel . selected . length ) {
823
+ Promise . resolve ( ) . then ( ( ) => this . _setNextFocusToSelectedOption ( ) ) ;
824
+ }
825
+
803
826
this . listKeyManager . change . subscribe ( ( ) => this . _focusActiveOption ( ) ) ;
804
827
}
805
828
@@ -820,6 +843,20 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
820
843
this . selectionModel . clear ( false ) ;
821
844
}
822
845
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
+ }
823
860
}
824
861
825
862
/** Update the internal value of the listbox based on the selection model. */
0 commit comments