aboutsummaryrefslogtreecommitdiff
path: root/emccpre.js
diff options
context:
space:
mode:
authorBen Harris <bjh21@bjh21.me.uk>2022-11-12 23:39:05 +0000
committerBen Harris <bjh21@bjh21.me.uk>2022-11-23 21:53:59 +0000
commit52cd58043ac144eeafb92fd962662420506283c1 (patch)
treeba3a8401ead53edeea6bc6278d948ed37e26f368 /emccpre.js
parentb1b2da98961c3ec6561a934834026c117f4366d3 (diff)
downloadpuzzles-52cd58043ac144eeafb92fd962662420506283c1.zip
puzzles-52cd58043ac144eeafb92fd962662420506283c1.tar.gz
puzzles-52cd58043ac144eeafb92fd962662420506283c1.tar.bz2
puzzles-52cd58043ac144eeafb92fd962662420506283c1.tar.xz
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.
Diffstat (limited to 'emccpre.js')
-rw-r--r--emccpre.js89
1 files changed, 89 insertions, 0 deletions
diff --git a/emccpre.js b/emccpre.js
index 89954b6..2e1508c 100644
--- a/emccpre.js
+++ b/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) {