From 3e38550b3ac0435632ddf80edb62c66ee195d941 Mon Sep 17 00:00:00 2001 From: Thomas Burleson Date: Tue, 21 Jul 2015 10:45:20 -0500 Subject: [PATCH] fix(backdrop, dialog, css): improve position and animations of backdrop improve backdrop animations and css to account for scroll and parenting: - backdrop - css uses transition and ng-enter/ng-leav - css transitions opacity instead of background-color - css no longer uses key frames - postLink uses $animate.pin if available - postLink logs a warning if the backdrop parent has a style 'position:static' - util - disableElementScroll uses specified element or body; used by dialog to disable dialog parent scrolling when parent is explicitly specified - refactor(util): centralize use of backdrop $compile - dialog - css for 'md-dialog-container' is now 'position:absolute'; 'fixed' is deprecated - hide backdrop now runs in parallel with hide dialog - basic demo #1 now uses element '#popupContainer' as parent; instead of document.body - basic demo #1 has #popupContainer 'position:relative' styling Fixes #3826, #3828, #1967, #1106 --- config/build.config.js | 2 +- src/components/backdrop/backdrop.js | 40 +++++++++++++++---- src/components/backdrop/backdrop.scss | 25 +++++++----- src/components/bottomSheet/bottomSheet.js | 4 +- .../dialog/demoBasicUsage/index.html | 4 +- .../dialog/demoBasicUsage/script.js | 3 +- .../dialog/demoBasicUsage/style.css | 3 ++ src/components/dialog/dialog.js | 16 ++++---- src/components/dialog/dialog.scss | 2 +- src/components/menu/_menu.js | 1 - src/components/menu/menu-interim-element.js | 2 +- src/components/menu/menu.spec.js | 5 --- src/components/select/select.js | 6 +-- src/components/sidenav/sidenav.js | 6 +-- src/core/services/compiler/compiler.js | 3 +- .../interimElement/interimElement.spec.js | 7 +++- src/core/util/util.js | 19 ++++++--- 17 files changed, 89 insertions(+), 59 deletions(-) diff --git a/config/build.config.js b/config/build.config.js index abf1ebf0592..2bfc9be9472 100644 --- a/config/build.config.js +++ b/config/build.config.js @@ -3,7 +3,7 @@ var fs = require('fs'); var versionFile = __dirname + '/../dist/commit'; module.exports = { - ngVersion: '1.3.15', + ngVersion: '1.4.3', version: pkg.version, repository: pkg.repository.url .replace(/^git/,'https') diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js index 88ad2b28f3b..1243a51bb68 100644 --- a/src/components/backdrop/backdrop.js +++ b/src/components/backdrop/backdrop.js @@ -17,11 +17,35 @@ * */ -angular.module('material.components.backdrop', [ - 'material.core' -]) - .directive('mdBackdrop', BackdropDirective); - -function BackdropDirective($mdTheming) { - return $mdTheming; -} +angular + .module('material.components.backdrop', ['material.core']) + .directive('mdBackdrop', function BackdropDirective($mdTheming, $animate, $rootElement, $window, $log, $$rAF) { + + return { + restrict: 'E', + link: postLink + }; + + function postLink(scope, element, attrs) { + // backdrop may be outside the $rootElement, tell ngAnimate to animate regardless + if( $animate.pin ) $animate.pin(element,$rootElement); + + $$rAF(function(){ + // Often $animate.enter() is used to append the backDrop element + // so let's wait until $animate is done... + + var parent = element.parent()[0]; + if ( parent ) { + var position = $window.getComputedStyle(parent).getPropertyValue('position'); + if (position == 'static') { + // backdrop uses position:absolute and will not work properly with parent position:static (default) + var positionError = " may not work properly in a scrolled, static-positioned parent container."; + $log.warn( positionError ); + } + } + + $mdTheming.inherit(element, element.parent()); + }); + + }; + }); diff --git a/src/components/backdrop/backdrop.scss b/src/components/backdrop/backdrop.scss index b145e742e07..2f9fc94ebb3 100644 --- a/src/components/backdrop/backdrop.scss +++ b/src/components/backdrop/backdrop.scss @@ -16,6 +16,9 @@ md-backdrop { z-index: $z-index-sidenav - 1; } + opacity: 1; + transition: opacity 600ms $swift-ease-in-timing-function; + background-color: rgba(0,0,0,0); position: absolute; @@ -25,15 +28,23 @@ md-backdrop { right: 0; &.md-click-catcher { - top: 0; position: fixed; } &.ng-enter { - animation: $swift-ease-out-timing-function mdBackdropFadeIn 0.5s both; + opacity: 0; + &.ng-enter-active { + opacity: 1; + } } + &.ng-leave { - animation: $swift-ease-in-timing-function mdBackdropFadeOut 0.4s both; + opacity: 1; + transition: opacity 400ms linear; + + &.ng-leave-active { + opacity: 0; + } } &.md-opaque { @@ -41,11 +52,3 @@ md-backdrop { } } -@keyframes mdBackdropFadeIn { - from { opacity: 0; } - to { opacity: 1; } -} -@keyframes mdBackdropFadeOut { - from { opacity: 1; } - to { opacity: 0; } -} diff --git a/src/components/bottomSheet/bottomSheet.js b/src/components/bottomSheet/bottomSheet.js index 9abd006d193..62344aff5c8 100644 --- a/src/components/bottomSheet/bottomSheet.js +++ b/src/components/bottomSheet/bottomSheet.js @@ -125,7 +125,7 @@ function MdBottomSheetProvider($$interimElementProvider) { }); /* @ngInject */ - function bottomSheetDefaults($animate, $mdConstant, $mdUtil, $compile, $mdTheming, $mdBottomSheet, $rootElement, $mdGesture) { + function bottomSheetDefaults($animate, $mdConstant, $mdUtil, $mdTheming, $mdBottomSheet, $rootElement, $mdGesture) { var backdrop; return { @@ -143,7 +143,7 @@ function MdBottomSheetProvider($$interimElementProvider) { element = $mdUtil.extractElementByName(element, 'md-bottom-sheet'); // Add a backdrop that will close on click - backdrop = $compile('')(scope); + backdrop = $mdUtil.createBackdrop(scope, "md-bottom-sheet-backdrop md-opaque"); backdrop.on('click', function() { $mdUtil.nextTick($mdBottomSheet.cancel,true); }); diff --git a/src/components/dialog/demoBasicUsage/index.html b/src/components/dialog/demoBasicUsage/index.html index 789e170e10b..631d44a1296 100644 --- a/src/components/dialog/demoBasicUsage/index.html +++ b/src/components/dialog/demoBasicUsage/index.html @@ -1,10 +1,10 @@ -
+

