diff --git a/.Jules/palette.md b/.Jules/palette.md index 8d6b0da..809a956 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -9,3 +9,7 @@ ## 2025-05-24 - In-Page Navigation Focus Management **Learning:** In single-page documentation modals (common in custom nodes), in-page navigation buttons (`scrollToSection`) often scroll content but leave focus on the button. This forces keyboard users to traverse the entire menu again to reach the content. **Action:** Update scroll handlers to programmatically move focus to the target section's heading (setting `tabindex="-1"` if needed) to maintain logical reading flow. + +## 2025-05-25 - Modal Focus Trapping +**Learning:** Custom DOM modals often lack keyboard focus trapping, allowing users to tab out of the modal into the obscured background content. This is critical for keyboard and screen reader accessibility. +**Action:** Implement a 'keydown' listener on the modal container to intercept Tab/Shift+Tab. Dynamically query visible focusable elements (`offsetParent !== null`) to handle changing content (like tabs) and loop focus between the first and last elements. diff --git a/web/matrix_button.js b/web/matrix_button.js index 3224cdc..4a3935b 100644 --- a/web/matrix_button.js +++ b/web/matrix_button.js @@ -3167,6 +3167,33 @@ def apply_color_to_noise(noise_tensor, shader_params): modal.appendChild(content); document.body.appendChild(modal); + // Accessibility: Focus Trap & Initial Focus + const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; + + modal.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + const focusableElements = Array.from(modal.querySelectorAll(focusableSelectors)) + .filter(el => el.offsetParent !== null && !el.hasAttribute('disabled')); // Only visible and enabled elements + + if (focusableElements.length === 0) return; + + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + if (e.shiftKey) { // Shift + Tab + if (document.activeElement === firstElement) { + e.preventDefault(); + lastElement.focus(); + } + } else { // Tab + if (document.activeElement === lastElement) { + e.preventDefault(); + firstElement.focus(); + } + } + } + }); + // Accessibility: Set focus to the close button when modal opens // Using a small timeout to ensure DOM insertion is complete and to play nice with screen readers setTimeout(() => {