summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorarv@google.com <arv@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-24 17:39:49 +0000
committerarv@google.com <arv@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-24 17:39:49 +0000
commitb9eeba6713325a0fc67d6aeabc8475b5a2b37e0f (patch)
tree090ad08471e34369a644d7bcc5c0e4d4881bf30d /chrome
parentf08f8a20f786ea60745fbb3fce5a15dc1fbc6ed0 (diff)
downloadchromium_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.css5
-rw-r--r--chrome/browser/resources/new_new_tab.html2
-rw-r--r--chrome/browser/resources/new_new_tab.js196
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;