Skip to content

Commit 950216f

Browse files
committed
feat(sidenav): track sidenav vissibilty and enter / leave backdrop if needed.
- Revert scope value if sidenav is shown. - Throttle Resize Events Fixes angular#4595
1 parent 8ef798f commit 950216f

File tree

1 file changed

+63
-8
lines changed

1 file changed

+63
-8
lines changed

src/components/sidenav/sidenav.js

+63-8
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ function SidenavFocusDirective() {
208208
* - `<md-sidenav md-is-locked-open="$mdMedia('min-width: 1000px')"></md-sidenav>`
209209
* - `<md-sidenav md-is-locked-open="$mdMedia('sm')"></md-sidenav>` (locks open on small screens)
210210
*/
211-
function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document) {
211+
function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document, $$rAF) {
212212
return {
213213
restrict: 'E',
214214
scope: {
@@ -229,6 +229,9 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
229229
var lastParentOverFlow;
230230
var triggeringElement = null;
231231
var promise = $q.when(true);
232+
var skipSidenav = false;
233+
var skipNextUpdate = false;
234+
var $window = angular.element(window);
232235

233236
var isLockedOpenParsed = $parse(attr.mdIsLockedOpen);
234237
var isLocked = function() {
@@ -244,22 +247,55 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
244247

245248
$mdTheming.inherit(backdrop, element);
246249

250+
var throttleResize = $$rAF.throttle(revalidateVisibility);
251+
$window.on('resize', throttleResize);
252+
revalidateVisibility();
253+
247254
element.on('$destroy', function() {
248255
backdrop.remove();
249256
sidenavCtrl.destroy();
257+
$window.off('resize', throttleResize);
250258
});
251259

252260
scope.$on('$destroy', function(){
261+
$window.off('resize', throttleResize);
253262
backdrop.remove()
254263
});
255264

256265
scope.$watch(isLocked, updateIsLocked);
257266
scope.$watch('isOpen', updateIsOpen);
258267

259268

269+
260270
// Publish special accessor for the Controller instance
261271
sidenavCtrl.$toggleOpen = toggleOpen;
262272

273+
function revalidateVisibility() {
274+
var lastValue = scope.isOpen;
275+
if (attr.mdIsLockedOpen) return;
276+
277+
if (isHidden(element, true) && scope.isOpen) {
278+
scope.isOpen = false;
279+
} else if (!isHidden(element, true) && !scope.isOpen) {
280+
scope.isOpen = true;
281+
}
282+
283+
// If the revalidated isOpen Value got changed,
284+
// we should apply it to the view without running the updateOpen watcher
285+
if (lastValue != scope.isOpen) {
286+
skipSidenav = true;
287+
if (!scope.$$phase) scope.$apply();
288+
skipSidenav = false;
289+
}
290+
}
291+
292+
// Check if the element is hidden by any hide attribute (using computed style)
293+
function isHidden(element, compute) {
294+
if (element[0].offsetParent == null) return true;
295+
if (!compute) return false;
296+
return window.getComputedStyle(element[0]).display === 'none';
297+
}
298+
263299
/**
264300
* Toggle the DOM classes to indicate `locked`
265301
* @param isLocked
@@ -278,26 +314,45 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
278314
* Toggle the SideNav view and attach/detach listeners
279315
* @param isOpen
280316
*/
281-
function updateIsOpen(isOpen) {
317+
function updateIsOpen(isOpen, oldValue) {
318+
if (skipNextUpdate) {
319+
skipNextUpdate = false;
320+
return;
321+
}
322+
282323
// Support deprecated md-sidenav-focus attribute as fallback
283324
var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element;
284325
var parent = element.parent();
285326

327+
// Temporary remove md-closed class (won't affect the view, because it's outside of the viewport).
328+
// And then check the visbility due hide attributes. So if the isOpen variable changes
329+
// and a hide attribute is active we should revert the digest change
330+
if (!skipSidenav) {
331+
var wasClosed = element.hasClass('md-closed');
332+
element.removeClass('md-closed');
333+
if (isHidden(element, false) && !skipSidenav) {
334+
element.toggleClass('md-closed', wasClosed);
335+
skipNextUpdate = true;
336+
scope.isOpen = oldValue;
337+
return;
338+
}
339+
element.toggleClass('md-closed', wasClosed);
340+
}
341+
286342
parent[isOpen ? 'on' : 'off']('keydown', onKeyDown);
287343
backdrop[isOpen ? 'on' : 'off']('click', close);
288344

289-
if ( isOpen ) {
345+
if ( isOpen && !skipSidenav) {
290346
// Capture upon opening..
291347
triggeringElement = $document[0].activeElement;
292348
}
293349

294350
disableParentScroll(isOpen);
295351

296-
return promise = $q.all([
297-
isOpen ? $animate.enter(backdrop, parent) : $animate.leave(backdrop),
298-
$animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed')
299-
])
300-
.then(function() {
352+
var actions = [isOpen ? $animate.enter(backdrop, parent) : $animate.leave(backdrop)];
353+
if (!skipSidenav) actions.push(isOpen ? $animate.removeClass(element, 'md-closed') : $animate.addClass(element, 'md-closed'));
354+
355+
return promise = $q.all(actions).then(function() {
301356
// Perform focus when animations are ALL done...
302357
if (scope.isOpen) {
303358
focusEl && focusEl.focus();

0 commit comments

Comments
 (0)