Skip to content

Commit 3fd470e

Browse files
author
Kristiyan Kostadinov
committed
fix(select): reposition panel on scroll
Repositions the select panel after the user has scrolled, ensuring that it stays in the right place.
1 parent e964734 commit 3fd470e

File tree

2 files changed

+43
-3
lines changed

2 files changed

+43
-3
lines changed

src/lib/select/select.spec.ts

Lines changed: 26 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: string};
32+
let scrolledSubject = new Subject();
3033

3134
beforeEach(async(() => {
3235
TestBed.configureTestingModule({
@@ -64,7 +67,12 @@ describe('MdSelect', () => {
6467
{provide: Dir, useFactory: () => {
6568
return dir = { value: 'ltr' };
6669
}},
67-
{provide: ViewportRuler, useClass: FakeViewportRuler}
70+
{provide: ViewportRuler, useClass: FakeViewportRuler},
71+
{provide: ScrollDispatcher, useFactory: () => {
72+
return {scrolled: (delay: number, callback: () => any) => {
73+
return scrolledSubject.asObservable().subscribe(callback);
74+
}};
75+
}}
6876
]
6977
});
7078

@@ -918,7 +926,6 @@ describe('MdSelect', () => {
918926
checkTriggerAlignedWithOption(0);
919927
});
920928

921-
922929
it('should align a centered option properly when scrolled', () => {
923930
// Give the select enough space to open
924931
fixture.componentInstance.heightBelow = 400;
@@ -936,6 +943,23 @@ describe('MdSelect', () => {
936943
checkTriggerAlignedWithOption(4);
937944
});
938945

946+
it('should align a centered option properly when scrolling while the panel is open', () => {
947+
// Give the select enough space to open
948+
fixture.componentInstance.heightBelow = 400;
949+
fixture.componentInstance.heightAbove = 400;
950+
fixture.componentInstance.control.setValue('chips-4');
951+
fixture.detectChanges();
952+
953+
trigger.click();
954+
fixture.detectChanges();
955+
956+
setScrollTop(100);
957+
scrolledSubject.next();
958+
fixture.detectChanges();
959+
960+
checkTriggerAlignedWithOption(4);
961+
});
962+
939963
it('should fall back to "above" positioning properly when scrolled', () => {
940964
// Give the select insufficient space to open below the trigger
941965
fixture.componentInstance.heightBelow = 100;

src/lib/select/select.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {coerceBooleanProperty} from '../core/coercion/boolean-property';
2828
import {ConnectedOverlayDirective} from '../core/overlay/overlay-directives';
2929
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
3030
import {SelectionModel} from '../core/selection/selection';
31+
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
3132
import {MdSelectDynamicMultipleError, MdSelectNonArrayValueError} from './select-errors';
3233
import 'rxjs/add/observable/merge';
3334
import 'rxjs/add/operator/startWith';
@@ -131,6 +132,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
131132
/** Subscription to tab events while overlay is focused. */
132133
private _tabSubscription: Subscription;
133134

135+
/** Subscription to global scrolled events while the select is open. */
136+
private _scrolledSubscription: Subscription;
137+
134138
/** Whether filling out the select is required in the form. */
135139
private _required: boolean = false;
136140

@@ -295,8 +299,10 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
295299

296300
constructor(private _element: ElementRef, private _renderer: Renderer,
297301
private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef,
298-
@Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl,
302+
private _scrollDispatcher: ScrollDispatcher, @Optional() private _dir: Dir,
303+
@Self() @Optional() public _control: NgControl,
299304
@Attribute('tabindex') tabIndex: string) {
305+
300306
if (this._control) {
301307
this._control.valueAccessor = this;
302308
}
@@ -344,15 +350,25 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
344350
this._calculateOverlayPosition();
345351
this._placeholderState = this._floatPlaceholderState();
346352
this._panelOpen = true;
353+
this._scrolledSubscription = this._scrollDispatcher.scrolled(0, () => {
354+
this.overlayDir.overlayRef.updatePosition();
355+
});
347356
}
348357

349358
/** Closes the overlay panel and focuses the host element. */
350359
close(): void {
351360
if (this._panelOpen) {
352361
this._panelOpen = false;
362+
353363
if (this._selectionModel.isEmpty()) {
354364
this._placeholderState = '';
355365
}
366+
367+
if (this._scrolledSubscription) {
368+
this._scrolledSubscription.unsubscribe();
369+
this._scrolledSubscription = null;
370+
}
371+
356372
this._focusHost();
357373
}
358374
}

0 commit comments

Comments
 (0)