Skip to content

Commit 152afdd

Browse files
Merge pull request #1452 from GuillaumeGomez/keyboard-events
Unify keyboard events on docs.rs results
2 parents 0e91690 + 516cc65 commit 152afdd

File tree

3 files changed

+74
-64
lines changed

3 files changed

+74
-64
lines changed

static/keyboard.js

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,101 @@
11
(function() {
2+
function focusSearchInput() {
3+
// On the index page, we have a "#search" input. If we are on this page, we want to go back
4+
// to this one and not the one in the header navbar.
5+
var searchInput = document.getElementById("search");
6+
if (searchInput) {
7+
searchInput.focus();
8+
} else {
9+
document.getElementById("nav-search").focus()
10+
}
11+
}
12+
13+
function focusFirstSearchResult() {
14+
var elem = document.querySelector(".recent-releases-container a.release");
15+
if (elem) {
16+
elem.focus();
17+
}
18+
}
19+
20+
function getWrappingLi(elem) {
21+
while (elem.tagName !== "LI") {
22+
elem = elem.parentElement;
23+
}
24+
return elem;
25+
}
26+
27+
function focusOnLi(li) {
28+
var elem = li.querySelector(".release");
29+
if (elem) {
30+
elem.focus();
31+
}
32+
}
33+
234
function getKey(ev) {
335
if ("key" in ev && typeof ev.key != "undefined") {
436
return ev.key;
537
}
638
return String.fromCharCode(ev.charCode || ev.keyCode);
739
}
840

9-
var active = null;
41+
function checkIfHasParent(elem, className) {
42+
while (elem && elem.tagName !== "BODY") {
43+
elem = elem.parentElement;
44+
if (elem.classList.constains(className)) {
45+
return true;
46+
}
47+
}
48+
return false;
49+
}
50+
1051
function handleKey(ev) {
11-
if (ev.ctrlKey || ev.altKey || ev.metaKey || document.activeElement.tagName === "INPUT") {
52+
if (ev.ctrlKey || ev.altKey || ev.metaKey) {
53+
return;
54+
}
55+
var tagName = document.activeElement.tagName;
56+
if (["BODY", "INPUT"].indexOf(tagName) === -1 &&
57+
tagName !== "A" &&
58+
!checkIfHasParent(document.activeElement, "recent-releases-container"))
59+
{
1260
return;
1361
}
1462

1563
if (ev.which === 40) { // Down arrow
1664
ev.preventDefault();
17-
if (active === null) {
18-
active = document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li")[0];
19-
} else if (active.nextElementSibling) {
20-
active.classList.remove("selected");
21-
active = active.nextElementSibling;
65+
if (tagName === "BODY") {
66+
focusFirstSearchResult();
67+
} else {
68+
var wrappingLi = getWrappingLi(document.activeElement);
69+
if (wrappingLi.nextElementSibling) {
70+
focusOnLi(wrappingLi.nextElementSibling);
71+
}
2272
}
23-
active.classList.add("selected");
2473
} else if (ev.which === 38) { // Up arrow
2574
ev.preventDefault();
26-
if (active === null) {
27-
active = document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li")[0];
28-
} else if (active.previousElementSibling) {
29-
active.classList.remove("selected");
30-
active = active.previousElementSibling;
31-
}
32-
active.classList.add("selected");
33-
active.focus();
34-
} else if (ev.which === 13) { // Return
35-
if (active !== null) {
36-
document.location.href = active.getElementsByTagName("a")[0].href;
75+
if (tagName === "A") {
76+
var wrappingLi = getWrappingLi(document.activeElement);
77+
if (wrappingLi.previousElementSibling)
78+
{
79+
focusOnLi(wrappingLi.previousElementSibling);
80+
} else {
81+
focusSearchInput();
82+
}
83+
} else if (tagName === "BODY") {
84+
focusFirstSearchResult();
3785
}
38-
} else {
86+
} else if (ev.which === 27) { // Escape
87+
document.activeElement.blur();
88+
} else if (tagName !== "INPUT") {
3989
switch (getKey(ev)) {
4090
case "s":
4191
case "S":
4292
ev.preventDefault();
43-
var searchInputNav = document.getElementsByClassName("search-input-nav");
44-
if (searchInputNav.length > 0) {
45-
searchInputNav[0].focus();
46-
}
93+
focusSearchInput();
4794
break;
4895
}
4996
}
5097
}
5198

5299
document.onkeypress = handleKey;
53100
document.onkeydown = handleKey;
54-
55-
var crates = Array.prototype.slice.call(document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li"));
56-
for (var i = 0; i < crates.length; ++i) {
57-
crates[i].addEventListener("mouseover", function (event) {
58-
this.classList.remove("selected");
59-
active = null;
60-
});
61-
crates[i].addEventListener("mouseout", function (event) {
62-
this.classList.remove("selected");
63-
active = null;
64-
});
65-
}
66101
})();

templates/core/home.html

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -72,30 +72,5 @@ <h1 class="brand">{{ "cubes" | fas(fw=true) }} Docs.rs</h1>
7272
{%- endblock body -%}
7373

7474
{%- block javascript -%}
75-
<script type="text/javascript" nonce="{{ csp_nonce }}">
76-
function getKey(ev) {
77-
if ("key" in ev && typeof ev.key != "undefined") {
78-
return ev.key;
79-
}
80-
81-
return String.fromCharCode(ev.charCode || ev.keyCode);
82-
}
83-
84-
function handleShortcut(ev) {
85-
if (ev.ctrlKey || ev.altKey || ev.metaKey || document.activeElement.tagName === "INPUT") {
86-
return;
87-
}
88-
89-
switch (getKey(ev)) {
90-
case "s":
91-
case "S":
92-
ev.preventDefault();
93-
document.getElementById("search").focus();
94-
break;
95-
}
96-
}
97-
98-
document.onkeypress = handleShortcut;
99-
document.onkeydown = handleShortcut;
100-
</script>
75+
<script nonce="{{ csp_nonce }}" type="text/javascript" src="/-/static/keyboard.js?{{ docsrs_version() | slugify }}"></script>
10176
{%- endblock javascript -%}

templates/style/base.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,11 @@ div.recent-releases-container {
293293
}
294294

295295
.release:hover,
296-
li.selected > .release {
296+
a.release:focus {
297297
background-color: var(--color-background-code);
298298
}
299299

300-
li:last-child .release {
300+
li:last-of-type .release {
301301
border-bottom: none;
302302
}
303303

0 commit comments

Comments
 (0)