mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-20 23:51:29 -07:00
js: Add keyboard navigation for menus
Once the input focus is in the menu system (for instance by Shift+Tab from the puzzle), you can move left and right through the menu bar and up and down within each menu. Enter selects a menu item. The current menu item is tracked by giving it the input focus.
This commit is contained in:
@ -111,6 +111,7 @@ mergeInto(LibraryManager.library, {
|
||||
var tick = document.createElement("span");
|
||||
tick.className = "tick";
|
||||
label.appendChild(tick);
|
||||
label.tabIndex = 0;
|
||||
label.appendChild(document.createTextNode(" " + name));
|
||||
item.appendChild(label);
|
||||
var submenu = document.createElement("ul");
|
||||
|
89
emccpre.js
89
emccpre.js
@ -416,6 +416,95 @@ function initPuzzle() {
|
||||
gametypesubmenus.push(gametypelist);
|
||||
menuform = document.getElementById("gamemenu");
|
||||
|
||||
// Find the next or previous item in a menu, or null if there
|
||||
// isn't one. Skip list items that don't have a child (i.e.
|
||||
// separators) or whose child is disabled.
|
||||
function isuseful(item) {
|
||||
return item.querySelector(":scope > :not(:disabled)");
|
||||
}
|
||||
function nextmenuitem(item) {
|
||||
do item = item.nextElementSibling;
|
||||
while (item !== null && !isuseful(item));
|
||||
return item;
|
||||
}
|
||||
function prevmenuitem(item) {
|
||||
do item = item.previousElementSibling;
|
||||
while (item !== null && !isuseful(item));
|
||||
return item;
|
||||
}
|
||||
function firstmenuitem(menu) {
|
||||
var item = menu && menu.firstElementChild;
|
||||
while (item !== null && !isuseful(item))
|
||||
item = item.nextElementSibling;
|
||||
return item;
|
||||
}
|
||||
function lastmenuitem(menu) {
|
||||
var item = menu && menu.lastElementChild;
|
||||
while (item !== null && !isuseful(item))
|
||||
item = item.previousElementSibling;
|
||||
return item;
|
||||
}
|
||||
// Keyboard handlers for the menus.
|
||||
function menukey(event) {
|
||||
var thisitem = event.target.closest("li");
|
||||
var thismenu = thisitem.closest("ul");
|
||||
var targetitem = null;
|
||||
var parentitem;
|
||||
var parentitem_up = null;
|
||||
var parentitem_sideways = null;
|
||||
var submenu;
|
||||
function ishorizontal(menu) {
|
||||
// Which direction does this menu go in?
|
||||
var cs = window.getComputedStyle(menu);
|
||||
return cs.display == "flex" && cs.flexDirection == "row";
|
||||
}
|
||||
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Enter"]
|
||||
.includes(event.key))
|
||||
return;
|
||||
if (ishorizontal(thismenu)) {
|
||||
// Top-level menu bar.
|
||||
if (event.key == "ArrowLeft")
|
||||
targetitem = prevmenuitem(thisitem) || lastmenuitem(thismenu);
|
||||
else if (event.key == "ArrowRight")
|
||||
targetitem = nextmenuitem(thisitem) || firstmenuitem(thismenu);
|
||||
else if (event.key == "ArrowUp")
|
||||
targetitem = lastmenuitem(thisitem.querySelector("ul"));
|
||||
else if (event.key == "ArrowDown" || event.key == "Enter")
|
||||
targetitem = firstmenuitem(thisitem.querySelector("ul"));
|
||||
} else {
|
||||
// Ordinary vertical menu.
|
||||
parentitem = thismenu.closest("li");
|
||||
if (parentitem) {
|
||||
if (ishorizontal(parentitem.closest("ul")))
|
||||
parentitem_up = parentitem;
|
||||
else
|
||||
parentitem_sideways = parentitem;
|
||||
}
|
||||
if (event.key == "ArrowUp")
|
||||
targetitem = prevmenuitem(thisitem) || parentitem_up ||
|
||||
lastmenuitem(thismenu);
|
||||
else if (event.key == "ArrowDown")
|
||||
targetitem = nextmenuitem(thisitem) || parentitem_up ||
|
||||
firstmenuitem(thismenu);
|
||||
else if (event.key == "ArrowRight")
|
||||
targetitem = thisitem.querySelector("li") ||
|
||||
(parentitem_up && nextmenuitem(parentitem_up));
|
||||
else if (event.key == "Enter")
|
||||
targetitem = thisitem.querySelector("li");
|
||||
else if (event.key == "ArrowLeft")
|
||||
targetitem = parentitem_sideways ||
|
||||
(parentitem_up && prevmenuitem(parentitem_up));
|
||||
}
|
||||
if (targetitem)
|
||||
targetitem.firstElementChild.focus();
|
||||
else if (event.key == "Enter")
|
||||
event.target.click();
|
||||
// Prevent default even if we didn't do anything, as long as this
|
||||
// was an interesting key.
|
||||
event.preventDefault();
|
||||
}
|
||||
menuform.addEventListener("keydown", menukey);
|
||||
|
||||
// In IE, the canvas doesn't automatically gain focus on a mouse
|
||||
// click, so make sure it does
|
||||
onscreen_canvas.addEventListener("mousedown", function(event) {
|
||||
|
@ -127,7 +127,8 @@ EOF
|
||||
color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
#gamemenu li > :hover:not(:disabled) {
|
||||
#gamemenu li > :hover:not(:disabled),
|
||||
#gamemenu li > :focus-within {
|
||||
/* When the mouse is over a menu item, highlight it */
|
||||
background: rgba(0,0,0,0.3);
|
||||
}
|
||||
@ -184,7 +185,8 @@ EOF
|
||||
left: inherit; right: 100%;
|
||||
}
|
||||
|
||||
#gamemenu :hover > ul {
|
||||
#gamemenu :hover > ul,
|
||||
#gamemenu :focus-within > ul {
|
||||
/* Last but by no means least, the all-important line that makes
|
||||
* submenus be displayed! Any <ul> whose parent <li> is being
|
||||
* hovered over gets display:flex overriding the display:none
|
||||
@ -309,13 +311,13 @@ ${unfinishedpara}
|
||||
<hr>
|
||||
<div id="puzzle" style="display: none">
|
||||
<form id="gamemenu"><ul>
|
||||
<li><div>Game...<ul>
|
||||
<li><div tabindex="0">Game...<ul>
|
||||
<li><button type="button" id="specific">Enter game ID</button></li>
|
||||
<li><button type="button" id="random">Enter random seed</button></li>
|
||||
<li><button type="button" id="save">Download save file</button></li>
|
||||
<li><button type="button" id="load">Upload save file</button></li>
|
||||
</ul></div></li>
|
||||
<li><div>Type...<ul id="gametype"></ul></div></li>
|
||||
<li><div tabindex="0">Type...<ul role="menu" id="gametype"></ul></div></li>
|
||||
<li role="separator"></li>
|
||||
<li><button type="button" id="new">
|
||||
New<span class="verbiage"> game</span>
|
||||
@ -335,7 +337,7 @@ ${unfinishedpara}
|
||||
</ul></form>
|
||||
<div align=center>
|
||||
<div id="resizable">
|
||||
<canvas id="puzzlecanvas" width="1px" height="1px" tabindex="1">
|
||||
<canvas id="puzzlecanvas" width="1px" height="1px" tabindex="0">
|
||||
</canvas>
|
||||
<div id="statusbarholder">
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user