diff options
author | arv@google.com <arv@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-24 17:39:49 +0000 |
---|---|---|
committer | arv@google.com <arv@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-24 17:39:49 +0000 |
commit | b9eeba6713325a0fc67d6aeabc8475b5a2b37e0f (patch) | |
tree | 090ad08471e34369a644d7bcc5c0e4d4881bf30d /chrome | |
parent | f08f8a20f786ea60745fbb3fce5a15dc1fbc6ed0 (diff) | |
download | chromium_src-b9eeba6713325a0fc67d6aeabc8475b5a2b37e0f.zip chromium_src-b9eeba6713325a0fc67d6aeabc8475b5a2b37e0f.tar.gz chromium_src-b9eeba6713325a0fc67d6aeabc8475b5a2b37e0f.tar.bz2 |
Make the options menu keyboard navigatable. Hide the menu when the
button is clicked a second time.
BUG=14959, 15067
TEST=Tab to the options button. Try pressing the down arrow, up arrow
or enter. These should show the menu. Try pressing esc to hide the
menu. Try pressing enter to execute the item. Try opening the menu,
both using the mouse and keyboard and make sure that it is hidden as
expected.
Review URL: http://codereview.chromium.org/147071
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19141 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/resources/new_new_tab.css | 5 | ||||
-rw-r--r-- | chrome/browser/resources/new_new_tab.html | 2 | ||||
-rw-r--r-- | chrome/browser/resources/new_new_tab.js | 196 |
3 files changed, 167 insertions, 36 deletions
diff --git a/chrome/browser/resources/new_new_tab.css b/chrome/browser/resources/new_new_tab.css index 77e380b..80e71c6 100644 --- a/chrome/browser/resources/new_new_tab.css +++ b/chrome/browser/resources/new_new_tab.css @@ -633,7 +633,6 @@ html[dir='rtl'] #view-toolbar { cursor: default; pointer-events: all; min-width: 175px; - outline: 0; } html[dir='rtl'] #option-menu { @@ -647,9 +646,7 @@ html[dir='rtl'] #option-menu { text-overflow: ellipsis; } -#option-menu > div:hover, -#option-menu > div:focus { +#option-menu > [selected] { background-color: hsla(213, 66%, 57%, 1); color: white; - outline: none; } diff --git a/chrome/browser/resources/new_new_tab.html b/chrome/browser/resources/new_new_tab.html index 307efc9..c94a539 100644 --- a/chrome/browser/resources/new_new_tab.html +++ b/chrome/browser/resources/new_new_tab.html @@ -58,7 +58,7 @@ logEvent('log start'); ><input type="button" id="option-button"></div> -<div id="option-menu" class="window-menu" tabindex="0"> +<div id="option-menu" class="window-menu"> <div section="THUMB" show="true" jscontent="showthumbnails"></div> <div section="THUMB" show="false" jscontent="hidethumbnails"></div> <div section="LIST" show="true" jscontent="showlist"></div> diff --git a/chrome/browser/resources/new_new_tab.js b/chrome/browser/resources/new_new_tab.js index 9c04e09..2064e67 100644 --- a/chrome/browser/resources/new_new_tab.js +++ b/chrome/browser/resources/new_new_tab.js @@ -670,43 +670,177 @@ function showNotification(text, actionText, f) { }, 10000); } -// Options menu -// TODO(arv): Keyboard navigation of the menu items. - -function showMenu(button, menu) { - function hide() { - menu.style.display = ''; - menu.removeEventListener('blur', hide); - window.removeEventListener('blur', hide); - }; - menu.addEventListener('blur', hide); - window.addEventListener('blur', hide); - menu.style.display = 'block'; - menu.focus(); +/** + * This handles the option menu. + * @param {Element} button The button element. + * @param {Element} menu The menu element. + * @constructor + */ +function OptionMenu(button, menu) { + this.button = button; + this.menu = menu; + this.button.onmousedown = bind(this.handleMouseDown, this); + this.button.onkeydown = bind(this.handleKeyDown, this); + this.boundHideMenu_ = bind(this.hideMenu, this); + this.boundMaybeHide_ = bind(this.maybeHide_, this); } -$('option-button').addEventListener('click', function(e) { - showMenu(this, $('option-menu')); -}); +OptionMenu.prototype = { + showMenu: function() { + this.menu.style.display = 'block'; + this.button.focus(); -$('option-menu').addEventListener('click', function(e) { - var section = Section[e.target.getAttribute('section')]; - var show = e.target.getAttribute('show') == 'true'; - if (show) { - showSection(section); - } else { - hideSection(section); - } + // Listen to document and window events so that we hide the menu when the + // user clicks outside the menu or tabs away or the whole window is blurred. + document.addEventListener('focus', this.boundMaybeHide_, true); + document.addEventListener('mousedown', this.boundMaybeHide_, true); + window.addEventListener('blur', this.boundHideMenu_); + }, - // Hide menu now. - this.style.display = 'none'; + hideMenu: function() { + this.menu.style.display = 'none'; + this.setSelectedIndex(-1); - layoutLowerSections(); - mostVisited.updateDisplayMode(); - layoutMostVisited(); + document.removeEventListener('focus', this.boundMaybeHide_, true); + document.removeEventListener('mousedown', this.boundMaybeHide_, true); + window.removeEventListener('blur', this.boundHide_); + }, - saveShownSections(); -}); + isMenuShown: function() { + return this.menu.style.display == 'block'; + }, + + /** + * Callback for document mousedown and focus. It checks if the user tried to + * navigate to a different element on the page and if so hides the menu. + * @param {Event} e The mouse or focus event. + * @private + */ + maybeHide_: function(e) { + if (!this.menu.contains(e.target) && !this.button.contains(e.target)) { + this.hideMenu(); + } + }, + + handleMouseDown: function(e) { + if (this.isMenuShown()) { + this.hideMenu(); + } else { + this.showMenu(); + } + }, + + handleMouseOver: function(e) { + var el = e.target; + var index = Array.prototype.indexOf.call(this.menu.children, el); + console.log(el, index); + this.setSelectedIndex(index); + }, + + handleMouseOut: function(e) { + this.setSelectedIndex(-1); + }, + + handleMouseUp: function(e) { + var item = this.getSelectedItem(); + if (item) { + this.executeItem(item); + } + }, + + handleKeyDown: function(e) { + var item = this.getSelectedItem(); + + var self = this; + function selectNextVisible(m) { + var children = self.menu.children; + var len = children.length; + var i = self.selectedIndex_; + if (i == -1 && m == -1) { + // Edge case when we need to go the last item fisrt. + i = 0; + } + while (true) { + i = (i + m + len) % len; + item = children[i]; + if (item && item.style.display != 'none') { + break; + } + } + if (item) { + self.setSelectedIndex(i); + } + } + + switch (e.keyIdentifier) { + case 'Down': + if (!this.isMenuShown()) { + this.showMenu(); + } + selectNextVisible(1); + break; + case 'Up': + if (!this.isMenuShown()) { + this.showMenu(); + } + selectNextVisible(-1); + break; + case 'Esc': + case 'U+001B': // Maybe this is remote desktop playing a prank? + this.hideMenu(); + break; + case 'Enter': + if (this.isMenuShown()) { + if (item) { + this.executeItem(item); + } + } else { + this.showMenu(); + } + break; + } + }, + + selectedIndex_: -1, + setSelectedIndex: function(i) { + if (i != this.selectedIndex_) { + var items = this.menu.children; + var oldItem = this.items[this.selectedIndex_]; + if (oldItem) { + oldItem.removeAttribute('selected'); + } + var newItem = items[i]; + if (newItem) { + newItem.setAttribute('selected', 'selected'); + } + this.selectedIndex_ = i; + } + }, + + getSelectedItem: function() { + return this.menu.children[this.selectedIndex_] || null; + }, + + executeItem: function(item) { + var section = Section[item.getAttribute('section')]; + var show = item.getAttribute('show') == 'true'; + if (show) { + showSection(section); + } else { + hideSection(section); + } + + this.hideMenu(); + + layoutLowerSections(); + mostVisited.updateDisplayMode(); + layoutMostVisited(); + + saveShownSections(); + } +}; + +new OptionMenu($('option-button'), $('option-menu')); $('most-visited').addEventListener('click', function(e) { var target = e.target; |