feat(overlay): add scroll handling strategies (#4293)

* feat(overlay): add scroll handling strategies

* Adds the `scrollStrategy` option to the overlay state, allowing the consumer to specify what scroll handling strategy they'd want to use. Also includes a `ScrollStrategy` interface that users can utilize to build their own strategies.
* Adds the `RepositionScrollStrategy`, `CloseScrollStrategy` and `NoopScrollStrategy` as initial, out-of-the-box strategies.
* Sets the `RepositionScrollStrategy` by default on all the connected overlays and removes some repetitive logic from the tooltip, autocomplete, menu and select.

**Note:** I'll add a `BlockScrollStrategy` in a follow-up PR. I wanted to keep this one shorter.

Relates to #4093.

* fix: missing types on the scroll dispatcher

* refactor: use class for fake scroll strategy

* refactor: add onAttached and onDetached observables

* chore: rename observables
This commit is contained in:
Kristiyan Kostadinov
2017-05-03 01:38:24 +02:00
committed by Andrew Seguin
parent afaa2dc05b
commit c8ec9814bc
17 changed files with 416 additions and 59 deletions
+2 -15
View File
@@ -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} from '../core';
import {Overlay, OverlayRef, OverlayState, TemplatePortal, RepositionScrollStrategy} from '../core';
import {MdAutocomplete} from './autocomplete';
import {PositionStrategy} from '../core/overlay/position/position-strategy';
import {ConnectedPositionStrategy} from '../core/overlay/position/connected-position-strategy';
@@ -76,9 +76,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
/** The subscription to positioning changes in the autocomplete panel. */
private _panelPositionSubscription: Subscription;
/** Subscription to global scroll events. */
private _scrollSubscription: Subscription;
/** Strategy that is used to position the panel. */
private _positionStrategy: ConnectedPositionStrategy;
@@ -139,12 +136,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
this._subscribeToClosingActions();
}
if (!this._scrollSubscription) {
this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => {
this._overlayRef.updatePosition();
});
}
this.autocomplete._setVisibility();
this._floatPlaceholder();
this._panelOpen = true;
@@ -156,11 +147,6 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
this._overlayRef.detach();
}
if (this._scrollSubscription) {
this._scrollSubscription.unsubscribe();
this._scrollSubscription = null;
}
this._panelOpen = false;
this._resetPlaceholder();
@@ -374,6 +360,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);
return overlayState;
}
+4
View File
@@ -51,6 +51,10 @@ export * from './overlay/position/global-position-strategy';
export * from './overlay/position/connected-position-strategy';
export * from './overlay/position/connected-position';
export {ScrollDispatcher} from './overlay/scroll/scroll-dispatcher';
export {ScrollStrategy} from './overlay/scroll/scroll-strategy';
export {RepositionScrollStrategy} from './overlay/scroll/reposition-scroll-strategy';
export {CloseScrollStrategy} from './overlay/scroll/close-scroll-strategy';
export {NoopScrollStrategy} from './overlay/scroll/noop-scroll-strategy';
// Gestures
export {GestureConfig} from './gestures/gesture-config';
@@ -23,8 +23,11 @@ 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';
@@ -119,6 +122,9 @@ export class ConnectedOverlayDirective implements OnDestroy {
/** The custom class to be set on the backdrop element. */
@Input() backdropClass: string;
/** Strategy to be used when handling scroll events while the overlay is open. */
@Input() scrollStrategy: ScrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
/** Whether or not the overlay should attach a backdrop. */
@Input()
get hasBackdrop() {
@@ -156,6 +162,7 @@ export class ConnectedOverlayDirective implements OnDestroy {
constructor(
private _overlay: Overlay,
private _renderer: Renderer2,
private _scrollDispatcher: ScrollDispatcher,
templateRef: TemplateRef<any>,
viewContainerRef: ViewContainerRef,
@Optional() private _dir: Dir) {
@@ -213,6 +220,7 @@ export class ConnectedOverlayDirective implements OnDestroy {
this._position = this._createPositionStrategy() as ConnectedPositionStrategy;
overlayConfig.positionStrategy = this._position;
overlayConfig.scrollStrategy = this.scrollStrategy;
return overlayConfig;
}
+25 -1
View File
@@ -1,6 +1,7 @@
import {NgZone} from '@angular/core';
import {PortalHost, Portal} from '../portal/portal';
import {OverlayState} from './overlay-state';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
@@ -12,12 +13,17 @@ import {Subject} from 'rxjs/Subject';
export class OverlayRef implements PortalHost {
private _backdropElement: HTMLElement = null;
private _backdropClick: Subject<any> = new Subject();
private _attachments = new Subject<void>();
private _detachments = new Subject<void>();
constructor(
private _portalHost: PortalHost,
private _pane: HTMLElement,
private _state: OverlayState,
private _ngZone: NgZone) { }
private _ngZone: NgZone) {
this._state.scrollStrategy.attach(this);
}
/** The overlay's HTML element */
get overlayElement(): HTMLElement {
@@ -37,6 +43,8 @@ export class OverlayRef implements PortalHost {
this.updateSize();
this.updateDirection();
this.updatePosition();
this._attachments.next();
this._state.scrollStrategy.enable();
// Enable pointer events for the overlay pane element.
this._togglePointerEvents(true);
@@ -59,6 +67,8 @@ 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._detachments.next();
return this._portalHost.detach();
}
@@ -73,6 +83,10 @@ export class OverlayRef implements PortalHost {
this.detachBackdrop();
this._portalHost.dispose();
this._state.scrollStrategy.disable();
this._detachments.next();
this._detachments.complete();
this._attachments.complete();
}
/**
@@ -89,6 +103,16 @@ export class OverlayRef implements PortalHost {
return this._backdropClick.asObservable();
}
/** Returns an observable that emits when the overlay has been attached. */
attachments(): Observable<void> {
return this._attachments.asObservable();
}
/** Returns an observable that emits when the overlay has been detached. */
detachments(): Observable<void> {
return this._detachments.asObservable();
}
/**
* Gets the current state config of the overlay.
*/
+5
View File
@@ -1,5 +1,7 @@
import {PositionStrategy} from './position/position-strategy';
import {LayoutDirection} from '../rtl/dir';
import {ScrollStrategy} from './scroll/scroll-strategy';
import {NoopScrollStrategy} from './scroll/noop-scroll-strategy';
/**
@@ -10,6 +12,9 @@ export class OverlayState {
/** Strategy with which to position the overlay. */
positionStrategy: PositionStrategy;
/** Strategy to be used when handling scroll events while the overlay is open. */
scrollStrategy: ScrollStrategy = new NoopScrollStrategy();
/** Whether the overlay has a backdrop. */
hasBackdrop: boolean = false;
+98
View File
@@ -5,8 +5,10 @@ import {TemplatePortal, ComponentPortal} from '../portal/portal';
import {Overlay} from './overlay';
import {OverlayContainer} from './overlay-container';
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';
describe('Overlay', () => {
@@ -135,6 +137,44 @@ describe('Overlay', () => {
expect(pane.getAttribute('dir')).toEqual('rtl');
});
it('should emit when an overlay is attached', () => {
let overlayRef = overlay.create();
let spy = jasmine.createSpy('attachments spy');
overlayRef.attachments().subscribe(spy);
overlayRef.attach(componentPortal);
expect(spy).toHaveBeenCalled();
});
it('should emit when an overlay is detached', () => {
let overlayRef = overlay.create();
let spy = jasmine.createSpy('detachments spy');
overlayRef.detachments().subscribe(spy);
overlayRef.attach(componentPortal);
overlayRef.detach();
expect(spy).toHaveBeenCalled();
});
it('should emit and complete the observables when an overlay is disposed', () => {
let overlayRef = overlay.create();
let disposeSpy = jasmine.createSpy('dispose spy');
let attachCompleteSpy = jasmine.createSpy('attachCompleteSpy spy');
let detachCompleteSpy = jasmine.createSpy('detachCompleteSpy spy');
overlayRef.attachments().subscribe(null, null, attachCompleteSpy);
overlayRef.detachments().subscribe(disposeSpy, null, detachCompleteSpy);
overlayRef.attach(componentPortal);
overlayRef.dispose();
expect(disposeSpy).toHaveBeenCalled();
expect(attachCompleteSpy).toHaveBeenCalled();
expect(detachCompleteSpy).toHaveBeenCalled();
});
describe('positioning', () => {
let state: OverlayState;
@@ -295,6 +335,48 @@ describe('Overlay', () => {
});
});
describe('scroll strategy', () => {
let fakeScrollStrategy: FakeScrollStrategy;
let config: OverlayState;
beforeEach(() => {
config = new OverlayState();
fakeScrollStrategy = new FakeScrollStrategy();
config.scrollStrategy = fakeScrollStrategy;
});
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.');
overlayRef.detach();
expect(fakeScrollStrategy.isEnabled).toBe(false, 'Expected scroll strategy to be disabled.');
});
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.');
});
});
});
describe('OverlayContainer theming', () => {
@@ -365,3 +447,19 @@ class FakePositionStrategy implements PositionStrategy {
dispose() {}
}
class FakeScrollStrategy implements ScrollStrategy {
isEnabled = false;
overlayRef: OverlayRef;
attach(overlayRef: OverlayRef) {
this.overlayRef = overlayRef;
}
enable() {
this.isEnabled = true;
}
disable() {
this.isEnabled = false;
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ let defaultState = new OverlayState();
*
* An overlay *is* a PortalHost, so any kind of Portal can be loaded into one.
*/
@Injectable()
@Injectable()
export class Overlay {
constructor(private _overlayContainer: OverlayContainer,
private _componentFactoryResolver: ComponentFactoryResolver,
@@ -0,0 +1,78 @@
import {inject, TestBed, async} from '@angular/core/testing';
import {NgModule, Component} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import {
PortalModule,
ComponentPortal,
Overlay,
OverlayState,
OverlayRef,
OverlayModule,
ScrollStrategy,
ScrollDispatcher,
CloseScrollStrategy,
} from '../../core';
describe('CloseScrollStrategy', () => {
let overlayRef: OverlayRef;
let componentPortal: ComponentPortal<MozarellaMsg>;
let scrolledSubject = new Subject();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [OverlayModule, PortalModule, OverlayTestModule],
providers: [
{provide: ScrollDispatcher, useFactory: () => {
return {scrolled: (delay: number, callback: () => any) => {
return scrolledSubject.asObservable().subscribe(callback);
}};
}}
]
});
TestBed.compileComponents();
}));
beforeEach(inject([Overlay, ScrollDispatcher], (overlay: Overlay,
scrollDispatcher: ScrollDispatcher) => {
let overlayState = new OverlayState();
overlayState.scrollStrategy = new CloseScrollStrategy(scrollDispatcher);
overlayRef = overlay.create(overlayState);
componentPortal = new ComponentPortal(MozarellaMsg);
}));
it('should detach the overlay as soon as the user scrolls', () => {
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'detach');
scrolledSubject.next();
expect(overlayRef.detach).toHaveBeenCalled();
});
it('should not attempt to detach the overlay after it has been detached', () => {
overlayRef.attach(componentPortal);
overlayRef.detach();
spyOn(overlayRef, 'detach');
scrolledSubject.next();
expect(overlayRef.detach).not.toHaveBeenCalled();
});
});
/** Simple component that we can attach to the overlay. */
@Component({template: '<p>Mozarella</p>'})
class MozarellaMsg { }
/** Test module to hold the component. */
@NgModule({
imports: [OverlayModule, PortalModule],
declarations: [MozarellaMsg],
entryComponents: [MozarellaMsg],
})
class OverlayTestModule { }
@@ -0,0 +1,38 @@
import {ScrollStrategy} from './scroll-strategy';
import {OverlayRef} from '../overlay-ref';
import {Subscription} from 'rxjs/Subscription';
import {ScrollDispatcher} from './scroll-dispatcher';
/**
* Strategy that will close the overlay as soon as the user starts scrolling.
*/
export class CloseScrollStrategy implements ScrollStrategy {
private _scrollSubscription: Subscription|null = null;
private _overlayRef: OverlayRef;
constructor(private _scrollDispatcher: ScrollDispatcher) { }
attach(overlayRef: OverlayRef) {
this._overlayRef = overlayRef;
}
enable() {
if (!this._scrollSubscription) {
this._scrollSubscription = this._scrollDispatcher.scrolled(null, () => {
if (this._overlayRef.hasAttached()) {
this._overlayRef.detach();
}
this.disable();
});
}
}
disable() {
if (this._scrollSubscription) {
this._scrollSubscription.unsubscribe();
this._scrollSubscription = null;
}
}
}
@@ -0,0 +1,10 @@
import {ScrollStrategy} from './scroll-strategy';
/**
* Scroll strategy that doesn't do anything.
*/
export class NoopScrollStrategy implements ScrollStrategy {
enable() { }
disable() { }
attach() { }
}
@@ -0,0 +1,91 @@
import {inject, TestBed, async} from '@angular/core/testing';
import {NgModule, Component} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import {
PortalModule,
ComponentPortal,
Overlay,
OverlayState,
OverlayRef,
OverlayModule,
ScrollStrategy,
ScrollDispatcher,
RepositionScrollStrategy,
} from '../../core';
describe('RepositionScrollStrategy', () => {
let overlayRef: OverlayRef;
let componentPortal: ComponentPortal<PastaMsg>;
let scrolledSubject = new Subject();
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [OverlayModule, PortalModule, OverlayTestModule],
providers: [
{provide: ScrollDispatcher, useFactory: () => {
return {scrolled: (delay: number, callback: () => any) => {
return scrolledSubject.asObservable().subscribe(callback);
}};
}}
]
});
TestBed.compileComponents();
}));
beforeEach(inject([Overlay, ScrollDispatcher], (overlay: Overlay,
scrollDispatcher: ScrollDispatcher) => {
let overlayState = new OverlayState();
overlayState.scrollStrategy = new RepositionScrollStrategy(scrollDispatcher);
overlayRef = overlay.create(overlayState);
componentPortal = new ComponentPortal(PastaMsg);
}));
it('should update the overlay position when the page is scrolled', () => {
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
scrolledSubject.next();
expect(overlayRef.updatePosition).toHaveBeenCalledTimes(1);
scrolledSubject.next();
expect(overlayRef.updatePosition).toHaveBeenCalledTimes(2);
});
it('should not be updating the position after the overlay is detached', () => {
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
overlayRef.detach();
scrolledSubject.next();
expect(overlayRef.updatePosition).not.toHaveBeenCalled();
});
it('should not be updating the position after the overlay is destroyed', () => {
overlayRef.attach(componentPortal);
spyOn(overlayRef, 'updatePosition');
overlayRef.dispose();
scrolledSubject.next();
expect(overlayRef.updatePosition).not.toHaveBeenCalled();
});
});
/** Simple component that we can attach to the overlay. */
@Component({template: '<p>Pasta</p>'})
class PastaMsg { }
/** Test module to hold the component. */
@NgModule({
imports: [OverlayModule, PortalModule],
declarations: [PastaMsg],
entryComponents: [PastaMsg],
})
class OverlayTestModule { }
@@ -0,0 +1,34 @@
import {Subscription} from 'rxjs/Subscription';
import {ScrollStrategy} from './scroll-strategy';
import {OverlayRef} from '../overlay-ref';
import {ScrollDispatcher} from './scroll-dispatcher';
/**
* Strategy that will update the element position as the user is scrolling.
*/
export class RepositionScrollStrategy implements ScrollStrategy {
private _scrollSubscription: Subscription|null = null;
private _overlayRef: OverlayRef;
constructor(private _scrollDispatcher: ScrollDispatcher, private _scrollThrottle = 0) { }
attach(overlayRef: OverlayRef) {
this._overlayRef = overlayRef;
}
enable() {
if (!this._scrollSubscription) {
this._scrollSubscription = this._scrollDispatcher.scrolled(this._scrollThrottle, () => {
this._overlayRef.updatePosition();
});
}
}
disable() {
if (this._scrollSubscription) {
this._scrollSubscription.unsubscribe();
this._scrollSubscription = null;
}
}
}
@@ -0,0 +1,11 @@
import {OverlayRef} from '../overlay-ref';
/**
* Describes a strategy that will be used by an overlay
* to handle scroll events while it is open.
*/
export interface ScrollStrategy {
enable: () => void;
disable: () => void;
attach: (overlayRef: OverlayRef) => void;
}
+5 -1
View File
@@ -22,6 +22,8 @@ import {
ConnectedPositionStrategy,
HorizontalConnectionPos,
VerticalConnectionPos,
RepositionScrollStrategy,
ScrollDispatcher,
} from '../core';
import {Subscription} from 'rxjs/Subscription';
import {MenuPositionX, MenuPositionY} from './menu-positions';
@@ -78,7 +80,8 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
@Output() onMenuClose = new EventEmitter<void>();
constructor(private _overlay: Overlay, private _element: ElementRef,
private _viewContainerRef: ViewContainerRef, @Optional() private _dir: Dir) { }
private _viewContainerRef: ViewContainerRef, @Optional() private _dir: Dir,
private _scrollDispatcher: ScrollDispatcher) { }
ngAfterViewInit() {
this._checkMenu();
@@ -216,6 +219,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
overlayState.hasBackdrop = true;
overlayState.backdropClass = 'cdk-overlay-transparent-backdrop';
overlayState.direction = this.dir;
overlayState.scrollStrategy = new RepositionScrollStrategy(this._scrollDispatcher);
return overlayState;
}
+1 -14
View File
@@ -29,7 +29,6 @@ import {coerceBooleanProperty} from '../core/coercion/boolean-property';
import {ConnectedOverlayDirective} from '../core/overlay/overlay-directives';
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
import {SelectionModel} from '../core/selection/selection';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
import {MdSelectDynamicMultipleError, MdSelectNonArrayValueError} from './select-errors';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/startWith';
@@ -134,9 +133,6 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
/** Subscription to tab events while overlay is focused. */
private _tabSubscription: Subscription;
/** Subscription to global scrolled events while the select is open. */
private _scrollSubscription: Subscription;
/** Whether filling out the select is required in the form. */
private _required: boolean = false;
@@ -314,8 +310,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
constructor(private _element: ElementRef, private _renderer: Renderer2,
private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef,
private _scrollDispatcher: ScrollDispatcher, @Optional() private _dir: Dir,
@Self() @Optional() public _control: NgControl,
@Optional() private _dir: Dir, @Self() @Optional() public _control: NgControl,
@Attribute('tabindex') tabIndex: string) {
if (this._control) {
@@ -374,9 +369,6 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
this._calculateOverlayPosition();
this._placeholderState = this._floatPlaceholderState();
this._panelOpen = true;
this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => {
this.overlayDir.overlayRef.updatePosition();
});
}
/** Closes the overlay panel and focuses the host element. */
@@ -388,11 +380,6 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
this._placeholderState = '';
}
if (this._scrollSubscription) {
this._scrollSubscription.unsubscribe();
this._scrollSubscription = null;
}
this._focusHost();
}
}
-8
View File
@@ -444,14 +444,6 @@ describe('MdTooltip', () => {
}));
});
describe('destroy', () => {
it('does not throw an error on destroy', () => {
const fixture = TestBed.createComponent(BasicTooltipDemo);
fixture.detectChanges();
delete fixture.componentInstance.tooltip.scrollSubscription;
expect(fixture.destroy.bind(fixture)).not.toThrow();
});
});
});
@Component({
+5 -19
View File
@@ -8,7 +8,6 @@ import {
Optional,
OnDestroy,
Renderer2,
OnInit,
ChangeDetectorRef,
} from '@angular/core';
import {
@@ -26,6 +25,7 @@ import {
ComponentPortal,
OverlayConnectionPosition,
OriginConnectionPosition,
RepositionScrollStrategy,
} from '../core';
import {MdTooltipInvalidPositionError} from './tooltip-errors';
import {Observable} from 'rxjs/Observable';
@@ -34,13 +34,12 @@ import {Dir} from '../core/rtl/dir';
import {Platform} from '../core/platform/index';
import 'rxjs/add/operator/first';
import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher';
import {Subscription} from 'rxjs/Subscription';
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after';
/** Time in ms to delay before changing the tooltip visibility to hidden */
export const TOUCHEND_HIDE_DELAY = 1500;
export const TOUCHEND_HIDE_DELAY = 1500;
/** Time in ms to throttle repositioning after scroll events. */
export const SCROLL_THROTTLE_MS = 20;
@@ -59,10 +58,9 @@ export const SCROLL_THROTTLE_MS = 20;
},
exportAs: 'mdTooltip',
})
export class MdTooltip implements OnInit, OnDestroy {
export class MdTooltip implements OnDestroy {
_overlayRef: OverlayRef;
_tooltipInstance: TooltipComponent;
scrollSubscription: Subscription;
private _position: TooltipPosition = 'below';
private _disabled: boolean = false;
@@ -164,16 +162,6 @@ export class MdTooltip implements OnInit, OnDestroy {
}
}
ngOnInit() {
// When a scroll on the page occurs, update the position in case this tooltip needs
// to be repositioned.
this.scrollSubscription = this._scrollDispatcher.scrolled(SCROLL_THROTTLE_MS, () => {
if (this._overlayRef) {
this._overlayRef.updatePosition();
}
});
}
/**
* Dispose the tooltip when destroyed.
*/
@@ -181,10 +169,6 @@ export class MdTooltip implements OnInit, OnDestroy {
if (this._tooltipInstance) {
this._disposeTooltip();
}
if (this.scrollSubscription) {
this.scrollSubscription.unsubscribe();
}
}
/** Shows the tooltip after the delay in ms, defaults to tooltip-delay-show or 0ms if no input */
@@ -249,6 +233,8 @@ export class MdTooltip implements OnInit, OnDestroy {
});
let config = new OverlayState();
config.positionStrategy = strategy;
config.scrollStrategy =
new RepositionScrollStrategy(this._scrollDispatcher, SCROLL_THROTTLE_MS);
this._overlayRef = this._overlay.create(config);
}