Makes the network dropdown keyboard accessible (focus friendly).

Also:
 * fixes drop-down buttons layout.
 * overlay for catching outside the menu clicks is implemented (mimics standard select control behavior).

BUG=chromium-os:18826
TEST=manual

Review URL: http://codereview.chromium.org/7550070

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96147 0039d316-1c4b-4281-b951-d872f2087c98
parent 6cd8666d
...@@ -164,12 +164,7 @@ hr.bottom { ...@@ -164,12 +164,7 @@ hr.bottom {
.control-with-label { .control-with-label {
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
display: -webkit-box; display: -webkit-box;
} -webkit-box-align: start;
.label {
margin: 5px 5px 5px 0;
padding: 5px 5px 5px 0;
width: 170px;
} }
.menu-area { .menu-area {
...@@ -180,6 +175,12 @@ hr.bottom { ...@@ -180,6 +175,12 @@ hr.bottom {
width: 250px; width: 250px;
} }
.label {
margin: 5px 5px 5px 0;
padding: 5px 5px 5px 0;
width: 170px;
}
#connect { #connect {
box-sizing: border-box; box-sizing: border-box;
padding: 60px 0 0 145px; padding: 60px 0 0 145px;
...@@ -564,6 +565,7 @@ body.login-display #progress { ...@@ -564,6 +565,7 @@ body.login-display #progress {
text-indent: 4px; text-indent: 4px;
white-space: nowrap; white-space: nowrap;
width: 250px; width: 250px;
z-index: 10;
} }
.dropdown-title:hover { .dropdown-title:hover {
...@@ -590,7 +592,7 @@ body.login-display #progress { ...@@ -590,7 +592,7 @@ body.login-display #progress {
position: relative; position: relative;
top: 100%; top: 100%;
width: 248px; width: 248px;
z-index: 1; z-index: 10;
} }
.dropdown-item-container { .dropdown-item-container {
...@@ -599,7 +601,7 @@ body.login-display #progress { ...@@ -599,7 +601,7 @@ body.login-display #progress {
padding-left: 5px; padding-left: 5px;
} }
.dropdown-item-container:hover { .dropdown-item-container.hover {
background: #dce4fa; background: #dce4fa;
} }
...@@ -659,3 +661,12 @@ html[dir=rtl] .error-message { ...@@ -659,3 +661,12 @@ html[dir=rtl] .error-message {
position: absolute; position: absolute;
text-shadow: 0 1px 1px #fff; text-shadow: 0 1px 1px #fff;
} }
.dropdown-overlay {
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 5;
}
<div class="step hidden" id="connect"> <div class="step hidden" id="connect">
<div class="control-with-label"> <div class="control-with-label">
<label for="language-select" i18n-content="selectLanguage" <div i18n-content="selectLanguage" class="label menu-control"></div>
class="label menu-control"></label>
<div class="menu-area"> <div class="menu-area">
<select id="language-select" class="menu-control"></select> <select id="language-select" class="menu-control"></select>
</div> </div>
</div> </div>
<div class="control-with-label"> <div class="control-with-label">
<label for="keyboard-select" i18n-content="selectKeyboard" <div i18n-content="selectKeyboard" class="label menu-control"></div>
class="label menu-control"></label>
<div class="menu-area"> <div class="menu-area">
<select id="keyboard-select" class="menu-control"></select> <select id="keyboard-select" class="menu-control"></select>
</div> </div>
</div> </div>
<div class="control-with-label"> <div class="control-with-label">
<label for="networks-list" i18n-content="selectNetwork" <div i18n-content="selectNetwork" class="label menu-control"></div>
class="label menu-control"></label>
<div class="menu-area"> <div class="menu-area">
<div id="networks-list" class="menu-control" <div id="networks-list" class="menu-control" aria-haspopup="true"></div>
tabindex="0" role="button" aria-haspopup="true"></div>
</div> </div>
</div> </div>
</div> </div>
...@@ -7,6 +7,37 @@ ...@@ -7,6 +7,37 @@
*/ */
cr.define('oobe', function() { cr.define('oobe', function() {
/**
* Creates a new container for the drop down menu items.
* @constructor
* @extends{HTMLDivElement}
*/
var DropDownContainer = cr.ui.define('div');
DropDownContainer.prototype = {
__proto__: HTMLDivElement.prototype,
/** @inheritDoc */
decorate: function() {
this.classList.add('dropdown-container');
// Selected item in the menu list.
this.selectedItem = null;
// First item which could be selected.
this.firstItem = null;
},
/**
* Selects new item.
* @param {!Object} selectedItem Item to be selected.
*/
selectItem: function(selectedItem) {
if (this.selectedItem)
this.selectedItem.classList.remove('hover');
selectedItem.classList.add('hover');
this.selectedItem = selectedItem;
}
};
/** /**
* Creates a new DropDown div. * Creates a new DropDown div.
* @constructor * @constructor
...@@ -21,13 +52,12 @@ cr.define('oobe', function() { ...@@ -21,13 +52,12 @@ cr.define('oobe', function() {
/** @inheritDoc */ /** @inheritDoc */
decorate: function() { decorate: function() {
this.appendChild(this.createOverlay_());
this.appendChild(this.createTitle_()); this.appendChild(this.createTitle_());
this.appendChild(new DropDownContainer());
// Create menu items container.
var container = this.ownerDocument.createElement('div')
container.classList.add('dropdown-container');
this.appendChild(container);
this.isShown = false; this.isShown = false;
this.addEventListener('keydown', this.keyDownHandler_);
}, },
/** /**
...@@ -35,7 +65,7 @@ cr.define('oobe', function() { ...@@ -35,7 +65,7 @@ cr.define('oobe', function() {
* @type {bool} Whether menu element is shown. * @type {bool} Whether menu element is shown.
*/ */
get isShown() { get isShown() {
return !this.lastElementChild.hidden; return !this.container.hidden;
}, },
/** /**
...@@ -43,7 +73,24 @@ cr.define('oobe', function() { ...@@ -43,7 +73,24 @@ cr.define('oobe', function() {
* @param {bool} show New visibility state for dropdown menu. * @param {bool} show New visibility state for dropdown menu.
*/ */
set isShown(show) { set isShown(show) {
this.lastElementChild.hidden = !show; this.firstElementChild.hidden = !show;
this.container.hidden = !show;
if (show)
this.container.selectItem(this.container.firstItem);
},
/**
* Returns title button.
*/
get titleButton() {
return this.childNodes[1];
},
/**
* Returns container of the menu items.
*/
get container() {
return this.lastElementChild;
}, },
/** /**
...@@ -53,7 +100,7 @@ cr.define('oobe', function() { ...@@ -53,7 +100,7 @@ cr.define('oobe', function() {
*/ */
setTitle: function(title, icon) { setTitle: function(title, icon) {
// TODO(nkostylev): Icon support for dropdown title. // TODO(nkostylev): Icon support for dropdown title.
this.firstElementChild.textContent = title; this.titleButton.textContent = title;
}, },
/** /**
...@@ -61,19 +108,21 @@ cr.define('oobe', function() { ...@@ -61,19 +108,21 @@ cr.define('oobe', function() {
* @param {Array} items Dropdown items array. * @param {Array} items Dropdown items array.
*/ */
setItems: function(items) { setItems: function(items) {
var container = this.lastElementChild; this.container.innerHTML = '';
container.innerHTML = ''; this.container.firstItem = null;
this.container.selectedItem = null;
for (var i = 0; i < items.length; ++i) { for (var i = 0; i < items.length; ++i) {
var item = items[i]; var item = items[i];
if ('sub' in item) { if ('sub' in item) {
// Workaround for submenus, add items on top level. // Workaround for submenus, add items on top level.
// TODO(altimofeev): support submenus. // TODO(altimofeev): support submenus.
for (var j = 0; j < item.sub.length; ++j) for (var j = 0; j < item.sub.length; ++j)
this.createItem_(container, item.sub[j]); this.createItem_(this.container, item.sub[j]);
continue; continue;
} }
this.createItem_(container, item); this.createItem_(this.container, item);
} }
this.container.selectItem(this.container.firstItem);
}, },
/** /**
...@@ -120,13 +169,34 @@ cr.define('oobe', function() { ...@@ -120,13 +169,34 @@ cr.define('oobe', function() {
var item = this.lastElementChild; var item = this.lastElementChild;
if (item.iid < -1 || item.classList.contains('disabled-item')) if (item.iid < -1 || item.classList.contains('disabled-item'))
return; return;
item.controller.isShown = !item.controller.isShown; item.controller.isShown = false;
if (item.iid >= 0) if (item.iid >= 0)
chrome.send('networkItemChosen', [item.iid]); chrome.send('networkItemChosen', [item.iid]);
}); });
wrapperDiv.addEventListener('mouseover', function f(e) {
this.parentNode.selectItem(this);
});
itemElement = wrapperDiv; itemElement = wrapperDiv;
} }
container.appendChild(itemElement); container.appendChild(itemElement);
if (!container.firstItem && item.id >= 0) {
container.firstItem = itemElement;
}
},
/**
* Creates dropdown overlay element, which catches outside clicks.
* @type {HTMLElement}
* @private
*/
createOverlay_: function() {
var overlay = this.ownerDocument.createElement('div');
overlay.classList.add('dropdown-overlay');
overlay.addEventListener('click', function() {
this.parentNode.titleButton.focus();
this.parentNode.isShown = false;
});
return overlay;
}, },
/** /**
...@@ -139,10 +209,65 @@ cr.define('oobe', function() { ...@@ -139,10 +209,65 @@ cr.define('oobe', function() {
el.classList.add('dropdown-title'); el.classList.add('dropdown-title');
el.iid = -1; el.iid = -1;
el.controller = this; el.controller = this;
el.enterPressed = false;
el.addEventListener('click', function f(e) { el.addEventListener('click', function f(e) {
this.focus();
this.controller.isShown = !this.controller.isShown; this.controller.isShown = !this.controller.isShown;
if (this.enterPressed) {
this.enterPressed = false;
if (!this.controller.isShown) {
var item = this.controller.container.selectedItem.lastElementChild;
if (item.iid >= 0 && !item.classList.contains('disabled-item'))
chrome.send('networkItemChosen', [item.iid]);
}
}
}); });
return el; return el;
},
/**
* Handles keydown event from the keyboard.
* @private
* @param {!Event} e Keydown event.
*/
keyDownHandler_: function(e) {
if (!this.isShown)
return;
var selected = this.container.selectedItem;
switch(e.keyCode) {
case 38: { // Key up.
do {
selected = selected.previousSibling;
if (!selected)
selected = this.container.lastElementChild;
} while (selected.iid < 0);
this.container.selectItem(selected);
break;
}
case 40: { // Key down.
do {
selected = selected.nextSibling;
if (!selected)
selected = this.container.firstItem;
} while (selected.iid < 0);
this.container.selectItem(selected);
break;
}
case 27: { // Esc.
this.isShown = false;
break;
}
case 9: { // Tab.
this.isShown = false;
break;
}
case 13: { // Enter.
this.titleButton.enterPressed = true;
break;
}
};
} }
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment