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> | |
David Tseng
2016/01/12 20:10:57
Invalid js doc? @type {!A...}
dmazzoni
2016/01/13 23:26:40
Done.
| |
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 } | |
David Tseng
2016/01/12 20:10:57
nit: remove braces
dmazzoni
2016/01/13 23:26:40
Done.
| |
143 }, | |
144 | |
145 /** | |
146 * Advanced the active menu item index by a given number. | |
147 * @param {number} delta The number to add to the active menu item index. | |
148 */ | |
149 advanceItemBy: function(delta) { | |
150 if (this.activeIndex_ >= 0) { | |
151 this.activeIndex_ += delta; | |
152 this.activeIndex_ = | |
153 (this.activeIndex_ + this.items_.length) % this.items_.length; | |
154 } else { | |
155 if (delta >= 0) | |
156 this.activeIndex_ = 0; | |
157 else | |
158 this.activeIndex_ = this.menus_.length - 1; | |
159 } | |
160 | |
161 this.items_[this.activeIndex_].element.focus(); | |
162 }, | |
163 | |
164 /** | |
165 * Get the callback for the active menu item. | |
166 * @return {Function} The callback. | |
167 */ | |
168 getCallbackForCurrentItem: function() { | |
169 if (this.activeIndex_ >= 0 && this.activeIndex_ < this.items_.length) { | |
170 return this.items_[this.activeIndex_].callback; | |
171 } | |
172 return null; | |
173 }, | |
174 | |
175 /** | |
176 * Get the callback for a menu item given its DOM element. | |
177 * @param {Element} element The DOM element. | |
178 * @return {Function} The callback. | |
179 */ | |
180 getCallbackForElement: function(element) { | |
181 for (var i = 0; i < this.items_.length; i++) { | |
David Tseng
2016/01/12 20:10:57
Can use: this.items_.find
dmazzoni
2016/01/13 23:26:40
Not any cleaner in this case because it returns th
| |
182 if (element == this.items_[i].element) | |
183 return this.items_[i].callback; | |
184 } | |
185 return null; | |
186 } | |
187 }; | |
OLD | NEW |