Skip to content

Commit

Permalink
fix(autocomplete): reposition panel on scroll (#3745)
Browse files Browse the repository at this point in the history
  • Loading branch information
crisbeto authored and kara committed Apr 21, 2017
1 parent 76faae5 commit 81a6f8d
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 1 deletion.
17 changes: 17 additions & 0 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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) {}
Expand Down Expand Up @@ -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;
Expand All @@ -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();

Expand Down
35 changes: 34 additions & 1 deletion src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand All @@ -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);
}};
}}
]
});

Expand Down Expand Up @@ -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';
Expand Down

0 comments on commit 81a6f8d

Please sign in to comment.