Add focus trapping for modal dialogs
- Focus moves to first focusable element when modal opens - Tab/Shift+Tab cycles within modal only - Escape key closes modal - Focus returns to previously focused element on close 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
54
index.html
54
index.html
@@ -1142,12 +1142,64 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previouslyFocusedElement = null;
|
||||||
|
let activeModalId = null;
|
||||||
|
|
||||||
function openModal(id) {
|
function openModal(id) {
|
||||||
document.getElementById(id).classList.add('active');
|
previouslyFocusedElement = document.activeElement;
|
||||||
|
activeModalId = id;
|
||||||
|
const modal = document.getElementById(id);
|
||||||
|
modal.classList.add('active');
|
||||||
|
|
||||||
|
// Focus first focusable element in modal
|
||||||
|
const focusable = modal.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||||
|
if (focusable.length > 0) {
|
||||||
|
setTimeout(() => focusable[0].focus(), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listeners for focus trap and Escape
|
||||||
|
document.addEventListener('keydown', handleModalKeydown);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeModal(id) {
|
function closeModal(id) {
|
||||||
document.getElementById(id).classList.remove('active');
|
document.getElementById(id).classList.remove('active');
|
||||||
|
document.removeEventListener('keydown', handleModalKeydown);
|
||||||
|
activeModalId = null;
|
||||||
|
|
||||||
|
// Restore focus to previously focused element
|
||||||
|
if (previouslyFocusedElement) {
|
||||||
|
previouslyFocusedElement.focus();
|
||||||
|
previouslyFocusedElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleModalKeydown(e) {
|
||||||
|
if (!activeModalId) return;
|
||||||
|
const modal = document.getElementById(activeModalId);
|
||||||
|
|
||||||
|
// Close on Escape
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
closeModal(activeModalId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trap focus on Tab
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
const focusable = modal.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||||
|
if (focusable.length === 0) return;
|
||||||
|
|
||||||
|
const first = focusable[0];
|
||||||
|
const last = focusable[focusable.length - 1];
|
||||||
|
|
||||||
|
if (e.shiftKey && document.activeElement === first) {
|
||||||
|
e.preventDefault();
|
||||||
|
last.focus();
|
||||||
|
} else if (!e.shiftKey && document.activeElement === last) {
|
||||||
|
e.preventDefault();
|
||||||
|
first.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifyPin() {
|
async function verifyPin() {
|
||||||
|
|||||||
Reference in New Issue
Block a user