feat(reposition-scroll-strategy): add option for closing once the user scrolls away (#8233)

Adds an option to the `RepositionScrollStrategy` that tells it to close the overlay once the user has scrolled away. This is a steps towards moving the scroll clipping logic away from the `ConnectedPositionStrategy`.
This commit is contained in:
Kristiyan Kostadinov
2017-11-20 19:54:01 +01:00
committed by Jeremy Elbourn
parent dad5922025
commit 58598c457f
3 changed files with 69 additions and 7 deletions
@@ -14,6 +14,7 @@ import {
describe('RepositionScrollStrategy', () => {
let overlayRef: OverlayRef;
let overlay: Overlay;
let componentPortal: ComponentPortal<PastaMsg>;
let scrolledSubject = new Subject();
@@ -30,9 +31,8 @@ describe('RepositionScrollStrategy', () => {
TestBed.compileComponents();
}));
beforeEach(inject([Overlay], (overlay: Overlay) => {
let overlayConfig = new OverlayConfig({scrollStrategy: overlay.scrollStrategies.reposition()});
overlayRef = overlay.create(overlayConfig);
beforeEach(inject([Overlay], (o: Overlay) => {
overlay = o;
componentPortal = new ComponentPortal(PastaMsg);
}));
@@ -42,6 +42,11 @@ describe('RepositionScrollStrategy', () => {
}));
it('should update the overlay position when the page is scrolled', () => {
const overlayConfig = new OverlayConfig({
scrollStrategy: overlay.scrollStrategies.reposition()
});
overlayRef = overlay.create(overlayConfig);
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
@@ -53,6 +58,11 @@ describe('RepositionScrollStrategy', () => {
});
it('should not be updating the position after the overlay is detached', () => {
const overlayConfig = new OverlayConfig({
scrollStrategy: overlay.scrollStrategies.reposition()
});
overlayRef = overlay.create(overlayConfig);
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
@@ -63,6 +73,11 @@ describe('RepositionScrollStrategy', () => {
});
it('should not be updating the position after the overlay is destroyed', () => {
const overlayConfig = new OverlayConfig({
scrollStrategy: overlay.scrollStrategies.reposition()
});
overlayRef = overlay.create(overlayConfig);
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
@@ -72,6 +87,30 @@ describe('RepositionScrollStrategy', () => {
expect(overlayRef.updatePosition).not.toHaveBeenCalled();
});
it('should be able to close the overlay once it is out of view', () => {
const overlayConfig = new OverlayConfig({
scrollStrategy: overlay.scrollStrategies.reposition({
autoClose: true
})
});
overlayRef = overlay.create(overlayConfig);
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
spyOn(overlayRef, 'detach');
spyOn(overlayRef.overlayElement, 'getBoundingClientRect').and.returnValue({
top: -1000,
bottom: -900,
left: 0,
right: 100,
width: 100,
height: 100
});
scrolledSubject.next();
expect(overlayRef.detach).toHaveBeenCalledTimes(1);
});
});
@@ -6,16 +6,22 @@
* found in the LICENSE file at https://angular.io/license
*/
import {NgZone} from '@angular/core';
import {Subscription} from 'rxjs/Subscription';
import {ScrollStrategy, getMatScrollStrategyAlreadyAttachedError} from './scroll-strategy';
import {OverlayRef} from '../overlay-ref';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
import {ScrollDispatcher, ViewportRuler} from '@angular/cdk/scrolling';
import {isElementScrolledOutsideView} from '../position/scroll-clip';
/**
* Config options for the RepositionScrollStrategy.
*/
export interface RepositionScrollStrategyConfig {
/** Time in milliseconds to throttle the scroll events. */
scrollThrottle?: number;
/** Whether to close the overlay once the user has scrolled away completely. */
autoClose?: boolean;
}
/**
@@ -27,6 +33,8 @@ export class RepositionScrollStrategy implements ScrollStrategy {
constructor(
private _scrollDispatcher: ScrollDispatcher,
private _viewportRuler: ViewportRuler,
private _ngZone: NgZone,
private _config?: RepositionScrollStrategyConfig) { }
/** Attaches this scroll strategy to an overlay. */
@@ -41,10 +49,25 @@ export class RepositionScrollStrategy implements ScrollStrategy {
/** Enables repositioning of the attached overlay on scroll. */
enable() {
if (!this._scrollSubscription) {
let throttle = this._config ? this._config.scrollThrottle : 0;
const throttle = this._config ? this._config.scrollThrottle : 0;
this._scrollSubscription = this._scrollDispatcher.scrolled(throttle).subscribe(() => {
this._overlayRef.updatePosition();
// TODO(crisbeto): make `close` on by default once all components can handle it.
if (this._config && this._config.autoClose) {
const overlayRect = this._overlayRef.overlayElement.getBoundingClientRect();
const {width, height} = this._viewportRuler.getViewportSize();
// TODO(crisbeto): include all ancestor scroll containers here once
// we have a way of exposing the trigger element to the scroll strategy.
const parentRects = [{width, height, bottom: height, right: width, top: 0, left: 0}];
if (isElementScrolledOutsideView(overlayRect, parentRects)) {
this.disable();
this._ngZone.run(() => this._overlayRef.detach());
}
}
});
}
}
@@ -45,6 +45,6 @@ export class ScrollStrategyOptions {
* @param config Configuration to be used inside the scroll strategy.
* Allows debouncing the reposition calls.
*/
reposition = (config?: RepositionScrollStrategyConfig) =>
new RepositionScrollStrategy(this._scrollDispatcher, config)
reposition = (config?: RepositionScrollStrategyConfig) => new RepositionScrollStrategy(
this._scrollDispatcher, this._viewportRuler, this._ngZone, config)
}