fix(autocomplete): reposition panel on scroll (#3745)

This commit is contained in:
Kristiyan Kostadinov
2017-04-21 18:14:19 +02:00
committed by Kara
parent 76faae5f37
commit 81a6f8dbd8
2 changed files with 51 additions and 1 deletions
@@ -22,6 +22,7 @@ import {MdOptionSelectionChange, MdOption} from '../core/option/option';
import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes';
import {Dir} from '../core/rtl/dir';
import {MdInputContainer} from '../input/input-container';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
import {Subscription} from 'rxjs/Subscription';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/fromEvent';
@@ -76,6 +77,10 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
/** The subscription to positioning changes in the autocomplete panel. */
private _panelPositionSubscription: Subscription;
/** Subscription to global scroll events. */
private _scrollSubscription: Subscription;
/** Strategy that is used to position the panel. */
private _positionStrategy: ConnectedPositionStrategy;
/** Whether or not the placeholder state is being overridden. */
@@ -103,6 +108,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
constructor(private _element: ElementRef, private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef,
private _changeDetectorRef: ChangeDetectorRef,
private _scrollDispatcher: ScrollDispatcher,
@Optional() private _dir: Dir, private _zone: NgZone,
@Optional() @Host() private _inputContainer: MdInputContainer,
@Optional() @Inject(DOCUMENT) private _document: any) {}
@@ -134,6 +140,12 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
this._subscribeToClosingActions();
}
if (!this._scrollSubscription) {
this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => {
this._overlayRef.updatePosition();
});
}
this.autocomplete._setVisibility();
this._floatPlaceholder();
this._panelOpen = true;
@@ -145,6 +157,11 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
this._overlayRef.detach();
}
if (this._scrollSubscription) {
this._scrollSubscription.unsubscribe();
this._scrollSubscription = null;
}
this._panelOpen = false;
this._resetPlaceholder();
+34 -1
View File
@@ -23,14 +23,17 @@ import {FakeViewportRuler} from '../core/overlay/position/fake-viewport-ruler';
import {MdAutocomplete} from './autocomplete';
import {MdInputContainer} from '../input/input-container';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
import {typeInElement} from '../core/testing/type-in-element';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
import 'rxjs/add/operator/map';
describe('MdAutocomplete', () => {
let overlayContainerElement: HTMLElement;
let dir: LayoutDirection;
let scrolledSubject = new Subject();
beforeEach(async(() => {
dir = 'ltr';
@@ -52,6 +55,8 @@ describe('MdAutocomplete', () => {
providers: [
{provide: OverlayContainer, useFactory: () => {
overlayContainerElement = document.createElement('div');
overlayContainerElement.classList.add('cdk-overlay-container');
document.body.appendChild(overlayContainerElement);
// remove body padding to keep consistent cross-browser
@@ -63,7 +68,12 @@ describe('MdAutocomplete', () => {
{provide: Dir, useFactory: () => {
return {value: dir};
}},
{provide: ViewportRuler, useClass: FakeViewportRuler}
{provide: ViewportRuler, useClass: FakeViewportRuler},
{provide: ScrollDispatcher, useFactory: () => {
return {scrolled: (delay: number, callback: () => any) => {
return scrolledSubject.asObservable().subscribe(callback);
}};
}}
]
});
@@ -925,6 +935,29 @@ describe('MdAutocomplete', () => {
.toEqual('below', `Expected autocomplete positionY to default to below.`);
});
it('should reposition the panel on scroll', () => {
const spacer = document.createElement('div');
spacer.style.height = '1000px';
document.body.appendChild(spacer);
fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
window.scroll(0, 100);
scrolledSubject.next();
fixture.detectChanges();
const inputBottom = input.getBoundingClientRect().bottom;
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel');
const panelTop = panel.getBoundingClientRect().top;
expect((inputBottom + 6).toFixed(1)).toEqual(panelTop.toFixed(1),
'Expected panel top to match input bottom after scrolling.');
document.body.removeChild(spacer);
});
it('should fall back to above position if panel cannot fit below', () => {
// Push the autocomplete trigger down so it won't have room to open "below"
input.style.top = '600px';