Bug 955087 - Improve keyboard accessibility of the contact list, r=florian.

This commit is contained in:
aleth 2012-10-27 05:09:58 +02:00
Родитель f1dac03acb
Коммит 9293563b86
5 изменённых файлов: 132 добавлений и 7 удалений

Просмотреть файл

@ -82,8 +82,13 @@ tooltip[type="buddy"] {
-moz-binding: url("chrome://instantbird/content/buddytooltip.xml#tooltip");
}
#userIcon {
-moz-user-focus: normal;
}
#statusTypeIcon {
cursor: pointer;
-moz-user-focus: ignore;
}
#displayName,
@ -139,6 +144,10 @@ conv {
display: none;
}
#buddyListMsg[listedConvCount="0"] > #convlistbox {
-moz-user-focus: ignore;
}
/* Make the notification bar work with narrow windows. */
.notification-inner > hbox {
display: inline-block;

Просмотреть файл

@ -475,7 +475,8 @@ var buddyList = {
let elt = document.getElementById("statusMessage");
if (!elt.hasAttribute("editing")) {
elt.setAttribute("editing", "true");
elt.addEventListener("keypress", this.statusMessageKeyPress);
elt.removeAttribute("role");
elt.removeAttribute("aria-haspopup");
elt.addEventListener("blur", this.statusMessageBlur);
if (elt.hasAttribute("usingDefault")) {
if ("_statusTypeBeforeEditing" in this &&
@ -509,6 +510,23 @@ var buddyList = {
},
statusMessageKeyPress: function bl_statusMessageKeyPress(aEvent) {
let editing = document.getElementById("statusMessage").hasAttribute("editing");
if (!editing) {
switch (aEvent.keyCode) {
case aEvent.DOM_VK_DOWN:
buddyList.openStatusTypePopup();
aEvent.preventDefault();
return;
case aEvent.DOM_VK_TAB:
break;
default:
if (aEvent.charCode == aEvent.DOM_VK_SPACE)
buddyList.statusMessageClick();
return;
}
}
switch (aEvent.keyCode) {
case aEvent.DOM_VK_RETURN:
case aEvent.DOM_VK_ENTER:
@ -519,6 +537,14 @@ var buddyList = {
buddyList.finishEditStatusMessage(false);
break;
case aEvent.DOM_VK_TAB:
if (aEvent.shiftKey)
break;
// Ensure some item is selected when navigating by keyboard.
if (!this.selectFirstItem("convlistbox"))
this.selectFirstItem("buddylistbox");
break;
default:
buddyList.statusMessageRefreshTimer();
}
@ -556,7 +582,8 @@ var buddyList = {
elt.setAttribute("value", elt.getAttribute("usingDefault"));
TextboxSpellChecker.unregisterTextbox(elt);
elt.removeAttribute("editing");
elt.removeEventListener("keypress", this.statusMessageKeyPress, false);
elt.setAttribute("role", "button");
elt.setAttribute("aria-haspopup", "true");
elt.removeEventListener("blur", this.statusMessageBlur, false);
if (!elt.getAttribute("focused"))
return;
@ -565,6 +592,39 @@ var buddyList = {
elt.focus();
},
openStatusTypePopup: function() {
let button = document.getElementById("statusTypeIcon");
document.getElementById("setStatusTypeMenupopup").openPopup(button, "after_start");
},
onStatusTypePopupShown: function() {
// Without this, the #userIcon gains focus when the popup is opened
// from the #statusMessage whenever the #statusMessage has been edited
// at least once (thus changing the binding).
document.getElementById("statusMessage").focus();
},
userIconKeyPress: function bl_userIconKeyPress(aEvent) {
switch (aEvent.keyCode) {
case aEvent.DOM_VK_RETURN:
case aEvent.DOM_VK_ENTER:
this.userIconClick();
break;
case aEvent.DOM_VK_TAB:
if (!aEvent.shiftKey)
break;
// Ensure a contact is selected when navigating by keyboard.
this.selectFirstItem("buddylistbox");
break;
default:
if (aEvent.charCode == aEvent.DOM_VK_SPACE)
this.userIconClick();
break;
}
},
userIconClick: function bl_userIconClick() {
const nsIFilePicker = Components.interfaces.nsIFilePicker;
let fp = Components.classes["@mozilla.org/filepicker;1"]
@ -581,9 +641,9 @@ var buddyList = {
let elt = document.getElementById("displayName");
if (!elt.hasAttribute("editing")) {
elt.setAttribute("editing", "true");
elt.removeAttribute("role");
if (elt.hasAttribute("usingDefault"))
elt.removeAttribute("value");
elt.addEventListener("keypress", this.displayNameKeyPress);
elt.addEventListener("blur", this.displayNameBlur);
// force binding attachment by forcing layout
elt.getBoundingClientRect();
@ -607,6 +667,12 @@ var buddyList = {
},
displayNameKeyPress: function bl_displayNameKeyPress(aEvent) {
let editing = document.getElementById("displayName").hasAttribute("editing");
if (!editing) {
if (aEvent.charCode == aEvent.DOM_VK_SPACE)
buddyList.displayNameClick();
return;
}
switch (aEvent.keyCode) {
case aEvent.DOM_VK_RETURN:
case aEvent.DOM_VK_ENTER:
@ -632,7 +698,7 @@ var buddyList = {
elt.setAttribute("value", elt.getAttribute("usingDefault"));
elt.removeAttribute("editing");
elt.removeEventListener("keypress", this.displayNameKeyPress, false);
elt.setAttribute("role", "button");
elt.removeEventListener("blur", this.displayNameBlur, false);
if (!elt.getAttribute("focused"))
return;
@ -776,11 +842,28 @@ var buddyList = {
Services.prefs.removeObserver(showOfflineBuddiesPref, buddyList);
},
selectFirstItem: function (aListboxID) {
let listbox = document.getElementById(aListboxID);
if (!listbox.itemCount)
return false;
if (listbox.selectedIndex == -1)
listbox.selectedIndex = 0;
return true;
},
// Handle key pressing
keyPress: function bl_keyPress(aEvent) {
let target = aEvent.target;
while (target && target.localName != "richlistbox")
target = target.parentNode;
if (aEvent.keyCode == aEvent.DOM_VK_TAB) {
// Ensure some item is selected when navigating by keyboard.
if (target.id == "convlistbox" && !aEvent.shiftKey)
this.selectFirstItem("buddylistbox");
if (target.id == "buddylistbox" && aEvent.shiftKey)
this.selectFirstItem("convlistbox");
return;
}
var item = target.selectedItem;
if (!item || !item.parentNode) // empty list or item no longer in the list
return;

Просмотреть файл

@ -119,9 +119,13 @@
<toolbox id="mainToolbox">
<toolbar id="statusArea">
<stack id="statusImageStack">
<image id="userIcon" onclick="buddyList.userIconClick();"/>
<image id="userIcon" role="button"
aria-label="&userIcon.label;" tooltiptext="&userIcon.label;"
onclick="buddyList.userIconClick();"
onkeypress="buddyList.userIconKeyPress(event);"/>
<button type="menu" id="statusTypeIcon" status="available">
<menupopup id="setStatusTypeMenupopup"
onpopupshown="buddyList.onStatusTypePopupShown();"
oncommand="buddyList.editStatus(event);">
<menuitem id="statusTypeAvailable" label="&available;"
status="available" class="menuitem-iconic"/>
@ -135,10 +139,14 @@
</stack>
<stack id="displayNameAndstatusMessageStack" flex="1">
<vbox flex="1" pack="center">
<label id="displayName" onclick="buddyList.displayNameClick();"/>
<label id="displayName" role="button"
onclick="buddyList.displayNameClick();"
onkeypress="buddyList.displayNameKeyPress(event);"/>
</vbox>
<label id="statusMessage" crop="end" value=""
onclick="buddyList.statusMessageClick();"/>
aria-haspopup="true" role="button"
onclick="buddyList.statusMessageClick();"
onkeypress="buddyList.statusMessageKeyPress(event);"/>
</stack>
</toolbar>
</toolbox>

Просмотреть файл

@ -83,6 +83,7 @@
<!ENTITY copyEmailCmd.accesskey "E">
<!ENTITY engineManagerCmd.label "Manage Search Engines…">
<!ENTITY userIcon.label "Change your icon">
<!ENTITY contactsHeader.label "Contacts">
<!ENTITY convsHeader.label "Conversations on hold">

Просмотреть файл

@ -341,6 +341,7 @@ group[closed] .twisty {
background-repeat: no-repeat;
}
#userIcon:focus,
#userIcon:hover {
border-color: rgba(0,0,0,0.35);
background-color: rgba(0,0,0,0.35);
@ -431,3 +432,26 @@ group[closed] .twisty {
margin-bottom: 18px;
}
%endif
#statusMessage:-moz-focusring:not([editing]) {
%ifdef XP_MACOSX
margin: 30px -2px -1px -22px;
border: 2px solid rgba(0,0,0,0.15);
border-radius: 5px;
%else
margin: 31px -1px -1px -21px;
border: dotted 1px -moz-dialogtext;
%endif
padding-left: 20px; /* 16px for the statusTypeIcon and 4px from the margins on the two stacks */
}
#displayName:-moz-focusring:not([editing]) {
%ifdef XP_MACOSX
margin: -1px -1px 16px -2px;
border: 2px solid rgba(0,0,0,0.15);
border-radius: 5px;
%else
margin: -1px -1px 17px;
border: dotted 1px -moz-dialogtext;
%endif
}