Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 218c3ec

Browse files
crisbetojelbourn
authored andcommitted
feat(sidenav): configurable scroll prevent target (#9338)
* Adds the ability to select which element's scrolling will be disabled when a sidenav is open. This can be useful in the cases where the scrollable container isn't the direct parent of the sidenav. * Adds unit tests for the parent scroll prevention. Fixes #8634
1 parent 3cf220a commit 218c3ec

File tree

2 files changed

+64
-10
lines changed

2 files changed

+64
-10
lines changed

src/components/sidenav/sidenav.js

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ function SidenavFocusDirective() {
232232
* @param {expression=} md-is-locked-open When this expression evaluates to true,
233233
* the sidenav 'locks open': it falls into the content's flow instead
234234
* of appearing over it. This overrides the `md-is-open` attribute.
235+
* @param {string=} md-disable-scroll-target Selector, pointing to an element, whose scrolling will
236+
* be disabled when the sidenav is opened. By default this is the sidenav's direct parent.
235237
*
236238
* The $mdMedia() service is exposed to the is-locked-open attribute, which
237239
* can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
@@ -261,6 +263,7 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
261263
function postLink(scope, element, attr, sidenavCtrl) {
262264
var lastParentOverFlow;
263265
var backdrop;
266+
var disableScrollTarget = null;
264267
var triggeringElement = null;
265268
var previousContainerStyles;
266269
var promise = $q.when(true);
@@ -275,8 +278,23 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
275278
});
276279
};
277280

281+
if (attr.mdDisableScrollTarget) {
282+
disableScrollTarget = $document[0].querySelector(attr.mdDisableScrollTarget);
283+
284+
if (disableScrollTarget) {
285+
disableScrollTarget = angular.element(disableScrollTarget);
286+
} else {
287+
$log.warn($mdUtil.supplant('mdSidenav: couldn\'t find element matching ' +
288+
'selector "{selector}". Falling back to parent.', { selector: attr.mdDisableScrollTarget }));
289+
}
290+
}
291+
292+
if (!disableScrollTarget) {
293+
disableScrollTarget = element.parent();
294+
}
295+
278296
// Only create the backdrop if the backdrop isn't disabled.
279-
if (!angular.isDefined(attr.mdDisableBackdrop)) {
297+
if (!attr.hasOwnProperty('mdDisableBackdrop')) {
280298
backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
281299
}
282300

@@ -393,25 +411,20 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
393411
backdrop[0].style.height = null;
394412

395413
previousContainerStyles = null;
396-
}
414+
};
397415
}
398416
}
399417

400418
/**
401419
* Prevent parent scrolling (when the SideNav is open)
402420
*/
403421
function disableParentScroll(disabled) {
404-
var parent = element.parent();
405422
if ( disabled && !lastParentOverFlow ) {
406-
407-
lastParentOverFlow = parent.css('overflow');
408-
parent.css('overflow', 'hidden');
409-
423+
lastParentOverFlow = disableScrollTarget.css('overflow');
424+
disableScrollTarget.css('overflow', 'hidden');
410425
} else if (angular.isDefined(lastParentOverFlow)) {
411-
412-
parent.css('overflow', lastParentOverFlow);
426+
disableScrollTarget.css('overflow', lastParentOverFlow);
413427
lastParentOverFlow = undefined;
414-
415428
}
416429
}
417430

src/components/sidenav/sidenav.spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,47 @@ describe('mdSidenav', function() {
164164
});
165165
});
166166

167+
describe('parent scroll prevention', function() {
168+
it('should prevent scrolling on the parent element', inject(function($rootScope) {
169+
var parent = setup('md-is-open="isOpen"').parent()[0];
170+
171+
expect(parent.style.overflow).toBeFalsy();
172+
$rootScope.$apply('isOpen = true');
173+
expect(parent.style.overflow).toBe('hidden');
174+
}));
175+
176+
it('should prevent scrolling on a custom element', inject(function($compile, $rootScope) {
177+
var preventScrollTarget = angular.element('<div id="prevent-scroll-target"></div>');
178+
var parent = angular.element(
179+
'<div>' +
180+
'<md-sidenav md-disable-scroll-target="#prevent-scroll-target" md-is-open="isOpen"></md-sidenav>' +
181+
'</div>'
182+
);
183+
184+
preventScrollTarget.append(parent);
185+
angular.element(document.body).append(preventScrollTarget);
186+
$compile(preventScrollTarget)($rootScope);
187+
188+
expect(preventScrollTarget[0].style.overflow).toBeFalsy();
189+
expect(parent[0].style.overflow).toBeFalsy();
190+
191+
$rootScope.$apply('isOpen = true');
192+
expect(preventScrollTarget[0].style.overflow).toBe('hidden');
193+
expect(parent[0].style.overflow).toBeFalsy();
194+
preventScrollTarget.remove();
195+
}));
196+
197+
it('should log a warning and fall back to the parent if the custom scroll target does not exist',
198+
inject(function($rootScope, $log) {
199+
spyOn($log, 'warn');
200+
var parent = setup('md-is-open="isOpen" md-disable-scroll-target="does-not-exist"').parent()[0];
201+
202+
$rootScope.$apply('isOpen = true');
203+
expect($log.warn).toHaveBeenCalled();
204+
expect(parent.style.overflow).toBe('hidden');
205+
}));
206+
});
207+
167208
});
168209

169210
describe('controller', function() {

0 commit comments

Comments
 (0)