mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Andrew Seguin
parent
693c8e843e
commit
c0ff761250
@@ -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();
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user