Open a dialog over the app's content. Press escape or click outside to close the dialog and send focus back to the triggering button.

-
+
Alert Dialog diff --git a/src/components/dialog/demoBasicUsage/script.js b/src/components/dialog/demoBasicUsage/script.js index 582fa1b54f8..e631d1290a5 100644 --- a/src/components/dialog/demoBasicUsage/script.js +++ b/src/components/dialog/demoBasicUsage/script.js @@ -9,7 +9,7 @@ angular.module('dialogDemo1', ['ngMaterial']) // to prevent interaction outside of dialog $mdDialog.show( $mdDialog.alert() - .parent(angular.element(document.body)) + .parent(angular.element(document.querySelector('#popupContainer'))) .clickOutsideToClose(true) .title('This is an alert title') .content('You can specify some description text in here.') @@ -22,7 +22,6 @@ angular.module('dialogDemo1', ['ngMaterial']) $scope.showConfirm = function(ev) { // Appending dialog to document.body to cover sidenav in docs app var confirm = $mdDialog.confirm() - .parent(angular.element(document.body)) .title('Would you like to delete your debt?') .content('All of the banks have agreed to forgive you your debts.') .ariaLabel('Lucky day') diff --git a/src/components/dialog/demoBasicUsage/style.css b/src/components/dialog/demoBasicUsage/style.css index e69de29bb2d..69d9bcb8961 100644 --- a/src/components/dialog/demoBasicUsage/style.css +++ b/src/components/dialog/demoBasicUsage/style.css @@ -0,0 +1,3 @@ +#popupContainer { + position:relative; +} diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index a5f9efa9097..c5d02f5049f 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -430,8 +430,7 @@ function MdDialogProvider($$interimElementProvider) { } /* @ngInject */ - function dialogDefaultOptions($mdAria, $document, $mdUtil, $mdConstant, $mdTheming, $mdDialog, $animate, $q ) { - + function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document) { return { hasBackdrop: true, isolateScope: true, @@ -456,7 +455,7 @@ function MdDialogProvider($$interimElementProvider) { captureSourceAndParent(element, options); configureAria(element.find('md-dialog'), options); - showBackdrop(element, options); + showBackdrop(scope, element, options); return dialogPopIn(element, options) .then(function () { @@ -492,10 +491,11 @@ function MdDialogProvider($$interimElementProvider) { options.deactivateListeners(); options.unlockScreenReader(); + options.hideBackdrop(); + return dialogPopOut(element, options) .finally(function () { angular.element($document[0].body).removeClass('md-dialog-is-showing'); - options.hideBackdrop(); element.remove(); options.origin.focus(); @@ -522,7 +522,7 @@ function MdDialogProvider($$interimElementProvider) { options.parent = angular.element(options.parent); if (options.disableParentScroll) { - options.restoreScroll = $mdUtil.disableScrollAround(element); + options.restoreScroll = $mdUtil.disableScrollAround(element,options.parent); } } @@ -594,7 +594,7 @@ function MdDialogProvider($$interimElementProvider) { /** * Show modal backdrop element... */ - function showBackdrop(element, options) { + function showBackdrop(scope, element, options) { if (options.hasBackdrop) { // Fix for IE 10 @@ -605,9 +605,7 @@ function MdDialogProvider($$interimElementProvider) { element.css('top', parentOffset + 'px'); - options.backdrop = angular.element(''); - options.backdrop.css('top', parentOffset + 'px'); - $mdTheming.inherit(options.backdrop, options.parent); + options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque"); $animate.enter(options.backdrop, options.parent); } diff --git a/src/components/dialog/dialog.scss b/src/components/dialog/dialog.scss index e5588189bb6..350ea3aa6a6 100644 --- a/src/components/dialog/dialog.scss +++ b/src/components/dialog/dialog.scss @@ -7,7 +7,7 @@ $dialog-padding: $baseline-grid * 3; display: flex; justify-content: center; align-items: center; - position: fixed; + position: absolute; top: 0; left: 0; width: 100%; diff --git a/src/components/menu/_menu.js b/src/components/menu/_menu.js index f056945b040..3f84a064529 100644 --- a/src/components/menu/_menu.js +++ b/src/components/menu/_menu.js @@ -151,7 +151,6 @@ function MenuDirective($mdMenu) { triggerElement = triggerElement.querySelector('[ng-click]'); } triggerElement && triggerElement.setAttribute('aria-haspopup', 'true'); - triggerElement.setAttribute('type', 'button'); if (templateElement.children().length != 2) { throw Error('Invalid HTML for md-menu. Expected two children elements.'); } diff --git a/src/components/menu/menu-interim-element.js b/src/components/menu/menu-interim-element.js index 5eb54ee4970..dee19afa862 100644 --- a/src/components/menu/menu-interim-element.js +++ b/src/components/menu/menu-interim-element.js @@ -83,7 +83,7 @@ function MenuProvider($$interimElementProvider) { target: angular.element(opts.target), //make sure it's not a naked dom node parent: angular.element(opts.parent), menuContentEl: angular.element(element[0].querySelector('md-menu-content')), - backdrop: opts.hasBackdrop && angular.element('') + backdrop: opts.hasBackdrop && $mdUtil.createBackdrop(scope, "md-menu-backdrop md-click-catcher") }); } diff --git a/src/components/menu/menu.spec.js b/src/components/menu/menu.spec.js index 5ab31980d37..1eb08d4c905 100644 --- a/src/components/menu/menu.spec.js +++ b/src/components/menu/menu.spec.js @@ -30,11 +30,6 @@ describe('md-menu directive', function () { expect(menu.firstElementChild.nodeName).toBe('BUTTON'); }); - it('specifies button type', inject(function($compile, $rootScope) { - var menu = setup()[0]; - expect(menu.firstElementChild.getAttribute('type')).toBe('button'); - })); - it('opens on click', function () { var menu = setup(); openMenu(menu); diff --git a/src/components/select/select.js b/src/components/select/select.js index d1c50c6d554..0ec2721702a 100755 --- a/src/components/select/select.js +++ b/src/components/select/select.js @@ -748,7 +748,7 @@ function SelectProvider($$interimElementProvider) { }); /* @ngInject */ - function selectDefaultOptions($mdSelect, $mdConstant, $$rAF, $mdUtil, $mdTheming, $window, $q, $log ) { + function selectDefaultOptions($mdSelect, $mdConstant, $$rAF, $mdUtil, $mdTheming, $window, $q, $compile ) { var animator = $mdUtil.dom.animator; return { @@ -774,7 +774,7 @@ function SelectProvider($$interimElementProvider) { parent: angular.element(opts.parent), selectEl: element.find('md-select-menu'), contentEl: element.find('md-content'), - backdrop: opts.hasBackdrop && angular.element('') + backdrop: opts.hasBackdrop && $mdUtil.createBackdrop(scope, "md-select-backdrop md-click-catcher") }); opts.resizeFn = function() { @@ -847,8 +847,6 @@ function SelectProvider($$interimElementProvider) { element.addClass('md-clickable'); opts.backdrop && opts.backdrop.on('click', function(e) { - $log.debug("backdrop click"); - e.preventDefault(); e.stopPropagation(); opts.restoreFocus = false; diff --git a/src/components/sidenav/sidenav.js b/src/components/sidenav/sidenav.js index 641ce2a5a47..9c2f8be33df 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, $parse, $log, $compile, $q, $document) { +function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $compile, $parse, $log, $q, $document) { return { restrict: 'E', scope: { @@ -240,9 +240,7 @@ function SidenavDirective($mdMedia, $mdUtil, $mdConstant, $mdTheming, $animate, $mdMedia: $mdMedia }); }; - var backdrop = $compile( - '' - )(scope); + var backdrop = $mdUtil.createBackdrop(scope, "md-sidenav-backdrop md-opaque ng-enter"); element.on('$destroy', sidenavCtrl.destroy); $mdTheming.inherit(backdrop, element); diff --git a/src/core/services/compiler/compiler.js b/src/core/services/compiler/compiler.js index 074918b7691..d51cbe83a4d 100644 --- a/src/core/services/compiler/compiler.js +++ b/src/core/services/compiler/compiler.js @@ -1,4 +1,5 @@ -angular.module('material.core') +angular + .module('material.core') .service('$mdCompiler', mdCompilerService); function mdCompilerService($q, $http, $injector, $compile, $controller, $templateCache) { diff --git a/src/core/services/interimElement/interimElement.spec.js b/src/core/services/interimElement/interimElement.spec.js index a94a7b0b744..d90e1873c32 100644 --- a/src/core/services/interimElement/interimElement.spec.js +++ b/src/core/services/interimElement/interimElement.spec.js @@ -2,7 +2,7 @@ describe('$$interimElement service', function() { beforeEach(module('material.core')); - var $rootScope, $animate, $timeout; + var $rootScope, $animate, $q, $timeout; var $compilerSpy, $themingSpy; describe('provider', function() { @@ -250,7 +250,10 @@ describe('$$interimElement service', function() { beforeEach(function() { setup(); - inject(function($$interimElement) { + inject(function($$interimElement, _$q_, _$timeout_) { + $q = _$q_; + $timeout = _$timeout_; + Service = $$interimElement(); Service.show = tailHook(Service.show, flush); diff --git a/src/core/util/util.js b/src/core/util/util.js index 34dbb7e7ddd..d2f0bfbcf2a 100644 --- a/src/core/util/util.js +++ b/src/core/util/util.js @@ -7,7 +7,7 @@ var nextUniqueId = 0; angular.module('material.core') - .factory('$mdUtil', function ($cacheFactory, $document, $timeout, $q, $window, $mdConstant, $$rAF, $rootScope, $$mdAnimate) { + .factory('$mdUtil', function ($cacheFactory, $document, $timeout, $q, $compile, $window, $mdConstant, $$rAF, $rootScope, $$mdAnimate) { var $mdUtil = { dom : { }, now: window.performance ? @@ -47,14 +47,14 @@ angular.module('material.core') }, // Disables scroll around the passed element. - disableScrollAround: function (element) { + disableScrollAround: function (element, parent) { $mdUtil.disableScrollAround._count = $mdUtil.disableScrollAround._count || 0; ++$mdUtil.disableScrollAround._count; if ($mdUtil.disableScrollAround._enableScrolling) return $mdUtil.disableScrollAround._enableScrolling; element = angular.element(element); var body = $document[0].body, restoreBody = disableBodyScroll(), - restoreElement = disableElementScroll(); + restoreElement = disableElementScroll(parent); return $mdUtil.disableScrollAround._enableScrolling = function () { if (!--$mdUtil.disableScrollAround._count) { @@ -65,13 +65,14 @@ angular.module('material.core') }; // Creates a virtual scrolling mask to absorb touchmove, keyboard, scrollbar clicking, and wheel events - function disableElementScroll() { + function disableElementScroll(element) { + element = angular.element(element || body)[0]; var zIndex = 50; var scrollMask = angular.element( '
' + '
' + '
'); - body.appendChild(scrollMask[0]); + element.appendChild(scrollMask[0]); scrollMask.on('wheel', preventDefault); scrollMask.on('touchmove', preventDefault); @@ -174,6 +175,14 @@ angular.module('material.core') node.dispatchEvent(newEvent); }, + /** + * facade to build md-backdrop element with desired styles + * NOTE: Use $compile to trigger backdrop postLink function + */ + createBackdrop : function(scope, addClass){ + return $compile( $mdUtil.supplant('',[addClass]) )(scope); + }, + /** * supplant() method from Crockford's `Remedial Javascript` * Equivalent to use of $interpolate; without dependency on