From 15657b3076cce0607eac223e9ecb64bf3d5c3b95 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 27 Mar 2017 22:56:30 +0200 Subject: [PATCH 1/3] fix(select): reposition panel on scroll Repositions the select panel if the user scrolls while it is open. --- src/lib/select/select.spec.ts | 27 +++++++++++++++++++++++++-- src/lib/select/select.ts | 18 +++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) 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..52c5186eb223 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 _scrolledSubscription: 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._scrolledSubscription = 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._scrolledSubscription) { + this._scrolledSubscription.unsubscribe(); + this._scrolledSubscription = null; + } + this._focusHost(); } } From 4e3d1e62fcae43176a462e378f8fc635ef4be41d Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 27 Mar 2017 23:17:07 +0200 Subject: [PATCH 2/3] chore: fix linting error --- src/lib/select/select.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 52c5186eb223..7c860ea8b5ff 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -321,7 +321,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal constructor(private _element: ElementRef, private _renderer: Renderer2, private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef, - private _scrollDispatcher: ScrollDispatcher, @Optional() private _dir: Dir, + private _scrollDispatcher: ScrollDispatcher, @Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string) { From c2f4252d5eac9861ac25cfc3646b33d3616a37e1 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 20 Apr 2017 21:14:31 +0200 Subject: [PATCH 3/3] chore: rename to scrollSubscription --- src/lib/select/select.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 7c860ea8b5ff..3e129b1521aa 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -135,7 +135,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal private _tabSubscription: Subscription; /** Subscription to global scrolled events while the select is open. */ - private _scrolledSubscription: Subscription; + private _scrollSubscription: Subscription; /** Whether filling out the select is required in the form. */ private _required: boolean = false; @@ -381,7 +381,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal this._calculateOverlayPosition(); this._placeholderState = this._floatPlaceholderState(); this._panelOpen = true; - this._scrolledSubscription = this._scrollDispatcher.scrolled(0, () => { + this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => { this.overlayDir.overlayRef.updatePosition(); }); } @@ -395,9 +395,9 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal this._placeholderState = ''; } - if (this._scrolledSubscription) { - this._scrolledSubscription.unsubscribe(); - this._scrolledSubscription = null; + if (this._scrollSubscription) { + this._scrollSubscription.unsubscribe(); + this._scrollSubscription = null; } this._focusHost();