Skip to content

Commit 7d682fd

Browse files
committed
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.
1 parent a555108 commit 7d682fd

File tree

14 files changed

+216
-35
lines changed

14 files changed

+216
-35
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@ import {
1818
ViewContainerRef,
1919
Inject,
2020
ChangeDetectorRef,
21+
InjectionToken,
2122
} from '@angular/core';
2223
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
2324
import {DOCUMENT} from '@angular/platform-browser';
24-
import {Overlay, OverlayRef, OverlayState, TemplatePortal} from '../core';
25+
import {
26+
Overlay,
27+
OverlayRef,
28+
OverlayState,
29+
TemplatePortal,
30+
ScrollStrategy,
31+
RepositionScrollStrategy,
32+
} from '../core';
2533
import {MdAutocomplete} from './autocomplete';
2634
import {PositionStrategy} from '../core/overlay/position/position-strategy';
2735
import {ConnectedPositionStrategy} from '../core/overlay/position/connected-position-strategy';
@@ -48,6 +56,22 @@ export const AUTOCOMPLETE_OPTION_HEIGHT = 48;
4856
/** The total height of the autocomplete panel. */
4957
export const AUTOCOMPLETE_PANEL_HEIGHT = 256;
5058

59+
/** Injection token that determines the scroll handling while the autocomplete panel is open. */
60+
export const MD_AUTOCOMPLETE_SCROLL_STRATEGY =
61+
new InjectionToken<() => ScrollStrategy>('md-autocomplete-scroll-strategy');
62+
63+
/** @docs-private */
64+
export function MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) {
65+
return () => overlay.scrollStrategies.reposition();
66+
}
67+
68+
/** @docs-private */
69+
export const MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER = {
70+
provide: MD_AUTOCOMPLETE_SCROLL_STRATEGY,
71+
deps: [Overlay],
72+
useFactory: MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER_FACTORY,
73+
};
74+
5175
/**
5276
* Provider that allows the autocomplete to register as a ControlValueAccessor.
5377
* @docs-private
@@ -118,12 +142,15 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
118142
this.autocomplete = autocomplete;
119143
}
120144

121-
constructor(private _element: ElementRef, private _overlay: Overlay,
122-
private _viewContainerRef: ViewContainerRef,
123-
private _changeDetectorRef: ChangeDetectorRef,
124-
@Optional() private _dir: Dir, private _zone: NgZone,
125-
@Optional() @Host() private _inputContainer: MdInputContainer,
126-
@Optional() @Inject(DOCUMENT) private _document: any) {}
145+
constructor(
146+
private _element: ElementRef,
147+
private _overlay: Overlay,
148+
private _viewContainerRef: ViewContainerRef,
149+
private _changeDetectorRef: ChangeDetectorRef,
150+
@Inject(MD_AUTOCOMPLETE_SCROLL_STRATEGY) private _scrollStrategy,
151+
@Optional() private _dir: Dir, private _zone: NgZone,
152+
@Optional() @Host() private _inputContainer: MdInputContainer,
153+
@Optional() @Inject(DOCUMENT) private _document: any) {}
127154

128155
ngOnDestroy() {
129156
if (this._panelPositionSubscription) {
@@ -388,7 +415,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
388415
overlayState.positionStrategy = this._getOverlayPosition();
389416
overlayState.width = this._getHostWidth();
390417
overlayState.direction = this._dir ? this._dir.value : 'ltr';
391-
overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition();
418+
overlayState.scrollStrategy = this._scrollStrategy();
392419
return overlayState;
393420
}
394421

src/lib/autocomplete/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ import {NgModule} from '@angular/core';
1010
import {MdOptionModule, OverlayModule, MdCommonModule} from '../core';
1111
import {CommonModule} from '@angular/common';
1212
import {MdAutocomplete} from './autocomplete';
13-
import {MdAutocompleteTrigger} from './autocomplete-trigger';
13+
import {
14+
MdAutocompleteTrigger,
15+
MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER,
16+
} from './autocomplete-trigger';
1417

1518
@NgModule({
1619
imports: [MdOptionModule, OverlayModule, MdCommonModule, CommonModule],
1720
exports: [MdAutocomplete, MdOptionModule, MdAutocompleteTrigger, MdCommonModule],
1821
declarations: [MdAutocomplete, MdAutocompleteTrigger],
22+
providers: [MD_AUTOCOMPLETE_SCROLL_STRATEGY_PROVIDER],
1923
})
2024
export class MdAutocompleteModule {}
2125

src/lib/core/overlay/overlay-directives.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
Renderer2,
2121
OnChanges,
2222
SimpleChanges,
23+
InjectionToken,
24+
Inject,
2325
} from '@angular/core';
2426
import {Overlay, OVERLAY_PROVIDERS} from './overlay';
2527
import {OverlayRef} from './overlay-ref';
@@ -32,16 +34,19 @@ import {
3234
import {PortalModule} from '../portal/portal-directives';
3335
import {ConnectedPositionStrategy} from './position/connected-position-strategy';
3436
import {Dir, LayoutDirection} from '../rtl/dir';
35-
import {Scrollable} from './scroll/scrollable';
36-
import {ScrollStrategy} from './scroll/scroll-strategy';
3737
import {coerceBooleanProperty} from '../coercion/boolean-property';
3838
import {ESCAPE} from '../keyboard/keycodes';
3939
import {Subscription} from 'rxjs/Subscription';
40-
import {ScrollDispatchModule} from './scroll/index';
40+
import {
41+
ScrollStrategy,
42+
RepositionScrollStrategy,
43+
Scrollable,
44+
ScrollDispatchModule,
45+
} from './scroll/index';
4146

4247

4348
/** Default set of positions for the overlay. Follows the behavior of a dropdown. */
44-
let defaultPositionList = [
49+
const defaultPositionList = [
4550
new ConnectionPositionPair(
4651
{originX: 'start', originY: 'bottom'},
4752
{overlayX: 'start', overlayY: 'top'}),
@@ -50,6 +55,23 @@ let defaultPositionList = [
5055
{overlayX: 'start', overlayY: 'bottom'}),
5156
];
5257

58+
/** Injection token that determines the scroll handling while the connected overlay is open. */
59+
export const MD_CONNECTED_OVERLAY_SCROLL_STRATEGY =
60+
new InjectionToken<() => ScrollStrategy>('md-connected-overlay-scroll-strategy');
61+
62+
/** @docs-private */
63+
export function MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) {
64+
return () => overlay.scrollStrategies.reposition();
65+
}
66+
67+
/** @docs-private */
68+
export const MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER = {
69+
provide: MD_CONNECTED_OVERLAY_SCROLL_STRATEGY,
70+
deps: [Overlay],
71+
useFactory: MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER_FACTORY,
72+
};
73+
74+
5375

