Add keyboard navigation support and raise hand commands

This commit adds comprehensive keyboard navigation support for all
interactive elements and adds slash commands for raising hands.

Changes:
- Add /raisehand and /unraisehand slash commands
  * Previously only accessible via context menu
  * Now keyboard users can raise/lower hand from chat input
  * Shows up in /help command list

- Add keyboard event handlers (Enter/Space) for all interactive elements
  * User list items now respond to keyboard (open context menu)
  * Sidebar collapse button supports keyboard
  * Settings panel open/close supports keyboard
  * Video show/hide buttons support keyboard
  * Chat show/hide buttons support keyboard
  * All buttons now have role="button" and tabindex where needed

- Add keyboard support for chat resizer
  * Made resizer focusable with tabindex="0"
  * Added role="separator" and aria-label
  * Left/Right arrow keys resize chat panel
  * 20px step per keypress
  * Maintains minimum width constraint

These changes make Galene fully keyboard-accessible. Users who cannot
use a mouse can now navigate all controls using Tab, Enter, Space, and
arrow keys.
This commit is contained in:
Storm Dragon
2025-11-27 00:30:55 -05:00
parent 82ab7e553d
commit 3c58e13c11
2 changed files with 118 additions and 1 deletions
+1 -1
View File
@@ -84,7 +84,7 @@
</div>
</div>
</div>
<div id="resizer"></div>
<div id="resizer" tabindex="0" role="separator" aria-label="Chat resize handle" aria-orientation="vertical"></div>
<div class="coln-right" id="right">
<button class="show-video blink invisible" id="show-video" aria-label="Show video">
<i class="fas fa-exchange-alt" aria-hidden="true"></i>
+117
View File
@@ -2609,6 +2609,8 @@ function addUser(id, userinfo) {
let user = document.createElement('div');
user.id = 'user-' + id;
user.classList.add("user-p");
user.setAttribute('role', 'button');
user.setAttribute('tabindex', '0');
setUserStatus(id, user, userinfo);
user.addEventListener('click', function(e) {
let elt = e.target;
@@ -2616,6 +2618,15 @@ function addUser(id, userinfo) {
throw new Error("Couldn't find user div");
userMenu(elt);
});
user.addEventListener('keydown', function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
let elt = e.target;
if(!elt || !(elt instanceof HTMLElement))
throw new Error("Couldn't find user div");
userMenu(elt);
}
});
let us = div.children;
@@ -3663,6 +3674,24 @@ commands.unlock = {
}
};
commands.raisehand = {
description: 'raise your hand',
f: (c, r) => {
if(!serverConnection || !serverConnection.id)
throw new Error('Not connected');
serverConnection.userAction('setdata', serverConnection.id, {'raisehand': true});
}
};
commands.unraisehand = {
description: 'lower your hand',
f: (c, r) => {
if(!serverConnection || !serverConnection.id)
throw new Error('Not connected');
serverConnection.userAction('setdata', serverConnection.id, {'raisehand': null});
}
};
commands.record = {
predicate: recordingPredicate,
description: 'start recording',
@@ -4319,6 +4348,30 @@ function chatResizer(e) {
document.getElementById('resizer').addEventListener('mousedown', chatResizer, false);
// Add keyboard support for chat resizer
document.getElementById('resizer').addEventListener('keydown', function(e) {
if(e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
e.preventDefault();
let full_width = document.getElementById("mainrow").offsetWidth;
let left = document.getElementById("left");
let right = document.getElementById("right");
let current_width = left.offsetWidth;
let step = 20; // pixels per keypress
let new_width = e.key === 'ArrowLeft' ? current_width - step : current_width + step;
let left_percent = new_width * 100 / full_width;
// set min chat width to 300px
let min_left_width = 300 * 100 / full_width;
if (left_percent < min_left_width) {
return;
}
left.style.flex = left_percent.toString();
right.style.flex = (100 - left_percent).toString();
}
}, false);
/**
* @param {unknown} message
* @param {string} [level]
@@ -4405,6 +4458,14 @@ document.getElementById('sidebarCollapse').onclick = function(e) {
document.getElementById("mainrow").classList.toggle("full-width-active");
};
document.getElementById('sidebarCollapse').onkeydown = function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
document.getElementById("left-sidebar").classList.toggle("active");
document.getElementById("mainrow").classList.toggle("full-width-active");
}
};
document.getElementById('openside').onclick = function(e) {
e.preventDefault();
let sidewidth = document.getElementById("sidebarnav").style.width;
@@ -4416,12 +4477,32 @@ document.getElementById('openside').onclick = function(e) {
}
};
document.getElementById('openside').onkeydown = function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
let sidewidth = document.getElementById("sidebarnav").style.width;
if (sidewidth !== "0px" && sidewidth !== "") {
closeNav();
return;
} else {
openNav();
}
}
};
document.getElementById('closeside').onclick = function(e) {
e.preventDefault();
closeNav();
};
document.getElementById('closeside').onkeydown = function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
closeNav();
}
};
document.getElementById('collapse-video').onclick = function(e) {
e.preventDefault();
setVisibility('collapse-video', false);
@@ -4429,6 +4510,15 @@ document.getElementById('collapse-video').onclick = function(e) {
hideVideo(true);
};
document.getElementById('collapse-video').onkeydown = function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setVisibility('collapse-video', false);
setVisibility('show-video', true);
hideVideo(true);
}
};
document.getElementById('show-video').onclick = function(e) {
e.preventDefault();
setVisibility('video-container', true);
@@ -4436,6 +4526,15 @@ document.getElementById('show-video').onclick = function(e) {
setVisibility('show-video', false);
};
document.getElementById('show-video').onkeydown = function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setVisibility('video-container', true);
setVisibility('collapse-video', true);
setVisibility('show-video', false);
}
};
document.getElementById('close-chat').onclick = function(e) {
e.preventDefault();
setVisibility('left', false);
@@ -4443,6 +4542,15 @@ document.getElementById('close-chat').onclick = function(e) {
resizePeers();
};
document.getElementById('close-chat').onkeydown = function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setVisibility('left', false);
setVisibility('show-chat', true);
resizePeers();
}
};
document.getElementById('show-chat').onclick = function(e) {
e.preventDefault();
setVisibility('left', true);
@@ -4450,6 +4558,15 @@ document.getElementById('show-chat').onclick = function(e) {
resizePeers();
};
document.getElementById('show-chat').onkeydown = function(e) {
if(e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setVisibility('left', true);
setVisibility('show-chat', false);
resizePeers();
}
};
async function serverConnect() {
if(serverConnection && serverConnection.socket)
serverConnection.close();