1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 cr.define('cr.ui', function() { 6 7 const MenuItem = cr.ui.MenuItem; 8 9 /** 10 * Creates a new menu element. 11 * @param {Object=} opt_propertyBag Optional properties. 12 * @constructor 13 * @extends {HTMLMenuElement} 14 */ 15 var Menu = cr.ui.define('menu'); 16 17 Menu.prototype = { 18 __proto__: HTMLMenuElement.prototype, 19 20 selectedIndex_: -1, 21 22 /** 23 * Initializes the menu element. 24 */ 25 decorate: function() { 26 this.addEventListener('mouseover', this.handleMouseOver_); 27 this.addEventListener('mouseout', this.handleMouseOut_); 28 29 // Decorate the children as menu items. 30 var children = this.children; 31 for (var i = 0, child; child = children[i]; i++) { 32 cr.ui.decorate(child, MenuItem); 33 } 34 }, 35 36 /** 37 * Walks up the ancestors until a menu item belonging to this menu is found. 38 * @param {Element} el 39 * @return {cr.ui.MenuItem} The found menu item or null. 40 * @private 41 */ 42 findMenuItem_: function(el) { 43 while (el && el.parentNode != this) { 44 el = el.parentNode; 45 } 46 return el; 47 }, 48 49 /** 50 * Handles mouseover events and selects the hovered item. 51 * @param {Event} e The mouseover event. 52 * @private 53 */ 54 handleMouseOver_: function(e) { 55 var overItem = this.findMenuItem_(e.target); 56 this.selectedItem = overItem; 57 }, 58 59 /** 60 * Handles mouseout events and deselects any selected item. 61 * @param {Event} e The mouseout event. 62 * @private 63 */ 64 handleMouseOut_: function(e) { 65 this.selectedItem = null; 66 }, 67 68 /** 69 * The selected menu item or null if none. 70 * @type {cr.ui.MenuItem} 71 */ 72 get selectedItem() { 73 return this.children[this.selectedIndex]; 74 }, 75 set selectedItem(item) { 76 var index = Array.prototype.indexOf.call(this.children, item); 77 this.selectedIndex = index; 78 }, 79 80 /** 81 * This is the function that handles keyboard navigation. This is usually 82 * called by the element responsible for managing the menu. 83 * @param {Event} e The keydown event object. 84 * @return {boolean} Whether the event was handled be the menu. 85 */ 86 handleKeyDown: function(e) { 87 var item = this.selectedItem; 88 89 var self = this; 90 function selectNextVisible(m) { 91 var children = self.children; 92 var len = children.length; 93 var i = self.selectedIndex; 94 if (i == -1 && m == -1) { 95 // Edge case when we need to go the last item fisrt. 96 i = 0; 97 } 98 while (true) { 99 i = (i + m + len) % len; 100 item = children[i]; 101 if (item && !item.isSeparator() && !item.hidden) 102 break; 103 } 104 if (item) 105 self.selectedIndex = i; 106 } 107 108 switch (e.keyIdentifier) { 109 case 'Down': 110 selectNextVisible(1); 111 return true; 112 case 'Up': 113 selectNextVisible(-1); 114 return true; 115 case 'Enter': 116 case 'U+0020': // Space 117 if (item) { 118 if (cr.dispatchSimpleEvent(item, 'activate', true, true)) { 119 if (item.command) 120 item.command.execute(); 121 } 122 } 123 return true; 124 } 125 126 return false; 127 } 128 }; 129 130 function selectedIndexChanged(selectedIndex, oldSelectedIndex) { 131 var oldSelectedItem = this.children[oldSelectedIndex]; 132 if (oldSelectedItem) 133 oldSelectedItem.selected = false; 134 var item = this.selectedItem; 135 if (item) 136 item.selected = true; 137 } 138 /** 139 * The selected menu item. 140 * @type {number} 141 */ 142 cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS, 143 selectedIndexChanged); 144 145 // Export 146 return { 147 Menu: Menu 148 }; 149 }); 150