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 // <include src="../../assert.js">
7 cr.exportPath('cr.ui');
10 * Enum for type of hide. Delayed is used when called by clicking on a
11 * checkable menu item.
19 cr.define('cr.ui', function() {
21 var Menu = cr.ui.Menu;
24 var HideType = cr.ui.HideType;
27 var positionPopupAroundElement = cr.ui.positionPopupAroundElement;
30 * Creates a new menu button element.
31 * @param {Object=} opt_propertyBag Optional properties.
33 * @extends {HTMLButtonElement}
34 * @implements {EventListener}
36 var MenuButton = cr.ui.define('button');
38 MenuButton.prototype = {
39 __proto__: HTMLButtonElement.prototype,
42 * Initializes the menu button.
44 decorate: function() {
45 this.addEventListener('mousedown', this);
46 this.addEventListener('keydown', this);
48 // Adding the 'custom-appearance' class prevents widgets.css from changing
49 // the appearance of this element.
50 this.classList.add('custom-appearance');
51 this.classList.add('menu-button'); // For styles in menu_button.css.
54 if ((menu = this.getAttribute('menu')))
57 // An event tracker for events we only connect to while the menu is
59 this.showingEvents_ = new EventTracker();
61 this.anchorType = cr.ui.AnchorType.BELOW;
62 this.invertLeftRight = false;
66 * The menu associated with the menu button.
73 if (typeof menu == 'string' && menu[0] == '#') {
74 menu = assert(this.ownerDocument.getElementById(menu.slice(1)));
75 cr.ui.decorate(menu, Menu);
81 this.setAttribute('menu', '#' + menu.id);
86 * Whether to show the menu on press of the Up or Down arrow keys.
88 respondToArrowKeys: true,
91 * Handles event callbacks.
92 * @param {Event} e The event object.
94 handleEvent: function(e) {
100 if (e.currentTarget == this.ownerDocument) {
101 if (e.target instanceof Node && !this.contains(e.target) &&
102 !this.menu.contains(e.target)) {
108 if (this.isMenuShown()) {
110 } else if (e.button == 0) { // Only show the menu when using left
112 this.showMenu(false);
114 // Prevent the button from stealing focus on mousedown.
119 // Hide the focus ring on mouse click.
120 this.classList.add('using-mouse');
123 this.handleKeyDown(e);
124 // If the menu is visible we let it handle all the keyboard events.
125 if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
126 if (this.menu.handleKeyDown(e)) {
132 // Show the focus ring on keypress.
133 this.classList.remove('using-mouse');
136 if (e.target instanceof Node && !this.contains(e.target) &&
137 !this.menu.contains(e.target)) {
139 // Show the focus ring on focus - if it's come from a mouse event,
140 // the focus ring will be hidden in the mousedown event handler,
141 // executed after this.
142 this.classList.remove('using-mouse');
146 var hideDelayed = e.target instanceof cr.ui.MenuItem &&
148 this.hideMenu(hideDelayed ? HideType.DELAYED : HideType.INSTANT);
151 if (!(e.target == this.menu || this.menu.contains(e.target)))
163 * @param {boolean} shouldSetFocus Whether to set focus on the
164 * selected menu item.
166 showMenu: function(shouldSetFocus) {
169 this.menu.updateCommands(this);
171 var event = document.createEvent('UIEvents');
172 event.initUIEvent('menushow', true, true, window, null);
174 if (!this.dispatchEvent(event))
177 this.menu.hidden = false;
179 this.setAttribute('menu-shown', '');
181 // When the menu is shown we steal all keyboard events.
182 var doc = this.ownerDocument;
183 var win = doc.defaultView;
184 this.showingEvents_.add(doc, 'keydown', this, true);
185 this.showingEvents_.add(doc, 'mousedown', this, true);
186 this.showingEvents_.add(doc, 'focus', this, true);
187 this.showingEvents_.add(doc, 'scroll', this, true);
188 this.showingEvents_.add(win, 'popstate', this);
189 this.showingEvents_.add(win, 'resize', this);
190 this.showingEvents_.add(this.menu, 'activate', this);
191 this.positionMenu_();
194 this.menu.focusSelectedItem();
198 * Hides the menu. If your menu can go out of scope, make sure to call this
200 * @param {cr.ui.HideType=} opt_hideType Type of hide.
201 * default: cr.ui.HideType.INSTANT.
203 hideMenu: function(opt_hideType) {
204 if (!this.isMenuShown())
207 this.removeAttribute('menu-shown');
208 if (opt_hideType == HideType.DELAYED)
209 this.menu.classList.add('hide-delayed');
211 this.menu.classList.remove('hide-delayed');
212 this.menu.hidden = true;
214 this.showingEvents_.removeAll();
219 * Whether the menu is shown.
221 isMenuShown: function() {
222 return this.hasAttribute('menu-shown');
226 * Positions the menu below the menu button. At this point we do not use any
227 * advanced positioning logic to ensure the menu fits in the viewport.
230 positionMenu_: function() {
231 positionPopupAroundElement(this, this.menu, this.anchorType,
232 this.invertLeftRight);
236 * Handles the keydown event for the menu button.
238 handleKeyDown: function(e) {
239 switch (e.keyIdentifier) {
242 if (!this.respondToArrowKeys)
245 case 'U+0020': // Space
246 if (!this.isMenuShown())
251 case 'U+001B': // Maybe this is remote desktop playing a prank?
252 case 'U+0009': // Tab
260 * Helper for styling a menu button with a drop-down arrow indicator.
261 * Creates a new 2D canvas context and draws a downward-facing arrow into it.
262 * @param {string} canvasName The name of the canvas. The canvas can be
263 * addressed from CSS using -webkit-canvas(<canvasName>).
264 * @param {number} width The width of the canvas and the arrow.
265 * @param {number} height The height of the canvas and the arrow.
266 * @param {string} colorSpec The CSS color to use when drawing the arrow.
268 function createDropDownArrowCanvas(canvasName, width, height, colorSpec) {
269 var ctx = document.getCSSCanvasContext('2d', canvasName, width, height);
270 ctx.fillStyle = ctx.strokeStyle = colorSpec;
273 ctx.lineTo(width, 0);
274 ctx.lineTo(height, height);
280 /** @const */ var ARROW_WIDTH = 6;
281 /** @const */ var ARROW_HEIGHT = 3;
284 * Create the images used to style drop-down-style MenuButtons.
285 * This should be called before creating any MenuButtons that will have the
286 * CSS class 'drop-down'. If no colors are specified, defaults will be used.
287 * @param {string=} opt_normalColor CSS color for the default button state.
288 * @param {string=} opt_hoverColor CSS color for the hover button state.
289 * @param {string=} opt_activeColor CSS color for the active button state.
291 MenuButton.createDropDownArrows = function(
292 opt_normalColor, opt_hoverColor, opt_activeColor) {
293 opt_normalColor = opt_normalColor || 'rgb(192, 195, 198)';
294 opt_hoverColor = opt_hoverColor || 'rgb(48, 57, 66)';
295 opt_activeColor = opt_activeColor || 'white';
297 createDropDownArrowCanvas(
298 'drop-down-arrow', ARROW_WIDTH, ARROW_HEIGHT, opt_normalColor);
299 createDropDownArrowCanvas(
300 'drop-down-arrow-hover', ARROW_WIDTH, ARROW_HEIGHT, opt_hoverColor);
301 createDropDownArrowCanvas(
302 'drop-down-arrow-active', ARROW_WIDTH, ARROW_HEIGHT, opt_activeColor);
307 MenuButton: MenuButton,