OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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 /** |
| 6 * @fileoverview A drop-down menu in the ChromeVox panel. |
| 7 */ |
| 8 |
| 9 goog.provide('PanelMenu'); |
| 10 |
| 11 goog.require('PanelMenuItem'); |
| 12 |
| 13 /** |
| 14 * @param {string} menuTitle The title of the menu. |
| 15 * @constructor |
| 16 */ |
| 17 PanelMenu = function(menuTitle) { |
| 18 // The item in the menu bar containing the menu's title. |
| 19 this.menuBarItemElement = document.createElement('div'); |
| 20 this.menuBarItemElement.className = 'menu-bar-item'; |
| 21 this.menuBarItemElement.setAttribute('role', 'menu'); |
| 22 this.menuBarItemElement.textContent = menuTitle; |
| 23 |
| 24 // The container for the menu. This part is fixed and scrolls its |
| 25 // contents if necessary. |
| 26 this.menuContainerElement = document.createElement('div'); |
| 27 this.menuContainerElement.className = 'menu-container'; |
| 28 this.menuContainerElement.style.visibility = 'hidden'; |
| 29 |
| 30 // The menu itself. It contains all of the items, and it scrolls within |
| 31 // its container. |
| 32 this.menuElement = document.createElement('table'); |
| 33 this.menuElement.className = 'menu'; |
| 34 this.menuElement.setAttribute('role', 'menu'); |
| 35 this.menuElement.setAttribute('aria-label', menuTitle); |
| 36 this.menuContainerElement.appendChild(this.menuElement); |
| 37 |
| 38 /** |
| 39 * The items in the menu. |
| 40 * @type {Array<PanelMenuItem>} |
| 41 * @private |
| 42 */ |
| 43 this.items_ = []; |
| 44 |
| 45 /** |
| 46 * The return value from window.setTimeout for a function to update the |
| 47 * scroll bars after an item has been added to a menu. Used so that we |
| 48 * don't re-layout too many times. |
| 49 * @type {?number} |
| 50 * @private |
| 51 */ |
| 52 this.updateScrollbarsTimeout_ = null; |
| 53 |
| 54 /** |
| 55 * The current active menu item index, or -1 if none. |
| 56 * @type {number} |
| 57 * @private |
| 58 */ |
| 59 this.activeIndex_ = -1; |
| 60 }; |
| 61 |
| 62 PanelMenu.prototype = { |
| 63 /** |
| 64 * @param {string} menuItemTitle The title of the menu item. |
| 65 * @param {string} menuItemShortcut The keystrokes to select this item. |
| 66 * @param {Function} callback The function to call if this item is selected. |
| 67 * @return {!PanelMenuItem} The menu item just created. |
| 68 */ |
| 69 addMenuItem: function(menuItemTitle, menuItemShortcut, callback) { |
| 70 var menuItem = new PanelMenuItem(menuItemTitle, menuItemShortcut, callback); |
| 71 this.items_.push(menuItem); |
| 72 this.menuElement.appendChild(menuItem.element); |
| 73 |
| 74 // Sync the active index with focus. |
| 75 menuItem.element.addEventListener('focus', (function(index, event) { |
| 76 this.activeIndex_ = index; |
| 77 }).bind(this, this.items_.length - 1), false); |
| 78 |
| 79 // Update the container height, adding a scroll bar if necessary - but |
| 80 // to avoid excessive layout, schedule this once per batch of adding |
| 81 // menu items rather than after each add. |
| 82 if (!this.updateScrollbarsTimeout_) { |
| 83 this.updateScrollbarsTimeout_ = window.setTimeout((function() { |
| 84 var menuBounds = this.menuElement.getBoundingClientRect(); |
| 85 var maxHeight = window.innerHeight - menuBounds.top; |
| 86 this.menuContainerElement.style.maxHeight = maxHeight + 'px'; |
| 87 this.updateScrollbarsTimeout_ = null; |
| 88 }).bind(this), 0); |
| 89 } |
| 90 |
| 91 return menuItem; |
| 92 }, |
| 93 |
| 94 /** |
| 95 * Activate this menu, which means showing it and positioning it on the |
| 96 * screen underneath its title in the menu bar. |
| 97 */ |
| 98 activate: function() { |
| 99 this.menuContainerElement.style.visibility = 'visible'; |
| 100 this.menuContainerElement.style.opacity = 1; |
| 101 this.menuBarItemElement.classList.add('active'); |
| 102 var barBounds = |
| 103 this.menuBarItemElement.parentElement.getBoundingClientRect(); |
| 104 var titleBounds = this.menuBarItemElement.getBoundingClientRect(); |
| 105 var menuBounds = this.menuElement.getBoundingClientRect(); |
| 106 |
| 107 this.menuElement.style.minWidth = titleBounds.width + 'px'; |
| 108 this.menuContainerElement.style.minWidth = titleBounds.width + 'px'; |
| 109 if (titleBounds.left + menuBounds.width < barBounds.width) { |
| 110 this.menuContainerElement.style.left = titleBounds.left + 'px'; |
| 111 } else { |
| 112 this.menuContainerElement.style.left = |
| 113 (titleBounds.right - menuBounds.width) + 'px'; |
| 114 } |
| 115 |
| 116 // Make the first item active. |
| 117 this.activateItem(0); |
| 118 }, |
| 119 |
| 120 /** |
| 121 * Hide this menu. Make it invisible first to minimize spurious |
| 122 * accessibility events before the next menu activates. |
| 123 */ |
| 124 deactivate: function() { |
| 125 this.menuContainerElement.style.opacity = 0.001; |
| 126 this.menuBarItemElement.classList.remove('active'); |
| 127 this.activeIndex_ = -1; |
| 128 |
| 129 window.setTimeout((function() { |
| 130 this.menuContainerElement.style.visibility = 'hidden'; |
| 131 }).bind(this), 0); |
| 132 }, |
| 133 |
| 134 /** |
| 135 * Make a specific menu item index active. |
| 136 * @param {number} itemIndex The index of the menu item. |
| 137 */ |
| 138 activateItem: function(itemIndex) { |
| 139 this.activeIndex_ = itemIndex; |
| 140 if (this.activeIndex_ >= 0 && this.activeIndex_ < this.items_.length) |
| 141 this.items_[this.activeIndex_].element.focus(); |
| 142 }, |
| 143 |
| 144 /** |
| 145 * Advanced the active menu item index by a given number. |
| 146 * @param {number} delta The number to add to the active menu item index. |
| 147 */ |
| 148 advanceItemBy: function(delta) { |
| 149 if (this.activeIndex_ >= 0) { |
| 150 this.activeIndex_ += delta; |
| 151 this.activeIndex_ = |
| 152 (this.activeIndex_ + this.items_.length) % this.items_.length; |
| 153 } else { |
| 154 if (delta >= 0) |
| 155 this.activeIndex_ = 0; |
| 156 else |
| 157 this.activeIndex_ = this.menus_.length - 1; |
| 158 } |
| 159 |
| 160 this.items_[this.activeIndex_].element.focus(); |
| 161 }, |
| 162 |
| 163 /** |
| 164 * Get the callback for the active menu item. |
| 165 * @return {Function} The callback. |
| 166 */ |
| 167 getCallbackForCurrentItem: function() { |
| 168 if (this.activeIndex_ >= 0 && this.activeIndex_ < this.items_.length) { |
| 169 return this.items_[this.activeIndex_].callback; |
| 170 } |
| 171 return null; |
| 172 }, |
| 173 |
| 174 /** |
| 175 * Get the callback for a menu item given its DOM element. |
| 176 * @param {Element} element The DOM element. |
| 177 * @return {Function} The callback. |
| 178 */ |
| 179 getCallbackForElement: function(element) { |
| 180 for (var i = 0; i < this.items_.length; i++) { |
| 181 if (element == this.items_[i].element) |
| 182 return this.items_[i].callback; |
| 183 } |
| 184 return null; |
| 185 } |
| 186 }; |
OLD | NEW |