From a1804232c6566b515590a2d88c0a732c4fcd61d7 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sat, 2 Jan 2016 14:15:15 +0100 Subject: [PATCH] feat(sidenav): track sidenav vissibilty and enter / leave backdrop if needed. - Revert scope value if sidenav is shown. - Throttle Resize Events Fixes #4595 --- src/components/sidenav/sidenav.js | 82 ++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/src/components/sidenav/sidenav.js b/src/components/sidenav/sidenav.js index 593a90ff066..82bc10010fb 100644 --- a/src/components/sidenav/sidenav.js +++ b/src/components/sidenav/sidenav.js @@ -208,7 +208,7 @@ function SidenavFocusDirective() { * - `` * - `` (locks open on small screens) */ -function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document) { +function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document, $$rAF, $window) { return { restrict: 'E', scope: { @@ -229,6 +229,9 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, var lastParentOverFlow; var triggeringElement = null; var promise = $q.when(true); + var skipSidenav = false; + var skipNextUpdate = false; + var windowElement = angular.element($window); var isLockedOpenParsed = $parse(attr.mdIsLockedOpen); var isLocked = function() { @@ -244,12 +247,18 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $mdTheming.inherit(backdrop, element); + var throttleResize = $$rAF.throttle(revalidateVisibility); + windowElement.on('resize', throttleResize); + revalidateVisibility(); + element.on('$destroy', function() { backdrop.remove(); sidenavCtrl.destroy(); + windowElement.off('resize', throttleResize); }); scope.$on('$destroy', function(){ + windowElement.off('resize', throttleResize); backdrop.remove() }); @@ -257,9 +266,36 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, scope.$watch('isOpen', updateIsOpen); + // Publish special accessor for the Controller instance sidenavCtrl.$toggleOpen = toggleOpen; + function revalidateVisibility() { + var lastValue = scope.isOpen; + if (attr.mdIsLockedOpen) return; + + if (isHidden(element, true) && scope.isOpen) { + scope.isOpen = false; + } else if (!isHidden(element, true) && !scope.isOpen) { + scope.isOpen = true; + } + + // If the revalidated isOpen Value got changed, + // we should apply it to the view without running the updateOpen watcher + if (lastValue != scope.isOpen) { + skipSidenav = true; + if (!scope.$$phase) scope.$apply(); + skipSidenav = false; + } + } + + // Check if the element is hidden by any hide attribute (using computed style) + function isHidden(element, compute) { + if (element[0].offsetParent == null) return true; + if (!compute) return false; + return window.getComputedStyle(element[0]).display === 'none'; + } + /** * Toggle the DOM classes to indicate `locked` * @param isLocked @@ -278,31 +314,51 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, * Toggle the SideNav view and attach/detach listeners * @param isOpen */ - function updateIsOpen(isOpen) { + function updateIsOpen(isOpen, oldValue) { + if (skipNextUpdate) { + skipNextUpdate = false; + return; + } + // Support deprecated md-sidenav-focus attribute as fallback var focusEl = $mdUtil.findFocusTarget(element) || $mdUtil.findFocusTarget(element,'[md-sidenav-focus]') || element; var parent = element.parent(); + // Temporary remove md-closed class (won't affect the view, because it's outside of the viewport). + // And then check the visbility due hide attributes. So if the isOpen variable changes + // and a hide attribute is active we should revert the digest change + if (!skipSidenav) { + var wasClosed = element.hasClass('md-closed'); + element.removeClass('md-closed'); + if (isHidden(element, false) && !skipSidenav) { + element.toggleClass('md-closed', wasClosed); + skipNextUpdate = true; + scope.isOpen = !!oldValue; + return; + } + element.toggleClass('md-closed', wasClosed); + } + parent[isOpen ? 'on' : 'off']('keydown', onKeyDown); backdrop[isOpen ? 'on' : 'off']('click', close); - if ( isOpen ) { + if ( isOpen && !skipSidenav) { // Capture upon opening.. triggeringElement = $document[0].activeElement; } disableParentScroll(isOpen); - return promise = $q.all([ - isOpen ? $animate.enter(backdrop, parent) : $animate.leave(backdrop), - $animate[isOpen ? 'removeClass' : 'addClass'](element, 'md-closed') - ]) - .then(function() { - // Perform focus when animations are ALL done... - if (scope.isOpen) { - focusEl && focusEl.focus(); - } - }); + var actions = [isOpen ? $animate.enter(backdrop, parent) : $animate.leave(backdrop)]; + if (!skipSidenav) actions.push(isOpen ? $animate.removeClass(element, 'md-closed') : $animate.addClass(element, 'md-closed')); + + return promise = $q.all(actions) + .then(function() { + // Perform focus when animations are ALL done... + if (scope.isOpen) { + focusEl && focusEl.focus(); + } + }); } /**