mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 04:11:08 -05:00
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols. - Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage. - Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
76 lines
2.8 KiB
JavaScript
76 lines
2.8 KiB
JavaScript
/**
|
|
* Search Results Keyboard Navigation
|
|
* Handles Arrow Up/Down, Enter, and Escape keys for accessible search
|
|
*
|
|
* This module enhances search inputs with keyboard navigation for WCAG compliance:
|
|
* - Arrow Down: Navigate to next search result
|
|
* - Arrow Up: Navigate to previous search result (or back to input)
|
|
* - Enter: Select current result (navigate to link)
|
|
* - Escape: Close search results and blur input
|
|
*
|
|
* Usage:
|
|
* The script automatically initializes on DOMContentLoaded for all search inputs
|
|
* with hx-target attribute pointing to a results container.
|
|
*
|
|
* HTMX Integration:
|
|
* Results should include role="option" on each selectable item.
|
|
* The script listens for htmx:afterSwap to reinitialize when results change.
|
|
*/
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const searchInputs = document.querySelectorAll('input[type="search"]');
|
|
|
|
searchInputs.forEach(input => {
|
|
const resultsContainer = document.querySelector(input.getAttribute('hx-target'));
|
|
if (!resultsContainer) return;
|
|
|
|
let currentIndex = -1;
|
|
|
|
input.addEventListener('keydown', (e) => {
|
|
const results = resultsContainer.querySelectorAll('[role="option"]');
|
|
if (results.length === 0) return;
|
|
|
|
switch(e.key) {
|
|
case 'ArrowDown':
|
|
e.preventDefault();
|
|
currentIndex = Math.min(currentIndex + 1, results.length - 1);
|
|
updateSelection(results, currentIndex);
|
|
break;
|
|
case 'ArrowUp':
|
|
e.preventDefault();
|
|
currentIndex = Math.max(currentIndex - 1, -1);
|
|
if (currentIndex === -1) {
|
|
input.focus();
|
|
} else {
|
|
updateSelection(results, currentIndex);
|
|
}
|
|
break;
|
|
case 'Enter':
|
|
if (currentIndex >= 0) {
|
|
e.preventDefault();
|
|
results[currentIndex].querySelector('a').click();
|
|
}
|
|
break;
|
|
case 'Escape':
|
|
e.preventDefault();
|
|
resultsContainer.innerHTML = '';
|
|
input.blur();
|
|
break;
|
|
}
|
|
});
|
|
|
|
function updateSelection(results, index) {
|
|
results.forEach((result, i) => {
|
|
if (i === index) {
|
|
result.setAttribute('aria-selected', 'true');
|
|
result.classList.add('bg-accent');
|
|
result.scrollIntoView({ block: 'nearest' });
|
|
} else {
|
|
result.setAttribute('aria-selected', 'false');
|
|
result.classList.remove('bg-accent');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|