5476
/**
5577
* Directive applied to an element to make it usable as an origin for an Overlay using a
@@ -131,7 +153,7 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
131153
@Input() backdropClass: string;
132154

133155
/** Strategy to be used when handling scroll events while the overlay is open. */
134-
@Input() scrollStrategy: ScrollStrategy = this._overlay.scrollStrategies.reposition();
156+
@Input() scrollStrategy: ScrollStrategy = this._scrollStrategy();
135157

136158
/** Whether the overlay is open. */
137159
@Input() open: boolean = false;
@@ -165,7 +187,8 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
165187
private _renderer: Renderer2,
166188
templateRef: TemplateRef<any>,
167189
viewContainerRef: ViewContainerRef,
168-
@Optional() private _dir: Dir) {
190+
@Optional() private _dir: Dir,
191+
@Inject(MD_CONNECTED_OVERLAY_SCROLL_STRATEGY) private _scrollStrategy) {
169192
this._templatePortal = new TemplatePortal(templateRef, viewContainerRef);
170193
}
171194

@@ -332,6 +355,6 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges {
332355
imports: [PortalModule, ScrollDispatchModule],
333356
exports: [ConnectedOverlayDirective, OverlayOrigin, ScrollDispatchModule],
334357
declarations: [ConnectedOverlayDirective, OverlayOrigin],
335-
providers: [OVERLAY_PROVIDERS],
358+
providers: [OVERLAY_PROVIDERS, MD_CONNECTED_OVERLAY_SCROLL_STRATEGY_PROVIDER],
336359
})
337360
export class OverlayModule {}

