Skip to content

Commit b2d0548

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 b2d0548

File tree

1 file changed

+73
-13
lines changed

1 file changed

+73
-13
lines changed

src/components/sidenav/sidenav.js

+73-13
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, $window) {
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 windowElement = angular.element($window);
232235

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

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

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

252260
scope.$on('$destroy', function(){
261+
windowElement.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(true, true) && scope.isOpen) {
278+
scope.isOpen = false;
279+
} else if (!isHidden(true, 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+
/**
293+
* Checks the sidenav's hide state
294+
* @param computedStyle Check Computed Style
295+
* @param offsetParent Check Offset Parent
296+
*/
297+
function isHidden(computedStyle, offsetParent) {
298+
if (computedStyle && window.getComputedStyle(element[0]).display === 'none') return true;
299+
if (offsetParent && element[0].offsetParent == null) return true;
300+
return false;
301+
}
302+
263303
/**
264304
* Toggle the DOM classes to indicate `locked`
265305
* @param isLocked
@@ -278,31 +318,51 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate,
278318
* Toggle the SideNav view and attach/detach listeners
279319
* @param isOpen
280320
*/
281-
function updateIsOpen(isOpen) {
321+
function updateIsOpen(isOpen, oldValue) {
322+
if (skipNextUpdate) {
323+
skipNextUpdate = false;
324+
return;
325+
}
326+
282327
// Support deprecated md-sidenav-focus attribute as fallback
283328
var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element;
284329
var parent = element.parent();
285330

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

289-
if ( isOpen ) {
349+
if ( isOpen && !skipSidenav) {
290350
// Capture upon opening..
291351
triggeringElement = $document[0].activeElement;
292352
}
293353

294354
disableParentScroll(isOpen);
295355

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() {
301-
// Perform focus when animations are ALL done...
302-
if (scope.isOpen) {
303-
focusEl && focusEl.focus();
304-
}
305-
});
356+
var actions = [isOpen ? $animate.enter(backdrop, parent) : $animate.leave(backdrop)];
357+
if (!skipSidenav) actions.push(isOpen ? $animate.removeClass(element, 'md-closed') : $animate.addClass(element, 'md-closed'));
358+
359+
return promise = $q.all(actions)
360+
.then(function() {
361+
// Perform focus when animations are ALL done...
362+
if (scope.isOpen) {
363+
focusEl && focusEl.focus();
364+
}
365+
});
306366
}
307367

308368
/**

0 commit comments

Comments
 (0)