Skip to content

feat(overlay): more flexible scroll strategy API and ability to define/override custom strategies #4855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {BlockScrollStrategy, ViewportRuler} from '@angular/material';
import {Overlay, ScrollStrategy} from '@angular/material';

@Component({
moduleId: module.id,
Expand All @@ -8,6 +8,6 @@ import {BlockScrollStrategy, ViewportRuler} from '@angular/material';
styleUrls: ['block-scroll-strategy-e2e.css'],
})
export class BlockScrollStrategyE2E {
constructor(private _viewportRuler: ViewportRuler) { }
scrollStrategy = new BlockScrollStrategy(this._viewportRuler);
constructor(private _overlay: Overlay) { }
scrollStrategy: ScrollStrategy = this._overlay.scrollStrategies.block();
}
6 changes: 2 additions & 4 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {DOCUMENT} from '@angular/platform-browser';
import {Overlay, OverlayRef, OverlayState, TemplatePortal, RepositionScrollStrategy} from '../core';
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
import {MdAutocomplete} from './autocomplete';
import {PositionStrategy} from '../core/overlay/position/position-strategy';
import {ConnectedPositionStrategy} from '../core/overlay/position/connected-position-strategy';
Expand All @@ -22,7 +22,6 @@ import {MdOptionSelectionChange, MdOption} from '../core/option/option';
import {ENTER, UP_ARROW, DOWN_ARROW, ESCAPE} from '../core/keyboard/keycodes';
import {Dir} from '../core/rtl/dir';
import {MdInputContainer} from '../input/input-container';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
import {Subscription} from 'rxjs/Subscription';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/fromEvent';
Expand Down Expand Up @@ -104,7 +103,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
constructor(private _element: ElementRef, private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef,
private _changeDetectorRef: ChangeDetectorRef,
private _scrollDispatcher: ScrollDispatcher,
@Optional() private _dir: Dir, private _zone: NgZone,
@Optional() @Host() private _inputContainer: MdInputContainer,
@Optional() @Inject(DOCUMENT) private _document: any) {}
Expand Down Expand Up @@ -368,7 +366,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
overlayState.positionStrategy = this._getOverlayPosition();
overlayState.width = this._getHostWidth();
overlayState.direction = this._dir ? this._dir.value : 'ltr';
overlayState.scrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition();
return overlayState;
}

Expand Down
5 changes: 1 addition & 4 deletions src/lib/core/overlay/overlay-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ import {PortalModule} from '../portal/portal-directives';
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
import {Dir, LayoutDirection} from '../rtl/dir';
import {Scrollable} from './scroll/scrollable';
import {RepositionScrollStrategy} from './scroll/reposition-scroll-strategy';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {coerceBooleanProperty} from '../coercion/boolean-property';
import {ESCAPE} from '../keyboard/keycodes';
import {ScrollDispatcher} from './scroll/scroll-dispatcher';
import {Subscription} from 'rxjs/Subscription';
import {ScrollDispatchModule} from './scroll/index';

Expand Down Expand Up @@ -125,7 +123,7 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
@Input() backdropClass: string;

/** Strategy to be used when handling scroll events while the overlay is open. */
@Input() scrollStrategy: ScrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
@Input() scrollStrategy: ScrollStrategy = this._overlay.scrollStrategies.reposition();

/** Whether the overlay is open. */
@Input() open: boolean = false;
Expand Down Expand Up @@ -157,7 +155,6 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
constructor(
private _overlay: Overlay,
private _renderer: Renderer2,
private _scrollDispatcher: ScrollDispatcher,
templateRef: TemplateRef<any>,
viewContainerRef: ViewContainerRef,
@Optional() private _dir: Dir) {
Expand Down
13 changes: 9 additions & 4 deletions src/lib/core/overlay/overlay-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export class OverlayRef implements PortalHost {
private _portalHost: PortalHost,
private _pane: HTMLElement,
private _state: OverlayState,
private _scrollStrategy: ScrollStrategy,
private _ngZone: NgZone) {

this._state.scrollStrategy.attach(this);
_scrollStrategy.attach(this);
}

/** The overlay's HTML element */
Expand All @@ -44,7 +45,7 @@ export class OverlayRef implements PortalHost {
this.updateDirection();
this.updatePosition();
this._attachments.next();
this._state.scrollStrategy.enable();
this._scrollStrategy.enable();

// Enable pointer events for the overlay pane element.
this._togglePointerEvents(true);
Expand All @@ -71,7 +72,7 @@ export class OverlayRef implements PortalHost {
// This is necessary because otherwise the pane element will cover the page and disable
// pointer events therefore. Depends on the position strategy and the applied pane boundaries.
this._togglePointerEvents(false);
this._state.scrollStrategy.disable();
this._scrollStrategy.disable();
this._detachments.next();

return this._portalHost.detach();
Expand All @@ -85,9 +86,13 @@ export class OverlayRef implements PortalHost {
this._state.positionStrategy.dispose();
}

if (this._scrollStrategy) {
this._scrollStrategy.disable();
this._scrollStrategy = null;
}

this.detachBackdrop();
this._portalHost.dispose();
this._state.scrollStrategy.disable();
this._detachments.next();
this._detachments.complete();
this._attachments.complete();
Expand Down
3 changes: 1 addition & 2 deletions src/lib/core/overlay/overlay-state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {PositionStrategy} from './position/position-strategy';
import {LayoutDirection} from '../rtl/dir';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {NoopScrollStrategy} from './scroll/noop-scroll-strategy';


/**
Expand All @@ -13,7 +12,7 @@ export class OverlayState {
positionStrategy: PositionStrategy;

/** Strategy to be used when handling scroll events while the overlay is open. */
scrollStrategy: ScrollStrategy = new NoopScrollStrategy();
scrollStrategy: ScrollStrategy;

/** Custom class to add to the overlay pane. */
panelClass: string = '';
Expand Down
30 changes: 12 additions & 18 deletions src/lib/core/overlay/overlay.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {OverlayState} from './overlay-state';
import {OverlayRef} from './overlay-ref';
import {PositionStrategy} from './position/position-strategy';
import {OverlayModule} from './overlay-directives';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {ViewportRuler} from './position/viewport-ruler';
import {ScrollStrategy, ScrollDispatcher} from './scroll/index';


describe('Overlay', () => {
Expand All @@ -21,15 +22,14 @@ describe('Overlay', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [OverlayModule, PortalModule, OverlayTestModule],
providers: [
{provide: OverlayContainer, useFactory: () => {
providers: [{
provide: OverlayContainer,
useFactory: () => {
overlayContainerElement = document.createElement('div');
return {getContainerElement: () => overlayContainerElement};
}}
]
});

TestBed.compileComponents();
}
}]
}).compileComponents();
}));

