Skip to content

Commit

Permalink
fix(select): reposition panel on scroll (angular#3808)
Browse files Browse the repository at this point in the history
  • Loading branch information
crisbeto authored and kara committed Apr 21, 2017
1 parent 317952a commit 5983a2b
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 3 deletions.
27 changes: 25 additions & 2 deletions src/lib/select/select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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);
}};
}}
]
});

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
18 changes: 17 additions & 1 deletion src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
}
}
Expand Down

0 comments on commit 5983a2b

Please sign in to comment.