From 100e123e37c54eb85a8a744e88d0807aca16707a Mon Sep 17 00:00:00 2001 From: crisbeto Date: Wed, 14 Jun 2017 22:33:58 +0200 Subject: [PATCH] feat(overlay): add providers for overriding the scroll strategies per component Adds providers to the autocomplete, connected overlay, datepicker, dialog, menu, select and tooltip components, that allow for the default scroll strategy to be overwritten. Fixes #4093. --- src/lib/autocomplete/autocomplete-trigger.ts | 32 +++++++++++++++++-- src/lib/autocomplete/index.ts | 6 +++- src/lib/core/overlay/index.ts | 7 ++++- src/lib/core/overlay/overlay-directives.ts | 26 +++++++++++++-- src/lib/datepicker/datepicker.ts | 33 +++++++++++++++++--- src/lib/datepicker/index.ts | 7 ++++- src/lib/dialog/dialog.ts | 28 +++++++++++++++-- src/lib/dialog/index.ts | 3 +- src/lib/menu/index.ts | 3 +- src/lib/menu/menu-trigger.ts | 27 +++++++++++++++- src/lib/select/index.ts | 3 +- src/lib/select/select.html | 1 + src/lib/select/select.ts | 29 ++++++++++++++++- src/lib/tooltip/index.ts | 3 +- src/lib/tooltip/tooltip.ts | 29 +++++++++++++++-- 15 files changed, 214 insertions(+), 23 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 475a45fa5b0c..6ce9c1040cc8 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -18,10 +18,21 @@ import { ViewContainerRef, Inject, ChangeDetectorRef, + InjectionToken, } 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, + // This import is only used to define a generic type. The current TypeScript version incorrectly + // considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953) + // tslint:disable-next-line:no-unused-variable + ScrollStrategy, +} from '../core'; import {MdAutocomplete} from './autocomplete'; import {PositionStrategy} from '../core/overlay/position/position-strategy'; import {ConnectedPositionStrategy} from '../core/overlay/position/connected-position-strategy'; @@ -48,6 +59,22 @@ export const AUTOCOMPLETE_OPTION_HEIGHT = 48; /** The total height of the autocomplete panel. */ export const AUTOCOMPLETE_PANEL_HEIGHT = 256; +/** Injection token that determines the scroll handling while the autocomplete panel is open. */ +export const MD_AUTOCOMPLETE_SCROLL_STRATEGY = + new InjectionToken<() => ScrollStrategy>('md-autocomplete-scroll-strategy'); + +/** @docs-private */ +export function MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) { + return () => overlay.scrollStrategies.reposition(); +} + +/** @docs-private */ +export const MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER = { + provide: MD_AUTOCOMPLETE_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER_FACTORY, +}; + /** * Provider that allows the autocomplete to register as a ControlValueAccessor. * @docs-private @@ -127,6 +154,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { private _viewContainerRef: ViewContainerRef, private _zone: NgZone, private _changeDetectorRef: ChangeDetectorRef, + @Inject(MD_AUTOCOMPLETE_SCROLL_STRATEGY) private _scrollStrategy, @Optional() private _dir: Directionality, @Optional() @Host() private _inputContainer: MdInputContainer, @Optional() @Inject(DOCUMENT) private _document: any) {} @@ -419,7 +447,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 = this._overlay.scrollStrategies.reposition(); + overlayState.scrollStrategy = this._scrollStrategy(); return overlayState; } diff --git a/src/lib/autocomplete/index.ts b/src/lib/autocomplete/index.ts index 1ec27f6d292e..ff52c4b6a0ee 100644 --- a/src/lib/autocomplete/index.ts +++ b/src/lib/autocomplete/index.ts @@ -10,12 +10,16 @@ import {NgModule} from '@angular/core'; import {MdOptionModule, OverlayModule, MdCommonModule} from '../core'; import {CommonModule} from '@angular/common'; import {MdAutocomplete} from './autocomplete'; -import {MdAutocompleteTrigger} from './autocomplete-trigger'; +import { + MdAutocompleteTrigger, + MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER, +} from './autocomplete-trigger'; @NgModule({ imports: [MdOptionModule, OverlayModule, MdCommonModule, CommonModule], exports: [MdAutocomplete, MdOptionModule, MdAutocompleteTrigger, MdCommonModule], declarations: [MdAutocomplete, MdAutocompleteTrigger], + providers: [MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER], }) export class MdAutocompleteModule {} diff --git a/src/lib/core/overlay/index.ts b/src/lib/core/overlay/index.ts index 4b25874c6a11..bb6945f68f3f 100644 --- a/src/lib/core/overlay/index.ts +++ b/src/lib/core/overlay/index.ts @@ -9,7 +9,11 @@ import {NgModule, Provider} from '@angular/core'; import {Overlay} from './overlay'; import {ScrollDispatchModule} from './scroll/index'; import {PortalModule} from '../portal/portal-directives'; -import {ConnectedOverlayDirective, OverlayOrigin} from './overlay-directives'; +import { + ConnectedOverlayDirective, + OverlayOrigin, + MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER, +} from './overlay-directives'; import {OverlayPositionBuilder} from './position/overlay-position-builder'; import {VIEWPORT_RULER_PROVIDER} from './position/viewport-ruler'; import {OVERLAY_CONTAINER_PROVIDER} from './overlay-container'; @@ -20,6 +24,7 @@ export const OVERLAY_PROVIDERS: Provider[] = [ OverlayPositionBuilder, VIEWPORT_RULER_PROVIDER, OVERLAY_CONTAINER_PROVIDER, + MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER, ]; @NgModule({ diff --git a/src/lib/core/overlay/overlay-directives.ts b/src/lib/core/overlay/overlay-directives.ts index 57ec25d30eba..643d06b60ec0 100644 --- a/src/lib/core/overlay/overlay-directives.ts +++ b/src/lib/core/overlay/overlay-directives.ts @@ -19,6 +19,8 @@ import { Renderer2, OnChanges, SimpleChanges, + InjectionToken, + Inject, } from '@angular/core'; import {Overlay} from './overlay'; import {OverlayRef} from './overlay-ref'; @@ -33,14 +35,14 @@ import { } from './position/connected-position'; import {ConnectedPositionStrategy} from './position/connected-position-strategy'; import {Directionality, Direction} from '../bidi/index'; -import {ScrollStrategy} from './scroll/scroll-strategy'; import {coerceBooleanProperty} from '@angular/cdk'; +import {ScrollStrategy, RepositionScrollStrategy} from './scroll/index'; import {ESCAPE} from '../keyboard/keycodes'; import {Subscription} from 'rxjs/Subscription'; /** Default set of positions for the overlay. Follows the behavior of a dropdown. */ -let defaultPositionList = [ +const defaultPositionList = [ new ConnectionPositionPair( {originX: 'start', originY: 'bottom'}, {overlayX: 'start', overlayY: 'top'}), @@ -49,6 +51,23 @@ let defaultPositionList = [ {overlayX: 'start', overlayY: 'bottom'}), ]; +/** Injection token that determines the scroll handling while the connected overlay is open. */ +export const MD_CONNECTED_OVERLAY_SCROLL_STRATEGY = + new InjectionToken<() => ScrollStrategy>('md-connected-overlay-scroll-strategy'); + +/** @docs-private */ +export function MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) { + return () => overlay.scrollStrategies.reposition(); +} + +/** @docs-private */ +export const MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER = { + provide: MD_CONNECTED_OVERLAY_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY, +}; + + /** * Directive applied to an element to make it usable as an origin for an Overlay using a @@ -130,7 +149,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 = this._overlay.scrollStrategies.reposition(); + @Input() scrollStrategy: ScrollStrategy = this._scrollStrategy(); /** Whether the overlay is open. */ @Input() open: boolean = false; @@ -164,6 +183,7 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges { private _renderer: Renderer2, templateRef: TemplateRef, viewContainerRef: ViewContainerRef, + @Inject(MD_CONNECTED_OVERLAY_SCROLL_STRATEGY) private _scrollStrategy, @Optional() private _dir: Directionality) { this._templatePortal = new TemplatePortal(templateRef, viewContainerRef); } diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index 0f68427a6200..7d2fd8226401 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -21,12 +21,20 @@ import { ViewEncapsulation, NgZone, Inject, + InjectionToken, } from '@angular/core'; import {DOCUMENT} from '@angular/platform-browser'; -import {Overlay} from '../core/overlay/overlay'; -import {OverlayRef} from '../core/overlay/overlay-ref'; +import { + Overlay, + OverlayRef, + OverlayState, + RepositionScrollStrategy, + // This import is only used to define a generic type. The current TypeScript version incorrectly + // considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953) + // tslint:disable-next-line:no-unused-variable + ScrollStrategy, +} from '../core/overlay/index'; import {ComponentPortal} from '../core/portal/portal'; -import {OverlayState} from '../core/overlay/overlay-state'; import {Directionality} from '../core/bidi/index'; import {MdDialog} from '../dialog/dialog'; import {MdDialogRef} from '../dialog/dialog-ref'; @@ -44,6 +52,22 @@ import {coerceBooleanProperty} from '@angular/cdk'; /** Used to generate a unique ID for each datepicker instance. */ let datepickerUid = 0; +/** Injection token that determines the scroll handling while the calendar is open. */ +export const MD_DATEPICKER_SCROLL_STRATEGY = + new InjectionToken<() => ScrollStrategy>('md-datepicker-scroll-strategy'); + +/** @docs-private */ +export function MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) { + return () => overlay.scrollStrategies.reposition(); +} + +/** @docs-private */ +export const MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER = { + provide: MD_DATEPICKER_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER_FACTORY, +}; + /** * Component used as the content for the datepicker dialog and popup. We use this instead of using @@ -174,6 +198,7 @@ export class MdDatepicker implements OnDestroy { private _overlay: Overlay, private _ngZone: NgZone, private _viewContainerRef: ViewContainerRef, + @Inject(MD_DATEPICKER_SCROLL_STRATEGY) private _scrollStrategy, @Optional() private _dateAdapter: DateAdapter, @Optional() private _dir: Directionality, @Optional() @Inject(DOCUMENT) private _document: any) { @@ -293,7 +318,7 @@ export class MdDatepicker implements OnDestroy { overlayState.hasBackdrop = true; overlayState.backdropClass = 'md-overlay-transparent-backdrop'; overlayState.direction = this._dir ? this._dir.value : 'ltr'; - overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition(); + overlayState.scrollStrategy = this._scrollStrategy(); this._popupRef = this._overlay.create(overlayState); } diff --git a/src/lib/datepicker/index.ts b/src/lib/datepicker/index.ts index 5c453e874734..4961fa27345a 100644 --- a/src/lib/datepicker/index.ts +++ b/src/lib/datepicker/index.ts @@ -12,7 +12,11 @@ import {CommonModule} from '@angular/common'; import {A11yModule, OverlayModule, StyleModule} from '../core'; import {MdCalendarBody} from './calendar-body'; import {MdYearView} from './year-view'; -import {MdDatepicker, MdDatepickerContent} from './datepicker'; +import { + MdDatepicker, + MdDatepickerContent, + MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER, +} from './datepicker'; import {MdDatepickerInput} from './datepicker-input'; import {MdDialogModule} from '../dialog/index'; import {MdCalendar} from './calendar'; @@ -58,6 +62,7 @@ export * from './year-view'; ], providers: [ MdDatepickerIntl, + MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER, ], entryComponents: [ MdDatepickerContent, diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index 2a5b830c882a..23f1a56e6c06 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -8,12 +8,13 @@ import { Injector, - InjectionToken, ComponentRef, Injectable, Optional, SkipSelf, TemplateRef, + Inject, + InjectionToken, } from '@angular/core'; import {Location} from '@angular/common'; import {Observable} from 'rxjs/Observable'; @@ -24,6 +25,11 @@ import { ComponentType, OverlayState, ComponentPortal, + BlockScrollStrategy, + // This import is only used to define a generic type. The current TypeScript version incorrectly + // considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953) + // tslint:disable-next-line:no-unused-variable + ScrollStrategy, } from '../core'; import {PortalInjector} from '../core/portal/portal-injector'; import {extendObject} from '../core/util/object-extend'; @@ -36,6 +42,23 @@ import {TemplatePortal} from '../core/portal/portal'; export const MD_DIALOG_DATA = new InjectionToken('MdDialogData'); +/** Injection token that determines the scroll handling while the dialog is open. */ +export const MD_DIALOG_SCROLL_STRATEGY = + new InjectionToken<() => ScrollStrategy>('md-dialog-scroll-strategy'); + +/** @docs-private */ +export function MD_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) { + return () => overlay.scrollStrategies.block(); +} + +/** @docs-private */ +export const MD_DIALOG_SCROLL_STRATEGY_PROVIDER = { + provide: MD_DIALOG_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: MD_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY, +}; + + /** * Service to open Material Design modal dialogs. */ @@ -71,6 +94,7 @@ export class MdDialog { constructor( private _overlay: Overlay, private _injector: Injector, + @Inject(MD_DIALOG_SCROLL_STRATEGY) private _scrollStrategy, @Optional() private _location: Location, @Optional() @SkipSelf() private _parentDialog: MdDialog) { @@ -143,7 +167,7 @@ export class MdDialog { let overlayState = new OverlayState(); overlayState.panelClass = dialogConfig.panelClass; overlayState.hasBackdrop = dialogConfig.hasBackdrop; - overlayState.scrollStrategy = this._overlay.scrollStrategies.block(); + overlayState.scrollStrategy = this._scrollStrategy(); overlayState.direction = dialogConfig.direction; if (dialogConfig.backdropClass) { overlayState.backdropClass = dialogConfig.backdropClass; diff --git a/src/lib/dialog/index.ts b/src/lib/dialog/index.ts index 7dd3b29dd8db..76572bcf9e24 100644 --- a/src/lib/dialog/index.ts +++ b/src/lib/dialog/index.ts @@ -14,7 +14,7 @@ import { A11yModule, MdCommonModule, } from '../core'; -import {MdDialog} from './dialog'; +import {MdDialog, MD_DIALOG_SCROLL_STRATEGY_PROVIDER} from './dialog'; import {MdDialogContainer} from './dialog-container'; import { MdDialogClose, @@ -49,6 +49,7 @@ import { ], providers: [ MdDialog, + MD_DIALOG_SCROLL_STRATEGY_PROVIDER, ], entryComponents: [MdDialogContainer], }) diff --git a/src/lib/menu/index.ts b/src/lib/menu/index.ts index 41a9e9f5672a..e2a8fae18c6d 100644 --- a/src/lib/menu/index.ts +++ b/src/lib/menu/index.ts @@ -11,7 +11,7 @@ import {CommonModule} from '@angular/common'; import {OverlayModule, MdCommonModule} from '../core'; import {MdMenu} from './menu-directive'; import {MdMenuItem} from './menu-item'; -import {MdMenuTrigger} from './menu-trigger'; +import {MdMenuTrigger, MD_MENU_SCROLL_STRATEGY_PROVIDER} from './menu-trigger'; import {MdRippleModule} from '../core/ripple/index'; @@ -24,6 +24,7 @@ import {MdRippleModule} from '../core/ripple/index'; ], exports: [MdMenu, MdMenuItem, MdMenuTrigger, MdCommonModule], declarations: [MdMenu, MdMenuItem, MdMenuTrigger], + providers: [MD_MENU_SCROLL_STRATEGY_PROVIDER], }) export class MdMenuModule {} diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index e70c42658050..23a83e4ed20c 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -16,6 +16,8 @@ import { Optional, Output, ViewContainerRef, + InjectionToken, + Inject, } from '@angular/core'; import {MdMenuPanel} from './menu-panel'; import {throwMdMenuMissingError} from './menu-errors'; @@ -30,10 +32,32 @@ import { ConnectedPositionStrategy, HorizontalConnectionPos, VerticalConnectionPos, + RepositionScrollStrategy, + // This import is only used to define a generic type. The current TypeScript version incorrectly + // considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953) + // tslint:disable-next-line:no-unused-variable + ScrollStrategy, } from '../core'; import {Subscription} from 'rxjs/Subscription'; import {MenuPositionX, MenuPositionY} from './menu-positions'; +/** Injection token that determines the scroll handling while the menu is open. */ +export const MD_MENU_SCROLL_STRATEGY = + new InjectionToken<() => ScrollStrategy>('md-menu-scroll-strategy'); + +/** @docs-private */ +export function MD_MENU_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) { + return () => overlay.scrollStrategies.reposition(); +} + +/** @docs-private */ +export const MD_MENU_SCROLL_STRATEGY_PROVIDER = { + provide: MD_MENU_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: MD_MENU_SCROLL_STRATEGY_PROVIDER_FACTORY, +}; + + // TODO(andrewseguin): Remove the kebab versions in favor of camelCased attribute selectors /** @@ -87,6 +111,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { constructor(private _overlay: Overlay, private _element: ElementRef, private _viewContainerRef: ViewContainerRef, + @Inject(MD_MENU_SCROLL_STRATEGY) private _scrollStrategy, @Optional() private _dir: Directionality) { } ngAfterViewInit() { @@ -228,7 +253,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { overlayState.hasBackdrop = true; overlayState.backdropClass = 'cdk-overlay-transparent-backdrop'; overlayState.direction = this.dir; - overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition(); + overlayState.scrollStrategy = this._scrollStrategy(); return overlayState; } diff --git a/src/lib/select/index.ts b/src/lib/select/index.ts index de0337e3a297..bf5f903905fb 100644 --- a/src/lib/select/index.ts +++ b/src/lib/select/index.ts @@ -8,7 +8,7 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; -import {MdSelect} from './select'; +import {MdSelect, MD_SELECT_SCROLL_STRATEGY_PROVIDER} from './select'; import {MdCommonModule, OverlayModule, MdOptionModule} from '../core'; @@ -21,6 +21,7 @@ import {MdCommonModule, OverlayModule, MdOptionModule} from '../core'; ], exports: [MdSelect, MdOptionModule, MdCommonModule], declarations: [MdSelect], + providers: [MD_SELECT_SCROLL_STRATEGY_PROVIDER] }) export class MdSelectModule {} diff --git a/src/lib/select/select.html b/src/lib/select/select.html index 6549ea92568b..250d37f2079e 100644 --- a/src/lib/select/select.html +++ b/src/lib/select/select.html @@ -23,6 +23,7 @@ cdk-connected-overlay hasBackdrop backdropClass="cdk-overlay-transparent-backdrop" + [scrollStrategy]="_scrollStrategy" [origin]="origin" [open]="panelOpen" [positions]="_positions" diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 4a1ef59c5009..0ee23309ac06 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -26,6 +26,7 @@ import { OnInit, Inject, ChangeDetectionStrategy, + InjectionToken, } from '@angular/core'; import {MdOption, MdOptionSelectionChange, MdOptgroup} from '../core/option/index'; import {ENTER, SPACE, UP_ARROW, DOWN_ARROW, HOME, END} from '../core/keyboard/keycodes'; @@ -39,6 +40,7 @@ import {coerceBooleanProperty} from '@angular/cdk'; import {ConnectedOverlayDirective} from '../core/overlay/overlay-directives'; import {ViewportRuler} from '../core/overlay/position/viewport-ruler'; import {SelectionModel} from '../core/selection/selection'; +import {Overlay} from '../core/overlay/overlay'; import {getMdSelectDynamicMultipleError, getMdSelectNonArrayValueError} from './select-errors'; import {startWith, filter} from '../core/rxjs/index'; import {merge} from 'rxjs/observable/merge'; @@ -49,6 +51,10 @@ import { PlaceholderOptions, MD_PLACEHOLDER_GLOBAL_OPTIONS } from '../core/placeholder/placeholder-options'; +// This import is only used to define a generic type. The current TypeScript version incorrectly +// considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953) +// tslint:disable-next-line:no-unused-variable +import {ScrollStrategy, RepositionScrollStrategy} from '../core/overlay/scroll'; /** * The following style constants are necessary to save here in order @@ -104,6 +110,22 @@ export const SELECT_PANEL_PADDING_Y = 16; */ export const SELECT_PANEL_VIEWPORT_PADDING = 8; +/** Injection token that determines the scroll handling while a select is open. */ +export const MD_SELECT_SCROLL_STRATEGY = + new InjectionToken<() => ScrollStrategy>('md-select-scroll-strategy'); + +/** @docs-private */ +export function MD_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) { + return () => overlay.scrollStrategies.reposition(); +} + +/** @docs-private */ +export const MD_SELECT_SCROLL_STRATEGY_PROVIDER = { + provide: MD_SELECT_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: MD_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY, +}; + /** Change event object that is emitted when the select value has changed. */ export class MdSelectChange { constructor(public source: MdSelect, public value: any) { } @@ -214,6 +236,9 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On /** Whether the panel's animation is done. */ _panelDoneAnimating: boolean = false; + /** Strategy that will be used to handle scrolling while the select panel is open. */ + _scrollStrategy = this._scrollStrategyFactory(); + /** * The y-offset of the overlay panel in relation to the trigger's top start corner. * This must be adjusted to align the selected option text over the trigger text. @@ -323,12 +348,14 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On constructor( private _viewportRuler: ViewportRuler, private _changeDetectorRef: ChangeDetectorRef, + private _overlay: Overlay, renderer: Renderer2, elementRef: ElementRef, @Optional() private _dir: Directionality, @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string, - @Optional() @Inject(MD_PLACEHOLDER_GLOBAL_OPTIONS) placeholderOptions: PlaceholderOptions) { + @Optional() @Inject(MD_PLACEHOLDER_GLOBAL_OPTIONS) placeholderOptions: PlaceholderOptions, + @Inject(MD_SELECT_SCROLL_STRATEGY) private _scrollStrategyFactory) { super(renderer, elementRef); if (this._control) { diff --git a/src/lib/tooltip/index.ts b/src/lib/tooltip/index.ts index 3c28451acfba..9b6095568de8 100644 --- a/src/lib/tooltip/index.ts +++ b/src/lib/tooltip/index.ts @@ -10,7 +10,7 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {OverlayModule, MdCommonModule} from '../core'; import {PlatformModule} from '../core/platform/index'; -import {MdTooltip, TooltipComponent} from './tooltip'; +import {MdTooltip, TooltipComponent, MD_TOOLTIP_SCROLL_STRATEGY_PROVIDER} from './tooltip'; @NgModule({ @@ -23,6 +23,7 @@ import {MdTooltip, TooltipComponent} from './tooltip'; exports: [MdTooltip, TooltipComponent, MdCommonModule], declarations: [MdTooltip, TooltipComponent], entryComponents: [TooltipComponent], + providers: [MD_TOOLTIP_SCROLL_STRATEGY_PROVIDER], }) export class MdTooltipModule {} diff --git a/src/lib/tooltip/tooltip.ts b/src/lib/tooltip/tooltip.ts index ae6b78bdf5b1..b12a68438687 100644 --- a/src/lib/tooltip/tooltip.ts +++ b/src/lib/tooltip/tooltip.ts @@ -19,6 +19,8 @@ import { ChangeDetectorRef, ChangeDetectionStrategy, ViewEncapsulation, + InjectionToken, + Inject, } from '@angular/core'; import { style, @@ -35,6 +37,11 @@ import { ComponentPortal, OverlayConnectionPosition, OriginConnectionPosition, + RepositionScrollStrategy, + // This import is only used to define a generic type. The current TypeScript version incorrectly + // considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953) + // tslint:disable-next-line:no-unused-variable + ScrollStrategy, } from '../core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; @@ -60,6 +67,23 @@ export function getMdTooltipInvalidPositionError(position: string) { return Error(`Tooltip position "${position}" is invalid.`); } +/** Injection token that determines the scroll handling while a tooltip is visible. */ +export const MD_TOOLTIP_SCROLL_STRATEGY = + new InjectionToken<() => ScrollStrategy>('md-tooltip-scroll-strategy'); + +/** @docs-private */ +export function MD_TOOLTIP_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) { + return () => overlay.scrollStrategies.reposition({ scrollThrottle: SCROLL_THROTTLE_MS }); +} + +/** @docs-private */ +export const MD_TOOLTIP_SCROLL_STRATEGY_PROVIDER = { + provide: MD_TOOLTIP_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: MD_TOOLTIP_SCROLL_STRATEGY_PROVIDER_FACTORY +}; + + /** * Directive that attaches a material design tooltip to the host element. Animates the showing and * hiding of a tooltip provided position (defaults to below the element). @@ -185,6 +209,7 @@ export class MdTooltip implements OnDestroy { private _ngZone: NgZone, private _renderer: Renderer2, private _platform: Platform, + @Inject(MD_TOOLTIP_SCROLL_STRATEGY) private _scrollStrategy, @Optional() private _dir: Directionality) { // The mouse events shouldn't be bound on iOS devices, because @@ -279,9 +304,7 @@ export class MdTooltip implements OnDestroy { config.direction = this._dir ? this._dir.value : 'ltr'; config.positionStrategy = strategy; config.panelClass = TOOLTIP_PANEL_CLASS; - config.scrollStrategy = this._overlay.scrollStrategies.reposition({ - scrollThrottle: SCROLL_THROTTLE_MS - }); + config.scrollStrategy = this._scrollStrategy(); this._overlayRef = this._overlay.create(config);