audit pass 6: accessibility, ARIA, and keyboard fixes
- JS: fix checkbox/radio required validation using .checked not .value - JS: guard _cpTrigger.focus() with document.contains() check - JS: add arrow/Home/End key navigation to tab groups (WCAG 2.1) - JS: clamp context menu left edge with Math.max(8, ...) to prevent off-screen - JS: fix wizard _show() to removeAttribute aria-hidden on active step - HTML: add role="region" + aria-label to notification panel - HTML: convert Assigned To span+div to label+select with for/id association - HTML: add role="article" tabindex="0" aria-label to all kanban cards - HTML: remove aria-hidden="false" anti-pattern from wizard active step - CSS/HTML/JS: replace aria-hidden="false" show-hook with :not([aria-hidden]) so open state is represented by absent attribute rather than false value Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -282,8 +282,19 @@
|
||||
const saved = localStorage.getItem('lt_activeTab_' + location.pathname);
|
||||
if (saved && document.getElementById(saved)) { switchTab(saved); }
|
||||
} catch (_) {}
|
||||
document.querySelectorAll('.lt-tab[data-tab]').forEach(btn => {
|
||||
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
|
||||
document.querySelectorAll('[role="tablist"]').forEach(tablist => {
|
||||
const btns = Array.from(tablist.querySelectorAll('.lt-tab[data-tab]'));
|
||||
btns.forEach((btn, i) => {
|
||||
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
|
||||
btn.addEventListener('keydown', e => {
|
||||
let idx = -1;
|
||||
if (e.key === 'ArrowRight') idx = (i + 1) % btns.length;
|
||||
else if (e.key === 'ArrowLeft') idx = (i - 1 + btns.length) % btns.length;
|
||||
else if (e.key === 'Home') idx = 0;
|
||||
else if (e.key === 'End') idx = btns.length - 1;
|
||||
if (idx >= 0) { e.preventDefault(); btns[idx].focus(); switchTab(btns[idx].dataset.tab); }
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -849,7 +860,7 @@
|
||||
const ov = document.getElementById('lt-cmd-overlay'); if (!ov) return;
|
||||
ov.classList.remove('is-open');
|
||||
_unlockScroll();
|
||||
if (_cpTrigger) { _cpTrigger.focus(); _cpTrigger = null; }
|
||||
if (_cpTrigger) { if (document.contains(_cpTrigger)) _cpTrigger.focus(); _cpTrigger = null; }
|
||||
}
|
||||
|
||||
function _cpHighlight(text, q) {
|
||||
@@ -970,6 +981,7 @@
|
||||
|
||||
function _validateField(el) {
|
||||
const val = el.value || '', type = (el.type || '').toLowerCase();
|
||||
if ((type === 'checkbox' || type === 'radio') && el.required && !el.checked) return { valid: false, message: 'This field is required' };
|
||||
if (el.required && !val.trim()) return { valid: false, message: 'This field is required' };
|
||||
if (el.minLength > 0 && val.length < el.minLength) return { valid: false, message: 'Minimum ' + el.minLength + ' characters' };
|
||||
if (el.maxLength > 0 && val.length > el.maxLength) return { valid: false, message: 'Maximum ' + el.maxLength + ' characters' };
|
||||
@@ -1663,7 +1675,7 @@
|
||||
// Position — keep on screen
|
||||
const vw = window.innerWidth, vh = window.innerHeight;
|
||||
const mw = _ctxMenu.offsetWidth || 180, mh = _ctxMenu.offsetHeight || 200;
|
||||
_ctxMenu.style.left = Math.min(x, vw - mw - 8) + 'px';
|
||||
_ctxMenu.style.left = Math.max(8, Math.min(x, vw - mw - 8)) + 'px';
|
||||
_ctxMenu.style.top = Math.min(y, vh - mh - 8) + 'px';
|
||||
// Focus first item
|
||||
const first = _ctxMenu.querySelector('[role="menuitem"]');
|
||||
@@ -2110,7 +2122,11 @@
|
||||
group._sbInit = true;
|
||||
const label = group.querySelector('.lt-sidebar-group-label');
|
||||
if (!label) return;
|
||||
label.addEventListener('click', () => group.classList.toggle('is-open'));
|
||||
label.setAttribute('tabindex', '0');
|
||||
label.setAttribute('role', 'button');
|
||||
const _toggle = () => group.classList.toggle('is-open');
|
||||
label.addEventListener('click', _toggle);
|
||||
label.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _toggle(); } });
|
||||
// Open group if it contains the active link
|
||||
if (group.querySelector('.lt-sidebar-sub-link.active, .lt-sidebar-sub-link[aria-current="page"]')) {
|
||||
group.classList.add('is-open');
|
||||
@@ -2210,7 +2226,7 @@
|
||||
function _show(idx) {
|
||||
steps.forEach((s, i) => {
|
||||
s.classList.toggle('is-active', i === idx);
|
||||
s.setAttribute('aria-hidden', i !== idx ? 'true' : 'false');
|
||||
if (i !== idx) s.setAttribute('aria-hidden', 'true'); else s.removeAttribute('aria-hidden');
|
||||
});
|
||||
// Update step indicators
|
||||
container.querySelectorAll('[data-wizard-indicator]').forEach((ind, i) => {
|
||||
|
||||
Reference in New Issue
Block a user