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

Commit 4a4dde4

Browse files
oliversalzburgSplaktar
authored andcommitted
fix(menu-bar): z-index not restored on menu close
When an mdMenuBar contains a menu and that menu is opened, the menu bar will receives a CSS class that ultimately affects its z-index. When the menu is closed, the CSS class is supposed to be removed from the menu bar. When the menu has multiple levels, the menu hierarchy can not be correctly resolved upon menu closure. This results in the CSS class not being removed from the menu bar. I assumed that this failure is mostly due to the attempt to detect menu structure through DOM traversal, which will not work in this case as the menu hierarchy is not directly reflected in the DOM structure (menus are attached to the body instead). Fixes #11235
1 parent 7a16778 commit 4a4dde4

File tree

4 files changed

+117
-5
lines changed

4 files changed

+117
-5
lines changed

src/components/menu/js/menuController.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ function MenuController($mdMenu, $attrs, $element, $scope, $mdUtil, $timeout, $r
2929
triggerElement.setAttribute('aria-expanded', 'false');
3030

3131
this.isInMenuBar = opts.isInMenuBar;
32+
this.mdMenuBarCtrl = opts.mdMenuBarCtrl;
3233
this.nestedMenus = $mdUtil.nodesToArray(menuContainer[0].querySelectorAll('.md-nested-menu'));
3334

3435
menuContainer.on('$mdInterimElementRemove', function() {

src/components/menu/js/menuDirective.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ function MenuDirective($mdUtil) {
258258
function link(scope, element, attr, ctrls) {
259259
var mdMenuCtrl = ctrls[0];
260260
var isInMenuBar = !!ctrls[1];
261+
var mdMenuBarCtrl = ctrls[1];
261262
// Move everything into a md-menu-container and pass it to the controller
262263
var menuContainer = angular.element('<div class="_md md-open-menu-container md-whiteframe-z2"></div>');
263264
var menuContents = element.children()[1];
@@ -275,7 +276,6 @@ function MenuDirective($mdUtil) {
275276

276277
element.append(menuContainer);
277278
menuContainer[0].style.display = 'none';
278-
mdMenuCtrl.init(menuContainer, { isInMenuBar: isInMenuBar });
279-
279+
mdMenuCtrl.init(menuContainer, { isInMenuBar: isInMenuBar, mdMenuBarCtrl: mdMenuBarCtrl });
280280
}
281281
}

src/components/menuBar/js/menuBarController.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,18 @@ MenuBarController.prototype.init = function() {
5151
el[0].classList.remove('md-open');
5252
}
5353

54-
if ($element[0].contains(el[0])) {
54+
var ctrl = angular.element(el[0]).controller('mdMenu');
55+
if (ctrl.isInMenuBar && ctrl.mdMenuBarCtrl === self) {
5556
var parentMenu = el[0];
5657
while (parentMenu && rootMenus.indexOf(parentMenu) == -1) {
5758
parentMenu = $mdUtil.getClosest(parentMenu, 'MD-MENU', true);
5859
}
5960
if (parentMenu) {
6061
if (!opts.skipFocus) parentMenu.querySelector('button:not([disabled])').focus();
6162
self.currentlyOpenMenu = undefined;
62-
self.disableOpenOnHover();
63-
self.setKeyboardMode(true);
6463
}
64+
self.disableOpenOnHover();
65+
self.setKeyboardMode(true);
6566
}
6667
}));
6768

src/components/menuBar/menu-bar.spec.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,116 @@ describe('material.components.menuBar', function() {
6161
});
6262
}));
6363

