Skip to content

Commit 577548c

Browse files
committed
fix(autocomplete): update overlay position when options are changed
Ensures that changes to the autocomplete options will cause the component to check that its current overlay position is still within the viewport so that the overlay can fall back to the above position, if necessary, while the panel is still open. Fixes #11492
1 parent 69e4ca7 commit 577548c

File tree

2 files changed

+31
-1
lines changed

2 files changed

+31
-1
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,9 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
431431
private _subscribeToClosingActions(): Subscription {
432432
const firstStable = this._zone.onStable.asObservable().pipe(take(1));
433433
const optionChanges = this.autocomplete.options.changes.pipe(
434-
tap(() => this._positionStrategy.reapplyLastPosition()),
434+
// Adding/removing options may mean the overlay now exceeds the viewport,
435+
// so update its position
436+
tap(() => this._overlayRef!.updatePosition()),
435437
// Defer emitting to the stream until the next tick, because changing
436438
// bindings in here will cause "changed after checked" errors.
437439
delay(0)

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,34 @@ describe('MatAutocomplete', () => {
19321932
});
19331933
}));
19341934

1935+
it('should fall back to the above position if options are initialized while ' +
1936+
'the panel is open and there is not enough room to display them below', fakeAsync(() => {
1937+
let fixture = createComponent(AutocompleteWithOnPushDelay);
1938+
fixture.detectChanges();
1939+
1940+
let inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
1941+
let inputReference = fixture.debugElement.query(By.css('.mat-form-field-flex')).nativeElement;
1942+
1943+
// Push the element down so it has a little bit of space, but not enough to render.
1944+
inputReference.style.bottom = '10px';
1945+
inputReference.style.position = 'fixed';
1946+
1947+
dispatchFakeEvent(inputEl, 'focusin');
1948+
tick(1000);
1949+
1950+
fixture.detectChanges();
1951+
tick();
1952+
1953+
Promise.resolve().then(() => {
1954+
const inputTop = inputReference.getBoundingClientRect().top;
1955+
const panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!;
1956+
const panelBottom = panel.getBoundingClientRect().bottom;
1957+
1958+
expect(Math.floor(inputTop))
1959+
.toEqual(Math.floor(panelBottom), `Expected panel to fall back to above position.`);
1960+
});
1961+
}));
1962+
19351963
it('should emit an event when an option is selected', fakeAsync(() => {
19361964
let fixture = createComponent(AutocompleteWithSelectEvent);
19371965

0 commit comments

Comments
 (0)