Skip to content

Commit

Permalink
feat(reposition-scroll-strategy): add option for closing once the use…
Browse files Browse the repository at this point in the history
…r scrolls away

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`.
  • Loading branch information
crisbeto committed Nov 4, 2017
1 parent 24f0471 commit 0593f40
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 7 deletions.
45 changes: 42 additions & 3 deletions src/cdk/overlay/scroll/reposition-scroll-strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {

describe('RepositionScrollStrategy', () => {
let overlayRef: OverlayRef;
let overlay: Overlay;
let componentPortal: ComponentPortal<PastaMsg>;
let scrolledSubject = new Subject();

Expand All @@ -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);
}));

Expand All @@ -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');

Expand All @@ -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');

Expand All @@ -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');

Expand All @@ -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({
close: 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);
});

});


Expand Down
27 changes: 25 additions & 2 deletions src/cdk/overlay/scroll/reposition-scroll-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, isElementClippedByScrolling} 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. */
close?: boolean;
}

/**
Expand All @@ -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. */
Expand All @@ -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.close) {
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());
}
}
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/cdk/overlay/scroll/scroll-strategy-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 0593f40

Please sign in to comment.