Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
feat(sidenav): configurable scroll prevent target (#9338)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
crisbeto authored and jelbourn committed Aug 23, 2016
1 parent 3cf220a commit 218c3ec
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 10 deletions.
33 changes: 23 additions & 10 deletions src/components/sidenav/sidenav.js
Expand Up @@ -232,6 +232,8 @@ function SidenavFocusDirective() {
* @param {expression=} md-is-locked-open When this expression evaluates to true,
* the sidenav 'locks open': it falls into the content's flow instead
* of appearing over it. This overrides the `md-is-open` attribute.
* @param {string=} md-disable-scroll-target Selector, pointing to an element, whose scrolling will
* be disabled when the sidenav is opened. By default this is the sidenav's direct parent.
*
* The $mdMedia() service is exposed to the is-locked-open attribute, which
* can be given a media query or one of the `sm`, `gt-sm`, `md`, `gt-md`, `lg` or `gt-lg` presets.
Expand Down Expand Up @@ -261,6 +263,7 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
function postLink(scope, element, attr, sidenavCtrl) {
var lastParentOverFlow;
var backdrop;
var disableScrollTarget = null;
var triggeringElement = null;
var previousContainerStyles;
var promise = $q.when(true);
Expand All @@ -275,8 +278,23 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
});
};

if (attr.mdDisableScrollTarget) {
disableScrollTarget = $document[0].querySelector(attr.mdDisableScrollTarget);

if (disableScrollTarget) {
disableScrollTarget = angular.element(disableScrollTarget);
} else {
$log.warn($mdUtil.supplant('mdSidenav: couldn\'t find element matching ' +
'selector "{selector}". Falling back to parent.', { selector: attr.mdDisableScrollTarget }));
}
}

if (!disableScrollTarget) {
disableScrollTarget = element.parent();
}

// Only create the backdrop if the backdrop isn't disabled.
if (!angular.isDefined(attr.mdDisableBackdrop)) {
if (!attr.hasOwnProperty('mdDisableBackdrop')) {
backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter");
}

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

previousContainerStyles = null;
}
};
}
}

/**
* Prevent parent scrolling (when the SideNav is open)
*/
function disableParentScroll(disabled) {
var parent = element.parent();
if ( disabled && !lastParentOverFlow ) {

lastParentOverFlow = parent.css('overflow');
parent.css('overflow', 'hidden');

lastParentOverFlow = disableScrollTarget.css('overflow');
disableScrollTarget.css('overflow', 'hidden');
} else if (angular.isDefined(lastParentOverFlow)) {

parent.css('overflow', lastParentOverFlow);
disableScrollTarget.css('overflow', lastParentOverFlow);
lastParentOverFlow = undefined;

}
}

Expand Down
41 changes: 41 additions & 0 deletions src/components/sidenav/sidenav.spec.js
Expand Up @@ -164,6 +164,47 @@ describe('mdSidenav', function() {
});
});

describe('parent scroll prevention', function() {
it('should prevent scrolling on the parent element', inject(function($rootScope) {
var parent = setup('md-is-open="isOpen"').parent()[0];

expect(parent.style.overflow).toBeFalsy();
$rootScope.$apply('isOpen = true');
expect(parent.style.overflow).toBe('hidden');
}));

it('should prevent scrolling on a custom element', inject(function($compile, $rootScope) {
var preventScrollTarget = angular.element('<div id="prevent-scroll-target"></div>');
var parent = angular.element(
'<div>' +
'<md-sidenav md-disable-scroll-target="#prevent-scroll-target" md-is-open="isOpen"></md-sidenav>' +
'</div>'
);

preventScrollTarget.append(parent);
angular.element(document.body).append(preventScrollTarget);
$compile(preventScrollTarget)($rootScope);

expect(preventScrollTarget[0].style.overflow).toBeFalsy();
expect(parent[0].style.overflow).toBeFalsy();

$rootScope.$apply('isOpen = true');
expect(preventScrollTarget[0].style.overflow).toBe('hidden');
expect(parent[0].style.overflow).toBeFalsy();
preventScrollTarget.remove();
}));

it('should log a warning and fall back to the parent if the custom scroll target does not exist',
inject(function($rootScope, $log) {
spyOn($log, 'warn');
var parent = setup('md-is-open="isOpen" md-disable-scroll-target="does-not-exist"').parent()[0];

$rootScope.$apply('isOpen = true');
expect($log.warn).toHaveBeenCalled();
expect(parent.style.overflow).toBe('hidden');
}));
});

});

describe('controller', function() {
Expand Down

0 comments on commit 218c3ec

Please sign in to comment.