1 // Copyright (c) 2012 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.
5 cr.define('cr.ui', function() {
10 var positionPopupAroundElement = cr.ui.positionPopupAroundElement;
13 * Enum for type of hide. Delayed is used when called by clicking on a
14 * checkable menu item.
23 * Creates a new menu button element.
24 * @param {Object=} opt_propertyBag Optional properties.
26 * @extends {HTMLButtonElement}
28 var MenuButton = cr.ui.define('button');
30 MenuButton.prototype = {
31 __proto__: HTMLButtonElement.prototype,
34 * Initializes the menu button.
36 decorate: function() {
37 this.addEventListener('mousedown', this);
38 this.addEventListener('keydown', this);
40 // Adding the 'custom-appearance' class prevents widgets.css from changing
41 // the appearance of this element.
42 this.classList.add('custom-appearance');
43 this.classList.add('menu-button'); // For styles in menu_button.css.
46 if ((menu = this.getAttribute('menu')))
49 // An event tracker for events we only connect to while the menu is
51 this.showingEvents_ = new EventTracker();
53 this.anchorType = cr.ui.AnchorType.BELOW;
54 this.invertLeftRight = false;
58 * The menu associated with the menu button.
65 if (typeof menu == 'string' && menu[0] == '#') {
66 menu = this.ownerDocument.getElementById(menu.slice(1));
67 cr.ui.decorate(menu, Menu);
73 this.setAttribute('menu', '#' + menu.id);
78 * Handles event callbacks.
79 * @param {Event} e The event object.
81 handleEvent: function(e) {
87 if (e.currentTarget == this.ownerDocument) {
88 if (!this.contains(e.target) && !this.menu.contains(e.target))
93 if (this.isMenuShown()) {
95 } else if (e.button == 0) { // Only show the menu when using left
99 // Prevent the button from stealing focus on mousedown.
104 // Hide the focus ring on mouse click.
105 this.classList.add('using-mouse');
108 this.handleKeyDown(e);
109 // If the menu is visible we let it handle all the keyboard events.
110 if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
111 if (this.menu.handleKeyDown(e)) {
117 // Show the focus ring on keypress.
118 this.classList.remove('using-mouse');
121 if (!this.contains(e.target) && !this.menu.contains(e.target)) {
123 // Show the focus ring on focus - if it's come from a mouse event,
124 // the focus ring will be hidden in the mousedown event handler,
125 // executed after this.
126 this.classList.remove('using-mouse');
130 var hideDelayed = e.target instanceof cr.ui.MenuItem &&
132 this.hideMenu(hideDelayed ? HideType.DELAYED : HideType.INSTANT);
135 if (!(e.target == this.menu || this.menu.contains(e.target)))
147 * @param {boolean} shouldSetFocus Whether to set focus on the
148 * selected menu item.
150 showMenu: function(shouldSetFocus) {
153 this.menu.updateCommands(this);
155 var event = document.createEvent('UIEvents');
156 event.initUIEvent('menushow', true, true, window, null);
158 if (!this.dispatchEvent(event))
161 this.menu.hidden = false;
163 this.setAttribute('menu-shown', '');
165 // When the menu is shown we steal all keyboard events.
166 var doc = this.ownerDocument;
167 var win = doc.defaultView;
168 this.showingEvents_.add(doc, 'keydown', this, true);
169 this.showingEvents_.add(doc, 'mousedown', this, true);
170 this.showingEvents_.add(doc, 'focus', this, true);
171 this.showingEvents_.add(doc, 'scroll', this, true);
172 this.showingEvents_.add(win, 'popstate', this);
173 this.showingEvents_.add(win, 'resize', this);
174 this.showingEvents_.add(this.menu, 'activate', this);
175 this.positionMenu_();
178 this.menu.focusSelectedItem();
182 * Hides the menu. If your menu can go out of scope, make sure to call this
184 * @param {HideType=} opt_hideType Type of hide.
185 * default: HideType.INSTANT.
187 hideMenu: function(opt_hideType) {
188 if (!this.isMenuShown())
191 this.removeAttribute('menu-shown');
192 if (opt_hideType == HideType.DELAYED)
193 this.menu.classList.add('hide-delayed');
195 this.menu.classList.remove('hide-delayed');
196 this.menu.hidden = true;
198 this.showingEvents_.removeAll();
203 * Whether the menu is shown.
205 isMenuShown: function() {
206 return this.hasAttribute('menu-shown');
210 * Positions the menu below the menu button. At this point we do not use any
211 * advanced positioning logic to ensure the menu fits in the viewport.
214 positionMenu_: function() {
215 positionPopupAroundElement(this, this.menu, this.anchorType,
216 this.invertLeftRight);
220 * Handles the keydown event for the menu button.
222 handleKeyDown: function(e) {
223 switch (e.keyIdentifier) {
227 case 'U+0020': // Space
228 if (!this.isMenuShown())
233 case 'U+001B': // Maybe this is remote desktop playing a prank?
234 case 'U+0009': // Tab
242 * Helper for styling a menu button with a drop-down arrow indicator.
243 * Creates a new 2D canvas context and draws a downward-facing arrow into it.
244 * @param {string} canvasName The name of the canvas. The canvas can be
245 * addressed from CSS using -webkit-canvas(<canvasName>).
246 * @param {number} width The width of the canvas and the arrow.
247 * @param {number} height The height of the canvas and the arrow.
248 * @param {string} colorSpec The CSS color to use when drawing the arrow.
250 function createDropDownArrowCanvas(canvasName, width, height, colorSpec) {
251 var ctx = document.getCSSCanvasContext('2d', canvasName, width, height);
252 ctx.fillStyle = ctx.strokeStyle = colorSpec;
255 ctx.lineTo(width, 0);
256 ctx.lineTo(height, height);
262 /** @const */ var ARROW_WIDTH = 6;
263 /** @const */ var ARROW_HEIGHT = 3;
266 * Create the images used to style drop-down-style MenuButtons.
267 * This should be called before creating any MenuButtons that will have the
268 * CSS class 'drop-down'. If no colors are specified, defaults will be used.
269 * @param {=string} normalColor CSS color for the default button state.
270 * @param {=string} hoverColor CSS color for the hover button state.
271 * @param {=string} activeColor CSS color for the active button state.
273 MenuButton.createDropDownArrows = function(
274 normalColor, hoverColor, activeColor) {
275 normalColor = normalColor || 'rgb(192, 195, 198)';
276 hoverColor = hoverColor || 'rgb(48, 57, 66)';
277 activeColor = activeColor || 'white';
279 createDropDownArrowCanvas(
280 'drop-down-arrow', ARROW_WIDTH, ARROW_HEIGHT, normalColor);
281 createDropDownArrowCanvas(
282 'drop-down-arrow-hover', ARROW_WIDTH, ARROW_HEIGHT, hoverColor);
283 createDropDownArrowCanvas(
284 'drop-down-arrow-active', ARROW_WIDTH, ARROW_HEIGHT, activeColor);
289 MenuButton: MenuButton,