Complete 4.5.1: Keyboard Navigation - implement full keyboard accessibility with skip links, focus management, and modal controls
This commit is contained in:
parent
313ee80726
commit
4c2b627e2e
4 changed files with 145 additions and 4 deletions
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue