diff --git a/src/components/list/demoListControls/index.html b/src/components/list/demoListControls/index.html index 4bfd0436037..4421931c851 100644 --- a/src/components/list/demoListControls/index.html +++ b/src/components/list/demoListControls/index.html @@ -20,6 +20,38 @@ + Secondary Menus + +

Click anywhere to fire the secondary action

+ + + + + + + + + Redial + + + + + Check voicemail + + + + + + Notifications + + + + + +
+ + + Clickable Items with Secondary Controls diff --git a/src/components/list/list.js b/src/components/list/list.js index e627f6a9d42..3e22481d0c1 100644 --- a/src/components/list/list.js +++ b/src/components/list/list.js @@ -83,7 +83,7 @@ function mdListDirective($mdTheming) { * that is inside the list, but does not wrap the contents._ */ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { - var proxiedTypes = ['md-checkbox', 'md-switch']; + var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu']; return { restrict: 'E', controller: 'MdListController', @@ -112,9 +112,13 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { tEl.addClass('_md-no-proxy'); } } + wrapSecondaryItems(); setupToggleAria(); + if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") { + setupProxiedMenu(); + } function setupToggleAria() { var toggleTypes = ['md-switch', 'md-checkbox']; @@ -131,6 +135,35 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { } } + function setupProxiedMenu() { + var menuEl = angular.element(proxyElement); + + var isEndAligned = menuEl.parent().hasClass('_md-secondary-container') || + proxyElement.parentNode.firstElementChild !== proxyElement; + + var xAxisPosition = 'left'; + + if (isEndAligned) { + // When the proxy item is aligned at the end of the list, we have to set the origin to the end. + xAxisPosition = 'right'; + } + + // Set the position mode / origin of the proxied menu. + if (!menuEl.attr('md-position-mode')) { + menuEl.attr('md-position-mode', xAxisPosition + ' target'); + } + + // Apply menu open binding to menu button + var menuOpenButton = menuEl.children().eq(0); + if (!hasClickEvent(menuOpenButton[0])) { + menuOpenButton.attr('ng-click', '$mdOpenMenu($event)'); + } + + if (!menuOpenButton.attr('aria-label')) { + menuOpenButton.attr('aria-label', 'Open List Menu'); + } + } + function wrapIn(type) { if (type == 'div') { itemContainer = angular.element('
'); @@ -277,6 +310,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { } } + function computeClickable() { if (proxies.length == 1 || hasClick) { $element.addClass('md-clickable'); @@ -313,6 +347,9 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) { if (!parentButton && clickChild.contains(e.target)) { angular.forEach(proxies, function(proxy) { if (e.target !== proxy && !proxy.contains(e.target)) { + if (proxy.nodeName === 'MD-MENU') { + proxy = proxy.children[0]; + } angular.element(proxy).triggerHandler('click'); } }); diff --git a/src/components/list/list.spec.js b/src/components/list/list.spec.js index 32df25231b9..12337504bf5 100644 --- a/src/components/list/list.spec.js +++ b/src/components/list/list.spec.js @@ -301,6 +301,61 @@ describe('mdListItem directive', function() { expect(button[0].hasAttribute('ng-disabled')).toBeTruthy(); }); + describe('with a md-menu', function() { + it('should forward click events on the md-menu trigger button', function() { + var template = + '' + + '' + + '' + + '' + + ''; + + var listItem = setup(template); + var cntr = listItem[0].querySelector('div'); + var openMenu = jasmine.createSpy('openMenu'); + + $rootScope.openMenu = openMenu; + + if (cntr && cntr.click) { + cntr.click(); + expect(openMenu).toHaveBeenCalled(); + } + + }); + + it('should detect the menu position mode when md-menu is aligned at right', function() { + var template = + '' + + 'Menu should be aligned right' + + '' + + '' + + '' + + ''; + + var listItem = setup(template); + + var mdMenu = listItem.find('md-menu'); + + expect(mdMenu.attr('md-position-mode')).toBe('right target'); + }); + + it('should detect the menu position mode when md-menu is aligned at left', function() { + var template = + '' + + '' + + '' + + '' + + 'Menu should be aligned left' + + ''; + + var listItem = setup(template); + + var mdMenu = listItem.find('md-menu'); + + expect(mdMenu.attr('md-position-mode')).toBe('left target'); + }); + }); + describe('with a clickable item', function() { it('should wrap secondary icons in a md-button', function() { diff --git a/src/components/menu/js/menuServiceProvider.js b/src/components/menu/js/menuServiceProvider.js index 73fbf2aecbd..97258e6539f 100644 --- a/src/components/menu/js/menuServiceProvider.js +++ b/src/components/menu/js/menuServiceProvider.js @@ -461,8 +461,17 @@ function MenuProvider($$interimElementProvider) { position.left = willFitRight ? originNodeRect.right - originNode.style.left : originNodeRect.left - originNode.style.left - openMenuNodeRect.width; transformOrigin += willFitRight ? 'left' : 'right'; break; + case 'right': + if (rtl) { + position.left = originNodeRect.right - originNodeRect.width; + transformOrigin += 'left'; + } else { + position.left = originNodeRect.right - openMenuNodeRect.width; + transformOrigin += 'right'; + } + break; case 'left': - if(rtl) { + if (rtl) { position.left = originNodeRect.right - openMenuNodeRect.width; transformOrigin += 'right'; } else {