Complete 4.5.1: Keyboard Navigation - implement full keyboard accessibility with skip links, focus management, and modal controls

This commit is contained in:
VinnyNC 2025-09-30 18:47:51 -04:00
parent 313ee80726
commit 4c2b627e2e
4 changed files with 145 additions and 4 deletions

View file

@ -171,6 +171,49 @@
recentActivity.insertAdjacentHTML('afterbegin', activityHtml);
}
// Focus management for modals
function trapFocus(element) {
const focusableElements = element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
function handleKeyDown(e) {
if (e.key === 'Tab') {
if (e.shiftKey) {
// Shift + Tab
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
}
} else {
// Tab
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
}
if (e.key === 'Escape') {
// Close modal on Escape
toggleChat();
}
}
element.addEventListener('keydown', handleKeyDown);
// Return cleanup function
return function() {
element.removeEventListener('keydown', handleKeyDown);
};
}
// Store for focus management
let previousFocusElement = null;
let focusCleanup = null;
// Chat toggle functionality
function toggleChat() {
const chatSection = document.getElementById('chatSection');
@ -180,21 +223,54 @@
if (!chatSection || !videoSection || !toggleBtn) return;
const isMobile = window.innerWidth <= AppConfig.ui.mobileBreakpoint;
const wasCollapsed = AppState.chatCollapsed;
AppState.chatCollapsed = !AppState.chatCollapsed;
if (AppState.chatCollapsed) {
// Closing chat
if (isMobile) {
chatSection.classList.remove('mobile-visible');
toggleBtn.textContent = 'Show Chat';
// Clean up modal focus management
if (focusCleanup) {
focusCleanup();
focusCleanup = null;
}
// Restore focus to the toggle button
if (previousFocusElement) {
previousFocusElement.focus();
previousFocusElement = null;
}
} else {
chatSection.classList.add('collapsed');
videoSection.classList.add('expanded');
toggleBtn.textContent = 'Show Chat';
}
} else {
// Opening chat
if (isMobile) {
chatSection.classList.add('mobile-visible');
toggleBtn.textContent = 'Hide Chat';
// Store current focus element for restoration
previousFocusElement = document.activeElement;
// Set up modal focus management
focusCleanup = trapFocus(chatSection);
// Focus first focusable element in chat (nickname input)
const nicknameInput = document.getElementById('nickname');
if (nicknameInput) {
setTimeout(() => nicknameInput.focus(), 100); // Delay to ensure element is visible
}
// Add modal backdrop styling
chatSection.setAttribute('role', 'dialog');
chatSection.setAttribute('aria-modal', 'true');
chatSection.setAttribute('aria-labelledby', 'chatSection');
} else {
chatSection.classList.remove('collapsed');
videoSection.classList.remove('expanded');