diff --git a/src/components/panel/demoBasicUsage/script.js b/src/components/panel/demoBasicUsage/script.js index 97c93829c77..6298923ca90 100644 --- a/src/components/panel/demoBasicUsage/script.js +++ b/src/components/panel/demoBasicUsage/script.js @@ -78,7 +78,8 @@ BasicDemoCtrl.prototype.showMenu = function(ev) { openFrom: ev, clickOutsideToClose: true, escapeToClose: true, - focusOnOpen: false + focusOnOpen: false, + zIndex: 2 }; this._mdPanel.open(config); diff --git a/src/components/panel/panel.js b/src/components/panel/panel.js index 2ee6673f8d1..86079551c32 100644 --- a/src/components/panel/panel.js +++ b/src/components/panel/panel.js @@ -766,6 +766,12 @@ function MdPanelRef(config, $injector) { /** @private @const {!angular.$log} */ this._$log = $injector.get('$log'); + /** @private @const {!angular.$window} */ + this._$window = $injector.get('$window'); + + /** @private @const {!Function} */ + this._$$rAF = $injector.get('$$rAF'); + // Public variables. /** * Unique id for the panelRef. @@ -1148,22 +1154,32 @@ MdPanelRef.prototype._addStyles = function() { // correctly. This is necessary so that the panel will have a defined height // and width. self._$rootScope['$$postDigest'](function() { - positionConfig._calculatePanelPosition(self._panelEl); - self._panelEl.css('top', positionConfig.getTop()); - self._panelEl.css('bottom', positionConfig.getBottom()); - self._panelEl.css('left', positionConfig.getLeft()); - self._panelEl.css('right', positionConfig.getRight()); - - // Use the vendor prefixed version of transform. - var prefixedTransform = self._$mdConstant.CSS.TRANSFORM; - self._panelEl.css(prefixedTransform, positionConfig.getTransform()); - + self._updatePosition(); resolve(self); }); }); }; +/** + * Calculates and updates the position of the panel. + * @private + */ +MdPanelRef.prototype._updatePosition = function() { + var positionConfig = this._config['position']; + + positionConfig._calculatePanelPosition(this._panelEl); + this._panelEl.css('top', positionConfig.getTop()); + this._panelEl.css('bottom', positionConfig.getBottom()); + this._panelEl.css('left', positionConfig.getLeft()); + this._panelEl.css('right', positionConfig.getRight()); + + // Use the vendor prefixed version of transform. + var prefixedTransform = this._$mdConstant.CSS.TRANSFORM; + this._panelEl.css(prefixedTransform, positionConfig.getTransform()); +}; + + /** * Focuses on the panel or the first focus target. * @private @@ -1221,6 +1237,7 @@ MdPanelRef.prototype._createBackdrop = function() { MdPanelRef.prototype._addEventListeners = function() { this._configureEscapeToClose(); this._configureClickOutsideToClose(); + this._configureScrollListener(); }; @@ -1310,6 +1327,31 @@ MdPanelRef.prototype._configureClickOutsideToClose = function() { }; +/** + * Configures the listeners for updating the panel position on scroll. + * @private +*/ +MdPanelRef.prototype._configureScrollListener = function() { + var updatePosition = angular.bind(this, this._updatePosition); + var debouncedUpdatePosition = this._$$rAF.throttle(updatePosition); + var self = this; + + var onScroll = function() { + if (!self._config['disableParentScroll']) { + debouncedUpdatePosition(); + } + }; + + // Add listeners. + this._$window.addEventListener('scroll', onScroll, true); + + // Queue remove listeners function. + this._removeListeners.push(function() { + self._$window.removeEventListener('scroll', onScroll, true); + }); +}; + + /** * Setup the focus traps. These traps will wrap focus when tabbing past the * panel. When shift-tabbing, the focus will stick in place. @@ -1460,8 +1502,8 @@ function MdPanelPosition() { /** @private {boolean} */ this._absolute = false; - /** @private {!DOMRect} */ - this._relativeToRect; + /** @private {!angular.JQLite} */ + this._relativeToEl; /** @private {string} */ this._top = ''; @@ -1619,7 +1661,7 @@ MdPanelPosition.prototype.center = function() { */ MdPanelPosition.prototype.relativeTo = function(element) { this._absolute = false; - this._relativeToRect = getElement(element)[0].getBoundingClientRect(); + this._relativeToEl = getElement(element); return this; }; @@ -1631,7 +1673,7 @@ MdPanelPosition.prototype.relativeTo = function(element) { * @returns {MdPanelPosition} */ MdPanelPosition.prototype.addPanelPosition = function(xPosition, yPosition) { - if (!this._relativeToRect) { + if (!this._relativeToEl) { throw new Error('addPanelPosition can only be used with relative ' + 'positioning. Set relativeTo first.'); } @@ -1810,14 +1852,13 @@ MdPanelPosition.prototype._calculatePanelPosition = function(panelEl) { return; } - // TODO(ErinCoughlan): Update position on scroll. // TODO(ErinCoughlan): Position panel intelligently to keep it on screen. var panelBounds = panelEl[0].getBoundingClientRect(); var panelWidth = panelBounds.width; var panelHeight = panelBounds.height; - var targetBounds = this._relativeToRect; + var targetBounds = this._relativeToEl[0].getBoundingClientRect(); var targetLeft = targetBounds.left; var targetRight = targetBounds.right; diff --git a/src/components/panel/panel.spec.js b/src/components/panel/panel.spec.js index 91557d56cb5..7d9357f2f40 100644 --- a/src/components/panel/panel.spec.js +++ b/src/components/panel/panel.spec.js @@ -1,6 +1,6 @@ describe('$mdPanel', function() { var $mdPanel, $rootScope, $rootEl, $templateCache, $q, $material, $mdConstant, - $mdUtil, $animate; + $mdUtil, $animate, $$rAF, $window; var panelRef; var attachedElements = []; var PANEL_WRAPPER_CLASS = '.md-panel-outer-wrapper'; @@ -28,6 +28,8 @@ describe('$mdPanel', function() { $mdConstant = $injector.get('$mdConstant'); $mdUtil = $injector.get('$mdUtil'); $animate = $injector.get('$animate'); + $window = $injector.get('$window'); + $$rAF = $injector.get('$$rAF'); }; beforeEach(function() { @@ -1426,7 +1428,7 @@ describe('$mdPanel', function() { .relativeTo(myButton) .addPanelPosition(xPosition.ALIGN_START, yPosition.ALIGN_TOPS) .addPanelPosition(xPosition.ALIGN_END, yPosition.ALIGN_BOTTOMS) - .addPanelPosition(xPosition.ALIGN_OFFSET_END, yPosition.BELOW); + .addPanelPosition(xPosition.OFFSET_END, yPosition.BELOW); config['position'] = position; @@ -1590,34 +1592,26 @@ describe('$mdPanel', function() { expect(panelRect.left).toBeApproximately(myButtonRect.right); }); }); - }); - it('should throw if xPosition is not valid', function() { - var myButton = ''; - attachToBody(myButton); - myButton = angular.element(document.querySelector('button')); - - var expression = function() { - mdPanelPosition - .relativeTo(myButton) - .addPanelPosition('fake-x-position', null); - }; - - expect(expression).toThrow(); - }); + it('should throw if xPosition is not valid', function() { + var expression = function() { + mdPanelPosition + .relativeTo(myButton) + .addPanelPosition('fake-x-position', null); + }; - it('should throw if yPosition is not valid', function() { - var myButton = ''; - attachToBody(myButton); - myButton = angular.element(document.querySelector('button')); + expect(expression).toThrow(); + }); - var expression = function() { - mdPanelPosition - .relativeTo(myButton) - .withPanelYPosition('fake-y-position'); - }; + it('should throw if yPosition is not valid', function() { + var expression = function() { + mdPanelPosition + .relativeTo(myButton) + .addPanelPosition(null, 'fake-y-position'); + }; - expect(expression).toThrow(); + expect(expression).toThrow(); + }); }); });