src/lib/datepicker/datepicker.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ import {
2020
ViewContainerRef,
2121
ViewEncapsulation,
2222
NgZone,
23+
Inject,
24+
InjectionToken,
2325
} from '@angular/core';
24-
import {Overlay} from '../core/overlay/overlay';
25-
import {OverlayRef} from '../core/overlay/overlay-ref';
26+
import {
27+
Overlay,
28+
OverlayRef,
29+
OverlayState,
30+
ScrollStrategy,
31+
RepositionScrollStrategy,
32+
} from '../core/overlay/index';
2633
import {ComponentPortal} from '../core/portal/portal';
27-
import {OverlayState} from '../core/overlay/overlay-state';
2834
import {Dir} from '../core/rtl/dir';
2935
import {MdDialog} from '../dialog/dialog';
3036
import {MdDialogRef} from '../dialog/dialog-ref';
@@ -42,6 +48,22 @@ import 'rxjs/add/operator/first';
4248
/** Used to generate a unique ID for each datepicker instance. */
4349
let datepickerUid = 0;
4450

51+
/** Injection token that determines the scroll handling while the calendar is open. */
52+
export const MD_DATEPICKER_SCROLL_STRATEGY =
53+
new InjectionToken<() => ScrollStrategy>('md-datepicker-scroll-strategy');
54+
55+
/** @docs-private */
56+
export function MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) {
57+
return () => overlay.scrollStrategies.reposition();
58+
}
59+
60+
/** @docs-private */
61+
export const MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER = {
62+
provide: MD_DATEPICKER_SCROLL_STRATEGY,
63+
deps: [Overlay],
64+
useFactory: MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER_FACTORY,
65+
};
66+
4567

