feat(close-scroll-strategy): add scroll threshold option (#8656)

Adds the `threshold` option to the `CloseScrollStrategy` that allows for the consumer to specify the amount of pixels that the user has to scroll before the overlay is closed.
This commit is contained in:
Kristiyan Kostadinov
2017-12-13 22:54:45 +01:00
committed by Andrew Seguin
parent 693c8e843e
commit c0ff761250
3 changed files with 112 additions and 19 deletions
@@ -1,8 +1,8 @@
import {inject, TestBed, async} from '@angular/core/testing';
import {inject, TestBed, fakeAsync} from '@angular/core/testing';
import {NgModule, Component, NgZone} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import {ComponentPortal, PortalModule} from '@angular/cdk/portal';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
import {ScrollDispatcher, ViewportRuler} from '@angular/cdk/scrolling';
import {
Overlay,
OverlayConfig,
@@ -16,13 +16,19 @@ describe('CloseScrollStrategy', () => {
let overlayRef: OverlayRef;
let componentPortal: ComponentPortal<MozarellaMsg>;
let scrolledSubject = new Subject();
let scrollPosition: number;
beforeEach(fakeAsync(() => {
scrollPosition = 0;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [OverlayModule, PortalModule, OverlayTestModule],
providers: [
{provide: ScrollDispatcher, useFactory: () => ({
scrolled: () => scrolledSubject.asObservable()
})},
{provide: ViewportRuler, useFactory: () => ({
getViewportScrollPosition: () => ({top: scrollPosition})
})}
]
});
@@ -70,6 +76,60 @@ describe('CloseScrollStrategy', () => {
subscription.unsubscribe();
});
it('should be able to reposition the overlay up to a certain threshold before closing',
inject([Overlay], (overlay: Overlay) => {
overlayRef.dispose();
overlayRef = overlay.create({
scrollStrategy: overlay.scrollStrategies.close({threshold: 50})
});
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
spyOn(overlayRef, 'detach');
for (let i = 0; i < 50; i++) {
scrollPosition++;
scrolledSubject.next();
}
expect(overlayRef.updatePosition).toHaveBeenCalledTimes(50);
expect(overlayRef.detach).not.toHaveBeenCalled();
scrollPosition++;
scrolledSubject.next();
expect(overlayRef.detach).toHaveBeenCalledTimes(1);
}));
it('should not close if the user starts scrolling away and comes back',
inject([Overlay], (overlay: Overlay) => {
overlayRef.dispose();
scrollPosition = 100;
overlayRef = overlay.create({
scrollStrategy: overlay.scrollStrategies.close({threshold: 50})
});
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
spyOn(overlayRef, 'detach');
// Scroll down 30px.
for (let i = 0; i < 30; i++) {
scrollPosition++;
scrolledSubject.next();
}
// Scroll back up 30px.
for (let i = 0; i < 30; i++) {
scrollPosition--;
scrolledSubject.next();
}
expect(overlayRef.updatePosition).toHaveBeenCalledTimes(60);
expect(overlayRef.detach).not.toHaveBeenCalled();
}));
});
+42 -12
View File
@@ -5,13 +5,19 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgZone} from '@angular/core';
import {ScrollStrategy, getMatScrollStrategyAlreadyAttachedError} from './scroll-strategy';
import {OverlayRef} from '../overlay-ref';
import {Subscription} from 'rxjs/Subscription';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
import {ScrollDispatcher, ViewportRuler} from '@angular/cdk/scrolling';
/**
* Config options for the CloseScrollStrategy.
*/
export interface CloseScrollStrategyConfig {
/** Amount of pixels the user has to scroll before the overlay is closed. */
threshold?: number;
}
/**
* Strategy that will close the overlay as soon as the user starts scrolling.
@@ -19,8 +25,13 @@ import {ScrollDispatcher} from '@angular/cdk/scrolling';
export class CloseScrollStrategy implements ScrollStrategy {
private _scrollSubscription: Subscription|null = null;
private _overlayRef: OverlayRef;
private _initialScrollPosition: number;
constructor(private _scrollDispatcher: ScrollDispatcher, private _ngZone: NgZone) { }
constructor(
private _scrollDispatcher: ScrollDispatcher,
private _ngZone: NgZone,
private _viewportRuler: ViewportRuler,
private _config?: CloseScrollStrategyConfig) {}
/** Attaches this scroll strategy to an overlay. */
attach(overlayRef: OverlayRef) {
@@ -31,18 +42,28 @@ export class CloseScrollStrategy implements ScrollStrategy {
this._overlayRef = overlayRef;
}
/** Enables the closing of the attached on scroll. */
/** Enables the closing of the attached overlay on scroll. */
enable() {
if (!this._scrollSubscription) {
this._scrollSubscription = this._scrollDispatcher.scrolled(0).subscribe(() => {
this._ngZone.run(() => {
this.disable();
if (this._scrollSubscription) {
return;
}
if (this._overlayRef.hasAttached()) {
this._overlayRef.detach();
}
});
const stream = this._scrollDispatcher.scrolled(0);
if (this._config && this._config.threshold && this._config.threshold > 1) {
this._initialScrollPosition = this._viewportRuler.getViewportScrollPosition().top;
this._scrollSubscription = stream.subscribe(() => {
const scrollPosition = this._viewportRuler.getViewportScrollPosition().top;
if (Math.abs(scrollPosition - this._initialScrollPosition) > this._config!.threshold!) {
this._detach();
} else {
this._overlayRef.updatePosition();
}
});
} else {
this._scrollSubscription = stream.subscribe(this._detach);
}
}
@@ -53,4 +74,13 @@ export class CloseScrollStrategy implements ScrollStrategy {
this._scrollSubscription = null;
}
}
/** Detaches the overlay ref and disables the scroll strategy. */
private _detach = () => {
this.disable();
if (this._overlayRef.hasAttached()) {
this._ngZone.run(() => this._overlayRef.detach());
}
}
}
@@ -5,9 +5,8 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable, NgZone} from '@angular/core';
import {CloseScrollStrategy} from './close-scroll-strategy';
import {CloseScrollStrategy, CloseScrollStrategyConfig} from './close-scroll-strategy';
import {NoopScrollStrategy} from './noop-scroll-strategy';
import {BlockScrollStrategy} from './block-scroll-strategy';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
@@ -34,8 +33,12 @@ export class ScrollStrategyOptions {
/** Do nothing on scroll. */
noop = () => new NoopScrollStrategy();
/** Close the overlay as soon as the user scrolls. */
close = () => new CloseScrollStrategy(this._scrollDispatcher, this._ngZone);
/**
* Close the overlay as soon as the user scrolls.
* @param config Configuration to be used inside the scroll strategy.
*/
close = (config?: CloseScrollStrategyConfig) => new CloseScrollStrategy(this._scrollDispatcher,
this._ngZone, this._viewportRuler, config)
/** Block scrolling. */
block = () => new BlockScrollStrategy(this._viewportRuler);