Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 5a0836c

Browse files
devversionThomasBurleson
authored andcommitted
feat(list): add support for md-menu as proxied element
* Allow md-menu to be a proxied element in a list * Smart detection of being right or left aligned * Allows md-menu to be a secondary element * Support for RTL pages, automatically detecting the menu origin. Fixes #3339 Closes #6459
1 parent d381619 commit 5a0836c

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed

src/components/list/demoListControls/index.html

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,38 @@
2020

2121
<md-divider></md-divider>
2222

23+
<md-subheader class="md-no-sticky">Secondary Menus</md-subheader>
24+
<md-list-item>
25+
<p>Click anywhere to fire the secondary action</p>
26+
27+
<md-menu class="md-secondary">
28+
<md-button class="md-icon-button">
29+
<md-icon md-svg-icon="communication:message"></md-icon>
30+
</md-button>
31+
<md-menu-content width="4">
32+
<md-menu-item>
33+
<md-button>
34+
Redial
35+
</md-button>
36+
</md-menu-item>
37+
<md-menu-item>
38+
<md-button>
39+
Check voicemail
40+
</md-button>
41+
</md-menu-item>
42+
<md-menu-divider></md-menu-divider>
43+
<md-menu-item>
44+
<md-button>
45+
Notifications
46+
</md-button>
47+
</md-menu-item>
48+
</md-menu-content>
49+
</md-menu>
50+
51+
</md-list-item>
52+
53+
<md-divider></md-divider>
54+
2355
<md-subheader class="md-no-sticky">Clickable Items with Secondary Controls</md-subheader>
2456
<md-list-item ng-click="navigateTo(setting.extraScreen, $event)" ng-repeat="setting in settings">
2557
<md-icon md-svg-icon="{{setting.icon}}"></md-icon>

src/components/list/list.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ function mdListDirective($mdTheming) {
181181
*
182182
*/
183183
function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
184-
var proxiedTypes = ['md-checkbox', 'md-switch'];
184+
var proxiedTypes = ['md-checkbox', 'md-switch', 'md-menu'];
185185
return {
186186
restrict: 'E',
187187
controller: 'MdListController',
@@ -210,9 +210,13 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
210210
tEl.addClass('_md-no-proxy');
211211
}
212212
}
213+
213214
wrapSecondaryItems();
214215
setupToggleAria();
215216

217+
if (hasProxiedElement && proxyElement.nodeName === "MD-MENU") {
218+
setupProxiedMenu();
219+
}
216220

217221
function setupToggleAria() {
218222
var toggleTypes = ['md-switch', 'md-checkbox'];
@@ -229,6 +233,35 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
229233
}
230234
}
231235

