diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 471726da3a92..6a1ea2db754a 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -19,15 +19,18 @@ import {Dir} from '../core/rtl/dir'; import { ControlValueAccessor, FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'; +import {Subject} from 'rxjs/Subject'; import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; import {dispatchFakeEvent, dispatchKeyboardEvent} from '../core/testing/dispatch-events'; import {wrappedErrorMessage} from '../core/testing/wrapped-error-message'; import {TAB} from '../core/keyboard/keycodes'; +import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher'; describe('MdSelect', () => { let overlayContainerElement: HTMLElement; let dir: {value: 'ltr'|'rtl'}; + let scrolledSubject = new Subject(); beforeEach(async(() => { TestBed.configureTestingModule({ @@ -69,7 +72,12 @@ describe('MdSelect', () => { {provide: Dir, useFactory: () => { return dir = { value: 'ltr' }; }}, - {provide: ViewportRuler, useClass: FakeViewportRuler} + {provide: ViewportRuler, useClass: FakeViewportRuler}, + {provide: ScrollDispatcher, useFactory: () => { + return {scrolled: (delay: number, callback: () => any) => { + return scrolledSubject.asObservable().subscribe(callback); + }}; + }} ] }); @@ -971,7 +979,6 @@ describe('MdSelect', () => { checkTriggerAlignedWithOption(0); }); - it('should align a centered option properly when scrolled', () => { // Give the select enough space to open fixture.componentInstance.heightBelow = 400; @@ -989,6 +996,22 @@ describe('MdSelect', () => { checkTriggerAlignedWithOption(4); }); + it('should align a centered option properly when scrolling while the panel is open', () => { + fixture.componentInstance.heightBelow = 400; + fixture.componentInstance.heightAbove = 400; + fixture.componentInstance.control.setValue('chips-4'); + fixture.detectChanges(); + + trigger.click(); + fixture.detectChanges(); + + setScrollTop(100); + scrolledSubject.next(); + fixture.detectChanges(); + + checkTriggerAlignedWithOption(4); + }); + it('should fall back to "above" positioning properly when scrolled', () => { // Give the select insufficient space to open below the trigger fixture.componentInstance.heightBelow = 100; diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index a44d58f1d1d2..3e129b1521aa 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -29,6 +29,7 @@ import {coerceBooleanProperty} from '../core/coercion/boolean-property'; import {ConnectedOverlayDirective} from '../core/overlay/overlay-directives'; import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; import {SelectionModel} from '../core/selection/selection'; +import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher'; import {MdSelectDynamicMultipleError, MdSelectNonArrayValueError} from './select-errors'; import 'rxjs/add/observable/merge'; import 'rxjs/add/operator/startWith'; @@ -133,6 +134,9 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal /** Subscription to tab events while overlay is focused. */ private _tabSubscription: Subscription; + /** Subscription to global scrolled events while the select is open. */ + private _scrollSubscription: Subscription; + /** Whether filling out the select is required in the form. */ private _required: boolean = false; @@ -317,8 +321,10 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal constructor(private _element: ElementRef, private _renderer: Renderer2, private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef, - @Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl, + private _scrollDispatcher: ScrollDispatcher, @Optional() private _dir: Dir, + @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string) { + if (this._control) { this._control.valueAccessor = this; } @@ -375,15 +381,25 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal this._calculateOverlayPosition(); this._placeholderState = this._floatPlaceholderState(); this._panelOpen = true; + this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => { + this.overlayDir.overlayRef.updatePosition(); + }); } /** Closes the overlay panel and focuses the host element. */ close(): void { if (this._panelOpen) { this._panelOpen = false; + if (this._selectionModel.isEmpty()) { this._placeholderState = ''; } + + if (this._scrollSubscription) { + this._scrollSubscription.unsubscribe(); + this._scrollSubscription = null; + } + this._focusHost(); } }