mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(autocomplete): reposition panel on scroll (#3745)
This commit is contained in:
committed by
Kara
parent
76faae5f37
commit
81a6f8dbd8
@@ -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();
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user