From 6286eccc0133656a2f5d30cd4aecdec5fc933067 Mon Sep 17 00:00:00 2001 From: Ben Elliott <4996462+benelliott@users.noreply.github.com> Date: Fri, 22 Jun 2018 01:04:36 +0100 Subject: [PATCH] feat(autocomplete): add updatePosition() method to MatAutocompleteTrigger Add an updatePosition() method to MatAutocompleteTrigger that re-evaluates the position of the autocomplete panel overlay and falls back to the above position if necessary. This is useful if enough options have been added to the panel while it is open to cause it to exceed the viewport. Fixes #11492 --- src/lib/autocomplete/autocomplete-trigger.ts | 10 +++++ src/lib/autocomplete/autocomplete.spec.ts | 45 ++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 7be99c44f356..21fe3363ec78 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -224,6 +224,16 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy { } } + /** + * Updates the position of the autocomplete suggestion panel to ensure that it fits all options + * within the viewport. + */ + updatePosition(): void { + if (this._overlayAttached) { + this._overlayRef!.updatePosition(); + } + } + /** * A stream of actions that should close the autocomplete panel, including * when an option is selected, on blur, and when TAB is pressed. diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 7d1240b602f4..84ed0cc86538 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -1478,6 +1478,50 @@ describe('MatAutocomplete', () => { .toEqual(Math.floor(panelBottom), `Expected panel to stay aligned after filtering.`); })); + it('should fall back to above position when requested if options are added while ' + + 'the panel is open', fakeAsync(() => { + let fixture = createComponent(AutocompleteWithOnPushDelay); + fixture.detectChanges(); + + let inputEl = fixture.debugElement.query(By.css('input')).nativeElement; + let inputReference = fixture.debugElement.query(By.css('.mat-form-field-flex')).nativeElement; + + // Push the element down so it has a little bit of space, but not enough to render. + inputReference.style.bottom = '10px'; + inputReference.style.position = 'fixed'; + + // Focus the input to load the deferred options. + dispatchFakeEvent(inputEl, 'focusin'); + tick(1000); + + fixture.detectChanges(); + tick(); + + const inputBottom = inputReference.getBoundingClientRect().bottom; + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!; + const panelTop = panel.getBoundingClientRect().top; + + expect(Math.floor(inputBottom)) + .toEqual(Math.floor(panelTop), + `Expected panel top to be below input before repositioning.`); + + // Request a position update now that there are too many suggestions to fit in the viewport. + fixture.componentInstance.trigger.updatePosition(); + + const inputTop = inputReference.getBoundingClientRect().top; + const panelBottom = panel.getBoundingClientRect().bottom; + + expect(Math.floor(inputTop)) + .toEqual(Math.floor(panelBottom), + `Expected panel to fall back to above position after repositioning.`); + })); + + it('should not throw if a panel reposition is requested while the panel is closed', () => { + let fixture = createComponent(SimpleAutocomplete); + fixture.detectChanges(); + + expect(() => fixture.componentInstance.trigger.updatePosition()).not.toThrow(); + }); }); describe('Option selection', () => { @@ -2198,6 +2242,7 @@ class AutocompleteWithNumbers { ` }) class AutocompleteWithOnPushDelay implements OnInit { + @ViewChild(MatAutocompleteTrigger) trigger: MatAutocompleteTrigger; options: string[]; ngOnInit() {