4668
/**
4769
* Component used as the content for the datepicker dialog and popup. We use this instead of using
@@ -164,6 +186,7 @@ export class MdDatepicker<D> implements OnDestroy {
164186
private _overlay: Overlay,
165187
private _ngZone: NgZone,
166188
private _viewContainerRef: ViewContainerRef,
189+
@Inject(MD_DATEPICKER_SCROLL_STRATEGY) private _scrollStrategy,
167190
@Optional() private _dateAdapter: DateAdapter<D>,
168191
@Optional() private _dir: Dir) {
169192
if (!this._dateAdapter) {
@@ -275,7 +298,7 @@ export class MdDatepicker<D> implements OnDestroy {
275298
overlayState.hasBackdrop = true;
276299
overlayState.backdropClass = 'md-overlay-transparent-backdrop';
277300
overlayState.direction = this._dir ? this._dir.value : 'ltr';
278-
overlayState.scrollStrategy = this._overlay.scrollStrategies.reposition();
301+
overlayState.scrollStrategy = this._scrollStrategy();
279302

280303
this._popupRef = this._overlay.create(overlayState);
281304
}

src/lib/datepicker/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import {CommonModule} from '@angular/common';
1212
import {StyleModule, OverlayModule, A11yModule} from '../core';
1313
import {MdCalendarBody} from './calendar-body';
1414
import {MdYearView} from './year-view';
15-
import {MdDatepicker, MdDatepickerContent} from './datepicker';
15+
import {
16+
MdDatepicker,
17+
MdDatepickerContent,
18+
MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER,
19+
} from './datepicker';
1620
import {MdDatepickerInput} from './datepicker-input';
1721
import {MdDialogModule} from '../dialog/index';
1822
import {MdCalendar} from './calendar';
@@ -58,6 +62,7 @@ export * from './year-view';
5862
],
5963
providers: [
6064
MdDatepickerIntl,
65+
MD_DATEPICKER_SCROLL_STRATEGY_PROVIDER,
6166
],
6267
entryComponents: [
6368
MdDatepickerContent,

src/lib/dialog/dialog.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Injector, ComponentRef, Injectable, Optional, SkipSelf, TemplateRef} from '@angular/core';
9+
import {
10+
Injector,
11+
ComponentRef,
12+
Injectable,
13+
Optional,
14+
SkipSelf,
15+
TemplateRef,
16+
Inject,
17+
InjectionToken,
18+
} from '@angular/core';
1019
import {Location} from '@angular/common';
1120
import {Observable} from 'rxjs/Observable';
1221
import {Subject} from 'rxjs/Subject';
@@ -16,6 +25,8 @@ import {
1625
ComponentType,
1726
OverlayState,
1827
ComponentPortal,
28+
ScrollStrategy,
29+
BlockScrollStrategy,
1930
} from '../core';
2031
import {extendObject} from '../core/util/object-extend';
2132
import {ESCAPE} from '../core/keyboard/keycodes';
@@ -26,6 +37,23 @@ import {MdDialogContainer} from './dialog-container';
2637
import {TemplatePortal} from '../core/portal/portal';
2738

2839

40+
/** Injection token that determines the scroll handling while the dialog is open. */
41+
export const MD_DIALOG_SCROLL_STRATEGY =
42+
new InjectionToken<() => ScrollStrategy>('md-dialog-scroll-strategy');
43+
44+
/** @docs-private */
45+
export function MD_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay: Overlay) {
46+
return () => overlay.scrollStrategies.block();
47+
}
48+
49+
/** @docs-private */
50+
export const MD_DIALOG_SCROLL_STRATEGY_PROVIDER = {
51+
provide: MD_DIALOG_SCROLL_STRATEGY,
52+
deps: [Overlay],
53+
useFactory: MD_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY,
54+
};
55+
56+
2957
/**
3058
* Service to open Material Design modal dialogs.
3159
*/
@@ -61,6 +89,7 @@ export class MdDialog {
6189
constructor(
6290
private _overlay: Overlay,
6391
private _injector: Injector,
92+
@Inject(MD_DIALOG_SCROLL_STRATEGY) private _scrollStrategy,
6493
@Optional() private _location: Location,
6594
@Optional() @SkipSelf() private _parentDialog: MdDialog) {
6695

@@ -133,7 +162,7 @@ export class MdDialog {
133162
let overlayState = new OverlayState();
134163
overlayState.panelClass = dialogConfig.panelClass;
135164
overlayState.hasBackdrop = dialogConfig.hasBackdrop;
136-
overlayState.scrollStrategy = this._overlay.scrollStrategies.block();
165+
overlayState.scrollStrategy = this._scrollStrategy();
137166
overlayState.direction = dialogConfig.direction;
138167
if (dialogConfig.backdropClass) {
139168
overlayState.backdropClass = dialogConfig.backdropClass;

src/lib/dialog/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
A11yModule,
1515
MdCommonModule,
1616
} from '../core';
17-
import {MdDialog} from './dialog';
17+
import {MdDialog, MD_DIALOG_SCROLL_STRATEGY_PROVIDER} from './dialog';
1818
import {MdDialogContainer} from './dialog-container';
1919
import {
2020
MdDialogClose,
@@ -49,6 +49,7 @@ import {
4949
],
5050
providers: [
5151
MdDialog,
52+
MD_DIALOG_SCROLL_STRATEGY_PROVIDER,
5253
],
5354
entryComponents: [MdDialogContainer],
5455
})

src/lib/menu/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {CommonModule} from '@angular/common';
1111
import {OverlayModule, MdCommonModule} from '../core';
1212
import {MdMenu} from './menu-directive';
1313
import {MdMenuItem} from './menu-item';
14-
import {MdMenuTrigger} from './menu-trigger';
14+
import {MdMenuTrigger, MD_MENU_SCROLL_STRATEGY_PROVIDER} from './menu-trigger';
1515
import {MdRippleModule} from '../core/ripple/index';
1616

1717

@@ -24,6 +24,7 @@ import {MdRippleModule} from '../core/ripple/index';
2424
],
2525
exports: [MdMenu, MdMenuItem, MdMenuTrigger, MdCommonModule],
2626
declarations: [MdMenu, MdMenuItem, MdMenuTrigger],
27+
providers: [MD_MENU_SCROLL_STRATEGY_PROVIDER],
2728
})
2829
export class MdMenuModule {}
2930

0 commit comments

Comments
 (0)