@@ -144,6 +144,9 @@ export class MatAutocompleteTrigger
144
144
/** Value of the input element when the panel was attached (even if there are no options). */
145
145
private _valueOnAttach : string | number | null ;
146
146
147
+ /** Value on the previous keydown event. */
148
+ private _valueOnLastKeydown : string | null ;
149
+
147
150
/** Strategy that is used to position the panel. */
148
151
private _positionStrategy : FlexibleConnectedPositionStrategy ;
149
152
@@ -285,13 +288,7 @@ export class MatAutocompleteTrigger
285
288
286
289
/** Opens the autocomplete suggestion panel. */
287
290
openPanel ( ) : void {
288
- this . _attachOverlay ( ) ;
289
- this . _floatLabel ( ) ;
290
- // Add aria-owns attribute when the autocomplete becomes visible.
291
- if ( this . _trackedModal ) {
292
- const panelId = this . autocomplete . id ;
293
- addAriaReferencedId ( this . _trackedModal , 'aria-owns' , panelId ) ;
294
- }
291
+ this . _openPanelInternal ( ) ;
295
292
}
296
293
297
294
/** Closes the autocomplete suggestion panel. */
@@ -461,6 +458,8 @@ export class MatAutocompleteTrigger
461
458
event . preventDefault ( ) ;
462
459
}
463
460
461
+ this . _valueOnLastKeydown = this . _element . nativeElement . value ;
462
+
464
463
if ( this . activeOption && keyCode === ENTER && this . panelOpen && ! hasModifier ) {
465
464
this . activeOption . _selectViaInteraction ( ) ;
466
465
this . _resetActiveItem ( ) ;
@@ -472,15 +471,15 @@ export class MatAutocompleteTrigger
472
471
if ( keyCode === TAB || ( isArrowKey && ! hasModifier && this . panelOpen ) ) {
473
472
this . autocomplete . _keyManager . onKeydown ( event ) ;
474
473
} else if ( isArrowKey && this . _canOpen ( ) ) {
475
- this . openPanel ( ) ;
474
+ this . _openPanelInternal ( this . _valueOnLastKeydown ) ;
476
475
}
477
476
478
477
if ( isArrowKey || this . autocomplete . _keyManager . activeItem !== prevActiveItem ) {
479
478
this . _scrollToOption ( this . autocomplete . _keyManager . activeItemIndex || 0 ) ;
480
479
481
480
if ( this . autocomplete . autoSelectActiveOption && this . activeOption ) {
482
481
if ( ! this . _pendingAutoselectedOption ) {
483
- this . _valueBeforeAutoSelection = this . _element . nativeElement . value ;
482
+ this . _valueBeforeAutoSelection = this . _valueOnLastKeydown ;
484
483
}
485
484
486
485
this . _pendingAutoselectedOption = this . activeOption ;
@@ -523,7 +522,7 @@ export class MatAutocompleteTrigger
523
522
const selectedOption = this . autocomplete . options ?. find ( option => option . selected ) ;
524
523
525
524
if ( selectedOption ) {
526
- const display = this . autocomplete . displayWith ?. ( selectedOption ) ?? selectedOption . value ;
525
+ const display = this . _getDisplayValue ( selectedOption . value ) ;
527
526
528
527
if ( value !== display ) {
529
528
selectedOption . deselect ( false ) ;
@@ -532,7 +531,14 @@ export class MatAutocompleteTrigger
532
531
}
533
532
534
533
if ( this . _canOpen ( ) && this . _document . activeElement === event . target ) {
535
- this . openPanel ( ) ;
534
+ // When the `input` event fires, the input's value will have already changed. This means
535
+ // that if we take the `this._element.nativeElement.value` directly, it'll be one keystroke
536
+ // behind. This can be a problem when the user selects a value, changes a character while
537
+ // the input still has focus and then clicks away (see #28432). To work around it, we
538
+ // capture the value in `keydown` so we can use it here.
539
+ const valueOnAttach = this . _valueOnLastKeydown ?? this . _element . nativeElement . value ;
540
+ this . _valueOnLastKeydown = null ;
541
+ this . _openPanelInternal ( valueOnAttach ) ;
536
542
}
537
543
}
538
544
}
@@ -542,14 +548,14 @@ export class MatAutocompleteTrigger
542
548
this . _canOpenOnNextFocus = true ;
543
549
} else if ( this . _canOpen ( ) ) {
544
550
this . _previousValue = this . _element . nativeElement . value ;
545
- this . _attachOverlay ( ) ;
551
+ this . _attachOverlay ( this . _previousValue ) ;
546
552
this . _floatLabel ( true ) ;
547
553
}
548
554
}
549
555
550
556
_handleClick ( ) : void {
551
557
if ( this . _canOpen ( ) && ! this . panelOpen ) {
552
- this . openPanel ( ) ;
558
+ this . _openPanelInternal ( ) ;
553
559
}
554
560
}
555
561
@@ -657,11 +663,14 @@ export class MatAutocompleteTrigger
657
663
}
658
664
}
659
665
666
+ /** Given a value, returns the string that should be shown within the input. */
667
+ private _getDisplayValue < T > ( value : T ) : T | string {
668
+ const autocomplete = this . autocomplete ;
669
+ return autocomplete && autocomplete . displayWith ? autocomplete . displayWith ( value ) : value ;
670
+ }
671
+
660
672
private _assignOptionValue ( value : any ) : void {
661
- const toDisplay =
662
- this . autocomplete && this . autocomplete . displayWith
663
- ? this . autocomplete . displayWith ( value )
664
- : value ;
673
+ const toDisplay = this . _getDisplayValue ( value ) ;
665
674
666
675
if ( value == null ) {
667
676
this . _clearPreviousSelectedOption ( null , false ) ;
@@ -733,7 +742,17 @@ export class MatAutocompleteTrigger
733
742
} ) ;
734
743
}
735
744
736
- private _attachOverlay ( ) : void {
745
+ private _openPanelInternal ( valueOnAttach = this . _element . nativeElement . value ) {
746
+ this . _attachOverlay ( valueOnAttach ) ;
747
+ this . _floatLabel ( ) ;
748
+ // Add aria-owns attribute when the autocomplete becomes visible.
749
+ if ( this . _trackedModal ) {
750
+ const panelId = this . autocomplete . id ;
751
+ addAriaReferencedId ( this . _trackedModal , 'aria-owns' , panelId ) ;
752
+ }
753
+ }
754
+
755
+ private _attachOverlay ( valueOnAttach : string ) : void {
737
756
if ( ! this . autocomplete && ( typeof ngDevMode === 'undefined' || ngDevMode ) ) {
738
757
throw getMatAutocompleteMissingPanelError ( ) ;
739
758
}
@@ -759,7 +778,8 @@ export class MatAutocompleteTrigger
759
778
760
779
if ( overlayRef && ! overlayRef . hasAttached ( ) ) {
761
780
overlayRef . attach ( this . _portal ) ;
762
- this . _valueOnAttach = this . _element . nativeElement . value ;
781
+ this . _valueOnAttach = valueOnAttach ;
782
+ this . _valueOnLastKeydown = null ;
763
783
this . _closingActionsSubscription = this . _subscribeToClosingActions ( ) ;
764
784
}
765
785
0 commit comments