beforeEach(inject([Overlay], (o: Overlay) => {
Expand Down Expand Up @@ -354,30 +354,25 @@ describe('Overlay', () => {
describe('scroll strategy', () => {
let fakeScrollStrategy: FakeScrollStrategy;
let config: OverlayState;
let overlayRef: OverlayRef;

beforeEach(() => {
config = new OverlayState();
fakeScrollStrategy = new FakeScrollStrategy();
config.scrollStrategy = fakeScrollStrategy;
fakeScrollStrategy = config.scrollStrategy = new FakeScrollStrategy();
overlayRef = overlay.create(config);
});

it('should attach the overlay ref to the scroll strategy', () => {
let overlayRef = overlay.create(config);

expect(fakeScrollStrategy.overlayRef).toBe(overlayRef,
'Expected scroll strategy to have been attached to the current overlay ref.');
});

it('should enable the scroll strategy when the overlay is attached', () => {
let overlayRef = overlay.create(config);

overlayRef.attach(componentPortal);
expect(fakeScrollStrategy.isEnabled).toBe(true, 'Expected scroll strategy to be enabled.');
});

it('should disable the scroll strategy once the overlay is detached', () => {
let overlayRef = overlay.create(config);

overlayRef.attach(componentPortal);
expect(fakeScrollStrategy.isEnabled).toBe(true, 'Expected scroll strategy to be enabled.');

Expand All @@ -386,8 +381,6 @@ describe('Overlay', () => {
});

it('should disable the scroll strategy when the overlay is destroyed', () => {
let overlayRef = overlay.create(config);

overlayRef.dispose();
expect(fakeScrollStrategy.isEnabled).toBe(false, 'Expected scroll strategy to be disabled.');
});
Expand Down Expand Up @@ -466,6 +459,7 @@ class FakePositionStrategy implements PositionStrategy {
dispose() {}
}


class FakeScrollStrategy implements ScrollStrategy {
isEnabled = false;
overlayRef: OverlayRef;
Expand Down
12 changes: 8 additions & 4 deletions src/lib/core/overlay/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {OverlayRef} from './overlay-ref';
import {OverlayPositionBuilder} from './position/overlay-position-builder';
import {VIEWPORT_RULER_PROVIDER} from './position/viewport-ruler';
import {OverlayContainer, OVERLAY_CONTAINER_PROVIDER} from './overlay-container';
import {ScrollStrategy, ScrollStrategyOptions} from './scroll/index';


/** Next overlay unique ID. */
Expand All @@ -31,12 +32,13 @@ let defaultState = new OverlayState();
*/
@Injectable()
export class Overlay {
constructor(private _overlayContainer: OverlayContainer,
constructor(public scrollStrategies: ScrollStrategyOptions,
private _overlayContainer: OverlayContainer,
private _componentFactoryResolver: ComponentFactoryResolver,
private _positionBuilder: OverlayPositionBuilder,
private _appRef: ApplicationRef,
private _injector: Injector,
private _ngZone: NgZone) {}
private _ngZone: NgZone) { }

/**
* Creates an overlay.
Expand All @@ -61,9 +63,9 @@ export class Overlay {
*/
private _createPaneElement(): HTMLElement {
let pane = document.createElement('div');

pane.id = `cdk-overlay-${nextUniqueId++}`;
pane.classList.add('cdk-overlay-pane');

this._overlayContainer.getContainerElement().appendChild(pane);

return pane;
Expand All @@ -84,7 +86,9 @@ export class Overlay {
* @param state
*/
private _createOverlayRef(pane: HTMLElement, state: OverlayState): OverlayRef {
return new OverlayRef(this._createPortalHost(pane), pane, state, this._ngZone);
let scrollStrategy = state.scrollStrategy || this.scrollStrategies.noop();
let portalHost = this._createPortalHost(pane);
return new OverlayRef(portalHost, pane, state, scrollStrategy, this._ngZone);
}
}

Expand Down
Loading