mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-22 00:15:46 -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");
|
var tick = document.createElement("span");
|
||||||
tick.className = "tick";
|
tick.className = "tick";
|
||||||
label.appendChild(tick);
|
label.appendChild(tick);
|
||||||
|
label.tabIndex = 0;
|
||||||
label.appendChild(document.createTextNode(" " + name));
|
label.appendChild(document.createTextNode(" " + name));
|
||||||
item.appendChild(label);
|
item.appendChild(label);
|
||||||
var submenu = document.createElement("ul");
|
var submenu = document.createElement("ul");
|
||||||
|
89
emccpre.js
89
emccpre.js
@ -416,6 +416,95 @@ function initPuzzle() {
|
|||||||
gametypesubmenus.push(gametypelist);
|
gametypesubmenus.push(gametypelist);
|
||||||
menuform = document.getElementById("gamemenu");
|
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
|
// In IE, the canvas doesn't automatically gain focus on a mouse
|
||||||
// click, so make sure it does
|
// click, so make sure it does
|
||||||
onscreen_canvas.addEventListener("mousedown", function(event) {
|
onscreen_canvas.addEventListener("mousedown", function(event) {
|
||||||
|
@ -127,7 +127,8 @@ EOF
|
|||||||
color: rgba(0,0,0,0.5);
|
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 */
|
/* When the mouse is over a menu item, highlight it */
|
||||||
background: rgba(0,0,0,0.3);
|
background: rgba(0,0,0,0.3);
|
||||||
}
|
}
|
||||||
@ -184,7 +185,8 @@ EOF
|
|||||||
left: inherit; right: 100%;
|
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
|
/* Last but by no means least, the all-important line that makes
|
||||||
* submenus be displayed! Any <ul> whose parent <li> is being
|
* submenus be displayed! Any <ul> whose parent <li> is being
|
||||||
* hovered over gets display:flex overriding the display:none
|
* hovered over gets display:flex overriding the display:none
|
||||||
@ -309,13 +311,13 @@ ${unfinishedpara}
|
|||||||
<hr>
|
<hr>
|
||||||
<div id="puzzle" style="display: none">
|
<div id="puzzle" style="display: none">
|
||||||
<form id="gamemenu"><ul>
|
<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="specific">Enter game ID</button></li>
|
||||||
<li><button type="button" id="random">Enter random seed</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="save">Download save file</button></li>
|
||||||
<li><button type="button" id="load">Upload save file</button></li>
|
<li><button type="button" id="load">Upload save file</button></li>
|
||||||
</ul></div></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 role="separator"></li>
|
||||||
<li><button type="button" id="new">
|
<li><button type="button" id="new">
|
||||||
New<span class="verbiage"> game</span>
|
New<span class="verbiage"> game</span>
|
||||||
@ -335,7 +337,7 @@ ${unfinishedpara}
|
|||||||
</ul></form>
|
</ul></form>
|
||||||
<div align=center>
|
<div align=center>
|
||||||
<div id="resizable">
|
<div id="resizable">
|
||||||
<canvas id="puzzlecanvas" width="1px" height="1px" tabindex="1">
|
<canvas id="puzzlecanvas" width="1px" height="1px" tabindex="0">
|
||||||
</canvas>
|
</canvas>
|
||||||
<div id="statusbarholder">
|
<div id="statusbarholder">
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user