Skip to content

Commit e07b618

Browse files
committed
fix(select): reposition panel on scroll
Repositions the select panel if the user scrolls while it is open.
1 parent 8d0cd04 commit e07b618

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

src/lib/select/select.spec.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ import {Dir} from '../core/rtl/dir';
1919
import {
2020
ControlValueAccessor, FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule
2121
} from '@angular/forms';
22+
import {Subject} from 'rxjs/Subject';
2223
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
2324
import {dispatchFakeEvent} from '../core/testing/dispatch-events';
2425
import {wrappedErrorMessage} from '../core/testing/wrapped-error-message';
26+
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
2527

2628

2729
describe('MdSelect', () => {
2830
let overlayContainerElement: HTMLElement;
2931
let dir: {value: 'ltr'|'rtl'};
32+
let scrolledSubject = new Subject();
3033

3134
beforeEach(async(() => {
3235
TestBed.configureTestingModule({
@@ -67,7 +70,12 @@ describe('MdSelect', () => {
6770
{provide: Dir, useFactory: () => {
6871
return dir = { value: 'ltr' };
6972
}},
70-
{provide: ViewportRuler, useClass: FakeViewportRuler}
73+
{provide: ViewportRuler, useClass: FakeViewportRuler},
74+
{provide: ScrollDispatcher, useFactory: () => {
75+
return {scrolled: (delay: number, callback: () => any) => {
76+
return scrolledSubject.asObservable().subscribe(callback);
77+
}};
78+
}}
7179
]
7280
});
7381

@@ -955,7 +963,6 @@ describe('MdSelect', () => {
955963
checkTriggerAlignedWithOption(0);
956964
});
957965

958-
959966
it('should align a centered option properly when scrolled', () => {
960967
// Give the select enough space to open
961968
fixture.componentInstance.heightBelow = 400;
@@ -973,6 +980,22 @@ describe('MdSelect', () => {
973980
checkTriggerAlignedWithOption(4);
974981
});
975982

983+
it('should align a centered option properly when scrolling while the panel is open', () => {
984+
fixture.componentInstance.heightBelow = 400;
985+
fixture.componentInstance.heightAbove = 400;
986+
fixture.componentInstance.control.setValue('chips-4');
987+
fixture.detectChanges();
988+
989+
trigger.click();
990+
fixture.detectChanges();
991+
992+
setScrollTop(100);
993+
scrolledSubject.next();
994+
fixture.detectChanges();
995+
996+
checkTriggerAlignedWithOption(4);
997+
});
998+
976999
it('should fall back to "above" positioning properly when scrolled', () => {
9771000
// Give the select insufficient space to open below the trigger
9781001
fixture.componentInstance.heightBelow = 100;

src/lib/select/select.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {coerceBooleanProperty} from '../core/coercion/boolean-property';
2929
import {ConnectedOverlayDirective} from '../core/overlay/overlay-directives';
3030
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
3131
import {SelectionModel} from '../core/selection/selection';
32+
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
3233
import {MdSelectDynamicMultipleError, MdSelectNonArrayValueError} from './select-errors';
3334
import 'rxjs/add/observable/merge';
3435
import 'rxjs/add/operator/startWith';
@@ -133,6 +134,9 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
133134
/** Subscription to tab events while overlay is focused. */
134135
private _tabSubscription: Subscription;
135136

137+
/** Subscription to global scrolled events while the select is open. */
138+
private _scrolledSubscription: Subscription;
139+
136140
/** Whether filling out the select is required in the form. */
137141
private _required: boolean = false;
138142

@@ -303,8 +307,10 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
303307

304308
constructor(private _element: ElementRef, private _renderer: Renderer,
305309
private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef,
306-
@Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl,
310+
private _scrollDispatcher: ScrollDispatcher, @Optional() private _dir: Dir,
311+
@Self() @Optional() public _control: NgControl,
307312
@Attribute('tabindex') tabIndex: string) {
313+
308314
if (this._control) {
309315
this._control.valueAccessor = this;
310316
}
@@ -360,15 +366,25 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
360366
this._calculateOverlayPosition();
361367
this._placeholderState = this._floatPlaceholderState();
362368
this._panelOpen = true;
369+
this._scrolledSubscription = this._scrollDispatcher.scrolled(0, () => {
370+
this.overlayDir.overlayRef.updatePosition();
371+
});
363372
}
364373

365374
/** Closes the overlay panel and focuses the host element. */
366375
close(): void {
367376
if (this._panelOpen) {
368377
this._panelOpen = false;
378+
369379
if (this._selectionModel.isEmpty()) {
370380
this._placeholderState = '';
371381
}
382+
383+
if (this._scrolledSubscription) {
384+
this._scrolledSubscription.unsubscribe();
385+
this._scrolledSubscription = null;
386+
}
387+
372388
this._focusHost();
373389
}
374390
}

0 commit comments

Comments
 (0)