64+
it('should close when clicking on a menu item', inject(function($compile, $rootScope, $timeout, $material) {
65+
var toolbar = $compile(
66+
'<md-toolbar class="md-menu-toolbar">' +
67+
'<md-menu-bar>' +
68+
'<md-menu>' +
69+
'<button ng-click="clicked=true">root</button>' +
70+
'<md-menu-content>' +
71+
'<md-menu-item>' +
72+
'<md-button ng-click="subclicked=true">child</md-button>' +
73+
'</md-menu-item>' +
74+
'</md-menu-content>' +
75+
'</md-menu>' +
76+
'</md-menu-bar>' +
77+
'</md-toolbar>'
78+
)($rootScope);
79+
80+
$rootScope.$digest();
81+
attachedMenuElements.push(toolbar); // ensure it gets cleaned up
82+
83+
var ctrl = toolbar.find('md-menu-bar').controller('mdMenuBar');
84+
var rootMenu = toolbar[0].querySelector('md-menu');
85+
86+
angular.element(document.body).append(toolbar);
87+
88+
var menuCtrl = angular.element(rootMenu).controller('mdMenu');
89+
90+
menuCtrl.open();
91+
waitForMenuOpen();
92+
93+
expect(toolbar).toHaveClass('md-has-open-menu');
94+
spyOn(menuCtrl, 'close').and.callThrough();
95+
96+
var subMenu = getOpenSubMenu();
97+
var childButton = subMenu[0].querySelector('md-button');
98+
childButton.dispatchEvent(new MouseEvent('click'));
99+
waitForMenuClose();
100+
101+
expect(toolbar).not.toHaveClass('md-has-open-menu');
102+
expect(ctrl.getOpenMenuIndex()).toBe(-1);
103+
expect(menuCtrl.close).toHaveBeenCalledWith(true, {
104+
closeAll: true
105+
});
106+
107+
function getOpenSubMenu() {
108+
var containers = document.body.querySelectorAll('.md-open-menu-container.md-active');
109+
var lastContainer = containers.item(containers.length - 1);
110+
111+
return angular.element(lastContainer.querySelector('md-menu-content'));
112+
}
113+
}));
114+
115+
it('should close when clicking on a nested menu item', inject(function($compile, $rootScope, $timeout, $material) {
116+
var toolbar = $compile(
117+
'<md-toolbar class="md-menu-toolbar">' +
118+
'<md-menu-bar>' +
119+
'<md-menu>' +
120+
'<button>root</button>' +
121+
'<md-menu-content>' +
122+
'<md-menu-item>' +
123+
'<md-menu>' +
124+
'<md-button ng-click="$mdMenu.open()">child</md-button>' +
125+
'<md-menu-content>' +
126+
'<md-menu-item>' +
127+
'<md-button>grandchild</md-button>' +
128+
'</md-menu-item>' +
129+
'</md-menu-content>' +
130+
'</md-menu>' +
131+
'</md-menu-item>' +
132+
'</md-menu-content>' +
133+
'</md-menu>' +
134+
'</md-menu-bar>' +
135+
'</md-toolbar>'
136+
)($rootScope);
137+
138+
$rootScope.$digest();
139+
attachedMenuElements.push(toolbar); // ensure it gets cleaned up
140+
141+
var ctrl = toolbar.find('md-menu-bar').controller('mdMenuBar');
142+
var rootMenu = toolbar[0].querySelector('md-menu');
143+
144+
angular.element(document.body).append(toolbar);
145+
146+
var menuCtrl = angular.element(rootMenu).controller('mdMenu');
147+
148+
menuCtrl.open();
149+
waitForMenuOpen();
150+
151+
expect(toolbar).toHaveClass('md-has-open-menu');
152+
153+
var subMenu = getLastOpenSubMenu();
154+
var childButton = subMenu[0].querySelector('md-button');
155+
childButton.dispatchEvent(new MouseEvent('click'));
156+
waitForMenuOpen();
157+
158+
var nestedMenu = getLastOpenSubMenu();
159+
var nestedButton = nestedMenu[0].querySelector('md-button');
160+
nestedButton.dispatchEvent(new MouseEvent('click'));
161+
waitForMenuClose();
162+
163+
expect(toolbar).not.toHaveClass('md-has-open-menu');
164+
expect(ctrl.getOpenMenuIndex()).toBe(-1);
165+
166+
function getLastOpenSubMenu() {
167+
var containers = document.body.querySelectorAll('.md-open-menu-container.md-active');
168+
var lastContainer = containers.item(containers.length - 1);
169+
170+
return angular.element(lastContainer.querySelector('md-menu-content'));
171+
}
172+
}));
173+
64174
describe('ARIA', function() {
65175

66176
it('sets role="menubar" on the menubar', function() {

0 commit comments

Comments
 (0)