Skip to content

Commit 0fd9446

Browse files
Replace topbar dropdown menus working with JS by <details>
1 parent 69edcb8 commit 0fd9446

File tree

4 files changed

+268
-451
lines changed

4 files changed

+268
-451
lines changed

static/menu.js

+21-209
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
const updateMenuPositionForSubMenu = (currentMenuSupplier) => {
2-
const currentMenu = currentMenuSupplier();
1+
const updateMenuPositionForSubMenu = () => {
2+
const currentMenu = document.querySelector(".nav-container details[open]");
33
const subMenu = currentMenu?.getElementsByClassName('pure-menu-children')?.[0];
44

55
subMenu?.style.setProperty('--menu-x', `${currentMenu.getBoundingClientRect().x}px`);
66
}
77

88
// Allow menus to be open and used by keyboard.
99
(function() {
10-
var currentMenu;
11-
var backdrop = document.createElement("div");
12-
backdrop.style = "display:none;position:fixed;width:100%;height:100%;z-index:1";
13-
document.documentElement.insertBefore(backdrop, document.querySelector("body"));
14-
15-
addEventListener('resize', () => updateMenuPositionForSubMenu(() => currentMenu));
10+
addEventListener('resize', updateMenuPositionForSubMenu);
1611

1712
function previous(allItems, item) {
1813
var i = 1;
@@ -37,216 +32,33 @@ const updateMenuPositionForSubMenu = (currentMenuSupplier) => {
3732
function last(allItems) {
3833
return allItems[allItems.length - 1];
3934
}
40-
function closeMenu() {
41-
if (this === backdrop) {
42-
document.documentElement.focus();
43-
} else if (currentMenu.querySelector(".pure-menu-link:focus")) {
44-
currentMenu.firstElementChild.focus();
35+
function closeMenu(ignore) {
36+
const menus = Array.prototype.slice.call(
37+
document.querySelectorAll(".nav-container details[open]"));
38+
for (const menu of menus) {
39+
if (menu !== ignore) {
40+
menu.open = false;
41+
}
4542
}
46-
currentMenu.className = currentMenu.className.replace("pure-menu-active", "");
4743
currentMenu = null;
48-
backdrop.style.display = "none";
49-
}
50-
backdrop.onclick = closeMenu;
51-
function openMenu(newMenu) {
52-
updateMenuPositionForSubMenu(() => newMenu);
53-
currentMenu = newMenu;
54-
newMenu.className += " pure-menu-active";
55-
backdrop.style.display = "block";
5644
}
5745
function menuOnClick(e) {
58-
if (this.getAttribute("href") != "#") {
59-
return;
60-
}
61-
if (this.parentNode === currentMenu) {
62-
closeMenu();
63-
this.blur();
46+
if (!this.open) {
47+
this.focus();
6448
} else {
65-
if (currentMenu) closeMenu();
66-
67-
openMenu(this.parentNode);
49+
closeMenu(this);
50+
updateMenuPositionForSubMenu();
6851
}
69-
e.preventDefault();
70-
e.stopPropagation();
7152
};
72-
function menuKeyDown(e) {
73-
if (currentMenu) {
74-
var children = currentMenu.querySelector(".pure-menu-children");
75-
var currentLink = children.querySelector(".pure-menu-link:focus");
76-
var currentItem;
77-
if (currentLink && currentLink.parentNode.className.indexOf("pure-menu-item") !== -1) {
78-
currentItem = currentLink.parentNode;
79-
}
80-
var allItems = [];
81-
if (children) {
82-
allItems = children.querySelectorAll(".pure-menu-item .pure-menu-link");
83-
}
84-
var switchTo = null;
85-
switch (e.key.toLowerCase()) {
86-
case "escape":
87-
case "esc":
88-
closeMenu();
89-
e.preventDefault();
90-
e.stopPropagation();
91-
return;
92-
case "arrowdown":
93-
case "down":
94-
if (currentLink) {
95-
// Arrow down when an item other than the last is focused: focus next item.
96-
// Arrow down when the last item is focused: jump to top.
97-
switchTo = (next(allItems, currentLink) || allItems[0]);
98-
} else {
99-
// Arrow down when a menu is open and nothing is focused: focus first item.
100-
switchTo = allItems[0];
101-
}
102-
break;
103-
case "arrowup":
104-
case "up":
105-
if (currentLink) {
106-
// Arrow up when an item other than the first is focused: focus previous item.
107-
// Arrow up when the first item is focused: jump to bottom.
108-
switchTo = (previous(allItems, currentLink) || last(allItems));
109-
} else {
110-
// Arrow up when a menu is open and nothing is focused: focus last item.
111-
switchTo = last(allItems);
112-
}
113-
break;
114-
case "tab":
115-
if (!currentLink) {
116-
// if the menu is open, we should focus trap into it
117-
// this is the behavior of the WAI example
118-
// it is not the same as GitHub, but GitHub allows you to tab yourself out
119-
// of the menu without closing it (which is horrible behavior)
120-
switchTo = e.shiftKey ? last(allItems) : allItems[0];
121-
} else if (e.shiftKey && currentLink === allItems[0]) {
122-
// if you tab your way out of the menu, close it
123-
// this is neither what GitHub nor the WAI example do,
124-
// but is a rationalization of GitHub's behavior: we don't want users who know how to
125-
// use tab and enter, but don't know that they can close menus with Escape,
126-
// to find themselves completely trapped in the menu
127-
closeMenu();
128-
e.preventDefault();
129-
e.stopPropagation();
130-
} else if (!e.shiftKey && currentLink === last(allItems)) {
131-
// same as above.
132-
// if you tab your way out of the menu, close it
133-
closeMenu();
134-
}
135-
break;
136-
case "enter":
137-
case "return":
138-
// enter and return have the default browser behavior,
139-
// but they also close the menu
140-
// this behavior is identical between both the WAI example, and GitHub's
141-
setTimeout(function() {
142-
closeMenu();
143-
}, 100);
144-
break;
145-
case "space":
146-
case " ":
147-
// space closes the menu, and activates the current link
148-
// this behavior is identical between both the WAI example, and GitHub's
149-
if (document.activeElement instanceof HTMLAnchorElement && !document.activeElement.hasAttribute("aria-haspopup")) {
150-
// It's supposed to copy the behaviour of the WAI Menu Bar
151-
// page, and of GitHub's menus. I've been using these two
152-
// sources to judge what is basically "industry standard"
153-
// behaviour for menu keyboard activity on the web.
154-
//
155-
// On GitHub, here's what I notice:
156-
//
157-
// 1 If you click open a menu, the menu button remains
158-
// focused. If, in this stage, I press space, the menu will
159-
// close.
160-
//
161-
// 2 If I use the arrow keys to focus a menu item, and then
162-
// press space, the menu item will be activated. For
163-
// example, clicking "+", then pressing down, then pressing
164-
// space will open the New Repository page.
165-
//
166-
// Behaviour 1 is why the
167-
// `!document.activeElement.hasAttribute("aria-haspopup")`
168-
// condition is there. It's to make sure the menu-link on
169-
// things like the About dropdown don't get activated.
170-
// Behaviour 2 is why this code is required at all; I want to
171-
// activate the currently highlighted menu item.
172-
document.activeElement.click();
173-
}
174-
setTimeout(function() {
175-
closeMenu();
176-
}, 100);
177-
e.preventDefault();
178-
e.stopPropagation();
179-
break;
180-
case "home":
181-
// home: focus first menu item.
182-
// This is the behavior of WAI, while GitHub scrolls,
183-
// but it's unlikely that a user will try to scroll the page while the menu is open,
184-
// so they won't do it on accident.
185-
switchTo = allItems[0];
186-
break;
187-
case "end":
188-
// end: focus last menu item.
189-
// This is the behavior of WAI, while GitHub scrolls,
190-
// but it's unlikely that a user will try to scroll the page while the menu is open,
191-
// so they won't do it on accident.
192-
switchTo = last(allItems);
193-
break;
194-
case "pageup":
195-
// page up: jump five items up, stopping at the top
196-
// the number 5 is used so that we go one page in the
197-
// inner-scrolled Dependencies and Versions fields
198-
switchTo = currentItem || allItems[0];
199-
for (var n = 0; n < 5; ++n) {
200-
if (switchTo.previousElementSibling && switchTo.previousElementSibling.className == 'pure-menu-item') {
201-
switchTo = switchTo.previousElementSibling;
202-
}
203-
}
204-
break;
205-
case "pagedown":
206-
// page down: jump five items down, stopping at the bottom
207-
// the number 5 is used so that we go one page in the
208-
// inner-scrolled Dependencies and Versions fields
209-
switchTo = currentItem || last(allItems);
210-
for (var n = 0; n < 5; ++n) {
211-
if (switchTo.nextElementSibling && switchTo.nextElementSibling.className == 'pure-menu-item') {
212-
switchTo = switchTo.nextElementSibling;
213-
}
214-
}
215-
break;
216-
}
217-
if (switchTo) {
218-
var switchToLink = switchTo.querySelector("a");
219-
if (switchToLink) {
220-
switchToLink.focus();
221-
} else {
222-
switchTo.focus();
223-
}
224-
e.preventDefault();
225-
e.stopPropagation();
226-
}
227-
} else if (e.target.parentNode.className && e.target.parentNode.className.indexOf("pure-menu-has-children") !== -1) {
228-
switch (e.key.toLowerCase()) {
229-
case "arrowdown":
230-
case "down":
231-
case "space":
232-
case " ":
233-
openMenu(e.target.parentNode);
234-
e.preventDefault();
235-
e.stopPropagation();
236-
break;
237-
}
53+
54+
const setEvents = (menus) => {
55+
menus = Array.prototype.slice.call(menus);
56+
for (const menu of menus) {
57+
menu.addEventListener("toggle", menuOnClick);
23858
}
23959
};
240-
var menus = Array.prototype.slice.call(document.querySelectorAll(".pure-menu-has-children"));
241-
var menusLength = menus.length;
242-
var menu;
243-
for (var i = 0; i < menusLength; ++i) {
244-
menu = menus[i];
245-
menu.firstElementChild.setAttribute("aria-haspopup", "menu");
246-
menu.firstElementChild.nextElementSibling.setAttribute("role", "menu");
247-
menu.firstElementChild.addEventListener("click", menuOnClick);
248-
}
249-
document.documentElement.addEventListener("keydown", menuKeyDown);
60+
setEvents(document.querySelectorAll(".nav-container details"));
61+
25062
document.documentElement.addEventListener("keydown", function(ev) {
25163
if (ev.key == "y" && ev.target.tagName != "INPUT") {
25264
let permalink = document.getElementById("permalink");

templates/header/topbar_end.html

+9-10
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,24 @@
44
{# The global alert, if there is one #}
55
{% include "header/global_alert.html" -%}
66

7-
<ul class="pure-menu-list">
8-
<li class="pure-menu-item pure-menu-has-children">
9-
<a href="#" class="pure-menu-link">
7+
<div class="pure-menu-list">
8+
<details class="pure-menu-item" aria-haspopup="menu">
9+
<summary>
1010
<span title="Releases">{{ "leaf" | fas }}</span>
1111
<span class="title">Releases</span>
12-
</a>
13-
12+
</summary>
1413
<ul class="pure-menu-children">
1514
{{ macros::menu_link(href="/releases", text="All Releases") }}
1615
{{ macros::menu_link(href="/releases/stars", text="Releases by Stars") }}
1716
{{ macros::menu_link(href="/releases/recent-failures", text="Recent Build Failures") }}
1817
{{ macros::menu_link(href="/releases/failures", text="Build Failures by Stars") }}
1918
{{ macros::menu_link(href="/releases/activity", text="Release Activity") }}
2019
</ul>
21-
</li>{#
20+
</details>{#
2221

2322
The Rust dropdown menu
24-
#}<li class="pure-menu-item pure-menu-has-children">
25-
<a href="#" class="pure-menu-link" aria-label="Rust">Rust</a>
23+
#}<details class="pure-menu-item" aria-haspopup="menu">
24+
<summary aria-label="Rust">Rust</summary>
2625
<ul class="pure-menu-children">
2726
{{ macros::menu_link(
2827
href="https://www.rust-lang.org/",
@@ -59,8 +58,8 @@
5958
target="_blank"
6059
) }}
6160
</ul>
62-
</li>
63-
</ul>
61+
</details>
62+
</div>
6463
{# The search bar #}
6564
<div id="search-input-nav">
6665
<label for="nav-search">

0 commit comments

Comments
 (0)