236+
function setupProxiedMenu() {
237+
var menuEl = angular.element(proxyElement);
238+
239+
var isEndAligned = menuEl.parent().hasClass('_md-secondary-container') ||
240+
proxyElement.parentNode.firstElementChild !== proxyElement;
241+
242+
var xAxisPosition = 'left';
243+
244+
if (isEndAligned) {
245+
// When the proxy item is aligned at the end of the list, we have to set the origin to the end.
246+
xAxisPosition = 'right';
247+
}
248+
249+
// Set the position mode / origin of the proxied menu.
250+
if (!menuEl.attr('md-position-mode')) {
251+
menuEl.attr('md-position-mode', xAxisPosition + ' target');
252+
}
253+
254+
// Apply menu open binding to menu button
255+
var menuOpenButton = menuEl.children().eq(0);
256+
if (!hasClickEvent(menuOpenButton[0])) {
257+
menuOpenButton.attr('ng-click', '$mdOpenMenu($event)');
258+
}
259+
260+
if (!menuOpenButton.attr('aria-label')) {
261+
menuOpenButton.attr('aria-label', 'Open List Menu');
262+
}
263+
}
264+
232265
function wrapIn(type) {
233266
if (type == 'div') {
234267
itemContainer = angular.element('<div class="_md-no-style _md-list-item-inner">');
@@ -373,6 +406,7 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
373406

374407
}
375408
}
409+
376410
function computeClickable() {
377411
if (proxies.length == 1 || hasClick) {
378412
$element.addClass('md-clickable');
@@ -409,6 +443,9 @@ function mdListItemDirective($mdAria, $mdConstant, $mdUtil, $timeout) {
409443
if (!parentButton && clickChild.contains(e.target)) {
410444
angular.forEach(proxies, function(proxy) {
411445
if (e.target !== proxy && !proxy.contains(e.target)) {
446+
if (proxy.nodeName === 'MD-MENU') {
447+
proxy = proxy.children[0];
448+
}
412449
angular.element(proxy).triggerHandler('click');
413450
}
414451
});

src/components/list/list.spec.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,61 @@ describe('mdListItem directive', function() {
327327
expect(button[0].hasAttribute('ng-disabled')).toBeTruthy();
328328
});
329329

330+
describe('with a md-menu', function() {
331+
it('should forward click events on the md-menu trigger button', function() {
332+
var template =
333+
'<md-list-item>' +
334+
'<md-menu>' +
335+
'<md-button ng-click="openMenu()"></md-button>' +
336+
'</md-menu>' +
337+
'</md-list-item>';
338+
339+
var listItem = setup(template);
340+
var cntr = listItem[0].querySelector('div');
341+
var openMenu = jasmine.createSpy('openMenu');
342+
343+
$rootScope.openMenu = openMenu;
344+
345+
if (cntr && cntr.click) {
346+
cntr.click();
347+
expect(openMenu).toHaveBeenCalled();
348+
}
349+
350+
});
351+
352+
it('should detect the menu position mode when md-menu is aligned at right', function() {
353+
var template =
354+
'<md-list-item>' +
355+
'<span>Menu should be aligned right</span>' +
356+
'<md-menu>' +
357+
'<md-button ng-click="openMenu()"></md-button>' +
358+
'</md-menu>' +
359+
'</md-list-item>';
360+
361+
var listItem = setup(template);
362+
363+
var mdMenu = listItem.find('md-menu');
364+
365+
expect(mdMenu.attr('md-position-mode')).toBe('right target');
366+
});
367+
368+
it('should detect the menu position mode when md-menu is aligned at left', function() {
369+
var template =
370+
'<md-list-item>' +
371+
'<md-menu>' +
372+
'<md-button ng-click="openMenu()"></md-button>' +
373+
'</md-menu>' +
374+
'<span>Menu should be aligned left</span>' +
375+
'</md-list-item>';
376+
377+
var listItem = setup(template);
378+
379+
var mdMenu = listItem.find('md-menu');
380+
381+
expect(mdMenu.attr('md-position-mode')).toBe('left target');
382+
});
383+
});
384+
330385
describe('with a clickable item', function() {
331386

332387
it('should wrap secondary icons in a md-button', function() {

src/components/menu/js/menuServiceProvider.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,8 +456,17 @@ function MenuProvider($$interimElementProvider) {
456456
position.left = willFitRight ? originNodeRect.right - originNode.style.left : originNodeRect.left - originNode.style.left - openMenuNodeRect.width;
457457
transformOrigin += willFitRight ? 'left' : 'right';
458458
break;
459+
case 'right':
460+
if (rtl) {
461+
position.left = originNodeRect.right - originNodeRect.width;
462+
transformOrigin += 'left';
463+
} else {
464+
position.left = originNodeRect.right - openMenuNodeRect.width;
465+
transformOrigin += 'right';
466+
}
467+
break;
459468
case 'left':
460-
if(rtl) {
469+
if (rtl) {
461470
position.left = originNodeRect.right - openMenuNodeRect.width;
462471
transformOrigin += 'right';
463472
} else {

0 commit comments

Comments
 (0)