Improve tab navigation and gamepad D-pad support
Enhanced directional navigation logic to prioritize active tab panel content when moving from tab links, improving keyboard and controller accessibility. Added robust D-pad detection for gamepads, supporting both button and axis-based D-pads for broader controller compatibility. Updated settings tab logic to allow ArrowDown/ArrowRight to move focus from tabs to the active panel, with global fallback for improved accessibility.
This commit is contained in:
@@ -311,6 +311,30 @@ function activateTab(tabName) {
|
||||
|
||||
function initTabs() {
|
||||
const links = document.querySelectorAll('.tab-link');
|
||||
|
||||
const getFocusableElements = (container) => {
|
||||
if (!container) return [];
|
||||
const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
||||
return Array.from(container.querySelectorAll(selector))
|
||||
.filter(el => !el.disabled && el.getAttribute('aria-hidden') !== 'true' && el.offsetParent !== null);
|
||||
};
|
||||
|
||||
const focusFirstInActivePanel = () => {
|
||||
const activePanel = document.querySelector('.tab-panel.active') || null;
|
||||
const focusables = getFocusableElements(activePanel);
|
||||
if (focusables.length > 0) {
|
||||
focusables[0].focus({ preventScroll: true });
|
||||
return true;
|
||||
}
|
||||
if (activePanel) {
|
||||
if (!activePanel.hasAttribute('tabindex')) {
|
||||
activePanel.setAttribute('tabindex', '-1');
|
||||
}
|
||||
activePanel.focus({ preventScroll: true });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Direct listeners (for accessibility focus handling)
|
||||
links.forEach((link, index) => {
|
||||
@@ -324,6 +348,18 @@ function initTabs() {
|
||||
}
|
||||
activateTab(name);
|
||||
});
|
||||
|
||||
// Controller/keyboard: move from tab to panel content
|
||||
link.addEventListener('keydown', (e) => {
|
||||
if (e.defaultPrevented) return;
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
||||
const moved = focusFirstInActivePanel();
|
||||
if (moved) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Delegation as a fallback if elements are re-rendered
|
||||
@@ -341,6 +377,24 @@ function initTabs() {
|
||||
});
|
||||
}
|
||||
|
||||
// Global fallback: if focus is on sidebar tabs, move into active panel on down/right
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.defaultPrevented) return;
|
||||
if (e.key !== 'ArrowDown' && e.key !== 'ArrowRight') return;
|
||||
|
||||
const activeEl = document.activeElement;
|
||||
const inTabs = activeEl && (activeEl.classList?.contains('tab-link') || activeEl.closest?.('.tabs'));
|
||||
const inSidebar = activeEl && activeEl.closest?.('.sidebar');
|
||||
|
||||
if (inTabs || inSidebar) {
|
||||
const moved = focusFirstInActivePanel();
|
||||
if (moved) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Resolve initial tab: hash > storage > default 'general'
|
||||
let initial = (location.hash || '').replace('#', '') || null;
|
||||
if (!initial) {
|
||||
|
||||
Reference in New Issue
Block a user