Skip to content

Commit 5983a2b

Browse files
crisbetokara
authored andcommitted
fix(select): reposition panel on scroll (#3808)
1 parent 317952a commit 5983a2b

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,15 +19,18 @@ 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, dispatchKeyboardEvent} from '../core/testing/dispatch-events';
2425
import {wrappedErrorMessage} from '../core/testing/wrapped-error-message';
2526
import {TAB} from '../core/keyboard/keycodes';
27+
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
2628

2729

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

3235
beforeEach(async(() => {
3336
TestBed.configureTestingModule({
@@ -69,7 +72,12 @@ describe('MdSelect', () => {
6972
{provide: Dir, useFactory: () => {
7073
return dir = { value: 'ltr' };
7174
}},
72-
{provide: ViewportRuler, useClass: FakeViewportRuler}
75+
{provide: ViewportRuler, useClass: FakeViewportRuler},
76+
{provide: ScrollDispatcher, useFactory: () => {
77+
return {scrolled: (delay: number, callback: () => any) => {
78+
return scrolledSubject.asObservable().subscribe(callback);
79+
}};
80+
}}
7381
]
7482
});
7583

@@ -971,7 +979,6 @@ describe('MdSelect', () => {
971979
checkTriggerAlignedWithOption(0);
972980
});
973981

974-
975982
it('should align a centered option properly when scrolled', () => {
976983
// Give the select enough space to open
977984
fixture.componentInstance.heightBelow = 400;
@@ -989,6 +996,22 @@ describe('MdSelect', () => {
989996
checkTriggerAlignedWithOption(4);
990997
});
991998

999+
it('should align a centered option properly when scrolling while the panel is open', () => {
1000+
fixture.componentInstance.heightBelow = 400;
1001+
fixture.componentInstance.heightAbove = 400;
1002+
fixture.componentInstance.control.setValue('chips-4');
1003+
fixture.detectChanges();
1004+
1005+
trigger.click();
1006+
fixture.detectChanges();
1007+
1008+
setScrollTop(100);
1009+
scrolledSubject.next();
1010+
fixture.detectChanges();
1011+
1012+
checkTriggerAlignedWithOption(4);
1013+
});
1014+
9921015
it('should fall back to "above" positioning properly when scrolled', () => {
9931016
// Give the select insufficient space to open below the trigger
9941017
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 _scrollSubscription: Subscription;
139+
136140
/** Whether filling out the select is required in the form. */
137141
private _required: boolean = false;
138142

@@ -317,8 +321,10 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
317321

318322
constructor(private _element: ElementRef, private _renderer: Renderer2,
319323
private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef,
320-
@Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl,
324+
private _scrollDispatcher: ScrollDispatcher, @Optional() private _dir: Dir,
325+
@Self() @Optional() public _control: NgControl,
321326
@Attribute('tabindex') tabIndex: string) {
327+
322328
if (this._control) {
323329
this._control.valueAccessor = this;
324330
}
@@ -375,15 +381,25 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
375381
this._calculateOverlayPosition();
376382
this._placeholderState = this._floatPlaceholderState();
377383
this._panelOpen = true;
384+
this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => {
385+
this.overlayDir.overlayRef.updatePosition();
386+
});
378387
}
379388

380389
/** Closes the overlay panel and focuses the host element. */
381390
close(): void {
382391
if (this._panelOpen) {
383392
this._panelOpen = false;
393+
384394
if (this._selectionModel.isEmpty()) {
385395
this._placeholderState = '';
386396
}
397+
398+
if (this._scrollSubscription) {
399+
this._scrollSubscription.unsubscribe();
400+
this._scrollSubscription = null;
401+
}
402+
387403
this._focusHost();
388404
}
389405
}

0 commit comments

Comments
 (0)